[traffxml] Add module

Signed-off-by: mvglasow <michael -at- vonglasow.com>
This commit is contained in:
mvglasow
2025-04-21 13:14:06 +03:00
parent 6e8d400611
commit bb410fc3bc
10 changed files with 1175 additions and 0 deletions

View File

@@ -383,6 +383,7 @@ add_subdirectory(shaders)
add_subdirectory(storage) add_subdirectory(storage)
add_subdirectory(tracking) add_subdirectory(tracking)
add_subdirectory(traffic) add_subdirectory(traffic)
add_subdirectory(traffxml)
add_subdirectory(transit) add_subdirectory(transit)
if (PLATFORM_DESKTOP) if (PLATFORM_DESKTOP)

View File

@@ -28,4 +28,5 @@ target_link_libraries(${PROJECT_NAME}
qt_common qt_common
map map
gflags::gflags gflags::gflags
traffxml
) )

View File

@@ -71,6 +71,7 @@ target_link_libraries(${PROJECT_NAME}
map map
gflags::gflags gflags::gflags
openlr openlr
traffxml
) )
if (BUILD_DESIGNER) if (BUILD_DESIGNER)

16
traffxml/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
project(traffxml)
set(SRC
traff_decoder.cpp
traff_decoder.hpp
traff_model.cpp
traff_model.hpp
traff_model_xml.cpp
traff_model_xml.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
pugixml
)

View File

@@ -0,0 +1,7 @@
#include "traffxml/traff_decoder.hpp"
//#include "traffxml/traff_foo.hpp"
namespace traffxml
{
} // namespace traffxml

View File

@@ -0,0 +1,7 @@
#pragma once
//#include "traffxml/traff_foo.hpp"
namespace traffxml
{
} // namespace traffxml

248
traffxml/traff_model.cpp Normal file
View File

