diff --git a/search/result.cpp b/search/result.cpp index ba388711bd..d5b9fbc435 100644 --- a/search/result.cpp +++ b/search/result.cpp @@ -86,7 +86,10 @@ bool Result::HasPoint() const FeatureID Result::GetFeatureID() const { - ASSERT_EQUAL(GetResultType(), RESULT_FEATURE, ()); +#if defined(DEBUG) + auto const type = GetResultType(); + ASSERT(type == RESULT_FEATURE || type == RESULT_SUGGEST_FROM_FEATURE, (type)); +#endif return m_id; } diff --git a/search/search_integration_tests/search_query_v2_test.cpp b/search/search_integration_tests/search_query_v2_test.cpp index 3f3e517bda..1f830c10f0 100644 --- a/search/search_integration_tests/search_query_v2_test.cpp +++ b/search/search_integration_tests/search_query_v2_test.cpp @@ -276,3 +276,59 @@ UNIT_TEST(SearchQueryV2_Smoke) TEST(MatchResults(engine, rules, request.Results()), ()); } } + +UNIT_TEST(SearchQueryV2_SearchInWorld) +{ + my::ScopedLogLevelChanger const debugLogLevel(LDEBUG); + + classificator::Load(); + platform::LocalCountryFile testWorld(GetPlatform().WritableDir(), + platform::CountryFile("testWorld"), 0); + auto cleanup = [&]() {Cleanup(testWorld);}; + cleanup(); + MY_SCOPE_GUARD(cleanupAtExit, cleanup); + + vector countries; + countries.emplace_back("Wonderland", m2::RectD(m2::PointD(-1.0, -1.0), m2::PointD(1.0, 1.0))); + + TestSearchEngine engine("en", make_unique(countries), + make_unique()); + + auto const wonderland = make_shared(m2::PointD(0, 0), "Wonderland", "en"); + auto const losAlamos = + make_shared(m2::PointD(0, 0), "Los Alamos", "en", 100 /* rank */); + + { + TestMwmBuilder builder(testWorld, feature::DataHeader::world); + builder.Add(*wonderland); + builder.Add(*losAlamos); + } + + auto const result = engine.RegisterMap(testWorld); + TEST_EQUAL(result.second, MwmSet::RegResult::Success, ()); + + auto worldId = result.first; + + m2::RectD const viewport(m2::PointD(-1.0, -1.0), m2::PointD(-0.5, -0.5)); + { + TestSearchRequest request(engine, "Los Alamos", "en", search::SearchParams::ALL, viewport); + request.Wait(); + vector> rules = {make_shared(worldId, losAlamos)}; + TEST(MatchResults(engine, rules, request.Results()), ()); + } + + { + TestSearchRequest request(engine, "Wonderland", "en", search::SearchParams::ALL, viewport); + request.Wait(); + vector> rules = {make_shared(worldId, wonderland)}; + TEST(MatchResults(engine, rules, request.Results()), ()); + } + + { + TestSearchRequest request(engine, "Wonderland Los Alamos", "en", search::SearchParams::ALL, + viewport); + request.Wait(); + vector> rules = {make_shared(worldId, losAlamos)}; + TEST(MatchResults(engine, rules, request.Results()), ()); + } +} diff --git a/search/v2/geocoder.cpp b/search/v2/geocoder.cpp index 6663d3b931..6e6d36dd0b 100644 --- a/search/v2/geocoder.cpp +++ b/search/v2/geocoder.cpp @@ -60,7 +60,7 @@ size_t constexpr kMaxNumLocalities = kMaxNumCities + kMaxNumStates + kMaxNumCoun // List of countries we're supporting search by state. Elements of the // list should be valid prefixes of corresponding mwms names. -string const kCountriesWithStates[] = {"USA_", "Canada_"}; +string const kCountriesWithStates[] = {"US_", "Canada_"}; double constexpr kComparePoints = MercatorBounds::GetCellID2PointAbsEpsilon(); template @@ -193,7 +193,7 @@ void GetEnglishName(FeatureType const & ft, string & name) continue; strings::AsciiToLower(name); if (HasAllSubstrings(name, kUSA)) - name = "usa"; + name = "us"; else if (HasAllSubstrings(name, kUK)) name = "uk"; else @@ -340,6 +340,7 @@ Geocoder::Geocoder(Index & index, storage::CountryInfoGetter const & infoGetter) , m_matcher(nullptr) , m_villages(nullptr) , m_finder(static_cast(*this)) + , m_lastMatchedRegion(nullptr) , m_results(nullptr) { } @@ -483,7 +484,7 @@ void Geocoder::GoImpl(vector> & infos, bool inViewport) m_context = move(context); MY_SCOPE_GUARD(cleanup, [&]() { - LOG(LDEBUG, (m_context->GetMwmName(), "processing complete.")); + LOG(LDEBUG, (m_context->GetName(), "processing complete.")); m_matcher->OnQueryFinished(); m_matcher = nullptr; m_context.reset(); @@ -529,6 +530,8 @@ void Geocoder::GoImpl(vector> & infos, bool inViewport) }); m_usedTokens.assign(m_numTokens, false); + + m_lastMatchedRegion = nullptr; MatchRegions(REGION_TYPE_COUNTRY); if (index < numIntersectingMaps || m_results->empty()) @@ -709,6 +712,7 @@ void Geocoder::FillLocalitiesTable() if (numStates < kMaxNumStates && ft.GetFeatureType() == feature::GEOM_POINT) { Region state(l, REGION_TYPE_STATE); + state.m_center = ft.GetCenter(); string name; GetEnglishName(ft, name); @@ -735,6 +739,8 @@ void Geocoder::FillLocalitiesTable() if (numCountries < kMaxNumCountries && ft.GetFeatureType() == feature::GEOM_POINT) { Region country(l, REGION_TYPE_COUNTRY); + country.m_center = ft.GetCenter(); + GetEnglishName(ft, country.m_enName); m_infoGetter.GetMatchedRegions(country.m_enName, country.m_ids); @@ -794,7 +800,7 @@ void Geocoder::ForEachCountry(vector> const & infos, TFn && for (size_t i = 0; i < infos.size(); ++i) { auto const & info = infos[i]; - if (info->GetType() != MwmInfo::COUNTRY) + if (info->GetType() != MwmInfo::COUNTRY && info->GetType() != MwmInfo::WORLD) continue; auto handle = m_index.GetMwmHandleById(MwmSet::MwmId(info)); if (!handle.IsAlive()) @@ -827,7 +833,8 @@ void Geocoder::MatchRegions(RegionType type) auto const & regions = m_regions[type]; - auto const & fileName = m_context->GetMwmName(); + auto const & fileName = m_context->GetName(); + bool const isWorld = m_context->GetInfo()->GetType() == MwmInfo::WORLD; // Try to match regions. for (auto const & p : regions) @@ -839,83 +846,95 @@ void Geocoder::MatchRegions(RegionType type) if (HasUsedTokensInRange(startToken, endToken)) continue; - ScopedMarkTokens mark(m_usedTokens, startToken, endToken); - if (AllTokensUsed()) - { - // Region matches to search query, we need to emit it as is. - for (auto const & region : p.second) - m_results->emplace_back(m_worldId, region.m_featureId); - continue; - } - - bool matches = false; for (auto const & region : p.second) { - if (m_infoGetter.IsBelongToRegions(fileName, region.m_ids)) + bool matches = false; + + // On the World.mwm we need to check that CITY - STATE - COUNTRY + // form a nested sequence. Otherwise, as mwm borders do not + // intersect state or country boundaries, it's enough to check + // that the currently processing mwm belongs to region. + if (isWorld) + { + matches = m_lastMatchedRegion == nullptr || + m_infoGetter.IsBelongToRegions(region.m_center, m_lastMatchedRegion->m_ids); + } + else if (m_infoGetter.IsBelongToRegions(fileName, region.m_ids)) { matches = true; - break; } - } - if (!matches) - continue; + if (!matches) + continue; - switch (type) - { - case REGION_TYPE_STATE: - MatchCities(); - break; - case REGION_TYPE_COUNTRY: - MatchRegions(REGION_TYPE_STATE); - break; - case REGION_TYPE_COUNT: - ASSERT(false, ("Invalid region type.")); - break; + ScopedMarkTokens mark(m_usedTokens, startToken, endToken); + if (AllTokensUsed()) + { + // Region matches to search query, we need to emit it as is. + m_results->emplace_back(m_worldId, region.m_featureId); + continue; + } + + m_lastMatchedRegion = ®ion; + MY_SCOPE_GUARD(cleanup, [this]() { m_lastMatchedRegion = nullptr; }); + switch (type) + { + case REGION_TYPE_STATE: + MatchCities(); + break; + case REGION_TYPE_COUNTRY: + MatchRegions(REGION_TYPE_STATE); + break; + case REGION_TYPE_COUNT: + ASSERT(false, ("Invalid region type.")); + break; + } } } } void Geocoder::MatchCities() { - m2::RectD const countryBounds = m_context->m_value.GetHeader().GetBounds(); - // Localities are ordered my (m_startToken, m_endToken) pairs. for (auto const & p : m_cities) { - BailIfCancelled(); - size_t const startToken = p.first.first; size_t const endToken = p.first.second; if (HasUsedTokensInRange(startToken, endToken)) continue; - ScopedMarkTokens mark(m_usedTokens, startToken, endToken); - if (AllTokensUsed()) - { - // Localities match to search query. - for (auto const & city : p.second) - m_results->emplace_back(city.m_countryId, city.m_featureId); - continue; - } - - // Unites features from all localities and uses the resulting bit - // vector as a filter for features retrieved during geocoding. - CBVPtr allFeatures; for (auto const & city : p.second) { - m2::RectD rect = city.m_rect; - if (!rect.Intersect(countryBounds)) + BailIfCancelled(); + + if (m_lastMatchedRegion && + !m_infoGetter.IsBelongToRegions(city.m_rect.Center(), m_lastMatchedRegion->m_ids)) + { + continue; + } + + ScopedMarkTokens mark(m_usedTokens, startToken, endToken); + if (AllTokensUsed()) + { + // City matches to search query. + m_results->emplace_back(m_worldId, city.m_featureId); + continue; + } + + // No need to search features in the World map. + if (m_context->GetInfo()->GetType() == MwmInfo::WORLD) continue; - allFeatures.Union(RetrieveGeometryFeatures(*m_context, rect, city.m_featureId)); + // Unites features from all localities and uses the resulting bit + // vector as a filter for features retrieved during geocoding. + auto const * cityFeatures = RetrieveGeometryFeatures(*m_context, city.m_rect, CITY_ID); + + if (coding::CompressedBitVector::IsEmpty(cityFeatures)) + continue; + + // Filter will be applied for all non-empty bit vectors. + LimitedSearch(cityFeatures, 0 /* filterThreshold */); } - - if (allFeatures.IsEmpty()) - continue; - - // Filter will be applied for all non-empty bit vectors. - LimitedSearch(allFeatures.Get(), 0); } } @@ -937,7 +956,7 @@ void Geocoder::MatchViewportAndPosition() } // Filter will be applied only for large bit vectors. - LimitedSearch(allFeatures.Get(), m_params.m_maxNumResults); + LimitedSearch(allFeatures.Get(), m_params.m_maxNumResults /* filterThreshold */); } void Geocoder::LimitedSearch(coding::CompressedBitVector const * filter, size_t filterThreshold) diff --git a/search/v2/geocoder.hpp b/search/v2/geocoder.hpp index 7cbe2bb65c..0f69d37f5b 100644 --- a/search/v2/geocoder.hpp +++ b/search/v2/geocoder.hpp @@ -111,10 +111,11 @@ private: // is used to filter maps before search. struct Region : public Locality { - Region(Locality const & l, RegionType type) : Locality(l), m_type(type) {} + Region(Locality const & l, RegionType type) : Locality(l), m_center(0, 0), m_type(type) {} storage::CountryInfoGetter::IdSet m_ids; string m_enName; + m2::PointD m_center; RegionType m_type; }; @@ -133,7 +134,12 @@ private: template using TLocalitiesCache = map, vector>; - enum { VIEWPORT_ID = -1, POSITION_ID = -2 }; + enum + { + VIEWPORT_ID, + POSITION_ID, + CITY_ID + }; SearchQueryParams::TSynonymsVector const & GetTokens(size_t i) const; @@ -278,6 +284,9 @@ private: // Search query params prepared for retrieval. SearchQueryParams m_retrievalParams; + // Pointer to the most nested region filled during geocoding. + Region const * m_lastMatchedRegion; + // Stack of layers filled during geocoding. vector m_layers; diff --git a/search/v2/mwm_context.cpp b/search/v2/mwm_context.cpp index c493ac6145..c574fe0d32 100644 --- a/search/v2/mwm_context.cpp +++ b/search/v2/mwm_context.cpp @@ -15,10 +15,8 @@ MwmContext::MwmContext(MwmSet::MwmHandle handle) { } -string const & MwmContext::GetMwmName() const -{ - return m_id.GetInfo()->GetCountryName(); -} +string const & MwmContext::GetName() const { return m_id.GetInfo()->GetCountryName(); } +shared_ptr const & MwmContext::GetInfo() const { return m_id.GetInfo(); } } // namespace v2 } // namespace search diff --git a/search/v2/mwm_context.hpp b/search/v2/mwm_context.hpp index 3cf62173a5..2285400671 100644 --- a/search/v2/mwm_context.hpp +++ b/search/v2/mwm_context.hpp @@ -22,7 +22,8 @@ struct MwmContext FeaturesVector m_vector; ScaleIndex m_index; - string const & GetMwmName() const; + string const & GetName() const; + shared_ptr const & GetInfo() const; DISALLOW_COPY_AND_MOVE(MwmContext); };