diff --git a/generator/generator_tests/road_access_test.cpp b/generator/generator_tests/road_access_test.cpp index 4e17b88a6e..738134d1d7 100644 --- a/generator/generator_tests/road_access_test.cpp +++ b/generator/generator_tests/road_access_test.cpp @@ -229,4 +229,76 @@ UNIT_TEST(RoadAccessWriter_Merge) "Car Private 2 3\n"; TEST_EQUAL(buffer.str(), correctAnswer, ()); } + +UNIT_TEST(RoadAccessCoditionalParse) +{ + AccessConditionalTagParser parser; + + using ConditionalVector = std::vector; + std::vector> tests = { + {"no @ Mo-Su", + {{RoadAccess::Type::No, "Mo-Su"}}}, + + {"no @ Mo-Su;", + {{RoadAccess::Type::No, "Mo-Su"}}}, + + {"yes @ (10:00 - 20:00)", + {{RoadAccess::Type::Yes, "10:00 - 20:00"}}}, + + {"private @ Mo-Fr 15:00-20:00", + {{RoadAccess::Type::Private, "Mo-Fr 15:00-20:00"}}}, + + {"destination @ 10:00-20:00", + {{RoadAccess::Type::Destination, "10:00-20:00"}}}, + + {"yes @ Mo-Fr ; Sa-Su", + {{RoadAccess::Type::Yes, "Mo-Fr ; Sa-Su"}}}, + + {"no @ (Mo-Su) ; yes @ (Fr-Su)", + {{RoadAccess::Type::No, "Mo-Su"}, + + {RoadAccess::Type::Yes, "Fr-Su"}}}, + {"private @ (18:00-09:00; Oct-Mar)", {{RoadAccess::Type::Private, "18:00-09:00; Oct-Mar"}}}, + + {"no @ (Nov-May); no @ (20:00-07:00)", + {{RoadAccess::Type::No, "Nov-May"}, + {RoadAccess::Type::No, "20:00-07:00"}}}, + + {"no @ 22:30-05:00", + {{RoadAccess::Type::No, "22:30-05:00"}}}, + + {"destination @ (Mo-Fr 06:00-15:00); yes @ (Mo-Fr 15:00-21:00; Sa,Su,SH,PH 09:00-21:00)", + {{RoadAccess::Type::Destination, "Mo-Fr 06:00-15:00"}, + {RoadAccess::Type::Yes, "Mo-Fr 15:00-21:00; Sa,Su,SH,PH 09:00-21:00"}}}, + + {"no @ (Mar 15-Jul 15); private @ (Jan- Dec)", + {{RoadAccess::Type::No, "Mar 15-Jul 15"}, + {RoadAccess::Type::Private, "Jan- Dec"}}}, + + {"no @ (06:30-08:30);destination @ (06:30-08:30 AND agricultural)", + {{RoadAccess::Type::No, "06:30-08:30"}, + {RoadAccess::Type::Destination, "06:30-08:30 AND agricultural"}}}, + + {"no @ (Mo-Fr 00:00-08:00,20:00-24:00; Sa-Su 00:00-24:00; PH 00:00-24:00)", + {{RoadAccess::Type::No, "Mo-Fr 00:00-08:00,20:00-24:00; Sa-Su 00:00-24:00; PH 00:00-24:00"}}} + }; + + std::vector tags = { + "motorcar:conditional", + "vehicle:conditional", + "motor_vehicle:conditional", + "bicycle:conditional", + "foot:conditional" + }; + + for (auto const & tag : tags) + { + for (auto const & test : tests) + { + auto const & [value, answer] = test; + auto const access = parser.ParseAccessConditionalTag(tag, value); + TEST(access == answer, (value, tag)); + } + } +} } // namespace diff --git a/generator/road_access_generator.cpp b/generator/road_access_generator.cpp index 1741941a2c..0614c6012e 100644 --- a/generator/road_access_generator.cpp +++ b/generator/road_access_generator.cpp @@ -39,6 +39,26 @@ namespace char constexpr kDelim[] = " \t\r\n"; using TagMapping = routing::RoadAccessTagProcessor::TagMapping; +using ConditionalTagsList = routing::RoadAccessTagProcessor::ConditionalTagsList; + +// Get from: https://taginfo.openstreetmap.org/search?q=%3Aconditional +// @{ +vector const kCarAccessConditionalTags = { + "motorcar", "vehicle", "motor_vehicle" +}; + +vector const kDefaultAccessConditionalTags = { + "access" +}; + +vector const kPedestrianAccessConditionalTags = { + "foot" +}; + +vector const kBycicleAccessConditionalTags = { + "bicycle" +}; +// @} TagMapping const kMotorCarTagMapping = { {OsmElement::Tag("motorcar", "yes"), RoadAccess::Type::Yes}, @@ -245,6 +265,30 @@ RoadAccess::Type GetAccessTypeFromMapping(OsmElement const & elem, TagMapping co } return RoadAccess::Type::Count; } + +optional GetConditionalTag(OsmElement const & elem, + vector const & tagsList) +{ + for (auto const & tags : tagsList) + { + for (auto const & tag : tags) + if (elem.HasTag(tag)) + return {elem.GetTag(tag)}; + } + return nullopt; +} + +// "motor_vehicle:conditional" -> "motor_vehicle" +// "access:conditional" -> "access" +// etc. +string GetVehicleTypeForAccessConditional(string const & accessConditionalTag) +{ + auto const pos = accessConditionalTag.find(":"); + CHECK_NOT_EQUAL(pos, string::npos, (accessConditionalTag)); + + string result(accessConditionalTag.begin(), accessConditionalTag.begin() + pos); + return result; +} } // namespace namespace routing @@ -262,10 +306,14 @@ RoadAccessTagProcessor::RoadAccessTagProcessor(VehicleType vehicleType) m_accessMappings.push_back(&kDefaultTagMapping); m_barrierMappings.push_back(&kCarBarriersTagMapping); m_hwIgnoreBarriersWithoutAccess = kHighwaysWhereIgnoreBarriersWithoutAccess; + m_conditionalTagsVector.push_back(kCarAccessConditionalTags); + m_conditionalTagsVector.push_back(kDefaultAccessConditionalTags); break; case VehicleType::Pedestrian: m_accessMappings.push_back(&kPedestrianTagMapping); m_accessMappings.push_back(&kDefaultTagMapping); + m_conditionalTagsVector.push_back(kPedestrianAccessConditionalTags); + m_conditionalTagsVector.push_back(kDefaultAccessConditionalTags); break; case VehicleType::Bicycle: m_accessMappings.push_back(&kBicycleTagMapping); @@ -273,6 +321,8 @@ RoadAccessTagProcessor::RoadAccessTagProcessor(VehicleType vehicleType) m_accessMappings.push_back(&kDefaultTagMapping); m_barrierMappings.push_back(&kBicycleBarriersTagMapping); m_hwIgnoreBarriersWithoutAccess = kHighwaysWhereIgnoreBarriersWithoutAccess; + m_conditionalTagsVector.push_back(kBycicleAccessConditionalTags); + m_conditionalTagsVector.push_back(kDefaultAccessConditionalTags); break; case VehicleType::Transit: // Use kTransitTagMapping to keep transit section empty. We'll use pedestrian section for @@ -320,6 +370,11 @@ void RoadAccessTagProcessor::Process(OsmElement const & elem) m_barriersWithoutAccessTag.emplace(elem.m_id, *op); } +void RoadAccessTagProcessor::ProcessConditional(OsmElement const & elem) +{ + // to be implemented +} + void RoadAccessTagProcessor::WriteWayToAccess(ostream & stream) { // All feature tags. @@ -485,6 +540,121 @@ RoadAccessCollector::RoadAccessCollector(string const & dataFilePath, string con m_roadAccessByVehicleType.swap(roadAccessByVehicleType); } +// AccessConditionalTagParser ---------------------------------------------------------------------- +AccessConditionalTagParser::AccessConditionalTagParser() +{ + m_vehiclesToRoadAccess.push_back(kMotorCarTagMapping); + m_vehiclesToRoadAccess.push_back(kMotorVehicleTagMapping); + m_vehiclesToRoadAccess.push_back(kVehicleTagMapping); + m_vehiclesToRoadAccess.push_back(kPedestrianTagMapping); + m_vehiclesToRoadAccess.push_back(kBicycleTagMapping); + m_vehiclesToRoadAccess.push_back(kDefaultTagMapping); +} + +vector AccessConditionalTagParser:: + ParseAccessConditionalTag(string const & tag, string const & value) +{ + size_t pos = 0; + + string const vehicleType = GetVehicleTypeForAccessConditional(tag); + vector accessConditionals; + while (pos < value.size()) + { + AccessConditionalTagParser::AccessConditional access; + auto accessOp = ReadUntilSymbol(value, pos, '@'); + if (!accessOp) + break; + + string accessString; + tie(pos, accessString) = *accessOp; + ++pos; // skip '@' + strings::Trim(accessString); + + access.m_accessType = GetAccessByVehicleAndStringValue(vehicleType, accessString); + + auto substrOp = ReadUntilSymbol(value, pos, ';'); + if (!substrOp) + { + auto openingHours = std::string(value.begin() + pos, value.end()); + access.m_openingHours = TrimAndDropAroundParentheses(move(openingHours)); + pos = value.size(); + } + else + { + auto [newPos, _] = *substrOp; + // We cannot distinguish these two situations: + // 1) no @ Mo-Fr ; yes @ Sa-Su + // 2) no @ Mo-Fr ; Sa 10:00-19:00 + auto nextAccessOp = ReadUntilSymbol(value, newPos, '@'); + if (nextAccessOp) + { + // This is 1) case. + auto openingHours = std::string(value.begin() + pos, value.begin() + newPos); + access.m_openingHours = TrimAndDropAroundParentheses(move(openingHours)); + pos = newPos; + ++pos; // skip ';' + } + else + { + // This is 2) case. + auto openingHours = std::string(value.begin() + pos, value.end()); + access.m_openingHours = TrimAndDropAroundParentheses(move(openingHours)); + pos = value.size(); + } + } + + accessConditionals.emplace_back(move(access)); + } + + return accessConditionals; +} + +optional> AccessConditionalTagParser::ReadUntilSymbol(string const & input, + size_t startPos, + char symbol) +{ + string result; + while (startPos < input.size() && input[startPos] != symbol) + { + result += input[startPos]; + ++startPos; + } + + if (input[startPos] == symbol) + return make_pair(startPos, result); + + return nullopt; +} + +RoadAccess::Type AccessConditionalTagParser::GetAccessByVehicleAndStringValue( + string const & vehicleFromTag, string const & stringAccessValue) +{ + for (auto const & vehicleToAccess : m_vehiclesToRoadAccess) + { + auto const it = vehicleToAccess.find({vehicleFromTag, stringAccessValue}); + if (it != vehicleToAccess.end()) + return it->second; + } + + return RoadAccess::Type::Count; +} + +string AccessConditionalTagParser::TrimAndDropAroundParentheses(string input) +{ + strings::Trim(input); + + if (input.back() == ';') + input.erase(std::prev(input.end())); + + if (input.front() == '(' && input.back() == ')') + { + input.erase(input.begin()); + input.erase(std::prev(input.end())); + } + + return input; +} + // Functions ------------------------------------------------------------------ void BuildRoadAccessInfo(string const & dataFilePath, string const & roadAccessPath, string const & osmIdsToFeatureIdsPath) diff --git a/generator/road_access_generator.hpp b/generator/road_access_generator.hpp index ce6861d8b4..32380769bb 100644 --- a/generator/road_access_generator.hpp +++ b/generator/road_access_generator.hpp @@ -41,10 +41,12 @@ class RoadAccessTagProcessor { public: using TagMapping = std::map; + using ConditionalTagsList = std::vector; explicit RoadAccessTagProcessor(VehicleType vehicleType); void Process(OsmElement const & elem); + void ProcessConditional(OsmElement const & elem); void WriteWayToAccess(std::ostream & stream); void WriteBarrierTags(std::ostream & stream, uint64_t id, std::vector const & points, bool ignoreBarrierWithoutAccess); @@ -59,6 +61,8 @@ private: // e.g. for car: motorcar, motorvehicle, vehicle, general access tags. std::vector m_accessMappings; + std::vector m_conditionalTagsVector; + // We decided to ignore some barriers without access on some type of highways // because we almost always do not need to add penalty for passes through such nodes. std::optional> m_hwIgnoreBarriersWithoutAccess; @@ -77,8 +81,8 @@ public: ~RoadAccessWriter() override; std::shared_ptr Clone( - std::shared_ptr const & = {}) - const override; + std::shared_ptr const & = {}) + const override; void CollectFeature(feature::FeatureBuilder const & fb, OsmElement const & elem) override; void Finish() override; @@ -113,6 +117,39 @@ private: bool m_valid = true; }; +class AccessConditionalTagParser +{ +public: + struct AccessConditional + { + AccessConditional() = default; + AccessConditional(RoadAccess::Type accessType, std::string const & openingHours) + : m_accessType(accessType), m_openingHours(openingHours) + { + } + + bool operator==(AccessConditional const & rhs) const + { + return std::tie(m_accessType, m_openingHours) == std::tie(rhs.m_accessType, rhs.m_openingHours); + } + + RoadAccess::Type m_accessType = RoadAccess::Type::Count; + std::string m_openingHours; + }; + + AccessConditionalTagParser(); + std::vector ParseAccessConditionalTag(std::string const & tag, std::string const & value); + +private: + RoadAccess::Type GetAccessByVehicleAndStringValue(std::string const & vehicleFromTag, + std::string const & stringAccessValue); + std::optional> ReadUntilSymbol(std::string const & input, + size_t startPos, char symbol); + std::string TrimAndDropAroundParentheses(std::string input); + + std::vector m_vehiclesToRoadAccess; +}; + // The generator tool's interface to writing the section with // road accessibility information for one mwm file. void BuildRoadAccessInfo(std::string const & dataFilePath, std::string const & roadAccessPath,