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 }