diff --git a/data/osm_test_data/named_address.osm b/data/osm_test_data/named_address.osm
new file mode 100644
index 0000000000..62f044edd8
--- /dev/null
+++ b/data/osm_test_data/named_address.osm
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/generator/feature_builder.cpp b/generator/feature_builder.cpp
index c7d215416b..3d0842ff7c 100644
--- a/generator/feature_builder.cpp
+++ b/generator/feature_builder.cpp
@@ -197,6 +197,23 @@ bool FeatureBuilder::PreSerialize()
if (!m_params.IsValid())
return false;
+ auto const checkHouseNumber = [this]()
+ {
+ if (!m_params.house.IsEmpty())
+ {
+ // Hack/Patch here. Convert non-number into default name for the search index.
+ // Happens with building-address: https://github.com/organicmaps/organicmaps/issues/4994
+ /// @todo Refactor to store raw name: and addr: values in FeatureBuilderParams and make one
+ /// _finalization_ function here.
+ auto const & hn = m_params.house.Get();
+ if (FeatureParams::LooksLikeHouseNumber(hn) || !m_params.SetDefaultNameIfEmpty(hn))
+ return true;
+ else
+ m_params.house.Clear();
+ }
+ return false;
+ };
+
// Conform serialization logic (see HeaderMask::HEADER_MASK_HAS_ADDINFO):
// - rank (city) is stored only for Point
// - ref (road number, address range) is stored only for Line
@@ -204,9 +221,9 @@ bool FeatureBuilder::PreSerialize()
switch (m_params.GetGeomType())
{
case GeomType::Point:
- // Store house number like HeaderGeomType::PointEx.
- if (!m_params.house.IsEmpty())
+ if (checkHouseNumber())
{
+ // Store house number like HeaderGeomType::PointEx.
m_params.SetGeomTypePointEx();
m_params.rank = 0;
}
@@ -242,8 +259,22 @@ bool FeatureBuilder::PreSerialize()
}
case GeomType::Area:
+ checkHouseNumber();
+
+ if (!m_params.ref.empty())
+ {
+ auto const & types = GetTypes();
+ if (m_params.name.IsEmpty() &&
+ (ftypes::IsRailwayStationChecker::Instance()(types) ||
+ ftypes::IsPlatformChecker::Instance()(types)))
+ {
+ m_params.name.AddString(StringUtf8Multilang::kDefaultCode, m_params.ref);
+ }
+
+ m_params.ref.clear();
+ }
+
m_params.rank = 0;
- m_params.ref.clear();
break;
default:
diff --git a/generator/generator_tests/feature_builder_test.cpp b/generator/generator_tests/feature_builder_test.cpp
index 205bdf2f69..8df07f2b0d 100644
--- a/generator/generator_tests/feature_builder_test.cpp
+++ b/generator/generator_tests/feature_builder_test.cpp
@@ -38,8 +38,7 @@ UNIT_CLASS_TEST(TestWithClassificator, FBuilder_ManyTypes)
AddTypes(params, arr);
params.FinishAddingTypes();
- params.AddHouseNumber("75");
- params.AddHouseName("Best House");
+ params.SetHouseNumberAndHouseName("75", "Best House");
params.AddName("default", "Name");
fb1.SetParams(params);
@@ -224,8 +223,7 @@ UNIT_CLASS_TEST(TestWithClassificator, FBuilder_SerializeLocalityObjectForBuildi
AddTypes(params, arr);
params.FinishAddingTypes();
- params.AddHouseNumber("75");
- params.AddHouseName("Best House");
+ params.SetHouseNumberAndHouseName("75", "Best House");
params.AddName("default", "Name");
fb.AddOsmId(base::MakeOsmNode(1));
@@ -244,6 +242,44 @@ UNIT_CLASS_TEST(TestWithClassificator, FBuilder_SerializeLocalityObjectForBuildi
TEST(fb.PreSerializeAndRemoveUselessNamesForMwm(buffer), ());
}
+UNIT_TEST(LooksLikeHouseNumber)
+{
+ TEST(FeatureParams::LooksLikeHouseNumber("1 bis"), ());
+ TEST(FeatureParams::LooksLikeHouseNumber("18-20"), ());
+
+ // Brno (Czech) has a lot of fancy samples.
+ TEST(FeatureParams::LooksLikeHouseNumber("ev.8"), ());
+ TEST(FeatureParams::LooksLikeHouseNumber("D"), ());
+ TEST(FeatureParams::LooksLikeHouseNumber("A5"), ());
+
+ TEST(!FeatureParams::LooksLikeHouseNumber("Building 2"), ());
+ TEST(!FeatureParams::LooksLikeHouseNumber("Unit 3"), ());
+}
+
+UNIT_CLASS_TEST(TestWithClassificator, FBuilder_HouseName)
+{
+ FeatureBuilder fb;
+ FeatureBuilderParams params;
+
+ base::StringIL arr[] = {{ "building" }};
+ AddTypes(params, arr);
+ params.FinishAddingTypes();
+
+ params.SetHouseNumberAndHouseName("", "St. Nicholas Lodge");
+
+ fb.SetParams(params);
+ fb.AssignArea({{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}}, {});
+ fb.SetArea();
+
+ TEST(fb.RemoveInvalidTypes(), ());
+ TEST(fb.IsValid(), ());
+
+ TEST(fb.PreSerializeAndRemoveUselessNamesForIntermediate(), ());
+ TEST(fb.IsValid(), ());
+ TEST_EQUAL(fb.GetName(StringUtf8Multilang::kDefaultCode), "St. Nicholas Lodge", ());
+ TEST(fb.GetParams().house.IsEmpty(), ());
+}
+
UNIT_CLASS_TEST(TestWithClassificator, FBuilder_SerializeAccuratelyForIntermediate)
{
FeatureBuilder fb1;
diff --git a/generator/generator_tests/osm_type_test.cpp b/generator/generator_tests/osm_type_test.cpp
index 707585d047..308501a785 100644
--- a/generator/generator_tests/osm_type_test.cpp
+++ b/generator/generator_tests/osm_type_test.cpp
@@ -243,6 +243,27 @@ UNIT_CLASS_TEST(TestWithClassificator, OsmType_Address)
TEST_EQUAL(params.GetAddressData().Get(AddrType::Street), "Leutschenbachstrasse", ());
TEST_EQUAL(params.GetAddressData().Get(AddrType::Postcode), "8050", ());
}
+
+ {
+ Tags const tags = {
+ {"addr:city", "Šķaune"},
+ {"addr:country", "LV"},
+ {"addr:district", "Krāslavas novads"},
+ {"addr:housename", "Rozemnieki"},
+ {"addr:postcode", "LV-5695"},
+ {"addr:subdistrict", "Šķaunes pagasts"},
+ {"ref:LV:addr", "104934702"},
+ };
+
+ auto const params = GetFeatureBuilderParams(tags);
+
+ TEST_EQUAL(params.m_types.size(), 1, (params));
+ TEST(params.IsTypeExist(addrType), ());
+
+ TEST_EQUAL(params.house.Get(), "Rozemnieki", ());
+ TEST(params.GetAddressData().Get(AddrType::Street).empty(), ());
+ TEST_EQUAL(params.GetAddressData().Get(AddrType::Postcode), "LV-5695", ());
+ }
}
UNIT_CLASS_TEST(TestWithClassificator, OsmType_PlaceState)
diff --git a/generator/generator_tests/raw_generator_test.cpp b/generator/generator_tests/raw_generator_test.cpp
index fb6d856f28..33831544a2 100644
--- a/generator/generator_tests/raw_generator_test.cpp
+++ b/generator/generator_tests/raw_generator_test.cpp
@@ -904,4 +904,33 @@ UNIT_CLASS_TEST(TestRawGenerator, Addr_Interpolation)
TEST_EQUAL(count, 1, ());
}
+// https://github.com/organicmaps/organicmaps/issues/4994
+UNIT_CLASS_TEST(TestRawGenerator, NamedAddress)
+{
+ std::string const mwmName = "Address";
+
+ BuildFB("./data/osm_test_data/named_address.osm", mwmName);
+
+ uint32_t const addrType = classif().GetTypeByPath({"building", "address"});
+
+ size_t withName = 0, withNumber = 0;
+ ForEachFB(mwmName, [&](feature::FeatureBuilder const & fb)
+ {
+ TEST_EQUAL(fb.GetGeomType(), feature::GeomType::Point, ());
+ TEST(fb.HasType(addrType), ());
+
+ TEST(fb.GetParams().house.IsEmpty() != fb.GetName().empty(), ());
+ if (fb.GetParams().house.IsEmpty())
+ ++withName;
+ else
+ ++withNumber;
+
+ TEST(fb.GetAddressData().Get(feature::AddressData::Type::Street).empty(), ());
+ TEST_EQUAL(fb.GetAddressData().Get(feature::AddressData::Type::Postcode), "LV-5695", ());
+ });
+
+ TEST_EQUAL(withName, 3, ());
+ TEST_EQUAL(withNumber, 0, ());
+}
+
} // namespace raw_generator_tests
diff --git a/generator/osm2type.cpp b/generator/osm2type.cpp
index d3714c811e..685a2f6494 100644
--- a/generator/osm2type.cpp
+++ b/generator/osm2type.cpp
@@ -1069,16 +1069,17 @@ void GetNameAndType(OsmElement * p, FeatureBuilderParams & params,
namesExtractor.Finish();
// Stage3: Process base feature tags.
+ std::string houseName, houseNumber;
TagProcessor(p).ApplyRules({
{"addr:housenumber", "*",
- [¶ms](string & k, string & v) {
- params.AddHouseName(v);
+ [&houseNumber](string & k, string & v) {
+ houseNumber = std::move(v);
k.clear();
v.clear();
}},
{"addr:housename", "*",
- [¶ms](string & k, string & v) {
- params.AddHouseName(v);
+ [&houseName](string & k, string & v) {
+ houseName = std::move(v);
k.clear();
v.clear();
}},
@@ -1119,6 +1120,8 @@ void GetNameAndType(OsmElement * p, FeatureBuilderParams & params,
}},
});
+ params.SetHouseNumberAndHouseName(std::move(houseNumber), std::move(houseName));
+
// Stage4: Match tags to classificator feature types via mapcss-mapping.csv.
MatchTypes(p, params, filterType);
diff --git a/indexer/feature_data.cpp b/indexer/feature_data.cpp
index 6bfe70358d..cfa29a6262 100644
--- a/indexer/feature_data.cpp
+++ b/indexer/feature_data.cpp
@@ -241,6 +241,16 @@ void FeatureParamsBase::MakeZero()
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 &&
@@ -296,49 +306,55 @@ bool FeatureParams::AddName(string_view lang, string_view s)
return true;
}
-bool FeatureParams::AddHouseName(string const & s)
+bool FeatureParams::LooksLikeHouseNumber(std::string const & hn)
{
- if (IsDummyName(s) || name.FindString(s) != StringUtf8Multilang::kUnsupportedLanguageCode)
- return false;
+ // Very naive implementation to _lightly_ promote hn -> name (for search index) if suitable.
+ /// @todo Conform with search::LooksLikeHouseNumber.
- // Most names are house numbers by statistics.
- if (house.IsEmpty() && AddHouseNumber(s))
- return true;
+ 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;
+}
- // If we got a clear number, replace the house number with it.
- // Example: housename=16th Street, housenumber=34
- if (strings::IsASCIINumeric(s))
+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
{
- string housename(house.Get());
- if (AddHouseNumber(s))
+ if (!LooksLikeHouseNumber(houseNumber) && LooksLikeHouseNumber(houseName))
{
- // Duplicating code to avoid changing the method header.
- string_view dummy;
- if (!name.GetString(StringUtf8Multilang::kDefaultCode, dummy))
- name.AddString(StringUtf8Multilang::kDefaultCode, housename);
- return true;
+ LOG(LWARNING, (kHNLogTag, "Fancy house name:", houseName, "and number:", houseNumber));
+ houseNumber.swap(houseName);
}
- }
- // Add as a default name if we don't have it yet.
- string_view dummy;
- if (!name.GetString(StringUtf8Multilang::kDefaultCode, dummy))
- {
- name.AddString(StringUtf8Multilang::kDefaultCode, s);
- return true;
+ AddHouseNumber(std::move(houseNumber));
+ SetDefaultNameIfEmpty(houseName);
}
-
- 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."));
+ 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);
@@ -350,12 +366,10 @@ bool FeatureParams::AddHouseNumber(string houseNumber)
++i;
houseNumber.erase(0, i);
- if (any_of(houseNumber.cbegin(), houseNumber.cend(), &strings::IsASCIIDigit))
- {
- house.Set(houseNumber);
- return true;
- }
- return false;
+ // 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)
diff --git a/indexer/feature_data.hpp b/indexer/feature_data.hpp
index 10d0a75b84..8929212850 100644
--- a/indexer/feature_data.hpp
+++ b/indexer/feature_data.hpp
@@ -154,6 +154,7 @@ struct FeatureParamsBase
FeatureParamsBase() : layer(feature::LAYER_EMPTY), rank(0) {}
void MakeZero();
+ bool SetDefaultNameIfEmpty(std::string const & s);
bool operator == (FeatureParamsBase const & rhs) const;
@@ -226,13 +227,17 @@ struct FeatureParamsBase
class FeatureParams : public FeatureParamsBase
{
+ static char const * kHNLogTag;
+
public:
using Types = std::vector;
void ClearName();
bool AddName(std::string_view lang, std::string_view s);
- bool AddHouseName(std::string const & 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);