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
62QT_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
143struct Vertex
144{
145 QVector2D position;
146};
147
148QGeoMapCircleGeometry::QGeoMapCircleGeometry()
149{
150}
151
152/*!
153 \internal
154*/
155void 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
276struct 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
285Q_GLOBAL_STATIC(CircleBackendSelector, mapCircleBackendSelector)
286
287QDeclarativeCircleMapItem::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
308QDeclarativeCircleMapItem::~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*/
324QDeclarativeMapLineProperties *QDeclarativeCircleMapItem::border()
325{
326 return &m_border;
327}
328
329void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate()
330{
331 m_d->markSourceDirtyAndUpdate();
332}
333
334void QDeclarativeCircleMapItem::onLinePropertiesChanged()
335{
336 m_d->onLinePropertiesChanged();
337}
338
339void 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*/
353void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate &center)
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
364QGeoCoordinate 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*/
375void 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
385QColor 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*/
397void 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
408qreal 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*/
426QSGNode *QDeclarativeCircleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
427{
428 return m_d->updateMapItemPaintNode(oldNode, data);
429}
430
431/*!
432 \internal
433*/
434void 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*/
447void 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*/
471void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
472{
473 if (event.mapSize.isEmpty())
474 return;
475
476 m_d->afterViewportChanged();
477}
478
479/*!
480 \internal
481*/
482bool QDeclarativeCircleMapItem::contains(const QPointF &point) const
483{
484 return m_d->contains(point);
485 //
486}
487
488const QGeoShape &QDeclarativeCircleMapItem::geoShape() const
489{
490 return m_circle;
491}
492
493void 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
531QDeclarativeCircleMapItem::Backend QDeclarativeCircleMapItem::backend() const
532{
533 return m_backend;
534}
535
536void 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*/
560void 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
576QDeclarativeCircleMapItemPrivate::~QDeclarativeCircleMapItemPrivate() {}
577
578QDeclarativeCircleMapItemPrivateCPU::~QDeclarativeCircleMapItemPrivateCPU() {}
579
580#if QT_CONFIG(opengl)
581QDeclarativeCircleMapItemPrivateOpenGL::~QDeclarativeCircleMapItemPrivateOpenGL() {}
582#endif
583
584bool 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 */
612void 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(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
671bool 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(other: northPole);
678 qreal distanceToSouthPole = center.distanceTo(other: southPole);
679 if (distanceToNorthPole < distance || distanceToSouthPole < distance)
680 return true;
681 return false;
682}
683
684void 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(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
733QT_END_NAMESPACE
734

source code of qtlocation/src/location/declarativemaps/qdeclarativecirclemapitem.cpp