From 7fd1ee481ef6c327b0af760752648a86d542aeae Mon Sep 17 00:00:00 2001 From: Maksim Andrianov Date: Tue, 11 Sep 2018 16:12:19 +0300 Subject: [PATCH] [generator] Improved processing of city boundaries in region kv --- base/geo_object_id.hpp | 2 + generator/CMakeLists.txt | 15 +- generator/feature_builder.hpp | 1 + .../region_info_collector_tests.cpp | 32 +- generator/generator_tests/regions_tests.cpp | 5 +- generator/generator_tool/generator_tool.cpp | 2 +- generator/osm_source.cpp | 6 +- generator/regions.cpp | 743 +++--------------- generator/regions.hpp | 115 +-- generator/regions/city.hpp | 27 + generator/regions/node.cpp | 163 ++++ generator/regions/node.hpp | 49 ++ generator/regions/region.cpp | 115 +++ generator/regions/region.hpp | 44 ++ generator/regions/region_base.cpp | 135 ++++ generator/regions/region_base.hpp | 72 ++ .../{ => regions}/region_info_collector.cpp | 36 +- .../{ => regions}/region_info_collector.hpp | 10 + generator/regions/regions_builder.cpp | 200 +++++ generator/regions/regions_builder.hpp | 47 ++ generator/regions/to_string_policy.cpp | 64 ++ generator/regions/to_string_policy.hpp | 31 + generator/translator_region.cpp | 40 +- generator/translator_region.hpp | 8 +- 24 files changed, 1189 insertions(+), 773 deletions(-) create mode 100644 generator/regions/city.hpp create mode 100644 generator/regions/node.cpp create mode 100644 generator/regions/node.hpp create mode 100644 generator/regions/region.cpp create mode 100644 generator/regions/region.hpp create mode 100644 generator/regions/region_base.cpp create mode 100644 generator/regions/region_base.hpp rename generator/{ => regions}/region_info_collector.cpp (86%) rename generator/{ => regions}/region_info_collector.hpp (95%) create mode 100644 generator/regions/regions_builder.cpp create mode 100644 generator/regions/regions_builder.hpp create mode 100644 generator/regions/to_string_policy.cpp create mode 100644 generator/regions/to_string_policy.hpp diff --git a/base/geo_object_id.hpp b/base/geo_object_id.hpp index 8d01f7db0d..4c6caa8899 100644 --- a/base/geo_object_id.hpp +++ b/base/geo_object_id.hpp @@ -73,6 +73,8 @@ public: // Returns the source type of the object. Type GetType() const; + bool IsValid() const { return m_encodedId != kInvalid; } + bool operator<(GeoObjectId const & other) const { return m_encodedId < other.m_encodedId; } bool operator==(GeoObjectId const & other) const { return m_encodedId == other.m_encodedId; } bool operator!=(GeoObjectId const & other) const { return !(*this == other); } diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt index 4039a55f72..54ad116575 100644 --- a/generator/CMakeLists.txt +++ b/generator/CMakeLists.txt @@ -87,12 +87,23 @@ set(SRC place.cpp relation_tags.cpp relation_tags.hpp - region_info_collector.cpp - region_info_collector.hpp region_meta.cpp region_meta.hpp regions.cpp regions.hpp + regions/city.hpp + regions/node.cpp + regions/node.hpp + regions/region.cpp + regions/region.hpp + regions/region_base.cpp + regions/region_base.hpp + regions/region_info_collector.cpp + regions/region_info_collector.hpp + regions/regions_builder.cpp + regions/regions_builder.hpp + regions/to_string_policy.cpp + regions/to_string_policy.hpp restriction_collector.cpp restriction_collector.hpp restriction_generator.cpp diff --git a/generator/feature_builder.hpp b/generator/feature_builder.hpp index ef8ad0bd4a..ba80b2fe9e 100644 --- a/generator/feature_builder.hpp +++ b/generator/feature_builder.hpp @@ -55,6 +55,7 @@ public: void SetAreaAddHoles(Geometry const & holes); void SetArea() { m_params.SetGeomType(feature::GEOM_AREA); } + bool IsPoint() const { return (GetGeomType() == feature::GEOM_POINT); } bool IsLine() const { return (GetGeomType() == feature::GEOM_LINE); } bool IsArea() const { return (GetGeomType() == feature::GEOM_AREA); } diff --git a/generator/generator_tests/region_info_collector_tests.cpp b/generator/generator_tests/region_info_collector_tests.cpp index 7cd5e93884..a69cc767ea 100644 --- a/generator/generator_tests/region_info_collector_tests.cpp +++ b/generator/generator_tests/region_info_collector_tests.cpp @@ -1,7 +1,7 @@ #include "testing/testing.hpp" #include "generator/osm_element.hpp" -#include "generator/region_info_collector.hpp" +#include "generator/regions/region_info_collector.hpp" #include "coding/file_name_utils.hpp" @@ -15,6 +15,8 @@ #include #include +using namespace generator::regions; + namespace { using Tags = std::vector>; @@ -45,13 +47,13 @@ base::GeoObjectId CastId(uint64_t id) UNIT_TEST(RegionInfoCollector_Add) { - generator::RegionInfoCollector regionInfoCollector; + RegionInfoCollector regionInfoCollector; regionInfoCollector.Add(CastId(kOsmElementCity.id), kOsmElementCity); { auto const regionData = regionInfoCollector.Get(CastId(kOsmElementCity.id)); TEST_EQUAL(regionData.GetOsmId(), CastId(kOsmElementCity.id), ()); - TEST_EQUAL(regionData.GetAdminLevel(), generator::AdminLevel::Six, ()); - TEST_EQUAL(regionData.GetPlaceType(), generator::PlaceType::City, ()); + TEST_EQUAL(regionData.GetAdminLevel(), AdminLevel::Six, ()); + TEST_EQUAL(regionData.GetPlaceType(), PlaceType::City, ()); TEST(!regionData.HasIsoCodeAlpha2(), ()); TEST(!regionData.HasIsoCodeAlpha3(), ()); TEST(!regionData.HasIsoCodeAlphaNumeric(), ()); @@ -61,8 +63,8 @@ UNIT_TEST(RegionInfoCollector_Add) { auto const regionData = regionInfoCollector.Get(CastId(kOsmElementCountry.id)); TEST_EQUAL(regionData.GetOsmId(), CastId(kOsmElementCountry.id), ()); - TEST_EQUAL(regionData.GetAdminLevel(), generator::AdminLevel::Two, ()); - TEST_EQUAL(regionData.GetPlaceType(), generator::PlaceType::Unknown, ()); + TEST_EQUAL(regionData.GetAdminLevel(), AdminLevel::Two, ()); + TEST_EQUAL(regionData.GetPlaceType(), PlaceType::Unknown, ()); TEST(regionData.HasIsoCodeAlpha2(), ()); TEST(regionData.HasIsoCodeAlpha3(), ()); @@ -76,8 +78,8 @@ UNIT_TEST(RegionInfoCollector_Add) { auto const regionDataEmpty = regionInfoCollector.Get(CastId(kOsmElementEmpty.id)); TEST_EQUAL(regionDataEmpty.GetOsmId(), CastId(kOsmElementEmpty.id), ()); - TEST_EQUAL(regionDataEmpty.GetAdminLevel(), generator::AdminLevel::Unknown, ()); - TEST_EQUAL(regionDataEmpty.GetPlaceType(), generator::PlaceType::Unknown, ()); + TEST_EQUAL(regionDataEmpty.GetAdminLevel(), AdminLevel::Unknown, ()); + TEST_EQUAL(regionDataEmpty.GetPlaceType(), PlaceType::Unknown, ()); TEST(!regionDataEmpty.HasIsoCodeAlpha2(), ()); TEST(!regionDataEmpty.HasIsoCodeAlpha3(), ()); TEST(!regionDataEmpty.HasIsoCodeAlphaNumeric(), ()); @@ -86,18 +88,18 @@ UNIT_TEST(RegionInfoCollector_Add) UNIT_TEST(RegionInfoCollector_Get) { - generator::RegionInfoCollector regionInfoCollector; + RegionInfoCollector regionInfoCollector; regionInfoCollector.Add(CastId(kOsmElementCity.id), kOsmElementCity); auto const regionData = regionInfoCollector.Get(CastId(kOsmElementCity.id)); TEST_EQUAL(regionData.GetOsmId(), CastId(kOsmElementCity.id), ()); - TEST_EQUAL(regionData.GetAdminLevel(), generator::AdminLevel::Six, ()); - TEST_EQUAL(regionData.GetPlaceType(), generator::PlaceType::City, ()); + TEST_EQUAL(regionData.GetAdminLevel(), AdminLevel::Six, ()); + TEST_EQUAL(regionData.GetPlaceType(), PlaceType::City, ()); } UNIT_TEST(RegionInfoCollector_Exists) { - generator::RegionInfoCollector regionInfoCollector; + RegionInfoCollector regionInfoCollector; regionInfoCollector.Add(CastId(kOsmElementCity.id), kOsmElementCity); regionInfoCollector.Add(CastId(kOsmElementCountry.id), kOsmElementCountry); @@ -131,7 +133,7 @@ UNIT_TEST(RegionInfoCollector_Exists) UNIT_TEST(RegionInfoCollector_Save) { - generator::RegionInfoCollector regionInfoCollector; + RegionInfoCollector regionInfoCollector; regionInfoCollector.Add(CastId(kOsmElementCity.id), kOsmElementCity); auto const regionCity = regionInfoCollector.Get(CastId(kOsmElementCity.id)); regionInfoCollector.Add(CastId(kOsmElementCountry.id), kOsmElementCountry); @@ -143,7 +145,7 @@ UNIT_TEST(RegionInfoCollector_Save) auto const name = base::JoinPath(tmpDir, "RegionInfoCollector.bin"); regionInfoCollector.Save(name); { - generator::RegionInfoCollector regionInfoCollector(name); + RegionInfoCollector regionInfoCollector(name); auto const rRegionData = regionInfoCollector.Get(CastId(kOsmElementCity.id)); TEST_EQUAL(regionCity.GetOsmId(), rRegionData.GetOsmId(), ()); @@ -155,7 +157,7 @@ UNIT_TEST(RegionInfoCollector_Save) } { - generator::RegionInfoCollector regionInfoCollector(name); + RegionInfoCollector regionInfoCollector(name); auto const rRegionData = regionInfoCollector.Get(CastId(kOsmElementCountry.id)); TEST_EQUAL(regionCountry.GetOsmId(), rRegionData.GetOsmId(), ()); diff --git a/generator/generator_tests/regions_tests.cpp b/generator/generator_tests/regions_tests.cpp index 786a305ac3..b192fbc97a 100644 --- a/generator/generator_tests/regions_tests.cpp +++ b/generator/generator_tests/regions_tests.cpp @@ -2,7 +2,9 @@ #include "generator/osm_element.hpp" #include "generator/regions.hpp" -#include "generator/region_info_collector.hpp" +#include "generator/regions/region_info_collector.hpp" +#include "generator/regions/regions_builder.hpp" +#include "generator/regions/to_string_policy.hpp" #include "platform/platform.hpp" @@ -18,7 +20,6 @@ #include #include -using namespace generator; using namespace generator::regions; namespace diff --git a/generator/generator_tool/generator_tool.cpp b/generator/generator_tool/generator_tool.cpp index 4f153cd164..aa8e1df3dd 100644 --- a/generator/generator_tool/generator_tool.cpp +++ b/generator/generator_tool/generator_tool.cpp @@ -450,7 +450,7 @@ int main(int argc, char ** argv) if (FLAGS_generate_cameras) { string const camerasFilename = - genInfo.GetIntermediateFileName(CAMERAS_TO_WAYS_FILENAME); + genInfo.GetIntermediateFileName(CAMERAS_TO_WAYS_FILENAME); BuildCamerasInfo(datFile, camerasFilename, osmToFeatureFilename); } diff --git a/generator/osm_source.cpp b/generator/osm_source.cpp index 1cacc7c2e3..8fa2df3503 100644 --- a/generator/osm_source.cpp +++ b/generator/osm_source.cpp @@ -11,7 +11,7 @@ #include "generator/osm_o5m_source.hpp" #include "generator/osm_xml_source.hpp" #include "generator/polygonizer.hpp" -#include "generator/region_info_collector.hpp" +#include "generator/regions/region_info_collector.hpp" #include "generator/tag_admixer.hpp" #include "generator/towns_dumper.hpp" #include "generator/translator_factory.hpp" @@ -301,13 +301,13 @@ bool GenerateRegionFeatures(feature::GenerateInfo & info, shared_ptr +#include #include #include #include #include +#include #include #include +#include #include #include #include "defines.hpp" -#include "3party/jansson/myjansson.hpp" -#include "3party/ThreadPool/ThreadPool.h" -#include "3party/boost/boost/math/special_functions/relative_difference.hpp" -#include "3party/boost/boost/range/adaptor/reversed.hpp" - namespace generator { namespace regions { namespace { -using MergeFunc = std::function; +using PointCitiesMap = std::unordered_map; -class JsonPolicy : public ToStringPolicyInterface +struct RegionsFixer { -public: - JsonPolicy(bool extendedOutput = false) - : m_extendedOutput(extendedOutput) + RegionsFixer(RegionsBuilder::Regions & regions, PointCitiesMap const & pointCitiesMap) + : m_regions(regions), m_pointCitiesMap(pointCitiesMap) { + SplitRegionsByAdminCenter(); + CreateNameRegionMap(); } - std::string ToString(Node::PtrList const & nodePtrList) override + RegionsBuilder::Regions & FixRegions() { - auto const & main = nodePtrList.front()->GetData(); - auto const & country = nodePtrList.back()->GetData(); - - auto geometry = base::NewJSONObject(); - ToJSONObject(*geometry, "type", "Point"); - auto coordinates = base::NewJSONArray(); - auto const center = main.GetCenter(); - ToJSONArray(*coordinates, center.get<0>()); - ToJSONArray(*coordinates, center.get<1>()); - ToJSONObject(*geometry, "coordinates", coordinates); - - auto localeEn = base::NewJSONObject(); - auto address = base::NewJSONObject(); - for (auto const & p : boost::adaptors::reverse(nodePtrList)) + SortRegionsByArea(); + std::vector unsuitable; + unsuitable.resize(m_regionsWithAdminCenter.size()); + for (size_t i = 0; i < m_regionsWithAdminCenter.size(); ++i) { - auto const & region = p->GetData(); - auto const label = region.GetLabel(); - ToJSONObject(*address, label, region.GetName()); - if (m_extendedOutput) + if (unsuitable[i]) + continue; + + auto & regionWithAdminCenter = m_regionsWithAdminCenter[i]; + if (regionWithAdminCenter.IsCountry()) + continue; + + auto const id = regionWithAdminCenter.GetAdminCenterId(); + if (!m_pointCitiesMap.count(id)) + continue; + + auto const & adminCenter = m_pointCitiesMap.at(id); + auto const placeType = adminCenter.GetPlaceType(); + if (placeType == PlaceType::Town || placeType == PlaceType::City) { - ToJSONObject(*address, label + "_i", region.GetId().GetSerialId()); - ToJSONObject(*address, label + "_a", region.GetArea()); - ToJSONObject(*address, label + "_r", region.GetRank()); + for (size_t j = i + 1; j < m_regionsWithAdminCenter.size() - 1; ++j) + { + if (m_regionsWithAdminCenter[j].ContainsRect(regionWithAdminCenter)) + unsuitable[j] = true; + } } - ToJSONObject(*localeEn, label, region.GetEnglishOrTransliteratedName()); + if (ExistsRegionAsCity(adminCenter)) + continue; + + regionWithAdminCenter.SetInfo(adminCenter); } - auto locales = base::NewJSONObject(); - ToJSONObject(*locales, "en", localeEn); - - auto properties = base::NewJSONObject(); - ToJSONObject(*properties, "name", main.GetName()); - ToJSONObject(*properties, "rank", main.GetRank()); - ToJSONObject(*properties, "address", address); - ToJSONObject(*properties, "locales", locales); - if (country.HasIsoCode()) - ToJSONObject(*properties, "code", country.GetIsoCode()); - - auto feature = base::NewJSONObject(); - ToJSONObject(*feature, "type", "Feature"); - ToJSONObject(*feature, "geometry", geometry); - ToJSONObject(*feature, "properties", properties); - - auto const cstr = json_dumps(feature.get(), JSON_COMPACT); - std::unique_ptr buffer(cstr); - return buffer.get(); + std::move(std::begin(m_regionsWithAdminCenter), std::end(m_regionsWithAdminCenter), + std::back_inserter(m_regions)); + m_regionsWithAdminCenter = {}; + return m_regions; } private: - bool m_extendedOutput; + bool ExistsRegionAsCity(const City & city) + { + auto const range = m_nameRegionMap.equal_range(city.GetName()); + for (auto it = range.first; it != range.second; ++it) + { + Region const & r = it->second; + if (city.GetRank() == r.GetRank() && r.Contains(city)) + return true; + } + + return false; + } + + void SplitRegionsByAdminCenter() + { + auto const pred = [](Region const & region) { return region.GetAdminCenterId().IsValid(); }; + std::copy_if(std::begin(m_regions), std::end(m_regions), + std::back_inserter(m_regionsWithAdminCenter), pred); + auto const it = std::remove_if(std::begin(m_regions), std::end(m_regions), pred); + m_regions.erase(it, std::end(m_regions)); + } + + void CreateNameRegionMap() + { + for (auto const & region : m_regions) + { + auto const name = region.GetName(); + if (region.GetLabel() == "locality" && !name.empty()) + m_nameRegionMap.emplace(name, region); + } + } + + void SortRegionsByArea() + { + auto const cmp = [](Region const & l, Region const & r) { return l.GetArea() < r.GetArea(); }; + std::sort(std::begin(m_regions), std::end(m_regions), cmp); + } + + RegionsBuilder::Regions & m_regions; + PointCitiesMap const & m_pointCitiesMap; + RegionsBuilder::Regions m_regionsWithAdminCenter; + std::multimap> m_nameRegionMap; }; -// This function is for debugging only and can be used for statistics collection. -size_t TreeSize(Node::Ptr node) -{ - if (node == nullptr) - return 0; - - size_t size = 1; - for (auto const & n : node->GetChildren()) - size += TreeSize(n); - - return size; -} - -// This function is for debugging only and can be used for statistics collection. -size_t MaxDepth(Node::Ptr node) -{ - if (node == nullptr) - return 0; - - size_t depth = 1; - for (auto const & n : node->GetChildren()) - depth = std::max(MaxDepth(n), depth); - - return depth; -} - -// This function is for debugging only and can be used for statistics collection. -void PrintTree(Node::Ptr node, std::ostream & stream = std::cout, std::string prefix = "", - bool isTail = true) -{ - auto const & childern = node->GetChildren(); - stream << prefix; - if (isTail) - { - stream << "└───"; - prefix += " "; - } - else - { - stream << "├───"; - prefix += "│ "; - } - - auto const & d = node->GetData(); - auto const point = d.GetCenter(); - stream << d.GetName() << "<" << d.GetEnglishOrTransliteratedName() << "> (" - << d.GetId() - << ";" << d.GetLabel() - << ";" << static_cast(d.GetRank()) - << ";[" << point.get<0>() << "," << point.get<1>() << "])" - << std::endl; - for (size_t i = 0, size = childern.size(); i < size; ++i) - PrintTree(childern[i], stream, prefix, i == size - 1); -} - -void DebugPrintCountry(Node::Ptr tree, std::ostream & stream = std::cout) -{ - stream << "COUNTRY: " << tree->GetData().GetName() << std::endl; - stream << "MAX DEPTH: " << MaxDepth(tree) << std::endl; - stream << "TREE SIZE: " << TreeSize(tree) << std::endl; - PrintTree(tree, stream); - stream << std::endl; -} - -template -void FillBoostGeometry(BoostGeometry & geometry, FbGeometry const & fbGeometry) -{ - geometry.reserve(fbGeometry.size()); - for (auto const & p : fbGeometry) - boost::geometry::append(geometry, Region::BoostPoint{p.x, p.y}); -} - -RegionsBuilder::Regions ReadRegionsFromTmpMwm(feature::GenerateInfo const & genInfo, - RegionInfoCollector const & regionsInfoCollector) +std::tuple +ReadDatasetFromTmpMwm(feature::GenerateInfo const & genInfo, RegionInfoCollector const & collector) { RegionsBuilder::Regions regions; + PointCitiesMap pointCitiesMap; auto const tmpMwmFilename = genInfo.GetTmpFileName(genInfo.m_fileName); - auto const toDo = [®ions, ®ionsInfoCollector](FeatureBuilder1 const & fb, uint64_t /* currPos */) + auto const toDo = [®ions, &pointCitiesMap, &collector](FeatureBuilder1 const & fb, uint64_t /* currPos */) { - // We expect only the type of osm of the object - relation. But some settlements can be - // presented as ways. We must remember about this. - if (!fb.IsArea() || !fb.IsGeometryClosed()) - return; - - auto const id = fb.GetMostGenericOsmId(); - auto region = Region(fb, regionsInfoCollector.Get(id)); - - auto const & label = region.GetLabel(); - auto const & name = region.GetName(); - if (label.empty() || name.empty()) - return; - - regions.emplace_back(std::move(region)); + if (fb.IsArea() && fb.IsGeometryClosed()) + { + auto const id = fb.GetMostGenericOsmId(); + auto region = Region(fb, collector.Get(id)); + regions.emplace_back(std::move(region)); + } + else if (fb.IsPoint()) + { + auto const id = fb.GetMostGenericOsmId(); + pointCitiesMap.emplace(id, City(fb, collector.Get(id))); + } }; feature::ForEachFromDatRawFormat(tmpMwmFilename, toDo); - return regions; + return std::make_tuple(regions, pointCitiesMap); } -bool LessNodePtrByName(Node::Ptr l, Node::Ptr r) +void FilterRegions(RegionsBuilder::Regions & regions) { - auto const & lRegion = l->GetData(); - auto const & rRegion = r->GetData(); - return lRegion.GetName() < rRegion.GetName(); -} - -Node::PtrList MergeChildren(Node::PtrList const & l, Node::PtrList const & r, Node::Ptr newParent) -{ - Node::PtrList result(l); - std::copy(std::begin(r), std::end(r), std::back_inserter(result)); - for (auto & p : result) - p->SetParent(newParent); - - std::sort(std::begin(result), std::end(result), LessNodePtrByName); - return result; -} - -Node::PtrList NormalizeChildren(Node::PtrList const & children, MergeFunc mergeTree) -{ - Node::PtrList uniqueChildren; - auto const pred = [](Node::Ptr l, Node::Ptr r) + auto const pred = [](Region const & region) { - auto const & lRegion = l->GetData(); - auto const & rRegion = r->GetData(); - return lRegion.GetName() == rRegion.GetName(); + auto const & label = region.GetLabel(); + auto const & name = region.GetName(); + return label.empty() || name.empty(); }; - std::unique_copy(std::begin(children), std::end(children), - std::back_inserter(uniqueChildren), pred); - Node::PtrList result; - for (auto const & ch : uniqueChildren) - { - auto const bounds = std::equal_range(std::begin(children), std::end(children), - ch, LessNodePtrByName); - auto merged = std::accumulate(bounds.first, bounds.second, Node::Ptr(), mergeTree); - result.emplace_back(std::move(merged)); - } - - return result; -} - -Node::Ptr MergeHelper(Node::Ptr l, Node::Ptr r, MergeFunc mergeTree) -{ - auto const & lChildren = l->GetChildren(); - auto const & rChildren = r->GetChildren(); - auto const children = MergeChildren(lChildren, rChildren, l); - if (children.empty()) - return l; - - auto resultChildren = NormalizeChildren(children, mergeTree); - l->SetChildren(std::move(resultChildren)); - r->RemoveChildren(); - return l; -} - -// This function merges two trees if the roots have the same name. -Node::Ptr MergeTree(Node::Ptr l, Node::Ptr r) -{ - if (l == nullptr) - return r; - - if (r == nullptr) - return l; - - auto const & lRegion = l->GetData(); - auto const & rRegion = r->GetData(); - if (lRegion.GetName() != rRegion.GetName()) - return nullptr; - - if (lRegion.GetArea() > rRegion.GetArea()) - return MergeHelper(l, r, MergeTree); - else - return MergeHelper(r, l, MergeTree); -} - -// This function corrects the tree. It traverses the whole node and unites children with -// the same names. -void NormalizeTree(Node::Ptr tree) -{ - if (tree == nullptr) - return; - - auto & children = tree->GetChildren(); - std::sort(std::begin(children), std::end(children), LessNodePtrByName); - auto newChildren = NormalizeChildren(children, MergeTree); - tree->SetChildren(std::move(newChildren)); - for (auto const & ch : tree->GetChildren()) - NormalizeTree(ch); -} -} // namespace - -Region::Region(FeatureBuilder1 const & fb, RegionDataProxy const & rd) - : m_name(fb.GetParams().name), - m_regionData(rd), - m_polygon(std::make_shared()) -{ - FillPolygon(fb); - auto rect = fb.GetLimitRect(); - m_rect = BoostRect({{rect.minX(), rect.minY()}, {rect.maxX(), rect.maxY()}}); - m_area = boost::geometry::area(*m_polygon); -} - -std::string Region::GetName(int8_t lang) const -{ - std::string s; - VERIFY(m_name.GetString(lang, s) != s.empty(), ()); - return s; -} - -std::string Region::GetEnglishOrTransliteratedName() const -{ - std::string s = GetName(StringUtf8Multilang::kEnglishCode); - if (!s.empty()) - return s; - - auto const fn = [&s](int8_t code, std::string const & name) - { - if (code != StringUtf8Multilang::kDefaultCode && - Transliteration::Instance().Transliterate(name, code, s)) - { - return base::ControlFlow::Break; - } - - return base::ControlFlow::Continue; - }; - - m_name.ForEach(fn); - return s; -} - -void Region::DeletePolygon() -{ - m_polygon = nullptr; -} - -void Region::FillPolygon(FeatureBuilder1 const & fb) -{ - CHECK(m_polygon, ()); - - auto const & fbGeometry = fb.GetGeometry(); - CHECK(!fbGeometry.empty(), ()); - auto it = std::begin(fbGeometry); - FillBoostGeometry(m_polygon->outer(), *it); - m_polygon->inners().resize(fbGeometry.size() - 1); - int i = 0; - ++it; - for (; it != std::end(fbGeometry); ++it) - FillBoostGeometry(m_polygon->inners()[i++], *it); - - boost::geometry::correct(*m_polygon); -} - -bool Region::IsCountry() const -{ - static auto const kAdminLevelCountry = AdminLevel::Two; - return m_regionData.GetAdminLevel() == kAdminLevelCountry; -} - -bool Region::HasIsoCode() const -{ - return m_regionData.HasIsoCodeAlpha2(); -} - -std::string Region::GetIsoCode() const -{ - return m_regionData.GetIsoCodeAlpha2(); -} - -bool Region::Contains(Region const & smaller) const -{ - CHECK(m_polygon, ()); - CHECK(smaller.m_polygon, ()); - - return boost::geometry::covered_by(*smaller.m_polygon, *m_polygon); -} - -double Region::CalculateOverlapPercentage(Region const & other) const -{ - CHECK(m_polygon, ()); - CHECK(other.m_polygon, ()); - - std::vector coll; - boost::geometry::intersection(*other.m_polygon, *m_polygon, coll); - auto const min = std::min(boost::geometry::area(*other.m_polygon), - boost::geometry::area(*m_polygon)); - auto const binOp = [] (double x, BoostPolygon const & y) { return x + boost::geometry::area(y); }; - auto const sum = std::accumulate(std::begin(coll), std::end(coll), 0., binOp); - return (sum / min) * 100; -} - -bool Region::ContainsRect(Region const & smaller) const -{ - return boost::geometry::covered_by(smaller.m_rect, m_rect); -} - -// The values ​​of the administrative level and place are indirectly dependent. -// This is used when calculating the rank. -uint8_t Region::GetRank() const -{ - auto const adminLevel = m_regionData.GetAdminLevel(); - auto const placeType = m_regionData.GetPlaceType(); - switch (adminLevel) - { - case AdminLevel::Two: - case AdminLevel::Four: return static_cast(adminLevel); - default: break; - } - - switch (placeType) - { - case PlaceType::City: - case PlaceType::Town: - case PlaceType::Village: - case PlaceType::Hamlet: return static_cast(placeType); - default: break; - } - - switch (adminLevel) - { - case AdminLevel::Six: return static_cast(adminLevel); - default: break; - } - - switch (placeType) - { - case PlaceType::Suburb: - case PlaceType::Neighbourhood: - case PlaceType::Locality: - case PlaceType::IsolatedDwelling: return static_cast(placeType); - default: break; - } - - return kNoRank; -} - -std::string Region::GetLabel() const -{ - auto const adminLevel = m_regionData.GetAdminLevel(); - auto const placeType = m_regionData.GetPlaceType(); - switch (adminLevel) - { - case AdminLevel::Two: return "country"; - case AdminLevel::Four: return "region"; - default: break; - } - - switch (placeType) - { - case PlaceType::City: - case PlaceType::Town: - case PlaceType::Village: - case PlaceType::Hamlet: return "locality"; - default: break; - } - - switch (adminLevel) - { - case AdminLevel::Six: return "subregion"; - default: break; - } - - switch (placeType) - { - case PlaceType::Suburb: - case PlaceType::Neighbourhood: return "suburb"; - case PlaceType::Locality: - case PlaceType::IsolatedDwelling: return "sublocality"; - default: break; - } - - return ""; -} - -Region::BoostPoint Region::GetCenter() const -{ - BoostPoint p; - boost::geometry::centroid(m_rect, p); - return p; -} - -Region::BoostRect const & Region::GetRect() const -{ - return m_rect; -} - -std::shared_ptr const Region::GetPolygon() const -{ - return m_polygon; -} - -double Region::GetArea() const -{ - return m_area; -} - -base::GeoObjectId Region::GetId() const -{ - return m_regionData.GetOsmId(); -} - -RegionsBuilder::RegionsBuilder(Regions && regions) - : RegionsBuilder(std::move(regions), std::make_unique()) -{ -} - -RegionsBuilder::RegionsBuilder(Regions && regions, - std::unique_ptr toStringPolicy) - : m_toStringPolicy(std::move(toStringPolicy)) -{ - ASSERT(m_toStringPolicy, ()); - - auto const isCountry = [](Region const & r){ return r.IsCountry(); }; - std::copy_if(std::begin(regions), std::end(regions), std::back_inserter(m_countries), isCountry); - auto const it = std::remove_if(std::begin(regions), std::end(regions), isCountry); + auto const it = std::remove_if(std::begin(regions), std::end(regions), pred); regions.erase(it, std::end(regions)); - auto const cmp = [](Region const & l, Region const & r) { return l.GetArea() > r.GetArea(); }; - std::sort(std::begin(m_countries), std::end(m_countries), cmp); - - MakeCountryTrees(regions); } -RegionsBuilder::Regions const & RegionsBuilder::GetCountries() const +RegionsBuilder::Regions ReadData(feature::GenerateInfo const & genInfo, + RegionInfoCollector const & regionsInfoCollector) { - return m_countries; + RegionsBuilder::Regions regions; + PointCitiesMap pointCitiesMap; + std::tie(regions, pointCitiesMap) = ReadDatasetFromTmpMwm(genInfo, regionsInfoCollector); + RegionsFixer fixer(regions, pointCitiesMap); + regions = fixer.FixRegions(); + FilterRegions(regions); + return std::move(regions); } -RegionsBuilder::StringsList RegionsBuilder::GetCountryNames() const -{ - StringsList result; - std::unordered_set set; - for (auto const & c : GetCountries()) - { - auto name = c.GetName(); - if (set.insert(name).second) - result.emplace_back(std::move(name)); - } - - return result; -} - -RegionsBuilder::CountryTrees const & RegionsBuilder::GetCountryTrees() const -{ - return m_countryTrees; -} - -RegionsBuilder::IdStringList RegionsBuilder::ToIdStringList(Node::Ptr tree) const -{ - IdStringList result; - std::queue queue; - queue.push(tree); - while (!queue.empty()) - { - const auto el = queue.front(); - queue.pop(); - Node::PtrList nodes; - auto current = el; - while (current) - { - nodes.push_back(current); - current = current->GetParent(); - } - - auto string = m_toStringPolicy->ToString(nodes); - auto const id = nodes.front()->GetData().GetId(); - result.emplace_back(std::make_pair(id, std::move(string))); - for (auto const & n : el->GetChildren()) - queue.push(n); - } - - return result; -} - -Node::PtrList RegionsBuilder::MakeSelectedRegionsByCountry(Region const & country, - Regions const & allRegions) -{ - Regions regionsInCountry; - auto filterCopy = [&country] (const Region & r) { return country.ContainsRect(r); }; - std::copy_if(std::begin(allRegions), std::end(allRegions), - std::back_inserter(regionsInCountry), filterCopy); - - regionsInCountry.emplace_back(country); - auto const comp = [](const Region & l, const Region & r) - { - auto const lArea = l.GetArea(); - auto const rArea = r.GetArea(); - return lArea != rArea ? lArea > rArea : l.GetRank() < r.GetRank(); - }; - std::sort(std::begin(regionsInCountry), std::end(regionsInCountry), comp); - - Node::PtrList nodes; - nodes.reserve(regionsInCountry.size()); - for (auto && region : regionsInCountry) - nodes.emplace_back(std::make_shared(std::move(region))); - - return nodes; -} - -Node::Ptr RegionsBuilder::BuildCountryRegionTree(Region const & country, - Regions const & allRegions) -{ - auto nodes = MakeSelectedRegionsByCountry(country, allRegions); - while (nodes.size() > 1) - { - auto itFirstNode = std::rbegin(nodes); - auto & firstRegion = (*itFirstNode)->GetData(); - auto itCurr = itFirstNode + 1; - for (; itCurr != std::rend(nodes); ++itCurr) - { - auto const & currRegion = (*itCurr)->GetData(); - // If Contains returns false, then we calculate the percent overlap of polygons. - // We believe that if one polygon overlaps by 98 percent, then we can assume that one - // contains another. - auto const kAvaliableOverlapPercentage = 98; - if (currRegion.ContainsRect(firstRegion) && - (currRegion.Contains(firstRegion) || - currRegion.CalculateOverlapPercentage(firstRegion) > kAvaliableOverlapPercentage)) - { - // In general, we assume that a region with the larger rank has the larger area. - // But sometimes it does not. In this case, we will make an inversion. - if (firstRegion.GetRank() < currRegion.GetRank()) - { - (*itCurr)->SetParent(*itFirstNode); - (*itFirstNode)->AddChild(*itCurr); - } - else - { - (*itFirstNode)->SetParent(*itCurr); - (*itCurr)->AddChild(*itFirstNode); - } - // We want to free up memory. - firstRegion.DeletePolygon(); - nodes.pop_back(); - break; - } - } - - if (itCurr == std::rend(nodes)) - nodes.pop_back(); - } - - return nodes.empty() ? std::shared_ptr() : nodes.front(); -} - -void RegionsBuilder::MakeCountryTrees(Regions const & regions) -{ - std::vector> results; - { - auto const cpuCount = std::thread::hardware_concurrency(); - ASSERT_GREATER(cpuCount, 0, ()); - ThreadPool threadPool(cpuCount); - for (auto const & country : GetCountries()) - { - auto f = threadPool.enqueue(&RegionsBuilder::BuildCountryRegionTree, country, regions); - results.emplace_back(std::move(f)); - } - } - - for (auto & r : results) - { - auto tree = r.get(); - m_countryTrees.emplace(tree->GetData().GetName(), std::move(tree)); - } -} +} // namespace } // namespace regions bool GenerateRegions(feature::GenerateInfo const & genInfo) @@ -672,12 +192,10 @@ bool GenerateRegions(feature::GenerateInfo const & genInfo) auto timer = base::Timer(); Transliteration::Instance().Init(GetPlatform().ResourcesDir()); - - auto const collectorFilename = - genInfo.GetTmpFileName(genInfo.m_fileName, RegionInfoCollector::kDefaultExt); + auto const collectorFilename = genInfo.GetTmpFileName(genInfo.m_fileName, + RegionInfoCollector::kDefaultExt); RegionInfoCollector regionsInfoCollector(collectorFilename); - - auto regions = ReadRegionsFromTmpMwm(genInfo, regionsInfoCollector); + RegionsBuilder::Regions regions = ReadData(genInfo, regionsInfoCollector); auto jsonPolicy = std::make_unique(genInfo.m_verbose); auto kvBuilder = std::make_unique(std::move(regions), std::move(jsonPolicy)); auto const countryTrees = kvBuilder->GetCountryTrees(); @@ -688,21 +206,14 @@ bool GenerateRegions(feature::GenerateInfo const & genInfo) size_t countIds = 0; for (auto const & countryName : kvBuilder->GetCountryNames()) { - auto const keyRange = countryTrees.equal_range(countryName); - using countryTreeItem = typename RegionsBuilder::CountryTrees::value_type; - auto const binOp = [](Node::Ptr l, countryTreeItem r) { return MergeTree(l, r.second); }; - Node::Ptr mergedTree = std::accumulate(keyRange.first, keyRange.second, Node::Ptr(), binOp); - if (!mergedTree) - continue; - - NormalizeTree(mergedTree); + auto const tree = kvBuilder->GetNormalizedCountryTree(countryName); if (genInfo.m_verbose) - DebugPrintCountry(mergedTree); + DebugPrintTree(tree); - auto const idStringList = kvBuilder->ToIdStringList(mergedTree); + auto const idStringList = kvBuilder->ToIdStringList(tree); for (auto const & s : idStringList) { - ofs << s.first << " " << s.second << std::endl; + ofs << static_cast(s.first.GetEncodedId()) << " " << s.second << std::endl; ++countIds; if (!setIds.insert(s.first).second) LOG(LWARNING, ("Id alredy exists:", s.first)); diff --git a/generator/regions.hpp b/generator/regions.hpp index 1cea953ab8..754508bf5a 100644 --- a/generator/regions.hpp +++ b/generator/regions.hpp @@ -1,11 +1,11 @@ #pragma once #include "generator/feature_builder.hpp" -#include "generator/region_info_collector.hpp" +#include "generator/regions/region_info_collector.hpp" #include "geometry/rect2d.hpp" -#include "coding/multilang_utf8_string.hpp" + #include "base/geo_object_id.hpp" @@ -23,116 +23,5 @@ struct GenerateInfo; namespace generator { -namespace regions -{ -// This is a helper class that is needed to represent the region. -// With this view, further processing is simplified. -struct Region -{ - static uint8_t constexpr kNoRank = 0; - - using Point = FeatureBuilder1::PointSeq::value_type; - using BoostPoint = boost::geometry::model::point; - using BoostPolygon = boost::geometry::model::polygon; - using BoostRect = boost::geometry::model::box; - - explicit Region(FeatureBuilder1 const & fb, RegionDataProxy const & rd); - - void DeletePolygon(); - // This function will take the following steps: - // 1. Return the english name if it exists. - // 2. Return transliteration if it succeeds. - // 3. Otherwise, return empty string. - std::string GetEnglishOrTransliteratedName() const; - std::string GetName(int8_t lang = StringUtf8Multilang::kDefaultCode) const; - bool IsCountry() const; - bool HasIsoCode() const; - std::string GetIsoCode() const; - bool Contains(Region const & smaller) const; - bool ContainsRect(Region const & smaller) const; - double CalculateOverlapPercentage(Region const & other) const; - // Absolute rank values do not mean anything. But if the rank of the first object is more than the - // rank of the second object, then the first object is considered more nested. - uint8_t GetRank() const; - std::string GetLabel() const; - BoostPoint GetCenter() const; - std::shared_ptr const GetPolygon() const; - BoostRect const & GetRect() const; - double GetArea() const; - base::GeoObjectId GetId() const; - -private: - void FillPolygon(FeatureBuilder1 const & fb); - - StringUtf8Multilang m_name; - RegionDataProxy m_regionData; - std::shared_ptr m_polygon; - BoostRect m_rect; - double m_area; -}; - -struct Node -{ - using Ptr = std::shared_ptr; - using WeakPtr = std::weak_ptr; - using PtrList = std::vector; - - explicit Node(Region && region) : m_region(std::move(region)) {} - - void AddChild(Ptr child) { m_children.push_back(child); } - PtrList const & GetChildren() const { return m_children; } - PtrList & GetChildren() { return m_children; } - void SetChildren(PtrList const children) { m_children = children; } - void RemoveChildren() { m_children.clear(); } - bool HasChildren() { return m_children.size(); } - void SetParent(Ptr parent) { m_parent = parent; } - Ptr GetParent() const { return m_parent.lock(); } - Region & GetData() { return m_region; } - -private: - Region m_region; - PtrList m_children; - WeakPtr m_parent; -}; - -class ToStringPolicyInterface -{ -public: - virtual ~ToStringPolicyInterface() = default; - - virtual std::string ToString(Node::PtrList const & nodePtrList) = 0; -}; - -// This class is needed to build a hierarchy of regions. We can have several nodes for a region -// with the same name, represented by a multi-polygon (several polygons). -class RegionsBuilder -{ -public: - using Regions = std::vector; - using StringsList = std::vector; - using IdStringList = std::vector>; - using CountryTrees = std::multimap; - - explicit RegionsBuilder(Regions && regions); - explicit RegionsBuilder(Regions && regions, - std::unique_ptr toStringPolicy); - - Regions const & GetCountries() const; - StringsList GetCountryNames() const; - CountryTrees const & GetCountryTrees() const; - IdStringList ToIdStringList(Node::Ptr tree) const; - -private: - static Node::PtrList MakeSelectedRegionsByCountry(Region const & country, - Regions const & allRegions); - static Node::Ptr BuildCountryRegionTree(Region const & country, Regions const & allRegions); - void MakeCountryTrees(Regions const & regions); - - std::unique_ptr m_toStringPolicy; - CountryTrees m_countryTrees; - Regions m_countries; -}; -} // namespace regions - bool GenerateRegions(feature::GenerateInfo const & genInfo); } // namespace generator diff --git a/generator/regions/city.hpp b/generator/regions/city.hpp new file mode 100644 index 0000000000..545252af93 --- /dev/null +++ b/generator/regions/city.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "generator/feature_builder.hpp" +#include "generator/regions/region_base.hpp" +#include "generator/regions/region_info_collector.hpp" + +namespace generator +{ +namespace regions +{ +struct City : public RegionWithName, public RegionWithData +{ + explicit City(FeatureBuilder1 const & fb, RegionDataProxy const & rd) + : RegionWithName(fb.GetParams().name), + RegionWithData(rd) + { + auto const p = fb.GetKeyPoint(); + m_center = {p.x, p.y}; + } + + BoostPoint GetCenter() const { return m_center; } + +private: + BoostPoint m_center; +}; +} // namespace regions +} // namespace generator diff --git a/generator/regions/node.cpp b/generator/regions/node.cpp new file mode 100644 index 0000000000..ac7643a6d9 --- /dev/null +++ b/generator/regions/node.cpp @@ -0,0 +1,163 @@ +#include "generator/regions/node.hpp" + +#include +#include + +namespace generator +{ +namespace regions +{ +namespace +{ +using MergeFunc = std::function; + +bool LessNodePtrById(Node::Ptr l, Node::Ptr r) +{ + auto const & lRegion = l->GetData(); + auto const & rRegion = r->GetData(); + return lRegion.GetId() < rRegion.GetId(); +} + +Node::PtrList MergeChildren(Node::PtrList const & l, Node::PtrList const & r, Node::Ptr newParent) +{ + Node::PtrList result(l); + std::copy(std::begin(r), std::end(r), std::back_inserter(result)); + for (auto & p : result) + p->SetParent(newParent); + + std::sort(std::begin(result), std::end(result), LessNodePtrById); + return result; +} + +Node::PtrList NormalizeChildren(Node::PtrList const & children, MergeFunc mergeTree) +{ + Node::PtrList uniqueChildren; + auto const pred = [](Node::Ptr l, Node::Ptr r) + { + auto const & lRegion = l->GetData(); + auto const & rRegion = r->GetData(); + return lRegion.GetId() == rRegion.GetId(); + }; + std::unique_copy(std::begin(children), std::end(children), + std::back_inserter(uniqueChildren), pred); + Node::PtrList result; + for (auto const & ch : uniqueChildren) + { + auto const bounds = std::equal_range(std::begin(children), std::end(children), ch, + LessNodePtrById); + auto merged = std::accumulate(bounds.first, bounds.second, Node::Ptr(), mergeTree); + result.emplace_back(std::move(merged)); + } + + return result; +} + +Node::Ptr MergeHelper(Node::Ptr l, Node::Ptr r, MergeFunc mergeTree) +{ + auto const & lChildren = l->GetChildren(); + auto const & rChildren = r->GetChildren(); + auto const children = MergeChildren(lChildren, rChildren, l); + if (children.empty()) + return l; + + auto resultChildren = NormalizeChildren(children, mergeTree); + l->SetChildren(std::move(resultChildren)); + r->RemoveChildren(); + return l; +} +} // nmespace + +size_t TreeSize(Node::Ptr node) +{ + if (node == nullptr) + return 0; + + size_t size = 1; + for (auto const & n : node->GetChildren()) + size += TreeSize(n); + + return size; +} + +size_t MaxDepth(Node::Ptr node) +{ + if (node == nullptr) + return 0; + + size_t depth = 1; + for (auto const & n : node->GetChildren()) + depth = std::max(MaxDepth(n), depth); + + return depth; +} + +void PrintTree(Node::Ptr node, std::ostream & stream = std::cout, std::string prefix = "", + bool isTail = true) +{ + auto const & childern = node->GetChildren(); + stream << prefix; + if (isTail) + { + stream << "└───"; + prefix += " "; + } + else + { + stream << "├───"; + prefix += "│ "; + } + + auto const & d = node->GetData(); + auto const point = d.GetCenter(); + stream << d.GetName() << "<" << d.GetEnglishOrTransliteratedName() << "> (" + << d.GetId() + << ";" << d.GetLabel() + << ";" << static_cast(d.GetRank()) + << ";[" << point.get<0>() << "," << point.get<1>() << "])" + << std::endl; + for (size_t i = 0, size = childern.size(); i < size; ++i) + PrintTree(childern[i], stream, prefix, i == size - 1); +} + +void DebugPrintTree(Node::Ptr tree, std::ostream & stream) +{ + stream << "ROOT NAME: " << tree->GetData().GetName() << std::endl; + stream << "MAX DEPTH: " << MaxDepth(tree) << std::endl; + stream << "TREE SIZE: " << TreeSize(tree) << std::endl; + PrintTree(tree, stream); + stream << std::endl; +} + +Node::Ptr MergeTree(Node::Ptr l, Node::Ptr r) +{ + if (l == nullptr) + return r; + + if (r == nullptr) + return l; + + auto const & lRegion = l->GetData(); + auto const & rRegion = r->GetData(); + if (lRegion.GetId() != rRegion.GetId()) + return nullptr; + + if (lRegion.GetArea() > rRegion.GetArea()) + return MergeHelper(l, r, MergeTree); + else + return MergeHelper(r, l, MergeTree); +} + +void NormalizeTree(Node::Ptr tree) +{ + if (tree == nullptr) + return; + + auto & children = tree->GetChildren(); + std::sort(std::begin(children), std::end(children), LessNodePtrById); + auto newChildren = NormalizeChildren(children, MergeTree); + tree->SetChildren(std::move(newChildren)); + for (auto const & ch : tree->GetChildren()) + NormalizeTree(ch); +} +} // namespace regions +} // namespace generator diff --git a/generator/regions/node.hpp b/generator/regions/node.hpp new file mode 100644 index 0000000000..c2f399d674 --- /dev/null +++ b/generator/regions/node.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "generator/regions/region.hpp" + +#include +#include +#include + +#include "3party/boost/boost/geometry.hpp" + +namespace generator +{ +namespace regions +{ +struct Node +{ + using Ptr = std::shared_ptr; + using WeakPtr = std::weak_ptr; + using PtrList = std::vector; + + explicit Node(Region && region) : m_region(std::move(region)) {} + + void AddChild(Ptr child) { m_children.push_back(child); } + PtrList const & GetChildren() const { return m_children; } + PtrList & GetChildren() { return m_children; } + void SetChildren(PtrList const children) { m_children = children; } + void RemoveChildren() { m_children.clear(); } + bool HasChildren() { return m_children.size(); } + void SetParent(Ptr parent) { m_parent = parent; } + Ptr GetParent() const { return m_parent.lock(); } + Region & GetData() { return m_region; } + +private: + Region m_region; + PtrList m_children; + WeakPtr m_parent; +}; + +size_t TreeSize(Node::Ptr node); +size_t MaxDepth(Node::Ptr node); +void DebugPrintTree(Node::Ptr tree, std::ostream & stream = std::cout); +// This function merges two trees if the roots have the same ids. +Node::Ptr MergeTree(Node::Ptr l, Node::Ptr r); +// This function corrects the tree. It traverses the whole node and unites children with +// the same ids. +void NormalizeTree(Node::Ptr tree); + +} // namespace regions +} // namespace generator diff --git a/generator/regions/region.cpp b/generator/regions/region.cpp new file mode 100644 index 0000000000..84cd40f348 --- /dev/null +++ b/generator/regions/region.cpp @@ -0,0 +1,115 @@ +#include "generator/regions/region.hpp" + +#include "generator/regions/city.hpp" +#include "generator/regions/region_info_collector.hpp" + +#include "base/assert.hpp" + +#include +#include + +#include "3party/boost/boost/geometry.hpp" + +namespace generator +{ +namespace regions +{ +namespace +{ +template +void FillBoostGeometry(BoostGeometry & geometry, FbGeometry const & fbGeometry) +{ + geometry.reserve(fbGeometry.size()); + for (auto const & p : fbGeometry) + boost::geometry::append(geometry, BoostPoint{p.x, p.y}); +} +} // namespace + +Region::Region(FeatureBuilder1 const & fb, RegionDataProxy const & rd) + : RegionWithName(fb.GetParams().name), + RegionWithData(rd), + m_polygon(std::make_shared()) +{ + FillPolygon(fb); + auto rect = fb.GetLimitRect(); + m_rect = BoostRect({{rect.minX(), rect.minY()}, {rect.maxX(), rect.maxY()}}); + m_area = boost::geometry::area(*m_polygon); +} + +void Region::DeletePolygon() +{ + m_polygon = nullptr; +} + +void Region::FillPolygon(FeatureBuilder1 const & fb) +{ + CHECK(m_polygon, ()); + + auto const & fbGeometry = fb.GetGeometry(); + CHECK(!fbGeometry.empty(), ()); + auto it = std::begin(fbGeometry); + FillBoostGeometry(m_polygon->outer(), *it); + m_polygon->inners().resize(fbGeometry.size() - 1); + int i = 0; + ++it; + for (; it != std::end(fbGeometry); ++it) + FillBoostGeometry(m_polygon->inners()[i++], *it); + + boost::geometry::correct(*m_polygon); +} + +bool Region::IsCountry() const +{ + static auto const kAdminLevelCountry = AdminLevel::Two; + return !HasPlaceType() && GetAdminLevel() == kAdminLevelCountry; +} + +bool Region::Contains(Region const & smaller) const +{ + CHECK(m_polygon, ()); + CHECK(smaller.m_polygon, ()); + + return boost::geometry::covered_by(*smaller.m_polygon, *m_polygon); +} + +double Region::CalculateOverlapPercentage(Region const & other) const +{ + CHECK(m_polygon, ()); + CHECK(other.m_polygon, ()); + + std::vector coll; + boost::geometry::intersection(*other.m_polygon, *m_polygon, coll); + auto const min = std::min(boost::geometry::area(*other.m_polygon), + boost::geometry::area(*m_polygon)); + auto const binOp = [] (double x, BoostPolygon const & y) { return x + boost::geometry::area(y); }; + auto const sum = std::accumulate(std::begin(coll), std::end(coll), 0., binOp); + return (sum / min) * 100; +} + +bool Region::ContainsRect(Region const & smaller) const +{ + return boost::geometry::covered_by(smaller.m_rect, m_rect); +} + +BoostPoint Region::GetCenter() const +{ + BoostPoint p; + boost::geometry::centroid(m_rect, p); + return p; +} + +bool Region::Contains(City const & cityPoint) const +{ + CHECK(m_polygon, ()); + + return boost::geometry::covered_by(cityPoint.GetCenter(), *m_polygon); +} + +void Region::SetInfo(City const & cityPoint) +{ + SetStringUtf8MultilangName(cityPoint.GetStringUtf8MultilangName()); + SetAdminLevel(cityPoint.GetAdminLevel()); + SetPlaceType(cityPoint.GetPlaceType()); +} +} // namespace regions +} // namespace generator diff --git a/generator/regions/region.hpp b/generator/regions/region.hpp new file mode 100644 index 0000000000..4b6e835b2b --- /dev/null +++ b/generator/regions/region.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "generator/feature_builder.hpp" +#include "generator/regions/region_base.hpp" + +#include + +class FeatureBuilder1; + +namespace generator +{ +class RegionDataProxy; + +namespace regions +{ +class City; + +// This is a helper class that is needed to represent the region. +// With this view, further processing is simplified. +struct Region : public RegionWithName, public RegionWithData +{ + explicit Region(FeatureBuilder1 const & fb, RegionDataProxy const & rd); + + void DeletePolygon(); + bool IsCountry() const; + bool Contains(Region const & smaller) const; + bool ContainsRect(Region const & smaller) const; + double CalculateOverlapPercentage(Region const & other) const; + BoostPoint GetCenter() const; + std::shared_ptr const GetPolygon() const { return m_polygon; } + BoostRect const & GetRect() const { return m_rect; } + double GetArea() const { return m_area; } + bool Contains(City const & cityPoint) const; + void SetInfo(City const & cityPoint); + +private: + void FillPolygon(FeatureBuilder1 const & fb); + + std::shared_ptr m_polygon; + BoostRect m_rect; + double m_area; +}; +} // namespace regions +} // namespace generator diff --git a/generator/regions/region_base.cpp b/generator/regions/region_base.cpp new file mode 100644 index 0000000000..c81f6861ce --- /dev/null +++ b/generator/regions/region_base.cpp @@ -0,0 +1,135 @@ +#include "generator/regions/region_base.hpp" + +#include "coding/transliteration.hpp" + +#include "base/assert.hpp" +#include "base/control_flow.hpp" + +namespace generator +{ +namespace regions +{ +std::string RegionWithName::GetName(int8_t lang) const +{ + std::string s; + VERIFY(m_name.GetString(lang, s) != s.empty(), ()); + return s; +} + +std::string RegionWithName::GetEnglishOrTransliteratedName() const +{ + std::string s = GetName(StringUtf8Multilang::kEnglishCode); + if (!s.empty()) + return s; + + auto const fn = [&s](int8_t code, std::string const & name) + { + if (code != StringUtf8Multilang::kDefaultCode && + Transliteration::Instance().Transliterate(name, code, s)) + { + return base::ControlFlow::Break; + } + + return base::ControlFlow::Continue; + }; + + m_name.ForEach(fn); + return s; +} + +StringUtf8Multilang const & RegionWithName::GetStringUtf8MultilangName() const +{ + return m_name; +} + +void RegionWithName::SetStringUtf8MultilangName(StringUtf8Multilang const & name) +{ + m_name = name; +} + +base::GeoObjectId RegionWithData::GetId() const +{ + return m_regionData.GetOsmId(); +} + +bool RegionWithData::HasAdminCenter() const +{ + return m_regionData.HasAdminCenter(); +} + +base::GeoObjectId RegionWithData::GetAdminCenterId() const +{ + return m_regionData.GetAdminCenter(); +} + +bool RegionWithData::HasIsoCode() const +{ + return m_regionData.HasIsoCodeAlpha2(); +} + +std::string RegionWithData::GetIsoCode() const +{ + return m_regionData.GetIsoCodeAlpha2(); +} + +// The values ​​of the administrative level and place are indirectly dependent. +// This is used when calculating the rank. +uint8_t RegionWithData::GetRank() const +{ + auto const adminLevel = GetAdminLevel(); + auto const placeType = GetPlaceType(); + + switch (placeType) + { + case PlaceType::City: + case PlaceType::Town: + case PlaceType::Village: + case PlaceType::Hamlet: + case PlaceType::Suburb: + case PlaceType::Neighbourhood: + case PlaceType::Locality: + case PlaceType::IsolatedDwelling: return static_cast(placeType); + default: break; + } + + switch (adminLevel) + { + case AdminLevel::Two: + case AdminLevel::Four: + case AdminLevel::Six: return static_cast(adminLevel); + default: break; + } + + return kNoRank; +} + +std::string RegionWithData::GetLabel() const +{ + auto const adminLevel = GetAdminLevel(); + auto const placeType = GetPlaceType(); + + switch (placeType) + { + case PlaceType::City: + case PlaceType::Town: + case PlaceType::Village: + case PlaceType::Hamlet: return "locality"; + case PlaceType::Suburb: + case PlaceType::Neighbourhood: return "suburb"; + case PlaceType::Locality: + case PlaceType::IsolatedDwelling: return "sublocality"; + default: break; + } + + switch (adminLevel) + { + case AdminLevel::Two: return "country"; + case AdminLevel::Four: return "region"; + case AdminLevel::Six: return "subregion"; + default: break; + } + + return ""; +} +} // namespace regions +} // namespace generator diff --git a/generator/regions/region_base.hpp b/generator/regions/region_base.hpp new file mode 100644 index 0000000000..a3e1490480 --- /dev/null +++ b/generator/regions/region_base.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "generator/feature_builder.hpp" +#include "generator/regions/region_info_collector.hpp" + +#include "geometry/rect2d.hpp" + +#include "coding/multilang_utf8_string.hpp" + +#include "base/geo_object_id.hpp" + +#include +#include + +#include "3party/boost/boost/geometry.hpp" + +namespace generator +{ +namespace regions +{ +using Point = FeatureBuilder1::PointSeq::value_type; +using BoostPoint = boost::geometry::model::point; +using BoostPolygon = boost::geometry::model::polygon; +using BoostRect = boost::geometry::model::box; + +struct RegionWithName +{ + RegionWithName(StringUtf8Multilang const & name) : m_name(name) {} + + // This function will take the following steps: + // 1. Return the english name if it exists. + // 2. Return transliteration if it succeeds. + // 3. Otherwise, return empty string. + std::string GetEnglishOrTransliteratedName() const; + std::string GetName(int8_t lang = StringUtf8Multilang::kDefaultCode) const; + StringUtf8Multilang const & GetStringUtf8MultilangName() const; + void SetStringUtf8MultilangName(StringUtf8Multilang const & name); + +protected: + StringUtf8Multilang m_name; +}; + +struct RegionWithData +{ + static uint8_t constexpr kNoRank = 0; + + RegionWithData(RegionDataProxy const & regionData) : m_regionData(regionData) {} + + base::GeoObjectId GetId() const; + bool HasAdminCenter() const; + base::GeoObjectId GetAdminCenterId() const; + bool HasIsoCode() const; + std::string GetIsoCode() const; + // Absolute rank values do not mean anything. But if the rank of the first object is more than the + // rank of the second object, then the first object is considered more nested. + uint8_t GetRank() const; + std::string GetLabel() const; + + AdminLevel GetAdminLevel() const { return m_regionData.GetAdminLevel(); } + PlaceType GetPlaceType() const { return m_regionData.GetPlaceType(); } + + void SetAdminLevel(AdminLevel adminLevel) { m_regionData.SetAdminLevel(adminLevel); } + void SetPlaceType(PlaceType placeType) { m_regionData.SetPlaceType(placeType); } + + bool HasAdminLevel() const { return m_regionData.HasAdminLevel(); } + bool HasPlaceType() const { return m_regionData.HasPlaceType(); } + +protected: + RegionDataProxy m_regionData; +}; +} // namespace regions +} // namespace generator diff --git a/generator/region_info_collector.cpp b/generator/regions/region_info_collector.cpp similarity index 86% rename from generator/region_info_collector.cpp rename to generator/regions/region_info_collector.cpp index dd3684abea..531d5e31bf 100644 --- a/generator/region_info_collector.cpp +++ b/generator/regions/region_info_collector.cpp @@ -1,4 +1,4 @@ -#include "generator/region_info_collector.hpp" +#include "generator/regions/region_info_collector.hpp" #include "generator/feature_builder.hpp" #include "generator/osm_element.hpp" @@ -19,6 +19,8 @@ uint8_t const kVersion = 0; namespace generator { +namespace regions +{ std::string const RegionInfoCollector::kDefaultExt = ".regions.bin"; PlaceType EncodePlaceType(std::string const & place) @@ -124,8 +126,14 @@ void RegionInfoCollector::FillRegionData(base::GeoObjectId const & osmId, OsmEle { rd.m_osmId = osmId; rd.m_place = EncodePlaceType(el.GetTag("place")); - auto const al = el.GetTag("admin_level"); + auto const & members = el.Members(); + auto const it = std::find_if(std::begin(members), std::end(members), + [](OsmElement::Member const & m) { return m.role == "admin_centre"; }); + if (it != std::end(members)) + rd.m_osmIdAdminCenter = base::MakeOsmNode(it->ref); + + auto const al = el.GetTag("admin_level"); if (al.empty()) return; @@ -165,11 +173,13 @@ RegionInfoCollector const & RegionDataProxy::GetCollector() const return m_regionInfoCollector; } + RegionInfoCollector::MapRegionData const & RegionDataProxy::GetMapRegionData() const { return GetCollector().m_mapRegionData; } + RegionInfoCollector::MapIsoCode const & RegionDataProxy::GetMapIsoCode() const { return GetCollector().m_mapIsoCode; @@ -190,6 +200,16 @@ PlaceType RegionDataProxy::GetPlaceType() const return GetMapRegionData().at(m_osmId).m_place; } +void RegionDataProxy::SetAdminLevel(AdminLevel adminLevel) +{ + const_cast(GetMapRegionData().at(m_osmId).m_adminLevel) = adminLevel; +} + +void RegionDataProxy::SetPlaceType(PlaceType placeType) +{ + const_cast(GetMapRegionData().at(m_osmId).m_place) = placeType; +} + bool RegionDataProxy::HasAdminLevel() const { return (GetMapRegionData().count(m_osmId) != 0) && @@ -236,4 +256,16 @@ std::string RegionDataProxy::GetIsoCodeAlphaNumeric() const { return GetMapIsoCode().at(m_osmId).GetNumeric(); } + +bool RegionDataProxy::HasAdminCenter() const +{ + return (GetMapRegionData().count(m_osmId) != 0) && + (GetMapRegionData().at(m_osmId).m_osmIdAdminCenter.IsValid()); +} + +base::GeoObjectId RegionDataProxy::GetAdminCenter() const +{ + return GetMapRegionData().at(m_osmId).m_osmIdAdminCenter; +} +} // namespace regions } // namespace generator diff --git a/generator/region_info_collector.hpp b/generator/regions/region_info_collector.hpp similarity index 95% rename from generator/region_info_collector.hpp rename to generator/regions/region_info_collector.hpp index e5f3c7ae7f..56706f82f4 100644 --- a/generator/region_info_collector.hpp +++ b/generator/regions/region_info_collector.hpp @@ -20,6 +20,8 @@ class FileWriter; namespace generator { +namespace regions +{ // https://wiki.openstreetmap.org/wiki/Tag:boundary=administrative enum class AdminLevel : uint8_t { @@ -101,6 +103,7 @@ private: base::GeoObjectId m_osmId; AdminLevel m_adminLevel = AdminLevel::Unknown; PlaceType m_place = PlaceType::Unknown; + base::GeoObjectId m_osmIdAdminCenter; }; using MapRegionData = std::unordered_map; @@ -147,6 +150,9 @@ public: AdminLevel GetAdminLevel() const; PlaceType GetPlaceType() const; + void SetAdminLevel(AdminLevel adminLevel); + void SetPlaceType(PlaceType placeType); + bool HasAdminLevel() const; bool HasPlaceType() const; @@ -158,6 +164,9 @@ public: std::string GetIsoCodeAlpha3() const; std::string GetIsoCodeAlphaNumeric() const; + bool HasAdminCenter() const; + base::GeoObjectId GetAdminCenter() const; + private: bool HasIsoCode() const; RegionInfoCollector const & GetCollector() const; @@ -179,4 +188,5 @@ inline std::ostream & operator<<(std::ostream & out, PlaceType const & t) out << static_cast(t); return out; } +} // namespace regions } // namespace generator diff --git a/generator/regions/regions_builder.cpp b/generator/regions/regions_builder.cpp new file mode 100644 index 0000000000..4464dab015 --- /dev/null +++ b/generator/regions/regions_builder.cpp @@ -0,0 +1,200 @@ +#include "generator/regions/regions_builder.hpp" + +#include "base/assert.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "3party/ThreadPool/ThreadPool.h" + +namespace generator +{ +namespace regions +{ +RegionsBuilder::RegionsBuilder(Regions && regions, + std::unique_ptr toStringPolicy, + int cpuCount) + : m_toStringPolicy(std::move(toStringPolicy)), + m_cpuCount(cpuCount) +{ + ASSERT(m_toStringPolicy, ()); + ASSERT(m_cpuCount != 0, ()); + + auto const isCountry = [](Region const & r){ return r.IsCountry(); }; + std::copy_if(std::begin(regions), std::end(regions), std::back_inserter(m_countries), isCountry); + auto const it = std::remove_if(std::begin(regions), std::end(regions), isCountry); + regions.erase(it, std::end(regions)); + auto const cmp = [](Region const & l, Region const & r) { return l.GetArea() > r.GetArea(); }; + std::sort(std::begin(m_countries), std::end(m_countries), cmp); + + MakeCountryTrees(regions); +} + +RegionsBuilder::RegionsBuilder(Regions && regions, int cpuCount) + : RegionsBuilder(std::move(regions), std::make_unique(), cpuCount) {} + +RegionsBuilder::Regions const & RegionsBuilder::GetCountries() const +{ + return m_countries; +} + +RegionsBuilder::StringsList RegionsBuilder::GetCountryNames() const +{ + StringsList result; + std::unordered_set set; + for (auto const & c : GetCountries()) + { + auto name = c.GetName(); + if (set.insert(name).second) + result.emplace_back(std::move(name)); + } + + return result; +} + +RegionsBuilder::CountryTrees const & RegionsBuilder::GetCountryTrees() const +{ + return m_countryTrees; +} + +RegionsBuilder::IdStringList RegionsBuilder::ToIdStringList(Node::Ptr tree) const +{ + IdStringList result; + std::queue queue; + queue.push(tree); + while (!queue.empty()) + { + const auto el = queue.front(); + queue.pop(); + Node::PtrList nodes; + auto current = el; + while (current) + { + nodes.push_back(current); + current = current->GetParent(); + } + + auto string = m_toStringPolicy->ToString(nodes); + auto const id = nodes.front()->GetData().GetId(); + result.emplace_back(std::make_pair(id, std::move(string))); + for (auto const & n : el->GetChildren()) + queue.push(n); + } + + return result; +} + +Node::PtrList RegionsBuilder::MakeSelectedRegionsByCountry(Region const & country, + Regions const & allRegions) +{ + Regions regionsInCountry; + auto filterCopy = [&country] (const Region & r) { return country.ContainsRect(r); }; + std::copy_if(std::begin(allRegions), std::end(allRegions), + std::back_inserter(regionsInCountry), filterCopy); + + regionsInCountry.emplace_back(country); + auto const comp = [](const Region & l, const Region & r) + { + auto const lArea = l.GetArea(); + auto const rArea = r.GetArea(); + return lArea != rArea ? lArea > rArea : l.GetRank() < r.GetRank(); + }; + std::sort(std::begin(regionsInCountry), std::end(regionsInCountry), comp); + + Node::PtrList nodes; + nodes.reserve(regionsInCountry.size()); + for (auto && region : regionsInCountry) + nodes.emplace_back(std::make_shared(std::move(region))); + + return nodes; +} + +Node::Ptr RegionsBuilder::BuildCountryRegionTree(Region const & country, + Regions const & allRegions) +{ + auto nodes = MakeSelectedRegionsByCountry(country, allRegions); + while (nodes.size() > 1) + { + auto itFirstNode = std::rbegin(nodes); + auto & firstRegion = (*itFirstNode)->GetData(); + auto itCurr = itFirstNode + 1; + for (; itCurr != std::rend(nodes); ++itCurr) + { + auto const & currRegion = (*itCurr)->GetData(); + // If Contains returns false, then we calculate the percent overlap of polygons. + // We believe that if one polygon overlaps by 98 percent, then we can assume that one + // contains another. + auto const kAvaliableOverlapPercentage = 98; + if (currRegion.ContainsRect(firstRegion) && + (currRegion.Contains(firstRegion) || + currRegion.CalculateOverlapPercentage(firstRegion) > kAvaliableOverlapPercentage)) + { + // In general, we assume that a region with the larger rank has the larger area. + // But sometimes it does not. In this case, we will make an inversion. + if (firstRegion.GetRank() < currRegion.GetRank()) + { + (*itCurr)->SetParent(*itFirstNode); + (*itFirstNode)->AddChild(*itCurr); + } + else + { + (*itFirstNode)->SetParent(*itCurr); + (*itCurr)->AddChild(*itFirstNode); + } + // We want to free up memory. + firstRegion.DeletePolygon(); + nodes.pop_back(); + break; + } + } + + if (itCurr == std::rend(nodes)) + nodes.pop_back(); + } + + return nodes.empty() ? std::shared_ptr() : nodes.front(); +} + +void RegionsBuilder::MakeCountryTrees(Regions const & regions) +{ + std::vector> results; + { + int const cpuCount = m_cpuCount > 0 ? m_cpuCount : std::thread::hardware_concurrency(); + ASSERT_GREATER(cpuCount, 0, ()); + ThreadPool threadPool(cpuCount); + for (auto const & country : GetCountries()) + { + auto f = threadPool.enqueue(&RegionsBuilder::BuildCountryRegionTree, country, regions); + results.emplace_back(std::move(f)); + } + } + + for (auto & r : results) + { + auto tree = r.get(); + m_countryTrees.emplace(tree->GetData().GetName(), std::move(tree)); + } +} + +Node::Ptr RegionsBuilder::GetNormalizedCountryTree(std::string const & name) +{ + Node::Ptr mergedTree; + CountryTrees const & countryTrees = GetCountryTrees(); + auto const keyRange = countryTrees.equal_range(name); + using countryTreeItem = typename RegionsBuilder::CountryTrees::value_type; + auto const binOp = [](Node::Ptr l, countryTreeItem r) { return MergeTree(l, r.second); }; + mergedTree = std::accumulate(keyRange.first, keyRange.second, Node::Ptr(), binOp); + NormalizeTree(mergedTree); + return mergedTree; +} +} // namespace regions +} // namespace generator diff --git a/generator/regions/regions_builder.hpp b/generator/regions/regions_builder.hpp new file mode 100644 index 0000000000..69a74b6cdc --- /dev/null +++ b/generator/regions/regions_builder.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "generator/regions/node.hpp" +#include "generator/regions/region.hpp" +#include "generator/regions/to_string_policy.hpp" + +#include + +namespace generator +{ +namespace regions +{ +// This class is needed to build a hierarchy of regions. We can have several nodes for a region +// with the same name, represented by a multi-polygon (several polygons). +class RegionsBuilder +{ +public: + using Regions = std::vector; + using StringsList = std::vector; + using IdStringList = std::vector>; + using CountryTrees = std::multimap; + + explicit RegionsBuilder(Regions && regions, + std::unique_ptr toStringPolicy, + int cpuCount = -1); + explicit RegionsBuilder(Regions && regions, int cpuCount = -1); + + Regions const & GetCountries() const; + StringsList GetCountryNames() const; + CountryTrees const & GetCountryTrees() const; + IdStringList ToIdStringList(Node::Ptr tree) const; + Node::Ptr GetNormalizedCountryTree(std::string const & name); + +private: + static Node::PtrList MakeSelectedRegionsByCountry(Region const & country, + Regions const & allRegions); + static Node::Ptr BuildCountryRegionTree(Region const & country, Regions const & allRegions); + void MakeCountryTrees(Regions const & regions); + + std::unique_ptr m_toStringPolicy; + CountryTrees m_countryTrees; + Regions m_countries; + int m_cpuCount; +}; + +} // namespace regions +} // namespace generator diff --git a/generator/regions/to_string_policy.cpp b/generator/regions/to_string_policy.cpp new file mode 100644 index 0000000000..6704722e41 --- /dev/null +++ b/generator/regions/to_string_policy.cpp @@ -0,0 +1,64 @@ +#include "generator/regions/to_string_policy.hpp" + +#include + +#include "3party/jansson/myjansson.hpp" +#include "3party/boost/boost/range/adaptor/reversed.hpp" + +namespace generator +{ +namespace regions +{ +std::string JsonPolicy::ToString(Node::PtrList const & nodePtrList) +{ + auto const & main = nodePtrList.front()->GetData(); + auto const & country = nodePtrList.back()->GetData(); + + auto geometry = base::NewJSONObject(); + ToJSONObject(*geometry, "type", "Point"); + auto coordinates = base::NewJSONArray(); + auto const center = main.GetCenter(); + ToJSONArray(*coordinates, center.get<0>()); + ToJSONArray(*coordinates, center.get<1>()); + ToJSONObject(*geometry, "coordinates", coordinates); + + auto localeEn = base::NewJSONObject(); + auto address = base::NewJSONObject(); + for (auto const & p : boost::adaptors::reverse(nodePtrList)) + { + + auto const & region = p->GetData(); + auto const label = region.GetLabel(); + ToJSONObject(*address, label, region.GetName()); + if (m_extendedOutput) + { + ToJSONObject(*address, label + "_i", region.GetId().GetSerialId()); + ToJSONObject(*address, label + "_a", region.GetArea()); + ToJSONObject(*address, label + "_r", region.GetRank()); + } + + ToJSONObject(*localeEn, label, region.GetEnglishOrTransliteratedName()); + } + + auto locales = base::NewJSONObject(); + ToJSONObject(*locales, "en", localeEn); + + auto properties = base::NewJSONObject(); + ToJSONObject(*properties, "name", main.GetName()); + ToJSONObject(*properties, "rank", main.GetRank()); + ToJSONObject(*properties, "address", address); + ToJSONObject(*properties, "locales", locales); + if (country.HasIsoCode()) + ToJSONObject(*properties, "code", country.GetIsoCode()); + + auto feature = base::NewJSONObject(); + ToJSONObject(*feature, "type", "Feature"); + ToJSONObject(*feature, "geometry", geometry); + ToJSONObject(*feature, "properties", properties); + + auto const cstr = json_dumps(feature.get(), JSON_COMPACT); + std::unique_ptr buffer(cstr); + return buffer.get(); +} +} // namespace regions +} // namespace generator diff --git a/generator/regions/to_string_policy.hpp b/generator/regions/to_string_policy.hpp new file mode 100644 index 0000000000..76b0908f48 --- /dev/null +++ b/generator/regions/to_string_policy.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "generator/regions/node.hpp" + +#include + +namespace generator +{ +namespace regions +{ +class ToStringPolicyInterface +{ +public: + virtual ~ToStringPolicyInterface() = default; + + virtual std::string ToString(Node::PtrList const & nodePtrList) = 0; +}; + + +class JsonPolicy : public ToStringPolicyInterface +{ +public: + JsonPolicy(bool extendedOutput = false) : m_extendedOutput(extendedOutput) {} + + std::string ToString(Node::PtrList const & nodePtrList) override; + +private: + bool m_extendedOutput; +}; +} // namespace regions +} // namespace generator diff --git a/generator/translator_region.cpp b/generator/translator_region.cpp index b62f69b545..331a487978 100644 --- a/generator/translator_region.cpp +++ b/generator/translator_region.cpp @@ -7,7 +7,7 @@ #include "generator/intermediate_data.hpp" #include "generator/osm2type.hpp" #include "generator/osm_element.hpp" -#include "generator/region_info_collector.hpp" +#include "generator/regions/region_info_collector.hpp" #include "indexer/classificator.hpp" @@ -23,7 +23,7 @@ namespace generator { TranslatorRegion::TranslatorRegion(std::shared_ptr emitter, cache::IntermediateDataReader & holder, - RegionInfoCollector & regionInfoCollector) + regions::RegionInfoCollector & regionInfoCollector) : m_emitter(emitter), m_holder(holder), m_regionInfoCollector(regionInfoCollector) @@ -40,16 +40,15 @@ void TranslatorRegion::EmitElement(OsmElement * p) switch (p->type) { + case OsmElement::EntityType::Node: + BuildFeatureAndEmitFromNode(p, params); + break; case OsmElement::EntityType::Relation: - { BuildFeatureAndEmitFromRelation(p, params); break; - } case OsmElement::EntityType::Way: - { BuildFeatureAndEmitFromWay(p, params); break; - } default: break; } @@ -57,26 +56,21 @@ void TranslatorRegion::EmitElement(OsmElement * p) bool TranslatorRegion::IsSuitableElement(OsmElement const * p) const { - static std::set const adminLevels = {"2", "4", "5", "6", "7", "8"}; static std::set const places = {"city", "town", "village", "suburb", "neighbourhood", "hamlet", "locality", "isolated_dwelling"}; - bool haveBoundary = false; - bool haveAdminLevel = false; - bool haveName = false; for (auto const & t : p->Tags()) { if (t.key == "place" && places.find(t.value) != places.end()) return true; - if (t.key == "boundary" && t.value == "administrative") - haveBoundary = true; - else if (t.key == "admin_level" && adminLevels.find(t.value) != adminLevels.end()) - haveAdminLevel = true; - else if (t.key == "name" && !t.value.empty()) - haveName = true; + auto const & members = p->Members(); + auto const pred = [](OsmElement::Member const & m) { return m.role == "admin_centre"; }; + if (t.key == "boundary" && t.value == "police" && + std::find_if(std::begin(members), std::end(members), pred) != std::end(members)) + return true; - if (haveBoundary && haveAdminLevel && haveName) + if (t.key == "boundary" && t.value == "administrative") return true; } @@ -146,4 +140,16 @@ void TranslatorRegion::BuildFeatureAndEmitFromWay(OsmElement const * p, FeatureP AddInfoAboutRegion(p, id); (*m_emitter)(fb); } + +void TranslatorRegion::BuildFeatureAndEmitFromNode(OsmElement const * p, FeatureParams & params) +{ + m2::PointD const pt = MercatorBounds::FromLatLon(p->lat, p->lon); + FeatureBuilder1 fb; + fb.SetCenter(pt); + auto const id = base::MakeOsmNode(p->id); + fb.SetOsmId(id); + fb.SetParams(params); + AddInfoAboutRegion(p, id); + (*m_emitter)(fb); +} } // namespace generator diff --git a/generator/translator_region.hpp b/generator/translator_region.hpp index e836ab28c3..827f735ea4 100644 --- a/generator/translator_region.hpp +++ b/generator/translator_region.hpp @@ -18,7 +18,10 @@ struct GenerateInfo; namespace generator { class EmitterInterface; +namespace regions +{ class RegionInfoCollector; +} // namespace regions namespace cache { class IntermediateDataReader; @@ -30,7 +33,7 @@ class TranslatorRegion : public TranslatorInterface public: explicit TranslatorRegion(std::shared_ptr emitter, cache::IntermediateDataReader & holder, - RegionInfoCollector & regionInfoCollector); + regions::RegionInfoCollector & regionInfoCollector); void EmitElement(OsmElement * p) override; @@ -40,10 +43,11 @@ private: bool ParseParams(OsmElement * p, FeatureParams & params) const; void BuildFeatureAndEmitFromRelation(OsmElement const * p, FeatureParams & params); void BuildFeatureAndEmitFromWay(OsmElement const * p, FeatureParams & params); + void BuildFeatureAndEmitFromNode(OsmElement const * p, FeatureParams & params); private: std::shared_ptr m_emitter; cache::IntermediateDataReader & m_holder; - RegionInfoCollector & m_regionInfoCollector; + regions::RegionInfoCollector & m_regionInfoCollector; }; } // namespace generator