@@ -0,0 +1,248 @@
#include "traffxml/traff_model.hpp"
using namespace std;
namespace traffxml
{
/*
string DebugPrint(LinearSegmentSource source)
{
switch (source)
{
case LinearSegmentSource::NotValid: return "NotValid";
case LinearSegmentSource::FromLocationReferenceTag: return "FromLocationReferenceTag";
case LinearSegmentSource::FromCoordinatesTag: return "FromCoordinatesTag";
}
UNREACHABLE();
}
*/
std::string DebugPrint(IsoTime time)
{
std::ostringstream os;
os << std::put_time(&time, "%Y-%m-%d %H:%M:%S %z");
return os.str();
}
std::string DebugPrint(Directionality directionality)
{
switch (directionality)
{
case Directionality::OneDirection: return "OneDirection";
case Directionality::BothDirections: return "BothDirections";
}
UNREACHABLE();
}
std::string DebugPrint(Ramps ramps)
{
switch (ramps)
{
case Ramps::All: return "All";
case Ramps::Entry: return "Entry";
case Ramps::Exit: return "Exit";
case Ramps::None: return "None";
}
UNREACHABLE();
}
std::string DebugPrint(RoadClass roadClass)
{
switch (roadClass)
{
case RoadClass::Motorway: return "Motorway";
case RoadClass::Trunk: return "Trunk";
case RoadClass::Primary: return "Primary";
case RoadClass::Secondary: return "Secondary";
case RoadClass::Tertiary: return "Tertiary";
case RoadClass::Other: return "Other";
}
UNREACHABLE();
}
std::string DebugPrint(EventClass eventClass)
{
switch (eventClass)
{
case EventClass::Invalid: return "Invalid";
case EventClass::Activity: return "Activity";
case EventClass::Authority: return "Authority";
case EventClass::Carpool: return "Carpool";
case EventClass::Congestion: return "Congestion";
case EventClass::Construction: return "Construction";
case EventClass::Delay: return "Delay";
case EventClass::Environment: return "Environment";
case EventClass::EquipmentStatus: return "EquipmentStatus";
case EventClass::Hazard: return "Hazard";
case EventClass::Incident: return "Incident";
case EventClass::Restriction: return "Restriction";
case EventClass::Security: return "Security";
case EventClass::Transport: return "Transport";
case EventClass::Weather: return "Weather";
}
UNREACHABLE();
}
std::string DebugPrint(EventType eventType)
{
switch (eventType)
{
case EventType::Invalid: return "Invalid";
// TODO Activity*, Authority*, Carpool* (not in enum yet)
case EventType::CongestionCleared: return "CongestionCleared";
case EventType::CongestionForecastWithdrawn: return "CongestionForecastWithdrawn";
case EventType::CongestionHeavyTraffic: return "CongestionHeavyTraffic";
case EventType::CongestionLongQueue: return "CongestionLongQueue";
case EventType::CongestionNone: return "CongestionNone";
case EventType::CongestionNormalTraffic: return "CongestionNormalTraffic";
case EventType::CongestionQueue: return "CongestionQueue";
case EventType::CongestionQueueLikely: return "CongestionQueueLikely";
case EventType::CongestionSlowTraffic: return "CongestionSlowTraffic";
case EventType::CongestionStationaryTraffic: return "CongestionStationaryTraffic";
case EventType::CongestionStationaryTrafficLikely: return "CongestionStationaryTrafficLikely";
case EventType::CongestionTrafficBuildingUp: return "CongestionTrafficBuildingUp";
case EventType::CongestionTrafficCongestion: return "CongestionTrafficCongestion";
case EventType::CongestionTrafficEasing: return "CongestionTrafficEasing";
case EventType::CongestionTrafficFlowingFreely: return "CongestionTrafficFlowingFreely";
case EventType::CongestionTrafficHeavierThanNormal: return "CongestionTrafficHeavierThanNormal";
case EventType::CongestionTrafficLighterThanNormal: return "CongestionTrafficLighterThanNormal";
case EventType::CongestionTrafficMuchHeavierThanNormal: return "CongestionTrafficMuchHeavierThanNormal";
case EventType::CongestionTrafficProblem: return "CongestionTrafficProblem";
// TODO Construction* (not in enum yet)
case EventType::DelayClearance: return "DelayClearance";
case EventType::DelayDelay: return "DelayDelay";
case EventType::DelayDelayPossible: return "DelayDelayPossible";
case EventType::DelayForecastWithdrawn: return "DelayForecastWithdrawn";
case EventType::DelayLongDelay: return "DelayLongDelay";
case EventType::DelaySeveralHours: return "DelaySeveralHours";
case EventType::DelayUncertainDuration: return "DelayUncertainDuration";
case EventType::DelayVeryLongDelay: return "DelayVeryLongDelay";
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
// TODO complete Restriction* (not in enum yet)
case EventType::RestrictionBlocked: return "RestrictionBlocked";
case EventType::RestrictionBlockedAhead: return "RestrictionBlockedAhead";
case EventType::RestrictionCarriagewayBlocked: return "RestrictionCarriagewayBlocked";
case EventType::RestrictionCarriagewayClosed: return "RestrictionCarriagewayClosed";
case EventType::RestrictionClosed: return "RestrictionClosed";
case EventType::RestrictionClosedAhead: return "RestrictionClosedAhead";
case EventType::RestrictionEntryBlocked: return "RestrictionEntryBlocked";
case EventType::RestrictionEntryReopened: return "RestrictionEntryReopened";
case EventType::RestrictionExitBlocked: return "RestrictionExitBlocked";
case EventType::RestrictionExitReopened: return "RestrictionExitReopened";
case EventType::RestrictionOpen: return "RestrictionOpen";
case EventType::RestrictionRampBlocked: return "RestrictionRampBlocked";
case EventType::RestrictionRampClosed: return "RestrictionRampClosed";
case EventType::RestrictionRampReopened: return "RestrictionRampReopened";
case EventType::RestrictionReopened: return "RestrictionReopened";
case EventType::RestrictionSpeedLimit: return "RestrictionSpeedLimit";
case EventType::RestrictionSpeedLimitLifted: return "RestrictionSpeedLimitLifted";
// TODO Security*, Transport*, Weather* (not in enum yet)
}
UNREACHABLE();
}
std::string DebugPrint(Point point)
{
std::ostringstream os;
os << "Point { ";
os << "coordinates: " << DebugPrint(point.m_coordinates) << ", ";
// TODO optional float m_distance; (not in struct yet)
os << "junctionName: " << point.m_junctionName.value_or("nullopt") << ", ";
os << "junctionRef: " << point.m_junctionRef.value_or("nullopt");
os << " }";
return os.str();
}
std::string DebugPrint(TraffLocation location)
{
std::ostringstream os;
os << "TraffLocation { ";
os << "from: " << (location.m_from ? DebugPrint(location.m_from.value()) : "nullopt") << ", ";
os << "at: " << (location.m_at ? DebugPrint(location.m_at.value()) : "nullopt") << ", ";
os << "via: " << (location.m_via ? DebugPrint(location.m_via.value()) : "nullopt") << ", ";
os << "to: " << (location.m_to ? DebugPrint(location.m_to.value()) : "nullopt") << ", ";
os << "notVia: " << (location.m_notVia ? DebugPrint(location.m_notVia.value()) : "nullopt") << ", ";
// TODO fuzziness (not yet implemented in struct)
os << "country: " << location.m_country.value_or("nullopt") << ", ";
os << "territory: " << location.m_territory.value_or("nullopt") << ", ";
os << "town: " << location.m_town.value_or("nullopt") << ", ";
os << "roadClass: " << (location.m_roadClass ? DebugPrint(location.m_roadClass.value()) : "nullopt") << ", ";
os << "roadRef: " << location.m_roadRef.value_or("nullopt") << ", ";
os << "roadName: " << location.m_roadName.value_or("nullopt") << ", ";
os << "origin: " << location.m_origin.value_or("nullopt") << ", ";
os << "destination: " << location.m_destination.value_or("nullopt") << ", ";
os << "direction: " << location.m_direction.value_or("nullopt") << ", ";
os << "directionality: " << DebugPrint(location.m_directionality) << ", ";
os << "ramps: " << (location.m_ramps ? DebugPrint(location.m_ramps.value()) : "nullopt");
os << " }";
return os.str();
}
std::string DebugPrint(TraffEvent event)
{
std::ostringstream os;
os << "TraffEvent { ";
os << "class: " << DebugPrint(event.m_Class) << ", ";
os << "type: " << DebugPrint(event.m_Type) << ", ";
os << "length: " << (event.m_length ? std::to_string(event.m_length.value()) : "nullopt") << ", ";
os << "probability: " << (event.m_probability ? std::to_string(event.m_probability.value()) : "nullopt") << ", ";
// TODO optional quantifier
os << "speed: " << (event.m_speed ? std::to_string(event.m_speed.value()) : "nullopt");
// TODO supplementary information
os << " }";
return os.str();
}
std::string DebugPrint(TraffMessage message)
{
std::string sep;
std::ostringstream os;
os << "TraffMessage { ";
os << "id: " << message.m_id << ", ";
os << "replaces: [";
sep = " ";
for (auto const & replacedId : message.m_replaces)
{
os << sep << replacedId;
sep = ", ";
}
os << " ], ";
os << "receiveTime: " << DebugPrint(message.m_receiveTime) << ", ";
os << "updateTime: " << DebugPrint(message.m_updateTime) << ", ";
os << "expirationTime: " << DebugPrint(message.m_expirationTime) << ", ";
os << "startTime: " << (message.m_startTime ? DebugPrint(message.m_startTime.value()) : "nullopt") << ", ";
os << "endTime: " << (message.m_endTime ? DebugPrint(message.m_endTime.value()) : "nullopt") << ", ";
os << "cancellation: " << message.m_cancellation << ", ";
os << "forecast: " << message.m_forecast << ", ";
// TODO std::optional<Urgency> m_urgency; (not in struct yet)
os << "location: " << (message.m_location ? DebugPrint(message.m_location.value()) : "nullopt") << ", ";
os << "events: [";
sep = " ";
for (auto const & event : message.m_events)
{
os << sep << DebugPrint(event);
sep = ", ";
}
os << " ]";
os << " }";
return os.str();
}
std::string DebugPrint(TraffFeed feed)
{
std::string sep;
std::ostringstream os;
os << "[ ";
sep = "";
for (auto const & message : feed)
{
os << sep << DebugPrint(message);
sep = ", ";
}
os << " ]";
return os.str();
}
} // namespace traffxml

