diff --git a/editor/changeset_wrapper.cpp b/editor/changeset_wrapper.cpp new file mode 100644 index 0000000000..ebda09e58b --- /dev/null +++ b/editor/changeset_wrapper.cpp @@ -0,0 +1,113 @@ +#include "indexer/feature.hpp" + +#include "editor/changeset_wrapper.hpp" + +#include "std/algorithm.hpp" + +#include "private.h" + +using editor::XMLFeature; + +namespace osm +{ + +ChangesetWrapper::ChangesetWrapper(TKeySecret const & keySecret, + ServerApi06::TKeyValueTags const & comments) + : m_changesetComments(comments), + // TODO(AlexZ): Replace with production server. + m_api(OsmOAuth::IZServerAuth().SetToken(keySecret)) +{ +} + +ChangesetWrapper::~ChangesetWrapper() +{ + if (m_changesetId) + m_api.CloseChangeSet(m_changesetId); +} + +XMLFeature ChangesetWrapper::GetMatchingFeatureFromOSM(XMLFeature const & ourPatch, FeatureType const & feature) +{ + if (feature.GetFeatureType() == feature::EGeomType::GEOM_POINT) + { + // Match with OSM node. + ms::LatLon const ll = ourPatch.GetCenter(); + auto const response = m_api.GetXmlNodeByLatLon(ll.lat, ll.lon); + if (response.first == OsmOAuth::ResponseCode::NetworkError) + MYTHROW(NetworkErrorException, ("NetworkError with GetXmlNodeByLatLon request.")); + if (response.first != OsmOAuth::ResponseCode::OK) + MYTHROW(HttpErrorException, ("HTTP error", response.first, "with GetXmlNodeByLatLon", ll)); + + pugi::xml_document doc; + if (pugi::status_ok != doc.load(response.second.c_str()).status) + MYTHROW(OsmXmlParseException, ("Can't parse OSM server response for GetXmlNodeByLatLon request", response.second)); + + // TODO(AlexZ): Select best matching OSM node, not just the first one. + pugi::xml_node const firstNode = doc.child("osm").child("node"); + if (firstNode.empty()) + MYTHROW(OsmObjectWasDeletedException, ("OSM does not have any nodes at the coordinates", ll, ", server has returned:", response.second)); + + return XMLFeature(firstNode); + } + else if (feature.GetFeatureType() == feature::EGeomType::GEOM_AREA) + { + using m2::PointD; + // Set filters out duplicate points for closed ways or triangles' vertices. + set 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); + auto const response = m_api.GetXmlNodeByLatLon(ll.lat, ll.lon); + if (response.first == OsmOAuth::ResponseCode::NetworkError) + MYTHROW(NetworkErrorException, ("NetworkError with GetXmlNodeByLatLon request.")); + if (response.first != OsmOAuth::ResponseCode::OK) + MYTHROW(HttpErrorException, ("HTTP error", response.first, "with GetXmlNodeByLatLon", ll)); + + pugi::xml_document doc; + if (pugi::status_ok != doc.load(response.second.c_str()).status) + MYTHROW(OsmXmlParseException, ("Can't parse OSM server response for GetXmlNodeByLatLon request", response.second)); + + // TODO(AlexZ): Select best matching OSM way from possible many ways. + pugi::xml_node const firstWay = doc.child("osm").child("way"); + if (firstWay.empty()) + continue; + + XMLFeature const way(firstWay); + 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.")); +} + +void ChangesetWrapper::ModifyNode(XMLFeature node) +{ + // TODO(AlexZ): ServerApi can be much better with exceptions. + if (m_changesetId == kInvalidChangesetId && !m_api.CreateChangeSet(m_changesetComments, m_changesetId)) + MYTHROW(CreateChangeSetFailedException, ("CreateChangeSetFailedException")); + + uint64_t nodeId; + if (!strings::to_uint64(node.GetAttribute("id"), nodeId)) + MYTHROW(CreateChangeSetFailedException, ("CreateChangeSetFailedException")); + + // Changeset id should be updated for every OSM server commit. + node.SetAttribute("changeset", strings::to_string(m_changesetId)); + + if (!m_api.ModifyNode(node.ToOSMString(), nodeId)) + MYTHROW(ModifyNodeFailedException, ("ModifyNodeFailedException")); +} + +} // namespace osm diff --git a/editor/changeset_wrapper.hpp b/editor/changeset_wrapper.hpp new file mode 100644 index 0000000000..122da31a23 --- /dev/null +++ b/editor/changeset_wrapper.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "editor/server_api.hpp" +#include "editor/xml_feature.hpp" + +#include "base/exception.hpp" + +class FeatureType; + +namespace osm +{ + +struct ClientToken; + +class ChangesetWrapper +{ +public: + DECLARE_EXCEPTION(ChangesetWrapperException, RootException); + DECLARE_EXCEPTION(NetworkErrorException, ChangesetWrapperException); + DECLARE_EXCEPTION(HttpErrorException, ChangesetWrapperException); + DECLARE_EXCEPTION(OsmXmlParseException, ChangesetWrapperException); + DECLARE_EXCEPTION(OsmObjectWasDeletedException, ChangesetWrapperException); + DECLARE_EXCEPTION(CreateChangeSetFailedException, ChangesetWrapperException); + DECLARE_EXCEPTION(ModifyNodeFailedException, ChangesetWrapperException); + DECLARE_EXCEPTION(LinearFeaturesAreNotSupportedException, ChangesetWrapperException); + + ChangesetWrapper(TKeySecret const & keySecret, ServerApi06::TKeyValueTags const & comments); + ~ChangesetWrapper(); + + /// Throws many exceptions, 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); + + /// Throws. + void ModifyNode(editor::XMLFeature node); + +private: + ServerApi06::TKeyValueTags m_changesetComments; + ServerApi06 m_api; + static constexpr int const kInvalidChangesetId = 0; + uint64_t m_changesetId = kInvalidChangesetId; +}; + +} // namespace osm diff --git a/editor/editor.pro b/editor/editor.pro index ce3506fdbe..03db84f275 100644 --- a/editor/editor.pro +++ b/editor/editor.pro @@ -9,15 +9,17 @@ ROOT_DIR = .. include($$ROOT_DIR/common.pri) SOURCES += \ + changeset_wrapper.cpp \ opening_hours_ui.cpp \ + osm_auth.cpp \ server_api.cpp \ ui2oh.cpp \ xml_feature.cpp \ - osm_auth.cpp \ HEADERS += \ + changeset_wrapper.hpp \ opening_hours_ui.hpp \ + osm_auth.hpp \ server_api.hpp \ ui2oh.hpp \ xml_feature.hpp \ - osm_auth.hpp \