diff --git a/include/engine/api/route_api.hpp b/include/engine/api/route_api.hpp index eeeb5f5c0..d87f69553 100644 --- a/include/engine/api/route_api.hpp +++ b/include/engine/api/route_api.hpp @@ -106,7 +106,8 @@ class RouteAPI : public BaseAPI * 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 + * 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 | @@ -118,16 +119,22 @@ class RouteAPI : public BaseAPI * | | * | | * - * Coming from S via V to T, we end up with the legs S->V and V->T. V-T will say to take + * 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 + * 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. */ - leg.steps = guidance::postProcess(std::move(steps)); - leg_geometry = guidance::resyncGeometry(std::move(leg_geometry),leg.steps); + leg.steps = guidance::postProcess(std::move(steps)); + guidance::trimShortSegments(leg.steps, leg_geometry); + 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)); diff --git a/include/engine/guidance/assemble_steps.hpp b/include/engine/guidance/assemble_steps.hpp index 13ff4c965..c6d19eead 100644 --- a/include/engine/guidance/assemble_steps.hpp +++ b/include/engine/guidance/assemble_steps.hpp @@ -62,25 +62,11 @@ std::vector assembleSteps(const DataFacadeT &facade, std::size_t segment_index = 0; BOOST_ASSERT(leg_geometry.locations.size() >= 2); - // We report the relative position of source/target to the road only within a range that is - // sufficiently different but not full of the path - const constexpr double MINIMAL_RELATIVE_DISTANCE = 5., MAXIMAL_RELATIVE_DISTANCE = 300.; - const auto distance_to_start = util::coordinate_calculation::haversineDistance( - source_node.input_location, leg_geometry.locations[0]); - const auto initial_modifier = - distance_to_start >= MINIMAL_RELATIVE_DISTANCE && - distance_to_start <= MAXIMAL_RELATIVE_DISTANCE - ? angleToDirectionModifier(util::coordinate_calculation::computeAngle( - source_node.input_location, leg_geometry.locations[0], leg_geometry.locations[1])) - : extractor::guidance::DirectionModifier::UTurn; - if (leg_data.size() > 0) { StepManeuver maneuver = detail::stepManeuverFromGeometry( - extractor::guidance::TurnInstruction{extractor::guidance::TurnType::NoTurn, - initial_modifier}, - WaypointType::Depart, leg_geometry); + extractor::guidance::TurnInstruction::NO_TURN(), WaypointType::Depart, leg_geometry); maneuver.location = source_node.location; // PathData saves the information we need of the segment _before_ the turn, @@ -134,9 +120,7 @@ std::vector assembleSteps(const DataFacadeT &facade, // |---------| target_duration StepManeuver maneuver = detail::stepManeuverFromGeometry( - extractor::guidance::TurnInstruction{extractor::guidance::TurnType::NoTurn, - initial_modifier}, - WaypointType::Depart, leg_geometry); + extractor::guidance::TurnInstruction::NO_TURN(), WaypointType::Depart, leg_geometry); int duration = target_duration - source_duration; BOOST_ASSERT(duration >= 0); @@ -151,20 +135,9 @@ std::vector assembleSteps(const DataFacadeT &facade, } BOOST_ASSERT(segment_index == number_of_segments - 1); - const auto distance_from_end = util::coordinate_calculation::haversineDistance( - target_node.input_location, leg_geometry.locations.back()); - const auto final_modifier = - distance_from_end >= MINIMAL_RELATIVE_DISTANCE && - distance_from_end <= MAXIMAL_RELATIVE_DISTANCE - ? angleToDirectionModifier(util::coordinate_calculation::computeAngle( - leg_geometry.locations[leg_geometry.locations.size() - 2], - leg_geometry.locations[leg_geometry.locations.size() - 1], - target_node.input_location)) - : extractor::guidance::DirectionModifier::UTurn; // This step has length zero, the only reason we need it is the target location auto final_maneuver = detail::stepManeuverFromGeometry( - extractor::guidance::TurnInstruction{extractor::guidance::TurnType::NoTurn, final_modifier}, - WaypointType::Arrive, leg_geometry); + extractor::guidance::TurnInstruction::NO_TURN(), WaypointType::Arrive, leg_geometry); steps.push_back(RouteStep{target_node.name_id, facade.GetNameForID(target_node.name_id), ZERO_DURATION, diff --git a/include/engine/guidance/post_processing.hpp b/include/engine/guidance/post_processing.hpp index 92fb67b8d..32f6ee5ca 100644 --- a/include/engine/guidance/post_processing.hpp +++ b/include/engine/guidance/post_processing.hpp @@ -1,6 +1,7 @@ #ifndef ENGINE_GUIDANCE_POST_PROCESSING_HPP #define ENGINE_GUIDANCE_POST_PROCESSING_HPP +#include "engine/phantom_node.hpp" #include "engine/guidance/route_step.hpp" #include "engine/guidance/leg_geometry.hpp" @@ -16,6 +17,19 @@ namespace guidance // passed as none-reference to modify in-place and move out again std::vector postProcess(std::vector steps); +// trim initial/final segment of very short length. +// This function uses in/out parameter passing to modify both steps and geometry in place. +// We use this method since both steps and geometry are closely coupled logically but +// are not coupled in the same way in the background. To avoid the additional overhead +// of introducing intermediate structions, we resolve to the in/out scheme at this point. +void trimShortSegments(std::vector &steps, LegGeometry &geometry); + +// assign relative locations to depart/arrive instructions +std::vector assignRelativeLocations(std::vector steps, + const LegGeometry &geometry, + const PhantomNode &source_node, + const PhantomNode &target_node); + // postProcess will break the connection between the leg geometry // for which a segment is supposed to represent exactly the coordinates // between routing maneuvers and the route steps itself. diff --git a/src/engine/guidance/assemble_steps.cpp b/src/engine/guidance/assemble_steps.cpp index 9b701e18f..207a6d287 100644 --- a/src/engine/guidance/assemble_steps.cpp +++ b/src/engine/guidance/assemble_steps.cpp @@ -22,7 +22,7 @@ StepManeuver stepManeuverFromGeometry(extractor::guidance::TurnInstruction instr double pre_turn_bearing = 0, post_turn_bearing = 0; Coordinate turn_coordinate; - if (waypoint_type == WaypointType::Arrive) + if (waypoint_type == WaypointType::Depart) { turn_coordinate = leg_geometry.locations.front(); const auto post_turn_coordinate = *(leg_geometry.locations.begin() + 1); @@ -31,7 +31,7 @@ StepManeuver stepManeuverFromGeometry(extractor::guidance::TurnInstruction instr } else { - BOOST_ASSERT(waypoint_type == WaypointType::Depart); + BOOST_ASSERT(waypoint_type == WaypointType::Arrive); turn_coordinate = leg_geometry.locations.back(); const auto pre_turn_coordinate = *(leg_geometry.locations.end() - 2); pre_turn_bearing = diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp index 967d8d682..24c3d5aaa 100644 --- a/src/engine/guidance/post_processing.cpp +++ b/src/engine/guidance/post_processing.cpp @@ -2,12 +2,16 @@ #include "extractor/guidance/turn_instruction.hpp" #include "engine/guidance/toolkit.hpp" +#include "engine/guidance/assemble_steps.hpp" #include #include +#include #include +#include #include +#include #include using TurnInstruction = osrm::extractor::guidance::TurnInstruction; @@ -292,6 +296,131 @@ std::vector postProcess(std::vector steps) return steps; } +void trimShortSegments(std::vector &steps, LegGeometry &geometry) +{ + // Doing this step in post-processing provides a few challenges we cannot overcome. + // The removal of an initial step imposes some copy overhead in the steps, moving all later + // steps to the front. + // In addition, we cannot reduce the travel time that is accumulated at a different location. + // As a direct implication, we have to keep the time of the initial/final turns (which adds a + // few seconds of inaccuracy at both ends. This is acceptable, however, since the turn should + // usually not be as relevant. + + if (steps.size() <= 2) + return; + + // if phantom node is located at the connection of two segments, either one can be selected as + // turn + // + // a --- b + // | + // c + // + // If a route from b to c is requested, both a--b and b--c could be selected as start segment. + // In case of a--b, we end up with an unwanted turn saying turn-right onto b-c. + // These cases start off with an initial segment which is of zero length. + // We have to be careful though, since routing that starts in a roundabout has a valid + // initial segment of length zero and we cannot delete the upcoming segment. + + if (steps.front().distance <= std::numeric_limits::epsilon() && + !entersRoundabout((steps.begin() + 1)->maneuver.instruction)) + { + // We have to adjust the first step both for its name and the bearings + const auto ¤t_depart = steps.front(); + auto &designated_depart = *(steps.begin() + 1); + + // FIXME this is required to be consistent with the route durations. The initial turn is not + // actually part of the route, though + designated_depart.duration += current_depart.duration; + + geometry.locations.erase(geometry.locations.begin()); + + BOOST_ASSERT(geometry.segment_offsets[1] == 1); + // geometry offsets have to be adjusted. Move all offsets to the front and reduce by one. + std::transform(geometry.segment_offsets.begin() + 1, geometry.segment_offsets.end(), + geometry.segment_offsets.begin(), [](const std::size_t val) + { + return val - 1; + }); + geometry.segment_offsets.pop_back(); + + // remove the initial distance value + geometry.segment_distances.erase(geometry.segment_distances.begin()); + + // update initial turn direction/bearings. Due to the duplicated first coordinate, the + // initial bearing is invalid + designated_depart.maneuver = detail::stepManeuverFromGeometry( + TurnInstruction::NO_TURN(), WaypointType::Depart, geometry); + + // finally remove the initial (now duplicated move) + steps.erase(steps.begin()); + + // and update the leg geometry indices for the removed entry + std::for_each(steps.begin(), steps.end(), [](RouteStep &step) + { + --step.geometry_begin; + --step.geometry_end; + }); + } + + // make sure we still have enough segments + if (steps.size() <= 2) + return; + + auto &next_to_last_step = *(steps.end() - 2); + // in the end, the situation with the roundabout cannot occur. As a result, we can remove all + // zero-length instructions + if (next_to_last_step.distance <= std::numeric_limits::epsilon()) + { + geometry.locations.pop_back(); + geometry.segment_offsets.pop_back(); + BOOST_ASSERT(geometry.segment_distances.back() < std::numeric_limits::epsilon()); + geometry.segment_distances.pop_back(); + + next_to_last_step.maneuver = detail::stepManeuverFromGeometry( + TurnInstruction::NO_TURN(), WaypointType::Arrive, geometry); + steps.pop_back(); + // the geometry indices of the last step are already correct; + } +} + +// assign relative locations to depart/arrive instructions +std::vector assignRelativeLocations(std::vector steps, + const LegGeometry &leg_geometry, + const PhantomNode &source_node, + const PhantomNode &target_node) +{ + // We report the relative position of source/target to the road only within a range that is + // sufficiently different but not full of the path + BOOST_ASSERT(steps.size() >= 2 ); + BOOST_ASSERT(leg_geometry.locations.size() >= 2 ); + const constexpr double MINIMAL_RELATIVE_DISTANCE = 5., MAXIMAL_RELATIVE_DISTANCE = 300.; + const auto distance_to_start = util::coordinate_calculation::haversineDistance( + source_node.input_location, leg_geometry.locations[0]); + const auto initial_modifier = + distance_to_start >= MINIMAL_RELATIVE_DISTANCE && + distance_to_start <= MAXIMAL_RELATIVE_DISTANCE + ? angleToDirectionModifier(util::coordinate_calculation::computeAngle( + source_node.input_location, leg_geometry.locations.at(0), leg_geometry.locations.at(1))) + : extractor::guidance::DirectionModifier::UTurn; + + steps.front().maneuver.instruction.direction_modifier = initial_modifier; + + const auto distance_from_end = util::coordinate_calculation::haversineDistance( + target_node.input_location, leg_geometry.locations.back()); + const auto final_modifier = + distance_from_end >= MINIMAL_RELATIVE_DISTANCE && + distance_from_end <= MAXIMAL_RELATIVE_DISTANCE + ? angleToDirectionModifier(util::coordinate_calculation::computeAngle( + leg_geometry.locations.at(leg_geometry.locations.size() - 2), + leg_geometry.locations.at(leg_geometry.locations.size() - 1), + target_node.input_location)) + : extractor::guidance::DirectionModifier::UTurn; + + steps.back().maneuver.instruction.direction_modifier = final_modifier; + return steps; +} + LegGeometry resyncGeometry(LegGeometry leg_geometry, const std::vector &steps) { // The geometry uses an adjacency array-like structure for representation. @@ -308,7 +437,7 @@ LegGeometry resyncGeometry(LegGeometry leg_geometry, const std::vector