203
traffxml/traff_model.hpp Normal file
View File

@@ -0,0 +1,203 @@
#pragma once
//#include "traffxml/traff_foo.hpp"
#include "geometry/latlon.hpp"
#include "geometry/point2d.hpp"
#include <string>
#include <vector>
namespace traffxml
{
/**
* @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`.
*
* Code using `IsoTime` must not rely on it being identical to any other type, as this is not
* guaranteed to be stable.
*/
/*
* Where no time zone is indicated, the timestamp shall always be interpreted as UTC.
* `IsoTime` currently maps to `std::tm`, but this is not guaranteed.
*/
using IsoTime = std::tm;
// TODO enum urgency
enum class Directionality
{
OneDirection,
BothDirections
};
// TODO enum fuzziness
enum class Ramps
{
None,
All,
Entry,
Exit
};
enum class RoadClass
{
Motorway,
Trunk,
Primary,
Secondary,
Tertiary,
Other
};
enum class EventClass
{
Invalid,
Activity,
Authority,
Carpool,
Congestion,
Construction,
Delay,
Environment,
EquipmentStatus,
Hazard,
Incident,
Restriction,
Security,
Transport,
Weather
};
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*
};
struct Point
{
// TODO role?
ms::LatLon m_coordinates = ms::LatLon::Zero();
// TODO optional float m_distance;
std::optional<std::string> m_junctionName;
std::optional<std::string> m_junctionRef;
};
struct TraffLocation
{
std::optional<std::string> m_country;
std::optional<std::string> m_destination;
std::optional<std::string> m_direction;
Directionality m_directionality = Directionality::BothDirections;
// TODO std::optional<Fuzziness> m_fuzziness;
std::optional<std::string> m_origin;
std::optional<Ramps> m_ramps;
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<uint8_t> m_length;
std::optional<uint8_t> m_probability;
// TODO optional quantifier
std::optional<uint8_t> m_speed;
// TODO supplementary information
};
struct TraffMessage
{
std::string m_id;
IsoTime m_receiveTime = {};
IsoTime m_updateTime = {};
IsoTime m_expirationTime = {};
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;
};
using TraffFeed = std::vector<TraffMessage>;
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(Point point);
std::string DebugPrint(TraffLocation location);
std::string DebugPrint(TraffEvent event);
std::string DebugPrint(TraffMessage message);
std::string DebugPrint(TraffFeed feed);
} // namespace traffxml

