diff --git a/data/test_data/traff/LT-A5-Kaunas-Eastbound.xml b/data/test_data/traff/LT-A5-Kaunas-Eastbound.xml new file mode 100644 index 000000000..d700afd4c --- /dev/null +++ b/data/test_data/traff/LT-A5-Kaunas-Eastbound.xml @@ -0,0 +1,23 @@ + + + + + + 54.939945 23.879789 + 54.940094 23.881950 + + + + + + + + diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 7a0a4d29e..55cf13ad7 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -815,6 +815,63 @@ void RoutingTraffDecoder::AddDecodedSegment(traffxml::MultiMwmColoring & decoded decoded[mwmId][traffic::TrafficInfo::RoadSegmentId(fid, sid, direction)] = traffic::SpeedGroup::Unknown; } +void RoutingTraffDecoder::TruncateRoute(std::vector & rsegments, + routing::Checkpoints const & checkpoints) +{ + double const endWeight = rsegments.back().GetTimeFromBeginningSec(); + + // erase leading and trailing fake segments + while(!rsegments.empty() && rsegments.front().GetSegment().GetMwmId() == routing::kFakeNumMwmId) + rsegments.erase(rsegments.begin()); + while(!rsegments.empty() && rsegments.back().GetSegment().GetMwmId() == routing::kFakeNumMwmId) + rsegments.pop_back(); + + if (rsegments.size() < 2) + return; + + // Index of first segment to keep, or number of segments to truncate at start. + size_t start = 0; + // Cost saved by omitting all segments prior to `start`. + double startSaving = 0; + + // Index of last segment to keep. + size_t end = rsegments.size() - 1; + // Cost saved by omitting the last `end` segments. + double endSaving = 0; + + TruncateStart(rsegments, checkpoints, start, startSaving); + TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight); + + /* + * If start <= end, we can truncate both ends at the same time. + * Else, the segments to truncate overlap. In this case, first truncate where the saving is bigger, + * then recalculate the other end and truncate it as well. + */ + if (start <= end) + { + rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); + rsegments.erase(rsegments.begin(), rsegments.begin() + start); + } + else if (startSaving > endSaving) + { + // truncate start, then recalculate and truncate end + rsegments.erase(rsegments.begin(), rsegments.begin() + start); + end = rsegments.size() - 1; + endSaving = 0; + TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight); + rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); + } + else + { + // truncate end, then recalculate and truncate start + rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); + start = 0; + startSaving = 0; + TruncateStart(rsegments, checkpoints, start, startSaving); + rsegments.erase(rsegments.begin(), rsegments.begin() + start); + } +} + void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded, bool backwards) { @@ -909,11 +966,7 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa { std::vector rsegments(route->GetRouteSegments()); - // erase leading and trailing fake segments - while(!rsegments.empty() && rsegments.front().GetSegment().GetMwmId() == routing::kFakeNumMwmId) - rsegments.erase(rsegments.begin()); - while(!rsegments.empty() && rsegments.back().GetSegment().GetMwmId() == routing::kFakeNumMwmId) - rsegments.pop_back(); + TruncateRoute(rsegments, checkpoints); if (!backwards && message.m_location.value().m_at && !message.m_location.value().m_to) // from–at in forward direction, add last segment @@ -1134,4 +1187,38 @@ std::vector ParseRef(std::string & ref) res.push_back(curr); return res; } + +void TruncateStart(std::vector & rsegments, + routing::Checkpoints const & checkpoints, + size_t & start, double & startSaving) +{ + for (size_t i = 0; i < rsegments.size(); i++) + { + double newStartSaving = rsegments[i].GetTimeFromBeginningSec() + - (mercator::DistanceOnEarth(checkpoints.GetStart(), rsegments[i].GetJunction().GetPoint()) + * kOffroadPenalty); + if (newStartSaving > startSaving) + { + start = i + 1; // add 1 because we are ditching this segment and keeping the next one + startSaving = newStartSaving; + } + } +} + +void TruncateEnd(std::vector & rsegments, + routing::Checkpoints const & checkpoints, + size_t & end, double & endSaving, double const endWeight) +{ + for (size_t i = 0; i < rsegments.size(); i++) + { + double newEndSaving = endWeight - rsegments[i].GetTimeFromBeginningSec() + - (mercator::DistanceOnEarth(rsegments[i].GetJunction().GetPoint(), checkpoints.GetFinish()) + * kOffroadPenalty); + if (newEndSaving > endSaving) + { + end = i; + endSaving = newEndSaving; + } + } +} } // namespace traffxml diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index dbe1ab4bb..2d31abb08 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -382,6 +382,30 @@ protected: */ void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) override; + /** + * @brief Truncates the route so its endpoints best match the reference points. + * + * Leading and trailing fake segments are discarded. + * + * When building the graph, the router creates fake segments to the nearest roads. These are not + * necessarily the best for location decoding, which may result in “heads” or “tails” being added + * to the decoded location. This function attempts to detect and remove them. + * + * To do this, it iterates over the nodes (taken from `rsegments`) and determines if any of them + * is a better start/end candidate. This is done by calculating the cost of leaping between the + * node and the corresponding checkpoint; if this is cheaper than the stretch of route bypassed + * in this way, the node becomes a candidate for the corresponding endpoint. The higher the cost + * saving, the better the candidate. + * + * After identifying the best candidate for each endpoint, segments outside these nodes are + * discarded. + * + * @param rsegments The segments of the route + * @param checkpoints The reference points (at least two) + */ + void TruncateRoute(std::vector & rsegments, + routing::Checkpoints const & checkpoints); + private: static void LogCode(routing::RouterResultCode code, double const elapsedSec); @@ -433,4 +457,39 @@ bool IsRamp(routing::HighwayType highwayType); * would be broken down into `a, 4, 2`. */ std::vector ParseRef(std::string & ref); + +/** + * @brief Calculates the segments to truncate at the start of the route. + * + * The route is not actually truncated by this function. + * + * `start` and `startSaving` should be 0 when calling this function. After it returns, these values + * will indicate the first segment to keep and the cost saved by truncating everything before. + * + * @param rsegments The segments of the route + * @param checkpoints The reference points (at least two) + * @param start Index of the first segment to keep + * @param startSaving Cost saved by truncating + */ +void TruncateStart(std::vector & rsegments, + routing::Checkpoints const & checkpoints, + size_t & start, double & startSaving); + +/** + * @brief Calculates the segments to truncate at the start of the route. + * + * The route is not actually truncated by this function. + * + * `end` should be `rsegments.size() - 1` and `endSaving` should be 0 when calling this function. + * After it returns, these values will indicate the last segment to keep and the cost saved by + * truncating everything after. + * + * @param rsegments The segments of the route + * @param checkpoints The reference points (at least two) + * @param end Index of the last segment to keep + * @param endSaving Cost saved by truncating + */ +void TruncateEnd(std::vector & rsegments, + routing::Checkpoints const & checkpoints, + size_t & end, double & endSaving, double const endWeight); } // namespace traffxml