From 5e726cd235885eed6f56216c926cd61d419184f2 Mon Sep 17 00:00:00 2001 From: gmoryes Date: Tue, 18 Feb 2020 20:42:41 +0300 Subject: [PATCH] [routing] access:conditional in routing This implementation uses weight to the vertex from A*. This implementation uses start time of route bulding. --- 3party/opening_hours/opening_hours.cpp | 13 + 3party/opening_hours/opening_hours.hpp | 4 + 3party/opening_hours/rules_evaluation.cpp | 8 +- .../generator_tests/road_access_test.cpp | 66 ++- generator/road_access_generator.cpp | 12 +- routing/index_graph.cpp | 114 +++-- routing/index_graph.hpp | 28 +- routing/road_access.cpp | 106 ++++- routing/road_access.hpp | 31 +- routing/route_weight.cpp | 32 +- routing/route_weight.hpp | 21 +- routing/routing_tests/index_graph_tools.cpp | 132 +++++- routing/routing_tests/index_graph_tools.hpp | 42 +- .../opening_hours_serdes_tests.cpp | 72 ++-- routing/routing_tests/road_access_test.cpp | 401 ++++++++++++++++++ routing/transit_graph.cpp | 6 +- 16 files changed, 933 insertions(+), 155 deletions(-) diff --git a/3party/opening_hours/opening_hours.cpp b/3party/opening_hours/opening_hours.cpp index ef859969d0..3420d72397 100644 --- a/3party/opening_hours/opening_hours.cpp +++ b/3party/opening_hours/opening_hours.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -540,6 +541,12 @@ bool DateOffset::operator==(DateOffset const & rhs) const m_offset == rhs.m_offset; } +bool DateOffset::operator<(DateOffset const & rhs) const +{ + return std::tie(m_wdayOffest, m_positive, m_offset) < + std::tie(rhs.m_wdayOffest, rhs.m_positive, rhs.m_offset); +} + // MonthDay ---------------------------------------------------------------------------------------- std::ostream & operator<<(std::ostream & ost, MonthDay::Month const month) { @@ -648,6 +655,12 @@ bool MonthDay::operator==(MonthDay const & rhs) const m_variable_date == rhs.m_variable_date && m_offset == rhs.m_offset; } +bool MonthDay::operator<(MonthDay const & rhs) const +{ + return std::tie(m_year, m_month, m_daynum, m_variable_date, m_offset) < + std::tie(rhs.m_year, rhs.m_month, rhs.m_daynum, rhs.m_variable_date, rhs.m_offset); +} + // MonthdayRange ----------------------------------------------------------------------------------- std::ostream & operator<<(std::ostream & ost, MonthdayRange const & range) { diff --git a/3party/opening_hours/opening_hours.hpp b/3party/opening_hours/opening_hours.hpp index 60f17fab73..da9623cb94 100644 --- a/3party/opening_hours/opening_hours.hpp +++ b/3party/opening_hours/opening_hours.hpp @@ -425,6 +425,8 @@ public: void SetWDayOffsetPositive(bool const on) { m_positive = on; } bool operator==(DateOffset const & rhs) const; + bool operator!=(DateOffset const & rhs) const { return !(*this == rhs); } + bool operator<(DateOffset const & rhs) const; private: Weekday m_wdayOffest = Weekday::None; @@ -484,6 +486,8 @@ public: void SetVariableDate(VariableDate const date) { m_variable_date = date; } bool operator==(MonthDay const & rhs) const; + bool operator<(MonthDay const & rhs) const; + bool operator<=(MonthDay const & rhs) const { return *this < rhs || *this == rhs; } private: TYear m_year = 0; diff --git a/3party/opening_hours/rules_evaluation.cpp b/3party/opening_hours/rules_evaluation.cpp index 9f23b0eb78..bc6865b269 100644 --- a/3party/opening_hours/rules_evaluation.cpp +++ b/3party/opening_hours/rules_evaluation.cpp @@ -274,8 +274,12 @@ bool IsActive(MonthdayRange const & range, std::tm const & date) if (range.HasEnd()) { - return range.GetStart() <= date && - date <= NormalizeEnd(range.GetStart(), range.GetEnd()); + auto const & start = range.GetStart(); + auto const end = NormalizeEnd(range.GetStart(), range.GetEnd()); + if (start <= end) + return start <= date && date <= end; + else + return start <= date || date <= end; } return range.GetStart() == date; diff --git a/generator/generator_tests/road_access_test.cpp b/generator/generator_tests/road_access_test.cpp index 8d8637a07b..8bd4b17a40 100644 --- a/generator/generator_tests/road_access_test.cpp +++ b/generator/generator_tests/road_access_test.cpp @@ -186,7 +186,8 @@ UNIT_TEST(RoadAccess_AccessPrivate) auto const roadAccessAllTypes = SaveAndLoadRoadAccess(roadAccessContent, osmIdsToFeatureIdsContent); auto const carRoadAccess = roadAccessAllTypes[static_cast(VehicleType::Car)]; - TEST_EQUAL(carRoadAccess.GetAccess(0 /* featureId */), RoadAccess::Type::Private, ()); + TEST_EQUAL(carRoadAccess.GetAccessWithoutConditional(0 /* featureId */), + make_pair(RoadAccess::Type::Private, RoadAccess::Confidence::Sure), ()); } UNIT_TEST(RoadAccess_Access_Multiple_Vehicle_Types) @@ -203,12 +204,20 @@ UNIT_TEST(RoadAccess_Access_Multiple_Vehicle_Types) SaveAndLoadRoadAccess(roadAccessContent, osmIdsToFeatureIdsContent); auto const carRoadAccess = roadAccessAllTypes[static_cast(VehicleType::Car)]; auto const bicycleRoadAccess = roadAccessAllTypes[static_cast(VehicleType::Bicycle)]; - TEST_EQUAL(carRoadAccess.GetAccess(1 /* featureId */), RoadAccess::Type::Private, ()); - TEST_EQUAL(carRoadAccess.GetAccess(2 /* featureId */), RoadAccess::Type::Private, ()); - TEST_EQUAL(carRoadAccess.GetAccess(3 /* featureId */), RoadAccess::Type::Yes, ()); - TEST_EQUAL(carRoadAccess.GetAccess(4 /* featureId */), RoadAccess::Type::Destination, - ()); - TEST_EQUAL(bicycleRoadAccess.GetAccess(3 /* featureId */), RoadAccess::Type::No, ()); + TEST_EQUAL(carRoadAccess.GetAccessWithoutConditional(1 /* featureId */), + make_pair(RoadAccess::Type::Private, RoadAccess::Confidence::Sure), ()); + + TEST_EQUAL(carRoadAccess.GetAccessWithoutConditional(2 /* featureId */), + make_pair(RoadAccess::Type::Private, RoadAccess::Confidence::Sure), ()); + + TEST_EQUAL(carRoadAccess.GetAccessWithoutConditional(3 /* featureId */), + make_pair(RoadAccess::Type::Yes, RoadAccess::Confidence::Sure), ()); + + TEST_EQUAL(carRoadAccess.GetAccessWithoutConditional(4 /* featureId */), + make_pair(RoadAccess::Type::Destination, RoadAccess::Confidence::Sure), ()); + + TEST_EQUAL(bicycleRoadAccess.GetAccessWithoutConditional(3 /* featureId */), + make_pair(RoadAccess::Type::No, RoadAccess::Confidence::Sure), ()); } UNIT_TEST(RoadAccessWriter_Merge) @@ -224,10 +233,17 @@ UNIT_TEST(RoadAccessWriter_Merge) auto const w3 = MakeOsmElementWithNodes(3 /* id */, {{"highway", "motorway"}} /* tags */, OsmElement::EntityType::Way, {30, 31, 32, 33}); - auto const p1 = generator_tests::MakeOsmElement(11 /* id */, {{"barrier", "lift_gate"}, {"motor_vehicle", "private"}}, OsmElement::EntityType::Node); - auto const p2 = generator_tests::MakeOsmElement(22 /* id */, {{"barrier", "lift_gate"}, {"motor_vehicle", "private"}}, OsmElement::EntityType::Node); + auto const p1 = generator_tests::MakeOsmElement( + 11 /* id */, {{"barrier", "lift_gate"}, {"motor_vehicle", "private"}}, + OsmElement::EntityType::Node); + + auto const p2 = generator_tests::MakeOsmElement( + 22 /* id */, {{"barrier", "lift_gate"}, {"motor_vehicle", "private"}}, + OsmElement::EntityType::Node); + // We should ignore this barrier because it's without access tag and placed on highway-motorway. - auto const p3 = generator_tests::MakeOsmElement(32 /* id */, {{"barrier", "lift_gate"}}, OsmElement::EntityType::Node); + auto const p3 = generator_tests::MakeOsmElement(32 /* id */, {{"barrier", "lift_gate"}}, + OsmElement::EntityType::Node); auto c1 = make_shared(filename); auto c2 = c1->Clone(); @@ -391,16 +407,20 @@ UNIT_TEST(RoadAccessWriter_ConditionalMerge) }; test({"Car 3 2", - "Private 12:00-19:00", - "No Mo-Su"}); + "Private", + "12:00-19:00", + "No", + "Mo-Su"}); test({ "Car 2 1", - "Private 10:00-20:00"}); + "Private", + "10:00-20:00"}); test({ "Car 1 1", - "No Mo-Su"}); + "No", + "Mo-Su"}); TEST_EQUAL(GetLinesNumber(buffer.str()), linesNumber, ()); } @@ -441,17 +461,23 @@ UNIT_TEST(RoadAccessWriter_Conditional_WinterRoads) }; test({"Pedestrian 2 1", - "No Mar - Nov"}); + "No", + "Mar - Nov"}); test({"Pedestrian 1 1", - "No Mar - Nov"}); + "No", + "Mar - Nov"}); test({"Bicycle 2 1", - "No Mar - Nov"}); + "No", + "Mar - Nov"}); test({"Bicycle 1 1", - "No Mar - Nov"}); + "No", + "Mar - Nov"}); test({"Car 2 1", - "No Mar - Nov"}); + "No", + "Mar - Nov"}); test({"Car 1 1", - "No Mar - Nov"}); + "No", + "Mar - Nov"}); TEST_EQUAL(GetLinesNumber(buffer.str()), linesNumber, ()); } diff --git a/generator/road_access_generator.cpp b/generator/road_access_generator.cpp index 09bbb48700..d97b8aa552 100644 --- a/generator/road_access_generator.cpp +++ b/generator/road_access_generator.cpp @@ -279,10 +279,11 @@ void ParseRoadAccessConditional( array pointToAccessConditional; string buffer; - VehicleType vehicleType; + VehicleType vehicleType = VehicleType::Count; while (stream >> buffer) { FromString(buffer, vehicleType); + CHECK_NOT_EQUAL(vehicleType, VehicleType::Count, (buffer)); uint64_t osmId = 0; stream >> osmId; @@ -291,17 +292,16 @@ void ParseRoadAccessConditional( stream >> accessNumber; CHECK_NOT_EQUAL(accessNumber, 0, ()); RoadAccess::Conditional conditional; + char const newline = stream.get(); + CHECK_EQUAL(newline, '\n', ()); for (size_t i = 0; i < accessNumber; ++i) { getline(stream, buffer); - stringstream tmpStream; - tmpStream << buffer; RoadAccess::Type roadAccessType = RoadAccess::Type::Count; - tmpStream >> buffer; FromString(buffer, roadAccessType); CHECK_NOT_EQUAL(roadAccessType, RoadAccess::Type::Count, ()); - buffer = tmpStream.str(); + getline(stream, buffer); osmoh::OpeningHours openingHours(buffer); if (!openingHours.IsValid()) continue; @@ -497,7 +497,7 @@ void RoadAccessTagProcessor::WriteWayToAccessConditional(std::ostream & stream) CHECK(!accesses.empty(), ()); stream << ToString(m_vehicleType) << " " << osmId << " " << accesses.size() << endl; for (auto const & access : accesses) - stream << ToString(access.m_accessType) << " " << access.m_openingHours << endl; + stream << ToString(access.m_accessType) << endl << access.m_openingHours << endl; } } diff --git a/routing/index_graph.cpp b/routing/index_graph.cpp index aed6ef6a81..d168dbda3a 100644 --- a/routing/index_graph.cpp +++ b/routing/index_graph.cpp @@ -330,6 +330,7 @@ void IndexGraph::ReconstructJointSegment(astar::VertexData cons bool useAccessConditional) { auto const & from = fromVertexData.m_vertex; + auto const & weightToFrom = fromVertexData.m_realDistance; if (IsUTurn(from, to) && IsUTurnAndRestricted(from, to, isOutgoing)) return; @@ -419,17 +433,16 @@ void IndexGraph::GetNeighboringEdge(astar::VertexData cons if (IsRestricted(from, from.GetFeatureId(), to.GetFeatureId(), isOutgoing, parents)) return; - // TODO (@gmoryes) use access conditional here. - UNUSED_VALUE(useAccessConditional); - - if (m_roadAccess.GetAccess(to.GetFeatureId()) == RoadAccess::Type::No) + if (IsAccessNoForSure(to.GetFeatureId(), weightToFrom, useAccessConditional)) return; RoadPoint const rp = from.GetRoadPoint(isOutgoing); - if (m_roadAccess.GetAccess(rp) == RoadAccess::Type::No) + if (IsAccessNoForSure(rp, weightToFrom, useAccessConditional)) return; - auto const weight = CalculateEdgeWeight(EdgeEstimator::Purpose::Weight, isOutgoing, from, to); + auto const weight = + CalculateEdgeWeight(EdgeEstimator::Purpose::Weight, isOutgoing, from, to, weightToFrom); + edges.emplace_back(to, weight); } @@ -443,8 +456,8 @@ IndexGraph::PenaltyData IndexGraph::GetRoadPenaltyData(Segment const & segment) return result; } -RouteWeight IndexGraph::GetPenalties(EdgeEstimator::Purpose purpose, - Segment const & u, Segment const & v) +RouteWeight IndexGraph::GetPenalties(EdgeEstimator::Purpose purpose, Segment const & u, + Segment const & v, optional const & prevWeight) { auto const & fromPenaltyData = GetRoadPenaltyData(u); auto const & toPenaltyData = GetRoadPenaltyData(v); @@ -453,18 +466,55 @@ RouteWeight IndexGraph::GetPenalties(EdgeEstimator::Purpose purpose, int8_t const passThroughPenalty = fromPenaltyData.m_passThroughAllowed == toPenaltyData.m_passThroughAllowed ? 0 : 1; - // We do not distinguish between RoadAccess::Type::Private and RoadAccess::Type::Destination for now. - bool const fromAccessAllowed = m_roadAccess.GetAccess(u.GetFeatureId()) == RoadAccess::Type::Yes; - bool const toAccessAllowed = m_roadAccess.GetAccess(v.GetFeatureId()) == RoadAccess::Type::Yes; - // Route crosses border of access=yes/access={private, destination} area if |u| and |v| have different - // access restrictions. - int8_t accessPenalty = fromAccessAllowed == toAccessAllowed ? 0 : 1; + int8_t accessPenalty = 0; + int8_t accessConditionalPenalties = 0; + + if (u.GetFeatureId() != v.GetFeatureId()) + { + // We do not distinguish between RoadAccess::Type::Private and RoadAccess::Type::Destination for + // now. + auto const [fromAccess, fromConfidence] = + prevWeight ? m_roadAccess.GetAccess(u.GetFeatureId(), *prevWeight) + : m_roadAccess.GetAccessWithoutConditional(u.GetFeatureId()); + + auto const [toAccess, toConfidence] = + prevWeight ? m_roadAccess.GetAccess(v.GetFeatureId(), *prevWeight) + : m_roadAccess.GetAccessWithoutConditional(v.GetFeatureId()); + + if (fromConfidence == RoadAccess::Confidence::Sure && + toConfidence == RoadAccess::Confidence::Sure) + { + bool const fromAccessAllowed = fromAccess == RoadAccess::Type::Yes; + bool const toAccessAllowed = toAccess == RoadAccess::Type::Yes; + // Route crosses border of access=yes/access={private, destination} area if |u| and |v| have + // different access restrictions. + accessPenalty = fromAccessAllowed == toAccessAllowed ? 0 : 1; + } + else if (toConfidence == RoadAccess::Confidence::Maybe) + { + accessConditionalPenalties = 1; + } + } // RoadPoint between u and v is front of u. auto const rp = u.GetRoadPoint(true /* front */); - // No double penalty for barriers on the border of access=yes/access={private, destination} area. - if (m_roadAccess.GetAccess(rp) != RoadAccess::Type::Yes) - accessPenalty = 1; + auto const [rpAccessType, rpConfidence] = prevWeight + ? m_roadAccess.GetAccess(rp, *prevWeight) + : m_roadAccess.GetAccessWithoutConditional(rp); + switch (rpConfidence) + { + case RoadAccess::Confidence::Sure: + { + if (rpAccessType != RoadAccess::Type::Yes) + accessPenalty = 1; + break; + } + case RoadAccess::Confidence::Maybe: + { + accessConditionalPenalties = 1; + break; + } + } double weightPenalty = 0.0; if (IsUTurn(u, v)) @@ -473,7 +523,8 @@ RouteWeight IndexGraph::GetPenalties(EdgeEstimator::Purpose purpose, if (IsBoarding(fromPenaltyData.m_isFerry, toPenaltyData.m_isFerry)) weightPenalty += m_estimator->GetFerryLandingPenalty(purpose); - return {weightPenalty /* weight */, passThroughPenalty, accessPenalty, 0.0 /* transitTime */}; + return {weightPenalty /* weight */, passThroughPenalty, accessPenalty, accessConditionalPenalties, + 0.0 /* transitTime */}; } WorldGraphMode IndexGraph::GetMode() const { return WorldGraphMode::Undefined; } @@ -505,16 +556,15 @@ bool IndexGraph::IsUTurnAndRestricted(Segment const & parent, Segment const & ch } RouteWeight IndexGraph::CalculateEdgeWeight(EdgeEstimator::Purpose purpose, bool isOutgoing, - Segment const & from, Segment const & to) + Segment const & from, Segment const & to, + std::optional const & prevWeight) { - auto const getWeight = [this, isOutgoing, &to, &from, purpose]() { - auto const & segment = isOutgoing ? to : from; - auto const & road = m_geometry->GetRoad(segment.GetFeatureId()); - return RouteWeight(m_estimator->CalcSegmentWeight(segment, road, purpose)); - }; + auto const & segment = isOutgoing ? to : from; + auto const & road = m_geometry->GetRoad(segment.GetFeatureId()); - auto const & weight = getWeight(); - auto const & penalties = GetPenalties(purpose, isOutgoing ? from : to, isOutgoing ? to : from); + auto const weight = RouteWeight(m_estimator->CalcSegmentWeight(segment, road, purpose)); + auto const & penalties = + GetPenalties(purpose, isOutgoing ? from : to, isOutgoing ? to : from, prevWeight); return weight + penalties; } diff --git a/routing/index_graph.hpp b/routing/index_graph.hpp index 9e481ac6cc..9c45e69cde 100644 --- a/routing/index_graph.hpp +++ b/routing/index_graph.hpp @@ -75,7 +75,8 @@ public: RoadAccess::Type GetAccessType(Segment const & segment) const { - return m_roadAccess.GetAccess(segment.GetFeatureId()); + auto [type, _] = m_roadAccess.GetAccessWithoutConditional(segment.GetFeatureId()); + return type; } uint32_t GetNumRoads() const { return m_roadIndex.GetSize(); } @@ -129,7 +130,8 @@ public: bool IsUTurnAndRestricted(Segment const & parent, Segment const & child, bool isOutgoing) const; RouteWeight CalculateEdgeWeight(EdgeEstimator::Purpose purpose, bool isOutgoing, - Segment const & from, Segment const & to); + Segment const & from, Segment const & to, + std::optional const & prevWeight = std::nullopt); template void SetCurrentTimeGetter(T && t) { m_currentTimeGetter = std::forward(t); } @@ -163,7 +165,13 @@ private: }; PenaltyData GetRoadPenaltyData(Segment const & segment); - RouteWeight GetPenalties(EdgeEstimator::Purpose purpose, Segment const & u, Segment const & v); + + /// \brief Calculates penalties for moving from |u| to |v|. + /// \param |prevWeight| uses for fetching access:conditional. In fact it is time when user + /// will be at |u|. This time is based on start time of route building and weight of calculated + /// path until |u|. + RouteWeight GetPenalties(EdgeEstimator::Purpose purpose, Segment const & u, Segment const & v, + std::optional const & prevWeight); void GetSegmentCandidateForRoadPoint(RoadPoint const & rp, NumMwmId numMwmId, bool isOutgoing, std::vector & children); @@ -177,6 +185,10 @@ private: std::vector & parentWeights, Parents const & parents); + template + bool IsAccessNoForSure(AccessPositionType const & accessPositionType, + RouteWeight const & weight, bool useAccessConditional) const; + std::shared_ptr m_geometry; std::shared_ptr m_estimator; RoadIndex m_roadIndex; @@ -206,6 +218,16 @@ private: }; }; +template +bool IndexGraph::IsAccessNoForSure(AccessPositionType const & accessPositionType, + RouteWeight const & weight, bool useAccessConditional) const +{ + auto const [accessType, confidence] = + useAccessConditional ? m_roadAccess.GetAccess(accessPositionType, weight) + : m_roadAccess.GetAccessWithoutConditional(accessPositionType); + return accessType == RoadAccess::Type::No && confidence == RoadAccess::Confidence::Sure; +} + template bool IndexGraph::IsRestricted(ParentVertex const & parent, uint32_t parentFeatureId, diff --git a/routing/road_access.cpp b/routing/road_access.cpp index 5c13428850..5800b24b1f 100644 --- a/routing/road_access.cpp +++ b/routing/road_access.cpp @@ -37,24 +37,78 @@ RoadAccess::RoadAccess() : m_currentTimeGetter([](){ return GetCurrentTimestamp( { } -RoadAccess::Type RoadAccess::GetAccess(uint32_t featureId) const +pair RoadAccess::GetAccess( + uint32_t featureId, RouteWeight const & weightToFeature) const { -// todo(@m) This may or may not be too slow. Consider profiling this and using -// a Bloom filter or anything else that is faster than std::map. - auto const it = m_wayToAccess.find(featureId); - if (it != m_wayToAccess.cend()) - return it->second; - - return RoadAccess::Type::Yes; + return GetAccess(featureId, m_currentTimeGetter() + weightToFeature.GetWeight()); } -RoadAccess::Type RoadAccess::GetAccess(RoadPoint const & point) const +pair RoadAccess::GetAccess( + RoadPoint const & point, RouteWeight const & weightToPoint) const +{ + return GetAccess(point, m_currentTimeGetter() + weightToPoint.GetWeight()); +} + +pair RoadAccess::GetAccess(uint32_t featureId, + time_t momentInTime) const +{ + auto const itConditional = m_wayToAccessConditional.find(featureId); + if (itConditional != m_wayToAccessConditional.cend()) + { + auto const & conditional = itConditional->second; + for (auto const & access : conditional.GetAccesses()) + { + auto const op = GetConfidenceForAccessConditional(momentInTime, access.m_openingHours); + if (!op) + continue; + + return {access.m_type, *op}; + } + } + + return GetAccessWithoutConditional(featureId); +} + +pair RoadAccess::GetAccess(RoadPoint const & point, + time_t momentInTime) const +{ + auto const itConditional = m_pointToAccessConditional.find(point); + if (itConditional != m_pointToAccessConditional.cend()) + { + auto const & conditional = itConditional->second; + for (auto const & access : conditional.GetAccesses()) + { + auto const op = GetConfidenceForAccessConditional(momentInTime, access.m_openingHours); + if (!op) + continue; + + return {access.m_type, *op}; + } + } + + return GetAccessWithoutConditional(point); +} + +pair RoadAccess::GetAccessWithoutConditional( + uint32_t featureId) const +{ + // todo(@m) This may or may not be too slow. Consider profiling this and using + // a Bloom filter or anything else that is faster than ska::flat_hash_map + auto const it = m_wayToAccess.find(featureId); + if (it != m_wayToAccess.cend()) + return {it->second, Confidence::Sure}; + + return {Type::Yes, Confidence::Sure}; +} + +pair RoadAccess::GetAccessWithoutConditional( + RoadPoint const & point) const { auto const it = m_pointToAccess.find(point); if (it != m_pointToAccess.cend()) - return it->second; + return {it->second, Confidence::Sure}; - return RoadAccess::Type::Yes; + return {Type::Yes, Confidence::Sure}; } bool RoadAccess::operator==(RoadAccess const & rhs) const @@ -64,10 +118,26 @@ bool RoadAccess::operator==(RoadAccess const & rhs) const m_pointToAccessConditional == rhs.m_pointToAccessConditional; } +// static +optional RoadAccess::GetConfidenceForAccessConditional( + time_t momentInTime, osmoh::OpeningHours const & openingHours) +{ + auto const left = momentInTime - kConfidenceIntervalSeconds / 2; + auto const right = momentInTime + kConfidenceIntervalSeconds / 2; + + auto const leftOpen = openingHours.IsOpen(left); + auto const rightOpen = openingHours.IsOpen(right); + + if (!leftOpen && !rightOpen) + return nullopt; + + return leftOpen && rightOpen ? Confidence::Sure : Confidence::Maybe; +} + // Functions --------------------------------------------------------------------------------------- time_t GetCurrentTimestamp() { - using system_clock = std::chrono::system_clock; + using system_clock = chrono::system_clock; return system_clock::to_time_t(system_clock::now()); } @@ -90,7 +160,7 @@ void FromString(string const & s, RoadAccess::Type & result) } } result = RoadAccess::Type::Count; - ASSERT(false, ("Could not read RoadAccess from the string", s)); + CHECK(false, ("Could not read RoadAccess from the string", s)); } string DebugPrint(RoadAccess::Conditional const & conditional) @@ -105,6 +175,16 @@ string DebugPrint(RoadAccess::Conditional const & conditional) return ss.str(); } +string DebugPrint(RoadAccess::Confidence confidence) +{ + switch (confidence) + { + case RoadAccess::Confidence::Maybe: return "Maybe"; + case RoadAccess::Confidence::Sure: return "Sure"; + } + UNREACHABLE(); +} + string DebugPrint(RoadAccess::Type type) { return ToString(type); } string DebugPrint(RoadAccess const & r) diff --git a/routing/road_access.hpp b/routing/road_access.hpp index 33fda17b7b..cf392c0169 100644 --- a/routing/road_access.hpp +++ b/routing/road_access.hpp @@ -6,6 +6,7 @@ #include "base/assert.hpp" #include +#include #include #include #include @@ -44,6 +45,12 @@ public: Count }; + enum class Confidence + { + Maybe, + Sure + }; + class Conditional { public: @@ -101,8 +108,13 @@ public: return m_pointToAccessConditional; } - Type GetAccess(uint32_t featureId) const; - Type GetAccess(RoadPoint const & point) const; + std::pair GetAccess(uint32_t featureId, + RouteWeight const & weightToFeature) const; + std::pair GetAccess(RoadPoint const & point, + RouteWeight const & weightToPoint) const; + + std::pair GetAccessWithoutConditional(uint32_t featureId) const; + std::pair GetAccessWithoutConditional(RoadPoint const & point) const; template void SetAccess(WayToAccess && wayToAccess, PointToAccess && pointToAccess) @@ -131,7 +143,21 @@ public: void SetCurrentTimeGetter(T && getter) { m_currentTimeGetter = std::forward(getter); } private: + // When we check access:conditional, we check such interval in fact: + // is access:conditional is open at: curTime - |kConfidenceIntervalSeconds| / 2 + // is access:conditional is open at: curTime + |kConfidenceIntervalSeconds| / 2 + // If at both ends access:conditional is open we say, we are sure that this access:conditional is open, + // if only one is open, we say access:conditional is maybe open. + inline static time_t constexpr kConfidenceIntervalSeconds = 2 * 3600; // 2 hours + + static std::optional GetConfidenceForAccessConditional( + time_t momentInTime, osmoh::OpeningHours const & openingHours); + + std::pair GetAccess(uint32_t featureId, time_t momentInTime) const; + std::pair GetAccess(RoadPoint const & point, time_t momentInTime) const; + std::function m_currentTimeGetter; + // If segmentIdx of a key in this map is 0, it means the // entire feature has the corresponding access type. // Otherwise, the information is about the segment with number (segmentIdx-1). @@ -146,6 +172,7 @@ time_t GetCurrentTimestamp(); std::string ToString(RoadAccess::Type type); void FromString(std::string const & s, RoadAccess::Type & result); +std::string DebugPrint(RoadAccess::Confidence confidence); std::string DebugPrint(RoadAccess::Conditional const & conditional); std::string DebugPrint(RoadAccess::Type type); std::string DebugPrint(RoadAccess const & r); diff --git a/routing/route_weight.cpp b/routing/route_weight.cpp index e1a53adb0d..7e553b9b50 100644 --- a/routing/route_weight.cpp +++ b/routing/route_weight.cpp @@ -34,8 +34,12 @@ RouteWeight RouteWeight::operator+(RouteWeight const & rhs) const (m_numPassThroughChanges, rhs.m_numPassThroughChanges)); ASSERT(!SumWillOverflow(m_numAccessChanges, rhs.m_numAccessChanges), (m_numAccessChanges, rhs.m_numAccessChanges)); + ASSERT(!SumWillOverflow(m_numAccessConditionalPenalties, rhs.m_numAccessConditionalPenalties), + (m_numAccessConditionalPenalties, rhs.m_numAccessConditionalPenalties)); + return RouteWeight(m_weight + rhs.m_weight, m_numPassThroughChanges + rhs.m_numPassThroughChanges, m_numAccessChanges + rhs.m_numAccessChanges, + m_numAccessConditionalPenalties + rhs.m_numAccessConditionalPenalties, m_transitTime + rhs.m_transitTime); } @@ -43,12 +47,18 @@ RouteWeight RouteWeight::operator-(RouteWeight const & rhs) const { ASSERT_NOT_EQUAL(m_numPassThroughChanges, numeric_limits::min(), ()); ASSERT_NOT_EQUAL(m_numAccessChanges, numeric_limits::min(), ()); - ASSERT(!SumWillOverflow(m_numPassThroughChanges, static_cast(-rhs.m_numPassThroughChanges)), - (m_numPassThroughChanges, -rhs.m_numPassThroughChanges)); + ASSERT( + !SumWillOverflow(m_numPassThroughChanges, static_cast(-rhs.m_numPassThroughChanges)), + (m_numPassThroughChanges, -rhs.m_numPassThroughChanges)); ASSERT(!SumWillOverflow(m_numAccessChanges, static_cast(-rhs.m_numAccessChanges)), (m_numAccessChanges, -rhs.m_numAccessChanges)); + ASSERT(!SumWillOverflow(m_numAccessConditionalPenalties, + static_cast(-rhs.m_numAccessConditionalPenalties)), + (m_numAccessConditionalPenalties, -rhs.m_numAccessConditionalPenalties)); + return RouteWeight(m_weight - rhs.m_weight, m_numPassThroughChanges - rhs.m_numPassThroughChanges, m_numAccessChanges - rhs.m_numAccessChanges, + m_numAccessConditionalPenalties - rhs.m_numAccessConditionalPenalties, m_transitTime - rhs.m_transitTime); } @@ -58,24 +68,36 @@ RouteWeight & RouteWeight::operator+=(RouteWeight const & rhs) (m_numPassThroughChanges, rhs.m_numPassThroughChanges)); ASSERT(!SumWillOverflow(m_numAccessChanges, rhs.m_numAccessChanges), (m_numAccessChanges, rhs.m_numAccessChanges)); + ASSERT(!SumWillOverflow(m_numAccessConditionalPenalties, rhs.m_numAccessConditionalPenalties), + (m_numAccessConditionalPenalties, rhs.m_numAccessConditionalPenalties)); m_weight += rhs.m_weight; m_numPassThroughChanges += rhs.m_numPassThroughChanges; m_numAccessChanges += rhs.m_numAccessChanges; + m_numAccessConditionalPenalties += rhs.m_numAccessConditionalPenalties; m_transitTime += rhs.m_transitTime; return *this; } +void RouteWeight::AddAccessConditionalPenalty() +{ + ASSERT_LESS(m_numAccessConditionalPenalties, + std::numeric_limits::max(), ()); + ++m_numAccessConditionalPenalties; +} + ostream & operator<<(ostream & os, RouteWeight const & routeWeight) { os << "(" << static_cast(routeWeight.GetNumPassThroughChanges()) << ", " - << static_cast(routeWeight.GetNumAccessChanges()) << ", " << routeWeight.GetWeight() - << ", " << routeWeight.GetTransitTime() << ")"; + << static_cast(routeWeight.GetNumAccessChanges()) << ", " + << static_cast(routeWeight.GetNumAccessConditionalPenalties()) << ", " + << routeWeight.GetWeight() << ", " << routeWeight.GetTransitTime() << ")"; return os; } RouteWeight operator*(double lhs, RouteWeight const & rhs) { return RouteWeight(lhs * rhs.GetWeight(), rhs.GetNumPassThroughChanges(), - rhs.GetNumAccessChanges(), lhs * rhs.GetTransitTime()); + rhs.GetNumAccessChanges(), rhs.GetNumAccessConditionalPenalties(), + lhs * rhs.GetTransitTime()); } } // namespace routing diff --git a/routing/route_weight.hpp b/routing/route_weight.hpp index 5faf4a63e3..045b0ec4b3 100644 --- a/routing/route_weight.hpp +++ b/routing/route_weight.hpp @@ -18,10 +18,11 @@ public: explicit constexpr RouteWeight(double weight) : m_weight(weight) {} constexpr RouteWeight(double weight, int8_t numPassThroughChanges, int8_t numAccessChanges, - double transitTime) + int8_t numAccessConditionalPenalties, double transitTime) : m_weight(weight) , m_numPassThroughChanges(numPassThroughChanges) , m_numAccessChanges(numAccessChanges) + , m_numAccessConditionalPenalties(numAccessConditionalPenalties) , m_transitTime(transitTime) { } @@ -34,6 +35,9 @@ public: int8_t GetNumAccessChanges() const { return m_numAccessChanges; } double GetTransitTime() const { return m_transitTime; } + int8_t GetNumAccessConditionalPenalties() const { return m_numAccessConditionalPenalties; } + void AddAccessConditionalPenalty(); + bool operator<(RouteWeight const & rhs) const { if (m_numPassThroughChanges != rhs.m_numPassThroughChanges) @@ -46,6 +50,10 @@ public: // access={private, destination} crosses. if (m_numAccessChanges != rhs.m_numAccessChanges) return m_numAccessChanges < rhs.m_numAccessChanges; + + if (m_numAccessConditionalPenalties != rhs.m_numAccessConditionalPenalties) + return m_numAccessConditionalPenalties < rhs.m_numAccessConditionalPenalties; + if (m_weight != rhs.m_weight) return m_weight < rhs.m_weight; // Prefer bigger transit time if total weights are same. @@ -72,13 +80,16 @@ public: { ASSERT_NOT_EQUAL(m_numPassThroughChanges, std::numeric_limits::min(), ()); ASSERT_NOT_EQUAL(m_numAccessChanges, std::numeric_limits::min(), ()); - return RouteWeight(-m_weight, -m_numPassThroughChanges, -m_numAccessChanges, -m_transitTime); + ASSERT_NOT_EQUAL(m_numAccessConditionalPenalties, std::numeric_limits::min(), ()); + return RouteWeight(-m_weight, -m_numPassThroughChanges, -m_numAccessChanges, + -m_numAccessConditionalPenalties, -m_transitTime); } bool IsAlmostEqualForTests(RouteWeight const & rhs, double epsilon) { return m_numPassThroughChanges == rhs.m_numPassThroughChanges && m_numAccessChanges == rhs.m_numAccessChanges && + m_numAccessConditionalPenalties == rhs.m_numAccessConditionalPenalties && base::AlmostEqualAbs(m_weight, rhs.m_weight, epsilon) && base::AlmostEqualAbs(m_transitTime, rhs.m_transitTime, epsilon); } @@ -90,6 +101,9 @@ private: int8_t m_numPassThroughChanges = 0; // Number of access=yes/access={private,destination} zone changes. int8_t m_numAccessChanges = 0; + // Number of access:conditional dangerous zones (when RoadAccess::GetAccess() return + // |Confidence::Maybe|). + int8_t m_numAccessConditionalPenalties = 0; // Transit time. It's already included in |m_weight| (m_transitTime <= m_weight). double m_transitTime = 0.0; }; @@ -104,6 +118,7 @@ constexpr RouteWeight GetAStarWeightMax() return RouteWeight(std::numeric_limits::max() /* weight */, std::numeric_limits::max() /* numPassThroughChanges */, std::numeric_limits::max() /* numAccessChanges */, + std::numeric_limits::max() /* numAccessConditionalPenalties */, 0.0 /* transitTime */); // operator< prefers bigger transit time } @@ -111,6 +126,6 @@ template <> constexpr RouteWeight GetAStarWeightZero() { return RouteWeight(0.0 /* weight */, 0 /* numPassThroughChanges */, 0 /* numAccessChanges */, - 0.0 /* transitTime */); + 0 /* numAccessConditionalPenalties */, 0.0 /* transitTime */); } } // namespace routing diff --git a/routing/routing_tests/index_graph_tools.cpp b/routing/routing_tests/index_graph_tools.cpp index 35eeaa9223..9dde9c4105 100644 --- a/routing/routing_tests/index_graph_tools.cpp +++ b/routing/routing_tests/index_graph_tools.cpp @@ -13,6 +13,8 @@ #include +#include "3party/opening_hours/opening_hours.hpp" + using namespace std; namespace routing_test @@ -168,6 +170,22 @@ void TestIndexGraphTopology::SetEdgeAccess(Vertex from, Vertex to, RoadAccess::T CHECK(false, ("Cannot set access for edge that is not in the graph", from, to)); } +void TestIndexGraphTopology::SetEdgeAccessConditional(Vertex from, Vertex to, RoadAccess::Type type, + string const & condition) +{ + for (auto & r : m_edgeRequests) + { + if (r.m_from == from && r.m_to == to) + { + osmoh::OpeningHours openingHours(condition); + CHECK(openingHours.IsValid(), (condition)); + r.m_accessConditionalType.Insert(type, move(openingHours)); + return; + } + } + CHECK(false, ("Cannot set access for edge that is not in the graph", from, to)); +} + void TestIndexGraphTopology::SetVertexAccess(Vertex v, RoadAccess::Type type) { bool found = false; @@ -178,11 +196,40 @@ void TestIndexGraphTopology::SetVertexAccess(Vertex v, RoadAccess::Type type) r.m_fromAccessType = type; found = true; } - if (r.m_to == v) + else if (r.m_to == v) { r.m_toAccessType = type; found = true; } + + if (found) + break; + } + CHECK(found, ("Cannot set access for vertex that is not in the graph", v)); +} + +void TestIndexGraphTopology::SetVertexAccessConditional(Vertex v, RoadAccess::Type type, + string const & condition) +{ + osmoh::OpeningHours openingHours(condition); + CHECK(openingHours.IsValid(), (condition)); + + bool found = false; + for (auto & r : m_edgeRequests) + { + if (r.m_from == v) + { + r.m_fromAccessConditionalType.Insert(type, move(openingHours)); + found = true; + } + else if (r.m_to == v) + { + r.m_toAccessConditionalType.Insert(type, move(openingHours)); + found = true; + } + + if (found) + break; } CHECK(found, ("Cannot set access for vertex that is not in the graph", v)); } @@ -218,6 +265,7 @@ bool TestIndexGraphTopology::FindPath(Vertex start, Vertex finish, double & path true /* forward */); Builder builder(m_numVertices); + builder.SetCurrentTimeGetter(m_currentTimeGetter); builder.BuildGraphFromRequests(edgeRequests); auto worldGraph = builder.PrepareIndexGraph(); CHECK(worldGraph != nullptr, ()); @@ -286,7 +334,10 @@ unique_ptr TestIndexGraphTopology::Builder::PrepareInde BuildJoints(); auto worldGraph = BuildWorldGraph(move(loader), estimator, m_joints); - worldGraph->GetIndexGraphForTests(kTestNumMwmId).SetRoadAccess(move(m_roadAccess)); + auto & indexGraph = worldGraph->GetIndexGraphForTests(kTestNumMwmId); + if (m_currentTimeGetter) + indexGraph.SetCurrentTimeGetter(m_currentTimeGetter); + indexGraph.SetRoadAccess(move(m_roadAccess)); return worldGraph; } @@ -307,22 +358,43 @@ void TestIndexGraphTopology::Builder::BuildJoints() void TestIndexGraphTopology::Builder::BuildGraphFromRequests(vector const & requests) { - RoadAccess::WayToAccess featureTypes; - RoadAccess::PointToAccess pointTypes; + RoadAccess::WayToAccess wayToAccess; + RoadAccess::WayToAccessConditional wayToAccessConditional; + RoadAccess::PointToAccess pointToAccess; + RoadAccess::PointToAccessConditional pointToAccessConditional; for (auto const & request : requests) { BuildSegmentFromEdge(request); if (request.m_accessType != RoadAccess::Type::Yes) - featureTypes[request.m_id] = request.m_accessType; + wayToAccess[request.m_id] = request.m_accessType; + + if (!request.m_accessConditionalType.IsEmpty()) + wayToAccessConditional[request.m_id] = request.m_accessConditionalType; // All features have 1 segment. |from| has point index 0, |to| has point index 1. if (request.m_fromAccessType != RoadAccess::Type::Yes) - pointTypes[RoadPoint(request.m_id, 0 /* pointId */)] = request.m_fromAccessType; + pointToAccess[RoadPoint(request.m_id, 0 /* pointId */)] = request.m_fromAccessType; + + if (!request.m_fromAccessConditionalType.IsEmpty()) + { + pointToAccessConditional[RoadPoint(request.m_id, 0 /* pointId */)] = + request.m_fromAccessConditionalType; + } + if (request.m_toAccessType != RoadAccess::Type::Yes) - pointTypes[RoadPoint(request.m_id, 1 /* pointId */)] = request.m_toAccessType; + pointToAccess[RoadPoint(request.m_id, 1 /* pointId */)] = request.m_toAccessType; + + if (!request.m_toAccessConditionalType.IsEmpty()) + { + pointToAccessConditional[RoadPoint(request.m_id, 1 /* pointId */)] = + request.m_toAccessConditionalType; + } } - m_roadAccess.SetAccess(move(featureTypes), move(pointTypes)); + m_roadAccess.SetAccess(move(wayToAccess), move(pointToAccess)); + m_roadAccess.SetAccessConditional(move(wayToAccessConditional), move(pointToAccessConditional)); + if (m_currentTimeGetter) + m_roadAccess.SetCurrentTimeGetter(m_currentTimeGetter); } void TestIndexGraphTopology::Builder::BuildSegmentFromEdge(EdgeRequest const & request) @@ -551,4 +623,48 @@ unique_ptr MakeStarter(FakeEnding const & start, FakeEnding c return make_unique(start, finish, 0 /* fakeNumerationStart */, false /* strictForward */, graph); } + +time_t GetUnixtimeByDate(uint16_t year, Month month, uint8_t monthDay, uint8_t hours, + uint8_t minutes) +{ + std::tm t{}; + t.tm_year = year - 1900; + t.tm_mon = static_cast(month) - 1; + t.tm_mday = monthDay; + t.tm_hour = hours; + t.tm_min = minutes; + + time_t moment = mktime(&t); + return moment; +} + +time_t GetUnixtimeByDate(uint16_t year, Month month, Weekday weekday, uint8_t hours, + uint8_t minutes) +{ + int monthDay = 1; + auto createUnixtime = [&]() { + std::tm t{}; + t.tm_year = year - 1900; + t.tm_mon = static_cast(month) - 1; + t.tm_mday = monthDay; + t.tm_wday = static_cast(weekday) - 1; + t.tm_hour = hours; + t.tm_min = minutes; + + return mktime(&t); + }; + + int wday = -1; + for (;;) + { + auto unixtime = createUnixtime(); + auto timeOut = localtime(&unixtime); + wday = timeOut->tm_wday; + if (wday == static_cast(weekday) - 1) + break; + ++monthDay; + } + + return createUnixtime(); +} } // namespace routing_test diff --git a/routing/routing_tests/index_graph_tools.hpp b/routing/routing_tests/index_graph_tools.hpp index 3ae95072dc..adba903d74 100644 --- a/routing/routing_tests/index_graph_tools.hpp +++ b/routing/routing_tests/index_graph_tools.hpp @@ -2,6 +2,7 @@ #include "generator/generator_tests_support/routing_helpers.hpp" +#include "routing/base/astar_algorithm.hpp" #include "routing/edge_estimator.hpp" #include "routing/fake_ending.hpp" #include "routing/index_graph.hpp" @@ -12,25 +13,24 @@ #include "routing/road_point.hpp" #include "routing/route.hpp" #include "routing/segment.hpp" -#include "routing/speed_camera_ser_des.hpp" #include "routing/single_vehicle_world_graph.hpp" +#include "routing/speed_camera_ser_des.hpp" #include "routing/transit_graph_loader.hpp" #include "routing/transit_world_graph.hpp" -#include "routing/base/astar_algorithm.hpp" - -#include "routing_common/num_mwm_id.hpp" - #include "traffic/traffic_info.hpp" #include "transit/transit_types.hpp" +#include "routing_common/num_mwm_id.hpp" + #include "indexer/classificator_loader.hpp" #include "geometry/point2d.hpp" #include #include +#include #include #include #include @@ -65,16 +65,16 @@ public: std::vector & edges) override { edges.clear(); - m_graph->GetEdgeList(vertexData.m_vertex, true /* isOutgoing */, true /* useRoutingOptions */, - edges); + m_graph->GetEdgeList(vertexData, true /* isOutgoing */, true /* useRoutingOptions */, + true /* useAccessConditional */, edges); } void GetIngoingEdgesList(astar::VertexData const & vertexData, std::vector & edges) override { edges.clear(); - m_graph->GetEdgeList(vertexData.m_vertex, false /* isOutgoing */, true /* useRoutingOptions */, - edges); + m_graph->GetEdgeList(vertexData, false /* isOutgoing */, true /* useRoutingOptions */, + true /* useAccessConditional */, edges); } RouteWeight GetAStarWeightEpsilon() override { return RouteWeight(0.0); } @@ -205,14 +205,22 @@ public: // Sets access for previously added edge. void SetEdgeAccess(Vertex from, Vertex to, RoadAccess::Type type); + /// \param |condition| in osm opening hours format. + void SetEdgeAccessConditional(Vertex from, Vertex to, RoadAccess::Type type, + std::string const & condition); // Sets access type for previously added point. void SetVertexAccess(Vertex v, RoadAccess::Type type); + /// \param |condition| in osm opening hours format. + void SetVertexAccessConditional(Vertex v, RoadAccess::Type type, std::string const & condition); // Finds a path between the start and finish vertices. Returns true iff a path exists. bool FindPath(Vertex start, Vertex finish, double & pathWeight, std::vector & pathEdges) const; + template + void SetCurrentTimeGetter(T && getter) { m_currentTimeGetter = std::forward(getter); } + private: struct EdgeRequest { @@ -222,10 +230,13 @@ private: double m_weight = 0.0; // Access type for edge. RoadAccess::Type m_accessType = RoadAccess::Type::Yes; + RoadAccess::Conditional m_accessConditionalType; // Access type for vertex from. RoadAccess::Type m_fromAccessType = RoadAccess::Type::Yes; + RoadAccess::Conditional m_fromAccessConditionalType; // Access type for vertex to. RoadAccess::Type m_toAccessType = RoadAccess::Type::Yes; + RoadAccess::Conditional m_toAccessConditionalType; EdgeRequest(uint32_t id, Vertex from, Vertex to, double weight) : m_id(id), m_from(from), m_to(to), m_weight(weight) @@ -236,11 +247,12 @@ private: // Builder builds a graph from edge requests. struct Builder { - Builder(uint32_t numVertices) : m_numVertices(numVertices) {} + explicit Builder(uint32_t numVertices) : m_numVertices(numVertices) {} std::unique_ptr PrepareIndexGraph(); void BuildJoints(); void BuildGraphFromRequests(std::vector const & requests); void BuildSegmentFromEdge(EdgeRequest const & request); + void SetCurrentTimeGetter(std::function const & getter) { m_currentTimeGetter = getter; } uint32_t const m_numVertices; std::map m_edgeWeights; @@ -250,11 +262,13 @@ private: std::map> m_ingoingSegments; std::vector m_joints; RoadAccess m_roadAccess; + std::function m_currentTimeGetter; }; void AddDirectedEdge(std::vector & edgeRequests, Vertex from, Vertex to, double weight) const; + std::function m_currentTimeGetter; uint32_t const m_numVertices; std::vector m_edgeRequests; }; @@ -310,4 +324,12 @@ FakeEnding MakeFakeEnding(uint32_t featureId, uint32_t segmentIdx, m2::PointD co std::unique_ptr MakeStarter(FakeEnding const & start, FakeEnding const & finish, WorldGraph & graph); + +using Month = osmoh::MonthDay::Month; +using Weekday = osmoh::Weekday; + +time_t GetUnixtimeByDate(uint16_t year, Month month, uint8_t monthDay, uint8_t hours, + uint8_t minutes); +time_t GetUnixtimeByDate(uint16_t year, Month month, Weekday weekday, uint8_t hours, + uint8_t minutes); } // namespace routing_test diff --git a/routing/routing_tests/opening_hours_serdes_tests.cpp b/routing/routing_tests/opening_hours_serdes_tests.cpp index c0d71677a6..8bee025b03 100644 --- a/routing/routing_tests/opening_hours_serdes_tests.cpp +++ b/routing/routing_tests/opening_hours_serdes_tests.cpp @@ -1,3 +1,5 @@ +#include "routing/routing_tests/index_graph_tools.hpp" + #include "testing/testing.hpp" #include "routing/opening_hours_serdes.hpp" @@ -14,58 +16,12 @@ #include using namespace routing; +using namespace routing_test; using Buffer = std::vector; namespace { -using Month = osmoh::MonthDay::Month; -using Weekday = osmoh::Weekday; - -time_t GetUnixtimeByDate(uint16_t year, Month month, uint8_t monthDay, uint8_t hours, - uint8_t minutes) -{ - std::tm t{}; - t.tm_year = year - 1900; - t.tm_mon = static_cast(month) - 1; - t.tm_mday = monthDay; - t.tm_hour = hours; - t.tm_min = minutes; - - std::time_t moment = std::mktime(&t); - return moment; -} - -time_t GetUnixtimeByDate(uint16_t year, Month month, Weekday weekday, uint8_t hours, - uint8_t minutes) -{ - int monthDay = 1; - auto createUnixtime = [&]() { - std::tm t{}; - t.tm_year = year - 1900; - t.tm_mon = static_cast(month) - 1; - t.tm_mday = monthDay; - t.tm_wday = static_cast(weekday) - 1; - t.tm_hour = hours; - t.tm_min = minutes; - - return std::mktime(&t); - }; - - int wday = -1; - for (;;) - { - auto unixtime = createUnixtime(); - auto time_out = std::localtime(&unixtime); - wday = time_out->tm_wday; - if (wday == static_cast(weekday) - 1) - break; - ++monthDay; - } - - return createUnixtime(); -} - struct BitReaderWriter { BitReaderWriter() @@ -858,7 +814,7 @@ UNIT_CLASS_TEST(BitReaderWriter, OpeningHoursSerDes_MonthHours_Usage) TEST(oh.IsOpen(GetUnixtimeByDate(2020, Month::May, 6, 01 /* hh */, 32 /* mm */)), ()); } -UNIT_CLASS_TEST(BitReaderWriter, OpeningHoursSerDes_InverseMonths_Usage) +UNIT_CLASS_TEST(BitReaderWriter, OpeningHoursSerDes_InverseMonths_Usage_1) { EnableAll(); @@ -874,4 +830,24 @@ UNIT_CLASS_TEST(BitReaderWriter, OpeningHoursSerDes_InverseMonths_Usage) TEST(!oh.IsOpen(GetUnixtimeByDate(2019, Month::Jan, 20, 20 /* hh */, 00 /* mm */)), ()); TEST(!oh.IsOpen(GetUnixtimeByDate(2020, Month::Feb, 20, 20 /* hh */, 00 /* mm */)), ()); } + +UNIT_CLASS_TEST(BitReaderWriter, OpeningHoursSerDes_InverseMonths_Usage_2) +{ + EnableAll(); + + TEST(Serialize("Nov - Mar"), ()); + Flush(); + + auto const oh = Deserialize(); + + TEST(!oh.IsOpen(GetUnixtimeByDate(2019, Month::Sep, 20, 20 /* hh */, 00 /* mm */)), ()); + TEST(!oh.IsOpen(GetUnixtimeByDate(2019, Month::Oct, 20, 20 /* hh */, 00 /* mm */)), ()); + TEST(oh.IsOpen(GetUnixtimeByDate(2019, Month::Nov, 20, 20 /* hh */, 00 /* mm */)), ()); + TEST(oh.IsOpen(GetUnixtimeByDate(2019, Month::Dec, 20, 20 /* hh */, 00 /* mm */)), ()); + TEST(oh.IsOpen(GetUnixtimeByDate(2019, Month::Jan, 20, 20 /* hh */, 00 /* mm */)), ()); + TEST(oh.IsOpen(GetUnixtimeByDate(2019, Month::Feb, 20, 20 /* hh */, 00 /* mm */)), ()); + TEST(oh.IsOpen(GetUnixtimeByDate(2019, Month::Mar, 20, 20 /* hh */, 00 /* mm */)), ()); + TEST(!oh.IsOpen(GetUnixtimeByDate(2020, Month::Apr, 20, 20 /* hh */, 00 /* mm */)), ()); + TEST(!oh.IsOpen(GetUnixtimeByDate(2020, Month::May, 20, 20 /* hh */, 00 /* mm */)), ()); +} } // namespace diff --git a/routing/routing_tests/road_access_test.cpp b/routing/routing_tests/road_access_test.cpp index 16147a001a..48ab274042 100644 --- a/routing/routing_tests/road_access_test.cpp +++ b/routing/routing_tests/road_access_test.cpp @@ -335,4 +335,405 @@ UNIT_TEST(RoadAccess_BarrierBypassing) graph.SetVertexAccess(3, RoadAccess::Type::No); TestTopologyGraph(graph, 0, 5, false /* pathFound */, expectedWeight, expectedEdges); } + +UNIT_TEST(RoadAccess_WayBlockedConditional) +{ + uint32_t const numVertices = 4; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(1, 2, 1.0); + graph.AddDirectedEdge(2, 3, 1.0); + + double expectedWeight = 3.0; + vector expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetEdgeAccessConditional(1, 2, RoadAccess::Type::No, "Jan - Jul"); + + auto const april = []() { + return GetUnixtimeByDate(2020, Month::Apr, 1, 12 /* hh */, 00 /* mm */); + }; + + graph.SetCurrentTimeGetter(april); + expectedWeight = 0; + expectedEdges = {}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, false /* pathFound */, expectedWeight, + expectedEdges); + + auto const november = []() { + return GetUnixtimeByDate(2020, Month::Nov, 1, 12 /* hh */, 00 /* mm */); + }; + + graph.SetCurrentTimeGetter(november); + expectedWeight = 3.0; + expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); +} + +UNIT_TEST(RoadAccess_PointBlockedConditional) +{ + uint32_t const numVertices = 4; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(1, 2, 1.0); + graph.AddDirectedEdge(2, 3, 1.0); + + double expectedWeight = 3.0; + vector expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetVertexAccessConditional(1, RoadAccess::Type::No, "Jan - Jul"); + + auto const april = []() { + return GetUnixtimeByDate(2020, Month::Apr, 1, 12 /* hh */, 00 /* mm */); + }; + + graph.SetCurrentTimeGetter(april); + expectedWeight = 0; + expectedEdges = {}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, false /* pathFound */, expectedWeight, + expectedEdges); + + auto const november = []() { + return GetUnixtimeByDate(2020, Month::Nov, 1, 12 /* hh */, 00 /* mm */); + }; + + graph.SetCurrentTimeGetter(november); + expectedWeight = 3.0; + expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); +} + +UNIT_TEST(RoadAccess_WayBlockedAvoidConditional) +{ + uint32_t const numVertices = 4; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(0, 2, 10.0); + graph.AddDirectedEdge(1, 3, 1.0); + graph.AddDirectedEdge(2, 3, 10.0); + + double expectedWeight = 2.0; + vector expectedEdges = {{0, 1}, {1, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetEdgeAccessConditional(0, 1, RoadAccess::Type::No, "Mo-Fr 10:00 - 19:00"); + + auto const mondayAlmostTenHours = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 9 /* hh */, 50 /* mm */); + }; + + // In this time we probably will able to pass 0->1 edge, but we are not sure, so we should avoid + // such edges. + graph.SetCurrentTimeGetter(mondayAlmostTenHours); + expectedWeight = 20.0; + expectedEdges = {{0, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetEdgeAccess(0, 2, RoadAccess::Type::No); + + // But if this is the only path (we blocked 0->2 above), we should pass edge 0->1 anyway. + graph.SetCurrentTimeGetter(mondayAlmostTenHours); + expectedWeight = 2.0; + expectedEdges = {{0, 1}, {1, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + auto const mondayTwelveHours = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 12 /* hh */, 00 /* mm */); + }; + + // But if we sure that in this time edge: 0->1 will be blocked, we definitely should not pass + // 0->1. In this case no way will be found. + graph.SetCurrentTimeGetter(mondayTwelveHours); + expectedWeight = 0.0; + expectedEdges = {}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, false /* pathFound */, expectedWeight, + expectedEdges); +} + +UNIT_TEST(RoadAccess_PointBlockedAvoidConditional) +{ + uint32_t const numVertices = 4; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(0, 2, 10.0); + graph.AddDirectedEdge(1, 3, 1.0); + graph.AddDirectedEdge(2, 3, 10.0); + + double expectedWeight = 2.0; + vector expectedEdges = {{0, 1}, {1, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetVertexAccessConditional(1, RoadAccess::Type::No, "Mo-Fr 10:00 - 19:00"); + + auto const mondayAlmostTenHours = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 9 /* hh */, 50 /* mm */); + }; + + // In this time we probably will able to pass vertex: 1, but we are not sure, so we should avoid + // such edges. + graph.SetCurrentTimeGetter(mondayAlmostTenHours); + expectedWeight = 20.0; + expectedEdges = {{0, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetEdgeAccess(0, 2, RoadAccess::Type::No); + + // But if this is the only path (we blocked 0->2 above), we should pass through vertex: 1 anyway. + graph.SetCurrentTimeGetter(mondayAlmostTenHours); + expectedWeight = 2.0; + expectedEdges = {{0, 1}, {1, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + auto const mondayTwelveHours = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 12 /* hh */, 00 /* mm */); + }; + + // But if we sure that in this time vertex: 1 will be blocked, we definitely should not pass + // through vertex: 1. In this case no way will be found. + graph.SetCurrentTimeGetter(mondayTwelveHours); + expectedWeight = 0.0; + expectedEdges = {}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, false /* pathFound */, expectedWeight, + expectedEdges); +} + +UNIT_TEST(RoadAccess_WayBlockedConditional_Yes_No) +{ + uint32_t const numVertices = 4; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(1, 2, 1.0); + graph.AddDirectedEdge(2, 3, 1.0); + + double expectedWeight = 3.0; + vector expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetEdgeAccessConditional(1, 2, RoadAccess::Type::No, "Mo-Fr"); + graph.SetEdgeAccessConditional(1, 2, RoadAccess::Type::Yes, "Sa-Su"); + + auto const tuesday = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Tuesday, 10 /* hh */, 00 /* mm */); + }; + + // Way is blocked from Monday to Friday + graph.SetCurrentTimeGetter(tuesday); + expectedWeight = 0; + expectedEdges = {}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, false /* pathFound */, expectedWeight, + expectedEdges); + + auto const saturday = []() { + return GetUnixtimeByDate(2020, Month::Nov, Weekday::Saturday, 10 /* hh */, 00 /* mm */); + }; + + // And open from Saturday to Sunday + graph.SetCurrentTimeGetter(saturday); + expectedWeight = 3.0; + expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); +} + +UNIT_TEST(RoadAccess_PointBlockedConditional_Yes_No) +{ + uint32_t const numVertices = 4; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(1, 2, 1.0); + graph.AddDirectedEdge(2, 3, 1.0); + + double expectedWeight = 3.0; + vector expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetVertexAccessConditional(1, RoadAccess::Type::No, "Mo-Fr"); + graph.SetVertexAccessConditional(1, RoadAccess::Type::Yes, "Sa-Su"); + + auto const tuesday = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Tuesday, 10 /* hh */, 00 /* mm */); + }; + + // Way is blocked from Monday to Friday + graph.SetCurrentTimeGetter(tuesday); + expectedWeight = 0; + expectedEdges = {}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, false /* pathFound */, expectedWeight, + expectedEdges); + + auto const saturday = []() { + return GetUnixtimeByDate(2020, Month::Nov, Weekday::Saturday, 10 /* hh */, 00 /* mm */); + }; + + // And open from Saturday to Sunday + graph.SetCurrentTimeGetter(saturday); + expectedWeight = 3.0; + expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); +} + +UNIT_TEST(RoadAccess_WayBlockedAvoidPrivateConditional) +{ + uint32_t const numVertices = 4; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(0, 2, 10.0); + graph.AddDirectedEdge(1, 3, 1.0); + graph.AddDirectedEdge(2, 3, 10.0); + + double expectedWeight = 2.0; + vector expectedEdges = {{0, 1}, {1, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetEdgeAccessConditional(0, 1, RoadAccess::Type::Private, "Mo-Fr 19:00 - 23:00"); + + auto const mondayAlmostTwentyHalfHours = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 20 /* hh */, 30 /* mm */); + }; + + // We should avoid ways with private accesses. At 20:30 edge: 0->1 definitely has private access, + // thus the answer is: 0->2->3. + graph.SetCurrentTimeGetter(mondayAlmostTwentyHalfHours); + expectedWeight = 20.0; + expectedEdges = {{0, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + graph.SetEdgeAccess(0, 2, RoadAccess::Type::No); + + // But if this is the only path (we blocked 0->2 above), we should pass through edge: 0->1 anyway. + graph.SetCurrentTimeGetter(mondayAlmostTwentyHalfHours); + expectedWeight = 2.0; + expectedEdges = {{0, 1}, {1, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); +} + +UNIT_TEST(RoadAccess_WayBlockedAlwaysNoExceptMonday) +{ + uint32_t const numVertices = 4; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(1, 2, 1.0); + graph.AddDirectedEdge(2, 3, 1.0); + + double expectedWeight = 3.0; + vector expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + // Always access no for edge: 1->2. + graph.SetEdgeAccess(1, 2, RoadAccess::Type::No); + expectedWeight = 0; + expectedEdges = {}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, false /* pathFound */, expectedWeight, + expectedEdges); + + // Except Monday, access yes in this day. + graph.SetEdgeAccessConditional(1, 2, RoadAccess::Type::Yes, "Mo"); + + auto const monday = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 10 /* hh */, 00 /* mm */); + }; + + graph.SetCurrentTimeGetter(monday); + expectedWeight = 3.0; + expectedEdges = {{0, 1}, {1, 2}, {2, 3}}; + TestTopologyGraph(graph, 0 /* from */, 3 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); +} + +UNIT_TEST(RoadAccess_WayBlockedWhenStartButOpenWhenReach) +{ + uint32_t const numVertices = 7; + TestIndexGraphTopology graph(numVertices); + + graph.AddDirectedEdge(0, 1, 1.0); + graph.AddDirectedEdge(1, 2, 1.0); + graph.AddDirectedEdge(2, 3, 10800.0); + graph.AddDirectedEdge(3, 4, 1.0); + graph.AddDirectedEdge(4, 5, 1.0); + + // Alternative way from |3| to |5|. + graph.AddDirectedEdge(3, 6, 1000.0); + graph.AddDirectedEdge(6, 5, 1000.0); + + double expectedWeight = 10804.0; + vector expectedEdges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}}; + TestTopologyGraph(graph, 0 /* from */, 5 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + auto const startAt_11_50 = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 11 /* hh */, 50 /* mm */); + }; + + graph.SetCurrentTimeGetter(startAt_11_50); + // When we will be at |3|, current time should be: + // 11:50:00 + (0, 1) weight + (1, 2) weight + (2, 3) weight == 14:50:01, so we should ignore + // access: (3, 4). + graph.SetEdgeAccessConditional(3, 4, RoadAccess::Type::No, "10:00 - 13:00"); + expectedWeight = 10804.0; + expectedEdges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}}; + TestTopologyGraph(graph, 0 /* from */, 5 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + auto const startAt_10_50 = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 10 /* hh */, 50 /* mm */); + }; + + graph.SetCurrentTimeGetter(startAt_10_50); + // When we will be at |3|, current time should be: + // 11:50:00 + (0, 1) weight + (1, 2) weight + (2, 3) weight == 12:50:01. This time places in + // dangerous zone, but we are not sure, so we should chose alternative way. + expectedWeight = 12802.0; + expectedEdges = {{0, 1}, {1, 2}, {2, 3}, {3, 6}, {6, 5}}; + TestTopologyGraph(graph, 0 /* from */, 5 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + // Block alternative way. + graph.SetEdgeAccess(3, 6, RoadAccess::Type::No); + // We are still in dangerous zone, but alternative way is blocked, so we should chose dangerous + // way. + expectedWeight = 10804.0; + expectedEdges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}}; + TestTopologyGraph(graph, 0 /* from */, 5 /* to */, true /* pathFound */, expectedWeight, + expectedEdges); + + auto const startAt_9_00 = []() { + return GetUnixtimeByDate(2020, Month::Apr, Weekday::Monday, 9 /* hh */, 00 /* mm */); + }; + + graph.SetCurrentTimeGetter(startAt_9_00); + // If we start at 9:00:00 we will arrive at |3| at: + // 9:00:00 + (0, 1) weight + (1, 2) weight + (2, 3) weight == 12:00:02 + // At this time are sure that (3, 4) way is blocked, so (remember that we also blocked alternative + // way) no way should be found. + expectedWeight = 0.0; + expectedEdges = {}; + TestTopologyGraph(graph, 0 /* from */, 5 /* to */, false /* pathFound */, expectedWeight, + expectedEdges); +} } // namespace diff --git a/routing/transit_graph.cpp b/routing/transit_graph.cpp index 2174aba283..8ffdee6035 100644 --- a/routing/transit_graph.cpp +++ b/routing/transit_graph.cpp @@ -61,14 +61,14 @@ RouteWeight TransitGraph::CalcSegmentWeight(Segment const & segment, { auto const weight = GetGate(segment).GetWeight(); return RouteWeight(weight /* weight */, 0 /* numPassThroughChanges */, 0 /* numAccessChanges */, - weight /* transitTime */); + 0 /* numAccessConditionalPenalties */, weight /* transitTime */); } if (IsEdge(segment)) { auto const weight = GetEdge(segment).GetWeight(); return RouteWeight(weight /* weight */, 0 /* numPassThroughChanges */, 0 /* numAccessChanges */, - weight /* transitTime */); + 0 /* numAccessConditionalPenalties */, weight /* transitTime */); } return RouteWeight( @@ -101,7 +101,7 @@ RouteWeight TransitGraph::GetTransferPenalty(Segment const & from, Segment const auto const it = m_transferPenalties.find(lineIdTo); CHECK(it != m_transferPenalties.cend(), ("Segment", to, "belongs to unknown line:", lineIdTo)); return RouteWeight(it->second /* weight */, 0 /* nonPassThrougCross */, 0 /* numAccessChanges */, - it->second /* transitTime */); + 0 /* numAccessConditionalPenalties */, it->second /* transitTime */); } void TransitGraph::GetTransitEdges(Segment const & segment, bool isOutgoing,