Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

165
libs/indexer/CMakeLists.txt Normal file
View file

@ -0,0 +1,165 @@
project(indexer)
set(SRC
altitude_loader.cpp
altitude_loader.hpp
brands_holder.cpp
brands_holder.hpp
caching_rank_table_loader.cpp
caching_rank_table_loader.hpp
categories_holder.cpp
categories_holder.hpp
categories_holder_loader.cpp
categories_index.cpp
categories_index.hpp
cell_coverer.hpp
cell_id.hpp
cell_value_pair.hpp
centers_table.cpp
centers_table.hpp
cities_boundaries_serdes.hpp
city_boundary.hpp
classificator.cpp
classificator.hpp
classificator_loader.cpp
classificator_loader.hpp
complex/serdes.cpp
complex/serdes.hpp
complex/serdes_utils.hpp
complex/tree_node.hpp
cuisines.cpp
cuisines.hpp
custom_keyvalue.cpp
custom_keyvalue.hpp
data_header.cpp
data_header.hpp
data_source.cpp
data_source.hpp
data_source_helpers.cpp
data_source_helpers.hpp
displacement_manager.hpp
drawing_rule_def.cpp
drawing_rule_def.hpp
drawing_rules.cpp
drawing_rules.hpp
drules_include.hpp
drules_selector.cpp
drules_selector.hpp
drules_selector_parser.cpp
drules_selector_parser.hpp
drules_struct.pb.cc
drules_struct.pb.h
edit_journal.cpp
edit_journal.hpp
editable_map_object.cpp
editable_map_object.hpp
fake_feature_ids.cpp
fake_feature_ids.hpp
feature.cpp
feature.hpp
feature_algo.cpp
feature_algo.hpp
feature_altitude.hpp
feature_charge_sockets.cpp
feature_charge_sockets.hpp
feature_covering.cpp
feature_covering.hpp
feature_data.cpp
feature_data.hpp
feature_decl.cpp
feature_decl.hpp
feature_impl.cpp
feature_impl.hpp
feature_meta.cpp
feature_meta.hpp
feature_processor.hpp
feature_source.cpp
feature_source.hpp
feature_to_osm.cpp
feature_to_osm.hpp
feature_utils.cpp
feature_utils.hpp
feature_visibility.cpp
feature_visibility.hpp
features_offsets_table.cpp
features_offsets_table.hpp
features_vector.cpp
features_vector.hpp
ftraits.hpp
ftypes_matcher.cpp
ftypes_matcher.hpp
house_to_street_iface.hpp
index_builder.cpp
index_builder.hpp
interval_index.hpp
interval_index_builder.hpp
isolines_info.cpp
isolines_info.hpp
map_object.cpp
map_object.hpp
map_style.cpp
map_style.hpp
map_style_reader.cpp
map_style_reader.hpp
metadata_serdes.cpp
metadata_serdes.hpp
mwm_set.cpp
mwm_set.hpp
postcodes_matcher.cpp # it's in indexer EditableMapObject depends on postcodes_matcher
postcodes_matcher.hpp # it's in indexer EditableMapObject depends on postcodes_matcher
rank_table.cpp
rank_table.hpp
route_relation.hpp
road_shields_parser.cpp
road_shields_parser.hpp
scale_index.hpp
scale_index_builder.hpp
scales.cpp
scales.hpp
scales_patch.hpp
search_delimiters.cpp # it's in indexer because of CategoriesHolder dependency.
search_delimiters.hpp # it's in indexer because of CategoriesHolder dependency.
search_string_utils.cpp # it's in indexer because of CategoriesHolder dependency.
search_string_utils.hpp # it's in indexer because of CategoriesHolder dependency.
shared_load_info.cpp
shared_load_info.hpp
string_set.hpp
string_slice.cpp
string_slice.hpp
succinct_trie_builder.hpp
succinct_trie_reader.hpp
transliteration_loader.cpp
transliteration_loader.hpp
tree_structure.hpp
trie.hpp
trie_builder.hpp
trie_reader.hpp
types_mapping.cpp
types_mapping.hpp
unique_index.hpp
utils.cpp
utils.hpp
validate_and_format_contacts.cpp
validate_and_format_contacts.hpp
)
set(OTHER_FILES drules_struct.proto)
# Disable warnings.
set_source_files_properties(drules_struct.pb.cc PROPERTIES COMPILE_FLAGS
"$<$<CXX_COMPILER_ID:AppleClang,Clang>:-Wno-shorten-64-to-32> $<$<CXX_COMPILER_ID:GNU>:-Wno-deprecated-declarations>"
)
file(COPY ${OTHER_FILES} DESTINATION ${CMAKE_BINARY_DIR})
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
search # search::DummyRankTable in CachingRankTableLoader
platform
geometry
protobuf
coding
)
omim_add_test_subdirectory(indexer_tests)

View file

@ -0,0 +1,111 @@
#include "indexer/altitude_loader.hpp"
#include "indexer/mwm_set.hpp"
#include "coding/reader.hpp"
#include "coding/succinct_mapper.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include "base/thread.hpp"
#include "defines.hpp"
#include <algorithm>
#include "3party/succinct/mapper.hpp"
namespace feature
{
namespace
{
template <class TCont>
void LoadAndMap(size_t dataSize, ReaderSource<FilesContainerR::TReader> & src, TCont & cont,
std::unique_ptr<CopiedMemoryRegion> & region)
{
std::vector<uint8_t> data(dataSize);
src.Read(data.data(), data.size());
region = std::make_unique<CopiedMemoryRegion>(std::move(data));
coding::MapVisitor visitor(region->ImmutableData());
cont.map(visitor);
}
} // namespace
AltitudeLoaderBase::AltitudeLoaderBase(MwmValue const & mwmValue)
{
m_countryFileName = mwmValue.GetCountryFileName();
if (!mwmValue.m_cont.IsExist(ALTITUDES_FILE_TAG))
return;
try
{
m_reader = std::make_unique<FilesContainerR::TReader>(mwmValue.m_cont.GetReader(ALTITUDES_FILE_TAG));
ReaderSource<FilesContainerR::TReader> src(*m_reader);
m_header.Deserialize(src);
LoadAndMap(m_header.GetAltitudeAvailabilitySize(), src, m_altitudeAvailability, m_altitudeAvailabilityRegion);
LoadAndMap(m_header.GetFeatureTableSize(), src, m_featureTable, m_featureTableRegion);
}
catch (Reader::OpenException const & e)
{
m_header.Reset();
LOG(LERROR, ("File", m_countryFileName, "Error while reading", ALTITUDES_FILE_TAG, "section.", e.Msg()));
}
}
bool AltitudeLoaderBase::HasAltitudes() const
{
return m_reader != nullptr && m_header.m_minAltitude != geometry::kInvalidAltitude;
}
geometry::Altitudes AltitudeLoaderBase::GetAltitudes(uint32_t featureId, size_t pointCount)
{
if (!HasAltitudes())
{
// There's no altitude section in mwm.
return geometry::Altitudes(pointCount, geometry::kDefaultAltitudeMeters);
}
if (!m_altitudeAvailability[featureId])
return geometry::Altitudes(pointCount, m_header.m_minAltitude);
uint64_t const r = m_altitudeAvailability.rank(featureId);
CHECK_LESS(r, m_altitudeAvailability.size(), ("Feature Id", featureId, "of", m_countryFileName));
uint64_t const offset = m_featureTable.select(r);
CHECK_LESS_OR_EQUAL(offset, m_featureTable.size(), ("Feature Id", featureId, "of", m_countryFileName));
uint64_t const altitudeInfoOffsetInSection = m_header.m_altitudesOffset + offset;
CHECK_LESS(altitudeInfoOffsetInSection, m_reader->Size(), ("Feature Id", featureId, "of", m_countryFileName));
try
{
Altitudes altitudes;
ReaderSource<FilesContainerR::TReader> src(*m_reader);
src.Skip(altitudeInfoOffsetInSection);
altitudes.Deserialize(m_header.m_minAltitude, pointCount, m_countryFileName, featureId, src);
// It's filtered on generator stage.
ASSERT(none_of(altitudes.m_altitudes.begin(), altitudes.m_altitudes.end(),
[](geometry::Altitude a) { return a == geometry::kInvalidAltitude; }),
(featureId, m_countryFileName));
return std::move(altitudes.m_altitudes);
}
catch (Reader::OpenException const & e)
{
LOG(LERROR, ("Feature Id", featureId, "of", m_countryFileName, ". Error while getting altitude data:", e.Msg()));
return geometry::Altitudes(pointCount, m_header.m_minAltitude);
}
}
geometry::Altitudes const & AltitudeLoaderCached::GetAltitudes(uint32_t featureId, size_t pointCount)
{
auto const it = m_cache.find(featureId);
if (it != m_cache.end())
return it->second;
return m_cache.emplace(featureId, AltitudeLoaderBase::GetAltitudes(featureId, pointCount)).first->second;
}
} // namespace feature

View file

@ -0,0 +1,56 @@
#pragma once
#include "indexer/feature_altitude.hpp"
#include "coding/memory_region.hpp"
#include "geometry/point_with_altitude.hpp"
#include <memory>
#include <string>
#include <vector>
#include "3party/succinct/elias_fano.hpp"
#include "3party/succinct/rs_bit_vector.hpp"
class MwmValue;
namespace feature
{
class AltitudeLoaderBase
{
public:
explicit AltitudeLoaderBase(MwmValue const & mwmValue);
/// \returns altitude of feature with |featureId|. All items of the returned vector are valid
/// or the returned vector is empty.
geometry::Altitudes GetAltitudes(uint32_t featureId, size_t pointCount);
bool HasAltitudes() const;
private:
std::unique_ptr<CopiedMemoryRegion> m_altitudeAvailabilityRegion;
std::unique_ptr<CopiedMemoryRegion> m_featureTableRegion;
succinct::rs_bit_vector m_altitudeAvailability;
succinct::elias_fano m_featureTable;
std::unique_ptr<FilesContainerR::TReader> m_reader;
AltitudeHeader m_header;
std::string m_countryFileName;
};
class AltitudeLoaderCached : public AltitudeLoaderBase
{
public:
explicit AltitudeLoaderCached(MwmValue const & mwmValue) : AltitudeLoaderBase(mwmValue) {}
/// \returns altitude of feature with |featureId|. All items of the returned vector are valid
/// or the returned vector is empty.
geometry::Altitudes const & GetAltitudes(uint32_t featureId, size_t pointCount);
void ClearCache() { m_cache.clear(); }
private:
std::map<uint32_t, geometry::Altitudes> m_cache;
};
} // namespace feature

View file

@ -0,0 +1,139 @@
#include "indexer/brands_holder.hpp"
#include "platform/platform.hpp"
#include "coding/reader.hpp"
#include "coding/reader_streambuf.hpp"
#include "base/string_utils.hpp"
#include <sstream>
#include "defines.hpp"
namespace
{
enum class State
{
ParseBrand,
ParseLanguages
};
} // namespace
namespace indexer
{
bool BrandsHolder::Brand::Name::operator==(BrandsHolder::Brand::Name const & rhs) const
{
return m_locale == rhs.m_locale && m_name == rhs.m_name;
}
bool BrandsHolder::Brand::Name::operator<(BrandsHolder::Brand::Name const & rhs) const
{
if (m_locale != rhs.m_locale)
return m_locale < rhs.m_locale;
return m_name < rhs.m_name;
}
BrandsHolder::BrandsHolder(std::unique_ptr<Reader> && reader)
{
ReaderStreamBuf buffer(std::move(reader));
std::istream s(&buffer);
LoadFromStream(s);
}
void BrandsHolder::ForEachNameByKeyAndLang(std::string const & key, std::string const & lang,
std::function<void(std::string const &)> const & toDo) const
{
int8_t const locale = StringUtf8Multilang::GetLangIndex(lang);
ForEachNameByKey(key, [&](Brand::Name const & name)
{
if (name.m_locale == locale)
toDo(name.m_name);
});
}
void BrandsHolder::LoadFromStream(std::istream & s)
{
m_keys.clear();
m_keyToName.clear();
State state = State::ParseBrand;
std::string line;
Brand brand;
std::string key;
char const kKeyPrefix[] = "brand.";
size_t const kKeyPrefixLength = std::strlen(kKeyPrefix);
int lineNumber = 0;
while (s.good())
{
++lineNumber;
std::getline(s, line);
strings::Trim(line);
// Allow comments starting with '#' character.
if (!line.empty() && line[0] == '#')
continue;
strings::SimpleTokenizer iter(line, state == State::ParseBrand ? "|" : ":|");
if (state == State::ParseBrand)
{
if (!iter)
continue;
AddBrand(brand, key);
CHECK_GREATER((*iter).size(), kKeyPrefixLength, (lineNumber, *iter));
ASSERT_EQUAL((*iter).find(kKeyPrefix), 0, (lineNumber, *iter));
key = (*iter).substr(kKeyPrefixLength);
CHECK(!++iter, (lineNumber));
state = State::ParseLanguages;
}
else if (state == State::ParseLanguages)
{
if (!iter)
{
state = State::ParseBrand;
continue;
}
int8_t const langCode = StringUtf8Multilang::GetLangIndex(*iter);
CHECK_NOT_EQUAL(langCode, StringUtf8Multilang::kUnsupportedLanguageCode, ());
while (++iter)
brand.m_synonyms.emplace_back(*iter, langCode);
}
}
// Add last brand.
AddBrand(brand, key);
}
void BrandsHolder::AddBrand(Brand & brand, std::string const & key)
{
if (key.empty())
return;
CHECK(!brand.m_synonyms.empty(), ());
std::shared_ptr<Brand> p(new Brand());
p->Swap(brand);
m_keyToName.emplace(key, p);
CHECK(m_keys.insert(key).second, ("Key duplicate", key));
}
std::string DebugPrint(BrandsHolder::Brand::Name const & name)
{
std::ostringstream out;
out << "BrandName[" << StringUtf8Multilang::GetLangByCode(name.m_locale) << ", " << name.m_name << "]";
return out.str();
}
BrandsHolder const & GetDefaultBrands()
{
static BrandsHolder const instance(GetPlatform().GetReader(SEARCH_BRAND_CATEGORIES_FILE_NAME));
return instance;
}
} // namespace indexer

View file

@ -0,0 +1,93 @@
#pragma once
#include "coding/string_utf8_multilang.hpp"
#include <deque>
#include <functional>
#include <istream>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
class Reader;
namespace indexer
{
class BrandsHolder
{
public:
struct Brand
{
struct Name
{
Name(std::string_view name, int8_t locale) : m_name(name), m_locale(locale) {}
bool operator==(Name const & rhs) const;
bool operator<(Name const & rhs) const;
std::string m_name;
// Same with StringUtf8Multilang locales.
int8_t m_locale;
};
void Swap(Brand & r) { m_synonyms.swap(r.m_synonyms); }
std::deque<Name> m_synonyms;
};
explicit BrandsHolder(std::unique_ptr<Reader> && reader);
std::set<std::string> const & GetKeys() const { return m_keys; }
template <class FnT>
void ForEachNameByKey(std::string_view key, FnT && fn) const
{
auto const it = m_keyToName.find(key);
if (it == m_keyToName.end())
return;
for (auto const & name : it->second->m_synonyms)
fn(name);
}
void ForEachNameByKeyAndLang(std::string const & key, std::string const & lang,
std::function<void(std::string const &)> const & toDo) const;
private:
void LoadFromStream(std::istream & s);
void AddBrand(Brand & brand, std::string const & key);
struct StringViewHash
{
using hash_type = std::hash<std::string_view>;
using is_transparent = void;
// std::size_t operator()(const char* str) const { return hash_type{}(str); }
size_t operator()(std::string_view str) const { return hash_type{}(str); }
size_t operator()(std::string const & str) const { return hash_type{}(str); }
};
std::unordered_map<std::string, std::shared_ptr<Brand>, StringViewHash, std::equal_to<>> m_keyToName;
std::set<std::string> m_keys;
};
std::string DebugPrint(BrandsHolder::Brand::Name const & name);
BrandsHolder const & GetDefaultBrands();
template <class FnT>
void ForEachLocalizedBrands(std::string_view brand, FnT && fn)
{
bool processed = false;
/// Localized brands are not working as expected now because we store raw names from OSM, not brand IDs.
GetDefaultBrands().ForEachNameByKey(brand, [&fn, &processed](auto const & name)
{
fn(name);
processed = true;
});
if (!processed)
fn(BrandsHolder::Brand::Name(brand, StringUtf8Multilang::kDefaultCode));
}
} // namespace indexer

View file

@ -0,0 +1,45 @@
#include "indexer/caching_rank_table_loader.hpp"
#include "search/dummy_rank_table.hpp"
#include "indexer/data_source.hpp"
CachingRankTableLoader::CachingRankTableLoader(DataSource const & dataSource, std::string const & sectionName)
: m_dataSource(dataSource)
, m_sectionName(sectionName)
{}
uint8_t CachingRankTableLoader::Get(FeatureID const & featureId) const
{
auto const handle = m_dataSource.GetMwmHandleById(featureId.m_mwmId);
if (!handle.IsAlive())
return search::RankTable::kNoRank;
auto it = m_deserializers.find(featureId.m_mwmId);
if (it == m_deserializers.end())
{
auto rankTable = search::RankTable::Load(handle.GetValue()->m_cont, m_sectionName);
if (!rankTable)
rankTable = std::make_unique<search::DummyRankTable>();
auto const result = m_deserializers.emplace(featureId.m_mwmId, std::move(rankTable));
it = result.first;
}
return it->second->Get(featureId.m_index);
}
void CachingRankTableLoader::OnMwmDeregistered(platform::LocalCountryFile const & localFile)
{
for (auto it = m_deserializers.begin(); it != m_deserializers.end(); ++it)
{
if (it->first.IsDeregistered(localFile))
{
m_deserializers.erase(it);
return;
}
}
}

View file

@ -0,0 +1,32 @@
#pragma once
#include "indexer/feature_decl.hpp"
#include "indexer/mwm_set.hpp"
#include "indexer/rank_table.hpp"
#include "base/macros.hpp"
#include <map>
#include <memory>
#include <string>
class DataSource;
struct FeatureID;
// *NOTE* This class IS NOT thread-safe.
class CachingRankTableLoader
{
public:
CachingRankTableLoader(DataSource const & dataSource, std::string const & sectionName);
/// @return 0 if there is no rank for feature.
uint8_t Get(FeatureID const & featureId) const;
void OnMwmDeregistered(platform::LocalCountryFile const & localFile);
private:
DataSource const & m_dataSource;
std::string const m_sectionName;
mutable std::map<MwmSet::MwmId, std::unique_ptr<search::RankTable>> m_deserializers;
DISALLOW_COPY(CachingRankTableLoader);
};

View file

@ -0,0 +1,333 @@
#include "indexer/categories_holder.hpp"
#include "indexer/classificator.hpp"
#include "indexer/search_string_utils.hpp"
#include "coding/reader.hpp"
#include "coding/reader_streambuf.hpp"
#include "base/logging.hpp"
namespace
{
enum ParseState
{
EParseTypes,
EParseLanguages
};
void AddGroupTranslationsToSynonyms(std::vector<std::string> const & groups,
CategoriesHolder::GroupTranslations const & translations,
std::vector<CategoriesHolder::Category::Name> & synonyms)
{
for (std::string const & group : groups)
{
auto it = translations.find(group);
if (it == translations.end())
continue;
for (auto & synonym : it->second)
synonyms.push_back(synonym);
}
}
bool ParseEmoji(CategoriesHolder::Category::Name & name)
{
using namespace strings;
auto const code = name.m_name;
int c;
if (!to_int(name.m_name.c_str() + 2, c, 16))
{
LOG(LWARNING, ("Bad emoji code:", code));
return false;
}
name.m_name = ToUtf8(UniString(1 /* numChars */, static_cast<UniChar>(c)));
if (IsASCIIString(ToUtf8(search::NormalizeAndSimplifyString(name.m_name))))
{
LOG(LWARNING, ("Bad emoji code:", code));
return false;
}
return true;
}
void FillPrefixLengthToSuggest(CategoriesHolder::Category::Name & name)
{
if (std::isdigit(name.m_name.front()) && name.m_name.front() != '0')
{
name.m_prefixLengthToSuggest = name.m_name[0] - '0';
name.m_name = name.m_name.substr(1);
}
else
{
name.m_prefixLengthToSuggest = CategoriesHolder::Category::kEmptyPrefixLength;
}
}
void ProcessName(CategoriesHolder::Category::Name name, std::vector<std::string> const & groups,
std::vector<uint32_t> const & types, CategoriesHolder::GroupTranslations & translations,
std::vector<CategoriesHolder::Category::Name> & synonyms)
{
if (name.m_name.empty())
{
LOG(LWARNING, ("Incorrect name for category:", groups));
return;
}
FillPrefixLengthToSuggest(name);
if (name.m_name.starts_with("U+") && !ParseEmoji(name))
return;
if (groups.size() == 1 && types.empty())
{
// Not a translation, but a category group definition.
translations[groups[0]].push_back(name);
}
else
{
synonyms.push_back(name);
}
}
void ProcessCategory(std::string_view line, std::vector<std::string> & groups, std::vector<uint32_t> & types)
{
// Check if category is a group reference.
if (line[0] == '@')
{
CHECK((groups.empty() || !types.empty()), ("Two groups in a group definition, line:", line));
groups.push_back(std::string(line));
return;
}
// Get classificator type.
uint32_t const type = classif().GetTypeByPathSafe(strings::Tokenize(line, "-"));
if (type == Classificator::INVALID_TYPE)
{
LOG(LWARNING, ("Invalid type when parsing the line:", line));
return;
}
types.push_back(type);
}
} // namespace
// static
int8_t constexpr CategoriesHolder::kEnglishCode;
int8_t constexpr CategoriesHolder::kUnsupportedLocaleCode;
CategoriesHolder::CategoriesHolder(std::unique_ptr<Reader> && reader)
{
ReaderStreamBuf buffer(std::move(reader));
std::istream s(&buffer);
LoadFromStream(s);
#if defined(DEBUG)
for (auto const & entry : kLocaleMapping)
ASSERT_LESS_OR_EQUAL(uint8_t(entry.m_code), kLocaleMapping.size(), ());
#endif
}
void CategoriesHolder::AddCategory(Category & cat, std::vector<uint32_t> & types)
{
if (!cat.m_synonyms.empty() && !types.empty())
{
std::shared_ptr<Category> p(new Category());
p->Swap(cat);
for (uint32_t const t : types)
m_type2cat.insert(std::make_pair(t, p));
for (auto const & synonym : p->m_synonyms)
{
auto const locale = synonym.m_locale;
ASSERT_NOT_EQUAL(locale, kUnsupportedLocaleCode, ());
auto const localePrefix = strings::UniString(1, static_cast<strings::UniChar>(locale));
search::ForEachNormalizedToken(synonym.m_name, [&](strings::UniString const & token)
{
if (ValidKeyToken(token))
for (uint32_t const t : types)
m_name2type.Add(localePrefix + token, t);
});
}
}
cat.m_synonyms.clear();
types.clear();
}
bool CategoriesHolder::ValidKeyToken(strings::UniString const & s)
{
if (s.size() > 2)
return true;
/// @todo We need to have global stop words array for the most used languages.
for (char const * token : {"a", "z", "s", "d", "di", "de", "le", "ra", "ao"})
if (s.IsEqualAscii(token))
return false;
return true;
}
void CategoriesHolder::LoadFromStream(std::istream & s)
{
m_type2cat.clear();
m_name2type.Clear();
m_groupTranslations.clear();
ParseState state = EParseTypes;
std::string line;
Category cat;
std::vector<uint32_t> types;
std::vector<std::string> currentGroups;
int lineNumber = 0;
while (s.good())
{
++lineNumber;
getline(s, line);
strings::Trim(line);
// Allow for comments starting with '#' character.
if (!line.empty() && line[0] == '#')
continue;
strings::SimpleTokenizer iter(line, state == EParseTypes ? "|" : ":|");
if (state == EParseTypes)
{
AddCategory(cat, types);
currentGroups.clear();
while (iter)
{
ProcessCategory(*iter, currentGroups, types);
++iter;
}
if (!types.empty() || currentGroups.size() == 1)
{
state = EParseLanguages;
AddGroupTranslationsToSynonyms(currentGroups, m_groupTranslations, cat.m_synonyms);
}
}
else if (state == EParseLanguages)
{
if (!iter)
{
state = EParseTypes;
continue;
}
int8_t const langCode = MapLocaleToInteger(*iter);
CHECK(langCode != kUnsupportedLocaleCode, ("Invalid language code:", *iter, "at line:", lineNumber));
while (++iter)
{
Category::Name name;
name.m_locale = langCode;
name.m_name = *iter;
ProcessName(name, currentGroups, types, m_groupTranslations, cat.m_synonyms);
}
}
}
// Add the last category.
AddCategory(cat, types);
}
bool CategoriesHolder::GetNameByType(uint32_t type, int8_t locale, std::string & name) const
{
auto const range = m_type2cat.equal_range(type);
std::string enName;
for (auto it = range.first; it != range.second; ++it)
{
Category const & cat = *it->second;
for (auto const & synonym : cat.m_synonyms)
{
if (synonym.m_locale == locale)
{
name = synonym.m_name;
return true;
}
else if (enName.empty() && (synonym.m_locale == kEnglishCode))
{
enName = synonym.m_name;
}
}
}
if (!enName.empty())
{
name = enName;
return true;
}
return false;
}
std::string CategoriesHolder::GetReadableFeatureType(uint32_t type, int8_t locale) const
{
ASSERT_NOT_EQUAL(type, 0, ());
uint8_t level = ftype::GetLevel(type);
ASSERT_GREATER(level, 0, ());
uint32_t originalType = type;
std::string name;
while (true)
{
if (GetNameByType(type, locale, name))
return name;
if (--level == 0)
break;
ftype::TruncValue(type, level);
}
return classif().GetReadableObjectName(originalType);
}
bool CategoriesHolder::IsTypeExist(uint32_t type) const
{
auto const range = m_type2cat.equal_range(type);
return range.first != range.second;
}
// static
int8_t CategoriesHolder::MapLocaleToInteger(std::string_view const locale)
{
ASSERT(!kLocaleMapping.empty(), ());
ASSERT_EQUAL(kLocaleMapping[0].m_name, std::string_view("en"), ());
ASSERT_EQUAL(kLocaleMapping[0].m_code, kEnglishCode, ());
for (auto it = kLocaleMapping.crbegin(); it != kLocaleMapping.crend(); ++it)
if (locale.find(it->m_name) == 0)
return it->m_code;
// Special cases for different Chinese variations
if (locale.find("zh") == 0)
{
std::string lower(locale);
strings::AsciiToLower(lower);
for (char const * s : {"hant", "tw", "hk", "mo"})
if (lower.find(s) != std::string::npos)
return kTraditionalChineseCode;
// Simplified Chinese by default for all other cases.
return kSimplifiedChineseCode;
}
return kUnsupportedLocaleCode;
}
// static
std::string CategoriesHolder::MapIntegerToLocale(int8_t code)
{
if (code <= 0 || static_cast<size_t>(code) > kLocaleMapping.size())
return {};
return kLocaleMapping[code - 1].m_name;
}

View file

@ -0,0 +1,208 @@
#pragma once
#include "base/mem_trie.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <array>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
class Reader;
class CategoriesHolder
{
public:
struct Category
{
static constexpr uint8_t kEmptyPrefixLength = 10;
struct Name
{
std::string m_name;
/// This language/locale code is completely different from our built-in langs in
/// string_utf8_multilang.cpp and is only used for mapping user's input language to our values
/// in categories.txt file
int8_t m_locale;
uint8_t m_prefixLengthToSuggest;
};
std::vector<Name> m_synonyms;
void Swap(Category & r) { m_synonyms.swap(r.m_synonyms); }
};
struct Mapping
{
char const * m_name;
int8_t m_code;
};
using GroupTranslations = std::unordered_map<std::string, std::vector<Category::Name>>;
private:
using Type2CategoryCont = std::multimap<uint32_t, std::shared_ptr<Category>>;
using Trie = base::MemTrie<strings::UniString, base::VectorValues<uint32_t>>;
Type2CategoryCont m_type2cat;
// Maps locale and category token to the list of corresponding types.
// Locale is treated as a special symbol prepended to the token.
Trie m_name2type;
GroupTranslations m_groupTranslations;
public:
// Should match codes in the array below.
static int8_t constexpr kEnglishCode = 1;
static int8_t constexpr kUnsupportedLocaleCode = -1;
static int8_t constexpr kSimplifiedChineseCode = 44;
static int8_t constexpr kTraditionalChineseCode = 45;
// *NOTE* These constants should be updated when adding new
// translation to categories.txt. When editing, keep in mind to check
// CategoriesHolder::MapLocaleToInteger() and
// CategoriesHolder::MapIntegerToLocale() as their implementations
// strongly depend on the contents of the variable.
// TODO: Refactor for more flexibility and to avoid breaking rules in two methods mentioned above.
static std::array<CategoriesHolder::Mapping, 45> constexpr kLocaleMapping = {{
{"en", kEnglishCode},
{"en-AU", 2},
{"en-GB", 3},
{"en-US", 4},
{"ar", 5},
{"be", 6},
{"bg", 7},
{"ca", 8},
{"cs", 9},
{"da", 10},
{"de", 11},
{"el", 12},
{"es", 13},
{"es-MX", 14},
{"et", 15},
{"eu", 16},
{"fa", 17},
{"fi", 18},
{"fr", 19},
{"he", 20},
{"hi", 21},
{"hu", 22},
{"id", 23},
{"it", 24},
{"ja", 25},
{"ko", 26},
{"lv", 27},
{"mr", 28},
{"nb", 29},
{"nl", 30},
{"pl", 31},
{"pt", 32},
{"pt-BR", 33},
{"ro", 34},
{"ru", 35},
{"sk", 36},
{"sr", 37},
{"sv", 38},
{"sw", 39},
{"th", 40},
{"tr", 41},
{"uk", 42},
{"vi", 43},
{"zh-Hans", kSimplifiedChineseCode},
{"zh-Hant", kTraditionalChineseCode},
}};
explicit CategoriesHolder(std::unique_ptr<Reader> && reader);
template <class ToDo>
void ForEachCategory(ToDo && toDo) const
{
for (auto const & p : m_type2cat)
toDo(*p.second);
}
template <class ToDo>
void ForEachTypeAndCategory(ToDo && toDo) const
{
for (auto const & it : m_type2cat)
toDo(it.first, *it.second);
}
template <class ToDo>
void ForEachName(ToDo && toDo) const
{
for (auto const & p : m_type2cat)
for (auto const & synonym : p.second->m_synonyms)
toDo(synonym);
}
template <class ToDo>
void ForEachNameAndType(ToDo && toDo) const
{
for (auto const & p : m_type2cat)
for (auto const & synonym : p.second->m_synonyms)
toDo(synonym, p.first);
}
template <class ToDo>
void ForEachNameByType(uint32_t type, ToDo && toDo) const
{
auto it = m_type2cat.find(type);
if (it == m_type2cat.end())
return;
for (auto const & name : it->second->m_synonyms)
toDo(name);
}
template <class ToDo>
void ForEachTypeByName(int8_t locale, strings::UniString const & name, ToDo && toDo) const
{
auto const localePrefix = strings::UniString(1, static_cast<strings::UniChar>(locale));
m_name2type.ForEachInNode(localePrefix + name, toDo);
}
GroupTranslations const & GetGroupTranslations() const { return m_groupTranslations; }
/// Search name for type with preffered locale language.
/// If no name for this language, return en name.
/// @return false if no categories for type.
bool GetNameByType(uint32_t type, int8_t locale, std::string & name) const;
/// @returns raw classificator type if it's not localized in categories.txt.
std::string GetReadableFeatureType(uint32_t type, int8_t locale) const;
// Exposes the tries that map category tokens to types.
Trie const & GetNameToTypesTrie() const { return m_name2type; }
bool IsTypeExist(uint32_t type) const;
void Swap(CategoriesHolder & r)
{
m_type2cat.swap(r.m_type2cat);
std::swap(m_name2type, r.m_name2type);
}
// Converts any language |locale| from UI to the corresponding
// internal integer code.
static int8_t MapLocaleToInteger(std::string_view const locale);
// Returns corresponding string representation for an internal
// integer |code|. Returns an empty string in case of invalid
// |code|.
static std::string MapIntegerToLocale(int8_t code);
private:
void LoadFromStream(std::istream & s);
void AddCategory(Category & cat, std::vector<uint32_t> & types);
static bool ValidKeyToken(strings::UniString const & s);
};
inline void swap(CategoriesHolder & a, CategoriesHolder & b)
{
return a.Swap(b);
}
// Defined in categories_holder_loader.cpp.
CategoriesHolder const & GetDefaultCategories();
CategoriesHolder const & GetDefaultCuisineCategories();

View file

@ -0,0 +1,17 @@
#include "categories_holder.hpp"
#include "platform/platform.hpp"
#include "defines.hpp"
CategoriesHolder const & GetDefaultCategories()
{
static CategoriesHolder const instance(GetPlatform().GetReader(SEARCH_CATEGORIES_FILE_NAME));
return instance;
}
CategoriesHolder const & GetDefaultCuisineCategories()
{
static CategoriesHolder const instance(GetPlatform().GetReader(SEARCH_CUISINE_CATEGORIES_FILE_NAME));
return instance;
}

View file

@ -0,0 +1,123 @@
#include "categories_index.hpp"
#include "search_string_utils.hpp"
#include "base/assert.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <set>
namespace indexer
{
using namespace std;
namespace
{
void AddAllNonemptySubstrings(base::MemTrie<std::string, base::VectorValues<uint32_t>> & trie, std::string const & s,
uint32_t value)
{
ASSERT(!s.empty(), ());
for (size_t i = 0; i < s.length(); ++i)
{
std::string t;
for (size_t j = i; j < s.length(); ++j)
{
t.push_back(s[j]);
trie.Add(t, value);
}
}
}
template <typename TF>
void ForEachToken(std::string const & s, TF && fn)
{
search::ForEachNormalizedToken(s, [&fn](strings::UniString const & token) { fn(strings::ToUtf8(token)); });
}
void TokenizeAndAddAllSubstrings(base::MemTrie<std::string, base::VectorValues<uint32_t>> & trie, std::string const & s,
uint32_t value)
{
auto fn = [&](std::string const & token) { AddAllNonemptySubstrings(trie, token, value); };
ForEachToken(s, fn);
}
} // namespace
void CategoriesIndex::AddCategoryByTypeAndLang(uint32_t type, int8_t lang)
{
ASSERT(lang >= 1 && static_cast<size_t>(lang) <= CategoriesHolder::kLocaleMapping.size(),
("Invalid lang code:", lang));
m_catHolder->ForEachNameByType(type, [&](Category::Name const & name)
{
if (name.m_locale == lang)
TokenizeAndAddAllSubstrings(m_trie, name.m_name, type);
});
}
void CategoriesIndex::AddCategoryByTypeAllLangs(uint32_t type)
{
for (size_t i = 1; i <= CategoriesHolder::kLocaleMapping.size(); ++i)
AddCategoryByTypeAndLang(type, i);
}
void CategoriesIndex::AddAllCategoriesInLang(int8_t lang)
{
ASSERT(lang >= 1 && static_cast<size_t>(lang) <= CategoriesHolder::kLocaleMapping.size(),
("Invalid lang code:", lang));
m_catHolder->ForEachTypeAndCategory([&](uint32_t type, Category const & cat)
{
for (auto const & name : cat.m_synonyms)
if (name.m_locale == lang)
TokenizeAndAddAllSubstrings(m_trie, name.m_name, type);
});
}
void CategoriesIndex::AddAllCategoriesInAllLangs()
{
m_catHolder->ForEachTypeAndCategory([this](uint32_t type, Category const & cat)
{
for (auto const & name : cat.m_synonyms)
TokenizeAndAddAllSubstrings(m_trie, name.m_name, type);
});
}
void CategoriesIndex::GetCategories(std::string const & query, std::vector<Category> & result) const
{
std::vector<uint32_t> types;
GetAssociatedTypes(query, types);
base::SortUnique(types);
m_catHolder->ForEachTypeAndCategory([&](uint32_t type, Category const & cat)
{
if (binary_search(types.begin(), types.end(), type))
result.push_back(cat);
});
}
void CategoriesIndex::GetAssociatedTypes(std::string const & query, std::vector<uint32_t> & result) const
{
bool first = true;
std::set<uint32_t> intersection;
auto processToken = [&](std::string const & token)
{
std::set<uint32_t> types;
auto fn = [&](std::string const &, uint32_t type) { types.insert(type); };
m_trie.ForEachInSubtree(token, fn);
if (first)
{
intersection.swap(types);
}
else
{
std::set<uint32_t> tmp;
set_intersection(intersection.begin(), intersection.end(), types.begin(), types.end(),
inserter(tmp, tmp.begin()));
intersection.swap(tmp);
}
first = false;
};
ForEachToken(query, processToken);
result.insert(result.end(), intersection.begin(), intersection.end());
}
} // namespace indexer

View file

@ -0,0 +1,71 @@
#pragma once
#include "categories_holder.hpp"
#include "base/mem_trie.hpp"
#include <string>
#include <vector>
namespace indexer
{
// This class is used to simplify searches of categories by
// synonyms to their names (in various languages).
// An example usage is helping a user who is trying to add
// a new feature with our editor.
// All category data is taken from data/categories.txt.
// All types returned are those from classificator.
class CategoriesIndex
{
DISALLOW_COPY(CategoriesIndex);
public:
using Category = CategoriesHolder::Category;
CategoriesIndex() : m_catHolder(&GetDefaultCategories()) {}
explicit CategoriesIndex(CategoriesHolder const & catHolder) : m_catHolder(&catHolder) {}
CategoriesIndex(CategoriesIndex &&) = default;
CategoriesIndex & operator=(CategoriesIndex &&) = default;
// Adds all categories that match |type|. Only synonyms
// in language |lang| are added. See indexer/categories_holder.cpp
// for language enumeration.
void AddCategoryByTypeAndLang(uint32_t type, int8_t lang);
// Adds all categories that match |type|. All known synonyms
// are added.
void AddCategoryByTypeAllLangs(uint32_t type);
// Adds all categories from data/classificator.txt. Only
// names in language |lang| are added.
void AddAllCategoriesInLang(int8_t lang);
// Adds all categories from data/classificator.txt.
void AddAllCategoriesInAllLangs();
// Returns all categories that have |query| as a substring. Note
// that all synonyms for a category are contained in a returned
// value even if only one language was used when adding this
// category's name to index.
// Beware weird results when query is a malformed UTF-8 string.
void GetCategories(std::string const & query, std::vector<Category> & result) const;
// Returns all types that match to categories that have |query| as substring.
// Beware weird results when query is a malformed UTF-8 string.
// Note: no types are returned if the query is empty.
void GetAssociatedTypes(std::string const & query, std::vector<uint32_t> & result) const;
#ifdef DEBUG
inline size_t GetNumTrieNodes() const { return m_trie.GetNumNodes(); }
#endif
private:
// There is a raw pointer instead of const reference
// here because this class may be used from Objectvie-C
// so a default constructor is needed.
CategoriesHolder const * m_catHolder = nullptr;
base::MemTrie<std::string, base::VectorValues<uint32_t>> m_trie;
};
} // namespace indexer

View file

@ -0,0 +1,214 @@
#pragma once
#include "indexer/cell_id.hpp"
#include "geometry/rect2d.hpp"
#include "base/buffer_vector.hpp"
#include <array>
#include <cstdint>
#include <queue>
#include <utility>
#include <vector>
// TODO: Move neccessary functions to geometry/covering_utils.hpp and delete this file.
constexpr int SPLIT_RECT_CELLS_COUNT = 512;
template <typename Bounds, typename CellId>
inline size_t SplitRectCell(CellId const & id, m2::RectD const & rect,
std::array<std::pair<CellId, m2::RectD>, 4> & result)
{
size_t index = 0;
for (int8_t i = 0; i < 4; ++i)
{
auto const child = id.Child(i);
double minCellX, minCellY, maxCellX, maxCellY;
CellIdConverter<Bounds, CellId>::GetCellBounds(child, minCellX, minCellY, maxCellX, maxCellY);
m2::RectD const childRect(minCellX, minCellY, maxCellX, maxCellY);
if (rect.IsIntersect(childRect))
result[index++] = {child, childRect};
}
return index;
}
// Covers |rect| with at most |cellsCount| cells that have levels equal to or less than |maxLevel|.
template <typename Bounds, typename CellId>
inline void CoverRect(m2::RectD rect, size_t cellsCount, int maxLevel, std::vector<CellId> & result)
{
ASSERT(result.empty(), ());
{
// Cut rect with world bound coordinates.
if (!rect.Intersect(Bounds::FullRect()))
return;
ASSERT(rect.IsValid(), ());
}
auto const commonCell =
CellIdConverter<Bounds, CellId>::Cover2PointsWithCell(rect.minX(), rect.minY(), rect.maxX(), rect.maxY());
std::priority_queue<CellId, buffer_vector<CellId, SPLIT_RECT_CELLS_COUNT>, typename CellId::GreaterLevelOrder>
cellQueue;
cellQueue.push(commonCell);
CHECK_GREATER_OR_EQUAL(maxLevel, 0, ());
while (!cellQueue.empty() && cellQueue.size() + result.size() < cellsCount)
{
auto id = cellQueue.top();
cellQueue.pop();
while (id.Level() > maxLevel)
id = id.Parent();
if (id.Level() == maxLevel)
{
result.push_back(id);
break;
}
std::array<std::pair<CellId, m2::RectD>, 4> arr;
size_t const count = SplitRectCell<Bounds>(id, rect, arr);
if (cellQueue.size() + result.size() + count <= cellsCount)
{
for (size_t i = 0; i < count; ++i)
if (rect.IsRectInside(arr[i].second))
result.push_back(arr[i].first);
else
cellQueue.push(arr[i].first);
}
else
{
result.push_back(id);
}
}
for (; !cellQueue.empty(); cellQueue.pop())
{
auto id = cellQueue.top();
while (id.Level() < maxLevel)
{
std::array<std::pair<CellId, m2::RectD>, 4> arr;
size_t const count = SplitRectCell<Bounds>(id, rect, arr);
ASSERT_GREATER(count, 0, ());
if (count > 1)
break;
id = arr[0].first;
}
result.push_back(id);
}
}
// Covers |rect| with cells using spiral order starting from the rect center cell of |maxLevel|.
template <typename Bounds, typename CellId>
void CoverSpiral(m2::RectD rect, int maxLevel, std::vector<CellId> & result)
{
using Converter = CellIdConverter<Bounds, CellId>;
enum class Direction : uint8_t
{
Right = 0,
Down = 1,
Left = 2,
Up = 3
};
CHECK(result.empty(), ());
// Cut rect with world bound coordinates.
if (!rect.Intersect(Bounds::FullRect()))
return;
CHECK(rect.IsValid(), ());
CHECK_GREATER_OR_EQUAL(maxLevel, 0, ());
auto centralCell = Converter::ToCellId(rect.Center().x, rect.Center().y);
while (maxLevel < centralCell.Level())
centralCell = centralCell.Parent();
result.push_back(centralCell);
// Area around CentralCell will be covered with surrounding cells.
//
// * -> * -> * -> *
// ^ |
// | V
// * C -> * *
// ^ | |
// | V V
// * <- * <- * *
//
// To get the best ranking quality we should use the smallest cell size but it's not
// efficient because it generates too many index requests. To get good quality-performance
// tradeoff we cover area with |maxCount| small cells, then increase cell size and cover
// area with |maxCount| bigger cells. We increase cell size until |rect| is covered.
// We start covering from the center each time and it's ok for ranking because each object
// appears in result at most once.
// |maxCount| may be adjusted after testing to ensure better quality-performance tradeoff.
uint32_t constexpr maxCount = 64;
auto const nextDirection = [](Direction direction)
{ return static_cast<Direction>((static_cast<uint8_t>(direction) + 1) % 4); };
auto const nextCoords = [](std::pair<int32_t, int32_t> const & xy, Direction direction, uint32_t step)
{
auto res = xy;
switch (direction)
{
case Direction::Right: res.first += step; break;
case Direction::Down: res.second -= step; break;
case Direction::Left: res.first -= step; break;
case Direction::Up: res.second += step; break;
}
return res;
};
auto const coordsAreValid = [](std::pair<int32_t, int32_t> const & xy)
{
return xy.first >= 0 && xy.second >= 0 && static_cast<decltype(CellId::MAX_COORD)>(xy.first) <= CellId::MAX_COORD &&
static_cast<decltype(CellId::MAX_COORD)>(xy.second) <= CellId::MAX_COORD;
};
m2::RectD coveredRect;
static_assert(CellId::MAX_COORD == static_cast<int32_t>(CellId::MAX_COORD), "");
while (centralCell.Level() > 0 && !coveredRect.IsRectInside(rect))
{
uint32_t count = 0;
auto const centerXY = centralCell.XY();
// We support negative coordinates while covering and check coordinates validity before pushing
// cell to |result|.
std::pair<int32_t, int32_t> xy{centerXY.first, centerXY.second};
auto direction = Direction::Right;
int sideLength = 1;
// Indicates whether it is the first pass with current |sideLength|. We use spiral cells order and
// must increment |sideLength| every second side pass. |sideLength| and |direction| will behave like:
// 1 right, 1 down, 2 left, 2 up, 3 right, 3 down, etc.
bool evenPass = true;
while (count <= maxCount && !coveredRect.IsRectInside(rect))
{
for (int i = 0; i < sideLength; ++i)
{
xy = nextCoords(xy, direction, centralCell.Radius() * 2);
if (coordsAreValid(xy))
{
auto const cell = CellId::FromXY(xy.first, xy.second, centralCell.Level());
double minCellX, minCellY, maxCellX, maxCellY;
Converter::GetCellBounds(cell, minCellX, minCellY, maxCellX, maxCellY);
auto const cellRect = m2::RectD(minCellX, minCellY, maxCellX, maxCellY);
coveredRect.Add(cellRect);
if (rect.IsIntersect(cellRect))
result.push_back(cell);
}
++count;
}
if (!evenPass)
++sideLength;
direction = nextDirection(direction);
evenPass = !evenPass;
}
centralCell = centralCell.Parent();
}
}

87
libs/indexer/cell_id.hpp Normal file
View file

@ -0,0 +1,87 @@
#pragma once
#include "geometry/cellid.hpp"
#include "geometry/mercator.hpp"
#include "geometry/rect2d.hpp"
#include "base/assert.hpp"
#include <cstdint>
#include <utility>
using RectId = m2::CellId<19>;
// 24 is enough to have cell size < 2.5m * 2.5m for world.
constexpr int kGeoObjectsDepthLevels = 24;
// Cell size < 40m * 40m for world is good for regions.
constexpr int kRegionsDepthLevels = 20;
template <typename Bounds, typename CellId>
class CellIdConverter
{
public:
static double XToCellIdX(double x) { return (x - Bounds::kMinX) / StepX(); }
static double YToCellIdY(double y) { return (y - Bounds::kMinY) / StepY(); }
static double CellIdXToX(double x) { return (x * StepX() + Bounds::kMinX); }
static double CellIdYToY(double y) { return (y * StepY() + Bounds::kMinY); }
static CellId ToCellId(double x, double y)
{
uint32_t const ix = static_cast<uint32_t>(XToCellIdX(x));
uint32_t const iy = static_cast<uint32_t>(YToCellIdY(y));
CellId id = CellId::FromXY(ix, iy, CellId::DEPTH_LEVELS - 1);
#if 0 // DEBUG
std::pair<uint32_t, uint32_t> ixy = id.XY();
ASSERT(Abs(ixy.first - ix) <= 1, (x, y, id, ixy));
ASSERT(Abs(ixy.second - iy) <= 1, (x, y, id, ixy));
CoordT minX, minY, maxX, maxY;
GetCellBounds(id, minX, minY, maxX, maxY);
ASSERT(minX <= x && x <= maxX, (x, y, id, minX, minY, maxX, maxY));
ASSERT(minY <= y && y <= maxY, (x, y, id, minX, minY, maxX, maxY));
#endif
return id;
}
static CellId Cover2PointsWithCell(double x1, double y1, double x2, double y2)
{
CellId id1 = ToCellId(x1, y1);
CellId id2 = ToCellId(x2, y2);
while (id1 != id2)
{
id1 = id1.Parent();
id2 = id2.Parent();
}
#if 0 // DEBUG
double minX, minY, maxX, maxY;
GetCellBounds(id1, minX, minY, maxX, maxY);
ASSERT(math::Between(minX, maxX, x1), (x1, minX, maxX));
ASSERT(math::Between(minX, maxX, x2), (x2, minX, maxX));
ASSERT(math::Between(minY, maxY, y1), (y1, minY, maxY));
ASSERT(math::Between(minY, maxY, y2), (y2, minY, maxY));
#endif
return id1;
}
static m2::PointD FromCellId(CellId id)
{
std::pair<uint32_t, uint32_t> const xy = id.XY();
return m2::PointD(CellIdXToX(xy.first), CellIdYToY(xy.second));
}
static void GetCellBounds(CellId id, double & minX, double & minY, double & maxX, double & maxY)
{
std::pair<uint32_t, uint32_t> const xy = id.XY();
uint32_t const r = id.Radius();
minX = (xy.first - r) * StepX() + Bounds::kMinX;
maxX = (xy.first + r) * StepX() + Bounds::kMinX;
minY = (xy.second - r) * StepY() + Bounds::kMinY;
maxY = (xy.second + r) * StepY() + Bounds::kMinY;
}
private:
inline static double StepX() { return static_cast<double>(Bounds::kRangeX) / CellId::MAX_COORD; }
inline static double StepY() { return static_cast<double>(Bounds::kRangeY) / CellId::MAX_COORD; }
};

View file

@ -0,0 +1,40 @@
#pragma once
#include "base/macros.hpp"
#include <cstdint>
namespace covering
{
template <typename Value>
class CellValuePair
{
public:
using ValueType = Value;
CellValuePair() = default;
CellValuePair(uint64_t cell, Value value) : m_cellLo(UINT64_LO(cell)), m_cellHi(UINT64_HI(cell)), m_value(value) {}
bool operator<(CellValuePair const & rhs) const
{
if (m_cellHi != rhs.m_cellHi)
return m_cellHi < rhs.m_cellHi;
if (m_cellLo != rhs.m_cellLo)
return m_cellLo < rhs.m_cellLo;
return m_value < rhs.m_value;
}
uint64_t GetCell() const { return UINT64_FROM_UINT32(m_cellHi, m_cellLo); }
Value GetValue() const { return m_value; }
private:
uint32_t m_cellLo = 0;
uint32_t m_cellHi = 0;
Value m_value = 0;
};
// For backward compatibility.
static_assert(sizeof(CellValuePair<uint32_t>) == 12);
static_assert(std::is_trivially_copyable<CellValuePair<uint32_t>>::value);
} // namespace covering

View file

@ -0,0 +1,187 @@
#include "indexer/centers_table.hpp"
#include "indexer/feature_processor.hpp"
#include "coding/files_container.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/point_coding.hpp"
#include "coding/reader.hpp"
#include "coding/succinct_mapper.hpp"
#include "coding/varint.hpp"
#include "coding/write_to_sink.hpp"
#include "coding/writer.hpp"
#include "base/assert.hpp"
#include "base/checked_cast.hpp"
namespace search
{
void CentersTable::Header::Read(Reader & reader)
{
NonOwningReaderSource source(reader);
m_version = static_cast<Version>(ReadPrimitiveFromSource<uint8_t>(source));
CHECK_EQUAL(static_cast<uint8_t>(m_version), static_cast<uint8_t>(Version::V1), ());
m_geometryParamsOffset = ReadPrimitiveFromSource<uint32_t>(source);
m_geometryParamsSize = ReadPrimitiveFromSource<uint32_t>(source);
m_centersOffset = ReadPrimitiveFromSource<uint32_t>(source);
m_centersSize = ReadPrimitiveFromSource<uint32_t>(source);
}
bool CentersTable::Get(uint32_t id, m2::PointD & center)
{
m2::PointU pointu;
if (!m_map->Get(id, pointu))
return false;
if (m_version == Version::V0)
center = PointUToPointD(pointu, m_codingParams.GetCoordBits());
else if (m_version == Version::V1)
center = PointUToPointD(pointu, m_codingParams.GetCoordBits(), m_limitRect);
else
CHECK(false, ("Unknown CentersTable format."));
return true;
}
// CentersTable ------------------------------------------------------------------------------------
// static
std::unique_ptr<CentersTable> CentersTable::LoadV0(Reader & reader, serial::GeometryCodingParams const & codingParams)
{
auto table = std::make_unique<CentersTable>();
table->m_version = Version::V0;
if (!table->Init(reader, codingParams, {} /* limitRect */))
return {};
return table;
}
std::unique_ptr<CentersTable> CentersTable::LoadV1(Reader & reader)
{
auto table = std::make_unique<CentersTable>();
table->m_version = Version::V1;
Header header;
header.Read(reader);
NonOwningReaderSource src(reader, header.m_geometryParamsOffset,
header.m_geometryParamsOffset + header.m_geometryParamsSize);
serial::GeometryCodingParams codingParams;
codingParams.Load(src);
auto minX = ReadPrimitiveFromSource<uint32_t>(src);
auto minY = ReadPrimitiveFromSource<uint32_t>(src);
auto maxX = ReadPrimitiveFromSource<uint32_t>(src);
auto maxY = ReadPrimitiveFromSource<uint32_t>(src);
m2::RectD limitRect(PointUToPointD({minX, minY}, kPointCoordBits), PointUToPointD({maxX, maxY}, kPointCoordBits));
table->m_centersSubreader = reader.CreateSubReader(header.m_centersOffset, header.m_centersSize);
if (!table->m_centersSubreader)
return {};
if (!table->Init(*(table->m_centersSubreader), codingParams, limitRect))
return {};
return table;
}
bool CentersTable::Init(Reader & reader, serial::GeometryCodingParams const & codingParams, m2::RectD const & limitRect)
{
m_codingParams = codingParams;
m_limitRect = limitRect;
// Decodes block encoded by writeBlockCallback from CentersTableBuilder::Freeze.
auto const readBlockCallback =
[&](NonOwningReaderSource & source, uint32_t blockSize, std::vector<m2::PointU> & values)
{
values.reserve(blockSize);
auto prev = m_codingParams.GetBasePoint();
while (source.Size() > 0)
{
auto const pt = coding::DecodePointDeltaFromUint(ReadVarUint<uint64_t>(source), prev);
values.push_back(pt);
prev = pt;
}
};
m_map = Map::Load(reader, readBlockCallback);
return m_map != nullptr;
}
// CentersTableBuilder -----------------------------------------------------------------------------
void CentersTableBuilder::SetGeometryParams(m2::RectD const & limitRect, double pointAccuracy)
{
CHECK(limitRect.IsValid(), (limitRect));
auto const coordBits = GetCoordBits(limitRect, pointAccuracy);
m_codingParams = serial::GeometryCodingParams(coordBits, limitRect.Center());
m_limitRect = limitRect;
}
void CentersTableBuilder::Put(uint32_t featureId, m2::PointD const & center)
{
m_builder.Put(featureId, PointDToPointU(center, m_codingParams.GetCoordBits(), m_limitRect));
}
// Each center is encoded as delta from some prediction.
// For the first center in the block map base point is used as a prediction, for all other
// centers in the block previous center is used as a prediction.
void CentersTableBuilder::WriteBlock(Writer & w, std::vector<m2::PointU>::const_iterator begin,
std::vector<m2::PointU>::const_iterator end) const
{
uint64_t delta = coding::EncodePointDeltaAsUint(*begin, m_codingParams.GetBasePoint());
WriteVarUint(w, delta);
auto prevIt = begin;
for (auto it = begin + 1; it != end; ++it)
{
delta = coding::EncodePointDeltaAsUint(*it, *prevIt);
WriteVarUint(w, delta);
prevIt = it;
}
}
void CentersTableBuilder::Freeze(Writer & writer) const
{
uint64_t const startOffset = writer.Pos();
CHECK(coding::IsAlign8(startOffset), ());
CentersTable::Header header;
header.Serialize(writer);
uint64_t bytesWritten = writer.Pos();
coding::WritePadding(writer, bytesWritten);
header.m_geometryParamsOffset = base::asserted_cast<uint32_t>(writer.Pos() - startOffset);
m_codingParams.Save(writer);
auto leftBottom = PointDToPointU(m_limitRect.LeftBottom(), kPointCoordBits);
WriteToSink(writer, leftBottom.x);
WriteToSink(writer, leftBottom.y);
auto rightTop = PointDToPointU(m_limitRect.RightTop(), kPointCoordBits);
WriteToSink(writer, rightTop.x);
WriteToSink(writer, rightTop.y);
header.m_geometryParamsSize =
base::asserted_cast<uint32_t>(writer.Pos() - header.m_geometryParamsOffset - startOffset);
bytesWritten = writer.Pos();
coding::WritePadding(writer, bytesWritten);
header.m_centersOffset = base::asserted_cast<uint32_t>(writer.Pos() - startOffset);
m_builder.Freeze(writer, [&](auto & w, auto begin, auto end) { WriteBlock(w, begin, end); });
header.m_centersSize = base::asserted_cast<uint32_t>(writer.Pos() - header.m_centersOffset - startOffset);
auto const endOffset = writer.Pos();
writer.Seek(startOffset);
header.Serialize(writer);
writer.Seek(endOffset);
}
void CentersTableBuilder::SetGeometryCodingParamsV0ForTests(serial::GeometryCodingParams const & codingParams)
{
m_codingParams = codingParams;
}
void CentersTableBuilder::PutV0ForTests(uint32_t featureId, m2::PointD const & center)
{
m_builder.Put(featureId, PointDToPointU(center, m_codingParams.GetCoordBits()));
}
void CentersTableBuilder::FreezeV0ForTests(Writer & writer) const
{
m_builder.Freeze(writer, [&](auto & w, auto begin, auto end) { WriteBlock(w, begin, end); });
}
} // namespace search

View file

@ -0,0 +1,96 @@
#pragma once
#include "coding/geometry_coding.hpp"
#include "coding/map_uint32_to_val.hpp"
#include "coding/point_coding.hpp"
#include "geometry/point2d.hpp"
#include <cstdint>
#include <memory>
#include <vector>
class FilesContainerR;
class Reader;
class Writer;
namespace search
{
// A wrapper class around serialized centers-table.
class CentersTable
{
public:
enum class Version : uint8_t
{
V0 = 0,
V1 = 1,
Latest = V1
};
struct Header
{
template <typename Sink>
void Serialize(Sink & sink) const
{
CHECK_EQUAL(static_cast<uint8_t>(m_version), static_cast<uint8_t>(Version::V1), ());
WriteToSink(sink, static_cast<uint8_t>(m_version));
WriteToSink(sink, m_geometryParamsOffset);
WriteToSink(sink, m_geometryParamsSize);
WriteToSink(sink, m_centersOffset);
WriteToSink(sink, m_centersSize);
}
void Read(Reader & reader);
Version m_version = Version::Latest;
// All offsets are relative to the start of the section (offset of header is zero).
uint32_t m_geometryParamsOffset = 0;
uint32_t m_geometryParamsSize = 0;
uint32_t m_centersOffset = 0;
uint32_t m_centersSize = 0;
};
// Tries to get |center| of the feature identified by |id|. Returns
// false if table does not have entry for the feature.
[[nodiscard]] bool Get(uint32_t id, m2::PointD & center);
uint64_t Count() const { return m_map->Count(); }
// Loads CentersTable instance. Note that |reader| must be alive
// until the destruction of loaded table. Returns nullptr if
// CentersTable can't be loaded.
static std::unique_ptr<CentersTable> LoadV0(Reader & reader, serial::GeometryCodingParams const & codingParams);
static std::unique_ptr<CentersTable> LoadV1(Reader & reader);
private:
using Map = MapUint32ToValue<m2::PointU>;
bool Init(Reader & reader, serial::GeometryCodingParams const & codingParams, m2::RectD const & limitRect);
serial::GeometryCodingParams m_codingParams;
std::unique_ptr<Map> m_map;
std::unique_ptr<Reader> m_centersSubreader;
m2::RectD m_limitRect;
Version m_version = Version::Latest;
};
class CentersTableBuilder
{
public:
void SetGeometryParams(m2::RectD const & limitRect, double pointAccuracy = kMwmPointAccuracy);
void Put(uint32_t featureId, m2::PointD const & center);
void WriteBlock(Writer & w, std::vector<m2::PointU>::const_iterator begin,
std::vector<m2::PointU>::const_iterator end) const;
void Freeze(Writer & writer) const;
void SetGeometryCodingParamsV0ForTests(serial::GeometryCodingParams const & codingParams);
void PutV0ForTests(uint32_t featureId, m2::PointD const & center);
void FreezeV0ForTests(Writer & writer) const;
private:
serial::GeometryCodingParams m_codingParams;
m2::RectD m_limitRect;
MapUint32ToValueBuilder<m2::PointU> m_builder;
};
} // namespace search

View file

@ -0,0 +1,390 @@
#pragma once
#include "indexer/city_boundary.hpp"
#include "coding/bit_streams.hpp"
#include "coding/elias_coder.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/point_coding.hpp"
#include "coding/reader.hpp"
#include "coding/varint.hpp"
#include "coding/write_to_sink.hpp"
#include "geometry/bounding_box.hpp"
#include "geometry/calipers_box.hpp"
#include "geometry/diamond_box.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include "base/assert.hpp"
#include "base/macros.hpp"
#include <algorithm>
#include <cmath>
#include <limits>
#include <type_traits>
#include <vector>
namespace indexer
{
template <typename Sink>
class CitiesBoundariesEncoder
{
public:
struct Visitor
{
public:
Visitor(Sink & sink, serial::GeometryCodingParams const & params)
: m_sink(sink)
, m_params(params)
, m_last(params.GetBasePoint())
{}
void Save(m2::PointU const & p) { WriteVarUint(m_sink, coding::EncodePointDeltaAsUint(p, m_last)); }
void SaveWithLast(m2::PointU const & p)
{
Save(p);
m_last = p;
}
void operator()(m2::BoundingBox const & bbox)
{
auto const min = ToU(bbox.Min());
auto const max = ToU(bbox.Max());
SaveWithLast(min);
EncodeNonNegativePointDelta(min, max);
}
void operator()(m2::CalipersBox const & cbox)
{
auto ps = cbox.Points();
CHECK(!ps.empty(), ());
CHECK_LESS_OR_EQUAL(ps.size(), 4, ());
CHECK(ps.size() != 3, ());
if (ps.size() == 1)
{
auto const p0 = ps[0];
while (ps.size() != 4)
ps.push_back(p0);
}
else if (ps.size() == 2)
{
auto const p0 = ps[0];
auto const p1 = ps[1];
ps.push_back(p1);
ps.push_back(p0);
}
ASSERT_EQUAL(ps.size(), 4, ());
auto const us = ToU(ps);
SaveWithLast(us[0]);
Save(us[1]);
Save(us[3]);
}
void operator()(m2::DiamondBox const & dbox)
{
auto const ps = ToU(dbox.Points());
auto const base = ps[0];
auto const next = ps[1];
auto const prev = ps[3];
SaveWithLast(base);
ASSERT_GREATER_OR_EQUAL(next.x, base.x, ());
ASSERT_GREATER_OR_EQUAL(next.y, base.y, ());
auto const nx = next.x >= base.x ? next.x - base.x : 0;
ASSERT_GREATER_OR_EQUAL(prev.x, base.x, ());
ASSERT_LESS_OR_EQUAL(prev.y, base.y, ());
auto const px = prev.x >= base.x ? prev.x - base.x : 0;
WriteVarUint(m_sink, bits::BitwiseMerge(nx, px));
}
template <typename R>
void operator()(R const & r)
{
r.Visit(*this);
}
private:
m2::PointU ToU(m2::PointD const & p) const { return PointDToPointU(p, m_params.GetCoordBits()); }
std::vector<m2::PointU> ToU(std::vector<m2::PointD> const & ps) const
{
std::vector<m2::PointU> us(ps.size());
for (size_t i = 0; i < ps.size(); ++i)
us[i] = ToU(ps[i]);
return us;
}
// Writes the difference of two 2d vectors to |m_sink|. The vector |next|
// must have both its coordinates greater than or equal to those
// of |curr|. While this condition is unlikely when encoding polylines,
// this method may be useful when encoding rectangles, in particular
// bounding boxes of shapes.
void EncodeNonNegativePointDelta(m2::PointU const & curr, m2::PointU const & next)
{
ASSERT_GREATER_OR_EQUAL(next.x, curr.x, ());
ASSERT_GREATER_OR_EQUAL(next.y, curr.y, ());
// Paranoid checks due to possible floating point artifacts
// here. In general, next.x >= curr.x and next.y >= curr.y.
auto const dx = next.x >= curr.x ? next.x - curr.x : 0;
auto const dy = next.y >= curr.y ? next.y - curr.y : 0;
WriteVarUint(m_sink, bits::BitwiseMerge(dx, dy));
}
Sink & m_sink;
serial::GeometryCodingParams m_params;
m2::PointU m_last;
};
CitiesBoundariesEncoder(Sink & sink, serial::GeometryCodingParams const & params)
: m_sink(sink)
, m_visitor(sink, params)
{}
void operator()(std::vector<std::vector<CityBoundary>> const & boundaries)
{
WriteVarUint(m_sink, boundaries.size());
{
BitWriter<Sink> writer(m_sink);
for (auto const & bs : boundaries)
{
CHECK_LESS(bs.size(), std::numeric_limits<uint64_t>::max(), ());
auto const success = coding::GammaCoder::Encode(writer, static_cast<uint64_t>(bs.size()) + 1);
ASSERT(success, ());
UNUSED_VALUE(success);
}
}
for (auto const & bs : boundaries)
for (auto const & b : bs)
m_visitor(b);
}
private:
Sink & m_sink;
Visitor m_visitor;
};
template <typename Source>
class CitiesBoundariesDecoder
{
public:
struct DecoderV0
{
static m2::PointU DecodeNonNegativeDelta(Source & src)
{
auto const dx = ReadVarUint<uint32_t>(src);
auto const dy = ReadVarUint<uint32_t>(src);
return {dx, dy};
}
static m2::PointU DecodePoint(Source & src, m2::PointU const & base) { return coding::DecodePointDelta(src, base); }
};
struct DecoderV1
{
static m2::PointU DecodeNonNegativeDelta(Source & src)
{
uint32_t dx, dy;
bits::BitwiseSplit(ReadVarUint<uint64_t>(src), dx, dy);
return {dx, dy};
}
static m2::PointU DecodePoint(Source & src, m2::PointU const & base)
{
return coding::DecodePointDeltaFromUint(ReadVarUint<uint64_t>(src), base);
}
};
template <class TDecoder>
struct Visitor
{
public:
Visitor(Source & source, serial::GeometryCodingParams const & params)
: m_source(source)
, m_params(params)
, m_last(params.GetBasePoint())
{}
m2::PointU LoadPoint() { return coding::DecodePointDeltaFromUint(ReadVarUint<uint64_t>(m_source), m_last); }
m2::PointU LoadPointWithLast()
{
m_last = LoadPoint();
return m_last;
}
void operator()(m2::BoundingBox & bbox)
{
auto const min = LoadPointWithLast();
m2::PointU const delta = TDecoder::DecodeNonNegativeDelta(m_source);
bbox = m2::BoundingBox();
bbox.Add(FromU(min));
bbox.Add(FromU(min + delta));
}
void operator()(m2::CalipersBox & cbox)
{
std::vector<m2::PointU> us(4);
us[0] = LoadPointWithLast();
us[1] = TDecoder::DecodePoint(m_source, m_last);
us[3] = TDecoder::DecodePoint(m_source, m_last);
auto ps = FromU(us);
auto const dp = ps[3] - ps[0];
ps[2] = ps[1] + dp;
cbox.Deserialize(std::move(ps));
}
void operator()(m2::DiamondBox & dbox)
{
auto const base = LoadPointWithLast();
// {nx, px}
auto const delta = TDecoder::DecodeNonNegativeDelta(m_source);
dbox = m2::DiamondBox();
dbox.Add(FromU(base));
dbox.Add(FromU(base + m2::PointU(delta.x, delta.x)));
dbox.Add(FromU(base + m2::PointU(delta.y, -delta.y)));
dbox.Add(FromU(base + m2::PointU(delta.x + delta.y, delta.x - delta.y)));
}
template <typename R>
void operator()(R & r)
{
r.Visit(*this);
}
private:
m2::PointD FromU(m2::PointU const & u) const { return PointUToPointD(u, m_params.GetCoordBits()); }
std::vector<m2::PointD> FromU(std::vector<m2::PointU> const & us) const
{
std::vector<m2::PointD> ps(us.size());
for (size_t i = 0; i < us.size(); ++i)
ps[i] = FromU(us[i]);
return ps;
}
Source & m_source;
serial::GeometryCodingParams const m_params;
m2::PointU m_last;
};
using BoundariesT = std::vector<std::vector<CityBoundary>>;
CitiesBoundariesDecoder(Source & source, serial::GeometryCodingParams const & params, BoundariesT & res)
: m_source(source)
, m_params(params)
, m_boundaries(res)
{}
void LoadSizes()
{
{
auto const size = static_cast<size_t>(ReadVarUint<uint64_t>(m_source));
m_boundaries.resize(size);
BitReader<Source> reader(m_source);
for (auto & bs : m_boundaries)
{
auto const size = static_cast<size_t>(coding::GammaCoder::Decode(reader));
ASSERT_GREATER_OR_EQUAL(size, 1, ());
bs.resize(size - 1);
}
}
}
template <class TDecoder>
void LoadBoundaries()
{
Visitor<TDecoder> visitor(m_source, m_params);
for (auto & bs : m_boundaries)
for (auto & b : bs)
visitor(b);
}
private:
Source & m_source;
serial::GeometryCodingParams const m_params;
BoundariesT & m_boundaries;
};
struct CitiesBoundariesSerDes
{
struct Header
{
// 0 - initial version
// 1 - optimized delta-points encoding
static uint8_t constexpr kLatestVersion = 1;
static uint8_t constexpr kDefaultCoordBits = 19;
uint8_t m_coordBits = kDefaultCoordBits;
uint8_t m_version = kLatestVersion;
template <class Sink>
void Serialize(Sink & sink) const
{
WriteToSink(sink, m_version);
WriteToSink(sink, m_coordBits);
}
template <class Source>
void Deserialize(Source & src)
{
m_version = ReadPrimitiveFromSource<uint8_t>(src);
m_coordBits = ReadPrimitiveFromSource<uint8_t>(src);
}
};
using BoundariesT = std::vector<std::vector<CityBoundary>>;
template <typename Sink>
static void Serialize(Sink & sink, BoundariesT const & boundaries)
{
Header header;
header.Serialize(sink);
using mercator::Bounds;
serial::GeometryCodingParams const params(header.m_coordBits, {Bounds::kMinX, Bounds::kMinY});
CitiesBoundariesEncoder<Sink> encoder(sink, params);
encoder(boundaries);
}
template <typename Source>
static void Deserialize(Source & source, BoundariesT & boundaries, double & precision)
{
Header header;
header.Deserialize(source);
using mercator::Bounds;
precision = std::max(Bounds::kRangeX, Bounds::kRangeY) / double(1 << header.m_coordBits);
serial::GeometryCodingParams const params(header.m_coordBits, {Bounds::kMinX, Bounds::kMinY});
using DecoderT = CitiesBoundariesDecoder<Source>;
DecoderT decoder(source, params, boundaries);
decoder.LoadSizes();
if (header.m_version == 0)
decoder.template LoadBoundaries<typename DecoderT::DecoderV0>();
else
decoder.template LoadBoundaries<typename DecoderT::DecoderV1>();
}
};
} // namespace indexer

View file

@ -0,0 +1,45 @@
#pragma once
#include "geometry/bounding_box.hpp"
#include "geometry/calipers_box.hpp"
#include "geometry/diamond_box.hpp"
#include "geometry/point2d.hpp"
#include "base/visitor.hpp"
#include <vector>
namespace indexer
{
// Approximates city boundary.
// City points are inside the intersection of boxes.
struct CityBoundary
{
CityBoundary() = default;
explicit CityBoundary(std::vector<m2::PointD> const & ps) : m_bbox(ps), m_cbox(ps), m_dbox(ps) {}
bool HasPoint(m2::PointD const & p) const { return m_bbox.HasPoint(p) && m_dbox.HasPoint(p) && m_cbox.HasPoint(p); }
bool HasPoint(double x, double y) const { return HasPoint(m2::PointD(x, y)); }
bool HasPoint(m2::PointD const & p, double eps) const
{
return m_bbox.HasPoint(p, eps) && m_dbox.HasPoint(p, eps) && m_cbox.HasPoint(p, eps);
}
bool HasPoint(double x, double y, double eps) const { return HasPoint(m2::PointD(x, y), eps); }
bool operator==(CityBoundary const & rhs) const
{
return m_bbox == rhs.m_bbox && m_cbox == rhs.m_cbox && m_dbox == rhs.m_dbox;
}
DECLARE_VISITOR(visitor(m_bbox), visitor(m_cbox), visitor(m_dbox))
DECLARE_DEBUG_PRINT(CityBoundary)
m2::BoundingBox m_bbox;
m2::CalipersBox m_cbox;
m2::DiamondBox m_dbox;
};
} // namespace indexer

View file

@ -0,0 +1,458 @@
#include "indexer/classificator.hpp"
#include "indexer/map_style_reader.hpp"
#include "indexer/tree_structure.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
using std::string;
namespace
{
struct less_scales
{
bool operator()(drule::Key const & l, int r) const { return l.m_scale < r; }
bool operator()(int l, drule::Key const & r) const { return l < r.m_scale; }
bool operator()(drule::Key const & l, drule::Key const & r) const { return l.m_scale < r.m_scale; }
};
} // namespace
/////////////////////////////////////////////////////////////////////////////////////////
// ClassifObject implementation
/////////////////////////////////////////////////////////////////////////////////////////
ClassifObject * ClassifObject::AddImpl(string const & s)
{
if (m_objs.empty())
m_objs.reserve(30);
m_objs.emplace_back(s);
return &(m_objs.back());
}
ClassifObject * ClassifObject::Add(string const & s)
{
ClassifObject * p = Find(s);
return (p ? p : AddImpl(s));
}
ClassifObject * ClassifObject::Find(string const & s)
{
for (auto & obj : m_objs)
if (obj.m_name == s)
return &obj;
return nullptr;
}
void ClassifObject::AddDrawRule(drule::Key const & k)
{
auto i = std::lower_bound(m_drawRules.begin(), m_drawRules.end(), k.m_scale, less_scales());
for (; i != m_drawRules.end() && i->m_scale == k.m_scale; ++i)
if (k == *i)
return; // already exists
m_drawRules.insert(i, k);
if (k.m_priority > m_maxOverlaysPriority && (k.m_type == drule::symbol || k.m_type == drule::caption ||
k.m_type == drule::shield || k.m_type == drule::pathtext))
m_maxOverlaysPriority = k.m_priority;
}
ClassifObjectPtr ClassifObject::BinaryFind(std::string_view const s) const
{
auto const i = std::lower_bound(m_objs.begin(), m_objs.end(), s, LessName());
if ((i == m_objs.end()) || ((*i).m_name != s))
return {nullptr, 0};
else
return {&(*i), static_cast<size_t>(std::distance(m_objs.begin(), i))};
}
void ClassifObject::LoadPolicy::Start(size_t i)
{
ClassifObject * p = Current();
p->m_objs.emplace_back();
base_type::Start(i);
}
void ClassifObject::LoadPolicy::EndChilds()
{
ClassifObject * p = Current();
ASSERT(p->m_objs.back().m_name.empty(), ());
p->m_objs.pop_back();
}
void ClassifObject::Sort()
{
sort(m_drawRules.begin(), m_drawRules.end(), less_scales());
sort(m_objs.begin(), m_objs.end(), LessName());
for (auto & obj : m_objs)
obj.Sort();
}
void ClassifObject::Swap(ClassifObject & r)
{
swap(m_name, r.m_name);
swap(m_drawRules, r.m_drawRules);
swap(m_objs, r.m_objs);
swap(m_visibility, r.m_visibility);
}
ClassifObject const * ClassifObject::GetObject(size_t i) const
{
if (i < m_objs.size())
return &(m_objs[i]);
else
{
LOG(LINFO, ("Map contains object that has no classificator entry", i, m_name));
return nullptr;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// Classificator implementation
/////////////////////////////////////////////////////////////////////////////////////////
Classificator & classif()
{
static Classificator c[MapStyleCount];
MapStyle const mapStyle = GetStyleReader().GetCurrentStyle();
return c[mapStyle];
}
namespace ftype
{
uint8_t const bits_count = 7;
uint8_t const levels_count = 4;
uint8_t const max_value = (1 << bits_count) - 1;
void set_value(uint32_t & type, uint8_t level, uint8_t value)
{
level *= bits_count; // level to bits
uint32_t const m1 = uint32_t(max_value) << level;
type &= (~m1); // zero bits
uint32_t const m2 = uint32_t(value) << level;
type |= m2; // set bits
}
uint8_t get_value(uint32_t type, uint8_t level)
{
level *= bits_count; // level to bits;
uint32_t const m = uint32_t(max_value) << level;
type &= m; // leave only our bits
type = type >> level; // move to start
ASSERT(type <= max_value, ("invalid output value", type));
return uint8_t(type); // conversion
}
uint8_t get_control_level(uint32_t type)
{
uint8_t count = 0;
while (type > 1)
{
type = type >> bits_count;
++count;
}
return count;
}
void PushValue(uint32_t & type, uint8_t value)
{
ASSERT(value <= max_value, ("invalid input value", value));
uint8_t const cl = get_control_level(type);
// to avoid warning in release
#ifdef RELEASE
UNUSED_VALUE(levels_count);
#endif
ASSERT(cl < levels_count, (cl));
set_value(type, cl, value);
// set control level
set_value(type, cl + 1, 1);
}
uint8_t GetValue(uint32_t type, uint8_t level)
{
return get_value(type, level);
}
void PopValue(uint32_t & type)
{
uint8_t const cl = get_control_level(type);
ASSERT_GREATER(cl, 0, ());
// remove control level
set_value(type, cl, 0);
// set control level
set_value(type, cl - 1, 1);
}
void TruncValue(uint32_t & type, uint8_t level)
{
ASSERT_GREATER(level, 0, ());
uint8_t cl = get_control_level(type);
while (cl > level)
{
// remove control level
set_value(type, cl, 0);
--cl;
// set control level
set_value(type, cl, 1);
}
}
uint8_t GetLevel(uint32_t type)
{
return get_control_level(type);
}
} // namespace ftype
namespace
{
class suitable_getter
{
typedef std::vector<drule::Key> vec_t;
typedef vec_t::const_iterator iter_t;
vec_t const & m_rules;
drule::KeysT & m_keys;
void add_rule(int ft, iter_t i)
{
// Define which drule types are applicable to which feature geom types.
static int const visible[3][drule::count_of_rules] = {
//{ line, area, symbol, caption, circle, pathtext, waymarker, shield }, see drule::Key::rule_type_t
{0, 0, 1, 1, 1, 0, 0, 0}, // fpoint
{1, 0, 0, 0, 0, 1, 0, 1}, // fline
{0, 1, 1, 1, 1, 0, 0, 0} // farea (!!! different from IsDrawableLike(): here area feature can use point styles)
};
if (visible[ft][i->m_type] == 1)
m_keys.push_back(*i);
}
public:
suitable_getter(vec_t const & rules, drule::KeysT & keys) : m_rules(rules), m_keys(keys) {}
void find(int ft, int scale)
{
auto i = std::lower_bound(m_rules.begin(), m_rules.end(), scale, less_scales());
while (i != m_rules.end() && i->m_scale == scale)
add_rule(ft, i++);
}
};
} // namespace
void ClassifObject::GetSuitable(int scale, feature::GeomType gt, drule::KeysT & keys) const
{
ASSERT(static_cast<int>(gt) >= 0 && static_cast<int>(gt) <= 2, ());
// 2. Check visibility criterion for scale first.
if (!m_visibility[scale])
return;
// find rules for 'scale'
suitable_getter rulesGetter(m_drawRules, keys);
rulesGetter.find(static_cast<int>(gt), scale);
}
bool ClassifObject::IsDrawable(int scale) const
{
return (m_visibility[scale] && IsDrawableAny());
}
bool ClassifObject::IsDrawableAny() const
{
return (m_visibility != VisibleMask() && !m_drawRules.empty());
}
bool ClassifObject::IsDrawableLike(feature::GeomType gt, bool emptyName) const
{
ASSERT(static_cast<int>(gt) >= 0 && static_cast<int>(gt) <= 2, ());
// check the very common criterion first
if (!IsDrawableAny())
return false;
// Define which feature geom types can use which drule types for rendering.
static int const visible[3][drule::count_of_rules] = {
//{ line, area, symbol, caption, circle, pathtext, waymarker, shield }, see drule::Key::rule_type_t
{0, 0, 1, 1, 1, 0, 0, 0}, // fpoint
{1, 0, 0, 0, 0, 1, 0, 1}, // fline
{0, 1, 0, 0, 0, 0, 0, 0} // farea (!!! key difference with GetSuitable, see suitable_getter::add_rule())
};
for (auto const & k : m_drawRules)
{
ASSERT_LESS(k.m_type, drule::count_of_rules, ());
// In case when feature name is empty we don't take into account caption drawing rules.
if ((visible[static_cast<int>(gt)][k.m_type] == 1) &&
(!emptyName || (k.m_type != drule::caption && k.m_type != drule::pathtext)))
{
return true;
}
}
return false;
}
std::pair<int, int> ClassifObject::GetDrawScaleRange() const
{
if (!IsDrawableAny())
return {-1, -1};
int const count = static_cast<int>(m_visibility.size());
int left = -1;
for (int i = 0; i < count; ++i)
if (m_visibility[i])
{
left = i;
break;
}
ASSERT_NOT_EQUAL(left, -1, ());
int right = left;
for (int i = count - 1; i > left; --i)
if (m_visibility[i])
{
right = i;
break;
}
return {left, right};
}
void Classificator::ReadClassificator(std::istream & s)
{
ClassifObject::LoadPolicy policy(&m_root);
tree::LoadTreeAsText(s, policy);
m_root.Sort();
m_coastType = GetTypeByPath({"natural", "coastline"});
m_stubType = GetTypeByPath({"mapswithme"});
}
template <typename Iter>
uint32_t Classificator::GetTypeByPathImpl(Iter beg, Iter end) const
{
ClassifObject const * p = GetRoot();
uint32_t type = ftype::GetEmptyValue();
while (beg != end)
{
ClassifObjectPtr ptr = p->BinaryFind(*beg++);
if (!ptr)
return INVALID_TYPE;
ftype::PushValue(type, ptr.GetIndex());
p = ptr.get();
}
return type;
}
uint32_t Classificator::GetTypeByPathSafe(std::vector<std::string_view> const & path) const
{
return GetTypeByPathImpl(path.begin(), path.end());
}
uint32_t Classificator::GetTypeByPath(std::vector<std::string> const & path) const
{
uint32_t const type = GetTypeByPathImpl(path.cbegin(), path.cend());
ASSERT_NOT_EQUAL(type, INVALID_TYPE, (path));
return type;
}
uint32_t Classificator::GetTypeByPath(std::vector<std::string_view> const & path) const
{
uint32_t const type = GetTypeByPathImpl(path.cbegin(), path.cend());
ASSERT_NOT_EQUAL(type, INVALID_TYPE, (path));
return type;
}
uint32_t Classificator::GetTypeByPath(base::StringIL const & lst) const
{
uint32_t const type = GetTypeByPathImpl(lst.begin(), lst.end());
ASSERT_NOT_EQUAL(type, INVALID_TYPE, (lst));
return type;
}
uint32_t Classificator::GetTypeByReadableObjectName(std::string const & name) const
{
ASSERT(!name.empty(), ());
return GetTypeByPathSafe(strings::Tokenize(name, "-"));
}
void Classificator::ReadTypesMapping(std::istream & s)
{
m_mapping.Load(s);
}
void Classificator::Clear()
{
ClassifObject("world").Swap(m_root);
m_mapping.Clear();
}
template <class ToDo>
void Classificator::ForEachPathObject(uint32_t type, ToDo && toDo) const
{
ClassifObject const * p = &m_root;
uint8_t const level = ftype::GetLevel(type);
for (uint8_t i = 0; i < level; ++i)
{
p = p->GetObject(ftype::GetValue(type, i));
toDo(p);
}
}
ClassifObject const * Classificator::GetObject(uint32_t type) const
{
ClassifObject const * res = nullptr;
ForEachPathObject(type, [&res](ClassifObject const * p) { res = p; });
return res;
}
std::string Classificator::GetFullObjectName(uint32_t type) const
{
std::string res;
ForEachPathObject(type, [&res](ClassifObject const * p) { res = res + p->GetName() + '|'; });
return res;
}
std::vector<std::string> Classificator::GetFullObjectNamePath(uint32_t type) const
{
std::vector<std::string> res;
ForEachPathObject(type, [&res](ClassifObject const * p) { res.push_back(p->GetName()); });
return res;
}
string Classificator::GetReadableObjectName(uint32_t type) const
{
string s = GetFullObjectName(type);
// Remove ending dummy symbol.
ASSERT(!s.empty(), ());
s.pop_back();
// Replace separator.
replace(s.begin(), s.end(), '|', '-');
return s;
}

View file

@ -0,0 +1,263 @@
#pragma once
#include "indexer/drawing_rule_def.hpp"
#include "indexer/feature_decl.hpp"
#include "indexer/scales.hpp"
#include "indexer/types_mapping.hpp"
#include "base/macros.hpp"
#include "base/stl_helpers.hpp"
#include <bitset>
#include <string>
#include <utility>
#include <vector>
class ClassifObject;
namespace ftype
{
inline uint32_t GetEmptyValue()
{
return 1;
}
void PushValue(uint32_t & type, uint8_t value);
/// @pre level < GetLevel(type).
uint8_t GetValue(uint32_t type, uint8_t level);
void PopValue(uint32_t & type);
void TruncValue(uint32_t & type, uint8_t level);
inline uint32_t Trunc(uint32_t type, uint8_t level)
{
TruncValue(type, level);
return type;
}
uint8_t GetLevel(uint32_t type);
} // namespace ftype
class ClassifObjectPtr
{
ClassifObject const * m_p;
size_t m_ind;
public:
ClassifObjectPtr() : m_p(nullptr), m_ind(0) {}
ClassifObjectPtr(ClassifObject const * p, size_t i) : m_p(p), m_ind(i) {}
ClassifObject const * get() const { return m_p; }
ClassifObject const * operator->() const { return m_p; }
explicit operator bool() const { return (m_p != nullptr); }
size_t GetIndex() const { return m_ind; }
};
class ClassifObject
{
struct LessName
{
bool operator()(ClassifObject const & r1, ClassifObject const & r2) const { return (r1.m_name < r2.m_name); }
bool operator()(ClassifObject const & r1, std::string_view r2) const { return (r1.m_name < r2); }
bool operator()(std::string_view r1, ClassifObject const & r2) const { return (r1 < r2.m_name); }
};
public:
ClassifObject() = default; // for serialization only
explicit ClassifObject(std::string s) : m_name(std::move(s)) {}
/// @name Fill from osm draw rule files.
//@{
private:
ClassifObject * AddImpl(std::string const & s);
public:
ClassifObject * Add(std::string const & s);
ClassifObject * Find(std::string const & s);
void AddDrawRule(drule::Key const & k);
//@}
/// @name Find substitution when reading osm features.
//@{
ClassifObjectPtr BinaryFind(std::string_view s) const;
//@}
void Sort();
void Swap(ClassifObject & r);
std::string const & GetName() const { return m_name; }
ClassifObject const * GetObject(size_t i) const;
std::vector<drule::Key> const & GetDrawRules() const { return m_drawRules; }
void GetSuitable(int scale, feature::GeomType gt, drule::KeysT & keys) const;
// Returns std::numeric_limits<int>::min() if there are no overlay drules.
int GetMaxOverlaysPriority() const { return m_maxOverlaysPriority; }
bool IsDrawable(int scale) const;
bool IsDrawableAny() const;
bool IsDrawableLike(feature::GeomType gt, bool emptyName = false) const;
std::pair<int, int> GetDrawScaleRange() const;
/// @name Iterate first level children only.
/// @{
template <class ToDo>
void ForEachObject(ToDo && toDo)
{
for (auto & e : m_objs)
toDo(&e);
}
template <class ToDo>
void ForEachObject(ToDo && toDo) const
{
for (auto const & e : m_objs)
toDo(e);
}
/// @}
// Recursive subtree iteration.
template <typename ToDo>
void ForEachObjectInTree(ToDo && toDo, uint32_t const start) const
{
for (size_t i = 0; i < m_objs.size(); ++i)
{
uint32_t type = start;
ftype::PushValue(type, static_cast<uint8_t>(i));
toDo(&m_objs[i], type);
m_objs[i].ForEachObjectInTree(toDo, type);
}
}
using VisibleMask = std::bitset<scales::UPPER_STYLE_SCALE + 1>;
void SetVisibilityOnScale(bool isVisible, int scale) { m_visibility.set(scale, isVisible); }
/// @name Policies for classificator tree serialization.
//@{
class BasePolicy
{
protected:
std::vector<ClassifObject *> m_stack;
ClassifObject * Current() const { return m_stack.back(); }
public:
explicit BasePolicy(ClassifObject * pRoot) { m_stack.push_back(pRoot); }
// No polymorphism here.
void Start(size_t i) { m_stack.push_back(&(Current()->m_objs[i])); }
void End() { m_stack.pop_back(); }
};
class LoadPolicy : public BasePolicy
{
typedef BasePolicy base_type;
public:
explicit LoadPolicy(ClassifObject * pRoot) : base_type(pRoot) {}
void Name(std::string const & name) { Current()->m_name = name; }
void Start(size_t i);
void EndChilds();
};
//@}
private:
std::string m_name;
std::vector<drule::Key> m_drawRules;
std::vector<ClassifObject> m_objs;
VisibleMask m_visibility;
int m_maxOverlaysPriority = std::numeric_limits<int>::min();
};
inline void swap(ClassifObject & r1, ClassifObject & r2)
{
r1.Swap(r2);
}
class Classificator
{
public:
Classificator() : m_root("world") {}
/// @name Serialization-like functions.
//@{
void ReadClassificator(std::istream & s);
void ReadTypesMapping(std::istream & s);
//@}
void Clear();
bool HasTypesMapping() const { return m_mapping.IsLoaded(); }
static constexpr uint32_t INVALID_TYPE = IndexAndTypeMapping::INVALID_TYPE;
/// @name Type by \a path in classificator tree, for example {"natural", "caostline"}.
///@{
/// @return INVALID_TYPE in case of nonexisting type
uint32_t GetTypeByPathSafe(std::vector<std::string_view> const & path) const;
/// Invokes ASSERT in case of nonexisting type
uint32_t GetTypeByPath(std::vector<std::string> const & path) const;
uint32_t GetTypeByPath(std::vector<std::string_view> const & path) const;
uint32_t GetTypeByPath(base::StringIL const & lst) const;
///@}
/// @see GetReadableObjectName().
/// @returns INVALID_TYPE in case of nonexisting type.
uint32_t GetTypeByReadableObjectName(std::string const & name) const;
uint32_t GetIndexForType(uint32_t t) const { return m_mapping.GetIndex(t); }
/// @return INVALID_TYPE if \a ind is out of bounds.
uint32_t GetTypeForIndex(uint32_t i) const { return m_mapping.GetType(i); }
bool IsTypeValid(uint32_t t) const { return m_mapping.HasIndex(t); }
inline uint32_t GetCoastType() const { return m_coastType; }
inline uint32_t GetStubType() const { return m_stubType; }
/// @name used in osm2type.cpp, not for public use.
//@{
ClassifObject const * GetRoot() const { return &m_root; }
ClassifObject * GetMutableRoot() { return &m_root; }
//@}
/// Iterate through all classificator tree.
/// Functor receives pointer to object and uint32 type.
template <typename ToDo>
void ForEachTree(ToDo && toDo) const
{
GetRoot()->ForEachObjectInTree(std::forward<ToDo>(toDo), ftype::GetEmptyValue());
}
template <typename ToDo>
void ForEachInSubtree(ToDo && toDo, uint32_t root) const
{
toDo(root);
GetObject(root)->ForEachObjectInTree([&toDo](ClassifObject const *, uint32_t c) { toDo(c); }, root);
}
ClassifObject const * GetObject(uint32_t type) const;
std::string GetFullObjectName(uint32_t type) const;
std::vector<std::string> GetFullObjectNamePath(uint32_t type) const;
/// @return Object name to show in UI (not for debug purposes).
std::string GetReadableObjectName(uint32_t type) const;
private:
template <class ToDo>
void ForEachPathObject(uint32_t type, ToDo && toDo) const;
template <typename Iter>
uint32_t GetTypeByPathImpl(Iter beg, Iter end) const;
ClassifObject m_root;
IndexAndTypeMapping m_mapping;
uint32_t m_coastType = 0;
uint32_t m_stubType = 0;
DISALLOW_COPY_AND_MOVE(Classificator);
};
Classificator & classif();

View file

@ -0,0 +1,75 @@
#include "indexer/classificator_loader.hpp"
#include "indexer/classificator.hpp"
#include "indexer/drawing_rules.hpp"
#include "indexer/map_style_reader.hpp"
#include "platform/platform.hpp"
#include "coding/reader.hpp"
#include "coding/reader_streambuf.hpp"
#include "base/logging.hpp"
#include <iostream>
#include <memory>
#include <string>
namespace
{
void ReadCommon(std::unique_ptr<Reader> classificator, std::unique_ptr<Reader> types)
{
Classificator & c = classif();
c.Clear();
{
// LOG(LINFO, ("Reading classificator"));
ReaderStreamBuf buffer(std::move(classificator));
std::istream s(&buffer);
c.ReadClassificator(s);
}
{
// LOG(LINFO, ("Reading types mapping"));
ReaderStreamBuf buffer(std::move(types));
std::istream s(&buffer);
c.ReadTypesMapping(s);
}
}
} // namespace
namespace classificator
{
void Load()
{
LOG(LDEBUG, ("Reading of classificator started"));
Platform & p = GetPlatform();
MapStyle const originMapStyle = GetStyleReader().GetCurrentStyle();
for (size_t i = 0; i < MapStyleCount; ++i)
{
auto const mapStyle = static_cast<MapStyle>(i);
// Read the merged style only if it was requested.
if (mapStyle != MapStyleMerged || originMapStyle == MapStyleMerged)
{
GetStyleReader().SetCurrentStyle(mapStyle);
ReadCommon(p.GetReader("classificator.txt"), p.GetReader("types.txt"));
drule::LoadRules();
}
}
GetStyleReader().SetCurrentStyle(originMapStyle);
LOG(LDEBUG, ("Reading of classificator finished"));
}
void LoadTypes(std::string const & classificatorFileStr, std::string const & typesFileStr)
{
ReadCommon(std::make_unique<MemReaderWithExceptions>(classificatorFileStr.data(), classificatorFileStr.size()),
std::make_unique<MemReaderWithExceptions>(typesFileStr.data(), typesFileStr.size()));
}
} // namespace classificator

View file

@ -0,0 +1,13 @@
#pragma once
#include <string>
namespace classificator
{
void Load();
// This method loads only classificator and types. It does not load and apply
// style rules. It can be used only in separate modules (like pybindings) to
// operate with number-string representations of types.
void LoadTypes(std::string const & classificatorFileStr, std::string const & typesFileStr);
} // namespace classificator

View file

@ -0,0 +1,7 @@
#include "indexer/complex/serdes.hpp"
namespace complex
{
// static
ComplexSerdes::Version const ComplexSerdes::kLatestVersion = Version::V0;
} // namespace complex

View file

@ -0,0 +1,134 @@
#pragma once
#include "indexer/complex/serdes_utils.hpp"
#include "indexer/complex/tree_node.hpp"
#include "coding/reader.hpp"
#include "coding/varint.hpp"
#include "coding/writer.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include <string>
#include <vector>
namespace complex
{
class ComplexSerdes
{
public:
using Ids = std::vector<uint32_t>;
enum class Version : uint8_t
{
// There aren't optimized serialization and deserialization. It's experimental verison.
// todo(m.andrianov): Explore better ways for serialization and deserialization.
V0,
};
static Version const kLatestVersion;
template <typename Sink>
static void Serialize(Sink & sink, tree_node::Forest<Ids> const & forest)
{
SerializeLatestVersion(sink);
Serialize(sink, kLatestVersion, forest);
}
template <typename Reader>
static void Deserialize(Reader & reader, tree_node::Forest<Ids> & forest)
{
ReaderSource<decltype(reader)> src(reader);
auto const version = DeserializeVersion(src);
Deserialize(src, version, forest);
}
template <typename Sink>
static void Serialize(Sink & sink, Version version, tree_node::Forest<Ids> const & forest)
{
switch (version)
{
case Version::V0: V0::Serialize(sink, forest); break;
default: UNREACHABLE();
}
}
template <typename Src>
static void Deserialize(Src & src, Version version, tree_node::Forest<Ids> & forest)
{
switch (version)
{
case Version::V0: V0::Deserialize(src, forest); break;
default: UNREACHABLE();
}
}
private:
class V0
{
public:
template <typename Sink>
static void Serialize(Sink & sink, tree_node::Forest<Ids> const & forest)
{
forest.ForEachTree([&](auto const & tree) { Serialize(sink, tree); });
}
template <typename Src>
static void Deserialize(Src & src, tree_node::Forest<Ids> & forest)
{
while (src.Size() > 0)
{
tree_node::types::Ptr<Ids> tree;
Deserialize(src, tree);
forest.Append(tree);
}
}
private:
template <typename Sink>
static void Serialize(Sink & sink, tree_node::types::Ptr<Ids> const & tree)
{
tree_node::PreOrderVisit(tree, [&](auto const & node)
{
coding_utils::WriteCollectionPrimitive(sink, node->GetData());
auto const size = base::checked_cast<coding_utils::CollectionSizeType>(node->GetChildren().size());
WriteVarUint(sink, size);
});
}
template <typename Src>
static void Deserialize(Src & src, tree_node::types::Ptr<Ids> & tree)
{
std::function<void(tree_node::types::Ptr<Ids> &)> deserializeTree;
deserializeTree = [&](auto & tree)
{
Ids ids;
coding_utils::ReadCollectionPrimitive(src, std::back_inserter(ids));
tree = tree_node::MakeTreeNode(std::move(ids));
auto const size = ReadVarUint<coding_utils::CollectionSizeType>(src);
tree_node::types::Ptrs<Ids> children(size);
for (auto & n : children)
{
deserializeTree(n);
n->SetParent(tree);
}
tree->SetChildren(std::move(children));
};
deserializeTree(tree);
}
};
template <typename Sink>
static void SerializeLatestVersion(Sink & sink)
{
WriteToSink(sink, base::Underlying(kLatestVersion));
}
template <typename Src>
static Version DeserializeVersion(Src & src)
{
return static_cast<Version>(ReadPrimitiveFromSource<std::underlying_type_t<Version>>(src));
}
};
} // namespace complex

View file

@ -0,0 +1,34 @@
#pragma once
#include "coding/reader.hpp"
#include "coding/varint.hpp"
#include "base/assert.hpp"
#include "base/checked_cast.hpp"
namespace coding_utils
{
// Type of collection size. Used for reading and writing collections.
using CollectionSizeType = uint64_t;
// WriteCollectionPrimitive writes collection. It uses WriteToSink function.
template <typename Sink, typename Cont>
void WriteCollectionPrimitive(Sink & sink, Cont const & container)
{
auto const contSize = base::checked_cast<CollectionSizeType>(container.size());
WriteVarUint(sink, contSize);
for (auto value : container)
WriteToSink(sink, value);
}
// ReadCollectionPrimitive reads collection. It uses ReadPrimitiveFromSource function.
template <typename Source, typename OutIt>
void ReadCollectionPrimitive(Source & src, OutIt it)
{
using ValueType = typename OutIt::container_type::value_type;
auto size = ReadVarUint<CollectionSizeType>(src);
while (size--)
*it++ = ReadPrimitiveFromSource<ValueType>(src);
}
} // namespace coding_utils

View file

@ -0,0 +1,308 @@
#pragma once
#include <algorithm>
#include <limits>
#include <memory>
#include <sstream>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/control_flow.hpp"
#include "base/stl_helpers.hpp"
namespace tree_node
{
template <typename Data>
class TreeNode;
namespace types
{
template <typename Data>
using Ptr = std::shared_ptr<TreeNode<Data>>;
template <typename Data>
using WeakPtr = std::weak_ptr<TreeNode<Data>>;
template <typename Data>
using Ptrs = std::vector<Ptr<Data>>;
} // namespace types
template <typename Data>
class TreeNode
{
public:
using Ptr = types::Ptr<Data>;
using WeakPtr = types::WeakPtr<Data>;
using Ptrs = types::Ptrs<Data>;
explicit TreeNode(Data && data) : m_data(std::forward<Data>(data)) {}
bool HasChildren() const { return !m_children.empty(); }
Ptrs const & GetChildren() const { return m_children; }
void AddChild(Ptr const & child) { m_children.push_back(child); }
void AddChildren(Ptrs && children) { std::move(std::begin(children), std::end(children), std::end(m_children)); }
void SetChildren(Ptrs && children) { m_children = std::move(children); }
void RemoveChildren() { m_children.clear(); }
template <typename Fn>
void RemoveChildrenIf(Fn && fn)
{
base::EraseIf(m_children, std::forward<Fn>(fn));
}
bool HasParent() const { return m_parent.lock() != nullptr; }
Ptr GetParent() const { return m_parent.lock(); }
void SetParent(Ptr const & parent) { m_parent = parent; }
Data & GetData() { return m_data; }
Data const & GetData() const { return m_data; }
private:
Data m_data;
Ptrs m_children;
WeakPtr m_parent;
};
template <typename Data>
decltype(auto) MakeTreeNode(Data && data)
{
return std::make_shared<TreeNode<Data>>(std::forward<Data>(data));
}
template <typename Data>
void Link(types::Ptr<Data> const & node, types::Ptr<Data> const & parent)
{
parent->AddChild(node);
node->SetParent(parent);
}
template <typename Data>
void Unlink(types::Ptr<Data> const & node, types::Ptr<Data> const & parent)
{
parent->RemoveChildrenIf([&](auto const & n) { return n == node; });
node->SetParent(nullptr);
}
template <typename Data>
size_t GetDepth(types::Ptr<Data> node)
{
size_t depth = 0;
while (node)
{
node = node->GetParent();
++depth;
}
return depth;
}
template <typename Data, typename Fn>
void PreOrderVisit(types::Ptr<Data> const & node, Fn && fn)
{
base::ControlFlowWrapper<Fn> wrapper(std::forward<Fn>(fn));
std::function<base::ControlFlow(types::Ptr<Data> const &)> preOrderVisitDetail;
preOrderVisitDetail = [&](auto const & node)
{
if (wrapper(node) == base::ControlFlow::Break)
return base::ControlFlow::Break;
for (auto const & ch : node->GetChildren())
if (preOrderVisitDetail(ch) == base::ControlFlow::Break)
return base::ControlFlow::Break;
return base::ControlFlow::Continue;
};
preOrderVisitDetail(node);
}
template <typename Data, typename Fn>
void PostOrderVisit(types::Ptr<Data> const & node, Fn && fn)
{
base::ControlFlowWrapper<Fn> wrapper(std::forward<Fn>(fn));
std::function<base::ControlFlow(types::Ptr<Data> const &)> postOrderVisitDetail;
postOrderVisitDetail = [&](auto const & node)
{
for (auto const & ch : node->GetChildren())
if (postOrderVisitDetail(ch) == base::ControlFlow::Break)
return base::ControlFlow::Break;
return wrapper(node);
};
postOrderVisitDetail(node);
}
template <typename Data, typename Fn>
void ForEach(types::Ptr<Data> const & node, Fn && fn)
{
PreOrderVisit(node, [&](auto const & node) { fn(node->GetData()); });
}
template <typename Data, typename Fn>
decltype(auto) FindIf(types::Ptr<Data> const & node, Fn && fn)
{
types::Ptr<Data> res = nullptr;
PreOrderVisit(node, [&](auto const & node)
{
if (fn(node->GetData()))
{
res = node;
return base::ControlFlow::Break;
}
return base::ControlFlow::Continue;
});
return res;
}
template <typename Data>
size_t Size(types::Ptr<Data> const & node)
{
size_t size = 0;
PreOrderVisit(node, [&](auto const &) { ++size; });
return size;
}
template <typename Data>
decltype(auto) GetRoot(types::Ptr<Data> node)
{
while (node->HasParent())
node = node->GetParent();
return node;
}
template <typename Data>
decltype(auto) GetPathToRoot(types::Ptr<Data> node)
{
types::Ptrs<Data> path;
while (node)
{
path.emplace_back(node);
node = node->GetParent();
}
return path;
}
template <typename Data, typename Fn>
types::Ptr<typename std::invoke_result<Fn, Data const &>::type> TransformToTree(types::Ptr<Data> const & node, Fn && fn)
{
auto n = MakeTreeNode(fn(node->GetData()));
for (auto const & ch : node->GetChildren())
n->AddChild(TransformToTree(ch, fn));
return n;
}
template <typename Data>
bool IsEqual(types::Ptr<Data> const & lhs, types::Ptr<Data> const & rhs)
{
if (!(lhs->GetData() == rhs->GetData()))
return false;
auto const & lhsCh = lhs->GetChildren();
auto const & rhsCh = rhs->GetChildren();
if (lhsCh.size() != rhsCh.size())
return false;
for (size_t i = 0; i < lhsCh.size(); ++i)
if (!IsEqual(lhsCh[i], rhsCh[i]))
return false;
return true;
}
template <typename Data, typename Fn>
size_t CountIf(types::Ptr<Data> const & node, Fn && fn)
{
size_t count = 0;
PreOrderVisit(node, [&](auto const & node)
{
if (fn(node->GetData()))
++count;
});
return count;
}
template <typename Data>
void Print(types::Ptr<Data> const & node, std::ostream & stream, std::string prefix = "", bool isTail = true)
{
stream << prefix;
if (isTail)
{
stream << "└───";
prefix += " ";
}
else
{
stream << "├───";
prefix += "";
}
auto str = DebugPrint(node->GetData());
std::replace(std::begin(str), std::end(str), '\n', '|');
stream << str << '\n';
auto const & children = node->GetChildren();
size_t size = children.size();
for (size_t i = 0; i < size; ++i)
Print(children[i], stream, prefix, i == size - 1 /* isTail */);
}
template <typename Data>
std::string DebugPrint(types::Ptr<Data> const & node)
{
std::stringstream stream;
Print(node, stream);
return stream.str();
}
template <typename Data>
class Forest
{
public:
bool operator==(Forest<Data> const & other) const
{
auto const size = Size();
if (size != other.Size())
return false;
for (size_t i = 0; i < size; ++i)
if (!IsEqual(m_trees[i], other.m_trees[i]))
return false;
return true;
}
bool operator!=(Forest<Data> const & other) const { return !(*this == other); }
void Append(types::Ptr<Data> const & tree) { m_trees.emplace_back(tree); }
size_t Size() const { return m_trees.size(); }
template <typename Fn>
void ForEachTree(Fn && fn) const
{
base::ControlFlowWrapper<Fn> wrapper(std::forward<Fn>(fn));
for (auto const & tree : m_trees)
if (wrapper(tree) == base::ControlFlow::Break)
return;
}
private:
types::Ptrs<Data> m_trees;
};
template <typename Data, typename Fn>
decltype(auto) FindIf(Forest<Data> const & forest, Fn && fn)
{
types::Ptr<Data> res = nullptr;
forest.ForEachTree([&](auto const & tree)
{
res = FindIf(tree, fn);
return res ? base::ControlFlow::Break : base::ControlFlow::Continue;
});
return res;
}
template <typename Data>
std::string DebugPrint(Forest<Data> const & forest)
{
std::stringstream stream;
forest.ForEachTree([&](auto const & tree) { stream << DebugPrint(tree) << '\n'; });
return stream.str();
}
} // namespace tree_node

55
libs/indexer/cuisines.cpp Normal file
View file

@ -0,0 +1,55 @@
#include "indexer/cuisines.hpp"
#include "indexer/classificator.hpp"
#include "platform/localization.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
namespace osm
{
using namespace std;
Cuisines::Cuisines()
{
auto const & c = classif();
/// @todo Better to have GetObjectByPath().
// Assume that "cuisine" hierarchy is one-level.
uint32_t const cuisineType = c.GetTypeByPath({"cuisine"});
c.GetObject(cuisineType)
->ForEachObject([this](ClassifObject const & o)
{
auto const & name = o.GetName();
m_allCuisines.emplace_back(name, platform::GetLocalizedTypeName("cuisine-" + name));
});
sort(m_allCuisines.begin(), m_allCuisines.end(),
[](auto const & lhs, auto const & rhs) { return lhs.second < rhs.second; });
}
// static
Cuisines const & Cuisines::Instance()
{
static Cuisines instance;
return instance;
}
string const & Cuisines::Translate(string const & singleCuisine) const
{
static string const kEmptyString;
auto const it = find_if(m_allCuisines.begin(), m_allCuisines.end(),
[&](auto const & cuisine) { return cuisine.first == singleCuisine; });
if (it != m_allCuisines.end())
return it->second;
return kEmptyString;
}
AllCuisines const & Cuisines::AllSupportedCuisines() const
{
return m_allCuisines;
}
} // namespace osm

24
libs/indexer/cuisines.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <utility>
#include <vector>
namespace osm
{
using AllCuisines = std::vector<std::pair<std::string, std::string>>;
// This class IS thread-safe.
class Cuisines
{
public:
static Cuisines const & Instance();
std::string const & Translate(std::string const & singleCuisine) const;
AllCuisines const & AllSupportedCuisines() const;
private:
Cuisines();
AllCuisines m_allCuisines;
};
} // namespace osm

View file

@ -0,0 +1,46 @@
#include "custom_keyvalue.hpp"
#include "coding/reader.hpp"
#include "coding/varint.hpp"
#include "coding/writer.hpp"
#include <sstream>
namespace indexer
{
CustomKeyValue::CustomKeyValue(std::string_view buffer)
{
MemReader reader(buffer.data(), buffer.size());
ReaderSource src(reader);
while (src.Size() > 0)
{
uint8_t const type = ReadPrimitiveFromSource<uint8_t>(src);
m_vals.emplace_back(type, ReadVarUint<uint64_t>(src));
}
}
std::string CustomKeyValue::ToString() const
{
std::string res;
MemWriter<std::string> writer(res);
for (auto const & v : m_vals)
{
WriteToSink(writer, v.first);
WriteVarUint(writer, v.second);
}
return res;
}
std::string DebugPrint(CustomKeyValue const & kv)
{
std::ostringstream ss;
ss << "CustomKeyValue [";
for (auto const & v : kv.m_vals)
ss << "(" << v.first << ", " << v.second << "), ";
ss << "]";
return ss.str();
}
} // namespace indexer

View file

@ -0,0 +1,42 @@
#pragma once
#include "base/buffer_vector.hpp"
#include <optional>
#include <string_view>
#include <utility>
namespace indexer
{
class CustomKeyValue
{
buffer_vector<std::pair<uint8_t, uint64_t>, 2> m_vals;
public:
CustomKeyValue() = default;
explicit CustomKeyValue(std::string_view buffer);
void Add(uint8_t k, uint64_t v) { m_vals.emplace_back(k, v); }
std::optional<uint64_t> Get(uint8_t k) const
{
for (auto const & v : m_vals)
if (v.first == k)
return v.second;
return {};
}
uint64_t GetSure(uint8_t k) const
{
auto const res = Get(k);
ASSERT(res, ());
return *res;
}
std::string ToString() const;
friend std::string DebugPrint(CustomKeyValue const & kv);
};
} // namespace indexer

View file

@ -0,0 +1,49 @@
#pragma once
#include "coding/reader.hpp"
namespace feature
{
struct DatSectionHeader
{
public:
enum class Version : uint8_t
{
V0 = 0,
V1, // 2025.06, get some free bits in Feature::Header2
Latest = V1
};
template <typename Sink>
void Serialize(Sink & sink) const
{
WriteToSink(sink, static_cast<uint8_t>(m_version));
WriteToSink(sink, m_featuresOffset);
WriteToSink(sink, m_featuresSize);
}
template <typename Source>
void Read(Source & source)
{
m_version = static_cast<Version>(ReadPrimitiveFromSource<uint8_t>(source));
CHECK(Version::V0 <= m_version && m_version <= Version::Latest, (m_version));
m_featuresOffset = ReadPrimitiveFromSource<uint32_t>(source);
m_featuresSize = ReadPrimitiveFromSource<uint32_t>(source);
}
Version m_version = Version::Latest;
// All offsets are relative to the start of the section (offset of header is zero).
uint32_t m_featuresOffset = 0;
uint32_t m_featuresSize = 0;
};
inline std::string DebugPrint(DatSectionHeader::Version v)
{
switch (v)
{
case DatSectionHeader::Version::V0: return "V0";
default: return "Latest(V1)";
}
}
} // namespace feature

View file

@ -0,0 +1,136 @@
#include "indexer/data_header.hpp"
#include "indexer/scales.hpp"
#include "platform/mwm_version.hpp"
#include "platform/platform.hpp"
#include "coding/file_writer.hpp"
#include "coding/files_container.hpp"
#include "coding/point_coding.hpp"
#include "coding/varint.hpp"
#include "defines.hpp"
namespace feature
{
namespace
{
template <class Sink, class Cont>
void SaveBytes(Sink & sink, Cont const & cont)
{
static_assert(sizeof(typename Cont::value_type) == 1);
auto const count = static_cast<uint32_t>(cont.size());
WriteVarUint(sink, count);
if (count > 0)
sink.Write(&cont[0], count);
}
template <class Source, class Cont>
void LoadBytes(Source & src, Cont & cont)
{
static_assert(sizeof(typename Cont::value_type) == 1);
ASSERT(cont.empty(), ());
auto const count = ReadVarUint<uint32_t>(src);
if (count > 0)
{
cont.resize(count);
src.Read(&cont[0], count);
}
}
} // namespace
DataHeader::DataHeader(std::string const & fileName) : DataHeader((FilesContainerR(GetPlatform().GetReader(fileName))))
{}
DataHeader::DataHeader(FilesContainerR const & cont)
{
Load(cont);
}
serial::GeometryCodingParams DataHeader::GetGeometryCodingParams(int scaleIndex) const
{
return {static_cast<uint8_t>(m_codingParams.GetCoordBits() - (m_scales.back() - m_scales[scaleIndex]) / 2),
m_codingParams.GetBasePointUint64()};
}
m2::RectD DataHeader::GetBounds() const
{
return Int64ToRectObsolete(m_bounds, m_codingParams.GetCoordBits());
}
void DataHeader::SetBounds(m2::RectD const & r)
{
m_bounds = RectToInt64Obsolete(r, m_codingParams.GetCoordBits());
}
std::pair<int, int> DataHeader::GetScaleRange() const
{
using namespace scales;
int const low = 0;
int const high = GetUpperScale();
int const worldH = GetUpperWorldScale();
MapType const type = GetType();
switch (type)
{
case MapType::World: return {low, worldH};
case MapType::WorldCoasts: return {low, high};
default:
ASSERT_EQUAL(type, MapType::Country, ());
return {worldH + 1, high};
// Uncomment this to test countries drawing in all scales.
// return {1, high};
}
}
void DataHeader::Save(FileWriter & w) const
{
m_codingParams.Save(w);
WriteVarInt(w, m_bounds.first);
WriteVarInt(w, m_bounds.second);
SaveBytes(w, m_scales);
SaveBytes(w, m_langs);
WriteVarInt(w, static_cast<int32_t>(m_type));
}
void DataHeader::Load(FilesContainerR const & cont)
{
Load(cont.GetReader(HEADER_FILE_TAG));
}
void DataHeader::Load(ModelReaderPtr const & r)
{
ReaderSource<ModelReaderPtr> src(r);
m_codingParams.Load(src);
m_bounds.first = ReadVarInt<int64_t>(src);
m_bounds.second = ReadVarInt<int64_t>(src);
LoadBytes(src, m_scales);
LoadBytes(src, m_langs);
m_type = static_cast<MapType>(ReadVarInt<int32_t>(src));
if (m_type < MapType::World || m_type > MapType::Country || m_scales.size() != kMaxScalesCount)
MYTHROW(CorruptedMwmFile, (r.GetName()));
}
std::string DebugPrint(DataHeader::MapType type)
{
switch (type)
{
case DataHeader::MapType::World: return "World";
case DataHeader::MapType::WorldCoasts: return "WorldCoasts";
case DataHeader::MapType::Country: return "Country";
}
UNREACHABLE();
}
} // namespace feature

View file

@ -0,0 +1,80 @@
#pragma once
#include "coding/geometry_coding.hpp"
#include "geometry/rect2d.hpp"
#include "base/assert.hpp"
#include "base/buffer_vector.hpp"
#include <string>
#include <utility>
class FilesContainerR;
class FileWriter;
class ModelReaderPtr;
namespace feature
{
class DataHeader
{
public:
/// @note An order is important here, @see Load function.
enum class MapType : uint8_t
{
World,
WorldCoasts,
Country
};
/// Max possible geometry scales. @see arrays in feature_impl.hpp
static constexpr size_t kMaxScalesCount = 4;
DataHeader() = default;
explicit DataHeader(std::string const & fileName);
explicit DataHeader(FilesContainerR const & cont);
inline void SetGeometryCodingParams(serial::GeometryCodingParams const & cp) { m_codingParams = cp; }
inline serial::GeometryCodingParams const & GetDefGeometryCodingParams() const { return m_codingParams; }
serial::GeometryCodingParams GetGeometryCodingParams(int scaleIndex) const;
m2::RectD GetBounds() const;
void SetBounds(m2::RectD const & r);
template <size_t N>
void SetScales(int const (&arr)[N])
{
m_scales.assign(arr, arr + N);
}
size_t GetScalesCount() const { return m_scales.size(); }
int GetScale(size_t i) const { return static_cast<int>(m_scales[i]); }
int GetLastScale() const { return m_scales.back(); }
std::pair<int, int> GetScaleRange() const;
void Save(FileWriter & w) const;
void Load(FilesContainerR const & cont);
void SetType(MapType t) { m_type = t; }
MapType GetType() const { return m_type; }
private:
void Load(ModelReaderPtr const & r);
MapType m_type = MapType::World;
serial::GeometryCodingParams m_codingParams;
// Rect around region border. Features which cross region border may cross this rect.
std::pair<int64_t, int64_t> m_bounds;
buffer_vector<uint8_t, kMaxScalesCount> m_scales;
// Is not used now (see data/countries_meta.txt). Can be reused for something else.
buffer_vector<uint8_t, 2> m_langs;
};
std::string DebugPrint(DataHeader::MapType type);
} // namespace feature

View file

@ -0,0 +1,311 @@
#include "indexer/data_source.hpp"
#include "indexer/scale_index.hpp"
#include "indexer/unique_index.hpp"
#include "platform/mwm_version.hpp"
#include <algorithm>
using platform::CountryFile;
using platform::LocalCountryFile;
namespace
{
class ReadMWMFunctor
{
public:
template <class FnT>
ReadMWMFunctor(FeatureSourceFactory const & factory, FnT && fn) : m_factory(factory)
, m_fn(std::forward<FnT>(fn))
{}
ReadMWMFunctor(FeatureSourceFactory const & factory, DataSource::FeatureCallback const & fn,
DataSource::StopSearchCallback stop = {})
: m_factory(factory)
, m_stop(std::move(stop))
{
m_fn = [&fn](uint32_t index, FeatureSource & src) { ReadFeatureType(fn, src, index); };
}
// Reads features visible at |scale| covered by |cov| from mwm and applies |m_fn| to them.
// Feature reading process consists of two steps: untouched (original) features reading and
// touched (created, edited etc.) features reading.
void operator()(MwmSet::MwmHandle const & handle, covering::CoveringGetter & cov, int scale) const
{
auto src = m_factory(handle);
MwmValue const * mwmValue = handle.GetValue();
if (mwmValue)
{
// Untouched (original) features reading. Applies covering |cov| to geometry index, gets
// feature ids from it, gets untouched features by ids from |src| and applies |m_fn| by
// ProcessElement.
feature::DataHeader const & header = mwmValue->GetHeader();
CheckUniqueIndexes checkUnique;
// In case of WorldCoasts we should pass correct scale in ForEachInIntervalAndScale.
auto const lastScale = header.GetLastScale();
if (scale > lastScale)
scale = lastScale;
// Use last coding scale for covering (see index_builder.cpp).
covering::Intervals const & intervals = cov.Get<RectId::DEPTH_LEVELS>(lastScale);
ScaleIndex<ModelReaderPtr> index(mwmValue->m_cont.GetReader(INDEX_FILE_TAG));
// iterate through intervals
for (auto const & i : intervals)
{
index.ForEachInIntervalAndScale(i.first, i.second, scale, [&](uint64_t /* key */, uint32_t value)
{
if (checkUnique(value))
m_fn(value, *src);
});
if (m_stop && m_stop())
break;
}
}
// Check created features container.
// Need to do it on a per-mwm basis, because Drape relies on features in a sorted order.
// Touched (created, edited) features reading.
auto f = [&](uint32_t i) { m_fn(i, *src); };
src->ForEachAdditionalFeature(cov.GetRect(), scale, f);
}
private:
FeatureSourceFactory const & m_factory;
std::function<void(uint32_t, FeatureSource & src)> m_fn;
DataSource::StopSearchCallback m_stop;
private:
static void ReadFeatureType(DataSource::FeatureCallback const & fn, FeatureSource & src, uint32_t index)
{
std::unique_ptr<FeatureType> ft;
switch (src.GetFeatureStatus(index))
{
case FeatureStatus::Deleted:
case FeatureStatus::Obsolete: return;
case FeatureStatus::Created:
case FeatureStatus::Modified:
{
ft = src.GetModifiedFeature(index);
break;
}
case FeatureStatus::Untouched:
{
ft = src.GetOriginalFeature(index);
break;
}
}
CHECK(ft, ());
fn(*ft);
}
};
} // namespace
// FeaturesLoaderGuard ---------------------------------------------------------------------
std::string FeaturesLoaderGuard::GetCountryFileName() const
{
ASSERT(m_handle.IsAlive(), ());
return m_handle.GetValue()->GetCountryFileName();
}
int64_t FeaturesLoaderGuard::GetVersion() const
{
ASSERT(m_handle.IsAlive(), ());
return m_handle.GetInfo()->GetVersion();
}
bool FeaturesLoaderGuard::IsWorld() const
{
ASSERT(m_handle.IsAlive(), ());
return m_handle.GetValue()->GetHeader().GetType() == feature::DataHeader::MapType::World;
}
std::unique_ptr<FeatureType> FeaturesLoaderGuard::GetOriginalOrEditedFeatureByIndex(uint32_t index) const
{
ASSERT(m_handle.IsAlive(), ());
ASSERT_NOT_EQUAL(m_source->GetFeatureStatus(index), FeatureStatus::Created, ());
return GetFeatureByIndex(index);
}
std::unique_ptr<FeatureType> FeaturesLoaderGuard::GetFeatureByIndex(uint32_t index) const
{
ASSERT(m_handle.IsAlive(), ());
ASSERT_NOT_EQUAL(FeatureStatus::Deleted, m_source->GetFeatureStatus(index),
("Deleted feature was cached. It should not be here. Please review your code."));
auto ft = m_source->GetModifiedFeature(index);
if (ft)
return ft;
return GetOriginalFeatureByIndex(index);
}
std::unique_ptr<FeatureType> FeaturesLoaderGuard::GetOriginalFeatureByIndex(uint32_t index) const
{
return m_handle.IsAlive() ? m_source->GetOriginalFeature(index) : nullptr;
}
// DataSource ----------------------------------------------------------------------------------
std::unique_ptr<MwmInfo> DataSource::CreateInfo(platform::LocalCountryFile const & localFile) const
{
MwmValue value(localFile);
feature::DataHeader const & h = value.GetHeader();
auto info = std::make_unique<MwmInfoEx>();
info->m_bordersRect = h.GetBounds();
auto const scaleR = h.GetScaleRange();
info->m_minScale = static_cast<uint8_t>(scaleR.first);
info->m_maxScale = static_cast<uint8_t>(scaleR.second);
info->m_version = value.GetMwmVersion();
if (value.m_cont.IsExist(REGION_INFO_FILE_TAG))
{
ReaderSource<FilesContainerR::TReader> src(value.m_cont.GetReader(REGION_INFO_FILE_TAG));
info->m_data.Deserialize(src);
}
return info;
}
std::unique_ptr<MwmValue> DataSource::CreateValue(MwmInfo & info) const
{
platform::LocalCountryFile const & localFile = info.GetLocalFile();
auto p = std::make_unique<MwmValue>(localFile);
p->SetTable(dynamic_cast<MwmInfoEx &>(info));
p->m_metaDeserializer = indexer::MetadataDeserializer::Load(p->m_cont);
CHECK(p->m_metaDeserializer, ());
return p;
}
std::pair<MwmSet::MwmId, MwmSet::RegResult> DataSource::RegisterMap(LocalCountryFile const & localFile)
{
return Register(localFile);
}
bool DataSource::DeregisterMap(CountryFile const & countryFile)
{
return Deregister(countryFile);
}
void DataSource::ForEachInIntervals(ReaderCallback const & fn, covering::CoveringMode mode, m2::RectD const & rect,
int scale) const
{
std::vector<std::shared_ptr<MwmInfo>> mwms;
GetMwmsInfo(mwms);
covering::CoveringGetter cov(rect, mode);
MwmId worldID[2];
for (auto const & info : mwms)
{
if (info->m_minScale <= scale && scale <= info->m_maxScale && rect.IsIntersect(info->m_bordersRect))
{
MwmId const mwmId(info);
switch (info->GetType())
{
case MwmInfo::COUNTRY: fn(GetMwmHandleById(mwmId), cov, scale); break;
case MwmInfo::COASTS: worldID[0] = mwmId; break;
case MwmInfo::WORLD: worldID[1] = mwmId; break;
}
}
}
if (worldID[0].IsAlive())
fn(GetMwmHandleById(worldID[0]), cov, scale);
if (worldID[1].IsAlive())
fn(GetMwmHandleById(worldID[1]), cov, scale);
}
void DataSource::ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale,
covering::CoveringMode mode /* = covering::ViewportWithLowLevels */) const
{
auto readFeatureId = [&f](uint32_t index, FeatureSource & src)
{
if (src.GetFeatureStatus(index) != FeatureStatus::Deleted)
f({src.GetMwmId(), index});
};
ReadMWMFunctor readFunctor(*m_factory, readFeatureId);
ForEachInIntervals(readFunctor, mode, rect, scale);
}
void DataSource::ForEachInRect(FeatureCallback const & f, m2::RectD const & rect, int scale) const
{
ReadMWMFunctor readFunctor(*m_factory, f);
ForEachInIntervals(readFunctor, covering::ViewportWithLowLevels, rect, scale);
}
void DataSource::ForClosestToPoint(FeatureCallback const & f, StopSearchCallback const & stop,
m2::PointD const & center, double sizeM, int scale) const
{
auto const rect = mercator::RectByCenterXYAndSizeInMeters(center, sizeM);
ReadMWMFunctor readFunctor(*m_factory, f, stop);
ForEachInIntervals(readFunctor, covering::CoveringMode::Spiral, rect, scale);
}
void DataSource::ForEachInScale(FeatureCallback const & f, int scale) const
{
ReadMWMFunctor readFunctor(*m_factory, f);
ForEachInIntervals(readFunctor, covering::FullCover, m2::RectD::GetInfiniteRect(), scale);
}
void DataSource::ForEachInRectForMWM(FeatureCallback const & f, m2::RectD const & rect, int scale,
MwmId const & id) const
{
MwmHandle const handle = GetMwmHandleById(id);
if (handle.IsAlive())
{
covering::CoveringGetter cov(rect, covering::ViewportWithLowLevels);
ReadMWMFunctor(*m_factory, f)(handle, cov, scale);
}
}
void DataSource::ReadFeatures(FeatureCallback const & fn, std::vector<FeatureID> const & features) const
{
ASSERT(is_sorted(features.begin(), features.end()), ());
auto fidIter = features.begin();
auto const endIter = features.end();
while (fidIter != endIter)
{
MwmId const & id = fidIter->m_mwmId;
MwmHandle const handle = GetMwmHandleById(id);
if (handle.IsAlive())
{
// Prepare features reading.
auto src = (*m_factory)(handle);
do
{
auto const fts = src->GetFeatureStatus(fidIter->m_index);
ASSERT_NOT_EQUAL(FeatureStatus::Deleted, fts,
("Deleted feature was cached. It should not be here. Please review your code."));
std::unique_ptr<FeatureType> ft;
if (fts == FeatureStatus::Modified || fts == FeatureStatus::Created)
ft = src->GetModifiedFeature(fidIter->m_index);
else
ft = src->GetOriginalFeature(fidIter->m_index);
CHECK(ft, ());
fn(*ft);
}
while (++fidIter != endIter && id == fidIter->m_mwmId);
}
else
{
// Skip unregistered mwm files.
while (++fidIter != endIter && id == fidIter->m_mwmId)
;
}
}
}

View file

@ -0,0 +1,109 @@
#pragma once
#include "indexer/feature_covering.hpp"
#include "indexer/feature_source.hpp"
#include "indexer/mwm_set.hpp"
#include <functional>
#include <memory>
#include <utility>
#include <vector>
class DataSource : public MwmSet
{
public:
using FeatureCallback = std::function<void(FeatureType &)>;
using FeatureIdCallback = std::function<void(FeatureID const &)>;
using StopSearchCallback = std::function<bool(void)>;
/// Registers a new map.
std::pair<MwmId, RegResult> RegisterMap(platform::LocalCountryFile const & localFile);
/// Deregisters a map from internal records.
///
/// \param countryFile A countryFile denoting a map to be deregistered.
/// \return True if the map was successfully deregistered. If map is locked
/// now, returns false.
bool DeregisterMap(platform::CountryFile const & countryFile);
void ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale,
covering::CoveringMode mode = covering::ViewportWithLowLevels) const;
void ForEachInRect(FeatureCallback const & f, m2::RectD const & rect, int scale) const;
// Calls |f| for features closest to |center| until |stopCallback| returns true or distance
// |sizeM| from has been reached. Then for EditableDataSource calls |f| for each edited feature
// inside square with center |center| and side |2 * sizeM|. Edited features are not in the same
// hierarchy and there is no fast way to merge frozen and edited features.
void ForClosestToPoint(FeatureCallback const & f, StopSearchCallback const & stopCallback, m2::PointD const & center,
double sizeM, int scale) const;
void ForEachInScale(FeatureCallback const & f, int scale) const;
void ForEachInRectForMWM(FeatureCallback const & f, m2::RectD const & rect, int scale, MwmId const & id) const;
// "features" must be sorted using FeatureID::operator< as predicate.
void ReadFeatures(FeatureCallback const & fn, std::vector<FeatureID> const & features) const;
void ReadFeature(FeatureCallback const & fn, FeatureID const & feature) const { return ReadFeatures(fn, {feature}); }
std::unique_ptr<FeatureSource> CreateFeatureSource(DataSource::MwmHandle const & handle) const
{
return (*m_factory)(handle);
}
protected:
using ReaderCallback =
std::function<void(MwmSet::MwmHandle const & handle, covering::CoveringGetter & cov, int scale)>;
explicit DataSource(std::unique_ptr<FeatureSourceFactory> factory) : m_factory(std::move(factory)) {}
void ForEachInIntervals(ReaderCallback const & fn, covering::CoveringMode mode, m2::RectD const & rect,
int scale) const;
/// @name MwmSet overrides
/// @{
std::unique_ptr<MwmInfo> CreateInfo(platform::LocalCountryFile const & localFile) const override;
std::unique_ptr<MwmValue> CreateValue(MwmInfo & info) const override;
/// @}
private:
std::unique_ptr<FeatureSourceFactory> m_factory;
};
// DataSource which operates with features from mwm file and does not support features creation
// deletion or modification.
class FrozenDataSource : public DataSource
{
public:
FrozenDataSource() : DataSource(std::make_unique<FeatureSourceFactory>()) {}
};
/// Guard for loading features from particular MWM by demand.
/// @note If you need to work with FeatureType from different threads you need to use
/// a unique FeaturesLoaderGuard instance for every thread.
/// For an example of concurrent extracting feature details please see ConcurrentFeatureParsingTest.
class FeaturesLoaderGuard
{
public:
FeaturesLoaderGuard(DataSource const & dataSource, DataSource::MwmId const & id)
: m_handle(dataSource.GetMwmHandleById(id))
, m_source(dataSource.CreateFeatureSource(m_handle))
{
// FeaturesLoaderGuard is always created in-place, so MWM should always be alive.
ASSERT(id.IsAlive(), ());
}
MwmSet::MwmId const & GetId() const { return m_handle.GetId(); }
MwmSet::MwmHandle const & GetHandle() const { return m_handle; }
std::string GetCountryFileName() const;
int64_t GetVersion() const;
bool IsWorld() const;
/// Editor core only method, to get 'untouched', original version of feature.
std::unique_ptr<FeatureType> GetOriginalFeatureByIndex(uint32_t index) const;
std::unique_ptr<FeatureType> GetOriginalOrEditedFeatureByIndex(uint32_t index) const;
/// Everyone, except Editor core, should use this method.
std::unique_ptr<FeatureType> GetFeatureByIndex(uint32_t index) const;
size_t GetNumFeatures() const { return m_source->GetNumFeatures(); }
private:
MwmSet::MwmHandle m_handle;
std::unique_ptr<FeatureSource> m_source;
};

View file

@ -0,0 +1,47 @@
#include "indexer/data_source_helpers.hpp"
#include "indexer/data_source.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/scales.hpp"
namespace indexer
{
using namespace std;
void ForEachFeatureAtPoint(DataSource const & dataSource, function<void(FeatureType &)> && fn,
m2::PointD const & mercator, double toleranceInMeters)
{
double constexpr kSelectRectWidthInMeters = 1.1;
double constexpr kMetersToLinearFeature = 3;
int constexpr kScale = scales::GetUpperScale();
m2::RectD const rect = mercator::RectByCenterXYAndSizeInMeters(mercator, kSelectRectWidthInMeters);
auto const emitter = [&fn, &rect, &mercator, toleranceInMeters](FeatureType & ft)
{
switch (ft.GetGeomType())
{
case feature::GeomType::Point:
if (rect.IsPointInside(ft.GetCenter()))
fn(ft);
break;
case feature::GeomType::Line:
if (feature::GetMinDistanceMeters(ft, mercator) < kMetersToLinearFeature)
fn(ft);
break;
case feature::GeomType::Area:
{
auto limitRect = ft.GetLimitRect(kScale);
// Be a little more tolerant. When used by editor mercator is given
// with some error, so we must extend limit rect a bit.
limitRect.Inflate(kMwmPointAccuracy, kMwmPointAccuracy);
if (limitRect.IsPointInside(mercator) && feature::GetMinDistanceMeters(ft, mercator) <= toleranceInMeters)
fn(ft);
}
break;
case feature::GeomType::Undefined: ASSERT(false, ("case feature::GeomType::Undefined")); break;
}
};
dataSource.ForEachInRect(emitter, rect, kScale);
}
} // namespace indexer

View file

@ -0,0 +1,14 @@
#pragma once
#include "geometry/point2d.hpp"
#include <functional>
class DataSource;
class FeatureType;
namespace indexer
{
void ForEachFeatureAtPoint(DataSource const & dataSource, std::function<void(FeatureType &)> && fn,
m2::PointD const & mercator, double toleranceInMeters = 0.0);
}

View file

@ -0,0 +1,212 @@
#pragma once
#include "indexer/cell_id.hpp"
#include "indexer/cell_value_pair.hpp"
#include "indexer/classificator.hpp"
#include "indexer/drawing_rule_def.hpp"
#include "indexer/feature_data.hpp"
#include "indexer/feature_visibility.hpp"
#include "indexer/scales.hpp"
#include "geometry/point2d.hpp"
#include "geometry/rect2d.hpp"
#include "geometry/screenbase.hpp"
#include "geometry/tree4d.hpp"
#include "std/target_os.hpp"
#include <algorithm>
#include <functional>
#include <vector>
namespace covering
{
double constexpr kPOIDisplacementRadiusPixels = 80.;
// Displacement radius in pixels * half of the world in degrees / meaned graphics tile size.
// So average displacement radius will be: this / tiles in row count.
double constexpr kPOIDisplacementRadiusMultiplier = kPOIDisplacementRadiusPixels * 180. / 512.;
class CellFeatureBucketTuple
{
public:
using CellFeaturePair = CellValuePair<uint32_t>;
CellFeatureBucketTuple() : m_bucket(0) {}
CellFeatureBucketTuple(CellFeaturePair const & p, uint32_t bucket) : m_pair(p), m_bucket(bucket) {}
bool operator<(CellFeatureBucketTuple const & rhs) const
{
if (m_bucket != rhs.m_bucket)
return m_bucket < rhs.m_bucket;
return m_pair < rhs.m_pair;
}
CellFeaturePair const & GetCellFeaturePair() const { return m_pair; }
uint32_t GetBucket() const { return m_bucket; }
private:
CellFeaturePair m_pair;
uint32_t m_bucket;
};
static_assert(sizeof(CellFeatureBucketTuple) == 16);
static_assert(std::is_trivially_copyable<CellFeatureBucketTuple>::value);
/// Displacement manager filters incoming single-point features to simplify runtime
/// feature visibility displacement.
template <typename Sorter>
class DisplacementManager
{
public:
using CellFeaturePair = CellFeatureBucketTuple::CellFeaturePair;
explicit DisplacementManager(Sorter & sorter) : m_sorter(sorter) {}
/// Add feature at bucket (zoom) to displaceable queue if possible. Pass to bucket otherwise.
template <typename Feature>
void Add(std::vector<int64_t> const & cells, uint32_t bucket, Feature & ft, uint32_t index)
{
// Add to displaceable storage if we need to displace POI.
if (bucket != scales::GetUpperScale() && IsDisplaceable(ft))
{
m_storage.emplace_back(cells, ft, index, bucket);
return;
}
// Pass feature to the index otherwise.
for (auto const & cell : cells)
m_sorter(CellFeatureBucketTuple(CellFeaturePair(cell, index), bucket));
}
/// Check features intersection and supress drawing of intersected features.
/// As a result some features may have bigger scale parameter than style describes.
/// But every feature has MaxScale at least.
/// After all features passed to sorter.
void Displace()
{
m4::Tree<DisplaceableNode> acceptedNodes;
// Sort in priority descend mode.
std::sort(m_storage.begin(), m_storage.end(), std::greater<DisplaceableNode>());
for (auto const & node : m_storage)
{
auto scale = node.m_minScale;
// Do not filter high level objects. Including metro and country names.
static auto const maximumIgnoredZoom =
feature::GetDrawableScaleRange(classif().GetTypeByPath({"railway", "station", "subway"})).first;
if (maximumIgnoredZoom < 0 || scale <= maximumIgnoredZoom)
{
AddNodeToSorter(node, static_cast<uint32_t>(scale));
acceptedNodes.Add(node);
continue;
}
for (; scale < scales::GetUpperScale(); ++scale)
{
float const delta = CalculateDeltaForZoom(scale);
float const squaredDelta = delta * delta;
m2::RectD const displacementRect(node.m_center, node.m_center);
bool isDisplaced = false;
acceptedNodes.ForEachInRect(m2::Inflate(displacementRect, {delta, delta}),
[&isDisplaced, &node, &squaredDelta, &scale](DisplaceableNode const & rhs)
{
if (node.m_center.SquaredLength(rhs.m_center) < squaredDelta && rhs.m_maxScale > scale)
isDisplaced = true;
});
if (isDisplaced)
continue;
// Add feature to index otherwise.
AddNodeToSorter(node, scale);
acceptedNodes.Add(node);
break;
}
if (scale == scales::GetUpperScale())
AddNodeToSorter(node, scale);
}
}
private:
struct DisplaceableNode
{
uint32_t m_index;
FeatureID m_fID;
m2::PointD m_center;
std::vector<int64_t> m_cells;
int m_minScale;
int m_maxScale;
uint32_t m_priority;
DisplaceableNode() : m_index(0), m_minScale(0), m_maxScale(0), m_priority(0) {}
template <typename Feature>
DisplaceableNode(std::vector<int64_t> const & cells, Feature & ft, uint32_t index, int zoomLevel)
: m_index(index)
, m_fID(ft.GetID())
, m_center(ft.GetCenter())
, m_cells(cells)
, m_minScale(zoomLevel)
{
feature::TypesHolder const types(ft);
m_maxScale = feature::GetDrawableScaleRange(types).second;
// Calculate depth field
drule::KeysT keys;
feature::GetDrawRule(types, zoomLevel, keys);
// While the function has "runtime" in its name, it merely filters by metadata-based rules.
feature::FilterRulesByRuntimeSelector(ft, zoomLevel, keys);
drule::MakeUnique(keys);
float depth = 0;
for (auto const & k : keys)
if (depth < k.m_priority)
depth = k.m_priority;
// @todo: make sure features are prioritised the same way as in the run-time displacer,
// see overlay_handle.cpp::CalculateOverlayPriority()
ASSERT(-drule::kOverlaysMaxPriority <= depth && depth < drule::kOverlaysMaxPriority, (depth));
uint8_t rank = ft.GetRank();
m_priority = (static_cast<uint32_t>(depth) << 8) | rank;
}
// Same to dynamic displacement behaviour.
bool operator>(DisplaceableNode const & rhs) const
{
if (m_priority > rhs.m_priority)
return true;
return (m_priority == rhs.m_priority && m_fID < rhs.m_fID);
}
m2::RectD const GetLimitRect() const { return m2::RectD(m_center, m_center); }
};
template <typename Feature>
static bool IsDisplaceable(Feature & ft)
{
feature::TypesHolder const types(ft);
return types.GetGeomType() == feature::GeomType::Point;
}
float CalculateDeltaForZoom(int32_t zoom) const
{
// zoom - 1 is similar to drape.
double const worldSizeDivisor = 1 << (zoom - 1);
return kPOIDisplacementRadiusMultiplier / worldSizeDivisor;
}
void AddNodeToSorter(DisplaceableNode const & node, uint32_t scale)
{
for (auto const & cell : node.m_cells)
m_sorter(CellFeatureBucketTuple(CellFeaturePair(cell, node.m_index), scale));
}
Sorter & m_sorter;
std::vector<DisplaceableNode> m_storage;
};
} // namespace covering

View file

@ -0,0 +1,78 @@
#include "indexer/drawing_rule_def.hpp"
#include <algorithm>
#include <iterator>
namespace drule
{
using namespace std;
namespace
{
struct less_key
{
bool operator()(drule::Key const & r1, drule::Key const & r2) const
{
// assume that unique algo leaves the first element (with max priority), others - go away
if (r1.m_type == r2.m_type)
{
if (r1.m_hatching != r2.m_hatching)
return r1.m_hatching;
return (r1.m_priority > r2.m_priority);
}
else
return (r1.m_type < r2.m_type);
}
};
struct equal_key
{
bool operator()(drule::Key const & r1, drule::Key const & r2) const
{
// many line rules - is ok, other rules - one is enough
if (r1.m_type == drule::line)
return (r1 == r2);
else
{
if (r1.m_type == r2.m_type)
{
// Keep several area styles if bigger one (r1) is hatching and second is not.
if (r1.m_type == drule::area && r1.m_hatching && !r2.m_hatching)
return false;
return true;
}
return false;
}
}
};
} // namespace
void MakeUnique(KeysT & keys)
{
sort(keys.begin(), keys.end(), less_key());
keys.resize(distance(keys.begin(), unique(keys.begin(), keys.end(), equal_key())));
}
double CalcAreaBySizeDepth(FeatureType & f)
{
// Calculate depth based on areas' bbox sizes instead of style-set priorities.
m2::RectD const r = f.GetLimitRectChecked();
// Raw areas' size range is about (1e-11, 3000).
double const areaSize = r.SizeX() * r.SizeY();
// Compacted range is approx (-37;13).
double constexpr kMinSize = -37, kMaxSize = 13, kStretchFactor = kDepthRangeBgBySize / (kMaxSize - kMinSize);
// Use log2() to have more precision distinguishing smaller areas.
/// @todo We still can get here with areaSize == 0.
double const areaSizeCompact = std::max(kMinSize, (areaSize > 0) ? std::log2(areaSize) : kMinSize);
// Adjust the range to fit into [kBaseDepthBgBySize;kBaseDepthBgTop).
double const areaDepth = kBaseDepthBgBySize + (kMaxSize - areaSizeCompact) * kStretchFactor;
ASSERT(kBaseDepthBgBySize <= areaDepth && areaDepth < kBaseDepthBgTop,
(areaDepth, areaSize, areaSizeCompact, f.GetID()));
return areaDepth;
}
} // namespace drule

View file

@ -0,0 +1,91 @@
#pragma once
#include "base/buffer_vector.hpp"
#include "indexer/feature.hpp"
namespace drule
{
// Priority range for area and line drules. Should be same as LAYER_PRIORITY_RANGE in kothic.
// Each layer = +/-1 value shifts the range by this number, so that e.g. priorities
// of the default layer=0 range [0;1000) don't intersect with layer=-1 range [-1000;0) and so on..
double constexpr kLayerPriorityRange = 1000;
// Should be same as OVERLAYS_MAX_PRIORITY in kothic.
// The overlays range is [-kOverlaysMaxPriority; kOverlaysMaxPriority), negative values are used
// for optional captions which are below all other overlays.
int32_t constexpr kOverlaysMaxPriority = 10000;
/*
* Besides overlays, the overall rendering depth range [dp::kMinDepth;dp::kMaxDepth] is divided
* into following specific depth ranges:
* FG - foreground lines and areas (buildings..), rendered on top of other geometry always,
* even if a fg feature is layer=-10 (e.g. tunnels should be visibile over landcover and water).
* BG-top - rendered just on top of BG-by-size range, because ordering by size
* doesn't always work with e.g. water mapped over a forest,
* so water should be on top of other landcover always,
* but linear waterways should be hidden beneath it.
* BG-by-size - landcover areas rendered in bbox size order, smaller areas are above larger ones.
* Still, a BG-top water area with layer=-1 should go below other landcover,
* and a layer=1 landcover area should be displayed above water,
* so BG-top and BG-by-size should share the same "layer" space.
*/
// Priority values coming from style files are expected to be separated into following ranges.
static double constexpr kBasePriorityFg = 0, kBasePriorityBgTop = -1000, kBasePriorityBgBySize = -2000;
// Define depth ranges boundaries to accomodate for all possible layer=* values.
// One layer space is drule::kLayerPriorityRange (1000). So each +1/-1 layer value shifts
// depth of the drule by -1000/+1000.
// FG depth range: [0;1000).
static double constexpr kBaseDepthFg = 0,
// layer=-10/10 gives an overall FG range of [-10000;11000).
kMaxLayeredDepthFg = kBaseDepthFg + (1 + feature::LAYER_HIGH) * kLayerPriorityRange,
kMinLayeredDepthFg = kBaseDepthFg + int8_t{feature::LAYER_LOW} * kLayerPriorityRange,
// Split the background layer space as 100 for BG-top and 900 for BG-by-size.
kBgTopRangeFraction = 0.1, kDepthRangeBgTop = kBgTopRangeFraction * kLayerPriorityRange,
kDepthRangeBgBySize = kLayerPriorityRange - kDepthRangeBgTop,
// So the BG-top range is [-10100,-10000).
kBaseDepthBgTop = kMinLayeredDepthFg - kDepthRangeBgTop,
// And BG-by-size range is [-11000,-10100).
kBaseDepthBgBySize = kBaseDepthBgTop - kDepthRangeBgBySize,
// Minimum BG depth for layer=-10 is -21000.
kMinLayeredDepthBg = kBaseDepthBgBySize + int8_t{feature::LAYER_LOW} * kLayerPriorityRange;
class Key
{
public:
uint8_t m_scale = -1;
int m_type = -1;
size_t m_index = std::numeric_limits<size_t>::max(); // an index to RulesHolder.m_dRules[]
int m_priority = -1;
bool m_hatching = false;
Key() = default;
Key(uint8_t s, int t, size_t i) : m_scale(s), m_type(t), m_index(i), m_priority(-1) {}
bool operator==(Key const & r) const { return (m_index == r.m_index); }
void SetPriority(int pr) { m_priority = pr; }
};
/// drawing type of rule - can be one of ...
enum TypeT
{
line,
area,
symbol,
caption,
circle,
pathtext,
waymarker,
shield,
count_of_rules
};
typedef buffer_vector<Key, 16> KeysT;
void MakeUnique(KeysT & keys);
double CalcAreaBySizeDepth(FeatureType & f);
} // namespace drule

View file

@ -0,0 +1,419 @@
#include "indexer/drawing_rules.hpp"
#include "indexer/classificator.hpp"
#include "indexer/drules_include.hpp"
#include "indexer/map_style_reader.hpp"
#include "indexer/scales.hpp"
#include "defines.hpp"
#include "platform/platform.hpp"
#include "base/logging.hpp"
#include <functional>
#include <boost/iterator/iterator_facade.hpp>
#include <google/protobuf/text_format.h>
namespace drule
{
using namespace std;
namespace
{
uint32_t const DEFAULT_BG_COLOR = 0xEEEEDD;
} // namespace
LineRuleProto const * BaseRule::GetLine() const
{
return 0;
}
AreaRuleProto const * BaseRule::GetArea() const
{
return 0;
}
SymbolRuleProto const * BaseRule::GetSymbol() const
{
return 0;
}
CaptionRuleProto const * BaseRule::GetCaption() const
{
return 0;
}
PathTextRuleProto const * BaseRule::GetPathtext() const
{
return 0;
}
ShieldRuleProto const * BaseRule::GetShield() const
{
return nullptr;
}
bool BaseRule::TestFeature(FeatureType & ft, int zoom) const
{
if (nullptr == m_selector)
return true;
return m_selector->Test(ft, zoom);
}
void BaseRule::SetSelector(unique_ptr<ISelector> && selector)
{
m_selector = std::move(selector);
}
RulesHolder::RulesHolder() : m_bgColors(scales::UPPER_STYLE_SCALE + 1, DEFAULT_BG_COLOR) {}
RulesHolder::~RulesHolder()
{
Clean();
}
void RulesHolder::Clean()
{
for (size_t i = 0; i < m_dRules.size(); ++i)
delete m_dRules[i];
m_dRules.clear();
m_colors.clear();
}
Key RulesHolder::AddRule(int scale, TypeT type, BaseRule * p)
{
ASSERT(0 <= scale && scale <= scales::GetUpperStyleScale(), (scale));
ASSERT(0 <= type && type < count_of_rules, ());
m_dRules.push_back(p);
auto const index = m_dRules.size() - 1;
Key const k(scale, type, index);
ASSERT(Find(k) == p, (index));
return k;
}
BaseRule const * RulesHolder::Find(Key const & k) const
{
ASSERT_LESS(k.m_index, m_dRules.size(), ());
return m_dRules[k.m_index];
}
uint32_t RulesHolder::GetBgColor(int scale) const
{
ASSERT_LESS(scale, static_cast<int>(m_bgColors.size()), ());
ASSERT_GREATER_OR_EQUAL(scale, 0, ());
return m_bgColors[scale];
}
uint32_t RulesHolder::GetColor(std::string const & name) const
{
auto const it = m_colors.find(name);
if (it == m_colors.end())
{
LOG(LWARNING, ("Requested color '" + name + "' is not found"));
return 0;
}
return it->second;
}
namespace
{
RulesHolder & rules(MapStyle mapStyle)
{
static RulesHolder h[MapStyleCount];
return h[mapStyle];
}
} // namespace
RulesHolder & rules()
{
return rules(GetStyleReader().GetCurrentStyle());
}
namespace
{
namespace proto_rules
{
class Line : public BaseRule
{
LineRuleProto m_line;
public:
explicit Line(LineRuleProto const & r) : m_line(r)
{
ASSERT(r.has_pathsym() || r.width() > 0, ("Zero width line w/o path symbol)"));
}
virtual LineRuleProto const * GetLine() const { return &m_line; }
};
class Area : public BaseRule
{
AreaRuleProto m_area;
public:
explicit Area(AreaRuleProto const & r) : m_area(r) {}
virtual AreaRuleProto const * GetArea() const { return &m_area; }
};
class Symbol : public BaseRule
{
SymbolRuleProto m_symbol;
public:
explicit Symbol(SymbolRuleProto const & r) : m_symbol(r) {}
virtual SymbolRuleProto const * GetSymbol() const { return &m_symbol; }
};
class Caption : public BaseRule
{
CaptionRuleProto m_caption;
public:
explicit Caption(CaptionRuleProto const & r) : m_caption(r) { ASSERT(r.has_primary(), ()); }
virtual CaptionRuleProto const * GetCaption() const { return &m_caption; }
};
class PathText : public BaseRule
{
PathTextRuleProto m_pathtext;
public:
explicit PathText(PathTextRuleProto const & r) : m_pathtext(r) {}
virtual PathTextRuleProto const * GetPathtext() const { return &m_pathtext; }
};
class Shield : public BaseRule
{
ShieldRuleProto m_shield;
public:
explicit Shield(ShieldRuleProto const & r) : m_shield(r) {}
virtual ShieldRuleProto const * GetShield() const { return &m_shield; }
};
} // namespace proto_rules
class DoSetIndex
{
public:
ContainerProto m_cont;
private:
vector<string> m_names;
typedef ClassifElementProto ElementT;
class RandI : public boost::iterator_facade<RandI, ElementT const, boost::random_access_traversal_tag>
{
ContainerProto const * m_cont;
public:
int m_index;
RandI() : m_cont(0), m_index(-1) {}
RandI(ContainerProto const & cont, int ind) : m_cont(&cont), m_index(ind) {}
ElementT const & dereference() const { return m_cont->cont(m_index); }
bool equal(RandI const & r) const { return m_index == r.m_index; }
void increment() { ++m_index; }
void decrement() { --m_index; }
void advance(size_t n) { m_index += n; }
difference_type distance_to(RandI const & r) const { return (r.m_index - m_index); }
};
struct less_name
{
bool operator()(ElementT const & e1, ElementT const & e2) const { return (e1.name() < e2.name()); }
bool operator()(string const & e1, ElementT const & e2) const { return (e1 < e2.name()); }
bool operator()(ElementT const & e1, string const & e2) const { return (e1.name() < e2); }
};
int FindIndex() const
{
string name = m_names[0];
for (size_t i = 1; i < m_names.size(); ++i)
name = name + "-" + m_names[i];
int const count = m_cont.cont_size();
int const i = lower_bound(RandI(m_cont, 0), RandI(m_cont, count), name, less_name()).m_index;
ASSERT_GREATER_OR_EQUAL(i, 0, ());
ASSERT_LESS_OR_EQUAL(i, count, ());
if (i < count && m_cont.cont(i).name() == name)
return i;
else
return -1;
}
RulesHolder & m_holder;
template <class TRule, class TProtoRule>
void AddRule(ClassifObject * p, int scale, TypeT type, TProtoRule const & rule, vector<string> const & apply_if)
{
unique_ptr<ISelector> selector;
if (!apply_if.empty())
{
selector = ParseSelector(apply_if);
if (selector == nullptr)
{
LOG(LERROR, ("Runtime selector has not been created:", apply_if));
return;
}
}
BaseRule * obj = new TRule(rule);
obj->SetSelector(std::move(selector));
Key k = m_holder.AddRule(scale, type, obj);
p->SetVisibilityOnScale(true, scale);
k.SetPriority(rule.priority());
p->AddDrawRule(k);
}
static void DrawElementGetApplyIf(DrawElementProto const & de, vector<string> & apply_if)
{
apply_if.clear();
apply_if.reserve(de.apply_if_size());
for (int i = 0; i < de.apply_if_size(); ++i)
apply_if.emplace_back(de.apply_if(i));
}
public:
explicit DoSetIndex(RulesHolder & holder) : m_holder(holder) {}
void operator()(ClassifObject * p)
{
m_names.push_back(p->GetName());
int const i = FindIndex();
if (i != -1)
{
vector<string> apply_if;
ClassifElementProto const & ce = m_cont.cont(i);
for (int j = 0; j < ce.element_size(); ++j)
{
DrawElementProto const & de = ce.element(j);
using namespace proto_rules;
DrawElementGetApplyIf(de, apply_if);
for (int k = 0; k < de.lines_size(); ++k)
AddRule<Line>(p, de.scale(), line, de.lines(k), apply_if);
if (de.has_area())
AddRule<Area>(p, de.scale(), area, de.area(), apply_if);
if (de.has_symbol())
AddRule<Symbol>(p, de.scale(), symbol, de.symbol(), apply_if);
if (de.has_caption())
AddRule<Caption>(p, de.scale(), caption, de.caption(), apply_if);
if (de.has_path_text())
AddRule<PathText>(p, de.scale(), pathtext, de.path_text(), apply_if);
if (de.has_shield())
AddRule<Shield>(p, de.scale(), shield, de.shield(), apply_if);
}
}
p->ForEachObject(ref(*this));
m_names.pop_back();
}
};
} // namespace
void RulesHolder::InitBackgroundColors(ContainerProto const & cont)
{
// WARNING!
// Background color is not specified in current format.
// Therefore, we use color of "natural-land" area element as background color.
// If such element is not present or if the element is not area then we use default background color
// Default background color is any found color, it is used if color is not specified for scale
uint32_t bgColorDefault = DEFAULT_BG_COLOR;
// Background colors specified for scales
unordered_map<int, uint32_t> bgColorForScale;
// Find the "natural-land" classification element
for (int i = 0; i < cont.cont_size(); ++i)
{
ClassifElementProto const & ce = cont.cont(i);
if (ce.name() == "natural-land")
{
// Take any area draw element
for (int j = 0; j < ce.element_size(); ++j)
{
DrawElementProto const & de = ce.element(j);
if (de.has_area())
{
// Take the color of the draw element
AreaRuleProto const & rule = de.area();
bgColorDefault = rule.color();
if (de.scale() != 0)
bgColorForScale.insert(make_pair(de.scale(), rule.color()));
}
}
break;
}
}
ASSERT_EQUAL(m_bgColors.size(), scales::UPPER_STYLE_SCALE + 1, ());
for (int scale = 0; scale <= scales::UPPER_STYLE_SCALE; ++scale)
{
auto const i = bgColorForScale.find(scale);
if (bgColorForScale.end() != i)
m_bgColors[scale] = i->second;
else
m_bgColors[scale] = bgColorDefault;
}
}
void RulesHolder::InitColors(ContainerProto const & cp)
{
if (!cp.has_colors())
return;
ASSERT_EQUAL(m_colors.size(), 0, ());
for (int i = 0; i < cp.colors().value_size(); i++)
{
ColorElementProto const & proto = cp.colors().value(i);
m_colors.insert(std::make_pair(proto.name(), proto.color()));
}
}
void RulesHolder::LoadFromBinaryProto(string const & s)
{
Clean();
DoSetIndex doSet(*this);
CHECK(doSet.m_cont.ParseFromString(s), ("Error in proto loading!"));
classif().GetMutableRoot()->ForEachObject(ref(doSet));
InitBackgroundColors(doSet.m_cont);
InitColors(doSet.m_cont);
}
void LoadRules()
{
string buffer;
GetStyleReader().GetDrawingRulesReader().ReadAsString(buffer);
rules().LoadFromBinaryProto(buffer);
}
} // namespace drule

View file

@ -0,0 +1,95 @@
#pragma once
#include "indexer/drawing_rule_def.hpp"
#include "indexer/drules_selector.hpp"
#include "indexer/map_style.hpp"
#include "base/base.hpp"
#include "base/buffer_vector.hpp"
#include "std/target_os.hpp"
#include <array>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
class LineRuleProto;
class AreaRuleProto;
class SymbolRuleProto;
class CaptionRuleProto;
class PathTextRuleProto;
class ShieldRuleProto;
class ContainerProto;
class FeatureType;
namespace drule
{
class BaseRule
{
public:
BaseRule() = default;
virtual ~BaseRule() = default;
virtual LineRuleProto const * GetLine() const;
virtual AreaRuleProto const * GetArea() const;
virtual SymbolRuleProto const * GetSymbol() const;
virtual CaptionRuleProto const * GetCaption() const;
virtual PathTextRuleProto const * GetPathtext() const;
virtual ShieldRuleProto const * GetShield() const;
// Test feature by runtime feature style selector
// Returns true if rule is applicable for feature, otherwise it returns false
bool TestFeature(FeatureType & ft, int zoom) const;
// Set runtime feature style selector
void SetSelector(std::unique_ptr<ISelector> && selector);
private:
std::unique_ptr<ISelector> m_selector;
};
class RulesHolder
{
public:
RulesHolder();
~RulesHolder();
Key AddRule(int scale, TypeT type, BaseRule * p);
BaseRule const * Find(Key const & k) const;
uint32_t GetBgColor(int scale) const;
uint32_t GetColor(std::string const & name) const;
#ifdef OMIM_OS_DESKTOP
void LoadFromTextProto(std::string const & buffer);
static void SaveToBinaryProto(std::string const & buffer, std::ostream & s);
#endif
void LoadFromBinaryProto(std::string const & s);
template <class ToDo>
void ForEachRule(ToDo && toDo)
{
for (auto const dRule : m_dRules)
toDo(dRule);
}
private:
void InitBackgroundColors(ContainerProto const & cp);
void InitColors(ContainerProto const & cp);
void Clean();
/// background color for scales in range [0...scales::UPPER_STYLE_SCALE]
std::vector<uint32_t> m_bgColors;
std::unordered_map<std::string, uint32_t> m_colors;
std::vector<BaseRule *> m_dRules;
};
RulesHolder & rules();
void LoadRules();
} // namespace drule

View file

@ -0,0 +1,15 @@
#pragma once
#include "std/target_os.hpp"
// Both clang and gcc implements `#pragma GCC`
#if !defined(__INTEL_COMPILER)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif // !defined(__INTEL_COMPILER)
#include "indexer/drules_struct.pb.h"
#if !defined(__INTEL_COMPILER)
#pragma GCC diagnostic pop
#endif // !defined(__INTEL_COMPILER)

View file

@ -0,0 +1,247 @@
#include "indexer/drules_selector.hpp"
#include "indexer/drules_selector_parser.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "geometry/mercator.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
namespace drule
{
using namespace std;
namespace
{
class CompositeSelector : public ISelector
{
public:
explicit CompositeSelector(size_t capacity) { m_selectors.reserve(capacity); }
void Add(unique_ptr<ISelector> && selector) { m_selectors.emplace_back(std::move(selector)); }
// ISelector overrides:
bool Test(FeatureType & ft, int zoom) const override
{
for (auto const & selector : m_selectors)
if (!selector->Test(ft, zoom))
return false;
return true;
}
private:
vector<unique_ptr<ISelector>> m_selectors;
};
// Runtime feature style selector implementation
template <typename TType>
class Selector : public ISelector
{
public:
// Signature of function which takes a property from a feature
typedef bool (*TGetFeatureTagValueFn)(FeatureType &, int, TType & value);
Selector(TGetFeatureTagValueFn fn, SelectorOperatorType op, TType const & value)
: m_getFeatureValueFn(fn)
, m_evalFn(nullptr)
, m_value(value)
{
ASSERT(fn != nullptr, ());
switch (op)
{
case SelectorOperatorUnknown: m_evalFn = nullptr; break;
case SelectorOperatorNotEqual: m_evalFn = &Selector<TType>::NotEqual; break;
case SelectorOperatorLessOrEqual: m_evalFn = &Selector<TType>::LessOrEqual; break;
case SelectorOperatorGreaterOrEqual: m_evalFn = &Selector<TType>::GreaterOrEqual; break;
case SelectorOperatorEqual: m_evalFn = &Selector<TType>::Equal; break;
case SelectorOperatorLess: m_evalFn = &Selector<TType>::Less; break;
case SelectorOperatorGreater: m_evalFn = &Selector<TType>::Greater; break;
case SelectorOperatorIsNotSet: m_evalFn = &Selector<TType>::IsNotSet; break;
case SelectorOperatorIsSet: m_evalFn = &Selector<TType>::IsSet; break;
}
ASSERT(m_evalFn != nullptr, ("Unknown or unexpected selector operator type"));
if (nullptr == m_evalFn)
m_evalFn = &Selector<TType>::Unknown;
}
// ISelector overrides:
bool Test(FeatureType & ft, int zoom) const override
{
TType tagValue;
if (!m_getFeatureValueFn(ft, zoom, tagValue))
return false;
return (this->*m_evalFn)(tagValue);
}
private:
bool Unknown(TType const &) const { return false; }
bool NotEqual(TType const & tagValue) const { return tagValue != m_value; }
bool LessOrEqual(TType const & tagValue) const { return tagValue <= m_value; }
bool GreaterOrEqual(TType const & tagValue) const { return tagValue >= m_value; }
bool Equal(TType const & tagValue) const { return tagValue == m_value; }
bool Less(TType const & tagValue) const { return tagValue < m_value; }
bool Greater(TType const & tagValue) const { return tagValue > m_value; }
bool IsNotSet(TType const & tagValue) const { return tagValue == TType(); }
bool IsSet(TType const & tagValue) const { return tagValue != TType(); }
typedef bool (Selector<TType>::*TOperationFn)(TType const &) const;
TGetFeatureTagValueFn m_getFeatureValueFn;
TOperationFn m_evalFn;
TType const m_value;
};
class HasSelector : public ISelector
{
public:
typedef bool (*THasFeatureTagValueFn)(FeatureType &, int);
HasSelector(THasFeatureTagValueFn fn, SelectorOperatorType op)
: m_hasFeatureValueFn(fn)
, m_testHas(op == SelectorOperatorIsSet)
{
ASSERT(op == SelectorOperatorIsSet || op == SelectorOperatorIsNotSet, ());
ASSERT(fn != nullptr, ());
}
// ISelector overrides:
bool Test(FeatureType & ft, int zoom) const override { return m_hasFeatureValueFn(ft, zoom) == m_testHas; }
private:
THasFeatureTagValueFn m_hasFeatureValueFn;
bool m_testHas;
};
/*
uint32_t TagSelectorToType(string value)
{
vector<string> path;
strings::ParseCSVRow(value, '=', path);
return path.size() > 0 && path.size() <= 2 ? classif().GetTypeByPathSafe(path) : 0;
}
class TypeSelector : public ISelector
{
public:
TypeSelector(uint32_t type, SelectorOperatorType op) : m_type(type)
{
m_equals = op == SelectorOperatorEqual;
}
bool Test(FeatureType & ft) const override
{
bool found = false;
ft.ForEachType([&found, this](uint32_t type)
{
ftype::TruncValue(type, ftype::GetLevel(m_type));
if (type == m_type)
found = true;
});
return found == m_equals;
}
private:
uint32_t m_type;
bool m_equals;
};
*/
// Feature tag value evaluator for tag 'population'
bool GetPopulation(FeatureType & ft, int, uint64_t & population)
{
population = ftypes::GetPopulation(ft);
return true;
}
bool HasName(FeatureType & ft, int)
{
return ft.HasName();
}
// Feature tag value evaluator for tag 'bbox_area' (bounding box area in sq.meters)
bool GetBoundingBoxArea(FeatureType & ft, int zoom, double & sqM)
{
if (feature::GeomType::Area != ft.GetGeomType())
return false;
// https://github.com/organicmaps/organicmaps/issues/2840
sqM = mercator::AreaOnEarth(ft.GetLimitRect(zoom));
return true;
}
} // namespace
unique_ptr<ISelector> ParseSelector(string const & str)
{
SelectorExpression e;
if (!ParseSelector(str, e))
{
// bad string format
LOG(LDEBUG, ("Invalid selector format:", str));
return {};
}
if (e.m_tag == "population")
{
uint64_t value = 0;
if (!e.m_value.empty() && !strings::to_uint64(e.m_value, value))
{
// bad string format
LOG(LDEBUG, ("Invalid selector:", str));
return {};
}
return make_unique<Selector<uint64_t>>(&GetPopulation, e.m_operator, value);
}
else if (e.m_tag == "name")
{
return make_unique<HasSelector>(&HasName, e.m_operator);
}
else if (e.m_tag == "bbox_area")
{
double value = 0;
if (!e.m_value.empty() && (!strings::to_double(e.m_value, value) || value < 0))
{
// bad string format
LOG(LDEBUG, ("Invalid selector:", str));
return {};
}
return make_unique<Selector<double>>(&GetBoundingBoxArea, e.m_operator, value);
}
// else if (e.m_tag == "extra_tag")
// {
// ASSERT(false, ());
// uint32_t const type = TagSelectorToType(e.m_value);
// if (type == Classificator::INVALID_TYPE)
// {
// // Type was not found.
// LOG(LDEBUG, ("Invalid selector:", str));
// return unique_ptr<ISelector>();
// }
// return make_unique<TypeSelector>(type, e.m_operator);
// }
LOG(LERROR, ("Unrecognized selector:", str));
return {};
}
unique_ptr<ISelector> ParseSelector(vector<string> const & strs)
{
unique_ptr<CompositeSelector> cs = make_unique<CompositeSelector>(strs.size());
for (string const & str : strs)
{
unique_ptr<ISelector> s = ParseSelector(str);
if (nullptr == s)
{
LOG(LDEBUG, ("Invalid composite selector:", str));
return unique_ptr<ISelector>();
}
cs->Add(std::move(s));
}
return unique_ptr<ISelector>(cs.release());
}
} // namespace drule

View file

@ -0,0 +1,29 @@
#pragma once
#include "indexer/feature.hpp"
#include <memory>
#include <string>
#include <vector>
namespace drule
{
// Runtime feature style selector absract interface.
class ISelector
{
public:
virtual ~ISelector() = default;
// If ISelector.Test returns true then style is applicable for the feature,
// otherwise, if ISelector.Test returns false, style cannot be applied to the feature.
virtual bool Test(FeatureType & ft, int zoom) const = 0;
};
// Factory method which builds ISelector from a string.
std::unique_ptr<ISelector> ParseSelector(std::string const & str);
// Factory method which builds composite ISelector from a set of string.
std::unique_ptr<ISelector> ParseSelector(std::vector<std::string> const & strs);
} // namespace drule

View file

@ -0,0 +1,123 @@
#include "indexer/drules_selector_parser.hpp"
#include "base/assert.hpp"
#include <algorithm>
namespace drule
{
using namespace std;
namespace
{
bool IsTag(string const & str)
{
// tag consists of a-z or A-Z letters or _ and not empty
for (auto const c : str)
if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && c != '_')
return false;
return !str.empty();
}
} // namespace
bool ParseSelector(string const & str, SelectorExpression & e)
{
// See https://wiki.openstreetmap.org/wiki/MapCSS/0.2
// Now we support following expressions
// [tag!=value]
// [tag>=value]
// [tag<=value]
// [tag=value]
// [tag>value]
// [tag<value]
// [!tag]
// [tag]
if (str.empty())
return false; // invalid format
// [!tag]
if (str[0] == '!')
{
string tag(str.begin() + 1, str.end());
if (!IsTag(tag))
return false; // invalid format
e.m_operator = SelectorOperatorIsNotSet;
e.m_tag = std::move(tag);
e.m_value.clear();
return true;
}
// [tag]
if (IsTag(str))
{
e.m_operator = SelectorOperatorIsSet;
e.m_tag = str;
e.m_value.clear();
return true;
}
// Find first entrance of >, < or =
size_t pos = string::npos;
size_t len = 0;
char const c[] = {'>', '<', '=', 0};
for (size_t i = 0; c[i] != 0; ++i)
{
size_t p = str.find(c[i]);
if (p != string::npos)
{
pos = (pos == string::npos) ? p : min(p, pos);
len = 1;
}
}
// If there is no entrance or no space for tag or value then it is invalid format
if (pos == 0 || len == 0 || pos == str.size() - 1)
return false; // invalid format
// Dedicate the operator type, real operator position and length
SelectorOperatorType op = SelectorOperatorUnknown;
if (str[pos] == '>')
{
op = SelectorOperatorGreater;
if (str[pos + 1] == '=')
{
++len;
op = SelectorOperatorGreaterOrEqual;
}
}
else if (str[pos] == '<')
{
op = SelectorOperatorLess;
if (str[pos + 1] == '=')
{
++len;
op = SelectorOperatorLessOrEqual;
}
}
else
{
ASSERT(str[pos] == '=', ());
op = SelectorOperatorEqual;
if (str[pos - 1] == '!')
{
--pos;
++len;
op = SelectorOperatorNotEqual;
}
}
string tag(str.begin(), str.begin() + pos);
if (!IsTag(tag))
return false; // invalid format
e.m_operator = op;
e.m_tag = std::move(tag);
e.m_value = string(str.begin() + pos + len, str.end());
return true;
}
} // namespace drule

View file

@ -0,0 +1,48 @@
#pragma once
#include <string>
namespace drule
{
enum SelectorOperatorType
{
SelectorOperatorUnknown = 0,
// [tag!=value]
SelectorOperatorNotEqual,
// [tag<=value]
SelectorOperatorLessOrEqual,
// [tag>=value]
SelectorOperatorGreaterOrEqual,
// [tag=value]
SelectorOperatorEqual,
// [tag<value]
SelectorOperatorLess,
// [tag>value]
SelectorOperatorGreater,
// [!tag]
SelectorOperatorIsNotSet,
// [tag]
SelectorOperatorIsSet,
};
struct SelectorExpression
{
SelectorOperatorType m_operator;
std::string m_tag;
std::string m_value;
SelectorExpression() : m_operator(SelectorOperatorUnknown) {}
};
bool ParseSelector(std::string const & str, SelectorExpression & e);
} // namespace drule

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,147 @@
syntax = "proto3";
option optimize_for = LITE_RUNTIME;
message DashDotProto
{
repeated double dd = 1;
double offset = 2;
}
message PathSymProto
{
string name = 1;
double step = 2;
double offset = 3;
}
enum LineJoin
{
ROUNDJOIN = 0;
BEVELJOIN = 1;
NOJOIN = 2;
}
enum LineCap
{
ROUNDCAP = 0;
BUTTCAP = 1;
SQUARECAP = 2;
}
message LineRuleProto
{
double width = 1;
uint32 color = 2;
DashDotProto dashdot = 3;
int32 priority = 4;
PathSymProto pathsym = 5;
LineJoin join = 6;
LineCap cap = 7;
}
message LineDefProto
{
double width = 1;
uint32 color = 2;
DashDotProto dashdot = 3;
PathSymProto pathsym = 4;
LineJoin join = 6;
LineCap cap = 7;
}
message AreaRuleProto
{
uint32 color = 1;
LineDefProto border = 2;
int32 priority = 3;
}
message SymbolRuleProto
{
string name = 1;
int32 apply_for_type = 2; // 1 - for nodes, 2 - for ways, default - for all
int32 priority = 3;
int32 min_distance = 4;
}
message CaptionDefProto
{
int32 height = 1;
uint32 color = 2;
uint32 stroke_color = 3;
int32 offset_x = 4;
int32 offset_y = 5;
string text = 6;
bool is_optional = 7;
}
message CaptionRuleProto
{
CaptionDefProto primary = 1;
CaptionDefProto secondary = 2;
int32 priority = 3;
}
message CircleRuleProto
{
double radius = 1;
uint32 color = 2;
LineDefProto border = 3;
int32 priority = 4;
}
message PathTextRuleProto
{
CaptionDefProto primary = 1;
CaptionDefProto secondary = 2;
int32 priority = 3;
}
message ShieldRuleProto
{
int32 height = 1;
uint32 color = 2;
uint32 stroke_color = 3;
int32 priority = 4;
int32 min_distance = 5;
uint32 text_color = 6;
uint32 text_stroke_color = 7;
}
message DrawElementProto
{
int32 scale = 1;
repeated LineRuleProto lines = 2;
AreaRuleProto area = 3;
SymbolRuleProto symbol = 4;
CaptionRuleProto caption = 5;
CircleRuleProto circle = 6;
PathTextRuleProto path_text = 7;
ShieldRuleProto shield = 8;
repeated string apply_if = 9;
}
message ClassifElementProto
{
string name = 1;
repeated DrawElementProto element = 2;
}
message ColorElementProto
{
string name = 1;
uint32 color = 2;
float x = 3;
float y = 4;
}
message ColorsElementProto
{
repeated ColorElementProto value = 1;
}
message ContainerProto
{
repeated ClassifElementProto cont = 1;
ColorsElementProto colors = 2;
}

View file

@ -0,0 +1,152 @@
#include "indexer/edit_journal.hpp"
#include "base/assert.hpp"
#include "base/control_flow.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <cmath>
#include <regex>
#include <sstream>
namespace osm
{
std::list<JournalEntry> const & EditJournal::GetJournal() const
{
return m_journal;
}
osm::EditingLifecycle EditJournal::GetEditingLifecycle() const
{
if (m_journal.empty())
return EditingLifecycle::IN_SYNC;
else if (m_journal.front().journalEntryType == JournalEntryType::ObjectCreated)
return EditingLifecycle::CREATED;
return EditingLifecycle::MODIFIED;
}
void EditJournal::AddTagChange(std::string key, std::string old_value, std::string new_value)
{
LOG(LDEBUG, ("Key ", key, "changed from \"", old_value, "\" to \"", new_value, "\""));
AddJournalEntry({JournalEntryType::TagModification, time(nullptr),
TagModData{std::move(key), std::move(old_value), std::move(new_value)}});
}
void EditJournal::MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator)
{
ASSERT(m_journal.empty(), ("Only empty journals can be marked as created"));
LOG(LDEBUG, ("Object of type ", classif().GetReadableObjectName(type), " created"));
AddJournalEntry({JournalEntryType::ObjectCreated, time(nullptr), osm::ObjCreateData{type, geomType, mercator}});
}
void EditJournal::AddBusinessReplacement(uint32_t old_type, uint32_t new_type)
{
LOG(LDEBUG, ("Business of type ", classif().GetReadableObjectName(old_type), " was replaced by a ",
classif().GetReadableObjectName(new_type)));
AddJournalEntry(
{JournalEntryType::BusinessReplacement, time(nullptr), osm::BusinessReplacementData{old_type, new_type}});
}
void EditJournal::AddJournalEntry(JournalEntry entry)
{
m_journal.push_back(std::move(entry));
}
void EditJournal::Clear()
{
for (JournalEntry & entry : m_journal)
m_journalHistory.push_back(std::move(entry));
m_journal = {};
}
std::list<JournalEntry> const & EditJournal::GetJournalHistory() const
{
return m_journalHistory;
}
void EditJournal::AddJournalHistoryEntry(JournalEntry entry)
{
m_journalHistory.push_back(std::move(entry));
}
std::string EditJournal::JournalToString() const
{
std::string string;
std::for_each(m_journal.begin(), m_journal.end(),
[&](auto const & journalEntry) { string += ToString(journalEntry) + "\n"; });
return string;
}
std::string EditJournal::ToString(osm::JournalEntry const & journalEntry)
{
switch (journalEntry.journalEntryType)
{
case osm::JournalEntryType::TagModification:
{
TagModData const & tagModData = std::get<TagModData>(journalEntry.data);
return ToString(journalEntry.journalEntryType)
.append(": Key ")
.append(tagModData.key)
.append(" changed from \"")
.append(tagModData.old_value)
.append("\" to \"")
.append(tagModData.new_value)
.append("\"");
}
case osm::JournalEntryType::ObjectCreated:
{
ObjCreateData const & objCreatedData = std::get<ObjCreateData>(journalEntry.data);
return ToString(journalEntry.journalEntryType)
.append(": ")
.append(classif().GetReadableObjectName(objCreatedData.type))
.append(" (")
.append(std::to_string(objCreatedData.type))
.append(")");
}
case osm::JournalEntryType::LegacyObject:
{
LegacyObjData const & legacyObjData = std::get<LegacyObjData>(journalEntry.data);
return ToString(journalEntry.journalEntryType).append(": version=\"").append(legacyObjData.version).append("\"");
}
case osm::JournalEntryType::BusinessReplacement:
{
BusinessReplacementData const & businessReplacementData = std::get<BusinessReplacementData>(journalEntry.data);
return ToString(journalEntry.journalEntryType)
.append(": Category changed from ")
.append(classif().GetReadableObjectName(businessReplacementData.old_type))
.append(" to ")
.append(classif().GetReadableObjectName(businessReplacementData.new_type));
}
default: UNREACHABLE();
}
}
std::string EditJournal::ToString(osm::JournalEntryType journalEntryType)
{
switch (journalEntryType)
{
case osm::JournalEntryType::TagModification: return "TagModification";
case osm::JournalEntryType::ObjectCreated: return "ObjectCreated";
case osm::JournalEntryType::LegacyObject: return "LegacyObject";
case osm::JournalEntryType::BusinessReplacement: return "BusinessReplacement";
default: UNREACHABLE();
}
}
std::optional<JournalEntryType> EditJournal::TypeFromString(std::string const & entryType)
{
if (entryType == "TagModification")
return JournalEntryType::TagModification;
else if (entryType == "ObjectCreated")
return JournalEntryType::ObjectCreated;
else if (entryType == "LegacyObject")
return JournalEntryType::LegacyObject;
else if (entryType == "BusinessReplacement")
return JournalEntryType::BusinessReplacement;
else
return {};
}
} // namespace osm

View file

@ -0,0 +1,99 @@
#pragma once
#include "indexer/feature_decl.hpp"
#include "indexer/feature_meta.hpp"
#include "indexer/feature_utils.hpp"
#include <functional>
#include <string>
#include <variant>
#include <vector>
namespace osm
{
enum class JournalEntryType
{
TagModification,
ObjectCreated,
LegacyObject, // object without full journal history, used for transition to new editor
BusinessReplacement,
// Possible future values: ObjectDeleted, ObjectDisused, ObjectNotDisused, LocationChanged, FeatureTypeChanged
};
struct TagModData
{
std::string key;
std::string old_value;
std::string new_value;
};
struct ObjCreateData
{
uint32_t type;
feature::GeomType geomType;
m2::PointD mercator;
};
struct LegacyObjData
{
std::string version;
};
struct BusinessReplacementData
{
uint32_t old_type;
uint32_t new_type;
};
struct JournalEntry
{
JournalEntryType journalEntryType = JournalEntryType::TagModification;
time_t timestamp;
std::variant<TagModData, ObjCreateData, LegacyObjData, BusinessReplacementData> data;
};
/// Used to determine whether existing OSM object should be updated or new one created
enum class EditingLifecycle
{
CREATED, // newly created and not synced with OSM
MODIFIED, // modified and not synced with OSM
IN_SYNC // synced with OSM (including never edited)
};
class EditJournal
{
std::list<JournalEntry> m_journal{};
std::list<JournalEntry> m_journalHistory{};
public:
std::list<JournalEntry> const & GetJournal() const;
osm::EditingLifecycle GetEditingLifecycle() const;
/// Log object edits in the journal
void AddTagChange(std::string key, std::string old_value, std::string new_value);
/// Log object creation in the journal
void MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
/// Log business replacement in the journal
void AddBusinessReplacement(uint32_t old_type, uint32_t new_type);
void AddJournalEntry(JournalEntry entry);
/// Clear Journal and move content to journalHistory, used after upload to OSM
void Clear();
std::list<JournalEntry> const & GetJournalHistory() const;
void AddJournalHistoryEntry(JournalEntry entry);
std::string JournalToString() const;
static std::string ToString(osm::JournalEntry const & journalEntry);
static std::string ToString(osm::JournalEntryType journalEntryType);
static std::optional<JournalEntryType> TypeFromString(std::string const & entryType);
};
} // namespace osm

View file

@ -0,0 +1,968 @@
#include "indexer/editable_map_object.hpp"
#include "indexer/classificator.hpp"
#include "indexer/edit_journal.hpp"
#include "indexer/feature_charge_sockets.hpp"
#include "indexer/feature_meta.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "indexer/postcodes_matcher.hpp"
#include "indexer/validate_and_format_contacts.hpp"
#include "platform/preferred_languages.hpp"
#include "base/control_flow.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <cmath>
#include <regex>
#include <sstream>
namespace osm
{
using namespace std;
namespace
{
bool ExtractName(StringUtf8Multilang const & names, int8_t const langCode, vector<osm::LocalizedName> & result)
{
if (StringUtf8Multilang::kUnsupportedLanguageCode == langCode)
return false;
// Exclude languages that are already present.
auto const it = base::FindIf(
result, [langCode](osm::LocalizedName const & localizedName) { return localizedName.m_code == langCode; });
if (result.end() != it)
return false;
string_view name;
names.GetString(langCode, name);
result.emplace_back(langCode, name);
return true;
}
} // namespace
// LocalizedName -----------------------------------------------------------------------------------
LocalizedName::LocalizedName(int8_t const code, string_view name)
: m_code(code)
, m_lang(StringUtf8Multilang::GetLangByCode(code))
, m_langName(StringUtf8Multilang::GetLangNameByCode(code))
, m_name(name)
{}
LocalizedName::LocalizedName(string const & langCode, string const & name)
: m_code(StringUtf8Multilang::GetLangIndex(langCode))
, m_lang(StringUtf8Multilang::GetLangByCode(m_code))
, m_langName(StringUtf8Multilang::GetLangNameByCode(m_code))
, m_name(name)
{}
// EditableMapObject -------------------------------------------------------------------------------
bool EditableMapObject::IsNameEditable() const
{
return m_editableProperties.m_name;
}
bool EditableMapObject::IsAddressEditable() const
{
return m_editableProperties.m_address;
}
vector<MapObject::MetadataID> EditableMapObject::GetEditableProperties() const
{
auto props = m_editableProperties.m_metadata;
if (m_editableProperties.m_cuisine)
{
// Props are already sorted by Metadata::EType value.
auto insertBefore = props.begin();
if (insertBefore != props.end() && *insertBefore == MetadataID::FMD_OPEN_HOURS)
++insertBefore;
props.insert(insertBefore, MetadataID::FMD_CUISINE);
}
return props;
}
bool EditableMapObject::CanMarkPlaceAsDisused() const
{
if (GetEditingLifecycle() == EditingLifecycle::CREATED)
return false;
auto types = GetTypes();
types.SortBySpec();
uint32_t mainType = *types.begin();
std::string mainTypeStr = classif().GetReadableObjectName(mainType);
constexpr string_view typePrefixes[] = {
"shop",
"amenity-restaurant",
"amenity-fast_food",
"amenity-cafe",
"amenity-pub",
"amenity-bar",
};
for (auto const & typePrefix : typePrefixes)
if (mainTypeStr.starts_with(typePrefix))
return true;
return false;
}
NamesDataSource EditableMapObject::GetNamesDataSource()
{
auto const mwmInfo = GetID().m_mwmId.GetInfo();
if (!mwmInfo)
return NamesDataSource();
vector<int8_t> mwmLanguages;
mwmInfo->GetRegionData().GetLanguages(mwmLanguages);
auto const userLangCode = StringUtf8Multilang::GetLangIndex(languages::GetCurrentMapLanguage());
return GetNamesDataSource(m_name, mwmLanguages, userLangCode);
}
// static
NamesDataSource EditableMapObject::GetNamesDataSource(StringUtf8Multilang const & source,
vector<int8_t> const & mwmLanguages, int8_t const userLangCode)
{
NamesDataSource result;
auto & names = result.names;
auto & mandatoryCount = result.mandatoryNamesCount;
// Push default/native for country language.
if (ExtractName(source, StringUtf8Multilang::kDefaultCode, names))
++mandatoryCount;
// Push other languages.
source.ForEach([&names, mandatoryCount](int8_t const code, string_view name)
{
auto const mandatoryNamesEnd = names.begin() + mandatoryCount;
// Exclude languages which are already in container (languages with top priority).
auto const it = find_if(names.begin(), mandatoryNamesEnd,
[code](LocalizedName const & localizedName) { return localizedName.m_code == code; });
if (mandatoryNamesEnd == it)
names.emplace_back(code, name);
});
return result;
}
vector<LocalizedStreet> const & EditableMapObject::GetNearbyStreets() const
{
return m_nearbyStreets;
}
void EditableMapObject::ForEachMetadataItem(function<void(string_view tag, string_view value)> const & fn) const
{
m_metadata.ForEach([&fn](MetadataID type, std::string_view value)
{
switch (type)
{
// Multilang description may produce several tags with different values.
case MetadataID::FMD_DESCRIPTION:
{
auto const mlDescr = StringUtf8Multilang::FromBuffer(std::string(value));
mlDescr.ForEach([&fn](int8_t code, string_view v)
{
if (code == StringUtf8Multilang::kDefaultCode)
fn("description", v);
else
fn(string("description:").append(StringUtf8Multilang::GetLangByCode(code)), v);
});
break;
}
// Skip non-string values (they are not related to OSM anyway).
case MetadataID::FMD_CUSTOM_IDS:
case MetadataID::FMD_PRICE_RATES:
case MetadataID::FMD_RATINGS:
case MetadataID::FMD_EXTERNAL_URI:
case MetadataID::FMD_WHEELCHAIR: // Value is runtime only, data is taken from the classificator types, should not
// be used to update the OSM database
case MetadataID::FMD_CHARGE_SOCKETS: // multiple keys; handled via the edit journal
break;
default: fn(ToString(type), value); break;
}
});
}
void EditableMapObject::SetTestId(uint64_t id)
{
m_metadata.Set(feature::Metadata::FMD_TEST_ID, std::to_string(id));
}
void EditableMapObject::SetEditableProperties(osm::EditableProperties const & props)
{
m_editableProperties = props;
}
void EditableMapObject::SetName(StringUtf8Multilang const & name)
{
m_name = name;
}
void EditableMapObject::SetName(string_view name, int8_t langCode)
{
strings::Trim(name);
m_name.AddString(langCode, name);
}
// static
bool EditableMapObject::CanUseAsDefaultName(int8_t const lang, vector<int8_t> const & mwmLanguages)
{
for (auto const & mwmLang : mwmLanguages)
{
if (StringUtf8Multilang::kUnsupportedLanguageCode == mwmLang)
continue;
if (lang == mwmLang)
return true;
}
return false;
}
void EditableMapObject::SetMercator(m2::PointD const & center)
{
m_mercator = center;
}
void EditableMapObject::SetType(uint32_t featureType)
{
if (m_types.GetGeomType() == feature::GeomType::Undefined)
{
// Support only point type for newly created features.
m_types = feature::TypesHolder(feature::GeomType::Point);
m_types.Assign(featureType);
}
else
{
// Correctly replace "main" type in cases when feature holds more types.
ASSERT(!m_types.Empty(), ());
feature::TypesHolder copy = m_types;
// TODO(mgsergio): Replace by correct sorting from editor's config.
copy.SortBySpec();
m_types.Remove(*copy.begin());
m_types.Add(featureType);
}
}
void EditableMapObject::SetTypes(feature::TypesHolder const & types)
{
m_types = types;
}
void EditableMapObject::SetID(FeatureID const & fid)
{
m_featureID = fid;
}
void EditableMapObject::SetStreet(LocalizedStreet const & st)
{
m_street = st;
}
void EditableMapObject::SetNearbyStreets(vector<LocalizedStreet> && streets)
{
m_nearbyStreets = std::move(streets);
}
void EditableMapObject::SetHouseNumber(string const & houseNumber)
{
m_houseNumber = houseNumber;
}
void EditableMapObject::SetPostcode(std::string const & postcode)
{
m_metadata.Set(MetadataID::FMD_POSTCODE, postcode);
}
bool EditableMapObject::IsValidMetadata(MetadataID type, std::string const & value)
{
switch (type)
{
case MetadataID::FMD_WEBSITE: return ValidateWebsite(value);
case MetadataID::FMD_WEBSITE_MENU: return ValidateWebsite(value);
case MetadataID::FMD_CONTACT_FACEBOOK: return ValidateFacebookPage(value);
case MetadataID::FMD_CONTACT_INSTAGRAM: return ValidateInstagramPage(value);
case MetadataID::FMD_CONTACT_TWITTER: return ValidateTwitterPage(value);
case MetadataID::FMD_CONTACT_VK: return ValidateVkPage(value);
case MetadataID::FMD_CONTACT_LINE: return ValidateLinePage(value);
case MetadataID::FMD_CONTACT_FEDIVERSE: return ValidateFediversePage(value);
case MetadataID::FMD_CONTACT_BLUESKY: return ValidateBlueskyPage(value);
case MetadataID::FMD_STARS:
{
uint32_t stars;
return strings::to_uint(value, stars) && stars > 0 && stars <= feature::kMaxStarsCount;
}
case MetadataID::FMD_ELE:
{
/// @todo Reuse existing validadors in generator (osm2meta).
double ele;
return strings::to_double(value, ele) && ele > -11000 && ele < 9000;
}
case MetadataID::FMD_BUILDING_LEVELS: return ValidateBuildingLevels(value);
case MetadataID::FMD_LEVEL: return ValidateLevel(value);
case MetadataID::FMD_FLATS: return ValidateFlats(value);
case MetadataID::FMD_POSTCODE: return ValidatePostCode(value);
case MetadataID::FMD_PHONE_NUMBER: return ValidatePhoneList(value);
case MetadataID::FMD_EMAIL: return ValidateEmail(value);
default: return true;
}
}
void EditableMapObject::SetMetadata(MetadataID type, std::string value)
{
switch (type)
{
case MetadataID::FMD_WEBSITE: value = ValidateAndFormat_website(value); break;
case MetadataID::FMD_WEBSITE_MENU: value = ValidateAndFormat_website(value); break;
case MetadataID::FMD_CONTACT_FACEBOOK: value = ValidateAndFormat_facebook(value); break;
case MetadataID::FMD_CONTACT_INSTAGRAM: value = ValidateAndFormat_instagram(value); break;
case MetadataID::FMD_CONTACT_TWITTER: value = ValidateAndFormat_twitter(value); break;
case MetadataID::FMD_CONTACT_VK: value = ValidateAndFormat_vk(value); break;
case MetadataID::FMD_CONTACT_LINE: value = ValidateAndFormat_contactLine(value); break;
case MetadataID::FMD_CONTACT_FEDIVERSE: value = ValidateAndFormat_fediverse(value); break;
case MetadataID::FMD_CONTACT_BLUESKY: value = ValidateAndFormat_bluesky(value); break;
default: break;
}
m_metadata.Set(type, std::move(value));
}
bool EditableMapObject::UpdateMetadataValue(string_view key, string value)
{
MetadataID type;
if (!feature::Metadata::TypeFromString(key, type))
return false;
SetMetadata(type, std::move(value));
return true;
}
void EditableMapObject::SetOpeningHours(std::string oh)
{
m_metadata.Set(MetadataID::FMD_OPEN_HOURS, std::move(oh));
}
void EditableMapObject::SetChargeSockets(std::string sockets)
{
// parse the list of sockets provided by the frontend, and re-generate the
// socket list, thus ensuring it is valid & sorted.
ChargeSocketsHelper helper(sockets);
m_metadata.Set(MetadataID::FMD_CHARGE_SOCKETS, helper.ToString());
}
void EditableMapObject::SetInternet(feature::Internet internet)
{
m_metadata.Set(MetadataID::FMD_INTERNET, DebugPrint(internet));
uint32_t const wifiType = ftypes::IsWifiChecker::Instance().GetType();
bool const hasWiFi = m_types.Has(wifiType);
if (hasWiFi && internet != feature::Internet::Wlan)
m_types.Remove(wifiType);
else if (!hasWiFi && internet == feature::Internet::Wlan)
m_types.SafeAdd(wifiType);
}
LocalizedStreet const & EditableMapObject::GetStreet() const
{
return m_street;
}
template <class T>
void EditableMapObject::SetCuisinesImpl(vector<T> const & cuisines)
{
FeatureParams params;
// Ignore cuisine types as these will be set from the cuisines param
auto const & isCuisine = ftypes::IsCuisineChecker::Instance();
for (uint32_t const type : m_types)
if (!isCuisine(type))
params.m_types.push_back(type);
Classificator const & cl = classif();
for (auto const & cuisine : cuisines)
params.m_types.push_back(cl.GetTypeByPath({string_view("cuisine"), cuisine}));
// Move useless types to the end and resize to fit TypesHolder.
params.FinishAddingTypes();
m_types.Assign(params.m_types.begin(), params.m_types.end());
}
void EditableMapObject::SetCuisines(std::vector<std::string_view> const & cuisines)
{
SetCuisinesImpl(cuisines);
}
void EditableMapObject::SetCuisines(std::vector<std::string> const & cuisines)
{
SetCuisinesImpl(cuisines);
}
void EditableMapObject::SetPointType()
{
m_geomType = feature::GeomType::Point;
}
void EditableMapObject::RemoveBlankNames()
{
StringUtf8Multilang editedName;
m_name.ForEach([&editedName](int8_t langCode, string_view name)
{
if (!name.empty())
editedName.AddString(langCode, name);
});
m_name = editedName;
}
// static
bool EditableMapObject::ValidateBuildingLevels(string const & buildingLevels)
{
if (buildingLevels.empty())
return true;
if (buildingLevels.size() > 18 /* max number of digits in uint_64 */)
return false;
if ('0' == buildingLevels.front())
return false;
uint64_t levels;
return strings::to_uint64(buildingLevels, levels) && levels > 0 && levels <= kMaximumLevelsEditableByUsers;
}
// static
bool EditableMapObject::ValidateHouseNumber(string const & houseNumber)
{
// TODO(mgsergio): Use LooksLikeHouseNumber!
if (houseNumber.empty())
return true;
strings::UniString us = strings::MakeUniString(houseNumber);
// TODO: Improve this basic limit - it was choosen by @Zverik.
auto constexpr kMaxHouseNumberLength = 15;
if (us.size() > kMaxHouseNumberLength)
return false;
// TODO: Should we allow arabic numbers like U+0661 ١ Arabic-Indic Digit One?
strings::NormalizeDigits(us);
for (auto const c : us)
{
// Valid house numbers contain at least one digit.
if (strings::IsASCIIDigit(c))
return true;
}
return false;
}
bool EditableMapObject::CheckHouseNumberWhenIsAddress() const
{
// House number is mandatory for the address type. For other types it's optional.
return !m_houseNumber.empty() || !m_types.Has(classif().GetTypeByReadableObjectName("building-address"));
}
// static
bool EditableMapObject::ValidateFlats(string const & flats)
{
for (auto it = strings::SimpleTokenizer(flats, ";"); it; ++it)
{
string_view token = *it;
strings::Trim(token);
auto range = strings::Tokenize(token, "-");
if (range.empty() || range.size() > 2)
return false;
for (auto const & rangeBorder : range)
if (!all_of(begin(rangeBorder), end(rangeBorder), ::isalnum))
return false;
}
return true;
}
// static
bool EditableMapObject::ValidatePostCode(string const & postCode)
{
if (postCode.empty())
return true;
return search::LooksLikePostcode(postCode, false /* IsPrefix */);
}
// static
bool EditableMapObject::ValidatePhoneList(string const & phone)
{
// BNF:
// <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
// <available_char> ::= ' ' | '+' | '-' | '(' | ')'
// <delimeter> ::= ',' | ';'
// <phone> ::= (<digit> | <available_chars>)+
// <phone_list> ::= '' | <phone> | <phone> <delimeter> <phone_list>
if (phone.empty())
return true;
auto constexpr kMaxNumberLen = 15;
auto constexpr kMinNumberLen = 5;
if (phone.size() < kMinNumberLen)
return false;
auto curr = phone.begin();
auto last = phone.begin();
do
{
last = find_if(curr, phone.end(), [](string::value_type const & ch) { return ch == ',' || ch == ';'; });
auto digitsCount = 0;
string const symbols = "+-() ";
for (; curr != last; ++curr)
{
if (!isdigit(*curr) && find(symbols.begin(), symbols.end(), *curr) == symbols.end())
return false;
if (isdigit(*curr))
++digitsCount;
}
if (digitsCount < kMinNumberLen || digitsCount > kMaxNumberLen)
return false;
curr = last;
}
while (last != phone.end() && ++curr != phone.end());
return true;
}
// static
bool EditableMapObject::ValidateEmail(string const & email)
{
if (email.empty())
return true;
if (strings::IsASCIIString(email))
{
static auto const s_emailRegex = regex(R"([^@\s]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$)");
return regex_match(email, s_emailRegex);
}
if ('@' == email.front() || '@' == email.back())
return false;
if ('.' == email.back())
return false;
auto const atPos = find(begin(email), end(email), '@');
if (atPos == end(email))
return false;
// There should be only one '@' sign.
if (find(next(atPos), end(email), '@') != end(email))
return false;
// There should be at least one '.' sign after '@'
if (find(next(atPos), end(email), '.') == end(email))
return false;
return true;
}
// static
bool EditableMapObject::ValidateLevel(string const & level)
{
if (level.empty())
return true;
if (level.front() == ';' || level.back() == ';' || level.find(";;") != std::string::npos)
return false;
// validate ";" separated values separately
vector<std::string_view> const tokenizedValues = strings::Tokenize(level, ";");
for (std::string_view const & value : tokenizedValues)
{
auto const isValidNumber = [](std::string_view const & s)
{
auto constexpr kMinBuildingLevel = -9;
double valueDouble;
return strings::to_double(s, valueDouble) && valueDouble > kMinBuildingLevel && valueDouble <= kMaximumLevelsEditableByUsers;
};
// Check for simple value (e.g. "42")
if (!isValidNumber(value))
{
// Check for range (e.g. "-3-12")
size_t rangeSymbol = value.find('-', 1); // skip first as it could be a negative sign
if (rangeSymbol == std::string::npos)
return false;
std::string_view from = value.substr(0, rangeSymbol);
std::string_view to = value.substr(rangeSymbol + 1, value.size());
if (!isValidNumber(from) || !isValidNumber(to))
return false;
}
// Forbid leading zero (e.g. "04")
if (value.front() == '0' && value.size() >= 2 && value[1] != '.')
return false;
}
return true;
}
// static
bool EditableMapObject::ValidateName(string const & name)
{
if (name.empty())
return true;
static std::u32string_view constexpr excludedSymbols = U"^§><*=_±√•÷×";
using Iter = utf8::unchecked::iterator<string::const_iterator>;
for (Iter it{name.cbegin()}; it != Iter{name.cend()}; ++it)
{
auto const ch = *it;
// Exclude ASCII control characters.
if (ch <= 0x1F)
return false;
// Exclude {|}~ DEL and C1 control characters.
if (ch >= 0x7B && ch <= 0x9F)
return false;
// Exclude arrows, mathematical symbols, borders, geometric shapes.
if (ch >= 0x2190 && ch <= 0x2BFF)
return false;
// Emoji modifiers https://en.wikipedia.org/wiki/Emoji#Emoji_versus_text_presentation
if (ch == 0xFE0E || ch == 0xFE0F)
return false;
// Exclude format controls, musical symbols, emoticons, ornamental and pictographs,
// ancient and exotic alphabets.
if (ch >= 0xFFF0 && ch <= 0x1F9FF)
return false;
if (excludedSymbols.find(ch) != std::u32string_view::npos)
return false;
}
return true;
}
EditJournal const & EditableMapObject::GetJournal() const
{
return m_journal;
}
void EditableMapObject::SetJournal(EditJournal && editJournal)
{
m_journal = std::move(editJournal);
}
EditingLifecycle EditableMapObject::GetEditingLifecycle() const
{
return m_journal.GetEditingLifecycle();
}
void EditableMapObject::MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator)
{
m_journal.MarkAsCreated(type, geomType, std::move(mercator));
}
void EditableMapObject::MarkAsDisused()
{
auto types = GetTypes();
types.SortBySpec();
uint32_t old_type = *types.begin();
uint32_t new_type = classif().GetTypeByReadableObjectName("disusedbusiness");
ApplyBusinessReplacement(new_type);
m_journal.AddBusinessReplacement(old_type, new_type);
}
void EditableMapObject::ClearJournal()
{
m_journal.Clear();
}
void EditableMapObject::ApplyEditsFromJournal(EditJournal const & editJournal)
{
for (JournalEntry const & entry : editJournal.GetJournalHistory())
ApplyJournalEntry(entry);
for (JournalEntry const & entry : editJournal.GetJournal())
ApplyJournalEntry(entry);
}
void EditableMapObject::ApplyJournalEntry(JournalEntry const & entry)
{
LOG(LDEBUG, ("Applying Journal Entry: ", osm::EditJournal::ToString(entry)));
switch (entry.journalEntryType)
{
case JournalEntryType::TagModification:
{
TagModData const & tagModData = std::get<TagModData>(entry.data);
// Metadata
MetadataID type;
if (feature::Metadata::TypeFromString(tagModData.key, type))
{
m_metadata.Set(type, tagModData.new_value);
if (type == MetadataID::FMD_INTERNET)
{
uint32_t const wifiType = ftypes::IsWifiChecker::Instance().GetType();
if (tagModData.new_value == "wifi")
m_types.SafeAdd(wifiType);
else
m_types.Remove(wifiType);
}
break;
}
// Names
int8_t langCode = StringUtf8Multilang::GetCodeByOSMTag(tagModData.key);
if (langCode != StringUtf8Multilang::kUnsupportedLanguageCode)
{
m_name.AddString(langCode, tagModData.new_value);
break;
}
if (tagModData.key == "addr:street")
m_street.m_defaultName = tagModData.new_value;
else if (tagModData.key == "addr:housenumber")
m_houseNumber = tagModData.new_value;
else if (tagModData.key == "cuisine")
{
Classificator const & cl = classif();
// Remove old cuisine values
vector<std::string_view> oldCuisines = strings::Tokenize(tagModData.old_value, ";");
for (std::string_view const & cuisine : oldCuisines)
m_types.Remove(cl.GetTypeByPath({string_view("cuisine"), cuisine}));
// Add new cuisine values
vector<std::string_view> newCuisines = strings::Tokenize(tagModData.new_value, ";");
for (std::string_view const & cuisine : newCuisines)
m_types.SafeAdd(cl.GetTypeByPath({string_view("cuisine"), cuisine}));
}
else if (tagModData.key == "diet:vegetarian")
{
Classificator const & cl = classif();
uint32_t const vegetarianType = cl.GetTypeByPath({string_view("cuisine"), "vegetarian"});
if (tagModData.new_value == "yes")
m_types.SafeAdd(vegetarianType);
else
m_types.Remove(vegetarianType);
}
else if (tagModData.key == "diet:vegan")
{
Classificator const & cl = classif();
uint32_t const veganType = cl.GetTypeByPath({string_view("cuisine"), "vegan"});
if (tagModData.new_value == "yes")
m_types.SafeAdd(veganType);
else
m_types.Remove(veganType);
}
else
LOG(LWARNING, ("OSM key \"", tagModData.key, "\" is unknown, skipped"));
break;
}
case JournalEntryType::ObjectCreated:
{
ObjCreateData const & objCreatedData = std::get<ObjCreateData>(entry.data);
ASSERT_EQUAL(feature::GeomType::Point, objCreatedData.geomType,
("At the moment only new nodes (points) can be created."));
SetPointType();
SetMercator(objCreatedData.mercator);
m_types.Add(objCreatedData.type);
break;
}
case JournalEntryType::LegacyObject:
{
ASSERT_FAIL(("Legacy Objects can not be loaded from Journal"));
break;
}
case JournalEntryType::BusinessReplacement:
{
BusinessReplacementData const & businessReplacementData = std::get<BusinessReplacementData>(entry.data);
ApplyBusinessReplacement(businessReplacementData.new_type);
break;
}
}
}
void EditableMapObject::LogDiffInJournal(EditableMapObject const & unedited_emo)
{
LOG(LDEBUG, ("Executing LogDiffInJournal"));
// Name
for (StringUtf8Multilang::Lang language : StringUtf8Multilang::GetSupportedLanguages())
{
int8_t langCode = StringUtf8Multilang::GetLangIndex(language.m_code);
std::string_view new_name;
std::string_view old_name;
m_name.GetString(langCode, new_name);
unedited_emo.GetNameMultilang().GetString(langCode, old_name);
if (new_name != old_name)
{
std::string osmLangName = StringUtf8Multilang::GetOSMTagByCode(langCode);
m_journal.AddTagChange(std::move(osmLangName), std::string(old_name), std::string(new_name));
}
}
// Address
if (m_street.m_defaultName != unedited_emo.GetStreet().m_defaultName)
m_journal.AddTagChange("addr:street", unedited_emo.GetStreet().m_defaultName, m_street.m_defaultName);
if (m_houseNumber != unedited_emo.GetHouseNumber())
m_journal.AddTagChange("addr:housenumber", unedited_emo.GetHouseNumber(), m_houseNumber);
// Metadata
for (uint8_t i = 0; i < static_cast<uint8_t>(feature::Metadata::FMD_COUNT); ++i)
{
auto const type = static_cast<feature::Metadata::EType>(i);
// CHARGE_SOCKETS have multiple keys/values; handled separately further down
if (type == feature::Metadata::FMD_CHARGE_SOCKETS)
continue;
std::string_view const & value = GetMetadata(type);
std::string_view const & old_value = unedited_emo.GetMetadata(type);
if (value != old_value)
m_journal.AddTagChange(ToString(type), std::string(old_value), std::string(value));
}
// cuisine and diet
std::vector<std::string> new_cuisines = GetCuisines();
std::vector<std::string> old_cuisines = unedited_emo.GetCuisines();
auto const findAndErase = [](std::vector<std::string> & cuisinesPtr, std::string_view s)
{
auto it = std::find(cuisinesPtr.begin(), cuisinesPtr.end(), s);
if (it != cuisinesPtr.end())
{
cuisinesPtr.erase(it);
return "yes";
}
return "";
};
std::string new_vegetarian = findAndErase(new_cuisines, "vegetarian");
std::string old_vegetarian = findAndErase(old_cuisines, "vegetarian");
if (new_vegetarian != old_vegetarian)
m_journal.AddTagChange("diet:vegetarian", old_vegetarian, new_vegetarian);
std::string new_vegan = findAndErase(new_cuisines, "vegan");
std::string old_vegan = findAndErase(old_cuisines, "vegan");
if (new_vegan != old_vegan)
m_journal.AddTagChange("diet:vegan", old_vegan, new_vegan);
bool cuisinesModified = false;
if (new_cuisines.size() != old_cuisines.size())
cuisinesModified = true;
else
{
for (auto const & new_cuisine : new_cuisines)
{
if (!base::IsExist(old_cuisines, new_cuisine))
{
cuisinesModified = true;
break;
}
}
}
if (cuisinesModified)
m_journal.AddTagChange("cuisine", strings::JoinStrings(old_cuisines, ";"), strings::JoinStrings(new_cuisines, ";"));
// charge sockets
auto chargeSocketsDiff = ChargeSocketsHelper(GetChargeSockets()).Diff(unedited_emo.GetChargeSockets());
for (auto const & kvdiff : chargeSocketsDiff)
{
std::string key, old_value, new_value;
std::tie(key, old_value, new_value) = kvdiff;
m_journal.AddTagChange(key, old_value, new_value);
}
}
void EditableMapObject::ApplyBusinessReplacement(uint32_t new_type)
{
// Types
feature::TypesHolder new_feature_types;
new_feature_types.Add(new_type); // Update feature type
std::string wheelchairType = feature::GetReadableWheelchairType(m_types);
if (!wheelchairType.empty())
new_feature_types.SafeAdd(classif().GetTypeByReadableObjectName(wheelchairType));
std::vector<uint32_t> const buildingTypes = ftypes::IsBuildingChecker::Instance().GetTypes();
for (uint32_t const & type : buildingTypes)
if (m_types.Has(type))
new_feature_types.SafeAdd(type);
m_types = new_feature_types;
// Names
m_name.Clear();
// Metadata
feature::Metadata new_metadata;
constexpr MetadataID metadataToKeep[] = {
MetadataID::FMD_WHEELCHAIR,
MetadataID::FMD_POSTCODE,
MetadataID::FMD_LEVEL,
MetadataID::FMD_ELE,
MetadataID::FMD_HEIGHT,
MetadataID::FMD_MIN_HEIGHT,
MetadataID::FMD_BUILDING_LEVELS,
MetadataID::FMD_BUILDING_MIN_LEVEL
};
for (MetadataID const & metadataID : metadataToKeep)
new_metadata.Set(metadataID, std::string(m_metadata.Get(metadataID)));
m_metadata = new_metadata;
}
bool AreObjectsEqualIgnoringStreet(EditableMapObject const & lhs, EditableMapObject const & rhs)
{
feature::TypesHolder const & lhsTypes = lhs.GetTypes();
feature::TypesHolder const & rhsTypes = rhs.GetTypes();
if (!lhsTypes.Equals(rhsTypes))
return false;
if (lhs.GetHouseNumber() != rhs.GetHouseNumber())
return false;
if (lhs.GetCuisines() != rhs.GetCuisines())
return false;
if (!lhs.m_metadata.Equals(rhs.m_metadata))
return false;
if (lhs.GetNameMultilang() != rhs.GetNameMultilang())
return false;
return true;
}
} // namespace osm

View file

@ -0,0 +1,172 @@
#pragma once
#include "indexer/edit_journal.hpp"
#include "indexer/feature_data.hpp"
#include "indexer/feature_decl.hpp"
#include "indexer/feature_meta.hpp"
#include "indexer/feature_utils.hpp"
#include "indexer/map_object.hpp"
#include "coding/string_utf8_multilang.hpp"
#include <functional>
#include <string>
#include <vector>
namespace osm
{
/// Holds information to construct editor's UI.
struct EditableProperties
{
EditableProperties() = default;
EditableProperties(std::vector<feature::Metadata::EType> metadata, bool name, bool address, bool cuisine)
: m_metadata(std::move(metadata))
, m_name(name)
, m_address(address)
, m_cuisine(cuisine)
{}
bool IsEditable() const { return m_name || m_address || m_cuisine || !m_metadata.empty(); }
std::vector<feature::Metadata::EType> m_metadata;
bool m_name = false;
/// Can edit house number, street address and postcode.
bool m_address = false;
bool m_cuisine = false;
};
struct LocalizedName
{
LocalizedName(int8_t code, std::string_view name);
LocalizedName(std::string const & langCode, std::string const & name);
/// m_code, m_lang and m_langName are defined in StringUtf8Multilang.
int8_t const m_code;
/// Non-owning pointers to internal static char const * array.
std::string_view const m_lang;
std::string_view const m_langName;
std::string const m_name;
};
/// Class which contains vector of localized names with following priority:
/// 1. Default Name
/// 2. Other names
/// and mandatoryNamesCount - count of names which should be always shown.
struct NamesDataSource
{
std::vector<LocalizedName> names;
size_t mandatoryNamesCount = 0;
};
struct LocalizedStreet
{
std::string m_defaultName;
std::string m_localizedName;
bool operator==(LocalizedStreet const & st) const { return m_defaultName == st.m_defaultName; }
};
class EditableMapObject : public MapObject
{
public:
static uint8_t constexpr kMaximumLevelsEditableByUsers = 50;
bool IsNameEditable() const;
bool IsAddressEditable() const;
/// @todo Can implement polymorphic approach here and store map<MetadataID, MetadataEntryIFace>.
/// All store/load/valid operations will be via MetadataEntryIFace interface instead of switch-case.
std::vector<MetadataID> GetEditableProperties() const;
bool CanMarkPlaceAsDisused() const;
/// See comment for NamesDataSource class.
NamesDataSource GetNamesDataSource();
LocalizedStreet const & GetStreet() const;
std::vector<LocalizedStreet> const & GetNearbyStreets() const;
/// @note { tag, value } are temporary string views and can't be stored for later use.
void ForEachMetadataItem(std::function<void(std::string_view tag, std::string_view value)> const & fn) const;
// Used only in testing framework.
void SetTestId(uint64_t id);
void SetEditableProperties(osm::EditableProperties const & props);
// void SetFeatureID(FeatureID const & fid);
void SetName(StringUtf8Multilang const & name);
void SetName(std::string_view name, int8_t langCode);
void SetMercator(m2::PointD const & center);
void SetType(uint32_t featureType);
void SetTypes(feature::TypesHolder const & types);
void SetID(FeatureID const & fid);
void SetStreet(LocalizedStreet const & st);
void SetNearbyStreets(std::vector<LocalizedStreet> && streets);
void SetHouseNumber(std::string const & houseNumber);
void SetPostcode(std::string const & postcode);
static bool IsValidMetadata(MetadataID type, std::string const & value);
void SetMetadata(MetadataID type, std::string value);
bool UpdateMetadataValue(std::string_view key, std::string value);
void SetOpeningHours(std::string oh);
void SetChargeSockets(std::string sockets);
void SetInternet(feature::Internet internet);
/// @param[in] cuisine is a vector of osm cuisine ids.
private:
template <class T>
void SetCuisinesImpl(std::vector<T> const & cuisines);
public:
void SetCuisines(std::vector<std::string_view> const & cuisines);
void SetCuisines(std::vector<std::string> const & cuisines);
/// Special mark that it's a point feature, not area or line.
void SetPointType();
/// Remove blank names
void RemoveBlankNames();
static bool ValidateBuildingLevels(std::string const & buildingLevels);
static bool ValidateHouseNumber(std::string const & houseNumber);
bool CheckHouseNumberWhenIsAddress() const;
static bool ValidateFlats(std::string const & flats);
static bool ValidatePostCode(std::string const & postCode);
static bool ValidatePhoneList(std::string const & phone);
static bool ValidateEmail(std::string const & email);
static bool ValidateLevel(std::string const & level);
static bool ValidateName(std::string const & name);
/// Journal that stores changes to map object
EditJournal const & GetJournal() const;
void SetJournal(EditJournal && editJournal);
EditingLifecycle GetEditingLifecycle() const;
void MarkAsCreated(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
void MarkAsDisused();
void ClearJournal();
void ApplyEditsFromJournal(EditJournal const & journal);
void ApplyJournalEntry(JournalEntry const & entry);
void LogDiffInJournal(EditableMapObject const & unedited_emo);
private:
void ApplyBusinessReplacement(uint32_t new_type);
public:
/// Check whether langCode can be used as default name.
static bool CanUseAsDefaultName(int8_t const langCode, std::vector<int8_t> const & nativeMwmLanguages);
/// See comment for NamesDataSource class.
static NamesDataSource GetNamesDataSource(StringUtf8Multilang const & source,
std::vector<int8_t> const & nativeMwmLanguages, int8_t const userLanguage);
/// Compares editable fields connected with feature ignoring street.
friend bool AreObjectsEqualIgnoringStreet(EditableMapObject const & lhs, EditableMapObject const & rhs);
private:
LocalizedStreet m_street;
std::vector<LocalizedStreet> m_nearbyStreets;
EditableProperties m_editableProperties;
osm::EditJournal m_journal;
};
} // namespace osm

View file

@ -0,0 +1,9 @@
#include "indexer/fake_feature_ids.hpp"
namespace feature
{
// static
uint32_t constexpr FakeFeatureIds::k20BitsOffset;
// static
uint32_t constexpr FakeFeatureIds::kEditorCreatedFeaturesStart;
} // namespace feature

View file

@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
#include <limits>
// Before creating new fake feature interval note that routing has
// it's own fake features in routing/fake_feature_ids.hpp
namespace feature
{
struct FakeFeatureIds
{
static bool IsEditorCreatedFeature(uint32_t id) { return id >= kEditorCreatedFeaturesStart; }
static uint32_t constexpr k20BitsOffset = 0xfffff;
static uint32_t constexpr kEditorCreatedFeaturesStart = std::numeric_limits<uint32_t>::max() - k20BitsOffset;
};
} // namespace feature

909
libs/indexer/feature.cpp Normal file
View file

@ -0,0 +1,909 @@
#include "indexer/feature.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/feature_impl.hpp"
#include "indexer/feature_utils.hpp"
#include "indexer/map_object.hpp"
#include "indexer/shared_load_info.hpp"
#include "geometry/mercator.hpp"
#include "platform/preferred_languages.hpp"
#include "coding/byte_stream.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include <algorithm>
#include <limits>
using namespace feature;
using namespace std;
namespace
{
uint32_t constexpr kInvalidOffset = numeric_limits<uint32_t>::max();
bool IsRealGeomOffset(uint32_t offset)
{
return (offset != kInvalidOffset && offset != kGeomOffsetFallback);
}
// Get an index of inner geometry scale range.
// @param[in] scale:
// -1 : index for the best geometry
// -2 : index for the worst geometry
// default : index for the request scale
int GetScaleIndex(SharedLoadInfo const & loadInfo, int scale)
{
int const count = loadInfo.GetScalesCount();
switch (scale)
{
case FeatureType::WORST_GEOMETRY: return 0;
case FeatureType::BEST_GEOMETRY: return count - 1;
default:
// In case of WorldCoasts we should get correct last geometry.
int const lastScale = loadInfo.GetLastScale();
if (scale > lastScale)
scale = lastScale;
for (int i = 0; i < count; ++i)
if (scale <= loadInfo.GetScale(i))
return i;
ASSERT(false, ("No suitable geometry scale range in the map file."));
return -1;
}
}
// Get an index of outer geometry scale range.
int GetScaleIndex(SharedLoadInfo const & loadInfo, int scale, FeatureType::GeometryOffsets const & offsets)
{
int const count = loadInfo.GetScalesCount();
ASSERT_EQUAL(count, static_cast<int>(offsets.size()), ());
int ind = 0;
switch (scale)
{
case FeatureType::BEST_GEOMETRY:
// Choose the best existing geometry for the last visible scale.
ind = count - 1;
while (ind >= 0 && !IsRealGeomOffset(offsets[ind]))
--ind;
if (ind >= 0)
return ind;
break;
case FeatureType::WORST_GEOMETRY:
// Choose the worst existing geometry for the first visible scale.
while (ind < count && !IsRealGeomOffset(offsets[ind]))
++ind;
if (ind < count)
return ind;
break;
default:
{
// In case of WorldCoasts we should get correct last geometry.
int const lastScale = loadInfo.GetLastScale();
if (scale > lastScale)
scale = lastScale;
// If there is no geometry for the requested scale (kHasGeoOffsetFlag) fallback to the next more detailed one.
while (ind < count && (scale > loadInfo.GetScale(ind) || offsets[ind] == kGeomOffsetFallback))
++ind;
// Some WorldCoasts features have idx == 0 geometry only and its possible
// other features to be visible on e.g. idx == 1 only,
// but then they shouldn't be attempted to be drawn using other geom scales.
ASSERT_LESS(ind, count, ("No suitable geometry scale range in the map file."));
return (offsets[ind] != kInvalidOffset ? ind : -1);
}
}
ASSERT(false, ("A feature doesn't have any geometry."));
return -1;
}
uint32_t CalcOffset(ArrayByteSource const & source, uint8_t const * start)
{
ASSERT_GREATER_OR_EQUAL(source.PtrUint8(), start, ());
return static_cast<uint32_t>(distance(start, source.PtrUint8()));
}
uint8_t Header(vector<uint8_t> const & data)
{
CHECK(!data.empty(), ());
return data[0];
}
void ReadOffsets(SharedLoadInfo const & loadInfo, ArrayByteSource & src, uint8_t mask,
FeatureType::GeometryOffsets & offsets)
{
ASSERT(offsets.empty(), ());
ASSERT_GREATER(mask, 0, ());
offsets.resize(loadInfo.GetScalesCount(), kInvalidOffset);
size_t ind = 0;
while (mask > 0)
{
if (mask & 0x01)
offsets[ind] = ReadVarUint<uint32_t>(src);
++ind;
mask = mask >> 1;
}
}
class BitSource
{
uint8_t const * m_ptr;
uint8_t m_pos;
public:
explicit BitSource(uint8_t const * p) : m_ptr(p), m_pos(0) {}
uint8_t Read(uint8_t count)
{
ASSERT_LESS(count, 9, ());
uint8_t v = *m_ptr;
v >>= m_pos;
v &= ((1 << count) - 1);
m_pos += count;
if (m_pos >= 8)
{
ASSERT_EQUAL(m_pos, 8, ());
++m_ptr;
m_pos = 0;
}
return v;
}
uint8_t const * RoundPtr()
{
if (m_pos > 0)
{
++m_ptr;
m_pos = 0;
}
return m_ptr;
}
};
template <class TSource>
uint8_t ReadByte(TSource & src)
{
return ReadPrimitiveFromSource<uint8_t>(src);
}
} // namespace
FeatureType::FeatureType(SharedLoadInfo const * loadInfo, vector<uint8_t> && buffer)
: m_loadInfo(loadInfo)
, m_data(std::move(buffer))
{
CHECK(m_loadInfo, ());
m_header = Header(m_data); // Parse the header and optional name/layer/addinfo.
}
std::unique_ptr<FeatureType> FeatureType::CreateFromMapObject(osm::MapObject const & emo)
{
auto ft = std::unique_ptr<FeatureType>(new FeatureType());
HeaderGeomType headerGeomType = HeaderGeomType::Point;
ft->m_limitRect.MakeEmpty();
switch (emo.GetGeomType())
{
case feature::GeomType::Undefined:
// It is not possible because of FeatureType::GetGeomType() never returns GeomType::Undefined.
UNREACHABLE();
case feature::GeomType::Point:
headerGeomType = HeaderGeomType::Point;
ft->m_center = emo.GetMercator();
ft->m_limitRect.Add(ft->m_center);
break;
case feature::GeomType::Line:
headerGeomType = HeaderGeomType::Line;
assign_range(ft->m_points, emo.GetPoints());
for (auto const & p : ft->m_points)
ft->m_limitRect.Add(p);
break;
case feature::GeomType::Area:
headerGeomType = HeaderGeomType::Area;
assign_range(ft->m_triangles, emo.GetTriangesAsPoints());
for (auto const & p : ft->m_triangles)
ft->m_limitRect.Add(p);
break;
}
ft->m_parsed.m_points = ft->m_parsed.m_triangles = true;
ft->m_params.name = emo.GetNameMultilang();
string const & house = emo.GetHouseNumber();
if (house.empty())
ft->m_params.house.Clear();
else
ft->m_params.house.Set(house);
ft->m_parsed.m_common = true;
emo.AssignMetadata(ft->m_metadata);
ft->m_parsed.m_metadata = true;
ft->m_parsed.m_metaIds = true;
CHECK_LESS_OR_EQUAL(emo.GetTypes().Size(), feature::kMaxTypesCount, ());
copy(emo.GetTypes().begin(), emo.GetTypes().end(), ft->m_types.begin());
ft->m_parsed.m_types = true;
ft->m_header = CalculateHeader(emo.GetTypes().Size(), headerGeomType, ft->m_params);
ft->m_parsed.m_header2 = true;
ft->m_parsed.m_relations = true;
ft->m_id = emo.GetID();
return ft;
}
feature::GeomType FeatureType::GetGeomType() const
{
// FeatureType::FeatureType(osm::MapObject const & emo) expects
// that GeomType::Undefined is never returned.
auto const headerGeomType = static_cast<HeaderGeomType>(m_header & HEADER_MASK_GEOMTYPE);
switch (headerGeomType)
{
case HeaderGeomType::Line: return GeomType::Line;
case HeaderGeomType::Area: return GeomType::Area;
default: return GeomType::Point; // HeaderGeomType::Point/PointEx
}
}
void FeatureType::ParseTypes()
{
if (m_parsed.m_types)
return;
auto const typesOffset = sizeof(m_header);
Classificator & c = classif();
ArrayByteSource source(m_data.data() + typesOffset);
size_t const count = GetTypesCount();
for (size_t i = 0; i < count; ++i)
{
uint32_t index = ReadVarUint<uint32_t>(source);
uint32_t const type = c.GetTypeForIndex(index);
if (type > 0)
m_types[i] = type;
else
{
// Possible for newer MWMs with added types.
LOG(LWARNING, ("Incorrect type index for feature. FeatureID:", m_id, ". Incorrect index:", index,
". Loaded feature types:", m_types, ". Total count of types:", count));
m_types[i] = c.GetStubType();
}
}
m_offsets.m_common = CalcOffset(source, m_data.data());
m_parsed.m_types = true;
}
void FeatureType::ParseCommon()
{
if (m_parsed.m_common)
return;
ParseTypes();
ArrayByteSource source(m_data.data() + m_offsets.m_common);
uint8_t const h = Header(m_data);
m_params.Read(source, h);
if (GetGeomType() == GeomType::Point)
{
uint64_t decoded = ReadVarUint<uint64_t>(source);
if (m_loadInfo->m_version >= DatSectionHeader::Version::V1)
{
m_hasRelations = (decoded & 0x1) == 1;
decoded >>= 1;
}
auto const & cp = m_loadInfo->GetDefGeometryCodingParams();
m_center = PointUToPointD(coding::DecodePointDeltaFromUint(decoded, cp.GetBasePoint()), cp.GetCoordBits());
m_limitRect.Add(m_center);
}
m_offsets.m_header2 = CalcOffset(source, m_data.data());
m_parsed.m_common = true;
}
m2::PointD FeatureType::GetCenter()
{
ASSERT_EQUAL(GetGeomType(), feature::GeomType::Point, ());
ParseCommon();
return m_center;
}
int8_t FeatureType::GetLayer()
{
if ((m_header & feature::HEADER_MASK_HAS_LAYER) == 0)
return feature::LAYER_EMPTY;
ParseCommon();
return m_params.layer;
}
// TODO: One of the free bits could be used for a closed line flag to avoid storing identical first + last points.
void FeatureType::ParseHeader2()
{
if (m_parsed.m_header2)
return;
ParseCommon();
m_parsed.m_header2 = true;
auto const headerGeomType = static_cast<HeaderGeomType>(Header(m_data) & HEADER_MASK_GEOMTYPE);
if (headerGeomType != HeaderGeomType::Line && headerGeomType != HeaderGeomType::Area)
{
m_offsets.m_relations = m_offsets.m_header2;
return;
}
BitSource bitSource(m_data.data() + m_offsets.m_header2);
uint8_t elemsCount = bitSource.Read(4);
uint8_t geomScalesMask = 0;
if (m_loadInfo->m_version == DatSectionHeader::Version::V0)
{
// For outer geometry read the geom scales (offsets) mask.
// For inner geometry remaining 4 bits are not used.
if (elemsCount == 0)
geomScalesMask = bitSource.Read(4);
}
else
{
ASSERT(m_loadInfo->m_version >= DatSectionHeader::Version::V1, ());
bool const isOuter = (bitSource.Read(1) == 1);
if (isOuter)
{
geomScalesMask = elemsCount;
elemsCount = 0;
}
m_hasRelations = (bitSource.Read(1) == 1);
}
ArrayByteSource src(bitSource.RoundPtr());
serial::GeometryCodingParams const & cp = m_loadInfo->GetDefGeometryCodingParams();
if (headerGeomType == HeaderGeomType::Line)
{
if (elemsCount > 0)
{
// Inner geometry.
// Number of bytes in simplification mask:
// first and last points are never simplified/discarded,
// 2 bits are used per each other point, i.e.
// 3-6 pts - 1 byte, 7-10 pts - 2b, 11-14 pts - 3b.
/// @see FeatureBuilder::SerializeForMwm
int const count = (elemsCount - 2 + 3) / 4;
ASSERT_LESS(count, 4, ());
for (int i = 0; i < count; ++i)
{
uint32_t mask = ReadByte(src);
m_ptsSimpMask += (mask << (i << 3));
}
auto const * start = src.PtrUint8();
src = ArrayByteSource(serial::LoadInnerPath(start, elemsCount, cp, m_points));
// TODO: here and further m_innerStats is needed for stats calculation in generator_tool only
m_innerStats.m_points = CalcOffset(src, start);
}
else
{
// Outer geometry: first (base) point is stored in the header still.
auto const * start = src.PtrUint8();
m_points.emplace_back(serial::LoadPoint(src, cp));
m_innerStats.m_firstPoints = CalcOffset(src, start);
ReadOffsets(*m_loadInfo, src, geomScalesMask, m_offsets.m_pts);
}
}
else
{
ASSERT(headerGeomType == HeaderGeomType::Area, ());
if (elemsCount > 0)
{
// Inner geometry (strips).
elemsCount += 2;
auto const * start = src.PtrUint8();
src = ArrayByteSource(serial::LoadInnerTriangles(start, elemsCount, cp, m_triangles));
m_innerStats.m_strips = CalcOffset(src, start);
}
else
{
// Outer geometry.
ReadOffsets(*m_loadInfo, src, geomScalesMask, m_offsets.m_trg);
}
}
m_offsets.m_relations = CalcOffset(src, m_data.data());
}
void FeatureType::ParseRelations()
{
if (m_parsed.m_relations)
return;
ParseHeader2();
ArrayByteSource src(m_data.data() + m_offsets.m_relations);
if (m_hasRelations)
ReadVarUInt32SortedShortArray(src, m_relationIDs);
m_parsed.m_relations = true;
// Size of the whole header incl. inner geometry / triangles.
m_innerStats.m_size = CalcOffset(src, m_data.data());
}
void FeatureType::ResetGeometry()
{
// Do not reset geometry for features created from MapObjects.
if (!m_loadInfo)
return;
m_points.clear();
m_triangles.clear();
if (GetGeomType() != GeomType::Point)
m_limitRect = m2::RectD();
m_parsed.m_relations = m_parsed.m_header2 = m_parsed.m_points = m_parsed.m_triangles = false;
m_offsets.m_pts.clear();
m_offsets.m_trg.clear();
m_ptsSimpMask = 0;
}
void FeatureType::ParseGeometry(int scale)
{
if (!m_parsed.m_points)
{
ParseHeader2();
auto const headerGeomType = static_cast<HeaderGeomType>(Header(m_data) & HEADER_MASK_GEOMTYPE);
if (headerGeomType == HeaderGeomType::Line)
{
size_t const pointsCount = m_points.size();
if (pointsCount < 2)
{
ASSERT_EQUAL(pointsCount, 1, ());
// Outer geometry.
int const ind = GetScaleIndex(*m_loadInfo, scale, m_offsets.m_pts);
if (ind != -1)
{
ReaderSource<FilesContainerR::TReader> src(m_loadInfo->GetGeometryReader(ind));
src.Skip(m_offsets.m_pts[ind]);
serial::GeometryCodingParams cp = m_loadInfo->GetGeometryCodingParams(ind);
cp.SetBasePoint(m_points[0]);
serial::LoadOuterPath(src, cp, m_points);
}
}
else
{
// Filter inner geometry according to points simplification mask.
PointsBufferT points;
points.reserve(pointsCount);
int const ind = GetScaleIndex(*m_loadInfo, scale);
points.emplace_back(m_points.front());
for (size_t i = 1; i + 1 < pointsCount; ++i)
{
// Check if the point is visible in the requested scale index.
if (static_cast<int>((m_ptsSimpMask >> (2 * (i - 1))) & 0x3) <= ind)
points.emplace_back(m_points[i]);
}
points.emplace_back(m_points.back());
m_points.swap(points);
}
CalcRect(m_points, m_limitRect);
}
m_parsed.m_points = true;
}
}
FeatureType::GeomStat FeatureType::GetOuterGeometryStats()
{
CHECK(m_loadInfo && m_parsed.m_relations && !m_parsed.m_points, ());
size_t const scalesCount = m_loadInfo->GetScalesCount();
ASSERT_LESS_OR_EQUAL(scalesCount, DataHeader::kMaxScalesCount, ());
FeatureType::GeomStat res;
auto const headerGeomType = static_cast<HeaderGeomType>(Header(m_data) & HEADER_MASK_GEOMTYPE);
if (headerGeomType == HeaderGeomType::Line)
{
size_t const pointsCount = m_points.size();
if (pointsCount < 2)
{
// Outer geometry present.
ASSERT_EQUAL(pointsCount, 1, ());
PointsBufferT points;
for (size_t ind = 0; ind < scalesCount; ++ind)
{
uint32_t const scaleOffset = m_offsets.m_pts[ind];
if (IsRealGeomOffset(scaleOffset))
{
points.clear();
points.emplace_back(m_points.front());
ReaderSource<FilesContainerR::TReader> src(m_loadInfo->GetGeometryReader(ind));
src.Skip(scaleOffset);
serial::GeometryCodingParams cp = m_loadInfo->GetGeometryCodingParams(static_cast<int>(ind));
cp.SetBasePoint(points[0]);
serial::LoadOuterPath(src, cp, points);
res.m_sizes[ind] = static_cast<uint32_t>(src.Pos() - scaleOffset);
res.m_elements[ind] = static_cast<uint32_t>(points.size());
}
}
// Retain best geometry.
m_points.swap(points);
}
CalcRect(m_points, m_limitRect);
}
m_parsed.m_points = true;
// Points count can come from the inner geometry.
res.m_elements[scalesCount - 1] = static_cast<uint32_t>(m_points.size());
return res;
}
void FeatureType::ParseTriangles(int scale)
{
if (!m_parsed.m_triangles)
{
ParseHeader2();
auto const headerGeomType = static_cast<HeaderGeomType>(Header(m_data) & HEADER_MASK_GEOMTYPE);
if (headerGeomType == HeaderGeomType::Area)
{
if (m_triangles.empty())
{
int const ind = GetScaleIndex(*m_loadInfo, scale, m_offsets.m_trg);
if (ind != -1)
{
ReaderSource<FilesContainerR::TReader> src(m_loadInfo->GetTrianglesReader(ind));
src.Skip(m_offsets.m_trg[ind]);
serial::LoadOuterTriangles(src, m_loadInfo->GetGeometryCodingParams(ind), m_triangles);
}
}
CalcRect(m_triangles, m_limitRect);
}
m_parsed.m_triangles = true;
}
}
FeatureType::GeomStat FeatureType::GetOuterTrianglesStats()
{
CHECK(m_loadInfo && m_parsed.m_relations && !m_parsed.m_triangles, ());
int const scalesCount = m_loadInfo->GetScalesCount();
ASSERT_LESS_OR_EQUAL(scalesCount, static_cast<int>(DataHeader::kMaxScalesCount), ());
FeatureType::GeomStat res;
auto const headerGeomType = static_cast<HeaderGeomType>(Header(m_data) & HEADER_MASK_GEOMTYPE);
if (headerGeomType == HeaderGeomType::Area)
{
if (m_triangles.empty())
{
for (int ind = 0; ind < scalesCount; ++ind)
{
uint32_t const scaleOffset = m_offsets.m_trg[ind];
if (IsRealGeomOffset(scaleOffset))
{
m_triangles.clear();
ReaderSource<FilesContainerR::TReader> src(m_loadInfo->GetTrianglesReader(ind));
src.Skip(scaleOffset);
serial::LoadOuterTriangles(src, m_loadInfo->GetGeometryCodingParams(ind), m_triangles);
res.m_sizes[ind] = static_cast<uint32_t>(src.Pos() - scaleOffset);
res.m_elements[ind] = static_cast<uint32_t>(m_triangles.size() / 3);
}
}
// The best geometry is retained in m_triangles.
}
CalcRect(m_triangles, m_limitRect);
}
m_parsed.m_triangles = true;
// Triangles count can come from the inner geometry.
res.m_elements[scalesCount - 1] = static_cast<uint32_t>(m_triangles.size() / 3);
return res;
}
void FeatureType::ParseMetadata()
{
if (m_parsed.m_metadata)
return;
CHECK(m_loadInfo->m_metaDeserializer, ());
try
{
UNUSED_VALUE(m_loadInfo->m_metaDeserializer->Get(m_id.m_index, m_metadata));
}
catch (Reader::OpenException const &)
{
LOG(LERROR, ("Error reading metadata", m_id));
}
m_parsed.m_metadata = true;
}
void FeatureType::ParseMetaIds()
{
if (m_parsed.m_metaIds)
return;
CHECK(m_loadInfo->m_metaDeserializer, ());
try
{
UNUSED_VALUE(m_loadInfo->m_metaDeserializer->GetIds(m_id.m_index, m_metaIds));
}
catch (Reader::OpenException const &)
{
LOG(LERROR, ("Error reading metadata", m_id));
}
m_parsed.m_metaIds = true;
}
StringUtf8Multilang const & FeatureType::GetNames()
{
ParseCommon();
return m_params.name;
}
std::string FeatureType::DebugString()
{
ParseGeometryAndTriangles(FeatureType::BEST_GEOMETRY);
std::string res = DebugPrint(m_id) + " " + DebugPrint(GetGeomType());
m2::PointD keyPoint;
switch (GetGeomType())
{
case GeomType::Point: keyPoint = m_center; break;
case GeomType::Line:
if (m_points.empty())
break;
keyPoint = m_points.front();
break;
case GeomType::Area:
if (m_triangles.empty())
break;
ASSERT_GREATER(m_triangles.size(), 2, ());
keyPoint = (m_triangles[0] + m_triangles[1] + m_triangles[2]) / 3.0;
break;
case GeomType::Undefined: ASSERT(false, ()); break;
}
// Print coordinates in (lat,lon) for better investigation capabilities.
res += ": " + DebugPrint(keyPoint) + "; " + DebugPrint(mercator::ToLatLon(keyPoint)) + "\n";
Classificator const & c = classif();
res += "Types";
uint32_t const count = GetTypesCount();
for (size_t i = 0; i < count; ++i)
res += (" : " + c.GetReadableObjectName(m_types[i]));
res += "\n";
auto const paramsStr = m_params.DebugString();
if (!paramsStr.empty())
res += paramsStr + "\n";
return res;
}
m2::RectD FeatureType::GetLimitRect(int scale)
{
ParseGeometryAndTriangles(scale);
if (m_triangles.empty() && m_points.empty() && (GetGeomType() != GeomType::Point))
{
ASSERT(false, (m_id));
// This function is called during indexing, when we need
// to check visibility according to feature sizes.
// So, if no geometry for this scale, assume that rect has zero dimensions.
m_limitRect = m2::RectD(0, 0, 0, 0);
}
return m_limitRect;
}
m2::RectD const & FeatureType::GetLimitRectChecked() const
{
ASSERT(m_parsed.m_points && m_parsed.m_triangles, (m_id));
ASSERT(m_limitRect.IsValid(), (m_id));
return m_limitRect;
}
bool FeatureType::IsEmptyGeometry(int scale)
{
ParseGeometryAndTriangles(scale);
switch (GetGeomType())
{
case GeomType::Area: return m_triangles.empty();
case GeomType::Line: return m_points.empty();
default: return false;
}
}
size_t FeatureType::GetPointsCount() const
{
ASSERT(m_parsed.m_points, ());
return m_points.size();
}
m2::PointD const & FeatureType::GetPoint(size_t i) const
{
ASSERT_LESS(i, m_points.size(), ());
ASSERT(m_parsed.m_points, ());
return m_points[i];
}
FeatureType::PointsBufferT const & FeatureType::GetPoints(int scale)
{
ParseGeometry(scale);
return m_points;
}
FeatureType::PointsBufferT const & FeatureType::GetTrianglesAsPoints(int scale)
{
ParseTriangles(scale);
return m_triangles;
}
void FeatureType::ParseGeometryAndTriangles(int scale)
{
ParseGeometry(scale);
ParseTriangles(scale);
}
void FeatureType::GetPreferredNames(bool allowTranslit, int8_t deviceLang, feature::NameParamsOut & out)
{
if (!HasName())
return;
auto const & mwmInfo = m_id.m_mwmId.GetInfo();
if (!mwmInfo)
return;
ParseCommon();
feature::GetPreferredNames({GetNames(), mwmInfo->GetRegionData(), deviceLang, allowTranslit}, out);
}
string_view FeatureType::GetReadableName()
{
feature::NameParamsOut out;
GetReadableName(false /* allowTranslit */, StringUtf8Multilang::GetLangIndex(languages::GetCurrentMapLanguage()),
out);
return out.primary;
}
void FeatureType::GetReadableName(bool allowTranslit, int8_t deviceLang, feature::NameParamsOut & out)
{
if (!HasName())
return;
auto const & mwmInfo = m_id.m_mwmId.GetInfo();
if (!mwmInfo)
return;
ParseCommon();
feature::GetReadableName({GetNames(), mwmInfo->GetRegionData(), deviceLang, allowTranslit}, out);
}
string const & FeatureType::GetHouseNumber()
{
ParseCommon();
return m_params.house.Get();
}
string_view FeatureType::GetName(int8_t lang)
{
if (!HasName())
return {};
ParseCommon();
// We don't store empty names. UPD: We do for coast features :)
string_view name;
if (m_params.name.GetString(lang, name))
ASSERT(!name.empty() || m_id.m_mwmId.GetInfo()->GetType() == MwmInfo::COASTS, ());
return name;
}
uint8_t FeatureType::GetRank()
{
ParseCommon();
return m_params.rank;
}
uint64_t FeatureType::GetPopulation()
{
return feature::RankToPopulation(GetRank());
}
string const & FeatureType::GetRef()
{
ParseCommon();
return m_params.ref;
}
feature::Metadata const & FeatureType::GetMetadata()
{
ParseMetadata();
return m_metadata;
}
std::string_view FeatureType::GetMetadata(feature::Metadata::EType type)
{
ParseMetaIds();
auto meta = m_metadata.Get(type);
if (meta.empty())
{
auto const it = base::FindIf(m_metaIds, [&type](auto const & v) { return v.first == type; });
if (it != m_metaIds.end())
meta = m_metadata.Set(type, m_loadInfo->m_metaDeserializer->GetMetaById(it->second));
}
return meta;
}
bool FeatureType::HasMetadata(feature::Metadata::EType type)
{
ParseMetaIds();
if (m_metadata.Has(type))
return true;
return base::FindIf(m_metaIds, [&type](auto const & v) { return v.first == type; }) != m_metaIds.end();
}
FeatureType::RelationIDsV const & FeatureType::GetRelations()
{
ParseRelations();
return m_relationIDs;
}
/// @pre id is from m_relationIDs.
feature::RouteRelationBase FeatureType::ReadRelation(uint32_t id)
{
ParseRelations();
return m_loadInfo->ReadRelation(id);
}

274
libs/indexer/feature.hpp Normal file
View file

@ -0,0 +1,274 @@
#pragma once
#include "indexer/feature_data.hpp"
#include "indexer/metadata_serdes.hpp"
#include "indexer/route_relation.hpp"
#include "geometry/point2d.hpp"
#include "geometry/rect2d.hpp"
#include "base/buffer_vector.hpp"
#include "base/macros.hpp"
#include <array>
#include <string>
#include <vector>
namespace feature
{
class SharedLoadInfo;
struct NameParamsOut; // Include feature_utils.hpp when using
// "fallback" flag value (1 is taken to distinguish from the "normal" offset value),
// which means geometry should be loaded from the next offset of the more detailed geom level.
uint32_t constexpr kGeomOffsetFallback = 1;
} // namespace feature
namespace osm
{
class MapObject;
}
// Lazy feature loader. Loads needed data and caches it.
class FeatureType
{
FeatureType() = default;
public:
using GeometryOffsets = buffer_vector<uint32_t, feature::DataHeader::kMaxScalesCount>;
FeatureType(feature::SharedLoadInfo const * loadInfo, std::vector<uint8_t> && buffer);
static std::unique_ptr<FeatureType> CreateFromMapObject(osm::MapObject const & emo);
feature::GeomType GetGeomType() const;
uint8_t GetTypesCount() const { return (m_header & feature::HEADER_MASK_TYPE) + 1; }
bool HasName() const { return (m_header & feature::HEADER_MASK_HAS_NAME) != 0; }
StringUtf8Multilang const & GetNames();
m2::PointD GetCenter();
template <class T>
bool ForEachName(T && fn)
{
if (!HasName())
return false;
ParseCommon();
m_params.name.ForEach(std::forward<T>(fn));
return true;
}
template <typename ToDo>
void ForEachType(ToDo && f)
{
ParseTypes();
uint32_t const count = GetTypesCount();
for (size_t i = 0; i < count; ++i)
f(m_types[i]);
}
int8_t GetLayer();
// For better result this value should be greater than 17
// (number of points in inner triangle-strips).
using PointsBufferT = buffer_vector<m2::PointD, 32>;
PointsBufferT const & GetPoints(int scale);
PointsBufferT const & GetTrianglesAsPoints(int scale);
void SetID(FeatureID id) { m_id = std::move(id); }
FeatureID const & GetID() const { return m_id; }
void ParseHeader2();
void ParseRelations();
void ParseAllBeforeGeometry() { ParseRelations(); }
void ResetGeometry();
void ParseGeometry(int scale);
void ParseTriangles(int scale);
//@}
/// @name Geometry.
//@{
/// This constant values should be equal with feature::FeatureLoader implementation.
enum
{
BEST_GEOMETRY = -1,
WORST_GEOMETRY = -2
};
m2::RectD GetLimitRect(int scale);
m2::RectD const & GetLimitRectChecked() const;
bool IsEmptyGeometry(int scale);
template <typename Functor>
void ForEachPoint(Functor && f, int scale)
{
ParseGeometry(scale);
if (m_points.empty())
{
// it's a point feature
if (GetGeomType() == feature::GeomType::Point)
f(m_center);
}
else
{
for (size_t i = 0; i < m_points.size(); ++i)
f(m_points[i]);
}
}
size_t GetPointsCount() const;
size_t GetTrgVerticesCount(int scale)
{
ParseTriangles(scale);
return m_triangles.size();
}
m2::PointD const & GetPoint(size_t i) const;
template <typename TFunctor>
void ForEachTriangle(TFunctor && f, int scale)
{
ParseTriangles(scale);
for (size_t i = 0; i < m_triangles.size();)
{
f(m_triangles[i], m_triangles[i + 1], m_triangles[i + 2]);
i += 3;
}
}
template <typename Functor>
void ForEachTriangleEx(Functor && f, int scale)
{
f.StartPrimitive(m_triangles.size());
ForEachTriangle(std::forward<Functor>(f), scale);
f.EndPrimitive();
}
//@}
// No DebugPrint(f) as it requires its parameter to be const, but a feature is lazy loaded.
std::string DebugString();
std::string const & GetHouseNumber();
/// @name Get names for feature.
//@{
void GetPreferredNames(bool allowTranslit, int8_t deviceLang, feature::NameParamsOut & out);
/// Get one most suitable name for user.
std::string_view GetReadableName();
void GetReadableName(bool allowTranslit, int8_t deviceLang, feature::NameParamsOut & out);
std::string_view GetName(int8_t lang);
//@}
uint8_t GetRank();
uint64_t GetPopulation();
std::string const & GetRef();
feature::Metadata const & GetMetadata();
// Gets single metadata string. Does not parse all metadata.
std::string_view GetMetadata(feature::Metadata::EType type);
bool HasMetadata(feature::Metadata::EType type);
/// @name Stats functions.
//@{
struct InnerGeomStat
{
uint32_t m_points = 0, m_firstPoints = 0, m_strips = 0, m_size = 0;
};
InnerGeomStat GetInnerStats() const { return m_innerStats; }
using GeomArr = uint32_t[feature::DataHeader::kMaxScalesCount];
struct GeomStat
{
GeomArr m_sizes = {}, m_elements = {};
};
// Returns outer points/triangles stats for all geo levels and loads the best geometry.
GeomStat GetOuterGeometryStats();
GeomStat GetOuterTrianglesStats();
//@}
using RelationIDsV = feature::ShortArray;
RelationIDsV const & GetRelations();
feature::RouteRelationBase ReadRelation(uint32_t id);
private:
struct ParsedFlags
{
bool m_types : 1;
bool m_common : 1;
bool m_header2 : 1;
bool m_relations : 1;
bool m_points : 1;
bool m_triangles : 1;
bool m_metadata : 1;
bool m_metaIds : 1;
ParsedFlags() { Reset(); }
void Reset()
{
m_types = m_common = m_header2 = m_relations = m_points = m_triangles = m_metadata = m_metaIds = false;
}
};
struct Offsets
{
uint32_t m_common = 0;
uint32_t m_header2 = 0;
uint32_t m_relations = 0;
GeometryOffsets m_pts;
GeometryOffsets m_trg;
void Reset()
{
m_common = m_header2 = m_relations = 0;
m_pts.clear();
m_trg.clear();
}
};
void ParseTypes();
void ParseCommon();
void ParseMetadata();
void ParseMetaIds();
void ParseGeometryAndTriangles(int scale);
uint8_t m_header = 0;
std::array<uint32_t, feature::kMaxTypesCount> m_types = {};
FeatureID m_id;
FeatureParamsBase m_params;
m2::PointD m_center;
m2::RectD m_limitRect;
PointsBufferT m_points, m_triangles;
feature::Metadata m_metadata;
indexer::MetadataDeserializer::MetaIds m_metaIds;
// Non-owning pointer to shared load info. SharedLoadInfo created once per FeaturesVector.
feature::SharedLoadInfo const * m_loadInfo = nullptr;
std::vector<uint8_t> m_data;
ParsedFlags m_parsed;
Offsets m_offsets;
uint32_t m_ptsSimpMask = 0;
RelationIDsV m_relationIDs;
bool m_hasRelations = false;
InnerGeomStat m_innerStats;
DISALLOW_COPY_AND_MOVE(FeatureType);
};

View file

@ -0,0 +1,119 @@
#include "indexer/feature_algo.hpp"
#include "indexer/feature.hpp"
#include "geometry/algorithm.hpp"
#include "geometry/mercator.hpp"
#include "geometry/parametrized_segment.hpp"
#include "geometry/triangle2d.hpp"
#include <limits>
namespace feature
{
/// @returns point on a feature that is the closest to f.GetLimitRect().Center().
/// It is used by many entities in the core. Do not modify it's
/// logic unless you really-really know what you are doing.
m2::PointD GetCenter(FeatureType & f, int scale)
{
GeomType const type = f.GetGeomType();
switch (type)
{
case GeomType::Point:
{
return f.GetCenter();
}
/// @todo cache calculated area and line centers, as calculation could be quite heavy for big features (and
/// involves geometry reading) and a center could be requested multiple times during e.g. search, PP opening, etc.
case GeomType::Line:
{
m2::CalculatePolyLineCenter doCalc;
f.ForEachPoint(doCalc, scale);
return doCalc.GetResult();
}
default:
{
ASSERT_EQUAL(type, GeomType::Area, ());
m2::CalculatePointOnSurface doCalc(f.GetLimitRect(scale));
f.ForEachTriangle(doCalc, scale);
return doCalc.GetResult();
}
}
}
m2::PointD GetCenter(FeatureType & f)
{
return GetCenter(f, FeatureType::BEST_GEOMETRY);
}
double GetMinDistanceMeters(FeatureType & ft, m2::PointD const & pt, int scale)
{
double res = std::numeric_limits<double>::max();
auto updateDistanceFn = [&](m2::PointD const & p)
{
double const d = mercator::DistanceOnEarth(p, pt);
if (d < res)
res = d;
};
GeomType const type = ft.GetGeomType();
switch (type)
{
case GeomType::Point: updateDistanceFn(ft.GetCenter()); break;
case GeomType::Line:
{
ft.ParseGeometry(scale);
size_t const count = ft.GetPointsCount();
for (size_t i = 1; i < count; ++i)
{
m2::ParametrizedSegment<m2::PointD> segment(ft.GetPoint(i - 1), ft.GetPoint(i));
updateDistanceFn(segment.ClosestPointTo(pt));
}
break;
}
default:
ASSERT_EQUAL(type, GeomType::Area, ());
ft.ForEachTriangle([&](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
{
if (res == 0.0)
return;
if (m2::IsPointInsideTriangle(pt, p1, p2, p3))
{
res = 0.0;
return;
}
auto fn = [&](m2::PointD const & x1, m2::PointD const & x2)
{
m2::ParametrizedSegment<m2::PointD> const segment(x1, x2);
updateDistanceFn(segment.ClosestPointTo(pt));
};
fn(p1, p2);
fn(p2, p3);
fn(p3, p1);
}, scale);
break;
}
return res;
}
double GetMinDistanceMeters(FeatureType & ft, m2::PointD const & pt)
{
return GetMinDistanceMeters(ft, pt, FeatureType::BEST_GEOMETRY);
}
double CalcArea(FeatureType & ft)
{
double area = 0;
if (ft.GetGeomType() == GeomType::Area)
{
ft.ForEachTriangle([&area](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
{ area += m2::GetTriangleArea(p1, p2, p3); }, FeatureType::BEST_GEOMETRY);
}
return area;
}
} // namespace feature

View file

@ -0,0 +1,30 @@
#pragma once
#include "geometry/point2d.hpp"
#include "geometry/rect2d.hpp"
class FeatureType;
namespace feature
{
m2::PointD GetCenter(FeatureType & f, int scale);
m2::PointD GetCenter(FeatureType & f);
double GetMinDistanceMeters(FeatureType & ft, m2::PointD const & pt, int scale);
double GetMinDistanceMeters(FeatureType & ft, m2::PointD const & pt);
double CalcArea(FeatureType & ft);
template <class Iter>
void CalcRect(Iter b, Iter e, m2::RectD & rect)
{
while (b != e)
rect.Add(*b++);
}
template <class Cont>
void CalcRect(Cont const & points, m2::RectD & rect)
{
CalcRect(points.begin(), points.end(), rect);
}
} // namespace feature

View file

@ -0,0 +1,124 @@
#pragma once
#include "coding/bit_streams.hpp"
#include "coding/elias_coder.hpp"
#include "coding/reader.hpp"
#include "coding/varint.hpp"
#include "coding/write_to_sink.hpp"
#include "geometry/point_with_altitude.hpp"
#include "base/assert.hpp"
#include "base/bits.hpp"
#include <cstdint>
#include <limits>
#include <string>
#include <vector>
namespace feature
{
struct AltitudeHeader
{
using TAltitudeSectionVersion = uint16_t;
AltitudeHeader() { Reset(); }
template <class TSink>
void Serialize(TSink & sink) const
{
WriteToSink(sink, m_version);
WriteToSink(sink, m_minAltitude);
WriteToSink(sink, m_featureTableOffset);
WriteToSink(sink, m_altitudesOffset);
WriteToSink(sink, m_endOffset);
}
template <class TSource>
void Deserialize(TSource & src)
{
m_version = ReadPrimitiveFromSource<TAltitudeSectionVersion>(src);
m_minAltitude = ReadPrimitiveFromSource<geometry::Altitude>(src);
m_featureTableOffset = ReadPrimitiveFromSource<uint32_t>(src);
m_altitudesOffset = ReadPrimitiveFromSource<uint32_t>(src);
m_endOffset = ReadPrimitiveFromSource<uint32_t>(src);
}
// Methods below return sizes of parts of altitude section in bytes.
size_t GetAltitudeAvailabilitySize() const { return m_featureTableOffset - sizeof(AltitudeHeader); }
size_t GetFeatureTableSize() const { return m_altitudesOffset - m_featureTableOffset; }
size_t GetAltitudeInfoSize() const { return m_endOffset - m_altitudesOffset; }
void Reset()
{
m_version = 0;
m_minAltitude = geometry::kDefaultAltitudeMeters;
m_featureTableOffset = 0;
m_altitudesOffset = 0;
m_endOffset = 0;
}
TAltitudeSectionVersion m_version;
geometry::Altitude m_minAltitude;
uint32_t m_featureTableOffset;
uint32_t m_altitudesOffset;
uint32_t m_endOffset;
};
static_assert(sizeof(AltitudeHeader) == 16, "Wrong header size of altitude section.");
class Altitudes
{
public:
Altitudes() = default;
explicit Altitudes(geometry::Altitudes && altitudes) : m_altitudes(std::move(altitudes)) {}
template <class TSink>
void Serialize(geometry::Altitude minAltitude, TSink & sink) const
{
CHECK(!m_altitudes.empty(), ());
BitWriter<TSink> bits(sink);
geometry::Altitude prevAltitude = minAltitude;
for (auto const altitude : m_altitudes)
{
CHECK_LESS_OR_EQUAL(minAltitude, altitude, ());
uint32_t const delta = bits::ZigZagEncode(static_cast<int32_t>(altitude) - static_cast<int32_t>(prevAltitude));
// Making serialized value greater than zero.
CHECK(coding::DeltaCoder::Encode(bits, delta + 1), ());
prevAltitude = altitude;
}
}
template <class TSource>
void Deserialize(geometry::Altitude minAltitude, size_t pointCount, std::string const & countryFileName,
uint32_t featureId, TSource & src)
{
ASSERT_NOT_EQUAL(pointCount, 0, ());
BitReader<TSource> bits(src);
geometry::Altitude prevAltitude = minAltitude;
m_altitudes.resize(pointCount);
for (size_t i = 0; i < pointCount; ++i)
{
uint64_t const delta = coding::DeltaCoder::Decode(bits);
CHECK(delta > 0, (countryFileName, featureId));
m_altitudes[i] = static_cast<geometry::Altitude>(bits::ZigZagDecode(delta - 1) + prevAltitude);
CHECK_LESS_OR_EQUAL(minAltitude, m_altitudes[i], (countryFileName, featureId));
prevAltitude = m_altitudes[i];
}
}
/// \note |m_altitudes| is a vector of feature point altitudes. There's two possibilities:
/// * |m_altitudes| is empty. It means there is no altitude information for this feature.
/// * size of |m_pointAlt| is equal to the number of this feature's points. If so
/// all items of |m_altitudes| have valid value.
geometry::Altitudes m_altitudes;
};
} // namespace feature

View file

@ -0,0 +1,445 @@
#include "indexer/feature_charge_sockets.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <iomanip> // for formatting doubles
#include <sstream> // for std::to_string alternative
#include <string_view>
#include <vector>
// helper to format doubles (avoids trailing zeros)
inline std::string to_string_trimmed(double value, int precision = 2)
{
std::ostringstream oss;
oss << std::fixed << std::setprecision(precision) << value;
auto str = oss.str();
// Remove trailing zeros
auto pos = str.find('.');
if (pos != std::string::npos)
{
// erase trailing zeros
while (!str.empty() && str.back() == '0')
str.pop_back();
// if we end with a '.', remove it too
if (!str.empty() && str.back() == '.')
str.pop_back();
}
return str;
}
// we use a custom tokenizer, as strings::Tokenize discards empty
// tokens and we want to keep them:
//
// Example:
// "a||" → ["a", "", ""]
// "b|0|" → ["b", "0", ""]
// "c||34" → ["c", "", "34"]
// "d|43|54" → ["d", "43", "54"]
std::vector<std::string> tokenize(std::string_view s, char delim = '|')
{
std::vector<std::string> tokens;
size_t start = 0;
while (true)
{
size_t pos = s.find(delim, start);
if (pos == std::string_view::npos)
{
tokens.emplace_back(s.substr(start));
break;
}
tokens.emplace_back(s.substr(start, pos - start));
start = pos + 1;
}
return tokens;
}
ChargeSocketsHelper::ChargeSocketsHelper(std::string const & socketsList) : m_dirty(false)
{
if (socketsList.empty())
return;
auto tokens = tokenize(socketsList, ';');
for (auto token : tokens)
{
if (token.empty())
continue;
auto fields = tokenize(token, '|');
if (fields.size() != 3)
continue; // invalid entry, skip
ChargeSocketDescriptor desc;
if (std::find(SUPPORTED_TYPES.begin(), SUPPORTED_TYPES.end(), fields[0]) != SUPPORTED_TYPES.end())
desc.type = fields[0];
else
desc.type = UNKNOWN;
try
{
desc.count = std::stoi(std::string(fields[1]));
}
catch (...)
{
desc.count = 0;
}
try
{
desc.power = std::stod(std::string(fields[2]));
}
catch (...)
{
desc.power = 0;
}
m_chargeSockets.push_back(desc);
}
m_dirty = true;
}
void ChargeSocketsHelper::AddSocket(std::string const & type, unsigned int count, double power)
{
if (power < 0)
{
LOG(LWARNING, ("Invalid socket power. Must be >= 0:", power));
return;
}
m_chargeSockets.push_back({type, count, power});
m_dirty = true;
}
void ChargeSocketsHelper::AggregateChargeSocketKey(std::string const & k, std::string const & v)
{
auto keys = strings::Tokenize(k, ":");
ASSERT(keys[0] == "socket", ()); // key must start with "socket:"
if (keys.size() < 2 || keys.size() > 3)
{
LOG(LWARNING, ("Invalid socket key:", k));
return;
}
std::string type(keys[1]);
bool isOutput = false;
if (keys.size() == 3)
{
if (keys[2] == "output")
isOutput = true;
else
return; // ignore other suffixes
}
// normalize type
static std::unordered_map<std::string_view, std::string_view> const kTypeMap = {
{"tesla_supercharger", "nacs"},
{"tesla_destination", "nacs"},
{"tesla_standard", "nacs"},
{"tesla", "nacs"},
{"tesla_supercharger_ccs", "type2_combo"},
{"ccs", "type2_combo"},
{"type1_cable", "type1"},
};
auto itMap = kTypeMap.find(type);
if (itMap != kTypeMap.end())
type = itMap->second;
if (std::find(SUPPORTED_TYPES.begin(), SUPPORTED_TYPES.end(), type) == SUPPORTED_TYPES.end())
type = UNKNOWN;
// Tokenize counts or powers
auto tokens = strings::Tokenize(v, ";");
if (tokens.empty())
return;
if (!isOutput)
{
// Parse counts
std::vector<unsigned int> counts;
for (auto & t : tokens)
{
strings::Trim(t);
if (t.empty())
continue;
if (t == "yes")
counts.push_back(0);
else
{
try
{
auto c = std::stoi(std::string(t));
if (c > 0)
counts.push_back(c);
else
{
LOG(LWARNING, ("Invalid count. Ignoring:", t));
continue;
}
}
catch (...)
{
LOG(LWARNING, ("Invalid count. Ignoring:", t));
continue;
}
}
}
// Update existing sockets or add new ones
for (size_t i = 0; i < counts.size(); ++i)
{
// Find the i-th socket of this type
size_t matched = 0;
auto it = std::find_if(m_chargeSockets.begin(), m_chargeSockets.end(), [&](ChargeSocketDescriptor const & d)
{
if (d.type != type)
return false;
if (matched == i)
return true;
++matched;
return false;
});
if (it != m_chargeSockets.end())
it->count = counts[i];
else
m_chargeSockets.push_back({type, counts[i], 0});
}
}
else
{
// Parse powers
std::vector<double> powers;
for (auto & t : tokens)
{
strings::Trim(t);
if (t.empty())
continue;
std::string s = strings::MakeLowerCase(std::string(t));
size_t pos = s.find("va");
while (pos != std::string::npos)
{
s.replace(pos, 2, "w");
pos = s.find("va", pos + 1);
}
double value = 0;
enum PowerUnit
{
WATT,
KILOWATT,
MEGAWATT
} unit = KILOWATT;
if (!s.empty() && s.back() == 'w')
{
unit = WATT;
s.pop_back();
if (!s.empty() && s.back() == 'k')
{
unit = KILOWATT;
s.pop_back();
}
else if (!s.empty() && s.back() == 'm')
{
unit = MEGAWATT;
s.pop_back();
}
}
try
{
value = std::stod(s);
if (value < 0)
{
LOG(LWARNING, ("Invalid power value. Ignoring:", t));
continue;
}
switch (unit)
{
case WATT: value /= 1000.; break;
case MEGAWATT: value *= 1000.; break;
case KILOWATT: break;
}
}
catch (...)
{
LOG(LWARNING, ("Invalid power value. Ignoring:", t));
continue;
}
powers.push_back(value);
}
// Update existing sockets or add new ones
for (size_t i = 0; i < powers.size(); ++i)
{
size_t matched = 0;
auto it = std::find_if(m_chargeSockets.begin(), m_chargeSockets.end(), [&](ChargeSocketDescriptor const & d)
{
if (d.type != type)
return false;
if (matched == i)
return true;
++matched;
return false;
});
if (it != m_chargeSockets.end())
it->power = powers[i];
else
m_chargeSockets.push_back({type, 0, powers[i]}); // default count=0
}
}
m_dirty = true;
}
ChargeSocketDescriptors ChargeSocketsHelper::GetSockets()
{
if (m_dirty)
Sort();
return m_chargeSockets;
}
std::string ChargeSocketsHelper::ToString()
{
if (m_dirty)
Sort();
std::ostringstream oss;
for (size_t i = 0; i < m_chargeSockets.size(); ++i)
{
auto const & desc = m_chargeSockets[i];
oss << desc.type << "|";
if (desc.count > 0)
oss << desc.count;
oss << "|";
if (desc.power > 0)
oss << desc.power;
if (i + 1 < m_chargeSockets.size())
oss << ";";
}
return oss.str();
}
OSMKeyValues ChargeSocketsHelper::GetOSMKeyValues()
{
if (m_dirty)
Sort();
std::vector<std::pair<std::string, std::string>> result;
std::string lastType;
std::ostringstream countStream;
std::ostringstream powerStream;
bool firstCount = true;
bool firstPower = true;
for (auto const & s : m_chargeSockets)
{
if (s.type.empty())
continue;
// If we switch type, flush previous streams
if (s.type != lastType && !lastType.empty())
{
result.emplace_back("socket:" + lastType, countStream.str());
if (powerStream.str().size() > 0)
result.emplace_back("socket:" + lastType + ":output", powerStream.str());
countStream.str("");
countStream.clear();
powerStream.str("");
powerStream.clear();
firstCount = true;
firstPower = true;
}
lastType = s.type;
// Append count
if (!firstCount)
countStream << ";";
countStream << ((s.count > 0) ? std::to_string(s.count) : "yes");
firstCount = false;
// Append power if > 0
if (s.power > 0)
{
if (!firstPower)
powerStream << ";";
powerStream << to_string_trimmed(s.power);
firstPower = false;
}
}
// Flush last type
if (!lastType.empty())
{
result.emplace_back("socket:" + lastType, countStream.str());
if (powerStream.str().size() > 0)
result.emplace_back("socket:" + lastType + ":output", powerStream.str());
}
return result;
}
KeyValueDiffSet ChargeSocketsHelper::Diff(ChargeSocketDescriptors const & oldSockets)
{
KeyValueDiffSet result;
auto oldKeys = ChargeSocketsHelper(oldSockets).GetOSMKeyValues();
auto newKeys = GetOSMKeyValues();
std::unordered_map<std::string, std::string> oldMap, newMap;
for (auto && [k, v] : oldKeys)
oldMap.emplace(k, v);
for (auto && [k, v] : newKeys)
newMap.emplace(k, v);
// Handle keys that exist in old
for (auto && [k, oldVal] : oldMap)
if (auto it = newMap.find(k); it == newMap.end())
result.insert({k, oldVal, ""}); // deleted
else if (it->second != oldVal)
result.insert({k, oldVal, it->second}); // updated
// Handle keys that are only new
for (auto && [k, newVal] : newMap)
if (!oldMap.contains(k))
result.insert({k, "", newVal}); // created
return result;
}
void ChargeSocketsHelper::Sort()
{
size_t const unknownTypeOrder = SUPPORTED_TYPES.size();
std::sort(m_chargeSockets.begin(), m_chargeSockets.end(),
[&](ChargeSocketDescriptor const & a, ChargeSocketDescriptor const & b)
{
auto const itA = std::find(SUPPORTED_TYPES.begin(), SUPPORTED_TYPES.end(), a.type);
auto const orderA = (itA == SUPPORTED_TYPES.end()) ? unknownTypeOrder : std::distance(SUPPORTED_TYPES.begin(), itA);
auto const itB = std::find(SUPPORTED_TYPES.begin(), SUPPORTED_TYPES.end(), b.type);
auto const orderB = (itB == SUPPORTED_TYPES.end()) ? unknownTypeOrder : std::distance(SUPPORTED_TYPES.begin(), itB);
if (orderA != orderB)
return orderA < orderB;
return a.power > b.power; // Sort by power in descending order for sockets of the same type
});
m_dirty = false;
}

View file

@ -0,0 +1,121 @@
#pragma once
#include <array>
#include <set>
#include <string>
#include <tuple>
#include <utility> // for std::pair
#include <vector>
// struct to store the representation of a charging station socket
struct ChargeSocketDescriptor
{
std::string type; // https://wiki.openstreetmap.org/wiki/Key:socket:*
// e.g. "type1"
unsigned int count; // number of sockets; 0 means socket present, but unknown count
// (eg, OSM tag for count set to 'yes')
double power; // power output, in kW. 0 means unknown.
};
typedef std::vector<ChargeSocketDescriptor> ChargeSocketDescriptors;
typedef std::vector<std::pair<std::string, std::string>> OSMKeyValues;
typedef std::set<std::tuple<std::string, std::string, std::string>> KeyValueDiffSet;
class ChargeSocketsHelper
{
public:
ChargeSocketsHelper() : m_dirty(false) {}
ChargeSocketsHelper(ChargeSocketDescriptors const & sockets) : m_chargeSockets(sockets), m_dirty(true) {}
/** Create a ChareSocketsHelper instance from an existing list of sockets
* stored as "<type>|<nb>|[<power>];..."
*
* For instance:
* "type2_combo|2|150;chademo|1|50;type2|4|"
*/
ChargeSocketsHelper(std::string const & socketsList);
void AddSocket(std::string const & type, unsigned int count, double power);
/** Parse OSM attributes for socket types and add them to m_chargeSockets.
*
* Examples of (k,v) pairs:
* ("socket:type2_combo", "2")
* ("socket:type2_combo:output", "150 kW")
* ("socket:chademo", "1")
* ("socket:chademo:output", "50") // assumes kW
*/
void AggregateChargeSocketKey(std::string const & key, std::string const & value);
/** Returns the current list of sockets, ordered from the most powerful to the least.
*
* The list is guaranteed to be sorted first by socket type (same ordered as
* SUPPORTED_TYPES), then by power (descending).
*
* Note that this method is not const as it may trigger a re-ordering of the
* internal list of sockets.
*/
ChargeSocketDescriptors GetSockets();
/** Serialize the list of sockets into a string with format "<type>|<nb>|[<power>];..."
*
* For instance:
* "type2_combo|2|150;chademo|1|50;type2|4|"
*
* The list is guaranteed to be sorted first by socket type (same ordered as
* SUPPORTED_TYPES), then by power (descending).
*
* The same string can be use to re-create a the list of socket by creating a
* new instance of ChargeSocketsHelper with the
* ChargeSocketsHelper::ChargeSocketsHelper(std::string) ctor.
*
* Note that this method is not const as it may trigger a re-ordering of the
* internal list of sockets.
*/
std::string ToString();
/** Returns a list of OpenStreetMap (key, value) corresponding to the list of sockets.
*
* The list is guaranteed to be sorted first by socket type (same ordered as
* SUPPORTED_TYPES), then by power (descending).
*
* Note that this method is not const as it may trigger a re-ordering of the
* internal list of sockets.
*/
OSMKeyValues GetOSMKeyValues();
static constexpr std::string_view UNKNOWN = "unknown";
/** List of supported sockets, ~ordered from high-power to low-power.
* This order can be used in the UIs.
*/
static constexpr std::array<std::string_view, 12> SUPPORTED_TYPES = {
"mcs", "type2_combo", "chademo", "nacs", "type1", "gb_dc",
"chaoji", "type3c", "type2_cable", "type2", "gb_ac", "type3a"};
/** Return a list of OSM attributes that have changed between the current
* list of sockets and the provided old list.
*
* Returns a set of (key, old_value, new_value) tuples.
*
* If old_value="", the attribute is new (ie has been created)
* If new_value="", the attribute has been deleted
*
* Note that this method is not const as it may trigger a re-ordering of the
* internal list of sockets.
*/
KeyValueDiffSet Diff(ChargeSocketDescriptors const & oldSockets);
protected:
ChargeSocketDescriptors m_chargeSockets;
private:
/** sort sockets: first by type, then by power (descending).
*/
void Sort();
bool m_dirty;
};

View file

@ -0,0 +1,167 @@
#include "indexer/feature_covering.hpp"
#include "indexer/feature.hpp"
#include "geometry/covering_utils.hpp"
namespace
{
// This class should only be used with covering::CoverObject()!
template <int DEPTH_LEVELS>
class FeatureIntersector
{
public:
struct Trg
{
m2::PointD m_a, m_b, m_c;
Trg(m2::PointD const & a, m2::PointD const & b, m2::PointD const & c) : m_a(a), m_b(b), m_c(c) {}
};
std::vector<m2::PointD> m_polyline;
std::vector<Trg> m_trg;
m2::RectD m_rect;
// Note:
// 1. Here we don't need to differentiate between CELL_OBJECT_INTERSECT and OBJECT_INSIDE_CELL.
// 2. We can return CELL_OBJECT_INTERSECT instead of CELL_INSIDE_OBJECT - it's just
// a performance penalty.
covering::CellObjectIntersection operator()(m2::CellId<DEPTH_LEVELS> const & cell) const
{
using namespace covering;
m2::RectD cellRect;
{
// Check for limit rect intersection.
std::pair<uint32_t, uint32_t> const xy = cell.XY();
uint32_t const r = cell.Radius();
ASSERT_GREATER_OR_EQUAL(xy.first, r, ());
ASSERT_GREATER_OR_EQUAL(xy.second, r, ());
cellRect = m2::RectD(xy.first - r, xy.second - r, xy.first + r, xy.second + r);
if (!cellRect.IsIntersect(m_rect))
return CELL_OBJECT_NO_INTERSECTION;
}
for (size_t i = 0; i < m_trg.size(); ++i)
{
m2::RectD r;
r.Add(m_trg[i].m_a);
r.Add(m_trg[i].m_b);
r.Add(m_trg[i].m_c);
if (!cellRect.IsIntersect(r))
continue;
CellObjectIntersection const res = IntersectCellWithTriangle(cell, m_trg[i].m_a, m_trg[i].m_b, m_trg[i].m_c);
switch (res)
{
case CELL_OBJECT_NO_INTERSECTION: break;
case CELL_INSIDE_OBJECT: return CELL_INSIDE_OBJECT;
case CELL_OBJECT_INTERSECT:
case OBJECT_INSIDE_CELL: return CELL_OBJECT_INTERSECT;
}
}
for (size_t i = 1; i < m_polyline.size(); ++i)
{
CellObjectIntersection const res = IntersectCellWithLine(cell, m_polyline[i], m_polyline[i - 1]);
switch (res)
{
case CELL_OBJECT_NO_INTERSECTION: break;
case CELL_INSIDE_OBJECT: ASSERT(false, (cell, i, m_polyline)); return CELL_OBJECT_INTERSECT;
case CELL_OBJECT_INTERSECT:
case OBJECT_INSIDE_CELL: return CELL_OBJECT_INTERSECT;
}
}
return CELL_OBJECT_NO_INTERSECTION;
}
using Converter = CellIdConverter<mercator::Bounds, m2::CellId<DEPTH_LEVELS>>;
m2::PointD ConvertPoint(m2::PointD const & p)
{
m2::PointD const pt(Converter::XToCellIdX(p.x), Converter::YToCellIdY(p.y));
m_rect.Add(pt);
return pt;
}
void operator()(m2::PointD const & pt) { m_polyline.push_back(ConvertPoint(pt)); }
void operator()(m2::PointD const & a, m2::PointD const & b, m2::PointD const & c)
{
m_trg.emplace_back(ConvertPoint(a), ConvertPoint(b), ConvertPoint(c));
}
};
template <int DEPTH_LEVELS>
void GetIntersection(FeatureType & f, FeatureIntersector<DEPTH_LEVELS> & fIsect)
{
// We need to cover feature for the best geometry, because it's indexed once for the
// first top level scale. Do reset current cached geometry first.
f.ResetGeometry();
int const scale = FeatureType::BEST_GEOMETRY;
f.ForEachPoint(fIsect, scale);
f.ForEachTriangle(fIsect, scale);
CHECK(!(fIsect.m_trg.empty() && fIsect.m_polyline.empty()) && f.GetLimitRect(scale).IsValid(), (f.DebugString()));
}
template <int DEPTH_LEVELS>
std::vector<int64_t> CoverIntersection(FeatureIntersector<DEPTH_LEVELS> const & fIsect, int cellDepth,
uint64_t cellPenaltyArea)
{
if (fIsect.m_trg.empty() && fIsect.m_polyline.size() == 1)
{
m2::PointD const pt = fIsect.m_polyline[0];
return std::vector<int64_t>(
1, m2::CellId<DEPTH_LEVELS>::FromXY(static_cast<uint32_t>(pt.x), static_cast<uint32_t>(pt.y), DEPTH_LEVELS - 1)
.ToInt64(cellDepth));
}
std::vector<m2::CellId<DEPTH_LEVELS>> cells;
covering::CoverObject(fIsect, cellPenaltyArea, cells, cellDepth, m2::CellId<DEPTH_LEVELS>::Root());
std::vector<int64_t> res(cells.size());
for (size_t i = 0; i < cells.size(); ++i)
res[i] = cells[i].ToInt64(cellDepth);
return res;
}
} // namespace
namespace covering
{
std::vector<int64_t> CoverFeature(FeatureType & f, int cellDepth, uint64_t cellPenaltyArea)
{
FeatureIntersector<RectId::DEPTH_LEVELS> fIsect;
GetIntersection(f, fIsect);
return CoverIntersection(fIsect, cellDepth, cellPenaltyArea);
}
void SortAndMergeIntervals(Intervals v, Intervals & res)
{
#ifdef DEBUG
ASSERT(res.empty(), ());
for (size_t i = 0; i < v.size(); ++i)
ASSERT_LESS(v[i].first, v[i].second, (i));
#endif
std::sort(v.begin(), v.end());
res.reserve(v.size());
for (size_t i = 0; i < v.size(); ++i)
if (i == 0 || res.back().second < v[i].first)
res.push_back(v[i]);
else
res.back().second = std::max(res.back().second, v[i].second);
}
Intervals SortAndMergeIntervals(Intervals const & v)
{
Intervals res;
SortAndMergeIntervals(v, res);
return res;
}
} // namespace covering

View file

@ -0,0 +1,166 @@
#pragma once
#include "indexer/cell_coverer.hpp"
#include "indexer/cell_id.hpp"
#include "indexer/scales.hpp"
#include "coding/point_coding.hpp"
#include "geometry/cellid.hpp"
#include "geometry/mercator.hpp"
#include "geometry/rect2d.hpp"
#include "base/logging.hpp"
#include <cstdint>
#include <set>
#include <utility>
#include <vector>
class FeatureType;
namespace indexer
{
class LocalityObject;
} // namespace indexer
namespace covering
{
typedef std::pair<int64_t, int64_t> Interval;
typedef std::vector<Interval> Intervals;
// Cover feature with RectIds and return their integer representations.
std::vector<int64_t> CoverFeature(FeatureType & feature, int cellDepth, uint64_t cellPenaltyArea);
// Given a vector of intervals [a, b), sort them and merge overlapping intervals.
Intervals SortAndMergeIntervals(Intervals const & intervals);
void SortAndMergeIntervals(Intervals v, Intervals & res);
template <int DEPTH_LEVELS>
m2::CellId<DEPTH_LEVELS> GetRectIdAsIs(m2::RectD const & r)
{
double const eps = kMwmPointAccuracy;
using Converter = CellIdConverter<mercator::Bounds, m2::CellId<DEPTH_LEVELS>>;
return Converter::Cover2PointsWithCell(mercator::ClampX(r.minX() + eps), mercator::ClampY(r.minY() + eps),
mercator::ClampX(r.maxX() - eps), mercator::ClampY(r.maxY() - eps));
}
// Calculate cell coding depth according to max visual scale for mwm.
template <int DEPTH_LEVELS>
int GetCodingDepth(int scale)
{
int const delta = scales::GetUpperScale() - scale;
ASSERT_GREATER_OR_EQUAL(delta, 0, ());
return DEPTH_LEVELS - delta;
}
template <int DEPTH_LEVELS, typename ToDo>
void AppendLowerLevels(m2::CellId<DEPTH_LEVELS> id, int cellDepth, ToDo const & toDo)
{
int64_t idInt64 = id.ToInt64(cellDepth);
toDo(std::make_pair(idInt64, idInt64 + id.SubTreeSize(cellDepth)));
while (id.Level() > 0)
{
id = id.Parent();
idInt64 = id.ToInt64(cellDepth);
toDo(std::make_pair(idInt64, idInt64 + 1));
}
}
template <int DEPTH_LEVELS>
void CoverViewportAndAppendLowerLevels(m2::RectD const & r, int cellDepth, Intervals & res)
{
std::vector<m2::CellId<DEPTH_LEVELS>> ids;
ids.reserve(SPLIT_RECT_CELLS_COUNT);
CoverRect<mercator::Bounds, m2::CellId<DEPTH_LEVELS>>(r, SPLIT_RECT_CELLS_COUNT, cellDepth - 1, ids);
Intervals intervals;
for (auto const & id : ids)
{
AppendLowerLevels<DEPTH_LEVELS>(id, cellDepth,
[&intervals](Interval const & interval) { intervals.push_back(interval); });
}
SortAndMergeIntervals(intervals, res);
}
enum CoveringMode
{
ViewportWithLowLevels = 0,
LowLevelsOnly,
FullCover,
Spiral
};
class CoveringGetter
{
Intervals m_res[2];
m2::RectD const & m_rect;
CoveringMode m_mode;
public:
CoveringGetter(m2::RectD const & r, CoveringMode mode) : m_rect(r), m_mode(mode) {}
m2::RectD const & GetRect() const { return m_rect; }
template <int DEPTH_LEVELS>
Intervals const & Get(int scale)
{
int const cellDepth = GetCodingDepth<DEPTH_LEVELS>(scale);
int const ind = (cellDepth == DEPTH_LEVELS ? 0 : 1);
if (m_res[ind].empty())
{
switch (m_mode)
{
case ViewportWithLowLevels: CoverViewportAndAppendLowerLevels<DEPTH_LEVELS>(m_rect, cellDepth, m_res[ind]); break;
case LowLevelsOnly:
{
m2::CellId<DEPTH_LEVELS> id = GetRectIdAsIs<DEPTH_LEVELS>(m_rect);
while (id.Level() >= cellDepth)
id = id.Parent();
AppendLowerLevels<DEPTH_LEVELS>(id, cellDepth,
[this, ind](Interval const & interval) { m_res[ind].push_back(interval); });
// Check for optimal result intervals.
#if 0
size_t oldSize = m_res[ind].size();
Intervals res;
SortAndMergeIntervals(m_res[ind], res);
if (res.size() != oldSize)
LOG(LINFO, ("Old =", oldSize, "; New =", res.size()));
res.swap(m_res[ind]);
#endif
break;
}
case FullCover:
m_res[ind].push_back(Intervals::value_type(0, static_cast<int64_t>((uint64_t{1} << 63) - 1)));
break;
case Spiral:
{
std::vector<m2::CellId<DEPTH_LEVELS>> ids;
CoverSpiral<mercator::Bounds, m2::CellId<DEPTH_LEVELS>>(m_rect, cellDepth - 1, ids);
std::set<Interval> uniqueIds;
auto insertInterval = [this, ind, &uniqueIds](Interval const & interval)
{
if (uniqueIds.insert(interval).second)
m_res[ind].push_back(interval);
};
for (auto const & id : ids)
if (cellDepth > id.Level())
AppendLowerLevels<DEPTH_LEVELS>(id, cellDepth, insertInterval);
}
}
}
return m_res[ind];
}
};
} // namespace covering

View file

@ -0,0 +1,598 @@
#include "indexer/feature_data.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "base/assert.hpp"
#include "base/macros.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <sstream>
#include <vector>
using namespace feature;
////////////////////////////////////////////////////////////////////////////////////
// TypesHolder implementation
////////////////////////////////////////////////////////////////////////////////////
namespace feature
{
using namespace std;
template <class ContT>
string TypesToString(ContT const & holder)
{
Classificator const & c = classif();
string s;
for (uint32_t const type : holder)
s += c.GetReadableObjectName(type) + " ";
if (!s.empty())
s.pop_back();
return s;
}
std::string DebugPrint(TypesHolder const & holder)
{
return TypesToString(holder);
}
TypesHolder::TypesHolder(FeatureType & f) : m_size(0), m_geomType(f.GetGeomType())
{
f.ForEachType([this](uint32_t type) { Add(type); });
}
bool TypesHolder::HasWithSubclass(uint32_t type) const
{
uint8_t const level = ftype::GetLevel(type);
for (uint32_t t : *this)
{
ftype::TruncValue(t, level);
if (t == type)
return true;
}
return false;
}
void TypesHolder::Remove(uint32_t type)
{
UNUSED_VALUE(RemoveIf(base::EqualFunctor<uint32_t>(type)));
}
bool TypesHolder::Equals(TypesHolder const & other) const
{
if (m_size != other.m_size)
return false;
// Dynamic vector + sort for kMaxTypesCount array is a huge overhead.
auto const b = begin();
auto const e = end();
for (auto t : other)
if (std::find(b, e, t) == e)
return false;
return true;
}
namespace
{
class UselessTypesChecker
{
public:
static UselessTypesChecker const & Instance()
{
static UselessTypesChecker const inst;
return inst;
}
/// @return Type score, less is better.
uint8_t Score(uint32_t t) const
{
ftype::TruncValue(t, 2);
if (IsIn(2, t))
return 3;
ftype::TruncValue(t, 1);
if (IsIn(1, t))
return 2;
if (IsIn(0, t))
return 1;
return 0;
}
template <class ContT>
void SortUselessToEnd(ContT & cont) const
{
// Put "very common" types to the end of possible PP-description types.
std::stable_sort(cont.begin(), cont.end(), [this](uint32_t t1, uint32_t t2) { return Score(t1) < Score(t2); });
}
private:
UselessTypesChecker()
{
// Fill types that will be taken into account last,
// when we have many types for POI.
base::StringIL const types1[] = {
// 1-arity
{"building:part"}, {"hwtag"}, {"psurface"}, {"internet_access"}, {"organic"},
{"wheelchair"}, {"cuisine"}, {"recycling"}, {"area:highway"}, {"fee"},
};
Classificator const & c = classif();
m_types[0].push_back(c.GetTypeByPath({"building"}));
m_types[1].reserve(std::size(types1));
for (auto const & type : types1)
m_types[1].push_back(c.GetTypeByPath(type));
// Put _most_ useless types here, that are not fit in the arity logic above.
// This change is for generator, to eliminate "lit" type first when max types count exceeded.
m_types[2].push_back(c.GetTypeByPath({"hwtag", "lit"}));
for (auto & v : m_types)
std::sort(v.begin(), v.end());
}
bool IsIn(uint8_t idx, uint32_t t) const { return std::binary_search(m_types[idx].begin(), m_types[idx].end(), t); }
vector<uint32_t> m_types[3];
};
} // namespace
uint8_t CalculateHeader(size_t const typesCount, HeaderGeomType const headerGeomType, FeatureParamsBase const & params)
{
ASSERT(typesCount != 0, ("Feature should have at least one type."));
ASSERT_LESS_OR_EQUAL(typesCount, kMaxTypesCount, ());
uint8_t header = static_cast<uint8_t>(typesCount - 1);
if (!params.name.IsEmpty())
header |= HEADER_MASK_HAS_NAME;
if (params.layer != LAYER_EMPTY)
header |= HEADER_MASK_HAS_LAYER;
header |= static_cast<uint8_t>(headerGeomType);
// Geometry type for additional info is only one.
switch (headerGeomType)
{
case HeaderGeomType::Point:
if (params.rank != 0)
header |= HEADER_MASK_HAS_ADDINFO;
break;
case HeaderGeomType::Line:
if (!params.ref.empty())
header |= HEADER_MASK_HAS_ADDINFO;
break;
case HeaderGeomType::Area:
case HeaderGeomType::PointEx:
if (!params.house.IsEmpty())
header |= HEADER_MASK_HAS_ADDINFO;
break;
}
return header;
}
void TypesHolder::SortByUseless()
{
UselessTypesChecker::Instance().SortUselessToEnd(*this);
}
void TypesHolder::SortBySpec()
{
auto const & cl = classif();
auto const getPriority = [&cl](uint32_t type) { return cl.GetObject(type)->GetMaxOverlaysPriority(); };
auto const & checker = UselessTypesChecker::Instance();
std::stable_sort(begin(), end(), [&checker, &getPriority](uint32_t t1, uint32_t t2)
{
int const p1 = getPriority(t1);
int const p2 = getPriority(t2);
if (p1 != p2)
return p1 > p2;
// Score - less is better.
return checker.Score(t1) < checker.Score(t2);
});
}
vector<string> TypesHolder::ToObjectNames() const
{
Classificator const & c = classif();
vector<string> result;
for (uint32_t const type : *this)
result.push_back(c.GetReadableObjectName(type));
return result;
}
} // namespace feature
////////////////////////////////////////////////////////////////////////////////////
// FeatureParamsBase implementation
////////////////////////////////////////////////////////////////////////////////////
void FeatureParamsBase::MakeZero()
{
layer = 0;
rank = 0;
ref.clear();
house.Clear();
name.Clear();
}
bool FeatureParamsBase::SetDefaultNameIfEmpty(std::string const & s)
{
std::string_view existing;
if (name.GetString(StringUtf8Multilang::kDefaultCode, existing))
return existing == s;
name.AddString(StringUtf8Multilang::kDefaultCode, s);
return true;
}
bool FeatureParamsBase::operator==(FeatureParamsBase const & rhs) const
{
return (name == rhs.name && house == rhs.house && ref == rhs.ref && layer == rhs.layer && rank == rhs.rank);
}
bool FeatureParamsBase::IsValid() const
{
return layer >= LAYER_LOW && layer <= LAYER_HIGH;
}
string FeatureParamsBase::DebugString() const
{
string const utf8name = DebugPrint(name);
return ((!utf8name.empty() ? "Name:" + utf8name : "") +
(layer != LAYER_EMPTY ? " Layer:" + DebugPrint((int)layer) : "") +
(rank != 0 ? " Rank:" + DebugPrint((int)rank) : "") + (!house.IsEmpty() ? " House:" + house.Get() : "") +
(!ref.empty() ? " Ref:" + ref : ""));
}
bool FeatureParamsBase::IsEmptyNames() const
{
return name.IsEmpty() && house.IsEmpty() && ref.empty();
}
namespace
{
bool IsDummyName(string_view s)
{
return s.empty();
}
} // namespace
/////////////////////////////////////////////////////////////////////////////////////////
// FeatureParams implementation
/////////////////////////////////////////////////////////////////////////////////////////
void FeatureParams::ClearName()
{
name.Clear();
}
bool FeatureParams::AddName(string_view lang, string_view s)
{
if (IsDummyName(s))
return false;
// The "default" new name will replace the old one if any (e.g. from SetHouseNumberAndHouseName call).
name.AddString(lang, s);
return true;
}
bool FeatureParams::LooksLikeHouseNumber(std::string const & hn)
{
// Very naive implementation to _lightly_ promote hn -> name (for search index) if suitable.
/// @todo Conform with search::LooksLikeHouseNumber.
ASSERT(!hn.empty(), ());
size_t const sz = hn.size();
return strings::IsASCIIDigit(hn[0]) || (sz == 1 && strings::IsASCIILatin(hn[0])) ||
std::count_if(hn.begin(), hn.end(), &strings::IsASCIIDigit) > 0.2 * sz;
}
char const * FeatureParams::kHNLogTag = "HNLog";
void FeatureParams::SetHouseNumberAndHouseName(std::string houseNumber, std::string houseName)
{
if (IsDummyName(houseName) || name.FindString(houseName) != StringUtf8Multilang::kUnsupportedLanguageCode)
houseName.clear();
if (houseName.empty() && houseNumber.empty())
return;
if (houseName.empty())
AddHouseNumber(houseNumber);
else if (houseNumber.empty())
AddHouseNumber(houseName);
else
{
if (!LooksLikeHouseNumber(houseNumber) && LooksLikeHouseNumber(houseName))
{
LOG(LWARNING, (kHNLogTag, "Fancy house name:", houseName, "and number:", houseNumber));
houseNumber.swap(houseName);
}
AddHouseNumber(std::move(houseNumber));
SetDefaultNameIfEmpty(houseName);
}
}
bool FeatureParams::AddHouseNumber(string houseNumber)
{
ASSERT(!houseNumber.empty(), ());
// Negative house numbers are not supported.
if (houseNumber.front() == '-' || houseNumber.find("") == 0)
{
LOG(LWARNING, (kHNLogTag, "Negative house number:", houseNumber));
return false;
}
// Replace full-width digits, mostly in Japan, by ascii-ones.
strings::NormalizeDigits(houseNumber);
// Assign house number as-is to create building-address if needed.
// Final checks are made in FeatureBuilder::PreSerialize().
house.Set(houseNumber);
return true;
}
void FeatureParams::SetGeomType(feature::GeomType t)
{
switch (t)
{
case GeomType::Point: m_geomType = HeaderGeomType::Point; break;
case GeomType::Line: m_geomType = HeaderGeomType::Line; break;
case GeomType::Area: m_geomType = HeaderGeomType::Area; break;
default: ASSERT(false, ());
}
}
void FeatureParams::SetGeomTypePointEx()
{
ASSERT(m_geomType == HeaderGeomType::Point || m_geomType == HeaderGeomType::PointEx, ());
ASSERT(!house.IsEmpty(), ());
m_geomType = HeaderGeomType::PointEx;
}
feature::GeomType FeatureParams::GetGeomType() const
{
CHECK(IsValid(), ());
switch (*m_geomType)
{
case HeaderGeomType::Line: return GeomType::Line;
case HeaderGeomType::Area: return GeomType::Area;
default: return GeomType::Point;
}
}
HeaderGeomType FeatureParams::GetHeaderGeomType() const
{
CHECK(IsValid(), ());
return *m_geomType;
}
void FeatureParams::SetRwSubwayType(char const * cityName)
{
Classificator const & c = classif();
static uint32_t const src = c.GetTypeByPath({"railway", "station"});
uint32_t const dest = c.GetTypeByPath({"railway", "station", "subway", cityName});
for (size_t i = 0; i < m_types.size(); ++i)
{
if (ftype::Trunc(m_types[i], 2) == src)
{
m_types[i] = dest;
break;
}
}
}
FeatureParams::TypesResult FeatureParams::FinishAddingTypesEx()
{
base::SortUnique(m_types);
TypesResult res = TYPES_GOOD;
if (m_types.size() > kMaxTypesCount)
{
UselessTypesChecker::Instance().SortUselessToEnd(m_types);
m_types.resize(kMaxTypesCount);
sort(m_types.begin(), m_types.end());
res = TYPES_EXCEED_MAX;
}
// Patch fix that removes house number from localities.
/// @todo move this fix elsewhere (osm2type.cpp?)
if (!house.IsEmpty() && ftypes::IsLocalityChecker::Instance()(m_types))
house.Clear();
return (m_types.empty() ? TYPES_EMPTY : res);
}
std::string FeatureParams::PrintTypes()
{
base::SortUnique(m_types);
UselessTypesChecker::Instance().SortUselessToEnd(m_types);
return TypesToString(m_types);
}
void FeatureParams::SetType(uint32_t t)
{
m_types.clear();
m_types.push_back(t);
}
bool FeatureParams::PopAnyType(uint32_t & t)
{
CHECK(!m_types.empty(), ());
t = m_types.back();
m_types.pop_back();
return m_types.empty();
}
bool FeatureParams::PopExactType(uint32_t t)
{
m_types.erase(remove(m_types.begin(), m_types.end(), t), m_types.end());
return m_types.empty();
}
bool FeatureParams::IsTypeExist(uint32_t t) const
{
return base::IsExist(m_types, t);
}
bool FeatureParams::IsTypeExist(uint32_t comp, uint8_t level) const
{
return FindType(comp, level) != ftype::GetEmptyValue();
}
uint32_t FeatureParams::FindType(uint32_t comp, uint8_t level) const
{
for (uint32_t const type : m_types)
if (ftype::Trunc(type, level) == comp)
return type;
return ftype::GetEmptyValue();
}
bool FeatureParams::IsValid() const
{
if (m_types.empty() || m_types.size() > kMaxTypesCount || !m_geomType)
return false;
return FeatureParamsBase::IsValid();
}
uint8_t FeatureParams::GetHeader() const
{
return CalculateHeader(m_types.size(), GetHeaderGeomType(), *this);
}
uint32_t FeatureParams::GetIndexForType(uint32_t t)
{
return classif().GetIndexForType(t);
}
uint32_t FeatureParams::GetTypeForIndex(uint32_t i)
{
return classif().GetTypeForIndex(i);
}
void FeatureBuilderParams::SetStreet(string s)
{
m_addrTags.Set(AddressData::Type::Street, std::move(s));
}
std::string_view FeatureBuilderParams::GetStreet() const
{
return m_addrTags.Get(AddressData::Type::Street);
}
void FeatureBuilderParams::SetPostcode(string s)
{
if (!s.empty())
m_metadata.Set(Metadata::FMD_POSTCODE, std::move(s));
}
std::string_view FeatureBuilderParams::GetPostcode() const
{
return m_metadata.Get(Metadata::FMD_POSTCODE);
}
namespace
{
// Define types that can't live together in a feature.
class YesNoTypes
{
std::vector<std::pair<uint32_t, uint32_t>> m_types;
public:
YesNoTypes()
{
// Remain first type and erase second in case of conflict.
base::StringIL arr[][2] = {
{{"hwtag", "yescar"}, {"hwtag", "nocar"}},
{{"hwtag", "yesfoot"}, {"hwtag", "nofoot"}},
{{"hwtag", "yesbicycle"}, {"hwtag", "nobicycle"}},
{{"hwtag", "nobicycle"}, {"hwtag", "bidir_bicycle"}},
{{"hwtag", "nobicycle"}, {"hwtag", "onedir_bicycle"}},
{{"hwtag", "bidir_bicycle"}, {"hwtag", "onedir_bicycle"}},
{{"wheelchair", "yes"}, {"wheelchair", "no"}},
};
auto const & cl = classif();
for (auto const & p : arr)
m_types.emplace_back(cl.GetTypeByPath(p[0]), cl.GetTypeByPath(p[1]));
}
bool RemoveInconsistent(std::vector<uint32_t> & types) const
{
size_t const szBefore = types.size();
for (auto const & p : m_types)
{
uint32_t skip;
bool found1 = false, found2 = false;
for (uint32_t t : types)
{
if (t == p.first)
found1 = true;
if (t == p.second)
{
found2 = true;
skip = t;
}
}
if (found1 && found2)
base::EraseIf(types, [skip](uint32_t t) { return skip == t; });
}
return szBefore != types.size();
}
};
} // namespace
bool FeatureBuilderParams::RemoveInconsistentTypes()
{
static YesNoTypes ynTypes;
return ynTypes.RemoveInconsistent(m_types);
}
void FeatureBuilderParams::ClearPOIAttribs()
{
ClearName();
m_metadata.ClearPOIAttribs();
}
string DebugPrint(FeatureParams const & p)
{
string res = "Types: " + TypesToString(p.m_types) + "; ";
return (res + p.DebugString());
}
string DebugPrint(FeatureBuilderParams const & p)
{
ostringstream oss;
oss << "ReversedGeometry: " << (p.GetReversedGeometry() ? "true" : "false") << "; ";
oss << DebugPrint(p.m_metadata) << "; ";
oss << DebugPrint(p.m_addrTags) << "; ";
oss << DebugPrint(static_cast<FeatureParams const &>(p));
return oss.str();
}

View file

@ -0,0 +1,387 @@
#pragma once
#include "indexer/feature_decl.hpp"
#include "indexer/feature_meta.hpp"
#include "coding/reader.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "coding/value_opt_string.hpp"
#include <algorithm>
#include <array>
#include <optional>
#include <string>
#include <vector>
struct FeatureParamsBase;
class FeatureType;
namespace feature
{
enum HeaderMask
{
HEADER_MASK_TYPE = 7U,
HEADER_MASK_HAS_NAME = 1U << 3,
HEADER_MASK_HAS_LAYER = 1U << 4,
HEADER_MASK_GEOMTYPE = 3U << 5,
HEADER_MASK_HAS_ADDINFO = 1U << 7
};
enum class HeaderGeomType : uint8_t
{
/// Coding geometry feature type in 2 bits.
Point = 0, /// point feature (addinfo = rank)
Line = 1U << 5, /// linear feature (addinfo = ref)
Area = 1U << 6, /// area feature (addinfo = house)
PointEx = 3U << 5 /// point feature (addinfo = house)
};
static constexpr int kMaxTypesCount = HEADER_MASK_TYPE + 1; // 8, because there should be no features with 0 types
enum Layer : int8_t
{
LAYER_LOW = -10,
LAYER_EMPTY = 0,
LAYER_HIGH = 10
};
class TypesHolder
{
public:
using Types = std::array<uint32_t, kMaxTypesCount>;
TypesHolder() = default;
explicit TypesHolder(GeomType geomType) : m_geomType(geomType) {}
explicit TypesHolder(FeatureType & f);
void Assign(uint32_t type)
{
m_types[0] = type;
m_size = 1;
}
template <class IterT>
void Assign(IterT beg, IterT end)
{
m_size = std::distance(beg, end);
CHECK_LESS_OR_EQUAL(m_size, kMaxTypesCount, ());
std::copy(beg, end, m_types.begin());
}
void Add(uint32_t type)
{
CHECK_LESS(m_size, kMaxTypesCount, ());
m_types[m_size++] = type;
}
void SafeAdd(uint32_t type)
{
if (!Has(type))
{
if (m_size < kMaxTypesCount)
Add(type);
else
LOG(LWARNING, ("Type could not be added, MaxTypesCount exceeded"));
}
}
GeomType GetGeomType() const { return m_geomType; }
size_t Size() const { return m_size; }
bool Empty() const { return (m_size == 0); }
uint32_t front() const
{
ASSERT(m_size > 0, ());
return m_types[0];
}
auto begin() const { return m_types.cbegin(); }
auto end() const { return m_types.cbegin() + m_size; }
auto begin() { return m_types.begin(); }
auto end() { return m_types.begin() + m_size; }
/// Assume that m_types is already sorted by SortBySpec function.
uint32_t GetBestType() const
{
// 0 - is an empty type.
return (m_size > 0 ? m_types[0] : 0);
}
bool Has(uint32_t type) const { return base::IsExist(*this, type); }
// More _natural_ way of checking than Has, including subclass types hierarchy.
// "railway-station-subway" holder returns true for "railway-station" input.
bool HasWithSubclass(uint32_t type) const;
template <typename Fn>
bool RemoveIf(Fn && fn)
{
size_t const oldSize = m_size;
auto const e = std::remove_if(begin(), end(), std::forward<Fn>(fn));
m_size = std::distance(begin(), e);
return (m_size != oldSize);
}
void Remove(uint32_t type);
/// Used in tests only to check UselessTypesChecker
/// (which is used in the generator to discard least important types if max types count is exceeded).
void SortByUseless();
/// Sort types by it's specification (more detailed type goes first). Should be used in client app.
void SortBySpec();
bool Equals(TypesHolder const & other) const;
std::vector<std::string> ToObjectNames() const;
private:
Types m_types = {};
size_t m_size = 0;
GeomType m_geomType = GeomType::Undefined;
};
std::string DebugPrint(TypesHolder const & holder);
uint8_t CalculateHeader(size_t const typesCount, HeaderGeomType const headerGeomType, FeatureParamsBase const & params);
} // namespace feature
struct FeatureParamsBase
{
StringUtf8Multilang name;
StringNumericOptimal house;
std::string ref;
int8_t layer;
uint8_t rank;
FeatureParamsBase() : layer(feature::LAYER_EMPTY), rank(0) {}
void MakeZero();
bool SetDefaultNameIfEmpty(std::string const & s);
bool operator==(FeatureParamsBase const & rhs) const;
bool IsValid() const;
std::string DebugString() const;
/// @return true if feature doesn't have any drawable strings (names, houses, etc).
bool IsEmptyNames() const;
template <class Sink>
void Write(Sink & sink, uint8_t header) const
{
using namespace feature;
if (header & HEADER_MASK_HAS_NAME)
name.WriteNonEmpty(sink);
if (header & HEADER_MASK_HAS_LAYER)
WriteToSink(sink, layer);
if (header & HEADER_MASK_HAS_ADDINFO)
{
auto const headerGeomType = static_cast<HeaderGeomType>(header & HEADER_MASK_GEOMTYPE);
switch (headerGeomType)
{
case HeaderGeomType::Point: WriteToSink(sink, rank); break;
case HeaderGeomType::Line: rw::WriteNonEmpty(sink, ref); break;
case HeaderGeomType::Area:
case HeaderGeomType::PointEx: house.Write(sink); break;
}
}
}
template <class TSrc>
void Read(TSrc & src, uint8_t header)
{
using namespace feature;
if (header & HEADER_MASK_HAS_NAME)
name.ReadNonEmpty(src);
if (header & HEADER_MASK_HAS_LAYER)
layer = ReadPrimitiveFromSource<int8_t>(src);
if (header & HEADER_MASK_HAS_ADDINFO)
{
auto const headerGeomType = static_cast<HeaderGeomType>(header & HEADER_MASK_GEOMTYPE);
switch (headerGeomType)
{
case HeaderGeomType::Point: rank = ReadPrimitiveFromSource<uint8_t>(src); break;
case HeaderGeomType::Line: rw::ReadNonEmpty(src, ref); break;
case HeaderGeomType::Area:
case HeaderGeomType::PointEx: house.Read(src); break;
}
}
}
};
class FeatureParams : public FeatureParamsBase
{
static char const * kHNLogTag;
public:
using Types = std::vector<uint32_t>;
void ClearName();
bool AddName(std::string_view lang, std::string_view s);
static bool LooksLikeHouseNumber(std::string const & hn);
void SetHouseNumberAndHouseName(std::string houseNumber, std::string houseName);
bool AddHouseNumber(std::string houseNumber);
void SetGeomType(feature::GeomType t);
void SetGeomTypePointEx();
feature::GeomType GetGeomType() const;
void AddType(uint32_t t) { m_types.push_back(t); }
/// Special function to replace a regular railway station type with
/// the special subway type for the correspondent city.
void SetRwSubwayType(char const * cityName);
enum TypesResult
{
TYPES_GOOD,
TYPES_EMPTY,
TYPES_EXCEED_MAX
};
TypesResult FinishAddingTypesEx();
bool FinishAddingTypes() { return FinishAddingTypesEx() != TYPES_EMPTY; }
// For logging purpose.
std::string PrintTypes();
void SetType(uint32_t t);
bool PopAnyType(uint32_t & t);
bool PopExactType(uint32_t t);
bool IsTypeExist(uint32_t t) const;
bool IsTypeExist(uint32_t comp, uint8_t level) const;
/// @return Type that matches |comp| with |level| in classificator hierarchy.
/// ftype::GetEmptyValue() if type not found.
uint32_t FindType(uint32_t comp, uint8_t level) const;
bool IsValid() const;
uint8_t GetHeader() const;
template <class Sink>
void Write(Sink & sink) const
{
uint8_t const header = GetHeader();
WriteToSink(sink, header);
for (size_t i = 0; i < m_types.size(); ++i)
WriteVarUint(sink, GetIndexForType(m_types[i]));
Base::Write(sink, header);
}
template <class Source>
void Read(Source & src)
{
using namespace feature;
uint8_t const header = ReadPrimitiveFromSource<uint8_t>(src);
m_geomType = static_cast<HeaderGeomType>(header & HEADER_MASK_GEOMTYPE);
size_t const count = (header & HEADER_MASK_TYPE) + 1;
for (size_t i = 0; i < count; ++i)
m_types.push_back(GetTypeForIndex(ReadVarUint<uint32_t>(src)));
Base::Read(src, header);
}
/// @todo Make protected and update EditableMapObject code.
Types m_types;
friend std::string DebugPrint(FeatureParams const & p);
private:
using Base = FeatureParamsBase;
feature::HeaderGeomType GetHeaderGeomType() const;
static uint32_t GetIndexForType(uint32_t t);
static uint32_t GetTypeForIndex(uint32_t i);
std::optional<feature::HeaderGeomType> m_geomType;
};
/// @todo Take out into generator library.
class FeatureBuilderParams : public FeatureParams
{
public:
/// Assign parameters except geometry type.
void SetParams(FeatureBuilderParams const & rhs)
{
FeatureParamsBase::operator=(rhs);
m_types = rhs.m_types;
m_addrTags = rhs.m_addrTags;
m_metadata = rhs.m_metadata;
m_reversedGeometry = rhs.m_reversedGeometry;
}
void MakeZero()
{
m_metadata.Clear();
m_addrTags.Clear();
FeatureParams::MakeZero();
}
/// @name Used to store address to temporary TEMP_ADDR_EXTENSION file.
/// @{
void SetAddress(feature::AddressData && addr) { m_addrTags = std::move(addr); }
void SetStreet(std::string s);
std::string_view GetStreet() const;
template <class TSink>
void SerializeAddress(TSink & sink) const
{
m_addrTags.SerializeForMwmTmp(sink);
}
/// @}
void SetPostcode(std::string s);
std::string_view GetPostcode() const;
feature::Metadata const & GetMetadata() const { return m_metadata; }
feature::Metadata & GetMetadata() { return m_metadata; }
template <class Sink>
void Write(Sink & sink) const
{
FeatureParams::Write(sink);
m_metadata.SerializeForMwmTmp(sink);
m_addrTags.SerializeForMwmTmp(sink);
}
template <class Source>
void Read(Source & src)
{
FeatureParams::Read(src);
m_metadata.DeserializeFromMwmTmp(src);
m_addrTags.DeserializeFromMwmTmp(src);
}
bool GetReversedGeometry() const { return m_reversedGeometry; }
void SetReversedGeometry(bool reversedGeometry) { m_reversedGeometry = reversedGeometry; }
/// @return true If any inconsistency was found here.
bool RemoveInconsistentTypes();
void ClearPOIAttribs();
friend std::string DebugPrint(FeatureBuilderParams const & p);
private:
bool m_reversedGeometry = false;
feature::Metadata m_metadata;
feature::AddressData m_addrTags;
};

View file

@ -0,0 +1,77 @@
#include "indexer/feature_decl.hpp"
#include "std/boost_container_hash.hpp"
#include <sstream>
namespace feature
{
std::string DebugPrint(GeomType type)
{
return ToString(type);
}
std::string ToString(GeomType type)
{
switch (type)
{
case GeomType::Undefined: return "Undefined";
case GeomType::Point: return "Point";
case GeomType::Line: return "Line";
case GeomType::Area: return "Area";
}
UNREACHABLE();
}
GeomType TypeFromString(std::string type)
{
if (type == "Point")
return GeomType::Point;
else if (type == "Line")
return GeomType::Line;
else if (type == "Area")
return GeomType::Area;
else
return GeomType::Undefined;
}
} // namespace feature
std::string DebugPrint(FeatureID const & id)
{
return "{ " + DebugPrint(id.m_mwmId) + ", " + std::to_string(id.m_index) + " }";
}
std::string FeatureID::GetMwmName() const
{
return IsValid() ? m_mwmId.GetInfo()->GetCountryName() : std::string();
}
int64_t FeatureID::GetMwmVersion() const
{
return IsValid() ? m_mwmId.GetInfo()->GetVersion() : -1;
}
bool FeatureID::IsEqualCountry(base::StringIL const & lst) const
{
if (!IsValid())
return false;
auto const & name = m_mwmId.GetInfo()->GetCountryName();
for (char const * e : lst)
if (name.starts_with(e))
return true;
return false;
}
bool FeatureID::IsWorld() const
{
return m_mwmId.GetInfo()->GetType() == MwmInfo::MwmTypeT::WORLD;
}
size_t std::hash<FeatureID>::operator()(FeatureID const & fID) const
{
size_t seed = 0;
boost::hash_combine(seed, fID.m_mwmId.GetInfo());
boost::hash_combine(seed, fID.m_index);
return seed;
}

View file

@ -0,0 +1,65 @@
#pragma once
#include "indexer/mwm_set.hpp"
#include "base/stl_helpers.hpp"
#include <string>
namespace feature
{
enum class GeomType : int8_t
{
Undefined = -1,
Point = 0,
Line = 1,
Area = 2
};
std::string DebugPrint(GeomType type);
std::string ToString(GeomType type);
GeomType TypeFromString(std::string type);
} // namespace feature
uint32_t constexpr kInvalidFeatureId = std::numeric_limits<uint32_t>::max();
struct FeatureID
{
FeatureID() = default;
FeatureID(MwmSet::MwmId const & mwmId, uint32_t index) : m_mwmId(mwmId), m_index(index) {}
bool IsValid() const { return m_mwmId.IsAlive(); }
bool operator<(FeatureID const & r) const
{
if (m_mwmId != r.m_mwmId)
return m_mwmId < r.m_mwmId;
return m_index < r.m_index;
}
bool operator==(FeatureID const & r) const { return m_mwmId == r.m_mwmId && m_index == r.m_index; }
bool operator!=(FeatureID const & r) const { return !(*this == r); }
std::string GetMwmName() const;
/// @todo This function is used in Android only, but seems like useless.
int64_t GetMwmVersion() const;
bool IsEqualCountry(base::StringIL const & lst) const;
bool IsWorld() const;
MwmSet::MwmId m_mwmId;
uint32_t m_index = 0;
};
std::string DebugPrint(FeatureID const & id);
namespace std
{
template <>
struct hash<FeatureID>
{
size_t operator()(FeatureID const & fID) const;
};
} // namespace std

View file

@ -0,0 +1,19 @@
#include "indexer/feature_impl.hpp"
#include <algorithm>
#include <cmath>
namespace feature
{
uint8_t PopulationToRank(uint64_t p)
{
return std::min(0xFFl, std::lround(std::log(double(p)) / std::log(1.1)));
}
uint64_t RankToPopulation(uint8_t r)
{
return static_cast<uint64_t>(std::pow(1.1, r));
}
} // namespace feature

View file

@ -0,0 +1,28 @@
#pragma once
#include "indexer/scales.hpp"
#include "base/assert.hpp"
#include <string>
namespace strings
{
class UniString;
}
namespace feature
{
int constexpr g_arrWorldScales[] = {3, 5, 7, scales::GetUpperWorldScale()};
int constexpr g_arrCountryScales[] = {10, 12, 14, scales::GetUpperScale()};
static_assert(std::size(g_arrWorldScales) == std::size(g_arrCountryScales));
inline std::string GetTagForIndex(std::string const & prefix, size_t ind)
{
ASSERT(ind < std::size(g_arrWorldScales) && ind < std::size(g_arrCountryScales), (ind));
return prefix + char('0' + ind);
}
uint8_t PopulationToRank(uint64_t p);
uint64_t RankToPopulation(uint8_t r);
} // namespace feature

View file

@ -0,0 +1,361 @@
#include "indexer/feature_meta.hpp"
#include "custom_keyvalue.hpp"
#include "std/target_os.hpp"
namespace feature
{
using namespace std;
namespace
{
char constexpr const * kBaseWikiUrl =
#ifdef OMIM_OS_MOBILE
".m.wikipedia.org/wiki/";
#else
".wikipedia.org/wiki/";
#endif
char constexpr const * kBaseCommonsUrl =
#ifdef OMIM_OS_MOBILE
"https://commons.m.wikimedia.org/wiki/";
#else
"https://commons.wikimedia.org/wiki/";
#endif
} // namespace
std::string_view MetadataBase::Get(uint8_t type) const
{
std::string_view sv;
auto const it = m_metadata.find(type);
if (it != m_metadata.end())
{
sv = it->second;
ASSERT(!sv.empty(), ());
}
return sv;
}
std::string_view MetadataBase::Set(uint8_t type, std::string value)
{
std::string_view sv;
if (value.empty())
m_metadata.erase(type);
else
{
auto & res = m_metadata[type];
res = std::move(value);
sv = res;
}
return sv;
}
string Metadata::ToWikiURL(std::string v)
{
auto const colon = v.find(':');
if (colon == string::npos)
return v;
// Spaces, % and ? characters should be corrected to form a valid URL's path.
// Standard percent encoding also encodes other characters like (), which lead to an unnecessary HTTP redirection.
for (auto i = colon; i < v.size(); ++i)
{
auto & c = v[i];
if (c == ' ')
c = '_';
else if (c == '%')
v.insert(i + 1, "25"); // % => %25
else if (c == '?')
{
c = '%';
v.insert(i + 1, "3F"); // ? => %3F
}
}
// Trying to avoid redirects by constructing the right link.
// TODO: Wikipedia article could be opened in a user's language, but need
// generator level support to check for available article languages first.
return "https://" + v.substr(0, colon) + kBaseWikiUrl + v.substr(colon + 1);
}
std::string Metadata::GetWikiURL() const
{
return ToWikiURL(string(Get(FMD_WIKIPEDIA)));
}
string Metadata::ToWikimediaCommonsURL(std::string const & v)
{
if (v.empty())
return v;
// Use the media viewer for single files
if (v.starts_with("File:"))
return kBaseCommonsUrl + v + "#/media/" + v;
// or standard if it's a category
return kBaseCommonsUrl + v;
}
// static
bool Metadata::TypeFromString(string_view k, Metadata::EType & outType)
{
if (k == "opening_hours")
outType = Metadata::FMD_OPEN_HOURS;
else if (k == "check_date")
outType = Metadata::FMD_CHECK_DATE;
else if (k == "check_date:opening_hours")
outType = Metadata::FMD_CHECK_DATE_OPEN_HOURS;
else if (k == "phone" || k == "contact:phone" || k == "contact:mobile" || k == "mobile")
outType = Metadata::FMD_PHONE_NUMBER;
else if (k == "fax" || k == "contact:fax")
outType = Metadata::EType::FMD_FAX_NUMBER;
else if (k == "stars")
outType = Metadata::FMD_STARS;
else if (k.starts_with("operator"))
outType = Metadata::FMD_OPERATOR;
else if (k == "url" || k == "website" || k == "contact:website")
outType = Metadata::FMD_WEBSITE;
else if (k == "facebook" || k == "contact:facebook")
outType = Metadata::FMD_CONTACT_FACEBOOK;
else if (k == "instagram" || k == "contact:instagram")
outType = Metadata::FMD_CONTACT_INSTAGRAM;
else if (k == "twitter" || k == "contact:twitter")
outType = Metadata::FMD_CONTACT_TWITTER;
else if (k == "vk" || k == "contact:vk")
outType = Metadata::FMD_CONTACT_VK;
else if (k == "contact:line")
outType = Metadata::FMD_CONTACT_LINE;
else if (k == "contact:mastodon")
outType = Metadata::FMD_CONTACT_FEDIVERSE;
else if (k == "contact:bluesky")
outType = Metadata::FMD_CONTACT_BLUESKY;
else if (k == "internet_access" || k == "wifi")
outType = Metadata::FMD_INTERNET;
else if (k == "ele")
outType = Metadata::FMD_ELE;
else if (k == "destination")
outType = Metadata::FMD_DESTINATION;
else if (k == "destination:ref")
outType = Metadata::FMD_DESTINATION_REF;
else if (k == "junction:ref")
outType = Metadata::FMD_JUNCTION_REF;
else if (k == "turn:lanes")
outType = Metadata::FMD_TURN_LANES;
else if (k == "turn:lanes:forward")
outType = Metadata::FMD_TURN_LANES_FORWARD;
else if (k == "turn:lanes:backward")
outType = Metadata::FMD_TURN_LANES_BACKWARD;
else if (k == "email" || k == "contact:email")
outType = Metadata::FMD_EMAIL;
// Process only _main_ tag here, needed for editor ser/des. Actual postcode parsing happens in GetNameAndType.
else if (k == "addr:postcode")
outType = Metadata::FMD_POSTCODE;
else if (k == "wikipedia")
outType = Metadata::FMD_WIKIPEDIA;
else if (k == "wikimedia_commons")
outType = Metadata::FMD_WIKIMEDIA_COMMONS;
else if (k == "panoramax")
outType = Metadata::FMD_PANORAMAX;
else if (k == "addr:flats")
outType = Metadata::FMD_FLATS;
else if (k == "height")
outType = Metadata::FMD_HEIGHT;
else if (k == "min_height")
outType = Metadata::FMD_MIN_HEIGHT;
else if (k == "building:levels")
outType = Metadata::FMD_BUILDING_LEVELS;
else if (k == "building:min_level")
outType = Metadata::FMD_BUILDING_MIN_LEVEL;
else if (k == "denomination")
outType = Metadata::FMD_DENOMINATION;
else if (k == "level")
outType = Metadata::FMD_LEVEL;
else if (k == "iata")
outType = Metadata::FMD_AIRPORT_IATA;
else if (k.starts_with("brand"))
outType = Metadata::FMD_BRAND;
else if (k == "branch")
outType = Metadata::FMD_BRANCH;
else if (k == "duration")
outType = Metadata::FMD_DURATION;
else if (k == "capacity")
outType = Metadata::FMD_CAPACITY;
else if (k == "local_ref")
outType = Metadata::FMD_LOCAL_REF;
else if (k == "drive_through")
outType = Metadata::FMD_DRIVE_THROUGH;
else if (k == "website:menu")
outType = Metadata::FMD_WEBSITE_MENU;
else if (k == "self_service")
outType = Metadata::FMD_SELF_SERVICE;
else if (k == "outdoor_seating")
outType = Metadata::FMD_OUTDOOR_SEATING;
else if (k == "network")
outType = Metadata::FMD_NETWORK;
else if (k.starts_with("socket:"))
outType = Metadata::FMD_CHARGE_SOCKETS;
else
return false;
return true;
}
void Metadata::ClearPOIAttribs()
{
for (auto i = m_metadata.begin(); i != m_metadata.end();)
if (i->first != Metadata::FMD_ELE && i->first != Metadata::FMD_POSTCODE && i->first != Metadata::FMD_FLATS &&
i->first != Metadata::FMD_HEIGHT && i->first != Metadata::FMD_MIN_HEIGHT &&
i->first != Metadata::FMD_BUILDING_LEVELS && i->first != Metadata::FMD_TEST_ID &&
i->first != Metadata::FMD_BUILDING_MIN_LEVEL)
{
i = m_metadata.erase(i);
}
else
++i;
}
void RegionData::SetLanguages(vector<string> const & codes)
{
string value;
for (string const & code : codes)
{
int8_t const lang = StringUtf8Multilang::GetLangIndex(code);
if (lang != StringUtf8Multilang::kUnsupportedLanguageCode)
value.push_back(lang);
}
MetadataBase::Set(RegionData::Type::RD_LANGUAGES, value);
}
void RegionData::GetLanguages(vector<int8_t> & langs) const
{
for (auto const lang : Get(RegionData::Type::RD_LANGUAGES))
langs.push_back(lang);
}
bool RegionData::HasLanguage(int8_t const lang) const
{
for (auto const lng : Get(RegionData::Type::RD_LANGUAGES))
if (lng == lang)
return true;
return false;
}
bool RegionData::IsSingleLanguage(int8_t const lang) const
{
auto const value = Get(RegionData::Type::RD_LANGUAGES);
if (value.size() != 1)
return false;
return value.front() == lang;
}
void RegionData::AddPublicHoliday(int8_t month, int8_t offset)
{
string value(Get(RegionData::Type::RD_PUBLIC_HOLIDAYS));
value.push_back(month);
value.push_back(offset);
Set(RegionData::Type::RD_PUBLIC_HOLIDAYS, std::move(value));
}
// Warning: exact osm tag keys should be returned for valid enum values.
string ToString(Metadata::EType type)
{
switch (type)
{
case Metadata::FMD_CUISINE: return "cuisine";
case Metadata::FMD_OPEN_HOURS: return "opening_hours";
case Metadata::FMD_CHECK_DATE: return "check_date";
case Metadata::FMD_CHECK_DATE_OPEN_HOURS: return "check_date:opening_hours";
case Metadata::FMD_PHONE_NUMBER: return "phone";
case Metadata::FMD_FAX_NUMBER: return "fax";
case Metadata::FMD_STARS: return "stars";
case Metadata::FMD_OPERATOR: return "operator";
case Metadata::FMD_WEBSITE: return "website";
case Metadata::FMD_INTERNET: return "internet_access";
case Metadata::FMD_ELE: return "ele";
case Metadata::FMD_TURN_LANES: return "turn:lanes";
case Metadata::FMD_TURN_LANES_FORWARD: return "turn:lanes:forward";
case Metadata::FMD_TURN_LANES_BACKWARD: return "turn:lanes:backward";
case Metadata::FMD_EMAIL: return "email";
case Metadata::FMD_POSTCODE: return "addr:postcode";
case Metadata::FMD_WIKIPEDIA: return "wikipedia";
case Metadata::FMD_DESCRIPTION: return "description";
case Metadata::FMD_FLATS: return "addr:flats";
case Metadata::FMD_HEIGHT: return "height";
case Metadata::FMD_MIN_HEIGHT: return "min_height";
case Metadata::FMD_DENOMINATION: return "denomination";
case Metadata::FMD_BUILDING_LEVELS: return "building:levels";
case Metadata::FMD_TEST_ID: return "test_id";
case Metadata::FMD_CUSTOM_IDS: return "custom_ids";
case Metadata::FMD_PRICE_RATES: return "price_rates";
case Metadata::FMD_RATINGS: return "ratings";
case Metadata::FMD_EXTERNAL_URI: return "external_uri";
case Metadata::FMD_LEVEL: return "level";
case Metadata::FMD_AIRPORT_IATA: return "iata";
case Metadata::FMD_BRAND: return "brand";
case Metadata::FMD_BRANCH: return "branch";
case Metadata::FMD_DURATION: return "duration";
case Metadata::FMD_CONTACT_FACEBOOK: return "contact:facebook";
case Metadata::FMD_CONTACT_INSTAGRAM: return "contact:instagram";
case Metadata::FMD_CONTACT_TWITTER: return "contact:twitter";
case Metadata::FMD_CONTACT_VK: return "contact:vk";
case Metadata::FMD_CONTACT_LINE: return "contact:line";
case Metadata::FMD_CONTACT_FEDIVERSE: return "contact:mastodon";
case Metadata::FMD_CONTACT_BLUESKY: return "contact:bluesky";
case Metadata::FMD_DESTINATION: return "destination";
case Metadata::FMD_DESTINATION_REF: return "destination:ref";
case Metadata::FMD_JUNCTION_REF: return "junction:ref";
case Metadata::FMD_BUILDING_MIN_LEVEL: return "building:min_level";
case Metadata::FMD_WIKIMEDIA_COMMONS: return "wikimedia_commons";
case Metadata::FMD_PANORAMAX: return "panoramax";
case Metadata::FMD_CAPACITY: return "capacity";
case Metadata::FMD_WHEELCHAIR: return "wheelchair";
case Metadata::FMD_LOCAL_REF: return "local_ref";
case Metadata::FMD_DRIVE_THROUGH: return "drive_through";
case Metadata::FMD_WEBSITE_MENU: return "website:menu";
case Metadata::FMD_SELF_SERVICE: return "self_service";
case Metadata::FMD_OUTDOOR_SEATING: return "outdoor_seating";
case Metadata::FMD_NETWORK: return "network";
case Metadata::FMD_CHARGE_SOCKETS: CHECK(false, ("FMD_CHARGE_SOCKETS is a compound attribute."));
case Metadata::FMD_COUNT: CHECK(false, ("FMD_COUNT can not be used as a type."));
};
return {};
}
string DebugPrint(Metadata const & metadata)
{
bool first = true;
std::string res = "Metadata [";
for (uint8_t i = 0; i < static_cast<uint8_t>(Metadata::FMD_COUNT); ++i)
{
auto const t = static_cast<Metadata::EType>(i);
auto const sv = metadata.Get(t);
if (!sv.empty())
{
if (first)
first = false;
else
res += "; ";
res.append(DebugPrint(t)).append("=");
switch (t)
{
case Metadata::FMD_DESCRIPTION: res += DebugPrint(StringUtf8Multilang::FromBuffer(std::string(sv))); break;
case Metadata::FMD_CUSTOM_IDS:
case Metadata::FMD_PRICE_RATES:
case Metadata::FMD_RATINGS: res += DebugPrint(indexer::CustomKeyValue(sv)); break;
default: res.append(sv); break;
}
}
}
res += "]";
return res;
}
string DebugPrint(AddressData const & addressData)
{
return string("AddressData { Street = \"").append(addressData.Get(AddressData::Type::Street)) + "\" }";
}
} // namespace feature

View file

@ -0,0 +1,258 @@
#pragma once
#include "coding/string_utf8_multilang.hpp"
#include "base/stl_helpers.hpp"
#include <map>
#include <string>
#include <vector>
namespace feature
{
class MetadataBase
{
public:
bool Has(uint8_t type) const { return m_metadata.find(type) != m_metadata.end(); }
std::string_view Get(uint8_t type) const;
std::string_view Set(uint8_t type, std::string value);
inline bool Empty() const { return m_metadata.empty(); }
inline size_t Size() const { return m_metadata.size(); }
template <class Sink>
void SerializeForMwmTmp(Sink & sink) const
{
auto const sz = static_cast<uint32_t>(m_metadata.size());
WriteVarUint(sink, sz);
for (auto const & it : m_metadata)
{
WriteVarUint(sink, static_cast<uint32_t>(it.first));
rw::WriteNonEmpty(sink, it.second);
}
}
template <class Source>
void DeserializeFromMwmTmp(Source & src)
{
auto const sz = ReadVarUint<uint32_t>(src);
for (size_t i = 0; i < sz; ++i)
{
auto const key = ReadVarUint<uint32_t>(src);
rw::ReadNonEmpty(src, m_metadata[key]);
}
}
inline bool Equals(MetadataBase const & other) const { return m_metadata == other.m_metadata; }
void Clear() { m_metadata.clear(); }
protected:
/// @todo Change uint8_t to appropriate type when FMD_COUNT reaches 256.
std::map<uint8_t, std::string> m_metadata;
};
class Metadata : public MetadataBase
{
public:
/// @note! Do not change values here.
/// Add new types to the end of list, before FMD_COUNT.
/// Add new types to the corresponding list in android/.../Metadata.java.
/// For types parsed from OSM get corresponding OSM tag to MetadataTagProcessor::TypeFromString().
enum EType : int8_t
{
// Used as id only, because cuisines are defined by classifier types now. Will be active in future.
FMD_CUISINE = 1,
FMD_OPEN_HOURS = 2,
FMD_PHONE_NUMBER = 3,
FMD_FAX_NUMBER = 4,
FMD_STARS = 5,
FMD_OPERATOR = 6,
// FMD_URL = 7, // Deprecated, use FMD_WEBSITE
FMD_WEBSITE = 8,
/// @todo We have meta and classifier type at the same type. It's ok now for search, but should be revised in
/// future.
FMD_INTERNET = 9,
FMD_ELE = 10,
FMD_TURN_LANES = 11,
FMD_TURN_LANES_FORWARD = 12,
FMD_TURN_LANES_BACKWARD = 13,
FMD_EMAIL = 14,
FMD_POSTCODE = 15,
FMD_WIKIPEDIA = 16,
FMD_DESCRIPTION = 17,
FMD_FLATS = 18,
FMD_HEIGHT = 19,
FMD_MIN_HEIGHT = 20,
FMD_DENOMINATION = 21,
FMD_BUILDING_LEVELS = 22,
FMD_TEST_ID = 23,
FMD_CUSTOM_IDS = 24,
FMD_PRICE_RATES = 25,
FMD_RATINGS = 26,
FMD_EXTERNAL_URI = 27,
FMD_LEVEL = 28,
FMD_AIRPORT_IATA = 29,
FMD_BRAND = 30,
// Duration of routes by ferries and other rare means of transportation.
// The number of ferries having the duration key in OSM is low so we
// store the parsed tag value in Metadata instead of building a separate section for it.
// See https://wiki.openstreetmap.org/wiki/Key:duration
FMD_DURATION = 31,
FMD_CONTACT_FACEBOOK = 32,
FMD_CONTACT_INSTAGRAM = 33,
FMD_CONTACT_TWITTER = 34,
FMD_CONTACT_VK = 35,
FMD_CONTACT_LINE = 36,
FMD_DESTINATION = 37,
FMD_DESTINATION_REF = 38,
FMD_JUNCTION_REF = 39,
FMD_BUILDING_MIN_LEVEL = 40,
FMD_WIKIMEDIA_COMMONS = 41,
FMD_CAPACITY = 42,
FMD_WHEELCHAIR = 43, // Value is runtime only, data is taken from the classificator types
FMD_LOCAL_REF = 44,
FMD_DRIVE_THROUGH = 45,
FMD_WEBSITE_MENU = 46,
FMD_SELF_SERVICE = 47,
FMD_OUTDOOR_SEATING = 48,
FMD_NETWORK = 49,
FMD_CONTACT_FEDIVERSE = 50,
FMD_CONTACT_BLUESKY = 51,
FMD_PANORAMAX = 52,
FMD_CHECK_DATE = 53,
FMD_CHECK_DATE_OPEN_HOURS = 54,
FMD_BRANCH = 55,
FMD_CHARGE_SOCKETS = 56,
FMD_COUNT
};
/// Used to normalize tags like "contact:phone", "phone" and "contact:mobile" to a common metadata enum value.
static bool TypeFromString(std::string_view osmTagKey, EType & outType);
template <class FnT>
void ForEach(FnT && fn) const
{
for (auto const & e : m_metadata)
fn(static_cast<Metadata::EType>(e.first), e.second);
}
bool Has(EType type) const { return MetadataBase::Has(static_cast<uint8_t>(type)); }
std::string_view Get(EType type) const { return MetadataBase::Get(static_cast<uint8_t>(type)); }
std::string_view Set(EType type, std::string value)
{
return MetadataBase::Set(static_cast<uint8_t>(type), std::move(value));
}
void Drop(EType type) { Set(type, std::string()); }
static std::string ToWikiURL(std::string v);
std::string GetWikiURL() const;
static std::string ToWikimediaCommonsURL(std::string const & v);
void ClearPOIAttribs();
};
class AddressData : public MetadataBase
{
public:
enum class Type : uint8_t
{
Street,
Place,
};
// Store single value only.
void Set(Type type, std::string_view s) { Set(type, std::string(s)); }
void Set(Type type, std::string s)
{
if (!s.empty())
MetadataBase::Set(base::Underlying(type), std::move(s));
}
void SetIfAbsent(Type type, std::string s)
{
uint8_t const ut = base::Underlying(type);
if (!s.empty() && !Has(ut))
MetadataBase::Set(ut, std::move(s));
}
std::string_view Get(Type type) const { return MetadataBase::Get(base::Underlying(type)); }
};
class RegionData : public MetadataBase
{
public:
enum Type : int8_t
{
RD_LANGUAGES, // list of written languages
RD_DRIVING, // left- or right-hand driving (letter 'l' or 'r')
RD_TIMEZONE, // UTC timezone offset, floating signed number of hours: -3, 4.5
RD_ADDRESS_FORMAT, // address format, re: mapzen
RD_PHONE_FORMAT, // list of strings in "+N NNN NN-NN-NN" format
RD_POSTCODE_FORMAT, // list of strings in "AAA ANN" format
RD_PUBLIC_HOLIDAYS, // fixed PH dates
RD_ALLOW_HOUSENAMES, // 'y' if housenames are commonly used
RD_LEAP_WEIGHT_SPEED // speed factor for leap weight computation
};
// Special values for month references in public holiday definitions.
enum PHReference : int8_t
{
PH_EASTER = 20,
PH_ORTHODOX_EASTER = 21,
PH_VICTORIA_DAY = 22,
PH_CANADA_DAY = 23
};
template <class Sink>
void Serialize(Sink & sink) const
{
MetadataBase::SerializeForMwmTmp(sink);
}
template <class Source>
void Deserialize(Source & src)
{
MetadataBase::DeserializeFromMwmTmp(src);
}
void Set(Type type, std::string const & s)
{
CHECK_NOT_EQUAL(type, Type::RD_LANGUAGES, ("Please use RegionData::SetLanguages method"));
MetadataBase::Set(type, s);
}
void SetLanguages(std::vector<std::string> const & codes);
void GetLanguages(std::vector<int8_t> & langs) const;
bool HasLanguage(int8_t const lang) const;
bool IsSingleLanguage(int8_t const lang) const;
void AddPublicHoliday(int8_t month, int8_t offset);
// No public holidays getters until we know what to do with these.
void SetLeapWeightSpeed(double speedValue)
{
MetadataBase::Set(Type::RD_LEAP_WEIGHT_SPEED, std::to_string(speedValue));
}
/// @see EdgeEstimator::GetLeapWeightSpeed
// double GetLeapWeightSpeed(double defaultValue) const
// {
// if (Has(Type::RD_LEAP_WEIGHT_SPEED))
// return std::stod(Get(Type::RD_LEAP_WEIGHT_SPEED));
// return defaultValue;
// }
};
// Prints types in osm-friendly format.
std::string ToString(feature::Metadata::EType type);
inline std::string DebugPrint(feature::Metadata::EType type)
{
return ToString(type);
}
std::string DebugPrint(feature::Metadata const & metadata);
std::string DebugPrint(feature::AddressData const & addressData);
} // namespace feature

View file

@ -0,0 +1,31 @@
#pragma once
#include "indexer/features_vector.hpp"
#include "coding/file_reader.hpp"
#include "coding/files_container.hpp"
#include <memory>
#include <string>
namespace feature
{
template <class ToDo>
void ForEachFeature(FilesContainerR const & cont, ToDo && toDo)
{
FeaturesVectorTest features(cont);
features.GetVector().ForEach(toDo);
}
template <class ToDo>
void ForEachFeature(ModelReaderPtr reader, ToDo && toDo)
{
ForEachFeature(FilesContainerR(reader), toDo);
}
template <class ToDo>
void ForEachFeature(std::string const & fPath, ToDo && toDo)
{
ForEachFeature(std::make_unique<FileReader>(fPath), toDo);
}
} // namespace feature

View file

@ -0,0 +1,56 @@
#include "indexer/feature_source.hpp"
std::string ToString(FeatureStatus fs)
{
switch (fs)
{
case FeatureStatus::Untouched: return "Untouched";
case FeatureStatus::Deleted: return "Deleted";
case FeatureStatus::Obsolete: return "Obsolete";
case FeatureStatus::Modified: return "Modified";
case FeatureStatus::Created: return "Created";
};
return "Undefined";
}
FeatureSource::FeatureSource(MwmSet::MwmHandle const & handle) : m_handle(handle)
{
if (!m_handle.IsAlive())
return;
auto const & value = *m_handle.GetValue();
m_vector = std::make_unique<FeaturesVector>(value.m_cont, value.GetHeader(), value.m_ftTable.get(),
value.m_relTable.get(), value.m_metaDeserializer.get());
}
size_t FeatureSource::GetNumFeatures() const
{
if (!m_handle.IsAlive())
return 0;
ASSERT(m_vector, ());
return m_vector->GetNumFeatures();
}
std::unique_ptr<FeatureType> FeatureSource::GetOriginalFeature(uint32_t index) const
{
ASSERT(m_handle.IsAlive(), ());
ASSERT(m_vector, ());
auto ft = m_vector->GetByIndex(index);
ft->SetID({GetMwmId(), index});
return ft;
}
FeatureStatus FeatureSource::GetFeatureStatus(uint32_t index) const
{
return FeatureStatus::Untouched;
}
std::unique_ptr<FeatureType> FeatureSource::GetModifiedFeature(uint32_t index) const
{
return {};
}
void FeatureSource::ForEachAdditionalFeature(m2::RectD const & rect, int scale,
std::function<void(uint32_t)> const & fn) const
{}

View file

@ -0,0 +1,65 @@
#pragma once
#include "indexer/feature.hpp"
#include "indexer/features_vector.hpp"
#include "indexer/mwm_set.hpp"
#include "geometry/rect2d.hpp"
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
enum class FeatureStatus
{
Untouched, // The feature hasn't been saved in the editor.
Deleted, // The feature has been marked as deleted.
Obsolete, // The feature has been marked for deletion via note.
Modified, // The feature has been saved in the editor and differs from the original one.
Created // The feature was created by a user and has been saved in the editor.
// Note: If a feature was created by a user but hasn't been saved in the editor yet
// its status is Untouched.
}; // enum class FeatureStatus
std::string ToString(FeatureStatus fs);
inline std::string DebugPrint(FeatureStatus fs)
{
return ToString(fs);
}
class FeatureSource
{
public:
explicit FeatureSource(MwmSet::MwmHandle const & handle);
virtual ~FeatureSource() = default;
size_t GetNumFeatures() const;
std::unique_ptr<FeatureType> GetOriginalFeature(uint32_t index) const;
MwmSet::MwmId const & GetMwmId() const { return m_handle.GetId(); }
virtual FeatureStatus GetFeatureStatus(uint32_t index) const;
virtual std::unique_ptr<FeatureType> GetModifiedFeature(uint32_t index) const;
// Runs |fn| for each feature, that is not present in the mwm.
virtual void ForEachAdditionalFeature(m2::RectD const & rect, int scale,
std::function<void(uint32_t)> const & fn) const;
protected:
MwmSet::MwmHandle const & m_handle;
std::unique_ptr<FeaturesVector> m_vector;
};
// Lightweight FeatureSource factory. Each DataSource owns factory object.
class FeatureSourceFactory
{
public:
virtual ~FeatureSourceFactory() = default;
virtual std::unique_ptr<FeatureSource> operator()(MwmSet::MwmHandle const & handle) const
{
return std::make_unique<FeatureSource>(handle);
}
};

View file

@ -0,0 +1,158 @@
#include "indexer/feature_to_osm.hpp"
#include "indexer/data_source.hpp"
#include "indexer/mwm_set.hpp"
#include "indexer/utils.hpp"
#include "coding/reader.hpp"
#include "base/macros.hpp"
#include "defines.hpp"
namespace indexer
{
// FeatureIdToGeoObjectIdOneWay --------------------------------------------------------------------
FeatureIdToGeoObjectIdOneWay::FeatureIdToGeoObjectIdOneWay(DataSource const & dataSource)
: m_dataSource(dataSource)
, m_reader(std::unique_ptr<ModelReader>())
{}
bool FeatureIdToGeoObjectIdOneWay::Load()
{
auto const handle = indexer::FindWorld(m_dataSource);
if (!handle.IsAlive())
{
LOG(LWARNING, ("Can't find World map file."));
return false;
}
if (handle.GetId() == m_mwmId)
return true;
auto const & cont = handle.GetValue()->m_cont;
if (!cont.IsExist(FEATURE_TO_OSM_FILE_TAG))
{
LOG(LWARNING, ("No cities fid bimap in the world map."));
return false;
}
bool success = false;
try
{
m_reader = cont.GetReader(FEATURE_TO_OSM_FILE_TAG);
success = FeatureIdToGeoObjectIdSerDes::Deserialize(*m_reader.GetPtr(), *this);
}
catch (Reader::Exception const & e)
{
LOG(LERROR, ("Can't read cities fid bimap from the world map:", e.Msg()));
return false;
}
if (success)
{
m_mwmId = handle.GetId();
return true;
}
return false;
}
bool FeatureIdToGeoObjectIdOneWay::GetGeoObjectId(FeatureID const & fid, base::GeoObjectId & id)
{
if (fid.m_mwmId != m_mwmId)
{
LOG(LERROR, ("Wrong mwm: feature has", fid.m_mwmId, "expected:", m_mwmId));
return false;
}
if (m_mapNodes == nullptr || m_mapWays == nullptr || m_mapRelations == nullptr)
return false;
uint64_t serialId;
if (m_mapNodes->Get(fid.m_index, serialId))
{
id = base::MakeOsmNode(serialId);
return true;
}
if (m_mapWays->Get(fid.m_index, serialId))
{
id = base::MakeOsmWay(serialId);
return true;
}
if (m_mapRelations->Get(fid.m_index, serialId))
{
id = base::MakeOsmRelation(serialId);
return true;
}
return false;
}
// FeatureIdToGeoObjectIdTwoWay --------------------------------------------------------------------
FeatureIdToGeoObjectIdTwoWay::FeatureIdToGeoObjectIdTwoWay(DataSource const & dataSource) : m_dataSource(dataSource) {}
bool FeatureIdToGeoObjectIdTwoWay::Load()
{
auto const handle = indexer::FindWorld(m_dataSource);
if (!handle.IsAlive())
{
LOG(LWARNING, ("Can't find World map file."));
return false;
}
if (handle.GetId() == m_mwmId)
return true;
auto const & cont = handle.GetValue()->m_cont;
if (!cont.IsExist(FEATURE_TO_OSM_FILE_TAG))
{
LOG(LWARNING, ("No cities fid bimap in the world map."));
return false;
}
bool success = false;
try
{
FilesContainerR::TReader const reader = cont.GetReader(FEATURE_TO_OSM_FILE_TAG);
success = FeatureIdToGeoObjectIdSerDes::Deserialize(*reader.GetPtr(), m_map);
}
catch (Reader::Exception const & e)
{
LOG(LERROR, ("Can't read cities fid bimap from the world map:", e.Msg()));
return false;
}
if (success)
{
m_mwmId = handle.GetId();
return true;
}
return false;
}
bool FeatureIdToGeoObjectIdTwoWay::GetGeoObjectId(FeatureID const & fid, base::GeoObjectId & id)
{
if (fid.m_mwmId != m_mwmId)
{
LOG(LERROR, ("Wrong mwm: feature has", fid.m_mwmId, "expected:", m_mwmId));
return false;
}
return m_map.GetValue(fid.m_index, id);
}
bool FeatureIdToGeoObjectIdTwoWay::GetFeatureID(base::GeoObjectId const & id, FeatureID & fid)
{
uint32_t index;
if (!m_map.GetKey(id, index))
return false;
fid = FeatureID(m_mwmId, index);
return true;
}
} // namespace indexer

View file

@ -0,0 +1,358 @@
#pragma once
#include "indexer/data_source.hpp"
#include "indexer/feature_decl.hpp"
#include "coding/files_container.hpp"
#include "coding/map_uint32_to_val.hpp"
#include "coding/reader.hpp"
#include "coding/succinct_mapper.hpp"
#include "coding/varint.hpp"
#include "coding/write_to_sink.hpp"
#include "coding/writer.hpp"
#include "base/assert.hpp"
#include "base/bidirectional_map.hpp"
#include "base/checked_cast.hpp"
#include "base/geo_object_id.hpp"
#include "base/logging.hpp"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
class DataSource;
namespace indexer
{
// An in-memory implementation of the data structure that provides a
// serializable mapping of FeatureIDs to base::GeoObjectIds and back.
using FeatureIdToGeoObjectIdBimapMem = base::BidirectionalMap<uint32_t, base::GeoObjectId>;
// A unidirectional read-only map of FeatureIds from a single
// mwm of a fixed version to GeoObjectIds.
// Currently, only World.mwm of the latest version is supported.
class FeatureIdToGeoObjectIdOneWay
{
public:
friend class FeatureIdToGeoObjectIdSerDes;
explicit FeatureIdToGeoObjectIdOneWay(DataSource const & dataSource);
bool Load();
bool GetGeoObjectId(FeatureID const & fid, base::GeoObjectId & id);
template <typename Fn>
void ForEachEntry(Fn && fn) const
{
if (!m_mwmId.IsAlive())
return;
if (m_mapNodes != nullptr)
m_mapNodes->ForEach([&](uint32_t fid, uint64_t serialId) { fn(fid, base::MakeOsmNode(serialId)); });
if (m_mapWays != nullptr)
m_mapWays->ForEach([&](uint32_t fid, uint64_t serialId) { fn(fid, base::MakeOsmWay(serialId)); });
if (m_mapRelations != nullptr)
m_mapRelations->ForEach([&](uint32_t fid, uint64_t serialId) { fn(fid, base::MakeOsmRelation(serialId)); });
}
using Id2OsmMapT = MapUint32ToValue<uint64_t>;
private:
DataSource const & m_dataSource;
MwmSet::MwmId m_mwmId;
FilesContainerR::TReader m_reader;
std::unique_ptr<Id2OsmMapT> m_mapNodes;
std::unique_ptr<Id2OsmMapT> m_mapWays;
std::unique_ptr<Id2OsmMapT> m_mapRelations;
std::unique_ptr<Reader> m_nodesReader;
std::unique_ptr<Reader> m_waysReader;
std::unique_ptr<Reader> m_relationsReader;
};
// A bidirectional read-only map of FeatureIds from a single
// mwm of a fixed version to GeoObjectIds.
// Currently, only World.mwm of the latest version is supported.
// This class will likely be much heavier on RAM than FeatureIdToGeoObjectIdOneWay.
class FeatureIdToGeoObjectIdTwoWay
{
public:
friend class FeatureIdToGeoObjectIdSerDes;
explicit FeatureIdToGeoObjectIdTwoWay(DataSource const & dataSource);
bool Load();
bool GetGeoObjectId(FeatureID const & fid, base::GeoObjectId & id);
bool GetFeatureID(base::GeoObjectId const & id, FeatureID & fid);
template <typename Fn>
void ForEachEntry(Fn && fn) const
{
if (!m_mwmId.IsAlive())
return;
m_map.ForEachEntry(std::forward<Fn>(fn));
}
private:
DataSource const & m_dataSource;
MwmSet::MwmId m_mwmId;
FeatureIdToGeoObjectIdBimapMem m_map;
};
// Section format.
//
// Versions supported Name Offset in bytes Size in bytes
// all magic (to ease the debugging) 0 8
// all version 8 1
// v0 header_v0 16 20
// v0 fid -> osm nodes mapping saved in header_v0
// v0 fid -> osm ways mapping saved in header_v0
// v0 fid -> osm relations mapping saved in header_v0
//
// All offsets are aligned to 8 bytes.
// All integer values larger than a byte are little-endian.
class FeatureIdToGeoObjectIdSerDes
{
public:
enum class Version : uint8_t
{
V0,
};
inline static std::string const kHeaderMagic = "mwmftosm";
inline static Version constexpr kLatestVersion = FeatureIdToGeoObjectIdSerDes::Version::V0;
inline static size_t constexpr kMagicAndVersionSize = 9;
inline static size_t constexpr kHeaderOffset = 16;
struct HeaderV0
{
template <typename Sink>
void Write(Sink & sink)
{
WriteToSink(sink, m_numEntries);
WriteToSink(sink, m_nodesOffset);
WriteToSink(sink, m_waysOffset);
WriteToSink(sink, m_relationsOffset);
WriteToSink(sink, m_typesSentinelOffset);
}
template <typename Source>
void Read(Source & src)
{
m_numEntries = ReadPrimitiveFromSource<uint32_t>(src);
m_nodesOffset = ReadPrimitiveFromSource<uint32_t>(src);
m_waysOffset = ReadPrimitiveFromSource<uint32_t>(src);
m_relationsOffset = ReadPrimitiveFromSource<uint32_t>(src);
m_typesSentinelOffset = ReadPrimitiveFromSource<uint32_t>(src);
}
uint32_t m_numEntries = 0;
// All offsets are relative to the start of the payload (which may differ
// from the end of the header because of padding).
uint32_t m_nodesOffset = 0;
uint32_t m_waysOffset = 0;
uint32_t m_relationsOffset = 0;
uint32_t m_typesSentinelOffset = 0;
};
static_assert(sizeof(HeaderV0) == 20, "");
template <typename Sink>
static void Serialize(Sink & sink, FeatureIdToGeoObjectIdBimapMem const & map)
{
auto const startPos = sink.Pos();
sink.Write(kHeaderMagic.data(), kHeaderMagic.size());
WriteToSink(sink, static_cast<uint8_t>(kLatestVersion));
CHECK_EQUAL(sink.Pos() - startPos, kMagicAndVersionSize, ());
WriteZeroesToSink(sink, kHeaderOffset - kMagicAndVersionSize);
CHECK_EQUAL(sink.Pos() - startPos, kHeaderOffset, ());
switch (kLatestVersion)
{
case Version::V0:
{
HeaderV0 header;
header.Write(sink);
EnsurePadding(sink, startPos);
SerializeV0(sink, map, header);
auto savedPos = sink.Pos();
sink.Seek(startPos + kHeaderOffset);
header.Write(sink);
sink.Seek(savedPos);
}
return;
default: UNREACHABLE();
}
}
template <typename Reader, typename Map>
static bool Deserialize(Reader & reader, Map & map)
{
ReaderSource<decltype(reader)> src(reader);
if (src.Size() < kHeaderOffset)
{
LOG(LINFO, ("Unable to deserialize FeatureToOsm map: wrong header magic or version"));
return false;
}
std::string magic(kHeaderMagic.size(), '\0');
src.Read(&magic[0], magic.size());
if (magic != kHeaderMagic)
{
LOG(LINFO, ("Unable to deserialize FeatureToOsm map: wrong header magic:", magic));
return false;
}
auto const version = static_cast<Version>(ReadPrimitiveFromSource<uint8_t>(src));
src.Skip(kHeaderOffset - kMagicAndVersionSize);
switch (version)
{
case Version::V0:
{
HeaderV0 header;
header.Read(src);
coding::SkipPadding(src);
DeserializeV0(*src.CreateSubReader(), header, map);
}
return true;
default: LOG(LINFO, ("Unable to deserialize FeatureToOsm map: unknown version")); return false;
}
}
template <typename Sink>
static void SerializeV0(Sink & sink, FeatureIdToGeoObjectIdBimapMem const & map, HeaderV0 & header)
{
using Type = base::GeoObjectId::Type;
auto const startPos = base::checked_cast<uint32_t>(sink.Pos());
SerializeV0(sink, Type::OsmNode, map, header.m_nodesOffset);
SerializeV0(sink, Type::OsmWay, map, header.m_waysOffset);
SerializeV0(sink, Type::OsmRelation, map, header.m_relationsOffset);
header.m_numEntries = static_cast<uint32_t>(map.Size());
header.m_nodesOffset -= startPos;
header.m_waysOffset -= startPos;
header.m_relationsOffset -= startPos;
header.m_typesSentinelOffset = base::checked_cast<uint32_t>(sink.Pos()) - startPos;
}
template <typename Sink>
static void SerializeV0(Sink & sink, base::GeoObjectId::Type type, FeatureIdToGeoObjectIdBimapMem const & map,
uint32_t & offset)
{
offset = base::checked_cast<uint32_t>(sink.Pos());
std::vector<std::pair<uint32_t, base::GeoObjectId>> entries;
entries.reserve(map.Size());
type = NormalizedType(type);
map.ForEachEntry([&entries, type](uint32_t const fid, base::GeoObjectId gid)
{
if (NormalizedType(gid.GetType()) == type)
entries.emplace_back(fid, gid);
});
std::sort(entries.begin(), entries.end());
MapUint32ToValueBuilder<uint64_t> builder;
for (auto const & entry : entries)
builder.Put(entry.first, entry.second.GetSerialId());
builder.Freeze(sink, WriteBlockCallback);
EnsurePadding(sink, offset);
}
using Id2OsmMapT = FeatureIdToGeoObjectIdOneWay::Id2OsmMapT;
template <typename Reader>
static void DeserializeV0(Reader & reader, HeaderV0 const & header, FeatureIdToGeoObjectIdOneWay & map)
{
auto const nodesSize = header.m_waysOffset - header.m_nodesOffset;
auto const waysSize = header.m_relationsOffset - header.m_waysOffset;
auto const relationsSize = header.m_typesSentinelOffset - header.m_relationsOffset;
map.m_nodesReader = reader.CreateSubReader(header.m_nodesOffset, nodesSize);
map.m_waysReader = reader.CreateSubReader(header.m_waysOffset, waysSize);
map.m_relationsReader = reader.CreateSubReader(header.m_relationsOffset, relationsSize);
map.m_mapNodes = Id2OsmMapT::Load(*map.m_nodesReader, ReadBlockCallback);
map.m_mapWays = Id2OsmMapT::Load(*map.m_waysReader, ReadBlockCallback);
map.m_mapRelations = Id2OsmMapT::Load(*map.m_relationsReader, ReadBlockCallback);
}
template <typename Reader>
static void DeserializeV0(Reader & reader, HeaderV0 const & header, FeatureIdToGeoObjectIdBimapMem & memMap)
{
using Type = base::GeoObjectId::Type;
memMap.Clear();
auto const nodesSize = header.m_waysOffset - header.m_nodesOffset;
auto const waysSize = header.m_relationsOffset - header.m_waysOffset;
auto const relationsSize = header.m_typesSentinelOffset - header.m_relationsOffset;
DeserializeV0ToMem(*reader.CreateSubReader(header.m_nodesOffset, nodesSize), Type::ObsoleteOsmNode, memMap);
DeserializeV0ToMem(*reader.CreateSubReader(header.m_waysOffset, waysSize), Type::ObsoleteOsmWay, memMap);
DeserializeV0ToMem(*reader.CreateSubReader(header.m_relationsOffset, relationsSize), Type::ObsoleteOsmRelation,
memMap);
}
template <typename Reader>
static void DeserializeV0ToMem(Reader & reader, base::GeoObjectId::Type type, FeatureIdToGeoObjectIdBimapMem & memMap)
{
auto const map = MapUint32ToValue<uint64_t>::Load(reader, ReadBlockCallback);
CHECK(map, ());
map->ForEach([&](uint32_t fid, uint64_t & serialId) { memMap.Add(fid, base::GeoObjectId(type, serialId)); });
}
private:
template <typename Sink>
static void EnsurePadding(Sink & sink, uint64_t startPos)
{
uint64_t bytesWritten = sink.Pos() - startPos;
coding::WritePadding(sink, bytesWritten);
}
static base::GeoObjectId::Type NormalizedType(base::GeoObjectId::Type type)
{
using Type = base::GeoObjectId::Type;
switch (type)
{
case Type::ObsoleteOsmNode: return Type::OsmNode;
case Type::ObsoleteOsmWay: return Type::OsmWay;
case Type::ObsoleteOsmRelation: return Type::OsmRelation;
default: return type;
}
}
static void ReadBlockCallback(NonOwningReaderSource & src, uint32_t blockSize, std::vector<uint64_t> & values)
{
values.reserve(blockSize);
while (src.Size() > 0)
values.push_back(ReadVarUint<uint64_t>(src));
}
static void WriteBlockCallback(Writer & writer, std::vector<uint64_t>::const_iterator begin,
std::vector<uint64_t>::const_iterator end)
{
for (auto it = begin; it != end; ++it)
WriteVarUint(writer, *it);
}
};
} // namespace indexer

View file

@ -0,0 +1,596 @@
#include "indexer/feature_utils.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature_data.hpp"
#include "indexer/feature_visibility.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "indexer/scales.hpp"
#include "platform/distance.hpp"
#include "platform/localization.hpp"
#include "platform/preferred_languages.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "coding/transliteration.hpp"
#include "base/control_flow.hpp"
#include <unordered_map>
#include <utility>
namespace feature
{
using namespace std;
namespace
{
using StrUtf8 = StringUtf8Multilang;
int8_t GetIndex(string const & lang)
{
return StrUtf8::GetLangIndex(lang);
}
void GetMwmLangName(feature::RegionData const & regionData, StrUtf8 const & src, string_view & out)
{
vector<int8_t> mwmLangCodes;
regionData.GetLanguages(mwmLangCodes);
for (auto const code : mwmLangCodes)
if (src.GetString(code, out))
return;
}
bool GetTransliteratedName(RegionData const & regionData, StrUtf8 const & src, string & out)
{
vector<int8_t> mwmLangCodes;
regionData.GetLanguages(mwmLangCodes);
auto const & translator = Transliteration::Instance();
string_view srcName;
for (auto const code : mwmLangCodes)
if (src.GetString(code, srcName) && translator.Transliterate(srcName, code, out))
return true;
// If default name is available, interpret it as a name for the first mwm language.
if (!mwmLangCodes.empty() && src.GetString(StrUtf8::kDefaultCode, srcName))
return translator.Transliterate(srcName, mwmLangCodes[0], out);
return false;
}
bool GetBestName(StrUtf8 const & src, vector<int8_t> const & priorityList, string_view & out)
{
size_t bestIndex = priorityList.size();
src.ForEach([&](int8_t code, string_view name)
{
if (bestIndex == 0)
return base::ControlFlow::Break;
size_t const idx = std::distance(priorityList.begin(), find(priorityList.begin(), priorityList.end(), code));
if (bestIndex > idx)
{
bestIndex = idx;
out = name;
}
return base::ControlFlow::Continue;
});
// There are many "junk" names in Arabian island.
if (bestIndex < priorityList.size() && priorityList[bestIndex] == StrUtf8::kInternationalCode)
out = out.substr(0, out.find_first_of(','));
return bestIndex < priorityList.size();
}
vector<int8_t> GetSimilarLanguages(int8_t lang)
{
static unordered_map<int8_t, vector<int8_t>> const kSimilarLanguages = {
{GetIndex("be"), {GetIndex("ru")}},
{GetIndex("ja"), {GetIndex("ja_kana"), GetIndex("ja_rm")}},
{GetIndex("ko"), {GetIndex("ko_rm")}},
{GetIndex("zh"), {GetIndex("zh_pinyin")}}};
auto const it = kSimilarLanguages.find(lang);
if (it != kSimilarLanguages.cend())
return it->second;
return {};
}
bool IsNativeLang(feature::RegionData const & regionData, int8_t deviceLang)
{
if (regionData.HasLanguage(deviceLang))
return true;
for (auto const lang : languages::GetPreferredLangIndexes())
if (regionData.HasLanguage(lang))
return true;
for (auto const lang : GetSimilarLanguages(deviceLang))
if (regionData.HasLanguage(lang))
return true;
return false;
}
int8_t DefaultLanguage(feature::RegionData const & regionData, vector<int8_t> const & langs)
{
for (auto const lang : langs)
{
if (regionData.HasLanguage(lang))
return lang;
for (auto const similarLang : GetSimilarLanguages(lang))
if (regionData.HasLanguage(similarLang))
return similarLang;
}
return StrUtf8::kDefaultCode;
}
vector<int8_t> PrioritizedLanguages(vector<int8_t> const & langs, int8_t defaultLang)
{
vector<int8_t> prioritizedLangs = {};
for (auto const lang : langs)
{
if (find(prioritizedLangs.begin(), prioritizedLangs.end(), lang) == prioritizedLangs.end())
prioritizedLangs.push_back(lang);
if (defaultLang != StrUtf8::kUnsupportedLanguageCode && defaultLang == lang)
prioritizedLangs.push_back(StrUtf8::kDefaultCode);
auto const similarLangs = GetSimilarLanguages(lang);
prioritizedLangs.insert(prioritizedLangs.cend(), similarLangs.cbegin(), similarLangs.cend());
}
prioritizedLangs.push_back(StrUtf8::kInternationalCode);
prioritizedLangs.push_back(StrUtf8::kEnglishCode);
prioritizedLangs.push_back(StrUtf8::kDefaultCode);
return prioritizedLangs;
}
void GetReadableNameImpl(NameParamsIn const & in, bool preferDefault, NameParamsOut & out)
{
auto const preferredLangs = languages::GetPreferredLangIndexes();
auto const langPriority = PrioritizedLanguages(preferredLangs, DefaultLanguage(in.regionData, preferredLangs));
if (GetBestName(in.src, langPriority, out.primary))
return;
if (in.allowTranslit && GetTransliteratedName(in.regionData, in.src, out.transliterated))
return;
if (!preferDefault)
{
if (GetBestName(in.src, {StrUtf8::kDefaultCode}, out.primary))
return;
}
GetMwmLangName(in.regionData, in.src, out.primary);
}
// Filters types with |checker|, returns vector of raw type second components.
// For example for types {"cuisine-sushi", "cuisine-pizza", "cuisine-seafood"} vector
// of second components is {"sushi", "pizza", "seafood"}.
vector<string> GetRawTypeSecond(ftypes::BaseChecker const & checker, TypesHolder const & types)
{
auto const & c = classif();
vector<string> res;
for (auto const t : types)
{
if (!checker(t))
continue;
auto path = c.GetFullObjectNamePath(t);
CHECK_EQUAL(path.size(), 2, (path));
res.push_back(std::move(path[1]));
}
return res;
}
vector<string> GetLocalizedTypes(ftypes::BaseChecker const & checker, TypesHolder const & types)
{
auto const & c = classif();
vector<string> localized;
for (auto const t : types)
if (checker(t))
localized.push_back(platform::GetLocalizedTypeName(c.GetReadableObjectName(t)));
return localized;
}
class FeatureEstimator
{
template <size_t N>
static bool IsEqual(uint32_t t, uint32_t const (&arr)[N])
{
for (size_t i = 0; i < N; ++i)
if (arr[i] == t)
return true;
return false;
}
static bool InSubtree(uint32_t t, uint32_t const orig)
{
ftype::TruncValue(t, ftype::GetLevel(orig));
return t == orig;
}
public:
FeatureEstimator()
{
auto const & cl = classif();
m_TypeContinent = cl.GetTypeByPath({"place", "continent"});
m_TypeCountry = cl.GetTypeByPath({"place", "country"});
m_TypeState = cl.GetTypeByPath({"place", "state"});
m_TypeCounty[0] = cl.GetTypeByPath({"place", "region"});
m_TypeCounty[1] = cl.GetTypeByPath({"place", "county"});
m_TypeCity = cl.GetTypeByPath({"place", "city"});
m_TypeTown = cl.GetTypeByPath({"place", "town"});
m_TypeVillage[0] = cl.GetTypeByPath({"place", "village"});
m_TypeVillage[1] = cl.GetTypeByPath({"place", "suburb"});
m_TypeSmallVillage[0] = cl.GetTypeByPath({"place", "hamlet"});
m_TypeSmallVillage[1] = cl.GetTypeByPath({"place", "locality"});
m_TypeSmallVillage[2] = cl.GetTypeByPath({"place", "farm"});
}
void CorrectScaleForVisibility(TypesHolder const & types, int & scale) const
{
pair<int, int> const scaleR = GetDrawableScaleRangeForRules(types, RULE_ANY_TEXT);
ASSERT_LESS_OR_EQUAL(scaleR.first, scaleR.second, ());
// Result types can be without visible texts (matched by category).
if (scaleR.first != -1)
{
if (scale < scaleR.first)
scale = scaleR.first;
else if (scale > scaleR.second)
scale = scaleR.second;
}
}
int GetViewportScale(TypesHolder const & types) const
{
int scale = GetDefaultScale();
if (types.GetGeomType() == GeomType::Point)
for (uint32_t t : types)
scale = min(scale, GetScaleForType(t));
CorrectScaleForVisibility(types, scale);
return scale;
}
private:
static int GetDefaultScale() { return scales::GetUpperComfortScale(); }
int GetScaleForType(uint32_t const type) const
{
if (type == m_TypeContinent)
return 2;
/// @todo Load countries bounding rects.
if (type == m_TypeCountry)
return 4;
if (type == m_TypeState)
return 6;
if (IsEqual(type, m_TypeCounty))
return 7;
if (InSubtree(type, m_TypeCity))
return 9;
if (type == m_TypeTown)
return 9;
if (IsEqual(type, m_TypeVillage))
return 12;
if (IsEqual(type, m_TypeSmallVillage))
return 14;
return GetDefaultScale();
}
uint32_t m_TypeContinent;
uint32_t m_TypeCountry;
uint32_t m_TypeState;
uint32_t m_TypeCounty[2];
uint32_t m_TypeCity;
uint32_t m_TypeTown;
uint32_t m_TypeVillage[2];
uint32_t m_TypeSmallVillage[3];
};
FeatureEstimator const & GetFeatureEstimator()
{
static FeatureEstimator const featureEstimator;
return featureEstimator;
}
} // namespace
static constexpr std::string_view kStarSymbol = "";
static constexpr std::string_view kMountainSymbol = "";
static constexpr std::string_view kDrinkingWaterYes = "🚰";
static constexpr std::string_view kDrinkingWaterNo = "🚱";
NameParamsIn::NameParamsIn(StringUtf8Multilang const & src_, RegionData const & regionData_,
std::string_view deviceLang_, bool allowTranslit_)
: NameParamsIn(src_, regionData_, StringUtf8Multilang::GetLangIndex(deviceLang_), allowTranslit_)
{}
bool NameParamsIn::IsNativeOrSimilarLang() const
{
return IsNativeLang(regionData, deviceLang);
}
int GetFeatureViewportScale(TypesHolder const & types)
{
return GetFeatureEstimator().GetViewportScale(types);
}
vector<int8_t> GetSimilar(int8_t lang)
{
vector<int8_t> langs = {lang};
auto const similarLangs = GetSimilarLanguages(lang);
langs.insert(langs.cend(), similarLangs.cbegin(), similarLangs.cend());
return langs;
}
void GetPreferredNames(NameParamsIn const & in, NameParamsOut & out)
{
out.Clear();
if (in.src.IsEmpty())
return;
// When the language of the user is equal to one of the languages of the MWM
// (or similar languages) only single name scheme is used.
if (in.IsNativeOrSimilarLang())
return GetReadableNameImpl(in, true /* preferDefault */, out);
auto const preferredLangs = languages::GetPreferredLangIndexes();
auto const primaryCodes = PrioritizedLanguages(preferredLangs, DefaultLanguage(in.regionData, preferredLangs));
if (!GetBestName(in.src, primaryCodes, out.primary) && in.allowTranslit)
GetTransliteratedName(in.regionData, in.src, out.transliterated);
vector<int8_t> secondaryCodes = {StrUtf8::kDefaultCode, StrUtf8::kInternationalCode};
vector<int8_t> mwmLangCodes;
in.regionData.GetLanguages(mwmLangCodes);
secondaryCodes.insert(secondaryCodes.end(), mwmLangCodes.begin(), mwmLangCodes.end());
secondaryCodes.push_back(StrUtf8::kEnglishCode);
GetBestName(in.src, secondaryCodes, out.secondary);
if (out.primary.empty())
out.primary.swap(out.secondary);
else if (!out.secondary.empty() && out.primary.find(out.secondary) != string::npos)
out.secondary = {};
}
void GetReadableName(NameParamsIn const & in, NameParamsOut & out)
{
out.Clear();
if (!in.src.IsEmpty())
GetReadableNameImpl(in, in.IsNativeOrSimilarLang(), out);
}
string const GetReadableAddress(string const & address)
{
// Instead of housenumber range strings like 123:456, hyphenate like 123 - 456
string out = address;
size_t pos = 0;
while ((pos = out.find(":", pos)) != string::npos)
{
out.replace(pos, 1, "\u2009\u2013\u2009"); // thin space + en-dash + thin space
break;
}
return out;
}
/*
int8_t GetNameForSearchOnBooking(RegionData const & regionData, StringUtf8Multilang const & src, string & name)
{
if (src.GetString(StringUtf8Multilang::kDefaultCode, name))
return StringUtf8Multilang::kDefaultCode;
vector<int8_t> mwmLangs;
regionData.GetLanguages(mwmLangs);
for (auto mwmLang : mwmLangs)
{
if (src.GetString(mwmLang, name))
return mwmLang;
}
if (src.GetString(StringUtf8Multilang::kEnglishCode, name))
return StringUtf8Multilang::kEnglishCode;
name.clear();
return StringUtf8Multilang::kUnsupportedLanguageCode;
}
*/
bool GetPreferredName(StringUtf8Multilang const & src, int8_t deviceLang, string_view & out)
{
auto const preferredLangs = languages::GetPreferredLangIndexes();
auto const priorityList = PrioritizedLanguages(preferredLangs, StrUtf8::kUnsupportedLanguageCode);
return GetBestName(src, priorityList, out);
}
vector<int8_t> GetDescriptionLangPriority(RegionData const & regionData)
{
auto const preferredLangs = languages::GetPreferredLangIndexes();
return PrioritizedLanguages(preferredLangs, DefaultLanguage(regionData, preferredLangs));
}
vector<string> GetCuisines(TypesHolder const & types)
{
auto const & isCuisine = ftypes::IsCuisineChecker::Instance();
return GetRawTypeSecond(isCuisine, types);
}
vector<string> GetLocalizedCuisines(TypesHolder const & types)
{
auto const & isCuisine = ftypes::IsCuisineChecker::Instance();
return GetLocalizedTypes(isCuisine, types);
}
vector<string> GetRecyclingTypes(TypesHolder const & types)
{
auto const & isRecyclingType = ftypes::IsRecyclingTypeChecker::Instance();
return GetRawTypeSecond(isRecyclingType, types);
}
vector<string> GetLocalizedRecyclingTypes(TypesHolder const & types)
{
auto const & isRecyclingType = ftypes::IsRecyclingTypeChecker::Instance();
return GetLocalizedTypes(isRecyclingType, types);
}
string GetLocalizedFeeType(TypesHolder const & types)
{
auto const & isFeeType = ftypes::IsFeeTypeChecker::Instance();
auto localized_types = GetLocalizedTypes(isFeeType, types);
ASSERT_LESS_OR_EQUAL(localized_types.size(), 1, ());
if (localized_types.empty())
return "";
return localized_types[0];
}
string GetReadableWheelchairType(TypesHolder const & types)
{
auto const value = ftraits::Wheelchair::GetValue(types);
if (!value.has_value())
return "";
switch (*value)
{
case ftraits::WheelchairAvailability::No: return "wheelchair-no";
case ftraits::WheelchairAvailability::Yes: return "wheelchair-yes";
case ftraits::WheelchairAvailability::Limited: return "wheelchair-limited";
}
UNREACHABLE();
}
std::optional<ftraits::WheelchairAvailability> GetWheelchairType(TypesHolder const & types)
{
return ftraits::Wheelchair::GetValue(types);
}
bool HasAtm(TypesHolder const & types)
{
auto const & isAtmType = ftypes::IsATMChecker::Instance();
return isAtmType(types);
}
bool HasToilets(TypesHolder const & types)
{
auto const & isToiletsType = ftypes::IsToiletsChecker::Instance();
return isToiletsType(types);
}
string FormatDrinkingWater(TypesHolder const & types)
{
auto const value = ftraits::DrinkingWater::GetValue(types);
if (!value.has_value())
return "";
switch (*value)
{
case ftraits::DrinkingWaterAvailability::No: return std::string{kDrinkingWaterNo};
case ftraits::DrinkingWaterAvailability::Yes: return std::string{kDrinkingWaterYes};
}
UNREACHABLE();
}
string FormatStars(uint8_t starsCount)
{
std::string stars;
for (int i = 0; i < starsCount && i < kMaxStarsCount; ++i)
stars.append(kStarSymbol);
return stars;
}
string FormatElevation(string_view elevation)
{
if (!elevation.empty())
{
double value;
if (strings::to_double(elevation, value))
return std::string{kMountainSymbol} + platform::Distance::FormatAltitude(value);
else
LOG(LWARNING, ("Invalid elevation metadata:", elevation));
}
return {};
}
constexpr char const * kWlan = "wlan";
constexpr char const * kWired = "wired";
constexpr char const * kTerminal = "terminal";
constexpr char const * kYes = "yes";
constexpr char const * kNo = "no";
constexpr char const * kOnly = "only";
string DebugPrint(Internet internet)
{
switch (internet)
{
case Internet::No: return kNo;
case Internet::Yes: return kYes;
case Internet::Wlan: return kWlan;
case Internet::Wired: return kWired;
case Internet::Terminal: return kTerminal;
case Internet::Unknown: break;
}
return {};
}
Internet InternetFromString(std::string_view inet)
{
if (inet.empty())
return Internet::Unknown;
if (inet.find(kWlan) != string::npos)
return Internet::Wlan;
if (inet.find(kWired) != string::npos)
return Internet::Wired;
if (inet.find(kTerminal) != string::npos)
return Internet::Terminal;
if (inet == kYes)
return Internet::Yes;
if (inet == kNo)
return Internet::No;
return Internet::Unknown;
}
YesNoUnknown YesNoUnknownFromString(std::string_view str)
{
if (str.empty())
return Unknown;
if (str.find(kOnly) != string::npos)
return Yes;
if (str.find(kYes) != string::npos)
return Yes;
if (str.find(kNo) != string::npos)
return No;
else
return YesNoUnknown::Unknown;
}
} // namespace feature

View file

@ -0,0 +1,188 @@
#pragma once
#include <string>
#include <vector>
#include "indexer/ftraits.hpp"
#include "indexer/yes_no_unknown.hpp"
struct FeatureID;
class StringUtf8Multilang;
namespace feature
{
static constexpr uint8_t kMaxStarsCount = 7;
static constexpr std::string_view kFieldsSeparator = "";
static constexpr std::string_view kToiletsSymbol = "🚻";
static constexpr std::string_view kAtmSymbol = "💳";
static constexpr std::string_view kWheelchairSymbol = "♿️";
static constexpr std::string_view kWifiSymbol = "🛜";
/// OSM internet_access tag values.
enum class Internet
{
Unknown, //!< Internet state is unknown (default).
Wlan, //!< Wireless Internet access is present.
Terminal, //!< A computer with internet service.
Wired, //!< Wired Internet access is present.
Yes, //!< Unspecified Internet access is available.
No //!< There is definitely no any Internet access.
};
std::string DebugPrint(Internet internet);
/// @param[in] inet Should be lowercase like in DebugPrint.
Internet InternetFromString(std::string_view inet);
YesNoUnknown YesNoUnknownFromString(std::string_view str);
// Address house numbers interpolation.
enum class InterpolType : uint8_t
{
None,
Odd,
Even,
Any
};
class TypesHolder;
class RegionData;
/// Get viewport scale to show given feature. Used in search.
int GetFeatureViewportScale(TypesHolder const & types);
// Returns following languages given |lang|:
// - |lang|;
// - languages that we know are similar to |lang|;
std::vector<int8_t> GetSimilar(int8_t deviceLang);
struct NameParamsIn
{
NameParamsIn(StringUtf8Multilang const & src_, RegionData const & regionData_, int8_t deviceLang_,
bool allowTranslit_)
: src(src_)
, regionData(regionData_)
, deviceLang(deviceLang_)
, allowTranslit(allowTranslit_)
{}
NameParamsIn(StringUtf8Multilang const & src, RegionData const & regionData, std::string_view deviceLang,
bool allowTranslit);
StringUtf8Multilang const & src;
RegionData const & regionData;
int8_t const deviceLang;
bool allowTranslit;
bool IsNativeOrSimilarLang() const;
};
struct NameParamsOut
{
/// In case when primary name is empty it will be propagated from secondary and secondary will be
/// cleared. In case when primary name contains secondary name then secondary will be cleared.
std::string_view primary, secondary;
std::string transliterated;
/// Call this fuction to get primary name when allowTranslit == true.
std::string_view GetPrimary() const { return (!primary.empty() ? primary : std::string_view(transliterated)); }
void Clear()
{
primary = secondary = {};
transliterated.clear();
}
};
/// When the language of the device is equal to one of the languages of the MWM
/// (or similar to device languages) only single name scheme is used. See GetReadableName method.
/// Primary name using priority:
/// - device language name;
/// - languages that we know are similar to device language;
/// - international name;
/// - english name;
/// - transliterated name (if allowed).
/// Secondary name using priority:
/// - default name;
/// - international name;
/// - country language name;
/// - english name.
void GetPreferredNames(NameParamsIn const & in, NameParamsOut & out);
/// When MWM contains user's language (or similar to device languages if provided),
/// the priority is the following:
/// - device language name;
/// - default name;
/// - languages that we know are similar to device language;
/// - international name;
/// - english name;
/// - transliterated name (if allowed);
/// - country language name.
/// When MWM does not contain user's language (or similar to device languages),
/// the priority is the following:
/// - device language name;
/// - languages that we know are similar to device language;
/// - international name;
/// - english name;
/// - transliterated name (if allowed);
/// - default name;
/// - country language name.
void GetReadableName(NameParamsIn const & in, NameParamsOut & out);
/// Format house numbers etc to be more human-readable instead of using symbols like 123:456
std::string const GetReadableAddress(std::string const & address);
/// Returns language id as return result and name for search on booking in the @name parameter,
/// the priority is the following:
/// - default name;
/// - country language name;
/// - english name.
// int8_t GetNameForSearchOnBooking(RegionData const & regionData, StringUtf8Multilang const & src, std::string & name);
/// Returns preferred name when only the device language is available.
bool GetPreferredName(StringUtf8Multilang const & src, int8_t deviceLang, std::string_view & out);
/// Returns priority list of language codes for feature description,
/// the priority is the following:
/// - device language codes in order of preference;
/// - including default language code, if MWM contains the language (or similar to device language if provided);
/// - including languages that we know are similar to device language;
/// - international language code;
/// - english language code;
/// - default language code;
std::vector<int8_t> GetDescriptionLangPriority(RegionData const & regionData);
// Returns vector of cuisines readable names from classificator.
std::vector<std::string> GetCuisines(TypesHolder const & types);
// Returns vector of cuisines names localized by platform.
std::vector<std::string> GetLocalizedCuisines(TypesHolder const & types);
// Returns vector of recycling types readable names from classificator.
std::vector<std::string> GetRecyclingTypes(TypesHolder const & types);
// Returns vector of recycling types localized by platform.
std::vector<std::string> GetLocalizedRecyclingTypes(TypesHolder const & types);
// Returns fee type localized by platform.
std::string GetLocalizedFeeType(TypesHolder const & types);
// Returns readable wheelchair type.
std::string GetReadableWheelchairType(TypesHolder const & types);
/// @returns wheelchair availability.
std::optional<ftraits::WheelchairAvailability> GetWheelchairType(TypesHolder const & types);
/// Returns true if feature has ATM type.
bool HasAtm(TypesHolder const & types);
/// Returns true if feature has Toilets type.
bool HasToilets(TypesHolder const & types);
/// @returns formatted drinking water type.
std::string FormatDrinkingWater(TypesHolder const & types);
/// @returns starsCount of ★ symbol.
std::string FormatStars(uint8_t starsCount);
/// @returns formatted elevation with ▲ symbol and units.
std::string FormatElevation(std::string_view elevation);
} // namespace feature

View file

@ -0,0 +1,449 @@
#include "indexer/feature_visibility.hpp"
#include "indexer/classificator.hpp"
#include "indexer/drawing_rules.hpp"
#include "indexer/feature.hpp"
#include "indexer/feature_data.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "indexer/scales.hpp"
#include "base/assert.hpp"
#include "base/checked_cast.hpp"
#include <algorithm>
#include <array>
namespace feature
{
using namespace std;
namespace
{
int CorrectScale(int scale)
{
CHECK_LESS_OR_EQUAL(scale, scales::GetUpperStyleScale(), ());
return min(scale, scales::GetUpperStyleScale());
}
} // namespace
void GetDrawRule(TypesHolder const & types, int level, drule::KeysT & keys)
{
ASSERT(keys.empty(), ());
Classificator const & c = classif();
auto const geomType = types.GetGeomType();
level = CorrectScale(level);
for (uint32_t t : types)
c.GetObject(t)->GetSuitable(level, geomType, keys);
}
void GetDrawRule(vector<uint32_t> const & types, int level, GeomType geomType, drule::KeysT & keys)
{
ASSERT(keys.empty(), ());
Classificator const & c = classif();
level = CorrectScale(level);
for (uint32_t t : types)
c.GetObject(t)->GetSuitable(level, geomType, keys);
}
void FilterRulesByRuntimeSelector(FeatureType & f, int zoomLevel, drule::KeysT & keys)
{
keys.erase_if([&f, zoomLevel](drule::Key const & key)
{
drule::BaseRule const * const rule = drule::rules().Find(key);
if (rule == nullptr)
return true;
return !rule->TestFeature(f, zoomLevel);
});
}
namespace
{
class IsDrawableRulesChecker
{
int m_scale;
GeomType m_geomType;
bool m_arr[4];
public:
IsDrawableRulesChecker(int scale, GeomType geomType, int rules) : m_scale(scale), m_geomType(geomType)
{
m_arr[0] = rules & RULE_CAPTION;
m_arr[1] = rules & RULE_PATH_TEXT;
m_arr[2] = rules & RULE_SYMBOL;
m_arr[3] = rules & RULE_LINE;
}
bool operator()(ClassifObject const * p) const
{
drule::KeysT keys;
p->GetSuitable(m_scale, m_geomType, keys);
for (auto const & k : keys)
{
if ((m_arr[0] && k.m_type == drule::caption) || (m_arr[1] && k.m_type == drule::pathtext) ||
(m_arr[2] && k.m_type == drule::symbol) || (m_arr[3] && k.m_type == drule::line))
{
return true;
}
}
return false;
}
};
/// @name Add here classifier types that don't have drawing rules but needed for algorithms.
/// @todo The difference between \a TypeAlwaysExists and \a IsUsefulNondrawableType is not
/// obvious. TypeAlwaysExists are *indexed* in category search, while IsUsefulNondrawableType are *not*.
/// The functions names and set of types looks strange now and definitely should be revised.
/// @{
/// These types will be included in geometry index for the corresponding scale (World or Country).
/// Needed for search and routing algorithms.
int GetNondrawableStandaloneIndexScale(uint32_t type, GeomType geomType = GeomType::Undefined)
{
auto const & cl = classif();
static uint32_t const shuttle = cl.GetTypeByPath({"route", "shuttle_train"});
if ((geomType == GeomType::Line || geomType == GeomType::Undefined) && type == shuttle)
return scales::GetUpperScale();
static uint32_t const region = cl.GetTypeByPath({"place", "region"});
if ((geomType == GeomType::Point || geomType == GeomType::Undefined) && type == region)
return scales::GetUpperWorldScale();
ftype::TruncValue(type, 1);
static uint32_t const addr = cl.GetTypeByPath({"addr:interpolation"});
if ((geomType == GeomType::Line || geomType == GeomType::Undefined) && type == addr)
return scales::GetUpperScale();
return -1;
}
bool IsUsefulStandaloneType(uint32_t type, GeomType geomType = GeomType::Undefined)
{
return GetNondrawableStandaloneIndexScale(type, geomType) >= 0;
}
bool TypeAlwaysExists(uint32_t type, GeomType geomType = GeomType::Undefined)
{
auto const & cl = classif();
if (!cl.IsTypeValid(type))
return false;
if (IsUsefulStandaloneType(type, geomType))
return true;
uint8_t const typeLevel = ftype::GetLevel(type);
ftype::TruncValue(type, 1);
if (geomType != GeomType::Line)
{
static uint32_t const arrTypes[] = {
cl.GetTypeByPath({"internet_access"}),
cl.GetTypeByPath({"toilets"}),
cl.GetTypeByPath({"drinking_water"}),
cl.GetTypeByPath({"lateral"}),
cl.GetTypeByPath({"cardinal"}),
};
if (base::IsExist(arrTypes, type))
return true;
// Exclude generic 1-arity types like [organic].
if (typeLevel >= 2)
{
static uint32_t const arrTypes[] = {
cl.GetTypeByPath({"organic"}),
cl.GetTypeByPath({"recycling"}),
cl.GetTypeByPath({"wheelchair"}),
};
if (base::IsExist(arrTypes, type))
return true;
}
}
static uint32_t const complexEntry = cl.GetTypeByPath({"complex_entry"});
return (type == complexEntry);
}
bool IsUsefulNondrawableType(uint32_t type, GeomType geomType = GeomType::Undefined)
{
auto const & cl = classif();
if (!cl.IsTypeValid(type))
return false;
if (TypeAlwaysExists(type, geomType))
return true;
// Exclude generic 1-arity types like [wheelchair].
if (ftype::GetLevel(type) < 2)
return false;
static uint32_t const hwtag = cl.GetTypeByPath({"hwtag"});
static uint32_t const psurface = cl.GetTypeByPath({"psurface"});
/// @todo "roundabout" type itself has caption drawing rules (for point junctions?).
if ((geomType == GeomType::Line || geomType == GeomType::Undefined) && ftypes::IsRoundAboutChecker::Instance()(type))
return true;
ftype::TruncValue(type, 1);
if (geomType == GeomType::Line || geomType == GeomType::Undefined)
{
if (type == hwtag || type == psurface)
return true;
}
static uint32_t const arrTypes[] = {
cl.GetTypeByPath({"cuisine"}),
cl.GetTypeByPath({"fee"}),
};
return base::IsExist(arrTypes, type);
}
/// @}
} // namespace
bool IsCategoryNondrawableType(uint32_t type)
{
return TypeAlwaysExists(type);
}
bool IsUsefulType(uint32_t type)
{
return IsUsefulNondrawableType(type) || classif().GetObject(type)->IsDrawableAny();
}
bool CanGenerateLike(vector<uint32_t> const & types, GeomType geomType)
{
Classificator const & c = classif();
for (uint32_t t : types)
if (IsUsefulStandaloneType(t, geomType) || c.GetObject(t)->IsDrawableLike(geomType, false /* emptyName */))
return true;
return false;
}
namespace
{
bool IsDrawableForIndexGeometryOnly(TypesHolder const & types, m2::RectD const & limitRect, int level)
{
Classificator const & c = classif();
static uint32_t const buildingPartType = c.GetTypeByPath({"building:part"});
// Exclude too small area features unless it's a part of a coast or a building.
if (types.GetGeomType() == GeomType::Area && !types.Has(c.GetCoastType()) && !types.Has(buildingPartType) &&
!scales::IsGoodForLevel(level, limitRect))
return false;
return true;
}
bool IsDrawableForIndexClassifOnly(TypesHolder const & types, int level)
{
Classificator const & c = classif();
for (uint32_t t : types)
{
if (c.GetObject(t)->IsDrawable(level))
return true;
if (level == GetNondrawableStandaloneIndexScale(t))
return true;
}
return false;
}
} // namespace
bool IsDrawableForIndex(FeatureType & ft, int level)
{
return IsDrawableForIndexGeometryOnly(ft, level) && IsDrawableForIndexClassifOnly(TypesHolder(ft), level);
}
bool IsDrawableForIndex(TypesHolder const & types, m2::RectD const & limitRect, int level)
{
return IsDrawableForIndexGeometryOnly(types, limitRect, level) && IsDrawableForIndexClassifOnly(types, level);
}
bool IsDrawableForIndexGeometryOnly(FeatureType & ft, int level)
{
return IsDrawableForIndexGeometryOnly(TypesHolder(ft), ft.GetLimitRectChecked(), level);
}
bool IsUsefulType(uint32_t t, GeomType geomType, bool emptyName)
{
if (IsUsefulNondrawableType(t, geomType))
return true;
ClassifObject const * obj = classif().GetObject(t);
CHECK(obj, ());
if (obj->IsDrawableLike(geomType, emptyName))
return true;
// IsDrawableLike checks only unique area styles, so we need to take into account point styles too.
if (geomType == GeomType::Area)
{
if (obj->IsDrawableLike(GeomType::Point, emptyName))
return true;
}
return false;
}
bool RemoveUselessTypes(vector<uint32_t> & types, GeomType geomType, bool emptyName)
{
base::EraseIf(types, [&](uint32_t t) { return !IsUsefulType(t, geomType, emptyName); });
return !types.empty();
}
int GetMinDrawableScale(FeatureType & ft)
{
return GetMinDrawableScale(TypesHolder(ft), ft.GetLimitRectChecked());
}
int GetMinDrawableScale(TypesHolder const & types, m2::RectD const & limitRect)
{
int constexpr upBound = scales::GetUpperStyleScale();
for (int level = 0; level <= upBound; ++level)
if (IsDrawableForIndex(types, limitRect, level))
return level;
return -1;
}
/*
int GetMinDrawableScaleGeometryOnly(TypesHolder const & types, m2::RectD const & limitRect)
{
int constexpr upBound = scales::GetUpperStyleScale();
for (int level = 0; level <= upBound; ++level)
{
if (IsDrawableForIndexGeometryOnly(types, limitRect, level))
return level;
}
return -1;
}
*/
int GetMinDrawableScaleClassifOnly(TypesHolder const & types)
{
int constexpr upBound = scales::GetUpperStyleScale();
for (int level = 0; level <= upBound; ++level)
if (IsDrawableForIndexClassifOnly(types, level))
return level;
return -1;
}
namespace
{
void AddRange(pair<int, int> & dest, pair<int, int> const & src)
{
if (src.first != -1)
{
ASSERT_GREATER(src.first, -1, ());
ASSERT_GREATER(src.second, -1, ());
dest.first = min(dest.first, src.first);
dest.second = max(dest.second, src.second);
ASSERT_GREATER(dest.first, -1, ());
ASSERT_GREATER(dest.second, -1, ());
}
}
pair<int, int> kInvalidScalesRange(-1, -1);
} // namespace
pair<int, int> GetDrawableScaleRange(uint32_t type)
{
auto const res = classif().GetObject(type)->GetDrawScaleRange();
return (res.first > res.second ? kInvalidScalesRange : res);
}
pair<int, int> GetDrawableScaleRange(TypesHolder const & types)
{
pair<int, int> res(1000, -1000);
for (uint32_t t : types)
AddRange(res, GetDrawableScaleRange(t));
return (res.first > res.second ? kInvalidScalesRange : res);
}
bool IsVisibleInRange(uint32_t type, pair<int, int> const & scaleRange)
{
CHECK_LESS_OR_EQUAL(scaleRange.first, scaleRange.second, (scaleRange));
if (TypeAlwaysExists(type))
return true;
ClassifObject const * obj = classif().GetObject(type);
CHECK(obj, ());
for (int scale = scaleRange.first; scale <= scaleRange.second; ++scale)
if (obj->IsDrawable(scale))
return true;
return false;
}
namespace
{
bool IsDrawableForRules(TypesHolder const & types, int level, int rules)
{
Classificator const & c = classif();
IsDrawableRulesChecker doCheck(level, types.GetGeomType(), rules);
for (uint32_t t : types)
if (doCheck(c.GetObject(t)))
return true;
return false;
}
} // namespace
pair<int, int> GetDrawableScaleRangeForRules(TypesHolder const & types, int rules)
{
int constexpr upBound = scales::GetUpperStyleScale();
int lowL = -1;
for (int level = 0; level <= upBound; ++level)
{
if (IsDrawableForRules(types, level, rules))
{
lowL = level;
break;
}
}
if (lowL == -1)
return kInvalidScalesRange;
int highL = lowL;
for (int level = upBound; level > lowL; --level)
{
if (IsDrawableForRules(types, level, rules))
{
highL = level;
break;
}
}
return {lowL, highL};
}
TypeSetChecker::TypeSetChecker(initializer_list<char const *> const & lst)
{
m_type = classif().GetTypeByPath(lst);
m_level = base::checked_cast<uint8_t>(lst.size());
}
bool TypeSetChecker::IsEqual(uint32_t type) const
{
ftype::TruncValue(type, m_level);
return (m_type == type);
}
} // namespace feature

View file

@ -0,0 +1,90 @@
#pragma once
#include "indexer/drawing_rule_def.hpp"
#include "indexer/feature_decl.hpp"
#include <initializer_list>
#include <utility>
#include <vector>
class FeatureType;
namespace feature
{
class TypesHolder;
bool IsCategoryNondrawableType(uint32_t type);
bool IsUsefulType(uint32_t type);
bool IsDrawableForIndex(FeatureType & ft, int level);
bool IsDrawableForIndex(TypesHolder const & types, m2::RectD const & limitRect, int level);
// The separation into ClassifOnly and GeometryOnly versions is needed to speed up
// the geometrical index (see indexer/scale_index_builder.hpp).
// Technically, the GeometryOnly version uses the classificator, but it only does
// so when checking against coastlines.
// bool IsDrawableForIndexClassifOnly(TypesHolder const & types, int level);
bool IsDrawableForIndexGeometryOnly(FeatureType & ft, int level);
// bool IsDrawableForIndexGeometryOnly(TypesHolder const & types, m2::RectD const & limitRect, int level);
/// @name Generator check functions.
/// @{
/// Can object with \a types can be generated as \a geomType Feature.
/// Should have appropriate drawing rules or satisfy "IsUsefulStandaloneType".
bool CanGenerateLike(std::vector<uint32_t> const & types, GeomType geomType);
/// @return true, if at least one valid type remains.
bool RemoveUselessTypes(std::vector<uint32_t> & types, GeomType geomType, bool emptyName = false);
/// @}
int GetMinDrawableScale(FeatureType & ft);
int GetMinDrawableScale(TypesHolder const & types, m2::RectD const & limitRect);
// int GetMinDrawableScaleGeometryOnly(TypesHolder const & types, m2::RectD limitRect);
int GetMinDrawableScaleClassifOnly(TypesHolder const & types);
/// @return [-1, -1] if range is not drawable
/// @{
/// @name Get scale range when feature is visible.
std::pair<int, int> GetDrawableScaleRange(uint32_t type);
std::pair<int, int> GetDrawableScaleRange(TypesHolder const & types);
bool IsVisibleInRange(uint32_t type, std::pair<int, int> const & scaleRange);
/// @name Get scale range when feature's text or symbol is visible.
enum
{
RULE_CAPTION = 1,
RULE_PATH_TEXT = 2,
RULE_ANY_TEXT = RULE_CAPTION | RULE_PATH_TEXT,
RULE_SYMBOL = 4,
RULE_LINE = 8,
};
std::pair<int, int> GetDrawableScaleRangeForRules(TypesHolder const & types, int rules);
/// @}
void GetDrawRule(TypesHolder const & types, int level, drule::KeysT & keys);
void GetDrawRule(std::vector<uint32_t> const & types, int level, GeomType geomType, drule::KeysT & keys);
void FilterRulesByRuntimeSelector(FeatureType & f, int zoomLevel, drule::KeysT & keys);
/// Used to check whether user types belong to particular classificator set.
class TypeSetChecker
{
uint32_t m_type;
uint8_t m_level;
public:
explicit TypeSetChecker(std::initializer_list<char const *> const & lst);
bool IsEqual(uint32_t type) const;
template <class IterT>
bool IsEqualR(IterT beg, IterT end) const
{
while (beg != end)
if (IsEqual(*beg++))
return true;
return false;
}
bool IsEqualV(std::vector<uint32_t> const & v) const { return IsEqualR(v.begin(), v.end()); }
};
} // namespace feature

View file

@ -0,0 +1,138 @@
#include "indexer/features_offsets_table.hpp"
#include "indexer/features_vector.hpp"
#include "platform/platform.hpp"
#include "coding/files_container.hpp"
#include "coding/internal/file_data.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/scope_guard.hpp"
namespace feature
{
using namespace platform;
using namespace std;
void FeaturesOffsetsTable::Builder::PushOffset(uint32_t const offset)
{
ASSERT(m_offsets.empty() || m_offsets.back() < offset, ());
m_offsets.push_back(offset);
}
FeaturesOffsetsTable::FeaturesOffsetsTable(succinct::elias_fano::elias_fano_builder & builder) : m_table(&builder) {}
FeaturesOffsetsTable::FeaturesOffsetsTable(string const & filePath)
{
m_pReader.reset(new MmapReader(filePath));
succinct::mapper::map(m_table, reinterpret_cast<char const *>(m_pReader->Data()));
}
// static
unique_ptr<FeaturesOffsetsTable> FeaturesOffsetsTable::Build(Builder & builder)
{
vector<uint32_t> const & offsets = builder.m_offsets;
size_t const numOffsets = offsets.size();
uint32_t const maxOffset = offsets.empty() ? 0 : offsets.back();
succinct::elias_fano::elias_fano_builder elias_fano_builder(maxOffset, numOffsets);
for (uint32_t offset : offsets)
elias_fano_builder.push_back(offset);
return unique_ptr<FeaturesOffsetsTable>(new FeaturesOffsetsTable(elias_fano_builder));
}
// static
unique_ptr<FeaturesOffsetsTable> FeaturesOffsetsTable::LoadImpl(string const & filePath)
{
return unique_ptr<FeaturesOffsetsTable>(new FeaturesOffsetsTable(filePath));
}
// static
unique_ptr<FeaturesOffsetsTable> FeaturesOffsetsTable::Load(string const & filePath)
{
if (!GetPlatform().IsFileExistsByFullPath(filePath))
return unique_ptr<FeaturesOffsetsTable>();
return LoadImpl(filePath);
}
// static
unique_ptr<FeaturesOffsetsTable> FeaturesOffsetsTable::Load(FilesContainerR const & cont, std::string const & tag)
{
unique_ptr<FeaturesOffsetsTable> table(new FeaturesOffsetsTable());
table->m_file.Open(cont.GetFileName());
auto p = cont.GetAbsoluteOffsetAndSize(tag);
ASSERT(p.first % 4 == 0, (p.first)); // will get troubles in succinct otherwise
table->m_handle.Assign(table->m_file.Map(p.first, p.second, tag));
succinct::mapper::map(table->m_table, table->m_handle.GetData<char>());
return table;
}
void FeaturesOffsetsTable::Build(FilesContainerR const & cont, string const & storePath)
{
Builder builder;
FeaturesVector::ForEachOffset(cont, [&builder](uint32_t offset) { builder.PushOffset(offset); });
Build(builder)->Save(storePath);
}
void FeaturesOffsetsTable::Save(string const & filePath)
{
LOG(LINFO, ("Saving features offsets table to ", filePath));
string const fileNameTmp = filePath + EXTENSION_TMP;
succinct::mapper::freeze(m_table, fileNameTmp.c_str());
base::RenameFileX(fileNameTmp, filePath);
}
uint32_t FeaturesOffsetsTable::GetFeatureOffset(size_t index) const
{
ASSERT_LESS(index, size(), ("Index out of bounds", index, size()));
return static_cast<uint32_t>(m_table.select(index));
}
size_t FeaturesOffsetsTable::GetFeatureIndexbyOffset(uint32_t offset) const
{
ASSERT_GREATER(size(), 0, ("We must not ask empty table"));
ASSERT_LESS_OR_EQUAL(offset, m_table.select(size() - 1),
("Offset out of bounds", offset, m_table.select(size() - 1)));
ASSERT_GREATER_OR_EQUAL(offset, m_table.select(0), ("Offset out of bounds", offset, m_table.select(size() - 1)));
// Binary search in elias_fano list
size_t leftBound = 0, rightBound = size();
while (leftBound + 1 < rightBound)
{
size_t middle = leftBound + (rightBound - leftBound) / 2;
if (m_table.select(middle) <= offset)
leftBound = middle;
else
rightBound = middle;
}
ASSERT_EQUAL(offset, m_table.select(leftBound), ("Can't find offset", offset, "in the table"));
return leftBound;
}
bool BuildOffsetsTable(string const & filePath)
{
try
{
string const destPath = filePath + TMP_OFFSETS_EXT;
SCOPE_GUARD(fileDeleter, bind(FileWriter::DeleteFileX, destPath));
{
FilesContainerR cont(filePath);
feature::FeaturesOffsetsTable::Build(cont, destPath);
}
FilesContainerW(filePath, FileWriter::OP_WRITE_EXISTING).Write(destPath, FEATURE_OFFSETS_FILE_TAG);
return true;
}
catch (RootException const & ex)
{
LOG(LERROR, ("Generating offsets table failed for", filePath, "reason", ex.Msg()));
return false;
}
}
} // namespace feature

View file

@ -0,0 +1,113 @@
#pragma once
#include "coding/files_container.hpp"
#include "coding/mmap_reader.hpp"
#include "defines.hpp"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field"
#endif
#include "3party/succinct/elias_fano.hpp"
#include "3party/succinct/mapper.hpp"
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
namespace platform
{
class LocalCountryFile;
}
namespace feature
{
/// This class is a wrapper around elias-fano encoder, which allows
/// to efficiently encode a sequence of strictly increasing features
/// offsets in a MWM file and access them by feature's index.
class FeaturesOffsetsTable
{
public:
/// This class is used to accumulate strictly increasing features
/// offsets and then build FeaturesOffsetsTable.
class Builder
{
public:
/// Adds offset to the end of the sequence of already
/// accumulated offsets. Note that offset must be strictly
/// greater than all previously added offsets.
///
/// \param offset a feature's offset in a MWM file
void PushOffset(uint32_t offset);
/// \return number of already accumulated offsets
size_t size() const { return m_offsets.size(); }
private:
friend class FeaturesOffsetsTable;
std::vector<uint32_t> m_offsets;
};
/// Builds FeaturesOffsetsTable from the strictly increasing
/// sequence of file offsets.
///
/// \param builder Builder containing sequence of offsets.
/// \return a pointer to an instance of FeaturesOffsetsTable
static std::unique_ptr<FeaturesOffsetsTable> Build(Builder & builder);
/// Load table by full path to the table file.
static std::unique_ptr<FeaturesOffsetsTable> Load(std::string const & filePath);
static std::unique_ptr<FeaturesOffsetsTable> Load(FilesContainerR const & cont, std::string const & tag);
static void Build(FilesContainerR const & cont, std::string const & storePath);
FeaturesOffsetsTable(FeaturesOffsetsTable const &) = delete;
FeaturesOffsetsTable const & operator=(FeaturesOffsetsTable const &) = delete;
/// Serializes current instance to a section in container.
///
/// \param filePath a full path of the file to store data
void Save(std::string const & filePath);
/// \param index index of a feature
/// \return offset a feature
uint32_t GetFeatureOffset(size_t index) const;
/// \param offset offset of a feature
/// \return index of a feature
size_t GetFeatureIndexbyOffset(uint32_t offset) const;
/// \return number of features offsets in a table.
size_t size() const { return static_cast<size_t>(m_table.num_ones()); }
/// \return byte size of a table, may be slightly different from a
/// real byte size in memory or on disk due to alignment, but
/// can be used in benchmarks, logging, etc.
// size_t byte_size() { return static_cast<size_t>(succinct::mapper::size_of(m_table)); }
private:
FeaturesOffsetsTable(succinct::elias_fano::elias_fano_builder & builder);
FeaturesOffsetsTable(std::string const & filePath);
FeaturesOffsetsTable() = default;
static std::unique_ptr<FeaturesOffsetsTable> LoadImpl(std::string const & filePath);
succinct::elias_fano m_table;
std::unique_ptr<MmapReader> m_pReader;
::detail::MappedFile m_file;
::detail::MappedFile::Handle m_handle;
};
// Builds feature offsets table in an mwm or rebuilds an existing
// one.
bool BuildOffsetsTable(std::string const & filePath);
} // namespace feature

View file

@ -0,0 +1,64 @@
#include "features_vector.hpp"
#include "dat_section_header.hpp"
#include "features_offsets_table.hpp"
#include "platform/constants.hpp"
FeaturesVector::FeaturesVector(FilesContainerR const & cont, feature::DataHeader const & header,
feature::FeaturesOffsetsTable const * ftTable,
feature::FeaturesOffsetsTable const * relTable,
indexer::MetadataDeserializer * metaDeserializer)
: m_loadInfo(cont, header, relTable, metaDeserializer)
, m_table(ftTable)
{
InitRecordsReader();
}
void FeaturesVector::InitRecordsReader()
{
FilesContainerR::TReader reader = m_loadInfo.GetDataReader();
ReaderSource src(reader);
feature::DatSectionHeader header;
header.Read(src);
m_loadInfo.m_version = header.m_version;
m_recordReader = std::make_unique<RecordReader>(reader.SubReader(header.m_featuresOffset, header.m_featuresSize));
}
std::unique_ptr<FeatureType> FeaturesVector::GetByIndex(uint32_t index) const
{
auto const ftOffset = m_table ? m_table->GetFeatureOffset(index) : index;
return std::make_unique<FeatureType>(&m_loadInfo, m_recordReader->ReadRecord(ftOffset));
}
size_t FeaturesVector::GetNumFeatures() const
{
return m_table ? m_table->size() : 0;
}
FeaturesVectorTest::FeaturesVectorTest(std::string const & filePath)
: FeaturesVectorTest((FilesContainerR(filePath, READER_CHUNK_LOG_SIZE, READER_CHUNK_LOG_COUNT)))
{}
FeaturesVectorTest::FeaturesVectorTest(FilesContainerR const & cont)
: m_cont(cont)
, m_header(m_cont)
, m_vector(m_cont, m_header, nullptr, nullptr, nullptr)
{
m_vector.m_table = feature::FeaturesOffsetsTable::Load(m_cont, FEATURE_OFFSETS_FILE_TAG).release();
if (m_cont.IsExist(RELATION_OFFSETS_FILE_TAG))
m_vector.m_loadInfo.m_relTable = feature::FeaturesOffsetsTable::Load(m_cont, RELATION_OFFSETS_FILE_TAG).release();
if (m_cont.IsExist(METADATA_FILE_TAG))
m_vector.m_loadInfo.m_metaDeserializer = indexer::MetadataDeserializer::Load(m_cont).release();
}
FeaturesVectorTest::~FeaturesVectorTest()
{
delete m_vector.m_table;
delete m_vector.m_loadInfo.m_metaDeserializer;
delete m_vector.m_loadInfo.m_relTable;
}

View file

@ -0,0 +1,93 @@
#pragma once
#include "indexer/feature.hpp"
#include "indexer/shared_load_info.hpp"
#include "coding/var_record_reader.hpp"
#include <memory>
#include <vector>
namespace feature
{
class FeaturesOffsetsTable;
}
/// Note! This class is NOT Thread-Safe.
/// You should have separate instance of Vector for every thread.
class FeaturesVector
{
DISALLOW_COPY(FeaturesVector);
public:
FeaturesVector(FilesContainerR const & cont, feature::DataHeader const & header,
feature::FeaturesOffsetsTable const * ftTable, feature::FeaturesOffsetsTable const * relTable,
indexer::MetadataDeserializer * metaDeserializer);
std::unique_ptr<FeatureType> GetByIndex(uint32_t index) const;
size_t GetNumFeatures() const;
template <class ToDo>
void ForEach(ToDo && toDo) const
{
uint32_t index = 0;
m_recordReader->ForEachRecord([&](uint32_t pos, std::vector<uint8_t> && data)
{
FeatureType ft(&m_loadInfo, std::move(data));
// We can't properly set MwmId here, because FeaturesVector
// works with FileContainerR, not with MwmId/MwmHandle/MwmValue.
// But it's OK to set at least feature's index, because it can
// be used later for Metadata loading.
ft.SetID(FeatureID(MwmSet::MwmId(), index));
toDo(ft, m_table ? index++ : pos);
});
}
template <class ToDo>
static void ForEachOffset(FilesContainerR const & cont, ToDo && toDo)
{
feature::DataHeader header(cont);
FeaturesVector vec(cont, header);
vec.m_recordReader->ForEachRecord([&](uint32_t pos, std::vector<uint8_t> && /* data */) { toDo(pos); });
}
private:
/// Actually, this ctor is needed only for ForEachOffset call.
/// Didn't find a better solution without big refactoring.
FeaturesVector(FilesContainerR const & cont, feature::DataHeader const & header)
: m_loadInfo(cont, header, nullptr, nullptr)
{
InitRecordsReader();
}
void InitRecordsReader();
friend class FeaturesVectorTest;
using RecordReader = VarRecordReader<FilesContainerR::TReader>;
feature::SharedLoadInfo m_loadInfo;
std::unique_ptr<RecordReader> m_recordReader;
feature::FeaturesOffsetsTable const * m_table;
};
/// Test features vector (reader) that combines all the needed data for stand-alone work.
/// Used in generator_tool and unit tests.
class FeaturesVectorTest
{
DISALLOW_COPY_AND_MOVE(FeaturesVectorTest);
FilesContainerR m_cont;
feature::DataHeader m_header;
FeaturesVector m_vector;
public:
explicit FeaturesVectorTest(std::string const & filePath);
explicit FeaturesVectorTest(FilesContainerR const & cont);
~FeaturesVectorTest();
FilesContainerR const & GetContainer() const { return m_cont; }
feature::DataHeader const & GetHeader() const { return m_header; }
FeaturesVector const & GetVector() const { return m_vector; }
};

124
libs/indexer/ftraits.hpp Normal file
View file

@ -0,0 +1,124 @@
#pragma once
#include "indexer/feature_data.hpp"
#include "indexer/ftypes_mapping.hpp"
#include "coding/csv_reader.hpp"
#include "platform/platform.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <array>
#include <cstdint>
#include <initializer_list>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
namespace ftraits
{
template <typename Base, typename Value>
class TraitsBase
{
public:
static std::optional<Value> GetValue(feature::TypesHolder const & types)
{
auto const & instance = Instance();
auto const it = Find(types);
if (!instance.m_matcher.IsValid(it))
return std::nullopt;
return it->second;
}
static std::optional<uint32_t> GetType(feature::TypesHolder const & types)
{
auto const & instance = Instance();
auto const it = Find(types);
if (!instance.m_matcher.IsValid(it))
return std::nullopt;
return it->first;
}
private:
using ConstIterator = typename ftypes::HashMapMatcher<uint32_t, Value>::ConstIterator;
static ConstIterator Find(feature::TypesHolder const & types)
{
auto const & instance = Instance();
auto const excluded = instance.m_excluded.Find(types);
if (instance.m_excluded.IsValid(excluded))
return instance.m_matcher.End();
return instance.m_matcher.Find(types);
}
protected:
static TraitsBase const & Instance()
{
static Base instance;
return instance;
}
ftypes::HashMapMatcher<uint32_t, Value> m_matcher;
ftypes::HashSetMatcher<uint32_t> m_excluded;
};
enum class WheelchairAvailability
{
No,
Yes,
Limited,
};
inline std::string DebugPrint(WheelchairAvailability wheelchair)
{
switch (wheelchair)
{
case WheelchairAvailability::No: return "No";
case WheelchairAvailability::Yes: return "Yes";
case WheelchairAvailability::Limited: return "Limited";
}
UNREACHABLE();
}
class Wheelchair : public TraitsBase<Wheelchair, WheelchairAvailability>
{
friend TraitsBase;
using TypesInitializer = std::initializer_list<std::initializer_list<char const *>>;
Wheelchair()
{
m_matcher.Append<TypesInitializer>({{"wheelchair", "no"}}, WheelchairAvailability::No);
m_matcher.Append<TypesInitializer>({{"wheelchair", "yes"}}, WheelchairAvailability::Yes);
m_matcher.Append<TypesInitializer>({{"wheelchair", "limited"}}, WheelchairAvailability::Limited);
}
};
enum class DrinkingWaterAvailability
{
No,
Yes,
};
class DrinkingWater : public TraitsBase<DrinkingWater, DrinkingWaterAvailability>
{
friend TraitsBase;
using TypesInitializer = std::initializer_list<std::initializer_list<char const *>>;
DrinkingWater()
{
m_matcher.Append<TypesInitializer>({{"drinking_water", "no"}}, DrinkingWaterAvailability::No);
m_matcher.Append<TypesInitializer>({{"drinking_water", "yes"}}, DrinkingWaterAvailability::Yes);
m_matcher.Append<TypesInitializer>({{"amenity", "drinking_water"}}, DrinkingWaterAvailability::Yes);
}
};
} // namespace ftraits

View file

@ -0,0 +1,63 @@
#pragma once
#include "indexer/classificator.hpp"
#include "indexer/feature_data.hpp"
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace ftypes
{
template <typename Container>
class Matcher
{
public:
using ConstIterator = typename Container::const_iterator;
ConstIterator Find(feature::TypesHolder const & types) const
{
for (auto const t : types)
{
for (auto level = ftype::GetLevel(t); level; --level)
{
auto const it = m_mapping.find(ftype::Trunc(t, level));
if (it != m_mapping.cend())
return it;
}
}
return m_mapping.cend();
}
ConstIterator End() const { return m_mapping.cend(); }
bool IsValid(ConstIterator it) const { return it != m_mapping.cend(); }
bool Contains(feature::TypesHolder const & types) const { return IsValid(Find(types)); }
template <typename Type, typename... Args>
void AppendType(Type && type, Args &&... args)
{
m_mapping.emplace(classif().GetTypeByPath(std::forward<Type>(type)), std::forward<Args>(args)...);
}
template <typename TypesPaths, typename... Args>
void Append(TypesPaths && types, Args &&... args)
{
// We mustn't forward args in the loop below because it will be forwarded at first iteration.
for (auto const & type : types)
AppendType(type, args...);
}
private:
Container m_mapping;
};
template <typename Key, typename Value>
using HashMapMatcher = Matcher<std::unordered_map<Key, Value>>;
template <typename Key>
using HashSetMatcher = Matcher<std::unordered_set<Key>>;
} // namespace ftypes

View file

@ -0,0 +1,936 @@
#include "indexer/ftypes_matcher.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature.hpp"
#include "base/assert.hpp"
#include "base/stl_helpers.hpp"
#include <algorithm>
#include <cmath>
#include <functional>
#include <iterator>
#include <map>
#include <sstream>
namespace ftypes
{
using namespace std;
namespace
{
class HighwayClasses
{
map<uint32_t, HighwayClass> m_map;
public:
HighwayClasses()
{
auto const & c = classif();
m_map[c.GetTypeByPath({"route", "ferry"})] = HighwayClass::Transported;
m_map[c.GetTypeByPath({"route", "shuttle_train"})] = HighwayClass::Transported;
m_map[c.GetTypeByPath({"highway", "motorway"})] = HighwayClass::Motorway;
m_map[c.GetTypeByPath({"highway", "motorway_link"})] = HighwayClass::Motorway;
m_map[c.GetTypeByPath({"highway", "trunk"})] = HighwayClass::Trunk;
m_map[c.GetTypeByPath({"highway", "trunk_link"})] = HighwayClass::Trunk;
m_map[c.GetTypeByPath({"highway", "primary"})] = HighwayClass::Primary;
m_map[c.GetTypeByPath({"highway", "primary_link"})] = HighwayClass::Primary;
m_map[c.GetTypeByPath({"highway", "secondary"})] = HighwayClass::Secondary;
m_map[c.GetTypeByPath({"highway", "secondary_link"})] = HighwayClass::Secondary;
m_map[c.GetTypeByPath({"highway", "tertiary"})] = HighwayClass::Tertiary;
m_map[c.GetTypeByPath({"highway", "tertiary_link"})] = HighwayClass::Tertiary;
m_map[c.GetTypeByPath({"highway", "unclassified"})] = HighwayClass::LivingStreet;
m_map[c.GetTypeByPath({"highway", "residential"})] = HighwayClass::LivingStreet;
m_map[c.GetTypeByPath({"highway", "living_street"})] = HighwayClass::LivingStreet;
m_map[c.GetTypeByPath({"highway", "road"})] = HighwayClass::LivingStreet;
m_map[c.GetTypeByPath({"highway", "service"})] = HighwayClass::Service;
m_map[c.GetTypeByPath({"highway", "track"})] = HighwayClass::Service;
m_map[c.GetTypeByPath({"highway", "busway"})] = HighwayClass::Service;
m_map[c.GetTypeByPath({"man_made", "pier"})] = HighwayClass::Service;
// 3-level types.
m_map[c.GetTypeByPath({"highway", "service", "driveway"})] = HighwayClass::ServiceMinor;
m_map[c.GetTypeByPath({"highway", "service", "parking_aisle"})] = HighwayClass::ServiceMinor;
m_map[c.GetTypeByPath({"highway", "pedestrian"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "footway"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "bridleway"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "ladder"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "steps"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "cycleway"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "path"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "construction"})] = HighwayClass::Pedestrian;
}
HighwayClass Get(uint32_t t) const
{
auto const it = m_map.find(t);
if (it == m_map.cend())
return HighwayClass::Undefined;
return it->second;
}
};
char const * HighwayClassToString(HighwayClass const cls)
{
switch (cls)
{
case HighwayClass::Undefined: return "Undefined";
case HighwayClass::Transported: return "Transported";
case HighwayClass::Motorway: return "Motorway";
case HighwayClass::Trunk: return "Trunk";
case HighwayClass::Primary: return "Primary";
case HighwayClass::Secondary: return "Secondary";
case HighwayClass::Tertiary: return "Tertiary";
case HighwayClass::LivingStreet: return "LivingStreet";
case HighwayClass::Service: return "Service";
case HighwayClass::ServiceMinor: return "ServiceMinor";
case HighwayClass::Pedestrian: return "Pedestrian";
case HighwayClass::Count: return "Count";
}
ASSERT(false, ());
return "";
}
} // namespace
string DebugPrint(HighwayClass const cls)
{
stringstream out;
out << "[ " << HighwayClassToString(cls) << " ]";
return out.str();
}
string DebugPrint(LocalityType const localityType)
{
switch (localityType)
{
case LocalityType::None: return "None";
case LocalityType::Country: return "Country";
case LocalityType::State: return "State";
case LocalityType::City: return "City";
case LocalityType::Town: return "Town";
case LocalityType::Village: return "Village";
case LocalityType::Count: return "Count";
}
UNREACHABLE();
}
HighwayClass GetHighwayClass(feature::TypesHolder const & types)
{
static HighwayClasses highwayClasses;
for (auto t : types)
{
for (uint8_t level : {3, 2})
{
ftype::TruncValue(t, level);
HighwayClass const hc = highwayClasses.Get(t);
if (hc != HighwayClass::Undefined)
return hc;
}
}
return HighwayClass::Undefined;
}
uint32_t BaseChecker::PrepareToMatch(uint32_t type, uint8_t level)
{
ftype::TruncValue(type, level);
return type;
}
bool BaseChecker::IsMatched(uint32_t type) const
{
return base::IsExist(m_types, PrepareToMatch(type, m_level));
}
void BaseChecker::ForEachType(function<void(uint32_t)> const & fn) const
{
for (auto const & t : m_types)
fn(t);
}
bool BaseChecker::operator()(feature::TypesHolder const & types) const
{
for (uint32_t t : types)
if (IsMatched(t))
return true;
return false;
}
bool BaseChecker::operator()(FeatureType & ft) const
{
return this->operator()(feature::TypesHolder(ft));
}
bool BaseChecker::operator()(vector<uint32_t> const & types) const
{
for (uint32_t t : types)
if (IsMatched(t))
return true;
return false;
}
IsPeakChecker::IsPeakChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"natural", "peak"}));
}
IsATMChecker::IsATMChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"amenity", "atm"}));
}
IsSpeedCamChecker::IsSpeedCamChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"highway", "speed_camera"}));
}
IsPostBoxChecker::IsPostBoxChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"amenity", "post_box"}));
}
IsPostPoiChecker::IsPostPoiChecker()
{
Classificator const & c = classif();
for (char const * val : {"post_office", "post_box", "parcel_locker"})
m_types.push_back(c.GetTypeByPath({"amenity", val}));
}
IsOperatorOthersPoiChecker::IsOperatorOthersPoiChecker()
{
Classificator const & c = classif();
for (char const * val :
{"bicycle_rental", "bureau_de_change", "car_sharing", "car_rental", "fuel", "charging_station", "parking",
"motorcycle_parking", "bicycle_parking", "payment_terminal", "university", "vending_machine"})
m_types.push_back(c.GetTypeByPath({"amenity", val}));
}
IsRecyclingCentreChecker::IsRecyclingCentreChecker() : BaseChecker(3 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"amenity", "recycling", "centre"}));
}
uint32_t IsRecyclingCentreChecker::GetType() const
{
return m_types[0];
}
IsRecyclingContainerChecker::IsRecyclingContainerChecker() : BaseChecker(3 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"amenity", "recycling", "container"}));
// Treat default type also as a container, see https://taginfo.openstreetmap.org/keys/recycling_type#values
m_types.push_back(c.GetTypeByPath({"amenity", "recycling"}));
}
uint32_t IsRecyclingContainerChecker::GetType() const
{
return m_types[0];
}
IsRailwayStationChecker::IsRailwayStationChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"railway", "station"}));
m_types.push_back(c.GetTypeByPath({"building", "train_station"}));
}
IsSubwayStationChecker::IsSubwayStationChecker() : BaseChecker(3 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"railway", "station", "subway"}));
}
IsAirportChecker::IsAirportChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"aeroway", "aerodrome"}));
}
IsSquareChecker::IsSquareChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"place", "square"}));
}
IsSuburbChecker::IsSuburbChecker()
{
Classificator const & c = classif();
base::StringIL const types[] = {
{"landuse", "residential"}, {"place", "neighbourhood"}, {"place", "quarter"}, {"place", "suburb"}};
for (auto const & e : types)
m_types.push_back(c.GetTypeByPath(e));
// `types` order should match next indices.
static_assert(static_cast<size_t>(SuburbType::Residential) == 0, "");
static_assert(static_cast<size_t>(SuburbType::Neighbourhood) == 1, "");
static_assert(static_cast<size_t>(SuburbType::Quarter) == 2, "");
static_assert(static_cast<size_t>(SuburbType::Suburb) == 3, "");
}
SuburbType IsSuburbChecker::GetType(uint32_t t) const
{
ftype::TruncValue(t, 2);
for (size_t i = 0; i < m_types.size(); ++i)
if (m_types[i] == t)
return static_cast<SuburbType>(i);
return SuburbType::Count;
}
SuburbType IsSuburbChecker::GetType(feature::TypesHolder const & types) const
{
auto smallestType = SuburbType::Count;
for (uint32_t t : types)
{
auto const type = GetType(t);
if (type < smallestType)
smallestType = type;
}
return smallestType;
}
SuburbType IsSuburbChecker::GetType(FeatureType & f) const
{
feature::TypesHolder types(f);
return GetType(types);
}
IsWayChecker::IsWayChecker()
{
Classificator const & c = classif();
std::pair<char const *, SearchRank> const types[] = {
// type rank
{"cycleway", Cycleway},
{"footway", Pedestrian},
{"living_street", Residential},
{"motorway", Motorway},
{"motorway_link", Motorway},
{"path", Outdoor},
{"pedestrian", Pedestrian},
{"primary", Regular},
{"primary_link", Regular},
{"residential", Residential},
{"road", Minors},
{"secondary", Regular},
{"secondary_link", Regular},
{"service", Minors},
{"ladder", Pedestrian},
{"steps", Pedestrian},
{"tertiary", Regular},
{"tertiary_link", Regular},
{"track", Outdoor},
{"trunk", Motorway},
{"trunk_link", Motorway},
{"unclassified", Minors},
};
m_ranks.Reserve(std::size(types));
for (auto const & e : types)
{
uint32_t const type = c.GetTypeByPath({"highway", e.first});
m_types.push_back(type);
m_ranks.Insert(type, e.second);
}
}
IsWayChecker::SearchRank IsWayChecker::GetSearchRank(uint32_t type) const
{
ftype::TruncValue(type, 2);
if (auto const * res = m_ranks.Find(type))
return *res;
return Default;
}
IsStreetOrSquareChecker::IsStreetOrSquareChecker()
{
for (auto const t : IsWayChecker::Instance().GetTypes())
m_types.push_back(t);
for (auto const t : IsSquareChecker::Instance().GetTypes())
m_types.push_back(t);
}
// Used to determine for which features to display address in PP and in search results.
// If such a feature has a housenumber and a name then its enriched with a postcode (at the generation stage).
IsAddressObjectChecker::IsAddressObjectChecker() : BaseChecker(1 /* level */)
{
/// @todo(pastk): some objects in TwoLevelPOIChecker can have addresses also.
m_types = OneLevelPOIChecker().GetTypes();
Classificator const & c = classif();
for (auto const * p : {"addr:interpolation", "building", "entrance"})
m_types.push_back(c.GetTypeByPath({p}));
}
// Used to insert exact address (street and house number) instead of
// an empty name in search results (see ranker.cpp)
IsAddressChecker::IsAddressChecker() : BaseChecker(1 /* level */)
{
Classificator const & c = classif();
for (auto const * p : {"addr:interpolation", "building", "entrance"})
m_types.push_back(c.GetTypeByPath({p}));
}
IsVillageChecker::IsVillageChecker()
{
// TODO (@y, @m, @vng): this list must be up-to-date with
// data/categories.txt, so, it's worth it to generate or parse it
// from that file.
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"place", "village"}));
m_types.push_back(c.GetTypeByPath({"place", "hamlet"}));
}
IsOneWayChecker::IsOneWayChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"hwtag", "oneway"}));
}
IsRoundAboutChecker::IsRoundAboutChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"junction", "circular"}));
m_types.push_back(c.GetTypeByPath({"junction", "roundabout"}));
}
IsLinkChecker::IsLinkChecker()
{
Classificator const & c = classif();
base::StringIL const types[] = {{"highway", "motorway_link"},
{"highway", "trunk_link"},
{"highway", "primary_link"},
{"highway", "secondary_link"},
{"highway", "tertiary_link"}};
for (auto const & e : types)
m_types.push_back(c.GetTypeByPath(e));
}
IsBuildingChecker::IsBuildingChecker() : BaseChecker(1 /* level */)
{
m_types.push_back(classif().GetTypeByPath({"building"}));
}
IsBuildingPartChecker::IsBuildingPartChecker() : BaseChecker(1 /* level */)
{
m_types.push_back(classif().GetTypeByPath({"building:part"}));
}
IsBuildingHasPartsChecker::IsBuildingHasPartsChecker() : BaseChecker(2 /* level */)
{
m_types.push_back(classif().GetTypeByPath({"building", "has_parts"}));
}
IsUnderBuildingChecker::IsUnderBuildingChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"natural", "tree"}));
}
IsIsolineChecker::IsIsolineChecker() : BaseChecker(1 /* level */)
{
m_types.push_back(classif().GetTypeByPath({"isoline"}));
}
IsPisteChecker::IsPisteChecker() : BaseChecker(1 /* level */)
{
m_types.push_back(classif().GetTypeByPath({"piste:type"}));
}
// Used in IsPoiChecker and in IsAddressObjectChecker.
OneLevelPOIChecker::OneLevelPOIChecker() : ftypes::BaseChecker(1 /* level */)
{
Classificator const & c = classif();
for (auto const * path : {"amenity", "craft", "emergency", "healthcare", "historic", "leisure", "mountain_pass",
"office", "railway", "shop", "sport", "tourism"})
m_types.push_back(c.GetTypeByPath({path}));
}
// Used in IsPoiChecker and also in TypesSkipper to keep types in the search index.
TwoLevelPOIChecker::TwoLevelPOIChecker() : ftypes::BaseChecker(2 /* level */)
{
Classificator const & c = classif();
base::StringIL arr[] = {{"aeroway", "terminal"},
{"aeroway", "gate"},
{"building", "guardhouse"},
{"building", "train_station"},
{"emergency", "defibrillator"},
{"emergency", "fire_hydrant"},
{"emergency", "phone"},
{"highway", "bus_stop"},
{"highway", "elevator"},
{"highway", "ford"},
{"highway", "raceway"},
{"highway", "rest_area"},
{"highway", "services"},
{"highway", "speed_camera"},
{"man_made", "communications_tower"},
{"man_made", "cross"},
{"man_made", "lighthouse"},
{"man_made", "water_tap"},
{"man_made", "water_well"},
{"natural", "beach"},
{"natural", "cave_entrance"},
{"natural", "geyser"},
{"natural", "hot_spring"},
{"natural", "peak"},
{"natural", "saddle"},
{"natural", "spring"},
{"natural", "volcano"},
{"waterway", "waterfall"}};
for (auto const & path : arr)
m_types.push_back(c.GetTypeByPath(path));
}
IsAmenityChecker::IsAmenityChecker() : BaseChecker(1 /* level */)
{
m_types.push_back(classif().GetTypeByPath({"amenity"}));
}
IsCheckDateChecker::IsCheckDateChecker() : BaseChecker(1 /* level */)
{
Classificator const & c = classif();
for (auto const * path : {"amenity", "shop", "leisure", "tourism", "craft", "emergency", "healthcare", "office"})
m_types.push_back(c.GetTypeByPath({path}));
}
AttractionsChecker::AttractionsChecker() : BaseChecker(2 /* level */)
{
base::StringIL const primaryAttractionTypes[] = {
{"amenity", "arts_centre"},
{"amenity", "grave_yard"},
{"amenity", "fountain"},
{"amenity", "place_of_worship"},
{"amenity", "theatre"},
{"amenity", "townhall"},
{"amenity", "university"},
{"boundary", "national_park"},
{"building", "train_station"},
{"highway", "pedestrian"},
{"historic", "archaeological_site"},
{"historic", "boundary_stone"},
{"historic", "castle"},
{"historic", "city_gate"},
{"historic", "citywalls"},
{"historic", "fort"},
{"historic", "gallows"},
{"historic", "memorial"},
{"historic", "monument"},
{"historic", "locomotive"},
{"historic", "tank"},
{"historic", "aircraft"},
{"historic", "pillory"},
{"historic", "ruins"},
{"historic", "ship"},
{"historic", "tomb"},
{"historic", "wayside_cross"},
{"historic", "wayside_shrine"},
{"landuse", "cemetery"},
{"leisure", "beach_resort"},
{"leisure", "garden"},
{"leisure", "marina"},
{"leisure", "nature_reserve"},
{"leisure", "park"},
{"leisure", "water_park"},
{"man_made", "lighthouse"},
{"man_made", "windmill"},
{"natural", "beach"},
{"natural", "cave_entrance"},
{"natural", "geyser"},
{"natural", "glacier"},
{"natural", "hot_spring"},
{"natural", "peak"},
{"natural", "volcano"},
{"place", "square"},
{"tourism", "aquarium"},
{"tourism", "artwork"},
{"tourism", "museum"},
{"tourism", "gallery"},
{"tourism", "zoo"},
{"tourism", "theme_park"},
{"waterway", "waterfall"},
};
Classificator const & c = classif();
for (auto const & e : primaryAttractionTypes)
m_types.push_back(c.GetTypeByPath(e));
sort(m_types.begin(), m_types.end());
m_additionalTypesStart = m_types.size();
// Additional types are worse in "hierarchy" priority.
base::StringIL const additionalAttractionTypes[] = {
{"tourism", "viewpoint"},
{"tourism", "attraction"},
};
for (auto const & e : additionalAttractionTypes)
m_types.push_back(c.GetTypeByPath(e));
sort(m_types.begin() + m_additionalTypesStart, m_types.end());
}
uint32_t AttractionsChecker::GetBestType(FeatureParams::Types const & types) const
{
auto additionalType = ftype::GetEmptyValue();
auto const itAdditional = m_types.begin() + m_additionalTypesStart;
for (auto type : types)
{
type = PrepareToMatch(type, m_level);
if (binary_search(m_types.begin(), itAdditional, type))
return type;
if (binary_search(itAdditional, m_types.end(), type))
additionalType = type;
}
return additionalType;
}
IsPlaceChecker::IsPlaceChecker() : BaseChecker(1 /* level */)
{
m_types.push_back(classif().GetTypeByPath({"place"}));
}
IsBridgeOrTunnelChecker::IsBridgeOrTunnelChecker() : BaseChecker(3 /* level */) {}
bool IsBridgeOrTunnelChecker::IsMatched(uint32_t type) const
{
if (ftype::GetLevel(type) != 3)
return false;
ClassifObject const * p = classif().GetRoot()->GetObject(ftype::GetValue(type, 0));
if (p->GetName() != "highway")
return false;
p = p->GetObject(ftype::GetValue(type, 1));
p = p->GetObject(ftype::GetValue(type, 2));
return (p->GetName() == "bridge" || p->GetName() == "tunnel");
}
IsHotelChecker::IsHotelChecker()
{
base::StringIL const types[] = {
{"tourism", "alpine_hut"}, {"tourism", "apartment"}, {"tourism", "camp_site"},
{"tourism", "caravan_site"}, /// @todo Sure here?
{"tourism", "chalet"}, {"tourism", "guest_house"}, {"tourism", "hostel"},
{"tourism", "hotel"}, {"tourism", "motel"}, {"tourism", "wilderness_hut"},
{"leisure", "resort"},
};
Classificator const & c = classif();
for (auto const & e : types)
m_types.push_back(c.GetTypeByPath(e));
}
IsIslandChecker::IsIslandChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"place", "island"}));
m_types.push_back(c.GetTypeByPath({"place", "islet"}));
}
IsLandChecker::IsLandChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"natural", "land"}));
}
uint32_t IsLandChecker::GetLandType() const
{
CHECK_EQUAL(m_types.size(), 1, ());
return m_types[0];
}
IsCoastlineChecker::IsCoastlineChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"natural", "coastline"}));
}
uint32_t IsCoastlineChecker::GetCoastlineType() const
{
CHECK_EQUAL(m_types.size(), 1, ());
return m_types[0];
}
IsWifiChecker::IsWifiChecker()
{
m_types.push_back(classif().GetTypeByPath({"internet_access", "wlan"}));
}
IsEatChecker::IsEatChecker()
{
// The order should be the same as in "enum class Type" declaration.
/// @todo amenity=ice_cream if we already have biergarten :)
base::StringIL const types[] = {
{"amenity", "cafe"}, {"amenity", "fast_food"}, {"amenity", "restaurant"}, {"amenity", "bar"},
{"amenity", "pub"}, {"amenity", "biergarten"}, {"amenity", "food_court"},
};
Classificator const & c = classif();
// size_t i = 0;
for (auto const & e : types)
{
auto const type = c.GetTypeByPath(e);
m_types.push_back(type);
// m_eat2clType[i++] = type;
}
}
// IsEatChecker::Type IsEatChecker::GetType(uint32_t t) const
//{
// for (size_t i = 0; i < m_eat2clType.size(); ++i)
// {
// if (m_eat2clType[i] == t)
// return static_cast<Type>(i);
// }
// return IsEatChecker::Type::Count;
// }
IsCuisineChecker::IsCuisineChecker() : BaseChecker(1 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"cuisine"}));
}
IsRecyclingTypeChecker::IsRecyclingTypeChecker() : BaseChecker(1 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"recycling"}));
}
IsFeeTypeChecker::IsFeeTypeChecker() : BaseChecker(1 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"fee"}));
}
IsToiletsChecker::IsToiletsChecker() : BaseChecker(2 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"amenity", "toilets"}));
m_types.push_back(c.GetTypeByPath({"toilets", "yes"}));
}
IsCapitalChecker::IsCapitalChecker() : BaseChecker(3 /* level */)
{
m_types.push_back(classif().GetTypeByPath({"place", "city", "capital"}));
}
IsPublicTransportStopChecker::IsPublicTransportStopChecker()
{
Classificator const & c = classif();
/// @todo Add bus station into _major_ class (like IsRailwayStationChecker)?
m_types.push_back(c.GetTypeByPath({"amenity", "bus_station"}));
m_types.push_back(c.GetTypeByPath({"amenity", "ferry_terminal"}));
m_types.push_back(c.GetTypeByPath({"highway", "bus_stop"}));
m_types.push_back(c.GetTypeByPath({"railway", "halt"}));
m_types.push_back(c.GetTypeByPath({"railway", "tram_stop"}));
}
IsDirectionalChecker::IsDirectionalChecker() : ftypes::BaseChecker(1 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"cardinal"}));
m_types.push_back(c.GetTypeByPath({"lateral"}));
}
IsTaxiChecker::IsTaxiChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"amenity", "taxi"}));
}
IsChristmasChecker::IsChristmasChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"xmas", "tree"}));
}
IsMotorwayJunctionChecker::IsMotorwayJunctionChecker()
{
m_types.push_back(classif().GetTypeByPath({"highway", "motorway_junction"}));
}
IsWayWithDurationChecker::IsWayWithDurationChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"route", "ferry"}));
m_types.push_back(c.GetTypeByPath({"route", "shuttle_train"}));
}
LocalityType LocalityFromString(std::string_view place)
{
if (place == "village" || place == "hamlet")
return LocalityType::Village;
if (place == "town")
return LocalityType::Town;
if (place == "city")
return LocalityType::City;
if (place == "state")
return LocalityType::State;
if (place == "country")
return LocalityType::Country;
return LocalityType::None;
}
IsLocalityChecker::IsLocalityChecker()
{
Classificator const & c = classif();
// Note! The order should be equal with constants in Type enum (add other villages to the end).
base::StringIL const types[] = {{"place", "country"}, {"place", "state"}, {"place", "city"},
{"place", "town"}, {"place", "village"}, {"place", "hamlet"}};
for (auto const & e : types)
m_types.push_back(c.GetTypeByPath(e));
}
LocalityType IsLocalityChecker::GetType(uint32_t t) const
{
ftype::TruncValue(t, 2);
auto j = static_cast<size_t>(LocalityType::Country);
for (; j < static_cast<size_t>(LocalityType::Count); ++j)
if (t == m_types[j])
return static_cast<LocalityType>(j);
for (; j < m_types.size(); ++j)
if (t == m_types[j])
return LocalityType::Village;
return LocalityType::None;
}
LocalityType IsLocalityChecker::GetType(FeatureType & f) const
{
feature::TypesHolder types(f);
return GetType(types);
}
IsCountryChecker::IsCountryChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"place", "country"}));
}
IsStateChecker::IsStateChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"place", "state"}));
}
IsCityTownOrVillageChecker::IsCityTownOrVillageChecker()
{
base::StringIL const types[] = {{"place", "city"}, {"place", "town"}, {"place", "village"}, {"place", "hamlet"}};
Classificator const & c = classif();
for (auto const & e : types)
m_types.push_back(c.GetTypeByPath(e));
}
IsEntranceChecker::IsEntranceChecker() : BaseChecker(1 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"entrance"}));
}
IsAerowayGateChecker::IsAerowayGateChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"aeroway", "gate"}));
}
IsRailwaySubwayEntranceChecker::IsRailwaySubwayEntranceChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"railway", "subway_entrance"}));
}
IsPlatformChecker::IsPlatformChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"railway", "platform"}));
m_types.push_back(c.GetTypeByPath({"public_transport", "platform"}));
}
IsEmergencyAccessPointChecker::IsEmergencyAccessPointChecker()
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"emergency", "access_point"}));
}
IsAddressInterpolChecker::IsAddressInterpolChecker() : BaseChecker(1 /* level */)
{
Classificator const & c = classif();
m_types.push_back(c.GetTypeByPath({"addr:interpolation"}));
m_odd = c.GetTypeByPath({"addr:interpolation", "odd"});
m_even = c.GetTypeByPath({"addr:interpolation", "even"});
}
uint64_t GetDefPopulation(LocalityType localityType)
{
switch (localityType)
{
case LocalityType::Country: return 500000;
case LocalityType::State: return 100000;
case LocalityType::City: return 50000;
case LocalityType::Town: return 10000;
case LocalityType::Village: return 100;
default: return 0;
}
}
uint64_t GetPopulation(FeatureType & ft)
{
uint64_t const p = ft.GetPopulation();
return (p < 10 ? GetDefPopulation(IsLocalityChecker::Instance().GetType(ft)) : p);
}
double GetRadiusByPopulation(uint64_t p)
{
return pow(static_cast<double>(p), 1 / 3.6) * 550.0;
}
// Look to: https://confluence.mail.ru/pages/viewpage.action?pageId=287950469
// for details about factors.
// Shortly, we assume: radius = (population ^ (1 / base)) * mult
// We knew area info about some cities|towns|villages and did grid search.
// Interval for base: [0.1, 100].
// Interval for mult: [10, 1000].
double GetRadiusByPopulationForRouting(uint64_t p, LocalityType localityType)
{
switch (localityType)
{
case LocalityType::City: return pow(static_cast<double>(p), 1.0 / 2.5) * 34.0;
case LocalityType::Town: return pow(static_cast<double>(p), 1.0 / 6.8) * 354.0;
case LocalityType::Village: return pow(static_cast<double>(p), 1.0 / 15.1) * 610.0;
default: UNREACHABLE();
}
}
uint64_t GetPopulationByRadius(double r)
{
return std::lround(pow(r / 550.0, 3.6));
}
} // namespace ftypes

View file

@ -0,0 +1,728 @@
#pragma once
#include "indexer/feature_data.hpp"
#include "indexer/feature_utils.hpp"
#include "base/small_map.hpp"
#include "base/stl_helpers.hpp"
#include <functional>
#include <string>
#include <vector>
#define DECLARE_CHECKER_INSTANCE(CheckerType) \
static CheckerType const & Instance() \
{ \
static CheckerType const inst; \
return inst; \
}
namespace ftypes
{
class BaseChecker
{
protected:
uint8_t const m_level;
std::vector<uint32_t> m_types;
explicit BaseChecker(uint8_t level = 2) : m_level(level) {}
virtual ~BaseChecker() = default;
public:
virtual bool IsMatched(uint32_t type) const;
virtual void ForEachType(std::function<void(uint32_t)> const & fn) const;
std::vector<uint32_t> const & GetTypes() const { return m_types; }
bool operator()(feature::TypesHolder const & types) const;
bool operator()(FeatureType & ft) const;
bool operator()(std::vector<uint32_t> const & types) const;
bool operator()(uint32_t type) const { return IsMatched(type); }
static uint32_t PrepareToMatch(uint32_t type, uint8_t level);
};
class IsPeakChecker : public BaseChecker
{
IsPeakChecker();
public:
DECLARE_CHECKER_INSTANCE(IsPeakChecker);
};
class IsATMChecker : public BaseChecker
{
IsATMChecker();
public:
DECLARE_CHECKER_INSTANCE(IsATMChecker);
};
class IsSpeedCamChecker : public BaseChecker
{
IsSpeedCamChecker();
public:
DECLARE_CHECKER_INSTANCE(IsSpeedCamChecker);
};
class IsPostBoxChecker : public BaseChecker
{
IsPostBoxChecker();
public:
DECLARE_CHECKER_INSTANCE(IsPostBoxChecker);
};
class IsPostPoiChecker : public BaseChecker
{
IsPostPoiChecker();
public:
DECLARE_CHECKER_INSTANCE(IsPostPoiChecker);
};
class IsOperatorOthersPoiChecker : public BaseChecker
{
IsOperatorOthersPoiChecker();
public:
DECLARE_CHECKER_INSTANCE(IsOperatorOthersPoiChecker);
};
class IsRecyclingCentreChecker : public BaseChecker
{
IsRecyclingCentreChecker();
public:
DECLARE_CHECKER_INSTANCE(IsRecyclingCentreChecker);
uint32_t GetType() const;
};
class IsRecyclingContainerChecker : public BaseChecker
{
IsRecyclingContainerChecker();
public:
DECLARE_CHECKER_INSTANCE(IsRecyclingContainerChecker);
uint32_t GetType() const;
};
class IsRailwayStationChecker : public BaseChecker
{
IsRailwayStationChecker();
public:
DECLARE_CHECKER_INSTANCE(IsRailwayStationChecker);
};
class IsSubwayStationChecker : public BaseChecker
{
IsSubwayStationChecker();
public:
DECLARE_CHECKER_INSTANCE(IsSubwayStationChecker);
};
class IsAirportChecker : public BaseChecker
{
IsAirportChecker();
public:
DECLARE_CHECKER_INSTANCE(IsAirportChecker);
};
class IsSquareChecker : public BaseChecker
{
IsSquareChecker();
public:
DECLARE_CHECKER_INSTANCE(IsSquareChecker);
};
// Type of suburb.
// Do not change values and order - they are in the order of decreasing specificity.
// Suburb > Neighbourhood > Residential
enum class SuburbType
{
Residential = 0,
Neighbourhood,
Quarter,
Suburb,
Count
};
class IsSuburbChecker : public BaseChecker
{
IsSuburbChecker();
public:
SuburbType GetType(uint32_t t) const;
SuburbType GetType(feature::TypesHolder const & types) const;
SuburbType GetType(FeatureType & f) const;
DECLARE_CHECKER_INSTANCE(IsSuburbChecker);
};
/// @todo Better to rename like IsStreetChecker, as it is used in search context only?
class IsWayChecker : public BaseChecker
{
IsWayChecker();
public:
DECLARE_CHECKER_INSTANCE(IsWayChecker);
enum SearchRank : uint8_t
{
Default = 0, // Not a road, other linear way like river, rail ...
// Bigger is better (more important).
Pedestrian,
Cycleway,
Outdoor,
Minors,
Residential,
Regular,
Motorway,
Count
};
SearchRank GetSearchRank(uint32_t type) const;
private:
base::SmallMap<uint32_t, SearchRank> m_ranks;
};
class IsStreetOrSquareChecker : public BaseChecker
{
IsStreetOrSquareChecker();
public:
DECLARE_CHECKER_INSTANCE(IsStreetOrSquareChecker);
};
class IsAddressObjectChecker : public BaseChecker
{
IsAddressObjectChecker();
public:
DECLARE_CHECKER_INSTANCE(IsAddressObjectChecker);
};
class IsAddressChecker : public BaseChecker
{
IsAddressChecker();
public:
DECLARE_CHECKER_INSTANCE(IsAddressChecker);
};
class IsVillageChecker : public BaseChecker
{
IsVillageChecker();
public:
DECLARE_CHECKER_INSTANCE(IsVillageChecker);
};
class IsOneWayChecker : public BaseChecker
{
IsOneWayChecker();
public:
DECLARE_CHECKER_INSTANCE(IsOneWayChecker);
uint32_t GetType() const { return m_types[0]; }
};
class IsRoundAboutChecker : public BaseChecker
{
IsRoundAboutChecker();
public:
DECLARE_CHECKER_INSTANCE(IsRoundAboutChecker);
};
class IsLinkChecker : public BaseChecker
{
IsLinkChecker();
public:
DECLARE_CHECKER_INSTANCE(IsLinkChecker);
};
class IsBuildingChecker : public BaseChecker
{
IsBuildingChecker();
public:
DECLARE_CHECKER_INSTANCE(IsBuildingChecker);
};
class IsBuildingPartChecker : public ftypes::BaseChecker
{
IsBuildingPartChecker();
public:
DECLARE_CHECKER_INSTANCE(IsBuildingPartChecker);
};
class IsBuildingHasPartsChecker : public ftypes::BaseChecker
{
IsBuildingHasPartsChecker();
public:
DECLARE_CHECKER_INSTANCE(IsBuildingHasPartsChecker);
uint32_t GetType() const { return m_types[0]; }
};
class IsUnderBuildingChecker : public BaseChecker
{
IsUnderBuildingChecker();
public:
DECLARE_CHECKER_INSTANCE(IsUnderBuildingChecker);
};
class IsIsolineChecker : public BaseChecker
{
IsIsolineChecker();
public:
DECLARE_CHECKER_INSTANCE(IsIsolineChecker);
};
class IsPisteChecker : public BaseChecker
{
IsPisteChecker();
public:
DECLARE_CHECKER_INSTANCE(IsPisteChecker);
};
class OneLevelPOIChecker : public ftypes::BaseChecker
{
public:
OneLevelPOIChecker();
};
/// Describes 2-level POI-exception types that don't belong to any POI-common classes
/// (amenity, shop, tourism, ...). Used in search algo and search categories index generation.
class TwoLevelPOIChecker : public ftypes::BaseChecker
{
public:
TwoLevelPOIChecker();
};
// Used in:
// - search ranking (POIs are ranked somewhat higher than "unclassified" features, see search/model.hpp)
// - add type's translation into complex PP subtitle (see GetLocalizedAllTypes())
// - building lists of popular places (generator/popular_places_section_builder.cpp)
class IsPoiChecker
{
public:
DECLARE_CHECKER_INSTANCE(IsPoiChecker);
bool operator()(FeatureType & ft) const { return m_oneLevel(ft) || m_twoLevel(ft); }
template <class T>
bool operator()(T const & t) const
{
return m_oneLevel(t) || m_twoLevel(t);
}
private:
OneLevelPOIChecker const m_oneLevel;
TwoLevelPOIChecker const m_twoLevel;
};
class IsAmenityChecker : public BaseChecker
{
IsAmenityChecker();
public:
DECLARE_CHECKER_INSTANCE(IsAmenityChecker);
uint32_t GetType() const { return m_types[0]; }
};
class IsCheckDateChecker : public BaseChecker
{
IsCheckDateChecker();
public:
DECLARE_CHECKER_INSTANCE(IsCheckDateChecker);
uint32_t GetType() const { return m_types[0]; }
};
class AttractionsChecker : public BaseChecker
{
size_t m_additionalTypesStart;
AttractionsChecker();
public:
DECLARE_CHECKER_INSTANCE(AttractionsChecker);
// Used in generator.
uint32_t GetBestType(FeatureParams::Types const & types) const;
};
class IsPlaceChecker : public BaseChecker
{
IsPlaceChecker();
public:
DECLARE_CHECKER_INSTANCE(IsPlaceChecker);
};
class IsBridgeOrTunnelChecker : public BaseChecker
{
virtual bool IsMatched(uint32_t type) const override;
IsBridgeOrTunnelChecker();
public:
DECLARE_CHECKER_INSTANCE(IsBridgeOrTunnelChecker);
};
class IsIslandChecker : public BaseChecker
{
IsIslandChecker();
public:
DECLARE_CHECKER_INSTANCE(IsIslandChecker);
};
class IsLandChecker : public BaseChecker
{
IsLandChecker();
public:
DECLARE_CHECKER_INSTANCE(IsLandChecker);
uint32_t GetLandType() const;
};
class IsCoastlineChecker : public BaseChecker
{
IsCoastlineChecker();
public:
DECLARE_CHECKER_INSTANCE(IsCoastlineChecker);
uint32_t GetCoastlineType() const;
};
class IsHotelChecker : public BaseChecker
{
IsHotelChecker();
public:
DECLARE_CHECKER_INSTANCE(IsHotelChecker);
};
// WiFi is a type in classificator.txt,
// it should be checked for filling metadata in MapObject.
class IsWifiChecker : public BaseChecker
{
IsWifiChecker();
public:
DECLARE_CHECKER_INSTANCE(IsWifiChecker);
uint32_t GetType() const { return m_types[0]; }
};
class IsEatChecker : public BaseChecker
{
public:
// enum class Type
// {
// Cafe = 0,
// FastFood,
// Restaurant,
// Bar,
// Pub,
// Biergarten,
// Count
// };
DECLARE_CHECKER_INSTANCE(IsEatChecker);
// Type GetType(uint32_t t) const;
private:
IsEatChecker();
// std::array<uint32_t, base::Underlying(Type::Count)> m_eat2clType;
};
class IsCuisineChecker : public BaseChecker
{
IsCuisineChecker();
public:
DECLARE_CHECKER_INSTANCE(IsCuisineChecker);
};
class IsRecyclingTypeChecker : public BaseChecker
{
IsRecyclingTypeChecker();
public:
DECLARE_CHECKER_INSTANCE(IsRecyclingTypeChecker);
};
class IsFeeTypeChecker : public BaseChecker
{
IsFeeTypeChecker();
public:
DECLARE_CHECKER_INSTANCE(IsFeeTypeChecker);
};
class IsToiletsChecker : public BaseChecker
{
IsToiletsChecker();
public:
DECLARE_CHECKER_INSTANCE(IsToiletsChecker);
};
class IsCapitalChecker : public BaseChecker
{
IsCapitalChecker();
public:
DECLARE_CHECKER_INSTANCE(IsCapitalChecker);
};
class IsPublicTransportStopChecker : public BaseChecker
{
IsPublicTransportStopChecker();
public:
DECLARE_CHECKER_INSTANCE(IsPublicTransportStopChecker);
};
class IsDirectionalChecker : public ftypes::BaseChecker
{
IsDirectionalChecker();
public:
DECLARE_CHECKER_INSTANCE(IsDirectionalChecker);
};
class IsTaxiChecker : public BaseChecker
{
IsTaxiChecker();
public:
DECLARE_CHECKER_INSTANCE(IsTaxiChecker);
};
class IsChristmasChecker : public BaseChecker
{
IsChristmasChecker();
public:
DECLARE_CHECKER_INSTANCE(IsChristmasChecker);
};
class IsMotorwayJunctionChecker : public BaseChecker
{
IsMotorwayJunctionChecker();
public:
DECLARE_CHECKER_INSTANCE(IsMotorwayJunctionChecker);
};
/// Exotic OSM ways that potentially have "duration" tag.
class IsWayWithDurationChecker : public BaseChecker
{
IsWayWithDurationChecker();
public:
DECLARE_CHECKER_INSTANCE(IsWayWithDurationChecker);
};
/// Type of locality (do not change values and order - they have detalization order)
/// Country < State < City < ...
enum class LocalityType : int8_t
{
None = -1,
Country = 0,
State,
City,
Town,
Village,
Count
};
LocalityType LocalityFromString(std::string_view s);
static_assert(base::Underlying(LocalityType::Country) < base::Underlying(LocalityType::State), "");
static_assert(base::Underlying(LocalityType::State) < base::Underlying(LocalityType::City), "");
static_assert(base::Underlying(LocalityType::City) < base::Underlying(LocalityType::Town), "");
static_assert(base::Underlying(LocalityType::Town) < base::Underlying(LocalityType::Village), "");
static_assert(base::Underlying(LocalityType::Village) < base::Underlying(LocalityType::Count), "");
class IsLocalityChecker : public BaseChecker
{
IsLocalityChecker();
public:
LocalityType GetType(uint32_t t) const;
template <class Types>
LocalityType GetType(Types const & types) const
{
for (uint32_t const t : types)
{
LocalityType const type = GetType(t);
if (type != LocalityType::None)
return type;
}
return LocalityType::None;
}
LocalityType GetType(FeatureType & f) const;
DECLARE_CHECKER_INSTANCE(IsLocalityChecker);
};
class IsCountryChecker : public BaseChecker
{
IsCountryChecker();
public:
DECLARE_CHECKER_INSTANCE(IsCountryChecker);
};
class IsStateChecker : public BaseChecker
{
IsStateChecker();
public:
DECLARE_CHECKER_INSTANCE(IsStateChecker);
};
class IsCityTownOrVillageChecker : public BaseChecker
{
IsCityTownOrVillageChecker();
public:
DECLARE_CHECKER_INSTANCE(IsCityTownOrVillageChecker);
};
class IsEntranceChecker : public BaseChecker
{
IsEntranceChecker();
public:
DECLARE_CHECKER_INSTANCE(IsEntranceChecker);
};
class IsAerowayGateChecker : public BaseChecker
{
IsAerowayGateChecker();
public:
DECLARE_CHECKER_INSTANCE(IsAerowayGateChecker);
};
class IsRailwaySubwayEntranceChecker : public BaseChecker
{
IsRailwaySubwayEntranceChecker();
public:
DECLARE_CHECKER_INSTANCE(IsRailwaySubwayEntranceChecker);
};
class IsPlatformChecker : public BaseChecker
{
IsPlatformChecker();
public:
DECLARE_CHECKER_INSTANCE(IsPlatformChecker);
};
class IsEmergencyAccessPointChecker : public BaseChecker
{
IsEmergencyAccessPointChecker();
public:
DECLARE_CHECKER_INSTANCE(IsEmergencyAccessPointChecker);
};
class IsAddressInterpolChecker : public BaseChecker
{
IsAddressInterpolChecker();
uint32_t m_odd, m_even;
public:
DECLARE_CHECKER_INSTANCE(IsAddressInterpolChecker);
template <class Range>
feature::InterpolType GetInterpolType(Range const & range) const
{
for (uint32_t t : range)
{
if (t == m_odd)
return feature::InterpolType::Odd;
if (t == m_even)
return feature::InterpolType::Even;
ftype::TruncValue(t, 1);
if (t == m_types[0])
return feature::InterpolType::Any;
}
return feature::InterpolType::None;
}
feature::InterpolType GetInterpolType(FeatureType & ft) const { return GetInterpolType(feature::TypesHolder(ft)); }
};
/// @name Get city radius and population.
/// @param r Radius in meters.
//@{
uint64_t GetDefPopulation(LocalityType localityType);
uint64_t GetPopulation(FeatureType & ft);
double GetRadiusByPopulation(uint64_t p);
double GetRadiusByPopulationForRouting(uint64_t p, LocalityType localityType);
uint64_t GetPopulationByRadius(double r);
//@}
// Highway class. The order is important.
// The enum values follow from the biggest roads (Trunk) to the smallest ones (Service).
enum class HighwayClass
{
Undefined = 0, // There has not been any attempt of calculating HighwayClass.
Motorway,
Trunk,
Primary,
Secondary,
Tertiary,
LivingStreet,
Service,
// OSM highway=service type is widely used even for _significant_ roads.
// Adding a new type to distinguish mapped driveway or parking_aisle.
ServiceMinor,
Pedestrian,
Transported, // Vehicles are transported by train or ferry.
Count // This value is used for internals only.
};
std::string DebugPrint(HighwayClass const cls);
std::string DebugPrint(LocalityType const localityType);
HighwayClass GetHighwayClass(feature::TypesHolder const & types);
} // namespace ftypes

View file

@ -0,0 +1,61 @@
#pragma once
#include "coding/reader.hpp"
#include <cstdint>
#include <optional>
class HouseToStreetTable
{
public:
enum class Version : uint8_t
{
V0 = 0,
V1 = 1,
V2 = 2,
Latest = V2
};
enum class StreetIdType
{
// Table stores the index number of the correct street corresponding
// to the house in the list of streets generated by ReverseGeocoder.
Index,
// Table stores feature id of street corresponding to the house.
FeatureId,
// Empty table.
None
};
struct Header
{
template <class Sink>
void Serialize(Sink & sink) const
{
WriteToSink(sink, static_cast<uint8_t>(Version::Latest));
WriteToSink(sink, m_tableOffset);
WriteToSink(sink, m_tableSize);
}
template <class Source>
void Read(Source & source)
{
m_version = static_cast<Version>(ReadPrimitiveFromSource<uint8_t>(source));
m_tableOffset = ReadPrimitiveFromSource<uint32_t>(source);
m_tableSize = ReadPrimitiveFromSource<uint32_t>(source);
}
Version m_version = Version::Latest;
// All offsets are relative to the start of the section (offset of header is zero).
uint32_t m_tableOffset = 0;
uint32_t m_tableSize = 0;
};
virtual ~HouseToStreetTable() = default;
struct Result
{
uint32_t m_streetId;
StreetIdType m_type;
};
virtual std::optional<Result> Get(uint32_t houseId) const = 0;
};

View file

@ -0,0 +1,39 @@
#include "indexer/index_builder.hpp"
#include "indexer/features_vector.hpp"
#include "base/logging.hpp"
#include "defines.hpp"
namespace indexer
{
bool BuildIndexFromDataFile(std::string const & dataFile, std::string const & tmpFile)
{
try
{
std::string const idxFileName(tmpFile + GEOM_INDEX_TMP_EXT);
{
FeaturesVectorTest features(dataFile);
FileWriter writer(idxFileName);
BuildIndex(features.GetHeader(), features.GetVector(), writer, tmpFile);
}
FilesContainerW(dataFile, FileWriter::OP_WRITE_EXISTING).Write(idxFileName, INDEX_FILE_TAG);
FileWriter::DeleteFileX(idxFileName);
}
catch (Reader::Exception const & e)
{
LOG(LERROR, ("Error while reading file: ", e.Msg()));
return false;
}
catch (Writer::Exception const & e)
{
LOG(LERROR, ("Error writing index file: ", e.Msg()));
return false;
}
return true;
}
} // namespace indexer

View file

@ -0,0 +1,26 @@
#pragma once
#include "indexer/data_header.hpp"
#include "indexer/scale_index_builder.hpp"
#include <string>
namespace indexer
{
template <class TFeaturesVector, typename TWriter>
void BuildIndex(feature::DataHeader const & header, TFeaturesVector const & features, TWriter & writer,
std::string const & tmpFilePrefix)
{
LOG(LINFO, ("Building scale index."));
uint64_t indexSize;
{
SubWriter<TWriter> subWriter(writer);
covering::IndexScales(header, features, subWriter, tmpFilePrefix);
indexSize = subWriter.Size();
}
LOG(LINFO, ("Built scale index. Size =", indexSize));
}
// doesn't throw exceptions
bool BuildIndexFromDataFile(std::string const & dataFile, std::string const & tmpFile);
} // namespace indexer

View file

@ -0,0 +1,54 @@
project(indexer_tests)
set(SRC
bounds.hpp
brands_tests.cpp
categories_test.cpp
cell_coverer_test.cpp
cell_id_test.cpp
centers_table_test.cpp
checker_test.cpp
cities_boundaries_serdes_tests.cpp
classificator_tests.cpp
complex_serdes_tests.cpp
complex_serdes_utils_tests.cpp
custom_keyvalue_tests.cpp
data_source_test.cpp
drules_selector_parser_test.cpp
editable_map_object_test.cpp
feature_charge_sockets_test.cpp
feature_metadata_test.cpp
feature_names_test.cpp
feature_to_osm_tests.cpp
feature_types_test.cpp
features_offsets_table_test.cpp
features_vector_test.cpp
index_builder_test.cpp
interval_index_test.cpp
metadata_serdes_tests.cpp
mwm_set_test.cpp
postcodes_matcher_tests.cpp
rank_table_test.cpp
read_features_tests.cpp
road_shields_parser_tests.cpp
scale_index_reading_tests.cpp
search_string_utils_test.cpp
sort_and_merge_intervals_test.cpp
string_slice_tests.cpp
succinct_trie_test.cpp
test_mwm_set.hpp
test_type.cpp
tree_node_tests.cpp
trie_test.cpp
validate_and_format_contacts_test.cpp
visibility_test.cpp
wheelchair_tests.cpp
)
omim_add_test(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
generator_tests_support
platform_tests_support
indexer
)

View file

@ -0,0 +1,18 @@
#pragma once
#include "geometry/rect2d.hpp"
template <int MinX, int MinY, int MaxX, int MaxY>
struct Bounds
{
static double constexpr kMinX = MinX;
static double constexpr kMaxX = MaxX;
static double constexpr kMinY = MinY;
static double constexpr kMaxY = MaxY;
static double constexpr kRangeX = kMaxX - kMinX;
static double constexpr kRangeY = kMaxY - kMinY;
static m2::RectD FullRect() { return {MinX, MinY, MaxX, MaxY}; }
};
using OrthoBounds = Bounds<-180, -90, 180, 90>;

View file

@ -0,0 +1,89 @@
#include "testing/testing.hpp"
#include "indexer/brands_holder.hpp"
#include "coding/reader.hpp"
#include "coding/string_utf8_multilang.hpp"
#include <memory>
#include <set>
#include <string>
using namespace std;
using namespace indexer;
char const g_testBrandsTxt[] =
"brand.mcdonalds\n"
"en:McDonald's|Mc Donalds\n"
"ru:МакДональд'с|Мак Доналдс\n"
"uk:Макдональдз\n"
"\n"
"brand.subway\n"
"en:Subway\n"
"ru:Сабвэй|Сабвей";
UNIT_TEST(LoadDefaultBrands)
{
auto const & brands = GetDefaultBrands();
TEST(!brands.GetKeys().empty(), ());
}
UNIT_TEST(LoadBrands)
{
BrandsHolder const holder(make_unique<MemReader>(g_testBrandsTxt, sizeof(g_testBrandsTxt) - 1));
set<string> expectedKeys = {"mcdonalds", "subway"};
auto const keys = holder.GetKeys();
TEST_EQUAL(keys, expectedKeys, ());
using Names = set<BrandsHolder::Brand::Name>;
{
Names expectedNames;
expectedNames.emplace("McDonald's", StringUtf8Multilang::GetLangIndex("en"));
expectedNames.emplace("Mc Donalds", StringUtf8Multilang::GetLangIndex("en"));
expectedNames.emplace("МакДональд'с", StringUtf8Multilang::GetLangIndex("ru"));
expectedNames.emplace("Мак Доналдс", StringUtf8Multilang::GetLangIndex("ru"));
expectedNames.emplace("Макдональдз", StringUtf8Multilang::GetLangIndex("uk"));
Names names;
holder.ForEachNameByKey("mcdonalds", [&names](BrandsHolder::Brand::Name const & name) { names.insert(name); });
CHECK_EQUAL(names, expectedNames, ());
}
{
Names expectedNames;
expectedNames.emplace("Subway", StringUtf8Multilang::GetLangIndex("en"));
expectedNames.emplace("Сабвэй", StringUtf8Multilang::GetLangIndex("ru"));
expectedNames.emplace("Сабвей", StringUtf8Multilang::GetLangIndex("ru"));
Names names;
holder.ForEachNameByKey("subway", [&names](BrandsHolder::Brand::Name const & name) { names.insert(name); });
CHECK_EQUAL(names, expectedNames, ());
}
{
set<string> expectedNames = {"McDonald's", "Mc Donalds"};
set<string> names;
holder.ForEachNameByKeyAndLang("mcdonalds", "en", [&names](string const & name) { names.insert(name); });
CHECK_EQUAL(names, expectedNames, ());
}
{
set<string> expectedNames = {"МакДональд'с", "Мак Доналдс"};
set<string> names;
holder.ForEachNameByKeyAndLang("mcdonalds", "ru", [&names](string const & name) { names.insert(name); });
CHECK_EQUAL(names, expectedNames, ());
}
{
set<string> expectedNames = {"Макдональдз"};
set<string> names;
holder.ForEachNameByKeyAndLang("mcdonalds", "uk", [&names](string const & name) { names.insert(name); });
CHECK_EQUAL(names, expectedNames, ());
}
}

View file

@ -0,0 +1,328 @@
#include "testing/testing.hpp"
#include "indexer/categories_holder.hpp"
#include "indexer/categories_index.hpp"
#include "indexer/classificator.hpp"
#include "indexer/classificator_loader.hpp"
#include "coding/reader.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include <memory>
#include <vector>
using namespace indexer;
using namespace std;
char const g_testCategoriesTxt[] =
"amenity-bench\n"
"en:1bench|sit down|to sit\n"
"de:2bank|auf die strafbank schicken\n"
"zh-Hans:长凳\n"
"zh-Hant:長板凳\n"
"da:bænk\n"
"\n"
"place-village|place-hamlet\n"
"en:village\n"
"de:2dorf|4weiler";
struct Checker
{
explicit Checker(size_t & count) : m_count(count) {}
void operator()(CategoriesHolder::Category const & cat)
{
switch (m_count)
{
case 0:
{
TEST_EQUAL(cat.m_synonyms.size(), 8, ());
TEST_EQUAL(cat.m_synonyms[0].m_locale, CategoriesHolder::MapLocaleToInteger("en"), ());
TEST_EQUAL(cat.m_synonyms[0].m_name, "bench", ());
TEST_EQUAL(cat.m_synonyms[0].m_prefixLengthToSuggest, 1, ());
TEST_EQUAL(cat.m_synonyms[1].m_locale, CategoriesHolder::MapLocaleToInteger("en"), ());
TEST_EQUAL(cat.m_synonyms[1].m_name, "sit down", ());
TEST_EQUAL(cat.m_synonyms[1].m_prefixLengthToSuggest, 10, ());
TEST_EQUAL(cat.m_synonyms[2].m_locale, CategoriesHolder::MapLocaleToInteger("en"), ());
TEST_EQUAL(cat.m_synonyms[2].m_name, "to sit", ());
TEST_EQUAL(cat.m_synonyms[3].m_locale, CategoriesHolder::MapLocaleToInteger("de"), ());
TEST_EQUAL(cat.m_synonyms[3].m_name, "bank", ());
TEST_EQUAL(cat.m_synonyms[3].m_prefixLengthToSuggest, 2, ());
TEST_EQUAL(cat.m_synonyms[4].m_locale, CategoriesHolder::MapLocaleToInteger("de"), ());
TEST_EQUAL(cat.m_synonyms[4].m_name, "auf die strafbank schicken", ());
TEST_EQUAL(cat.m_synonyms[5].m_locale, CategoriesHolder::MapLocaleToInteger("zh_CN"), ());
TEST_EQUAL(cat.m_synonyms[5].m_locale, CategoriesHolder::MapLocaleToInteger("zh_rCN"), ());
TEST_EQUAL(cat.m_synonyms[5].m_locale, CategoriesHolder::MapLocaleToInteger("zh_HANS_CN"), ());
TEST_EQUAL(cat.m_synonyms[5].m_locale, CategoriesHolder::MapLocaleToInteger("zh-Hans"), ());
TEST_EQUAL(cat.m_synonyms[5].m_name, "长凳", ());
TEST_EQUAL(cat.m_synonyms[6].m_locale, CategoriesHolder::MapLocaleToInteger("zh_TW"), ());
TEST_EQUAL(cat.m_synonyms[6].m_locale, CategoriesHolder::MapLocaleToInteger("zh-MO"), ());
TEST_EQUAL(cat.m_synonyms[6].m_locale, CategoriesHolder::MapLocaleToInteger("zh-rTW"), ());
TEST_EQUAL(cat.m_synonyms[6].m_locale, CategoriesHolder::MapLocaleToInteger("zh_HANT_HK"), ());
TEST_EQUAL(cat.m_synonyms[6].m_locale, CategoriesHolder::MapLocaleToInteger("zh_HK"), ());
TEST_EQUAL(cat.m_synonyms[6].m_locale, CategoriesHolder::MapLocaleToInteger("zh-Hant"), ());
TEST_EQUAL(cat.m_synonyms[6].m_name, "長板凳", ());
TEST_EQUAL(cat.m_synonyms[7].m_locale, CategoriesHolder::MapLocaleToInteger("da"), ());
TEST_EQUAL(cat.m_synonyms[7].m_name, "bænk", ());
++m_count;
}
break;
case 1:
case 2:
{
TEST_EQUAL(cat.m_synonyms.size(), 3, ());
TEST_EQUAL(cat.m_synonyms[0].m_locale, CategoriesHolder::MapLocaleToInteger("en"), ());
TEST_EQUAL(cat.m_synonyms[0].m_name, "village", ());
TEST_EQUAL(cat.m_synonyms[1].m_locale, CategoriesHolder::MapLocaleToInteger("de"), ());
TEST_EQUAL(cat.m_synonyms[1].m_name, "dorf", ());
TEST_EQUAL(cat.m_synonyms[1].m_prefixLengthToSuggest, 2, ());
TEST_EQUAL(cat.m_synonyms[2].m_locale, CategoriesHolder::MapLocaleToInteger("de"), ());
TEST_EQUAL(cat.m_synonyms[2].m_name, "weiler", ());
TEST_EQUAL(cat.m_synonyms[2].m_prefixLengthToSuggest, 4, ());
++m_count;
}
break;
default: TEST(false, ("Too many categories"));
}
}
size_t & m_count;
};
UNIT_TEST(LoadCategories)
{
classificator::Load();
CategoriesHolder h(make_unique<MemReader>(g_testCategoriesTxt, sizeof(g_testCategoriesTxt) - 1));
size_t count = 0;
Checker f(count);
h.ForEachCategory(f);
TEST_EQUAL(count, 3, ());
}
UNIT_TEST(CategoriesHolder_Smoke)
{
auto const & mappings = CategoriesHolder::kLocaleMapping;
for (size_t i = 0; i < mappings.size(); ++i)
{
auto const & mapping = mappings[i];
TEST_EQUAL(static_cast<int8_t>(i + 1), mapping.m_code, ());
TEST_EQUAL(static_cast<int8_t>(i + 1), CategoriesHolder::MapLocaleToInteger(mapping.m_name), (mapping.m_name));
TEST_EQUAL(CategoriesHolder::MapIntegerToLocale(i + 1), mapping.m_name, ());
}
}
UNIT_TEST(CategoriesHolder_LoadDefault)
{
classificator::Load();
uint32_t counter = 0;
auto const count = [&counter](CategoriesHolder::Category const &) { ++counter; };
auto const & categoriesHolder = GetDefaultCategories();
categoriesHolder.ForEachCategory(count);
TEST_GREATER(counter, 0, ());
counter = 0;
auto const & cuisineCategoriesHolder = GetDefaultCuisineCategories();
cuisineCategoriesHolder.ForEachCategory(count);
TEST_GREATER(counter, 0, ());
}
UNIT_TEST(CategoriesHolder_ForEach)
{
char const kCategories[] =
"amenity-bar\n"
"en:abc|ddd-eee\n"
"\n"
"amenity-pub\n"
"en:ddd\n"
"\n"
"amenity-cafe\n"
"en:abc eee\n"
"\n"
"amenity-restaurant\n"
"en:ddd|eee\n"
"\n"
"";
classificator::Load();
CategoriesHolder holder(make_unique<MemReader>(kCategories, ARRAY_SIZE(kCategories) - 1));
{
uint32_t counter = 0;
holder.ForEachTypeByName(CategoriesHolder::kEnglishCode, strings::MakeUniString("abc"),
[&](uint32_t /* type */) { ++counter; });
TEST_EQUAL(counter, 2, ());
}
{
uint32_t counter = 0;
holder.ForEachTypeByName(CategoriesHolder::kEnglishCode, strings::MakeUniString("ddd"),
[&](uint32_t /* type */) { ++counter; });
TEST_EQUAL(counter, 3, ());
}
{
uint32_t counter = 0;
holder.ForEachTypeByName(CategoriesHolder::kEnglishCode, strings::MakeUniString("eee"),
[&](uint32_t /* type */) { ++counter; });
TEST_EQUAL(counter, 3, ());
}
}
UNIT_TEST(CategoriesIndex_Smoke)
{
classificator::Load();
CategoriesHolder holder(make_unique<MemReader>(g_testCategoriesTxt, sizeof(g_testCategoriesTxt) - 1));
CategoriesIndex index(holder);
uint32_t type1 = classif().GetTypeByPath({"amenity", "bench"});
uint32_t type2 = classif().GetTypeByPath({"place", "village"});
if (type1 > type2)
swap(type1, type2);
int8_t lang1 = CategoriesHolder::MapLocaleToInteger("en");
int8_t lang2 = CategoriesHolder::MapLocaleToInteger("de");
auto testTypes = [&](string const & query, vector<uint32_t> const & expected)
{
vector<uint32_t> result;
index.GetAssociatedTypes(query, result);
TEST_EQUAL(result, expected, (query));
};
index.AddCategoryByTypeAndLang(type1, lang1);
testTypes("bench", {type1});
testTypes("BENCH", {type1});
testTypes("down", {type1});
testTypes("benck", {});
testTypes("strafbank", {});
index.AddCategoryByTypeAndLang(type1, lang2);
testTypes("strafbank", {type1});
testTypes("ie strafbank sc", {type1});
testTypes("rafb", {type1});
index.AddCategoryByTypeAndLang(type2, lang1);
testTypes("i", {type1, type2});
CategoriesIndex fullIndex(holder);
fullIndex.AddCategoryByTypeAllLangs(type1);
fullIndex.AddCategoryByTypeAllLangs(type2);
vector<CategoriesHolder::Category> cats;
// The letter 'a' matches "strafbank" and "village".
// One language is not enough.
fullIndex.GetCategories("a", cats);
TEST_EQUAL(cats.size(), 2, ());
TEST_EQUAL(cats[0].m_synonyms.size(), 8, ());
TEST_EQUAL(cats[0].m_synonyms[4].m_locale, CategoriesHolder::MapLocaleToInteger("de"), ());
TEST_EQUAL(cats[0].m_synonyms[4].m_name, "auf die strafbank schicken", ());
TEST_EQUAL(cats[1].m_synonyms.size(), 3, ());
TEST_EQUAL(cats[1].m_synonyms[0].m_locale, CategoriesHolder::MapLocaleToInteger("en"), ());
TEST_EQUAL(cats[1].m_synonyms[0].m_name, "village", ());
}
UNIT_TEST(CategoriesIndex_MultipleTokens)
{
char const kCategories[] =
"shop-bakery\n"
"en:shop of buns\n"
"\n"
"shop-butcher\n"
"en:shop of meat";
classificator::Load();
CategoriesHolder holder(make_unique<MemReader>(kCategories, sizeof(kCategories) - 1));
CategoriesIndex index(holder);
index.AddAllCategoriesInAllLangs();
auto testTypes = [&](string const & query, vector<uint32_t> const & expected)
{
vector<uint32_t> result;
index.GetAssociatedTypes(query, result);
TEST_EQUAL(result, expected, (query));
};
uint32_t type1 = classif().GetTypeByPath({"shop", "bakery"});
uint32_t type2 = classif().GetTypeByPath({"shop", "butcher"});
if (type1 > type2)
swap(type1, type2);
testTypes("shop", {type1, type2});
testTypes("shop buns", {type1});
testTypes("shop meat", {type2});
}
UNIT_TEST(CategoriesIndex_Groups)
{
char const kCategories[] =
"@shop\n"
"en:shop\n"
"ru:магазин\n"
"\n"
"@meat\n"
"en:meat\n"
"\n"
"shop-bakery|@shop\n"
"en:buns\n"
"\n"
"shop-butcher|@shop|@meat\n"
"en:butcher\n"
"";
classificator::Load();
CategoriesHolder holder(make_unique<MemReader>(kCategories, sizeof(kCategories) - 1));
CategoriesIndex index(holder);
index.AddAllCategoriesInAllLangs();
auto testTypes = [&](string const & query, vector<uint32_t> const & expected)
{
vector<uint32_t> result;
index.GetAssociatedTypes(query, result);
TEST_EQUAL(result, expected, (query));
};
uint32_t type1 = classif().GetTypeByPath({"shop", "bakery"});
uint32_t type2 = classif().GetTypeByPath({"shop", "butcher"});
if (type1 > type2)
swap(type1, type2);
testTypes("buns", {type1});
testTypes("butcher", {type2});
testTypes("meat", {type2});
testTypes("shop", {type1, type2});
testTypes("магазин", {type1, type2});
testTypes("http", {});
}
#ifdef DEBUG
// A check that this data structure is not too heavy.
UNIT_TEST(CategoriesIndex_AllCategories)
{
classificator::Load();
CategoriesIndex index;
index.AddAllCategoriesInAllLangs();
// Consider deprecating this method if this bound rises as high as a million.
LOG(LINFO, ("Number of nodes in the CategoriesIndex trie:", index.GetNumTrieNodes()));
TEST_LESS(index.GetNumTrieNodes(), 900000, ());
}
// A check that this data structure is not too heavy.
UNIT_TEST(CategoriesIndex_AllCategoriesEnglishName)
{
classificator::Load();
CategoriesIndex index;
index.AddAllCategoriesInLang(CategoriesHolder::MapLocaleToInteger("en"));
TEST_LESS(index.GetNumTrieNodes(), 15000, ());
}
#endif

View file

@ -0,0 +1,66 @@
#include "testing/testing.hpp"
#include "geometry/covering_utils.hpp"
#include "indexer/cell_coverer.hpp"
#include "indexer/indexer_tests/bounds.hpp"
#include "coding/hex.hpp"
#include "base/logging.hpp"
#include <vector>
using namespace std;
// Unit test uses m2::CellId<30> for historical reasons, the actual production code uses RectId.
using CellId = m2::CellId<30>;
UNIT_TEST(CellIdToStringRecode)
{
char const kTest[] = "21032012203";
TEST_EQUAL(CellId::FromString(kTest).ToString(), kTest, ());
}
UNIT_TEST(GoldenCoverRect)
{
vector<CellId> cells;
CoverRect<OrthoBounds>({27.43, 53.83, 27.70, 53.96}, 4, RectId::DEPTH_LEVELS - 1, cells);
TEST_EQUAL(cells.size(), 4, ());
TEST_EQUAL(cells[0].ToString(), "32012211300", ());
TEST_EQUAL(cells[1].ToString(), "32012211301", ());
TEST_EQUAL(cells[2].ToString(), "32012211302", ());
TEST_EQUAL(cells[3].ToString(), "32012211303", ());
}
UNIT_TEST(ArtificialCoverRect)
{
typedef Bounds<0, 0, 16, 16> TestBounds;
vector<CellId> cells;
CoverRect<TestBounds>({5, 5, 11, 11}, 4, RectId::DEPTH_LEVELS - 1, cells);
TEST_EQUAL(cells.size(), 4, ());
TEST_EQUAL(cells[0].ToString(), "03", ());
TEST_EQUAL(cells[1].ToString(), "12", ());
TEST_EQUAL(cells[2].ToString(), "21", ());
TEST_EQUAL(cells[3].ToString(), "30", ());
}
UNIT_TEST(MaxDepthCoverSpiral)
{
using TestBounds = Bounds<0, 0, 8, 8>;
for (auto levelMax = 0; levelMax <= 2; ++levelMax)
{
auto cells = vector<m2::CellId<3>>{};
CoverSpiral<TestBounds, m2::CellId<3>>({2.1, 4.1, 2.1, 4.1}, levelMax, cells);
TEST_EQUAL(cells.size(), 1, ());
TEST_EQUAL(cells[0].Level(), levelMax, ());
}
}

View file

@ -0,0 +1,54 @@
#include "testing/testing.hpp"
#include "indexer/cell_id.hpp"
#include "indexer/indexer_tests/bounds.hpp"
#include "coding/hex.hpp"
#include <cmath>
#include <random>
#include <string>
#include <utility>
using namespace std;
typedef m2::CellId<30> CellIdT;
UNIT_TEST(ToCellId)
{
string s("2130000");
s.append(CellIdT::DEPTH_LEVELS - 1 - s.size(), '0');
TEST_EQUAL((CellIdConverter<Bounds<0, 0, 4, 4>, CellIdT>::ToCellId(1.5, 2.5).ToString()), s, ());
TEST_EQUAL(CellIdT::FromString(s), (CellIdConverter<Bounds<0, 0, 4, 4>, CellIdT>::ToCellId(1.5, 2.5)), ());
}
UNIT_TEST(CommonCell)
{
TEST_EQUAL((CellIdConverter<Bounds<0, 0, 4, 4>, CellIdT>::Cover2PointsWithCell(3.5, 2.5, 2.5, 3.5)),
CellIdT::FromString("3"), ());
TEST_EQUAL((CellIdConverter<Bounds<0, 0, 4, 4>, CellIdT>::Cover2PointsWithCell(2.25, 1.75, 2.75, 1.25)),
CellIdT::FromString("12"), ());
}
namespace
{
template <typename T1, typename T2>
bool PairsAlmostEqualULPs(pair<T1, T1> const & p1, pair<T2, T2> const & p2)
{
return fabs(p1.first - p2.first) + fabs(p1.second - p2.second) < 0.00001;
}
} // namespace
UNIT_TEST(CellId_RandomRecode)
{
mt19937 rng(0);
for (size_t i = 0; i < 1000; ++i)
{
uint32_t const x = rng() % 2000;
uint32_t const y = rng() % 1000;
m2::PointD const pt = CellIdConverter<Bounds<0, 0, 2000, 1000>, CellIdT>::FromCellId(
CellIdConverter<Bounds<0, 0, 2000, 1000>, CellIdT>::ToCellId(x, y));
TEST(fabs(pt.x - x) < 0.0002, (x, y, pt));
TEST(fabs(pt.y - y) < 0.0001, (x, y, pt));
}
}

Some files were not shown because too many files have changed in this diff Show more