diff --git a/generator/generator_tests/metadata_parser_test.cpp b/generator/generator_tests/metadata_parser_test.cpp index 8603f192fd..5903cb9d87 100644 --- a/generator/generator_tests/metadata_parser_test.cpp +++ b/generator/generator_tests/metadata_parser_test.cpp @@ -201,3 +201,59 @@ UNIT_TEST(Metadata_ValidateAndFormat_wikipedia) #undef WIKIHOST } + +UNIT_TEST(Metadata_ValidateAndFormat_duration) +{ + FeatureParams params; + MetadataTagProcessor p(params); + Metadata & md = params.GetMetadata(); + + auto const test = [&](std::string const & osm, std::string const & expected) { + p("duration", osm); + + if (expected.empty()) + { + TEST(md.Empty(), ()); + } + else + { + TEST_EQUAL(md.Get(Metadata::FMD_DURATION), expected, ()); + md.Drop(Metadata::FMD_DURATION); + } + }; + + test("10", "0.16667"); + test("10:00", "10"); + test("QWE", ""); + test("1:1:1", "1.0169"); + test("10:30", "10.5"); + test("30", "0.5"); + test("60", "1"); + test("120", "2"); + test("35:10", "35.167"); + + test("35::10", ""); + test("", ""); + test("0", ""); + test("asd", ""); + test("10 minutes", ""); + test("01:15 h", ""); + test("08:00;07:00;06:30", ""); + test("3-4 minutes", ""); + test("5:00 hours", ""); + test("12 min", ""); + + test("PT20S", "0.0055556"); + test("PT7M", "0.11667"); + test("PT10M40S", "0.17778"); + test("PT50M", "0.83333"); + test("PT2H", "2"); + test("PT7H50M", "7.8333"); + test("PT60M", "1"); + test("PT15M", "0.25"); + + test("PT1000Y", ""); + test("PTPT", ""); + test("P4D", ""); + test("PT50:20", ""); +} diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index 10283376d1..df12b8383e 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -7,17 +7,20 @@ #include "coding/url_encode.hpp" #include "base/logging.hpp" +#include "base/math.hpp" #include "base/string_utils.hpp" #include +#include #include #include #include -#include #include #include #include +#include "boost/optional.hpp" + using namespace std; namespace @@ -284,3 +287,110 @@ string MetadataTagProcessorImpl::ValidateAndFormat_airport_iata(string const & v } return str; } + +string MetadataTagProcessorImpl::ValidateAndFormat_duration(string const & v) const +{ + auto const format = [](double hours) -> std::string { + if (base::AlmostEqualAbs(hours, 0.0, 1e-5)) + return ""; + + std::stringstream ss; + ss << std::setprecision(5); + ss << hours; + return ss.str(); + }; + + auto const readNumber = [&v](size_t startPos, size_t & endPos) -> boost::optional { + uint32_t number = 0; + while (startPos < v.size() && isdigit(v[startPos])) + { + number *= 10; + number += v[startPos] - '0'; + ++startPos; + } + + if (startPos == endPos) + return {}; + + endPos = startPos; + return {number}; + }; + + auto const convert = [](char type, uint32_t number) -> boost::optional { + switch (type) + { + case 'H': return number; + case 'M': return number / 60.0; + case 'S': return number / 3600.0; + } + + return {}; + }; + + if (v.empty()) + return {}; + + double hours = 0; + size_t pos = 0; + boost::optional op; + + if (v[0] == 'P') + { + if (v.size() < 4) + return {}; + + if (v.substr(0, 2) != "PT") + return {}; + + pos = 2; + while (pos < v.size() && (op = readNumber(pos, pos))) + { + if (pos >= v.size()) + return {}; + + char const type = v[pos]; + auto const addHours = convert(type, *op); + if (addHours) + hours += *addHours; + else + return {}; + + ++pos; + } + + if (!op) + return {}; + + return format(hours); + } + + // "hh:mm:ss" or just "mm" + vector numbers; + while (pos < v.size() && (op = readNumber(pos, pos))) + { + numbers.emplace_back(*op); + if (pos >= v.size()) + break; + + if (v[pos] != ':') + return {}; + + ++pos; + } + + if (numbers.size() > 3 || !op) + return {}; + + if (numbers.size() == 1) + return format(numbers.back() / 60.0); + + double pow = 1; + for (auto number : numbers) + { + hours += number / pow; + pow *= 60; + } + + return format(hours); +} + diff --git a/generator/osm2meta.hpp b/generator/osm2meta.hpp index 23bf5938bc..3c95e20be8 100644 --- a/generator/osm2meta.hpp +++ b/generator/osm2meta.hpp @@ -34,6 +34,7 @@ struct MetadataTagProcessorImpl std::string ValidateAndFormat_denomination(std::string const & v) const; std::string ValidateAndFormat_wikipedia(std::string v) const; std::string ValidateAndFormat_airport_iata(std::string const & v) const; + std::string ValidateAndFormat_duration(std::string const & v) const; protected: FeatureParams & m_params; @@ -96,6 +97,7 @@ 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; // Metadata types we do not get from OSM. case Metadata::FMD_SPONSORED_ID: case Metadata::FMD_PRICE_RATE: diff --git a/indexer/feature_meta.cpp b/indexer/feature_meta.cpp index 745f612e9a..d8ce86c4b0 100644 --- a/indexer/feature_meta.cpp +++ b/indexer/feature_meta.cpp @@ -94,6 +94,8 @@ bool Metadata::TypeFromString(string const & k, Metadata::EType & outType) outType = Metadata::FMD_LEVEL; else if (k == "iata") outType = Metadata::FMD_AIRPORT_IATA; + else if (k == "duration") + outType = Metadata::FMD_DURATION; else return false; @@ -193,6 +195,7 @@ string ToString(Metadata::EType type) case Metadata::FMD_LEVEL: return "level"; case Metadata::FMD_AIRPORT_IATA: return "iata"; case Metadata::FMD_BRAND: return "brand"; + case Metadata::FMD_DURATION: return "duration"; case Metadata::FMD_COUNT: CHECK(false, ("FMD_COUNT can not be used as a type.")); }; diff --git a/indexer/feature_meta.hpp b/indexer/feature_meta.hpp index 43f0d48cb6..763721d9d0 100644 --- a/indexer/feature_meta.hpp +++ b/indexer/feature_meta.hpp @@ -133,6 +133,7 @@ public: FMD_LEVEL = 28, FMD_AIRPORT_IATA = 29, FMD_BRAND = 30, + FMD_DURATION = 31, FMD_COUNT }; diff --git a/indexer/map_object.hpp b/indexer/map_object.hpp index d87d94d077..c67cd1ad26 100644 --- a/indexer/map_object.hpp +++ b/indexer/map_object.hpp @@ -167,6 +167,7 @@ std::vector MetadataToProps(std::vector const & metadata) case Metadata::FMD_BANNER_URL: case Metadata::FMD_AIRPORT_IATA: case Metadata::FMD_BRAND: + case Metadata::FMD_DURATION: case Metadata::FMD_COUNT: break; // Please add new cases when compiler issues an "unhandled switch case" warning here.