Skip to content

Commit

Permalink
Lazily generate optional route path data (#6045)
Browse files Browse the repository at this point in the history
Currently route results are annotated with additional path information,
such as geometries, turn-by-turn steps and other metadata.

These annotations are generated if they are not requested or returned
in the response.
Datasets needed to generate these annotations are loaded and available
to the OSRM process even when unused.

This commit is a first step towards making the loading of these datasets
optional. We refactor the code so that route annotations are only
generated if explicitly requested and needed in the response.

Specifically, we change the following annotations to be lazily generated:
- Turn-by-turn steps
- Route Overview geometry
- Route segment metadata

For example. a /route/v1 request with
steps=false&overview=false&annotations=false
would no longer call the following data facade methods:
- GetOSMNodeIDOfNode
- GetTurnInstructionForEdgeID
- GetNameIndex
- GetNameForID
- GetRefForID
- GetTurnInstructionForEdgeID
- GetClassData
- IsLeftHandDriving
- GetTravelMode
- IsSegregated
- PreTurnBearing
- PostTurnBearing
- HasLaneData
- GetLaneData
- GetEntryClass

Requests that include segment metadata and/or overview geometry
but not turn-by-turn instructions will also benefit from this,
although there is some interdependency with the step instructions
- a call to GetTurnInstructionForEdgeID is still required.
Requests for OSM annotations will understandably still need to
call GetOSMNodeIDOfNode.

Making these changes unlocks the optional loading of data contained in
the following OSRM files:
- osrm.names
- osrm.icd
- osrm.nbg_nodes (partial)
- osrm.ebg_nodes (partial)
- osrm.edges
  • Loading branch information
mjjbell authored Aug 22, 2022
1 parent 3cfd0e8 commit 972a848
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 236 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- 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:
- CHANGED: Lazily generate optional route path data [#6045](https://github.com/Project-OSRM/osrm-backend/pull/6045)
- FIXED: Completed support for no_entry and no_exit turn restrictions. [#5988](https://github.com/Project-OSRM/osrm-backend/pull/5988)
- ADDED: Add support for non-round-trips with a single fixed endpoint. [#6050](https://github.com/Project-OSRM/osrm-backend/pull/6050)

Expand Down
170 changes: 91 additions & 79 deletions include/engine/api/route_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ class RouteAPI : public BaseAPI
// To maintain support for uses of the old default constructors, we check
// if annotations property was set manually after default construction
auto requested_annotations = parameters.annotations_type;
if ((parameters.annotations == true) &&
if (parameters.annotations &&
(parameters.annotations_type == RouteParameters::AnnotationsType::None))
{
requested_annotations = RouteParameters::AnnotationsType::All;
Expand Down Expand Up @@ -497,10 +497,10 @@ class RouteAPI : public BaseAPI
std::vector<uint32_t> nodes;
if (requested_annotations & RouteParameters::AnnotationsType::Nodes)
{
nodes.reserve(leg_geometry.osm_node_ids.size());
for (const auto node_id : leg_geometry.osm_node_ids)
nodes.reserve(leg_geometry.node_ids.size());
for (const auto node_id : leg_geometry.node_ids)
{
nodes.emplace_back(static_cast<uint64_t>(node_id));
nodes.emplace_back(static_cast<uint64_t>(facade.GetOSMNodeIDOfNode(node_id)));
}
}
auto nodes_vector = fb_result.CreateVector(nodes);
Expand All @@ -515,7 +515,7 @@ class RouteAPI : public BaseAPI
{
const auto name = facade.GetDatasourceName(i);
// Length of 0 indicates the first empty name, so we can stop here
if (name.size() == 0)
if (name.empty())
break;
names.emplace_back(
fb_result.CreateString(std::string(facade.GetDatasourceName(i))));
Expand Down Expand Up @@ -763,7 +763,7 @@ class RouteAPI : public BaseAPI
// To maintain support for uses of the old default constructors, we check
// if annotations property was set manually after default construction
auto requested_annotations = parameters.annotations_type;
if ((parameters.annotations == true) &&
if (parameters.annotations &&
(parameters.annotations_type == RouteParameters::AnnotationsType::None))
{
requested_annotations = RouteParameters::AnnotationsType::All;
Expand Down Expand Up @@ -825,10 +825,11 @@ class RouteAPI : public BaseAPI
if (requested_annotations & RouteParameters::AnnotationsType::Nodes)
{
util::json::Array nodes;
nodes.values.reserve(leg_geometry.osm_node_ids.size());
for (const auto node_id : leg_geometry.osm_node_ids)
nodes.values.reserve(leg_geometry.node_ids.size());
for (const auto node_id : leg_geometry.node_ids)
{
nodes.values.push_back(static_cast<std::uint64_t>(node_id));
nodes.values.push_back(
static_cast<std::uint64_t>(facade.GetOSMNodeIDOfNode(node_id)));
}
annotation.values["nodes"] = std::move(nodes);
}
Expand All @@ -842,7 +843,7 @@ class RouteAPI : public BaseAPI
{
const auto name = facade.GetDatasourceName(i);
// Length of 0 indicates the first empty name, so we can stop here
if (name.size() == 0)
if (name.empty())
break;
datasource_names.values.push_back(std::string(facade.GetDatasourceName(i)));
}
Expand Down Expand Up @@ -888,81 +889,92 @@ class RouteAPI : public BaseAPI
const bool reversed_source = source_traversed_in_reverse[idx];
const bool reversed_target = target_traversed_in_reverse[idx];

auto leg_geometry = guidance::assembleGeometry(BaseAPI::facade,
path_data,
phantoms.source_phantom,
phantoms.target_phantom,
reversed_source,
reversed_target);
auto leg = guidance::assembleLeg(facade,
path_data,
leg_geometry,
phantoms.source_phantom,
phantoms.target_phantom,
reversed_target,
parameters.steps);
reversed_target);

util::Log(logDEBUG) << "Assembling steps " << std::endl;
if (parameters.steps)
guidance::LegGeometry leg_geometry;

// Generate additional geometry data if request includes turn-by-turn steps,
// overview geometry or route geometry annotations.
// Note that overview geometry and route geometry annotations can return different
// results depending on whether turn-by-turn steps are also requested.
if (parameters.steps || parameters.annotations ||
parameters.overview != RouteParameters::OverviewType::False)
{
auto steps = guidance::assembleSteps(BaseAPI::facade,
path_data,
leg_geometry,
phantoms.source_phantom,
phantoms.target_phantom,
reversed_source,
reversed_target);

// Apply maneuver overrides before any other post
// processing is performed
guidance::applyOverrides(BaseAPI::facade, steps, leg_geometry);

// Collapse segregated steps before others
steps = guidance::collapseSegregatedTurnInstructions(std::move(steps));

/* Perform step-based post-processing.
*
* Using post-processing on basis of route-steps for a single leg at a time
* comes at the cost that we cannot count the correct exit for roundabouts.
* We can only emit the exit nr/intersections up to/starting at a part of the leg.
* If a roundabout is not terminated in a leg, we will end up with a
*enter-roundabout
* and exit-roundabout-nr where the exit nr is out of sync with the previous enter.
*
* | S |
* * *
* ----* * ----
* T
* ----* * ----
* V * *
* | |
* | |
*
* Coming from S via V to T, we end up with the legs S->V and V->T. V-T will say to
*take
* the second exit, even though counting from S it would be the third.
* For S, we only emit `roundabout` without an exit number, showing that we enter a
*roundabout
* to find a via point.
* The same exit will be emitted, though, if we should start routing at S, making
* the overall response consistent.
*
* ⚠ CAUTION: order of post-processing steps is important
* - handleRoundabouts must be called before collapseTurnInstructions that
* expects post-processed roundabouts
*/

guidance::trimShortSegments(steps, leg_geometry);
leg.steps = guidance::handleRoundabouts(std::move(steps));
leg.steps = guidance::collapseTurnInstructions(std::move(leg.steps));
leg.steps = guidance::anticipateLaneChange(std::move(leg.steps));
leg.steps = guidance::buildIntersections(std::move(leg.steps));
leg.steps = guidance::suppressShortNameSegments(std::move(leg.steps));
leg.steps = guidance::assignRelativeLocations(std::move(leg.steps),
leg_geometry,
phantoms.source_phantom,
phantoms.target_phantom);
leg_geometry = guidance::resyncGeometry(std::move(leg_geometry), leg.steps);

leg_geometry = guidance::assembleGeometry(BaseAPI::facade,
path_data,
phantoms.source_phantom,
phantoms.target_phantom,
reversed_source,
reversed_target);

util::Log(logDEBUG) << "Assembling steps " << std::endl;
if (parameters.steps)
{
leg.summary = guidance::assembleSummary(
facade, path_data, phantoms.target_phantom, reversed_target);

auto steps = guidance::assembleSteps(BaseAPI::facade,
path_data,
leg_geometry,
phantoms.source_phantom,
phantoms.target_phantom,
reversed_source,
reversed_target);

// Apply maneuver overrides before any other post
// processing is performed
guidance::applyOverrides(BaseAPI::facade, steps, leg_geometry);

// Collapse segregated steps before others
steps = guidance::collapseSegregatedTurnInstructions(std::move(steps));

/* Perform step-based post-processing.
*
* Using post-processing on basis of route-steps for a single leg at a time
* comes at the cost that we cannot count the correct exit for roundabouts.
* We can only emit the exit nr/intersections up to/starting at a part of the
*leg. If a roundabout is not terminated in a leg, we will end up with a
*enter-roundabout
* and exit-roundabout-nr where the exit nr is out of sync with the previous
*enter.
*
* | S |
* * *
* ----* * ----
* T
* ----* * ----
* V * *
* | |
* | |
*
* Coming from S via V to T, we end up with the legs S->V and V->T. V-T will say
*to take the second exit, even though counting from S it would be the third.
* For S, we only emit `roundabout` without an exit number, showing that we
*enter a roundabout to find a via point. The same exit will be emitted, though,
*if we should start routing at S, making the overall response consistent.
*
* ⚠ CAUTION: order of post-processing steps is important
* - handleRoundabouts must be called before collapseTurnInstructions that
* expects post-processed roundabouts
*/

guidance::trimShortSegments(steps, leg_geometry);
leg.steps = guidance::handleRoundabouts(std::move(steps));
leg.steps = guidance::collapseTurnInstructions(std::move(leg.steps));
leg.steps = guidance::anticipateLaneChange(std::move(leg.steps));
leg.steps = guidance::buildIntersections(std::move(leg.steps));
leg.steps = guidance::suppressShortNameSegments(std::move(leg.steps));
leg.steps = guidance::assignRelativeLocations(std::move(leg.steps),
leg_geometry,
phantoms.source_phantom,
phantoms.target_phantom);
leg_geometry = guidance::resyncGeometry(std::move(leg_geometry), leg.steps);
}
}

leg_geometries.push_back(std::move(leg_geometry));
Expand Down
20 changes: 10 additions & 10 deletions include/engine/guidance/assemble_geometry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
const auto source_geometry_id = facade.GetGeometryIndex(source_node_id).id;
const auto source_geometry = facade.GetUncompressedForwardGeometry(source_geometry_id);

geometry.osm_node_ids.push_back(
facade.GetOSMNodeIDOfNode(source_geometry(source_segment_start_coordinate)));
geometry.node_ids.push_back(source_geometry(source_segment_start_coordinate));

auto cumulative_distance = 0.;
auto current_distance = 0.;
Expand All @@ -71,19 +70,21 @@ inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
cumulative_distance += current_distance;

// all changes to this check have to be matched with assemble_steps
if (path_point.turn_instruction.type != osrm::guidance::TurnType::NoTurn)
auto turn_instruction = path_point.turn_edge
? facade.GetTurnInstructionForEdgeID(*path_point.turn_edge)
: osrm::guidance::TurnInstruction::NO_TURN();
if (turn_instruction.type != osrm::guidance::TurnType::NoTurn)
{
geometry.segment_distances.push_back(cumulative_distance);
geometry.segment_offsets.push_back(geometry.locations.size());
cumulative_distance = 0.;
}

prev_coordinate = coordinate;
const auto node_id = path_point.turn_via_node;

const auto osm_node_id = facade.GetOSMNodeIDOfNode(path_point.turn_via_node);

if (osm_node_id != geometry.osm_node_ids.back() ||
path_point.turn_instruction.type != osrm::guidance::TurnType::NoTurn)
if (node_id != geometry.node_ids.back() ||
turn_instruction.type != osrm::guidance::TurnType::NoTurn)
{
geometry.annotations.emplace_back(LegGeometry::Annotation{
current_distance,
Expand All @@ -99,7 +100,7 @@ inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
facade.GetWeightMultiplier(),
path_point.datasource_id});
geometry.locations.push_back(std::move(coordinate));
geometry.osm_node_ids.push_back(osm_node_id);
geometry.node_ids.push_back(node_id);
}
}
current_distance =
Expand Down Expand Up @@ -158,8 +159,7 @@ inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
const auto target_segment_end_coordinate =
target_node.fwd_segment_position + (reversed_target ? 0 : 1);
const auto target_geometry = facade.GetUncompressedForwardGeometry(target_geometry_id);
geometry.osm_node_ids.push_back(
facade.GetOSMNodeIDOfNode(target_geometry(target_segment_end_coordinate)));
geometry.node_ids.push_back(target_geometry(target_segment_end_coordinate));

BOOST_ASSERT(geometry.segment_distances.size() == geometry.segment_offsets.size() - 1);
BOOST_ASSERT(geometry.locations.size() > geometry.segment_distances.size());
Expand Down
Loading

0 comments on commit 972a848

Please sign in to comment.