From 4ae69054f4c4035b30cd85f7085cfa0277241ab3 Mon Sep 17 00:00:00 2001 From: vng Date: Thu, 15 May 2014 14:28:40 +0300 Subject: [PATCH] [search] Implement suggestions for countries, cities, streets. --- .../com/mapswithme/maps/SearchActivity.cpp | 6 +- .../com/mapswithme/maps/SearchActivity.java | 8 +- search/ftypes_matcher.cpp | 42 ++++ search/ftypes_matcher.hpp | 13 ++ search/intermediate_result.cpp | 34 ++- search/intermediate_result.hpp | 11 +- search/result.cpp | 32 ++- search/result.hpp | 8 +- search/search_query.cpp | 199 +++++++++++------- search/search_query.hpp | 11 +- 10 files changed, 242 insertions(+), 122 deletions(-) diff --git a/android/jni/com/mapswithme/maps/SearchActivity.cpp b/android/jni/com/mapswithme/maps/SearchActivity.cpp index 018e7517a4..cecea6b4dd 100644 --- a/android/jni/com/mapswithme/maps/SearchActivity.cpp +++ b/android/jni/com/mapswithme/maps/SearchActivity.cpp @@ -271,10 +271,12 @@ Java_com_mapswithme_maps_SearchActivity_nativeGetResult( } else { - jmethodID methodID = env->GetMethodID(klass, "", "(Ljava/lang/String;)V"); + jmethodID methodID = env->GetMethodID(klass, "", "(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())); } } diff --git a/android/src/com/mapswithme/maps/SearchActivity.java b/android/src/com/mapswithme/maps/SearchActivity.java index fa94a4d719..1e49270db8 100644 --- a/android/src/com/mapswithme/maps/SearchActivity.java +++ b/android/src/com/mapswithme/maps/SearchActivity.java @@ -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; } } } diff --git a/search/ftypes_matcher.cpp b/search/ftypes_matcher.cpp index dc4ff8125b..fd31140231 100644 --- a/search/ftypes_matcher.cpp +++ b/search/ftypes_matcher.cpp @@ -69,4 +69,46 @@ IsBuildingChecker::IsBuildingChecker() m_types.push_back(c.GetTypeByPath(vector(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(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); +} + } diff --git a/search/ftypes_matcher.hpp b/search/ftypes_matcher.hpp index b15446fb38..6a44091f05 100644 --- a/search/ftypes_matcher.hpp +++ b/search/ftypes_matcher.hpp @@ -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; +}; + } diff --git a/search/intermediate_result.cpp b/search/intermediate_result.cpp index c63ace3c5e..01af226039 100644 --- a/search/intermediate_result.cpp +++ b/search/intermediate_result.cpp @@ -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; diff --git a/search/intermediate_result.hpp b/search/intermediate_result.hpp index 6586a59ef0..1651fc27ee 100644 --- a/search/intermediate_result.hpp +++ b/search/intermediate_result.hpp @@ -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 friend bool LessRankT(T const & r1, T const & r2); template friend bool LessViewportDistanceT(T const & r1, T const & r2); template friend bool LessDistanceT(T const & r1, T const & r2); + bool IsEqualCommon(PreResult2 const & r) const; + string GetFeatureType(CategoriesHolder const * pCat, set const * pTypes, int8_t lang) const; @@ -136,7 +137,7 @@ private: uint32_t GetBestType(set const * pPrefferedTypes = 0) const; - string m_str, m_completionString; + string m_str; struct RegionInfo { diff --git a/search/result.cpp b/search/result.cpp index d1b3249185..cf9e9d38f1 100644 --- a/search/result.cpp +++ b/search/result.cpp @@ -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; } //////////////////////////////////////////////////////////////////////////////////// diff --git a/search/result.hpp b/search/result.hpp index db19031a4b..61671d4454 100644 --- a/search/result.hpp +++ b/search/result.hpp @@ -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(); } diff --git a/search/search_query.cpp b/search/search_query.cpp index 89bedbc36a..fd670b4e8b 100644 --- a/search/search_query.cpp +++ b/search/search_query.cpp @@ -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 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 vName; + SplitUniString(NormalizeAndSimplifyString(name), MakeBackInsertFunctor(vName), delims); + + // Find tokens that already present in input query. + vector 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 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 void Query::ProcessSuggestions(vector & vec, Results & res) const +{ + if (m_prefix.empty()) + return; + + int added = 0; + for (typename vector::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 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 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 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(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 & 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 const & regions) { size_t const regsCount = regions.size(); - vector & arr = m_localities[Locality::CITY]; + vector & arr = m_localities[ftypes::CITY]; // Interate in reverse order from better to generic locality. for (vector::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; } } diff --git a/search/search_query.hpp b/search/search_query.hpp index 203afeffec..580cc3293a 100644 --- a/search/search_query.hpp +++ b/search/search_query.hpp @@ -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 void ProcessSuggestions(vector & 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 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 m_tokens; strings::UniString m_prefix; + set 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