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