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 ¢er)
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 ¢er, 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 ¢er,
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 ¢er, 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 ¢er,
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