From 2adf4dc9df4025f112bf4d6e1738049e3fea5d5a Mon Sep 17 00:00:00 2001 From: Yuri Gorshenin Date: Wed, 2 Dec 2015 12:19:30 +0300 Subject: [PATCH] [search] Implemented intersection of POI and STREET layers. --- search/search.pro | 2 + .../search_query_v2_test.cpp | 18 +++++- search/search_tests_support/test_feature.cpp | 29 +++++++++ search/search_tests_support/test_feature.hpp | 15 +++++ search/v2/features_layer.cpp | 4 +- search/v2/features_layer.hpp | 2 +- search/v2/features_layer_matcher.cpp | 9 ++- search/v2/features_layer_matcher.hpp | 62 +++++++++++++++---- search/v2/features_layer_path_finder.cpp | 7 ++- search/v2/features_layer_path_finder.hpp | 7 ++- search/v2/geocoder.cpp | 38 ++++++++++-- search/v2/street_vicinity_loader.cpp | 50 +++++++++++++++ search/v2/street_vicinity_loader.hpp | 51 +++++++++++++++ 13 files changed, 266 insertions(+), 28 deletions(-) create mode 100644 search/v2/street_vicinity_loader.cpp create mode 100644 search/v2/street_vicinity_loader.hpp diff --git a/search/search.pro b/search/search.pro index eae239f7a3..6116e73dfc 100644 --- a/search/search.pro +++ b/search/search.pro @@ -47,6 +47,7 @@ HEADERS += \ v2/geocoder.hpp \ v2/search_model.hpp \ v2/search_query_v2.hpp \ + v2/street_vicinity_loader.hpp \ SOURCES += \ approximate_string_match.cpp \ @@ -77,3 +78,4 @@ SOURCES += \ v2/geocoder.cpp \ v2/search_model.cpp \ v2/search_query_v2.cpp \ + v2/street_vicinity_loader.cpp \ diff --git a/search/search_integration_tests/search_query_v2_test.cpp b/search/search_integration_tests/search_query_v2_test.cpp index 0c9bc8587c..a87b89c2c7 100644 --- a/search/search_integration_tests/search_query_v2_test.cpp +++ b/search/search_integration_tests/search_query_v2_test.cpp @@ -54,7 +54,10 @@ UNIT_TEST(SearchQueryV2_Smoke) m2::RectD viewport(m2::PointD(-1.0, -1.0), m2::PointD(1.0, 1.0)); Cleanup(map); - MY_SCOPE_GUARD(cleanup, [&]() { Cleanup(map); }); + MY_SCOPE_GUARD(cleanup, [&]() + { + Cleanup(map); + }); vector countries; countries.emplace_back(map.GetCountryName(), viewport); @@ -69,6 +72,9 @@ UNIT_TEST(SearchQueryV2_Smoke) auto const quantumTeleport2 = make_shared(m2::PointD(10, 10), "Quantum teleport 2", "en"); auto const quantumCafe = make_shared(m2::PointD(-0.0002, -0.0002), "Quantum cafe", "en"); + auto const faynmannStreet = make_shared( + vector{m2::PointD(9.999, 9.999), m2::PointD(10, 10), m2::PointD(10.001, 10.001)}, + "Faynmann street", "en"); { TestMwmBuilder builder(map, feature::DataHeader::country); @@ -78,6 +84,7 @@ UNIT_TEST(SearchQueryV2_Smoke) builder.Add(*quantumTeleport1); builder.Add(*quantumTeleport2); builder.Add(*quantumCafe); + builder.Add(*faynmannStreet); } auto const regResult = engine.RegisterMap(map); @@ -114,4 +121,13 @@ UNIT_TEST(SearchQueryV2_Smoke) request.Wait(); TEST_EQUAL(0, request.Results().size(), ()); } + + { + TestSearchRequest request(engine, "teleport faynmann street", "en", search::SearchParams::ALL, + viewport); + request.Wait(); + vector> rules = { + make_shared(regResult.first, quantumTeleport2)}; + TEST(MatchResults(engine, rules, request.Results()), ()); + } } diff --git a/search/search_tests_support/test_feature.cpp b/search/search_tests_support/test_feature.cpp index 10795fe954..a6bd3143e8 100644 --- a/search/search_tests_support/test_feature.cpp +++ b/search/search_tests_support/test_feature.cpp @@ -15,6 +15,8 @@ namespace search { namespace tests_support { +TestFeature::TestFeature(string const & name, string const & lang) : m_name(name), m_lang(lang) {} + TestFeature::TestFeature(m2::PointD const & center, string const & name, string const & lang) : m_center(center), m_name(name), m_lang(lang) { @@ -33,6 +35,7 @@ bool TestFeature::Matches(FeatureType const & feature) const return feature.GetName(langIndex, name) && m_name == name; } +// TestPOI ----------------------------------------------------------------------------------------- TestPOI::TestPOI(m2::PointD const & center, string const & name, string const & lang) : TestFeature(center, name, lang) { @@ -52,6 +55,7 @@ string TestPOI::ToString() const return os.str(); } +// TestCity ---------------------------------------------------------------------------------------- TestCity::TestCity(m2::PointD const & center, string const & name, string const & lang, uint8_t rank) : TestFeature(center, name, lang), m_rank(rank) @@ -73,6 +77,31 @@ string TestCity::ToString() const return os.str(); } +// TestStreet -------------------------------------------------------------------------------------- +TestStreet::TestStreet(vector const & points, string const & name, string const & lang) + : TestFeature(name, lang), m_points(points) +{ +} + +void TestStreet::Serialize(FeatureBuilder1 & fb) const +{ + CHECK(fb.AddName(m_lang, m_name), ("Can't set feature name:", m_name, "(", m_lang, ")")); + + auto const & classificator = classif(); + fb.SetType(classificator.GetTypeByPath({"highway", "living_street"})); + + for (auto const & point : m_points) + fb.AddPoint(point); + fb.SetLinear(false /* reverseGeometry */); +} + +string TestStreet::ToString() const +{ + ostringstream os; + os << "TestStreet [" << m_name << ", " << m_lang << ", " << ::DebugPrint(m_points) << "]"; + return os.str(); +} + string DebugPrint(TestFeature const & feature) { return feature.ToString(); } } // namespace tests_support } // namespace search diff --git a/search/search_tests_support/test_feature.hpp b/search/search_tests_support/test_feature.hpp index 00ee7ddb79..88eef792c1 100644 --- a/search/search_tests_support/test_feature.hpp +++ b/search/search_tests_support/test_feature.hpp @@ -3,6 +3,7 @@ #include "geometry/point2d.hpp" #include "std/string.hpp" +#include "std/vector.hpp" class FeatureBuilder1; class FeatureType; @@ -21,6 +22,7 @@ public: virtual string ToString() const = 0; protected: + TestFeature(string const & name, string const & lang); TestFeature(m2::PointD const & center, string const & name, string const & lang); m2::PointD const m_center; @@ -51,6 +53,19 @@ private: uint8_t const m_rank; }; +class TestStreet : public TestFeature +{ +public: + TestStreet(vector const & points, string const & name, string const & lang); + + // TestFeature overrides: + void Serialize(FeatureBuilder1 & fb) const override; + string ToString() const override; + +private: + vector m_points; +}; + string DebugPrint(TestFeature const & feature); } // namespace tests_support } // namespace search diff --git a/search/v2/features_layer.cpp b/search/v2/features_layer.cpp index 4d137d958c..7bd2bf60c3 100644 --- a/search/v2/features_layer.cpp +++ b/search/v2/features_layer.cpp @@ -12,7 +12,7 @@ FeaturesLayer::FeaturesLayer() { Clear(); } void FeaturesLayer::Clear() { - m_features.clear(); + m_sortedFeatures.clear(); m_startToken = 0; m_endToken = 0; m_type = SearchModel::SEARCH_TYPE_COUNT; @@ -21,7 +21,7 @@ void FeaturesLayer::Clear() string DebugPrint(FeaturesLayer const & layer) { ostringstream os; - os << "FeaturesLayer [ m_features: " << ::DebugPrint(layer.m_features) + os << "FeaturesLayer [ m_sortedFeatures: " << ::DebugPrint(layer.m_sortedFeatures) << ", m_startToken: " << layer.m_startToken << ", m_endToken: " << layer.m_endToken << ", m_type: " << DebugPrint(layer.m_type) << " ]"; return os.str(); diff --git a/search/v2/features_layer.hpp b/search/v2/features_layer.hpp index 97bac2a857..5beaccb6e7 100644 --- a/search/v2/features_layer.hpp +++ b/search/v2/features_layer.hpp @@ -17,7 +17,7 @@ struct FeaturesLayer void Clear(); - vector m_features; + vector m_sortedFeatures; size_t m_startToken; size_t m_endToken; SearchModel::SearchType m_type; diff --git a/search/v2/features_layer_matcher.cpp b/search/v2/features_layer_matcher.cpp index 7de71e3f89..0a19e41f52 100644 --- a/search/v2/features_layer_matcher.cpp +++ b/search/v2/features_layer_matcher.cpp @@ -4,8 +4,13 @@ namespace search { namespace v2 { -FeaturesLayerMatcher::FeaturesLayerMatcher(FeaturesVector const & featuresVector) - : m_featuresVector(featuresVector) +namespace +{ +static double constexpr kDefaultRadiusMeters = 200; +} // namespace + +FeaturesLayerMatcher::FeaturesLayerMatcher(MwmValue & value, FeaturesVector const & featuresVector) + : m_featuresVector(featuresVector), m_loader(value, featuresVector, kDefaultRadiusMeters) { } } // namespace v2 diff --git a/search/v2/features_layer_matcher.hpp b/search/v2/features_layer_matcher.hpp index 733768121c..be9d464cc7 100644 --- a/search/v2/features_layer_matcher.hpp +++ b/search/v2/features_layer_matcher.hpp @@ -2,11 +2,13 @@ #include "search/v2/features_layer.hpp" #include "search/v2/search_model.hpp" +#include "search/v2/street_vicinity_loader.hpp" #include "indexer/feature.hpp" #include "indexer/feature_algo.hpp" #include "indexer/features_vector.hpp" #include "indexer/ftypes_matcher.hpp" +#include "indexer/scales.hpp" #include "geometry/mercator.hpp" #include "geometry/point2d.hpp" @@ -14,8 +16,11 @@ #include "base/macros.hpp" +#include "std/algorithm.hpp" #include "std/vector.hpp" +class MwmValue; + namespace search { namespace v2 @@ -23,24 +28,24 @@ namespace v2 class FeaturesLayerMatcher { public: - FeaturesLayerMatcher(FeaturesVector const & featuresVector); + FeaturesLayerMatcher(MwmValue & value, FeaturesVector const & featuresVector); template - void Match(FeaturesLayer const & child, FeaturesLayer const & parent, TFn && fn) const + void Match(FeaturesLayer const & child, FeaturesLayer const & parent, TFn && fn) { if (child.m_type >= parent.m_type) return; - if (child.m_type == SearchModel::SEARCH_TYPE_BUILDING && - parent.m_type == SearchModel::SEARCH_TYPE_STREET) + if (parent.m_type == SearchModel::SEARCH_TYPE_STREET) { - // TODO (y@): match buildings with streets. + if (child.m_type == SearchModel::SEARCH_TYPE_POI) + MatchPOIsWithStreets(child, parent, forward(fn)); + else if (child.m_type == SearchModel::SEARCH_TYPE_BUILDING) + MatchBuildingsWithStreets(child, parent, forward(fn)); return; } - // TODO (y@): match POI with streets separately. - vector childCenters; - for (uint32_t featureId : child.m_features) + for (uint32_t featureId : child.m_sortedFeatures) { FeatureType ft; m_featuresVector.GetByIndex(featureId, ft); @@ -48,7 +53,7 @@ public: } vector parentRects; - for (uint32_t featureId : parent.m_features) + for (uint32_t featureId : parent.m_sortedFeatures) { FeatureType feature; m_featuresVector.GetByIndex(featureId, feature); @@ -57,9 +62,9 @@ public: parentRects.push_back(MercatorBounds::RectByCenterXYAndSizeInMeters(center, radius)); } - for (size_t j = 0; j < parent.m_features.size(); ++j) + for (size_t j = 0; j < parent.m_sortedFeatures.size(); ++j) { - for (size_t i = 0; i < child.m_features.size(); ++i) + for (size_t i = 0; i < child.m_sortedFeatures.size(); ++i) { if (parentRects[j].IsPointInside(childCenters[i])) fn(i, j); @@ -68,7 +73,42 @@ public: } private: + template + void MatchPOIsWithStreets(FeaturesLayer const & child, FeaturesLayer const & parent, TFn && fn) + { + ASSERT_EQUAL(child.m_type, SearchModel::SEARCH_TYPE_POI, ()); + ASSERT_EQUAL(parent.m_type, SearchModel::SEARCH_TYPE_STREET, ()); + + for (size_t j = 0; j < parent.m_sortedFeatures.size(); ++j) + { + auto match = [&](uint32_t poiId) + { + auto const it = + lower_bound(child.m_sortedFeatures.begin(), child.m_sortedFeatures.end(), poiId); + if (it != child.m_sortedFeatures.end() && *it == poiId) + { + size_t i = distance(child.m_sortedFeatures.begin(), it); + fn(i, j); + } + }; + + m_loader.ForEachInVicinity(parent.m_sortedFeatures[j], scales::GetUpperScale(), match); + } + } + + template + void MatchBuildingsWithStreets(FeaturesLayer const & child, FeaturesLayer const & parent, + TFn && fn) + { + ASSERT_EQUAL(child.m_type, SearchModel::SEARCH_TYPE_BUILDING, ()); + ASSERT_EQUAL(parent.m_type, SearchModel::SEARCH_TYPE_STREET, ()); + + // TODO (@y): implement this + NOTIMPLEMENTED(); + } + FeaturesVector const & m_featuresVector; + StreetVicinityLoader m_loader; }; } // namespace v2 } // namespace search diff --git a/search/v2/features_layer_path_finder.cpp b/search/v2/features_layer_path_finder.cpp index 7853e4d498..240bb5ec8a 100644 --- a/search/v2/features_layer_path_finder.cpp +++ b/search/v2/features_layer_path_finder.cpp @@ -8,8 +8,9 @@ namespace search { namespace v2 { -FeaturesLayerPathFinder::FeaturesLayerPathFinder(FeaturesVector const & featuresVector) - : m_matcher(featuresVector) +FeaturesLayerPathFinder::FeaturesLayerPathFinder(MwmValue & value, + FeaturesVector const & featuresVector) + : m_matcher(value, featuresVector) { } @@ -23,7 +24,7 @@ void FeaturesLayerPathFinder::BuildGraph(vector const & layers) auto & parent = (*layers[i + 1]); auto addEdges = [&](uint32_t from, uint32_t to) { - m_graph[parent.m_features[to]].push_back(child.m_features[from]); + m_graph[parent.m_sortedFeatures[to]].push_back(child.m_sortedFeatures[from]); }; m_matcher.Match(child, parent, addEdges); } diff --git a/search/v2/features_layer_path_finder.hpp b/search/v2/features_layer_path_finder.hpp index 1096185cb4..c94fb458f6 100644 --- a/search/v2/features_layer_path_finder.hpp +++ b/search/v2/features_layer_path_finder.hpp @@ -7,6 +7,7 @@ #include "std/vector.hpp" class FeaturesVector; +class MwmValue; namespace search { @@ -20,7 +21,7 @@ public: using TAdjList = vector; using TLayerGraph = unordered_map; - FeaturesLayerPathFinder(FeaturesVector const & featuresVector); + FeaturesLayerPathFinder(MwmValue & value, FeaturesVector const & featuresVector); template void ForEachReachableVertex(vector const & layers, TFn && fn) @@ -31,10 +32,10 @@ public: BuildGraph(layers); m_visited.clear(); - for (uint32_t featureId : (*layers.back()).m_features) + for (uint32_t featureId : (*layers.back()).m_sortedFeatures) Dfs(featureId); - for (uint32_t featureId : (*layers.front()).m_features) + for (uint32_t featureId : (*layers.front()).m_sortedFeatures) { if (m_visited.count(featureId) != 0) fn(featureId); diff --git a/search/v2/geocoder.cpp b/search/v2/geocoder.cpp index 1e65e187a9..c3f3d48525 100644 --- a/search/v2/geocoder.cpp +++ b/search/v2/geocoder.cpp @@ -6,6 +6,7 @@ #include "search/search_string_utils.hpp" #include "indexer/feature_decl.hpp" +#include "indexer/feature_impl.hpp" #include "indexer/index.hpp" #include "coding/multilang_utf8_string.hpp" @@ -25,6 +26,30 @@ namespace search { namespace v2 { +namespace +{ +void JoinQueryTokens(SearchQueryParams const & params, size_t curToken, size_t endToken, + string const & sep, string & res) +{ + ASSERT_LESS_OR_EQUAL(curToken, endToken, ()); + for (size_t i = curToken; i < endToken; ++i) + { + if (i < params.m_tokens.size()) + { + res.append(strings::ToUtf8(params.m_tokens[i].front())); + } + else + { + ASSERT_EQUAL(i, params.m_tokens.size(), ()); + res.append(strings::ToUtf8(params.m_prefixTokens.front())); + } + + if (i + 1 != endToken) + res.append(sep); + } +} +} // namespace + Geocoder::Geocoder(Index & index) : m_index(index) , m_numTokens(0) @@ -79,7 +104,7 @@ void Geocoder::Go(vector & results) }); m_loader.reset(new Index::FeaturesLoaderGuard(m_index, m_mwmId)); - m_finder.reset(new FeaturesLayerPathFinder(m_loader->GetFeaturesVector())); + m_finder.reset(new FeaturesLayerPathFinder(*m_value, m_loader->GetFeaturesVector())); m_cache.clear(); DoGeocoding(0 /* curToken */); @@ -172,7 +197,8 @@ void Geocoder::DoGeocoding(size_t curToken) // DoGeocoding(). This may lead to use-after-free. auto & layer = m_layers.back(); - layer.m_features.swap(clusters[i]); + layer.m_sortedFeatures.swap(clusters[i]); + ASSERT(is_sorted(layer.m_sortedFeatures.begin(), layer.m_sortedFeatures.end()), ()); layer.m_type = static_cast(i); if (IsLayerSequenceSane()) DoGeocoding(curToken + n); @@ -213,9 +239,11 @@ bool Geocoder::IsLayerSequenceSane() const bool Geocoder::LooksLikeHouseNumber(size_t curToken, size_t endToken) const { - // TODO (@y): implement this. - // NOTIMPLEMENTED(); - return false; + string res; + JoinQueryTokens(m_params, curToken, endToken, " " /* sep */, res); + + // TODO (@y): we need to implement a better check here. + return feature::IsHouseNumber(res); } void Geocoder::FindPaths() diff --git a/search/v2/street_vicinity_loader.cpp b/search/v2/street_vicinity_loader.cpp new file mode 100644 index 0000000000..469d19fe2a --- /dev/null +++ b/search/v2/street_vicinity_loader.cpp @@ -0,0 +1,50 @@ +#include "search/v2/street_vicinity_loader.hpp" + +#include "indexer/feature.hpp" +#include "indexer/feature_decl.hpp" +#include "indexer/features_vector.hpp" +#include "indexer/index.hpp" + +#include "geometry/mercator.hpp" + +namespace search +{ +namespace +{ +m2::RectD GetStreetLimitRect(FeatureType const & feature, double offsetMeters) +{ + m2::RectD rect; + if (feature.GetFeatureType() != feature::GEOM_LINE) + return rect; + + auto expandRect = [&rect, &offsetMeters](m2::PointD const & point) + { + rect.Add(MercatorBounds::RectByCenterXYAndSizeInMeters(point, offsetMeters)); + }; + feature.ForEachPoint(expandRect, FeatureType::BEST_GEOMETRY); + return rect; +} +} // namespace + +StreetVicinityLoader::StreetVicinityLoader(MwmValue & value, FeaturesVector const & featuresVector, + double offsetMeters) + : m_index(value.m_cont.GetReader(INDEX_FILE_TAG), value.m_factory) + , m_scaleRange(value.GetHeader().GetScaleRange()) + , m_featuresVector(featuresVector) + , m_offsetMeters(offsetMeters) +{ +} + +m2::RectD StreetVicinityLoader::GetLimitRect(uint32_t featureId) +{ + auto it = m_cache.find(featureId); + if (it != m_cache.end()) + return it->second; + + FeatureType feature; + m_featuresVector.GetByIndex(featureId, feature); + m2::RectD rect = GetStreetLimitRect(feature, m_offsetMeters); + m_cache[featureId] = rect; + return rect; +} +} // namespace search diff --git a/search/v2/street_vicinity_loader.hpp b/search/v2/street_vicinity_loader.hpp new file mode 100644 index 0000000000..a42fea4bee --- /dev/null +++ b/search/v2/street_vicinity_loader.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "indexer/feature_covering.hpp" +#include "indexer/scale_index.hpp" + +#include "coding/reader.hpp" + +#include "geometry/rect2d.hpp" + +#include "std/unordered_map.hpp" + +class MwmValue; +class FeaturesVector; + +namespace search +{ +class StreetVicinityLoader +{ +public: + StreetVicinityLoader(MwmValue & value, FeaturesVector const & featuresVector, + double offsetMeters); + + template + void ForEachInVicinity(uint32_t featureId, int scale, TFn const & fn) + { + m2::RectD const rect = GetLimitRect(featureId); + if (rect.IsEmptyInterior()) + return; + + scale = min(max(scale, m_scaleRange.first), m_scaleRange.second); + covering::CoveringGetter coveringGetter(rect, covering::ViewportWithLowLevels); + auto const & intervals = coveringGetter.Get(scale); + for (auto const & interval : intervals) + m_index.ForEachInIntervalAndScale(fn, interval.first, interval.second, scale); + } + + inline void ClearCache() { m_cache.clear(); } + +private: + m2::RectD GetLimitRect(uint32_t featureId); + + ScaleIndex m_index; + pair m_scaleRange; + + FeaturesVector const & m_featuresVector; + + double const m_offsetMeters; + + unordered_map m_cache; +}; +} // namespace search