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