[search] Implement suggestions for countries, cities, streets.

This commit is contained in:
vng 2014-05-15 14:28:40 +03:00 committed by Alex Zolotarev
parent 80498aa751
commit 4ae69054f4
10 changed files with 242 additions and 122 deletions

View file

@ -271,10 +271,12 @@ Java_com_mapswithme_maps_SearchActivity_nativeGetResult(
}
else
{
jmethodID methodID = env->GetMethodID(klass, "<init>", "(Ljava/lang/String;)V");
jmethodID methodID = env->GetMethodID(klass, "<init>", "(Ljava/lang/String;Ljava/lang/String;)V");
ASSERT ( methodID, () );
return env->NewObject(klass, methodID, jni::ToJavaString(env, res->GetSuggestionString()));
return env->NewObject(klass, methodID,
jni::ToJavaString(env, res->GetString()),
jni::ToJavaString(env, res->GetSuggestionString()));
}
}

View file

@ -209,6 +209,7 @@ public class SearchActivity extends MapsWithMeBaseListActivity implements Locati
public static class SearchResult
{
public String m_name;
public String m_suggestion;
public String m_country;
public String m_amenity;
public String m_distance;
@ -219,9 +220,10 @@ public class SearchActivity extends MapsWithMeBaseListActivity implements Locati
// Called from native code
@SuppressWarnings("unused")
public SearchResult(String suggestion)
public SearchResult(String name, String suggestion)
{
m_name = suggestion;
m_name = name;
m_suggestion = suggestion;
m_type = 0;
}
@ -389,7 +391,7 @@ public class SearchActivity extends MapsWithMeBaseListActivity implements Locati
else
{
// advise suggestion
return r.m_name + ' ';
return r.m_suggestion;
}
}
}

View file

@ -69,4 +69,46 @@ IsBuildingChecker::IsBuildingChecker()
m_types.push_back(c.GetTypeByPath(vector<string>(arr1, arr1 + 2)));
}
IsLocalityChecker::IsLocalityChecker()
{
Classificator const & c = classif();
char const * arr[][2] = {
{ "place", "country" },
{ "place", "state" },
{ "place", "city" },
{ "place", "town" }
};
for (size_t i = 0; i < ARRAY_SIZE(arr); ++i)
m_types.push_back(c.GetTypeByPath(vector<string>(arr[i], arr[i] + 2)));
}
Type IsLocalityChecker::GetLocalityType(feature::TypesHolder const & types) const
{
for (size_t i = 0; i < types.Size(); ++i)
{
uint32_t t = types[i];
ftype::TruncValue(t, 2);
if (t == m_types[0])
return COUNTRY;
if (t == m_types[1])
return STATE;
for (int j = 2; j < m_types.size(); ++j)
if (t == m_types[j])
return CITY;
}
return NONE;
}
Type IsLocalityChecker::GetLocalityType(const FeatureType & f) const
{
feature::TypesHolder types(f);
return GetLocalityType(types);
}
}

View file

@ -36,4 +36,17 @@ public:
IsBuildingChecker();
};
/// Type of locality (do not change values - they have detalization order)
/// COUNTRY < STATE < CITY
enum Type { NONE = -1, COUNTRY = 0, STATE, CITY };
class IsLocalityChecker : public BaseChecker
{
public:
IsLocalityChecker();
Type GetLocalityType(feature::TypesHolder const & types) const;
Type GetLocalityType(FeatureType const & f) const;
};
}

View file

