1 /****************************************************************************
2 **
3 ** Qt adaptation of geosimplify-js
4 ** Copyright (C) 2017 Daniel Patterson
5 ** See 3rdParty/geosimplify.js for the original license.
6 **
7 ** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com>
8 ** Copyright (C) 2020 The Qt Company Ltd.
9 ** Contact: http://www.qt.io/licensing/
10 **
11 ** This file is part of the QtLocation module of the Qt Toolkit.
12 **
13 ** $QT_BEGIN_LICENSE:LGPL3$
14 ** Commercial License Usage
15 ** Licensees holding valid commercial Qt licenses may use this file in
16 ** accordance with the commercial license agreement provided with the
17 ** Software or, alternatively, in accordance with the terms contained in
18 ** a written agreement between you and The Qt Company. For licensing terms
19 ** and conditions see http://www.qt.io/terms-conditions. For further
20 ** information use the contact form at http://www.qt.io/contact-us.
21 **
22 ** GNU Lesser General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU Lesser
24 ** General Public License version 3 as published by the Free Software
25 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
26 ** packaging of this file. Please review the following information to
27 ** ensure the GNU Lesser General Public License version 3 requirements
28 ** will be met: https://www.gnu.org/licenses/lgpl.html.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 2.0 or later as published by the Free
33 ** Software Foundation and appearing in the file LICENSE.GPL included in
34 ** the packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 2.0 requirements will be
36 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qgeosimplify_p.h"
43 #include <QtPositioning/private/qlocationutils_p.h>
44
45 QT_BEGIN_NAMESPACE
46
getDist(const QGeoCoordinate & p1,const QGeoCoordinate & p2)47 double QGeoSimplify::getDist(const QGeoCoordinate &p1, const QGeoCoordinate &p2)
48 {
49 return p1.distanceTo(p2);
50 }
51
closestPoint(const QDoubleVector2D & p,const QDoubleVector2D & a,const QDoubleVector2D & b)52 QDoubleVector2D QGeoSimplify::closestPoint(const QDoubleVector2D &p, const QDoubleVector2D &a, const QDoubleVector2D &b)
53 {
54 if (a == b)
55 return a;
56
57 const double u = ((p.x() - a.x()) * (b.x() - a.x()) + (p.y() - a.y()) * (b.y() - a.y()) ) / (b - a).lengthSquared();
58 const QDoubleVector2D intersection(a.x() + u * (b.x() - a.x()) , a.y() + u * (b.y() - a.y()) );
59 QDoubleVector2D candidate = ( (p-a).length() < (p-b).length() ) ? a : b;
60 if (u > 0 && u < 1
61 && (p-intersection).length() < (p-candidate).length() ) // And it falls in the segment
62 candidate = intersection;
63 return candidate;
64 }
65
closestPoint(const QGeoCoordinate & pc,const QGeoCoordinate & ac,const QGeoCoordinate & bc,const double & leftBound)66 QGeoCoordinate QGeoSimplify::closestPoint(const QGeoCoordinate &pc, const QGeoCoordinate &ac, const QGeoCoordinate &bc, const double &leftBound)
67 {
68 QDoubleVector2D p = QWebMercator::coordToMercator(pc);
69 if (p.x() < leftBound)
70 p.setX(p.x() + leftBound); // unwrap X
71
72 QDoubleVector2D a = QWebMercator::coordToMercator(ac);
73 if (a.x() < leftBound)
74 a.setX(a.x() + leftBound); // unwrap X
75
76 QDoubleVector2D b = QWebMercator::coordToMercator(bc);
77 if (b.x() < leftBound)
78 b.setX(b.x() + leftBound); // unwrap X
79
80 QDoubleVector2D intersection = closestPoint(p, a, b);
81 if (intersection.x() > 1.0)
82 intersection.setX(intersection.x() - leftBound); // wrap X
83
84 const QGeoCoordinate closest = QWebMercator::mercatorToCoord(intersection);
85 return closest;
86 }
87
getSegDist(const QGeoCoordinate & pc,const QGeoCoordinate & ac,const QGeoCoordinate & bc,const double & leftBound)88 double QGeoSimplify::getSegDist(const QGeoCoordinate &pc, const QGeoCoordinate &ac, const QGeoCoordinate &bc, const double &leftBound)
89 {
90 const QGeoCoordinate closest = closestPoint(pc, ac, bc, leftBound);
91 const double distanceMeters = pc.distanceTo(closest);
92 return distanceMeters;
93 }
94
getSegDist(const QDoubleVector2D & p,const QDoubleVector2D & a,const QDoubleVector2D & b,const double & leftBound)95 double QGeoSimplify::getSegDist(const QDoubleVector2D &p, const QDoubleVector2D &a, const QDoubleVector2D &b, const double &leftBound)
96 {
97 QDoubleVector2D intersection = closestPoint(p, a, b);
98 return getDist(intersection, p, leftBound);
99 }
100
simplifyDPStep(const QList<QGeoCoordinate> & points,const double & leftBound,int first,int last,double offsetTolerance,QList<QGeoCoordinate> & simplified)101 void QGeoSimplify::simplifyDPStep(const QList<QGeoCoordinate> &points, const double &leftBound, int first, int last, double offsetTolerance, QList<QGeoCoordinate> &simplified)
102 {
103 double maxDistanceFound = offsetTolerance;
104 int index = 0;
105
106 for (int i = first + 1; i < last; i++) {
107 const double distance = getSegDist(points.at(i),
108 points.at(first),
109 points.at(last),
110 leftBound);
111
112 if (distance > maxDistanceFound) {
113 index = i;
114 maxDistanceFound = distance;
115 }
116 }
117
118 if (index > 0) {
119 if (index - first > 1)
120 simplifyDPStep(points,
121 leftBound,
122 first,
123 index,
124 offsetTolerance,
125 simplified);
126 simplified.append(points.at(index));
127 if (last - index > 1)
128 simplifyDPStep(points,
129 leftBound,
130 index,
131 last,
132 offsetTolerance,
133 simplified);
134 }
135 }
136
getDist(QDoubleVector2D a,QDoubleVector2D b,const double & leftBound)137 double QGeoSimplify::getDist(QDoubleVector2D a, QDoubleVector2D b, const double &leftBound)
138 {
139 if (a.x() > 1.0)
140 a.setX(a.x() - leftBound); // wrap X
141 if (b.x() > 1.0)
142 b.setX(b.x() - leftBound); // wrap X
143 return QWebMercator::mercatorToCoord(a).distanceTo(
144 QWebMercator::mercatorToCoord(b));
145 }
146
simplifyDPStep(const QList<QDoubleVector2D> & points,const double & leftBound,int first,int last,double offsetTolerance,QList<QDoubleVector2D> & simplified)147 void QGeoSimplify::simplifyDPStep(const QList<QDoubleVector2D> &points,
148 const double &leftBound,
149 int first,
150 int last,
151 double offsetTolerance,
152 QList<QDoubleVector2D> &simplified)
153 {
154 double maxDistanceFound = offsetTolerance;
155 int index = 0;
156
157 for (int i = first + 1; i < last; i++) {
158 const double distance = getSegDist(points.at(i),
159 points.at(first),
160 points.at(last),
161 leftBound);
162
163 if (distance > maxDistanceFound) {
164 index = i;
165 maxDistanceFound = distance;
166 }
167 }
168
169 if (index > 0) {
170 if (index - first > 1)
171 simplifyDPStep(points,
172 leftBound,
173 first,
174 index,
175 offsetTolerance,
176 simplified);
177 simplified.append(points.at(index));
178 if (last - index > 1)
179 simplifyDPStep(points,
180 leftBound,
181 index,
182 last,
183 offsetTolerance,
184 simplified);
185 }
186 }
187
pixelDistanceAtZoomAndLatitude(int zoom,double latitude)188 static double pixelDistanceAtZoomAndLatitude(int zoom, double latitude)
189 {
190 const double den = double((1 << (zoom + 8)));
191 const double pixelDist = (QLocationUtils::earthMeanCircumference() *
192 std::cos(QLocationUtils::radians(latitude))) / den;
193 return pixelDist;
194 }
195
unwrappedToGeo(QDoubleVector2D p,double leftBound)196 static QGeoCoordinate unwrappedToGeo(QDoubleVector2D p, double leftBound)
197 {
198 if (p.x() > 1.0)
199 p.setX(p.x() - leftBound);
200 return QWebMercator::mercatorToCoord(p);
201 }
202
simplifyDPStepZL(const QList<QDoubleVector2D> & points,const double & leftBound,int first,int last,int zoomLevel,QList<QDoubleVector2D> & simplified)203 void QGeoSimplify::simplifyDPStepZL(const QList<QDoubleVector2D> &points,
204 const double &leftBound,
205 int first,
206 int last,
207 int zoomLevel,
208 QList<QDoubleVector2D> &simplified)
209 {
210 const QGeoCoordinate firstC = unwrappedToGeo(points.at(first), leftBound);
211 const QGeoCoordinate lastC = unwrappedToGeo(points.at(last), leftBound);
212 double maxDistanceFound = (pixelDistanceAtZoomAndLatitude(zoomLevel, firstC.latitude())
213 + pixelDistanceAtZoomAndLatitude(zoomLevel, lastC.latitude())) * 0.5;
214 int index = 0;
215
216 for (int i = first + 1; i < last; i++) {
217 const double distance = getSegDist(points.at(i),
218 points.at(first),
219 points.at(last),
220 leftBound);
221
222 if (distance > maxDistanceFound) {
223 index = i;
224 maxDistanceFound = distance;
225 }
226 }
227
228 if (index > 0) {
229 if (index - first > 1)
230 simplifyDPStepZL(points,
231 leftBound,
232 first,
233 index,
234 zoomLevel,
235 simplified);
236 simplified.append(points.at(index));
237 if (last - index > 1)
238 simplifyDPStepZL(points,
239 leftBound,
240 index,
241 last,
242 zoomLevel,
243 simplified);
244 }
245 }
246
simplifyDouglasPeucker(const QList<QGeoCoordinate> & points,const double & leftBound,double offsetTolerance)247 QList<QGeoCoordinate> QGeoSimplify::simplifyDouglasPeucker(const QList<QGeoCoordinate> &points,
248 const double &leftBound,
249 double offsetTolerance) {
250 const int last = points.size() - 1;
251 QList<QGeoCoordinate> simplified { points.first() };
252 simplifyDPStep(points, leftBound, 0, last, offsetTolerance, simplified);
253 simplified.append(points.at(last));
254 return simplified;
255 }
256
simplifyDouglasPeucker(const QList<QDoubleVector2D> & points,const double & leftBound,double offsetTolerance)257 QList<QDoubleVector2D> QGeoSimplify::simplifyDouglasPeucker(const QList<QDoubleVector2D> &points,
258 const double &leftBound,
259 double offsetTolerance) {
260 const int last = points.size() - 1;
261 QList<QDoubleVector2D> simplified { points.first() };
262 simplifyDPStep(points, leftBound, 0, last, offsetTolerance, simplified);
263 simplified.append(points.at(last));
264 return simplified;
265 }
266
simplifyDouglasPeuckerZL(const QList<QDoubleVector2D> & points,const double & leftBound,int zoomLevel)267 QList<QDoubleVector2D> QGeoSimplify::simplifyDouglasPeuckerZL(const QList<QDoubleVector2D> &points,
268 const double &leftBound,
269 int zoomLevel)
270 {
271 const int last = points.size() - 1;
272 QList<QDoubleVector2D> simplified { points.first() };
273 simplifyDPStepZL(points, leftBound, 0, last, zoomLevel, simplified);
274 simplified.append(points.at(last));
275 return simplified;
276 }
277
geoSimplify(const QList<QGeoCoordinate> & points,const double & leftBound,double offsetTolerance)278 QList<QGeoCoordinate> QGeoSimplify::geoSimplify(const QList<QGeoCoordinate> &points,
279 const double &leftBound,
280 double offsetTolerance) // also in meters
281 {
282 if (points.size() <= 2)
283 return points;
284 return simplifyDouglasPeucker(points,
285 leftBound,
286 offsetTolerance);
287 }
288
geoSimplify(const QList<QDoubleVector2D> & points,const double & leftBound,double offsetTolerance)289 QList<QDoubleVector2D> QGeoSimplify::geoSimplify(const QList<QDoubleVector2D> &points,
290 const double &leftBound,
291 double offsetTolerance) // also in meters
292 {
293 if (points.size() <= 2)
294 return points;
295 return simplifyDouglasPeucker(points,
296 leftBound,
297 offsetTolerance);
298 }
299
geoSimplifyZL(const QList<QDoubleVector2D> & points,const double & leftBound,int zoomLevel)300 QList<QDoubleVector2D> QGeoSimplify::geoSimplifyZL(const QList<QDoubleVector2D> &points,
301 const double &leftBound,
302 int zoomLevel)
303 {
304 if (points.size() <= 2)
305 return points;
306 return simplifyDouglasPeuckerZL(points,
307 leftBound,
308 zoomLevel);
309 }
310
311
312 QT_END_NAMESPACE
313
314