diff options
author | Maxim Pimenov <m@maps.me> | 2020-06-23 15:17:58 +0300 |
---|---|---|
committer | Tatiana Yan <tatiana.kondakova@gmail.com> | 2021-02-16 16:27:13 +0300 |
commit | 3a2ca5e4f4693c8b561e3446d7a5e2ceff5efc24 (patch) | |
tree | 76674a56758aa2341eba76a57d3b01e7fcf44e6a /search | |
parent | dc68ae3771f36faa3413c65e13243e053ace19ec (diff) |
[search] Add a mode to find features by id.
Diffstat (limited to 'search')
-rw-r--r-- | search/processor.cpp | 215 | ||||
-rw-r--r-- | search/processor.hpp | 12 | ||||
-rw-r--r-- | search/search_integration_tests/processor_test.cpp | 30 |
3 files changed, 256 insertions, 1 deletions
diff --git a/search/processor.cpp b/search/processor.cpp index 05cfc72aa9..43b7737df0 100644 --- a/search/processor.cpp +++ b/search/processor.cpp @@ -59,8 +59,11 @@ #include "base/string_utils.hpp" #include <algorithm> +#include <cctype> +#include <memory> #include <set> #include <sstream> +#include <utility> #include "3party/Alohalytics/src/alohalytics.h" #include "3party/open-location-code/openlocationcode.h" @@ -430,6 +433,212 @@ bool Processor::IsCancelled() const return ret; } +void Processor::SearchByFeatureId() +{ + // String processing is suboptimal in this method so + // we need a guard against very long strings. + size_t const kMaxFeatureIdStringSize = 1000; + if (m_query.size() > kMaxFeatureIdStringSize) + return; + + using Trie = base::MemTrie<storage::CountryId, base::VectorValues<bool>>; + static Trie countriesTrie; + if (countriesTrie.GetNumNodes() == 1) + { + for (auto const & country : m_infoGetter.GetCountries()) + countriesTrie.Add(country.m_countryId, true); + } + + auto trimLeadingSpaces = [](string & s) { + while (!s.empty() && s.front() == ' ') + s = s.substr(1); + }; + + auto const eatFid = [&trimLeadingSpaces](string & s, uint32_t & fid) -> bool { + trimLeadingSpaces(s); + + if (s.empty()) + return false; + + size_t i = 0; + while (i < s.size() && isdigit(s[i])) + ++i; + + auto const prefix = s.substr(0, i); + if (strings::to_uint32(prefix, fid)) + { + s = s.substr(prefix.size()); + return true; + } + return false; + }; + + auto const eatMwmName = [&trimLeadingSpaces](string & s, storage::CountryId & mwmName) -> bool { + trimLeadingSpaces(s); + + // Greedily eat as much as possible because some country names are prefixes of others. + optional<size_t> lastPos; + for (size_t i = 0; i < s.size(); ++i) + { + // todo(@m) This must be much faster but MemTrie's iterators do not expose nodes. + if (countriesTrie.HasKey(s.substr(0, i))) + lastPos = i; + } + if (!lastPos) + return false; + + mwmName = s.substr(0, *lastPos); + s = s.substr(*lastPos); + strings::EatPrefix(s, ".mwm"); + return true; + }; + + auto const eatVersion = [&trimLeadingSpaces](string & s, uint32_t & version) -> bool { + trimLeadingSpaces(s); + + if (!s.empty() && s.front() == '0' && (s.size() == 1 || !isdigit(s[1]))) + { + version = 0; + s = s.substr(1); + return true; + } + + size_t const kVersionLength = 6; + if (s.size() >= kVersionLength && all_of(s.begin(), s.begin() + kVersionLength, ::isdigit) && + (s.size() == kVersionLength || !isdigit(s[kVersionLength + 1]))) + { + VERIFY(strings::to_uint32(s.substr(0, kVersionLength), version), ()); + s = s.substr(kVersionLength); + return true; + } + + return false; + }; + + // Create a copy of the query to trim it in-place. + string query(m_query); + strings::Trim(query); + + string const kFidPrefix = "fid"; + bool hasPrefix = false; + + if (strings::EatPrefix(query, kFidPrefix)) + { + hasPrefix = true; + + strings::Trim(query); + if (strings::EatPrefix(query, "=")) + strings::Trim(query); + } + + vector<shared_ptr<MwmInfo>> infos; + m_dataSource.GetMwmsInfo(infos); + + // Case 0. + if (hasPrefix) + { + string s = query; + uint32_t fid; + if (eatFid(s, fid)) + { + // Loop over mwms, sort by distance. + + vector<tuple<double, m2::PointD, std::string, std::unique_ptr<FeatureType>>> results; + vector<unique_ptr<FeaturesLoaderGuard>> guards; + for (auto const & info : infos) + { + auto guard = make_unique<FeaturesLoaderGuard>(m_dataSource, MwmSet::MwmId(info)); + if (fid >= guard->GetNumFeatures()) + continue; + auto ft = guard->GetFeatureByIndex(fid); + if (!ft) + continue; + auto const center = feature::GetCenter(*ft, FeatureType::WORST_GEOMETRY); + double dist = center.SquaredLength(m_viewport.Center()); + auto pivot = m_viewport.Center(); + if (m_position) + { + auto const distPos = center.SquaredLength(*m_position); + if (dist > distPos) + { + dist = distPos; + pivot = *m_position; + } + } + results.emplace_back(dist, pivot, guard->GetCountryFileName(), move(ft)); + guards.push_back(move(guard)); + } + sort(results.begin(), results.end()); + for (auto const & [dist, pivot, country, ft] : results) + { + auto const center = feature::GetCenter(*ft, FeatureType::WORST_GEOMETRY); + string name; + ft->GetReadableName(name); + m_emitter.AddResultNoChecks( + m_ranker.MakeResult(RankerResult(*ft, center, pivot, name, country), + true /* needAddress */, true /* needHighlighting */)); + m_emitter.Emit(); + } + } + } + + auto const tryEmitting = [this, &infos](storage::CountryId const & mwmName, uint32_t fid) { + for (auto const & info : infos) + { + if (info->GetCountryName() != mwmName) + continue; + + auto guard = make_unique<FeaturesLoaderGuard>(m_dataSource, MwmSet::MwmId(info)); + if (fid >= guard->GetNumFeatures()) + continue; + auto ft = guard->GetFeatureByIndex(fid); + if (!ft) + continue; + auto const center = feature::GetCenter(*ft, FeatureType::WORST_GEOMETRY); + string name; + ft->GetReadableName(name); + m_emitter.AddResultNoChecks(m_ranker.MakeResult( + RankerResult(*ft, center, m2::PointD() /* pivot */, name, guard->GetCountryFileName()), + true /* needAddress */, true /* needHighlighting */)); + m_emitter.Emit(); + } + }; + + // Case 1. + if (hasPrefix) + { + string s = query; + bool const parenPref = strings::EatPrefix(s, "("); + bool const parenSuff = strings::EatSuffix(s, ")"); + storage::CountryId mwmName; + uint32_t fid; + if (parenPref == parenSuff && eatMwmName(s, mwmName) && strings::EatPrefix(s, ",") && + eatFid(s, fid)) + { + tryEmitting(mwmName, fid); + } + } + + // Case 2. + { + string s = query; + storage::CountryId mwmName; + uint32_t version; + uint32_t fid; + + bool ok = true; + ok = ok && strings::EatPrefix(s, "{ MwmId ["); + ok = ok && eatMwmName(s, mwmName); + ok = ok && strings::EatPrefix(s, ", "); + ok = ok && eatVersion(s, version); + ok = ok && strings::EatPrefix(s, "], "); + ok = ok && eatFid(s, fid); + ok = ok && strings::EatPrefix(s, " }"); + if (ok) + tryEmitting(mwmName, fid); + } +} + Locales Processor::GetCategoryLocales() const { static int8_t const enLocaleCode = CategoriesHolder::MapLocaleToInteger("en"); @@ -511,6 +720,7 @@ void Processor::Search(SearchParams const & params) try { + SearchDebug(); SearchCoordinates(); SearchPlusCode(); SearchPostcode(); @@ -552,6 +762,11 @@ void Processor::Search(SearchParams const & params) SendStatistics(params, viewport, m_emitter.GetResults()); } +void Processor::SearchDebug() +{ + SearchByFeatureId(); +} + void Processor::SearchCoordinates() { buffer_vector<ms::LatLon, 3> results; diff --git a/search/processor.hpp b/search/processor.hpp index 82d5b1c310..61d88934d6 100644 --- a/search/processor.hpp +++ b/search/processor.hpp @@ -72,6 +72,8 @@ public: void Search(SearchParams const & params); + // Tries to parse a custom debugging command from |m_query|. + void SearchDebug(); // Tries to generate a (lat, lon) result from |m_query|. void SearchCoordinates(); // Tries to parse a plus code from |m_query| and generate a (lat, lon) result. @@ -112,6 +114,16 @@ public: bool IsCancelled() const override; protected: + // Show feature by FeatureId. May try to guess as much as possible after the "fid=" prefix but + // at least supports the formats below. + // 0. fid=123 to search for the feature with index 123, results ordered by distance + // from |m_position| or |m_viewport|, whichever is present and closer. + // 1. fid=MwmName,123 or fid=(MwmName,123) to search for the feature with + // index 123 in the Mwm "MwmName" (for example, "Laos" or "Laos.mwm"). + // 2. fid={ MwmId [Laos, 200623], 123 } or just { MwmId [Laos, 200623], 123 } or whatever current + // format of the string returned by FeatureID's DebugPrint is. + void SearchByFeatureId(); + Locales GetCategoryLocales() const; template <typename ToDo> diff --git a/search/search_integration_tests/processor_test.cpp b/search/search_integration_tests/processor_test.cpp index 7bfc8e3975..dadac918ea 100644 --- a/search/search_integration_tests/processor_test.cpp +++ b/search/search_integration_tests/processor_test.cpp @@ -972,8 +972,36 @@ UNIT_CLASS_TEST(ProcessorTest, TestCategorialSearch) } } -UNIT_CLASS_TEST(ProcessorTest, TestCoords) +UNIT_CLASS_TEST(ProcessorTest, SearchDebug) { + string const countryName = "Wonderland"; + + TestCity debugville(m2::PointD(0, 0), "Debugville", "en", 100 /* rank */); + + TestCafe cafe(m2::PointD(0.01, 0), "", "ru"); + + TestPOI hotel(m2::PointD(0, 0.01), "", "ru"); + hotel.SetTypes({{"tourism", "hotel"}}); + + auto const testWorldId = BuildWorld([&](TestMwmBuilder & builder) { + builder.Add(debugville); + }); + auto wonderlandId = BuildCountry(countryName, [&](TestMwmBuilder & builder) { + builder.Add(hotel); + builder.Add(cafe); + }); + + auto const ruleCity = ExactMatch(testWorldId, debugville); + auto const ruleCafe = ExactMatch(wonderlandId, cafe); + auto const ruleHotel = ExactMatch(wonderlandId, hotel); + + TEST(ResultsMatch("fid=0", {ruleCity, ruleCafe}), ()); + TEST(ResultsMatch("fid=1 ", {ruleHotel}), ()); +} + +UNIT_CLASS_TEST(ProcessorTest, SearchCoordinates) +{ + // <query, lat, lon> vector<tuple<string, double, double>> tests = { {"51.681644 39.183481", 51.681644, 39.183481}, |