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 (m_backend != QDeclarativeCircleMapItem::OpenGL)
450         return;
451 
452     // if old does not cross and new crosses, move to CPU.
453     if (!QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius)
454             && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) {
455         QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this)));
456         m_d.swap(d);
457     } else if (QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius)
458                && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) { // else if old crosses and new does not cross, move back to OpenGL
459         QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateOpenGL(*this)));
460         m_d.swap(d);
461     }
462 }
463 
464 /*!
465     \internal
466 */
afterViewportChanged(const QGeoMapViewportChangeEvent & event)467 void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
468 {
469     if (event.mapSize.isEmpty())
470         return;
471 
472     m_d->afterViewportChanged();
473 }
474 
475 /*!
476     \internal
477 */
contains(const QPointF & point) const478 bool QDeclarativeCircleMapItem::contains(const QPointF &point) const
479 {
480     return m_d->contains(point);
481     //
482 }
483 
geoShape() const484 const QGeoShape &QDeclarativeCircleMapItem::geoShape() const
485 {
486     return m_circle;
487 }
488 
setGeoShape(const QGeoShape & shape)489 void QDeclarativeCircleMapItem::setGeoShape(const QGeoShape &shape)
490 {
491     if (shape == m_circle)
492         return;
493 
494     const QGeoCircle circle(shape); // if shape isn't a circle, circle will be created as a default-constructed circle
495     const bool centerHasChanged = circle.center() != m_circle.center();
496     const bool radiusHasChanged = circle.radius() != m_circle.radius();
497     possiblySwitchBackend(m_circle.center(), m_circle.radius(), circle.center(), circle.radius());
498     m_circle = circle;
499 
500     m_d->onGeoGeometryChanged();
501     if (centerHasChanged)
502         emit centerChanged(m_circle.center());
503     if (radiusHasChanged)
504         emit radiusChanged(m_circle.radius());
505 }
506 
507 /*!
508     \qmlproperty MapCircle.Backend QtLocation::MapCircle::backend
509 
510     This property holds which backend is in use to render the map item.
511     Valid values are \b MapCircle.Software and \b{MapCircle.OpenGL}.
512     The default value is \b{MapCircle.Software}.
513 
514     \note \b{The release of this API with Qt 5.15 is a Technology Preview}.
515     Ideally, as the OpenGL backends for map items mature, there will be
516     no more need to also offer the legacy software-projection backend.
517     So this property will likely disappear at some later point.
518     To select OpenGL-accelerated item backends without using this property,
519     it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS
520     to \b{1}.
521     Also note that all current OpenGL backends won't work as expected when enabling
522     layers on the individual item, or when running on OpenGL core profiles greater than 2.x.
523 
524     \since 5.15
525 */
526 
backend() const527 QDeclarativeCircleMapItem::Backend QDeclarativeCircleMapItem::backend() const
528 {
529     return m_backend;
530 }
531 
setBackend(QDeclarativeCircleMapItem::Backend b)532 void QDeclarativeCircleMapItem::setBackend(QDeclarativeCircleMapItem::Backend b)
533 {
534     if (b == m_backend)
535         return;
536     m_backend = b;
537     QScopedPointer<QDeclarativeCircleMapItemPrivate> d((m_backend == Software)
538                                                         ? static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this))
539                                                         : static_cast<QDeclarativeCircleMapItemPrivate * >(new QDeclarativeCircleMapItemPrivateOpenGL(*this)));
540     m_d.swap(d);
541     m_d->onGeoGeometryChanged();
542     emit backendChanged();
543 }
544 
545 /*!
546     \internal
547 */
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)548 void QDeclarativeCircleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
549 {
550     if (!map() || !m_circle.isValid() || m_updatingGeometry || newGeometry == oldGeometry) {
551         QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry);
552         return;
553     }
554 
555     QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) * 0.5;
556     QGeoCoordinate newCoordinate = map()->geoProjection().itemPositionToCoordinate(newPoint, false);
557     if (newCoordinate.isValid())
558         setCenter(newCoordinate); // ToDo: this is incorrect. setting such center might yield to another geometry changed.
559 
560     // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested
561     // call to this function.
562 }
563 
~QDeclarativeCircleMapItemPrivate()564 QDeclarativeCircleMapItemPrivate::~QDeclarativeCircleMapItemPrivate() {}
565 
~QDeclarativeCircleMapItemPrivateCPU()566 QDeclarativeCircleMapItemPrivateCPU::~QDeclarativeCircleMapItemPrivateCPU() {}
567 
~QDeclarativeCircleMapItemPrivateOpenGL()568 QDeclarativeCircleMapItemPrivateOpenGL::~QDeclarativeCircleMapItemPrivateOpenGL() {}
569 
preserveCircleGeometry(QList<QDoubleVector2D> & path,const QGeoCoordinate & center,qreal distance,const QGeoProjectionWebMercator & p)570 bool QDeclarativeCircleMapItemPrivate::preserveCircleGeometry (QList<QDoubleVector2D> &path,
571                                     const QGeoCoordinate &center, qreal distance, const QGeoProjectionWebMercator &p)
572 {
573     // if circle crosses north/south pole, then don't preserve circular shape,
574     if ( crossEarthPole(center, distance)) {
575         updateCirclePathForRendering(path, center, distance, p);
576         return false;
577     }
578     return true;
579 }
580 
581 /*
582  * A workaround for circle path to be drawn correctly using a polygon geometry
583  * This method generates a polygon like
584  *  _____________
585  *  |           |
586  *   \         /
587  *    |       |
588  *   /         \
589  *  |           |
590  *  -------------
591  *
592  * or a polygon like
593  *
594  *  ______________
595  *  |    ____    |
596  *   \__/    \__/
597  */
updateCirclePathForRendering(QList<QDoubleVector2D> & path,const QGeoCoordinate & center,qreal distance,const QGeoProjectionWebMercator & p)598 void QDeclarativeCircleMapItemPrivate::updateCirclePathForRendering(QList<QDoubleVector2D> &path,
599                                                              const QGeoCoordinate &center,
600                                                              qreal distance, const QGeoProjectionWebMercator &p)
601 {
602     const qreal poleLat = 90;
603     const qreal distanceToNorthPole = center.distanceTo(QGeoCoordinate(poleLat, 0));
604     const qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0));
605     bool crossNorthPole = distanceToNorthPole < distance;
606     bool crossSouthPole = distanceToSouthPole < distance;
607 
608     QList<int> wrapPathIndex;
609     QDoubleVector2D prev = p.wrapMapProjection(path.at(0));
610 
611     for (int i = 1; i <= path.count(); ++i) {
612         int index = i % path.count();
613         QDoubleVector2D point = p.wrapMapProjection(path.at(index));
614         double diff = qAbs(point.x() - prev.x());
615         if (diff > 0.5) {
616             continue;
617         }
618     }
619 
620     // find the points in path where wrapping occurs
621     for (int i = 1; i <= path.count(); ++i) {
622         int index = i % path.count();
623         QDoubleVector2D point = p.wrapMapProjection(path.at(index));
624         if ( (qAbs(point.x() - prev.x())) >= 0.5 ) {
625             wrapPathIndex << index;
626             if (wrapPathIndex.size() == 2 || !(crossNorthPole && crossSouthPole))
627                 break;
628         }
629         prev = point;
630     }
631     // insert two additional coords at top/bottom map corners of the map for shape
632     // to be drawn correctly
633     if (wrapPathIndex.size() > 0) {
634         qreal newPoleLat = 0; // 90 latitude
635         QDoubleVector2D wrapCoord = path.at(wrapPathIndex[0]);
636         if (wrapPathIndex.size() == 2) {
637             QDoubleVector2D wrapCoord2 = path.at(wrapPathIndex[1]);
638             if (wrapCoord2.y() < wrapCoord.y())
639                 newPoleLat = 1; // -90 latitude
640         } else if (center.latitude() < 0) {
641             newPoleLat = 1; // -90 latitude
642         }
643         for (int i = 0; i < wrapPathIndex.size(); ++i) {
644             int index = wrapPathIndex[i] == 0 ? 0 : wrapPathIndex[i] + i*2;
645             int prevIndex = (index - 1) < 0 ? (path.count() - 1): index - 1;
646             QDoubleVector2D coord0 = path.at(prevIndex);
647             QDoubleVector2D coord1 = path.at(index);
648             coord0.setY(newPoleLat);
649             coord1.setY(newPoleLat);
650             path.insert(index ,coord1);
651             path.insert(index, coord0);
652             newPoleLat = 1.0 - newPoleLat;
653         }
654     }
655 }
656 
crossEarthPole(const QGeoCoordinate & center,qreal distance)657 bool QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate &center, qreal distance)
658 {
659     qreal poleLat = 90;
660     QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude());
661     QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude());
662     // approximate using great circle distance
663     qreal distanceToNorthPole = center.distanceTo(northPole);
664     qreal distanceToSouthPole = center.distanceTo(southPole);
665     if (distanceToNorthPole < distance || distanceToSouthPole < distance)
666         return true;
667     return false;
668 }
669 
calculatePeripheralPoints(QList<QGeoCoordinate> & path,const QGeoCoordinate & center,qreal distance,int steps,QGeoCoordinate & leftBound)670 void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoordinate> &path,
671                                       const QGeoCoordinate &center,
672                                       qreal distance,
673                                       int steps,
674                                       QGeoCoordinate &leftBound)
675 {
676     // Calculate points based on great-circle distance
677     // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function
678     // but tweaked here for computing multiple points
679 
680     // pre-calculations
681     steps = qMax(steps, 3);
682     qreal centerLon = center.longitude();
683     qreal minLon = centerLon;
684     qreal latRad = QLocationUtils::radians(center.latitude());
685     qreal lonRad = QLocationUtils::radians(centerLon);
686     qreal cosLatRad = std::cos(latRad);
687     qreal sinLatRad = std::sin(latRad);
688     qreal ratio = (distance / QLocationUtils::earthMeanRadius());
689     qreal cosRatio = std::cos(ratio);
690     qreal sinRatio = std::sin(ratio);
691     qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio;
692     qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio;
693     int idx = 0;
694     for (int i = 0; i < steps; ++i) {
695         qreal azimuthRad = 2 * M_PI * i / steps;
696         qreal resultLatRad = std::asin(sinLatRad_x_cosRatio
697                                    + cosLatRad_x_sinRatio * std::cos(azimuthRad));
698         qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio,
699                                        cosRatio - sinLatRad * std::sin(resultLatRad));
700         qreal lat2 = QLocationUtils::degrees(resultLatRad);
701         qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad));
702 
703         path << QGeoCoordinate(lat2, lon2, center.altitude());
704         // Consider only points in the left half of the circle for the left bound.
705         if (azimuthRad > M_PI) {
706             if (lon2 > centerLon) // if point and center are on different hemispheres
707                 lon2 -= 360;
708             if (lon2 < minLon) {
709                 minLon = lon2;
710                 idx = i;
711             }
712         }
713     }
714     leftBound = path.at(idx);
715 }
716 
717 //////////////////////////////////////////////////////////////////////
718 
719 QT_END_NAMESPACE
720