| 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 "qdeclarativecirclemapitem_p.h" | 
| 38 | #include "qdeclarativepolygonmapitem_p.h" | 
| 39 |  | 
| 40 | #include "qwebmercator_p.h" | 
| 41 | #include <QtLocation/private/qgeomap_p.h> | 
| 42 |  | 
| 43 | #include <qmath.h> | 
| 44 | #include <algorithm> | 
| 45 |  | 
| 46 | #include <QtCore/QScopedValueRollback> | 
| 47 | #include <QPen> | 
| 48 | #include <QPainter> | 
| 49 | #include <QtGui/private/qtriangulator_p.h> | 
| 50 |  | 
| 51 | #include "qdoublevector2d_p.h" | 
| 52 | #include "qlocationutils_p.h" | 
| 53 | #include "qgeocircle.h" | 
| 54 |  | 
| 55 | /* poly2tri triangulator includes */ | 
| 56 | #include <common/shapes.h> | 
| 57 | #include <sweep/cdt.h> | 
| 58 |  | 
| 59 | #include <QtPositioning/private/qclipperutils_p.h> | 
| 60 | #include "qdeclarativecirclemapitem_p_p.h" | 
| 61 |  | 
| 62 | QT_BEGIN_NAMESPACE | 
| 63 |  | 
| 64 | /*! | 
| 65 |     \qmltype MapCircle | 
| 66 |     \instantiates QDeclarativeCircleMapItem | 
| 67 |     \inqmlmodule QtLocation | 
| 68 |     \ingroup qml-QtLocation5-maps | 
| 69 |     \since QtLocation 5.5 | 
| 70 |  | 
| 71 |     \brief The MapCircle type displays a geographic circle on a Map. | 
| 72 |  | 
| 73 |     The MapCircle type displays a geographic circle on a Map, which | 
| 74 |     consists of all points that are within a set distance from one | 
| 75 |     central point. Depending on map projection, a geographic circle | 
| 76 |     may not always be a perfect circle on the screen: for instance, in | 
| 77 |     the Mercator projection, circles become ovoid in shape as they near | 
| 78 |     the poles. To display a perfect screen circle around a point, use a | 
| 79 |     MapQuickItem containing a relevant Qt Quick type instead. | 
| 80 |  | 
| 81 |     By default, the circle is displayed as a 1 pixel black border with | 
| 82 |     no fill. To change its appearance, use the color, border.color | 
| 83 |     and border.width properties. | 
| 84 |  | 
| 85 |     Internally, a MapCircle is implemented as a many-sided polygon. To | 
| 86 |     calculate the radius points it uses a spherical model of the Earth, | 
| 87 |     similar to the atDistanceAndAzimuth method of the \l {coordinate} | 
| 88 |     type. These two things can occasionally have implications for the | 
| 89 |     accuracy of the circle's shape, depending on position and map | 
| 90 |     projection. | 
| 91 |  | 
| 92 |     \note Dragging a MapCircle (through the use of \l MouseArea) | 
| 93 |     causes new points to be generated at the same distance (in meters) | 
| 94 |     from the center. This is in contrast to other map items which store | 
| 95 |     their dimensions in terms of latitude and longitude differences between | 
| 96 |     vertices. | 
| 97 |  | 
| 98 |     \section2 Performance | 
| 99 |  | 
| 100 |     MapCircle performance is almost equivalent to that of a MapPolygon with | 
| 101 |     the same number of vertices. There is a small amount of additional | 
| 102 |     overhead with respect to calculating the vertices first. | 
| 103 |  | 
| 104 |     Like the other map objects, MapCircle is normally drawn without a smooth | 
| 105 |     appearance. Setting the opacity property will force the object to be | 
| 106 |     blended, which decreases performance considerably depending on the graphics | 
| 107 |     hardware in use. | 
| 108 |  | 
| 109 |     \section2 Example Usage | 
| 110 |  | 
| 111 |     The following snippet shows a map containing a MapCircle, centered at | 
| 112 |     the coordinate (-27, 153) with a radius of 5km. The circle is | 
| 113 |     filled in green, with a 3 pixel black border. | 
| 114 |  | 
| 115 |     \code | 
| 116 |     Map { | 
| 117 |         MapCircle { | 
| 118 |             center { | 
| 119 |                 latitude: -27.5 | 
| 120 |                 longitude: 153.0 | 
| 121 |             } | 
| 122 |             radius: 5000.0 | 
| 123 |             color: 'green' | 
| 124 |             border.width: 3 | 
| 125 |         } | 
| 126 |     } | 
| 127 |     \endcode | 
| 128 |  | 
| 129 |     \image api-mapcircle.png | 
| 130 | */ | 
| 131 |  | 
| 132 | /*! | 
| 133 |     \qmlproperty bool QtLocation::MapCircle::autoFadeIn | 
| 134 |  | 
| 135 |     This property holds whether the item automatically fades in when zooming into the map | 
| 136 |     starting from very low zoom levels. By default this is \c true. | 
| 137 |     Setting this property to \c false causes the map item to always have the opacity specified | 
| 138 |     with the \l QtQuick::Item::opacity property, which is 1.0 by default. | 
| 139 |  | 
| 140 |     \since 5.14 | 
| 141 | */ | 
| 142 |  | 
| 143 | struct Vertex | 
| 144 | { | 
| 145 |     QVector2D position; | 
| 146 | }; | 
| 147 |  | 
| 148 | QGeoMapCircleGeometry::QGeoMapCircleGeometry() | 
| 149 | { | 
| 150 | } | 
| 151 |  | 
| 152 | /*! | 
| 153 |     \internal | 
| 154 | */ | 
| 155 | void QGeoMapCircleGeometry::updateScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map) | 
| 156 | { | 
| 157 |     const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); | 
| 158 |     // Not checking for !screenDirty anymore, as everything is now recalculated. | 
| 159 |     clear(); | 
| 160 |     if (map.viewportWidth() == 0 || map.viewportHeight() == 0 || circlePath.size() < 3) // a circle requires at least 3 points; | 
| 161 |         return; | 
| 162 |  | 
| 163 |     /* | 
| 164 |      * No special case for no tilting as these items are very rare, and usually at most one per map. | 
| 165 |      * | 
| 166 |      * Approach: | 
| 167 |      * 1) subtract the circle from a rectangle filling the whole map, *in wrapped mercator space* | 
| 168 |      * 2) clip the resulting geometries against the visible region, *in wrapped mercator space* | 
| 169 |      * 3) create a QPainterPath with each of the resulting polygons projected to screen | 
| 170 |      * 4) use qTriangulate() to triangulate the painter path | 
| 171 |      */ | 
| 172 |  | 
| 173 |     // 1) | 
| 174 |     const double topLati = QLocationUtils::mercatorMaxLatitude(); | 
| 175 |     const double bottomLati = -(QLocationUtils::mercatorMaxLatitude()); | 
| 176 |     const double leftLongi = QLocationUtils::mapLeftLongitude(centerLongitude: map.cameraData().center().longitude()); | 
| 177 |     const double rightLongi = QLocationUtils::mapRightLongitude(centerLongitude: map.cameraData().center().longitude()); | 
| 178 |  | 
| 179 |     srcOrigin_ = QGeoCoordinate(topLati,leftLongi); | 
| 180 |     const QDoubleVector2D tl = p.geoToWrappedMapProjection(coordinate: QGeoCoordinate(topLati,leftLongi)); | 
| 181 |     const QDoubleVector2D tr = p.geoToWrappedMapProjection(coordinate: QGeoCoordinate(topLati,rightLongi)); | 
| 182 |     const QDoubleVector2D br = p.geoToWrappedMapProjection(coordinate: QGeoCoordinate(bottomLati,rightLongi)); | 
| 183 |     const QDoubleVector2D bl = p.geoToWrappedMapProjection(coordinate: QGeoCoordinate(bottomLati,leftLongi)); | 
| 184 |  | 
| 185 |     QList<QDoubleVector2D> fill; | 
| 186 |     fill << tl << tr << br << bl; | 
| 187 |  | 
| 188 |     QList<QDoubleVector2D> hole; | 
| 189 |     for (const QDoubleVector2D &c: circlePath) | 
| 190 |         hole << p.wrapMapProjection(projection: c); | 
| 191 |  | 
| 192 |     c2t::clip2tri clipper; | 
| 193 |     clipper.addSubjectPath(path: QClipperUtils::qListToPath(list: fill), closed: true); | 
| 194 |     clipper.addClipPolygon(path: QClipperUtils::qListToPath(list: hole)); | 
| 195 |     Paths difference = clipper.execute(op: c2t::clip2tri::Difference, subjFillType: QtClipperLib::pftEvenOdd, clipFillType: QtClipperLib::pftEvenOdd); | 
| 196 |  | 
| 197 |     // 2) | 
| 198 |     QDoubleVector2D lb = p.geoToWrappedMapProjection(coordinate: srcOrigin_); | 
| 199 |     QList<QList<QDoubleVector2D> > clippedPaths; | 
| 200 |     const QList<QDoubleVector2D> &visibleRegion = p.visibleGeometry(); | 
| 201 |     if (visibleRegion.size()) { | 
| 202 |         clipper.clearClipper(); | 
| 203 |         for (const Path &p: difference) | 
| 204 |             clipper.addSubjectPath(path: p, closed: true); | 
| 205 |         clipper.addClipPolygon(path: QClipperUtils::qListToPath(list: visibleRegion)); | 
| 206 |         Paths res = clipper.execute(op: c2t::clip2tri::Intersection, subjFillType: QtClipperLib::pftEvenOdd, clipFillType: QtClipperLib::pftEvenOdd); | 
| 207 |         clippedPaths = QClipperUtils::pathsToQList(paths: res); | 
| 208 |  | 
| 209 |         // 2.1) update srcOrigin_ with the point with minimum X/Y | 
| 210 |         lb = QDoubleVector2D(qInf(), qInf()); | 
| 211 |         for (const QList<QDoubleVector2D> &path: clippedPaths) { | 
| 212 |             for (const QDoubleVector2D &p: path) { | 
| 213 |                 if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) { | 
| 214 |                     lb = p; | 
| 215 |                 } | 
| 216 |             } | 
| 217 |         } | 
| 218 |         if (qIsInf(d: lb.x())) | 
| 219 |             return; | 
| 220 |  | 
| 221 |         // Prevent the conversion to and from clipper from introducing negative offsets which | 
| 222 |         // in turn will make the geometry wrap around. | 
| 223 |         lb.setX(qMax(a: tl.x(), b: lb.x())); | 
| 224 |         srcOrigin_ = p.mapProjectionToGeo(projection: p.unwrapMapProjection(wrappedProjection: lb)); | 
| 225 |     } else { | 
| 226 |         clippedPaths = QClipperUtils::pathsToQList(paths: difference); | 
| 227 |     } | 
| 228 |  | 
| 229 |     //3) | 
| 230 |     QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(wrappedProjection: lb); | 
| 231 |  | 
| 232 |     QPainterPath ppi; | 
| 233 |     for (const QList<QDoubleVector2D> &path: clippedPaths) { | 
| 234 |         QDoubleVector2D lastAddedPoint; | 
| 235 |         for (int i = 0; i < path.size(); ++i) { | 
| 236 |             QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(wrappedProjection: path.at(i)); | 
| 237 |             //point = point - origin; // Do this using ppi.translate() | 
| 238 |  | 
| 239 |             if (i == 0) { | 
| 240 |                 ppi.moveTo(p: point.toPointF()); | 
| 241 |                 lastAddedPoint = point; | 
| 242 |             } else { | 
| 243 |                 if ((point - lastAddedPoint).manhattanLength() > 3 || | 
| 244 |                         i == path.size() - 1) { | 
| 245 |                     ppi.lineTo(p: point.toPointF()); | 
| 246 |                     lastAddedPoint = point; | 
| 247 |                 } | 
| 248 |             } | 
| 249 |         } | 
| 250 |         ppi.closeSubpath(); | 
| 251 |     } | 
| 252 |     ppi.translate(offset: -1 * origin.toPointF()); | 
| 253 |  | 
| 254 |     QTriangleSet ts = qTriangulate(path: ppi); | 
| 255 |     qreal *vx = ts.vertices.data(); | 
| 256 |  | 
| 257 |     screenIndices_.reserve(asize: ts.indices.size()); | 
| 258 |     screenVertices_.reserve(asize: ts.vertices.size()); | 
| 259 |  | 
| 260 |     if (ts.indices.type() == QVertexIndexVector::UnsignedInt) { | 
| 261 |         const quint32 *ix = reinterpret_cast<const quint32 *>(ts.indices.data()); | 
| 262 |         for (int i = 0; i < (ts.indices.size()/3*3); ++i) | 
| 263 |             screenIndices_ << ix[i]; | 
| 264 |     } else { | 
| 265 |         const quint16 *ix = reinterpret_cast<const quint16 *>(ts.indices.data()); | 
| 266 |         for (int i = 0; i < (ts.indices.size()/3*3); ++i) | 
| 267 |             screenIndices_ << ix[i]; | 
| 268 |     } | 
| 269 |     for (int i = 0; i < (ts.vertices.size()/2*2); i += 2) | 
| 270 |         screenVertices_ << QPointF(vx[i], vx[i + 1]); | 
| 271 |  | 
| 272 |     screenBounds_ = ppi.boundingRect(); | 
| 273 |     sourceBounds_ = screenBounds_; | 
| 274 | } | 
| 275 |  | 
| 276 | struct CircleBackendSelector | 
| 277 | { | 
| 278 |     CircleBackendSelector() | 
| 279 |     { | 
| 280 |         backend = (qgetenv(varName: "QTLOCATION_OPENGL_ITEMS" ).toInt()) ? QDeclarativeCircleMapItem::OpenGL : QDeclarativeCircleMapItem::Software; | 
| 281 |     } | 
| 282 |     QDeclarativeCircleMapItem::Backend backend = QDeclarativeCircleMapItem::Software; | 
| 283 | }; | 
| 284 |  | 
| 285 | Q_GLOBAL_STATIC(CircleBackendSelector, mapCircleBackendSelector) | 
| 286 |  | 
| 287 | QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent) | 
| 288 | :   QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true), | 
| 289 |     m_updatingGeometry(false) | 
| 290 |   , m_d(new QDeclarativeCircleMapItemPrivateCPU(*this)) | 
| 291 | { | 
| 292 |     // ToDo: handle envvar, and switch implementation. | 
| 293 |     m_itemType = QGeoMap::MapCircle; | 
| 294 |     setFlag(flag: ItemHasContents, enabled: true); | 
| 295 |     QObject::connect(sender: &m_border, SIGNAL(colorChanged(QColor)), | 
| 296 |                      receiver: this, SLOT(onLinePropertiesChanged())); | 
| 297 |     QObject::connect(sender: &m_border, SIGNAL(widthChanged(qreal)), | 
| 298 |                      receiver: this, SLOT(onLinePropertiesChanged())); | 
| 299 |  | 
| 300 |     // assume that circles are not self-intersecting | 
| 301 |     // to speed up processing | 
| 302 |     // FIXME: unfortunately they self-intersect at the poles due to current drawing method | 
| 303 |     // so the line is commented out until fixed | 
| 304 |     //geometry_.setAssumeSimple(true); | 
| 305 |     setBackend(mapCircleBackendSelector->backend); | 
| 306 | } | 
| 307 |  | 
| 308 | QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem() | 
| 309 | { | 
| 310 | } | 
| 311 |  | 
| 312 | /*! | 
| 313 |     \qmlpropertygroup Location::MapCircle::border | 
| 314 |     \qmlproperty int MapCircle::border.width | 
| 315 |     \qmlproperty color MapCircle::border.color | 
| 316 |  | 
| 317 |     This property is part of the border group property. | 
| 318 |     The border property holds the width and color used to draw the border of the circle. | 
| 319 |     The width is in pixels and is independent of the zoom level of the map. | 
| 320 |  | 
| 321 |     The default values correspond to a black border with a width of 1 pixel. | 
| 322 |     For no line, use a width of 0 or a transparent color. | 
| 323 | */ | 
| 324 | QDeclarativeMapLineProperties *QDeclarativeCircleMapItem::border() | 
| 325 | { | 
| 326 |     return &m_border; | 
| 327 | } | 
| 328 |  | 
| 329 | void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate() | 
| 330 | { | 
| 331 |     m_d->markSourceDirtyAndUpdate(); | 
| 332 | } | 
| 333 |  | 
| 334 | void QDeclarativeCircleMapItem::onLinePropertiesChanged() | 
| 335 | { | 
| 336 |     m_d->onLinePropertiesChanged(); | 
| 337 | } | 
| 338 |  | 
| 339 | void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) | 
| 340 | { | 
| 341 |     QDeclarativeGeoMapItemBase::setMap(quickMap,map); | 
| 342 |     if (map) | 
| 343 |         m_d->onMapSet(); | 
| 344 | } | 
| 345 |  | 
| 346 | /*! | 
| 347 |     \qmlproperty coordinate MapCircle::center | 
| 348 |  | 
| 349 |     This property holds the central point about which the circle is defined. | 
| 350 |  | 
| 351 |     \sa radius | 
| 352 | */ | 
| 353 | void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate ¢er) | 
| 354 | { | 
| 355 |     if (m_circle.center() == center) | 
| 356 |         return; | 
| 357 |  | 
| 358 |     possiblySwitchBackend(oldCenter: m_circle.center(), oldRadius: m_circle.radius(), newCenter: center, newRadius: m_circle.radius()); | 
| 359 |     m_circle.setCenter(center); | 
| 360 |     m_d->onGeoGeometryChanged(); | 
| 361 |     emit centerChanged(center); | 
| 362 | } | 
| 363 |  | 
| 364 | QGeoCoordinate QDeclarativeCircleMapItem::center() | 
| 365 | { | 
| 366 |     return m_circle.center(); | 
| 367 | } | 
| 368 |  | 
| 369 | /*! | 
| 370 |     \qmlproperty color MapCircle::color | 
| 371 |  | 
| 372 |     This property holds the fill color of the circle when drawn. For no fill, | 
| 373 |     use a transparent color. | 
| 374 | */ | 
| 375 | void QDeclarativeCircleMapItem::setColor(const QColor &color) | 
| 376 | { | 
| 377 |     if (m_color == color) | 
| 378 |         return; | 
| 379 |     m_color = color; | 
| 380 |     m_dirtyMaterial = true; | 
| 381 |     update(); | 
| 382 |     emit colorChanged(color: m_color); | 
| 383 | } | 
| 384 |  | 
| 385 | QColor QDeclarativeCircleMapItem::color() const | 
| 386 | { | 
| 387 |     return m_color; | 
| 388 | } | 
| 389 |  | 
| 390 | /*! | 
| 391 |     \qmlproperty real MapCircle::radius | 
| 392 |  | 
| 393 |     This property holds the radius of the circle, in meters on the ground. | 
| 394 |  | 
| 395 |     \sa center | 
| 396 | */ | 
| 397 | void QDeclarativeCircleMapItem::setRadius(qreal radius) | 
| 398 | { | 
| 399 |     if (m_circle.radius() == radius) | 
| 400 |         return; | 
| 401 |  | 
| 402 |     possiblySwitchBackend(oldCenter: m_circle.center(), oldRadius: m_circle.radius(), newCenter: m_circle.center(), newRadius: radius); | 
| 403 |     m_circle.setRadius(radius); | 
| 404 |     m_d->onGeoGeometryChanged(); | 
| 405 |     emit radiusChanged(radius); | 
| 406 | } | 
| 407 |  | 
| 408 | qreal QDeclarativeCircleMapItem::radius() const | 
| 409 | { | 
| 410 |     return m_circle.radius(); | 
| 411 | } | 
| 412 |  | 
| 413 | /*! | 
| 414 |   \qmlproperty real MapCircle::opacity | 
| 415 |  | 
| 416 |   This property holds the opacity of the item.  Opacity is specified as a | 
| 417 |   number between 0 (fully transparent) and 1 (fully opaque).  The default is 1. | 
| 418 |  | 
| 419 |   An item with 0 opacity will still receive mouse events. To stop mouse events, set the | 
| 420 |   visible property of the item to false. | 
| 421 | */ | 
| 422 |  | 
| 423 | /*! | 
| 424 |     \internal | 
| 425 | */ | 
| 426 | QSGNode *QDeclarativeCircleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) | 
| 427 | { | 
| 428 |     return m_d->updateMapItemPaintNode(oldNode, data); | 
| 429 | } | 
| 430 |  | 
| 431 | /*! | 
| 432 |     \internal | 
| 433 | */ | 
| 434 | void QDeclarativeCircleMapItem::updatePolish() | 
| 435 | { | 
| 436 |     if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) | 
| 437 |         return; | 
| 438 |     m_d->updatePolish(); | 
| 439 | } | 
| 440 |  | 
| 441 | /*! | 
| 442 |     \internal | 
| 443 |  | 
| 444 |     The OpenGL backend doesn't do circles crossing poles yet. | 
| 445 |     So if that backend is selected and the circle crosses the poles, use the CPU backend instead. | 
| 446 | */ | 
| 447 | void QDeclarativeCircleMapItem::possiblySwitchBackend(const QGeoCoordinate &oldCenter, qreal oldRadius, const QGeoCoordinate &newCenter, qreal newRadius) | 
| 448 | { | 
| 449 | #if QT_CONFIG(opengl) | 
| 450 |     if (m_backend != QDeclarativeCircleMapItem::OpenGL) | 
| 451 |         return; | 
| 452 |  | 
| 453 |     // if old does not cross and new crosses, move to CPU. | 
| 454 |     if (!QDeclarativeCircleMapItemPrivate::crossEarthPole(center: oldCenter, distance: oldRadius) | 
| 455 |             && !QDeclarativeCircleMapItemPrivate::crossEarthPole(center: newCenter, distance: newRadius)) { | 
| 456 |         QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this))); | 
| 457 |         m_d.swap(other&: d); | 
| 458 |     } else if (QDeclarativeCircleMapItemPrivate::crossEarthPole(center: oldCenter, distance: oldRadius) | 
| 459 |                && !QDeclarativeCircleMapItemPrivate::crossEarthPole(center: newCenter, distance: newRadius)) { // else if old crosses and new does not cross, move back to OpenGL | 
| 460 |         QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateOpenGL(*this))); | 
| 461 |         m_d.swap(other&: d); | 
| 462 |     } | 
| 463 | #else | 
| 464 |     return; | 
| 465 | #endif | 
| 466 | } | 
| 467 |  | 
| 468 | /*! | 
| 469 |     \internal | 
| 470 | */ | 
| 471 | void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) | 
| 472 | { | 
| 473 |     if (event.mapSize.isEmpty()) | 
| 474 |         return; | 
| 475 |  | 
| 476 |     m_d->afterViewportChanged(); | 
| 477 | } | 
| 478 |  | 
| 479 | /*! | 
| 480 |     \internal | 
| 481 | */ | 
| 482 | bool QDeclarativeCircleMapItem::contains(const QPointF &point) const | 
| 483 | { | 
| 484 |     return m_d->contains(point); | 
| 485 |     // | 
| 486 | } | 
| 487 |  | 
| 488 | const QGeoShape &QDeclarativeCircleMapItem::geoShape() const | 
| 489 | { | 
| 490 |     return m_circle; | 
| 491 | } | 
| 492 |  | 
| 493 | void QDeclarativeCircleMapItem::setGeoShape(const QGeoShape &shape) | 
| 494 | { | 
| 495 |     if (shape == m_circle) | 
| 496 |         return; | 
| 497 |  | 
| 498 |     const QGeoCircle circle(shape); // if shape isn't a circle, circle will be created as a default-constructed circle | 
| 499 |     const bool centerHasChanged = circle.center() != m_circle.center(); | 
| 500 |     const bool radiusHasChanged = circle.radius() != m_circle.radius(); | 
| 501 |     possiblySwitchBackend(oldCenter: m_circle.center(), oldRadius: m_circle.radius(), newCenter: circle.center(), newRadius: circle.radius()); | 
| 502 |     m_circle = circle; | 
| 503 |  | 
| 504 |     m_d->onGeoGeometryChanged(); | 
| 505 |     if (centerHasChanged) | 
| 506 |         emit centerChanged(center: m_circle.center()); | 
| 507 |     if (radiusHasChanged) | 
| 508 |         emit radiusChanged(radius: m_circle.radius()); | 
| 509 | } | 
| 510 |  | 
| 511 | /*! | 
| 512 |     \qmlproperty MapCircle.Backend QtLocation::MapCircle::backend | 
| 513 |  | 
| 514 |     This property holds which backend is in use to render the map item. | 
| 515 |     Valid values are \b MapCircle.Software and \b{MapCircle.OpenGL}. | 
| 516 |     The default value is \b{MapCircle.Software}. | 
| 517 |  | 
| 518 |     \note \b{The release of this API with Qt 5.15 is a Technology Preview}. | 
| 519 |     Ideally, as the OpenGL backends for map items mature, there will be | 
| 520 |     no more need to also offer the legacy software-projection backend. | 
| 521 |     So this property will likely disappear at some later point. | 
| 522 |     To select OpenGL-accelerated item backends without using this property, | 
| 523 |     it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS | 
| 524 |     to \b{1}. | 
| 525 |     Also note that all current OpenGL backends won't work as expected when enabling | 
| 526 |     layers on the individual item, or when running on OpenGL core profiles greater than 2.x. | 
| 527 |  | 
| 528 |     \since 5.15 | 
| 529 | */ | 
| 530 |  | 
| 531 | QDeclarativeCircleMapItem::Backend QDeclarativeCircleMapItem::backend() const | 
| 532 | { | 
| 533 |     return m_backend; | 
| 534 | } | 
| 535 |  | 
| 536 | void QDeclarativeCircleMapItem::setBackend(QDeclarativeCircleMapItem::Backend b) | 
| 537 | { | 
| 538 |     if (b == m_backend) | 
| 539 |         return; | 
| 540 |     m_backend = b; | 
| 541 |     QScopedPointer<QDeclarativeCircleMapItemPrivate> d( | 
| 542 |             (m_backend == Software) ? static_cast<QDeclarativeCircleMapItemPrivate *>( | 
| 543 |                     new QDeclarativeCircleMapItemPrivateCPU(*this)) | 
| 544 | #if QT_CONFIG(opengl) | 
| 545 |                                     : static_cast<QDeclarativeCircleMapItemPrivate *>( | 
| 546 |                                             new QDeclarativeCircleMapItemPrivateOpenGL(*this))); | 
| 547 | #else | 
| 548 |                                     : nullptr); | 
| 549 |     qFatal("Requested non software rendering backend, but source code is compiled wihtout opengl "  | 
| 550 |            "support" ); | 
| 551 | #endif | 
| 552 |     m_d.swap(other&: d); | 
| 553 |     m_d->onGeoGeometryChanged(); | 
| 554 |     emit backendChanged(); | 
| 555 | } | 
| 556 |  | 
| 557 | /*! | 
| 558 |     \internal | 
| 559 | */ | 
| 560 | void QDeclarativeCircleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) | 
| 561 | { | 
| 562 |     if (!map() || !m_circle.isValid() || m_updatingGeometry || newGeometry == oldGeometry) { | 
| 563 |         QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); | 
| 564 |         return; | 
| 565 |     } | 
| 566 |  | 
| 567 |     QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) * 0.5; | 
| 568 |     QGeoCoordinate newCoordinate = map()->geoProjection().itemPositionToCoordinate(pos: newPoint, clipToViewport: false); | 
| 569 |     if (newCoordinate.isValid()) | 
| 570 |         setCenter(newCoordinate); // ToDo: this is incorrect. setting such center might yield to another geometry changed. | 
| 571 |  | 
| 572 |     // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested | 
| 573 |     // call to this function. | 
| 574 | } | 
| 575 |  | 
| 576 | QDeclarativeCircleMapItemPrivate::~QDeclarativeCircleMapItemPrivate() {} | 
| 577 |  | 
| 578 | QDeclarativeCircleMapItemPrivateCPU::~QDeclarativeCircleMapItemPrivateCPU() {} | 
| 579 |  | 
| 580 | #if QT_CONFIG(opengl) | 
| 581 | QDeclarativeCircleMapItemPrivateOpenGL::~QDeclarativeCircleMapItemPrivateOpenGL() {} | 
| 582 | #endif | 
| 583 |  | 
| 584 | bool QDeclarativeCircleMapItemPrivate::preserveCircleGeometry (QList<QDoubleVector2D> &path, | 
| 585 |                                     const QGeoCoordinate ¢er, qreal distance, const QGeoProjectionWebMercator &p) | 
| 586 | { | 
| 587 |     // if circle crosses north/south pole, then don't preserve circular shape, | 
| 588 |     if ( crossEarthPole(center, distance)) { | 
| 589 |         updateCirclePathForRendering(path, center, distance, p); | 
| 590 |         return false; | 
| 591 |     } | 
| 592 |     return true; | 
| 593 | } | 
| 594 |  | 
| 595 | /* | 
| 596 |  * A workaround for circle path to be drawn correctly using a polygon geometry | 
| 597 |  * This method generates a polygon like | 
| 598 |  *  _____________ | 
| 599 |  *  |           | | 
| 600 |  *   \         / | 
| 601 |  *    |       | | 
| 602 |  *   /         \ | 
| 603 |  *  |           | | 
| 604 |  *  ------------- | 
| 605 |  * | 
| 606 |  * or a polygon like | 
| 607 |  * | 
| 608 |  *  ______________ | 
| 609 |  *  |    ____    | | 
| 610 |  *   \__/    \__/ | 
| 611 |  */ | 
| 612 | void QDeclarativeCircleMapItemPrivate::updateCirclePathForRendering(QList<QDoubleVector2D> &path, | 
| 613 |                                                              const QGeoCoordinate ¢er, | 
| 614 |                                                              qreal distance, const QGeoProjectionWebMercator &p) | 
| 615 | { | 
| 616 |     const qreal poleLat = 90; | 
| 617 |     const qreal distanceToNorthPole = center.distanceTo(other: QGeoCoordinate(poleLat, 0)); | 
| 618 |     const qreal distanceToSouthPole = center.distanceTo(other: QGeoCoordinate(-poleLat, 0)); | 
| 619 |     bool crossNorthPole = distanceToNorthPole < distance; | 
| 620 |     bool crossSouthPole = distanceToSouthPole < distance; | 
| 621 |  | 
| 622 |     QList<int> wrapPathIndex; | 
| 623 |     QDoubleVector2D prev = p.wrapMapProjection(projection: path.at(i: 0)); | 
| 624 |  | 
| 625 |     for (int i = 1; i <= path.count(); ++i) { | 
| 626 |         int index = i % path.count(); | 
| 627 |         QDoubleVector2D point = p.wrapMapProjection(projection: path.at(i: index)); | 
| 628 |         double diff = qAbs(t: point.x() - prev.x()); | 
| 629 |         if (diff > 0.5) { | 
| 630 |             continue; | 
| 631 |         } | 
| 632 |     } | 
| 633 |  | 
| 634 |     // find the points in path where wrapping occurs | 
| 635 |     for (int i = 1; i <= path.count(); ++i) { | 
| 636 |         int index = i % path.count(); | 
| 637 |         QDoubleVector2D point = p.wrapMapProjection(projection: path.at(i: index)); | 
| 638 |         if ( (qAbs(t: point.x() - prev.x())) >= 0.5 ) { | 
| 639 |             wrapPathIndex << index; | 
| 640 |             if (wrapPathIndex.size() == 2 || !(crossNorthPole && crossSouthPole)) | 
| 641 |                 break; | 
| 642 |         } | 
| 643 |         prev = point; | 
| 644 |     } | 
| 645 |     // insert two additional coords at top/bottom map corners of the map for shape | 
| 646 |     // to be drawn correctly | 
| 647 |     if (wrapPathIndex.size() > 0) { | 
| 648 |         qreal newPoleLat = 0; // 90 latitude | 
| 649 |         QDoubleVector2D wrapCoord = path.at(i: wrapPathIndex[0]); | 
| 650 |         if (wrapPathIndex.size() == 2) { | 
| 651 |             QDoubleVector2D wrapCoord2 = path.at(i: wrapPathIndex[1]); | 
| 652 |             if (wrapCoord2.y() < wrapCoord.y()) | 
| 653 |                 newPoleLat = 1; // -90 latitude | 
| 654 |         } else if (center.latitude() < 0) { | 
| 655 |             newPoleLat = 1; // -90 latitude | 
| 656 |         } | 
| 657 |         for (int i = 0; i < wrapPathIndex.size(); ++i) { | 
| 658 |             int index = wrapPathIndex[i] == 0 ? 0 : wrapPathIndex[i] + i*2; | 
| 659 |             int prevIndex = (index - 1) < 0 ? (path.count() - 1): index - 1; | 
| 660 |             QDoubleVector2D coord0 = path.at(i: prevIndex); | 
| 661 |             QDoubleVector2D coord1 = path.at(i: index); | 
| 662 |             coord0.setY(newPoleLat); | 
| 663 |             coord1.setY(newPoleLat); | 
| 664 |             path.insert(i: index ,t: coord1); | 
| 665 |             path.insert(i: index, t: coord0); | 
| 666 |             newPoleLat = 1.0 - newPoleLat; | 
| 667 |         } | 
| 668 |     } | 
| 669 | } | 
| 670 |  | 
| 671 | bool QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate ¢er, qreal distance) | 
| 672 | { | 
| 673 |     qreal poleLat = 90; | 
| 674 |     QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude()); | 
| 675 |     QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude()); | 
| 676 |     // approximate using great circle distance | 
| 677 |     qreal distanceToNorthPole = center.distanceTo(other: northPole); | 
| 678 |     qreal distanceToSouthPole = center.distanceTo(other: southPole); | 
| 679 |     if (distanceToNorthPole < distance || distanceToSouthPole < distance) | 
| 680 |         return true; | 
| 681 |     return false; | 
| 682 | } | 
| 683 |  | 
| 684 | void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoordinate> &path, | 
| 685 |                                       const QGeoCoordinate ¢er, | 
| 686 |                                       qreal distance, | 
| 687 |                                       int steps, | 
| 688 |                                       QGeoCoordinate &leftBound) | 
| 689 | { | 
| 690 |     // Calculate points based on great-circle distance | 
| 691 |     // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function | 
| 692 |     // but tweaked here for computing multiple points | 
| 693 |  | 
| 694 |     // pre-calculations | 
| 695 |     steps = qMax(a: steps, b: 3); | 
| 696 |     qreal centerLon = center.longitude(); | 
| 697 |     qreal minLon = centerLon; | 
| 698 |     qreal latRad = QLocationUtils::radians(degrees: center.latitude()); | 
| 699 |     qreal lonRad = QLocationUtils::radians(degrees: centerLon); | 
| 700 |     qreal cosLatRad = std::cos(x: latRad); | 
| 701 |     qreal sinLatRad = std::sin(x: latRad); | 
| 702 |     qreal ratio = (distance / QLocationUtils::earthMeanRadius()); | 
| 703 |     qreal cosRatio = std::cos(x: ratio); | 
| 704 |     qreal sinRatio = std::sin(x: ratio); | 
| 705 |     qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio; | 
| 706 |     qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio; | 
| 707 |     int idx = 0; | 
| 708 |     for (int i = 0; i < steps; ++i) { | 
| 709 |         qreal azimuthRad = 2 * M_PI * i / steps; | 
| 710 |         qreal resultLatRad = std::asin(x: sinLatRad_x_cosRatio | 
| 711 |                                    + cosLatRad_x_sinRatio * std::cos(x: azimuthRad)); | 
| 712 |         qreal resultLonRad = lonRad + std::atan2(y: std::sin(x: azimuthRad) * cosLatRad_x_sinRatio, | 
| 713 |                                        x: cosRatio - sinLatRad * std::sin(x: resultLatRad)); | 
| 714 |         qreal lat2 = QLocationUtils::degrees(radians: resultLatRad); | 
| 715 |         qreal lon2 = QLocationUtils::wrapLong(lng: QLocationUtils::degrees(radians: resultLonRad)); | 
| 716 |  | 
| 717 |         path << QGeoCoordinate(lat2, lon2, center.altitude()); | 
| 718 |         // Consider only points in the left half of the circle for the left bound. | 
| 719 |         if (azimuthRad > M_PI) { | 
| 720 |             if (lon2 > centerLon) // if point and center are on different hemispheres | 
| 721 |                 lon2 -= 360; | 
| 722 |             if (lon2 < minLon) { | 
| 723 |                 minLon = lon2; | 
| 724 |                 idx = i; | 
| 725 |             } | 
| 726 |         } | 
| 727 |     } | 
| 728 |     leftBound = path.at(i: idx); | 
| 729 | } | 
| 730 |  | 
| 731 | ////////////////////////////////////////////////////////////////////// | 
| 732 |  | 
| 733 | QT_END_NAMESPACE | 
| 734 |  |