diff --git a/editor/xml_feature.cpp b/editor/xml_feature.cpp index b5729a98cb..27b7750781 100644 --- a/editor/xml_feature.cpp +++ b/editor/xml_feature.cpp @@ -10,7 +10,6 @@ namespace { -constexpr int const kLatLonTolerance = 7; constexpr char const * kTimestamp = "timestamp"; constexpr char const * kIndex = "mwm_file_index"; constexpr char const * kUploadTimestamp = "upload_timestamp"; @@ -193,20 +192,6 @@ XMLFeature::TMercatorGeometry XMLFeature::GetGeometry() const return geometry; } -/// Geometry points are now stored in nodes like in osm . -/// But they are not the same as osm's. I.e. osm's one stores reference to a -/// with it's own data and lat, lon. Here we store only cooridanes in mercator. -void XMLFeature::SetGeometry(TMercatorGeometry const & geometry) -{ - ASSERT_EQUAL(GetType(), Type::Way, ("Only ways have geometry")); - for (auto const & point : geometry) - { - auto nd = GetRootNode().append_child("nd"); - nd.append_attribute("x") = strings::to_string_dac(point.x, kLatLonTolerance).data(); - nd.append_attribute("y") = strings::to_string_dac(point.y, kLatLonTolerance).data(); - } -} - string XMLFeature::GetName(string const & lang) const { auto const suffix = (lang == kDefaultLang || lang.empty()) ? "" : ":" + lang; diff --git a/editor/xml_feature.hpp b/editor/xml_feature.hpp index 83578a89e9..fd6d167355 100644 --- a/editor/xml_feature.hpp +++ b/editor/xml_feature.hpp @@ -29,6 +29,9 @@ class XMLFeature static char const * const kDefaultLang; public: + // Used in point to string serialization. + static constexpr int kLatLonTolerance = 7; + enum class Type { Node, @@ -67,7 +70,26 @@ public: /// Sets geometry in mercator to match against FeatureType's geometry in mwm /// when megrating to a new mwm build. - void SetGeometry(TMercatorGeometry const & geometry); + /// Geometry points are now stored in nodes like in osm . + /// But they are not the same as osm's. I.e. osm's one stores reference to a + /// with it's own data and lat, lon. Here we store only cooridanes in mercator. + template + void SetGeometry(TIterator begin, TIterator end) + { + ASSERT_EQUAL(GetType(), Type::Way, ("Only ways have geometry")); + for (; begin != end; ++begin) + { + auto nd = GetRootNode().append_child("nd"); + nd.append_attribute("x") = strings::to_string_dac(begin->x, kLatLonTolerance).data(); + nd.append_attribute("y") = strings::to_string_dac(begin->y, kLatLonTolerance).data(); + } + } + + template + void SetGeometry(TCollection const & geometry) + { + SetGeometry(begin(geometry), end(geometry)); + } string GetName(string const & lang) const; string GetName(uint8_t const langCode = StringUtf8Multilang::DEFAULT_CODE) const; diff --git a/geometry/algorithm.cpp b/geometry/algorithm.cpp new file mode 100644 index 0000000000..76ebe95f09 --- /dev/null +++ b/geometry/algorithm.cpp @@ -0,0 +1,81 @@ +#include "geometry/algorithm.hpp" +#include "geometry/triangle2d.hpp" + +#include "base/logging.hpp" + +namespace m2 +{ +// CalculatePolyLineCenter ----------------------------------------------------------------------------- + +void CalculatePolyLineCenter::operator()(m2::PointD const & pt) +{ + m_length += (m_poly.empty() ? 0.0 : m_poly.back().m_p.Length(pt)); + m_poly.emplace_back(pt, m_length); +} + +PointD CalculatePolyLineCenter::GetCenter() const +{ + using TIter = vector::const_iterator; + + double const l = m_length / 2.0; + + TIter e = lower_bound(m_poly.begin(), m_poly.end(), Value(m2::PointD(0, 0), l)); + if (e == m_poly.begin()) + { + /// @todo It's very strange, but we have linear objects with zero length. + LOG(LWARNING, ("Zero length linear object")); + return e->m_p; + } + + TIter b = e - 1; + + double const f = (l - b->m_len) / (e->m_len - b->m_len); + + // For safety reasons (floating point calculations) do comparison instead of ASSERT. + if (0.0 <= f && f <= 1.0) + return (b->m_p * (1 - f) + e->m_p * f); + else + return ((b->m_p + e->m_p) / 2.0); +} + +// CalculatePointOnSurface ------------------------------------------------------------------------- + +CalculatePointOnSurface::CalculatePointOnSurface(m2::RectD const & rect) + : m_rectCenter(rect.Center()) + , m_center(m_rectCenter) + , m_squareDistanceToApproximate(numeric_limits::max()) +{ +} + +void CalculatePointOnSurface::operator()(PointD const & p1, PointD const & p2, PointD const & p3) +{ + if (m_squareDistanceToApproximate == 0.0) + return; + if (m2::IsPointInsideTriangle(m_rectCenter, p1, p2, p3)) + { + m_center = m_rectCenter; + m_squareDistanceToApproximate = 0.0; + return; + } + PointD triangleCenter(p1); + triangleCenter += p2; + triangleCenter += p3; + triangleCenter = triangleCenter / 3.0; + + double triangleDistance = m_rectCenter.SquareLength(triangleCenter); + if (triangleDistance <= m_squareDistanceToApproximate) + { + m_center = triangleCenter; + m_squareDistanceToApproximate = triangleDistance; + } +} + +// CalculateBoundingBox ---------------------------------------------------------------------------- + +void CalculateBoundingBox::operator()(PointD const & p) +{ + // Works just fine. If you don't belive me, see geometry/rect2d.hpp. + // Pay attention to MakeEmpty and Add functions. + m_boundingBox.Add(p); +} +} // namespace m2 diff --git a/geometry/algorithm.hpp b/geometry/algorithm.hpp new file mode 100644 index 0000000000..6513bd6885 --- /dev/null +++ b/geometry/algorithm.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "geometry/point2d.hpp" +#include "geometry/rect2d.hpp" + +#include "std/vector.hpp" + +namespace m2 +{ +// This class is used to calculate a point on a polyline +// such as that the paths (not the distance) from that point +// to both ends of a polyline have the same length. +class CalculatePolyLineCenter +{ +public: + CalculatePolyLineCenter() : m_length(0.0) {} + void operator()(PointD const & pt); + + PointD GetCenter() const; + +private: + struct Value + { + Value(PointD const & p, double l) : m_p(p), m_len(l) {} + bool operator<(Value const & r) const { return (m_len < r.m_len); } + PointD m_p; + double m_len; + }; + + vector m_poly; + double m_length; +}; + +// This class is used to calculate a point such as that +// it lays on the figure triangle that is the closest to +// figure bounding box center. +class CalculatePointOnSurface +{ +public: + CalculatePointOnSurface(RectD const & rect); + + void operator()(PointD const & p1, PointD const & p2, PointD const & p3); + PointD GetCenter() const { return m_center; } +private: + PointD m_rectCenter; + PointD m_center; + double m_squareDistanceToApproximate; +}; + +// Calculates the smallest rect that includes given geometry. +class CalculateBoundingBox +{ +public: + void operator()(PointD const & p); + RectD GetBoundingBox() const { return m_boundingBox; } +private: + RectD m_boundingBox; +}; +} // namespace m2 diff --git a/geometry/geometry.pro b/geometry/geometry.pro index 9f297dacbf..ff07f193ff 100644 --- a/geometry/geometry.pro +++ b/geometry/geometry.pro @@ -9,6 +9,7 @@ ROOT_DIR = .. include($$ROOT_DIR/common.pri) SOURCES += \ + algorithm.cpp \ angles.cpp \ distance_on_sphere.cpp \ latlon.cpp \ @@ -22,6 +23,7 @@ SOURCES += \ triangle2d.cpp \ HEADERS += \ + algorithm.hpp \ angles.hpp \ any_rect2d.hpp \ avg_vector.hpp \ diff --git a/geometry/geometry_tests/algorithm_test.cpp b/geometry/geometry_tests/algorithm_test.cpp new file mode 100644 index 0000000000..3f349f9791 --- /dev/null +++ b/geometry/geometry_tests/algorithm_test.cpp @@ -0,0 +1,166 @@ +#include "testing/testing.hpp" + +#include "geometry/algorithm.hpp" + +#include "std/vector.hpp" + +using m2::PointD; +using m2::RectD; +using m2::CalculatePolyLineCenter; +using m2::CalculatePointOnSurface; +using m2::CalculateBoundingBox; + +namespace +{ +PointD GetPolyLineCenter(vector const & points) +{ + CalculatePolyLineCenter doCalc; + for (auto const & p : points) + doCalc(p); + return doCalc.GetCenter(); +} + +RectD GetBoundingBox(vector const & points) +{ + CalculateBoundingBox doCalc; + for (auto const p : points) + doCalc(p); + return doCalc.GetBoundingBox(); +} + +PointD GetPointOnSurface(vector const & points) +{ + ASSERT(!points.empty() && points.size() % 3 == 0, ()); + CalculatePointOnSurface doCalc(GetBoundingBox(points)); + for (auto i = 0; i < points.size() - 3; i += 3) + doCalc(points[i], points[i + 1], points[i + 2]); + return doCalc.GetCenter(); + +} +} // namespace + +UNIT_TEST(CalculatePolyLineCenter) +{ + { + vector const points { + {0, 0}, + {1, 1}, + {2, 2} + }; + TEST_EQUAL(GetPolyLineCenter(points), PointD(1, 1), ()); + } + { + vector const points { + {0, 2}, + {1, 1}, + {2, 2} + }; + TEST_EQUAL(GetPolyLineCenter(points), PointD(1, 1), ()); + } + { + vector const points { + {1, 1}, + {2, 2}, + {4, 4}, + }; + TEST_EQUAL(GetPolyLineCenter(points), PointD(2.5, 2.5), ()); + } + { + vector const points { + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0} + }; + // Also logs a warning message. + TEST_EQUAL(GetPolyLineCenter(points), PointD(0, 0), ()); + } + { + vector const points { + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0.000001}, + {0, 0.000001}, + {0, 0.000001}, + {0, 0.000001} + }; + // Also logs a warning message. + TEST(GetPolyLineCenter(points).EqualDxDy(PointD(0, .0000005), 1e-7), ()); + } +} + +UNIT_TEST(CalculateCenter) +{ + { + vector const points { + {2, 2} + }; + TEST_EQUAL(GetBoundingBox(points), RectD({2, 2}, {2, 2}), ()); + } + { + vector const points { + {0, 0}, + {1, 1}, + {2, 2} + }; + TEST_EQUAL(GetBoundingBox(points), RectD({0, 0}, {2, 2}), ()); + } + { + vector const points { + {0, 2}, + {1, 1}, + {2, 2}, + {1, 2}, + {2, 2}, + {2, 0} + }; + TEST_EQUAL(GetBoundingBox(points), RectD({0, 0}, {2, 2}), ()); + } +} + +UNIT_TEST(CalculatePointOnSurface) +{ + { + vector const points { + {0, 0}, + {1, 1}, + {2, 2} + }; + TEST_EQUAL(GetPointOnSurface(points), PointD(1, 1), ()); + } + { + vector const points { + {0, 2}, + {1, 1}, + {2, 2}, + {1, 2}, + {2, 2}, + {2, 0} + }; + TEST_EQUAL(GetPointOnSurface(points), PointD(1, 1), ()); + } + { + vector const points { + {0, 0}, {1, 1}, {2, 0}, + {1, 1}, {2, 0}, {3, 1}, + {2, 0}, {3, 1}, {4, 0}, + + {4, 0}, {3, 1}, {4, 2}, + {3, 1}, {4, 2}, {3, 3}, + {4, 2}, {3, 3}, {4, 4}, + + {3, 3}, {4, 4}, {2, 4}, + {3, 3}, {2, 4}, {1, 3}, + {1, 3}, {2, 4}, {0, 4}, + + {0, 4}, {1, 3}, {0, 2}, + {1, 3}, {0, 2}, {1, 1}, // Center of this triangle is used as a result. + {0, 2}, {1, 1}, {0, 0}, + }; + TEST_EQUAL(GetPointOnSurface(points), PointD(2.0 / 3.0, 2), ()); + } +} diff --git a/geometry/geometry_tests/geometry_tests.pro b/geometry/geometry_tests/geometry_tests.pro index e9c04c136c..050968f793 100644 --- a/geometry/geometry_tests/geometry_tests.pro +++ b/geometry/geometry_tests/geometry_tests.pro @@ -19,6 +19,7 @@ HEADERS += \ SOURCES += \ ../../testing/testingmain.cpp \ + algorithm_test.cpp \ angle_test.cpp \ anyrect_test.cpp \ cellid_test.cpp \ diff --git a/indexer/edits_migration.cpp b/indexer/edits_migration.cpp new file mode 100644 index 0000000000..92beaff9f0 --- /dev/null +++ b/indexer/edits_migration.cpp @@ -0,0 +1,135 @@ +#include "geometry/algorithm.hpp" +#include "geometry/mercator.hpp" + +#include "indexer/edits_migration.hpp" +#include "indexer/feature.hpp" + +#include "base/logging.hpp" + +#include "std/algorithm.hpp" +#include "std/unique_ptr.hpp" + +namespace +{ +m2::PointD CalculateCenter(vector const & geometry) +{ + ASSERT(!geometry.empty() && geometry.size() % 3 == 0, + ("Invalid geometry should be handled in caller.")); + + m2::CalculateBoundingBox doCalcBox; + for (auto const & p : geometry) + doCalcBox(p); + + m2::CalculatePointOnSurface doCalcCenter(doCalcBox.GetBoundingBox()); + for (auto i = 0; i < geometry.size() - 3; i += 3) + doCalcCenter(geometry[i], geometry[i + 1], geometry[i + 2]); + + return doCalcCenter.GetCenter(); +} + +uint32_t GetGeometriesIntersectionCapacity(vector g1, + vector g2) +{ + struct Counter + { + Counter & operator++() { return *this; } + Counter & operator*() { return *this; } + Counter & operator=(m2::PointD const &) + { + ++m_count; + return *this; + } + uint32_t m_count = 0; + }; + + sort(begin(g1), end(g1)); + sort(begin(g2), end(g2)); + + Counter counter; + set_intersection(begin(g1), end(g1), + begin(g2), end(g2), + counter, [](m2::PointD const & p1, m2::PointD const & p2) + { + // TODO(mgsergio): Use 1e-7 everyware instead of MercatotBounds::GetCellID2PointAbsEpsilon + return p1 < p2 && !p1.EqualDxDy(p2, 1e-7); + }); + return counter.m_count; +} +} // namespace + +namespace editor +{ +FeatureID MigrateNodeFeatureIndex(osm::Editor::TForEachFeaturesNearByFn & forEach, + XMLFeature const & xml) +{ + unique_ptr feature; + auto count = 0; + forEach( + [&feature, &xml, &count](FeatureType const & ft) + { + if (ft.GetFeatureType() != feature::GEOM_POINT) + return; + // TODO(mgsergio): Check that ft and xml correspond to the same feature. + feature = make_unique(ft); + ++count; + }, + MercatorBounds::FromLatLon(xml.GetCenter())); + if (!feature) + MYTHROW(MigrationError, ("No pointed features returned.")); + if (count > 1) + { + LOG(LWARNING, + (count, "features returned for point", MercatorBounds::FromLatLon(xml.GetCenter()))); + } + return feature->GetID(); +} + +FeatureID MigrateWayFeatureIndex(osm::Editor::TForEachFeaturesNearByFn & forEach, + XMLFeature const & xml) +{ + unique_ptr feature; + auto bestScore = 0.6; // initial score is used as a threshold. + auto const geometry = xml.GetGeometry(); + + if (geometry.empty() || geometry.size() % 3 != 0) + MYTHROW(MigrationError, ("Feature has invalid geometry", xml)); + + auto const someFeaturePoint = CalculateCenter(geometry); + auto count = 0; + LOG(LDEBUG, ("SomePoint", someFeaturePoint)); + forEach( + [&feature, &xml, &geometry, &count, &bestScore](FeatureType const & ft) + { + if (ft.GetFeatureType() != feature::GEOM_AREA) + return; + ++count; + auto const ftGeometry = ft.GetTriangesAsPoints(FeatureType::BEST_GEOMETRY); + auto matched = GetGeometriesIntersectionCapacity(ftGeometry, geometry); + auto const score = static_cast(matched) / geometry.size(); + if (score > bestScore) + { + bestScore = score; + feature = make_unique(ft); + } + }, + someFeaturePoint); + if (count == 0) + MYTHROW(MigrationError, ("No ways returned for point", someFeaturePoint)); + if (!feature) + { + MYTHROW(MigrationError, + ("None of returned ways suffice. Possibly, the feature have been deleted.")); + } + return feature->GetID(); +} + +FeatureID MigrateFeatureIndex(osm::Editor::TForEachFeaturesNearByFn & forEach, + XMLFeature const & xml) +{ + switch (xml.GetType()) + { + case XMLFeature::Type::Node: return MigrateNodeFeatureIndex(forEach, xml); + case XMLFeature::Type::Way: return MigrateWayFeatureIndex(forEach, xml); + } +} +} // namespace editor diff --git a/indexer/edits_migration.hpp b/indexer/edits_migration.hpp new file mode 100644 index 0000000000..53e6ba9f8e --- /dev/null +++ b/indexer/edits_migration.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "indexer/feature_decl.hpp" +#include "indexer/osm_editor.hpp" + +#include "editor/xml_feature.hpp" + +#include "base/exception.hpp" + +namespace editor +{ +DECLARE_EXCEPTION(MigrationError, RootException); + +/// Tries to match xml feature with one on a new mwm and retruns FeatrueID +/// of a found feature, thows MigrationError if migration fails. +FeatureID MigrateFeatureIndex(osm::Editor::TForEachFeaturesNearByFn & forEach, + XMLFeature const & xml); +} // namespace editor diff --git a/indexer/feature.cpp b/indexer/feature.cpp index 5789fe9481..a5c43525ee 100644 --- a/indexer/feature.cpp +++ b/indexer/feature.cpp @@ -103,7 +103,7 @@ editor::XMLFeature FeatureType::ToXML() const else { ParseTriangles(BEST_GEOMETRY); - feature.SetGeometry({begin(m_triangles), end(m_triangles)}); + feature.SetGeometry(begin(m_triangles), end(m_triangles)); } ForEachName([&feature](uint8_t const & lang, string const & name) diff --git a/indexer/feature.hpp b/indexer/feature.hpp index 8559b15e98..ebd8ab8d5e 100644 --- a/indexer/feature.hpp +++ b/indexer/feature.hpp @@ -216,6 +216,7 @@ public: ASSERT(m_bPointsParsed, ()); return m_points.size(); } + inline m2::PointD const & GetPoint(size_t i) const { ASSERT_LESS(i, m_points.size(), ()); @@ -235,6 +236,12 @@ public: } } + inline vector GetTriangesAsPoints(int scale) const + { + ParseTriangles(scale); + return {begin(m_triangles), end(m_triangles)}; + } + template void ForEachTriangleEx(TFunctor && f, int scale) const { diff --git a/indexer/feature_algo.cpp b/indexer/feature_algo.cpp index 30c822b2d0..3112d37b0b 100644 --- a/indexer/feature_algo.cpp +++ b/indexer/feature_algo.cpp @@ -1,6 +1,7 @@ #include "indexer/feature_algo.hpp" #include "indexer/feature.hpp" +#include "geometry/algorithm.hpp" #include "geometry/distance.hpp" #include "geometry/triangle2d.hpp" @@ -9,99 +10,9 @@ namespace feature { - -class CalculateLineCenter -{ - typedef m2::PointD P; - - struct Value - { - Value(P const & p, double l) : m_p(p), m_len(l) {} - - bool operator< (Value const & r) const { return (m_len < r.m_len); } - - P m_p; - double m_len; - }; - - vector m_poly; - double m_length; - -public: - CalculateLineCenter() : m_length(0.0) {} - - void operator() (m2::PointD const & pt) - { - m_length += (m_poly.empty() ? 0.0 : m_poly.back().m_p.Length(pt)); - m_poly.emplace_back(pt, m_length); - } - - P GetCenter() const - { - typedef vector::const_iterator IterT; - - double const l = m_length / 2.0; - - IterT e = lower_bound(m_poly.begin(), m_poly.end(), Value(m2::PointD(0, 0), l)); - if (e == m_poly.begin()) - { - /// @todo It's very strange, but we have linear objects with zero length. - LOG(LWARNING, ("Zero length linear object")); - return e->m_p; - } - - IterT b = e-1; - - double const f = (l - b->m_len) / (e->m_len - b->m_len); - - // For safety reasons (floating point calculations) do comparison instead of ASSERT. - if (0.0 <= f && f <= 1.0) - return (b->m_p * (1-f) + e->m_p * f); - else - return ((b->m_p + e->m_p) / 2.0); - } -}; - -class CalculatePointOnSurface -{ - typedef m2::PointD P; - P m_center; - P m_rectCenter; - double m_squareDistanceToApproximate; - -public: - CalculatePointOnSurface(m2::RectD const & rect) - { - m_rectCenter = rect.Center(); - m_center = m_rectCenter; - m_squareDistanceToApproximate = numeric_limits::max(); - } - - void operator() (P const & p1, P const & p2, P const & p3) - { - if (m_squareDistanceToApproximate == 0.0) - return; - if (m2::IsPointInsideTriangle(m_rectCenter, p1, p2, p3)) - { - m_center = m_rectCenter; - m_squareDistanceToApproximate = 0.0; - return; - } - P triangleCenter(p1); - triangleCenter += p2; - triangleCenter += p3; - triangleCenter = triangleCenter / 3.0; - - double triangleDistance = m_rectCenter.SquareLength(triangleCenter); - if (triangleDistance <= m_squareDistanceToApproximate) - { - m_center = triangleCenter; - m_squareDistanceToApproximate = triangleDistance; - } - } - P GetCenter() const { return m_center; } -}; - +/// @returns point on a feature that is the closest to f.GetLimitRect().Center(). +/// It is used by many ednities in the core of mapsme. Do not modify it's +/// logic if you really-really know what you are doing. m2::PointD GetCenter(FeatureType const & f, int scale) { EGeomType const type = f.GetFeatureType(); @@ -112,7 +23,7 @@ m2::PointD GetCenter(FeatureType const & f, int scale) case GEOM_LINE: { - CalculateLineCenter doCalc; + m2::CalculatePolyLineCenter doCalc; f.ForEachPoint(doCalc, scale); return doCalc.GetCenter(); } @@ -120,7 +31,7 @@ m2::PointD GetCenter(FeatureType const & f, int scale) default: { ASSERT_EQUAL(type, GEOM_AREA, ()); - CalculatePointOnSurface doCalc(f.GetLimitRect(scale)); + m2::CalculatePointOnSurface doCalc(f.GetLimitRect(scale)); f.ForEachTriangle(doCalc, scale); return doCalc.GetCenter(); } @@ -197,5 +108,4 @@ double GetMinDistanceMeters(FeatureType const & ft, m2::PointD const & pt) { return GetMinDistanceMeters(ft, pt, FeatureType::BEST_GEOMETRY); } - } // namespace feature diff --git a/indexer/indexer.pro b/indexer/indexer.pro index ae7e529cd7..8036093582 100644 --- a/indexer/indexer.pro +++ b/indexer/indexer.pro @@ -19,6 +19,7 @@ SOURCES += \ drawing_rules.cpp \ drules_selector.cpp \ drules_selector_parser.cpp \ + edits_migration.cpp \ feature.cpp \ feature_algo.cpp \ feature_covering.cpp \ @@ -61,6 +62,7 @@ HEADERS += \ drules_include.hpp \ drules_selector.hpp \ drules_selector_parser.hpp \ + edits_migration.hpp \ feature.hpp \ feature_algo.hpp \ feature_covering.hpp \ diff --git a/indexer/osm_editor.cpp b/indexer/osm_editor.cpp index fabbb091dc..d670491211 100644 --- a/indexer/osm_editor.cpp +++ b/indexer/osm_editor.cpp @@ -1,4 +1,5 @@ #include "indexer/classificator.hpp" +#include "indexer/edits_migration.hpp" #include "indexer/feature_algo.hpp" #include "indexer/feature_decl.hpp" #include "indexer/feature_impl.hpp" @@ -17,6 +18,8 @@ #include "coding/internal/file_data.hpp" +#include "geometry/algorithm.hpp" + #include "base/exception.hpp" #include "base/logging.hpp" #include "base/stl_helpers.hpp" @@ -230,15 +233,6 @@ TypeDescription const * GetTypeDescription(uint32_t type, uint8_t typeTruncateLe return nullptr; } -uint32_t MigrateFeatureIndex(XMLFeature const & /*xml*/) -{ - // @TODO(mgsergio): Update feature's index when user has downloaded fresh MWM file and old indices point to other features. - // Possible implementation: use function to load features in rect (center feature's point) and somehow compare/choose from them. - // Probably we need to store more data about features in xml, e.g. types, may be other data, to match them correctly. - MYTHROW(RootException, ("TODO(mgsergio, AlexZ): Implement correct feature migrate code. Delete data/edits.xml to continue.")); - return 0; -} - /// Compares editable fields connected with feature ignoring street. bool AreFeaturesEqualButStreet(FeatureType const & a, FeatureType const & b) { @@ -268,18 +262,11 @@ XMLFeature GetMatchingFeatureFromOSM(osm::ChangesetWrapper & cw, if (featurePtr->GetFeatureType() == feature::GEOM_POINT) return cw.GetMatchingNodeFeatureFromOSM(featurePtr->GetCenter()); - vector geometry; - featurePtr->ForEachTriangle( - [&geometry](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3) - { - geometry.push_back(p1); - geometry.push_back(p2); - geometry.push_back(p3); - // Warning: geometry is cached in FeatureType. So if it wasn't BEST_GEOMETRY, - // we can never have it. Features here came from editors loader and should - // have BEST_GEOMETRY geometry. - }, - FeatureType::BEST_GEOMETRY); + // Warning: geometry is cached in FeatureType. So if it wasn't BEST_GEOMETRY, + // we can never have it. Features here came from editors loader and should + // have BEST_GEOMETRY geometry. + auto geometry = featurePtr->GetTriangesAsPoints(FeatureType::BEST_GEOMETRY); + // Filters out duplicate points for closed ways or triangles' vertices. my::SortUnique(geometry); @@ -328,17 +315,18 @@ void Editor::LoadMapEdits() }}; int deleted = 0, modified = 0, created = 0; + bool needRewriteEdits = false; + for (xml_node mwm : doc.child(kXmlRootNode).children(kXmlMwmNode)) { string const mapName = mwm.attribute("name").as_string(""); int64_t const mapVersion = mwm.attribute("version").as_llong(0); - MwmSet::MwmId const id = m_mwmIdByMapNameFn(mapName); - if (!id.IsAlive()) - { - // TODO(AlexZ): MWM file was deleted, but changes remain. What should we do in this case? - LOG(LWARNING, (mapName, "version", mapVersion, "references not existing MWM file.")); - continue; - } + MwmSet::MwmId const mwmId = m_mwmIdByMapNameFn(mapName); + // TODO(mgsergio, AlexZ): Get rid of mapVersion and switch to upload_time. + // TODO(mgsergio, AlexZ): Is it normal to have isMwmIdAlive and mapVersion + // NOT equal to mwmId.GetInfo()->GetVersion() at the same time? + auto const needMigrateEdits = !mwmId.IsAlive() || mapVersion != mwmId.GetInfo()->GetVersion(); + needRewriteEdits |= needMigrateEdits; for (auto const & section : sections) { @@ -347,12 +335,11 @@ void Editor::LoadMapEdits() try { XMLFeature const xml(nodeOrWay.node()); - // TODO(mgsergio, AlexZ): MigrateFeatureIndex() case will always throw now, so 'old' features are ignored. - uint32_t const featureIndex = mapVersion == id.GetInfo()->GetVersion() ? xml.GetMWMFeatureIndex() - : MigrateFeatureIndex(xml); - FeatureID const fid(id, featureIndex); + auto const fid = needMigrateEdits + ? MigrateFeatureIndex(m_forEachFeatureAtPointFn, xml) + : FeatureID(mwmId, xml.GetMWMFeatureIndex()); - FeatureTypeInfo & fti = m_features[id][fid.m_index]; + FeatureTypeInfo & fti = m_features[fid.m_mwmId][fid.m_index]; if (section.first == FeatureStatus::Created) { @@ -387,10 +374,17 @@ void Editor::LoadMapEdits() nodeOrWay.node().print(s, " "); LOG(LERROR, (ex.what(), "Can't create XMLFeature in section", section.second, s.str())); } + catch (editor::MigrationError const & ex) + { + LOG(LWARNING, (ex.Msg(), "mwmId =", mwmId, XMLFeature(nodeOrWay.node()))); + } } // for nodes } // for sections } // for mwms + // Save edits with new indexes and mwm version to avoid another migration on next startup. + if (needRewriteEdits) + Save(GetEditorFilePath()); LOG(LINFO, ("Loaded", modified, "modified,", created, "created and", deleted, "deleted features.")); } @@ -623,10 +617,6 @@ vector Editor::GetFeaturesByStatus(MwmSet::MwmId const & mwmId, Featur vector Editor::EditableMetadataForType(FeatureType const & feature) const { - // Cannot edit old mwm data. - if (platform::migrate::NeedMigrate()) - return {}; - // TODO(mgsergio): Load editable fields into memory from XML and query them here. feature::TypesHolder const types(feature); set fields; @@ -658,10 +648,6 @@ vector Editor::EditableMetadataForType(FeatureType const & feat bool Editor::IsNameEditable(FeatureType const & feature) const { - // Cannot edit old mwm data. - if (platform::migrate::NeedMigrate()) - return false; - feature::TypesHolder const types(feature); for (auto type : types) { @@ -674,10 +660,6 @@ bool Editor::IsNameEditable(FeatureType const & feature) const bool Editor::IsAddressEditable(FeatureType const & feature) const { - // Cannot edit old mwm data. - if (platform::migrate::NeedMigrate()) - return false; - feature::TypesHolder const types(feature); auto & isBuilding = ftypes::IsBuildingChecker::Instance(); for (auto type : types) diff --git a/indexer/osm_editor.hpp b/indexer/osm_editor.hpp index b1efc2e4c5..4339517cf4 100644 --- a/indexer/osm_editor.hpp +++ b/indexer/osm_editor.hpp @@ -2,6 +2,7 @@ #include "geometry/rect2d.hpp" +#include "indexer/feature.hpp" #include "indexer/feature_meta.hpp" #include "indexer/mwm_set.hpp" @@ -15,10 +16,6 @@ #include "std/set.hpp" #include "std/string.hpp" -struct FeatureID; -class FeatureType; -class Index; - namespace osm { class Editor final @@ -26,10 +23,14 @@ class Editor final Editor() = default; public: + using TFeatureTypeFn = function; // Mimics Framework::TFeatureTypeFn. + using TMwmIdByMapNameFn = function; using TInvalidateFn = function; using TFeatureLoaderFn = function (FeatureID const & /*fid*/)>; using TFeatureOriginalStreetFn = function; + using TForEachFeaturesNearByFn = + function; enum class UploadResult { Success, @@ -52,6 +53,7 @@ public: void SetInvalidateFn(TInvalidateFn const & fn) { m_invalidateFn = fn; } void SetFeatureLoaderFn(TFeatureLoaderFn const & fn) { m_getOriginalFeatureFn = fn; } void SetFeatureOriginalStreetFn(TFeatureOriginalStreetFn const & fn) { m_getOriginalFeatureStreetFn = fn; } + void SetForEachFeatureAtPointFn(TForEachFeaturesNearByFn const & fn) { m_forEachFeatureAtPointFn = fn; } void LoadMapEdits(); /// Resets editor to initial state: no any edits or created/deleted features. @@ -146,6 +148,8 @@ private: TFeatureLoaderFn m_getOriginalFeatureFn; /// Get feature original street name or empty string. TFeatureOriginalStreetFn m_getOriginalFeatureStreetFn; + /// Iterate over all features in some area that includes given point. + TForEachFeaturesNearByFn m_forEachFeatureAtPointFn; }; // class Editor string DebugPrint(Editor::FeatureStatus fs); diff --git a/map/framework.cpp b/map/framework.cpp index c04052c283..8320bf4818 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -360,6 +360,7 @@ Framework::Framework() return streets.first[streets.second].m_name; return {}; }); + editor.SetForEachFeatureAtPointFn(bind(&Framework::ForEachFeatureAtPoint, this, _1, _2)); editor.LoadMapEdits(); }