diff --git a/base/string_utils.cpp b/base/string_utils.cpp index 6d017e2405..67eed2cb12 100644 --- a/base/string_utils.cpp +++ b/base/string_utils.cpp @@ -96,6 +96,13 @@ bool to_int64(char const * s, int64_t & i) return *stop == 0 && s != stop; } +bool to_float(char const * s, float & f) +{ + char * stop; + f = strtof(s, &stop); + return *stop == 0 && s != stop && isfinite(f); +} + bool to_double(char const * s, double & d) { char * stop; diff --git a/base/string_utils.hpp b/base/string_utils.hpp index 179a5d560b..03c11fb61f 100644 --- a/base/string_utils.hpp +++ b/base/string_utils.hpp @@ -341,6 +341,7 @@ bool to_int(char const * s, int & i, int base = 10); bool to_uint(char const * s, unsigned int & i, int base = 10); bool to_uint64(char const * s, uint64_t & i); bool to_int64(char const * s, int64_t & i); +bool to_float(char const * s, float & f); bool to_double(char const * s, double & d); inline bool is_number(string const & s) @@ -356,6 +357,7 @@ inline bool to_uint(string const & s, unsigned int & i, int base = 10) } inline bool to_uint64(string const & s, uint64_t & i) { return to_uint64(s.c_str(), i); } inline bool to_int64(string const & s, int64_t & i) { return to_int64(s.c_str(), i); } +inline bool to_float(string const & s, float & f) { return to_float(s.c_str(), f); } inline bool to_double(string const & s, double & d) { return to_double(s.c_str(), d); } //@} diff --git a/generator/feature_builder.cpp b/generator/feature_builder.cpp index 5423274bbf..ed66b68c05 100644 --- a/generator/feature_builder.cpp +++ b/generator/feature_builder.cpp @@ -79,11 +79,6 @@ void FeatureBuilder1::SetRank(uint8_t rank) m_params.rank = rank; } -void FeatureBuilder1::SetTestId(uint64_t id) -{ - m_params.GetMetadata().Set(Metadata::FMD_TEST_ID, strings::to_string(id)); -} - void FeatureBuilder1::AddHouseNumber(string const & houseNumber) { m_params.AddHouseNumber(houseNumber); diff --git a/generator/feature_builder.hpp b/generator/feature_builder.hpp index ff27806cf0..e766211121 100644 --- a/generator/feature_builder.hpp +++ b/generator/feature_builder.hpp @@ -33,8 +33,6 @@ public: void SetRank(uint8_t rank); - void SetTestId(uint64_t id); - void AddHouseNumber(string const & houseNumber); void AddStreet(string const & streetName); @@ -61,6 +59,7 @@ public: inline feature::Metadata const & GetMetadata() const { return m_params.GetMetadata(); } + inline feature::Metadata & GetMetadataForTesting() { return m_params.GetMetadata(); } inline TGeometry const & GetGeometry() const { return m_polygons; } inline TPointSeq const & GetOuterGeometry() const { return m_polygons.front(); } inline feature::EGeomType GetGeomType() const { return m_params.GetGeomType(); } diff --git a/generator/generator_tests_support/test_feature.cpp b/generator/generator_tests_support/test_feature.cpp index bf645e3735..e3bc0ac932 100644 --- a/generator/generator_tests_support/test_feature.cpp +++ b/generator/generator_tests_support/test_feature.cpp @@ -15,6 +15,7 @@ #include "coding/multilang_utf8_string.hpp" #include "base/assert.hpp" +#include "base/string_utils.hpp" #include "std/atomic.hpp" #include "std/sstream.hpp" @@ -53,7 +54,9 @@ bool TestFeature::Matches(FeatureType const & feature) const void TestFeature::Serialize(FeatureBuilder1 & fb) const { - fb.SetTestId(m_id); + auto & metadata = fb.GetMetadataForTesting(); + metadata.Set(feature::Metadata::FMD_TEST_ID, strings::to_string(m_id)); + if (m_hasCenter) fb.SetCenter(m_center); if (!m_name.empty()) diff --git a/indexer/ftypes_matcher.hpp b/indexer/ftypes_matcher.hpp index 2e8fd6bc3e..17d1e77665 100644 --- a/indexer/ftypes_matcher.hpp +++ b/indexer/ftypes_matcher.hpp @@ -32,6 +32,12 @@ public: bool operator() (vector const & types) const; static uint32_t PrepareToMatch(uint32_t type, uint8_t level); + + template + void ForEachType(TFn && fn) const + { + for_each(m_types.cbegin(), m_types.cend(), forward(fn)); + } }; class IsPeakChecker : public BaseChecker @@ -73,12 +79,6 @@ class IsStreetChecker : public BaseChecker { IsStreetChecker(); public: - template - void ForEachType(TFn && fn) const - { - for_each(m_types.cbegin(), m_types.cend(), forward(fn)); - } - static IsStreetChecker const & Instance(); }; @@ -94,12 +94,6 @@ class IsVillageChecker : public BaseChecker IsVillageChecker(); public: - template - void ForEachType(TFn && fn) const - { - for_each(m_types.cbegin(), m_types.cend(), forward(fn)); - } - static IsVillageChecker const & Instance(); }; diff --git a/map/framework.cpp b/map/framework.cpp index 3bd858f5d7..9ac57322f7 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -1124,6 +1124,8 @@ bool Framework::SearchEverywhere(search::EverywhereSearchParams const & params) p.m_mode = search::Mode::Everywhere; p.m_forceSearch = true; p.m_suggestsEnabled = true; + p.m_hotelsFilter = params.m_hotelsFilter; + p.m_onResults = [params](search::Results const & results) { if (params.m_onResults) GetPlatform().RunOnGuiThread([params, results]() { params.m_onResults(results); }); @@ -1141,6 +1143,7 @@ bool Framework::SearchInViewport(search::ViewportSearchParams const & params) p.m_mode = search::Mode::Viewport; p.m_forceSearch = false; p.m_suggestsEnabled = false; + p.m_hotelsFilter = params.m_hotelsFilter; p.m_onStarted = [params]() { if (params.m_onStarted) diff --git a/search/categories_cache.cpp b/search/categories_cache.cpp index 214edb360d..233b21b84f 100644 --- a/search/categories_cache.cpp +++ b/search/categories_cache.cpp @@ -32,12 +32,6 @@ void UniteCBVs(vector & cbvs) } // namespace // CategoriesCache --------------------------------------------------------------------------------- -CategoriesCache::CategoriesCache(CategoriesSet const & categories, - my::Cancellable const & cancellable) - : m_categories(categories), m_cancellable(cancellable) -{ -} - CBV CategoriesCache::Get(MwmContext const & context) { if (!context.m_handle.IsAlive() || !context.m_value.HasSearchIndex()) @@ -85,17 +79,19 @@ CBV CategoriesCache::Load(MwmContext const & context) // StreetsCache ------------------------------------------------------------------------------------ StreetsCache::StreetsCache(my::Cancellable const & cancellable) - : m_cache(m_categories, cancellable) + : CategoriesCache(ftypes::IsStreetChecker::Instance(), cancellable) { - ftypes::IsStreetChecker::Instance().ForEachType( - [this](uint32_t type) { m_categories.Add(type); }); } // VillagesCache ----------------------------------------------------------------------------------- VillagesCache::VillagesCache(my::Cancellable const & cancellable) - : m_cache(m_categories, cancellable) + : CategoriesCache(ftypes::IsVillageChecker::Instance(), cancellable) +{ +} + +// HotelsCache ------------------------------------------------------------------------------------- +HotelsCache::HotelsCache(my::Cancellable const & cancellable) + : CategoriesCache(ftypes::IsHotelChecker::Instance(), cancellable) { - ftypes::IsVillageChecker::Instance().ForEachType( - [this](uint32_t type) { m_categories.Add(type); }); } } // namespace search diff --git a/search/categories_cache.hpp b/search/categories_cache.hpp index cf2da9297f..356ee2772d 100644 --- a/search/categories_cache.hpp +++ b/search/categories_cache.hpp @@ -16,46 +16,47 @@ class MwmContext; class CategoriesCache { public: - CategoriesCache(CategoriesSet const & categories, my::Cancellable const & cancellable); + template + CategoriesCache(TypesSource const & source, my::Cancellable const & cancellable) + : m_cancellable(cancellable) + { + source.ForEachType([this](uint32_t type) { m_categories.Add(type); }); + } + + virtual ~CategoriesCache() = default; CBV Get(MwmContext const & context); - inline void Clear() { m_cache.clear(); } -private: - CBV Load(MwmContext const & context); - - CategoriesSet const & m_categories; - my::Cancellable const & m_cancellable; - map m_cache; -}; - -class StreetsCache -{ - public: - StreetsCache(my::Cancellable const & cancellable); - - inline CBV Get(MwmContext const & context) { return m_cache.Get(context); } - inline void Clear() { m_cache.Clear(); } inline bool HasCategory(strings::UniString const & category) const { return m_categories.HasKey(category); } - private: - CategoriesSet m_categories; - CategoriesCache m_cache; -}; - -class VillagesCache -{ - public: - VillagesCache(my::Cancellable const & cancellable); - - inline CBV Get(MwmContext const & context) { return m_cache.Get(context); } - inline void Clear() { m_cache.Clear(); } + inline void Clear() { m_cache.clear(); } private: + CBV Load(MwmContext const & context); + CategoriesSet m_categories; - CategoriesCache m_cache; + my::Cancellable const & m_cancellable; + map m_cache; +}; + +class StreetsCache : public CategoriesCache +{ +public: + StreetsCache(my::Cancellable const & cancellable); +}; + +class VillagesCache : public CategoriesCache +{ +public: + VillagesCache(my::Cancellable const & cancellable); +}; + +class HotelsCache : public CategoriesCache +{ +public: + HotelsCache(my::Cancellable const & cancellable); }; } // namespace search diff --git a/search/everywhere_search_params.hpp b/search/everywhere_search_params.hpp index f3df639644..e6d668f7ff 100644 --- a/search/everywhere_search_params.hpp +++ b/search/everywhere_search_params.hpp @@ -1,5 +1,6 @@ #pragma once +#include "search/hotels_filter.hpp" #include "search/search_params.hpp" #include "std/string.hpp" @@ -10,6 +11,7 @@ struct EverywhereSearchParams { string m_query; string m_inputLocale; + shared_ptr m_hotelsFilter; SearchParams::TOnResults m_onResults; }; diff --git a/search/geocoder.cpp b/search/geocoder.cpp index 5345d18933..fb1562b1b5 100644 --- a/search/geocoder.cpp +++ b/search/geocoder.cpp @@ -344,6 +344,8 @@ Geocoder::Geocoder(Index const & index, storage::CountryInfoGetter const & infoG , m_infoGetter(infoGetter) , m_streetsCache(cancellable) , m_villagesCache(villagesCache) + , m_hotelsCache(cancellable) + , m_hotelsFilter(m_hotelsCache) , m_cancellable(cancellable) , m_model(SearchModel::Instance()) , m_pivotRectsCache(kPivotRectsCacheSize, m_cancellable, Processor::kMaxViewportRadiusM) @@ -558,7 +560,8 @@ void Geocoder::ClearCaches() m_matchersCache.clear(); m_streetsCache.Clear(); - m_villagesCache.Clear(); + m_hotelsCache.Clear(); + m_hotelsFilter.ClearCaches(); m_postcodes.Clear(); } @@ -595,6 +598,7 @@ void Geocoder::InitBaseContext(BaseContext & ctx) PrepareRetrievalParams(i, i + 1); ctx.m_features[i] = RetrieveAddressFeatures(*m_context, m_cancellable, m_retrievalParams); } + ctx.m_hotelsFilter = m_hotelsFilter.MakeScopedFilter(*m_context, m_params.m_hotelsFilter); } void Geocoder::InitLayer(SearchModel::SearchType type, size_t startToken, size_t endToken, @@ -854,7 +858,7 @@ void Geocoder::MatchRegions(BaseContext & ctx, RegionType type) if (ctx.AllTokensUsed()) { // Region matches to search query, we need to emit it as is. - EmitResult(region, startToken, endToken); + EmitResult(ctx, region, startToken, endToken); continue; } @@ -897,7 +901,7 @@ void Geocoder::MatchCities(BaseContext & ctx) if (ctx.AllTokensUsed()) { // City matches to search query, we need to emit it as is. - EmitResult(city, startToken, endToken); + EmitResult(ctx, city, startToken, endToken); continue; } @@ -1027,7 +1031,7 @@ void Geocoder::MatchPOIsAndBuildings(BaseContext & ctx, size_t curToken) // All tokens were consumed, find paths through layers, emit // features. if (m_postcodes.m_features.IsEmpty()) - return FindPaths(); + return FindPaths(ctx); // When there are no layers but user entered a postcode, we have // to emit all features matching to the postcode. @@ -1041,15 +1045,15 @@ void Geocoder::MatchPOIsAndBuildings(BaseContext & ctx, size_t curToken) SearchModel::SearchType searchType; if (GetSearchTypeInGeocoding(ctx, id, searchType)) { - EmitResult(m_context->GetId(), id, searchType, m_postcodes.m_startToken, - m_postcodes.m_endToken); + EmitResult(ctx, m_context->GetId(), id, searchType, + m_postcodes.m_startToken, m_postcodes.m_endToken); } }); return; } if (!(m_layers.size() == 1 && m_layers[0].m_type == SearchModel::SEARCH_TYPE_STREET)) - return FindPaths(); + return FindPaths(ctx); // If there're only one street layer but user also entered a // postcode, we need to emit all features matching to postcode on @@ -1063,7 +1067,7 @@ void Geocoder::MatchPOIsAndBuildings(BaseContext & ctx, size_t curToken) { if (!m_postcodes.m_features.HasBit(id)) continue; - EmitResult(m_context->GetId(), id, SearchModel::SEARCH_TYPE_STREET, + EmitResult(ctx, m_context->GetId(), id, SearchModel::SEARCH_TYPE_STREET, m_layers.back().m_startToken, m_layers.back().m_endToken); } } @@ -1080,7 +1084,7 @@ void Geocoder::MatchPOIsAndBuildings(BaseContext & ctx, size_t curToken) vector features; m_postcodes.m_features.ForEach(MakeBackInsertFunctor(features)); layer.m_sortedFeatures = &features; - return FindPaths(); + return FindPaths(ctx); } m_layers.emplace_back(); @@ -1244,7 +1248,7 @@ bool Geocoder::IsLayerSequenceSane() const return true; } -void Geocoder::FindPaths() +void Geocoder::FindPaths(BaseContext const & ctx) { if (m_layers.empty()) return; @@ -1263,21 +1267,24 @@ void Geocoder::FindPaths() else m_matcher->SetPostcodes(nullptr); m_finder.ForEachReachableVertex( - *m_matcher, sortedLayers, [this, &innermostLayer](IntersectionResult const & result) + *m_matcher, sortedLayers, [this, &ctx, &innermostLayer](IntersectionResult const & result) { ASSERT(result.IsValid(), ()); // TODO(@y, @m, @vng): use rest fields of IntersectionResult for // better scoring. - EmitResult(m_context->GetId(), result.InnermostResult(), innermostLayer.m_type, + EmitResult(ctx, m_context->GetId(), result.InnermostResult(), innermostLayer.m_type, innermostLayer.m_startToken, innermostLayer.m_endToken); }); } -void Geocoder::EmitResult(MwmSet::MwmId const & mwmId, uint32_t ftId, SearchModel::SearchType type, - size_t startToken, size_t endToken) +void Geocoder::EmitResult(BaseContext const & ctx, MwmSet::MwmId const & mwmId, uint32_t ftId, + SearchModel::SearchType type, size_t startToken, size_t endToken) { FeatureID id(mwmId, ftId); + if (ctx.m_hotelsFilter && !ctx.m_hotelsFilter->Matches(id)) + return; + // Distance and rank will be filled at the end, for all results at once. // // TODO (@y, @m): need to skip zero rank features that are too @@ -1291,7 +1298,8 @@ void Geocoder::EmitResult(MwmSet::MwmId const & mwmId, uint32_t ftId, SearchMode m_preRanker.Emplace(id, info); } -void Geocoder::EmitResult(Region const & region, size_t startToken, size_t endToken) +void Geocoder::EmitResult(BaseContext const & ctx, Region const & region, size_t startToken, + size_t endToken) { SearchModel::SearchType type; switch (region.m_type) @@ -1300,12 +1308,13 @@ void Geocoder::EmitResult(Region const & region, size_t startToken, size_t endTo case REGION_TYPE_COUNTRY: type = SearchModel::SEARCH_TYPE_COUNTRY; break; case REGION_TYPE_COUNT: type = SearchModel::SEARCH_TYPE_COUNT; break; } - EmitResult(m_worldId, region.m_featureId, type, startToken, endToken); + EmitResult(ctx, m_worldId, region.m_featureId, type, startToken, endToken); } -void Geocoder::EmitResult(City const & city, size_t startToken, size_t endToken) +void Geocoder::EmitResult(BaseContext const & ctx, City const & city, size_t startToken, + size_t endToken) { - EmitResult(city.m_countryId, city.m_featureId, city.m_type, startToken, endToken); + EmitResult(ctx, city.m_countryId, city.m_featureId, city.m_type, startToken, endToken); } void Geocoder::MatchUnclassified(BaseContext & ctx, size_t curToken) @@ -1341,7 +1350,7 @@ void Geocoder::MatchUnclassified(BaseContext & ctx, size_t curToken) if (!GetSearchTypeInGeocoding(ctx, featureId, searchType)) return; if (searchType == SearchModel::SEARCH_TYPE_UNCLASSIFIED) - EmitResult(m_context->GetId(), featureId, searchType, startToken, curToken); + EmitResult(ctx, m_context->GetId(), featureId, searchType, startToken, curToken); }; allFeatures.ForEach(emitUnclassified); } diff --git a/search/geocoder.hpp b/search/geocoder.hpp index d6b30a1e1d..ed178ee6e2 100644 --- a/search/geocoder.hpp +++ b/search/geocoder.hpp @@ -7,6 +7,7 @@ #include "search/features_layer_path_finder.hpp" #include "search/geocoder_context.hpp" #include "search/geometry_cache.hpp" +#include "search/hotels_filter.hpp" #include "search/mode.hpp" #include "search/model.hpp" #include "search/mwm_context.hpp" @@ -78,6 +79,7 @@ public: Mode m_mode; m2::RectD m_pivot; + shared_ptr m_hotelsFilter; }; enum RegionType @@ -246,13 +248,14 @@ private: // Finds all paths through layers and emits reachable features from // the lowest layer. - void FindPaths(); + void FindPaths(BaseContext const & ctx); // Forms result and feeds it to |m_preRanker|. - void EmitResult(MwmSet::MwmId const & mwmId, uint32_t ftId, SearchModel::SearchType type, - size_t startToken, size_t endToken); - void EmitResult(Region const & region, size_t startToken, size_t endToken); - void EmitResult(City const & city, size_t startToken, size_t endToken); + void EmitResult(BaseContext const & ctx, MwmSet::MwmId const & mwmId, uint32_t ftId, + SearchModel::SearchType type, size_t startToken, size_t endToken); + void EmitResult(BaseContext const & ctx, Region const & region, size_t startToken, + size_t endToken); + void EmitResult(BaseContext const & ctx, City const & city, size_t startToken, size_t endToken); // Tries to match unclassified objects from lower layers, like // parks, forests, lakes, rivers, etc. This method finds all @@ -276,6 +279,8 @@ private: StreetsCache m_streetsCache; VillagesCache & m_villagesCache; + HotelsCache m_hotelsCache; + hotels_filter::HotelsFilter m_hotelsFilter; my::Cancellable const & m_cancellable; diff --git a/search/geocoder_context.hpp b/search/geocoder_context.hpp index 63b7d4696f..216c323987 100644 --- a/search/geocoder_context.hpp +++ b/search/geocoder_context.hpp @@ -1,6 +1,7 @@ #pragma once #include "search/cbv.hpp" +#include "search/hotels_filter.hpp" #include "std/unique_ptr.hpp" #include "std/vector.hpp" @@ -37,5 +38,7 @@ struct BaseContext // Number of tokens in the query. size_t m_numTokens = 0; + + unique_ptr m_hotelsFilter; }; } // namespace search diff --git a/search/hotels_filter.cpp b/search/hotels_filter.cpp new file mode 100644 index 0000000000..10f335a39f --- /dev/null +++ b/search/hotels_filter.cpp @@ -0,0 +1,105 @@ +#include "search/hotels_filter.hpp" + +#include "indexer/feature.hpp" +#include "indexer/feature_meta.hpp" + +#include "base/assert.hpp" + +#include "std/algorithm.hpp" + +namespace search +{ +namespace hotels_filter +{ +// static +typename Rating::Value const Rating::kDefault = 0; + +// static +typename PriceRate::Value const PriceRate::kDefault = 0; + +// Description ------------------------------------------------------------------------------------- +void Description::FromFeature(FeatureType & ft) +{ + m_rating = Rating::kDefault; + m_priceRate = PriceRate::kDefault; + + auto const & metadata = ft.GetMetadata(); + + if (metadata.Has(feature::Metadata::FMD_RATING)) + { + string const rating = metadata.Get(feature::Metadata::FMD_RATING); + float r; + if (strings::to_float(rating, r)) + m_rating = r; + } + + if (metadata.Has(feature::Metadata::FMD_PRICE_RATE)) + { + string const priceRate = metadata.Get(feature::Metadata::FMD_PRICE_RATE); + int pr; + if (strings::to_int(priceRate, pr)) + m_priceRate = pr; + } +} + +// HotelsFilter::ScopedFilter ---------------------------------------------------------------------- +HotelsFilter::ScopedFilter::ScopedFilter(MwmSet::MwmId const & mwmId, + Descriptions const & descriptions, shared_ptr rule) + : m_mwmId(mwmId), m_descriptions(descriptions), m_rule(rule) +{ + CHECK(m_rule.get(), ()); +} + +bool HotelsFilter::ScopedFilter::Matches(FeatureID const & fid) const +{ + if (fid.m_mwmId != m_mwmId) + return false; + + auto it = + lower_bound(m_descriptions.begin(), m_descriptions.end(), + make_pair(fid.m_index, Description{}), + [](pair const & lhs, + pair const & rhs) { return lhs.first < rhs.first; }); + if (it == m_descriptions.end() || it->first != fid.m_index) + return false; + + return m_rule->Matches(it->second); +} + +// HotelsFilter ------------------------------------------------------------------------------------ +HotelsFilter::HotelsFilter(HotelsCache & hotels): m_hotels(hotels) {} + +unique_ptr HotelsFilter::MakeScopedFilter(MwmContext const & context, + shared_ptr rule) +{ + if (!rule) + return {}; + return make_unique(context.GetId(), GetDescriptions(context), rule); +} + +void HotelsFilter::ClearCaches() +{ + m_descriptions.clear(); +} + +HotelsFilter::Descriptions const & HotelsFilter::GetDescriptions(MwmContext const & context) +{ + auto const & mwmId = context.GetId(); + auto const it = m_descriptions.find(mwmId); + if (it != m_descriptions.end()) + return it->second; + + auto const hotels = m_hotels.Get(context); + auto & descriptions = m_descriptions[mwmId]; + hotels.ForEach([&descriptions, &context](uint32_t id) { + FeatureType ft; + + Description description; + if (context.GetFeature(id, ft)) + description.FromFeature(ft); + descriptions.emplace_back(id, description); + }); + return descriptions; +} +} // namespace hotels_filter +} // namespace search diff --git a/search/hotels_filter.hpp b/search/hotels_filter.hpp new file mode 100644 index 0000000000..6ea1e9a12a --- /dev/null +++ b/search/hotels_filter.hpp @@ -0,0 +1,227 @@ +#pragma once + +#include "search/categories_cache.hpp" +#include "search/cbv.hpp" +#include "search/mwm_context.hpp" + +#include "indexer/mwm_set.hpp" + +#include "std/map.hpp" +#include "std/shared_ptr.hpp" +#include "std/unique_ptr.hpp" +#include "std/utility.hpp" +#include "std/vector.hpp" + +class FeatureType; + +namespace search +{ +namespace hotels_filter +{ +struct Rating +{ + using Value = float; + + static Value const kDefault; + + static bool Lt(Value lhs, Value rhs) { return lhs + 0.05 < rhs; } + static bool Gt(Value lhs, Value rhs) { return lhs > rhs + 0.05; } + static bool Eq(Value lhs, Value rhs) { return !Lt(lhs, rhs) && !Gt(lhs, rhs); } + + template + static Value Select(Description const & d) + { + return d.m_rating; + } +}; + +struct PriceRate +{ + using Value = int; + + static Value const kDefault; + + static bool Lt(Value lhs, Value rhs) { return lhs < rhs; } + static bool Gt(Value lhs, Value rhs) { return lhs > rhs; } + static bool Eq(Value lhs, Value rhs) { return lhs == rhs; } + + template + static Value Select(Description const & d) + { + return d.m_priceRate; + } +}; + +struct Description +{ + void FromFeature(FeatureType & ft); + + Rating::Value m_rating = Rating::kDefault; + PriceRate::Value m_priceRate = PriceRate::kDefault; +}; + +struct Rule +{ + virtual ~Rule() = default; + virtual bool Matches(Description const & d) const = 0; +}; + +template +struct EqRule : public Rule +{ + using Value = typename Field::Value; + + EqRule(Value value) : m_value(value) {} + + // Rule overrides: + bool Matches(Description const & d) const override + { + return Field::Eq(Field::Select(d), m_value); + } + + Value const m_value; +}; + +template +struct LtRule : public Rule +{ + using Value = typename Field::Value; + + LtRule(Value value) : m_value(value) {} + + // Rule overrides: + bool Matches(Description const & d) const override + { + return Field::Lt(Field::Select(d), m_value); + } + + Value const m_value; +}; + +template +struct GtRule : public Rule +{ + using Value = typename Field::Value; + + GtRule(Value value) : m_value(value) {} + + // Rule overrides: + bool Matches(Description const & d) const override + { + return Field::Gt(Field::Select(d), m_value); + } + + Value const m_value; +}; + +struct AndRule : public Rule +{ + AndRule(shared_ptr lhs, shared_ptr rhs) : m_lhs(move(lhs)), m_rhs(move(rhs)) {} + + // Rule overrides: + bool Matches(Description const & d) const override + { + bool matches = true; + if (m_lhs) + matches = matches && m_lhs->Matches(d); + if (m_rhs) + matches = matches && m_rhs->Matches(d); + return matches; + } + + shared_ptr m_lhs; + shared_ptr m_rhs; +}; + +struct OrRule : public Rule +{ + OrRule(shared_ptr lhs, shared_ptr rhs) : m_lhs(move(lhs)), m_rhs(move(rhs)) {} + + // Rule overrides: + bool Matches(Description const & d) const override + { + bool matches = false; + if (m_lhs) + matches = matches || m_lhs->Matches(d); + if (m_rhs) + matches = matches || m_rhs->Matches(d); + return matches; + } + + shared_ptr m_lhs; + shared_ptr m_rhs; +}; + +template +shared_ptr Eq(typename Field::Value value) +{ + return make_shared>(value); +} + +template +shared_ptr Lt(typename Field::Value value) +{ + return make_shared>(value); +} + +template +inline shared_ptr Gt(typename Field::Value value) +{ + return make_shared>(value); +} + +inline shared_ptr And(shared_ptr lhs, shared_ptr rhs) +{ + return make_shared(lhs, rhs); +} + +inline shared_ptr Or(shared_ptr lhs, shared_ptr rhs) +{ + return make_shared(lhs, rhs); +} + +template +shared_ptr Le(typename Field::Value value) +{ + return Or(Lt(value), Eq(value)); +} + +template +shared_ptr Ge(typename Field::Value value) +{ + return Or(Gt(value), Eq(value)); +} + +class HotelsFilter +{ +public: + using Descriptions = vector>; + + class ScopedFilter + { + public: + ScopedFilter(MwmSet::MwmId const & mwmId, Descriptions const & descriptions, + shared_ptr rule); + + bool Matches(FeatureID const & fid) const; + + private: + MwmSet::MwmId const m_mwmId; + Descriptions const & m_descriptions; + shared_ptr const m_rule; + }; + + HotelsFilter(HotelsCache & hotels); + + unique_ptr MakeScopedFilter(MwmContext const & context, shared_ptr rule); + + void ClearCaches(); + +private: + Descriptions const & GetDescriptions(MwmContext const & context); + + HotelsCache & m_hotels; + map m_descriptions; +}; +} // namespace hotels_filter +} // namespace search diff --git a/search/processor.cpp b/search/processor.cpp index e076df2e96..659bd53f19 100644 --- a/search/processor.cpp +++ b/search/processor.cpp @@ -405,6 +405,7 @@ void Processor::Search(SearchParams const & params, m2::RectD const & viewport) SetMinDistanceOnMapBetweenResults(params.m_minDistanceOnMapBetweenResults); SetSuggestsEnabled(params.m_suggestsEnabled); + m_hotelsFilter = params.m_hotelsFilter; SetInputLocale(params.m_inputLocale); @@ -676,6 +677,7 @@ void Processor::InitGeocoder(Geocoder::Params & params) params.m_pivot = m_viewport[CURRENT_V]; else params.m_pivot = GetPivotRect(); + params.m_hotelsFilter = m_hotelsFilter; m_geocoder.SetParams(params); } @@ -735,6 +737,7 @@ void Processor::ClearCaches() ClearCache(i); m_geocoder.ClearCaches(); + m_villagesCache.Clear(); m_preRanker.ClearCaches(); m_ranker.ClearCaches(); } diff --git a/search/processor.hpp b/search/processor.hpp index 0aa694a3af..7ca2ea7552 100644 --- a/search/processor.hpp +++ b/search/processor.hpp @@ -3,6 +3,7 @@ #include "search/categories_set.hpp" #include "search/emitter.hpp" #include "search/geocoder.hpp" +#include "search/hotels_filter.hpp" #include "search/mode.hpp" #include "search/pre_ranker.hpp" #include "search/rank_table_cache.hpp" @@ -26,9 +27,10 @@ #include "std/function.hpp" #include "std/map.hpp" +#include "std/shared_ptr.hpp" #include "std/string.hpp" -#include "std/unordered_set.hpp" #include "std/unique_ptr.hpp" +#include "std/unordered_set.hpp" #include "std/vector.hpp" class FeatureType; @@ -162,6 +164,7 @@ protected: double m_minDistanceOnMapBetweenResults; Mode m_mode; bool m_suggestsEnabled; + shared_ptr m_hotelsFilter; SearchParams::TOnResults m_onResults; /// @name Get ranking params. diff --git a/search/search.pro b/search/search.pro index 8d747347aa..7c7cacc87f 100644 --- a/search/search.pro +++ b/search/search.pro @@ -33,6 +33,7 @@ HEADERS += \ geometry_cache.hpp \ geometry_utils.hpp \ hotels_classifier.hpp \ + hotels_filter.hpp \ house_detector.hpp \ house_numbers_matcher.hpp \ house_to_street_table.hpp \ @@ -98,6 +99,7 @@ SOURCES += \ geometry_cache.cpp \ geometry_utils.cpp \ hotels_classifier.cpp \ + hotels_filter.cpp \ house_detector.cpp \ house_numbers_matcher.cpp \ house_to_street_table.cpp \ diff --git a/search/search_integration_tests/processor_test.cpp b/search/search_integration_tests/processor_test.cpp index 03db7f3df8..8af3dfbd3a 100644 --- a/search/search_integration_tests/processor_test.cpp +++ b/search/search_integration_tests/processor_test.cpp @@ -6,6 +6,7 @@ #include "search/search_tests_support/test_search_request.hpp" #include "search/token_slice.hpp" +#include "generator/feature_builder.hpp" #include "generator/generator_tests_support/test_feature.hpp" #include "generator/generator_tests_support/test_mwm_builder.hpp" @@ -16,6 +17,7 @@ #include "geometry/point2d.hpp" #include "geometry/rect2d.hpp" +#include "base/assert.hpp" #include "base/math.hpp" #include "std/shared_ptr.hpp" @@ -28,6 +30,37 @@ namespace search { namespace { +class TestHotel : public TestPOI +{ +public: + TestHotel(m2::PointD const & center, string const & name, string const & lang, float rating, + int priceRate) + : TestPOI(center, name, lang), m_rating(rating), m_priceRate(priceRate) + { + CHECK_GREATER_OR_EQUAL(m_rating, 0.0, ()); + CHECK_LESS_OR_EQUAL(m_rating, 10.0, ()); + + CHECK_GREATER_OR_EQUAL(m_priceRate, 0, ()); + CHECK_LESS_OR_EQUAL(m_priceRate, 5, ()); + + SetTypes({{"tourism", "hotel"}}); + } + + // TestPOI overrides: + void Serialize(FeatureBuilder1 & fb) const override + { + TestPOI::Serialize(fb); + + auto & metadata = fb.GetMetadataForTesting(); + metadata.Set(feature::Metadata::FMD_RATING, strings::to_string(m_rating)); + metadata.Set(feature::Metadata::FMD_PRICE_RATE, strings::to_string(m_priceRate)); + } + +private: + float const m_rating; + int const m_priceRate; +}; + class ProcessorTest : public SearchTest { public: @@ -660,5 +693,62 @@ UNIT_CLASS_TEST(ProcessorTest, TestCoords) auto const actual = result.GetFeatureCenter(); TEST(MercatorBounds::DistanceOnEarth(expected, actual) <= 1.0, ()); } + +UNIT_CLASS_TEST(ProcessorTest, HotelsFiltering) +{ + char const countryName[] = "Wonderland"; + + TestHotel h1(m2::PointD(0, 0), "h1", "en", 8.0 /* rating */, 2 /* priceRate */); + TestHotel h2(m2::PointD(0, 1), "h2", "en", 7.0 /* rating */, 5 /* priceRate */); + TestHotel h3(m2::PointD(1, 0), "h3", "en", 9.0 /* rating */, 0 /* priceRate */); + TestHotel h4(m2::PointD(1, 1), "h4", "en", 2.0 /* rating */, 4 /* priceRate */); + + auto id = BuildCountry(countryName, [&](TestMwmBuilder & builder) { + builder.Add(h1); + builder.Add(h2); + builder.Add(h3); + builder.Add(h4); + }); + + SearchParams params; + params.m_query = "hotel"; + params.m_inputLocale = "en"; + params.m_mode = Mode::Everywhere; + params.m_suggestsEnabled = false; + + SetViewport(m2::RectD(m2::PointD(-1, -1), m2::PointD(2, 2))); + { + TestSearchRequest request(m_engine, params, m_viewport); + request.Run(); + TRules rules = {ExactMatch(id, h1), ExactMatch(id, h2), ExactMatch(id, h3), ExactMatch(id, h4)}; + TEST(MatchResults(rules, request.Results()), ()); + } + + using namespace hotels_filter; + + params.m_hotelsFilter = And(Gt(7.0), Le(2)); + { + TestSearchRequest request(m_engine, params, m_viewport); + request.Run(); + TRules rules = {ExactMatch(id, h1), ExactMatch(id, h3)}; + TEST(MatchResults(rules, request.Results()), ()); + } + + params.m_hotelsFilter = Or(Eq(9.0), Le(4)); + { + TestSearchRequest request(m_engine, params, m_viewport); + request.Run(); + TRules rules = {ExactMatch(id, h1), ExactMatch(id, h3), ExactMatch(id, h4)}; + TEST(MatchResults(rules, request.Results()), ()); + } + + params.m_hotelsFilter = Or(And(Eq(7.0), Eq(5)), Eq(4)); + { + TestSearchRequest request(m_engine, params, m_viewport); + request.Run(); + TRules rules = {ExactMatch(id, h2), ExactMatch(id, h4)}; + TEST(MatchResults(rules, request.Results()), ()); + } +} } // namespace } // namespace search diff --git a/search/search_integration_tests/smoke_test.cpp b/search/search_integration_tests/smoke_test.cpp index 69c3e28b26..f7bdae1809 100644 --- a/search/search_integration_tests/smoke_test.cpp +++ b/search/search_integration_tests/smoke_test.cpp @@ -9,10 +9,13 @@ #include "generator/generator_tests_support/test_mwm_builder.hpp" #include "indexer/classificator.hpp" +#include "indexer/feature_meta.hpp" #include "geometry/point2d.hpp" #include "geometry/rect2d.hpp" +#include "base/string_utils.hpp" + #include "std/shared_ptr.hpp" #include "std/vector.hpp" @@ -34,7 +37,7 @@ public: // TestFeature overrides: void Serialize(FeatureBuilder1 & fb) const override { - fb.SetTestId(m_id); + fb.GetMetadataForTesting().Set(feature::Metadata::FMD_TEST_ID, strings::to_string(m_id)); fb.SetCenter(m_center); if (!m_name.empty()) diff --git a/search/search_params.hpp b/search/search_params.hpp index e454d017de..5d21d9b3ec 100644 --- a/search/search_params.hpp +++ b/search/search_params.hpp @@ -1,5 +1,6 @@ #pragma once +#include "search/hotels_filter.hpp" #include "search/mode.hpp" #include "geometry/latlon.hpp" @@ -41,6 +42,8 @@ public: bool m_forceSearch = false; bool m_suggestsEnabled = true; + shared_ptr m_hotelsFilter; + friend string DebugPrint(SearchParams const & params); private: diff --git a/search/viewport_search_params.hpp b/search/viewport_search_params.hpp index 5067ef5eb4..4c38a98f17 100644 --- a/search/viewport_search_params.hpp +++ b/search/viewport_search_params.hpp @@ -1,6 +1,9 @@ #pragma once +#include "search/hotels_filter.hpp" + #include "std/function.hpp" +#include "std/shared_ptr.hpp" #include "std/string.hpp" namespace search @@ -12,6 +15,7 @@ struct ViewportSearchParams string m_query; string m_inputLocale; + shared_ptr m_hotelsFilter; TOnStarted m_onStarted; TOnCompleted m_onCompleted;