diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index c0c1b1812..dfcb55898 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -1,10 +1,7 @@ #include "map/traffic_manager.hpp" -#include "routing/maxspeeds.hpp" #include "routing/routing_helpers.hpp" -#include "routing_common/maxspeed_conversion.hpp" - #include "drape_frontend/drape_engine.hpp" #include "drape_frontend/visual_params.hpp" @@ -13,10 +10,6 @@ #include "geometry/mercator.hpp" -#include "openlr/decoded_path.hpp" -#include "openlr/openlr_decoder.hpp" -#include "openlr/openlr_model.hpp" - #include "platform/platform.hpp" #include "traffxml/traff_model_xml.hpp" @@ -43,15 +36,6 @@ auto constexpr kDrapeUpdateInterval = seconds(10); * Interval at which the traffic observer gets traffic updates while messages are being processed. */ auto constexpr kObserverUpdateInterval = minutes(1); - -// Number of worker threads for the OpenLR decoder -/* - * TODO how to determine the best number of worker threads? - * One per direction? Does not seem to help with bidirectional locations (two reference points). - * One per segment (from–via/from–at, via–to/at–to)? Not yet tested. - * Otherwise there is little to be gained, as we decode messages one at a time. - */ -auto constexpr kNumDecoderThreads = 1; } // namespace TrafficManager::CacheEntry::CacheEntry() @@ -78,7 +62,7 @@ TrafficManager::TrafficManager(DataSource & dataSource, traffic::TrafficObserver & observer) : m_dataSource(dataSource) , m_countryParentNameGetterFn(countryParentNameGetter) - , m_openLrDecoder(m_dataSource, countryParentNameGetter) + , m_traffDecoder(dataSource, countryParentNameGetter, m_messageCache) , m_getMwmsByRectFn(getMwmsByRectFn) , m_observer(observer) , m_currentDataVersion(0) @@ -439,146 +423,6 @@ void TrafficManager::ConsolidateFeedQueue() ++it; } -void TrafficManager::DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) -{ - decoded.clear(); - - // Convert the location to a format understood by the OpenLR decoder. - std::vector segments - = message.m_location.value().ToOpenLrSegments(message.m_id); - - for (auto segment : segments) - { - LOG(LINFO, (" Segment:", segment.m_segmentId)); - for (int i = 0; i < segment.m_locationReference.m_points.size(); i++) - { - LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon)); - if (i < segment.m_locationReference.m_points.size() - 1) - { - LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass)); - LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint)); - } - } - } - - // Decode the location into a path on the map. - // One path per segment - std::vector paths(segments.size()); - m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths); - - for (size_t i = 0; i < paths.size(); i++) - for (size_t j = 0; j < paths[i].m_path.size(); j++) - { - auto fid = paths[i].m_path[j].GetFeatureId().m_index; - auto segment = paths[i].m_path[j].GetSegId(); - uint8_t direction = paths[i].m_path[j].IsForward() ? - traffic::TrafficInfo::RoadSegmentId::kForwardDirection : - traffic::TrafficInfo::RoadSegmentId::kReverseDirection; - decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; - } -} - -void TrafficManager::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded) -{ - for (auto dit = decoded.begin(); dit != decoded.end(); dit++) - for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) - { - /* - * Consolidate TrafficImpact into a single SpeedGroup per segment. - * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate - * the rest. - */ - traffic::SpeedGroup sg = impact.m_speedGroup; - /* - * TODO also process m_delayMins if greater than zero. - * This would require a separate pass over all edges, calculating length, - * total (normal) travel time (length / maxspeed), then a speed group based on - * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between - * reduced and normal speed. That would give us a third potential speed group. - */ - if ((sg != traffic::SpeedGroup::TempBlock) && (impact.m_maxspeed != traffxml::kMaxspeedNone)) - { - auto const handle = m_dataSource.GetMwmHandleById(dit->first); - auto const speeds = routing::LoadMaxspeeds(handle); - if (speeds) - { - traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; - auto const speed = speeds->GetMaxspeed(cit->first.GetFid()); - auto const speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); - if (speedKmPH != routing::kInvalidSpeed) - { - fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.m_maxspeed * 100.0f / speedKmPH); - if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) - sg = fromMaxspeed; - } - } - /* - * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) - * If no maxspeed or delay is set, just give out speed groups. - * Else, examine segments, length, normal travel time, travel time considering impact, and - * determine the closest matching speed group. - */ - } - // TODO process all TrafficImpact fields and determine the speed group based on that - cit->second = sg; - } -} - -/* - * TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations). - * Decoding message by message kind of defeats the purpose. - * But after decoding the location, we need to examine the map features we got in order to - * determine the speed groups, thus we may need to decode one by one (TBD). - * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path - * structures to accept a TraFF message ID (string) rather than an integer, or derive - * [partner] segment IDs from TraFF message IDs. - */ -void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) -{ - if (!message.m_location) - return; - // Decode events into consolidated traffic impact - std::optional impact = message.GetTrafficImpact(); - - LOG(LINFO, (" Impact: ", impact)); - - // Skip further processing if there is no impact - if (!impact) - return; - - traffxml::MultiMwmColoring decoded; - - auto it = m_messageCache.find(message.m_id); - if ((it != m_messageCache.end()) - && !it->second.m_decoded.empty() - && (it->second.m_location == message.m_location)) - { - // cache already has a message with reusable location - - LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache")); - - std::optional cachedImpact = it->second.GetTrafficImpact(); - if (cachedImpact.has_value() && cachedImpact.value() == impact.value()) - { - LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring")); - - // same impact, m_decoded can be reused altogether - message.m_decoded = it->second.m_decoded; - return; - } - else - decoded = it->second.m_decoded; - } - else - DecodeLocation(message, decoded); - - if (impact) - { - ApplyTrafficImpact(impact.value(), decoded); - std::swap(message.m_decoded, decoded); - } -} - void TrafficManager::DecodeFirstMessage() { traffxml::TraffMessage message; @@ -618,7 +462,7 @@ void TrafficManager::DecodeFirstMessage() } LOG(LINFO, (" ", message.m_id, ":", message)); - DecodeMessage(message); + m_traffDecoder.DecodeMessage(message); // store message in cache m_messageCache.insert_or_assign(message.m_id, message); // store message coloring in AllMwmColoring diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 90f337385..b7f3eca65 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -7,11 +7,9 @@ #include "drape/pointers.hpp" -#include "indexer/data_source.hpp" #include "indexer/mwm_set.hpp" -#include "openlr/openlr_decoder.hpp" - +#include "traffxml/traff_decoder.hpp" #include "traffxml/traff_model.hpp" #include "geometry/point2d.hpp" @@ -259,31 +257,6 @@ private: */ void DecodeFirstMessage(); - /** - * @brief Decodes a TraFF location. - * - * @param message The message to decode. - * @param decoded Receives the decoded segments. The speed group will be `Unknown`. - */ - void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); - - /** - * @brief Applies traffic impact to a decoded TraFF location. - * - * Applying impact sets the corresponding speed groups of the decoded segments. Existing speed groups will be overwritten. - * - * @param impact The traffic impact to apply. - * @param decoded The decoded segments. - */ - void ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded); - - /** - * @brief Decodes a single message to its segments and their speed groups. - * - * @param message The message to decode. - */ - void DecodeMessage(traffxml::TraffMessage & message); - /** * @brief Event loop for the traffic worker thread. * @@ -558,11 +531,11 @@ private: std::map m_messageCache; /** - * @brief The OpenLR decoder instance. + * @brief The TraFF decoder instance. * * Used to decode TraFF locations into road segments on the map. */ - openlr::OpenLRDecoder m_openLrDecoder; + traffxml::DefaultTraffDecoder m_traffDecoder; /** * @brief Map between MWM IDs and their colorings. diff --git a/traffxml/CMakeLists.txt b/traffxml/CMakeLists.txt index e8deb4902..029698d24 100644 --- a/traffxml/CMakeLists.txt +++ b/traffxml/CMakeLists.txt @@ -13,5 +13,6 @@ omim_add_library(${PROJECT_NAME} ${SRC}) target_link_libraries(${PROJECT_NAME} pugixml + openlr coding ) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 0a30f26a5..00093b01b 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -2,6 +2,338 @@ //#include "traffxml/traff_foo.hpp" +#include "openlr/decoded_path.hpp" +#include "openlr/openlr_decoder.hpp" +#include "openlr/openlr_model.hpp" + +#include "routing/maxspeeds.hpp" + +#include "routing_common/maxspeed_conversion.hpp" + namespace traffxml { +// Number of worker threads for the OpenLR decoder +/* + * TODO how to determine the best number of worker threads? + * One per direction? Does not seem to help with bidirectional locations (two reference points). + * One per segment (from–via/from–at, via–to/at–to)? Not yet tested. + * Otherwise there is little to be gained, as we decode messages one at a time. + */ +auto constexpr kNumDecoderThreads = 1; + +TraffDecoder::TraffDecoder(DataSource & dataSource, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache) + : m_dataSource(dataSource) + , m_countryParentNameGetterFn(countryParentNameGetter) + , m_messageCache(messageCache) +{} + +void TraffDecoder::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded) +{ + for (auto dit = decoded.begin(); dit != decoded.end(); dit++) + for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) + { + /* + * Consolidate TrafficImpact into a single SpeedGroup per segment. + * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate + * the rest. + */ + traffic::SpeedGroup sg = impact.m_speedGroup; + /* + * TODO also process m_delayMins if greater than zero. + * This would require a separate pass over all edges, calculating length, + * total (normal) travel time (length / maxspeed), then a speed group based on + * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between + * reduced and normal speed. That would give us a third potential speed group. + */ + if ((sg != traffic::SpeedGroup::TempBlock) && (impact.m_maxspeed != traffxml::kMaxspeedNone)) + { + auto const handle = m_dataSource.GetMwmHandleById(dit->first); + auto const speeds = routing::LoadMaxspeeds(handle); + if (speeds) + { + traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; + auto const speed = speeds->GetMaxspeed(cit->first.GetFid()); + auto const speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); + if (speedKmPH != routing::kInvalidSpeed) + { + fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.m_maxspeed * 100.0f / speedKmPH); + if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) + sg = fromMaxspeed; + } + } + /* + * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) + * If no maxspeed or delay is set, just give out speed groups. + * Else, examine segments, length, normal travel time, travel time considering impact, and + * determine the closest matching speed group. + */ + } + // TODO process all TrafficImpact fields and determine the speed group based on that + cit->second = sg; + } +} + +void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) +{ + if (!message.m_location) + return; + // Decode events into consolidated traffic impact + std::optional impact = message.GetTrafficImpact(); + + LOG(LINFO, (" Impact: ", impact)); + + // Skip further processing if there is no impact + if (!impact) + return; + + traffxml::MultiMwmColoring decoded; + + auto it = m_messageCache.find(message.m_id); + if ((it != m_messageCache.end()) + && !it->second.m_decoded.empty() + && (it->second.m_location == message.m_location)) + { + // cache already has a message with reusable location + + LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache")); + + std::optional cachedImpact = it->second.GetTrafficImpact(); + if (cachedImpact.has_value() && cachedImpact.value() == impact.value()) + { + LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring")); + + // same impact, m_decoded can be reused altogether + message.m_decoded = it->second.m_decoded; + return; + } + else + decoded = it->second.m_decoded; + } + else + DecodeLocation(message, decoded); + + if (impact) + { + ApplyTrafficImpact(impact.value(), decoded); + std::swap(message.m_decoded, decoded); + } +} + +OpenLrV3TraffDecoder::OpenLrV3TraffDecoder(DataSource & dataSource, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache) + : TraffDecoder(dataSource, countryParentNameGetter, messageCache) + , m_openLrDecoder(dataSource, countryParentNameGetter) +{} + +openlr::FunctionalRoadClass OpenLrV3TraffDecoder::GetRoadClassFrc(std::optional & roadClass) +{ + if (!roadClass) + return openlr::FunctionalRoadClass::NotAValue; + switch (roadClass.value()) + { + case RoadClass::Motorway: return openlr::FunctionalRoadClass::FRC0; + case RoadClass::Trunk: return openlr::FunctionalRoadClass::FRC0; + case RoadClass::Primary: return openlr::FunctionalRoadClass::FRC1; + case RoadClass::Secondary: return openlr::FunctionalRoadClass::FRC2; + case RoadClass::Tertiary: return openlr::FunctionalRoadClass::FRC3; + /* + * TODO Revisit FRC for Other. + * Other corresponds to FRC4–7. + * FRC4 matches secondary/tertiary (zero score) and anything below (full score). + * FRC5–7 match anything below tertiary (full score); secondary/tertiary never match. + * Primary and above never matches any of these FRCs. + */ + case RoadClass::Other: return openlr::FunctionalRoadClass::FRC4; + } + UNREACHABLE(); +} + +// TODO tweak formula based on FRC, FOW and direct distance (lower FRC roads may have more and sharper turns) +uint32_t OpenLrV3TraffDecoder::GuessDnp(Point & p1, Point & p2) +{ + // direct distance + double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_coordinates), + mercator::FromLatLon(p2.m_coordinates)); + + /* + * Acceptance boundaries for candidate paths are currently: + * + * for `openlr::LinearSegmentSource::FromLocationReferenceTag`, 0.6 to ~1.67 (i.e. 1/0.6) times + * the direct distance, + * + * for `openlr::LinearSegmentSource::FromCoordinatesTag`, 0.25 to 4 times the direct distance. + * + * A tolerance factor of 1/0.6 is the maximum for which direct distance would be accepted in all + * cases, with an upper boundary of at least ~2.78 times the direct distance. However, this may + * cause the actual distance to be overestimated and an incorrect route chosen as a result, as + * path candidates are scored based on the match between DNP and their length. + * Also, since we use `openlr::LinearSegmentSource::FromCoordinatesTag`, acceptance limits are + * much wider than that. + * In practice, the shortest route from one valley to the next in a mountain area is seldom more + * than 3 times the direct distance, based on a brief examination. This would be even within the + * limits of direct distance, hence we do not need a large correction factor for this scenario. + * + * Candidate values: + * 1.66 (1/0.6) – upper boundary for direct distance to be just within the most stringent limits + * 1.41 (2^0.5) – ratio between two sides of a square and its diagonal + * 1.3 – close to the square root of 1.66 (halfway between 1 and 1.66) + * 1.19 – close to the square root of 1.41 + * 1 – direct distance unmodified + */ + double dist = doe * 1.19f; + + // if we have kilometric points, calculate nominal distance as the difference between them + if (p1.m_distance && p2.m_distance) + { + LOG(LINFO, ("Both points have distance, calculating nominal difference")); + float nominalDist = (p1.m_distance.value() - p2.m_distance.value()) * 1000.0; + if (nominalDist < 0) + nominalDist *= -1; + /* + * Plausibility check for nominal distance, as kilometric points along the route may not be + * continuous: discard if shorter than direct distance (geometrically impossible) or if longer + * than 4 times direct distance (somewhat arbitrary, based on the OpenLR acceptance limit for + * `openlr::LinearSegmentSource::FromCoordinatesTag`, as well as real-world observations of + * distances between two adjacent mountain valleys, which are up to roughly 3 times the direct + * distance). If nominal distance is outside these boundaries, discard it and use `dist` (direct + * distance with a tolerance factor). + */ + if ((nominalDist >= doe) && (nominalDist <= doe * 4)) + dist = nominalDist; + else + LOG(LINFO, ("Nominal distance:", nominalDist, "direct distance:", doe, "– discarding")); + } + return dist + 0.5f; +} + +openlr::LocationReferencePoint OpenLrV3TraffDecoder::PointToLrp(Point & point) +{ + openlr::LocationReferencePoint result; + result.m_latLon = ms::LatLon(point.m_coordinates.m_lat, point.m_coordinates.m_lon); + return result; +} + +openlr::LinearLocationReference OpenLrV3TraffDecoder::TraffLocationToLinearLocationReference(TraffLocation & location, bool backwards) +{ + openlr::LinearLocationReference locationReference; + locationReference.m_points.clear(); + std::vector points; + if (location.m_from) + points.push_back(location.m_from.value()); + if (location.m_at) + points.push_back(location.m_at.value()); + else if (location.m_via) + points.push_back(location.m_via.value()); + if (location.m_to) + points.push_back(location.m_to.value()); + if (backwards) + std::reverse(points.begin(), points.end()); + // m_notVia is ignored as OpenLR does not support this functionality. + CHECK_GREATER(points.size(), 1, ("At least two reference points must be given")); + for (size_t i = 0; i < points.size(); i++) + { + openlr::LocationReferencePoint lrp = PointToLrp(points[i]); + lrp.m_functionalRoadClass = GetRoadClassFrc(location.m_roadClass); + if (location.m_ramps.value_or(traffxml::Ramps::None) != traffxml::Ramps::None) + lrp.m_formOfWay = openlr::FormOfWay::Sliproad; + if (i < points.size() - 1) + { + lrp.m_distanceToNextPoint + = GuessDnp(points[i], points[i + 1]); + /* + * Somewhat hackish. LFRCNP is evaluated by the same function as FRC and the candidate is + * used or discarded based on whether a score was returned or not (the score itself is not + * used for LFRCNP). However, this means we can use FRC as LFRCNP. + */ + lrp.m_lfrcnp = GetRoadClassFrc(location.m_roadClass); + } + locationReference.m_points.push_back(lrp); + } + return locationReference; +} + +// TODO make segment ID in OpenLR a string value, and store messageId +std::vector OpenLrV3TraffDecoder::TraffLocationToOpenLrSegments(TraffLocation & location, std::string & messageId) +{ + // Convert the location to a format understood by the OpenLR decoder. + std::vector segments; + int dirs = (location.m_directionality == Directionality::BothDirections) ? 2 : 1; + for (int dir = 0; dir < dirs; dir++) + { + openlr::LinearSegment segment; + /* + * Segment IDs are used internally by the decoder but nowhere else. + * Since we decode TraFF locations one at a time, there are at most two segments in a single + * decoder instance (one segment per direction). Therefore, a segment ID derived from the + * direction is unique within the decoder instance. + */ + segment.m_segmentId = dir; + segment.m_messageId = messageId; + /* + * Segments generated from coordinates can have any number of points. Each point, except for + * the last point, must indicate the distance to the next point. Line properties (functional + * road class (FRC), form of way, bearing) or path properties other than distance to next point + * (lowest FRC to next point, againstDrivingDirection) are ignored. + * Segment length is never evaluated. + * TODO update OpenLR decoder to make all line and path properties optional. + */ + segment.m_source = openlr::LinearSegmentSource::FromCoordinatesTag; + segment.m_locationReference = TraffLocationToLinearLocationReference(location, dir == 0 ? false : true); + + segments.push_back(segment); + } + return segments; +} + +/* + * TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations). + * Decoding message by message kind of defeats the purpose. + * But after decoding the location, we need to examine the map features we got in order to + * determine the speed groups, thus we may need to decode one by one (TBD). + * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path + * structures to accept a TraFF message ID (string) rather than an integer, or derive + * [partner] segment IDs from TraFF message IDs. + */ +void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) +{ + ASSERT(message.m_location, ("Message has no location")); + decoded.clear(); + + // Convert the location to a format understood by the OpenLR decoder. + std::vector segments + = TraffLocationToOpenLrSegments(message.m_location.value(), message.m_id); + + for (auto segment : segments) + { + LOG(LINFO, (" Segment:", segment.m_segmentId)); + for (int i = 0; i < segment.m_locationReference.m_points.size(); i++) + { + LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon)); + if (i < segment.m_locationReference.m_points.size() - 1) + { + LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass)); + LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint)); + } + } + } + + // Decode the location into a path on the map. + // One path per segment + std::vector paths(segments.size()); + m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths); + + for (size_t i = 0; i < paths.size(); i++) + for (size_t j = 0; j < paths[i].m_path.size(); j++) + { + auto fid = paths[i].m_path[j].GetFeatureId().m_index; + auto segment = paths[i].m_path[j].GetSegId(); + uint8_t direction = paths[i].m_path[j].IsForward() ? + traffic::TrafficInfo::RoadSegmentId::kForwardDirection : + traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; + } +} } // namespace traffxml diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 075c20ab1..5e46d1ecf 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -1,7 +1,158 @@ #pragma once -//#include "traffxml/traff_foo.hpp" +#include "traffxml/traff_model.hpp" + +#include "indexer/data_source.hpp" + +#include "openlr/openlr_decoder.hpp" +#include "openlr/openlr_model.hpp" namespace traffxml { +/** + * @brief Abstract base class for all TraFF decoder implementations. + */ +class TraffDecoder +{ +public: + using CountryParentNameGetterFn = std::function; + + TraffDecoder(DataSource & dataSource, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache); + + /** + * @brief Decodes a single message to its segments and their speed groups. + * + * This method may access the message cache which was passed to the constructor. Access to the + * message cache is not thread-safe. Unless it is guaranteed that any operations on the message + * cache will happen on the same thread, calls to this method need to be synchronized with other + * operations on the message cache. + * + * @param message The message to decode. + */ + void DecodeMessage(traffxml::TraffMessage & message); + +protected: + /** + * @brief Decodes a TraFF location. + * + * @param message The message to decode. + * @param decoded Receives the decoded segments. The speed group will be `Unknown`. + */ + virtual void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) = 0; + + /** + * @brief Applies traffic impact to a decoded TraFF location. + * + * Applying impact sets the corresponding speed groups of the decoded segments. Existing speed groups will be overwritten. + * + * @param impact The traffic impact to apply. + * @param decoded The decoded segments. + */ + void ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded); + + DataSource & m_dataSource; + CountryParentNameGetterFn m_countryParentNameGetterFn; + + /** + * @brief Cache of all currently active TraFF messages. + * + * Keys are message IDs, values are messages. + */ + std::map & m_messageCache; + +private: +}; + +/** + * @brief A `TraffDecoder` implementation which internally uses the version 3 OpenLR decoder. + */ +class OpenLrV3TraffDecoder : public TraffDecoder +{ +public: + OpenLrV3TraffDecoder(DataSource & dataSource, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache); + +protected: + /** + * @brief Decodes a TraFF location. + * + * @param message The message to decode. + * @param decoded Receives the decoded segments. The speed group will be `Unknown`. + */ + void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); + +private: + /** + * @brief Returns the OpenLR functional road class (FRC) matching a TraFF road class. + * + * @param roadClass The TraFF road class. + * @return The FRC. + */ + static openlr::FunctionalRoadClass GetRoadClassFrc(std::optional & roadClass); + + /** + * @brief Guess the distance between two points. + * + * If both `p1` and `p2` have the `distance` attribute set, the difference between these two is + * evaluated. If it is within a certain tolerance margin of the direct distance between the two + * points, this value is returned. Otherwise, the distance is calculated from direct distance, + * multiplied with a tolerance factor to account for the fact that the road is not always a + * straight line. + * + * The result can be used to provide some semi-valid DNP values. + * + * @param p1 The first point. + * @param p2 The second point. + * @return The approximate distance on the ground, in meters. + */ + static uint32_t GuessDnp(Point & p1, Point & p2); + + /** + * @brief Converts a TraFF point to an OpenLR location reference point. + * + * Only coordinates are populated. + * + * @param point The point + * @return An OpenLR LRP with the coordinates of the point. + */ + static openlr::LocationReferencePoint PointToLrp(Point & point); + + /** + * @brief Converts a TraFF location to an OpenLR linear location reference. + * + * @param location The location + * @param backwards If true, gnerates a linear location reference for the backwards direction, + * with the order of points reversed. + * @return An OpenLR linear location reference which corresponds to the location. + */ + static openlr::LinearLocationReference TraffLocationToLinearLocationReference(TraffLocation & location, bool backwards); + + /** + * @brief Converts a TraFF location to a vector of OpenLR segments. + * + * Depending on the directionality, the resulting vector will hold one or two elements: one for + * the forward direction, and for bidirectional locations, a second one for the backward + * direction. + * + * @param location The location + * @param messageId The message ID + * @return A vector holding the resulting OpenLR segments. + */ + static std::vector TraffLocationToOpenLrSegments(TraffLocation & location, std::string & messageId); + + /** + * @brief The OpenLR decoder instance. + * + * Used to decode TraFF locations into road segments on the map. + */ + openlr::OpenLRDecoder m_openLrDecoder; +}; + +/** + * @brief The default TraFF decoder implementation, recommended for production use. + */ +using DefaultTraffDecoder = OpenLrV3TraffDecoder; } // namespace traffxml diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index fec9aa5eb..05a424e9e 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -2,8 +2,6 @@ #include "base/logging.hpp" -#include "geometry/mercator.hpp" - #include using namespace std; @@ -174,13 +172,6 @@ bool operator==(Point const & lhs, Point const & rhs) return lhs.m_coordinates == rhs.m_coordinates; } -openlr::LocationReferencePoint Point::ToLrp() -{ - openlr::LocationReferencePoint result; - result.m_latLon = ms::LatLon(this->m_coordinates.m_lat, this->m_coordinates.m_lon); - return result; -} - bool operator==(TraffLocation const & lhs, TraffLocation const & rhs) { return (lhs.m_from == rhs.m_from) @@ -190,101 +181,6 @@ bool operator==(TraffLocation const & lhs, TraffLocation const & rhs) && (lhs.m_to == rhs.m_to); } -openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool backwards) -{ - openlr::LinearLocationReference locationReference; - locationReference.m_points.clear(); - std::vector points; - if (m_from) - points.push_back(m_from.value()); - if (m_at) - points.push_back(m_at.value()); - else if (m_via) - points.push_back(m_via.value()); - if (m_to) - points.push_back(m_to.value()); - if (backwards) - std::reverse(points.begin(), points.end()); - // m_notVia is ignored as OpenLR does not support this functionality. - CHECK_GREATER(points.size(), 1, ("At least two reference points must be given")); - for (size_t i = 0; i < points.size(); i++) - { - openlr::LocationReferencePoint lrp = points[i].ToLrp(); - lrp.m_functionalRoadClass = GetFrc(); - if (m_ramps.value_or(traffxml::Ramps::None) != traffxml::Ramps::None) - lrp.m_formOfWay = openlr::FormOfWay::Sliproad; - if (i < points.size() - 1) - { - lrp.m_distanceToNextPoint - = GuessDnp(points[i], points[i + 1]); - /* - * Somewhat hackish. LFRCNP is evaluated by the same function as FRC and the candidate is - * used or discarded based on whether a score was returned or not (the score itself is not - * used for LFRCNP). However, this means we can use FRC as LFRCNP. - */ - lrp.m_lfrcnp = GetFrc(); - } - locationReference.m_points.push_back(lrp); - } - return locationReference; -} - -// TODO make segment ID in OpenLR a string value, and store messageId -std::vector TraffLocation::ToOpenLrSegments(std::string & messageId) -{ - // Convert the location to a format understood by the OpenLR decoder. - std::vector segments; - int dirs = (m_directionality == Directionality::BothDirections) ? 2 : 1; - for (int dir = 0; dir < dirs; dir++) - { - openlr::LinearSegment segment; - /* - * Segment IDs are used internally by the decoder but nowhere else. - * Since we decode TraFF locations one at a time, there are at most two segments in a single - * decoder instance (one segment per direction). Therefore, a segment ID derived from the - * direction is unique within the decoder instance. - */ - segment.m_segmentId = dir; - segment.m_messageId = messageId; - /* - * Segments generated from coordinates can have any number of points. Each point, except for - * the last point, must indicate the distance to the next point. Line properties (functional - * road class (FRC), form of way, bearing) or path properties other than distance to next point - * (lowest FRC to next point, againstDrivingDirection) are ignored. - * Segment length is never evaluated. - * TODO update OpenLR decoder to make all line and path properties optional. - */ - segment.m_source = openlr::LinearSegmentSource::FromCoordinatesTag; - segment.m_locationReference = this->ToLinearLocationReference(dir == 0 ? false : true); - - segments.push_back(segment); - } - return segments; -} - -openlr::FunctionalRoadClass TraffLocation::GetFrc() -{ - if (!m_roadClass) - return openlr::FunctionalRoadClass::NotAValue; - switch (m_roadClass.value()) - { - case RoadClass::Motorway: return openlr::FunctionalRoadClass::FRC0; - case RoadClass::Trunk: return openlr::FunctionalRoadClass::FRC0; - case RoadClass::Primary: return openlr::FunctionalRoadClass::FRC1; - case RoadClass::Secondary: return openlr::FunctionalRoadClass::FRC2; - case RoadClass::Tertiary: return openlr::FunctionalRoadClass::FRC3; - /* - * TODO Revisit FRC for Other. - * Other corresponds to FRC4–7. - * FRC4 matches secondary/tertiary (zero score) and anything below (full score). - * FRC5–7 match anything below tertiary (full score); secondary/tertiary never match. - * Primary and above never matches any of these FRCs. - */ - case RoadClass::Other: return openlr::FunctionalRoadClass::FRC4; - } - UNREACHABLE(); -} - std::optional TraffMessage::GetTrafficImpact() { // no events, no impact @@ -346,64 +242,6 @@ std::optional TraffMessage::GetTrafficImpact() return std::nullopt; } -// TODO tweak formula based on FRC, FOW and direct distance (lower FRC roads may have more and sharper turns) -uint32_t GuessDnp(Point & p1, Point & p2) -{ - // direct distance - double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_coordinates), - mercator::FromLatLon(p2.m_coordinates)); - - /* - * Acceptance boundaries for candidate paths are currently: - * - * for `openlr::LinearSegmentSource::FromLocationReferenceTag`, 0.6 to ~1.67 (i.e. 1/0.6) times - * the direct distance, - * - * for `openlr::LinearSegmentSource::FromCoordinatesTag`, 0.25 to 4 times the direct distance. - * - * A tolerance factor of 1/0.6 is the maximum for which direct distance would be accepted in all - * cases, with an upper boundary of at least ~2.78 times the direct distance. However, this may - * cause the actual distance to be overestimated and an incorrect route chosen as a result, as - * path candidates are scored based on the match between DNP and their length. - * Also, since we use `openlr::LinearSegmentSource::FromCoordinatesTag`, acceptance limits are - * much wider than that. - * In practice, the shortest route from one valley to the next in a mountain area is seldom more - * than 3 times the direct distance, based on a brief examination. This would be even within the - * limits of direct distance, hence we do not need a large correction factor for this scenario. - * - * Candidate values: - * 1.66 (1/0.6) – upper boundary for direct distance to be just within the most stringent limits - * 1.41 (2^0.5) – ratio between two sides of a square and its diagonal - * 1.3 – close to the square root of 1.66 (halfway between 1 and 1.66) - * 1.19 – close to the square root of 1.41 - * 1 – direct distance unmodified - */ - double dist = doe * 1.19f; - - // if we have kilometric points, calculate nominal distance as the difference between them - if (p1.m_distance && p2.m_distance) - { - LOG(LINFO, ("Both points have distance, calculating nominal difference")); - float nominalDist = (p1.m_distance.value() - p2.m_distance.value()) * 1000.0; - if (nominalDist < 0) - nominalDist *= -1; - /* - * Plausibility check for nominal distance, as kilometric points along the route may not be - * continuous: discard if shorter than direct distance (geometrically impossible) or if longer - * than 4 times direct distance (somewhat arbitrary, based on the OpenLR acceptance limit for - * `openlr::LinearSegmentSource::FromCoordinatesTag`, as well as real-world observations of - * distances between two adjacent mountain valleys, which are up to roughly 3 times the direct - * distance). If nominal distance is outside these boundaries, discard it and use `dist` (direct - * distance with a tolerance factor). - */ - if ((nominalDist >= doe) && (nominalDist <= doe * 4)) - dist = nominalDist; - else - LOG(LINFO, ("Nominal distance:", nominalDist, "direct distance:", doe, "– discarding")); - } - return dist + 0.5f; -} - void MergeMultiMwmColoring(MultiMwmColoring & delta, MultiMwmColoring & target) { // for each mwm in delta diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 412ea76d6..2bec230b8 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -3,12 +3,9 @@ //#include "traffxml/traff_foo.hpp" #include "geometry/latlon.hpp" -#include "geometry/point2d.hpp" #include "indexer/mwm_set.hpp" -#include "openlr/openlr_model.hpp" - #include "traffic/speed_groups.hpp" #include "traffic/traffic_info.hpp" @@ -264,15 +261,6 @@ struct Point friend bool operator==(Point const & lhs, Point const & rhs); friend bool operator!=(Point const & lhs, Point const & rhs) { return !(lhs == rhs); } - /** - * @brief Converts the point to an OpenLR location reference point. - * - * Only coordinates are populated. - * - * @return An OpenLR LRP with the coordinates of the point. - */ - openlr::LocationReferencePoint ToLrp(); - // TODO role? ms::LatLon m_coordinates = ms::LatLon::Zero(); std::optional m_distance; @@ -297,34 +285,6 @@ struct TraffLocation friend bool operator==(TraffLocation const & lhs, TraffLocation const & rhs); friend bool operator!=(TraffLocation const & lhs, TraffLocation const & rhs) { return !(lhs == rhs); } - /** - * @brief Converts the location to an OpenLR linear location reference. - * - * @param backwards If true, gnerates a linear location reference for the backwards direction, - * with the order of points reversed. - * @return An OpenLR linear location reference which corresponds to the location. - */ - openlr::LinearLocationReference ToLinearLocationReference(bool backwards); - - /** - * @brief Converts the location to a vector of OpenLR segments. - * - * Depending on the directionality, the resulting vector will hold one or two elements: one for - * the forward direction, and for bidirectional locations, a second one for the backward - * direction. - * - * @param messageId The message ID - * @return A vector holding the resulting OpenLR segments. - */ - std::vector ToOpenLrSegments(std::string & messageId); - - /** - * @brief Returns the OpenLR functional road class (FRC) matching `m_roadClass`. - * - * @return The FRC. - */ - openlr::FunctionalRoadClass GetFrc(); - std::optional m_country; std::optional m_destination; std::optional m_direction; @@ -411,20 +371,6 @@ using TraffFeed = std::vector; * the full filter list. */ -/** - * @brief Guess the distance to the next point. - * - * This is calculated as direct distance, multiplied with a tolerance factor to account for the - * fact that the road is not always a straight line. - * - * The result can be used to provide some semi-valid DNP values. - * - * @param p1 The first point. - * @param p2 The second point. - * @return The approximate distance on the ground, in meters. - */ -uint32_t GuessDnp(Point & p1, Point & p2); - /** * @brief Merges the contents of one `MultiMwmColoring` into another. *