diff --git a/omim.pro b/omim.pro index de10d87686..3a7fd68531 100644 --- a/omim.pro +++ b/omim.pro @@ -73,10 +73,14 @@ SUBDIRS = 3party base coding geometry editor indexer routing search CONFIG(desktop) { search_quality.subdir = search/search_quality search_quality.depends = $$SUBDIRS + search_quality_tool.subdir = search/search_quality/search_quality_tool search_quality_tool.depends = $$SUBDIRS search_quality - SUBDIRS *= search_quality search_quality_tool + features_collector_tool.subdir = search/search_quality/features_collector_tool + features_collector_tool.depends = $$SUBDIRS search_quality + + SUBDIRS *= search_quality search_quality_tool features_collector_tool } CONFIG(desktop):!CONFIG(no-tests) { diff --git a/search/search_engine.cpp b/search/search_engine.cpp index 20d01fef3b..41f1c85cf8 100644 --- a/search/search_engine.cpp +++ b/search/search_engine.cpp @@ -174,6 +174,14 @@ void Engine::SetSupportOldFormat(bool support) }); } +void Engine::SetLocale(string const & locale) +{ + PostMessage(Message::TYPE_BROADCAST, [this, locale](Query & processor) + { + processor.SetPreferredLocale(locale); + }); +} + void Engine::ClearCaches() { PostMessage(Message::TYPE_BROADCAST, [this](Query & processor) diff --git a/search/search_engine.hpp b/search/search_engine.hpp index 7a6d0866b8..9a970bf9ab 100644 --- a/search/search_engine.hpp +++ b/search/search_engine.hpp @@ -103,6 +103,8 @@ public: // Posts request to support old format to the queue. void SetSupportOldFormat(bool support); + void SetLocale(string const & locale); + // Posts request to clear caches to the queue. void ClearCaches(); diff --git a/search/search_quality/features_collector_tool/features_collector_tool.cpp b/search/search_quality/features_collector_tool/features_collector_tool.cpp new file mode 100644 index 0000000000..fb0c396047 --- /dev/null +++ b/search/search_quality/features_collector_tool/features_collector_tool.cpp @@ -0,0 +1,185 @@ +#include "search/result.hpp" +#include "search/search_quality/sample.hpp" +#include "search/search_tests_support/test_search_engine.hpp" +#include "search/search_tests_support/test_search_request.hpp" +#include "search/v2/ranking_info.hpp" + +#include "indexer/classificator_loader.hpp" +#include "indexer/feature_algo.hpp" + +#include "platform/local_country_file.hpp" +#include "platform/local_country_file_utils.hpp" +#include "platform/platform.hpp" + +#include "base/string_utils.hpp" + +#include "std/fstream.hpp" +#include "std/iostream.hpp" +#include "std/limits.hpp" +#include "std/string.hpp" +#include "std/unique_ptr.hpp" +#include "std/vector.hpp" + +#include "3party/gflags/src/gflags/gflags.h" + +using namespace search; +using namespace search::tests_support; + +DEFINE_string(data_path, "", "Path to data directory (resources dir)"); +DEFINE_string(mwm_path, "", "Path to mwm files (writable dir)"); +DEFINE_string(json_in, "", "Path to the json file with samples (default: stdin)"); + +struct Context +{ + Context(Index & index) : m_index(index) {} + + void GetFeature(FeatureID const & id, FeatureType & ft) + { + auto const & mwmId = id.m_mwmId; + if (!m_guard || m_guard->GetId() != mwmId) + m_guard = make_unique(m_index, mwmId); + m_guard->GetFeatureByIndex(id.m_index, ft); + } + + Index & m_index; + unique_ptr m_guard; +}; + +void GetContents(istream & is, string & contents) +{ + string line; + while (getline(is, line)) + { + contents.append(line); + contents.push_back('\n'); + } +} + +bool Matches(Context & context, Sample::Result const & golden, search::Result const & actual) +{ + static double const kEps = 2 * 1e-5; + if (actual.GetResultType() != Result::RESULT_FEATURE) + return false; + + FeatureType ft; + context.GetFeature(actual.GetFeatureID(), ft); + + string name; + if (!ft.GetName(FeatureType::DEFAULT_LANG, name)) + name.clear(); + string const houseNumber = ft.GetHouseNumber(); + auto const center = feature::GetCenter(ft); + + return golden.m_name == strings::MakeUniString(name) && golden.m_houseNumber == houseNumber && + my::AlmostEqualAbs(golden.m_pos, center, kEps); +} + +void SetRelevances(Context & context, vector const & golden, + vector const & actual, + vector & relevances) +{ + auto const n = golden.size(); + auto const m = actual.size(); + relevances.assign(m, Sample::Result::RELEVANCE_IRRELEVANT); + + vector matched(m); + + // TODO (@y, @m): use Kuhn algorithm here for maximum matching. + for (size_t i = 0; i < n; ++i) + { + auto const & g = golden[i]; + for (size_t j = 0; j < m; ++j) + { + if (matched[j]) + continue; + auto const & a = actual[j]; + if (Matches(context, g, a)) + { + matched[j] = true; + relevances[j] = g.m_relevance; + } + } + } +} + +int main(int argc, char * argv[]) +{ + google::SetUsageMessage("Features collector tool."); + google::ParseCommandLineFlags(&argc, &argv, true); + + Platform & platform = GetPlatform(); + + if (!FLAGS_data_path.empty()) + platform.SetResourceDir(FLAGS_data_path); + + if (!FLAGS_mwm_path.empty()) + platform.SetWritableDirForTests(FLAGS_mwm_path); + + LOG(LINFO, ("writable dir =", platform.WritableDir())); + LOG(LINFO, ("resources dir =", platform.ResourcesDir())); + + string jsonStr; + if (FLAGS_json_in.empty()) + { + GetContents(cin, jsonStr); + } + else + { + ifstream ifs(FLAGS_json_in); + if (!ifs.is_open()) + { + cerr << "Can't open input json file." << endl; + return -1; + } + GetContents(ifs, jsonStr); + } + + vector samples; + if (!Sample::DeserializeFromJSON(jsonStr, samples)) + { + cerr << "Can't parse input json file." << endl; + return -1; + } + + classificator::Load(); + TestSearchEngine engine(make_unique(), Engine::Params{}); + vector mwms; + platform::FindAllLocalMapsAndCleanup(numeric_limits::max() /* the latest version */, + mwms); + for (auto & mwm : mwms) + { + mwm.SyncWithDisk(); + engine.RegisterMap(mwm); + } + + cout << "SampleId,"; + v2::RankingInfo::PrintCSVHeader(cout); + cout << ",Relevance" << endl; + + Context context(engine); + for (size_t i = 0; i < samples.size(); ++i) + { + auto const & sample = samples[i]; + + engine.SetLocale(sample.m_locale); + string query = strings::ToUtf8(sample.m_query); + TestSearchRequest request(engine, query, sample.m_locale, Mode::Everywhere, sample.m_viewport, + sample.m_pos); + request.Wait(); + + auto const & results = request.Results(); + + vector relevances; + SetRelevances(context, sample.m_results, results, relevances); + + ASSERT_EQUAL(results.size(), relevances.size(), ()); + for (size_t j = 0; j < results.size(); ++j) + { + auto const & info = results[j].GetRankingInfo(); + cout << i << ","; + info.ToCSV(cout); + cout << "," << DebugPrint(relevances[j]) << endl; + } + } + return 0; +} diff --git a/search/search_quality/features_collector_tool/features_collector_tool.pro b/search/search_quality/features_collector_tool/features_collector_tool.pro new file mode 100644 index 0000000000..857d1595f2 --- /dev/null +++ b/search/search_quality/features_collector_tool/features_collector_tool.pro @@ -0,0 +1,27 @@ +# Features collector tool. + +TARGET = features_collector_tool +CONFIG += console warn_on +CONFIG -= app_bundle +TEMPLATE = app + +ROOT_DIR = ../../.. +# todo(@m) revise +DEPENDENCIES = map drape_frontend routing search_tests_support search search_quality storage indexer drape \ + platform editor geometry coding base freetype expat fribidi tomcrypt gflags \ + jansson protobuf osrm stats_client minizip succinct \ + opening_hours pugixml + +include($$ROOT_DIR/common.pri) + +INCLUDEPATH *= $$ROOT_DIR/3party/gflags/src +INCLUDEPATH += $$ROOT_DIR/3party/jansson/src + +# needed for Platform::WorkingDir() and unicode combining +QT *= core network opengl + +macx-* { + LIBS *= "-framework IOKit" "-framework SystemConfiguration" +} + +SOURCES += features_collector_tool.cpp \ diff --git a/search/search_tests_support/test_search_engine.hpp b/search/search_tests_support/test_search_engine.hpp index e1ce01a6ac..ac953375be 100644 --- a/search/search_tests_support/test_search_engine.hpp +++ b/search/search_tests_support/test_search_engine.hpp @@ -33,6 +33,8 @@ public: TestSearchEngine(unique_ptr<::search::SearchQueryFactory> factory, Engine::Params const & params); ~TestSearchEngine() override; + inline void SetLocale(string const & locale) { m_engine.SetLocale(locale); } + weak_ptr Search(search::SearchParams const & params, m2::RectD const & viewport); diff --git a/search/search_tests_support/test_search_request.cpp b/search/search_tests_support/test_search_request.cpp index 1bc224fb1b..725533cd94 100644 --- a/search/search_tests_support/test_search_request.cpp +++ b/search/search_tests_support/test_search_request.cpp @@ -2,6 +2,9 @@ #include "search/search_tests_support/test_search_engine.hpp" +#include "geometry/latlon.hpp" +#include "geometry/mercator.hpp" + #include "base/logging.hpp" namespace search @@ -19,6 +22,21 @@ TestSearchRequest::TestSearchRequest(TestSearchEngine & engine, string const & q engine.Search(params, viewport); } +TestSearchRequest::TestSearchRequest(TestSearchEngine & engine, string const & query, + string const & locale, Mode mode, m2::RectD const & viewport, + m2::PointD const & position) +{ + auto latLon = MercatorBounds::ToLatLon(position); + + SearchParams params; + params.m_query = query; + params.m_inputLocale = locale; + params.SetMode(mode); + params.SetPosition(latLon.lat, latLon.lon); + SetUpCallbacks(params); + engine.Search(params, viewport); +} + TestSearchRequest::TestSearchRequest(TestSearchEngine & engine, SearchParams params, m2::RectD const & viewport) { diff --git a/search/search_tests_support/test_search_request.hpp b/search/search_tests_support/test_search_request.hpp index 5ced915705..ac3168874e 100644 --- a/search/search_tests_support/test_search_request.hpp +++ b/search/search_tests_support/test_search_request.hpp @@ -26,7 +26,8 @@ class TestSearchRequest public: TestSearchRequest(TestSearchEngine & engine, string const & query, string const & locale, Mode mode, m2::RectD const & viewport); - + TestSearchRequest(TestSearchEngine & engine, string const & query, string const & locale, + Mode mode, m2::RectD const & viewport, m2::PointD const & position); TestSearchRequest(TestSearchEngine & engine, SearchParams params, m2::RectD const & viewport); void Wait(); diff --git a/search/v2/ranking_info.cpp b/search/v2/ranking_info.cpp index 96f033dac7..cd834c7c01 100644 --- a/search/v2/ranking_info.cpp +++ b/search/v2/ranking_info.cpp @@ -12,7 +12,6 @@ void RankingInfo::PrintCSVHeader(ostream & os) << ",Rank" << ",NameScore" << ",SearchType" - << ",SameCountry" << ",PositionInViewport"; } @@ -25,7 +24,6 @@ string DebugPrint(RankingInfo const & info) os << "m_rank:" << static_cast(info.m_rank) << ","; os << "m_nameScore:" << DebugPrint(info.m_nameScore) << ","; os << "m_searchType:" << DebugPrint(info.m_searchType) << ","; - os << "m_sameCountry:" << info.m_sameCountry << ","; os << "m_positionInViewport:" << info.m_positionInViewport; os << "]"; return os.str(); @@ -35,8 +33,8 @@ void RankingInfo::ToCSV(ostream & os) const { os << fixed; os << m_distanceToViewport << "," << m_distanceToPosition << "," << static_cast(m_rank) - << "," << DebugPrint(m_nameScore) << "," << DebugPrint(m_searchType) << "," << m_sameCountry - << "," << m_positionInViewport; + << "," << DebugPrint(m_nameScore) << "," << DebugPrint(m_searchType) << "," + << m_positionInViewport; } } // namespace v2 } // namespace search diff --git a/search/v2/ranking_info.hpp b/search/v2/ranking_info.hpp index 651fbe32f8..714aa1ce1d 100644 --- a/search/v2/ranking_info.hpp +++ b/search/v2/ranking_info.hpp @@ -28,9 +28,6 @@ struct RankingInfo // Search type for the feature. SearchModel::SearchType m_searchType = SearchModel::SEARCH_TYPE_COUNT; - // True if feature is located in the same country as the user. - bool m_sameCountry = false; - // True if user's position is in viewport. bool m_positionInViewport = false;