[traffxml] Store message cache in file

So far only API and tests, and without decoded segments

Signed-off-by: mvglasow <michael -at- vonglasow.com>
This commit is contained in:
mvglasow
2025-06-20 17:23:22 +03:00
parent 371a58f6f9
commit 9fb08bdc56
7 changed files with 246 additions and 18 deletions

View File

@@ -107,6 +107,12 @@ void TrafficManager::Teardown()
m_thread.join();
}
std::map<std::string, traffxml::TraffMessage> TrafficManager::GetMessageCache()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_messageCache;
}
TrafficManager::TrafficState TrafficManager::GetState() const
{
return m_state;

View File

@@ -111,6 +111,17 @@ public:
void Teardown();
/**
* @brief Returns a copy of the cache of all currently active TraFF messages.
*
* For testing purposes.
*
* Keys are message IDs, values are messages.
*
* This method is safe to call from any thread.
*/
std::map<std::string, traffxml::TraffMessage> GetMessageCache();
TrafficState GetState() const;
void SetStateListener(TrafficStateChangedFn const & onStateChangedFn);

View File

@@ -445,6 +445,13 @@ void MainWindow::OnSaveTrafficSample()
if (fileName.isEmpty())
return;
pugi::xml_document document;
auto const messageCache = m_framework.GetTrafficManager().GetMessageCache();
traffxml::GenerateTraff(messageCache, document);
document.save_file(fileName.toStdString().data(), " " /* indent */);
#ifdef openlr_obsolete
if (!m_trafficMode->SaveSampleAs(fileName.toStdString()))
{

View File

@@ -161,6 +161,11 @@ void IsoTime::Shift(IsoTime nowRef)
m_tp += offset;
}
std::string IsoTime::ToString() const
{
return std::format("{0:%F}T{0:%T}{0:%Ez}", time_point_cast<std::chrono::seconds>(m_tp));
}
bool IsoTime::operator< (IsoTime & rhs)
{
return m_tp < rhs.m_tp;

View File

@@ -83,6 +83,12 @@ public:
*/
void Shift(IsoTime nowRef);
/**
* @brief Returns a string representation of the instance.
* @return The timestamp in ISO 8601 format.
*/
std::string ToString() const;
bool operator< (IsoTime & rhs);
bool operator> (IsoTime & rhs);
private:

View File

@@ -9,34 +9,43 @@
#include <type_traits>
#include <utility>
#include <boost/bimap.hpp>
#include <pugixml.hpp>
using namespace std;
namespace traffxml
{
const std::map<std::string, Directionality> kDirectionalityMap{
template <typename L, typename R>
boost::bimap<L, R>
MakeBimap(std::initializer_list<typename boost::bimap<L, R>::value_type> list)
{
return boost::bimap<L, R>(list.begin(), list.end());
}
const boost::bimap<std::string, Directionality> kDirectionalityMap = MakeBimap<std::string, Directionality>({
{"ONE_DIRECTION", Directionality::OneDirection},
{"BOTH_DIRECTIONS", Directionality::BothDirections}
};
});
const std::map<std::string, Ramps> kRampsMap{
const boost::bimap<std::string, Ramps> kRampsMap = MakeBimap<std::string, Ramps>({
{"ALL_RAMPS", Ramps::All},
{"ENTRY_RAMP", Ramps::Entry},
{"EXIT_RAMP", Ramps::Exit},
{"NONE", Ramps::None}
};
});
const std::map<std::string, RoadClass> kRoadClassMap{
const boost::bimap<std::string, traffxml::RoadClass> kRoadClassMap = MakeBimap<std::string, RoadClass>({
{"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{
const boost::bimap<std::string, EventClass> kEventClassMap = MakeBimap<std::string, EventClass>({
{"INVALID", EventClass::Invalid},
{"ACTIVITY", EventClass::Activity},
{"AUTHORITY", EventClass::Authority},
@@ -52,9 +61,9 @@ const std::map<std::string, EventClass> kEventClassMap{
{"SECURITY", EventClass::Security},
{"TRANSPORT", EventClass::Transport},
{"WEATHER", EventClass::Weather}
};
});
const std::map<std::string, EventType> kEventTypeMap{
const boost::bimap<std::string, EventType> kEventTypeMap = MakeBimap<std::string, EventType>({
{"INVALID", EventType::Invalid},
// TODO Activity*, Authority*, Carpool* (not in enum yet)
{"CONGESTION_CLEARED", EventType::CongestionCleared},
@@ -105,7 +114,7 @@ const std::map<std::string, EventType> kEventTypeMap{
{"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.
@@ -291,13 +300,13 @@ std::optional<bool> OptionalBoolFromXml(pugi::xml_attribute attribute)
* @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)
bool EnumFromXml(pugi::xml_attribute attribute, Value & value, boost::bimap<std::string, Value> map)
{
std::string string;
if (StringFromXml(attribute, string))
{
auto it = map.find(string);
if (it != map.end())
auto it = map.left.find(string);
if (it != map.left.end())
{
value = it->second;
return true;
@@ -320,13 +329,13 @@ bool EnumFromXml(pugi::xml_attribute attribute, Value & value, std::map<std::str
* @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::optional<Value> OptionalEnumFromXml(pugi::xml_attribute attribute, boost::bimap<std::string, Value> map)
{
std::string string;
if (StringFromXml(attribute, string))
{
auto it = map.find(string);
if (it != map.end())
auto it = map.left.find(string);
if (it != map.left.end())
return it->second;
else
LOG(LWARNING, ("Unknown value for", attribute.name(), ":", string, "(ignoring)"));
@@ -334,6 +343,18 @@ std::optional<Value> OptionalEnumFromXml(pugi::xml_attribute attribute, std::map
return std::nullopt;
}
template <typename Value>
void EnumToXml(Value const & value, std::string name, pugi::xml_node & node, boost::bimap<std::string, Value> 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`).
@@ -431,6 +452,19 @@ std::optional<Point> OptionalPointFromXml(pugi::xml_node node)
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`).
@@ -480,6 +514,47 @@ bool LocationFromXml(pugi::xml_node node, TraffLocation & location)
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.
*
@@ -571,6 +646,26 @@ bool EventFromXml(pugi::xml_node node, TraffEvent & event)
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 associsted with a message from an XML element.
* @param node The XML element to retrieve (`events`).
@@ -664,6 +759,49 @@ bool MessageFromXml(pugi::xml_node node, TraffMessage & message)
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;
@@ -690,6 +828,27 @@ bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed)
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<std::string, traffxml::TraffMessage> 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<m2::RectD> & bboxRects)
{
std::ostringstream os;

View File

@@ -23,8 +23,6 @@ namespace traffxml
* 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.
@@ -36,12 +34,48 @@ namespace traffxml
* Parsing the feed will report failure if all its messages fail to parse, but not if it has no
* messages.
*
* @note Custom elements and attributes which are not part of the TraFF specification are currently
* ignored. Future versions may process certain custom elements.
*
* @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);
/**
* @brief Generates XML from a TraFF feed.
*
* The resulting document largely conforms to the TraFF specification (currently version 0.8), but
* may contain custom elements.
*
* The root element of the generated XML document is `feed`.
*
* @note Currently no custom elements are generated. Future versions may add the location decoded
* into MWM IDs, feature IDs, directions and segments, along with their speed groups.
*
* @param feed The TraFF feed to encode.
* @param document The XML document in which to store the messages.
*/
void GenerateTraff(TraffFeed const & feed, pugi::xml_document & document);
/**
* @brief Generates XML from a map of TraFF messages.
*
* The resulting document largely conforms to the TraFF specification (currently version 0.8), but
* may contain custom elements.
*
* The root element of the generated XML document is `feed`.
*
* @note Currently no custom elements are generated. Future versions may add the location decoded
* into MWM IDs, feature IDs, directions and segments, along with their speed groups.
*
* @param messages A map whose values contain the TraFF messages to encode.
* @param document The XML document in which to store the messages.
*/
void GenerateTraff(std::map<std::string, traffxml::TraffMessage> const & messages,
pugi::xml_document & document);
/**
* @brief Generates a list of XML `filter` elements from a vector of rects representing bboxes.
*