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 
QGeoMapCircleGeometry()148 QGeoMapCircleGeometry::QGeoMapCircleGeometry()
149 {
150 }
151 
152 /*!
153     \internal
154 */
updateScreenPointsInvert(const QList<QDoubleVector2D> & circlePath,const QGeoMap & map)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(map.cameraData().center().longitude());
177     const double rightLongi = QLocationUtils::mapRightLongitude(map.cameraData().center().longitude());
178 
179     srcOrigin_ = QGeoCoordinate(topLati,leftLongi);
180     const QDoubleVector2D tl = p.geoToWrappedMapProjection(QGeoCoordinate(topLati,leftLongi));
181     const QDoubleVector2D tr = p.geoToWrappedMapProjection(QGeoCoordinate(topLati,rightLongi));
182     const QDoubleVector2D br = p.geoToWrappedMapProjection(QGeoCoordinate(bottomLati,rightLongi));
183     const QDoubleVector2D bl = p.geoToWrappedMapProjection(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(c);
191 
192     c2t::clip2tri clipper;
193     clipper.addSubjectPath(QClipperUtils::qListToPath(fill), true);
194     clipper.addClipPolygon(QClipperUtils::qListToPath(hole));
195     Paths difference = clipper.execute(c2t::clip2tri::Difference, QtClipperLib::pftEvenOdd, QtClipperLib::pftEvenOdd);
196 
197     // 2)
198     QDoubleVector2D lb = p.geoToWrappedMapProjection(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(p, true);
205         clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion));
206         Paths res = clipper.execute(c2t::clip2tri::Intersection, QtClipperLib::pftEvenOdd, QtClipperLib::pftEvenOdd);
207         clippedPaths = QClipperUtils::pathsToQList(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(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(tl.x(), lb.x()));
224         srcOrigin_ = p.mapProjectionToGeo(p.unwrapMapProjection(lb));
225     } else {
226         clippedPaths = QClipperUtils::pathsToQList(difference);
227     }
228 
229     //3)
230     QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(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(path.at(i));
237             //point = point - origin; // Do this using ppi.translate()
238 
239             if (i == 0) {
240                 ppi.moveTo(point.toPointF());
241                 lastAddedPoint = point;
242             } else {
243                 if ((point - lastAddedPoint).manhattanLength() > 3 ||
244                         i == path.size() - 1) {
245                     ppi.lineTo(point.toPointF());
246                     lastAddedPoint = point;
247                 }
248             }
249         }
250         ppi.closeSubpath();
251     }
252     ppi.translate(-1 * origin.toPointF());
253 
254     QTriangleSet ts = qTriangulate(ppi);
255     qreal *vx = ts.vertices.data();
256 
257     screenIndices_.reserve(ts.indices.size());
258     screenVertices_.reserve(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 {
CircleBackendSelectorCircleBackendSelector278     CircleBackendSelector()
279     {
280         backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativeCircleMapItem::OpenGL : QDeclarativeCircleMapItem::Software;
281     }
282     QDeclarativeCircleMapItem::Backend backend = QDeclarativeCircleMapItem::Software;
283 };
284 
Q_GLOBAL_STATIC(CircleBackendSelector,mapCircleBackendSelector)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(ItemHasContents, true);
295     QObject::connect(&m_border, SIGNAL(colorChanged(QColor)),
296                      this, SLOT(onLinePropertiesChanged()));
297     QObject::connect(&m_border, SIGNAL(widthChanged(qreal)),
298                      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 
~QDeclarativeCircleMapItem()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 */
border()324 QDeclarativeMapLineProperties *QDeclarativeCircleMapItem::border()
325 {
326     return &m_border;
327 }
328 
markSourceDirtyAndUpdate()329 void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate()
330 {
331     m_d->markSourceDirtyAndUpdate();
332 }
333 
onLinePropertiesChanged()334 void QDeclarativeCircleMapItem::onLinePropertiesChanged()
335 {
336     m_d->onLinePropertiesChanged();
337 }
338 
setMap(QDeclarativeGeoMap * quickMap,QGeoMap * map)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 */
setCenter(const QGeoCoordinate & center)353 void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate &center)
354 {
355     if (m_circle.center() == center)
356         return;
357 
358     possiblySwitchBackend(m_circle.center(), m_circle.radius(), center, m_circle.radius());
359     m_circle.setCenter(center);
360     m_d->onGeoGeometryChanged();
361     emit centerChanged(center);
362 }
363 
center()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 */
setColor(const QColor & color)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(m_color);
383 }
384 
color() const385 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 */
setRadius(qreal radius)397 void QDeclarativeCircleMapItem::setRadius(qreal radius)
398 {
399     if (m_circle.radius() == radius)
400         return;
401 
402     possiblySwitchBackend(m_circle.center(), m_circle.radius(), m_circle.center(), radius);
403     m_circle.setRadius(radius);
404     m_d->onGeoGeometryChanged();
405     emit radiusChanged(radius);
406 }
407 
radius() const408 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 */
updateMapItemPaintNode(QSGNode * oldNode,UpdatePaintNodeData * data)426 QSGNode *QDeclarativeCircleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
427 {
428     return m_d->updateMapItemPaintNode(oldNode, data);
429 }
430 
431 /*!
432     \internal
433 */
updatePolish()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 */
possiblySwitchBackend(const QGeoCoordinate & oldCenter,qreal oldRadius,const QGeoCoordinate & newCenter,qreal newRadius)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(oldCenter, oldRadius)
455             && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) {
456         QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this)));
457         m_d.swap(d);
458     } else if (QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius)
459                && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, 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(d);
462     }
463 #else
464     return;
465 #endif
466 }
467 
468 /*!
469     \internal
470 */
afterViewportChanged(const QGeoMapViewportChangeEvent & event)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 */
contains(const QPointF & point) const482 bool QDeclarativeCircleMapItem::contains(const QPointF &point) const
483 {
484     return m_d->contains(point);
485     //
486 }
487 
geoShape() const488 const QGeoShape &QDeclarativeCircleMapItem::geoShape() const
489 {
490     return m_circle;
491 }
492 
setGeoShape(const QGeoShape & shape)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(m_circle.center(), m_circle.radius(), circle.center(), circle.radius());
502     m_circle = circle;
503 
504     m_d->onGeoGeometryChanged();
505     if (centerHasChanged)
506         emit centerChanged(m_circle.center());
507     if (radiusHasChanged)
508         emit radiusChanged(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 
backend() const531 QDeclarativeCircleMapItem::Backend QDeclarativeCircleMapItem::backend() const
532 {
533     return m_backend;
534 }
535 
setBackend(QDeclarativeCircleMapItem::Backend b)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(d);
553     m_d->onGeoGeometryChanged();
554     emit backendChanged();
555 }
556 
557 /*!
558     \internal
559 */
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)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(newPoint, 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 
~QDeclarativeCircleMapItemPrivate()576 QDeclarativeCircleMapItemPrivate::~QDeclarativeCircleMapItemPrivate() {}
577 
~QDeclarativeCircleMapItemPrivateCPU()578 QDeclarativeCircleMapItemPrivateCPU::~QDeclarativeCircleMapItemPrivateCPU() {}
579 
580 #if QT_CONFIG(opengl)
~QDeclarativeCircleMapItemPrivateOpenGL()581 QDeclarativeCircleMapItemPrivateOpenGL::~QDeclarativeCircleMapItemPrivateOpenGL() {}
582 #endif
583 
preserveCircleGeometry(QList<QDoubleVector2D> & path,const QGeoCoordinate & center,qreal distance,const QGeoProjectionWebMercator & p)584 bool QDeclarativeCircleMapItemPrivate::preserveCircleGeometry (QList<QDoubleVector2D> &path,
585                                     const QGeoCoordinate &center, 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  */
updateCirclePathForRendering(QList<QDoubleVector2D> & path,const QGeoCoordinate & center,qreal distance,const QGeoProjectionWebMercator & p)612 void QDeclarativeCircleMapItemPrivate::updateCirclePathForRendering(QList<QDoubleVector2D> &path,
613                                                              const QGeoCoordinate &center,
614                                                              qreal distance, const QGeoProjectionWebMercator &p)
615 {
616     const qreal poleLat = 90;
617     const qreal distanceToNorthPole = center.distanceTo(QGeoCoordinate(poleLat, 0));
618     const qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0));
619     bool crossNorthPole = distanceToNorthPole < distance;
620     bool crossSouthPole = distanceToSouthPole < distance;
621 
622     QList<int> wrapPathIndex;
623     QDoubleVector2D prev = p.wrapMapProjection(path.at(0));
624 
625     for (int i = 1; i <= path.count(); ++i) {
626         int index = i % path.count();
627         QDoubleVector2D point = p.wrapMapProjection(path.at(index));
628         double diff = qAbs(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(path.at(index));
638         if ( (qAbs(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(wrapPathIndex[0]);
650         if (wrapPathIndex.size() == 2) {
651             QDoubleVector2D wrapCoord2 = path.at(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(prevIndex);
661             QDoubleVector2D coord1 = path.at(index);
662             coord0.setY(newPoleLat);
663             coord1.setY(newPoleLat);
664             path.insert(index ,coord1);
665             path.insert(index, coord0);
666             newPoleLat = 1.0 - newPoleLat;
667         }
668     }
669 }
670 
crossEarthPole(const QGeoCoordinate & center,qreal distance)671 bool QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate &center, 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(northPole);
678     qreal distanceToSouthPole = center.distanceTo(southPole);
679     if (distanceToNorthPole < distance || distanceToSouthPole < distance)
680         return true;
681     return false;
682 }
683 
calculatePeripheralPoints(QList<QGeoCoordinate> & path,const QGeoCoordinate & center,qreal distance,int steps,QGeoCoordinate & leftBound)684 void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoordinate> &path,
685                                       const QGeoCoordinate &center,
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(steps, 3);
696     qreal centerLon = center.longitude();
697     qreal minLon = centerLon;
698     qreal latRad = QLocationUtils::radians(center.latitude());
699     qreal lonRad = QLocationUtils::radians(centerLon);
700     qreal cosLatRad = std::cos(latRad);
701     qreal sinLatRad = std::sin(latRad);
702     qreal ratio = (distance / QLocationUtils::earthMeanRadius());
703     qreal cosRatio = std::cos(ratio);
704     qreal sinRatio = std::sin(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(sinLatRad_x_cosRatio
711                                    + cosLatRad_x_sinRatio * std::cos(azimuthRad));
712         qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio,
713                                        cosRatio - sinLatRad * std::sin(resultLatRad));
714         qreal lat2 = QLocationUtils::degrees(resultLatRad);
715         qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(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(idx);
729 }
730 
731 //////////////////////////////////////////////////////////////////////
732 
733 QT_END_NAMESPACE
734