| 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 |  |