@ -176,19 +176,6 @@ PreResult2::PreResult2(m2::RectD const & viewport, m2::PointD const & pos, doubl
CalcParams(viewport, pos);
}
PreResult2::PreResult2(string const & name, int penalty)
: m_str(name), m_completionString(name + " "),
// Categories should always be the first:
m_distance(-1000.0), // smallest distance :)
m_distanceFromViewportCenter(-1000.0),
m_resultType(RESULT_CATEGORY),
m_rank(255), // best rank
m_viewportDistance(0), // closest to viewport
m_geomType(feature::GEOM_UNDEFINED)
{
}
namespace
{
class SkipRegionInfo
@ -243,12 +230,9 @@ Result PreResult2::GenerateFinalResult(
#endif
, type, m_distance);
case RESULT_LATLON:
return Result(GetCenter(), m_str, info.m_name, info.m_flag, m_distance);
default:
ASSERT_EQUAL ( m_resultType, RESULT_CATEGORY, () );
return Result(m_str, m_completionString);
ASSERT_EQUAL(m_resultType, RESULT_LATLON, ());
return Result(GetCenter(), m_str, info.m_name, info.m_flag, m_distance);
}
}
@ -271,7 +255,7 @@ bool PreResult2::StrictEqualF::operator() (PreResult2 const & r) const
{
if (m_r.m_resultType == r.m_resultType && m_r.m_resultType == RESULT_FEATURE)
{
if (m_r.m_str == r.m_str && m_r.GetBestType() == r.GetBestType())
if (m_r.IsEqualCommon(r))
return (PointDistance(m_r.GetCenter(), r.GetCenter()) < DIST_EQUAL_RESULTS);
}
@ -301,12 +285,18 @@ bool PreResult2::EqualLinearTypesF::operator() (PreResult2 const & r1, PreResult
{
// Note! Do compare for distance when filtering linear objects.
// Otherwise we will skip the results for different parts of the map.
return (r1.m_geomType == r2.m_geomType &&
r1.m_geomType == feature::GEOM_LINE &&
r1.m_str == r2.m_str &&
return (r1.m_geomType == feature::GEOM_LINE &&
r1.IsEqualCommon(r2) &&
PointDistance(r1.GetCenter(), r2.GetCenter()) < DIST_SAME_STREET);
}
bool PreResult2::IsEqualCommon(PreResult2 const & r) const
{
return (m_geomType == r.m_geomType &&
GetBestType() == r.GetBestType() &&
m_str == r.m_str);
}
bool PreResult2::IsStreet() const
{
static ftypes::IsStreetChecker checker;

View file

@ -66,7 +66,6 @@ public:
enum ResultType
{
RESULT_LATLON,
RESULT_CATEGORY,
RESULT_FEATURE
};
@ -79,9 +78,6 @@ public:
PreResult2(m2::RectD const & viewport, m2::PointD const & pos,
double lat, double lon);
// For RESULT_CATEGORY.
PreResult2(string const & name, int penalty);
/// @param[in] pInfo Need to get region for result.
/// @param[in] pCat Categories need to display readable type string.
/// @param[in] pTypes Set of preffered types that match input tokens by categories.
@ -120,13 +116,18 @@ public:
string DebugPrint() const;
bool IsStreet() const;
inline FeatureID const & GetID() const { return m_id; }
inline string const & GetName() const { return m_str; }
inline feature::TypesHolder const & GetTypes() const { return m_types; }
private:
template <class T> friend bool LessRankT(T const & r1, T const & r2);
template <class T> friend bool LessViewportDistanceT(T const & r1, T const & r2);
template <class T> friend bool LessDistanceT(T const & r1, T const & r2);
bool IsEqualCommon(PreResult2 const & r) const;
string GetFeatureType(CategoriesHolder const * pCat,
set<uint32_t> const * pTypes,
int8_t lang) const;
@ -136,7 +137,7 @@ private:
uint32_t GetBestType(set<uint32_t> const * pPrefferedTypes = 0) const;
string m_str, m_completionString;
string m_str;
struct RegionInfo
{

View file

@ -28,8 +28,8 @@ Result::Result(m2::PointD const & fCenter,
{
}
Result::Result(string const & str, string const & suggestionStr)
: m_str(str), m_suggestionStr(suggestionStr)
Result::Result(string const & str, string const & suggest)
: m_str(str), m_suggestionStr(suggest)
{
}
@ -70,23 +70,33 @@ char const * Result::GetSuggestionString() const
bool Result::operator== (Result const & r) const
{
ResultType const type = GetResultType();
if (type == r.GetResultType() && type != RESULT_SUGGESTION)
if (type == r.GetResultType())
{
// This function is used to filter duplicate results in cases:
// - emitted Wrold.mwm and Country.mwm
// - after additional search in all mwm
// so it's suitable here to test for 500m
return (m_str == r.m_str && m_region == r.m_region &&
m_featureType == r.m_featureType &&
PointDistance(m_center, r.m_center) < 500.0);
if (type == RESULT_SUGGESTION)
return m_suggestionStr == r.m_suggestionStr;
else
{
// This function is used to filter duplicate results in cases:
// - emitted World.mwm and Country.mwm
// - after additional search in all mwm
// so it's suitable here to test for 500m
return (m_str == r.m_str && m_region == r.m_region &&
m_featureType == r.m_featureType &&
PointDistance(m_center, r.m_center) < 500.0);
}
}
return false;
}
void Results::AddResultCheckExisting(Result const & r)
bool Results::AddResultCheckExistingEx(Result const & r)
{
if (find(m_vec.begin(), m_vec.end(), r) == m_vec.end())
{
AddResult(r);
return true;
}
else
return false;
}
////////////////////////////////////////////////////////////////////////////////////

View file

@ -33,7 +33,7 @@ public:
string const & flag, double distance);
/// For RESULT_SUGGESTION.
Result(string const & str, string const & suggestionStr);
Result(string const & str, string const & suggest);
/// Strings that is displayed in the GUI.
//@{
@ -100,7 +100,11 @@ public:
//@}
inline void AddResult(Result const & r) { m_vec.push_back(r); }
void AddResultCheckExisting(Result const & r);
inline void AddResultCheckExisting(Result const & r)
{
(void)AddResultCheckExistingEx(r);
}
bool AddResultCheckExistingEx(Result const & r);
inline void Clear() { m_vec.clear(); }

View file

@ -270,6 +270,8 @@ void Query::ClearQueues()
void Query::SetQuery(string const & query)
{
m_query = &query;
#ifdef HOUSE_SEARCH_TEST
m_house.clear();
m_streetID.clear();
@ -301,7 +303,6 @@ void Query::SetQuery(string const & query)
m_house.swap(m_prefix);
m_prefix.clear();
}
#endif
if (!m_tokens.empty() && !delims(strings::LastUniChar(query)))
@ -316,6 +317,17 @@ void Query::SetQuery(string const & query)
// assign tokens and prefix to scorer
m_keywordsScorer.SetKeywords(m_tokens.data(), m_tokens.size(), m_prefix);
// get preffered types to show in results
m_prefferedTypes.clear();
if (m_pCategories)
{
for (size_t i = 0; i < m_tokens.size(); ++i)
m_pCategories->ForEachTypeByName(m_tokens[i], MakeInsertFunctor(m_prefferedTypes));
if (!m_prefix.empty())
m_pCategories->ForEachTypeByName(m_prefix, MakeInsertFunctor(m_prefferedTypes));
}
}
void Query::SearchCoordinates(string const & query, Results & res) const
@ -635,16 +647,7 @@ void Query::FlushResults(Results & res, bool allMWMs, size_t resCount)
SortByIndexedValue(indV, CompFactory2());
// get preffered types to show in results
set<uint32_t> prefferedTypes;
if (m_pCategories)
{
for (size_t i = 0; i < m_tokens.size(); ++i)
m_pCategories->ForEachTypeByName(m_tokens[i], MakeInsertFunctor(prefferedTypes));
if (!m_prefix.empty())
m_pCategories->ForEachTypeByName(m_prefix, MakeInsertFunctor(prefferedTypes));
}
ProcessSuggestions(indV, res);
// emit feature results
size_t count = res.GetCount();
@ -654,7 +657,94 @@ void Query::FlushResults(Results & res, bool allMWMs, size_t resCount)
LOG(LDEBUG, (indV[i]));
(res.*addFn)(MakeResult(*(indV[i]), &prefferedTypes));
(res.*addFn)(MakeResult(*(indV[i])));
}
}
ftypes::Type Query::GetLocalityIndex(feature::TypesHolder const & types) const
{
static ftypes::IsLocalityChecker checker;
return checker.GetLocalityType(types);
}
void Query::GetSuggestion(string const & name, string & suggest) const
{
// Split result's name on tokens.
search::Delimiters delims;
vector<strings::UniString> vName;
SplitUniString(NormalizeAndSimplifyString(name), MakeBackInsertFunctor(vName), delims);
// Find tokens that already present in input query.
vector<bool> tokensMatched(vName.size());
bool prefixMatched = false;
for (size_t i = 0; i < vName.size(); ++i)
{
if (find(m_tokens.begin(), m_tokens.end(), vName[i]) != m_tokens.end())
tokensMatched[i] = true;
else
if (vName[i].size() > m_prefix.size() &&
StartsWith(vName[i].begin(), vName[i].end(), m_prefix.begin(), m_prefix.end()))
{
prefixMatched = true;
}
}
// Name doesn't match prefix - do nothing.
if (!prefixMatched)
return;
// Find start iterator of prefix in input query.
typedef utf8::unchecked::iterator<string::const_iterator> IterT;
IterT iter(m_query->end());
while (iter.base() != m_query->begin())
{
IterT prev = iter;
--prev;
if (delims(*prev))
break;
else
iter = prev;
}
// Assign suggest with input query without prefix.
suggest.assign(m_query->begin(), iter.base());
// Append unmatched result's tokens to the suggestion.
for (size_t i = 0; i < vName.size(); ++i)
if (!tokensMatched[i])
{
suggest += strings::ToUtf8(vName[i]);
suggest += " ";
}
}
template <class T> void Query::ProcessSuggestions(vector<T> & vec, Results & res) const
{
if (m_prefix.empty())
return;
int added = 0;
for (typename vector<T>::iterator i = vec.begin(); i != vec.end();)
{
impl::PreResult2 const & r = **i;
ftypes::Type const type = GetLocalityIndex(r.GetTypes());
if ((type == ftypes::COUNTRY || type == ftypes::CITY) || r.IsStreet())
{
string suggest;
GetSuggestion(r.GetName(), suggest);
if (!suggest.empty() && added < MAX_SUGGESTS_COUNT)
{
if (res.AddResultCheckExistingEx(Result(r.GetName(), suggest)))
{
++added;
i = vec.erase(i);
continue;
}
}
}
++i;
}
}
@ -705,9 +795,9 @@ void Query::GetBestMatchName(FeatureType const & f, string & name) const
(void)f.ForEachNameRef(bestNameFinder);
}
Result Query::MakeResult(impl::PreResult2 const & r, set<uint32_t> const * pPrefferedTypes/* = 0*/) const
Result Query::MakeResult(impl::PreResult2 const & r) const
{
return r.GenerateFinalResult(m_pInfoGetter, m_pCategories, pPrefferedTypes, GetLanguage(LANG_CURRENT));
return r.GenerateFinalResult(m_pInfoGetter, m_pCategories, &m_prefferedTypes, GetLanguage(LANG_CURRENT));
}
namespace impl
@ -1097,17 +1187,17 @@ namespace impl
Query::TrieValueT m_value;
vector<size_t> m_matchedTokens; ///< indexes of matched tokens for locality
/// Type of locality (do not change values - they have detalization order)
/// COUNTRY < STATE < CITY
enum Type { NONE = -1, COUNTRY = 0, STATE, CITY };
Type m_type;
ftypes::Type m_type;
Locality() : m_type(NONE) {}
Locality(Query::TrieValueT const & val, Type type) : m_value(val), m_type(type) {}
Locality() : m_type(ftypes::NONE) {}
Locality(Query::TrieValueT const & val, ftypes::Type type)
: m_value(val), m_type(type)
{
}
bool IsValid() const
{
if (m_type != NONE)
if (m_type != ftypes::NONE)
{
ASSERT ( !m_matchedTokens.empty(), () );
return true;
@ -1183,6 +1273,8 @@ namespace impl
bool const isMatched = IsFullNameMatched();
// Do filtering of possible localities.
using namespace ftypes;
switch (m_type)
{
case STATE: // we process USA, Canada states only for now
@ -1336,51 +1428,6 @@ namespace impl
FeaturesVector m_vector;
size_t m_index; ///< index of processing token
/// Check for "locality" types.
class TypeChecker
{
vector<uint32_t> m_vec;
public:
TypeChecker()
{
Classificator const & c = classif();
char const * arr[][2] = {
{ "place", "country" },
{ "place", "state" },
{ "place", "city" },
{ "place", "town" }
};
for (size_t i = 0; i < ARRAY_SIZE(arr); ++i)
m_vec.push_back(c.GetTypeByPath(vector<string>(arr[i], arr[i] + 2)));
}
/// @return Feature type and locality type @see Locality::m_index.
Locality::Type GetLocalityIndex(FeatureType const & f)
{
feature::TypesHolder types(f);
for (size_t i = 0; i < types.Size(); ++i)
{
uint32_t t = types[i];
ftype::TruncValue(t, 2);
if (t == m_vec[0])
return Locality::COUNTRY;
if (t == m_vec[1])
return Locality::STATE;
for (int j = 2; j < m_vec.size(); ++j)
if (t == m_vec[j])
return Locality::CITY;
}
return Locality::NONE;
}
};
Locality * PushLocality(Locality const & l)
{
ASSERT ( 0 <= l.m_type && l.m_type <= ARRAY_SIZE(m_localities), (l.m_type) );
@ -1508,9 +1555,8 @@ namespace impl
m_vector.Get(v.m_featureId, f);
// check, if feature is locality
static TypeChecker checker;
Locality::Type const index = checker.GetLocalityIndex(f);
if (index != Locality::NONE)
ftypes::Type const index = m_query.GetLocalityIndex(feature::TypesHolder(f));
if (index != ftypes::NONE)
{
Locality * loc = PushLocality(Locality(v, index));
if (loc)
@ -1527,17 +1573,17 @@ namespace impl
void SortLocalities()
{
for (int i = Locality::COUNTRY; i <= Locality::CITY; ++i)
for (int i = ftypes::COUNTRY; i <= ftypes::CITY; ++i)
sort(m_localities[i].begin(), m_localities[i].end());
}
void GetRegions(vector<Region> & regions) const
{
//LOG(LDEBUG, ("Countries before processing = ", m_localities[Locality::COUNTRY]));
//LOG(LDEBUG, ("States before processing = ", m_localities[Locality::STATE]));
//LOG(LDEBUG, ("Countries before processing = ", m_localities[ftypes::COUNTRY]));
//LOG(LDEBUG, ("States before processing = ", m_localities[ftypes::STATE]));
AddRegions(Locality::STATE, regions);
AddRegions(Locality::COUNTRY, regions);
AddRegions(ftypes::STATE, regions);
AddRegions(ftypes::COUNTRY, regions);
//LOG(LDEBUG, ("Regions after processing = ", regions));
}
@ -1545,7 +1591,7 @@ namespace impl
void GetBestCity(Locality & res, vector<Region> const & regions)
{
size_t const regsCount = regions.size();
vector<Locality> & arr = m_localities[Locality::CITY];
vector<Locality> & arr = m_localities[ftypes::CITY];
// Interate in reverse order from better to generic locality.
for (vector<Locality>::reverse_iterator i = arr.rbegin(); i != arr.rend(); ++i)
@ -1842,7 +1888,8 @@ bool Query::MatchForSuggestionsImpl(strings::UniString const & token, int8_t lan
(it->m_lang == lang) && // push suggestions only for needed language
StartsWith(s.begin(), s.end(), token.begin(), token.end()))
{
res.AddResult(MakeResult(impl::PreResult2(strings::ToUtf8(s), it->m_prefixLength)));
string const utf8Str = strings::ToUtf8(s);
res.AddResult(Result(utf8Str, utf8Str + " "));
ret = true;
}
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "intermediate_result.hpp"
#include "keyword_lang_matcher.hpp"
#include "ftypes_matcher.hpp"
#include "../indexer/search_trie.hpp"
#include "../indexer/index.hpp" // for Index::MwmLock
@ -161,6 +162,10 @@ private:
void FlushResults(Results & res, bool allMWMs, size_t resCount);
ftypes::Type GetLocalityIndex(feature::TypesHolder const & types) const;
void GetSuggestion(string const & name, string & suggest) const;
template <class T> void ProcessSuggestions(vector<T> & vec, Results & res) const;
void SearchAddress();
/// Search for best localities by input tokens.
@ -186,7 +191,7 @@ private:
void GetBestMatchName(FeatureType const & f, string & name) const;
Result MakeResult(impl::PreResult2 const & r, set<uint32_t> const * pPrefferedTypes = 0) const;
Result MakeResult(impl::PreResult2 const & r) const;
Index const * m_pIndex;
CategoriesHolder const * m_pCategories;
@ -195,8 +200,10 @@ private:
volatile bool m_cancel;
string const * m_query;
buffer_vector<strings::UniString, 32> m_tokens;
strings::UniString m_prefix;
set<uint32_t> m_prefferedTypes;
#ifdef HOUSE_SEARCH_TEST
strings::UniString m_house;
@ -204,6 +211,8 @@ private:
search::HouseDetector m_houseDetector;
#endif
static int const MAX_SUGGESTS_COUNT = 5;
/// 0 - current viewport rect
/// 1 - near me rect
/// 2 - around city rect