mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 13:03:36 +00:00
[traffxml] Add module
Signed-off-by: mvglasow <michael -at- vonglasow.com>
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -28,4 +28,5 @@ target_link_libraries(${PROJECT_NAME}
|
|||||||
qt_common
|
qt_common
|
||||||
map
|
map
|
||||||
gflags::gflags
|
gflags::gflags
|
||||||
|
traffxml
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
16
traffxml/CMakeLists.txt
Normal 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
|
||||||
|
)
|
||||||
7
traffxml/traff_decoder.cpp
Normal file
7
traffxml/traff_decoder.cpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "traffxml/traff_decoder.hpp"
|
||||||
|
|
||||||
|
//#include "traffxml/traff_foo.hpp"
|
||||||
|
|
||||||
|
namespace traffxml
|
||||||
|
{
|
||||||
|
} // namespace traffxml
|
||||||
7
traffxml/traff_decoder.hpp
Normal file
7
traffxml/traff_decoder.hpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
//#include "traffxml/traff_foo.hpp"
|
||||||
|
|
||||||
|
namespace traffxml
|
||||||
|
{
|
||||||
|
} // namespace traffxml
|
||||||
248
traffxml/traff_model.cpp
Normal file
248
traffxml/traff_model.cpp
Normal 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
203
traffxml/traff_model.hpp
Normal 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
|
||||||
677
traffxml/traff_model_xml.cpp
Normal file
677
traffxml/traff_model_xml.cpp
Normal 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
|
||||||
14
traffxml/traff_model_xml.hpp
Normal file
14
traffxml/traff_model_xml.hpp
Normal 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
|
||||||
Reference in New Issue
Block a user