diff --git a/indexer/feature.hpp b/indexer/feature.hpp index 4b056abd52..e2ff75771e 100644 --- a/indexer/feature.hpp +++ b/indexer/feature.hpp @@ -20,6 +20,9 @@ class FeatureBase; /// Used for serialization\deserialization of features during --generate_features. class FeatureBuilder1 { + // needed for hacky coastlines merging + friend class FeatureBuilder1Merger; + public: FeatureBuilder1(); diff --git a/indexer/feature_merger.cpp b/indexer/feature_merger.cpp new file mode 100644 index 0000000000..9f1b95ee53 --- /dev/null +++ b/indexer/feature_merger.cpp @@ -0,0 +1,50 @@ +#include "feature_merger.hpp" + +#include "../base/logging.hpp" + +FeatureBuilder1Merger::FeatureBuilder1Merger(FeatureBuilder1 const & fb) + : FeatureBuilder1(fb) +{ +} + +bool FeatureBuilder1Merger::MergeWith(FeatureBuilder1 const & fb) +{ + // check that both features are of linear type + if (!fb.m_bLinear || !m_bLinear) + return false; + + // check that classificator types are the same + if (fb.m_Types != m_Types) + return false; + + // check last-first points equality + //if (m2::AlmostEqual(m_Geometry.back(), fb.m_Geometry.front())) + if (m_Geometry.back() == fb.m_Geometry.front()) + { + // merge fb at the end + for (size_t i = 1; i < fb.m_Geometry.size(); ++i) + { + m_Geometry.push_back(fb.m_Geometry[i]); + m_LimitRect.Add(m_Geometry.back()); + } + } + // check first-last points equality + //else if (m2::AlmostEqual(m_Geometry.front(), fb.m_Geometry.back())) + else if (m_Geometry.front() == fb.m_Geometry.back()) + { + // merge fb in the beginning + m_Geometry.insert(m_Geometry.begin(), fb.m_Geometry.begin(), fb.m_Geometry.end()); + for (size_t i = 0; i < fb.m_Geometry.size() - 1; ++i) + m_LimitRect.Add(fb.m_Geometry[i]); + } + else + return false; // no common points were found... + + + static int counter = 0; + // @TODO check if we got AREA feature after merging, this can be useful for coastlines + LOG(LINFO, (++counter, "features were merged!")); + + return true; +} + diff --git a/indexer/feature_merger.hpp b/indexer/feature_merger.hpp new file mode 100644 index 0000000000..dab4936721 --- /dev/null +++ b/indexer/feature_merger.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "feature.hpp" + +class FeatureBuilder1Merger : public FeatureBuilder1 +{ +public: + FeatureBuilder1Merger(FeatureBuilder1 const & fb); + bool MergeWith(FeatureBuilder1 const & fb); +}; diff --git a/indexer/indexer.pro b/indexer/indexer.pro index d872803bc2..5712ad0666 100644 --- a/indexer/indexer.pro +++ b/indexer/indexer.pro @@ -31,6 +31,7 @@ SOURCES += \ geometry_coding.cpp \ geometry_serialization.cpp \ tesselator.cpp \ + feature_merger.cpp \ HEADERS += \ feature.hpp \ @@ -66,3 +67,4 @@ HEADERS += \ point_to_int64.hpp \ tesselator.hpp \ tesselator_decl.hpp \ + feature_merger.hpp \ diff --git a/indexer/indexer_tool/feature_bucketer.hpp b/indexer/indexer_tool/feature_bucketer.hpp index dcad2423d5..b0a5926e6b 100644 --- a/indexer/indexer_tool/feature_bucketer.hpp +++ b/indexer/indexer_tool/feature_bucketer.hpp @@ -38,14 +38,14 @@ public: template explicit CellFeatureBucketer(TInfo & info) : m_Level(info.cellBucketingLevel), m_FeatureOutInitData(info.datFilePrefix, info.datFileSuffix), - m_worldMap(info.maxScaleForWorldFeatures, m_FeatureOutInitData) + m_worldMap(info.maxScaleForWorldFeatures, info.mergeCoastlines, m_FeatureOutInitData) { Init(); } /// @note this constructor doesn't support world file generation CellFeatureBucketer(int level, typename FeatureOutT::InitDataType const & initData) - : m_Level(level), m_FeatureOutInitData(initData), m_worldMap(-1, initData) + : m_Level(level), m_FeatureOutInitData(initData), m_worldMap(-1, false, initData) { Init(); } diff --git a/indexer/indexer_tool/feature_generator.hpp b/indexer/indexer_tool/feature_generator.hpp index 04ee8e6e0d..5a89112eb7 100644 --- a/indexer/indexer_tool/feature_generator.hpp +++ b/indexer/indexer_tool/feature_generator.hpp @@ -17,7 +17,7 @@ namespace feature { GenerateInfo() : maxScaleForWorldFeatures(-1), splitByPolygons(false), - simplifyCountriesLevel(-1) {} + simplifyCountriesLevel(-1), mergeCoastlines(false) {} string tmpDir, datFilePrefix, datFileSuffix; /// If not -1, world will be split by buckets with specified level int cellBucketingLevel; @@ -28,6 +28,7 @@ namespace feature int maxScaleForWorldFeatures; bool splitByPolygons; int simplifyCountriesLevel; + bool mergeCoastlines; }; bool GenerateFeatures(GenerateInfo & info, bool lightNodes); diff --git a/indexer/indexer_tool/indexer_tool.cpp b/indexer/indexer_tool/indexer_tool.cpp index 505c598eb9..3f2902e9aa 100644 --- a/indexer/indexer_tool/indexer_tool.cpp +++ b/indexer/indexer_tool/indexer_tool.cpp @@ -49,6 +49,7 @@ DEFINE_int32(generate_world_scale, -1, "If specified, features for zoomlevels [0 "which are enabled in classificator will be MOVED to the separate world file"); DEFINE_bool(split_by_polygons, false, "Use kml shape files to split planet by regions and countries"); DEFINE_int32(simplify_countries_level, -1, "If positive, simplifies country polygons. Recommended values [10..15]"); +DEFINE_bool(merge_coastlines, true, "If defined, tries to merge coastlines when renerating World file"); string AddSlashIfNeeded(string const & str) { @@ -130,6 +131,7 @@ int main(int argc, char ** argv) genInfo.cellBucketingLevel = FLAGS_bucketing_level; genInfo.maxScaleForWorldFeatures = FLAGS_generate_world_scale; + genInfo.mergeCoastlines = FLAGS_merge_coastlines; if (!feature::GenerateFeatures(genInfo, FLAGS_use_light_nodes)) return -1; diff --git a/indexer/indexer_tool/indexer_tool.pro b/indexer/indexer_tool/indexer_tool.pro index c67b7f33fc..80aace0aec 100644 --- a/indexer/indexer_tool/indexer_tool.pro +++ b/indexer/indexer_tool/indexer_tool.pro @@ -24,7 +24,7 @@ SOURCES += \ update_generator.cpp \ grid_generator.cpp \ statistics.cpp \ - kml_parser.cpp + kml_parser.cpp \ HEADERS += \ osm_element.hpp \ @@ -39,4 +39,4 @@ HEADERS += \ statistics.hpp \ kml_parser.hpp \ polygonizer.hpp \ - world_map_generator.hpp + world_map_generator.hpp \ diff --git a/indexer/indexer_tool/polygonizer.hpp b/indexer/indexer_tool/polygonizer.hpp index c83fef1eb6..efd1be172f 100644 --- a/indexer/indexer_tool/polygonizer.hpp +++ b/indexer/indexer_tool/polygonizer.hpp @@ -28,7 +28,7 @@ namespace feature public: template Polygonizer(TInfo & info) : m_FeatureOutInitData(info.datFilePrefix, info.datFileSuffix), - m_worldMap(info.maxScaleForWorldFeatures, m_FeatureOutInitData) + m_worldMap(info.maxScaleForWorldFeatures, info.mergeCoastlines, m_FeatureOutInitData) { CHECK(kml::LoadCountriesList(info.datFilePrefix, m_countries, info.simplifyCountriesLevel), ("Error loading country polygons files")); diff --git a/indexer/indexer_tool/world_map_generator.hpp b/indexer/indexer_tool/world_map_generator.hpp index 270b539deb..fe2c79d707 100644 --- a/indexer/indexer_tool/world_map_generator.hpp +++ b/indexer/indexer_tool/world_map_generator.hpp @@ -3,8 +3,11 @@ #include "../../defines.hpp" #include "../../indexer/feature.hpp" +#include "../../indexer/feature_merger.hpp" #include "../../indexer/feature_visibility.hpp" +#include "../../std/list.hpp" + #include template @@ -14,25 +17,87 @@ class WorldMapGenerator boost::scoped_ptr m_worldBucket; /// features visible before or at this scale level will go to World map int m_maxWorldScale; + bool m_mergeCoastlines; + + typedef list FeaturesContainerT; + FeaturesContainerT m_features; + +private: + /// scans all features and tries to merge them with each other + /// @return true if one feature was merged + bool ReMergeFeatures() + { + bool merged = false; + for (FeaturesContainerT::iterator base = m_features.begin(); base != m_features.end(); ++base) + { + FeaturesContainerT::iterator ft = base; + for (++ft; ft != m_features.end();) + { + if (base->MergeWith(*ft)) + { + m_features.erase(ft++); + merged = true; + } + else + ++ft; + } + } + return merged; + } + + void TryToMerge(FeatureBuilder1 const & fb) + { + // @TODO group features by types (use map of lists?) + for (FeaturesContainerT::iterator it = m_features.begin(); it != m_features.end(); ++it) + { + if (it->MergeWith(fb)) + return; + } + // do not loose feature if it wasn't merged + m_features.push_back(fb); + } public: - WorldMapGenerator(int maxWorldScale, typename FeatureOutT::InitDataType featureOutInitData) - : m_maxWorldScale(maxWorldScale) + WorldMapGenerator(int maxWorldScale, bool mergeCoastlines, + typename FeatureOutT::InitDataType featureOutInitData) + : m_maxWorldScale(maxWorldScale), m_mergeCoastlines(mergeCoastlines) { if (maxWorldScale >= 0) m_worldBucket.reset(new FeatureOutT(WORLD_FILE_NAME, featureOutInitData)); } + ~WorldMapGenerator() + { + // try to merge all merged features with each other + while (ReMergeFeatures()) + { + } + + // emit all merged features + for (FeaturesContainerT::iterator it = m_features.begin(); it != m_features.end(); ++it) + (*m_worldBucket)(*it); + } + bool operator()(FeatureBuilder1 const & fb) { if (m_worldBucket) { - int minScale = feature::MinDrawableScaleForFeature(fb.GetFeatureBase()); + FeatureBase fBase = fb.GetFeatureBase(); + int minScale = feature::MinDrawableScaleForFeature(fBase); CHECK_GREATER(minScale, -1, ("Non-drawable feature found!?")); - // separately store features needed for world map if (m_maxWorldScale >= minScale) { - (*m_worldBucket)(fb); + if (m_mergeCoastlines) + { + // we're merging only linear features, + // areas and points are written immediately + if (fBase.GetFeatureType() != FeatureBase::FEATURE_TYPE_LINE) + (*m_worldBucket)(fb); + else + TryToMerge(fb); + } + else + (*m_worldBucket)(fb); return true; } }