diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 0d1b39eb5..f67130775 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -509,67 +509,6 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf } #endif -double RoutingTraffDecoder::TraffEstimator::GetRoadRefPenalty(std::string & ref) const -{ - // skip parsing if ref is empty - if (ref.empty()) - { - if (m_decoder.m_roadRef.empty()) - return 1; - else if (!m_decoder.m_roadRef.empty()) - return kAttributePenalty; - } - - // TODO does caching results per ref improve performance? - - std::vector r = ParseRef(ref); - - size_t matches = 0; - - if (m_decoder.m_roadRef.empty() && r.empty()) - return 1; - else if (m_decoder.m_roadRef.empty() || r.empty()) - return kAttributePenalty; - - // work on a copy of `m_decoder.m_roadRef` - std::vector l = m_decoder.m_roadRef; - - if ((l.size() > 1) && (r.size() > 1) && (l.front() == r.front())) - { - /* - * Discard generic prefixes, which are often used to denote the road class. - * This will turn `A1` and `A2` into `1` and `2`, causing them to be treated as a mismatch, - * not a partial match. - */ - l.erase(l.begin()); - r.erase(r.begin()); - } - - // for both sides, count items matched by the other side - for (auto & litem : l) - for (auto ritem : r) - if (litem == ritem) - { - matches++; - break; - } - - for (auto ritem : r) - for (auto & litem : l) - if (litem == ritem) - { - matches++; - break; - } - - if (matches == 0) - return kAttributePenalty; - else if (matches == (l.size() + r.size())) - return 1; - else - return kReducedAttributePenalty; -} - double RoutingTraffDecoder::TraffEstimator::GetUTurnPenalty(Purpose /* purpose */) const { // Adds 2 minutes penalty for U-turn. The value is quite arbitrary @@ -665,26 +604,9 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c if (!m_decoder.m_message || !m_decoder.m_message.value().m_location.value().m_roadClass) return result; - std::optional highwayType = road.GetHighwayType(); - - if (highwayType) - { - if (IsRamp(highwayType.value()) != (m_decoder.m_message.value().m_location.value().m_ramps != Ramps::None)) - // if one is a ramp and the other is not, treat it as a mismatch - result *= kAttributePenalty; - if (m_decoder.m_message.value().m_location.value().m_roadClass) - // if the message specifies a road class, penalize mismatches - result *= GetRoadClassPenalty(m_decoder.m_message.value().m_location.value().m_roadClass.value(), - GetRoadClass(highwayType.value())); - } - else // road has no highway class - { - // we can’t determine if it is a ramp, penalize for mismatch - result *= kAttributePenalty; - if (m_decoder.m_message.value().m_location.value().m_roadClass) - // we can’t determine if the road matches the required road class, treat it as mismatch - result *= kAttributePenalty; - } + result *= GetHighwayTypePenalty(road.GetHighwayType(), + m_decoder.m_message.value().m_location.value().m_roadClass, + m_decoder.m_message.value().m_location.value().m_ramps); if (!m_decoder.m_roadRef.empty()) { @@ -694,18 +616,7 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c auto f = g.GetOriginalFeatureByIndex(segment.GetFeatureId()); auto refs = ftypes::GetRoadShieldsNames(*f); - auto penalty = kAttributePenalty; - - for (auto & ref : refs) - { - auto newPenalty = GetRoadRefPenalty(ref); - if (newPenalty < penalty) - penalty = newPenalty; - if (penalty == 1) - break; - } - - result *= penalty; + result *= m_decoder.GetRoadRefPenalty(refs); } return result; @@ -747,6 +658,108 @@ RoutingTraffDecoder::RoutingTraffDecoder(DataSource & dataSource, CountryInfoGet InitRouter(); } +double RoutingTraffDecoder::GetHighwayTypePenalty(std::optional highwayType, + std::optional roadClass, + Ramps ramps) +{ + double result = 1.0; + if (highwayType) + { + if (IsRamp(highwayType.value()) != (ramps != Ramps::None)) + // if one is a ramp and the other is not, treat it as a mismatch + result *= kAttributePenalty; + if (roadClass) + // if the message specifies a road class, penalize mismatches + result *= GetRoadClassPenalty(roadClass.value(), GetRoadClass(highwayType.value())); + } + else // road has no highway class + { + // we can’t determine if it is a ramp, penalize for mismatch + result *= kAttributePenalty; + if (roadClass) + // we can’t determine if the road matches the required road class, treat it as mismatch + result *= kAttributePenalty; + } + return result; +} + +double RoutingTraffDecoder::GetRoadRefPenalty(std::vector & refs) const +{ + double result = kAttributePenalty; + + for (auto & ref : refs) + { + auto newResult = GetRoadRefPenalty(ref); + if (newResult < result) + result = newResult; + if (result == 1) + break; + } + + return result; +} + +double RoutingTraffDecoder::GetRoadRefPenalty(std::string const & ref) const +{ + // skip parsing if ref is empty + if (ref.empty()) + { + if (m_roadRef.empty()) + return 1; + else if (!m_roadRef.empty()) + return kAttributePenalty; + } + + // TODO does caching results per ref improve performance? + + std::vector r = ParseRef(ref); + + size_t matches = 0; + + if (m_roadRef.empty() && r.empty()) + return 1; + else if (m_roadRef.empty() || r.empty()) + return kAttributePenalty; + + // work on a copy of `m_decoder.m_roadRef` + std::vector l = m_roadRef; + + if ((l.size() > 1) && (r.size() > 1) && (l.front() == r.front())) + { + /* + * Discard generic prefixes, which are often used to denote the road class. + * This will turn `A1` and `A2` into `1` and `2`, causing them to be treated as a mismatch, + * not a partial match. + */ + l.erase(l.begin()); + r.erase(r.begin()); + } + + // for both sides, count items matched by the other side + for (auto & litem : l) + for (auto ritem : r) + if (litem == ritem) + { + matches++; + break; + } + + for (auto ritem : r) + for (auto & litem : l) + if (litem == ritem) + { + matches++; + break; + } + + if (matches == 0) + return kAttributePenalty; + else if (matches == (l.size() + r.size())) + return 1; + else + return kReducedAttributePenalty; +} + void RoutingTraffDecoder::OnMapRegistered(platform::LocalCountryFile const & localFile) { std::lock_guard lock(m_mutex); @@ -1234,7 +1247,7 @@ bool IsRamp(routing::HighwayType highwayType) } } -std::vector ParseRef(std::string & ref) +std::vector ParseRef(std::string const & ref) { std::vector res; std::string curr = ""; diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 753abec7c..1a3ec0dbd 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -303,19 +303,6 @@ public: double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose /* purpose */) const override; double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose /* purpose */) const override; - /** - * @brief Determines the penalty factor based on how two reference numbers match. - * - * Rules are subject to change. - * - * @param ref The reference number of the current segment, compared against `m_roadRef`. - * - * @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty` - * for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for - * a partial match (unclear whether both refs refer to the same object). - */ - double GetRoadRefPenalty(std::string & ref) const; - double GetUTurnPenalty(Purpose /* purpose */) const override; /** @@ -354,6 +341,63 @@ public: */ virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) override {} + /** + * @brief Determines the penalty factor bases on how highway attributes match. + * + * This compares the highway type of the candidate feature (as retrieved from OSM) against the + * road class and ramps attributes of the location. + * + * Rules are subject to change but principles are: + * + * Penalties for ramp mismatch and road class mismatch are applied consecutively, thus the maximum + * penalty is `kAttributePenalty ^ 2`. + * + * If ramps mismatch (location specifies a ramp but candidate is not a ramp, or vice versa), the + * penalty is `kAttributePenalty`. + * + * If road classes are similar, the penalty is `kReducedAttributePenalty`. For a complete + * mismatch, the penalty is `kAttributePenalty`. + * + * @param highwayType The OSM highway type of the candidate feature. + * @param roadClass The TraFF road class of the location. + * @param ramps The ramps atribute of the TraFF location. + * + * @return 1 for a perfect match (same road class and ramp type), up to `kAttributePenalty ^ 2` + * for a mismatch. + */ + static double GetHighwayTypePenalty(std::optional highwayType, + std::optional roadClass, + Ramps ramps); + + /** + * @brief Determines the penalty factor based on how two reference numbers match. + * + * Rules are subject to change. + * + * This method takes a vector as an argument, compares each element and returns the penalty for + * the best match. + * + * @param refs A vector of reference numbers of the current segment, compared against `m_roadRef`. + * + * @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty` + * for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for + * a partial match (unclear whether both refs refer to the same object). + */ + double GetRoadRefPenalty(std::vector & refs) const; + + /** + * @brief Determines the penalty factor based on how two reference numbers match. + * + * Rules are subject to change. + * + * @param ref The reference number of the current segment, compared against `m_roadRef`. + * + * @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty` + * for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for + * a partial match (unclear whether both refs refer to the same object). + */ + double GetRoadRefPenalty(std::string const & ref) const; + protected: /** * @brief Initializes the router. @@ -470,7 +514,7 @@ bool IsRamp(routing::HighwayType highwayType); * For example, each of `A42`, `A 42` and `-a42` would be broken down into `a, 42`, whereas `A4.2` * would be broken down into `a, 4, 2`. */ -std::vector ParseRef(std::string & ref); +std::vector ParseRef(std::string const & ref); /** * @brief Calculates the segments to truncate at the start of the route.