Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
165
libs/indexer/CMakeLists.txt
Normal file
165
libs/indexer/CMakeLists.txt
Normal 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)
|
||||
111
libs/indexer/altitude_loader.cpp
Normal file
111
libs/indexer/altitude_loader.cpp
Normal 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
|
||||
56
libs/indexer/altitude_loader.hpp
Normal file
56
libs/indexer/altitude_loader.hpp
Normal 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
|
||||
139
libs/indexer/brands_holder.cpp
Normal file
139
libs/indexer/brands_holder.cpp
Normal 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
|
||||
93
libs/indexer/brands_holder.hpp
Normal file
93
libs/indexer/brands_holder.hpp
Normal 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
|
||||
45
libs/indexer/caching_rank_table_loader.cpp
Normal file
45
libs/indexer/caching_rank_table_loader.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
libs/indexer/caching_rank_table_loader.hpp
Normal file
32
libs/indexer/caching_rank_table_loader.hpp
Normal 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);
|
||||
};
|
||||
333
libs/indexer/categories_holder.cpp
Normal file
333
libs/indexer/categories_holder.cpp
Normal 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;
|
||||
}
|
||||
208
libs/indexer/categories_holder.hpp
Normal file
208
libs/indexer/categories_holder.hpp
Normal 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();
|
||||
17
libs/indexer/categories_holder_loader.cpp
Normal file
17
libs/indexer/categories_holder_loader.cpp
Normal 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;
|
||||
}
|
||||
123
libs/indexer/categories_index.cpp
Normal file
123
libs/indexer/categories_index.cpp
Normal 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
|
||||
71
libs/indexer/categories_index.hpp
Normal file
71
libs/indexer/categories_index.hpp
Normal 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
|
||||
214
libs/indexer/cell_coverer.hpp
Normal file
214
libs/indexer/cell_coverer.hpp
Normal 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
87
libs/indexer/cell_id.hpp
Normal 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; }
|
||||
};
|
||||
40
libs/indexer/cell_value_pair.hpp
Normal file
40
libs/indexer/cell_value_pair.hpp
Normal 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
|
||||
187
libs/indexer/centers_table.cpp
Normal file
187
libs/indexer/centers_table.cpp
Normal 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
|
||||
96
libs/indexer/centers_table.hpp
Normal file
96
libs/indexer/centers_table.hpp
Normal 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
|
||||
390
libs/indexer/cities_boundaries_serdes.hpp
Normal file
390
libs/indexer/cities_boundaries_serdes.hpp
Normal 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
|
||||
45
libs/indexer/city_boundary.hpp
Normal file
45
libs/indexer/city_boundary.hpp
Normal 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
|
||||
458
libs/indexer/classificator.cpp
Normal file
458
libs/indexer/classificator.cpp
Normal 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;
|
||||
}
|
||||
263
libs/indexer/classificator.hpp
Normal file
263
libs/indexer/classificator.hpp
Normal 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();
|
||||
75
libs/indexer/classificator_loader.cpp
Normal file
75
libs/indexer/classificator_loader.cpp
Normal 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
|
||||
13
libs/indexer/classificator_loader.hpp
Normal file
13
libs/indexer/classificator_loader.hpp
Normal 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
|
||||
7
libs/indexer/complex/serdes.cpp
Normal file
7
libs/indexer/complex/serdes.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#include "indexer/complex/serdes.hpp"
|
||||
|
||||
namespace complex
|
||||
{
|
||||
// static
|
||||
ComplexSerdes::Version const ComplexSerdes::kLatestVersion = Version::V0;
|
||||
} // namespace complex
|
||||
134
libs/indexer/complex/serdes.hpp
Normal file
134
libs/indexer/complex/serdes.hpp
Normal 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
|
||||
34
libs/indexer/complex/serdes_utils.hpp
Normal file
34
libs/indexer/complex/serdes_utils.hpp
Normal 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
|
||||
308
libs/indexer/complex/tree_node.hpp
Normal file
308
libs/indexer/complex/tree_node.hpp
Normal 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
55
libs/indexer/cuisines.cpp
Normal 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
24
libs/indexer/cuisines.hpp
Normal 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
|
||||
46
libs/indexer/custom_keyvalue.cpp
Normal file
46
libs/indexer/custom_keyvalue.cpp
Normal 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
|
||||
42
libs/indexer/custom_keyvalue.hpp
Normal file
42
libs/indexer/custom_keyvalue.hpp
Normal 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
|
||||
49
libs/indexer/dat_section_header.hpp
Normal file
49
libs/indexer/dat_section_header.hpp
Normal 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
|
||||
136
libs/indexer/data_header.cpp
Normal file
136
libs/indexer/data_header.cpp
Normal 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
|
||||
80
libs/indexer/data_header.hpp
Normal file
80
libs/indexer/data_header.hpp
Normal 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
|
||||
311
libs/indexer/data_source.cpp
Normal file
311
libs/indexer/data_source.cpp
Normal 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)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
109
libs/indexer/data_source.hpp
Normal file
109
libs/indexer/data_source.hpp
Normal 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;
|
||||
};
|
||||
47
libs/indexer/data_source_helpers.cpp
Normal file
47
libs/indexer/data_source_helpers.cpp
Normal 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
|
||||
14
libs/indexer/data_source_helpers.hpp
Normal file
14
libs/indexer/data_source_helpers.hpp
Normal 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);
|
||||
}
|
||||
212
libs/indexer/displacement_manager.hpp
Normal file
212
libs/indexer/displacement_manager.hpp
Normal 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
|
||||
78
libs/indexer/drawing_rule_def.cpp
Normal file
78
libs/indexer/drawing_rule_def.cpp
Normal 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
|
||||
91
libs/indexer/drawing_rule_def.hpp
Normal file
91
libs/indexer/drawing_rule_def.hpp
Normal 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
|
||||
419
libs/indexer/drawing_rules.cpp
Normal file
419
libs/indexer/drawing_rules.cpp
Normal 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
|
||||
95
libs/indexer/drawing_rules.hpp
Normal file
95
libs/indexer/drawing_rules.hpp
Normal 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
|
||||
15
libs/indexer/drules_include.hpp
Normal file
15
libs/indexer/drules_include.hpp
Normal 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)
|
||||
247
libs/indexer/drules_selector.cpp
Normal file
247
libs/indexer/drules_selector.cpp
Normal 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
|
||||
29
libs/indexer/drules_selector.hpp
Normal file
29
libs/indexer/drules_selector.hpp
Normal 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
|
||||
123
libs/indexer/drules_selector_parser.cpp
Normal file
123
libs/indexer/drules_selector_parser.cpp
Normal 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
|
||||
48
libs/indexer/drules_selector_parser.hpp
Normal file
48
libs/indexer/drules_selector_parser.hpp
Normal 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
|
||||
6708
libs/indexer/drules_struct.pb.cc
Normal file
6708
libs/indexer/drules_struct.pb.cc
Normal file
File diff suppressed because it is too large
Load diff
3672
libs/indexer/drules_struct.pb.h
Normal file
3672
libs/indexer/drules_struct.pb.h
Normal file
File diff suppressed because it is too large
Load diff
147
libs/indexer/drules_struct.proto
Normal file
147
libs/indexer/drules_struct.proto
Normal 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;
|
||||
}
|
||||
152
libs/indexer/edit_journal.cpp
Normal file
152
libs/indexer/edit_journal.cpp
Normal 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
|
||||
99
libs/indexer/edit_journal.hpp
Normal file
99
libs/indexer/edit_journal.hpp
Normal 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
|
||||
968
libs/indexer/editable_map_object.cpp
Normal file
968
libs/indexer/editable_map_object.cpp
Normal 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
|
||||
172
libs/indexer/editable_map_object.hpp
Normal file
172
libs/indexer/editable_map_object.hpp
Normal 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
|
||||
9
libs/indexer/fake_feature_ids.cpp
Normal file
9
libs/indexer/fake_feature_ids.cpp
Normal 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
|
||||
18
libs/indexer/fake_feature_ids.hpp
Normal file
18
libs/indexer/fake_feature_ids.hpp
Normal 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
909
libs/indexer/feature.cpp
Normal 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
274
libs/indexer/feature.hpp
Normal 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);
|
||||
};
|
||||
119
libs/indexer/feature_algo.cpp
Normal file
119
libs/indexer/feature_algo.cpp
Normal 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
|
||||
30
libs/indexer/feature_algo.hpp
Normal file
30
libs/indexer/feature_algo.hpp
Normal 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
|
||||
124
libs/indexer/feature_altitude.hpp
Normal file
124
libs/indexer/feature_altitude.hpp
Normal 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
|
||||
445
libs/indexer/feature_charge_sockets.cpp
Normal file
445
libs/indexer/feature_charge_sockets.cpp
Normal 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;
|
||||
}
|
||||
121
libs/indexer/feature_charge_sockets.hpp
Normal file
121
libs/indexer/feature_charge_sockets.hpp
Normal 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;
|
||||
};
|
||||
167
libs/indexer/feature_covering.cpp
Normal file
167
libs/indexer/feature_covering.cpp
Normal 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
|
||||
166
libs/indexer/feature_covering.hpp
Normal file
166
libs/indexer/feature_covering.hpp
Normal 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
|
||||
598
libs/indexer/feature_data.cpp
Normal file
598
libs/indexer/feature_data.cpp
Normal 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();
|
||||
}
|
||||
387
libs/indexer/feature_data.hpp
Normal file
387
libs/indexer/feature_data.hpp
Normal 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;
|
||||
};
|
||||
77
libs/indexer/feature_decl.cpp
Normal file
77
libs/indexer/feature_decl.cpp
Normal 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;
|
||||
}
|
||||
65
libs/indexer/feature_decl.hpp
Normal file
65
libs/indexer/feature_decl.hpp
Normal 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
|
||||
19
libs/indexer/feature_impl.cpp
Normal file
19
libs/indexer/feature_impl.cpp
Normal 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
|
||||
28
libs/indexer/feature_impl.hpp
Normal file
28
libs/indexer/feature_impl.hpp
Normal 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
|
||||
361
libs/indexer/feature_meta.cpp
Normal file
361
libs/indexer/feature_meta.cpp
Normal 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
|
||||
258
libs/indexer/feature_meta.hpp
Normal file
258
libs/indexer/feature_meta.hpp
Normal 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
|
||||
31
libs/indexer/feature_processor.hpp
Normal file
31
libs/indexer/feature_processor.hpp
Normal 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
|
||||
56
libs/indexer/feature_source.cpp
Normal file
56
libs/indexer/feature_source.cpp
Normal 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
|
||||
{}
|
||||
65
libs/indexer/feature_source.hpp
Normal file
65
libs/indexer/feature_source.hpp
Normal 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);
|
||||
}
|
||||
};
|
||||
158
libs/indexer/feature_to_osm.cpp
Normal file
158
libs/indexer/feature_to_osm.cpp
Normal 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
|
||||
358
libs/indexer/feature_to_osm.hpp
Normal file
358
libs/indexer/feature_to_osm.hpp
Normal 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
|
||||
596
libs/indexer/feature_utils.cpp
Normal file
596
libs/indexer/feature_utils.cpp
Normal 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
|
||||
188
libs/indexer/feature_utils.hpp
Normal file
188
libs/indexer/feature_utils.hpp
Normal 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
|
||||
449
libs/indexer/feature_visibility.cpp
Normal file
449
libs/indexer/feature_visibility.cpp
Normal 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
|
||||
90
libs/indexer/feature_visibility.hpp
Normal file
90
libs/indexer/feature_visibility.hpp
Normal 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
|
||||
138
libs/indexer/features_offsets_table.cpp
Normal file
138
libs/indexer/features_offsets_table.cpp
Normal 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
|
||||
113
libs/indexer/features_offsets_table.hpp
Normal file
113
libs/indexer/features_offsets_table.hpp
Normal 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
|
||||
64
libs/indexer/features_vector.cpp
Normal file
64
libs/indexer/features_vector.cpp
Normal 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;
|
||||
}
|
||||
93
libs/indexer/features_vector.hpp
Normal file
93
libs/indexer/features_vector.hpp
Normal 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
124
libs/indexer/ftraits.hpp
Normal 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
|
||||
63
libs/indexer/ftypes_mapping.hpp
Normal file
63
libs/indexer/ftypes_mapping.hpp
Normal 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
|
||||
936
libs/indexer/ftypes_matcher.cpp
Normal file
936
libs/indexer/ftypes_matcher.cpp
Normal 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
|
||||
728
libs/indexer/ftypes_matcher.hpp
Normal file
728
libs/indexer/ftypes_matcher.hpp
Normal 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
|
||||
61
libs/indexer/house_to_street_iface.hpp
Normal file
61
libs/indexer/house_to_street_iface.hpp
Normal 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;
|
||||
};
|
||||
39
libs/indexer/index_builder.cpp
Normal file
39
libs/indexer/index_builder.cpp
Normal 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
|
||||
26
libs/indexer/index_builder.hpp
Normal file
26
libs/indexer/index_builder.hpp
Normal 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
|
||||
54
libs/indexer/indexer_tests/CMakeLists.txt
Normal file
54
libs/indexer/indexer_tests/CMakeLists.txt
Normal 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
|
||||
)
|
||||
18
libs/indexer/indexer_tests/bounds.hpp
Normal file
18
libs/indexer/indexer_tests/bounds.hpp
Normal 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>;
|
||||
89
libs/indexer/indexer_tests/brands_tests.cpp
Normal file
89
libs/indexer/indexer_tests/brands_tests.cpp
Normal 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, ());
|
||||
}
|
||||
}
|
||||
328
libs/indexer/indexer_tests/categories_test.cpp
Normal file
328
libs/indexer/indexer_tests/categories_test.cpp
Normal 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
|
||||
66
libs/indexer/indexer_tests/cell_coverer_test.cpp
Normal file
66
libs/indexer/indexer_tests/cell_coverer_test.cpp
Normal 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, ());
|
||||
}
|
||||
}
|
||||
54
libs/indexer/indexer_tests/cell_id_test.cpp
Normal file
54
libs/indexer/indexer_tests/cell_id_test.cpp
Normal 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
Loading…
Add table
Add a link
Reference in a new issue