Files
comaps/libs/traffxml/traff_model.hpp
mvglasow d47aa09053 [traffxml] Process fuzziness attribute in location
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-27 22:50:05 +02:00

581 lines
18 KiB
C++

#pragma once
//#include "traffxml/traff_foo.hpp"
#include "geometry/latlon.hpp"
#include "indexer/mwm_set.hpp"
#include "traffic/speed_groups.hpp"
#include "traffic/traffic_info.hpp"
#include <chrono>
#include <map>
#include <string>
#include <vector>
namespace traffxml
{
constexpr uint8_t kMaxspeedNone = 255;
/**
* @brief Date and time decoded from ISO 8601.
*
* `IsoTime` is an opaque type. It is only guaranteed to be capable of holding a timestamp
* converted from ISO 8601 which refers to the same UTC time as its ISO 8601 representation.
* Time zone information is not guaranteed to be preserved: `13:37+01:00` may be returned e.g. as
* `12:37Z` or `06:37-06:00`.
*/
class IsoTime
{
public:
/**
* @brief Parses time in ISO 8601 format from a string and stores it in an `IsoTime`.
*
* ISO 8601 timestamps have the format `yyyy-mm-ddThh:mm:ss[.sss]`, optionally followed by a UTC
* offset. For example, `2019-11-01T11:45:42+01:00` refers to 11:45:42 in the UTC+1 timezone, which
* is 10:45:42 UTC.
*
* A UTC offset of `Z` denotes UTC and is equivalent to `+00:00` or `-00:00`. UTC is also assumed
* if no UTC offset is specified.
*
* The UTC offset can be specified as `hh:mm`, `hhmm` or `hh`.
*
* Seconds can be specified as integer or float, but will be rounded to the nearest integer. For
* example, 42.645 seconds will be rounded to 43 seconds.
*
* @param timeString Time in ISO8601 format
* @return An `IsoTime` instance corresponding to `timeString`, or `std::nullopt` if `timeString` is not a valid ISO8601 time string.
*/
static std::optional<IsoTime> ParseIsoTime(std::string timeString);
/**
* @brief Returns an `IsoTime` corresponding to current wall clock time.
*
* @return An `IsoTime` corresponding to current wall clock time.
*/
static IsoTime Now();
/**
* @brief Whether the instance refers to a point of time in the past.
*
* Comparison is against system time.
*
* @return true if in the past, false of not.
*/
bool IsPast();
/**
* @brief Shifts time to the present.
*
* This method is intended for testing. It shifts the timestamp by a fixed amount, so that
* `nowRef` corresponds to current time. After this method returns, the timestamp will have the
* same offset from current time that it had from `nowRef` at the time the call was made.
*
* @param nowRef
*/
void Shift(IsoTime nowRef);
/**
* @brief Returns a string representation of the instance.
* @return The timestamp in ISO 8601 format.
*/
std::string ToString() const;
bool operator< (IsoTime & rhs);
bool operator> (IsoTime & rhs);
private:
friend std::string DebugPrint(IsoTime time);
IsoTime(std::chrono::time_point<std::chrono::system_clock> tp);
std::chrono::time_point<std::chrono::system_clock> m_tp;
};
// TODO enum urgency
enum class Directionality
{
OneDirection,
BothDirections
};
enum class Fuzziness
{
LowRes,
MediumRes,
EndUnknown,
StartUnknown,
ExtentUnknown
};
enum class Ramps
{
None,
All,
Entry,
Exit
};
enum class RoadClass
{
Motorway,
Trunk,
Primary,
Secondary,
Tertiary,
Other
};
enum class QuantifierType
{
Dimension,
Duration,
Int,
Ints,
Speed,
Temperature,
Time,
Weight,
Invalid
};
/*
* When adding a new event class to this enum, be sure to do the following:
*
* * in `traff_model_xml.cpp`, add the corresponding mapping to `kEventClassMap`
* * in `traff_model.cpp`, extend `DebugPrint(EventClass)` to correctly process the new event classes
* * in this file, add event types for this class to `EventType`
*/
enum class EventClass
{
Invalid,
Activity,
Authority,
Carpool,
Congestion,
Construction,
Delay,
Environment,
EquipmentStatus,
Hazard,
Incident,
Restriction,
Security,
Transport,
Weather
};
/*
* When adding a new event type to this enum, be sure to do the following:
*
* * in `traff_model_xml.cpp`, add the corresponding mapping to `kEventTypeMap`
* * in `traff_model.cpp`:
* * add speed group mappings in `kEventSpeedGroupMap`, if any
* * add maxspeed mappings in `kEventMaxspeedMap`, if any (uncomment if needed)
* * add delay mappings in `kEventDelayMap`, if any
* * extend `DebugPrint(TraffEvent)` to correctly process the new events
*/
enum class EventType
{
Invalid,
// TODO Activity*, Authority*, Carpool*
CongestionCleared,
CongestionForecastWithdrawn,
CongestionHeavyTraffic,
CongestionLongQueue,
CongestionNone,
CongestionNormalTraffic,
CongestionQueue,
CongestionQueueLikely,
CongestionSlowTraffic,
CongestionStationaryTraffic,
CongestionStationaryTrafficLikely,
CongestionTrafficBuildingUp,
CongestionTrafficCongestion,
CongestionTrafficEasing,
CongestionTrafficFlowingFreely,
CongestionTrafficHeavierThanNormal,
CongestionTrafficLighterThanNormal,
CongestionTrafficMuchHeavierThanNormal,
CongestionTrafficProblem,
// TODO Construction*
DelayClearance,
DelayDelay,
DelayDelayPossible,
DelayForecastWithdrawn,
DelayLongDelay,
DelaySeveralHours,
DelayUncertainDuration,
DelayVeryLongDelay,
// TODO Environment*, EquipmentStatus*, Hazard*, Incident*
// TODO complete Restriction*
RestrictionBlocked,
RestrictionBlockedAhead,
RestrictionCarriagewayBlocked,
RestrictionCarriagewayClosed,
RestrictionClosed,
RestrictionClosedAhead,
RestrictionEntryBlocked,
RestrictionEntryReopened,
RestrictionExitBlocked,
RestrictionExitReopened,
RestrictionOpen,
RestrictionRampBlocked,
RestrictionRampClosed,
RestrictionRampReopened,
RestrictionReopened,
RestrictionSpeedLimit,
RestrictionSpeedLimitLifted,
// TODO Security*, Transport*, Weather*
};
enum class ResponseStatus
{
/**
* The operation was successful.
*/
Ok,
/**
* The source rejected the operation as invalid
*
* This may happen when a nonexistent operation is attempted, or an operation is attempted with
* incomplete or otherwise invalid data.
*
* @note This corresponds to TraFF status `INVALID` but was renamed here.
* `ResponseStatus::Invalid` refers to a different kind of error.
*/
InvalidOperation,
/**
* The source rejected the subscription, e.g. because the filtered region is too large.
*/
SubscriptionRejected,
/**
* The source does not supply data for the requested area; the request has failed.
*/
NotCovered,
/**
* The source supplies data only for a subset of the requested area; the request was successful
* (i.e. the subscription was created or changed as requested) but the consumer should be prepared
* to receive incomplete data.
*/
PartiallyCovered,
/**
* An operation (change, push, pull) was attempted on a subscription which the recipient did not
* recognize. On transport channels which support stable identifiers for both communication
* parties, this is also used if a consumer attempts an operation on a subscription created by
* another consumer.
*/
SubscriptionUnknown,
/**
* The aggregator does not accept unsolicited push requests from the sensor. Reserved for future
* versions and not used as of TraFF 0.8.
*/
PushRejected,
/**
* An internal error prevented the recipient of the request from fulfilling it.
*
* This is either translated directly from `INTERNAL_ERROR` returned from the source, or may be
* inferred from errors on the transport channel (e.g. HTTP errors).
*/
InternalError,
/**
* An unrecognized status code.
*
* This is used for all situations where we got a response from the source, with no indication of
* an error, but could not obtain a known status code from it (e.g. XML failed to parse, did not
* contain a status code, or contained an unknown status code).
*
* @note Not to be confused with TraFF status `INVALID`, which maps to
* `ResponseStatus::InvalidOperation`.
*/
Invalid
};
/**
* @brief Represents the impact of one or more traffic events.
*
* Impact can be expressed in three ways:
*
* Traffic may flow at a certain percentage of the posted limit, often divided in bins. This is
* used by some traffic services which report e.g. “slow traffic”, “stationary traffic” or
* “queues”, and maps to speed groups in a straightforward way.
*
* Traffic may flow at, or be restricted to, a given speed. This is common with traffic flow
* measurement data, or with temporary speed limits. Converting this to a speed group requires
* knowledge of the regular speed limit.
*
* There may be a fixed delay, expressed as a duration in time. This may happen at checkpoints,
* at sections where traffic flow is limited or where there is single alternate-lane traffic.
* As the routing data model does not provide for explicit delays, they have to be converted into
* speed groups. Again, this requires knowledge of the regular travel time along the route, as well
* as its length.
*
* Closures can be expressed by setting `m_speedGroup` to `traffic::SpeedGroup::TempBlock`. If that
* is the case, the other struct members are to be ignored.
*/
struct TrafficImpact
{
/**
* @brief Whether two `TrafficImpact` instances are equal.
*
* Instances are considered equal if both have a speed group of `TempBlock`, in which case other
* members are not compared. Otherwise, they are equal if, and only if, all three members hold
* identical values between both instances.
*/
// Non-member friend as member operators do not work with std::optional
friend bool operator==(TrafficImpact const & lhs, TrafficImpact const & rhs);
friend bool operator!=(TrafficImpact const & lhs, TrafficImpact const & rhs) { return !(lhs == rhs); }
/**
* @brief The speed group for the affected segments, or `traffic::SpeedGroup::Unknown` if unknown.
*/
traffic::SpeedGroup m_speedGroup = traffic::SpeedGroup::Unknown;
/**
* @brief The speed limit, or speed of flowing traffic; `kMaxspeedNone` if none or unknown.
*/
uint8_t m_maxspeed = kMaxspeedNone;
/**
* @brief The delay in minutes; 0 if none or unknown.
*/
uint16_t m_delayMins = 0;
};
struct Point
{
/**
* @brief Whether two points are equal.
*
* Two points are equal if, and only if, their coordinates are. Other attributes are not compared.
*/
// Non-member friend as member operators do not work with std::optional
friend bool operator==(Point const & lhs, Point const & rhs);
friend bool operator!=(Point const & lhs, Point const & rhs) { return !(lhs == rhs); }
// TODO role?
ms::LatLon m_coordinates = ms::LatLon::Zero();
std::optional<float> m_distance;
std::optional<std::string> m_junctionName;
std::optional<std::string> m_junctionRef;
};
struct TraffLocation
{
/**
* @brief Whether two locations are equal.
*
* Two locations are equal if, and only if, they contain the same points in the same roles.
*
* @todo Road class and ramps are not compared, though these values are used by the decoder. Not
* comparing these values could lead to two seemingly equal locations resolving to a different
* path. However, given that comparison only takes place between messages with identical IDs
* (indicating both refer to the same event at the same location), such a situation is highly
* unlikely to occur in practice.
*/
// Non-member friend as member operators do not work with std::optional
friend bool operator==(TraffLocation const & lhs, TraffLocation const & rhs);
friend bool operator!=(TraffLocation const & lhs, TraffLocation const & rhs) { return !(lhs == rhs); }
std::optional<std::string> m_country;
std::optional<std::string> m_destination;
std::optional<std::string> m_direction;
Directionality m_directionality = Directionality::BothDirections;
std::optional<Fuzziness> m_fuzziness;
std::optional<std::string> m_origin;
Ramps m_ramps = Ramps::None;
std::optional<RoadClass> m_roadClass;
// disabled for now, optional<bool> behaves weird and we don't really need it
//std::optional<bool> m_roadIsUrban;
std::optional<std::string> m_roadRef;
std::optional<std::string> m_roadName;
std::optional<std::string> m_territory;
std::optional<std::string> m_town;
std::optional<Point> m_from;
std::optional<Point> m_to;
std::optional<Point> m_at;
std::optional<Point> m_via;
std::optional<Point> m_notVia;
};
struct TraffEvent
{
EventClass m_class = EventClass::Invalid;
EventType m_type = EventType::Invalid;
std::optional<uint16_t> m_length;
std::optional<uint8_t> m_probability;
std::optional<uint16_t> m_qDurationMins;
/*
* TODO remaining quantifiers
* q_dimension
* q_int
* q_ints
* q_speed
* q_temperature
* q_time
* q_weight
*/
std::optional<uint8_t> m_speed;
// TODO supplementary information
};
/**
* @brief Global mapping from feature segments to speed groups, across all MWMs.
*/
using MultiMwmColoring = std::map<MwmSet::MwmId, std::map<traffic::TrafficInfo::RoadSegmentId, traffic::SpeedGroup>>;
struct TraffMessage
{
/**
* @brief Gets the time after which this message effectively expires.
*
* The effective expiration time is the latest of `m_expirationTime`, `m_startTime` and
* `m_endTime`. `nullopt` values are ignored.
*
* @return The effective expiration time for the message.
*/
IsoTime GetEffectiveExpirationTime();
/**
* @brief Whether the message has expired.
*
* A message is considered to have expired if its effective expiration time (as returned by
* `GetEffectiveExpirationTime()` refers to a point in time before `now`.
*
* @param now The reference time to compare to (usually current time)
* @return True if the message has expired, false if not.
*/
bool IsExpired(IsoTime now);
/**
* @brief Retrieves the traffic impact of all events.
*
* If the message has multiple events, the traffic impact is determined separately for each
* event and then aggregated. Aggregation takes the most restrictive value in each category
* (speed group, maxspeed, delay).
*
* If the aggregated traffic impact includes `SpeedGroup::TempBlock`, its other members are to
* be considered invalid.
*
* @return The aggregated traffic impact, or `std::nullopt` if the message has no events with traffic impact.
*/
std::optional<TrafficImpact> GetTrafficImpact();
/**
* @brief Shifts timestamps to the present.
*
* This method is intended for testing. It shifts the timestamps of the message by a fixed amount,
* so that `m_updateTime` corresponds to current time, and all other timestamps maintain their
* offset to `m_updateTime`. If `m_startTime` and/or `m_endTime` are set, they may be adjusted
* further to maintain their offset from midnight or the full hour (currently not implemented).
*/
void ShiftTimestamps();
std::string m_id;
IsoTime m_receiveTime = IsoTime::Now();
IsoTime m_updateTime = IsoTime::Now();
IsoTime m_expirationTime = IsoTime::Now();
std::optional<IsoTime> m_startTime = {};
std::optional<IsoTime> m_endTime = {};
bool m_cancellation = false;
bool m_forecast = false;
// TODO std::optional<Urgency> m_urgency;
std::optional<TraffLocation> m_location;
std::vector<TraffEvent> m_events;
std::vector<std::string> m_replaces;
MultiMwmColoring m_decoded;
};
using TraffFeed = std::vector<TraffMessage>;
// TODO Capabilities
/*
* Filter: currently not implemented.
* We only use bbox, for which we have a suitable data type.
* min_road_class is not needed as we do not filter by road class.
*/
/*
* TraffSubscription: currently not implemented.
* We just store the ID as a string.
* Filters are only by bbox, not by min_road_class. The list is auto-generated from the list of
* active MWMs and changes exactly when the active MWM set changes, eliminating the need to store
* the full filter list.
*/
/**
* @brief Encapsulates the response to a TraFF request.
*/
struct TraffResponse
{
/**
* @brief The response status for the request which triggered the response.
*/
ResponseStatus m_status = ResponseStatus::Invalid;
/**
* @brief The subscription ID which the source has assigned to the subscriber.
*
* This attribute is how the source communicates the subscription ID to a subscriber. Required for
* responses to a subscription request; some transport channels may require it for every
* subscription-related operation; forbidden otherwise.
*/
std::string m_subscriptionId;
/**
* @brief The time in seconds after which the source will consider the subscription invalid if no
* activity occurs.
*
* Required for responses to a subscription request on some transport channels, optional on other
* channels, forbidden for other requests.
*
* If not used, the value is zero.
*/
uint32_t m_timeout = 0;
/**
* @brief A feed of traffic messages sent as part of the response.
*/
std::optional<TraffFeed> m_feed;
};
/**
* @brief Merges the contents of one `MultiMwmColoring` into another.
*
* After this function returns, `target` will hold the union of the entries it had prior to the
* function call and the entries from `delta`.
*
* In case of conflict, the more restrictive speed group wins. That is, `TempBlock` overrides
* everything else, `Unknown` never overrides anything else, and among `G0` to `G5`, the lowest
* group wins.
*
* @param delta Contains the entries to be added.
* @param target Receives the added entries.
*/
void MergeMultiMwmColoring(const MultiMwmColoring & delta, MultiMwmColoring & target);
std::string DebugPrint(IsoTime time);
std::string DebugPrint(Directionality directionality);
std::string DebugPrint(Ramps ramps);
std::string DebugPrint(RoadClass roadClass);
std::string DebugPrint(EventClass eventClass);
std::string DebugPrint(EventType eventType);
std::string DebugPrint(ResponseStatus status);
std::string DebugPrint(TrafficImpact impact);
std::string DebugPrint(Point point);
std::string DebugPrint(TraffLocation location);
std::string DebugPrint(TraffEvent event);
std::string DebugPrint(TraffMessage message);
std::string DebugPrint(TraffFeed feed);
} // namespace traffxml