File indexing completed on 2024-04-28 04:41:45

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "polygonsimplifier.h"
0008 
0009 #include <osm/geomath.h>
0010 
0011 #include <QDebug>
0012 #include <QPolygonF>
0013 
0014 #include <clipper.hpp>
0015 
0016 static QPolygonF douglasPeucker(const QPolygonF::const_iterator &begin, const QPolygonF::const_iterator &end, double threshold)
0017 {
0018     QPolygonF result;
0019     if (std::distance(begin, end) < 3) {
0020         std::copy(begin, end, std::back_inserter(result));
0021         return result;
0022     }
0023 
0024     double maxDistance = 0.0;
0025     auto maxDistIt = std::next(begin);
0026     for (auto it = maxDistIt; it != end; ++it) {
0027         const auto d = OSM::distance(OSM::Coordinate((*begin).y(), (*begin).x()), OSM::Coordinate((*std::prev(end)).y(), (*std::prev(end)).x()), OSM::Coordinate((*it).y(), (*it).x()));
0028         if (d > maxDistance) {
0029             maxDistance = d;
0030             maxDistIt = it;
0031         }
0032     }
0033 
0034     if (maxDistance >= threshold) {
0035         result += douglasPeucker(begin, std::next(maxDistIt), threshold);
0036         result.pop_back();
0037         result += douglasPeucker(maxDistIt, end, threshold);
0038         return result;
0039     }
0040 
0041     return QPolygonF(QVector<QPointF>({*begin, (*std::prev(end))}));
0042 }
0043 
0044 QPolygonF PolygonSimplifier::douglasPeucker(const QPolygonF &poly, double threshold)
0045 {
0046     auto result = douglasPeucker(poly.begin(), poly.end(), threshold);
0047     qDebug() << "got" << poly.size() << "dropped" << poly.size() - result.size() << "remaining" << result.size();
0048 
0049     // if the polygon itself is smaller than the threshold, the result can deteriorate into
0050     // an area-less structure, take the bounding rect in that case
0051     if (result.size() < 4) {
0052         qDebug() << "  result deteriorated, taking bounding rect instead";
0053         const auto bbox = poly.boundingRect();
0054         return QPolygonF({ bbox.topLeft(), bbox.topRight(), bbox.bottomRight(), bbox.bottomLeft(), bbox.topLeft() });
0055     }
0056 
0057     return result;
0058 }
0059 
0060 static ClipperLib::Path qPolygonToClipperPath(const QPolygonF &poly)
0061 {
0062     ClipperLib::Path result;
0063     result.reserve(poly.size());
0064     std::transform(poly.begin(), poly.end(), std::back_inserter(result), [](auto p) {
0065         return ClipperLib::IntPoint(p.x() * 10'000'000.0, p.y() * 10'000'000);
0066     });
0067     return result;
0068 }
0069 
0070 static QPolygonF clipperPathToQPolygon(const ClipperLib::Path &path)
0071 {
0072     QPolygonF result;
0073     result.reserve(path.size() + 1);
0074     std::transform(path.begin(), path.end(), std::back_inserter(result), [](auto p) {
0075         return QPointF(p.X / 10'000'000.0, p.Y / 10'000'000.0);
0076     });
0077     if (!result.empty() && !result.isClosed()) {
0078         result.push_back(result.front());
0079     }
0080     return result;
0081 }
0082 
0083 QPolygonF PolygonSimplifier::offset(const QPolygonF &poly, double distance)
0084 {
0085     if (poly.empty()) {
0086         return {};
0087     }
0088 
0089     // convert meter distance to 100 nano-degree, the unit used by the polygon offset algorithm
0090     const auto bbox = poly.boundingRect();
0091     const auto bboxWidth = OSM::distance(bbox.center().y(), bbox.left(), bbox.center().y(), bbox.right());
0092     const auto delta = bbox.width() / bboxWidth * distance * 10'000'000.0;
0093 
0094     ClipperLib::ClipperOffset co;
0095     co.AddPath(qPolygonToClipperPath(poly), ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
0096     ClipperLib::Paths result;
0097     co.Execute(result, delta);
0098     return clipperPathToQPolygon(result[0]);
0099 }