mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 13:03:36 +00:00
656 lines
28 KiB
C++
656 lines
28 KiB
C++
#include "traffxml/traff_decoder.hpp"
|
||
|
||
//#include "traffxml/traff_foo.hpp"
|
||
|
||
#include "openlr/decoded_path.hpp"
|
||
#include "openlr/openlr_decoder.hpp"
|
||
#include "openlr/openlr_model.hpp"
|
||
|
||
#include "routing/async_router.hpp"
|
||
#include "routing/checkpoints.hpp"
|
||
#include "routing/edge_estimator.hpp"
|
||
#include "routing/maxspeeds.hpp"
|
||
#include "routing/route.hpp"
|
||
#include "routing/router_delegate.hpp"
|
||
|
||
#include "routing_common/maxspeed_conversion.hpp"
|
||
|
||
#include "storage/routing_helpers.hpp"
|
||
|
||
#include "traffic/traffic_cache.hpp"
|
||
|
||
namespace traffxml
|
||
{
|
||
// Number of worker threads for the OpenLR decoder
|
||
/*
|
||
* TODO how to determine the best number of worker threads?
|
||
* One per direction? Does not seem to help with bidirectional locations (two reference points).
|
||
* One per segment (from–via/from–at, via–to/at–to)? Not yet tested.
|
||
* Otherwise there is little to be gained, as we decode messages one at a time.
|
||
*/
|
||
auto constexpr kNumDecoderThreads = 1;
|
||
|
||
// Timeout for the router in seconds, used by RoutingTraffDecoder
|
||
// TODO set to a sensible value
|
||
auto constexpr kRouterTimeoutSec = 30;
|
||
|
||
TraffDecoder::TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
|
||
const CountryParentNameGetterFn & countryParentNameGetter,
|
||
std::map<std::string, TraffMessage> & messageCache)
|
||
: m_dataSource(dataSource)
|
||
, m_countryInfoGetterFn(countryInfoGetter)
|
||
, m_countryParentNameGetterFn(countryParentNameGetter)
|
||
, m_messageCache(messageCache)
|
||
{}
|
||
|
||
void TraffDecoder::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded)
|
||
{
|
||
for (auto dit = decoded.begin(); dit != decoded.end(); dit++)
|
||
for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++)
|
||
{
|
||
/*
|
||
* Consolidate TrafficImpact into a single SpeedGroup per segment.
|
||
* Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate
|
||
* the rest.
|
||
*/
|
||
traffic::SpeedGroup sg = impact.m_speedGroup;
|
||
/*
|
||
* TODO also process m_delayMins if greater than zero.
|
||
* This would require a separate pass over all edges, calculating length,
|
||
* total (normal) travel time (length / maxspeed), then a speed group based on
|
||
* (normal_travel_time / delayed_travel_time) – which is the same as the ratio between
|
||
* reduced and normal speed. That would give us a third potential speed group.
|
||
*/
|
||
if ((sg != traffic::SpeedGroup::TempBlock) && (impact.m_maxspeed != traffxml::kMaxspeedNone))
|
||
{
|
||
auto const handle = m_dataSource.GetMwmHandleById(dit->first);
|
||
auto const speeds = routing::LoadMaxspeeds(handle);
|
||
if (speeds)
|
||
{
|
||
traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown;
|
||
auto const speed = speeds->GetMaxspeed(cit->first.GetFid());
|
||
auto const speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection);
|
||
if (speedKmPH != routing::kInvalidSpeed)
|
||
{
|
||
fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.m_maxspeed * 100.0f / speedKmPH);
|
||
if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg))
|
||
sg = fromMaxspeed;
|
||
}
|
||
}
|
||
/*
|
||
* TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else)
|
||
* If no maxspeed or delay is set, just give out speed groups.
|
||
* Else, examine segments, length, normal travel time, travel time considering impact, and
|
||
* determine the closest matching speed group.
|
||
*/
|
||
}
|
||
// TODO process all TrafficImpact fields and determine the speed group based on that
|
||
cit->second = sg;
|
||
}
|
||
}
|
||
|
||
void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message)
|
||
{
|
||
if (!message.m_location)
|
||
return;
|
||
// Decode events into consolidated traffic impact
|
||
std::optional<traffxml::TrafficImpact> impact = message.GetTrafficImpact();
|
||
|
||
LOG(LINFO, (" Impact: ", impact));
|
||
|
||
// Skip further processing if there is no impact
|
||
if (!impact)
|
||
return;
|
||
|
||
traffxml::MultiMwmColoring decoded;
|
||
|
||
auto it = m_messageCache.find(message.m_id);
|
||
if ((it != m_messageCache.end())
|
||
&& !it->second.m_decoded.empty()
|
||
&& (it->second.m_location == message.m_location))
|
||
{
|
||
// cache already has a message with reusable location
|
||
|
||
LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache"));
|
||
|
||
std::optional<traffxml::TrafficImpact> cachedImpact = it->second.GetTrafficImpact();
|
||
if (cachedImpact.has_value() && cachedImpact.value() == impact.value())
|
||
{
|
||
LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring"));
|
||
|
||
// same impact, m_decoded can be reused altogether
|
||
message.m_decoded = it->second.m_decoded;
|
||
return;
|
||
}
|
||
else
|
||
decoded = it->second.m_decoded;
|
||
}
|
||
else
|
||
DecodeLocation(message, decoded);
|
||
|
||
if (impact)
|
||
{
|
||
ApplyTrafficImpact(impact.value(), decoded);
|
||
std::swap(message.m_decoded, decoded);
|
||
}
|
||
}
|
||
|
||
OpenLrV3TraffDecoder::OpenLrV3TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
|
||
const CountryParentNameGetterFn & countryParentNameGetter,
|
||
std::map<std::string, TraffMessage> & messageCache)
|
||
: TraffDecoder(dataSource, countryInfoGetter, countryParentNameGetter, messageCache)
|
||
, m_openLrDecoder(dataSource, countryParentNameGetter)
|
||
{}
|
||
|
||
openlr::FunctionalRoadClass OpenLrV3TraffDecoder::GetRoadClassFrc(std::optional<RoadClass> & roadClass)
|
||
{
|
||
if (!roadClass)
|
||
return openlr::FunctionalRoadClass::NotAValue;
|
||
switch (roadClass.value())
|
||
{
|
||
case RoadClass::Motorway: return openlr::FunctionalRoadClass::FRC0;
|
||
case RoadClass::Trunk: return openlr::FunctionalRoadClass::FRC0;
|
||
case RoadClass::Primary: return openlr::FunctionalRoadClass::FRC1;
|
||
case RoadClass::Secondary: return openlr::FunctionalRoadClass::FRC2;
|
||
case RoadClass::Tertiary: return openlr::FunctionalRoadClass::FRC3;
|
||
/*
|
||
* TODO Revisit FRC for Other.
|
||
* Other corresponds to FRC4–7.
|
||
* FRC4 matches secondary/tertiary (zero score) and anything below (full score).
|
||
* FRC5–7 match anything below tertiary (full score); secondary/tertiary never match.
|
||
* Primary and above never matches any of these FRCs.
|
||
*/
|
||
case RoadClass::Other: return openlr::FunctionalRoadClass::FRC4;
|
||
}
|
||
UNREACHABLE();
|
||
}
|
||
|
||
// TODO tweak formula based on FRC, FOW and direct distance (lower FRC roads may have more and sharper turns)
|
||
uint32_t OpenLrV3TraffDecoder::GuessDnp(Point & p1, Point & p2)
|
||
{
|
||
// direct distance
|
||
double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_coordinates),
|
||
mercator::FromLatLon(p2.m_coordinates));
|
||
|
||
/*
|
||
* Acceptance boundaries for candidate paths are currently:
|
||
*
|
||
* for `openlr::LinearSegmentSource::FromLocationReferenceTag`, 0.6 to ~1.67 (i.e. 1/0.6) times
|
||
* the direct distance,
|
||
*
|
||
* for `openlr::LinearSegmentSource::FromCoordinatesTag`, 0.25 to 4 times the direct distance.
|
||
*
|
||
* A tolerance factor of 1/0.6 is the maximum for which direct distance would be accepted in all
|
||
* cases, with an upper boundary of at least ~2.78 times the direct distance. However, this may
|
||
* cause the actual distance to be overestimated and an incorrect route chosen as a result, as
|
||
* path candidates are scored based on the match between DNP and their length.
|
||
* Also, since we use `openlr::LinearSegmentSource::FromCoordinatesTag`, acceptance limits are
|
||
* much wider than that.
|
||
* In practice, the shortest route from one valley to the next in a mountain area is seldom more
|
||
* than 3 times the direct distance, based on a brief examination. This would be even within the
|
||
* limits of direct distance, hence we do not need a large correction factor for this scenario.
|
||
*
|
||
* Candidate values:
|
||
* 1.66 (1/0.6) – upper boundary for direct distance to be just within the most stringent limits
|
||
* 1.41 (2^0.5) – ratio between two sides of a square and its diagonal
|
||
* 1.3 – close to the square root of 1.66 (halfway between 1 and 1.66)
|
||
* 1.19 – close to the square root of 1.41
|
||
* 1 – direct distance unmodified
|
||
*/
|
||
double dist = doe * 1.19f;
|
||
|
||
// if we have kilometric points, calculate nominal distance as the difference between them
|
||
if (p1.m_distance && p2.m_distance)
|
||
{
|
||
LOG(LINFO, ("Both points have distance, calculating nominal difference"));
|
||
float nominalDist = (p1.m_distance.value() - p2.m_distance.value()) * 1000.0;
|
||
if (nominalDist < 0)
|
||
nominalDist *= -1;
|
||
/*
|
||
* Plausibility check for nominal distance, as kilometric points along the route may not be
|
||
* continuous: discard if shorter than direct distance (geometrically impossible) or if longer
|
||
* than 4 times direct distance (somewhat arbitrary, based on the OpenLR acceptance limit for
|
||
* `openlr::LinearSegmentSource::FromCoordinatesTag`, as well as real-world observations of
|
||
* distances between two adjacent mountain valleys, which are up to roughly 3 times the direct
|
||
* distance). If nominal distance is outside these boundaries, discard it and use `dist` (direct
|
||
* distance with a tolerance factor).
|
||
*/
|
||
if ((nominalDist >= doe) && (nominalDist <= doe * 4))
|
||
dist = nominalDist;
|
||
else
|
||
LOG(LINFO, ("Nominal distance:", nominalDist, "direct distance:", doe, "– discarding"));
|
||
}
|
||
return dist + 0.5f;
|
||
}
|
||
|
||
openlr::LocationReferencePoint OpenLrV3TraffDecoder::PointToLrp(Point & point)
|
||
{
|
||
openlr::LocationReferencePoint result;
|
||
result.m_latLon = ms::LatLon(point.m_coordinates.m_lat, point.m_coordinates.m_lon);
|
||
return result;
|
||
}
|
||
|
||
openlr::LinearLocationReference OpenLrV3TraffDecoder::TraffLocationToLinearLocationReference(TraffLocation & location, bool backwards)
|
||
{
|
||
openlr::LinearLocationReference locationReference;
|
||
locationReference.m_points.clear();
|
||
std::vector<Point> points;
|
||
if (location.m_from)
|
||
points.push_back(location.m_from.value());
|
||
if (location.m_at)
|
||
points.push_back(location.m_at.value());
|
||
else if (location.m_via)
|
||
points.push_back(location.m_via.value());
|
||
if (location.m_to)
|
||
points.push_back(location.m_to.value());
|
||
if (backwards)
|
||
std::reverse(points.begin(), points.end());
|
||
// m_notVia is ignored as OpenLR does not support this functionality.
|
||
CHECK_GREATER(points.size(), 1, ("At least two reference points must be given"));
|
||
for (size_t i = 0; i < points.size(); i++)
|
||
{
|
||
openlr::LocationReferencePoint lrp = PointToLrp(points[i]);
|
||
lrp.m_functionalRoadClass = GetRoadClassFrc(location.m_roadClass);
|
||
if (location.m_ramps.value_or(traffxml::Ramps::None) != traffxml::Ramps::None)
|
||
lrp.m_formOfWay = openlr::FormOfWay::Sliproad;
|
||
if (i < points.size() - 1)
|
||
{
|
||
lrp.m_distanceToNextPoint
|
||
= GuessDnp(points[i], points[i + 1]);
|
||
/*
|
||
* Somewhat hackish. LFRCNP is evaluated by the same function as FRC and the candidate is
|
||
* used or discarded based on whether a score was returned or not (the score itself is not
|
||
* used for LFRCNP). However, this means we can use FRC as LFRCNP.
|
||
*/
|
||
lrp.m_lfrcnp = GetRoadClassFrc(location.m_roadClass);
|
||
}
|
||
locationReference.m_points.push_back(lrp);
|
||
}
|
||
return locationReference;
|
||
}
|
||
|
||
// TODO make segment ID in OpenLR a string value, and store messageId
|
||
std::vector<openlr::LinearSegment> OpenLrV3TraffDecoder::TraffLocationToOpenLrSegments(TraffLocation & location, std::string & messageId)
|
||
{
|
||
// Convert the location to a format understood by the OpenLR decoder.
|
||
std::vector<openlr::LinearSegment> segments;
|
||
int dirs = (location.m_directionality == Directionality::BothDirections) ? 2 : 1;
|
||
for (int dir = 0; dir < dirs; dir++)
|
||
{
|
||
openlr::LinearSegment segment;
|
||
/*
|
||
* Segment IDs are used internally by the decoder but nowhere else.
|
||
* Since we decode TraFF locations one at a time, there are at most two segments in a single
|
||
* decoder instance (one segment per direction). Therefore, a segment ID derived from the
|
||
* direction is unique within the decoder instance.
|
||
*/
|
||
segment.m_segmentId = dir;
|
||
segment.m_messageId = messageId;
|
||
/*
|
||
* Segments generated from coordinates can have any number of points. Each point, except for
|
||
* the last point, must indicate the distance to the next point. Line properties (functional
|
||
* road class (FRC), form of way, bearing) or path properties other than distance to next point
|
||
* (lowest FRC to next point, againstDrivingDirection) are ignored.
|
||
* Segment length is never evaluated.
|
||
* TODO update OpenLR decoder to make all line and path properties optional.
|
||
*/
|
||
segment.m_source = openlr::LinearSegmentSource::FromCoordinatesTag;
|
||
segment.m_locationReference = TraffLocationToLinearLocationReference(location, dir == 0 ? false : true);
|
||
|
||
segments.push_back(segment);
|
||
}
|
||
return segments;
|
||
}
|
||
|
||
/*
|
||
* TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations).
|
||
* Decoding message by message kind of defeats the purpose.
|
||
* But after decoding the location, we need to examine the map features we got in order to
|
||
* determine the speed groups, thus we may need to decode one by one (TBD).
|
||
* If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path
|
||
* structures to accept a TraFF message ID (string) rather than an integer, or derive
|
||
* [partner] segment IDs from TraFF message IDs.
|
||
*/
|
||
void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded)
|
||
{
|
||
ASSERT(message.m_location, ("Message has no location"));
|
||
decoded.clear();
|
||
|
||
// Convert the location to a format understood by the OpenLR decoder.
|
||
std::vector<openlr::LinearSegment> segments
|
||
= TraffLocationToOpenLrSegments(message.m_location.value(), message.m_id);
|
||
|
||
for (auto segment : segments)
|
||
{
|
||
LOG(LINFO, (" Segment:", segment.m_segmentId));
|
||
for (int i = 0; i < segment.m_locationReference.m_points.size(); i++)
|
||
{
|
||
LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon));
|
||
if (i < segment.m_locationReference.m_points.size() - 1)
|
||
{
|
||
LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass));
|
||
LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Decode the location into a path on the map.
|
||
// One path per segment
|
||
std::vector<openlr::DecodedPath> paths(segments.size());
|
||
m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths);
|
||
|
||
for (size_t i = 0; i < paths.size(); i++)
|
||
for (size_t j = 0; j < paths[i].m_path.size(); j++)
|
||
{
|
||
auto fid = paths[i].m_path[j].GetFeatureId().m_index;
|
||
auto segment = paths[i].m_path[j].GetSegId();
|
||
uint8_t direction = paths[i].m_path[j].IsForward() ?
|
||
traffic::TrafficInfo::RoadSegmentId::kForwardDirection :
|
||
traffic::TrafficInfo::RoadSegmentId::kReverseDirection;
|
||
decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown;
|
||
}
|
||
}
|
||
|
||
class TraffEstimator final : public routing::EdgeEstimator
|
||
{
|
||
public:
|
||
TraffEstimator(DataSource * dataSourcePtr, std::shared_ptr<routing::NumMwmIds> numMwmIds,
|
||
double maxWeightSpeedKMpH,
|
||
routing::SpeedKMpH const & offroadSpeedKMpH)
|
||
: EdgeEstimator(maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds)
|
||
{
|
||
}
|
||
|
||
// EdgeEstimator overrides:
|
||
double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const override;
|
||
double GetUTurnPenalty(Purpose /* purpose */) const override
|
||
{
|
||
// Adds 2 minutes penalty for U-turn. The value is quite arbitrary
|
||
// and needs to be properly selected after a number of real-world
|
||
// experiments.
|
||
return 2 * 60; // seconds
|
||
}
|
||
|
||
double GetFerryLandingPenalty(Purpose purpose) const override
|
||
{
|
||
switch (purpose)
|
||
{
|
||
case Purpose::Weight: return 20 * 60; // seconds
|
||
case Purpose::ETA: return 20 * 60; // seconds
|
||
}
|
||
UNREACHABLE();
|
||
}
|
||
};
|
||
|
||
// TODO only needed temporarily
|
||
double GetSpeedMpS(routing::EdgeEstimator::Purpose purpose, routing::Segment const & segment, routing::RoadGeometry const & road)
|
||
{
|
||
routing::SpeedKMpH const & speed = road.GetSpeed(segment.IsForward());
|
||
double const speedMpS = measurement_utils::KmphToMps(purpose == routing::EdgeEstimator::Purpose::Weight ? speed.m_weight : speed.m_eta);
|
||
ASSERT_GREATER(speedMpS, 0.0, (segment));
|
||
return speedMpS;
|
||
}
|
||
|
||
double TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const
|
||
{
|
||
double result = road.GetDistance(segment.GetSegmentIdx()) / GetSpeedMpS(purpose, segment, road);
|
||
|
||
return result;
|
||
}
|
||
|
||
RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn,
|
||
routing::TCountryFileFn const & countryFileFn,
|
||
routing::CountryRectFn const & countryRectFn,
|
||
std::shared_ptr<routing::NumMwmIds> numMwmIds,
|
||
std::unique_ptr<m4::Tree<routing::NumMwmId>> numMwmTree,
|
||
DataSource & dataSource)
|
||
: routing::IndexRouter(routing::VehicleType::Car /* VehicleType vehicleType */,
|
||
false /* bool loadAltitudes */,
|
||
countryParentNameGetterFn,
|
||
countryFileFn,
|
||
countryRectFn,
|
||
numMwmIds,
|
||
std::move(numMwmTree),
|
||
//std::nullopt /* std::optional<traffic::TrafficCache> const & trafficCache */,
|
||
std::make_shared<TraffEstimator>(&dataSource, numMwmIds, 120.0f /* maxWeighSpeedKMpH */,
|
||
routing::SpeedKMpH(0.01 /* weight */, routing::kNotUsed /* eta */) /* offroadSpeedKMpH */),
|
||
dataSource)
|
||
//, m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource)) // TODO we don’t need directions, can we disable that?
|
||
{}
|
||
|
||
RoutingTraffDecoder::RoutingTraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
|
||
const CountryParentNameGetterFn & countryParentNameGetter,
|
||
std::map<std::string, TraffMessage> & messageCache)
|
||
: TraffDecoder(dataSource, countryInfoGetter, countryParentNameGetter, messageCache)
|
||
{
|
||
InitRouter();
|
||
}
|
||
|
||
bool RoutingTraffDecoder::InitRouter()
|
||
{
|
||
if (m_router)
|
||
return true;
|
||
|
||
// code mostly from RoutingManager::SetRouterImpl(RouterType)
|
||
/*
|
||
* RoutingManager::SetRouterImpl(RouterType) calls m_delegate.RegisterCountryFilesOnRoute(numMwmIds).
|
||
* m_delegate is the framework, and the routine cycles through the countries in storage.
|
||
* As we don’t have access to storage, we get our country files from the data source.
|
||
*/
|
||
std::vector<std::shared_ptr<MwmInfo>> mwmsInfo;
|
||
m_dataSource.GetMwmsInfo(mwmsInfo);
|
||
for (auto mwmInfo : mwmsInfo)
|
||
m_numMwmIds->RegisterFile(mwmInfo->GetLocalFile().GetCountryFile());
|
||
|
||
if (m_numMwmIds->IsEmpty())
|
||
return false;
|
||
|
||
auto const countryFileGetter = [this](m2::PointD const & p) -> std::string {
|
||
// TODO (@gorshenin): fix CountryInfoGetter to return CountryFile
|
||
// instances instead of plain strings.
|
||
return m_countryInfoGetterFn().GetRegionCountryId(p);
|
||
};
|
||
|
||
auto const getMwmRectByName = [this](std::string const & countryId) -> m2::RectD {
|
||
return m_countryInfoGetterFn().GetLimitRectForLeaf(countryId);
|
||
};
|
||
|
||
m_router =
|
||
make_unique<RoutingTraffDecoder::DecoderRouter>(m_countryParentNameGetterFn,
|
||
countryFileGetter, getMwmRectByName, m_numMwmIds,
|
||
routing::MakeNumMwmTree(*m_numMwmIds, m_countryInfoGetterFn()),
|
||
m_dataSource);
|
||
|
||
return true;
|
||
}
|
||
|
||
// Copied from AsyncRouter
|
||
// static
|
||
void RoutingTraffDecoder::LogCode(routing::RouterResultCode code, double const elapsedSec)
|
||
{
|
||
switch (code)
|
||
{
|
||
case routing::RouterResultCode::StartPointNotFound:
|
||
LOG(LWARNING, ("Can't find start or end node"));
|
||
break;
|
||
case routing::RouterResultCode::EndPointNotFound:
|
||
LOG(LWARNING, ("Can't find end point node"));
|
||
break;
|
||
case routing::RouterResultCode::PointsInDifferentMWM:
|
||
LOG(LWARNING, ("Points are in different MWMs"));
|
||
break;
|
||
case routing::RouterResultCode::RouteNotFound:
|
||
LOG(LWARNING, ("Route not found"));
|
||
break;
|
||
case routing::RouterResultCode::RouteFileNotExist:
|
||
LOG(LWARNING, ("There is no routing file"));
|
||
break;
|
||
case routing::RouterResultCode::NeedMoreMaps:
|
||
LOG(LINFO,
|
||
("Routing can find a better way with additional maps, elapsed seconds:", elapsedSec));
|
||
break;
|
||
case routing::RouterResultCode::Cancelled:
|
||
LOG(LINFO, ("Route calculation cancelled, elapsed seconds:", elapsedSec));
|
||
break;
|
||
case routing::RouterResultCode::NoError:
|
||
LOG(LINFO, ("Route found, elapsed seconds:", elapsedSec));
|
||
break;
|
||
case routing::RouterResultCode::NoCurrentPosition:
|
||
LOG(LINFO, ("No current position"));
|
||
break;
|
||
case routing::RouterResultCode::InconsistentMWMandRoute:
|
||
LOG(LINFO, ("Inconsistent mwm and route"));
|
||
break;
|
||
case routing::RouterResultCode::InternalError:
|
||
LOG(LINFO, ("Internal error"));
|
||
break;
|
||
case routing::RouterResultCode::FileTooOld:
|
||
LOG(LINFO, ("File too old"));
|
||
break;
|
||
case routing::RouterResultCode::IntermediatePointNotFound:
|
||
LOG(LWARNING, ("Can't find intermediate point node"));
|
||
break;
|
||
case routing::RouterResultCode::TransitRouteNotFoundNoNetwork:
|
||
LOG(LWARNING, ("No transit route is found because there's no transit network in the mwm of "
|
||
"the route point"));
|
||
break;
|
||
case routing::RouterResultCode::TransitRouteNotFoundTooLongPedestrian:
|
||
LOG(LWARNING, ("No transit route is found because pedestrian way is too long"));
|
||
break;
|
||
case routing::RouterResultCode::RouteNotFoundRedressRouteError:
|
||
LOG(LWARNING, ("Route not found because of a redress route error"));
|
||
break;
|
||
case routing::RouterResultCode::HasWarnings:
|
||
LOG(LINFO, ("Route has warnings, elapsed seconds:", elapsedSec));
|
||
break;
|
||
}
|
||
}
|
||
|
||
void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & message,
|
||
traffxml::MultiMwmColoring & decoded, bool backwards)
|
||
{
|
||
bool adjustToPrevRoute = false; // calculate a fresh route, no adjustments to previous one
|
||
uint64_t routeId = 0; // used in callbacks to identify the route, we might not need it at all
|
||
std::string routerName; // set later
|
||
|
||
std::vector<m2::PointD> points;
|
||
if (message.m_location.value().m_from)
|
||
points.push_back(mercator::FromLatLon(message.m_location.value().m_from.value().m_coordinates));
|
||
if (message.m_location.value().m_at)
|
||
points.push_back(mercator::FromLatLon(message.m_location.value().m_at.value().m_coordinates));
|
||
else if (message.m_location.value().m_via)
|
||
points.push_back(mercator::FromLatLon(message.m_location.value().m_via.value().m_coordinates));
|
||
if (message.m_location.value().m_to)
|
||
points.push_back(mercator::FromLatLon(message.m_location.value().m_to.value().m_coordinates));
|
||
if (backwards)
|
||
std::reverse(points.begin(), points.end());
|
||
// m_notVia is ignored as OpenLR does not support this functionality.
|
||
CHECK_GREATER(points.size(), 1, ("At least two reference points must be given"));
|
||
|
||
/*
|
||
* startDirection is the direction of travel at start. Can be m2::PointD::Zero() to ignore
|
||
* direction, or PositionAccumulator::GetDirection(), which basically returns the difference
|
||
* between the last position and an earlier one (offset between two points from which the
|
||
* direction of travel can be inferred).
|
||
*
|
||
* For our purposes, points[1] - points[0] would be as close as we could get to a start direction.
|
||
* This would be accurate on very straight roads, less accurate on not-so-straight ones. However,
|
||
* even on a near-straight road, the standard router (with the default EdgeEstimator) seemed quite
|
||
* unimpressed by the direction and insisted on starting off on the carriageway closest to the
|
||
* start point and sending us on a huge detour, instead of taking the direct route on the opposite
|
||
* carriageway.
|
||
*/
|
||
m2::PointD startDirection = m2::PointD::Zero();
|
||
|
||
routing::Checkpoints checkpoints(std::move(points));
|
||
|
||
/*
|
||
* This code is mostly lifted from:
|
||
* - AsyncRouter::CalculateRoute(Checkpoints const &, m2::PointD const &, bool, ReadyCallbackOwnership const &,
|
||
* NeedMoreMapsCallback const &, RemoveRouteCallback const &, ProgressCallback, uint32_t)
|
||
* - AsyncRouter::CalculateRoute()
|
||
*/
|
||
|
||
// AsyncRouter::CalculateRoute(with args)
|
||
/*
|
||
* AsyncRouter::CalculateRoute() has a `DelegateProxy`, which is private. We just need the return
|
||
* value of GetDelegate(), which is a `routing::RouterDelegate`, so use that instead. We don’t
|
||
* nedd any of the callbacks, therefore we don’t set them.
|
||
*/
|
||
routing::RouterDelegate delegate;
|
||
delegate.SetTimeout(kRouterTimeoutSec);
|
||
|
||
// AsyncRouter::CalculateRoute()
|
||
if (!m_router && !InitRouter())
|
||
return;
|
||
|
||
routerName = m_router->GetName();
|
||
// TODO is that for following a track? If so, can we use that with just 2–3 reference points?
|
||
//router->SetGuides(std::move(m_guides));
|
||
//m_guides.clear();
|
||
|
||
auto route = std::make_shared<routing::Route>(m_router->GetName(), routeId);
|
||
routing::RouterResultCode code;
|
||
|
||
base::Timer timer;
|
||
double elapsedSec = 0.0;
|
||
|
||
try
|
||
{
|
||
LOG(LINFO, ("Calculating the route of direct length", checkpoints.GetSummaryLengthBetweenPointsMeters(),
|
||
"m. checkpoints:", checkpoints, "startDirection:", startDirection, "router name:", m_router->GetName()));
|
||
|
||
// Run basic request.
|
||
code = m_router->CalculateRoute(checkpoints, startDirection, adjustToPrevRoute,
|
||
delegate, *route);
|
||
m_router->SetGuides({});
|
||
elapsedSec = timer.ElapsedSeconds(); // routing time
|
||
LogCode(code, elapsedSec);
|
||
LOG(LINFO, ("ETA:", route->GetTotalTimeSec(), "sec."));
|
||
}
|
||
catch (RootException const & e)
|
||
{
|
||
code = routing::RouterResultCode::InternalError;
|
||
LOG(LERROR, ("Exception happened while calculating route:", e.Msg()));
|
||
return;
|
||
}
|
||
|
||
if (code == routing::RouterResultCode::NoError)
|
||
{
|
||
LOG(LINFO, ("Decoded route:"));
|
||
for (auto rsegment : route->GetRouteSegments())
|
||
{
|
||
routing::Segment segment = rsegment.GetSegment();
|
||
if (segment.GetMwmId() == routing::kFakeNumMwmId)
|
||
{
|
||
LOG(LINFO, (" Fake segment:", segment));
|
||
continue;
|
||
}
|
||
|
||
LOG(LINFO, (" segment:", segment));
|
||
|
||
auto const countryFile = m_numMwmIds->GetFile(segment.GetMwmId());
|
||
MwmSet::MwmId mwmId = m_dataSource.GetMwmIdByCountryFile(countryFile);
|
||
|
||
auto const fid = segment.GetFeatureId();
|
||
auto const sid = segment.GetSegmentIdx();
|
||
uint8_t direction = segment.IsForward() ?
|
||
traffic::TrafficInfo::RoadSegmentId::kForwardDirection :
|
||
traffic::TrafficInfo::RoadSegmentId::kReverseDirection;
|
||
|
||
decoded[mwmId][traffic::TrafficInfo::RoadSegmentId(fid, sid, direction)] = traffic::SpeedGroup::Unknown;
|
||
}
|
||
}
|
||
}
|
||
|
||
void RoutingTraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded)
|
||
{
|
||
ASSERT(message.m_location, ("Message has no location"));
|
||
decoded.clear();
|
||
|
||
int dirs = (message.m_location.value().m_directionality == Directionality::BothDirections) ? 2 : 1;
|
||
for (int dir = 0; dir < dirs; dir++)
|
||
DecodeLocationDirection(message, decoded, dir == 0 ? false : true /* backwards */);
|
||
}
|
||
} // namespace traffxml
|