#include "indexer/feature_data.hpp" #include "indexer/classificator.hpp" #include "indexer/feature.hpp" #include "indexer/feature_impl.hpp" #include "indexer/feature_visibility.hpp" #include "indexer/ftypes_matcher.hpp" #include "base/assert.hpp" #include "base/macros.hpp" #include "base/stl_add.hpp" #include "base/string_utils.hpp" #include #include #include using namespace feature; using namespace std; using namespace std::placeholders; //////////////////////////////////////////////////////////////////////////////////// // TypesHolder implementation //////////////////////////////////////////////////////////////////////////////////// namespace feature { string DebugPrint(TypesHolder 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; } TypesHolder::TypesHolder(FeatureType & f) : m_size(0), m_geoType(f.GetFeatureType()) { f.ForEachType([this](uint32_t type) { Add(type); }); } void TypesHolder::Remove(uint32_t type) { UNUSED_VALUE(RemoveIf(EqualFunctor(type))); } bool TypesHolder::Equals(TypesHolder const & other) const { vector my(this->begin(), this->end()); vector his(other.begin(), other.end()); sort(::begin(my), ::end(my)); sort(::begin(his), ::end(his)); return my == his; } } // namespace feature namespace { class UselessTypesChecker { vector m_types; size_t m_count1; template void AddTypes(char const * (&arr)[N][M]) { Classificator const & c = classif(); for (size_t i = 0; i < N; ++i) m_types.push_back(c.GetTypeByPath(vector(arr[i], arr[i] + M))); } public: UselessTypesChecker() { // Fill types that will be taken into account last, // when we have many types for POI. // 1-arity char const * arr1[][1] = { { "building" }, { "building:part" }, { "hwtag" }, { "psurface" }, { "internet_access" }, { "wheelchair" }, { "sponsored" }, { "entrance" }, }; AddTypes(arr1); m_count1 = m_types.size(); // 2-arity char const * arr2[][2] = { { "amenity", "atm" }, { "amenity", "bench" }, { "amenity", "shelter" }, { "amenity", "toilets" }, { "building", "address" }, { "building", "has_parts" }, }; AddTypes(arr2); } bool operator() (uint32_t t) const { auto const end1 = m_types.begin() + m_count1; // check 2-arity types ftype::TruncValue(t, 2); if (find(end1, m_types.end(), t) != m_types.end()) return true; // check 1-arity types ftype::TruncValue(t, 1); if (find(m_types.begin(), end1, t) != end1) return true; return false; } }; } // namespace namespace feature { uint8_t CalculateHeader(size_t const typesCount, uint8_t const headerGeomType, FeatureParamsBase const & params) { ASSERT(typesCount != 0, ("Feature should have at least one type.")); uint8_t header = static_cast(typesCount - 1); if (!params.name.IsEmpty()) header |= HEADER_HAS_NAME; if (params.layer != 0) header |= HEADER_HAS_LAYER; header |= headerGeomType; // Geometry type for additional info is only one. switch (headerGeomType) { case HEADER_GEOM_POINT: if (params.rank != 0) header |= HEADER_HAS_ADDINFO; break; case HEADER_GEOM_LINE: if (!params.ref.empty()) header |= HEADER_HAS_ADDINFO; break; case HEADER_GEOM_AREA: case HEADER_GEOM_POINT_EX: if (!params.house.IsEmpty()) header |= HEADER_HAS_ADDINFO; break; } return header; } void TypesHolder::SortBySpec() { if (m_size < 2) return; // Put "very common" types to the end of possible PP-description types. static UselessTypesChecker checker; UNUSED_VALUE(RemoveIfKeepValid(begin(), end(), [](uint32_t t) { return checker(t); })); } vector TypesHolder::ToObjectNames() const { vector result; for (auto const type : *this) result.push_back(classif().GetReadableObjectName(type)); return result; } } // namespace feature //////////////////////////////////////////////////////////////////////////////////// // FeatureParamsBase implementation //////////////////////////////////////////////////////////////////////////////////// void FeatureParamsBase::MakeZero() { layer = 0; rank = 0; ref.clear(); house.Clear(); name.Clear(); } 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::CheckValid() const { CHECK(layer > LAYER_LOW && layer < LAYER_HIGH, ()); return true; } string FeatureParamsBase::DebugString() const { string const utf8name = DebugPrint(name); return ((!utf8name.empty() ? "Name:" + utf8name : "") + (rank != 0 ? " Rank:" + DebugPrint(rank) : "") + (!house.IsEmpty() ? " House:" + house.Get() : "") + (!ref.empty() ? " Ref:" + ref : "")); } bool FeatureParamsBase::IsEmptyNames() const { return name.IsEmpty() && house.IsEmpty() && ref.empty(); } namespace { // Most used dummy values are taken from // https://taginfo.openstreetmap.org/keys/addr%3Ahousename#values bool IsDummyName(string const & s) { return (s.empty() || s == "Bloc" || s == "bloc" || s == "жилой дом" || s == "Edificio" || s == "edificio"); } } ///////////////////////////////////////////////////////////////////////////////////////// // FeatureParams implementation ///////////////////////////////////////////////////////////////////////////////////////// void FeatureParams::ClearName() { name.Clear(); } bool FeatureParams::AddName(string const & lang, string const & s) { if (IsDummyName(s)) return false; // The "default" new name will replace the old one if any (e.g. from AddHouseName call). name.AddString(lang, s); return true; } bool FeatureParams::AddHouseName(string const & s) { if (IsDummyName(s) || name.FindString(s) != StringUtf8Multilang::kUnsupportedLanguageCode) return false; // Most names are house numbers by statistics. if (house.IsEmpty() && AddHouseNumber(s)) return true; // If we got a clear number, replace the house number with it. // Example: housename=16th Street, housenumber=34 if (strings::is_number(s)) { string housename(house.Get()); if (AddHouseNumber(s)) { // Duplicating code to avoid changing the method header. string dummy; if (!name.GetString(StringUtf8Multilang::kDefaultCode, dummy)) name.AddString(StringUtf8Multilang::kDefaultCode, housename); return true; } } // Add as a default name if we don't have it yet. string dummy; if (!name.GetString(StringUtf8Multilang::kDefaultCode, dummy)) { name.AddString(StringUtf8Multilang::kDefaultCode, s); return true; } return false; } bool FeatureParams::AddHouseNumber(string houseNumber) { ASSERT(!houseNumber.empty(), ("This check should be done by the caller.")); ASSERT_NOT_EQUAL(houseNumber.front(), ' ', ("Trim should be done by the caller.")); // Negative house numbers are not supported. if (houseNumber.front() == '-' || houseNumber.find(u8"-") == 0) return false; // Replace full-width digits, mostly in Japan, by ascii-ones. strings::NormalizeDigits(houseNumber); // Remove leading zeroes from house numbers. // It's important for debug checks of serialized-deserialized feature. size_t i = 0; while (i + 1 < houseNumber.size() && houseNumber[i] == '0') ++i; houseNumber.erase(0, i); if (any_of(houseNumber.cbegin(), houseNumber.cend(), IsDigit)) { house.Set(houseNumber); return true; } return false; } void FeatureParams::AddStreet(string s) { // Replace \n with spaces because we write addresses to txt file. replace(s.begin(), s.end(), '\n', ' '); m_addrTags.Add(AddressData::STREET, s); } void FeatureParams::AddAddress(string const & s) { size_t i = s.find_first_of("\t "); if (i != string::npos) { string const house = s.substr(0, i); if (feature::IsHouseNumber(house)) { AddHouseNumber(house); i = s.find_first_not_of("\t ", i); } else { i = 0; } } else { i = 0; } AddStreet(s.substr(i)); } void FeatureParams::AddPlace(string const & s) { m_addrTags.Add(AddressData::PLACE, s); } void FeatureParams::AddPostcode(string const & s) { m_addrTags.Add(AddressData::POSTCODE, s); } bool FeatureParams::FormatFullAddress(m2::PointD const & pt, string & res) const { string const street = GetStreet(); if (!street.empty() && !house.IsEmpty()) { res = street + "|" + house.Get() + "|" + strings::to_string_dac(MercatorBounds::YToLat(pt.y), 8) + "|" + strings::to_string_dac(MercatorBounds::XToLon(pt.x), 8) + '\n'; return true; } return false; } string FeatureParams::GetStreet() const { return m_addrTags.Get(AddressData::STREET); } void FeatureParams::SetGeomType(feature::EGeomType t) { switch (t) { case GEOM_POINT: m_geomType = HEADER_GEOM_POINT; break; case GEOM_LINE: m_geomType = HEADER_GEOM_LINE; break; case GEOM_AREA: m_geomType = HEADER_GEOM_AREA; break; default: ASSERT(false, ()); } } void FeatureParams::SetGeomTypePointEx() { ASSERT(m_geomType == HEADER_GEOM_POINT || m_geomType == HEADER_GEOM_POINT_EX, ()); ASSERT(!house.IsEmpty(), ()); m_geomType = HEADER_GEOM_POINT_EX; } feature::EGeomType FeatureParams::GetGeomType() const { CHECK_NOT_EQUAL(m_geomType, 0xFF, ()); switch (m_geomType) { case HEADER_GEOM_LINE: return GEOM_LINE; case HEADER_GEOM_AREA: return GEOM_AREA; default: return GEOM_POINT; } } uint8_t FeatureParams::GetTypeMask() const { CHECK_NOT_EQUAL(m_geomType, 0xFF, ()); 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) { uint32_t t = m_types[i]; ftype::TruncValue(t, 2); if (t == src) { m_types[i] = dest; break; } } } void FeatureParams::AddTypes(FeatureParams const & rhs, uint32_t skipType2) { if (skipType2 == 0) { m_types.insert(m_types.end(), rhs.m_types.begin(), rhs.m_types.end()); } else { for (size_t i = 0; i < rhs.m_types.size(); ++i) { uint32_t t = rhs.m_types[i]; ftype::TruncValue(t, 2); if (t != skipType2) m_types.push_back(rhs.m_types[i]); } } } bool FeatureParams::FinishAddingTypes() { static uint32_t const boundary = classif().GetTypeByPath({ "boundary", "administrative" }); vector newTypes; newTypes.reserve(m_types.size()); for (size_t i = 0; i < m_types.size(); ++i) { uint32_t candidate = m_types[i]; // Assume that classificator types are equal if they are equal for 2-arity dimension // (e.g. "place-city-capital" is equal to "place-city" and we leave the longest one "place-city-capital"). // The only exception is "boundary-administrative" type. uint32_t type = m_types[i]; ftype::TruncValue(type, 2); if (type != boundary) { // Find all equal types (2-arity). auto j = RemoveIfKeepValid(m_types.begin() + i + 1, m_types.end(), [type] (uint32_t t) { ftype::TruncValue(t, 2); return (type == t); }); // Choose the best type from equals by arity level. for (auto k = j; k != m_types.end(); ++k) if (ftype::GetLevel(*k) > ftype::GetLevel(candidate)) candidate = *k; // Delete equal types. m_types.erase(j, m_types.end()); } newTypes.push_back(candidate); } // Remove duplicated types. sort(newTypes.begin(), newTypes.end()); newTypes.erase(unique(newTypes.begin(), newTypes.end()), newTypes.end()); m_types.swap(newTypes); if (m_types.size() > kMaxTypesCount) m_types.resize(kMaxTypesCount); // Patch fix that removes house number from localities. if (!house.IsEmpty() && ftypes::IsLocalityChecker::Instance()(m_types)) { LOG(LINFO, ("Locality with house number", *this)); house.Clear(); } return !m_types.empty(); } 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 (find(m_types.begin(), m_types.end(), t) != m_types.end()); } uint32_t FeatureParams::FindType(uint32_t comp, uint8_t level) const { for (uint32_t const type : m_types) { uint32_t t = type; ftype::TruncValue(t, level); if (t == comp) return type; } return ftype::GetEmptyValue(); } bool FeatureParams::CheckValid() const { CHECK(!m_types.empty() && m_types.size() <= kMaxTypesCount, ()); CHECK_NOT_EQUAL(m_geomType, 0xFF, ()); return FeatureParamsBase::CheckValid(); } uint8_t FeatureParams::GetHeader() const { return CalculateHeader(m_types.size(), GetTypeMask(), *this); } uint32_t FeatureParams::GetIndexForType(uint32_t t) { return classif().GetIndexForType(t); } uint32_t FeatureParams::GetTypeForIndex(uint32_t i) { return classif().GetTypeForIndex(i); } string DebugPrint(FeatureParams const & p) { Classificator const & c = classif(); string res = "Types: "; for (size_t i = 0; i < p.m_types.size(); ++i) res = res + c.GetReadableObjectName(p.m_types[i]) + "; "; return (res + p.DebugString()); }