#ifndef ENGINE_GUIDANCE_ASSEMBLE_LEG_HPP_ #define ENGINE_GUIDANCE_ASSEMBLE_LEG_HPP_ #include "engine/datafacade/datafacade_base.hpp" #include "engine/guidance/leg_geometry.hpp" #include "engine/guidance/route_leg.hpp" #include "engine/guidance/route_step.hpp" #include "engine/internal_route_result.hpp" #include "util/typedefs.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace osrm { namespace engine { namespace guidance { namespace detail { const constexpr std::size_t MAX_USED_SEGMENTS = 2; struct NamedSegment { EdgeWeight duration; std::uint32_t position; std::uint32_t name_id; }; template std::array summarizeRoute(const datafacade::BaseDataFacade &facade, const std::vector &route_data, const PhantomNode &target_node, const bool target_traversed_in_reverse) { // merges segments with same name id const auto collapse_segments = [](std::vector &segments) { auto out = segments.begin(); auto end = segments.end(); // Do nothing if we were given an empty array if (out == end) { return end; } for (auto in = std::next(out); in != end; ++in) { if (in->name_id == out->name_id) { out->duration += in->duration; } else { ++out; BOOST_ASSERT(out != end); *out = *in; } } BOOST_ASSERT(out != end); return ++out; }; std::vector segments(route_data.size()); std::uint32_t index = 0; std::transform( route_data.begin(), route_data.end(), segments.begin(), [&index](const PathData &point) { return NamedSegment{point.duration_until_turn, index++, point.name_id}; }); const auto target_duration = target_traversed_in_reverse ? target_node.reverse_duration : target_node.forward_duration; const auto target_node_id = target_traversed_in_reverse ? target_node.reverse_segment_id.id : target_node.forward_segment_id.id; if (target_duration > 1) segments.push_back({target_duration, index++, facade.GetNameIndex(target_node_id)}); // this makes sure that the segment with the lowest position comes first std::sort( segments.begin(), segments.end(), [](const NamedSegment &lhs, const NamedSegment &rhs) { return lhs.name_id < rhs.name_id || (lhs.name_id == rhs.name_id && lhs.position < rhs.position); }); auto new_end = collapse_segments(segments); segments.resize(new_end - segments.begin()); // Filter out segments with an empty name (name_id == 0) new_end = std::remove_if(segments.begin(), segments.end(), [](const NamedSegment &segment) { return segment.name_id == 0; }); segments.resize(new_end - segments.begin()); // sort descending std::sort( segments.begin(), segments.end(), [](const NamedSegment &lhs, const NamedSegment &rhs) { return lhs.duration > rhs.duration || (lhs.duration == rhs.duration && lhs.position < rhs.position); }); // make sure the segments are sorted by position segments.resize(std::min(segments.size(), SegmentNumber)); std::sort( segments.begin(), segments.end(), [](const NamedSegment &lhs, const NamedSegment &rhs) { return lhs.position < rhs.position; }); std::array summary; std::fill(summary.begin(), summary.end(), EMPTY_NAMEID); std::transform(segments.begin(), segments.end(), summary.begin(), [](const NamedSegment &segment) { return segment.name_id; }); return summary; } } inline RouteLeg assembleLeg(const datafacade::BaseDataFacade &facade, const std::vector &route_data, const LegGeometry &leg_geometry, const PhantomNode &source_node, const PhantomNode &target_node, const bool target_traversed_in_reverse, const bool needs_summary) { const auto target_duration = (target_traversed_in_reverse ? target_node.reverse_duration : target_node.forward_duration); const auto target_weight = (target_traversed_in_reverse ? target_node.reverse_weight : target_node.forward_weight); auto distance = std::accumulate( leg_geometry.segment_distances.begin(), leg_geometry.segment_distances.end(), 0.); auto duration = std::accumulate( route_data.begin(), route_data.end(), 0, [](const double sum, const PathData &data) { return sum + data.duration_until_turn; }); auto weight = std::accumulate( route_data.begin(), route_data.end(), 0, [](const double sum, const PathData &data) { return sum + data.weight_until_turn; }); // s // | // Given a route a---b---c where there is a right turn at c. // | // d // |--t // e // (a, b, c) gets compressed to (a,c) // (c, d, e) gets compressed to (c,e) // The duration of the turn (a,c) -> (c,e) will be the duration of (a,c) (e.g. the duration // of (a,b,c)). // The phantom node of s will contain: // `forward_duration`: duration of (a,s) // `forward_offset`: 0 (its the first segment) // The phantom node of t will contain: // `forward_duration`: duration of (d,t) // `forward_offset`: duration of (c, d) // path_data will have entries for (s,b), (b, c), (c, d) but (d, t) is only // caputed by the phantom node. So we need to add the target duration here. // On local segments, the target duration is already part of the duration, however. duration = duration + target_duration; weight = weight + target_weight; if (route_data.empty()) { weight -= (target_traversed_in_reverse ? source_node.reverse_weight : source_node.forward_weight); duration -= (target_traversed_in_reverse ? source_node.reverse_duration : source_node.forward_duration); // use rectified linear unit function to avoid negative duration values // due to flooring errors in phantom snapping duration = std::max(0, duration); } std::string summary; if (needs_summary) { auto summary_array = detail::summarizeRoute( facade, route_data, target_node, target_traversed_in_reverse); BOOST_ASSERT(detail::MAX_USED_SEGMENTS > 0); BOOST_ASSERT(summary_array.begin() != summary_array.end()); // transform a name_id into a string containing either the name, or -if the name is empty- // the reference. const auto name_id_to_string = [&](const NameID name_id) { const auto name = facade.GetNameForID(name_id); if (!name.empty()) return name.to_string(); else { const auto ref = facade.GetRefForID(name_id); return ref.to_string(); } }; const auto not_empty = [&](const std::string &name) { return !name.empty(); }; const auto summary_names = summary_array | boost::adaptors::transformed(name_id_to_string) | boost::adaptors::filtered(not_empty); summary = boost::algorithm::join(summary_names, ", "); } return RouteLeg{std::round(distance * 10.) / 10., duration / 10., weight / facade.GetWeightMultiplier(), summary, {}}; } } // namespace guidance } // namespace engine } // namespace osrm #endif // ENGINE_GUIDANCE_SEGMENT_LIST_HPP_