/**************************************************************************** ** ** Qt adaptation of geosimplify-js ** Copyright (C) 2017 Daniel Patterson ** See 3rdParty/geosimplify.js for the original license. ** ** Copyright (C) 2020 Paolo Angelelli ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtLocation module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qgeosimplify_p.h" #include QT_BEGIN_NAMESPACE double QGeoSimplify::getDist(const QGeoCoordinate &p1, const QGeoCoordinate &p2) { return p1.distanceTo(p2); } QDoubleVector2D QGeoSimplify::closestPoint(const QDoubleVector2D &p, const QDoubleVector2D &a, const QDoubleVector2D &b) { if (a == b) return a; const double u = ((p.x() - a.x()) * (b.x() - a.x()) + (p.y() - a.y()) * (b.y() - a.y()) ) / (b - a).lengthSquared(); const QDoubleVector2D intersection(a.x() + u * (b.x() - a.x()) , a.y() + u * (b.y() - a.y()) ); QDoubleVector2D candidate = ( (p-a).length() < (p-b).length() ) ? a : b; if (u > 0 && u < 1 && (p-intersection).length() < (p-candidate).length() ) // And it falls in the segment candidate = intersection; return candidate; } QGeoCoordinate QGeoSimplify::closestPoint(const QGeoCoordinate &pc, const QGeoCoordinate &ac, const QGeoCoordinate &bc, const double &leftBound) { QDoubleVector2D p = QWebMercator::coordToMercator(pc); if (p.x() < leftBound) p.setX(p.x() + leftBound); // unwrap X QDoubleVector2D a = QWebMercator::coordToMercator(ac); if (a.x() < leftBound) a.setX(a.x() + leftBound); // unwrap X QDoubleVector2D b = QWebMercator::coordToMercator(bc); if (b.x() < leftBound) b.setX(b.x() + leftBound); // unwrap X QDoubleVector2D intersection = closestPoint(p, a, b); if (intersection.x() > 1.0) intersection.setX(intersection.x() - leftBound); // wrap X const QGeoCoordinate closest = QWebMercator::mercatorToCoord(intersection); return closest; } double QGeoSimplify::getSegDist(const QGeoCoordinate &pc, const QGeoCoordinate &ac, const QGeoCoordinate &bc, const double &leftBound) { const QGeoCoordinate closest = closestPoint(pc, ac, bc, leftBound); const double distanceMeters = pc.distanceTo(closest); return distanceMeters; } double QGeoSimplify::getSegDist(const QDoubleVector2D &p, const QDoubleVector2D &a, const QDoubleVector2D &b, const double &leftBound) { QDoubleVector2D intersection = closestPoint(p, a, b); return getDist(intersection, p, leftBound); } void QGeoSimplify::simplifyDPStep(const QList &points, const double &leftBound, int first, int last, double offsetTolerance, QList &simplified) { double maxDistanceFound = offsetTolerance; int index = 0; for (int i = first + 1; i < last; i++) { const double distance = getSegDist(points.at(i), points.at(first), points.at(last), leftBound); if (distance > maxDistanceFound) { index = i; maxDistanceFound = distance; } } if (index > 0) { if (index - first > 1) simplifyDPStep(points, leftBound, first, index, offsetTolerance, simplified); simplified.append(points.at(index)); if (last - index > 1) simplifyDPStep(points, leftBound, index, last, offsetTolerance, simplified); } } double QGeoSimplify::getDist(QDoubleVector2D a, QDoubleVector2D b, const double &leftBound) { if (a.x() > 1.0) a.setX(a.x() - leftBound); // wrap X if (b.x() > 1.0) b.setX(b.x() - leftBound); // wrap X return QWebMercator::mercatorToCoord(a).distanceTo( QWebMercator::mercatorToCoord(b)); } void QGeoSimplify::simplifyDPStep(const QList &points, const double &leftBound, int first, int last, double offsetTolerance, QList &simplified) { double maxDistanceFound = offsetTolerance; int index = 0; for (int i = first + 1; i < last; i++) { const double distance = getSegDist(points.at(i), points.at(first), points.at(last), leftBound); if (distance > maxDistanceFound) { index = i; maxDistanceFound = distance; } } if (index > 0) { if (index - first > 1) simplifyDPStep(points, leftBound, first, index, offsetTolerance, simplified); simplified.append(points.at(index)); if (last - index > 1) simplifyDPStep(points, leftBound, index, last, offsetTolerance, simplified); } } static double pixelDistanceAtZoomAndLatitude(int zoom, double latitude) { const double den = double((1 << (zoom + 8))); const double pixelDist = (QLocationUtils::earthMeanCircumference() * std::cos(QLocationUtils::radians(latitude))) / den; return pixelDist; } static QGeoCoordinate unwrappedToGeo(QDoubleVector2D p, double leftBound) { if (p.x() > 1.0) p.setX(p.x() - leftBound); return QWebMercator::mercatorToCoord(p); } void QGeoSimplify::simplifyDPStepZL(const QList &points, const double &leftBound, int first, int last, int zoomLevel, QList &simplified) { const QGeoCoordinate firstC = unwrappedToGeo(points.at(first), leftBound); const QGeoCoordinate lastC = unwrappedToGeo(points.at(last), leftBound); double maxDistanceFound = (pixelDistanceAtZoomAndLatitude(zoomLevel, firstC.latitude()) + pixelDistanceAtZoomAndLatitude(zoomLevel, lastC.latitude())) * 0.5; int index = 0; for (int i = first + 1; i < last; i++) { const double distance = getSegDist(points.at(i), points.at(first), points.at(last), leftBound); if (distance > maxDistanceFound) { index = i; maxDistanceFound = distance; } } if (index > 0) { if (index - first > 1) simplifyDPStepZL(points, leftBound, first, index, zoomLevel, simplified); simplified.append(points.at(index)); if (last - index > 1) simplifyDPStepZL(points, leftBound, index, last, zoomLevel, simplified); } } QList QGeoSimplify::simplifyDouglasPeucker(const QList &points, const double &leftBound, double offsetTolerance) { const int last = points.size() - 1; QList simplified { points.first() }; simplifyDPStep(points, leftBound, 0, last, offsetTolerance, simplified); simplified.append(points.at(last)); return simplified; } QList QGeoSimplify::simplifyDouglasPeucker(const QList &points, const double &leftBound, double offsetTolerance) { const int last = points.size() - 1; QList simplified { points.first() }; simplifyDPStep(points, leftBound, 0, last, offsetTolerance, simplified); simplified.append(points.at(last)); return simplified; } QList QGeoSimplify::simplifyDouglasPeuckerZL(const QList &points, const double &leftBound, int zoomLevel) { const int last = points.size() - 1; QList simplified { points.first() }; simplifyDPStepZL(points, leftBound, 0, last, zoomLevel, simplified); simplified.append(points.at(last)); return simplified; } QList QGeoSimplify::geoSimplify(const QList &points, const double &leftBound, double offsetTolerance) // also in meters { if (points.size() <= 2) return points; return simplifyDouglasPeucker(points, leftBound, offsetTolerance); } QList QGeoSimplify::geoSimplify(const QList &points, const double &leftBound, double offsetTolerance) // also in meters { if (points.size() <= 2) return points; return simplifyDouglasPeucker(points, leftBound, offsetTolerance); } QList QGeoSimplify::geoSimplifyZL(const QList &points, const double &leftBound, int zoomLevel) { if (points.size() <= 2) return points; return simplifyDouglasPeuckerZL(points, leftBound, zoomLevel); } QT_END_NAMESPACE