mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 13:03:36 +00:00
So far only API and tests, and without decoded segments Signed-off-by: mvglasow <michael -at- vonglasow.com>
581 lines
23 KiB
C++
581 lines
23 KiB
C++
#include "traffxml/traff_model.hpp"
|
|
|
|
#include "base/logging.hpp"
|
|
|
|
#include <regex>
|
|
|
|
using namespace std;
|
|
|
|
namespace traffxml
|
|
{
|
|
const std::map<EventType, traffic::SpeedGroup> kEventSpeedGroupMap{
|
|
// TODO Activity*, Authority*, Carpool* (not in enum yet)
|
|
{EventType::CongestionHeavyTraffic, traffic::SpeedGroup::G4},
|
|
{EventType::CongestionLongQueue, traffic::SpeedGroup::G0},
|
|
{EventType::CongestionNone, traffic::SpeedGroup::G5},
|
|
{EventType::CongestionNormalTraffic, traffic::SpeedGroup::G5},
|
|
{EventType::CongestionQueue, traffic::SpeedGroup::G2},
|
|
{EventType::CongestionQueueLikely, traffic::SpeedGroup::G3},
|
|
{EventType::CongestionSlowTraffic, traffic::SpeedGroup::G3},
|
|
{EventType::CongestionStationaryTraffic, traffic::SpeedGroup::G1},
|
|
{EventType::CongestionStationaryTrafficLikely, traffic::SpeedGroup::G2},
|
|
{EventType::CongestionTrafficBuildingUp, traffic::SpeedGroup::G4},
|
|
{EventType::CongestionTrafficCongestion, traffic::SpeedGroup::G3}, // TODO or G2? Unquantified, below normal
|
|
{EventType::CongestionTrafficFlowingFreely, traffic::SpeedGroup::G5},
|
|
{EventType::CongestionTrafficHeavierThanNormal, traffic::SpeedGroup::G4},
|
|
{EventType::CongestionTrafficLighterThanNormal, traffic::SpeedGroup::G5},
|
|
{EventType::CongestionTrafficMuchHeavierThanNormal, traffic::SpeedGroup::G3},
|
|
{EventType::CongestionTrafficProblem, traffic::SpeedGroup::G3}, // TODO or G2? Unquantified, below normal
|
|
// TODO Construction* (not in enum yet)
|
|
/*
|
|
* Some delay types have a duration which depends on the route. This is better expressed as a
|
|
* speed group, although the mapping may be somewhat arbitrary and may need to be corrected.
|
|
*/
|
|
{EventType::DelayDelay, traffic::SpeedGroup::G2},
|
|
{EventType::DelayDelayPossible, traffic::SpeedGroup::G3},
|
|
{EventType::DelayLongDelay, traffic::SpeedGroup::G1},
|
|
{EventType::DelayVeryLongDelay, traffic::SpeedGroup::G0},
|
|
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
|
|
// TODO complete Restriction* (not in enum yet)
|
|
{EventType::RestrictionBlocked, traffic::SpeedGroup::TempBlock},
|
|
{EventType::RestrictionBlockedAhead, traffic::SpeedGroup::TempBlock},
|
|
//{EventType::RestrictionCarriagewayBlocked, traffic::SpeedGroup::TempBlock}, // TODO FIXME other carriageways may still be open
|
|
//{EventType::RestrictionCarriagewayClosed, traffic::SpeedGroup::TempBlock}, // TODO FIXME other carriageways may still be open
|
|
{EventType::RestrictionClosed, traffic::SpeedGroup::TempBlock},
|
|
{EventType::RestrictionClosedAhead, traffic::SpeedGroup::TempBlock},
|
|
{EventType::RestrictionEntryBlocked, traffic::SpeedGroup::TempBlock},
|
|
{EventType::RestrictionExitBlocked, traffic::SpeedGroup::TempBlock},
|
|
{EventType::RestrictionRampBlocked, traffic::SpeedGroup::TempBlock},
|
|
{EventType::RestrictionRampClosed, traffic::SpeedGroup::TempBlock},
|
|
{EventType::RestrictionSpeedLimit, traffic::SpeedGroup::G4},
|
|
// TODO Security*, Transport*, Weather* (not in enum yet)
|
|
};
|
|
|
|
// none of the currently define events imply an explicit maxspeed
|
|
#if 0
|
|
const std::map<EventType, uint8_t> kEventMaxspeedMap{
|
|
// TODO Activity*, Authority*, Carpool* (not in enum yet)
|
|
// TODO Construction* (not in enum yet)
|
|
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
|
|
// TODO complete Restriction* (not in enum yet)
|
|
// TODO Security*, Transport*, Weather* (not in enum yet)
|
|
};
|
|
#endif
|
|
|
|
const std::map<EventType, uint16_t> kEventDelayMap{
|
|
// TODO Activity*, Authority*, Carpool* (not in enum yet)
|
|
// TODO Construction* (not in enum yet)
|
|
//{EventType::DelayDelay, }, // mapped to speed group
|
|
//{EventType::DelayDelayPossible, }, // mapped to speed group
|
|
//{EventType::DelayLongDelay, }, // mapped to speed group
|
|
{EventType::DelaySeveralHours, 150}, // assumption: 2.5 hours
|
|
{EventType::DelayUncertainDuration, 60}, // assumption: 1 hour
|
|
//{EventType::DelayVeryLongDelay, }, // mapped to speed group
|
|
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
|
|
// TODO complete Restriction* (not in enum yet)
|
|
// TODO Security*, Transport*, Weather* (not in enum yet)
|
|
};
|
|
|
|
std::optional<IsoTime> IsoTime::ParseIsoTime(std::string timeString)
|
|
{
|
|
/*
|
|
* We cannot use `std::chrono::from_stream` because it requires GCC 14+, and as of mid-2025, the
|
|
* supported development platform (Ubuntu 24.04) has GCC 13.2. Clang still does not support it.
|
|
*
|
|
* As a reasonably portable workaround, we first parse the time string into its constituent values
|
|
* using a regex, then build a `sys_seconds` instance from the values and use `clock_cast` to
|
|
* convert it to a `std::chrono::time_point<std::chrono::utc_clock>` instance, which is the data
|
|
* type we use internally.
|
|
*
|
|
* Once we have proper support for `std::chrono::from_stream` in all toolchains we support, this
|
|
* function can be rewritten accordingly. In GCC 14+, using `%FT%T%z` for the format string will
|
|
* work with all known UTC offset formats (+01, +0100 and +01:00), just like the regex does.
|
|
*/
|
|
/*
|
|
* 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;
|
|
|
|
const auto y = static_cast<std::chrono::year>(std::stoi(iso8601Matcher[1]));
|
|
const auto mo = static_cast<std::chrono::month>(std::stoi(iso8601Matcher[2]));
|
|
const auto d = static_cast<std::chrono::day>(std::stoi(iso8601Matcher[3]));
|
|
const std::chrono::hours h{std::stoi(iso8601Matcher[4]) - offset_h};
|
|
const std::chrono::minutes min{std::stoi(iso8601Matcher[5]) - offset_m};
|
|
const std::chrono::seconds s{static_cast<uint8_t>(std::stof(iso8601Matcher[6]) + 0.5f)};
|
|
|
|
std::chrono::sys_seconds sys_s = std::chrono::sys_days{y/mo/d};
|
|
sys_s = sys_s + h + min + s;
|
|
|
|
std::chrono::time_point<std::chrono::utc_clock> tp = std::chrono::clock_cast<std::chrono::utc_clock>(sys_s);
|
|
|
|
IsoTime result(tp);
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString));
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
IsoTime IsoTime::Now()
|
|
{
|
|
return IsoTime(std::chrono::utc_clock::now());
|
|
}
|
|
|
|
IsoTime::IsoTime(std::chrono::time_point<std::chrono::utc_clock> tp)
|
|
: m_tp(tp)
|
|
{}
|
|
|
|
bool IsoTime::IsPast()
|
|
{
|
|
return m_tp < std::chrono::utc_clock::now();
|
|
}\
|
|
|
|
void IsoTime::Shift(IsoTime nowRef)
|
|
{
|
|
auto const offset = std::chrono::utc_clock::now() - nowRef.m_tp;
|
|
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;
|
|
}
|
|
|
|
bool IsoTime::operator> (IsoTime & rhs)
|
|
{
|
|
return m_tp > rhs.m_tp;
|
|
}
|
|
|
|
bool operator==(TrafficImpact const & lhs, TrafficImpact const & rhs)
|
|
{
|
|
if ((lhs.m_speedGroup == traffic::SpeedGroup::TempBlock)
|
|
&& (rhs.m_speedGroup == traffic::SpeedGroup::TempBlock))
|
|
return true;
|
|
return (lhs.m_speedGroup == rhs.m_speedGroup)
|
|
&& (lhs.m_maxspeed == rhs.m_maxspeed)
|
|
&& (lhs.m_delayMins == rhs.m_delayMins);
|
|
}
|
|
|
|
bool operator==(Point const & lhs, Point const & rhs)
|
|
{
|
|
return lhs.m_coordinates == rhs.m_coordinates;
|
|
}
|
|
|
|
bool operator==(TraffLocation const & lhs, TraffLocation const & rhs)
|
|
{
|
|
return (lhs.m_from == rhs.m_from)
|
|
&& (lhs.m_at == rhs.m_at)
|
|
&& (lhs.m_via == rhs.m_via)
|
|
&& (lhs.m_notVia == rhs.m_notVia)
|
|
&& (lhs.m_to == rhs.m_to);
|
|
}
|
|
|
|
IsoTime TraffMessage::GetEffectiveExpirationTime()
|
|
{
|
|
IsoTime result = m_expirationTime;
|
|
if (m_startTime && m_startTime.value() > result)
|
|
result = m_startTime.value();
|
|
if (m_endTime && m_endTime.value() > result)
|
|
result = m_endTime.value();
|
|
return result;
|
|
}
|
|
|
|
bool TraffMessage::IsExpired(IsoTime now)
|
|
{
|
|
return GetEffectiveExpirationTime() < now;
|
|
}
|
|
|
|
std::optional<TrafficImpact> TraffMessage::GetTrafficImpact()
|
|
{
|
|
// no events, no impact
|
|
if (m_events.empty())
|
|
return std::nullopt;
|
|
|
|
// examine events
|
|
std::vector<TrafficImpact> impacts;
|
|
for (auto event : m_events)
|
|
{
|
|
TrafficImpact impact;
|
|
|
|
if (auto it = kEventSpeedGroupMap.find(event.m_type); it != kEventSpeedGroupMap.end())
|
|
impact.m_speedGroup = it->second;
|
|
|
|
if (event.m_speed)
|
|
impact.m_maxspeed = event.m_speed.value();
|
|
// TODO if no explicit speed given, look up in kEventMaxspeedMap (once we have entries)
|
|
|
|
if (event.m_class == EventClass::Delay
|
|
&& event.m_type != EventType::DelayClearance
|
|
&& event.m_type != EventType::DelayForecastWithdrawn
|
|
&& event.m_type != EventType::DelaySeveralHours
|
|
&& event.m_type != EventType::DelayUncertainDuration
|
|
&& event.m_qDurationMins)
|
|
impact.m_delayMins = event.m_qDurationMins.value();
|
|
else if (auto it = kEventDelayMap.find(event.m_type); it != kEventDelayMap.end())
|
|
impact.m_delayMins = it->second;
|
|
|
|
// TempBlock overrules everything else, return immediately
|
|
if (impact.m_speedGroup == traffic::SpeedGroup::TempBlock)
|
|
return impact;
|
|
// if there is no actual impact, discard
|
|
if ((impact.m_maxspeed < kMaxspeedNone)
|
|
|| (impact.m_delayMins > 0)
|
|
|| (impact.m_speedGroup != traffic::SpeedGroup::Unknown))
|
|
impacts.push_back(impact);
|
|
}
|
|
|
|
if (impacts.empty())
|
|
return std::nullopt;
|
|
|
|
TrafficImpact result;
|
|
for (auto impact : impacts)
|
|
{
|
|
ASSERT(impact.m_speedGroup != traffic::SpeedGroup::TempBlock, ("Got SpeedGroup::TempBlock, which should not happen at this stage"));
|
|
if (result.m_speedGroup == traffic::SpeedGroup::Unknown)
|
|
result.m_speedGroup = impact.m_speedGroup;
|
|
// TempBlock cannot occur here, so we can do just a simple comparison
|
|
else if ((impact.m_speedGroup != traffic::SpeedGroup::Unknown) && (impact.m_speedGroup < result.m_speedGroup))
|
|
result.m_speedGroup = impact.m_speedGroup;
|
|
|
|
if (impact.m_maxspeed < result.m_maxspeed)
|
|
result.m_maxspeed = impact.m_maxspeed;
|
|
|
|
if (impact.m_delayMins > result.m_delayMins)
|
|
result.m_delayMins = impact.m_delayMins;
|
|
}
|
|
if ((result.m_maxspeed < kMaxspeedNone)
|
|
|| (result.m_delayMins > 0)
|
|
|| (result.m_speedGroup != traffic::SpeedGroup::Unknown))
|
|
return result;
|
|
else
|
|
// should never happen, unless we have a bug somewhere
|
|
return std::nullopt;
|
|
}
|
|
|
|
void TraffMessage::ShiftTimestamps()
|
|
{
|
|
IsoTime nowRef = m_updateTime;
|
|
m_receiveTime.Shift(nowRef);
|
|
m_updateTime.Shift(nowRef);
|
|
m_expirationTime.Shift(nowRef);
|
|
if (m_startTime)
|
|
m_startTime.value().Shift(nowRef);
|
|
if (m_endTime)
|
|
m_endTime.value().Shift(nowRef);
|
|
}
|
|
|
|
void MergeMultiMwmColoring(const MultiMwmColoring & delta, MultiMwmColoring & target)
|
|
{
|
|
// for each mwm in delta
|
|
for (auto [mwmId, coloring] : delta)
|
|
// if target contains mwm
|
|
if (auto target_it = target.find(mwmId); target_it != target.end())
|
|
// for each segment in delta[mwm] (coloring)
|
|
for (auto [rsid, sg] : coloring)
|
|
// if target[mwm] contains segment
|
|
if (auto c_it = target_it->second.find(rsid) ; c_it != target_it->second.end())
|
|
{
|
|
// if delta overrules target (target is Unknown, delta is TempBlock or delta is slower than target)
|
|
if ((sg == traffic::SpeedGroup::TempBlock)
|
|
|| (c_it->second == traffic::SpeedGroup::Unknown) || (sg < c_it->second))
|
|
target_it->second[rsid] = sg;
|
|
}
|
|
else
|
|
// if target[mwm] does not contain segment, add speed group
|
|
target_it->second[rsid] = sg;
|
|
else
|
|
// if target does not contain mwm, add coloring
|
|
target[mwmId] = coloring;
|
|
}
|
|
|
|
/*
|
|
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.m_tm, "%Y-%m-%d %H:%M:%S %z");
|
|
// %FT%T%z
|
|
os << std::format("{0:%F} {0:%T} {0:%z}", time.m_tp);
|
|
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(TrafficImpact impact)
|
|
{
|
|
std::ostringstream os;
|
|
os << "TrafficImpact { ";
|
|
os << "speedGroup: " << DebugPrint(impact.m_speedGroup) << ", ";
|
|
os << "maxspeed: " << (impact.m_maxspeed == kMaxspeedNone ? "none" : std::to_string(impact.m_maxspeed)) << ", ";
|
|
os << "delayMins: " << impact.m_delayMins;
|
|
os << " }";
|
|
return os.str();
|
|
}
|
|
|
|
std::string DebugPrint(Point point)
|
|
{
|
|
std::ostringstream os;
|
|
os << "Point { ";
|
|
os << "coordinates: " << DebugPrint(point.m_coordinates) << ", ";
|
|
os << "distance: " << (point.m_distance ? std::to_string(point.m_distance.value()) : "nullopt") << ", ";
|
|
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: " << DebugPrint(location.m_ramps);
|
|
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") << ", ";
|
|
os << "q_duration: "
|
|
<< (event.m_qDurationMins
|
|
? std::format("{:1d}:{:02d}", event.m_qDurationMins.value() / 60, event.m_qDurationMins.value() % 60)
|
|
: "nullopt")
|
|
<< ", ";
|
|
// TODO other quantifiers
|
|
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
|