diff --git a/generator/generator_tests/metadata_parser_test.cpp b/generator/generator_tests/metadata_parser_test.cpp index 5903cb9d87..f324c8ea54 100644 --- a/generator/generator_tests/metadata_parser_test.cpp +++ b/generator/generator_tests/metadata_parser_test.cpp @@ -4,13 +4,8 @@ #include "indexer/classificator_loader.hpp" -#include "coding/writer.hpp" -#include "coding/reader.hpp" - #include "base/logging.hpp" -#include "std/target_os.hpp" - using feature::Metadata; UNIT_TEST(Metadata_ValidateAndFormat_stars) @@ -202,9 +197,13 @@ UNIT_TEST(Metadata_ValidateAndFormat_wikipedia) #undef WIKIHOST } +// Look at: https://wiki.openstreetmap.org/wiki/Key:duration for details +// about "duration" format. + UNIT_TEST(Metadata_ValidateAndFormat_duration) { FeatureParams params; + params.AddType(classif().GetTypeByPath({"route", "ferry"})); MetadataTagProcessor p(params); Metadata & md = params.GetMetadata(); @@ -222,10 +221,14 @@ UNIT_TEST(Metadata_ValidateAndFormat_duration) } }; + // "10" - 10 minutes ~ 0.16667 hours test("10", "0.16667"); + // 10:00 - 10 hours test("10:00", "10"); test("QWE", ""); + // 1:1:1 - 1 hour + 1 minute + 1 second test("1:1:1", "1.0169"); + // 10 hours and 30 minutes test("10:30", "10.5"); test("30", "0.5"); test("60", "1"); @@ -243,17 +246,24 @@ UNIT_TEST(Metadata_ValidateAndFormat_duration) test("5:00 hours", ""); test("12 min", ""); + // means 20 seconds test("PT20S", "0.0055556"); + // means 7 minutes test("PT7M", "0.11667"); + // means 10 minutes and 40 seconds test("PT10M40S", "0.17778"); test("PT50M", "0.83333"); + // means 2 hours test("PT2H", "2"); + // means 7 hours and 50 minutes test("PT7H50M", "7.8333"); test("PT60M", "1"); test("PT15M", "0.25"); + // means 1000 years, but we don't support such duration. test("PT1000Y", ""); test("PTPT", ""); + // means 4 day, but we don't support such duration. test("P4D", ""); test("PT50:20", ""); } diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index a24dd2edd1..a12b93f907 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -289,29 +289,29 @@ string MetadataTagProcessorImpl::ValidateAndFormat_airport_iata(string const & v string MetadataTagProcessorImpl::ValidateAndFormat_duration(string const & v) const { - auto const format = [](double hours) -> std::string { + auto const format = [](double hours) -> string { if (base::AlmostEqualAbs(hours, 0.0, 1e-5)) - return ""; + return {}; - std::stringstream ss; - ss << std::setprecision(5); + stringstream ss; + ss << setprecision(5); ss << hours; return ss.str(); }; - auto const readNumber = [&v](size_t startPos, size_t & endPos) -> boost::optional { + auto const readNumber = [&v](size_t & pos) -> boost::optional { uint32_t number = 0; - while (startPos < v.size() && isdigit(v[startPos])) + size_t const startPos = pos; + while (pos < v.size() && isdigit(v[pos])) { number *= 10; - number += v[startPos] - '0'; - ++startPos; + number += v[pos] - '0'; + ++pos; } - if (startPos == endPos) + if (startPos == pos) return {}; - endPos = startPos; return {number}; }; @@ -329,20 +329,17 @@ string MetadataTagProcessorImpl::ValidateAndFormat_duration(string const & v) co if (v.empty()) return {}; - double hours = 0; + double hours = 0.0; size_t pos = 0; boost::optional op; - if (v[0] == 'P') + if (strings::StartsWith(v, "PT")) { if (v.size() < 4) return {}; - if (v.substr(0, 2) != "PT") - return {}; - pos = 2; - while (pos < v.size() && (op = readNumber(pos, pos))) + while (pos < v.size() && (op = readNumber(pos))) { if (pos >= v.size()) return {}; @@ -365,7 +362,7 @@ string MetadataTagProcessorImpl::ValidateAndFormat_duration(string const & v) co // "hh:mm:ss" or just "mm" vector numbers; - while (pos < v.size() && (op = readNumber(pos, pos))) + while (pos < v.size() && (op = readNumber(pos))) { numbers.emplace_back(*op); if (pos >= v.size()) @@ -383,11 +380,11 @@ string MetadataTagProcessorImpl::ValidateAndFormat_duration(string const & v) co if (numbers.size() == 1) return format(numbers.back() / 60.0); - double pow = 1; + double pow = 1.0; for (auto number : numbers) { hours += number / pow; - pow *= 60; + pow *= 60.0; } return format(hours); diff --git a/generator/osm2meta.hpp b/generator/osm2meta.hpp index 3c95e20be8..281755962a 100644 --- a/generator/osm2meta.hpp +++ b/generator/osm2meta.hpp @@ -97,7 +97,14 @@ public: case Metadata::FMD_BANNER_URL: valid = ValidateAndFormat_url(v); break; case Metadata::FMD_LEVEL: valid = ValidateAndFormat_level(v); break; case Metadata::FMD_AIRPORT_IATA: valid = ValidateAndFormat_airport_iata(v); break; - case Metadata::FMD_DURATION: valid = ValidateAndFormat_duration(v); break; + case Metadata::FMD_DURATION: + { + static uint32_t const kFerryType = classif().GetTypeByPath({"route", "ferry"}); + if (m_params.FindType(kFerryType, 2 /* level */) != ftype::GetEmptyValue()) + valid = ValidateAndFormat_duration(v); + + break; + } // Metadata types we do not get from OSM. case Metadata::FMD_SPONSORED_ID: case Metadata::FMD_PRICE_RATE: diff --git a/indexer/feature_meta.hpp b/indexer/feature_meta.hpp index 763721d9d0..adbe19bfaf 100644 --- a/indexer/feature_meta.hpp +++ b/indexer/feature_meta.hpp @@ -133,6 +133,10 @@ public: FMD_LEVEL = 28, FMD_AIRPORT_IATA = 29, FMD_BRAND = 30, + // Duration of routes by ferries and other rare means of transportation. + // The number of ferries having the duration key in OSM is low so we + // store the parsed tag value in Metadata instead of building a separate section for it. + // See https://wiki.openstreetmap.org/wiki/Key:duration FMD_DURATION = 31, FMD_COUNT }; diff --git a/routing/edge_estimator.cpp b/routing/edge_estimator.cpp index bbda70cd73..5d002b4d6b 100644 --- a/routing/edge_estimator.cpp +++ b/routing/edge_estimator.cpp @@ -138,8 +138,8 @@ public: { switch (purpose) { - case Purpose::Weight: return 20 * 60; // seconds - case Purpose::ETA: return 8 * 60; // seconds + case Purpose::Weight: return 20.0 * 60.0; // seconds + case Purpose::ETA: return 8.0 * 60.0; // seconds } UNREACHABLE(); } diff --git a/routing/geometry.cpp b/routing/geometry.cpp index ca340f08e0..2b1917b3e3 100644 --- a/routing/geometry.cpp +++ b/routing/geometry.cpp @@ -27,6 +27,34 @@ namespace // Maximum road geometry cache size in items. size_t constexpr kRoadsCacheSize = 5000; +double CalcFerryDurationHours(string const & durationHours, double roadLenKm) +{ + // Look for more info: https://confluence.mail.ru/display/MAPSME/Ferries + // Shortly: the coefs were received from statistic about ferries with durations in OSM. + double constexpr kIntercept = 0.2490726747447476; + double constexpr kSlope = 0.02078913; + + if (durationHours.empty()) + return kIntercept + kSlope * roadLenKm; + + double durationH = 0.0; + CHECK(strings::to_double(durationHours.c_str(), durationH), (durationHours)); + + // See: https://confluence.mail.ru/download/attachments/249123157/image2019-8-22_16-15-53.png + // Shortly: we drop some points: (x: lengthKm, y: durationH), that are upper or lower these two lines. + double constexpr kUpperBoundIntercept = 4.0; + double constexpr kUpperBoundSlope = 0.037; + if (kUpperBoundIntercept + kUpperBoundSlope * roadLenKm - durationH < 0) + return kIntercept + kSlope * roadLenKm; + + double constexpr kLowerBoundIntercept = -2.0; + double constexpr kLowerBoundSlope = 0.015; + if (kLowerBoundIntercept + kLowerBoundSlope * roadLenKm - durationH > 0) + return kIntercept + kSlope * roadLenKm; + + return durationH; +} + // GeometryLoaderImpl ------------------------------------------------------------------------------ class GeometryLoaderImpl final : public GeometryLoader { @@ -181,21 +209,11 @@ void RoadGeometry::Load(VehicleModelInterface const & vehicleModel, FeatureType if (m_routingOptions.Has(RoutingOptions::Road::Ferry)) { - // Look for more info: https://confluence.mail.ru/display/MAPSME/Ferries - // Shortly: the coefs were received from statistic about ferries with durations in OSM. - double constexpr kBias = 0.2490726747447476; - double constexpr kCoef = 0.02078913; - auto const durationHours = feature.GetMetadata().Get(feature::Metadata::FMD_DURATION); auto const roadLenKm = GetRoadLengthM() / 1000.0; - double durationH = 0.0; + double durationH = CalcFerryDurationHours(durationHours, roadLenKm); - if (!durationHours.empty()) - CHECK(strings::to_double(durationHours.c_str(), durationH), (durationHours)); - else - durationH = kBias + kCoef * roadLenKm; - - CHECK(!base::AlmostEqualAbs(durationH, 0.0, 1e-5), ()); + CHECK(!base::AlmostEqualAbs(durationH, 0.0, 1e-5), (durationH)); m_forwardSpeed = m_backwardSpeed = SpeedKMpH(std::min(vehicleModel.GetMaxWeightSpeed(), roadLenKm / durationH)); } diff --git a/routing/index_graph.cpp b/routing/index_graph.cpp index f2a1c5f286..03c3d89058 100644 --- a/routing/index_graph.cpp +++ b/routing/index_graph.cpp @@ -28,6 +28,11 @@ bool IsUTurn(Segment const & u, Segment const & v) u.IsForward() != v.IsForward(); } +bool IsBoarding(bool prevIsFerry, bool nextIsFerry) +{ + return !prevIsFerry && nextIsFerry; +} + std::map IndexGraph::kEmptyParentsSegments = {}; IndexGraph::IndexGraph(shared_ptr geometry, shared_ptr estimator, @@ -386,9 +391,8 @@ IndexGraph::PenaltyData IndexGraph::GetRoadPenaltyData(Segment const & segment) { auto const & road = m_geometry->GetRoad(segment.GetFeatureId()); - PenaltyData result; - result.m_passThroughAllowed = road.IsPassThroughAllowed(); - result.m_isFerry = road.GetRoutingOptions().Has(RoutingOptions::Road::Ferry); + PenaltyData result(road.IsPassThroughAllowed(), + road.GetRoutingOptions().Has(RoutingOptions::Road::Ferry)); return result; } @@ -420,7 +424,7 @@ RouteWeight IndexGraph::GetPenalties(EdgeEstimator::Purpose purpose, if (IsUTurn(u, v)) weightPenalty += m_estimator->GetUTurnPenalty(purpose); - if (!fromPenaltyData.m_isFerry && toPenaltyData.m_isFerry) + if (IsBoarding(fromPenaltyData.m_isFerry, toPenaltyData.m_isFerry)) weightPenalty += m_estimator->GetFerryLandingPenalty(purpose); return {weightPenalty /* weight */, passThroughPenalty, accessPenalty, 0.0 /* transitTime */}; @@ -457,12 +461,18 @@ bool IndexGraph::IsUTurnAndRestricted(Segment const & parent, Segment const & ch RouteWeight IndexGraph::CalculateEdgeWeight(EdgeEstimator::Purpose purpose, bool isOutgoing, Segment const & from, Segment const & to) { - auto const & segment = isOutgoing ? to : from; - auto const & weight = - purpose == EdgeEstimator::Purpose::Weight ? - RouteWeight(m_estimator->CalcSegmentWeight(segment, m_geometry->GetRoad(segment.GetFeatureId()))) : - RouteWeight(m_estimator->CalcSegmentETA(segment, m_geometry->GetRoad(segment.GetFeatureId()))); + auto const getWeight = [this, isOutgoing, &to, &from, purpose]() { + auto const & segment = isOutgoing ? to : from; + auto const & road = m_geometry->GetRoad(segment.GetFeatureId()); + switch (purpose) + { + case EdgeEstimator::Purpose::Weight: return RouteWeight(m_estimator->CalcSegmentWeight(segment, road)); + case EdgeEstimator::Purpose::ETA: return RouteWeight(m_estimator->CalcSegmentETA(segment, road)); + } + UNREACHABLE(); + }; + auto const & weight = getWeight(); auto const & penalties = GetPenalties(purpose, isOutgoing ? from : to, isOutgoing ? to : from); return weight + penalties; diff --git a/routing/index_graph.hpp b/routing/index_graph.hpp index d6b3d13c6b..2e772325e6 100644 --- a/routing/index_graph.hpp +++ b/routing/index_graph.hpp @@ -132,8 +132,12 @@ private: struct PenaltyData { - bool m_passThroughAllowed = false; - bool m_isFerry = false; + PenaltyData(bool passThroughAllowed, bool isFerry) + : m_passThroughAllowed(passThroughAllowed), + m_isFerry(isFerry) {} + + bool m_passThroughAllowed; + bool m_isFerry; }; PenaltyData GetRoadPenaltyData(Segment const & segment); diff --git a/routing/routing_quality/routing_quality_tests/ferry_tests.cpp b/routing/routing_quality/routing_quality_tests/ferry_tests.cpp index ba56b554a5..5277b3688d 100644 --- a/routing/routing_quality/routing_quality_tests/ferry_tests.cpp +++ b/routing/routing_quality/routing_quality_tests/ferry_tests.cpp @@ -31,7 +31,7 @@ UNIT_TEST(Ferry_RoutingQuality_RussiaFromCrimeaFerry) } // For tests Ferry_RoutingQuality_1 - Ferry_RoutingQuality_15 -// Look to: https://confluence.mail.ru/display/MAPSME/Ferries for more details. +// Look at: https://confluence.mail.ru/display/MAPSME/Ferries for more details. UNIT_TEST(Ferry_RoutingQuality_1) { diff --git a/routing/transit_world_graph.cpp b/routing/transit_world_graph.cpp index 0f5f0eecae..f2d5324183 100644 --- a/routing/transit_world_graph.cpp +++ b/routing/transit_world_graph.cpp @@ -168,7 +168,9 @@ double TransitWorldGraph::CalculateETA(Segment const & from, Segment const & to) return m_estimator->CalcSegmentETA(to, GetRealRoadGeometry(to.GetMwmId(), to.GetFeatureId())); auto & indexGraph = m_indexLoader->GetIndexGraph(from.GetMwmId()); - return indexGraph.CalculateEdgeWeight(EdgeEstimator::Purpose::ETA, true /* isOutgoing */, from, to).GetWeight(); + return indexGraph + .CalculateEdgeWeight(EdgeEstimator::Purpose::ETA, true /* isOutgoing */, from, to) + .GetWeight(); } double TransitWorldGraph::CalculateETAWithoutPenalty(Segment const & segment)