File indexing completed on 2024-07-14 14:25:33

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 "clipper2/clipper.h"
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     static Clipper2Lib::Rect64 clipRect(const GeoDataLatLonBox &box);
0045     qreal area(const GeoDataLinearRing &ring);
0046 
0047     // convert radian-based coordinates to 10^-7 degree (100 nanodegree) integer coordinates used by the clipper library
0048     constexpr static qint64 const s_pointScale = 10000000 / M_PI * 180;
0049     static inline Clipper2Lib::Point64 coordinateToPoint(const GeoDataCoordinates &c)
0050     {
0051         return Clipper2Lib::Point64(qRound64(c.longitude() * s_pointScale), qRound64(c.latitude() * s_pointScale), reinterpret_cast<int64_t>(&c));
0052     }
0053     static inline GeoDataCoordinates pointToCoordinate(Clipper2Lib::Point64 p)
0054     {
0055         return GeoDataCoordinates((double)p.x / s_pointScale, (double)p.y / s_pointScale);
0056     }
0057 
0058     template<class T>
0059     static void pathToRing(const Clipper2Lib::Path64 &path, T *ring, const OsmPlacemarkData &originalOsmData, OsmPlacemarkData &newOsmData)
0060     {
0061         for(const auto &point: path) {
0062             if (point.z) {
0063                 const auto *node = reinterpret_cast<const GeoDataCoordinates*>(point.z);
0064                 *ring << *node;
0065                 auto const data = originalOsmData.nodeReference(*node);
0066                 if (data.id() > 0) {
0067                     newOsmData.addNodeReference(*node, data);
0068                 }
0069             } else {
0070                 *ring << pointToCoordinate(point);
0071             }
0072         }
0073     }
0074 
0075     template<class T>
0076     void clipString(const GeoDataPlacemark *placemark, const Clipper2Lib::Rect64 &tileBoundary, qreal minArea,
0077                     GeoDataDocument* document, QSet<qint64> &osmIds)
0078     {
0079         if (osmIds.contains(placemark->osmData().id())) {
0080             return;
0081         }
0082         bool isBuilding = false;
0083         const T* ring;
0084         std::unique_ptr<GeoDataPlacemark> copyPlacemark;
0085         if (const auto building = geodata_cast<GeoDataBuilding>(placemark->geometry())) {
0086             ring = geodata_cast<T>(&static_cast<const GeoDataMultiGeometry*>(building->multiGeometry())->at(0));
0087             isBuilding = true;
0088         } else {
0089             copyPlacemark.reset(new GeoDataPlacemark(*placemark));
0090             ring = geodata_cast<T>(copyPlacemark->geometry());
0091         }
0092         auto const & osmData = placemark->osmData();
0093         bool const isClosed = ring->isClosed() && (canBeArea(placemark->visualCategory()) || osmData.tagValue(QStringLiteral("area")) == QLatin1String("yes"));
0094         if (isClosed && minArea > 0.0 && area(*static_cast<const GeoDataLinearRing*>(ring)) < minArea) {
0095             return;
0096         }
0097         using namespace Clipper2Lib;
0098         Path64 subject;
0099         subject.reserve(ring->size());
0100         for(auto const & node: *ring) {
0101             subject.push_back(coordinateToPoint(node));
0102         }
0103 
0104         Paths64 paths;
0105         if (isClosed) {
0106             paths = Clipper2Lib::RectClip(tileBoundary, subject);
0107         } else {
0108             paths = Clipper2Lib::RectClipLines(tileBoundary, {subject});
0109         }
0110         for(const auto &path: paths) {
0111             GeoDataPlacemark* newPlacemark = new GeoDataPlacemark;
0112             newPlacemark->setVisible(placemark->isVisible());
0113             newPlacemark->setVisualCategory(placemark->visualCategory());
0114             T* newRing = new T;
0115             pathToRing(path, newRing, osmData, newPlacemark->osmData());
0116 
0117             if (isBuilding) {
0118                 const auto building = geodata_cast<GeoDataBuilding>(placemark->geometry());
0119                 GeoDataBuilding* newBuilding = new GeoDataBuilding(*building);
0120                 newBuilding->multiGeometry()->clear();
0121                 newBuilding->multiGeometry()->append(newRing);
0122                 newPlacemark->setGeometry(newBuilding);
0123             } else {
0124                 newPlacemark->setGeometry(newRing);
0125             }
0126             if (placemark->osmData().id() > 0) {
0127                 newPlacemark->osmData().addTag(QStringLiteral("mx:oid"), QString::number(placemark->osmData().id()));
0128             }
0129             copyTags(*placemark, *newPlacemark);
0130             OsmObjectManager::initializeOsmData(newPlacemark);
0131             document->append(newPlacemark);
0132             osmIds << placemark->osmData().id();
0133         }
0134     }
0135 
0136     void clipPolygon(const GeoDataPlacemark *placemark, const GeoDataLatLonBox &tileBoundary,
0137                      const Clipper2Lib::Rect64 &clip, qreal minArea,
0138                      GeoDataDocument* document, QSet<qint64> &osmIds);
0139 
0140     void copyTags(const GeoDataPlacemark &source, GeoDataPlacemark &target) const;
0141     void copyTags(const OsmPlacemarkData &originalPlacemarkData, OsmPlacemarkData& targetOsmData) const;
0142 
0143     QMap<TileId, QVector<GeoDataPlacemark*> > m_items;
0144     int m_maxZoomLevel;
0145     GeoSceneMercatorTileProjection m_tileProjection;
0146     QSet<GeoDataRelation*> m_relations;
0147 };
0148 
0149 }
0150 
0151 #endif // VECTORCLIPPER_H