From 92d8e4dc93400250b47f8dbe96ec7e2f748d8d4b Mon Sep 17 00:00:00 2001 From: Paolo Angelelli Date: Tue, 11 Jul 2017 15:55:27 +0200 Subject: Add QGeoPolygon to QtPositioning This patch introduces a new QGeoShape, QGeoPolygon, together with helper functions in the location singleton (QtPositioning.*) to create and convert geopolygons from QML. [ChangeLog][QtPositioning][QGeoPolygon] Added QGeoPolygon shape. Change-Id: I111c576d7428f2a953f0459d16c25eea7ab2bd7c Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/3rdparty/clip2tri/clip2tri.cpp | 5 + src/3rdparty/clip2tri/clip2tri.h | 2 + src/imports/positioning/locationsingleton.cpp | 43 +++ src/imports/positioning/locationsingleton.h | 5 + src/positioning/positioning.pro | 3 + src/positioning/qgeopath.cpp | 73 ++++- src/positioning/qgeopath_p.h | 11 +- src/positioning/qgeopolygon.cpp | 326 +++++++++++++++++++++ src/positioning/qgeopolygon.h | 99 +++++++ src/positioning/qgeoshape.cpp | 23 ++ src/positioning/qgeoshape.h | 3 +- tests/auto/auto.pro | 1 + tests/auto/qgeopolygon/qgeopolygon.pro | 8 + tests/auto/qgeopolygon/tst_qgeopolygon.cpp | 402 ++++++++++++++++++++++++++ 14 files changed, 988 insertions(+), 16 deletions(-) create mode 100644 src/positioning/qgeopolygon.cpp create mode 100644 src/positioning/qgeopolygon.h create mode 100644 tests/auto/qgeopolygon/qgeopolygon.pro create mode 100644 tests/auto/qgeopolygon/tst_qgeopolygon.cpp diff --git a/src/3rdparty/clip2tri/clip2tri.cpp b/src/3rdparty/clip2tri/clip2tri.cpp index e715d1c7..db4911c1 100644 --- a/src/3rdparty/clip2tri/clip2tri.cpp +++ b/src/3rdparty/clip2tri/clip2tri.cpp @@ -173,6 +173,11 @@ Paths clip2tri::execute(const clip2tri::Operation op, const PolyFillType subjFil return solution; } +int clip2tri::pointInPolygon(const IntPoint &pt, const Path &path) +{ + return PointInPolygon(pt, path); +} + Path clip2tri::upscaleClipperPoints(const vector &inputPolygon) { Path outputPolygon; diff --git a/src/3rdparty/clip2tri/clip2tri.h b/src/3rdparty/clip2tri/clip2tri.h index 61c8a0f5..3848d009 100644 --- a/src/3rdparty/clip2tri/clip2tri.h +++ b/src/3rdparty/clip2tri/clip2tri.h @@ -91,6 +91,8 @@ public: const PolyFillType subjFillType = pftNonZero, const PolyFillType clipFillType = pftNonZero); + static int pointInPolygon(const IntPoint &pt, const Path &path); + Clipper clipper; bool openSubject; }; diff --git a/src/imports/positioning/locationsingleton.cpp b/src/imports/positioning/locationsingleton.cpp index 19b05761..e9b58834 100644 --- a/src/imports/positioning/locationsingleton.cpp +++ b/src/imports/positioning/locationsingleton.cpp @@ -233,6 +233,37 @@ QGeoPath LocationSingleton::path(const QJSValue &value, qreal width) const return QGeoPath(pathList, width); } +/*! + \qmlmethod geopolygon QtPositioning::polygon() const + + Constructs an empty geopolygon. + + \sa {geopolygon} + \since 5.10 +*/ +QGeoPath LocationSingleton::polygon() const +{ + return QGeoPolygon(); +} + +/*! + \qmlmethod geopolygon QtPositioning::polygon(list coordinates) const + + Constructs a geopolygon from coordinates. + + \sa {geopolygon} + \since 5.10 +*/ +QGeoPath LocationSingleton::polygon(const QVariantList &coordinates) const +{ + QList internalCoordinates; + for (int i = 0; i < coordinates.size(); i++) { + if (coordinates.at(i).canConvert()) + internalCoordinates << coordinates.at(i).value(); + } + return QGeoPolygon(internalCoordinates); +} + /*! \qmlmethod geocircle QtPositioning::shapeToCircle(geoshape shape) const @@ -272,3 +303,15 @@ QGeoPath LocationSingleton::shapeToPath(const QGeoShape &shape) const return QGeoPath(shape); } +/*! + \qmlmethod geopath QtPositioning::shapeToPolygon(geoshape shape) const + + Converts \a shape to a geopolygon. + + \sa {geopolygon} + \since 5.10 +*/ +QGeoPolygon LocationSingleton::shapeToPolygon(const QGeoShape &shape) const +{ + return QGeoPolygon(shape); +} diff --git a/src/imports/positioning/locationsingleton.h b/src/imports/positioning/locationsingleton.h index 4faf2738..6a560fa8 100644 --- a/src/imports/positioning/locationsingleton.h +++ b/src/imports/positioning/locationsingleton.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -76,9 +77,13 @@ public: Q_INVOKABLE QGeoPath path() const; Q_INVOKABLE QGeoPath path(const QJSValue &value, qreal width = 0.0) const; + Q_INVOKABLE QGeoPath polygon() const; + Q_INVOKABLE QGeoPath polygon(const QVariantList &value) const; + Q_INVOKABLE QGeoCircle shapeToCircle(const QGeoShape &shape) const; Q_INVOKABLE QGeoRectangle shapeToRectangle(const QGeoShape &shape) const; Q_INVOKABLE QGeoPath shapeToPath(const QGeoShape &shape) const; + Q_INVOKABLE QGeoPolygon shapeToPolygon(const QGeoShape &shape) const; }; #endif // LOCATIONSINGLETON_H diff --git a/src/positioning/positioning.pro b/src/positioning/positioning.pro index 32f3c05c..974a8d48 100644 --- a/src/positioning/positioning.pro +++ b/src/positioning/positioning.pro @@ -38,6 +38,7 @@ PUBLIC_HEADERS += \ qnmeapositioninfosource.h \ qgeopositioninfosourcefactory.h \ qpositioningglobal.h \ + qgeopolygon.h \ qgeopath.h \ PRIVATE_HEADERS += \ @@ -59,6 +60,7 @@ PRIVATE_HEADERS += \ qlocationdata_simulator_p.h \ qdoublematrix4x4_p.h \ qgeopath_p.h \ + qgeopolygon_p.h \ qclipperutils_p.h SOURCES += \ @@ -82,6 +84,7 @@ SOURCES += \ qdoublevector2d.cpp \ qdoublevector3d.cpp \ qgeopath.cpp \ + qgeopolygon.cpp \ qlocationdata_simulator.cpp \ qwebmercator.cpp \ qdoublematrix4x4.cpp \ diff --git a/src/positioning/qgeopath.cpp b/src/positioning/qgeopath.cpp index 858dc0bd..5e7a4077 100644 --- a/src/positioning/qgeopath.cpp +++ b/src/positioning/qgeopath.cpp @@ -109,7 +109,7 @@ Q_GLOBAL_STATIC(PathVariantConversions, initPathConversions) Constructs a new, empty geo path. */ QGeoPath::QGeoPath() -: QGeoShape(new QGeoPathPrivate) +: QGeoShape(new QGeoPathPrivate(QGeoShape::PathType)) { initPathConversions(); } @@ -119,7 +119,7 @@ QGeoPath::QGeoPath() (\a path and \a width). */ QGeoPath::QGeoPath(const QList &path, const qreal &width) -: QGeoShape(new QGeoPathPrivate(path, width)) +: QGeoShape(new QGeoPathPrivate(QGeoShape::PathType, path, width)) { initPathConversions(); } @@ -141,7 +141,7 @@ QGeoPath::QGeoPath(const QGeoShape &other) { initPathConversions(); if (type() != QGeoShape::PathType) - d_ptr = new QGeoPathPrivate; + d_ptr = new QGeoPathPrivate(QGeoShape::PathType); } /*! @@ -341,22 +341,22 @@ QString QGeoPath::toString() const * QGeoPathPrivate *******************************************************************************/ -QGeoPathPrivate::QGeoPathPrivate() -: QGeoShapePrivate(QGeoShape::PathType), m_width(0) +QGeoPathPrivate::QGeoPathPrivate(QGeoShape::ShapeType type) +: QGeoShapePrivate(type), m_width(0), m_clipperDirty(true) { } -QGeoPathPrivate::QGeoPathPrivate(const QList &path, const qreal width) -: QGeoShapePrivate(QGeoShape::PathType), m_width(0) +QGeoPathPrivate::QGeoPathPrivate(QGeoShape::ShapeType type, const QList &path, const qreal width) +: QGeoShapePrivate(type), m_width(0), m_clipperDirty(true) { setPath(path); setWidth(width); } QGeoPathPrivate::QGeoPathPrivate(const QGeoPathPrivate &other) -: QGeoShapePrivate(QGeoShape::PathType), m_path(other.m_path), +: QGeoShapePrivate(other.type), m_path(other.m_path), m_deltaXs(other.m_deltaXs), m_minX(other.m_minX), m_maxX(other.m_maxX), m_minLati(other.m_minLati), - m_maxLati(other.m_maxLati), m_bbox(other.m_bbox), m_width(other.m_width) + m_maxLati(other.m_maxLati), m_bbox(other.m_bbox), m_width(other.m_width), m_clipperDirty(true) { } @@ -375,17 +375,25 @@ bool QGeoPathPrivate::operator==(const QGeoShapePrivate &other) const const QGeoPathPrivate &otherPath = static_cast(other); if (m_path.size() != otherPath.m_path.size()) return false; - return m_width == otherPath.m_width && m_path == otherPath.m_path; + + if (type == QGeoShape::PathType) + return m_width == otherPath.m_width && m_path == otherPath.m_path; + else + return m_path == otherPath.m_path; } bool QGeoPathPrivate::isValid() const { - return !isEmpty(); + if (type == QGeoShape::PathType) + return !isEmpty(); + else + return m_path.size() > 2; + } bool QGeoPathPrivate::isEmpty() const { - return m_path.isEmpty(); + return m_path.isEmpty(); // this should perhaps return geometric emptiness, less than 2 points for line, or empty polygon for polygons } const QList &QGeoPathPrivate::path() const @@ -416,6 +424,7 @@ void QGeoPathPrivate::setWidth(const qreal &width) double QGeoPathPrivate::length(int indexFrom, int indexTo) const { + bool wrap = indexTo == -1; if (indexTo < 0 || indexTo >= path().size()) indexTo = path().size() - 1; double len = 0.0; @@ -423,6 +432,8 @@ double QGeoPathPrivate::length(int indexFrom, int indexTo) const // instead of the shortest path from A to B. for (int i = indexFrom; i < indexTo; i++) len += m_path[i].distanceTo(m_path[i+1]); + if (wrap) + len += m_path.last().distanceTo(m_path.first()); return len; } @@ -435,6 +446,14 @@ int QGeoPathPrivate::size() const Returns true if coordinate is present in m_path. */ bool QGeoPathPrivate::contains(const QGeoCoordinate &coordinate) const +{ + if (type == QGeoShape::PathType) + return lineContains(coordinate); + else + return polygonContains(coordinate); +} + +bool QGeoPathPrivate::lineContains(const QGeoCoordinate &coordinate) const { // Unoptimized approach: // - consider each segment of the path @@ -503,6 +522,20 @@ bool QGeoPathPrivate::contains(const QGeoCoordinate &coordinate) const return (m_path[0].distanceTo(coordinate) <= lineRadius); } +bool QGeoPathPrivate::polygonContains(const QGeoCoordinate &coordinate) const +{ + if (m_clipperDirty) + const_cast(this)->updateClipperPath(); + + QDoubleVector2D coord = QWebMercator::coordToMercator(coordinate); + double tlx = QWebMercator::coordToMercator(m_bbox.topLeft()).x(); + if (coord.x() < tlx) + coord.setX(coord.x() + 1.0); + + IntPoint intCoord = QClipperUtils::toIntPoint(coord); + return c2t::clip2tri::pointInPolygon(intCoord, m_clipperPath) != 0; +} + QGeoCoordinate QGeoPathPrivate::center() const { return boundingGeoRectangle().center(); @@ -591,6 +624,7 @@ void QGeoPathPrivate::removeCoordinate(int index) void QGeoPathPrivate::computeBoundingBox() { + m_clipperDirty = true; if (m_path.isEmpty()) { m_deltaXs.clear(); m_minX = qInf(); @@ -641,6 +675,7 @@ void QGeoPathPrivate::computeBoundingBox() void QGeoPathPrivate::updateBoundingBox() { + m_clipperDirty = true; if (m_path.isEmpty()) { m_deltaXs.clear(); m_minX = qInf(); @@ -693,4 +728,18 @@ void QGeoPathPrivate::updateBoundingBox() QGeoCoordinate(m_minLati, currentMaxLongi)); } +void QGeoPathPrivate::updateClipperPath() +{ + m_clipperDirty = false; + double tlx = QWebMercator::coordToMercator(m_bbox.topLeft()).x(); + QList preservedPath; + for (const QGeoCoordinate &c : m_path) { + QDoubleVector2D crd = QWebMercator::coordToMercator(c); + if (crd.x() < tlx) + crd.setX(crd.x() + 1.0); + preservedPath << crd; + } + m_clipperPath = QClipperUtils::qListToPath(preservedPath); +} + QT_END_NAMESPACE diff --git a/src/positioning/qgeopath_p.h b/src/positioning/qgeopath_p.h index 48334017..3eceff24 100644 --- a/src/positioning/qgeopath_p.h +++ b/src/positioning/qgeopath_p.h @@ -54,6 +54,7 @@ #include "qgeoshape_p.h" #include "qgeocoordinate.h" #include "qlocationutils_p.h" +#include #include @@ -62,14 +63,16 @@ QT_BEGIN_NAMESPACE class QGeoPathPrivate : public QGeoShapePrivate { public: - QGeoPathPrivate(); - QGeoPathPrivate(const QList &path, const qreal width = 0.0); + QGeoPathPrivate(QGeoShape::ShapeType type); + QGeoPathPrivate(QGeoShape::ShapeType type, const QList &path, const qreal width = 0.0); QGeoPathPrivate(const QGeoPathPrivate &other); ~QGeoPathPrivate(); bool isValid() const Q_DECL_OVERRIDE; bool isEmpty() const Q_DECL_OVERRIDE; bool contains(const QGeoCoordinate &coordinate) const Q_DECL_OVERRIDE; + bool lineContains(const QGeoCoordinate &coordinate) const; + bool polygonContains(const QGeoCoordinate &coordinate) const; QGeoCoordinate center() const Q_DECL_OVERRIDE; QGeoRectangle boundingGeoRectangle() const Q_DECL_OVERRIDE; @@ -95,7 +98,7 @@ public: void removeCoordinate(int index); void computeBoundingBox(); void updateBoundingBox(); - + void updateClipperPath(); QList m_path; QVector m_deltaXs; // longitude deltas from m_path[0] @@ -105,6 +108,8 @@ public: double m_maxLati; // minimum latitude. paths do not wrap around through the poles QGeoRectangle m_bbox; qreal m_width; + bool m_clipperDirty; + QtClipperLib::Path m_clipperPath; }; QT_END_NAMESPACE diff --git a/src/positioning/qgeopolygon.cpp b/src/positioning/qgeopolygon.cpp new file mode 100644 index 00000000..d817ea07 --- /dev/null +++ b/src/positioning/qgeopolygon.cpp @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning 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 "qgeopolygon.h" +#include "qgeopath_p.h" + +#include "qgeocoordinate.h" +#include "qnumeric.h" +#include "qlocationutils_p.h" +#include "qwebmercator_p.h" + +#include "qdoublevector2d_p.h" +#include "qdoublevector3d_p.h" +#include "qwebmercator_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGeoPolygon + \inmodule QtPositioning + \ingroup QtPositioning-positioning + \since 5.10 + + \brief The QGeoPolygon class defines a geographic polygon. + + The polygon is defined by an ordered list of QGeoCoordinates representing its perimeter. + + Each two adjacent elements in this list are intended to be connected + together by the shortest line segment of constant bearing passing + through both elements. + This type of connection can cross the date line in the longitudinal direction, + but never crosses the poles. + + This is relevant for the calculation of the bounding box returned by + \l QGeoShape::boundingGeoRectangle() for this shape, which will have the latitude of + the top left corner set to the maximum latitude in the path point set. + Similarly, the latitude of the bottom right corner will be the minimum latitude + in the path point set. + + This class is a \l Q_GADGET. + It can be \l{Cpp_value_integration_positioning}{directly used from C++ and QML}. +*/ + +/*! + \property QGeoPolygon::path + \brief This property holds the list of coordinates for the geo polygon. + + The polygon is both invalid and empty if it contains no coordinate. + + A default constructed QGeoPolygon is therefore invalid. +*/ + +inline QGeoPolygonPrivate *QGeoPolygon::d_func() +{ + return static_cast(d_ptr.data()); +} + +inline const QGeoPolygonPrivate *QGeoPolygon::d_func() const +{ + return static_cast(d_ptr.constData()); +} + +struct PolygonVariantConversions +{ + PolygonVariantConversions() + { + QMetaType::registerConverter(); + QMetaType::registerConverter(); + } +}; + +Q_GLOBAL_STATIC(PolygonVariantConversions, initPathConversions) + +/*! + Constructs a new, empty geo path. +*/ +QGeoPolygon::QGeoPolygon() +: QGeoShape(new QGeoPolygonPrivate(QGeoShape::PolygonType)) +{ + initPathConversions(); +} + +/*! + Constructs a new geo path from a list of coordinates + (\a path and \a width). +*/ +QGeoPolygon::QGeoPolygon(const QList &path) +: QGeoShape(new QGeoPolygonPrivate(QGeoShape::PolygonType, path)) +{ + initPathConversions(); +} + +/*! + Constructs a new geo path from the contents of \a other. +*/ +QGeoPolygon::QGeoPolygon(const QGeoPolygon &other) +: QGeoShape(other) +{ + initPathConversions(); +} + +/*! + Constructs a new geo path from the contents of \a other. +*/ +QGeoPolygon::QGeoPolygon(const QGeoShape &other) +: QGeoShape(other) +{ + initPathConversions(); + if (type() != QGeoShape::PolygonType) + d_ptr = new QGeoPolygonPrivate(QGeoShape::PolygonType); +} + +/*! + Destroys this path. +*/ +QGeoPolygon::~QGeoPolygon() {} + +/*! + Assigns \a other to this geo path and returns a reference to this geo path. +*/ +QGeoPolygon &QGeoPolygon::operator=(const QGeoPolygon &other) +{ + QGeoShape::operator=(other); + return *this; +} + +/*! + Returns whether this geo path is equal to \a other. +*/ +bool QGeoPolygon::operator==(const QGeoPolygon &other) const +{ + Q_D(const QGeoPolygon); + return *d == *other.d_func(); +} + +/*! + Returns whether this geo path is not equal to \a other. +*/ +bool QGeoPolygon::operator!=(const QGeoPolygon &other) const +{ + Q_D(const QGeoPolygon); + return !(*d == *other.d_func()); +} + +void QGeoPolygon::setPath(const QList &path) +{ + Q_D(QGeoPolygon); + return d->setPath(path); +} + +/*! + Returns all the elements. Equivalent to QGeoShape::center(). + The center coordinate, in case of a QGeoPolygon, is the center of its bounding box. +*/ +const QList &QGeoPolygon::path() const +{ + Q_D(const QGeoPolygon); + return d->path(); +} + +/*! + Translates this geo path by \a degreesLatitude northwards and \a degreesLongitude eastwards. + + Negative values of \a degreesLatitude and \a degreesLongitude correspond to + southward and westward translation respectively. +*/ +void QGeoPolygon::translate(double degreesLatitude, double degreesLongitude) +{ + Q_D(QGeoPolygon); + d->translate(degreesLatitude, degreesLongitude); +} + +/*! + Returns a copy of this geo polygon translated by \a degreesLatitude northwards and + \a degreesLongitude eastwards. + + Negative values of \a degreesLatitude and \a degreesLongitude correspond to + southward and westward translation respectively. + + \sa translate() +*/ +QGeoPolygon QGeoPolygon::translated(double degreesLatitude, double degreesLongitude) const +{ + QGeoPolygon result(*this); + result.translate(degreesLatitude, degreesLongitude); + return result; +} + +/*! + Returns the length of the polygon's perimeter, in meters, from the element \a indexFrom to the element \a indexTo. + The length is intended to be the sum of the shortest distances for each pair of adjacent points. +*/ +double QGeoPolygon::length(int indexFrom, int indexTo) const +{ + Q_D(const QGeoPolygon); + return d->length(indexFrom, indexTo); +} + +/*! + Returns the number of elements in the polygon. + + \since 5.10 +*/ +int QGeoPolygon::size() const +{ + Q_D(const QGeoPolygon); + return d->size(); +} + +/*! + Appends \a coordinate to the polygon. +*/ +void QGeoPolygon::addCoordinate(const QGeoCoordinate &coordinate) +{ + Q_D(QGeoPolygon); + d->addCoordinate(coordinate); +} + +/*! + Inserts \a coordinate at the specified \a index. +*/ +void QGeoPolygon::insertCoordinate(int index, const QGeoCoordinate &coordinate) +{ + Q_D(QGeoPolygon); + d->insertCoordinate(index, coordinate); +} + +/*! + Replaces the path element at the specified \a index with \a coordinate. +*/ +void QGeoPolygon::replaceCoordinate(int index, const QGeoCoordinate &coordinate) +{ + Q_D(QGeoPolygon); + d->replaceCoordinate(index, coordinate); +} + +/*! + Returns the coordinate at \a index . +*/ +QGeoCoordinate QGeoPolygon::coordinateAt(int index) const +{ + Q_D(const QGeoPolygon); + return d->coordinateAt(index); +} + +/*! + Returns true if the polygon's perimeter contains \a coordinate as one of the elements. +*/ +bool QGeoPolygon::containsCoordinate(const QGeoCoordinate &coordinate) const +{ + Q_D(const QGeoPolygon); + return d->containsCoordinate(coordinate); +} + +/*! + Removes the last occurrence of \a coordinate from the polygon. +*/ +void QGeoPolygon::removeCoordinate(const QGeoCoordinate &coordinate) +{ + Q_D(QGeoPolygon); + d->removeCoordinate(coordinate); +} + +/*! + Removes element at position \a index from the polygon. +*/ +void QGeoPolygon::removeCoordinate(int index) +{ + Q_D(QGeoPolygon); + d->removeCoordinate(index); +} + +/*! + Returns the geo path properties as a string. +*/ +QString QGeoPolygon::toString() const +{ + if (type() != QGeoShape::PolygonType) { + qWarning("Not a polygon"); + return QStringLiteral("QGeoPolygon(not a polygon)"); + } + + QString pathString; + for (const auto &p : path()) + pathString += p.toString() + QLatin1Char(','); + + return QStringLiteral("QGeoPolygon([ %1 ])").arg(pathString); +} + +QT_END_NAMESPACE diff --git a/src/positioning/qgeopolygon.h b/src/positioning/qgeopolygon.h new file mode 100644 index 00000000..90886da7 --- /dev/null +++ b/src/positioning/qgeopolygon.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning 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$ +** +****************************************************************************/ + +#ifndef QGEOPOLYGON_H +#define QGEOPOLYGON_H + +#include + +QT_BEGIN_NAMESPACE + +class QGeoCoordinate; +class QGeoPathPrivate; +typedef QGeoPathPrivate QGeoPolygonPrivate; + +class Q_POSITIONING_EXPORT QGeoPolygon : public QGeoShape +{ + Q_GADGET + +public: + QGeoPolygon(); + QGeoPolygon(const QList &path); + QGeoPolygon(const QGeoPolygon &other); + QGeoPolygon(const QGeoShape &other); + + ~QGeoPolygon(); + + QGeoPolygon &operator=(const QGeoPolygon &other); + + using QGeoShape::operator==; + bool operator==(const QGeoPolygon &other) const; + + using QGeoShape::operator!=; + bool operator!=(const QGeoPolygon &other) const; + + void setPath(const QList &path); + const QList &path() const; + + Q_INVOKABLE void translate(double degreesLatitude, double degreesLongitude); + Q_INVOKABLE QGeoPolygon translated(double degreesLatitude, double degreesLongitude) const; + Q_INVOKABLE double length(int indexFrom = 0, int indexTo = -1) const; + Q_INVOKABLE int size() const; + Q_INVOKABLE void addCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void insertCoordinate(int index, const QGeoCoordinate &coordinate); + Q_INVOKABLE void replaceCoordinate(int index, const QGeoCoordinate &coordinate); + Q_INVOKABLE QGeoCoordinate coordinateAt(int index) const; + Q_INVOKABLE bool containsCoordinate(const QGeoCoordinate &coordinate) const; + Q_INVOKABLE void removeCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void removeCoordinate(int index); + + Q_INVOKABLE QString toString() const; + +private: + inline QGeoPolygonPrivate *d_func(); + inline const QGeoPolygonPrivate *d_func() const; +}; + +Q_DECLARE_TYPEINFO(QGeoPolygon, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGeoPolygon) + +#endif // QGEOPOLYGON_H diff --git a/src/positioning/qgeoshape.cpp b/src/positioning/qgeoshape.cpp index ad7b0c58..7acbc9fa 100644 --- a/src/positioning/qgeoshape.cpp +++ b/src/positioning/qgeoshape.cpp @@ -42,6 +42,7 @@ #include "qgeorectangle.h" #include "qgeocircle.h" #include "qgeopath.h" +#include "qgeopolygon.h" #ifndef QT_NO_DEBUG_STREAM @@ -345,6 +346,9 @@ QDebug operator<<(QDebug dbg, const QGeoShape &shape) case QGeoShape::PathType: dbg << "Path"; break; + case QGeoShape::PolygonType: + dbg << "Polygon"; + break; case QGeoShape::CircleType: dbg << "Circle"; } @@ -379,6 +383,13 @@ QDataStream &operator<<(QDataStream &stream, const QGeoShape &shape) stream << c; break; } + case QGeoShape::PolygonType: { + QGeoPolygon p = shape; + stream << p.path().size(); + for (const auto &c: p.path()) + stream << c; + break; + } } return stream; @@ -419,6 +430,18 @@ QDataStream &operator>>(QDataStream &stream, QGeoShape &shape) shape = QGeoPath(l); break; } + case QGeoShape::PolygonType: { + QList l; + QGeoCoordinate c; + int sz; + stream >> sz; + for (int i = 0; i < sz; i++) { + stream >> c; + l.append(c); + } + shape = QGeoPolygon(l); + break; + } } return stream; diff --git a/src/positioning/qgeoshape.h b/src/positioning/qgeoshape.h index defd0eec..c0bc658b 100644 --- a/src/positioning/qgeoshape.h +++ b/src/positioning/qgeoshape.h @@ -66,7 +66,8 @@ public: UnknownType, RectangleType, CircleType, - PathType + PathType, + PolygonType }; ShapeType type() const; diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 5594b8b0..d97af779 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -78,6 +78,7 @@ SUBDIRS += \ qgeorectangle \ qgeocircle \ qgeopath \ + qgeopolygon \ qgeocoordinate \ qgeolocation \ qgeopositioninfo \ diff --git a/tests/auto/qgeopolygon/qgeopolygon.pro b/tests/auto/qgeopolygon/qgeopolygon.pro new file mode 100644 index 00000000..f6314ca4 --- /dev/null +++ b/tests/auto/qgeopolygon/qgeopolygon.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +CONFIG += testcase +TARGET = tst_qgeopolygon + +SOURCES += \ + tst_qgeopolygon.cpp + +QT += positioning testlib diff --git a/tests/auto/qgeopolygon/tst_qgeopolygon.cpp b/tests/auto/qgeopolygon/tst_qgeopolygon.cpp new file mode 100644 index 00000000..12e39f0f --- /dev/null +++ b/tests/auto/qgeopolygon/tst_qgeopolygon.cpp @@ -0,0 +1,402 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class tst_QGeoPolygon : public QObject +{ + Q_OBJECT + +private slots: + void defaultConstructor(); + void listConstructor(); + void assignment(); + + void comparison(); + void type(); + + void path(); + void size(); + + void translate_data(); + void translate(); + + void valid_data(); + void valid(); + + void contains_data(); + void contains(); + + void boundingGeoRectangle_data(); + void boundingGeoRectangle(); + + void extendShape(); + void extendShape_data(); +}; + +void tst_QGeoPolygon::defaultConstructor() +{ + QGeoPolygon p; + QVERIFY(!p.path().size()); + QVERIFY(!p.size()); + QVERIFY(!p.isValid()); + QVERIFY(p.isEmpty()); +} + +void tst_QGeoPolygon::listConstructor() +{ + QList coords; + coords.append(QGeoCoordinate(1,1)); + coords.append(QGeoCoordinate(2,2)); + QGeoPolygon p2(coords); + QCOMPARE(p2.path().size(), 2); + QCOMPARE(p2.size(), 2); + QVERIFY(!p2.isValid()); // a polygon can't have only 2 coords + QVERIFY(!p2.isEmpty()); + + coords.append(QGeoCoordinate(3,0)); + + QGeoPolygon p(coords); + QCOMPARE(p.path().size(), 3); + QCOMPARE(p.size(), 3); + QVERIFY(p.isValid()); + QVERIFY(!p.isEmpty()); + + + for (const QGeoCoordinate &c : coords) { + QCOMPARE(p.path().contains(c), true); + QCOMPARE(p.containsCoordinate(c), true); + } +} + +void tst_QGeoPolygon::assignment() +{ + QGeoPolygon p1; + QList coords; + coords.append(QGeoCoordinate(1,1)); + coords.append(QGeoCoordinate(2,2)); + coords.append(QGeoCoordinate(3,0)); + QGeoPolygon p2(coords); + + QVERIFY(p1 != p2); + + p1 = p2; + QCOMPARE(p1.path(), coords); + QCOMPARE(p1, p2); + + // Assign c1 to an area + QGeoShape area = p1; + QCOMPARE(area.type(), p1.type()); + QVERIFY(area == p1); + + // Assign the area back to a polygon + QGeoPolygon p3 = area; + QCOMPARE(p3.path(), coords); + QVERIFY(p3 == p1); + + // Check that the copy is not modified when modifying the original. + p1.addCoordinate(QGeoCoordinate(4,0)); + QVERIFY(p3 != p1); +} + +void tst_QGeoPolygon::comparison() +{ + QList coords; + coords.append(QGeoCoordinate(1,1)); + coords.append(QGeoCoordinate(2,2)); + coords.append(QGeoCoordinate(3,0)); + QList coords2; + coords2.append(QGeoCoordinate(3,1)); + coords2.append(QGeoCoordinate(4,2)); + coords2.append(QGeoCoordinate(3,0)); + QGeoPolygon c1(coords); + QGeoPolygon c2(coords); + QGeoPolygon c3(coords2); + + QVERIFY(c1 == c2); + QVERIFY(!(c1 != c2)); + + QVERIFY(!(c1 == c3)); + QVERIFY(c1 != c3); + + QVERIFY(!(c2 == c3)); + QVERIFY(c2 != c3); + + QGeoRectangle b1(QGeoCoordinate(20,20),QGeoCoordinate(10,30)); + QVERIFY(!(c1 == b1)); + QVERIFY(c1 != b1); + + QGeoShape *c2Ptr = &c2; + QVERIFY(c1 == *c2Ptr); + QVERIFY(!(c1 != *c2Ptr)); + + QGeoShape *c3Ptr = &c3; + QVERIFY(!(c1 == *c3Ptr)); + QVERIFY(c1 != *c3Ptr); +} + +void tst_QGeoPolygon::type() +{ + QGeoPolygon c; + QCOMPARE(c.type(), QGeoShape::PolygonType); +} + +void tst_QGeoPolygon::path() +{ + QList coords; + coords.append(QGeoCoordinate(1,1)); + coords.append(QGeoCoordinate(2,2)); + coords.append(QGeoCoordinate(3,0)); + + QGeoPolygon p; + p.setPath(coords); + QCOMPARE(p.path().size(), 3); + QCOMPARE(p.size(), 3); + + for (const QGeoCoordinate &c : coords) { + QCOMPARE(p.path().contains(c), true); + QCOMPARE(p.containsCoordinate(c), true); + } +} + +void tst_QGeoPolygon::size() +{ + QList coords; + + QGeoPolygon p1(coords); + QCOMPARE(p1.size(), coords.size()); + + coords.append(QGeoCoordinate(1,1)); + QGeoPolygon p2(coords); + QCOMPARE(p2.size(), coords.size()); + + coords.append(QGeoCoordinate(2,2)); + QGeoPolygon p3(coords); + QCOMPARE(p3.size(), coords.size()); + + coords.append(QGeoCoordinate(3,0)); + QGeoPolygon p4(coords); + QCOMPARE(p4.size(), coords.size()); + + p4.removeCoordinate(2); + QCOMPARE(p4.size(), coords.size() - 1); + + p4.removeCoordinate(coords.first()); + QCOMPARE(p4.size(), coords.size() - 2); +} + +void tst_QGeoPolygon::translate_data() +{ + QTest::addColumn("c1"); + QTest::addColumn("c2"); + QTest::addColumn("c3"); + QTest::addColumn("lat"); + QTest::addColumn("lon"); + + QTest::newRow("Simple") << QGeoCoordinate(1,1) << QGeoCoordinate(2,2) << + QGeoCoordinate(3,0) << 5.0 << 4.0; + QTest::newRow("Backward") << QGeoCoordinate(1,1) << QGeoCoordinate(2,2) << + QGeoCoordinate(3,0) << -5.0 << -4.0; +} + +void tst_QGeoPolygon::translate() +{ + QFETCH(QGeoCoordinate, c1); + QFETCH(QGeoCoordinate, c2); + QFETCH(QGeoCoordinate, c3); + QFETCH(double, lat); + QFETCH(double, lon); + + QList coords; + coords.append(c1); + coords.append(c2); + coords.append(c3); + QGeoPolygon p(coords); + + p.translate(lat, lon); + + for (int i = 0; i < p.path().size(); i++) { + QCOMPARE(coords[i].latitude(), p.path()[i].latitude() - lat ); + QCOMPARE(coords[i].longitude(), p.path()[i].longitude() - lon ); + } +} + +void tst_QGeoPolygon::valid_data() +{ + QTest::addColumn("c1"); + QTest::addColumn("c2"); + QTest::addColumn("c3"); + QTest::addColumn("valid"); + + QTest::newRow("empty coords") << QGeoCoordinate() << QGeoCoordinate() << QGeoCoordinate() << false; + QTest::newRow("invalid coord") << QGeoCoordinate(50, 50) << QGeoCoordinate(60, 60) << QGeoCoordinate(700, 700) << false; + QTest::newRow("good") << QGeoCoordinate(10, 10) << QGeoCoordinate(11, 11) << QGeoCoordinate(10, 12) << true; +} + +void tst_QGeoPolygon::valid() +{ + QFETCH(QGeoCoordinate, c1); + QFETCH(QGeoCoordinate, c2); + QFETCH(QGeoCoordinate, c3); + QFETCH(bool, valid); + + QList coords; + coords.append(c1); + coords.append(c2); + coords.append(c3); + QGeoPolygon p(coords); + + QCOMPARE(p.isValid(), valid); + + QGeoShape area = p; + QCOMPARE(area.isValid(), valid); +} + +void tst_QGeoPolygon::contains_data() +{ + QTest::addColumn("c1"); + QTest::addColumn("c2"); + QTest::addColumn("c3"); + QTest::addColumn("probe"); + QTest::addColumn("result"); + + QList c; + c.append(QGeoCoordinate(1,1)); + c.append(QGeoCoordinate(2,2)); + c.append(QGeoCoordinate(3,0)); + + QTest::newRow("One of the points") << c[0] << c[1] << c[2] << QGeoCoordinate(2, 2) << true; + QTest::newRow("Not so far away") << c[0] << c[1] << c[2] << QGeoCoordinate(0.8, 0.8) << false; + QTest::newRow("Not so far away and large line") << c[0] << c[1] << c[2] << QGeoCoordinate(0.8, 0.8) << false; + QTest::newRow("Inside") << c[0] << c[1] << c[2] << QGeoCoordinate(2.0, 1.0) << true; +} + +void tst_QGeoPolygon::contains() +{ + QFETCH(QGeoCoordinate, c1); + QFETCH(QGeoCoordinate, c2); + QFETCH(QGeoCoordinate, c3); + QFETCH(QGeoCoordinate, probe); + QFETCH(bool, result); + + QList coords; + coords.append(c1); + coords.append(c2); + coords.append(c3); + QGeoPolygon p(coords); + + QCOMPARE(p.contains(probe), result); + + QGeoShape area = p; + QCOMPARE(area.contains(probe), result); +} + +void tst_QGeoPolygon::boundingGeoRectangle_data() +{ + QTest::addColumn("c1"); + QTest::addColumn("c2"); + QTest::addColumn("c3"); + QTest::addColumn("probe"); + QTest::addColumn("result"); + + QList c; + c.append(QGeoCoordinate(1,1)); + c.append(QGeoCoordinate(2,2)); + c.append(QGeoCoordinate(3,0)); + + QTest::newRow("One of the points") << c[0] << c[1] << c[2] << QGeoCoordinate(2, 2) << true; + QTest::newRow("Not so far away") << c[0] << c[1] << c[2] << QGeoCoordinate(0, 0) << false; + QTest::newRow("Inside the bounds") << c[0] << c[1] << c[2] << QGeoCoordinate(1, 0) << true; + QTest::newRow("Inside the bounds") << c[0] << c[1] << c[2] << QGeoCoordinate(1.1, 0.1) << true; +} + +void tst_QGeoPolygon::boundingGeoRectangle() +{ + QFETCH(QGeoCoordinate, c1); + QFETCH(QGeoCoordinate, c2); + QFETCH(QGeoCoordinate, c3); + QFETCH(QGeoCoordinate, probe); + QFETCH(bool, result); + + QList coords; + coords.append(c1); + coords.append(c2); + coords.append(c3); + QGeoPolygon p(coords); + + QGeoRectangle box = p.boundingGeoRectangle(); + QCOMPARE(box.contains(probe), result); +} + +void tst_QGeoPolygon::extendShape() +{ + QFETCH(QGeoCoordinate, c1); + QFETCH(QGeoCoordinate, c2); + QFETCH(QGeoCoordinate, c3); + QFETCH(QGeoCoordinate, probe); + QFETCH(bool, before); + QFETCH(bool, after); + + QList coords; + coords.append(c1); + coords.append(c2); + coords.append(c3); + QGeoPolygon p(coords); + + + QCOMPARE(p.contains(probe), before); + p.extendShape(probe); + QCOMPARE(p.contains(probe), after); +} + +void tst_QGeoPolygon::extendShape_data() +{ + QTest::addColumn("c1"); + QTest::addColumn("c2"); + QTest::addColumn("c3"); + QTest::addColumn("probe"); + QTest::addColumn("before"); + QTest::addColumn("after"); + + QList c; + c.append(QGeoCoordinate(1,1)); + c.append(QGeoCoordinate(2,2)); + c.append(QGeoCoordinate(3,0)); + + QTest::newRow("One of the points") << c[0] << c[1] << c[2] << QGeoCoordinate(2, 2) << true << true; + QTest::newRow("Not so far away") << c[0] << c[1] << c[2] << QGeoCoordinate(0, 0) << false << true; + QTest::newRow("Contained point") << c[0] << c[1] << c[2] << QGeoCoordinate(2.0, 1.0) << true << true; +} + +QTEST_MAIN(tst_QGeoPolygon) +#include "tst_qgeopolygon.moc" -- cgit v1.2.1