diff --git a/editor/changeset_wrapper.cpp b/editor/changeset_wrapper.cpp index 9eac138694..0e946028a3 100644 --- a/editor/changeset_wrapper.cpp +++ b/editor/changeset_wrapper.cpp @@ -3,6 +3,7 @@ #include "indexer/feature.hpp" #include "editor/changeset_wrapper.hpp" +#include "editor/osm_feature_matcher.hpp" #include "std/algorithm.hpp" #include "std/sstream.hpp" @@ -14,223 +15,6 @@ using editor::XMLFeature; -using TGeometry = set; - -namespace -{ -bool LatLonEqual(ms::LatLon const & a, ms::LatLon const & b) -{ - double constexpr eps = MercatorBounds::GetCellID2PointAbsEpsilon(); - return a.EqualDxDy(b, eps); -} - -double ScoreLatLon(XMLFeature const & xmlFt, ms::LatLon const & latLon) -{ - // TODO: Find proper score values; - return LatLonEqual(latLon, xmlFt.GetCenter()) ? 10 : -10; -} - -double ScoreGeometry(pugi::xml_document const & osmResponse, pugi::xml_node const & way, - TGeometry geometry) -{ - int matched = 0; - int total = 0; - // TODO(mgsergio): optimize using sorting and scaning eps-squares. - // The idea is to build squares with side = eps on points from the first set. - // Sort them by y and x in y groups. - // Then pass points from the second set through constructed struct and register events - // like square started, square ended, point encounted. - - for (auto const xNodeRef : way.select_nodes("nd/@ref")) - { - ++total; - string const nodeRef = xNodeRef.attribute().value(); - auto const node = osmResponse.select_node(("node[@ref='" + nodeRef + "']").data()).node(); - if (!node) - { - LOG(LDEBUG, ("OSM response have ref", nodeRef, - "but have no node with such id.", osmResponse)); - continue; // TODO: or break and return -1000? - } - - XMLFeature xmlFt(node); - for (auto pointIt = begin(geometry); pointIt != end(geometry); ++pointIt) - { - if (LatLonEqual(xmlFt.GetCenter(), MercatorBounds::ToLatLon(*pointIt))) - { - ++matched; - geometry.erase(pointIt); - } - } - } - - return static_cast(matched) / total * 100; -} - -double ScoreNames(XMLFeature const & xmlFt, StringUtf8Multilang const & names) -{ - double score = 0; - names.ForEach([&score, &xmlFt](uint8_t const langCode, string const & name) - { - if (xmlFt.GetName(langCode) == name) - score += 1; - return true; - }); - - // TODO(mgsergio): Deafult name match should have greater wieght. Should it? - - return score; -} - -vector GetOsmOriginalTagsForType(feature::Metadata::EType const et) -{ - static multimap const kFmd2Osm = { - {feature::Metadata::EType::FMD_CUISINE, "cuisine"}, - {feature::Metadata::EType::FMD_OPEN_HOURS, "opening_hours"}, - {feature::Metadata::EType::FMD_PHONE_NUMBER, "phone"}, - {feature::Metadata::EType::FMD_PHONE_NUMBER, "contact:phone"}, - {feature::Metadata::EType::FMD_FAX_NUMBER, "fax"}, - {feature::Metadata::EType::FMD_FAX_NUMBER, "contact:fax"}, - {feature::Metadata::EType::FMD_STARS, "stars"}, - {feature::Metadata::EType::FMD_OPERATOR, "operator"}, - {feature::Metadata::EType::FMD_URL, "url"}, - {feature::Metadata::EType::FMD_WEBSITE, "website"}, - {feature::Metadata::EType::FMD_WEBSITE, "contact:website"}, - // TODO: {feature::Metadata::EType::FMD_INTERNET, ""}, - {feature::Metadata::EType::FMD_ELE, "ele"}, - {feature::Metadata::EType::FMD_TURN_LANES, "turn:lanes"}, - {feature::Metadata::EType::FMD_TURN_LANES_FORWARD, "turn:lanes:forward"}, - {feature::Metadata::EType::FMD_TURN_LANES_BACKWARD, "turn:lanes:backward"}, - {feature::Metadata::EType::FMD_EMAIL, "email"}, - {feature::Metadata::EType::FMD_EMAIL, "contact:email"}, - {feature::Metadata::EType::FMD_POSTCODE, "addr:postcode"}, - {feature::Metadata::EType::FMD_WIKIPEDIA, "wikipedia"}, - {feature::Metadata::EType::FMD_MAXSPEED, "maxspeed"}, - {feature::Metadata::EType::FMD_FLATS, "addr:flats"}, - {feature::Metadata::EType::FMD_HEIGHT, "height"}, - {feature::Metadata::EType::FMD_MIN_HEIGHT, "min_height"}, - {feature::Metadata::EType::FMD_MIN_HEIGHT, "building:min_level"}, - {feature::Metadata::EType::FMD_DENOMINATION, "denomination"}, - {feature::Metadata::EType::FMD_BUILDING_LEVELS, "building:levels"}, - }; - - vector result; - auto const range = kFmd2Osm.equal_range(et); - for (auto it = range.first; it != range.second; ++it) - result.emplace_back(it->second); - - return result; -} - -double ScoreMetadata(XMLFeature const & xmlFt, feature::Metadata const & metadata) -{ - double score = 0; - - for (auto const type : metadata.GetPresentTypes()) - { - for (auto const osm_tag : GetOsmOriginalTagsForType(static_cast(type))) - { - if (xmlFt.GetTagValue(osm_tag) == metadata.Get(type)) - { - score += 1; - break; - } - } - } - - return score; -} - -bool TypesEqual(XMLFeature const & xmlFt, feature::TypesHolder const & types) -{ - return false; -} - -pugi::xml_node GetBestOsmNode(pugi::xml_document const & osmResponse, FeatureType const & ft) -{ - double bestScore = -1; - pugi::xml_node bestMatchNode; - - for (auto const & xNode : osmResponse.select_nodes("node")) - { - try - { - XMLFeature xmlFt(xNode.node()); - - if (!TypesEqual(xmlFt, feature::TypesHolder(ft))) - continue; - - double nodeScore = ScoreLatLon(xmlFt, MercatorBounds::ToLatLon(ft.GetCenter())); - if (nodeScore < 0) - continue; - - nodeScore += ScoreNames(xmlFt, ft.GetNames()); - nodeScore += ScoreMetadata(xmlFt, ft.GetMetadata()); - - if (bestScore < nodeScore) - { - bestScore = nodeScore; - bestMatchNode = xNode.node(); - } - } - catch (editor::XMLFeatureNoLatLonError const & ex) - { - LOG(LWARNING, ("No lat/lon attribute in osm response node.", ex.Msg())); - continue; - } - } - - // TODO(mgsergio): Add a properly defined threshold. - // if (bestScore < minimumScoreThreshold) - // return pugi::xml_node; - - return bestMatchNode; -} - -pugi::xml_node GetBestOsmWay(pugi::xml_document const & osmResponse, FeatureType const & ft, - TGeometry const & geometry) -{ - double bestScore = -1; - pugi::xml_node bestMatchWay; - - for (auto const & xWay : osmResponse.select_nodes("Way")) - { - try - { - XMLFeature xmlFt(xWay.node()); - - if (!TypesEqual(xmlFt, feature::TypesHolder(ft))) - continue; - - double nodeScore = ScoreGeometry(osmResponse, xWay.node(), geometry); - if (nodeScore < 0) - continue; - - nodeScore += ScoreNames(xmlFt, ft.GetNames()); - nodeScore += ScoreMetadata(xmlFt, ft.GetMetadata()); - - if (bestScore < nodeScore) - { - bestScore = nodeScore; - bestMatchWay = xWay.node(); - } - } - catch (editor::XMLFeatureNoLatLonError const & ex) - { - LOG(LWARNING, ("No lat/lon attribute in osm response node.", ex.Msg())); - continue; - } - } - - // TODO(mgsergio): Add a properly defined threshold. - // if (bestScore < minimumScoreThreshold) - // return pugi::xml_node; - - return bestMatchWay; -} - -} // namespace - string DebugPrint(pugi::xml_document const & doc) { ostringstream stream; @@ -266,60 +50,48 @@ void ChangesetWrapper::LoadXmlFromOSM(ms::LatLon const & ll, pugi::xml_document MYTHROW(OsmXmlParseException, ("Can't parse OSM server response for GetXmlFeaturesAtLatLon request", response.second)); } -XMLFeature ChangesetWrapper::GetMatchingFeatureFromOSM(XMLFeature const & ourPatch, FeatureType const & feature) +XMLFeature ChangesetWrapper::GetMatchingNodeFeatureFromOSM(m2::PointD const & center) { - if (feature.GetFeatureType() == feature::EGeomType::GEOM_POINT) + // Match with OSM node. + ms::LatLon const ll = MercatorBounds::ToLatLon(center); + pugi::xml_document doc; + // Throws! + LoadXmlFromOSM(ll, doc); + + // feature must be the original one, not patched! + pugi::xml_node const bestNode = GetBestOsmNode(doc, ll); + if (bestNode.empty()) + MYTHROW(OsmObjectWasDeletedException, + ("OSM does not have any nodes at the coordinates", ll, ", server has returned:", doc)); + + return XMLFeature(bestNode); +} + +XMLFeature ChangesetWrapper::GetMatchingAreaFeatureFromOSM(set const & geometry) +{ + // TODO: Make two/four requests using points on inscribed rectagle. + for (auto const & pt : geometry) { - // Match with OSM node. - ms::LatLon const ll = ourPatch.GetCenter(); + ms::LatLon const ll = MercatorBounds::ToLatLon(pt); pugi::xml_document doc; // Throws! LoadXmlFromOSM(ll, doc); - // feature must be the original one, not patched! - pugi::xml_node const bestNode = GetBestOsmNode(doc, feature); - if (bestNode.empty()) - MYTHROW(OsmObjectWasDeletedException, ("OSM does not have any nodes at the coordinates", ll, ", server has returned:", doc)); + // TODO(AlexZ): Select best matching OSM way from possible many ways. + pugi::xml_node const bestWay = GetBestOsmWay(doc, geometry); + if (bestWay.empty()) + continue; - return XMLFeature(bestNode); + XMLFeature const way(bestWay); + if (!way.IsArea()) + continue; + + // TODO: Check that this way is really match our feature. + + return way; } - else if (feature.GetFeatureType() == feature::EGeomType::GEOM_AREA) - { - using m2::PointD; - // Set filters out duplicate points for closed ways or triangles' vertices. - TGeometry geometry; - feature.ForEachTriangle([&geometry](PointD const & p1, PointD const & p2, PointD const & p3) - { - geometry.insert(p1); - geometry.insert(p2); - geometry.insert(p3); - }, FeatureType::BEST_GEOMETRY); - - ASSERT_GREATER_OR_EQUAL(geometry.size(), 3, ("Is it an area feature?")); - - for (auto const & pt : geometry) - { - ms::LatLon const ll = MercatorBounds::ToLatLon(pt); - pugi::xml_document doc; - // Throws! - LoadXmlFromOSM(ll, doc); - - // TODO(AlexZ): Select best matching OSM way from possible many ways. - pugi::xml_node const bestWay = GetBestOsmWay(doc, feature, geometry); - if (bestWay.empty()) - continue; - - XMLFeature const way(bestWay); - if (!way.IsArea()) - continue; - - // TODO: Check that this way is really match our feature. - - return way; - } - MYTHROW(OsmObjectWasDeletedException, ("OSM does not have any matching way for feature", feature)); - } - MYTHROW(LinearFeaturesAreNotSupportedException, ("We don't edit linear features yet.")); + MYTHROW(OsmObjectWasDeletedException, + ("OSM does not have any matching way for feature")); } void ChangesetWrapper::ModifyNode(XMLFeature node) diff --git a/editor/changeset_wrapper.hpp b/editor/changeset_wrapper.hpp index 07d4696bd0..6f62ede85e 100644 --- a/editor/changeset_wrapper.hpp +++ b/editor/changeset_wrapper.hpp @@ -5,6 +5,8 @@ #include "base/exception.hpp" +#include "std/set.hpp" + class FeatureType; namespace osm @@ -29,7 +31,8 @@ public: /// Throws many exceptions from above list, plus including XMLNode's parsing ones. /// OsmObjectWasDeletedException means that node was deleted from OSM server by someone else. - editor::XMLFeature GetMatchingFeatureFromOSM(editor::XMLFeature const & ourPatch, FeatureType const & feature); + editor::XMLFeature GetMatchingNodeFeatureFromOSM(m2::PointD const & center); + editor::XMLFeature GetMatchingAreaFeatureFromOSM(set const & geomerty); /// Throws exceptions from above list. void ModifyNode(editor::XMLFeature node); diff --git a/editor/editor.pro b/editor/editor.pro index 03db84f275..95b7d1a640 100644 --- a/editor/editor.pro +++ b/editor/editor.pro @@ -12,6 +12,7 @@ SOURCES += \ changeset_wrapper.cpp \ opening_hours_ui.cpp \ osm_auth.cpp \ + osm_feature_matcher.cpp \ server_api.cpp \ ui2oh.cpp \ xml_feature.cpp \ @@ -20,6 +21,7 @@ HEADERS += \ changeset_wrapper.hpp \ opening_hours_ui.hpp \ osm_auth.hpp \ + osm_feature_matcher.hpp \ server_api.hpp \ ui2oh.hpp \ xml_feature.hpp \ diff --git a/editor/editor_tests/editor_tests.pro b/editor/editor_tests/editor_tests.pro index b4b5395839..07c62a11d0 100644 --- a/editor/editor_tests/editor_tests.pro +++ b/editor/editor_tests/editor_tests.pro @@ -19,3 +19,4 @@ SOURCES += \ xml_feature_test.cpp \ ui2oh_test.cpp \ osm_auth_test.cpp \ + osm_feature_matcher_test.cpp \ diff --git a/editor/editor_tests/osm_feature_matcher_test.cpp b/editor/editor_tests/osm_feature_matcher_test.cpp new file mode 100644 index 0000000000..306d0a24b7 --- /dev/null +++ b/editor/editor_tests/osm_feature_matcher_test.cpp @@ -0,0 +1,85 @@ +#include "testing/testing.hpp" + +#include "editor/osm_feature_matcher.hpp" +#include "editor/xml_feature.hpp" + +#include "3party/pugixml/src/pugixml.hpp" + + +static char const * const osmRawResponse = R"SEP( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +)SEP"; + + +UNIT_TEST(GetBestOsmWay_test) +{ + pugi::xml_document osmResponse; + TEST(osmResponse.load_buffer(osmRawResponse, ::strlen(osmRawResponse)), ()); + set geometry = { + {53.8977484, 27.557359}, + {53.8978710, 27.557681}, + {53.8978034, 27.557764}, + {53.8977652, 27.557803}, + {53.8977254, 27.557837}, + {53.8976570, 27.557661}, + {53.8976041, 27.557518}, + {53.8977484, 27.557359} + }; + + auto const bestWay = osm::GetBestOsmWay(osmResponse, geometry); + TEST_EQUAL(editor::XMLFeature(bestWay).GetName(), "Беллесбумпром", ()); +} + + +UNIT_TEST(GetBestOsmNode_test) +{ + pugi::xml_document osmResponse; + TEST(osmResponse.load_buffer(osmRawResponse, ::strlen(osmRawResponse)), ()); + + auto const bestWay = osm::GetBestOsmWay(osmResponse, geometry); + TEST_EQUAL(editor::XMLFeature(bestWay).GetCenter(), "Беллесбумпром", ()); + +} diff --git a/editor/osm_feature_matcher.cpp b/editor/osm_feature_matcher.cpp new file mode 100644 index 0000000000..92f835299a --- /dev/null +++ b/editor/osm_feature_matcher.cpp @@ -0,0 +1,218 @@ +#include "base/logging.hpp" + +#include "editor/osm_feature_matcher.hpp" + +namespace osm +{ +bool LatLonEqual(ms::LatLon const & a, ms::LatLon const & b) +{ + double constexpr eps = MercatorBounds::GetCellID2PointAbsEpsilon(); + return a.EqualDxDy(b, eps); +} + +double ScoreLatLon(XMLFeature const & xmlFt, ms::LatLon const & latLon) +{ + // TODO: Find proper score values; + return LatLonEqual(latLon, xmlFt.GetCenter()) ? 10 : -10; +} + +double ScoreGeometry(pugi::xml_document const & osmResponse, pugi::xml_node const & way, + set geometry) +{ + int matched = 0; + int total = 0; + // TODO(mgsergio): optimize using sorting and scaning eps-squares. + // The idea is to build squares with side = eps on points from the first set. + // Sort them by y and x in y groups. + // Then pass points from the second set through constructed struct and register events + // like square started, square ended, point encounted. + + for (auto const xNodeRef : way.select_nodes("nd/@ref")) + { + ++total; + string const nodeRef = xNodeRef.attribute().value(); + auto const node = osmResponse.select_node(("node[@ref='" + nodeRef + "']").data()).node(); + if (!node) + { + LOG(LDEBUG, ("OSM response have ref", nodeRef, + "but have no node with such id.", osmResponse)); + continue; // TODO: or break and return -1000? + } + + XMLFeature xmlFt(node); + for (auto pointIt = begin(geometry); pointIt != end(geometry); ++pointIt) + { + if (LatLonEqual(xmlFt.GetCenter(), MercatorBounds::ToLatLon(*pointIt))) + { + ++matched; + geometry.erase(pointIt); + } + } + } + + return static_cast(matched) / total * 100; +} + +// double ScoreNames(XMLFeature const & xmlFt, StringUtf8Multilang const & names) +// { +// double score = 0; +// names.ForEach([&score, &xmlFt](uint8_t const langCode, string const & name) +// { +// if (xmlFt.GetName(langCode) == name) +// score += 1; +// return true; +// }); + +// // TODO(mgsergio): Deafult name match should have greater wieght. Should it? + +// return score; +// } + +// vector GetOsmOriginalTagsForType(feature::Metadata::EType const et) +// { +// static multimap const kFmd2Osm = { +// {feature::Metadata::EType::FMD_CUISINE, "cuisine"}, +// {feature::Metadata::EType::FMD_OPEN_HOURS, "opening_hours"}, +// {feature::Metadata::EType::FMD_PHONE_NUMBER, "phone"}, +// {feature::Metadata::EType::FMD_PHONE_NUMBER, "contact:phone"}, +// {feature::Metadata::EType::FMD_FAX_NUMBER, "fax"}, +// {feature::Metadata::EType::FMD_FAX_NUMBER, "contact:fax"}, +// {feature::Metadata::EType::FMD_STARS, "stars"}, +// {feature::Metadata::EType::FMD_OPERATOR, "operator"}, +// {feature::Metadata::EType::FMD_URL, "url"}, +// {feature::Metadata::EType::FMD_WEBSITE, "website"}, +// {feature::Metadata::EType::FMD_WEBSITE, "contact:website"}, +// // TODO: {feature::Metadata::EType::FMD_INTERNET, ""}, +// {feature::Metadata::EType::FMD_ELE, "ele"}, +// {feature::Metadata::EType::FMD_TURN_LANES, "turn:lanes"}, +// {feature::Metadata::EType::FMD_TURN_LANES_FORWARD, "turn:lanes:forward"}, +// {feature::Metadata::EType::FMD_TURN_LANES_BACKWARD, "turn:lanes:backward"}, +// {feature::Metadata::EType::FMD_EMAIL, "email"}, +// {feature::Metadata::EType::FMD_EMAIL, "contact:email"}, +// {feature::Metadata::EType::FMD_POSTCODE, "addr:postcode"}, +// {feature::Metadata::EType::FMD_WIKIPEDIA, "wikipedia"}, +// {feature::Metadata::EType::FMD_MAXSPEED, "maxspeed"}, +// {feature::Metadata::EType::FMD_FLATS, "addr:flats"}, +// {feature::Metadata::EType::FMD_HEIGHT, "height"}, +// {feature::Metadata::EType::FMD_MIN_HEIGHT, "min_height"}, +// {feature::Metadata::EType::FMD_MIN_HEIGHT, "building:min_level"}, +// {feature::Metadata::EType::FMD_DENOMINATION, "denomination"}, +// {feature::Metadata::EType::FMD_BUILDING_LEVELS, "building:levels"}, +// }; + +// vector result; +// auto const range = kFmd2Osm.equal_range(et); +// for (auto it = range.first; it != range.second; ++it) +// result.emplace_back(it->second); + +// return result; +// } + +// double ScoreMetadata(XMLFeature const & xmlFt, feature::Metadata const & metadata) +// { +// double score = 0; + +// for (auto const type : metadata.GetPresentTypes()) +// { +// for (auto const osm_tag : GetOsmOriginalTagsForType(static_cast(type))) +// { +// if (xmlFt.GetTagValue(osm_tag) == metadata.Get(type)) +// { +// score += 1; +// break; +// } +// } +// } + +// return score; +// } + +// bool TypesEqual(XMLFeature const & xmlFt, feature::TypesHolder const & types) +// { +// // TODO: Use mapcss-mapping.csv to correctly match types. +// return true; +// } + +pugi::xml_node GetBestOsmNode(pugi::xml_document const & osmResponse, ms::LatLon const latLon) +{ + double bestScore = -1; + pugi::xml_node bestMatchNode; + + for (auto const & xNode : osmResponse.select_nodes("osm/node")) + { + try + { + XMLFeature xmlFt(xNode.node()); + + // if (!TypesEqual(xmlFt, feature::TypesHolder(ft))) + // continue; + + double nodeScore = ScoreLatLon(xmlFt, latLon); + if (nodeScore < 0) + continue; + + // nodeScore += ScoreNames(xmlFt, ft.GetNames()); + // nodeScore += ScoreMetadata(xmlFt, ft.GetMetadata()); + + if (bestScore < nodeScore) + { + bestScore = nodeScore; + bestMatchNode = xNode.node(); + } + } + catch (editor::XMLFeatureNoLatLonError const & ex) + { + LOG(LWARNING, ("No lat/lon attribute in osm response node.", ex.Msg())); + continue; + } + } + + // TODO(mgsergio): Add a properly defined threshold. + // if (bestScore < minimumScoreThreshold) + // return pugi::xml_node; + + return bestMatchNode; +} + +pugi::xml_node GetBestOsmWay(pugi::xml_document const & osmResponse, set const & geometry) +{ + double bestScore = -1; + pugi::xml_node bestMatchWay; + + for (auto const & xWay : osmResponse.select_nodes("osm/way")) + { + try + { + XMLFeature xmlFt(xWay.node()); + + // if (!TypesEqual(xmlFt, feature::TypesHolder(ft))) + // continue; + + double nodeScore = ScoreGeometry(osmResponse, xWay.node(), geometry); + if (nodeScore < 0) + continue; + + // nodeScore += ScoreNames(xmlFt, ft.GetNames()); + // nodeScore += ScoreMetadata(xmlFt, ft.GetMetadata()); + + if (bestScore < nodeScore) + { + bestScore = nodeScore; + bestMatchWay = xWay.node(); + } + } + catch (editor::XMLFeatureNoLatLonError const & ex) + { + LOG(LWARNING, ("No lat/lon attribute in osm response node.", ex.Msg())); + continue; + } + } + + // TODO(mgsergio): Add a properly defined threshold. + // if (bestScore < minimumScoreThreshold) + // return pugi::xml_node; + + return bestMatchWay; +} + +} // namespace osm diff --git a/editor/osm_feature_matcher.hpp b/editor/osm_feature_matcher.hpp new file mode 100644 index 0000000000..26313b8291 --- /dev/null +++ b/editor/osm_feature_matcher.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "editor/xml_feature.hpp" + +#include "geometry/mercator.hpp" + +#include "std/set.hpp" + +namespace osm +{ +using editor::XMLFeature; + +bool LatLonEqual(ms::LatLon const & a, ms::LatLon const & b); + +double ScoreLatLon(XMLFeature const & xmlFt, ms::LatLon const & latLon); + +double ScoreGeometry(pugi::xml_document const & osmResponse, pugi::xml_node const & way, + set geometry); + +// double ScoreNames(XMLFeature const & xmlFt, StringUtf8Multilang const & names); + +// vector GetOsmOriginalTagsForType(feature::Metadata::EType const et); + +// double ScoreMetadata(XMLFeature const & xmlFt, feature::Metadata const & metadata); + +// bool TypesEqual(XMLFeature const & xmlFt, feature::TypesHolder const & types); + +pugi::xml_node GetBestOsmNode(pugi::xml_document const & osmResponse, ms::LatLon const latLon); + +pugi::xml_node GetBestOsmWay(pugi::xml_document const & osmResponse, set const & geometry); +} // namespace osm diff --git a/indexer/osm_editor.cpp b/indexer/osm_editor.cpp index c7a7478756..d737501e54 100644 --- a/indexer/osm_editor.cpp +++ b/indexer/osm_editor.cpp @@ -243,6 +243,32 @@ bool AreFeaturesEqualButStreet(FeatureType const & a, FeatureType const & b) return true; } + +XMLFeature GetMatchingFeatureFromOSM(osm::ChangesetWrapper & cw, + unique_ptr featurePtr) +{ + ASSERT(featurePtr->GetFeatureType() != feature::GEOM_LINE, ("Line features are not supported yet.")); + if (featurePtr->GetFeatureType() == feature::GEOM_POINT) + { + return cw.GetMatchingNodeFeatureFromOSM(featurePtr->GetCenter()); + } + else + { + // Set filters out duplicate points for closed ways or triangles' vertices. + set geometry; + featurePtr->ForEachTriangle([&geometry](m2::PointD const & p1, + m2::PointD const & p2, m2::PointD const & p3) + { + geometry.insert(p1); + geometry.insert(p2); + geometry.insert(p3); + }, FeatureType::BEST_GEOMETRY); + + ASSERT_GREATER_OR_EQUAL(geometry.size(), 3, ("Is it an area feature?")); + + return cw.GetMatchingAreaFeatureFromOSM(geometry); + } +} } // namespace namespace osm @@ -693,7 +719,8 @@ void Editor::UploadChanges(string const & key, string const & secret, TChangeset try { - XMLFeature osmFeature = changeset.GetMatchingFeatureFromOSM(feature, fti.m_feature); + XMLFeature osmFeature = GetMatchingFeatureFromOSM(changeset, + m_getOriginalFeatureFn(fti.m_feature.GetID())); XMLFeature const osmFeatureCopy = osmFeature; osmFeature.ApplyPatch(feature); // Check to avoid duplicates.