diff options
Diffstat (limited to 'src/location/quickmapitems/qdeclarativecirclemapitem.cpp')
-rw-r--r-- | src/location/quickmapitems/qdeclarativecirclemapitem.cpp | 714 |
1 files changed, 714 insertions, 0 deletions
diff --git a/src/location/quickmapitems/qdeclarativecirclemapitem.cpp b/src/location/quickmapitems/qdeclarativecirclemapitem.cpp new file mode 100644 index 00000000..2bec3455 --- /dev/null +++ b/src/location/quickmapitems/qdeclarativecirclemapitem.cpp @@ -0,0 +1,714 @@ +/*************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativecirclemapitem_p.h" +#include "qdeclarativepolygonmapitem_p.h" +#include "qdeclarativecirclemapitem_p_p.h" + +#include <QtCore/QScopedValueRollback> +#include <QPen> +#include <QPainter> +#include <qgeocircle.h> + +#include <QtGui/private/qtriangulator_p.h> +#include <QtLocation/private/qgeomap_p.h> +#include <QtPositioning/private/qlocationutils_p.h> +#include <QtPositioning/private/qclipperutils_p.h> + +#include <qmath.h> +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapCircle + \instantiates QDeclarativeCircleMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since QtLocation 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 +*/ + +/*! + \qmlproperty bool QtLocation::MapCircle::autoFadeIn + + This property holds whether the item automatically fades in when zooming into the map + starting from very low zoom levels. By default this is \c true. + Setting this property to \c false causes the map item to always have the opacity specified + with the \l QtQuick::Item::opacity property, which is 1.0 by default. + + \since 5.14 +*/ + +struct Vertex +{ + QVector2D position; +}; + +QGeoMapCircleGeometry::QGeoMapCircleGeometry() +{ +} + +/*! + \internal +*/ +void QGeoMapCircleGeometry::updateScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map) +{ + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + // 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) + const double topLati = QLocationUtils::mercatorMaxLatitude(); + const double bottomLati = -(QLocationUtils::mercatorMaxLatitude()); + const double leftLongi = QLocationUtils::mapLeftLongitude(map.cameraData().center().longitude()); + const double rightLongi = QLocationUtils::mapRightLongitude(map.cameraData().center().longitude()); + + srcOrigin_ = QGeoCoordinate(topLati,leftLongi); + const QDoubleVector2D tl = p.geoToWrappedMapProjection(QGeoCoordinate(topLati,leftLongi)); + const QDoubleVector2D tr = p.geoToWrappedMapProjection(QGeoCoordinate(topLati,rightLongi)); + const QDoubleVector2D br = p.geoToWrappedMapProjection(QGeoCoordinate(bottomLati,rightLongi)); + const QDoubleVector2D bl = p.geoToWrappedMapProjection(QGeoCoordinate(bottomLati,leftLongi)); + + QList<QDoubleVector2D> fill; + fill << tl << tr << br << bl; + + QList<QDoubleVector2D> hole; + for (const QDoubleVector2D &c: circlePath) + hole << p.wrapMapProjection(c); + + QClipperUtils clipper; + clipper.addSubjectPath(fill, true); + clipper.addClipPolygon(hole); + auto difference = clipper.execute(QClipperUtils::Difference, QClipperUtils::pftEvenOdd, + QClipperUtils::pftEvenOdd); + + // 2) + QDoubleVector2D lb = p.geoToWrappedMapProjection(srcOrigin_); + QList<QList<QDoubleVector2D> > clippedPaths; + const QList<QDoubleVector2D> &visibleRegion = p.visibleGeometry(); + if (visibleRegion.size()) { + clipper.clearClipper(); + for (const auto &p: difference) + clipper.addSubjectPath(p, true); + clipper.addClipPolygon(visibleRegion); + clippedPaths = clipper.execute(QClipperUtils::Intersection, QClipperUtils::pftEvenOdd, + QClipperUtils::pftEvenOdd); + + // 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_ = p.mapProjectionToGeo(p.unwrapMapProjection(lb)); + } else { + clippedPaths = difference; + } + + //3) + const QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(lb); + + QPainterPath ppi; + for (const QList<QDoubleVector2D> &path: clippedPaths) { + QDoubleVector2D lastAddedPoint; + for (qsizetype i = 0; i < path.size(); ++i) { + QDoubleVector2D point = p.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()); + + 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 (qsizetype i = 0; i < (ts.indices.size()/3*3); ++i) + screenIndices_ << ix[i]; + } else { + const quint16 *ix = reinterpret_cast<const quint16 *>(ts.indices.data()); + for (qsizetype i = 0; i < (ts.indices.size()/3*3); ++i) + screenIndices_ << ix[i]; + } + for (qsizetype i = 0; i < (ts.vertices.size()/2*2); i += 2) + screenVertices_ << QPointF(vx[i], vx[i + 1]); + + screenBounds_ = ppi.boundingRect(); + sourceBounds_ = screenBounds_; +} + +struct CircleBackendSelector +{ + CircleBackendSelector() + { + backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativeCircleMapItem::OpenGL : QDeclarativeCircleMapItem::Software; + } + QDeclarativeCircleMapItem::Backend backend = QDeclarativeCircleMapItem::Software; +}; + +Q_GLOBAL_STATIC(CircleBackendSelector, mapCircleBackendSelector) + +QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true), + m_updatingGeometry(false) + , m_d(new QDeclarativeCircleMapItemPrivateCPU(*this)) +{ + // ToDo: handle envvar, and switch implementation. + m_itemType = QGeoMap::MapCircle; + setFlag(ItemHasContents, true); + QObject::connect(&m_border, &QDeclarativeMapLineProperties::colorChanged, + this, &QDeclarativeCircleMapItem::onLinePropertiesChanged); + QObject::connect(&m_border, &QDeclarativeMapLineProperties::widthChanged, + this, &QDeclarativeCircleMapItem::onLinePropertiesChanged); + + // 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); + setBackend(mapCircleBackendSelector->backend); +} + +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 &m_border; +} + +void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate() +{ + m_d->markSourceDirtyAndUpdate(); +} + +void QDeclarativeCircleMapItem::onLinePropertiesChanged() +{ + m_d->onLinePropertiesChanged(); +} + +void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map) + m_d->onMapSet(); +} + +/*! + \qmlproperty coordinate MapCircle::center + + This property holds the central point about which the circle is defined. + + \sa radius +*/ +void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate ¢er) +{ + if (m_circle.center() == center) + return; + + possiblySwitchBackend(m_circle.center(), m_circle.radius(), center, m_circle.radius()); + m_circle.setCenter(center); + m_d->onGeoGeometryChanged(); + emit centerChanged(center); +} + +QGeoCoordinate QDeclarativeCircleMapItem::center() +{ + return m_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 (m_color == color) + return; + m_color = color; + m_dirtyMaterial = true; + update(); + emit colorChanged(m_color); +} + +QColor QDeclarativeCircleMapItem::color() const +{ + return m_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 (m_circle.radius() == radius) + return; + + possiblySwitchBackend(m_circle.center(), m_circle.radius(), m_circle.center(), radius); + m_circle.setRadius(radius); + m_d->onGeoGeometryChanged(); + emit radiusChanged(radius); +} + +qreal QDeclarativeCircleMapItem::radius() const +{ + return m_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) +{ + return m_d->updateMapItemPaintNode(oldNode, data); +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::updatePolish() +{ + if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) + return; + m_d->updatePolish(); +} + +/*! + \internal + + The OpenGL backend doesn't do circles crossing poles yet. + So if that backend is selected and the circle crosses the poles, use the CPU backend instead. +*/ +void QDeclarativeCircleMapItem::possiblySwitchBackend(const QGeoCoordinate &oldCenter, qreal oldRadius, const QGeoCoordinate &newCenter, qreal newRadius) +{ + if (m_backend != QDeclarativeCircleMapItem::OpenGL) + return; + + // if old does not cross and new crosses, move to CPU. + if (!QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius) + && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) { + std::unique_ptr<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this))); + std::swap(m_d, d); + } else if (QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius) + && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) { // else if old crosses and new does not cross, move back to OpenGL + std::unique_ptr<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateOpenGL(*this))); + std::swap(m_d, d); + } +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.isEmpty()) + return; + + m_d->afterViewportChanged(); +} + +/*! + \internal +*/ +bool QDeclarativeCircleMapItem::contains(const QPointF &point) const +{ + return m_d->contains(point); + // +} + +const QGeoShape &QDeclarativeCircleMapItem::geoShape() const +{ + return m_circle; +} + +void QDeclarativeCircleMapItem::setGeoShape(const QGeoShape &shape) +{ + if (shape == m_circle) + return; + + const QGeoCircle circle(shape); // if shape isn't a circle, circle will be created as a default-constructed circle + const bool centerHasChanged = circle.center() != m_circle.center(); + const bool radiusHasChanged = circle.radius() != m_circle.radius(); + possiblySwitchBackend(m_circle.center(), m_circle.radius(), circle.center(), circle.radius()); + m_circle = circle; + + m_d->onGeoGeometryChanged(); + if (centerHasChanged) + emit centerChanged(m_circle.center()); + if (radiusHasChanged) + emit radiusChanged(m_circle.radius()); +} + +/*! + \qmlproperty MapCircle.Backend QtLocation::MapCircle::backend + + This property holds which backend is in use to render the map item. + Valid values are \b MapCircle.Software and \b{MapCircle.OpenGL}. + The default value is \b{MapCircle.Software}. + + \note \b{The release of this API with Qt 5.15 is a Technology Preview}. + Ideally, as the OpenGL backends for map items mature, there will be + no more need to also offer the legacy software-projection backend. + So this property will likely disappear at some later point. + To select OpenGL-accelerated item backends without using this property, + it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS + to \b{1}. + Also note that all current OpenGL backends won't work as expected when enabling + layers on the individual item, or when running on OpenGL core profiles greater than 2.x. + + \since 5.15 +*/ + +QDeclarativeCircleMapItem::Backend QDeclarativeCircleMapItem::backend() const +{ + return m_backend; +} + +void QDeclarativeCircleMapItem::setBackend(QDeclarativeCircleMapItem::Backend b) +{ + if (b == m_backend) + return; + m_backend = b; + std::unique_ptr<QDeclarativeCircleMapItemPrivate> d( + (m_backend == Software) ? static_cast<QDeclarativeCircleMapItemPrivate *>( + new QDeclarativeCircleMapItemPrivateCPU(*this)) + : static_cast<QDeclarativeCircleMapItemPrivate *>( + new QDeclarativeCircleMapItemPrivateOpenGL(*this))); + std::swap(m_d, d); + m_d->onGeoGeometryChanged(); + emit backendChanged(); +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (!map() || !m_circle.isValid() || m_updatingGeometry || newGeometry == oldGeometry) { + QDeclarativeGeoMapItemBase::geometryChange(newGeometry, oldGeometry); + return; + } + + QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) * 0.5; + QGeoCoordinate newCoordinate = map()->geoProjection().itemPositionToCoordinate(newPoint, false); + if (newCoordinate.isValid()) + setCenter(newCoordinate); // ToDo: this is incorrect. setting such center might yield to another geometry changed. + + // Not calling QDeclarativeGeoMapItemBase::geometryChange() as it will be called from a nested + // call to this function. +} + +QDeclarativeCircleMapItemPrivate::~QDeclarativeCircleMapItemPrivate() {} + +QDeclarativeCircleMapItemPrivateCPU::~QDeclarativeCircleMapItemPrivateCPU() {} + +QDeclarativeCircleMapItemPrivateOpenGL::~QDeclarativeCircleMapItemPrivateOpenGL() {} + +bool QDeclarativeCircleMapItemPrivate::preserveCircleGeometry (QList<QDoubleVector2D> &path, + const QGeoCoordinate ¢er, qreal distance, const QGeoProjectionWebMercator &p) +{ + // if circle crosses north/south pole, then don't preserve circular shape, + if ( crossEarthPole(center, distance)) { + updateCirclePathForRendering(path, center, distance, p); + 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 QDeclarativeCircleMapItemPrivate::updateCirclePathForRendering(QList<QDoubleVector2D> &path, + const QGeoCoordinate ¢er, + qreal distance, const QGeoProjectionWebMercator &p) +{ + const qreal poleLat = 90; + const qreal distanceToNorthPole = center.distanceTo(QGeoCoordinate(poleLat, 0)); + const qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0)); + bool crossNorthPole = distanceToNorthPole < distance; + bool crossSouthPole = distanceToSouthPole < distance; + + QList<qsizetype> wrapPathIndex; + QDoubleVector2D prev = p.wrapMapProjection(path.at(0)); + + for (qsizetype i = 1; i <= path.count(); ++i) { + const auto index = i % path.count(); + const QDoubleVector2D point = p.wrapMapProjection(path.at(index)); + double diff = qAbs(point.x() - prev.x()); + if (diff > 0.5) { + continue; + } + } + + // find the points in path where wrapping occurs + for (qsizetype i = 1; i <= path.count(); ++i) { + const auto index = i % path.count(); + const QDoubleVector2D point = p.wrapMapProjection(path.at(index)); + if ((qAbs(point.x() - prev.x())) >= 0.5) { + 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 = 0; // 90 latitude + QDoubleVector2D wrapCoord = path.at(wrapPathIndex[0]); + if (wrapPathIndex.size() == 2) { + QDoubleVector2D wrapCoord2 = path.at(wrapPathIndex[1]); + if (wrapCoord2.y() < wrapCoord.y()) + newPoleLat = 1; // -90 latitude + } else if (center.latitude() < 0) { + newPoleLat = 1; // -90 latitude + } + for (qsizetype i = 0; i < wrapPathIndex.size(); ++i) { + const qsizetype index = wrapPathIndex[i] == 0 ? 0 : wrapPathIndex[i] + i*2; + const qsizetype prevIndex = (index - 1) < 0 ? (path.count() - 1) : index - 1; + QDoubleVector2D coord0 = path.at(prevIndex); + QDoubleVector2D coord1 = path.at(index); + coord0.setY(newPoleLat); + coord1.setY(newPoleLat); + path.insert(index ,coord1); + path.insert(index, coord0); + newPoleLat = 1.0 - newPoleLat; + } + } +} + +bool QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate ¢er, 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; +} + +void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoordinate> &path, + const QGeoCoordinate ¢er, + qreal distance, + int steps, + QGeoCoordinate &leftBound) +{ + // 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 minLon = centerLon; + 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; + int idx = 0; + for (int i = 0; i < steps; ++i) { + const qreal azimuthRad = 2 * M_PI * i / steps; + const qreal resultLatRad = std::asin(sinLatRad_x_cosRatio + + cosLatRad_x_sinRatio * std::cos(azimuthRad)); + const qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio, + cosRatio - sinLatRad * std::sin(resultLatRad)); + const qreal lat2 = QLocationUtils::degrees(resultLatRad); + qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad)); + + path << QGeoCoordinate(lat2, lon2, center.altitude()); + // Consider only points in the left half of the circle for the left bound. + if (azimuthRad > M_PI) { + if (lon2 > centerLon) // if point and center are on different hemispheres + lon2 -= 360; + if (lon2 < minLon) { + minLon = lon2; + idx = i; + } + } + } + leftBound = path.at(idx); +} + +////////////////////////////////////////////////////////////////////// + +QT_END_NAMESPACE |