summaryrefslogtreecommitdiff
path: root/src/location/declarativemaps/qdeclarativecirclemapitem.cpp
diff options
context:
space:
mode:
authorPaolo Angelelli <paolo.angelelli@qt.io>2016-04-05 10:54:07 +0200
committerPaolo Angelelli <paolo.angelelli@qt.io>2017-01-26 14:46:08 +0000
commitd83f23e1ef19fe524c50b827ee408523dbb74bdc (patch)
treec04e66302e67c61c2909330ddf0e414a718f67ec /src/location/declarativemaps/qdeclarativecirclemapitem.cpp
parentc062403a894a270101ae821fd73fa848f918ac54 (diff)
downloadqtlocation-d83f23e1ef19fe524c50b827ee408523dbb74bdc.tar.gz
Moving location qml implementation to location/declarative*
QtLocation quick classes and headers are moved out of the import directory. The location quick classes are now in a subdirectory inside the main qtlocation module. This is necessary in order to privately export certain classes, such as Map Items classes, and create an API to inject these objects into QGeoMap to let plugins render the Map Items directly Change-Id: Ia6ba5f07c4eddd3c4c2ce54bf34f1afcd42c2558 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
Diffstat (limited to 'src/location/declarativemaps/qdeclarativecirclemapitem.cpp')
-rw-r--r--src/location/declarativemaps/qdeclarativecirclemapitem.cpp728
1 files changed, 728 insertions, 0 deletions
diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem.cpp b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp
new file mode 100644
index 00000000..39581dce
--- /dev/null
+++ b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp
@@ -0,0 +1,728 @@
+/***************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtLocation module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdeclarativecirclemapitem_p.h"
+#include "qdeclarativepolygonmapitem_p.h"
+#include "qgeocameracapabilities_p.h"
+
+#include "qwebmercator_p.h"
+#include <QtLocation/private/qgeomap_p.h>
+
+#include <cmath>
+#include <algorithm>
+
+#include <QtCore/QScopedValueRollback>
+#include <QPen>
+#include <QPainter>
+#include <QtGui/private/qtriangulator_p.h>
+
+#include "qdoublevector2d_p.h"
+#include "qlocationutils_p.h"
+#include "qgeocircle.h"
+
+/* poly2tri triangulator includes */
+#include <common/shapes.h>
+#include <sweep/cdt.h>
+
+#include <QtPositioning/private/qclipperutils_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype MapCircle
+ \instantiates QDeclarativeCircleMapItem
+ \inqmlmodule QtLocation
+ \ingroup qml-QtLocation5-maps
+ \since Qt Location 5.5
+
+ \brief The MapCircle type displays a geographic circle on a Map.
+
+ The MapCircle type displays a geographic circle on a Map, which
+ consists of all points that are within a set distance from one
+ central point. Depending on map projection, a geographic circle
+ may not always be a perfect circle on the screen: for instance, in
+ the Mercator projection, circles become ovoid in shape as they near
+ the poles. To display a perfect screen circle around a point, use a
+ MapQuickItem containing a relevant Qt Quick type instead.
+
+ By default, the circle is displayed as a 1 pixel black border with
+ no fill. To change its appearance, use the color, border.color
+ and border.width properties.
+
+ Internally, a MapCircle is implemented as a many-sided polygon. To
+ calculate the radius points it uses a spherical model of the Earth,
+ similar to the atDistanceAndAzimuth method of the \l {coordinate}
+ type. These two things can occasionally have implications for the
+ accuracy of the circle's shape, depending on position and map
+ projection.
+
+ \note Dragging a MapCircle (through the use of \l MouseArea)
+ causes new points to be generated at the same distance (in meters)
+ from the center. This is in contrast to other map items which store
+ their dimensions in terms of latitude and longitude differences between
+ vertices.
+
+ \section2 Performance
+
+ MapCircle performance is almost equivalent to that of a MapPolygon with
+ the same number of vertices. There is a small amount of additional
+ overhead with respect to calculating the vertices first.
+
+ Like the other map objects, MapCircle is normally drawn without a smooth
+ appearance. Setting the opacity property will force the object to be
+ blended, which decreases performance considerably depending on the graphics
+ hardware in use.
+
+ \section2 Example Usage
+
+ The following snippet shows a map containing a MapCircle, centered at
+ the coordinate (-27, 153) with a radius of 5km. The circle is
+ filled in green, with a 3 pixel black border.
+
+ \code
+ Map {
+ MapCircle {
+ center {
+ latitude: -27.5
+ longitude: 153.0
+ }
+ radius: 5000.0
+ color: 'green'
+ border.width: 3
+ }
+ }
+ \endcode
+
+ \image api-mapcircle.png
+*/
+
+#ifdef M_PI
+#undef M_PI
+#endif
+#define M_PI 3.14159265358979323846264338327950288
+
+static const int CircleSamples = 128;
+
+struct Vertex
+{
+ QVector2D position;
+};
+
+QGeoMapCircleGeometry::QGeoMapCircleGeometry()
+{
+}
+
+/*!
+ \internal
+*/
+void QGeoMapCircleGeometry::updateScreenPointsInvert(const QList<QGeoCoordinate> &circlePath, const QGeoMap &map)
+{
+ // Not checking for !screenDirty anymore, as everything is now recalculated.
+ clear();
+ if (map.viewportWidth() == 0 || map.viewportHeight() == 0 || circlePath.size() < 3) // a circle requires at least 3 points;
+ return;
+
+ /*
+ * No special case for no tilting as these items are very rare, and usually at most one per map.
+ *
+ * Approach:
+ * 1) subtract the circle from a rectangle filling the whole map, *in wrapped mercator space*
+ * 2) clip the resulting geometries against the visible region, *in wrapped mercator space*
+ * 3) create a QPainterPath with each of the resulting polygons projected to screen
+ * 4) use qTriangulate() to triangulate the painter path
+ */
+
+ // 1)
+ double topLati = QLocationUtils::mercatorMaxLatitude();
+ double bottomLati = -(QLocationUtils::mercatorMaxLatitude());
+ double leftLongi = QLocationUtils::mapLeftLongitude(map.cameraData().center().longitude());
+ double rightLongi = QLocationUtils::mapRightLongitude(map.cameraData().center().longitude());
+
+ srcOrigin_ = QGeoCoordinate(topLati,leftLongi);
+ QDoubleVector2D tl = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(topLati,leftLongi));
+ QDoubleVector2D tr = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(topLati,rightLongi));
+ QDoubleVector2D br = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(bottomLati,rightLongi));
+ QDoubleVector2D bl = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(bottomLati,leftLongi));
+
+ QList<QDoubleVector2D> fill;
+ fill << tl << tr << br << bl;
+
+ QList<QDoubleVector2D> hole;
+ for (const QGeoCoordinate &c: circlePath)
+ hole << map.geoProjection().geoToWrappedMapProjection(c);
+
+ c2t::clip2tri clipper;
+ clipper.addSubjectPath(QClipperUtils::qListToPath(fill), true);
+ clipper.addClipPolygon(QClipperUtils::qListToPath(hole));
+ Paths difference = clipper.execute(c2t::clip2tri::Difference, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
+
+ // 2)
+ QDoubleVector2D lb = map.geoProjection().geoToWrappedMapProjection(srcOrigin_);
+ QList<QList<QDoubleVector2D> > clippedPaths;
+ const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion();
+ if (visibleRegion.size()) {
+ clipper.clearClipper();
+ for (const Path &p: difference)
+ clipper.addSubjectPath(p, true);
+ clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion));
+ Paths res = clipper.execute(c2t::clip2tri::Intersection, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
+ clippedPaths = QClipperUtils::pathsToQList(res);
+
+ // 2.1) update srcOrigin_ with the point with minimum X/Y
+ lb = QDoubleVector2D(qInf(), qInf());
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ for (const QDoubleVector2D &p: path) {
+ if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) {
+ lb = p;
+ }
+ }
+ }
+ if (qIsInf(lb.x()))
+ return;
+
+ // Prevent the conversion to and from clipper from introducing negative offsets which
+ // in turn will make the geometry wrap around.
+ lb.setX(qMax(tl.x(), lb.x()));
+ srcOrigin_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(lb));
+ } else {
+ clippedPaths = QClipperUtils::pathsToQList(difference);
+ }
+
+ //3)
+ QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(lb);
+
+ QPainterPath ppi;
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ QDoubleVector2D lastAddedPoint;
+ for (int i = 0; i < path.size(); ++i) {
+ QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i));
+ //point = point - origin; // Do this using ppi.translate()
+
+ if (i == 0) {
+ ppi.moveTo(point.toPointF());
+ lastAddedPoint = point;
+ } else {
+ if ((point - lastAddedPoint).manhattanLength() > 3 ||
+ i == path.size() - 1) {
+ ppi.lineTo(point.toPointF());
+ lastAddedPoint = point;
+ }
+ }
+ }
+ ppi.closeSubpath();
+ }
+ ppi.translate(-1 * origin.toPointF());
+
+#if 0 // old poly2tri code, has to be ported to clip2tri in order to work with tilted projections
+ std::vector<p2t::Point*> borderPts;
+ borderPts.reserve(4);
+
+ std::vector<p2t::Point*> curPts;
+ curPts.reserve(ppi.elementCount());
+ for (int i = 0; i < ppi.elementCount(); ++i) {
+ const QPainterPath::Element e = ppi.elementAt(i);
+ if (e.isMoveTo() || i == ppi.elementCount() - 1
+ || (qAbs(e.x - curPts.front()->x) < 0.1
+ && qAbs(e.y - curPts.front()->y) < 0.1)) {
+ if (curPts.size() > 2) {
+ for (int j = 0; j < 4; ++j) {
+ const QPainterPath::Element e2 = ppiBorder.elementAt(j);
+ borderPts.push_back(new p2t::Point(e2.x, e2.y));
+ }
+ p2t::CDT *cdt = new p2t::CDT(borderPts);
+ cdt->AddHole(curPts);
+ cdt->Triangulate();
+ std::vector<p2t::Triangle*> tris = cdt->GetTriangles();
+ screenVertices_.reserve(screenVertices_.size() + int(tris.size()));
+ for (size_t i = 0; i < tris.size(); ++i) {
+ p2t::Triangle *t = tris.at(i);
+ for (int j = 0; j < 3; ++j) {
+ p2t::Point *p = t->GetPoint(j);
+ screenVertices_ << QPointF(p->x, p->y);
+ }
+ }
+ delete cdt;
+ }
+ curPts.clear();
+ curPts.reserve(ppi.elementCount() - i);
+ curPts.push_back(new p2t::Point(e.x, e.y));
+ } else if (e.isLineTo()) {
+ curPts.push_back(new p2t::Point(e.x, e.y));
+ } else {
+ qWarning("Unhandled element type in circle painterpath");
+ }
+ }
+
+ if (curPts.size() > 0) {
+ qDeleteAll(curPts.begin(), curPts.end());
+ curPts.clear();
+ }
+
+ if (borderPts.size() > 0) {
+ qDeleteAll(borderPts.begin(), borderPts.end());
+ borderPts.clear();
+ }
+#else // Using qTriangulate as this case is not frequent, and not many circles including both poles are usually used
+ QTriangleSet ts = qTriangulate(ppi);
+ qreal *vx = ts.vertices.data();
+
+ screenIndices_.reserve(ts.indices.size());
+ screenVertices_.reserve(ts.vertices.size());
+
+ if (ts.indices.type() == QVertexIndexVector::UnsignedInt) {
+ const quint32 *ix = reinterpret_cast<const quint32 *>(ts.indices.data());
+ for (int i = 0; i < (ts.indices.size()/3*3); ++i)
+ screenIndices_ << ix[i];
+ } else {
+ const quint16 *ix = reinterpret_cast<const quint16 *>(ts.indices.data());
+ for (int i = 0; i < (ts.indices.size()/3*3); ++i)
+ screenIndices_ << ix[i];
+ }
+ for (int i = 0; i < (ts.vertices.size()/2*2); i += 2)
+ screenVertices_ << QPointF(vx[i], vx[i + 1]);
+#endif
+
+ screenBounds_ = ppi.boundingRect();
+ sourceBounds_ = screenBounds_;
+}
+
+static bool crossEarthPole(const QGeoCoordinate &center, qreal distance)
+{
+ qreal poleLat = 90;
+ QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude());
+ QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude());
+ // approximate using great circle distance
+ qreal distanceToNorthPole = center.distanceTo(northPole);
+ qreal distanceToSouthPole = center.distanceTo(southPole);
+ if (distanceToNorthPole < distance || distanceToSouthPole < distance)
+ return true;
+ return false;
+}
+
+static void calculatePeripheralPoints(QList<QGeoCoordinate> &path,
+ const QGeoCoordinate &center,
+ qreal distance,
+ int steps)
+{
+ // Calculate points based on great-circle distance
+ // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function
+ // but tweaked here for computing multiple points
+
+ // pre-calculations
+ steps = qMax(steps, 3);
+ qreal centerLon = center.longitude();
+ qreal latRad = QLocationUtils::radians(center.latitude());
+ qreal lonRad = QLocationUtils::radians(centerLon);
+ qreal cosLatRad = std::cos(latRad);
+ qreal sinLatRad = std::sin(latRad);
+ qreal ratio = (distance / (QLocationUtils::earthMeanRadius()));
+ qreal cosRatio = std::cos(ratio);
+ qreal sinRatio = std::sin(ratio);
+ qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio;
+ qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio;
+ for (int i = 0; i < steps; ++i) {
+ qreal azimuthRad = 2 * M_PI * i / steps;
+ qreal resultLatRad = std::asin(sinLatRad_x_cosRatio
+ + cosLatRad_x_sinRatio * std::cos(azimuthRad));
+ qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio,
+ cosRatio - sinLatRad * std::sin(resultLatRad));
+ qreal lat2 = QLocationUtils::degrees(resultLatRad);
+ qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad));
+
+ path << QGeoCoordinate(lat2, lon2, center.altitude());
+ }
+}
+
+QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent)
+: QDeclarativeGeoMapItemBase(parent), color_(Qt::transparent), dirtyMaterial_(true),
+ updatingGeometry_(false)
+{
+ setFlag(ItemHasContents, true);
+ QObject::connect(&border_, SIGNAL(colorChanged(QColor)),
+ this, SLOT(markSourceDirtyAndUpdate()));
+ QObject::connect(&border_, SIGNAL(widthChanged(qreal)),
+ this, SLOT(markSourceDirtyAndUpdate()));
+
+ // assume that circles are not self-intersecting
+ // to speed up processing
+ // FIXME: unfortunately they self-intersect at the poles due to current drawing method
+ // so the line is commented out until fixed
+ //geometry_.setAssumeSimple(true);
+
+}
+
+QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem()
+{
+}
+
+/*!
+ \qmlpropertygroup Location::MapCircle::border
+ \qmlproperty int MapCircle::border.width
+ \qmlproperty color MapCircle::border.color
+
+ This property is part of the border group property.
+ The border property holds the width and color used to draw the border of the circle.
+ The width is in pixels and is independent of the zoom level of the map.
+
+ The default values correspond to a black border with a width of 1 pixel.
+ For no line, use a width of 0 or a transparent color.
+*/
+QDeclarativeMapLineProperties *QDeclarativeCircleMapItem::border()
+{
+ return &border_;
+}
+
+void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate()
+{
+ geometry_.markSourceDirty();
+ borderGeometry_.markSourceDirty();
+ polishAndUpdate();
+}
+
+void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map)
+{
+ QDeclarativeGeoMapItemBase::setMap(quickMap,map);
+ if (map)
+ markSourceDirtyAndUpdate();
+}
+
+/*!
+ \qmlproperty coordinate MapCircle::center
+
+ This property holds the central point about which the circle is defined.
+
+ \sa radius
+*/
+void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate &center)
+{
+ if (circle_.center() == center)
+ return;
+
+ circle_.setCenter(center);
+ markSourceDirtyAndUpdate();
+ emit centerChanged(center);
+}
+
+QGeoCoordinate QDeclarativeCircleMapItem::center()
+{
+ return circle_.center();
+}
+
+/*!
+ \qmlproperty color MapCircle::color
+
+ This property holds the fill color of the circle when drawn. For no fill,
+ use a transparent color.
+*/
+void QDeclarativeCircleMapItem::setColor(const QColor &color)
+{
+ if (color_ == color)
+ return;
+ color_ = color;
+ dirtyMaterial_ = true;
+ update();
+ emit colorChanged(color_);
+}
+
+QColor QDeclarativeCircleMapItem::color() const
+{
+ return color_;
+}
+
+/*!
+ \qmlproperty real MapCircle::radius
+
+ This property holds the radius of the circle, in meters on the ground.
+
+ \sa center
+*/
+void QDeclarativeCircleMapItem::setRadius(qreal radius)
+{
+ if (circle_.radius() == radius)
+ return;
+
+ circle_.setRadius(radius);
+ markSourceDirtyAndUpdate();
+ emit radiusChanged(radius);
+}
+
+qreal QDeclarativeCircleMapItem::radius() const
+{
+ return circle_.radius();
+}
+
+/*!
+ \qmlproperty real MapCircle::opacity
+
+ This property holds the opacity of the item. Opacity is specified as a
+ number between 0 (fully transparent) and 1 (fully opaque). The default is 1.
+
+ An item with 0 opacity will still receive mouse events. To stop mouse events, set the
+ visible property of the item to false.
+*/
+
+/*!
+ \internal
+*/
+QSGNode *QDeclarativeCircleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
+{
+ Q_UNUSED(data);
+
+ MapPolygonNode *node = static_cast<MapPolygonNode *>(oldNode);
+
+ if (!node)
+ node = new MapPolygonNode();
+
+ //TODO: update only material
+ if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) {
+ node->update(color_, border_.color(), &geometry_, &borderGeometry_);
+ geometry_.setPreserveGeometry(false);
+ borderGeometry_.setPreserveGeometry(false);
+ geometry_.markClean();
+ borderGeometry_.markClean();
+ dirtyMaterial_ = false;
+ }
+ return node;
+}
+
+/*!
+ \internal
+*/
+void QDeclarativeCircleMapItem::updatePolish()
+{
+ if (!map() || !circle_.isValid())
+ return;
+
+ QScopedValueRollback<bool> rollback(updatingGeometry_);
+ updatingGeometry_ = true;
+
+ if (geometry_.isSourceDirty()) {
+ circlePath_.clear();
+ calculatePeripheralPoints(circlePath_, circle_.center(), circle_.radius(), CircleSamples);
+ }
+
+ QList<QGeoCoordinate> originalCirclePath = circlePath_;
+
+ int pathCount = circlePath_.size();
+ bool preserve = preserveCircleGeometry(circlePath_, circle_.center(), circle_.radius());
+ geometry_.setPreserveGeometry(true, circle_.boundingGeoRectangle().topLeft()); // to set the geoLeftBound_
+ geometry_.setPreserveGeometry(preserve, circle_.boundingGeoRectangle().topLeft());
+
+ bool invertedCircle = false;
+ if (crossEarthPole(circle_.center(), circle_.radius()) && circlePath_.size() == pathCount) {
+ geometry_.updateScreenPointsInvert(circlePath_, *map()); // invert fill area for really huge circles
+ invertedCircle = true;
+ } else {
+ geometry_.updateSourcePoints(*map(), circlePath_);
+ geometry_.updateScreenPoints(*map());
+ }
+
+ borderGeometry_.clear();
+ QList<QGeoMapItemGeometry *> geoms;
+ geoms << &geometry_;
+
+ if (border_.color() != Qt::transparent && border_.width() > 0) {
+ QList<QGeoCoordinate> closedPath = circlePath_;
+ closedPath << closedPath.first();
+
+ if (invertedCircle) {
+ closedPath = originalCirclePath;
+ closedPath << closedPath.first();
+ std::reverse(closedPath.begin(), closedPath.end());
+ }
+
+ borderGeometry_.setPreserveGeometry(true, circle_.boundingGeoRectangle().topLeft()); // to set the geoLeftBound_
+ borderGeometry_.setPreserveGeometry(preserve, circle_.boundingGeoRectangle().topLeft());
+
+ // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail.
+ const QGeoCoordinate &geometryOrigin = geometry_.origin();
+
+ borderGeometry_.srcPoints_.clear();
+ borderGeometry_.srcPointTypes_.clear();
+
+ QDoubleVector2D borderLeftBoundWrapped;
+ QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped);
+ if (clippedPaths.size()) {
+ borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin);
+ borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped);
+ borderGeometry_.updateScreenPoints(*map(), border_.width());
+ geoms << &borderGeometry_;
+ } else {
+ borderGeometry_.clear();
+ }
+ }
+
+ QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ setWidth(combined.width());
+ setHeight(combined.height());
+
+ setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset());
+}
+
+/*!
+ \internal
+*/
+void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
+{
+ if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
+ return;
+
+ markSourceDirtyAndUpdate();
+}
+
+/*!
+ \internal
+*/
+bool QDeclarativeCircleMapItem::contains(const QPointF &point) const
+{
+ return (geometry_.contains(point) || borderGeometry_.contains(point));
+}
+
+const QGeoShape &QDeclarativeCircleMapItem::geoShape() const
+{
+ return circle_;
+}
+
+/*!
+ \internal
+*/
+void QDeclarativeCircleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ if (updatingGeometry_ || newGeometry == oldGeometry) {
+ QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry);
+ return;
+ }
+
+ QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) / 2;
+ QGeoCoordinate newCoordinate = map()->geoProjection().itemPositionToCoordinate(newPoint, false);
+ if (newCoordinate.isValid())
+ setCenter(newCoordinate);
+
+ // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested
+ // call to this function.
+}
+
+bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QGeoCoordinate> &path,
+ const QGeoCoordinate &center, qreal distance)
+{
+ // if circle crosses north/south pole, then don't preserve circular shape,
+ if ( crossEarthPole(center, distance)) {
+ updateCirclePathForRendering(path, center, distance);
+ return false;
+ }
+ return true;
+
+}
+
+/*
+ * A workaround for circle path to be drawn correctly using a polygon geometry
+ * This method generates a polygon like
+ * _____________
+ * | |
+ * \ /
+ * | |
+ * / \
+ * | |
+ * -------------
+ *
+ * or a polygon like
+ *
+ * ______________
+ * | ____ |
+ * \__/ \__/
+ */
+void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QGeoCoordinate> &path,
+ const QGeoCoordinate &center,
+ qreal distance)
+{
+ qreal poleLat = 90;
+ qreal distanceToNorthPole = center.distanceTo(QGeoCoordinate(poleLat, 0));
+ qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0));
+ bool crossNorthPole = distanceToNorthPole < distance;
+ bool crossSouthPole = distanceToSouthPole < distance;
+
+ QList<int> wrapPathIndex;
+ QDoubleVector2D prev = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(0)));
+
+ for (int i = 1; i <= path.count(); ++i) {
+ int index = i % path.count();
+ QDoubleVector2D point = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(index)));
+ double diff = qAbs(point.x() - prev.x());
+ if (diff > 0.5) {
+ continue;
+ }
+ }
+
+ // find the points in path where wrapping occurs
+ for (int i = 1; i <= path.count(); ++i) {
+ int index = i % path.count();
+ QDoubleVector2D point = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(index)));
+ if ( (qAbs(point.x() - prev.x())) >= 0.5 ) { // TODO: Add a projectionWidth to GeoProjection, perhaps?
+ wrapPathIndex << index;
+ if (wrapPathIndex.size() == 2 || !(crossNorthPole && crossSouthPole))
+ break;
+ }
+ prev = point;
+ }
+ // insert two additional coords at top/bottom map corners of the map for shape
+ // to be drawn correctly
+ if (wrapPathIndex.size() > 0) {
+ qreal newPoleLat = 90;
+ QGeoCoordinate wrapCoord = path.at(wrapPathIndex[0]);
+ if (wrapPathIndex.size() == 2) {
+ QGeoCoordinate wrapCoord2 = path.at(wrapPathIndex[1]);
+ if (wrapCoord2.latitude() > wrapCoord.latitude())
+ newPoleLat = -90;
+ } else if (center.latitude() < 0) {
+ newPoleLat = -90;
+ }
+ for (int i = 0; i < wrapPathIndex.size(); ++i) {
+ int index = wrapPathIndex[i] == 0 ? 0 : wrapPathIndex[i] + i*2;
+ int prevIndex = (index - 1) < 0 ? (path.count() - 1): index - 1;
+ QGeoCoordinate coord0 = path.at(prevIndex);
+ QGeoCoordinate coord1 = path.at(index);
+ coord0.setLatitude(newPoleLat);
+ coord1.setLatitude(newPoleLat);
+ path.insert(index ,coord1);
+ path.insert(index, coord0);
+ newPoleLat = -newPoleLat;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+QT_END_NAMESPACE