[search] Implemented hotels filter.

This commit is contained in:
Yuri Gorshenin 2016-10-06 22:25:34 +03:00
parent 09566c5ca7
commit da69d602c5
22 changed files with 547 additions and 88 deletions

View file

@ -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;

View file

@ -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); }
//@}

View file

@ -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);

View file

@ -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(); }

View file

@ -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())

View file

@ -32,6 +32,12 @@ public:
bool operator() (vector<uint32_t> const & types) const;
static uint32_t PrepareToMatch(uint32_t type, uint8_t level);
template <typename TFn>
void ForEachType(TFn && fn) const
{
for_each(m_types.cbegin(), m_types.cend(), forward<TFn>(fn));
}
};
class IsPeakChecker : public BaseChecker
@ -73,12 +79,6 @@ class IsStreetChecker : public BaseChecker
{
IsStreetChecker();
public:
template <typename TFn>
void ForEachType(TFn && fn) const
{
for_each(m_types.cbegin(), m_types.cend(), forward<TFn>(fn));
}
static IsStreetChecker const & Instance();
};
@ -94,12 +94,6 @@ class IsVillageChecker : public BaseChecker
IsVillageChecker();
public:
template <typename TFn>
void ForEachType(TFn && fn) const
{
for_each(m_types.cbegin(), m_types.cend(), forward<TFn>(fn));
}
static IsVillageChecker const & Instance();
};

View file

@ -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)

View file

@ -32,12 +32,6 @@ void UniteCBVs(vector<CBV> & 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

View file

@ -16,46 +16,47 @@ class MwmContext;
class CategoriesCache
{
public:
CategoriesCache(CategoriesSet const & categories, my::Cancellable const & cancellable);
template <typename TypesSource>
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<MwmSet::MwmId, CBV> 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<MwmSet::MwmId, CBV> 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

View file

@ -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<hotels_filter::Rule> m_hotelsFilter;
SearchParams::TOnResults m_onResults;
};

View file

@ -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<uint32_t> 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);
}

View file

@ -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<hotels_filter::Rule> 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;

View file

@ -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<hotels_filter::HotelsFilter::ScopedFilter> m_hotelsFilter;
};
} // namespace search

105
search/hotels_filter.cpp Normal file
View file

@ -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> 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<uint32_t, Description> const & lhs,
pair<uint32_t, Description> 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::ScopedFilter> HotelsFilter::MakeScopedFilter(MwmContext const & context,
shared_ptr<Rule> rule)
{
if (!rule)
return {};
return make_unique<ScopedFilter>(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

227
search/hotels_filter.hpp Normal file
View file

@ -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 <typename Description>
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 <typename Description>
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 <typename Field>
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 <typename Field>
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 <typename Field>
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<Rule> lhs, shared_ptr<Rule> 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<Rule> m_lhs;
shared_ptr<Rule> m_rhs;
};
struct OrRule : public Rule
{
OrRule(shared_ptr<Rule> lhs, shared_ptr<Rule> 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<Rule> m_lhs;
shared_ptr<Rule> m_rhs;
};
template <typename Field>
shared_ptr<Rule> Eq(typename Field::Value value)
{
return make_shared<EqRule<Field>>(value);
}
template <typename Field>
shared_ptr<Rule> Lt(typename Field::Value value)
{
return make_shared<LtRule<Field>>(value);
}
template <typename Field>
inline shared_ptr<Rule> Gt(typename Field::Value value)
{
return make_shared<GtRule<Field>>(value);
}
inline shared_ptr<Rule> And(shared_ptr<Rule> lhs, shared_ptr<Rule> rhs)
{
return make_shared<AndRule>(lhs, rhs);
}
inline shared_ptr<Rule> Or(shared_ptr<Rule> lhs, shared_ptr<Rule> rhs)
{
return make_shared<OrRule>(lhs, rhs);
}
template <typename Field>
shared_ptr<Rule> Le(typename Field::Value value)
{
return Or(Lt<Field>(value), Eq<Field>(value));
}
template <typename Field>
shared_ptr<Rule> Ge(typename Field::Value value)
{
return Or(Gt<Field>(value), Eq<Field>(value));
}
class HotelsFilter
{
public:
using Descriptions = vector<pair<uint32_t, Description>>;
class ScopedFilter
{
public:
ScopedFilter(MwmSet::MwmId const & mwmId, Descriptions const & descriptions,
shared_ptr<Rule> rule);
bool Matches(FeatureID const & fid) const;
private:
MwmSet::MwmId const m_mwmId;
Descriptions const & m_descriptions;
shared_ptr<Rule> const m_rule;
};
HotelsFilter(HotelsCache & hotels);
unique_ptr<ScopedFilter> MakeScopedFilter(MwmContext const & context, shared_ptr<Rule> rule);
void ClearCaches();
private:
Descriptions const & GetDescriptions(MwmContext const & context);
HotelsCache & m_hotels;
map<MwmSet::MwmId, Descriptions> m_descriptions;
};
} // namespace hotels_filter
} // namespace search

View file

@ -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();
}

View file

@ -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<hotels_filter::Rule> m_hotelsFilter;
SearchParams::TOnResults m_onResults;
/// @name Get ranking params.

View file

@ -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 \

View file

@ -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<Rating>(7.0), Le<PriceRate>(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<Rating>(9.0), Le<PriceRate>(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<Rating>(7.0), Eq<PriceRate>(5)), Eq<PriceRate>(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

View file

@ -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())

View file

@ -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<hotels_filter::Rule> m_hotelsFilter;
friend string DebugPrint(SearchParams const & params);
private:

View file

@ -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<hotels_filter::Rule> m_hotelsFilter;
TOnStarted m_onStarted;
TOnCompleted m_onCompleted;