[search] Implemented intersection of POI and STREET layers.

This commit is contained in:
Yuri Gorshenin 2015-12-02 12:19:30 +03:00 committed by Sergey Yershov
parent 6e6279c4d3
commit 2adf4dc9df
13 changed files with 266 additions and 28 deletions

View file

@ -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 \

View file

@ -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<storage::CountryDef> countries;
countries.emplace_back(map.GetCountryName(), viewport);
@ -69,6 +72,9 @@ UNIT_TEST(SearchQueryV2_Smoke)
auto const quantumTeleport2 =
make_shared<TestPOI>(m2::PointD(10, 10), "Quantum teleport 2", "en");
auto const quantumCafe = make_shared<TestPOI>(m2::PointD(-0.0002, -0.0002), "Quantum cafe", "en");
auto const faynmannStreet = make_shared<TestStreet>(
vector<m2::PointD>{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<shared_ptr<MatchingRule>> rules = {
make_shared<ExactMatch>(regResult.first, quantumTeleport2)};
TEST(MatchResults(engine, rules, request.Results()), ());
}
}

View file

@ -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<m2::PointD> 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

View file

@ -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<m2::PointD> const & points, string const & name, string const & lang);
// TestFeature overrides:
void Serialize(FeatureBuilder1 & fb) const override;
string ToString() const override;
private:
vector<m2::PointD> m_points;
};
string DebugPrint(TestFeature const & feature);
} // namespace tests_support
} // namespace search

View file

@ -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();

View file

@ -17,7 +17,7 @@ struct FeaturesLayer
void Clear();
vector<uint32_t> m_features;
vector<uint32_t> m_sortedFeatures;
size_t m_startToken;
size_t m_endToken;
SearchModel::SearchType m_type;

View file

@ -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

View file

@ -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 <typename TFn>
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<TFn>(fn));
else if (child.m_type == SearchModel::SEARCH_TYPE_BUILDING)
MatchBuildingsWithStreets(child, parent, forward<TFn>(fn));
return;
}
// TODO (y@): match POI with streets separately.
vector<m2::PointD> 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<m2::RectD> 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 <typename TFn>
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 <typename TFn>
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

View file

@ -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<FeaturesLayer *> 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);
}

View file

@ -7,6 +7,7 @@
#include "std/vector.hpp"
class FeaturesVector;
class MwmValue;
namespace search
{
@ -20,7 +21,7 @@ public:
using TAdjList = vector<uint32_t>;
using TLayerGraph = unordered_map<uint32_t, TAdjList>;
FeaturesLayerPathFinder(FeaturesVector const & featuresVector);
FeaturesLayerPathFinder(MwmValue & value, FeaturesVector const & featuresVector);
template <typename TFn>
void ForEachReachableVertex(vector<FeaturesLayer *> 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);

View file

@ -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<FeatureID> & 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<SearchModel::SearchType>(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()

View file

@ -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

View file

@ -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 <typename TFn>
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<ModelReaderPtr> m_index;
pair<int, int> m_scaleRange;
FeaturesVector const & m_featuresVector;
double const m_offsetMeters;
unordered_map<uint32_t, m2::RectD> m_cache;
};
} // namespace search