From 07e407b360aba59d7590c50388b90f483a00776f Mon Sep 17 00:00:00 2001 From: Denis Koronchik Date: Fri, 27 Jun 2014 20:00:47 +0300 Subject: [PATCH] Implement LocalityFinder component. --- search/locality_finder.cpp | 184 +++++++++++++++++++ search/locality_finder.hpp | 76 ++++++++ search/search.pro | 2 + search/search_tests/locality_finder_test.cpp | 92 ++++++++++ search/search_tests/search_tests.pro | 1 + 5 files changed, 355 insertions(+) create mode 100644 search/locality_finder.cpp create mode 100644 search/locality_finder.hpp create mode 100644 search/search_tests/locality_finder_test.cpp diff --git a/search/locality_finder.cpp b/search/locality_finder.cpp new file mode 100644 index 0000000000..c3aa157fc7 --- /dev/null +++ b/search/locality_finder.cpp @@ -0,0 +1,184 @@ +#include "locality_finder.hpp" +#include "ftypes_matcher.hpp" + +#include "../indexer/features_vector.hpp" + +#include "../geometry/distance_on_sphere.hpp" + + +namespace search +{ + +double const MAX_RADIUS_M = 30000.0; + +class DoLoader +{ +public: + DoLoader(LocalityFinder & finder, FeaturesVector const & loader, LocalityFinder::Cache & cache, m2::RectD const & rect) + : m_finder(finder), m_loader(loader), m_cache(cache), m_rect(rect) + { + } + + void operator() (uint32_t id) + { + FeatureType ft; + m_loader.Get(id, ft); + + if (ft.GetFeatureType() != feature::GEOM_POINT) + return; + + using namespace ftypes; + switch (IsLocalityChecker::Instance().GetType(ft)) + { + case CITY: + case TOWN: + break; + default: // cache only cities and towns at this moment + return; + } + + if (m_cache.m_loaded.count(id) > 0) + return; // already loaded + + uint32_t const population = ftypes::GetPopulation(ft); + if (population == 0) + return; + + double const radius = ftypes::GetRadiusByPopulation(population); + m2::RectD const rect = MercatorBounds::RectByCenterXYAndSizeInMeters(ft.GetCenter(), radius); + if (!rect.IsIntersect(m_rect)) + return; + + // read item + string name; + if (!ft.GetName(m_finder.m_lang, name)) + if (!ft.GetName(0, name)) + return; + + LocalityItem item(rect, population, id, name); + m_cache.m_tree.Add(item, item.GetLimitRect()); + m_cache.m_loaded.insert(id); + } + +private: + LocalityFinder & m_finder; + FeaturesVector const & m_loader; + LocalityFinder::Cache & m_cache; + m2::RectD m_rect; +}; + + +class DoSelectLocality +{ +public: + DoSelectLocality(string & name, m2::PointD const & p) + : m_name(name) , m_point(p), m_bestValue(numeric_limits::max()) + { + } + + void operator() (LocalityItem const & item) + { + m2::PointD const c = item.m_rect.Center(); + double const d = ms::DistanceOnEarth(MercatorBounds::YToLat(c.y), + MercatorBounds::XToLon(c.x), + MercatorBounds::YToLat(m_point.y), + MercatorBounds::XToLon(m_point.x)); + + double const value = ftypes::GetPopulationByRadius(d) / static_cast(item.m_population); + if (value < m_bestValue) + { + m_bestValue = value; + m_name = item.m_name; + } + } + +private: + string & m_name; + m2::PointD m_point; + double m_bestValue; +}; + + + +LocalityItem::LocalityItem(m2::RectD const & rect, uint32_t population, ID id, string const & name) + : m_rect(rect), m_name(name), m_population(population), m_id(id) +{ + +} + +LocalityFinder::LocalityFinder(Index const * pIndex) + : m_pIndex(pIndex), m_lang(0) +{ +} + +void LocalityFinder::CorrectMinimalRect(m2::RectD & rect) +{ + m2::RectD const rlt = MercatorBounds::RectByCenterXYAndSizeInMeters(rect.LeftTop(), MAX_RADIUS_M); + m2::RectD const rrb = MercatorBounds::RectByCenterXYAndSizeInMeters(rect.RightBottom(), MAX_RADIUS_M); + rect = m2::RectD(MercatorBounds::ClampX(rlt.minX()), + MercatorBounds::ClampY(rrb.minY()), + MercatorBounds::ClampX(rrb.maxX()), + MercatorBounds::ClampY(rlt.maxY())); +} + +void LocalityFinder::SetViewportByIndex(MWMVectorT const & mwmInfo, m2::RectD rect, size_t idx) +{ + ASSERT_LESS(idx, (size_t)MAX_VIEWPORT_COUNT, ()); + + ClearCache(idx); + + CorrectMinimalRect(rect); + covering::CoveringGetter cov(rect, covering::ViewportWithLowLevels); + + for (MwmSet::MwmId mwmId = 0; mwmId < mwmInfo.size(); ++mwmId) + { + typedef feature::DataHeader HeaderT; + + Index::MwmLock mwmLock(*m_pIndex, mwmId); + MwmValue * pMwm = mwmLock.GetValue(); + if (pMwm && pMwm->GetHeader().GetType() == HeaderT::world) + { + HeaderT const & header = pMwm->GetHeader(); + + int const scale = header.GetLastScale(); // scales::GetUpperWorldScale() + covering::IntervalsT const & interval = cov.Get(scale); + + ScaleIndex index(pMwm->m_cont.GetReader(INDEX_FILE_TAG), pMwm->m_factory); + + FeaturesVector loader(pMwm->m_cont, header); + + for (size_t i = 0; i < interval.size(); ++i) + { + index.ForEachInIntervalAndScale(DoLoader(*this, loader, m_cache[idx], rect), + interval[i].first, interval[i].second, + scale); + } + } + } +} + +void LocalityFinder::GetLocality(const m2::PointD & pt, string & name) const +{ + for (size_t i = 0; i < MAX_VIEWPORT_COUNT; ++i) + m_cache[i].m_tree.ForEachInRect(m2::RectD(pt, pt), DoSelectLocality(name, pt)); +} + +void LocalityFinder::ClearCacheAll() +{ + for (size_t i = 0; i < MAX_VIEWPORT_COUNT; ++i) + ClearCache(i); +} + +void LocalityFinder::ClearCache(size_t idx) +{ + ASSERT_LESS(idx, (size_t)MAX_VIEWPORT_COUNT, ()); + m_cache[idx].Clear(); +} + +void LocalityFinder::Cache::Clear() +{ + m_tree.Clear(); + m_loaded.clear(); +} + +} diff --git a/search/locality_finder.hpp b/search/locality_finder.hpp new file mode 100644 index 0000000000..ebcdc102e2 --- /dev/null +++ b/search/locality_finder.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "../indexer/index.hpp" + +#include "../geometry/point2d.hpp" +#include "../geometry/rect2d.hpp" +#include "../geometry/tree4d.hpp" + +#include "../std/set.hpp" + + +class Index; + +namespace search +{ + +struct LocalityItem +{ + m2::RectD m_rect; + string m_name; + uint32_t m_population; + + typedef uint32_t ID; + ID m_id; + + LocalityItem(m2::RectD const & rect, uint32_t population, ID id, string const & name); + + m2::RectD const & GetLimitRect() const { return m_rect; } +}; + +class LocalityFinder +{ +public: + typedef vector MWMVectorT; + + LocalityFinder(Index const * pIndex); + + void SetLanguage(int8_t lang) + { + if (m_lang != lang) + { + ClearCacheAll(); + m_lang = lang; + } + } + + void SetViewportByIndex(MWMVectorT const & mwmInfo, m2::RectD rect, size_t idx); + + void GetLocality(m2::PointD const & pt, string & name) const; + + void ClearCacheAll(); + void ClearCache(size_t idx); + +protected: + void CorrectMinimalRect(m2::RectD & rect); + +private: + friend class DoLoader; + + Index const * m_pIndex; + + struct Cache + { + m4::Tree m_tree; + set m_loaded; + + void Clear(); + }; + + enum { MAX_VIEWPORT_COUNT = 3 }; + Cache m_cache[MAX_VIEWPORT_COUNT]; + + int8_t m_lang; +}; + +} diff --git a/search/search.pro b/search/search.pro index 22ef8c6d27..f81aad664f 100644 --- a/search/search.pro +++ b/search/search.pro @@ -26,6 +26,7 @@ HEADERS += \ indexed_value.hpp \ geometry_utils.hpp \ search_string_intersection.hpp \ + locality_finder.hpp \ SOURCES += \ search_engine.cpp \ @@ -40,3 +41,4 @@ SOURCES += \ house_detector.cpp \ ftypes_matcher.cpp \ geometry_utils.cpp \ + locality_finder.cpp \ diff --git a/search/search_tests/locality_finder_test.cpp b/search/search_tests/locality_finder_test.cpp new file mode 100644 index 0000000000..7b4a4a8898 --- /dev/null +++ b/search/search_tests/locality_finder_test.cpp @@ -0,0 +1,92 @@ +#include "../../testing/testing.hpp" + +#include "../../indexer/index.hpp" +#include "../locality_finder.hpp" + +namespace +{ + +void doTests(search::LocalityFinder & finder, vector const & input, char const * results[]) +{ + for (size_t i = 0; i < input.size(); ++i) + { + string result; + finder.GetLocality(m2::PointD(MercatorBounds::LonToX(input[i].x), MercatorBounds::LatToY(input[i].y)), result); + TEST_EQUAL(result, results[i], ()); + } +} + +} + +UNIT_TEST(LocalityFinder) +{ + Index index; + m2::RectD rect; + if (!index.Add("World.mwm", rect)) + { + LOG(LWARNING, ("MWM file not found")); + return; + } + + search::LocalityFinder finder(&index); + finder.SetLanguage(StringUtf8Multilang::GetLangIndex("en")); + + search::LocalityFinder::MWMVectorT mwm; + index.GetMwmInfo(mwm); + finder.SetViewportByIndex(mwm, MercatorBounds::FullRect(), 0); + + vector input; + input.push_back(m2::PointD(27.5433964, 53.8993094)); // Minsk + input.push_back(m2::PointD(2.3521, 48.856517)); // Paris + input.push_back(m2::PointD(13.3908289, 52.5193859)); // Berlin + + char const * results[] = + { + "Minsk", + "Paris", + "Berlin" + }; + + // Tets one viewport based on whole map + doTests(finder, input, results); + + // Test two viewport based on quaters of worl map + m2::RectD rect1; + rect1.setMinX(rect.minX()); + rect1.setMinY(rect.minY()); + rect1.setMaxX(rect.Center().x); + rect1.setMaxY(rect.Center().y); + + m2::RectD rect2; + rect2.setMinX(rect.Center().x); + rect2.setMinY(rect.Center().y); + rect2.setMaxY(rect.maxY()); + rect2.setMaxX(rect.maxX()); + + input.clear(); + input.push_back(m2::PointD(-87.624367, 41.875)); // Chicago + input.push_back(m2::PointD(-43.209384, -22.911225)); // Rio de Janeiro + input.push_back(m2::PointD(144.96, -37.8142)); // Melbourne (Australia) + input.push_back(m2::PointD(27.69341, 53.883931)); // Parkin Minsk (near MKAD) + input.push_back(m2::PointD(27.707875, 53.917306)); // Lipki airport (Minsk) + input.push_back(m2::PointD(18.834407, 42.285901)); // Budva (Montenegro) + input.push_back(m2::PointD(12.452854, 41.903479)); // Vaticano (Rome) + input.push_back(m2::PointD(8.531262, 47.3345002)); // Zurich + + finder.SetViewportByIndex(mwm, rect1, 0); + finder.SetViewportByIndex(mwm, rect2, 1); + + char const * results2[] = + { + "", + "Rio de Janeiro", + "", + "Minsk", + "Minsk", + "Budva", + "Rome", + "Zurich" + }; + + doTests(finder, input, results2); +} diff --git a/search/search_tests/search_tests.pro b/search/search_tests/search_tests.pro index ee51ff10b8..e87dd1eaee 100644 --- a/search/search_tests/search_tests.pro +++ b/search/search_tests/search_tests.pro @@ -29,6 +29,7 @@ SOURCES += \ house_detector_tests.cpp \ algos_tests.cpp \ string_intersection_test.cpp \ + locality_finder_test.cpp \ HEADERS += \ match_cost_mock.hpp \