Skip to content

Commit

Permalink
[search] Add a mode to find features by id.
Browse files Browse the repository at this point in the history
  • Loading branch information
mpimenov authored and tatiana-yan committed Feb 16, 2021
1 parent dc68ae3 commit 3a2ca5e
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 5 deletions.
10 changes: 6 additions & 4 deletions qt/place_page_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,14 @@ PlacePageDialog::PlacePageDialog(QWidget * parent, place_page::Info const & info
if (info.IsFeature())
{
grid->addWidget(new QLabel("Feature ID"), row, 0);
grid->addWidget(new QLabel(QString::fromStdString(DebugPrint(info.GetID()))), row++, 1);
auto labelF = new QLabel(QString::fromStdString(DebugPrint(info.GetID())));
labelF->setTextInteractionFlags(Qt::TextSelectableByMouse);
grid->addWidget(labelF, row++, 1);

grid->addWidget(new QLabel("Raw Types"), row, 0);
QLabel * label = new QLabel(QString::fromStdString(DebugPrint(info.GetTypes())));
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
grid->addWidget(label, row++, 1);
QLabel * labelT = new QLabel(QString::fromStdString(DebugPrint(info.GetTypes())));
labelT->setTextInteractionFlags(Qt::TextSelectableByMouse);
grid->addWidget(labelT, row++, 1);
}
for (auto const prop : info.AvailableProperties())
{
Expand Down
215 changes: 215 additions & 0 deletions search/processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@
#include "base/string_utils.hpp"

#include <algorithm>
#include <cctype>
#include <memory>
#include <set>
#include <sstream>
#include <utility>

#include "3party/Alohalytics/src/alohalytics.h"
#include "3party/open-location-code/openlocationcode.h"
Expand Down Expand Up @@ -430,6 +433,212 @@ bool Processor::IsCancelled() const
return ret;
}

void Processor::SearchByFeatureId()
{
// String processing is suboptimal in this method so
// we need a guard against very long strings.
size_t const kMaxFeatureIdStringSize = 1000;
if (m_query.size() > kMaxFeatureIdStringSize)
return;

using Trie = base::MemTrie<storage::CountryId, base::VectorValues<bool>>;
static Trie countriesTrie;
if (countriesTrie.GetNumNodes() == 1)
{
for (auto const & country : m_infoGetter.GetCountries())
countriesTrie.Add(country.m_countryId, true);
}

auto trimLeadingSpaces = [](string & s) {
while (!s.empty() && s.front() == ' ')
s = s.substr(1);
};

auto const eatFid = [&trimLeadingSpaces](string & s, uint32_t & fid) -> bool {
trimLeadingSpaces(s);

if (s.empty())
return false;

size_t i = 0;
while (i < s.size() && isdigit(s[i]))
++i;

auto const prefix = s.substr(0, i);
if (strings::to_uint32(prefix, fid))
{
s = s.substr(prefix.size());
return true;
}
return false;
};

auto const eatMwmName = [&trimLeadingSpaces](string & s, storage::CountryId & mwmName) -> bool {
trimLeadingSpaces(s);

// Greedily eat as much as possible because some country names are prefixes of others.
optional<size_t> lastPos;
for (size_t i = 0; i < s.size(); ++i)
{
// todo(@m) This must be much faster but MemTrie's iterators do not expose nodes.
if (countriesTrie.HasKey(s.substr(0, i)))
lastPos = i;
}
if (!lastPos)
return false;

mwmName = s.substr(0, *lastPos);
s = s.substr(*lastPos);
strings::EatPrefix(s, ".mwm");
return true;
};

auto const eatVersion = [&trimLeadingSpaces](string & s, uint32_t & version) -> bool {
trimLeadingSpaces(s);

if (!s.empty() && s.front() == '0' && (s.size() == 1 || !isdigit(s[1])))
{
version = 0;
s = s.substr(1);
return true;
}

size_t const kVersionLength = 6;
if (s.size() >= kVersionLength && all_of(s.begin(), s.begin() + kVersionLength, ::isdigit) &&
(s.size() == kVersionLength || !isdigit(s[kVersionLength + 1])))
{
VERIFY(strings::to_uint32(s.substr(0, kVersionLength), version), ());
s = s.substr(kVersionLength);
return true;
}

return false;
};

// Create a copy of the query to trim it in-place.
string query(m_query);
strings::Trim(query);

string const kFidPrefix = "fid";
bool hasPrefix = false;

if (strings::EatPrefix(query, kFidPrefix))
{
hasPrefix = true;

strings::Trim(query);
if (strings::EatPrefix(query, "="))
strings::Trim(query);
}

vector<shared_ptr<MwmInfo>> infos;
m_dataSource.GetMwmsInfo(infos);

// Case 0.
if (hasPrefix)
{
string s = query;
uint32_t fid;
if (eatFid(s, fid))
{
// Loop over mwms, sort by distance.

vector<tuple<double, m2::PointD, std::string, std::unique_ptr<FeatureType>>> results;
vector<unique_ptr<FeaturesLoaderGuard>> guards;
for (auto const & info : infos)
{
auto guard = make_unique<FeaturesLoaderGuard>(m_dataSource, MwmSet::MwmId(info));
if (fid >= guard->GetNumFeatures())
continue;
auto ft = guard->GetFeatureByIndex(fid);
if (!ft)
continue;
auto const center = feature::GetCenter(*ft, FeatureType::WORST_GEOMETRY);
double dist = center.SquaredLength(m_viewport.Center());
auto pivot = m_viewport.Center();
if (m_position)
{
auto const distPos = center.SquaredLength(*m_position);
if (dist > distPos)
{
dist = distPos;
pivot = *m_position;
}
}
results.emplace_back(dist, pivot, guard->GetCountryFileName(), move(ft));
guards.push_back(move(guard));
}
sort(results.begin(), results.end());
for (auto const & [dist, pivot, country, ft] : results)
{
auto const center = feature::GetCenter(*ft, FeatureType::WORST_GEOMETRY);
string name;
ft->GetReadableName(name);
m_emitter.AddResultNoChecks(
m_ranker.MakeResult(RankerResult(*ft, center, pivot, name, country),
true /* needAddress */, true /* needHighlighting */));
m_emitter.Emit();
}
}
}

auto const tryEmitting = [this, &infos](storage::CountryId const & mwmName, uint32_t fid) {
for (auto const & info : infos)
{
if (info->GetCountryName() != mwmName)
continue;

auto guard = make_unique<FeaturesLoaderGuard>(m_dataSource, MwmSet::MwmId(info));
if (fid >= guard->GetNumFeatures())
continue;
auto ft = guard->GetFeatureByIndex(fid);
if (!ft)
continue;
auto const center = feature::GetCenter(*ft, FeatureType::WORST_GEOMETRY);
string name;
ft->GetReadableName(name);
m_emitter.AddResultNoChecks(m_ranker.MakeResult(
RankerResult(*ft, center, m2::PointD() /* pivot */, name, guard->GetCountryFileName()),
true /* needAddress */, true /* needHighlighting */));
m_emitter.Emit();
}
};

// Case 1.
if (hasPrefix)
{
string s = query;
bool const parenPref = strings::EatPrefix(s, "(");
bool const parenSuff = strings::EatSuffix(s, ")");
storage::CountryId mwmName;
uint32_t fid;
if (parenPref == parenSuff && eatMwmName(s, mwmName) && strings::EatPrefix(s, ",") &&
eatFid(s, fid))
{
tryEmitting(mwmName, fid);
}
}

// Case 2.
{
string s = query;
storage::CountryId mwmName;
uint32_t version;
uint32_t fid;

bool ok = true;
ok = ok && strings::EatPrefix(s, "{ MwmId [");
ok = ok && eatMwmName(s, mwmName);
ok = ok && strings::EatPrefix(s, ", ");
ok = ok && eatVersion(s, version);
ok = ok && strings::EatPrefix(s, "], ");
ok = ok && eatFid(s, fid);
ok = ok && strings::EatPrefix(s, " }");
if (ok)
tryEmitting(mwmName, fid);
}
}

Locales Processor::GetCategoryLocales() const
{
static int8_t const enLocaleCode = CategoriesHolder::MapLocaleToInteger("en");
Expand Down Expand Up @@ -511,6 +720,7 @@ void Processor::Search(SearchParams const & params)

try
{
SearchDebug();
SearchCoordinates();
SearchPlusCode();
SearchPostcode();
Expand Down Expand Up @@ -552,6 +762,11 @@ void Processor::Search(SearchParams const & params)
SendStatistics(params, viewport, m_emitter.GetResults());
}

void Processor::SearchDebug()
{
SearchByFeatureId();
}

void Processor::SearchCoordinates()
{
buffer_vector<ms::LatLon, 3> results;
Expand Down
12 changes: 12 additions & 0 deletions search/processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class Processor : public base::Cancellable

void Search(SearchParams const & params);

// Tries to parse a custom debugging command from |m_query|.
void SearchDebug();
// Tries to generate a (lat, lon) result from |m_query|.
void SearchCoordinates();
// Tries to parse a plus code from |m_query| and generate a (lat, lon) result.
Expand Down Expand Up @@ -112,6 +114,16 @@ class Processor : public base::Cancellable
bool IsCancelled() const override;

protected:
// Show feature by FeatureId. May try to guess as much as possible after the "fid=" prefix but
// at least supports the formats below.
// 0. fid=123 to search for the feature with index 123, results ordered by distance
// from |m_position| or |m_viewport|, whichever is present and closer.
// 1. fid=MwmName,123 or fid=(MwmName,123) to search for the feature with
// index 123 in the Mwm "MwmName" (for example, "Laos" or "Laos.mwm").
// 2. fid={ MwmId [Laos, 200623], 123 } or just { MwmId [Laos, 200623], 123 } or whatever current
// format of the string returned by FeatureID's DebugPrint is.
void SearchByFeatureId();

Locales GetCategoryLocales() const;

template <typename ToDo>
Expand Down
30 changes: 29 additions & 1 deletion search/search_integration_tests/processor_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -972,8 +972,36 @@ UNIT_CLASS_TEST(ProcessorTest, TestCategorialSearch)
}
}

UNIT_CLASS_TEST(ProcessorTest, TestCoords)
UNIT_CLASS_TEST(ProcessorTest, SearchDebug)
{
string const countryName = "Wonderland";

TestCity debugville(m2::PointD(0, 0), "Debugville", "en", 100 /* rank */);

TestCafe cafe(m2::PointD(0.01, 0), "", "ru");

TestPOI hotel(m2::PointD(0, 0.01), "", "ru");
hotel.SetTypes({{"tourism", "hotel"}});

auto const testWorldId = BuildWorld([&](TestMwmBuilder & builder) {
builder.Add(debugville);
});
auto wonderlandId = BuildCountry(countryName, [&](TestMwmBuilder & builder) {
builder.Add(hotel);
builder.Add(cafe);
});

auto const ruleCity = ExactMatch(testWorldId, debugville);
auto const ruleCafe = ExactMatch(wonderlandId, cafe);
auto const ruleHotel = ExactMatch(wonderlandId, hotel);

TEST(ResultsMatch("fid=0", {ruleCity, ruleCafe}), ());
TEST(ResultsMatch("fid=1 ", {ruleHotel}), ());
}

UNIT_CLASS_TEST(ProcessorTest, SearchCoordinates)
{
// <query, lat, lon>
vector<tuple<string, double, double>> tests = {
{"51.681644 39.183481", 51.681644, 39.183481},

Expand Down

0 comments on commit 3a2ca5e

Please sign in to comment.