Files
comaps/libs/traffxml/traff_decoder.hpp
mvglasow c1340a9941 [traffic] Truncate locations which overshoot their endpoints
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-19 22:39:06 +03:00

496 lines
20 KiB
C++

#pragma once
#include "traffxml/traff_model.hpp"
#include "indexer/data_source.hpp"
#include "indexer/mwm_set.hpp"
// Only needed for OpenlrTraffDecoder, see below
#if 0
#include "openlr/openlr_decoder.hpp"
#include "openlr/openlr_model.hpp"
#endif
#include "routing/index_router.hpp"
#include "routing/regions_decl.hpp"
#include "routing/router.hpp"
#include "routing/vehicle_mask.hpp"
#include "routing_common/num_mwm_id.hpp"
#include "storage/country_info_getter.hpp"
#include <optional>
namespace traffxml
{
/**
* @brief Abstract base class for all TraFF decoder implementations.
*
* At this point, `TraffDecoder` is single-threaded and not guaranteed to be thread-safe. This means
* that all `TraffDecoder` operations should be limited to one thread or use appropriate thread
* synchronization mechanisms. In particular, calling `DecodeMessage()` concurrently from multiple
* threads is not supported.
*/
class TraffDecoder
{
public:
using CountryInfoGetterFn = std::function<storage::CountryInfoGetter const &()>;
using CountryParentNameGetterFn = std::function<std::string(std::string const &)>;
TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
const CountryParentNameGetterFn & countryParentNameGetter,
std::map<std::string, traffxml::TraffMessage> & messageCache);
virtual ~TraffDecoder() {}
/**
* @brief Decodes a single message to its segments and their speed groups.
*
* This method is not guaranteed to be thread-safe. All calls to this method should either be
* strictly limited to one designated thread, or be synchronized using an appropriate mechanism.
*
* In addition to the above, this method may access the message cache which was passed to the
* constructor. This is not thread-safe and needs to be synchronized, unless all other operations
* on the message cache are guaranteed to happen on the same thread that called this method.
*
* @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;
CountryInfoGetterFn m_countryInfoGetterFn;
CountryParentNameGetterFn m_countryParentNameGetterFn;
/**
* @brief Cache of all currently active TraFF messages.
*
* Keys are message IDs, values are messages.
*/
std::map<std::string, traffxml::TraffMessage> & m_messageCache;
private:
};
// Disabled for now, as the OpenLR-based decoder is slow, buggy and not well suited to the task.
#if 0
/**
* @brief A `TraffDecoder` implementation which internally uses the version 3 OpenLR decoder.
*/
class OpenLrV3TraffDecoder : public TraffDecoder
{
public:
OpenLrV3TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
const CountryParentNameGetterFn & countryParentNameGetter,
std::map<std::string, traffxml::TraffMessage> & 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) override;
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> & 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<openlr::LinearSegment> 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;
};
#endif
/**
* @brief A `TraffDecoder` implementation which internally uses the routing engine.
*/
class RoutingTraffDecoder : public TraffDecoder,
public MwmSet::Observer
{
public:
class DecoderRouter : public routing::IndexRouter
{
public:
/**
* @brief Creates a new `DecoderRouter` instance.
*
* @param countryParentNameGetterFn Function which converts a country name into the name of its parent country)
* @param countryFileFn Function which converts a pointer to its country name
* @param countryRectFn Function which returns the rect for a country
* @param numMwmIds
* @param numMwmTree
* @param trafficCache The traffic cache (used only if `vehicleType` is `VehicleType::Car`)
* @param dataSource The MWM data source
* @param decoder The `TraffDecoder` instance to which this router instance is coupled
*/
DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn,
routing::TCountryFileFn const & countryFileFn,
routing::CountryRectFn const & countryRectFn,
std::shared_ptr<routing::NumMwmIds> numMwmIds,
std::unique_ptr<m4::Tree<routing::NumMwmId>> numMwmTree,
DataSource & dataSource, RoutingTraffDecoder & decoder);
protected:
/**
* @brief Whether the set of fake endings generated for the check points is restricted.
*
* The return value is used internally when snapping checkpoints to edges. If this function
* returns true, this instructs the `PointsOnEdgesSnapping` instance to consider only edges which
* are not fenced off, i.e. can be reached from the respective checkpoint without crossing any
* other edges. If it returns false, this restriction does not apply, and all nearby edges are
* considered.
*
* Restricting the set of fake endings in this manner decreases the options considered for routing
* and thus processing time, which is desirable for regular routing and has no side effects.
* For TraFF location matching, simplification has undesirable side effects: if reference points
* are located on one side of the road, the other carriageway may not be considered. This would
* lead to situations like these:
*
* --<--<-+<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<+-<--<--
* -->-->-+>==>==>==>==>==>==>-->-->-->-->-->-->-->-->-->-->-->==>==>==>==>==>==>==>==>+->-->--
* *< <*
*
* (-- carriageway, + junction, < > direction, *< end point, <* start point, == route)
*
* To avoid this, the `DecoderRouter` implementation always returns false.
*/
/**
* @brief Returns the mode in which the router is operating.
*
* The `DecoderRouter` always returns `Mode::Decoding`.
*
* In navigation mode, the router may exit with `RouterResultCode::NeedMoreMaps` if it determines
* that a better route can be calculated with additional maps. When snapping endpoints to edges,
* it will consider only edges which are not “fenced off” by other edges, i.e. which can be
* reached from the endpoint without crossing other edges. This decreases the number of fake
* endings and thus speeds up routing, without any undesirable side effects for that use case.
*
* Asking the user to download extra maps is neither practical for a TraFF decoder which runs in
* the background and may decode many locations, one by one, nor is it needed (if maps are
* missing, we do not need to decode traffic reports for them).
*
* Eliminating fenced-off edges from the snapping candidates has an undesirable side effect for
* TraFF location decoding on dual-carriageway roads: if the reference points are outside the
* carriageways, only one direction gets considered for snapping, as the opposite direction is
* fenced off by it. This may lead to situations like these:
*
* ~~~
* --<--<-+<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<+-<--<--
* -->-->-+>==>==>==>==>==>==>-->-->-->-->-->-->-->-->-->-->-->==>==>==>==>==>==>==>==>+->-->--
* |< <|
*
* (-- carriageway, + junction, < > direction, |< end point, <| start point, == route)
* ~~~
*
* Therefore, in decoding mode, the router will never exit with `RouterResultCode::NeedMoreMaps`
* but tries to find a route with the existing maps, or exits without a route. When snapping
* endpoints to edges, it considers all edges within the given radius, fenced off or not.
*/
IndexRouter::Mode GetMode() { return IndexRouter::Mode::Decoding; }
/**
* @brief Returns current routing options.
*
* For traffic decoding purposes, all roads are allowed.
*/
routing::RoutingOptions GetRoutingOptions() override;
private:
};
class TraffEstimator final : public routing::EdgeEstimator
{
public:
TraffEstimator(DataSource * dataSourcePtr, std::shared_ptr<routing::NumMwmIds> numMwmIds,
double maxWeightSpeedKMpH,
routing::SpeedKMpH const & offroadSpeedKMpH,
RoutingTraffDecoder & decoder)
: EdgeEstimator(routing::VehicleType::Car, maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds)
, m_decoder(decoder)
{
}
// EdgeEstimator overrides:
/**
* @brief Estimates travel time between two points along a direct fake edge.
*
* Estimates time in seconds it takes to go from point `from` to point `to` along direct fake edge.
*
* @param from The start point.
* @param to The destination point.
* @param purpose The purpose for which the result is to be used.
* @return Travel time in seconds.
*/
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;
double GetFerryLandingPenalty(Purpose /* purpose */) const override;
private:
RoutingTraffDecoder & m_decoder;
};
RoutingTraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
const CountryParentNameGetterFn & countryParentNameGetter,
std::map<std::string, traffxml::TraffMessage> & messageCache);
/**
* @brief Called when a map is registered for the first time and can be used.
*/
void OnMapRegistered(platform::LocalCountryFile const & localFile) override;
/**
* @brief Called when a map is deregistered and can no longer be used.
*
* This implementation does nothing, as `NumMwmIds` does not support removal.
*/
virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) override {}
protected:
/**
* @brief Initializes the router.
*
* This is usually done in the constructor but fails if no maps are loaded (attempting to
* construct a router without maps results in a crash, hence we check for maps and exit with an
* error if we have none). It can be repeated any time.
*
* Attempting to initialize a router which has already been succesfully initialized is a no-op. It
* will be reported as success.
*
* @return true if successful, false if not.
*/
bool InitRouter();
/**
* @brief Adds a segment to the decoded segments.
*
* @param decoded The decoded segments.
* @param segment The segment to add.
*/
void AddDecodedSegment(traffxml::MultiMwmColoring & decoded, routing::Segment & segment);
/**
* @brief Decodes one direction of a TraFF location.
*
* @param message The message to decode.
* @param decoded Receives the decoded segments. The speed group will be `Unknown`.
* @param backwards If true, decode the backward direction, else the forward direction.
*/
void DecodeLocationDirection(traffxml::TraffMessage & message,
traffxml::MultiMwmColoring & decoded, bool backwards);
/**
* @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) override;
/**
* @brief Truncates the route so its endpoints best match the reference points.
*
* Leading and trailing fake segments are discarded.
*
* When building the graph, the router creates fake segments to the nearest roads. These are not
* necessarily the best for location decoding, which may result in “heads” or “tails” being added
* to the decoded location. This function attempts to detect and remove them.
*
* To do this, it iterates over the nodes (taken from `rsegments`) and determines if any of them
* is a better start/end candidate. This is done by calculating the cost of leaping between the
* node and the corresponding checkpoint; if this is cheaper than the stretch of route bypassed
* in this way, the node becomes a candidate for the corresponding endpoint. The higher the cost
* saving, the better the candidate.
*
* After identifying the best candidate for each endpoint, segments outside these nodes are
* discarded.
*
* @param rsegments The segments of the route
* @param checkpoints The reference points (at least two)
*/
void TruncateRoute(std::vector<routing::RouteSegment> & rsegments,
routing::Checkpoints const & checkpoints);
private:
static void LogCode(routing::RouterResultCode code, double const elapsedSec);
/**
* @brief Mutex for access to shared members.
*
* This is to prevent adding newly-registered maps while the router is in use.
*
* @todo As per the `MwmSet::Observer` documentation, implementations should be quick and lean,
* as they may be called from any thread. Locking a mutex may be in conflict with this, as it may
* mean locking up the caller while a location is being decoded.
*/
std::mutex m_mutex;
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;
};
/**
* @brief The default TraFF decoder implementation, recommended for production use.
*/
//using DefaultTraffDecoder = OpenLrV3TraffDecoder;
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);
/**
* @brief Calculates the segments to truncate at the start of the route.
*
* The route is not actually truncated by this function.
*
* `start` and `startSaving` should be 0 when calling this function. After it returns, these values
* will indicate the first segment to keep and the cost saved by truncating everything before.
*
* @param rsegments The segments of the route
* @param checkpoints The reference points (at least two)
* @param start Index of the first segment to keep
* @param startSaving Cost saved by truncating
*/
void TruncateStart(std::vector<routing::RouteSegment> & rsegments,
routing::Checkpoints const & checkpoints,
size_t & start, double & startSaving);
/**
* @brief Calculates the segments to truncate at the start of the route.
*
* The route is not actually truncated by this function.
*
* `end` should be `rsegments.size() - 1` and `endSaving` should be 0 when calling this function.
* After it returns, these values will indicate the last segment to keep and the cost saved by
* truncating everything after.
*
* @param rsegments The segments of the route
* @param checkpoints The reference points (at least two)
* @param end Index of the last segment to keep
* @param endSaving Cost saved by truncating
*/
void TruncateEnd(std::vector<routing::RouteSegment> & rsegments,
routing::Checkpoints const & checkpoints,
size_t & end, double & endSaving, double const endWeight);
} // namespace traffxml