View File

@@ -0,0 +1,677 @@
#include "traffxml/traff_model_xml.hpp"
#include "traffxml/traff_model.hpp"
#include "base/logging.hpp"
#include <cstring>
#include <optional>
#include <regex>
#include <type_traits>
#include <utility>
#include <pugixml.hpp>
using namespace std;
namespace traffxml
{
const std::map<std::string, Directionality> kDirectionalityMap{
{"ONE_DIRECTION", Directionality::OneDirection},
{"BOTH_DIRECTIONS", Directionality::BothDirections}
};
const std::map<std::string, Ramps> kRampsMap{
{"ALL_RAMPS", Ramps::All},
{"ENTRY_RAMP", Ramps::Entry},
{"EXIT_RAMP", Ramps::Exit},
{"NONE", Ramps::None}
};
const std::map<std::string, RoadClass> kRoadClassMap{
{"MOTORWAY", RoadClass::Motorway},
{"TRUNK", RoadClass::Trunk},
{"PRIMARY", RoadClass::Primary},
{"SECONDARY", RoadClass::Secondary},
{"TERTIARY", RoadClass::Tertiary},
{"OTHER", RoadClass::Other}
};
const std::map<std::string, EventClass> kEventClassMap{
{"INVALID", EventClass::Invalid},
{"ACTIVITY", EventClass::Activity},
{"AUTHORITY", EventClass::Authority},
{"CARPOOL", EventClass::Carpool},
{"CONGESTION", EventClass::Congestion},
{"CONSTRUCTION", EventClass::Construction},
{"DELAY", EventClass::Delay},
{"ENVIRONMENT", EventClass::Environment},
{"EQUIPMENT_STATUS", EventClass::EquipmentStatus},
{"HAZARD", EventClass::Hazard},
{"INCIDENT", EventClass::Incident},
{"RESTRICTION", EventClass::Restriction},
{"SECURITY", EventClass::Security},
{"TRANSPORT", EventClass::Transport},
{"WEATHER", EventClass::Weather}
};
const std::map<std::string, EventType> kEventTypeMap{
{"INVALID", EventType::Invalid},
// TODO Activity*, Authority*, Carpool* (not in enum yet)
{"CONGESTION_CLEARED", EventType::CongestionCleared},
{"CONGESTION_FORECAST_WITHDRAWN", EventType::CongestionForecastWithdrawn},
{"CONGESTION_HEAVY_TRAFFIC", EventType::CongestionHeavyTraffic},
{"CONGESTION_LONG_QUEUE", EventType::CongestionLongQueue},
{"CONGESTION_NONE", EventType::CongestionNone},
{"CONGESTION_NORMAL_TRAFFIC", EventType::CongestionNormalTraffic},
{"CONGESTION_QUEUE", EventType::CongestionQueue},
{"CONGESTION_QUEUE_LIKELY", EventType::CongestionQueueLikely},
{"CONGESTION_SLOW_TRAFFIC", EventType::CongestionSlowTraffic},
{"CONGESTION_STATIONARY_TRAFFIC", EventType::CongestionStationaryTraffic},
{"CONGESTION_STATIONARY_TRAFFIC_LIKELY", EventType::CongestionStationaryTrafficLikely},
{"CONGESTION_TRAFFIC_BUILDING_UP", EventType::CongestionTrafficBuildingUp},
{"CONGESTION_TRAFFIC_CONGESTION", EventType::CongestionTrafficCongestion},
{"CONGESTION_TRAFFIC_EASING", EventType::CongestionTrafficEasing},
{"CONGESTION_TRAFFIC_FLOWING_FREELY", EventType::CongestionTrafficFlowingFreely},
{"CONGESTION_TRAFFIC_HEAVIER_THAN_NORMAL", EventType::CongestionTrafficHeavierThanNormal},
{"CONGESTION_TRAFFIC_LIGHTER_THAN_NORMAL", EventType::CongestionTrafficLighterThanNormal},
{"CONGESTION_TRAFFIC_MUCH_HEAVIER_THAN_NORMAL", EventType::CongestionTrafficMuchHeavierThanNormal},
{"CONGESTION_TRAFFIC_PROBLEM", EventType::CongestionTrafficProblem},
// TODO Construction* (not in enum yet)
{"DELAY_CLEARANCE", EventType::DelayClearance},
{"DELAY_DELAY", EventType::DelayDelay},
{"DELAY_DELAY_POSSIBLE", EventType::DelayDelayPossible},
{"DELAY_FORECAST_WITHDRAWN", EventType::DelayForecastWithdrawn},
{"DELAY_LONG_DELAY", EventType::DelayLongDelay},
{"DELAY_SEVERAL_HOURS", EventType::DelaySeveralHours},
{"DELAY_UNCERTAIN_DURATION", EventType::DelayUncertainDuration},
{"DELAY_VERY_LONG_DELAY", EventType::DelayVeryLongDelay},
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
// TODO complete Restriction* (not in enum yet)
{"RESTRICTION_BLOCKED", EventType::RestrictionBlocked},
{"RESTRICTION_BLOCKED_AHEAD", EventType::RestrictionBlockedAhead},
{"RESTRICTION_CARRIAGEWAY_BLOCKED", EventType::RestrictionCarriagewayBlocked},
{"RESTRICTION_CARRIAGEWAY_CLOSED", EventType::RestrictionCarriagewayClosed},
{"RESTRICTION_CLOSED", EventType::RestrictionClosed},
{"RESTRICTION_CLOSED_AHEAD", EventType::RestrictionClosedAhead},
{"RESTRICTION_ENTRY_BLOCKED", EventType::RestrictionEntryBlocked},
{"RESTRICTION_ENTRY_REOPENED", EventType::RestrictionEntryReopened},
{"RESTRICTION_EXIT_BLOCKED", EventType::RestrictionExitBlocked},
{"RESTRICTION_EXIT_REOPENED", EventType::RestrictionExitReopened},
{"RESTRICTION_OPEN", EventType::RestrictionOpen},
{"RESTRICTION_RAMP_BLOCKED", EventType::RestrictionRampBlocked},
{"RESTRICTION_RAMP_CLOSED", EventType::RestrictionRampClosed},
{"RESTRICTION_RAMP_REOPENED", EventType::RestrictionRampReopened},
{"RESTRICTION_REOPENED", EventType::RestrictionReopened},
{"RESTRICTION_SPEED_LIMIT", EventType::RestrictionSpeedLimit},
{"RESTRICTION_SPEED_LIMIT_LIFTED", EventType::RestrictionSpeedLimitLifted},
// TODO Security*, Transport*, Weather* (not in enum yet)
};
/**
* @brief Retrieves an integer value from an attribute.
*
* @param attribute The XML attribute to retrieve.
* @return `true` on success, `false` if the attribute is not set or does not contain an integer value.
*/
std::optional<uint8_t> OptionalIntegerFromXml(pugi::xml_attribute attribute)
{
if (attribute.empty())
return std::nullopt;
try
{
uint8_t result = std::stoi(attribute.as_string());
return result;
}
catch (std::invalid_argument const& ex)
{
return std::nullopt;
}
}
/**
* @brief Retrieves a string from an attribute.
*
* @param attribute The XML attribute to retrieve.
* @param string Receives the string retrieved.
* @return `true` on success, `false` if the attribute is not set or set to an empty string.
*/
bool StringFromXml(pugi::xml_attribute attribute, std::string & string)
{
if (attribute.empty())
return false;
string = attribute.as_string();
return true;
}
/**
* @brief Retrieves a string from an XML element.
*
* @param node The XML element to retrieve.
* @param string Receives the string retrieved.
* @return `true` on success, `false` if the node does not exist.
*/
bool StringFromXml(pugi::xml_node node, std::string & string)
{
if (!node)
return false;
string = node.text().as_string();
return true;
}
/**
* @brief Retrieves a string from an attribute.
*
* @param attribute The XML attribute to retrieve.
* @return The string, or `std::nullopt` if the attribute is not set or set to an empty string.
*/
std::optional<std::string> OptionalStringFromXml(pugi::xml_attribute attribute)
{
std::string result;
if (!StringFromXml(attribute, result))
return std::nullopt;
return result;
}
/**
* @brief Parses time in ISO 8601 format from a time attribute 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 attribute The XML attribute from which to receive time.
* @param tm Receives the parsed time.
* @return `true` on success, `false` if the attribute is not set or does not contain a timestamp.
*/
bool TimeFromXml(pugi::xml_attribute attribute, IsoTime & tm)
{
std::string timeString;
if (!StringFromXml(attribute, timeString))
return false;
/*
* Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher
* will contain the following items:
*
* 0: 2019-11-01T11:55:42+01:00 (entire expression)
* 1: 2019 (year)
* 2: 11 (month)
* 3: 01 (day)
* 4: 11 (hour, local)
* 5: 55 (minute, local)
* 6: 42.445 (second, local, float)
* 7: .445 (fractional seconds)
* 8: +01:00 (complete UTC offset, or Z; blank if not specified)
* 9: +01:00 (complete UTC offset, blank for Z or of not specified)
* 10: +01 (UTC offset, hours with sign; blank for Z or if not specified)
* 11: :00 (UTC offset, minutes, prefixed with separator)
* 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified)
*/
std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?");
std::smatch iso8601Matcher;
if (std::regex_search(timeString, iso8601Matcher, iso8601Regex))
{
int offset_h = iso8601Matcher[10].matched ? std::stoi(iso8601Matcher[10]) : 0;
int offset_m = iso8601Matcher[12].matched ? std::stoi(iso8601Matcher[12]) : 0;
if (offset_h < 0)
offset_m *= -1;
tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900;
tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1;
tm.tm_mday = std::stoi(iso8601Matcher[3]);
tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h;
tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m;
tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f;
// Call timegm once to normalize tm; return value can be discarded
timegm(&tm);
return true;
}
else
{
LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString));
return false;
}
}
/**
* @brief Parses time in ISO 8601 format from a time attribute 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 attribute The XML attribute from which to receive time.
* @return The parsed time, or `std::nullopt` if the attribute is not set or does not contain a timestamp.
*/
std::optional<IsoTime> OptionalTimeFromXml(pugi::xml_attribute attribute)
{
IsoTime result = {};
if (!TimeFromXml(attribute, result))
return std::nullopt;
return result;
}
/**
* @brief Retrieves a boolean value from an attribute.
* @param attribute The XML attribute to retrieve.
* @param defaultValue The default value to return.
* @return The value of the attribute, or `defaultValue` if the attribute is not set.
*/
bool BoolFromXml(pugi::xml_attribute attribute, bool defaultValue)
{
if (attribute.empty())
return defaultValue;
return attribute.as_bool();
}
/**
* @brief Retrieves a boolean value from an attribute.
* @param attribute The XML attribute to retrieve.
* @return The value of the attribute, or `std::nullopt` if the attribute is not set.
*/
std::optional<bool> OptionalBoolFromXml(pugi::xml_attribute attribute)
{
if (attribute.empty())
return std::nullopt;
return attribute.as_bool();
}
/**
* @brief Retrieves an enum value from an attribute.
*
* Enum values are retrieved in two steps: first, a string is retrieved, which is then decoded to
* an enum value. The enum type is determined by the type of `value` and the value type of `map`,
* both of which must match. The mapping between strings and their corresponding enum values is
* determined by the entries in `map`.
*
* @param attribute The XML attribute to retrieve.
* @param Value Receives the enum value to retrieve.
* @param map A map from strings to their respective enum values.
* @return `true` on success, `false` if the attribute is not set or its value is not found in `map`.
*/
template <typename Value>
bool EnumFromXml(pugi::xml_attribute attribute, Value & value, std::map<std::string, Value> map)
{
std::string string;
if (StringFromXml(attribute, string))
{
auto it = map.find(string);
if (it != map.end())
{
value = it->second;
return true;
}
else
LOG(LWARNING, ("Unknown value for", attribute.name(), ":", string, "(ignoring)"));
}
return false;
}
/**
* @brief Retrieves an enum value from an attribute.
*
* Enum values are retrieved in two steps: first, a string is retrieved, which is then decoded to
* an enum value. The enum type is determined by the value type of `map`. The mapping between
* strings and their corresponding enum values is determined by the entries in `map`.
*
* @param attribute The XML attribute to retrieve.
* @param map A map from strings to their respective enum values.
* @return The enum value, or `std::nullopt` if the attribute is not set or its value is not found in `map`.
*/
template <typename Value>
std::optional<Value> OptionalEnumFromXml(pugi::xml_attribute attribute, std::map<std::string, Value> map)
{
std::string string;
if (StringFromXml(attribute, string))
{
auto it = map.find(string);
if (it != map.end())
return it->second;
else
LOG(LWARNING, ("Unknown value for", attribute.name(), ":", string, "(ignoring)"));
}
return std::nullopt;
}
/**
* @brief Retrieves the IDs of replaced messages from an XML element.
* @param node The XML element to retrieve (`merge`).
* @param replacedIds Receives the replaced IDs.
* @return `true` on success (including if the node contains no replaced IDs), `false` if the node does not exist or does not contain valid data.
*/
bool ReplacedMessageIdsFromXml(pugi::xml_node node, std::vector<std::string> & replacedIds)
{
if (!node)
return false;
bool result = false;
auto const replacedIdNodes = node.select_nodes("./replaces");
if (replacedIdNodes.empty())
return true;
for (auto const & xpathNode : replacedIdNodes)
{
auto const replacedIdNode = xpathNode.node();
std::string replacedId;
if (StringFromXml(replacedIdNode.attribute("id"), replacedId))
{
replacedIds.push_back(replacedId);
result = true;
}
else
LOG(LWARNING, ("Could not parse merge element, skipping"));
}
return result;
}
/**
* @brief Retrieves a latitude/longitude pair from an XML element.
*
* Coordinates must be given as latitude, followed by as space, then longitude. Latitude and
* longitude are given as floating-point numbers, optionally with a sign (plus is assumed if no
* sign is given). Coordinates are interpreted as degrees in WGS84 format.
*
* @param node The XML element to retrieve.
* @param latLon Receives the latitude/longitude pair.
* @return `true` on success, `false` if the node does not exist or does not contain valid coordinates.
*/
bool LatLonFromXml(pugi::xml_node node, ms::LatLon & latLon)
{
if (!node)
return false;
std::string string;
if (StringFromXml(node, string))
{
std::regex latLonRegex("([+-]?[0-9]*\\.?[0-9]*)\\s+([+-]?[0-9]*\\.?[0-9]*)");
std::smatch latLonMatcher;
if (std::regex_search(string, latLonMatcher, latLonRegex) && latLonMatcher[1].matched && latLonMatcher[2].matched)
{
try
{
latLon.m_lat = std::stod(latLonMatcher[1]);
latLon.m_lon = std::stod(latLonMatcher[2]);
return true;
}
catch (std::invalid_argument const& ex)
{
LOG(LWARNING, ("Not a valid coordinate pair:", string));
}
}
else
LOG(LWARNING, ("Not a valid coordinate pair:", string));
}
return false;
}
/**
* @brief Retrieves a Traff `Point` from an XML element.
* @param node The XML element to retrieve (any child of `location`).
* @return The point, or `std::nullopt` if the node does not exist or does not contain valid point data,
*/
std::optional<Point> OptionalPointFromXml(pugi::xml_node node)
{
if (!node)
return std::nullopt;
Point result;
if (!LatLonFromXml(node, result.m_coordinates))
{
LOG(LWARNING, (node.name(), "has no coordinates, ignoring"));
return std::nullopt;
}
// TODO optional float m_distance (not yet implemented in struct)
result.m_junctionName = OptionalStringFromXml(node.attribute("junction_name"));
result.m_junctionRef = OptionalStringFromXml(node.attribute("junction_ref"));
return result;
}
/**
* @brief Retrieves a `TraffLocation` from an XML element.
* @param node The XML element to retrieve (`location`).
* @param location Receives the location.
* @return `true` on success, `false` if the node does not exist or does not contain valid location data.
*/
bool LocationFromXml(pugi::xml_node node, TraffLocation & location)
{
if (!node)
return false;
location.m_from = OptionalPointFromXml(node.child("from"));
location.m_to = OptionalPointFromXml(node.child("to"));
location.m_at = OptionalPointFromXml(node.child("at"));
location.m_via = OptionalPointFromXml(node.child("via"));
location.m_notVia = OptionalPointFromXml(node.child("not_via"));
if (!location.m_from && !location.m_to && !location.m_at)
{
LOG(LWARNING, ("Neither from, to nor at point is specified, ignoring location"));
return false;
}
location.m_country = OptionalStringFromXml(node.attribute("country"));
location.m_destination = OptionalStringFromXml(node.attribute("destination"));
location.m_direction = OptionalStringFromXml(node.attribute("direction"));
EnumFromXml(node.attribute("directionality"), location.m_directionality, kDirectionalityMap);
// TODO fuzziness (not yet implemented in struct)
location.m_origin = OptionalStringFromXml(node.attribute("origin"));
location.m_ramps = OptionalEnumFromXml(node.attribute("ramps"), kRampsMap);
location.m_roadClass = OptionalEnumFromXml(node.attribute("road_class"), kRoadClassMap);
// disabled for now
//location.m_roadIsUrban = OptionalBoolFromXml(node.attribute(("road_is_urban")));
location.m_roadRef = OptionalStringFromXml(node.attribute("road_ref"));
location.m_roadName = OptionalStringFromXml(node.attribute("road_name"));
location.m_territory = OptionalStringFromXml(node.attribute("territory"));
location.m_town = OptionalStringFromXml(node.attribute("town"));
return true;
}
/**
* @brief Retrieves a `TraffEvent` from an XML element.
* @param node The XML element to retrieve (`event`).
* @param event Receives the event.
* @return `true` on success, `false` if the node does not exist or does not contain valid event data.
*/
bool EventFromXml(pugi::xml_node node, TraffEvent & event)
{
std::string eventClass;
if (!StringFromXml(node.attribute("class"), eventClass))
{
LOG(LWARNING, ("No event class specified, ignoring"));
return false;
}
if (!EnumFromXml(node.attribute("class"), event.m_Class, kEventClassMap))
return false;
std::string eventType;
if (!StringFromXml(node.attribute("type"), eventType))
{
LOG(LWARNING, ("No event type specified, ignoring"));
return false;
}
if (!eventType.starts_with(eventClass + "_"))
{
LOG(LWARNING, ("Event type", eventType, "does not match event class", eventClass, "(ignoring)"));
return false;
}
if (!EnumFromXml(node.attribute("type"), event.m_Type, kEventTypeMap))
return false;
event.m_length = OptionalIntegerFromXml(node.attribute("length"));
event.m_probability = OptionalIntegerFromXml(node.attribute("probability"));
// TODO optional quantifier (not yet implemented in struct)
event.m_speed = OptionalIntegerFromXml(node.attribute("speed"));
// TODO supplementary information (not yet implemented in struct)
return true;
}
/**
* @brief Retrieves the TraFF events associsted with a message from an XML element.
* @param node The XML element to retrieve (`events`).
* @param events Receives the events.
* @return `true` on success, `false` if the node does not exist or does not contain valid event data (including if the node contains no events).
*/
bool EventsFromXml(pugi::xml_node node, std::vector<TraffEvent> & events)
{
if (!node)
return false;
bool result = false;
auto const eventNodes = node.select_nodes("./event");
if (eventNodes.empty())
return false;
for (auto const & xpathNode : eventNodes)
{
auto const eventNode = xpathNode.node();
TraffEvent event;
if (EventFromXml(eventNode, event))
{
events.push_back(event);
result = true;
}
else
LOG(LWARNING, ("Could not parse event, skipping"));
}
return result;
}
/**
* @brief Retrieves a TraFF message from an XML element.
* @param node The XML element to retrieve (`message`).
* @param message Receives the message.
* @return `true` on success, `false` if the node does not exist or does not contain valid message data.
*/
bool MessageFromXml(pugi::xml_node node, TraffMessage & message)
{
if (!StringFromXml(node.attribute("id"), message.m_id))
{
LOG(LWARNING, ("Message has no id"));
return false;
}
if (!TimeFromXml(node.attribute("receive_time"), message.m_receiveTime))
{
LOG(LWARNING, ("Message", message.m_id, "has no receive_time"));
return false;
}
if (!TimeFromXml(node.attribute("update_time"), message.m_updateTime))
{
LOG(LWARNING, ("Message", message.m_id, "has no update_time"));
return false;
}
if (!TimeFromXml(node.attribute("expiration_time"), message.m_expirationTime))
{
LOG(LWARNING, ("Message", message.m_id, "has no expiration_time"));
return false;
}
message.m_startTime = OptionalTimeFromXml(node.attribute("start_time"));
message.m_endTime = OptionalTimeFromXml(node.attribute("end_time"));
message.m_cancellation = BoolFromXml(node.attribute("cancellation"), false);
message.m_forecast = BoolFromXml(node.attribute("forecast"), false);
// TODO urgency (not yet implemented in struct)
ReplacedMessageIdsFromXml(node.child("merge"), message.m_replaces);
if (!message.m_cancellation)
{
message.m_location.emplace();
if (!LocationFromXml(node.child("location"), message.m_location.value()))
{
message.m_location.reset();
LOG(LWARNING, ("Message", message.m_id, "has no location but is not a cancellation message"));
return false;
}
if (!EventsFromXml(node.child("events"), message.m_events))
{
LOG(LWARNING, ("Message", message.m_id, "has no events but is not a cancellation message"));
return false;
}
}
return true;
}
/**
* @brief Retrieves a TraFF feed from an XML document.
*
* The document must conform loosely to the TraFF specification (currently version 0.8).
*
* The name of the root element is not verified, but the `message` elements must be its immediate
* children.
*
* Custom elements and attributes which are not part of the TraFF specification are ignored.
*
* Values which cannot be parsed correctly are skipped.
*
* Events whose event type does not match their event class are skipped.
*
* Messages, events, locations or points which lack mandatory information are skipped.
*
* If children are skipped but the parent remains valid, parsing it will report success.
*
* Parsing the feed will report failure if all its messages fail to parse, but not if it has no
* messages.
*
* @param document The XML document from which to retrieve the messages.
* @param feed Receives the TraFF feed.
* @return `true` on success, `false` on failure.
*/
bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed)
{
bool result = false;
// Select all messages elements that are direct children of the root.
auto const messages = document.document_element().select_nodes("./message");
if (messages.empty())
return true;
// TODO try block?
for (auto const & xpathNode : messages)
{
auto const messageNode = xpathNode.node();
TraffMessage message;
if (MessageFromXml(messageNode, message))
{
feed.push_back(message);
result = true;
}
else
LOG(LWARNING, ("Could not parse message, skipping"));
}
return result;
}
} // namespace openlr

View File

@@ -0,0 +1,14 @@
#pragma once
#include "traffxml/traff_model.hpp"
namespace pugi
{
class xml_document;
class xml_node;
} // namespace pugi
namespace traffxml
{
bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed);
} // namespace traffxml