Skip to content

Commit

Permalink
Add support for non-round-trips with a single fixed endpoint (Project…
Browse files Browse the repository at this point in the history
…-OSRM#6050)

Currently /trip supports finding round-trip routes where only the
start or end location is fixed. This PR extends this feature to
non-round-trip requests.

We do this by a new table manipulation that simulates non-round-trip
fixed endpoint requests as a round-trip request.
  • Loading branch information
mjjbell authored and mattwigway committed Jul 20, 2023
1 parent 31ebc3f commit 29b4726
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 70 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
- FIXED: Fixed Node docs generation check in CI. [#6058](https://github.com/Project-OSRM/osrm-backend/pull/6058)
- CHANGED: Docker build, enabled arm64 build layer [#6172](https://github.com/Project-OSRM/osrm-backend/pull/6172)
- CHANGED: Docker build, enabled apt-get update/install caching in separate layer for build phase [#6175](https://github.com/Project-OSRM/osrm-backend/pull/6175)
- Routing:
- ADDED: Add support for non-round-trips with a single fixed endpoint. [#6050](https://github.com/Project-OSRM/osrm-backend/pull/6050)

# 5.26.0
- Changes from 5.25.0
Expand Down
4 changes: 2 additions & 2 deletions docs/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,8 @@ Right now, the following combinations are possible:
| true | any | last | **yes** |
| true | any | any | **yes** |
| false | first | last | **yes** |
| false | first | any | no |
| false | any | last | no |
| false | first | any | **yes** |
| false | any | last | **yes** |
| false | any | any | no |

#### Example Requests
Expand Down
6 changes: 3 additions & 3 deletions features/step_definitions/trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ module.exports = function () {
var subTrips;
var trip_durations;
var trip_distance;
if (res.statusCode === 200) {
var ok = res.statusCode === 200;
if (ok) {
if (headers.has('trips')) {
subTrips = json.trips.filter(t => !!t).map(t => t.legs).map(tl => Array.prototype.concat.apply([], tl.map((sl, i) => {
var toAdd = [];
Expand All @@ -84,8 +85,7 @@ module.exports = function () {
}
}

var ok = true,
encodedResult = '';
var encodedResult = '';

if (json.trips) row.trips.split(',').forEach((sub, si) => {
if (si >= subTrips.length) {
Expand Down
82 changes: 71 additions & 11 deletions features/testbot/trip.feature
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Feature: Basic trip planning
Given the profile "testbot"
Given a grid size of 10 meters

Scenario: Testbot - Trip: Roundtrip with one waypoint
Scenario: Testbot - Trip: Roundtrip between same waypoint
Given the node map
"""
a b
Expand All @@ -21,7 +21,7 @@ Feature: Basic trip planning

When I plan a trip I should get
| waypoints | trips |
| a | aa |
| a,a | aa |

Scenario: Testbot - Trip: Roundtrip with waypoints (less than 10)
Given the node map
Expand Down Expand Up @@ -69,7 +69,37 @@ Feature: Basic trip planning
| waypoints | trips |
| a,b,c,d,e,f,g,h,i,j,k,l | alkjihgfedcba |

Scenario: Testbot - Trip: Roundtrip FS waypoints (more than 10)
Scenario: Testbot - Trip: FS waypoints (less than 10)
Given the query options
| source | first |
Given the node map
"""
a b c d
l e
j i g
"""

And the ways
| nodes |
| ab |
| bc |
| de |
| eg |
| gi |
| ij |
| jl |
| la |

When I plan a trip I should get
| waypoints | trips | roundtrip | durations |
| a,b,c,d,e,g,i,j,l | abcdegijla | true | 22 |
| a,b,c,d,e,g,i,j,l | abcljiged | false | 13 |


Scenario: Testbot - Trip: FS waypoints (more than 10)
Given the query options
| source | first |
Given the node map
"""
a b c d
Expand All @@ -93,12 +123,41 @@ Feature: Basic trip planning
| 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 |
| waypoints | trips | roundtrip | durations |
| a,b,c,d,e,f,g,h,i,j,k,l | alkjihgfedcba | true | 22 |
| a,b,c,d,e,f,g,h,i,j,k,l | acblkjihgfed | false | 13 |

Scenario: Testbot - Trip: Roundtrip FE waypoints (more than 10)

Scenario: Testbot - Trip: FE waypoints (less than 10)
Given the query options
| destination | last |
Given the node map
"""
a b c d
l e
j i g
"""

And the ways
| nodes |
| ab |
| bc |
| de |
| eg |
| gi |
| ij |
| jl |
| la |

When I plan a trip I should get
| waypoints | trips | roundtrip | durations |
| a,b,c,d,e,g,i,j,l | labcdegijl | true | 22 |
| a,b,c,d,e,g,i,j,l | degijabcl | false | 14 |

Scenario: Testbot - Trip: FE waypoints (more than 10)
Given the query options
| source | last |
| destination | last |
Given the node map
"""
a b c d
Expand All @@ -122,8 +181,9 @@ Feature: Basic trip planning
| la |

When I plan a trip I should get
| waypoints | trips |
| a,b,c,d,e,f,g,h,i,j,k,l | lkjihgfedcbal |
| waypoints | trips | roundtrip | durations |
| a,b,c,d,e,f,g,h,i,j,k,l | lkjihgfedcbal | true | 22 |
| a,b,c,d,e,f,g,h,i,j,k,l | cbakjihgfedl | false | 19 |

Scenario: Testbot - Trip: Unroutable roundtrip with waypoints (less than 10)
Given the node map
Expand Down Expand Up @@ -274,7 +334,7 @@ Feature: Basic trip planning
| a,b,d,e,c | first | last | true | abedca |


Scenario: Testbot - Trip: midway points in isoldated roads should return no trips
Scenario: Testbot - Trip: midway points in isolated roads should return no trips
Given the node map
"""
a 1 b
Expand Down Expand Up @@ -370,4 +430,4 @@ Feature: Basic trip planning
When I plan a trip I should get
| waypoints | trips | durations | geometry |
| a,b,c,d | abcda | 7.6 | 1,1,1,1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009,1,1 |
| 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 |
4 changes: 2 additions & 2 deletions features/testbot/zero-speed-updates.feature
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,5 @@ Feature: Check zero speed updates

When I plan a trip I should get
| waypoints | trips | code |
| a,b,c,d | abcda | NoTrips |
| d,b,c,a | dbcad | NoTrips |
| a,b,c,d | | NoTrips |
| d,b,c,a | | NoTrips |
73 changes: 52 additions & 21 deletions src/engine/plugins/trip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,7 @@ 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;
}
return roundtrip || fixed_start || fixed_end;
}

// given the node order in which to visit, compute the actual route (with geometry, travel time and
Expand Down Expand Up @@ -142,6 +131,32 @@ void ManipulateTableForFSE(const std::size_t source_id,
//********* End of changes to table *************************************
}

void ManipulateTableForNonRoundtripFS(const std::size_t source_id,
util::DistTableWrapper<EdgeWeight> &result_table)
{
// We can use the round-trip calculation to simulate non-round-trip fixed start
// by making all paths to the source location zero. Effectively finding an 'optimal'
// round-trip path that ignores the cost of getting back from any destination to the
// source.
for (const auto i : util::irange<size_t>(0, result_table.GetNumberOfNodes()))
{
result_table.SetValue(i, source_id, 0);
}
}

void ManipulateTableForNonRoundtripFE(const std::size_t destination_id,
util::DistTableWrapper<EdgeWeight> &result_table)
{
// We can use the round-trip calculation to simulate non-round-trip fixed end
// by making all paths from the destination to other locations zero.
// Effectively, finding an 'optimal' round-trip path that ignores the cost of getting
// from the destination to any source.
for (const auto i : util::irange<size_t>(0, result_table.GetNumberOfNodes()))
{
result_table.SetValue(destination_id, i, 0);
}
}

Status TripPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms,
const api::TripParameters &parameters,
osrm::engine::api::ResultT &result) const
Expand Down Expand Up @@ -225,7 +240,7 @@ Status TripPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms,
return Status::Error;
}

const constexpr std::size_t BF_MAX_FEASABLE = 10;
const constexpr std::size_t BF_MAX_FEASIBLE = 10;
BOOST_ASSERT_MSG(result_duration_table.size() == number_of_locations * number_of_locations,
"Distance Table has wrong size");

Expand All @@ -238,11 +253,19 @@ Status TripPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms,
{
ManipulateTableForFSE(source_id, destination_id, result_duration_table);
}
else if (!parameters.roundtrip && fixed_start)
{
ManipulateTableForNonRoundtripFS(source_id, result_duration_table);
}
else if (!parameters.roundtrip && fixed_end)
{
ManipulateTableForNonRoundtripFE(destination_id, result_duration_table);
}

std::vector<NodeID> duration_trip;
duration_trip.reserve(number_of_locations);
// get an optimized order in which the destinations should be visited
if (number_of_locations < BF_MAX_FEASABLE)
if (number_of_locations < BF_MAX_FEASIBLE)
{
duration_trip = trip::BruteForceTrip(number_of_locations, result_duration_table);
}
Expand All @@ -251,20 +274,28 @@ Status TripPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms,
duration_trip = trip::FarthestInsertionTrip(number_of_locations, result_duration_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)
{
// rotate result such that trip starts at node with index 0
auto desired_start_index = std::find(std::begin(duration_trip), std::end(duration_trip), 0);
BOOST_ASSERT(desired_start_index != std::end(duration_trip));
std::rotate(std::begin(duration_trip), desired_start_index, std::end(duration_trip));
}
else if (fixed_end && !fixed_start && parameters.roundtrip)
{
auto desired_start_index =
else
{ // fixed_end
auto destination_index =
std::find(std::begin(duration_trip), std::end(duration_trip), destination_id);
BOOST_ASSERT(desired_start_index != std::end(duration_trip));
std::rotate(std::begin(duration_trip), desired_start_index, std::end(duration_trip));
BOOST_ASSERT(destination_index != std::end(duration_trip));
if (!parameters.roundtrip)
{
// We want the location after destination to be at the front
std::advance(destination_index, 1);
if (destination_index == std::end(duration_trip))
{
destination_index = std::begin(duration_trip);
}
}
std::rotate(std::begin(duration_trip), destination_index, std::end(duration_trip));
}

// get the route when visiting all destinations in optimized order
Expand Down
55 changes: 28 additions & 27 deletions test/nodejs/trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,33 +262,19 @@ test('trip: routes Monaco with null hints', function(assert) {
});

test('trip: service combinations that are not implemented', function(assert) {
assert.plan(3);
assert.plan(1);
var osrm = new OSRM(data_path);

// fixed start, non-roundtrip
// no fixed start, no fixed end, non-roundtrip
var options = {
coordinates: two_test_coordinates,
source: 'first',
source: 'any',
destination: 'any',
roundtrip: false
};
osrm.trip(options, function(err, second) {
assert.equal('NotImplemented', err.message);
});

// fixed start, fixed end, non-roundtrip
options.source = 'any';
options.destination = 'any';
osrm.trip(options, function(err, second) {
assert.equal('NotImplemented', err.message);
});

// fixed end, non-roundtrip
delete options.source;
options.destination = 'last';
osrm.trip(options, function(err, second) {
assert.equal('NotImplemented', err.message);
});

});

test('trip: fixed start and end combinations', function(assert) {
Expand All @@ -302,31 +288,46 @@ test('trip: fixed start and end combinations', function(assert) {
geometries: 'geojson'
};

// fixed start and end, non-roundtrip
osrm.trip(options, function(err, fseTrip) {
assert.ifError(err);
assert.equal(1, fseTrip.trips.length);
var coordinates = fseTrip.trips[0].geometry.coordinates;
assert.notEqual(JSON.stringify(coordinates[0]), JSON.stringify(coordinates[coordinates.length - 1]));
});
// variations of non roundtrip
var nonRoundtripChecks = function(options) {
osrm.trip(options, function(err, fseTrip) {
assert.ifError(err);
assert.equal(1, fseTrip.trips.length);
var coordinates = fseTrip.trips[0].geometry.coordinates;
assert.notEqual(JSON.stringify(coordinates[0]), JSON.stringify(coordinates[coordinates.length - 1]));
});
};

// variations of roundtrip

var roundtripChecks = function(options) {
osrm.trip(options, function(err, trip) {
assert.ifError(err);
assert.equal(1, trip.trips.length);
var coordinates = trip.trips[0].geometry.coordinates;
assert.equal(JSON.stringify(coordinates[0]), JSON.stringify(coordinates[coordinates.length - 1]));
});
}
};

// fixed start and end, non-roundtrip
nonRoundtripChecks(options);

// fixed start, non-roundtrip
delete options.destination;
options.source = 'first';
nonRoundtripChecks(options);

// fixed end, non-roundtrip
delete options.source;
options.destination = 'last';
nonRoundtripChecks(options);

// roundtrip, source and destination not specified
roundtripChecks({coordinates: options.coordinates, geometries: options.geometries});

// roundtrip, fixed destination
options.roundtrip = true;
delete options.source;
options.destination = 'last';
roundtripChecks(options);

//roundtrip, fixed source
Expand Down
Loading

0 comments on commit 29b4726

Please sign in to comment.