[traffic] Refactor methods for penalty calculation

Signed-off-by: mvglasow <michael -at- vonglasow.com>
This commit is contained in:
mvglasow
2025-11-01 13:13:10 +02:00
parent 207d6c833d
commit c0fd405798
2 changed files with 165 additions and 108 deletions

View File

@@ -509,67 +509,6 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf
} }
#endif #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<std::string> 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<std::string> 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 double RoutingTraffDecoder::TraffEstimator::GetUTurnPenalty(Purpose /* purpose */) const
{ {
// Adds 2 minutes penalty for U-turn. The value is quite arbitrary // 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) if (!m_decoder.m_message || !m_decoder.m_message.value().m_location.value().m_roadClass)
return result; return result;
std::optional<routing::HighwayType> highwayType = road.GetHighwayType(); result *= GetHighwayTypePenalty(road.GetHighwayType(),
m_decoder.m_message.value().m_location.value().m_roadClass,
if (highwayType) m_decoder.m_message.value().m_location.value().m_ramps);
{
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 cant determine if it is a ramp, penalize for mismatch
result *= kAttributePenalty;
if (m_decoder.m_message.value().m_location.value().m_roadClass)
// we cant determine if the road matches the required road class, treat it as mismatch
result *= kAttributePenalty;
}
if (!m_decoder.m_roadRef.empty()) if (!m_decoder.m_roadRef.empty())
{ {
@@ -694,18 +616,7 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c
auto f = g.GetOriginalFeatureByIndex(segment.GetFeatureId()); auto f = g.GetOriginalFeatureByIndex(segment.GetFeatureId());
auto refs = ftypes::GetRoadShieldsNames(*f); auto refs = ftypes::GetRoadShieldsNames(*f);
auto penalty = kAttributePenalty; result *= m_decoder.GetRoadRefPenalty(refs);
for (auto & ref : refs)
{
auto newPenalty = GetRoadRefPenalty(ref);
if (newPenalty < penalty)
penalty = newPenalty;
if (penalty == 1)
break;
}
result *= penalty;
} }
return result; return result;
@@ -747,6 +658,108 @@ RoutingTraffDecoder::RoutingTraffDecoder(DataSource & dataSource, CountryInfoGet
InitRouter(); InitRouter();
} }
double RoutingTraffDecoder::GetHighwayTypePenalty(std::optional<routing::HighwayType> highwayType,
std::optional<RoadClass> 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 cant determine if it is a ramp, penalize for mismatch
result *= kAttributePenalty;
if (roadClass)
// we cant determine if the road matches the required road class, treat it as mismatch
result *= kAttributePenalty;
}
return result;
}
double RoutingTraffDecoder::GetRoadRefPenalty(std::vector<std::string> & 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<std::string> 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<std::string> 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) void RoutingTraffDecoder::OnMapRegistered(platform::LocalCountryFile const & localFile)
{ {
std::lock_guard<std::mutex> lock(m_mutex); std::lock_guard<std::mutex> lock(m_mutex);
@@ -1234,7 +1247,7 @@ bool IsRamp(routing::HighwayType highwayType)
} }
} }
std::vector<std::string> ParseRef(std::string & ref) std::vector<std::string> ParseRef(std::string const & ref)
{ {
std::vector<std::string> res; std::vector<std::string> res;
std::string curr = ""; std::string curr = "";

View File

@@ -303,19 +303,6 @@ public:
double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose /* purpose */) const override; 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; 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; double GetUTurnPenalty(Purpose /* purpose */) const override;
/** /**
@@ -354,6 +341,63 @@ public:
*/ */
virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) override {} 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<routing::HighwayType> highwayType,
std::optional<RoadClass> 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<std::string> & 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: protected:
/** /**
* @brief Initializes the router. * @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` * 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`. * would be broken down into `a, 4, 2`.
*/ */
std::vector<std::string> ParseRef(std::string & ref); std::vector<std::string> ParseRef(std::string const & ref);
/** /**
* @brief Calculates the segments to truncate at the start of the route. * @brief Calculates the segments to truncate at the start of the route.