#include "traffxml/traff_model_xml.hpp" #include "traffxml/traff_model.hpp" #include "base/logging.hpp" #include #include #include #include #include #include #include using namespace std; namespace traffxml { template boost::bimap MakeBimap(std::initializer_list::value_type> list) { return boost::bimap(list.begin(), list.end()); } const boost::bimap kDirectionalityMap = MakeBimap({ {"ONE_DIRECTION", Directionality::OneDirection}, {"BOTH_DIRECTIONS", Directionality::BothDirections} }); const boost::bimap kRampsMap = MakeBimap({ {"ALL_RAMPS", Ramps::All}, {"ENTRY_RAMP", Ramps::Entry}, {"EXIT_RAMP", Ramps::Exit}, {"NONE", Ramps::None} }); const boost::bimap kRoadClassMap = MakeBimap({ {"MOTORWAY", RoadClass::Motorway}, {"TRUNK", RoadClass::Trunk}, {"PRIMARY", RoadClass::Primary}, {"SECONDARY", RoadClass::Secondary}, {"TERTIARY", RoadClass::Tertiary}, {"OTHER", RoadClass::Other} }); const boost::bimap kEventClassMap = MakeBimap({ {"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 boost::bimap kEventTypeMap = MakeBimap({ {"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 OptionalIntegerFromXml(pugi::xml_attribute const & 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 float 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 a float value. */ std::optional OptionalFloatFromXml(pugi::xml_attribute const & attribute) { if (attribute.empty()) return std::nullopt; try { float result = std::stof(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 const & 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 const & 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 OptionalStringFromXml(pugi::xml_attribute const & 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 const & attribute, IsoTime & tm) { std::string timeString; if (!StringFromXml(attribute, timeString)) return false; std::optional result = IsoTime::ParseIsoTime(timeString); if (!result) return false; tm = result.value(); return true; } /** * @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 OptionalTimeFromXml(pugi::xml_attribute const & attribute) { IsoTime result = IsoTime::Now(); 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 const & 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 OptionalBoolFromXml(pugi::xml_attribute const & 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 bool EnumFromXml(pugi::xml_attribute const & attribute, Value & value, boost::bimap const & map) { std::string string; if (StringFromXml(attribute, string)) { auto it = map.left.find(string); if (it != map.left.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 std::optional OptionalEnumFromXml(pugi::xml_attribute const & attribute, boost::bimap const & map) { std::string string; if (StringFromXml(attribute, string)) { auto it = map.left.find(string); if (it != map.left.end()) return it->second; else LOG(LWARNING, ("Unknown value for", attribute.name(), ":", string, "(ignoring)")); } return std::nullopt; } template void EnumToXml(Value const & value, std::string name, pugi::xml_node & node, boost::bimap const & map) { auto it = map.right.find(value); if (it != map.right.end()) node.append_attribute(name).set_value(it->second); else { ASSERT(false, ("Enum value not found in map for", name)); } } /** * @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 const & node, std::vector & 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 const & 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 OptionalPointFromXml(pugi::xml_node const & 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; } result.m_junctionName = OptionalStringFromXml(node.attribute("junction_name")); result.m_junctionRef = OptionalStringFromXml(node.attribute("junction_ref")); result.m_distance = OptionalFloatFromXml(node.attribute("distance")); return result; } void PointToXml(Point const & point, std::string name, pugi::xml_node & parentNode) { auto node = parentNode.append_child(name); if (point.m_distance) node.append_attribute("distance").set_value(point.m_junctionName.value()); if (point.m_junctionName) node.append_attribute("junction_name").set_value(point.m_junctionName.value()); if (point.m_junctionRef) node.append_attribute("junction_ref").set_value(point.m_junctionRef.value()); node.text() = std::format("{0:+.5f} {1:+.5f}", point.m_coordinates.m_lat, point.m_coordinates.m_lon); } /** * @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 const & 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")); int numPoints = 0; for (std::optional point : {location.m_from, location.m_to, location.m_at}) if (point) numPoints++; // single-point locations are not supported, locations without points are not valid if (numPoints < 2) { LOG(LWARNING, ("Only", numPoints, "points of from/to/at 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")); EnumFromXml(node.attribute("ramps"), location.m_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; } void LocationToXml(TraffLocation const & location, pugi::xml_node & node) { if (location.m_country) node.append_attribute("country").set_value(location.m_country.value()); if (location.m_destination) node.append_attribute("destination").set_value(location.m_destination.value()); if (location.m_direction) node.append_attribute("direction").set_value(location.m_direction.value()); EnumToXml(location.m_directionality, "directionality", node, kDirectionalityMap); // TODO fuzziness (not yet implemented in struct) if (location.m_origin) node.append_attribute("origin").set_value(location.m_origin.value()); EnumToXml(location.m_ramps, "ramps", node, kRampsMap); if (location.m_roadClass) EnumToXml(location.m_roadClass.value(), "roadClass", node, kRoadClassMap); // TODO roadIsUrban (disabled for now) if (location.m_roadRef) node.append_attribute("roadRef").set_value(location.m_roadRef.value()); if (location.m_roadName) node.append_attribute("roadName").set_value(location.m_roadName.value()); if (location.m_territory) node.append_attribute("territory").set_value(location.m_territory.value()); if (location.m_town) node.append_attribute("town").set_value(location.m_town.value()); if (location.m_from) PointToXml(location.m_from.value(), "from", node); if (location.m_at) PointToXml(location.m_at.value(), "at", node); if (location.m_via) PointToXml(location.m_via.value(), "via", node); if (location.m_notVia) PointToXml(location.m_notVia.value(), "not_via", node); if (location.m_to) PointToXml(location.m_to.value(), "to", node); // TODO decoded segments } /** * @brief Retrieves a `TraffQuantifier` from an XML element. * * The TraFF specification allows only one quantifier per event. The quantifier type depends on the * event type, and not all events allow quantifiers. * * Quantifiers which violate these constraints are not filtered out, i.e. this function may return a * quantifier for event types that do not allow quantifiers, or of a type illegal for the event type. * If an event contains multiple quantifiers of different types, any one of these quantifiers may be * returned, with no preference for legal quantifiers over illegal ones. * * @param node The node from which to retrieve the quantifier (`event`). * @return The quantifier, or `std::nullopt` */ std::optional OptionalDurationFromXml(pugi::xml_attribute const & attribute) { std::string durationString; if (!StringFromXml(attribute, durationString)) return std::nullopt; /* * Valid time formats: * 01:30 (hh:mm) * 1 h * 30 min */ std::regex durationRegex("(([0-9]+):([0-9]{2}))|(([0-9]+) *h)|(([0-9]+) *min)"); std::smatch durationMatcher; if (std::regex_search(durationString, durationMatcher, durationRegex)) { if (!durationMatcher.str(2).empty() && !durationMatcher.str(3).empty()) return std::stoi(durationMatcher[2]) * 60 + std::stoi(durationMatcher[3]); else if (!durationMatcher.str(5).empty()) return std::stoi(durationMatcher[5]) * 60; else if (!durationMatcher.str(7).empty()) return std::stoi(durationMatcher[7]); UNREACHABLE(); return std::nullopt; } else { LOG(LINFO, ("Not a valid duration:", durationString)); return std::nullopt; } } /** * @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 const & 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")); event.m_qDurationMins = OptionalDurationFromXml(node.attribute("q_duration")); // TODO other quantifiers (not yet implemented in struct) event.m_speed = OptionalIntegerFromXml(node.attribute("speed")); // TODO supplementary information (not yet implemented in struct) return true; } void EventToXml(TraffEvent const & event, pugi::xml_node & node) { EnumToXml(event.m_class, "class", node, kEventClassMap); EnumToXml(event.m_type, "type", node, kEventTypeMap); if (event.m_length) node.append_attribute("length").set_value(event.m_length.value()); if (event.m_probability) node.append_attribute("probability").set_value(event.m_probability.value()); if (event.m_qDurationMins) node.append_attribute("q_duration").set_value(std::format("{:%H:%M}", std::chrono::minutes(event.m_qDurationMins.value()))); // TODO other quantifiers (not yet implemented in struct) if (event.m_speed) node.append_attribute("speed").set_value(event.m_speed.value()); // TODO supplementary information (not yet implemented in struct) } /** * @brief Retrieves the TraFF events associated 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 const & node, std::vector & 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 const & 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; } void MessageToXml(TraffMessage const & message, pugi::xml_node node) { node.append_attribute("id").set_value(message.m_id); node.append_attribute("receive_time").set_value(message.m_receiveTime.ToString()); node.append_attribute("update_time").set_value(message.m_updateTime.ToString()); node.append_attribute("expiration_time").set_value(message.m_expirationTime.ToString()); if (message.m_startTime) node.append_attribute("start_time").set_value(message.m_startTime.value().ToString()); if (message.m_endTime) node.append_attribute("end_time").set_value(message.m_endTime.value().ToString()); node.append_attribute("cancellation").set_value(message.m_cancellation); node.append_attribute("forecast").set_value(message.m_forecast); // TODO urgency (not yet implemented in struct) if (!message.m_replaces.empty()) { auto mergeNode = node.append_child("merge"); for (auto const & id : message.m_replaces) { auto replacesNode = mergeNode.append_child("replaces"); replacesNode.append_attribute("id").set_value(id); } } if (message.m_location) { auto locationNode = node.append_child("location"); LocationToXml(message.m_location.value(), locationNode); } if (!message.m_events.empty()) { auto eventsNode = node.append_child("events"); for (auto const event : message.m_events) { auto eventNode = eventsNode.append_child("event"); EventToXml(event, eventNode); } } } 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; } void GenerateTraff(TraffFeed const & feed, pugi::xml_document & document) { auto root = document.append_child("feed"); for (auto const & message : feed) { auto child = root.append_child("message"); MessageToXml(message, child); } } void GenerateTraff(std::map const & messages, pugi::xml_document & document) { auto root = document.append_child("feed"); for (auto const & [id, message] : messages) { auto child = root.append_child("message"); MessageToXml(message, child); } } std::string FiltersToXml(std::vector & bboxRects) { std::ostringstream os; for (auto rect : bboxRects) os << std::format("\n", mercator::YToLat(rect.minY()), mercator::XToLon(rect.minX()), mercator::YToLat(rect.maxY()), mercator::XToLon(rect.maxX())); return os.str(); } } // namespace openlr