File indexing completed on 2023-05-30 09:06:34
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2016 Dennis Nienhüser <nienhueser@kde.org> 0004 // SPDX-FileCopyrightText: 2016 David Kolozsvari <freedawson@gmail.com> 0005 // 0006 0007 #ifndef VECTORCLIPPER_H 0008 #define VECTORCLIPPER_H 0009 0010 #include "OsmPlacemarkData.h" 0011 0012 #include <GeoDataLatLonBox.h> 0013 #include "GeoDataPlacemark.h" 0014 #include "GeoDataLinearRing.h" 0015 #include "GeoDataBuilding.h" 0016 #include "GeoDataMultiGeometry.h" 0017 #include <TileId.h> 0018 #include <GeoSceneMercatorTileProjection.h> 0019 #include <OsmObjectManager.h> 0020 #include "GeoDataDocument.h" 0021 0022 #include "clipper/clipper.hpp" 0023 #include <QMap> 0024 #include <QSet> 0025 0026 #include <memory> 0027 0028 namespace Marble { 0029 0030 class GeoDataLinearRing; 0031 class GeoDataRelation; 0032 0033 class VectorClipper 0034 { 0035 public: 0036 VectorClipper(GeoDataDocument* document, int maxZoomLevel); 0037 0038 GeoDataDocument* clipTo(unsigned int zoomLevel, unsigned int tileX, unsigned int tileY); 0039 static bool canBeArea(GeoDataPlacemark::GeoDataVisualCategory visualCategory); 0040 0041 private: 0042 GeoDataDocument* clipTo(const GeoDataLatLonBox &box, int zoomLevel); 0043 QVector<GeoDataPlacemark*> potentialIntersections(const GeoDataLatLonBox &box) const; 0044 ClipperLib::Path clipPath(const GeoDataLatLonBox &box, int zoomLevel) const; 0045 qreal area(const GeoDataLinearRing &ring); 0046 void getBounds(const ClipperLib::Path &path, ClipperLib::cInt &minX, ClipperLib::cInt &maxX, ClipperLib::cInt &minY, ClipperLib::cInt &maxY) const; 0047 0048 // convert radian-based coordinates to 10^-7 degree (100 nanodegree) integer coordinates used by the clipper library 0049 constexpr static qint64 const s_pointScale = 10000000 / M_PI * 180; 0050 static inline ClipperLib::IntPoint coordinateToPoint(const GeoDataCoordinates &c) 0051 { 0052 return ClipperLib::IntPoint(qRound64(c.longitude() * s_pointScale), qRound64(c.latitude() * s_pointScale)); 0053 } 0054 static inline GeoDataCoordinates pointToCoordinate(ClipperLib::IntPoint p) 0055 { 0056 return GeoDataCoordinates((double)p.X / s_pointScale, (double)p.Y / s_pointScale); 0057 } 0058 0059 template<class T> 0060 static void pathToRing(const ClipperLib::Path &path, T *ring, const OsmPlacemarkData &originalOsmData, OsmPlacemarkData &newOsmData, const QHash<std::pair<ClipperLib::cInt, ClipperLib::cInt>, const GeoDataCoordinates*> &coordMap) 0061 { 0062 int index = 0; 0063 for(const auto &point: path) { 0064 const auto it = coordMap.find(std::make_pair(point.X, point.Y)); 0065 if (it != coordMap.end()) { 0066 *ring << *it.value(); 0067 auto const data = originalOsmData.nodeReference(*it.value()); 0068 if (data.id() > 0) { 0069 newOsmData.addNodeReference(*it.value(), data); 0070 } 0071 } else { 0072 *ring << pointToCoordinate(point); 0073 } 0074 ++index; 0075 } 0076 } 0077 0078 template<class T> 0079 void clipString(const GeoDataPlacemark *placemark, const ClipperLib::Path &tileBoundary, qreal minArea, 0080 GeoDataDocument* document, QSet<qint64> &osmIds) 0081 { 0082 if (osmIds.contains(placemark->osmData().id())) { 0083 return; 0084 } 0085 bool isBuilding = false; 0086 const T* ring; 0087 std::unique_ptr<GeoDataPlacemark> copyPlacemark; 0088 if (const auto building = geodata_cast<GeoDataBuilding>(placemark->geometry())) { 0089 ring = geodata_cast<T>(&static_cast<const GeoDataMultiGeometry*>(building->multiGeometry())->at(0)); 0090 isBuilding = true; 0091 } else { 0092 copyPlacemark.reset(new GeoDataPlacemark(*placemark)); 0093 ring = geodata_cast<T>(copyPlacemark->geometry()); 0094 } 0095 auto const & osmData = placemark->osmData(); 0096 bool const isClosed = ring->isClosed() && (canBeArea(placemark->visualCategory()) || osmData.tagValue(QStringLiteral("area")) == QLatin1String("yes")); 0097 if (isClosed && minArea > 0.0 && area(*static_cast<const GeoDataLinearRing*>(ring)) < minArea) { 0098 return; 0099 } 0100 using namespace ClipperLib; 0101 Path subject; 0102 QHash<std::pair<cInt, cInt>, const GeoDataCoordinates*> coordMap; 0103 for(auto const & node: *ring) { 0104 auto p = coordinateToPoint(node); 0105 coordMap.insert(std::make_pair(p.X, p.Y), &node); 0106 subject.push_back(std::move(p)); 0107 } 0108 cInt minX, maxX, minY, maxY; 0109 getBounds(tileBoundary, minX, maxX, minY, maxY); 0110 0111 Clipper clipper; 0112 clipper.PreserveCollinear(true); 0113 clipper.AddPath(tileBoundary, ptClip, true); 0114 clipper.AddPath(subject, ptSubject, isClosed); 0115 PolyTree tree; 0116 clipper.Execute(ctIntersection, tree); 0117 Paths paths; 0118 if (isClosed) { 0119 ClosedPathsFromPolyTree(tree, paths); 0120 } else { 0121 OpenPathsFromPolyTree(tree, paths); 0122 } 0123 for(const auto &path: paths) { 0124 GeoDataPlacemark* newPlacemark = new GeoDataPlacemark; 0125 newPlacemark->setVisible(placemark->isVisible()); 0126 newPlacemark->setVisualCategory(placemark->visualCategory()); 0127 T* newRing = new T; 0128 pathToRing(path, newRing, osmData, newPlacemark->osmData(), coordMap); 0129 0130 if (isBuilding) { 0131 const auto building = geodata_cast<GeoDataBuilding>(placemark->geometry()); 0132 GeoDataBuilding* newBuilding = new GeoDataBuilding(*building); 0133 newBuilding->multiGeometry()->clear(); 0134 newBuilding->multiGeometry()->append(newRing); 0135 newPlacemark->setGeometry(newBuilding); 0136 } else { 0137 newPlacemark->setGeometry(newRing); 0138 } 0139 if (placemark->osmData().id() > 0) { 0140 newPlacemark->osmData().addTag(QStringLiteral("mx:oid"), QString::number(placemark->osmData().id())); 0141 } 0142 copyTags(*placemark, *newPlacemark); 0143 OsmObjectManager::initializeOsmData(newPlacemark); 0144 document->append(newPlacemark); 0145 osmIds << placemark->osmData().id(); 0146 } 0147 } 0148 0149 void clipPolygon(const GeoDataPlacemark *placemark, const ClipperLib::Path &tileBoundary, qreal minArea, 0150 GeoDataDocument* document, QSet<qint64> &osmIds); 0151 0152 void copyTags(const GeoDataPlacemark &source, GeoDataPlacemark &target) const; 0153 void copyTags(const OsmPlacemarkData &originalPlacemarkData, OsmPlacemarkData& targetOsmData) const; 0154 0155 QMap<TileId, QVector<GeoDataPlacemark*> > m_items; 0156 int m_maxZoomLevel; 0157 GeoSceneMercatorTileProjection m_tileProjection; 0158 QSet<GeoDataRelation*> m_relations; 0159 }; 0160 0161 } 0162 0163 #endif // VECTORCLIPPER_H