1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtLocation module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or later as published by the Free |
28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
29 | ** the packaging of this file. Please review the following information to |
30 | ** ensure the GNU General Public License version 2.0 requirements will be |
31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
32 | ** |
33 | ** $QT_END_LICENSE$ |
34 | ** |
35 | ****************************************************************************/ |
36 | |
37 | #include "qdeclarativepolylinemapitem_p.h" |
38 | #include "qdeclarativepolylinemapitem_p_p.h" |
39 | #include "qdeclarativerectanglemapitem_p_p.h" |
40 | #include "qdeclarativecirclemapitem_p_p.h" |
41 | #include "qlocationutils_p.h" |
42 | #include "qdeclarativegeomapitemutils_p.h" |
43 | #include "error_messages_p.h" |
44 | #include "locationvaluetypehelper_p.h" |
45 | #include "qdoublevector2d_p.h" |
46 | #include <QtLocation/private/qgeomap_p.h> |
47 | #include <QtPositioning/private/qwebmercator_p.h> |
48 | |
49 | #include <QtCore/QScopedValueRollback> |
50 | #include <QtQml/QQmlInfo> |
51 | #include <QtQml/private/qqmlengine_p.h> |
52 | #include <QPainter> |
53 | #include <QPainterPath> |
54 | #include <QPainterPathStroker> |
55 | #include <qnumeric.h> |
56 | |
57 | #include <QtGui/private/qvectorpath_p.h> |
58 | #include <QtGui/private/qtriangulatingstroker_p.h> |
59 | #include <QtGui/private/qtriangulator_p.h> |
60 | |
61 | #include <QtPositioning/private/qclipperutils_p.h> |
62 | #include <QtPositioning/private/qgeopath_p.h> |
63 | #include <QtQuick/private/qsgmaterialshader_p.h> |
64 | #include <array> |
65 | #include <QThreadPool> |
66 | #include <QRunnable> |
67 | #include <QtLocation/private/qgeomapparameter_p.h> |
68 | #include "qgeosimplify_p.h" |
69 | |
70 | QT_BEGIN_NAMESPACE |
71 | |
72 | struct ThreadPool // to have a thread pool with max 1 thread for geometry processing |
73 | { |
74 | ThreadPool () |
75 | { |
76 | m_threadPool.setMaxThreadCount(1); |
77 | } |
78 | |
79 | void start(QRunnable *runnable, int priority = 0) |
80 | { |
81 | m_threadPool.start(runnable, priority); |
82 | } |
83 | |
84 | QThreadPool m_threadPool; |
85 | }; |
86 | |
87 | Q_GLOBAL_STATIC(ThreadPool, threadPool) |
88 | |
89 | |
90 | static const double kClipperScaleFactor = 281474976710656.0; // 48 bits of precision |
91 | |
92 | static inline IntPoint toIntPoint(const double x, const double y) |
93 | { |
94 | return IntPoint(cInt(x * kClipperScaleFactor), cInt(y * kClipperScaleFactor)); |
95 | } |
96 | |
97 | static IntPoint toIntPoint(const QDoubleVector2D &p) |
98 | { |
99 | return toIntPoint(x: p.x(), y: p.y()); |
100 | } |
101 | |
102 | static bool get_line_intersection(const double p0_x, |
103 | const double p0_y, |
104 | const double p1_x, |
105 | const double p1_y, |
106 | const double p2_x, |
107 | const double p2_y, |
108 | const double p3_x, |
109 | const double p3_y, |
110 | double *i_x, |
111 | double *i_y, |
112 | double *i_t) |
113 | { |
114 | const double s10_x = p1_x - p0_x; |
115 | const double s10_y = p1_y - p0_y; |
116 | const double s32_x = p3_x - p2_x; |
117 | const double s32_y = p3_y - p2_y; |
118 | |
119 | const double denom = s10_x * s32_y - s32_x * s10_y; |
120 | if (denom == 0.0) |
121 | return false; // Collinear |
122 | const bool denomPositive = denom > 0; |
123 | |
124 | const double s02_x = p0_x - p2_x; |
125 | const double s02_y = p0_y - p2_y; |
126 | const double s_numer = s10_x * s02_y - s10_y * s02_x; |
127 | if ((s_numer < 0.0) == denomPositive) |
128 | return false; // No collision |
129 | |
130 | const double t_numer = s32_x * s02_y - s32_y * s02_x; |
131 | if ((t_numer < 0.0) == denomPositive) |
132 | return false; // No collision |
133 | |
134 | if (((s_numer > denom) == denomPositive) || ((t_numer > denom) == denomPositive)) |
135 | return false; // No collision |
136 | // Collision detected |
137 | *i_t = t_numer / denom; |
138 | *i_x = p0_x + (*i_t * s10_x); |
139 | *i_y = p0_y + (*i_t * s10_y); |
140 | |
141 | return true; |
142 | } |
143 | |
144 | enum SegmentType { |
145 | NoIntersection, |
146 | OneIntersection, |
147 | TwoIntersections |
148 | }; |
149 | |
150 | static QList<QList<QDoubleVector2D> > clipLine( |
151 | const QList<QDoubleVector2D> &l, |
152 | const QList<QDoubleVector2D> &poly) |
153 | { |
154 | QList<QList<QDoubleVector2D> > res; |
155 | if (poly.size() < 2 || l.size() < 2) |
156 | return res; |
157 | |
158 | // Step 1: build edges |
159 | std::vector<std::array<double, 4> > edges; |
160 | for (int i = 1; i < poly.size(); i++) |
161 | edges.push_back(x: { ._M_elems: { poly.at(i: i-1).x(), poly.at(i: i-1).y(), poly.at(i).x(), poly.at(i).y() } }); |
162 | edges.push_back(x: { ._M_elems: { poly.at(i: poly.size()-1).x(), poly.at(i: poly.size()-1).y(), poly.at(i: 0).x(), poly.at(i: 0).y() } }); |
163 | |
164 | // Build Path to check for containment, for edges not intersecting |
165 | // This step could be speeded up by forcing the orientation of the polygon, and testing the cross products in the step |
166 | // below, thus avoiding to resort to clipper. |
167 | Path clip; |
168 | for (const auto &v: poly) |
169 | clip.push_back(x: toIntPoint(p: v)); |
170 | |
171 | // Step 2: check each segment against each edge |
172 | QList<QDoubleVector2D> subLine; |
173 | std::array<double, 4> intersections = { ._M_elems: { 0.0, 0.0, 0.0, 0.0 } }; |
174 | |
175 | for (int i = 0; i < l.size() - 1; ++i) { |
176 | SegmentType type = NoIntersection; |
177 | double t = -1; // valid values are in [0, 1]. Only written if intersects |
178 | double previousT = t; |
179 | double i_x, i_y; |
180 | |
181 | const int firstContained = c2t::clip2tri::pointInPolygon(pt: toIntPoint(x: l.at(i).x(), y: l.at(i).y()), path: clip); |
182 | const int secondContained = c2t::clip2tri::pointInPolygon(pt: toIntPoint(x: l.at(i: i+1).x(), y: l.at(i: i+1).y()), path: clip); |
183 | |
184 | if (firstContained && secondContained) { // Second most common condition, test early and skip inner loop if possible |
185 | if (!subLine.size()) |
186 | subLine.push_back(t: l.at(i)); // the initial element has to be pushed now. |
187 | subLine.push_back(t: l.at(i: i+1)); |
188 | continue; |
189 | } |
190 | |
191 | for (unsigned int j = 0; j < edges.size(); ++j) { |
192 | const bool intersects = get_line_intersection(p0_x: l.at(i).x(), |
193 | p0_y: l.at(i).y(), |
194 | p1_x: l.at(i: i+1).x(), |
195 | p1_y: l.at(i: i+1).y(), |
196 | p2_x: edges.at(n: j).at(n: 0), |
197 | p2_y: edges.at(n: j).at(n: 1), |
198 | p3_x: edges.at(n: j).at(n: 2), |
199 | p3_y: edges.at(n: j).at(n: 3), |
200 | i_x: &i_x, |
201 | i_y: &i_y, |
202 | i_t: &t); |
203 | if (intersects) { |
204 | if (previousT >= 0.0) { //One intersection already hit |
205 | if (t < previousT) { // Reorder |
206 | intersections[2] = intersections[0]; |
207 | intersections[3] = intersections[1]; |
208 | intersections[0] = i_x; |
209 | intersections[1] = i_y; |
210 | } else { |
211 | intersections[2] = i_x; |
212 | intersections[3] = i_y; |
213 | } |
214 | |
215 | type = TwoIntersections; |
216 | break; // no need to check anything else |
217 | } else { // First intersection |
218 | intersections[0] = i_x; |
219 | intersections[1] = i_y; |
220 | type = OneIntersection; |
221 | } |
222 | previousT = t; |
223 | } |
224 | } |
225 | |
226 | if (type == NoIntersection) { |
227 | if (!firstContained && !secondContained) { // Both outside |
228 | subLine.clear(); |
229 | } else if (firstContained && secondContained) { |
230 | // Handled above already. |
231 | } else { // Mismatch between PointInPolygon and get_line_intersection. Treat it as no intersection |
232 | if (subLine.size()) |
233 | res.push_back(t: subLine); |
234 | subLine.clear(); |
235 | } |
236 | } else if (type == OneIntersection) { // Need to check the following cases to avoid mismatch with PointInPolygon result. |
237 | if (firstContained <= 0 && secondContained > 0) { // subLine MUST be empty |
238 | if (!subLine.size()) |
239 | subLine.push_back(t: QDoubleVector2D(intersections[0], intersections[1])); |
240 | subLine.push_back(t: l.at(i: i+1)); |
241 | } else if (firstContained > 0 && secondContained <= 0) { // subLine MUST NOT be empty |
242 | if (!subLine.size()) |
243 | subLine.push_back(t: l.at(i)); |
244 | subLine.push_back(t: QDoubleVector2D(intersections[0], intersections[1])); |
245 | res.push_back(t: subLine); |
246 | subLine.clear(); |
247 | } else { |
248 | if (subLine.size()) |
249 | res.push_back(t: subLine); |
250 | subLine.clear(); |
251 | } |
252 | } else { // Two |
253 | // restart strip |
254 | subLine.clear(); |
255 | subLine.push_back(t: QDoubleVector2D(intersections[0], intersections[1])); |
256 | subLine.push_back(t: QDoubleVector2D(intersections[2], intersections[3])); |
257 | res.push_back(t: subLine); |
258 | subLine.clear(); |
259 | } |
260 | } |
261 | |
262 | if (subLine.size()) |
263 | res.push_back(t: subLine); |
264 | return res; |
265 | } |
266 | |
267 | /*! |
268 | \qmltype MapPolyline |
269 | \instantiates QDeclarativePolylineMapItem |
270 | \inqmlmodule QtLocation |
271 | \ingroup qml-QtLocation5-maps |
272 | \since QtLocation 5.0 |
273 | |
274 | \brief The MapPolyline type displays a polyline on a map. |
275 | |
276 | The MapPolyline type displays a polyline on a map, specified in terms of an ordered list of |
277 | \l {coordinate}{coordinates}. The \l {coordinate}{coordinates} on |
278 | the path cannot be directly changed after being added to the Polyline. Instead, copy the |
279 | \l path into a var, modify the copy and reassign the copy back to the \l path. |
280 | |
281 | \code |
282 | var path = mapPolyline.path; |
283 | path[0].latitude = 5; |
284 | mapPolyline.path = path; |
285 | \endcode |
286 | |
287 | Coordinates can also be added and removed at any time using the \l addCoordinate and |
288 | \l removeCoordinate methods. |
289 | |
290 | By default, the polyline is displayed as a 1-pixel thick black line. This |
291 | can be changed using the \l line.width and \l line.color properties. |
292 | |
293 | \section2 Performance |
294 | |
295 | MapPolylines have a rendering cost that is O(n) with respect to the number |
296 | of vertices. This means that the per frame cost of having a polyline on |
297 | the Map grows in direct proportion to the number of points in the polyline. |
298 | |
299 | Like the other map objects, MapPolyline is normally drawn without a smooth |
300 | appearance. Setting the \l {Item::opacity}{opacity} property will force the object to |
301 | be blended, which decreases performance considerably depending on the hardware in use. |
302 | |
303 | \section2 Example Usage |
304 | |
305 | The following snippet shows a MapPolyline with 4 points, making a shape |
306 | like the top part of a "question mark" (?), near Brisbane, Australia. |
307 | The line drawn is 3 pixels in width and green in color. |
308 | |
309 | \code |
310 | Map { |
311 | MapPolyline { |
312 | line.width: 3 |
313 | line.color: 'green' |
314 | path: [ |
315 | { latitude: -27, longitude: 153.0 }, |
316 | { latitude: -27, longitude: 154.1 }, |
317 | { latitude: -28, longitude: 153.5 }, |
318 | { latitude: -29, longitude: 153.5 } |
319 | ] |
320 | } |
321 | } |
322 | \endcode |
323 | |
324 | \image api-mappolyline.png |
325 | */ |
326 | |
327 | /*! |
328 | \qmlproperty bool QtLocation::MapPolyline::autoFadeIn |
329 | |
330 | This property holds whether the item automatically fades in when zooming into the map |
331 | starting from very low zoom levels. By default this is \c true. |
332 | Setting this property to \c false causes the map item to always have the opacity specified |
333 | with the \l QtQuick::Item::opacity property, which is 1.0 by default. |
334 | |
335 | \since 5.14 |
336 | */ |
337 | |
338 | QDeclarativeMapLineProperties::QDeclarativeMapLineProperties(QObject *parent) : |
339 | QObject(parent), |
340 | width_(1.0), |
341 | color_(Qt::black) |
342 | { |
343 | } |
344 | |
345 | /*! |
346 | \internal |
347 | */ |
348 | QColor QDeclarativeMapLineProperties::color() const |
349 | { |
350 | return color_; |
351 | } |
352 | |
353 | /*! |
354 | \internal |
355 | */ |
356 | void QDeclarativeMapLineProperties::setColor(const QColor &color) |
357 | { |
358 | if (color_ == color) |
359 | return; |
360 | |
361 | color_ = color; |
362 | emit colorChanged(color: color_); |
363 | } |
364 | |
365 | /*! |
366 | \internal |
367 | */ |
368 | qreal QDeclarativeMapLineProperties::width() const |
369 | { |
370 | return width_; |
371 | } |
372 | |
373 | /*! |
374 | \internal |
375 | */ |
376 | void QDeclarativeMapLineProperties::setWidth(qreal width) |
377 | { |
378 | if (width_ == width) |
379 | return; |
380 | |
381 | width_ = width; |
382 | emit widthChanged(width: width_); |
383 | } |
384 | |
385 | QGeoMapPolylineGeometry::QGeoMapPolylineGeometry() |
386 | { |
387 | } |
388 | |
389 | QList<QList<QDoubleVector2D> > QGeoMapPolylineGeometry::clipPath(const QGeoMap &map, |
390 | const QList<QDoubleVector2D> &path, |
391 | QDoubleVector2D &leftBoundWrapped) |
392 | { |
393 | /* |
394 | * Approach: |
395 | * 1) project coordinates to wrapped web mercator, and do unwrapBelowX |
396 | * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons) |
397 | * 2.1) recalculate the origin and geoLeftBound to prevent these parameters from ending in unprojectable areas |
398 | * 2.2) ensure the left bound does not wrap around due to QGeoCoordinate <-> clipper conversions |
399 | */ |
400 | const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); |
401 | srcOrigin_ = geoLeftBound_; |
402 | |
403 | double unwrapBelowX = 0; |
404 | leftBoundWrapped = p.wrapMapProjection(projection: p.geoToMapProjection(coordinate: geoLeftBound_)); |
405 | if (preserveGeometry_) |
406 | unwrapBelowX = leftBoundWrapped.x(); |
407 | |
408 | QList<QDoubleVector2D> wrappedPath; |
409 | wrappedPath.reserve(alloc: path.size()); |
410 | QDoubleVector2D wrappedLeftBound(qInf(), qInf()); |
411 | // 1) |
412 | for (int i = 0; i < path.size(); ++i) { |
413 | const QDoubleVector2D &coord = path.at(i); |
414 | QDoubleVector2D wrappedProjection = p.wrapMapProjection(projection: coord); |
415 | |
416 | // We can get NaN if the map isn't set up correctly, or the projection |
417 | // is faulty -- probably best thing to do is abort |
418 | if (!qIsFinite(d: wrappedProjection.x()) || !qIsFinite(d: wrappedProjection.y())) |
419 | return QList<QList<QDoubleVector2D> >(); |
420 | |
421 | const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x()); |
422 | // unwrap x to preserve geometry if moved to border of map |
423 | if (preserveGeometry_ && isPointLessThanUnwrapBelowX) { |
424 | double distance = wrappedProjection.x() - unwrapBelowX; |
425 | if (distance < 0.0) |
426 | distance += 1.0; |
427 | wrappedProjection.setX(unwrapBelowX + distance); |
428 | } |
429 | if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) { |
430 | wrappedLeftBound = wrappedProjection; |
431 | } |
432 | wrappedPath.append(t: wrappedProjection); |
433 | } |
434 | |
435 | #ifdef QT_LOCATION_DEBUG |
436 | m_wrappedPath = wrappedPath; |
437 | #endif |
438 | |
439 | // 2) |
440 | QList<QList<QDoubleVector2D> > clippedPaths; |
441 | const QList<QDoubleVector2D> &visibleRegion = p.projectableGeometry(); |
442 | if (visibleRegion.size()) { |
443 | clippedPaths = clipLine(l: wrappedPath, poly: visibleRegion); |
444 | |
445 | // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X |
446 | QDoubleVector2D lb(qInf(), qInf()); |
447 | for (const QList<QDoubleVector2D> &path: clippedPaths) { |
448 | for (const QDoubleVector2D &p: path) { |
449 | if (p == leftBoundWrapped) { |
450 | lb = p; |
451 | break; |
452 | } else if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) { |
453 | // y-minimization needed to find the same point on polygon and border |
454 | lb = p; |
455 | } |
456 | } |
457 | } |
458 | if (qIsInf(d: lb.x())) |
459 | return QList<QList<QDoubleVector2D> >(); |
460 | |
461 | // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which |
462 | // in turn will make the geometry wrap around. |
463 | lb.setX(qMax(a: wrappedLeftBound.x(), b: lb.x())); |
464 | leftBoundWrapped = lb; |
465 | } else { |
466 | clippedPaths.append(t: wrappedPath); |
467 | } |
468 | |
469 | #ifdef QT_LOCATION_DEBUG |
470 | m_clippedPaths = clippedPaths; |
471 | #endif |
472 | |
473 | return clippedPaths; |
474 | } |
475 | |
476 | void QGeoMapPolylineGeometry::pathToScreen(const QGeoMap &map, |
477 | const QList<QList<QDoubleVector2D> > &clippedPaths, |
478 | const QDoubleVector2D &leftBoundWrapped) |
479 | { |
480 | const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); |
481 | // 3) project the resulting geometry to screen position and calculate screen bounds |
482 | double minX = qInf(); |
483 | double minY = qInf(); |
484 | double maxX = -qInf(); |
485 | double maxY = -qInf(); |
486 | srcOrigin_ = p.mapProjectionToGeo(projection: p.unwrapMapProjection(wrappedProjection: leftBoundWrapped)); |
487 | QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(wrappedProjection: leftBoundWrapped); |
488 | for (const QList<QDoubleVector2D> &path: clippedPaths) { |
489 | QDoubleVector2D lastAddedPoint; |
490 | for (int i = 0; i < path.size(); ++i) { |
491 | QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(wrappedProjection: path.at(i)); |
492 | point = point - origin; // (0,0) if point == geoLeftBound_ |
493 | |
494 | minX = qMin(a: point.x(), b: minX); |
495 | minY = qMin(a: point.y(), b: minY); |
496 | maxX = qMax(a: point.x(), b: maxX); |
497 | maxY = qMax(a: point.y(), b: maxY); |
498 | |
499 | if (i == 0) { |
500 | srcPoints_ << point.x() << point.y(); |
501 | srcPointTypes_ << QPainterPath::MoveToElement; |
502 | lastAddedPoint = point; |
503 | } else { |
504 | if ((point - lastAddedPoint).manhattanLength() > 3 || |
505 | i == path.size() - 1) { |
506 | srcPoints_ << point.x() << point.y(); |
507 | srcPointTypes_ << QPainterPath::LineToElement; |
508 | lastAddedPoint = point; |
509 | } |
510 | } |
511 | } |
512 | } |
513 | |
514 | sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY)); |
515 | } |
516 | |
517 | /*! |
518 | \internal |
519 | */ |
520 | void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map, |
521 | const QList<QDoubleVector2D> &path, |
522 | const QGeoCoordinate geoLeftBound) |
523 | { |
524 | if (!sourceDirty_) |
525 | return; |
526 | |
527 | geoLeftBound_ = geoLeftBound; |
528 | |
529 | // clear the old data and reserve enough memory |
530 | srcPoints_.clear(); |
531 | srcPoints_.reserve(asize: path.size() * 2); |
532 | srcPointTypes_.clear(); |
533 | srcPointTypes_.reserve(asize: path.size()); |
534 | |
535 | /* |
536 | * Approach: |
537 | * 1) project coordinates to wrapped web mercator, and do unwrapBelowX |
538 | * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons) |
539 | * 3) project the resulting geometry to screen position and calculate screen bounds |
540 | */ |
541 | |
542 | QDoubleVector2D leftBoundWrapped; |
543 | // 1, 2) |
544 | const QList<QList<QDoubleVector2D> > &clippedPaths = clipPath(map, path, leftBoundWrapped); |
545 | |
546 | // 3) |
547 | pathToScreen(map, clippedPaths, leftBoundWrapped); |
548 | } |
549 | |
550 | // *** SCREEN CLIPPING *** // |
551 | |
552 | enum ClipPointType { |
553 | InsidePoint = 0x00, |
554 | LeftPoint = 0x01, |
555 | RightPoint = 0x02, |
556 | BottomPoint = 0x04, |
557 | TopPoint = 0x08 |
558 | }; |
559 | |
560 | static inline int clipPointType(qreal x, qreal y, const QRectF &rect) |
561 | { |
562 | int type = InsidePoint; |
563 | if (x < rect.left()) |
564 | type |= LeftPoint; |
565 | else if (x > rect.right()) |
566 | type |= RightPoint; |
567 | if (y < rect.top()) |
568 | type |= TopPoint; |
569 | else if (y > rect.bottom()) |
570 | type |= BottomPoint; |
571 | return type; |
572 | } |
573 | |
574 | static void clipSegmentToRect(qreal x0, qreal y0, qreal x1, qreal y1, |
575 | const QRectF &clipRect, |
576 | QVector<qreal> &outPoints, |
577 | QVector<QPainterPath::ElementType> &outTypes) |
578 | { |
579 | int type0 = clipPointType(x: x0, y: y0, rect: clipRect); |
580 | int type1 = clipPointType(x: x1, y: y1, rect: clipRect); |
581 | bool accept = false; |
582 | |
583 | while (true) { |
584 | if (!(type0 | type1)) { |
585 | accept = true; |
586 | break; |
587 | } else if (type0 & type1) { |
588 | break; |
589 | } else { |
590 | qreal x = 0.0; |
591 | qreal y = 0.0; |
592 | int outsideType = type0 ? type0 : type1; |
593 | |
594 | if (outsideType & BottomPoint) { |
595 | x = x0 + (x1 - x0) * (clipRect.bottom() - y0) / (y1 - y0); |
596 | y = clipRect.bottom() - 0.1; |
597 | } else if (outsideType & TopPoint) { |
598 | x = x0 + (x1 - x0) * (clipRect.top() - y0) / (y1 - y0); |
599 | y = clipRect.top() + 0.1; |
600 | } else if (outsideType & RightPoint) { |
601 | y = y0 + (y1 - y0) * (clipRect.right() - x0) / (x1 - x0); |
602 | x = clipRect.right() - 0.1; |
603 | } else if (outsideType & LeftPoint) { |
604 | y = y0 + (y1 - y0) * (clipRect.left() - x0) / (x1 - x0); |
605 | x = clipRect.left() + 0.1; |
606 | } |
607 | |
608 | if (outsideType == type0) { |
609 | x0 = x; |
610 | y0 = y; |
611 | type0 = clipPointType(x: x0, y: y0, rect: clipRect); |
612 | } else { |
613 | x1 = x; |
614 | y1 = y; |
615 | type1 = clipPointType(x: x1, y: y1, rect: clipRect); |
616 | } |
617 | } |
618 | } |
619 | |
620 | if (accept) { |
621 | if (outPoints.size() >= 2) { |
622 | qreal lastX, lastY; |
623 | lastY = outPoints.at(i: outPoints.size() - 1); |
624 | lastX = outPoints.at(i: outPoints.size() - 2); |
625 | |
626 | if (!qFuzzyCompare(p1: lastY, p2: y0) || !qFuzzyCompare(p1: lastX, p2: x0)) { |
627 | outTypes << QPainterPath::MoveToElement; |
628 | outPoints << x0 << y0; |
629 | } |
630 | } else { |
631 | outTypes << QPainterPath::MoveToElement; |
632 | outPoints << x0 << y0; |
633 | } |
634 | |
635 | outTypes << QPainterPath::LineToElement; |
636 | outPoints << x1 << y1; |
637 | } |
638 | } |
639 | |
640 | static void clipPathToRect(const QVector<qreal> &points, |
641 | const QVector<QPainterPath::ElementType> &types, |
642 | const QRectF &clipRect, |
643 | QVector<qreal> &outPoints, |
644 | QVector<QPainterPath::ElementType> &outTypes) |
645 | { |
646 | outPoints.clear(); |
647 | outPoints.reserve(asize: points.size()); |
648 | outTypes.clear(); |
649 | outTypes.reserve(asize: types.size()); |
650 | |
651 | qreal lastX = 0; |
652 | qreal lastY = 0; // or else used uninitialized |
653 | for (int i = 0; i < types.size(); ++i) { |
654 | if (i > 0 && types[i] != QPainterPath::MoveToElement) { |
655 | qreal x = points[i * 2], y = points[i * 2 + 1]; |
656 | clipSegmentToRect(x0: lastX, y0: lastY, x1: x, y1: y, clipRect, outPoints, outTypes); |
657 | } |
658 | |
659 | lastX = points[i * 2]; |
660 | lastY = points[i * 2 + 1]; |
661 | } |
662 | } |
663 | |
664 | //////////////////////////////////////////////////////////////////////////// |
665 | |
666 | /*! |
667 | \internal |
668 | */ |
669 | void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map, |
670 | qreal strokeWidth, |
671 | bool adjustTranslation) |
672 | { |
673 | if (!screenDirty_) |
674 | return; |
675 | |
676 | QPointF origin = map.geoProjection().coordinateToItemPosition(coordinate: srcOrigin_, clipToViewport: false).toPointF(); |
677 | |
678 | if (!qIsFinite(d: origin.x()) || !qIsFinite(d: origin.y()) || srcPointTypes_.size() < 2) { // the line might have been clipped away. |
679 | clear(); |
680 | return; |
681 | } |
682 | |
683 | // Create the viewport rect in the same coordinate system |
684 | // as the actual points |
685 | QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight()); |
686 | viewport.adjust(xp1: -strokeWidth, yp1: -strokeWidth, xp2: strokeWidth * 2, yp2: strokeWidth * 2); |
687 | viewport.translate(p: -1 * origin); |
688 | |
689 | QVector<qreal> points; |
690 | QVector<QPainterPath::ElementType> types; |
691 | |
692 | if (clipToViewport_) { |
693 | // Although the geometry has already been clipped against the visible region in wrapped mercator space. |
694 | // This is currently still needed to prevent a number of artifacts deriving from QTriangulatingStroker processing |
695 | // very large lines (that is, polylines that span many pixels in screen space) |
696 | clipPathToRect(points: srcPoints_, types: srcPointTypes_, clipRect: viewport, outPoints&: points, outTypes&: types); |
697 | } else { |
698 | points = srcPoints_; |
699 | types = srcPointTypes_; |
700 | } |
701 | |
702 | QVectorPath vp(points.data(), types.size(), types.data()); |
703 | QTriangulatingStroker ts; |
704 | // As of Qt5.11, the clip argument is not actually used, in the call below. |
705 | ts.process(path: vp, pen: QPen(QBrush(Qt::black), strokeWidth), clip: QRectF(), hints: QPainter::Qt4CompatiblePainting); |
706 | |
707 | clear(); |
708 | |
709 | // Nothing is on the screen |
710 | if (ts.vertexCount() == 0) |
711 | return; |
712 | |
713 | // QTriangulatingStroker#vertexCount is actually the length of the array, |
714 | // not the number of vertices |
715 | screenVertices_.reserve(asize: ts.vertexCount()); |
716 | |
717 | QRectF bb; |
718 | |
719 | QPointF pt; |
720 | const float *vs = ts.vertices(); |
721 | for (int i = 0; i < (ts.vertexCount()/2*2); i += 2) { |
722 | pt = QPointF(vs[i], vs[i + 1]); |
723 | screenVertices_ << pt; |
724 | |
725 | if (!qIsFinite(d: pt.x()) || !qIsFinite(d: pt.y())) |
726 | break; |
727 | |
728 | if (!bb.contains(p: pt)) { |
729 | if (pt.x() < bb.left()) |
730 | bb.setLeft(pt.x()); |
731 | |
732 | if (pt.x() > bb.right()) |
733 | bb.setRight(pt.x()); |
734 | |
735 | if (pt.y() < bb.top()) |
736 | bb.setTop(pt.y()); |
737 | |
738 | if (pt.y() > bb.bottom()) |
739 | bb.setBottom(pt.y()); |
740 | } |
741 | } |
742 | |
743 | screenBounds_ = bb; |
744 | const QPointF strokeOffset = (adjustTranslation) ? QPointF(strokeWidth, strokeWidth) * 0.5: QPointF(); |
745 | this->translate( offset: -1 * sourceBounds_.topLeft() + strokeOffset); |
746 | } |
747 | |
748 | void QGeoMapPolylineGeometry::clearSource() |
749 | { |
750 | srcPoints_.clear(); |
751 | srcPointTypes_.clear(); |
752 | } |
753 | |
754 | bool QGeoMapPolylineGeometry::contains(const QPointF &point) const |
755 | { |
756 | // screenOutline_.contains(screenPoint) doesn't work, as, it appears, that |
757 | // screenOutline_ for QGeoMapPolylineGeometry is empty (QRectF(0,0 0x0)) |
758 | const QVector<QPointF> &verts = vertices(); |
759 | QPolygonF tri; |
760 | for (int i = 0; i < verts.size(); ++i) { |
761 | tri << verts[i]; |
762 | if (tri.size() == 3) { |
763 | if (tri.containsPoint(pt: point,fillRule: Qt::OddEvenFill)) |
764 | return true; |
765 | tri.remove(i: 0); |
766 | } |
767 | } |
768 | |
769 | return false; |
770 | } |
771 | |
772 | #if QT_CONFIG(opengl) |
773 | void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly) |
774 | { |
775 | if (!sourceDirty_) |
776 | return; |
777 | QGeoPath p(poly.path()); |
778 | if (poly.path().size() && poly.path().last() != poly.path().first()) |
779 | p.addCoordinate(coordinate: poly.path().first()); |
780 | updateSourcePoints(map, poly: p); |
781 | } |
782 | |
783 | void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPath &poly) |
784 | { |
785 | if (!sourceDirty_) |
786 | return; |
787 | const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); |
788 | |
789 | // build the actual path |
790 | // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints |
791 | |
792 | |
793 | QDoubleVector2D leftBoundWrapped; |
794 | // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 |
795 | QList<QDoubleVector2D> wrappedPath; |
796 | QDeclarativeGeoMapItemUtils::wrapPath(perimeter: poly.path(), geoLeftBound: geoLeftBound_, p, |
797 | wrappedPath, leftBoundWrapped: &leftBoundWrapped); |
798 | |
799 | const QGeoRectangle &boundingRectangle = poly.boundingGeoRectangle(); |
800 | updateSourcePoints(p, wrappedPath, boundingRectangle); |
801 | } |
802 | |
803 | void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoProjectionWebMercator &p, |
804 | const QList<QDoubleVector2D> &wrappedPath, |
805 | const QGeoRectangle &boundingRectangle) { |
806 | if (!sourceDirty_) |
807 | return; |
808 | // 1.1) do the same for the bbox |
809 | // Beware: vertical lines (or horizontal lines) might have an "empty" bbox. Check for that |
810 | |
811 | QGeoCoordinate topLeft = boundingRectangle.topLeft(); |
812 | QGeoCoordinate bottomRight = boundingRectangle.bottomRight(); |
813 | const qreal epsilon = 0.000001; |
814 | if (qFuzzyCompare(p1: topLeft.latitude(), p2: bottomRight.latitude())) { |
815 | topLeft.setLatitude(qBound(min: -90.0, val: topLeft.latitude() + epsilon ,max: 90.0)); |
816 | bottomRight.setLatitude(qBound(min: -90.0, val: bottomRight.latitude() - epsilon ,max: 90.0)); |
817 | } |
818 | if (qFuzzyCompare(p1: topLeft.longitude(), p2: bottomRight.longitude())) { |
819 | topLeft.setLongitude(QLocationUtils::wrapLong(lng: topLeft.longitude() - epsilon)); |
820 | bottomRight.setLongitude(QLocationUtils::wrapLong(lng: bottomRight.longitude() + epsilon)); |
821 | } |
822 | QGeoPolygon bbox(QGeoRectangle(topLeft, bottomRight)); |
823 | QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1; |
824 | QDeclarativeGeoMapItemUtils::wrapPath(perimeter: bbox.path(), geoLeftBound: bbox.boundingGeoRectangle().topLeft(), p, |
825 | wrappedPath&: wrappedBbox, wrappedPathMinus1&: wrappedBboxMinus1, wrappedPathPlus1&: wrappedBboxPlus1, leftBoundWrapped: &m_bboxLeftBoundWrapped); |
826 | |
827 | // New pointers, some old LOD task might still be running and operating on the old pointers. |
828 | resetLOD(); |
829 | |
830 | for (const auto &v: qAsConst(t: wrappedPath)) m_screenVertices->append(t: v); |
831 | |
832 | m_wrappedPolygons.resize(asize: 3); |
833 | m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1; |
834 | m_wrappedPolygons[1].wrappedBboxes = wrappedBbox; |
835 | m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1; |
836 | srcOrigin_ = geoLeftBound_; |
837 | } |
838 | |
839 | void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoRectangle &rect) |
840 | { |
841 | const QGeoPath path(QDeclarativeRectangleMapItemPrivateCPU::perimeter(rect)); |
842 | updateSourcePoints(map, poly: path); |
843 | } |
844 | |
845 | void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoCircle &circle) |
846 | { |
847 | if (!sourceDirty_) |
848 | return; |
849 | const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); |
850 | |
851 | QDoubleVector2D leftBoundWrapped; |
852 | // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 |
853 | QList<QGeoCoordinate> path; |
854 | QGeoCoordinate leftBound; |
855 | QList<QDoubleVector2D> wrappedPath; |
856 | QDeclarativeCircleMapItemPrivateCPU::calculatePeripheralPoints(path, center: circle.center(), distance: circle.radius(), steps: QDeclarativeCircleMapItemPrivateCPU::CircleSamples, leftBound); |
857 | path << path.first(); |
858 | geoLeftBound_ = leftBound; |
859 | QDeclarativeGeoMapItemUtils::wrapPath(perimeter: path, geoLeftBound: leftBound, p, wrappedPath, leftBoundWrapped: &leftBoundWrapped); |
860 | const QGeoRectangle &boundingRectangle = circle.boundingGeoRectangle(); |
861 | updateSourcePoints(p, wrappedPath, boundingRectangle); |
862 | } |
863 | |
864 | void QGeoMapPolylineGeometryOpenGL::updateScreenPoints(const QGeoMap &map, qreal strokeWidth, bool /*adjustTranslation*/) |
865 | { |
866 | if (map.viewportWidth() == 0 || map.viewportHeight() == 0) { |
867 | clear(); |
868 | return; |
869 | } |
870 | |
871 | // 1) identify which set to use: std, +1 or -1 |
872 | const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); |
873 | const QDoubleVector2D leftBoundMercator = p.geoToMapProjection(coordinate: srcOrigin_); |
874 | m_wrapOffset = p.projectionWrapFactor(projection: leftBoundMercator) + 1; // +1 to get the offset into QLists |
875 | |
876 | if (sourceDirty_) { |
877 | // 1.1) select geometry set |
878 | // This could theoretically be skipped for those polylines whose bbox is not even projectable. |
879 | // However, such optimization could only be introduced if not calculating bboxes lazily. |
880 | // Hence not doing it. |
881 | // if (m_screenVertices.size() > 1) |
882 | m_dataChanged = true; |
883 | } |
884 | |
885 | updateQuickGeometry(p, strokeWidth); |
886 | } |
887 | |
888 | void QGeoMapPolylineGeometryOpenGL::updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth) |
889 | { |
890 | // 2) clip bbox |
891 | // BBox handling -- this is related to the bounding box geometry |
892 | // that has to inevitably follow the old projection codepath |
893 | // As it needs to provide projected coordinates for QtQuick interaction. |
894 | // This could be futher optimized to be updated in a lazy fashion. |
895 | const QList<QDoubleVector2D> &wrappedBbox = m_wrappedPolygons.at(i: m_wrapOffset).wrappedBboxes; |
896 | QList<QList<QDoubleVector2D> > clippedBbox; |
897 | QDoubleVector2D bboxLeftBoundWrapped = m_bboxLeftBoundWrapped; |
898 | bboxLeftBoundWrapped.setX(bboxLeftBoundWrapped.x() + double(m_wrapOffset - 1)); |
899 | QDeclarativeGeoMapItemUtils::clipPolygon(wrappedPath: wrappedBbox, p, clippedPaths&: clippedBbox, leftBoundWrapped: &bboxLeftBoundWrapped, closed: false); |
900 | |
901 | // 3) project bbox |
902 | QPainterPath ppi; |
903 | |
904 | if ( !clippedBbox.size() || |
905 | clippedBbox.first().size() < 3) { |
906 | sourceBounds_ = screenBounds_ = QRectF(); |
907 | firstPointOffset_ = QPointF(); |
908 | screenOutline_ = ppi; |
909 | return; |
910 | } |
911 | |
912 | QDeclarativeGeoMapItemUtils::projectBbox(clippedBbox: clippedBbox.first(), p, projectedBbox&: ppi); // Using first because a clipped box should always result in one polygon |
913 | const QRectF brect = ppi.boundingRect(); |
914 | firstPointOffset_ = QPointF(brect.topLeft()); |
915 | sourceBounds_ = brect; |
916 | screenOutline_ = ppi; |
917 | |
918 | // 4) Set Screen bbox |
919 | screenBounds_ = brect; |
920 | sourceBounds_.setX(0); |
921 | sourceBounds_.setY(0); |
922 | sourceBounds_.setWidth(brect.width() + strokeWidth); |
923 | sourceBounds_.setHeight(brect.height() + strokeWidth); |
924 | } |
925 | #endif // QT_CONFIG(opengl) |
926 | |
927 | /* |
928 | * QDeclarativePolygonMapItem Private Implementations |
929 | */ |
930 | |
931 | QDeclarativePolylineMapItemPrivate::~QDeclarativePolylineMapItemPrivate() {} |
932 | |
933 | QDeclarativePolylineMapItemPrivateCPU::~QDeclarativePolylineMapItemPrivateCPU() {} |
934 | |
935 | #if QT_CONFIG(opengl) |
936 | QDeclarativePolylineMapItemPrivateOpenGLLineStrip::~QDeclarativePolylineMapItemPrivateOpenGLLineStrip() {} |
937 | |
938 | QDeclarativePolylineMapItemPrivateOpenGLExtruded::~QDeclarativePolylineMapItemPrivateOpenGLExtruded() {} |
939 | #endif |
940 | |
941 | /* |
942 | * QDeclarativePolygonMapItem Implementation |
943 | */ |
944 | |
945 | struct PolylineBackendSelector |
946 | { |
947 | #if QT_CONFIG(opengl) |
948 | PolylineBackendSelector() |
949 | { |
950 | backend = (qgetenv(varName: "QTLOCATION_OPENGL_ITEMS" ).toInt()) ? QDeclarativePolylineMapItem::OpenGLExtruded : QDeclarativePolylineMapItem::Software; |
951 | } |
952 | #endif |
953 | QDeclarativePolylineMapItem::Backend backend = QDeclarativePolylineMapItem::Software; |
954 | }; |
955 | |
956 | Q_GLOBAL_STATIC(PolylineBackendSelector, mapPolylineBackendSelector) |
957 | |
958 | QDeclarativePolylineMapItem::QDeclarativePolylineMapItem(QQuickItem *parent) |
959 | : QDeclarativeGeoMapItemBase(parent), |
960 | m_line(this), |
961 | m_dirtyMaterial(true), |
962 | m_updatingGeometry(false), |
963 | m_d(new QDeclarativePolylineMapItemPrivateCPU(*this)) |
964 | { |
965 | m_itemType = QGeoMap::MapPolyline; |
966 | m_geopath = QGeoPathEager(); |
967 | setFlag(flag: ItemHasContents, enabled: true); |
968 | QObject::connect(sender: &m_line, SIGNAL(colorChanged(QColor)), |
969 | receiver: this, SLOT(updateAfterLinePropertiesChanged())); |
970 | QObject::connect(sender: &m_line, SIGNAL(widthChanged(qreal)), |
971 | receiver: this, SLOT(updateAfterLinePropertiesChanged())); |
972 | setBackend(mapPolylineBackendSelector->backend); |
973 | } |
974 | |
975 | QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem() |
976 | { |
977 | } |
978 | |
979 | /*! |
980 | \internal |
981 | */ |
982 | void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged() |
983 | { |
984 | m_d->onLinePropertiesChanged(); |
985 | } |
986 | |
987 | /*! |
988 | \internal |
989 | */ |
990 | void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) |
991 | { |
992 | QDeclarativeGeoMapItemBase::setMap(quickMap,map); |
993 | if (map) |
994 | m_d->onMapSet(); |
995 | } |
996 | |
997 | /*! |
998 | \qmlproperty list<coordinate> MapPolyline::path |
999 | |
1000 | This property holds the ordered list of coordinates which |
1001 | define the polyline. |
1002 | */ |
1003 | |
1004 | QJSValue QDeclarativePolylineMapItem::path() const |
1005 | { |
1006 | return fromList(object: this, list: m_geopath.path()); |
1007 | } |
1008 | |
1009 | void QDeclarativePolylineMapItem::setPath(const QJSValue &value) |
1010 | { |
1011 | if (!value.isArray()) |
1012 | return; |
1013 | |
1014 | setPathFromGeoList(toList(object: this, value)); |
1015 | } |
1016 | |
1017 | /*! |
1018 | \qmlmethod void MapPolyline::setPath(geopath path) |
1019 | |
1020 | Sets the \a path using a geopath type. |
1021 | |
1022 | \since 5.10 |
1023 | |
1024 | \sa path |
1025 | */ |
1026 | void QDeclarativePolylineMapItem::setPath(const QGeoPath &path) |
1027 | { |
1028 | if (m_geopath.path() == path.path()) |
1029 | return; |
1030 | |
1031 | m_geopath = QGeoPathEager(path); |
1032 | m_d->onGeoGeometryChanged(); |
1033 | emit pathChanged(); |
1034 | } |
1035 | |
1036 | /*! |
1037 | \internal |
1038 | */ |
1039 | void QDeclarativePolylineMapItem::setPathFromGeoList(const QList<QGeoCoordinate> &path) |
1040 | { |
1041 | if (m_geopath.path() == path) |
1042 | return; |
1043 | |
1044 | m_geopath.setPath(path); |
1045 | |
1046 | m_d->onGeoGeometryChanged(); |
1047 | emit pathChanged(); |
1048 | } |
1049 | |
1050 | /*! |
1051 | \qmlmethod int MapPolyline::pathLength() |
1052 | |
1053 | Returns the number of coordinates of the polyline. |
1054 | |
1055 | \since QtLocation 5.6 |
1056 | |
1057 | \sa path |
1058 | */ |
1059 | int QDeclarativePolylineMapItem::pathLength() const |
1060 | { |
1061 | return m_geopath.path().length(); |
1062 | } |
1063 | |
1064 | /*! |
1065 | \qmlmethod void MapPolyline::addCoordinate(coordinate) |
1066 | |
1067 | Adds the specified \a coordinate to the end of the path. |
1068 | |
1069 | \sa insertCoordinate, removeCoordinate, path |
1070 | */ |
1071 | void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate) |
1072 | { |
1073 | if (!coordinate.isValid()) |
1074 | return; |
1075 | |
1076 | m_geopath.addCoordinate(coordinate); |
1077 | |
1078 | m_d->onGeoGeometryUpdated(); |
1079 | emit pathChanged(); |
1080 | } |
1081 | |
1082 | /*! |
1083 | \qmlmethod void MapPolyline::insertCoordinate(index, coordinate) |
1084 | |
1085 | Inserts a \a coordinate to the path at the given \a index. |
1086 | |
1087 | \since QtLocation 5.6 |
1088 | |
1089 | \sa addCoordinate, removeCoordinate, path |
1090 | */ |
1091 | void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordinate &coordinate) |
1092 | { |
1093 | if (index < 0 || index > m_geopath.path().length()) |
1094 | return; |
1095 | |
1096 | m_geopath.insertCoordinate(index, coordinate); |
1097 | |
1098 | m_d->onGeoGeometryChanged(); |
1099 | emit pathChanged(); |
1100 | } |
1101 | |
1102 | /*! |
1103 | \qmlmethod void MapPolyline::replaceCoordinate(index, coordinate) |
1104 | |
1105 | Replaces the coordinate in the current path at the given \a index |
1106 | with the new \a coordinate. |
1107 | |
1108 | \since QtLocation 5.6 |
1109 | |
1110 | \sa addCoordinate, insertCoordinate, removeCoordinate, path |
1111 | */ |
1112 | void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordinate &coordinate) |
1113 | { |
1114 | if (index < 0 || index >= m_geopath.path().length()) |
1115 | return; |
1116 | |
1117 | m_geopath.replaceCoordinate(index, coordinate); |
1118 | |
1119 | m_d->onGeoGeometryChanged(); |
1120 | emit pathChanged(); |
1121 | } |
1122 | |
1123 | /*! |
1124 | \qmlmethod coordinate MapPolyline::coordinateAt(index) |
1125 | |
1126 | Gets the coordinate of the polyline at the given \a index. |
1127 | If the index is outside the path's bounds then an invalid |
1128 | coordinate is returned. |
1129 | |
1130 | \since QtLocation 5.6 |
1131 | */ |
1132 | QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const |
1133 | { |
1134 | if (index < 0 || index >= m_geopath.path().length()) |
1135 | return QGeoCoordinate(); |
1136 | |
1137 | return m_geopath.coordinateAt(index); |
1138 | } |
1139 | |
1140 | /*! |
1141 | \qmlmethod coordinate MapPolyline::containsCoordinate(coordinate) |
1142 | |
1143 | Returns true if the given \a coordinate is part of the path. |
1144 | |
1145 | \since QtLocation 5.6 |
1146 | */ |
1147 | bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coordinate) |
1148 | { |
1149 | return m_geopath.containsCoordinate(coordinate); |
1150 | } |
1151 | |
1152 | /*! |
1153 | \qmlmethod void MapPolyline::removeCoordinate(coordinate) |
1154 | |
1155 | Removes \a coordinate from the path. If there are multiple instances of the |
1156 | same coordinate, the one added last is removed. |
1157 | |
1158 | If \a coordinate is not in the path this method does nothing. |
1159 | |
1160 | \sa addCoordinate, insertCoordinate, path |
1161 | */ |
1162 | void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate) |
1163 | { |
1164 | int length = m_geopath.path().length(); |
1165 | m_geopath.removeCoordinate(coordinate); |
1166 | if (m_geopath.path().length() == length) |
1167 | return; |
1168 | |
1169 | m_d->onGeoGeometryChanged(); |
1170 | emit pathChanged(); |
1171 | } |
1172 | |
1173 | /*! |
1174 | \qmlmethod void MapPolyline::removeCoordinate(index) |
1175 | |
1176 | Removes a coordinate from the path at the given \a index. |
1177 | |
1178 | If \a index is invalid then this method does nothing. |
1179 | |
1180 | \since QtLocation 5.6 |
1181 | |
1182 | \sa addCoordinate, insertCoordinate, path |
1183 | */ |
1184 | void QDeclarativePolylineMapItem::removeCoordinate(int index) |
1185 | { |
1186 | if (index < 0 || index >= m_geopath.path().length()) |
1187 | return; |
1188 | |
1189 | m_geopath.removeCoordinate(index); |
1190 | |
1191 | m_d->onGeoGeometryChanged(); |
1192 | emit pathChanged(); |
1193 | } |
1194 | |
1195 | /*! |
1196 | \qmlpropertygroup Location::MapPolyline::line |
1197 | \qmlproperty int MapPolyline::line.width |
1198 | \qmlproperty color MapPolyline::line.color |
1199 | |
1200 | This property is part of the line property group. The line |
1201 | property group holds the width and color used to draw the line. |
1202 | |
1203 | The width is in pixels and is independent of the zoom level of the map. |
1204 | The default values correspond to a black border with a width of 1 pixel. |
1205 | |
1206 | For no line, use a width of 0 or a transparent color. |
1207 | */ |
1208 | |
1209 | QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line() |
1210 | { |
1211 | return &m_line; |
1212 | } |
1213 | |
1214 | /*! |
1215 | \qmlproperty MapPolyline.Backend QtLocation::MapPolyline::backend |
1216 | |
1217 | This property holds which backend is in use to render the map item. |
1218 | Valid values are \b MapPolyline.Software and \b{MapPolyline.OpenGLLineStrip} |
1219 | and \b{MapPolyline.OpenGLExtruded}. |
1220 | The default value is \b{MapPolyline.Software}. |
1221 | |
1222 | \note \b{The release of this API with Qt 5.15 is a Technology Preview}. |
1223 | Ideally, as the OpenGL backends for map items mature, there will be |
1224 | no more need to also offer the legacy software-projection backend. |
1225 | So this property will likely disappear at some later point. |
1226 | To select OpenGL-accelerated item backends without using this property, |
1227 | it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS |
1228 | to \b{1}. |
1229 | Also note that all current OpenGL backends won't work as expected when enabling |
1230 | layers on the individual item, or when running on OpenGL core profiles greater than 2.x. |
1231 | |
1232 | \since 5.15 |
1233 | */ |
1234 | QDeclarativePolylineMapItem::Backend QDeclarativePolylineMapItem::backend() const |
1235 | { |
1236 | return m_backend; |
1237 | } |
1238 | |
1239 | void QDeclarativePolylineMapItem::setBackend(QDeclarativePolylineMapItem::Backend b) |
1240 | { |
1241 | if (b == m_backend) |
1242 | return; |
1243 | m_backend = b; |
1244 | QScopedPointer<QDeclarativePolylineMapItemPrivate> d( |
1245 | (m_backend == Software) |
1246 | ? static_cast<QDeclarativePolylineMapItemPrivate *>( |
1247 | new QDeclarativePolylineMapItemPrivateCPU(*this)) |
1248 | #if QT_CONFIG(opengl) |
1249 | : ((m_backend == OpenGLExtruded) |
1250 | ? static_cast<QDeclarativePolylineMapItemPrivate *>( |
1251 | new QDeclarativePolylineMapItemPrivateOpenGLExtruded(*this)) |
1252 | : static_cast<QDeclarativePolylineMapItemPrivate *>( |
1253 | new QDeclarativePolylineMapItemPrivateOpenGLLineStrip( |
1254 | *this)))); |
1255 | #else |
1256 | : nullptr); |
1257 | qFatal("Requested non software rendering backend, but source code is compiled wihtout opengl " |
1258 | "support" ); |
1259 | #endif |
1260 | m_d.swap(other&: d); |
1261 | m_d->onGeoGeometryChanged(); |
1262 | emit backendChanged(); |
1263 | } |
1264 | |
1265 | /*! |
1266 | \internal |
1267 | */ |
1268 | void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) |
1269 | { |
1270 | if (newGeometry.topLeft() == oldGeometry.topLeft() || !map() || !m_geopath.isValid() || m_updatingGeometry) { |
1271 | QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); |
1272 | return; |
1273 | } |
1274 | // TODO: change the algorithm to preserve the distances and size! |
1275 | QGeoCoordinate newCenter = map()->geoProjection().itemPositionToCoordinate(pos: QDoubleVector2D(newGeometry.center()), clipToViewport: false); |
1276 | QGeoCoordinate oldCenter = map()->geoProjection().itemPositionToCoordinate(pos: QDoubleVector2D(oldGeometry.center()), clipToViewport: false); |
1277 | if (!newCenter.isValid() || !oldCenter.isValid()) |
1278 | return; |
1279 | double offsetLongi = newCenter.longitude() - oldCenter.longitude(); |
1280 | double offsetLati = newCenter.latitude() - oldCenter.latitude(); |
1281 | if (offsetLati == 0.0 && offsetLongi == 0.0) |
1282 | return; |
1283 | |
1284 | m_geopath.translate(degreesLatitude: offsetLati, degreesLongitude: offsetLongi); |
1285 | m_d->onGeoGeometryChanged(); |
1286 | emit pathChanged(); |
1287 | |
1288 | // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested |
1289 | // call to this function. |
1290 | } |
1291 | |
1292 | /*! |
1293 | \internal |
1294 | */ |
1295 | void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) |
1296 | { |
1297 | if (event.mapSize.isEmpty()) |
1298 | return; |
1299 | |
1300 | m_d->afterViewportChanged(); |
1301 | } |
1302 | |
1303 | /*! |
1304 | \internal |
1305 | */ |
1306 | void QDeclarativePolylineMapItem::updatePolish() |
1307 | { |
1308 | if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) |
1309 | return; |
1310 | m_d->updatePolish(); |
1311 | } |
1312 | |
1313 | void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p, |
1314 | const char *propertyName, |
1315 | bool update) |
1316 | { |
1317 | static const QByteArrayList acceptedParameterTypes = QByteArrayList() |
1318 | << QByteArrayLiteral("lineCap" ) |
1319 | << QByteArrayLiteral("pen" ); |
1320 | switch (acceptedParameterTypes.indexOf(t: QByteArray(propertyName))) { |
1321 | case -1: |
1322 | qWarning() << "Invalid property " << QLatin1String(propertyName) << " for parameter lineStyle" ; |
1323 | break; |
1324 | case 0: // lineCap |
1325 | { |
1326 | const QVariant lineCap = p->property(name: "lineCap" ); |
1327 | m_d->m_penCapStyle = lineCap.value<Qt::PenCapStyle>(); // if invalid, will return 0 == FlatCap |
1328 | if (update) |
1329 | markSourceDirtyAndUpdate(); |
1330 | break; |
1331 | } |
1332 | case 1: // penStyle |
1333 | { |
1334 | const QVariant penStyle = p->property(name: "pen" ); |
1335 | m_d->m_penStyle = penStyle.value<Qt::PenStyle>(); |
1336 | if (m_d->m_penStyle == Qt::NoPen) |
1337 | m_d->m_penStyle = Qt::SolidLine; |
1338 | if (update) |
1339 | markSourceDirtyAndUpdate(); |
1340 | break; |
1341 | } |
1342 | } |
1343 | } |
1344 | |
1345 | void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p, const char *propertyName) |
1346 | { |
1347 | updateLineStyleParameter(p, propertyName, update: true); |
1348 | } |
1349 | |
1350 | void QDeclarativePolylineMapItem::componentComplete() |
1351 | { |
1352 | QQuickItem::componentComplete(); |
1353 | // Set up Dynamic Parameters |
1354 | QList<QGeoMapParameter *> dynamicParameters = quickChildren<QGeoMapParameter>(); |
1355 | for (QGeoMapParameter *p : qAsConst(t&: dynamicParameters)) { |
1356 | if (p->type() == QLatin1String("lineStyle" )) { |
1357 | updateLineStyleParameter(p, propertyName: "lineCap" , update: false); |
1358 | updateLineStyleParameter(p, propertyName: "pen" , update: false); |
1359 | connect(sender: p, signal: &QGeoMapParameter::propertyUpdated, |
1360 | receiver: this, slot: static_cast<void (QDeclarativePolylineMapItem::*)(QGeoMapParameter *, const char *)>(&QDeclarativePolylineMapItem::updateLineStyleParameter)); |
1361 | markSourceDirtyAndUpdate(); |
1362 | } |
1363 | } |
1364 | } |
1365 | |
1366 | void QDeclarativePolylineMapItem::markSourceDirtyAndUpdate() |
1367 | { |
1368 | m_d->markSourceDirtyAndUpdate(); |
1369 | } |
1370 | |
1371 | /*! |
1372 | \internal |
1373 | */ |
1374 | QSGNode *QDeclarativePolylineMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) |
1375 | { |
1376 | return m_d->updateMapItemPaintNode(oldNode, data); |
1377 | } |
1378 | |
1379 | bool QDeclarativePolylineMapItem::contains(const QPointF &point) const |
1380 | { |
1381 | return m_d->contains(point); |
1382 | } |
1383 | |
1384 | const QGeoShape &QDeclarativePolylineMapItem::geoShape() const |
1385 | { |
1386 | return m_geopath; |
1387 | } |
1388 | |
1389 | void QDeclarativePolylineMapItem::setGeoShape(const QGeoShape &shape) |
1390 | { |
1391 | const QGeoPath geopath(shape); // if shape isn't a path, path will be created as a default-constructed path |
1392 | setPath(geopath); |
1393 | } |
1394 | |
1395 | ////////////////////////////////////////////////////////////////////// |
1396 | |
1397 | /*! |
1398 | \internal |
1399 | */ |
1400 | VisibleNode::VisibleNode() : m_blocked{true}, m_visible{true} |
1401 | { |
1402 | |
1403 | } |
1404 | |
1405 | VisibleNode::~VisibleNode() |
1406 | { |
1407 | |
1408 | } |
1409 | |
1410 | /*! |
1411 | \internal |
1412 | */ |
1413 | bool VisibleNode::subtreeBlocked() const |
1414 | { |
1415 | return m_blocked || !m_visible; |
1416 | } |
1417 | |
1418 | /*! |
1419 | \internal |
1420 | */ |
1421 | void VisibleNode::setSubtreeBlocked(bool blocked) |
1422 | { |
1423 | m_blocked = blocked; |
1424 | } |
1425 | |
1426 | bool VisibleNode::visible() const |
1427 | { |
1428 | return m_visible; |
1429 | } |
1430 | |
1431 | /*! |
1432 | \internal |
1433 | */ |
1434 | void VisibleNode::setVisible(bool visible) |
1435 | { |
1436 | m_visible = visible; |
1437 | } |
1438 | |
1439 | /*! |
1440 | \internal |
1441 | */ |
1442 | MapItemGeometryNode::~MapItemGeometryNode() |
1443 | { |
1444 | |
1445 | } |
1446 | |
1447 | bool MapItemGeometryNode::isSubtreeBlocked() const |
1448 | { |
1449 | return subtreeBlocked(); |
1450 | } |
1451 | |
1452 | |
1453 | /*! |
1454 | \internal |
1455 | */ |
1456 | MapPolylineNode::MapPolylineNode() : |
1457 | geometry_(QSGGeometry::defaultAttributes_Point2D(),0) |
1458 | { |
1459 | geometry_.setDrawingMode(QSGGeometry::DrawTriangleStrip); |
1460 | QSGGeometryNode::setMaterial(&fill_material_); |
1461 | QSGGeometryNode::setGeometry(&geometry_); |
1462 | } |
1463 | |
1464 | |
1465 | /*! |
1466 | \internal |
1467 | */ |
1468 | MapPolylineNode::~MapPolylineNode() |
1469 | { |
1470 | } |
1471 | |
1472 | /*! |
1473 | \internal |
1474 | */ |
1475 | void MapPolylineNode::update(const QColor &fillColor, |
1476 | const QGeoMapItemGeometry *shape) |
1477 | { |
1478 | if (shape->size() == 0) { |
1479 | setSubtreeBlocked(true); |
1480 | return; |
1481 | } else { |
1482 | setSubtreeBlocked(false); |
1483 | } |
1484 | |
1485 | QSGGeometry *fill = QSGGeometryNode::geometry(); |
1486 | shape->allocateAndFill(geom: fill); |
1487 | markDirty(bits: DirtyGeometry); |
1488 | |
1489 | if (fillColor != fill_material_.color()) { |
1490 | fill_material_.setColor(fillColor); |
1491 | setMaterial(&fill_material_); |
1492 | markDirty(bits: DirtyMaterial); |
1493 | } |
1494 | } |
1495 | |
1496 | #if QT_CONFIG(opengl) |
1497 | MapPolylineNodeOpenGLLineStrip::MapPolylineNodeOpenGLLineStrip() |
1498 | : geometry_(QSGGeometry::defaultAttributes_Point2D(), 0) |
1499 | { |
1500 | geometry_.setDrawingMode(QSGGeometry::DrawLineStrip); |
1501 | QSGGeometryNode::setMaterial(&fill_material_); |
1502 | QSGGeometryNode::setGeometry(&geometry_); |
1503 | } |
1504 | |
1505 | MapPolylineNodeOpenGLLineStrip::~MapPolylineNodeOpenGLLineStrip() |
1506 | { |
1507 | |
1508 | } |
1509 | |
1510 | void MapPolylineNodeOpenGLLineStrip::update(const QColor &fillColor, |
1511 | const qreal lineWidth, |
1512 | const QGeoMapPolylineGeometryOpenGL *shape, |
1513 | const QMatrix4x4 &geoProjection, |
1514 | const QDoubleVector3D ¢er, |
1515 | const Qt::PenCapStyle /*capStyle*/) |
1516 | { |
1517 | if (shape->m_screenVertices->size() < 2) { |
1518 | setSubtreeBlocked(true); |
1519 | return; |
1520 | } else { |
1521 | setSubtreeBlocked(false); |
1522 | } |
1523 | |
1524 | QSGGeometry *fill = QSGGeometryNode::geometry(); |
1525 | if (shape->m_dataChanged) { |
1526 | shape->allocateAndFillLineStrip(geom: fill); |
1527 | markDirty(bits: DirtyGeometry); |
1528 | shape->m_dataChanged = false; |
1529 | } |
1530 | fill->setLineWidth(lineWidth); |
1531 | fill_material_.setLineWidth(lineWidth); // to make the material not compare equal if linewidth changes |
1532 | |
1533 | // if (fillColor != fill_material_.color()) |
1534 | { |
1535 | fill_material_.setWrapOffset(shape->m_wrapOffset - 1); |
1536 | fill_material_.setColor(fillColor); |
1537 | fill_material_.setGeoProjection(geoProjection); |
1538 | fill_material_.setCenter(center); |
1539 | setMaterial(&fill_material_); |
1540 | markDirty(bits: DirtyMaterial); |
1541 | } |
1542 | } |
1543 | |
1544 | MapPolylineShaderLineStrip::MapPolylineShaderLineStrip() : QSGMaterialShader(*new QSGMaterialShaderPrivate) |
1545 | { |
1546 | |
1547 | } |
1548 | |
1549 | void MapPolylineShaderLineStrip::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) |
1550 | { |
1551 | Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type()); |
1552 | MapPolylineMaterial *oldMaterial = static_cast<MapPolylineMaterial *>(oldEffect); |
1553 | MapPolylineMaterial *newMaterial = static_cast<MapPolylineMaterial *>(newEffect); |
1554 | |
1555 | const QColor &c = newMaterial->color(); |
1556 | const QMatrix4x4 &geoProjection = newMaterial->geoProjection(); |
1557 | const QDoubleVector3D ¢er = newMaterial->center(); |
1558 | |
1559 | QVector3D vecCenter, vecCenter_lowpart; |
1560 | for (int i = 0; i < 3; i++) |
1561 | QLocationUtils::split_double(input: center.get(i), hipart: &vecCenter[i], lopart: &vecCenter_lowpart[i]); |
1562 | |
1563 | if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) { |
1564 | float opacity = state.opacity() * c.alphaF(); |
1565 | QVector4D v(c.redF() * opacity, |
1566 | c.greenF() * opacity, |
1567 | c.blueF() * opacity, |
1568 | opacity); |
1569 | program()->setUniformValue(location: m_color_id, value: v); |
1570 | } |
1571 | |
1572 | if (state.isMatrixDirty()) |
1573 | { |
1574 | program()->setUniformValue(location: m_matrix_id, value: state.projectionMatrix()); |
1575 | } |
1576 | |
1577 | program()->setUniformValue(location: m_mapProjection_id, value: geoProjection); |
1578 | |
1579 | program()->setUniformValue(location: m_center_id, value: vecCenter); |
1580 | program()->setUniformValue(location: m_center_lowpart_id, value: vecCenter_lowpart); |
1581 | program()->setUniformValue(location: m_wrapOffset_id, value: float(newMaterial->wrapOffset())); |
1582 | } |
1583 | |
1584 | const char * const *MapPolylineShaderLineStrip::attributeNames() const |
1585 | { |
1586 | static char const *const attr[] = { "vertex" , nullptr }; |
1587 | return attr; |
1588 | } |
1589 | |
1590 | QSGMaterialShader *MapPolylineMaterial::createShader() const |
1591 | { |
1592 | return new MapPolylineShaderLineStrip(); |
1593 | } |
1594 | |
1595 | QSGMaterialType *MapPolylineMaterial::type() const |
1596 | { |
1597 | static QSGMaterialType type; |
1598 | return &type; |
1599 | } |
1600 | |
1601 | int MapPolylineMaterial::compare(const QSGMaterial *other) const |
1602 | { |
1603 | const MapPolylineMaterial &o = *static_cast<const MapPolylineMaterial *>(other); |
1604 | if (o.m_center == m_center && o.m_geoProjection == m_geoProjection && o.m_wrapOffset == m_wrapOffset && o.m_lineWidth == m_lineWidth) |
1605 | return QSGFlatColorMaterial::compare(other); |
1606 | return -1; |
1607 | } |
1608 | |
1609 | const QSGGeometry::AttributeSet &MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated() |
1610 | { |
1611 | return MapPolylineEntry::attributes(); |
1612 | } |
1613 | |
1614 | MapPolylineNodeOpenGLExtruded::MapPolylineNodeOpenGLExtruded() |
1615 | : m_geometryTriangulating(MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated(), |
1616 | 0 /* vtx cnt */, 0 /* index cnt */, QSGGeometry::UnsignedIntType /* index type */) |
1617 | { |
1618 | m_geometryTriangulating.setDrawingMode(QSGGeometry::DrawTriangles); |
1619 | QSGGeometryNode::setMaterial(&fill_material_); |
1620 | QSGGeometryNode::setGeometry(&m_geometryTriangulating); |
1621 | } |
1622 | |
1623 | MapPolylineNodeOpenGLExtruded::~MapPolylineNodeOpenGLExtruded() |
1624 | { |
1625 | |
1626 | } |
1627 | |
1628 | bool QGeoMapPolylineGeometryOpenGL::allocateAndFillEntries(QSGGeometry *geom, |
1629 | bool closed, |
1630 | unsigned int zoom) const |
1631 | { |
1632 | // Select LOD. Generate if not present. Assign it to m_screenVertices; |
1633 | if (m_dataChanged) { |
1634 | // it means that the data really changed. |
1635 | // So synchronously produce LOD 1, and enqueue the requested one if != 0 or 1. |
1636 | // Select 0 if 0 is requested, or 1 in all other cases. |
1637 | selectLODOnDataChanged(zoom, leftBound: m_bboxLeftBoundWrapped.x()); |
1638 | } else { |
1639 | // Data has not changed, but active LOD != requested LOD. |
1640 | // So, if there are no active tasks, try to change to the correct one. |
1641 | if (!selectLODOnLODMismatch(zoom, leftBound: m_bboxLeftBoundWrapped.x(), closed)) |
1642 | return false; |
1643 | } |
1644 | |
1645 | const QVector<QDeclarativeGeoMapItemUtils::vec2> &v = *m_screenVertices; |
1646 | if (v.size() < 2) { |
1647 | geom->allocate(vertexCount: 0, indexCount: 0); |
1648 | return true; |
1649 | } |
1650 | const int numSegments = (v.size() - 1); |
1651 | |
1652 | const int numIndices = numSegments * 6; // six vertices per line segment |
1653 | geom->allocate(vertexCount: numIndices); |
1654 | MapPolylineNodeOpenGLExtruded::MapPolylineEntry *vertices = |
1655 | static_cast<MapPolylineNodeOpenGLExtruded::MapPolylineEntry *>(geom->vertexData()); |
1656 | |
1657 | for (int i = 0; i < numSegments; ++i) { |
1658 | MapPolylineNodeOpenGLExtruded::MapPolylineEntry e; |
1659 | const QDeclarativeGeoMapItemUtils::vec2 &cur = v[i]; |
1660 | const QDeclarativeGeoMapItemUtils::vec2 &next = v[i+1]; |
1661 | e.triangletype = 1.0; |
1662 | e.next = next; |
1663 | e.prev = cur; |
1664 | e.pos = cur; |
1665 | e.direction = 1.0; |
1666 | e.vertextype = -1.0; |
1667 | vertices[i*6] = e; |
1668 | e.direction = -1.0; |
1669 | vertices[i*6+1] = e; |
1670 | e.pos = next; |
1671 | e.vertextype = 1.0; |
1672 | vertices[i*6+2] = e; |
1673 | |
1674 | // Second tri |
1675 | e.triangletype = -1.0; |
1676 | e.direction = -1.0; |
1677 | vertices[i*6+3] = e; |
1678 | e.direction = 1.0; |
1679 | vertices[i*6+4] = e; |
1680 | e.pos = cur; |
1681 | e.vertextype = -1.0; |
1682 | vertices[i*6+5] = e; |
1683 | |
1684 | if (i != 0) { |
1685 | vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[i-1]; |
1686 | } else { |
1687 | if (closed) { |
1688 | vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[numSegments - 1]; |
1689 | } else { |
1690 | vertices[i*6].triangletype = vertices[i*6+1].triangletype = vertices[i*6+5].triangletype = 2.0; |
1691 | } |
1692 | } |
1693 | if (i != numSegments - 1) { |
1694 | vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[i+2]; |
1695 | } else { |
1696 | if (closed) { |
1697 | vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[1]; |
1698 | } else { |
1699 | vertices[i*6+2].triangletype = vertices[i*6+3].triangletype = vertices[i*6+4].triangletype = 3.0; |
1700 | } |
1701 | } |
1702 | } |
1703 | return true; |
1704 | } |
1705 | |
1706 | void QGeoMapPolylineGeometryOpenGL::allocateAndFillLineStrip(QSGGeometry *geom, |
1707 | int lod) const |
1708 | { |
1709 | // Select LOD. Generate if not present. Assign it to m_screenVertices; |
1710 | Q_UNUSED(lod) |
1711 | |
1712 | const QVector<QDeclarativeGeoMapItemUtils::vec2> &vx = *m_screenVertices; |
1713 | geom->allocate(vertexCount: vx.size()); |
1714 | |
1715 | QSGGeometry::Point2D *pts = geom->vertexDataAsPoint2D(); |
1716 | for (int i = 0; i < vx.size(); ++i) |
1717 | pts[i].set(nx: vx[i].x, ny: vx[i].y); |
1718 | } |
1719 | |
1720 | void MapPolylineNodeOpenGLExtruded::update(const QColor &fillColor, |
1721 | const float lineWidth, |
1722 | const QGeoMapPolylineGeometryOpenGL *shape, |
1723 | const QMatrix4x4 geoProjection, |
1724 | const QDoubleVector3D center, |
1725 | const Qt::PenCapStyle capStyle, |
1726 | bool closed, |
1727 | unsigned int zoom) |
1728 | { |
1729 | // shape->size() == number of triangles |
1730 | if (shape->m_screenVertices->size() < 2 |
1731 | || lineWidth < 0.5 || fillColor.alpha() == 0) { // number of points |
1732 | setSubtreeBlocked(true); |
1733 | return; |
1734 | } else { |
1735 | setSubtreeBlocked(false); |
1736 | } |
1737 | |
1738 | QSGGeometry *fill = QSGGeometryNode::geometry(); |
1739 | if (shape->m_dataChanged || !shape->isLODActive(lod: zoom) || !fill->vertexCount()) { // fill->vertexCount for when node gets destroyed by MapItemBase bcoz of opacity, then recreated. |
1740 | if (shape->allocateAndFillEntries(geom: fill, closed, zoom)) { |
1741 | markDirty(bits: DirtyGeometry); |
1742 | shape->m_dataChanged = false; |
1743 | } |
1744 | } |
1745 | |
1746 | // Update this |
1747 | // if (fillColor != fill_material_.color()) |
1748 | { |
1749 | fill_material_.setWrapOffset(shape->m_wrapOffset - 1); |
1750 | fill_material_.setColor(fillColor); |
1751 | fill_material_.setGeoProjection(geoProjection); |
1752 | fill_material_.setCenter(center); |
1753 | fill_material_.setLineWidth(lineWidth); |
1754 | fill_material_.setMiter(capStyle != Qt::FlatCap); |
1755 | setMaterial(&fill_material_); |
1756 | markDirty(bits: DirtyMaterial); |
1757 | } |
1758 | } |
1759 | |
1760 | MapPolylineShaderExtruded::MapPolylineShaderExtruded() : QSGMaterialShader(*new QSGMaterialShaderPrivate) |
1761 | { |
1762 | |
1763 | } |
1764 | |
1765 | void MapPolylineShaderExtruded::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) |
1766 | { |
1767 | Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type()); |
1768 | MapPolylineMaterialExtruded *oldMaterial = static_cast<MapPolylineMaterialExtruded *>(oldEffect); |
1769 | MapPolylineMaterialExtruded *newMaterial = static_cast<MapPolylineMaterialExtruded *>(newEffect); |
1770 | |
1771 | const QColor &c = newMaterial->color(); |
1772 | const QMatrix4x4 &geoProjection = newMaterial->geoProjection(); |
1773 | const QDoubleVector3D ¢er = newMaterial->center(); |
1774 | |
1775 | QVector3D vecCenter, vecCenter_lowpart; |
1776 | for (int i = 0; i < 3; i++) |
1777 | QLocationUtils::split_double(input: center.get(i), hipart: &vecCenter[i], lopart: &vecCenter_lowpart[i]); |
1778 | |
1779 | if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) { |
1780 | float opacity = state.opacity() * c.alphaF(); |
1781 | QVector4D v(c.redF() * opacity, |
1782 | c.greenF() * opacity, |
1783 | c.blueF() * opacity, |
1784 | opacity); |
1785 | program()->setUniformValue(location: m_color_id, value: v); |
1786 | } |
1787 | |
1788 | if (state.isMatrixDirty()) |
1789 | { |
1790 | program()->setUniformValue(location: m_matrix_id, value: state.projectionMatrix()); |
1791 | } |
1792 | |
1793 | // ToDo: dirty-flag all this |
1794 | program()->setUniformValue(location: m_mapProjection_id, value: geoProjection); |
1795 | |
1796 | program()->setUniformValue(location: m_center_id, value: vecCenter); |
1797 | program()->setUniformValue(location: m_center_lowpart_id, value: vecCenter_lowpart); |
1798 | program()->setUniformValue(location: m_miter_id, value: newMaterial->miter()); |
1799 | program()->setUniformValue(location: m_lineWidth_id, value: newMaterial->lineWidth()); |
1800 | program()->setUniformValue(location: m_wrapOffset_id, value: float(newMaterial->wrapOffset())); |
1801 | |
1802 | const QRectF viewportRect = state.viewportRect(); |
1803 | const float aspect = float(viewportRect.width() / viewportRect.height()); |
1804 | program()->setUniformValue(location: m_aspect_id, value: aspect); |
1805 | } |
1806 | |
1807 | const char * const *MapPolylineShaderExtruded::attributeNames() const |
1808 | { |
1809 | return MapPolylineNodeOpenGLExtruded::MapPolylineEntry::attributeNames(); |
1810 | } |
1811 | |
1812 | QSGMaterialShader *MapPolylineMaterialExtruded::createShader() const |
1813 | { |
1814 | return new MapPolylineShaderExtruded(); |
1815 | } |
1816 | |
1817 | QSGMaterialType *MapPolylineMaterialExtruded::type() const |
1818 | { |
1819 | static QSGMaterialType type; |
1820 | return &type; |
1821 | } |
1822 | |
1823 | int MapPolylineMaterialExtruded::compare(const QSGMaterial *other) const |
1824 | { |
1825 | const MapPolylineMaterialExtruded &o = *static_cast<const MapPolylineMaterialExtruded *>(other); |
1826 | if (o.m_miter == m_miter) |
1827 | return MapPolylineMaterial::compare(other); |
1828 | return -1; |
1829 | } |
1830 | |
1831 | const char *MapPolylineShaderExtruded::vertexShaderMiteredSegments() const |
1832 | { |
1833 | return |
1834 | "attribute highp vec4 vertex;\n" |
1835 | "attribute highp vec4 previous;\n" |
1836 | "attribute highp vec4 next;\n" |
1837 | "attribute lowp float direction;\n" |
1838 | "attribute lowp float triangletype;\n" |
1839 | "attribute lowp float vertextype;\n" // -1.0 if it is the "left" end of the segment, 1.0 if it is the "right" end. |
1840 | "\n" |
1841 | "uniform highp mat4 qt_Matrix;\n" |
1842 | "uniform highp mat4 mapProjection;\n" |
1843 | "uniform highp vec3 center;\n" |
1844 | "uniform highp vec3 center_lowpart;\n" |
1845 | "uniform lowp float lineWidth;\n" |
1846 | "uniform lowp float aspect;\n" |
1847 | "uniform lowp int miter;\n" // currently unused |
1848 | "uniform lowp vec4 color;\n" |
1849 | "uniform lowp float wrapOffset;\n" |
1850 | "\n" |
1851 | "varying vec4 primitivecolor;\n" |
1852 | "\n" |
1853 | " \n" |
1854 | "vec4 wrapped(in vec4 v) { return vec4(v.x + wrapOffset, v.y, 0.0, 1.0); }\n" |
1855 | "void main() {\n" // ln 22 |
1856 | " primitivecolor = color;\n" |
1857 | " vec2 aspectVec = vec2(aspect, 1.0);\n" |
1858 | " mat4 projViewModel = qt_Matrix * mapProjection;\n" |
1859 | " vec4 cur = wrapped(vertex) - vec4(center, 0.0);\n" |
1860 | " cur = cur - vec4(center_lowpart, 0.0);\n" |
1861 | " vec4 prev = wrapped(previous) - vec4(center, 0.0);\n" |
1862 | " prev = prev - vec4(center_lowpart, 0.0);\n" |
1863 | " vec4 nex = wrapped(next) - vec4(center, 0.0);\n" |
1864 | " nex = nex - vec4(center_lowpart, 0.0);\n" |
1865 | "\n" |
1866 | " vec4 centerProjected = projViewModel * vec4(center, 1.0);\n" |
1867 | " vec4 previousProjected = projViewModel * prev;\n" |
1868 | " vec4 currentProjected = projViewModel * cur;\n" |
1869 | " vec4 nextProjected = projViewModel * nex;\n" |
1870 | "\n" |
1871 | " //get 2D screen space with W divide and aspect correction\n" |
1872 | " vec2 currentScreen = (currentProjected.xy / currentProjected.w) * aspectVec;\n" |
1873 | " vec2 previousScreen = (previousProjected.xy / previousProjected.w) * aspectVec;\n" |
1874 | " vec2 nextScreen = (nextProjected.xy / nextProjected.w) * aspectVec;\n" |
1875 | " float len = (lineWidth);\n" |
1876 | " float orientation = direction;\n" |
1877 | " bool clipped = false;\n" |
1878 | " bool otherEndBelowFrustum = false;\n" |
1879 | " //starting point uses (next - current)\n" |
1880 | " vec2 dir = vec2(0.0);\n" |
1881 | " if (vertextype < 0.0) {\n" |
1882 | " dir = normalize(nextScreen - currentScreen);\n" |
1883 | " if (nextProjected.z < 0.0) dir = -dir;\n" |
1884 | " } else { \n" |
1885 | " dir = normalize(currentScreen - previousScreen);\n" |
1886 | " if (previousProjected.z < 0.0) dir = -dir;\n" |
1887 | " }\n" |
1888 | // first, clip current, and make sure currentProjected.z is > 0 |
1889 | " if (currentProjected.z < 0.0) {\n" |
1890 | " if ((nextProjected.z > 0.0 && vertextype < 0.0) || (vertextype > 0.0 && previousProjected.z > 0.0)) {\n" |
1891 | " dir = -dir;\n" |
1892 | " clipped = true;\n" |
1893 | " if (vertextype < 0.0 && nextProjected.y / nextProjected.w < -1.0) otherEndBelowFrustum = true;\n" |
1894 | " else if (vertextype > 0.0 && previousProjected.y / previousProjected.w < -1.0) otherEndBelowFrustum = true;\n" |
1895 | " } else {\n" |
1896 | " primitivecolor = vec4(0.0,0.0,0.0,0.0);\n" |
1897 | " gl_Position = vec4(-10000000.0, -1000000000.0, -1000000000.0, 1);\n" // get the vertex out of the way if the segment is fully invisible |
1898 | " return;\n" |
1899 | " }\n" |
1900 | " } else if (triangletype < 2.0) {\n" // vertex in the view, try to miter |
1901 | " //get directions from (C - B) and (B - A)\n" |
1902 | " vec2 dirA = normalize((currentScreen - previousScreen));\n" |
1903 | " if (previousProjected.z < 0.0) dirA = -dirA;\n" |
1904 | " vec2 dirB = normalize((nextScreen - currentScreen));\n" |
1905 | " //now compute the miter join normal and length\n" |
1906 | " if (nextProjected.z < 0.0) dirB = -dirB;\n" |
1907 | " vec2 tangent = normalize(dirA + dirB);\n" |
1908 | " vec2 perp = vec2(-dirA.y, dirA.x);\n" |
1909 | " vec2 vmiter = vec2(-tangent.y, tangent.x);\n" |
1910 | " len = lineWidth / dot(vmiter, perp);\n" |
1911 | // The following is an attempt to have a segment-length based miter threshold. |
1912 | // A mediocre workaround until better mitering will be added. |
1913 | " float lenTreshold = clamp( min(length((currentProjected.xy - previousProjected.xy) / aspectVec)," |
1914 | " length((nextProjected.xy - currentProjected.xy) / aspectVec)), 3.0, 6.0 ) * 0.5;\n" |
1915 | " if (len < lineWidth * lenTreshold && len > -lineWidth * lenTreshold \n" |
1916 | " ) {\n" |
1917 | " dir = tangent;\n" |
1918 | " } else {\n" |
1919 | " len = lineWidth;\n" |
1920 | " }\n" |
1921 | " }\n" |
1922 | " vec4 offset;\n" |
1923 | " if (!clipped) {\n" |
1924 | " vec2 normal = normalize(vec2(-dir.y, dir.x));\n" |
1925 | " normal *= len;\n" // fracZL apparently was needed before the (-2.0 / qt_Matrix[1][1]) factor was introduced |
1926 | " normal /= aspectVec;\n" // straighten the normal up again |
1927 | " float scaleFactor = currentProjected.w / centerProjected.w;\n" |
1928 | " offset = vec4(normal * orientation * scaleFactor * (centerProjected.w / (-2.0 / qt_Matrix[1][1])), 0.0, 0.0);\n" // ToDo: figure out why (-2.0 / qt_Matrix[1][1]), that is empirically what works |
1929 | " gl_Position = currentProjected + offset;\n" |
1930 | " } else {\n" |
1931 | " if (otherEndBelowFrustum) offset = vec4((dir * 1.0) / aspectVec, 0.0, 0.0);\n" // the if is necessary otherwise it seems the direction vector still flips in some obscure cases. |
1932 | " else offset = vec4((dir * 500000000000.0) / aspectVec, 0.0, 0.0);\n" // Hack alert: just 1 triangle, long enough to look like a rectangle. |
1933 | " if (vertextype < 0.0) gl_Position = nextProjected - offset; else gl_Position = previousProjected + offset;\n" |
1934 | " }\n" |
1935 | "}\n" ; |
1936 | } |
1937 | |
1938 | QVector<QDeclarativeGeoMapItemUtils::vec2> QGeoMapItemLODGeometry::getSimplified( |
1939 | QVector<QDeclarativeGeoMapItemUtils::vec2> &wrappedPath, // reference as it gets copied in the nested call |
1940 | double leftBoundWrapped, |
1941 | unsigned int zoom) |
1942 | { |
1943 | // Try a simplify step |
1944 | QList<QDoubleVector2D> data; |
1945 | for (auto e: wrappedPath) |
1946 | data << e.toDoubleVector2D(); |
1947 | const QList<QDoubleVector2D> simplified = QGeoSimplify::geoSimplifyZL(points: data, |
1948 | leftBound: leftBoundWrapped, |
1949 | zoomLevel: zoom); |
1950 | |
1951 | data.clear(); |
1952 | QVector<QDeclarativeGeoMapItemUtils::vec2> simple; |
1953 | for (auto e: simplified) |
1954 | simple << e; |
1955 | return simple; |
1956 | } |
1957 | |
1958 | |
1959 | bool QGeoMapItemLODGeometry::isLODActive(unsigned int lod) const |
1960 | { |
1961 | return m_screenVertices == m_verticesLOD[zoomToLOD(zoom: lod)].data(); |
1962 | } |
1963 | |
1964 | class PolylineSimplifyTask : public QRunnable |
1965 | { |
1966 | public: |
1967 | PolylineSimplifyTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, // reference as it gets copied in the nested call |
1968 | const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output, |
1969 | double leftBound, |
1970 | unsigned int zoom, |
1971 | QSharedPointer<unsigned int> &working) |
1972 | : m_zoom(zoom) |
1973 | , m_leftBound(leftBound) |
1974 | , m_input(input) |
1975 | , m_output(output) |
1976 | , m_working(working) |
1977 | { |
1978 | Q_ASSERT(!input.isNull()); |
1979 | Q_ASSERT(!output.isNull()); |
1980 | } |
1981 | |
1982 | ~PolylineSimplifyTask() override; |
1983 | |
1984 | void run() override |
1985 | { |
1986 | // Skip sending notifications for now. Updated data will be picked up eventually. |
1987 | // ToDo: figure out how to connect a signal from here to a slot in the item. |
1988 | *m_working = QGeoMapPolylineGeometryOpenGL::zoomToLOD(zoom: m_zoom); |
1989 | const QVector<QDeclarativeGeoMapItemUtils::vec2> res = |
1990 | QGeoMapPolylineGeometryOpenGL::getSimplified( wrappedPath&: *m_input, |
1991 | leftBoundWrapped: m_leftBound, |
1992 | zoom: QGeoMapPolylineGeometryOpenGL::zoomForLOD(zoom: m_zoom)); |
1993 | *m_output = res; |
1994 | *m_working = 0; |
1995 | } |
1996 | |
1997 | unsigned int m_zoom; |
1998 | double m_leftBound; |
1999 | QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > m_input, m_output; |
2000 | QSharedPointer<unsigned int> m_working; |
2001 | }; |
2002 | |
2003 | void QGeoMapItemLODGeometry::enqueueSimplificationTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, |
2004 | const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output, |
2005 | double leftBound, |
2006 | unsigned int zoom, |
2007 | QSharedPointer<unsigned int> &working) |
2008 | { |
2009 | Q_ASSERT(!input.isNull()); |
2010 | Q_ASSERT(!output.isNull()); |
2011 | PolylineSimplifyTask *task = new PolylineSimplifyTask(input, |
2012 | output, |
2013 | leftBound, |
2014 | zoom, |
2015 | working); |
2016 | threadPool->start(runnable: task); |
2017 | } |
2018 | |
2019 | PolylineSimplifyTask::~PolylineSimplifyTask() {} |
2020 | |
2021 | void QGeoMapItemLODGeometry::selectLOD(unsigned int zoom, double leftBound, bool /* closed */) // closed to tell if this is a polygon or a polyline. |
2022 | { |
2023 | unsigned int requestedLod = zoomToLOD(zoom); |
2024 | if (!m_verticesLOD[requestedLod].isNull()) { |
2025 | m_screenVertices = m_verticesLOD[requestedLod].data(); |
2026 | } else if (!m_verticesLOD.at(n: 0)->isEmpty()) { |
2027 | // if here, zoomToLOD != 0 and no current working task. |
2028 | // So select the last filled LOD != m_working (lower-bounded by 1, |
2029 | // guaranteed to exist), and enqueue the right one |
2030 | m_verticesLOD[requestedLod] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( |
2031 | new QVector<QDeclarativeGeoMapItemUtils::vec2>); |
2032 | |
2033 | for (unsigned int i = requestedLod - 1; i >= 1; i--) { |
2034 | if (*m_working != i && !m_verticesLOD[i].isNull()) { |
2035 | m_screenVertices = m_verticesLOD[i].data(); |
2036 | break; |
2037 | } else if (i == 1) { |
2038 | // get 1 synchronously if not computed already |
2039 | m_verticesLOD[1] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( |
2040 | new QVector<QDeclarativeGeoMapItemUtils::vec2>); |
2041 | *m_verticesLOD[1] = getSimplified( wrappedPath&: *m_verticesLOD[0], |
2042 | leftBoundWrapped: leftBound, |
2043 | zoom: zoomForLOD(zoom: 0)); |
2044 | if (requestedLod == 1) |
2045 | return; |
2046 | } |
2047 | } |
2048 | |
2049 | enqueueSimplificationTask( input: m_verticesLOD.at(n: 0), |
2050 | output: m_verticesLOD[requestedLod], |
2051 | leftBound, |
2052 | zoom, |
2053 | working&: m_working); |
2054 | |
2055 | } |
2056 | } |
2057 | |
2058 | void QGeoMapItemLODGeometry::selectLODOnDataChanged(unsigned int zoom, double leftBound) const |
2059 | { |
2060 | unsigned int lod = zoomToLOD(zoom); |
2061 | if (lod > 0) { |
2062 | // Generate ZL 1 as fallback for all cases != 0. Do not do if 0 is requested |
2063 | // (= old behavior, LOD disabled) |
2064 | m_verticesLOD[1] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( |
2065 | new QVector<QDeclarativeGeoMapItemUtils::vec2>); |
2066 | *m_verticesLOD[1] = getSimplified( wrappedPath&: *m_verticesLOD[0], |
2067 | leftBoundWrapped: leftBound, |
2068 | zoom: zoomForLOD(zoom: 0)); |
2069 | } |
2070 | if (lod > 1) { |
2071 | if (!m_verticesLOD[lod]) |
2072 | m_verticesLOD[lod] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( |
2073 | new QVector<QDeclarativeGeoMapItemUtils::vec2>); |
2074 | enqueueSimplificationTask( input: m_verticesLOD.at(n: 0), |
2075 | output: m_verticesLOD[lod], |
2076 | leftBound, |
2077 | zoom, |
2078 | working&: m_working); |
2079 | } |
2080 | m_screenVertices = m_verticesLOD[qMin<unsigned int>(a: lod, b: 1)].data(); // return only 0,1 synchronously |
2081 | } |
2082 | |
2083 | unsigned int QGeoMapItemLODGeometry::zoomToLOD(unsigned int zoom) |
2084 | { |
2085 | unsigned int res; |
2086 | if (zoom > 20) |
2087 | res = 0; |
2088 | else |
2089 | res = qBound<unsigned int>(min: 3, val: zoom, max: 20) / 3; // bound LOD'ing between ZL 3 and 20. Every 3 ZoomLevels |
2090 | return res; |
2091 | } |
2092 | |
2093 | unsigned int QGeoMapItemLODGeometry::zoomForLOD(unsigned int zoom) |
2094 | { |
2095 | unsigned int res = (qBound<unsigned int>(min: 3, val: zoom, max: 20) / 3) * 3; |
2096 | if (zoom < 6) |
2097 | return res; |
2098 | return res + 1; // give more resolution when closing in |
2099 | } |
2100 | #endif // QT_CONFIG(opengl) |
2101 | |
2102 | QT_END_NAMESPACE |
2103 | |