File indexing completed on 2023-05-30 09:06:33

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2016 Akshat Tandon <akshat.tandon@research.iiit.ac.in>
0004 //
0005 
0006 #ifndef MARBLE_NODEREDUCER_H
0007 #define MARBLE_NODEREDUCER_H
0008 
0009 #include "OsmPlacemarkData.h"
0010 #include "VectorClipper.h"
0011 
0012 namespace Marble {
0013 
0014 class NodeReducer {
0015 public:
0016     NodeReducer(GeoDataDocument* document, const TileId &tileId);
0017 
0018     qint64 removedNodes() const;
0019     qint64 remainingNodes() const;
0020 
0021 private:
0022     qreal epsilonFor(qreal multiplier) const;
0023     qreal perpendicularDistance(const GeoDataCoordinates &a, const GeoDataCoordinates &b, const GeoDataCoordinates &c) const;
0024     bool touchesTileBorder(const GeoDataCoordinates &coordinates) const;
0025     void setBorderPoints(OsmPlacemarkData &osmData, const QVector<int> &borderPoints, int length) const;
0026 
0027     GeoDataLinearRing* reducedRing(const GeoDataLinearRing& prevRing,
0028                                    GeoDataPlacemark* placemark,
0029                                    const GeoDataPlacemark::GeoDataVisualCategory& visualCategory);
0030     GeoDataPolygon* reducedPolygon(const GeoDataPolygon& prevPolygon,
0031                                    GeoDataPlacemark* placemark,
0032                                    const GeoDataPlacemark::GeoDataVisualCategory& visualCategory);
0033 
0034     template<class T>
0035     void reduce(T const & lineString, OsmPlacemarkData& osmData, GeoDataPlacemark::GeoDataVisualCategory visualCategory, T* reducedLine)
0036     {
0037         bool const isArea = lineString.isClosed() && VectorClipper::canBeArea(visualCategory);
0038         qreal const epsilon = epsilonFor(isArea ? 45.0 : 30.0);
0039         *reducedLine = douglasPeucker(lineString, osmData, epsilon);
0040 
0041         qint64 prevSize = lineString.size();
0042         qint64 reducedSize = reducedLine->size();
0043         m_removedNodes += (prevSize - reducedSize);
0044         m_remainingNodes += reducedSize;
0045 
0046         QVector<int> borderPoints;
0047         int index = 0;
0048         for (auto const &coordinate: *reducedLine) {
0049             if (touchesTileBorder(coordinate)) {
0050                 borderPoints << index;
0051             }
0052             ++index;
0053         }
0054         setBorderPoints(osmData, borderPoints, reducedLine->size());
0055     }
0056 
0057     template<class T>
0058     T extract(T const & lineString, int start, int end) const
0059     {
0060         T result;
0061         for (int i=start; i<=end; ++i) {
0062             result << lineString[i];
0063         }
0064         return result;
0065     }
0066 
0067     template<class T>
0068     T merge(T const & a, T const &b) const
0069     {
0070         T result = a;
0071         int const index = a.size();
0072         result << b;
0073         result.remove(index);
0074         return result;
0075     }
0076 
0077     template<class T>
0078     T douglasPeucker(T const & lineString, const OsmPlacemarkData &osmData, qreal epsilon) const
0079     {
0080         if (lineString.size() < 3) {
0081             return lineString;
0082         }
0083 
0084         double maxDistance = 0.0;
0085         int index = 1;
0086         int const end = lineString.size()-1;
0087         for (int i = 1; i<end; ++i) {
0088             double const distance = perpendicularDistance(lineString[i], lineString[0], lineString[end]);
0089             if (distance > maxDistance) {
0090                 index = i;
0091                 maxDistance = distance;
0092             }
0093         }
0094 
0095         if (maxDistance >= epsilon) {
0096             T const left = douglasPeucker(extract(lineString, 0, index), osmData, epsilon);
0097             T const right = douglasPeucker(extract(lineString, index, end), osmData, epsilon);
0098             return merge(left, right);
0099         }
0100 
0101         T result;
0102         result << lineString[0];
0103         for (int i=1; i<end; ++i) {
0104             // even if under the Douglas Peucker threshold, we keep the following nodes:
0105             // - nodes at a tile border
0106             // - nodes containing any OSM tag themselves
0107             // - nodes adjacent to synthetic nodes introduced by clipping. This is to avoid
0108             //   synthetic only line segments that geometry reassembly on the client might
0109             //   remove entirely and thus distort the original geometry.
0110             bool const keepNode = touchesTileBorder(lineString[i]) || !osmData.nodeReference(lineString[i]).isEmpty()
0111                 || osmData.nodeReference(lineString[i-1]).id() < 0 || ((i + 1) < end && osmData.nodeReference(lineString[i+1]).id() < 0);
0112             if (keepNode) {
0113                 result << lineString[i];
0114             }
0115         }
0116         result << lineString[end];
0117         return result;
0118     }
0119 
0120     qint64 m_removedNodes;
0121     qint64 m_remainingNodes;
0122 
0123     int m_zoomLevel;
0124     double m_tileBoundary[4];
0125     enum Boundary {
0126         West = 0,
0127         North = 1,
0128         East = 2,
0129         South = 3
0130     };
0131 };
0132 
0133 }
0134 
0135 #endif