[traffic] Calculate segment weight based on road ref

Signed-off-by: mvglasow <michael -at- vonglasow.com>
This commit is contained in:
mvglasow
2025-10-14 21:41:22 +03:00
parent c9f50cdc72
commit f07c8d66d8
2 changed files with 205 additions and 6 deletions

View File

@@ -6,6 +6,7 @@
#include "geometry/mercator.hpp"
#include "indexer/feature.hpp"
#include "indexer/road_shields_parser.hpp"
// Only needed for OpenlrTraffDecoder, see below
#if 0
@@ -27,8 +28,17 @@
#include "traffic/traffic_cache.hpp"
#include <boost/algorithm/string.hpp>
namespace traffxml
{
enum class RefParserState
{
Whitespace,
Alpha,
Numeric
};
// Only needed for OpenlrTraffDecoder, see below
#if 0
// Number of worker threads for the OpenLR decoder
@@ -54,13 +64,13 @@ auto constexpr kOneMpSInKmpH = 3.6;
/*
* Penalty factor for using a fake segment to get to a nearby road.
* Maximum penalty for roads is currently 16 (4 for ramps * 4 for road type), offroad penalty is
* twice the maximum road penalty. We might need to increase that, since offroad penalty applies to
* direct distance whereas road penalty applies to roads, which can be up to around 3 times the
* direct distance (theoretically unlimited). That would imply multiplying maximum road penalty by
* more than 3 (e.g. 4).
* Maximum penalty for roads is currently 64 (4 for ramps * 4 for road type * 4 for ref), offroad
* penalty is twice the maximum road penalty. We might need to increase that, since offroad penalty
* applies to direct distance whereas road penalty applies to roads, which can be up to around 3
* times the direct distance (theoretically unlimited). That would imply multiplying maximum road
* penalty by more than 3 (e.g. 4).
*/
auto constexpr kOffroadPenalty = 32;
auto constexpr kOffroadPenalty = 128;
/*
* Penalty factor for non-matching attributes
@@ -475,6 +485,67 @@ 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<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
{
// Adds 2 minutes penalty for U-turn. The value is quite arbitrary
@@ -517,6 +588,13 @@ double RoutingTraffDecoder::TraffEstimator::CalcOffroad(ms::LatLon const & from,
return result;
}
/*
* Currently, the attribute penalty (kAttributePenalty or kReducedAttributePenalty) can be applied
* up to 3 times:
* - ramp attribute mismatch
* - road class mismatch
* - road ref mismatch
*/
double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const
{
double result = road.GetDistance(segment.GetSegmentIdx());
@@ -545,6 +623,28 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c
result *= kAttributePenalty;
}
if (!m_decoder.m_roadRef.empty())
{
auto const countryFile = m_decoder.m_numMwmIds->GetFile(segment.GetMwmId());
auto const mwmId = m_decoder.m_dataSource.GetMwmIdByCountryFile(countryFile);
FeaturesLoaderGuard g(m_decoder.m_dataSource, mwmId);
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;
}
return result;
}
@@ -865,6 +965,11 @@ void RoutingTraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traff
m_message = message;
if (m_message.value().m_location.value().m_roadRef)
m_roadRef = ParseRef(m_message.value().m_location.value().m_roadRef.value());
else
m_roadRef.clear();
int dirs = (message.m_location.value().m_directionality == Directionality::BothDirections) ? 2 : 1;
for (int dir = 0; dir < dirs; dir++)
DecodeLocationDirection(message, decoded, dir == 0 ? false : true /* backwards */);
@@ -966,4 +1071,61 @@ bool IsRamp(routing::HighwayType highwayType)
return false;
}
}
std::vector<std::string> ParseRef(std::string & ref)
{
std::vector<std::string> res;
std::string curr = "";
RefParserState state = RefParserState::Whitespace;
for (size_t i = 0; i < ref.size(); i++)
{
// TODO this list of delimiters might not be exhaustive
if ((ref[i] <= 0x20) || (ref[i] == ',') || (ref[i] == '-') || (ref[i] == '.') || (ref[i] == '/'))
{
// whitespace
if (state != RefParserState::Whitespace)
{
if (state == RefParserState::Alpha)
boost::to_lower(curr);
res.push_back(curr);
curr = "";
}
state = RefParserState::Whitespace;
}
/*
* TODO adapt this to other number systems as well.
* Roman numerals (or any use of letters as numbers) are a stupid idea. If they are at least
* properly delimited, they are treated as a letter group, which sort of works for comparison.
* However, `IVbis` will be treated as one group, whereas `IV bis` will be treated as two.
*/
else if ((ref[i] >= '0') && (ref[i] <= '9'))
{
// numeric
if (state == RefParserState::Alpha)
{
boost::to_lower(curr);
res.push_back(curr);
curr = "";
}
curr += ref[i];
state = RefParserState::Numeric;
}
// anything that is not a delimiter or a digit (as per the above rules) is considered a letter
else
{
// alpha
if (state == RefParserState::Numeric)
{
res.push_back(curr);
curr = "";
}
curr += ref[i];
state = RefParserState::Alpha;
}
}
if (!curr.empty())
res.push_back(curr);
return res;
}
} // namespace traffxml

View File

@@ -295,6 +295,20 @@ 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;
double GetTurnPenalty(Purpose purpose, double angle, routing::RoadGeometry const & from_road,
routing::RoadGeometry const & to_road, bool is_left_hand_traffic = false) const override;
@@ -378,6 +392,11 @@ private:
std::shared_ptr<routing::NumMwmIds> m_numMwmIds = std::make_shared<routing::NumMwmIds>();
std::unique_ptr<routing::IRouter> m_router;
std::optional<traffxml::TraffMessage> m_message = std::nullopt;
/**
* @brief The road ref of `m_message`, parsed with `ParseRef()`
*/
std::vector<std::string> m_roadRef;
};
/**
@@ -389,4 +408,22 @@ using DefaultTraffDecoder = RoutingTraffDecoder;
traffxml::RoadClass GetRoadClass(routing::HighwayType highwayType);
double GetRoadClassPenalty(traffxml::RoadClass lhs, traffxml::RoadClass rhs);
bool IsRamp(routing::HighwayType highwayType);
/**
* @brief Breaks down a ref into groups for comparison.
*
* The result of this function can be used to determine if two reference numbers match partially
* (such as `A4`, `A4bis` and `A4.1`).
*
* Implementation details may change; currently the following applies:
*
* A whitespace character (or sequence of whitespace characters), or a switch between letters and
* digits, starts a new group.
*
* Letters are converted to lowercase.
*
* 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<std::string> ParseRef(std::string & ref);
} // namespace traffxml