diff options
Diffstat (limited to 'src/location/declarativemaps/qdeclarativecirclemapitem.cpp')
-rw-r--r-- | src/location/declarativemaps/qdeclarativecirclemapitem.cpp | 728 |
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 ¢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; +} + +static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, + const QGeoCoordinate ¢er, + 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 ¢er) +{ + 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 ¢er, 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 ¢er, + 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 |