From edadcb773fb0c96f1ca312524ca1feb1e8d757d7 Mon Sep 17 00:00:00 2001 From: lbartoletti Date: Tue, 15 Jan 2019 00:24:16 +0100 Subject: [PATCH] Refactoring of rectangle maptools Adds a new geometry class QgsQuadrilateral, for 4 sided geometries. --- .../geometry/qgsquadrilateral.sip.in | 201 +++++++++ python/core/auto_generated/qgsvector3d.sip.in | 20 + python/core/core_auto.sip | 1 + src/app/qgsmaptooladdrectangle.cpp | 94 +--- src/app/qgsmaptooladdrectangle.h | 39 +- src/app/qgsmaptoolrectangle3points.cpp | 43 +- src/app/qgsmaptoolrectanglecenter.cpp | 19 +- src/app/qgsmaptoolrectangleextent.cpp | 16 +- src/core/CMakeLists.txt | 2 + src/core/geometry/qgsquadrilateral.cpp | 424 ++++++++++++++++++ src/core/geometry/qgsquadrilateral.h | 214 +++++++++ src/core/qgsvector3d.h | 40 ++ tests/src/app/testqgsmaptoolrectangle.cpp | 24 +- tests/src/core/testqgsgeometry.cpp | 211 +++++++++ tests/src/core/testqgsvector.cpp | 17 + 15 files changed, 1179 insertions(+), 186 deletions(-) create mode 100644 python/core/auto_generated/geometry/qgsquadrilateral.sip.in create mode 100644 src/core/geometry/qgsquadrilateral.cpp create mode 100644 src/core/geometry/qgsquadrilateral.h diff --git a/python/core/auto_generated/geometry/qgsquadrilateral.sip.in b/python/core/auto_generated/geometry/qgsquadrilateral.sip.in new file mode 100644 index 00000000000..82598f67a0b --- /dev/null +++ b/python/core/auto_generated/geometry/qgsquadrilateral.sip.in @@ -0,0 +1,201 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geometry/qgsquadrilateral.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsQuadrilateral +{ +%Docstring +Quadrilateral geometry type. +A quadrilateral is a polygon with four edges (or sides) and four vertices or corners. +This class allows the creation of simple quadrilateral (which does not self-intersect). + +.. versionadded:: 3.6 +%End + +%TypeHeaderCode +#include "qgsquadrilateral.h" +%End + public: + QgsQuadrilateral(); + + QgsQuadrilateral( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ); +%Docstring +Construct a QgsQuadrilateral from four :py:class:`QgsPoint`. + +:param p1: first point +:param p2: second point +:param p3: third point +:param p4: fourth point + +.. seealso:: :py:func:`setPoints` +%End + + explicit QgsQuadrilateral( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3, const QgsPointXY &p4 ); +%Docstring +Construct a QgsQuadrilateral from four :py:class:`QgsPointXY`. + +:param p1: first point +:param p2: second point +:param p3: third point +:param p4: fourth point + +.. seealso:: :py:func:`setPoints` +%End + + + enum ConstructionOption + { + Distance, + Projected, + }; + + static QgsQuadrilateral rectangleFrom3Points( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, ConstructionOption mode ); +%Docstring +Construct a QgsQuadrilateral as a Rectangle from 3 points. +In the case where one of the points is of type PointZ. The other points +will also be of type Z, even if they are of type Point. In addition, +the z used will be the one of the first point with a Z. +This ensures consistency in point types and the ability to export to a +Polygon or LineString. + +:param p1: first point +:param p2: second point +:param p3: third point +:param mode: Construction mode to construct the rectangle from 3 points + +.. seealso:: ConstructionOption +%End + + static QgsQuadrilateral rectangleFromExtent( const QgsPoint &p1, const QgsPoint &p2 ); +%Docstring +Construct a QgsQuadrilateral as a rectangle from an extent, defined by +two opposite corner points. +Z is taken from point ``p1``. + +:param p1: first point +:param p2: second point +%End + + + static QgsQuadrilateral squareFromDiagonal( const QgsPoint &p1, const QgsPoint &p2 ); +%Docstring +Construct a QgsQuadrilateral as a square from a diagonal. +Z is taken from point ``p1``. + +:param p1: first point +:param p2: second point +%End + + static QgsQuadrilateral rectangleFromCenterPoint( const QgsPoint ¢er, const QgsPoint &point ); +%Docstring +Construct a QgsQuadrilateral as a rectangle from center point ``center`` +and another point ``point``. +Z is taken from ``center`` point. + +:param center: center point +:param point: corner point +%End + + static QgsQuadrilateral fromRectangle( const QgsRectangle &rectangle ); +%Docstring +Construct a QgsQuadrilateral as a rectangle from a :py:class:`QgsRectangle`. + +:param rectangle: rectangle +%End + + + bool equals( const QgsQuadrilateral &other, double epsilon = 4 * DBL_EPSILON ) const; +%Docstring +Compares two QgsQuadrilateral, allowing specification of the maximum allowable difference between points. + +:param other: the QgsQuadrilateral to compare +:param epsilon: the maximum difference allowed / tolerance +%End + bool operator==( const QgsQuadrilateral &other ) const; + bool operator!=( const QgsQuadrilateral &other ) const; + + bool isValid() const; +%Docstring +Convenient method to determine if a QgsQuadrilateral is valid. +A QgsQuadrilateral must be simple (not self-intersecting) and +cannot have collinear points. +%End + + enum Point + { + Point1, + Point2, + Point3, + Point4, + }; + + bool setPoint( const QgsPoint &newPoint, Point index ); +%Docstring +Sets the point ``newPoint`` at the ``index``. +Returns false if the QgsQuadrilateral is not valid. + +.. seealso:: Point +%End + + bool setPoints( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ); +%Docstring +Set all points +Returns false if the QgsQuadrilateral is not valid: +- The points do not have the same type +- The quadrilateral would have auto intersections +- The quadrilateral has double points +- The quadrilateral has collinear points +%End + + QgsPointSequence points() const; +%Docstring +Returns a list including the vertices of the quadrilateral. +%End + + QgsPolygon *toPolygon( bool force2D = false ) const /Factory/; +%Docstring +Returns the quadrilateral as a new polygon. Ownership is transferred to the caller. +%End + + QgsLineString *toLineString( bool force2D = false ) const /Factory/; +%Docstring +Returns the quadrilateral as a new linestring. Ownership is transferred to the caller. +%End + + QString toString( int pointPrecision = 17 ) const; +%Docstring +Returns a string representation of the quadrilateral. +Members will be truncated to the specified precision. +%End + + double area() const; +%Docstring +Returns the area of the quadrilateral, or 0 if the quadrilateral is empty. +%End + + double perimeter() const; +%Docstring +Returns the perimeter of the quadrilateral, or 0 if the quadrilateral is empty. +%End + SIP_PYOBJECT __repr__(); +%MethodCode + QString str = QStringLiteral( "" ).arg( sipCpp->toString() ); + sipRes = PyUnicode_FromString( str.toUtf8().constData() ); +%End +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geometry/qgsquadrilateral.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/auto_generated/qgsvector3d.sip.in b/python/core/auto_generated/qgsvector3d.sip.in index 0d346fc123d..81e65e37dbd 100644 --- a/python/core/auto_generated/qgsvector3d.sip.in +++ b/python/core/auto_generated/qgsvector3d.sip.in @@ -89,7 +89,27 @@ Returns the length of the vector Normalizes the current vector in place. %End + double distance( const QgsVector3D &other ) const; +%Docstring +Returns the distance with the ``other`` :py:class:`QgsVector3` +%End + static QgsVector3D perpendicularPoint( const QgsVector3D &v1, const QgsVector3D &v2, const QgsVector3D &vp ); +%Docstring +Returns the perpendicular point of vector ``vp`` from [``v1`` - ``v2``] +%End + + QString toString( int precision = 17 ) const; +%Docstring +Returns a string representation of the 3D vector. +Members will be truncated to the specified ``precision``. +%End + + SIP_PYOBJECT __repr__(); +%MethodCode + QString str = QStringLiteral( "" ).arg( sipCpp->toString() ); + sipRes = PyUnicode_FromString( str.toUtf8().constData() ); +%End }; /************************************************************************ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index aee4bc036b6..db53bcf6fee 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -288,6 +288,7 @@ %Include auto_generated/geometry/qgsmultipolygon.sip %Include auto_generated/geometry/qgsmultisurface.sip %Include auto_generated/geometry/qgspolygon.sip +%Include auto_generated/geometry/qgsquadrilateral.sip %Include auto_generated/geometry/qgsrectangle.sip %Include auto_generated/geometry/qgsreferencedgeometry.sip %Include auto_generated/geometry/qgsregularpolygon.sip diff --git a/src/app/qgsmaptooladdrectangle.cpp b/src/app/qgsmaptooladdrectangle.cpp index af904e05570..b150c0081ae 100644 --- a/src/app/qgsmaptooladdrectangle.cpp +++ b/src/app/qgsmaptooladdrectangle.cpp @@ -35,26 +35,6 @@ QgsMapToolAddRectangle::QgsMapToolAddRectangle( QgsMapToolCapture *parentTool, Q connect( QgisApp::instance(), &QgisApp::projectRead, this, &QgsMapToolAddRectangle::stopCapturing ); } -void QgsMapToolAddRectangle::setAzimuth( const double azimuth ) -{ - mAzimuth = azimuth; -} - -void QgsMapToolAddRectangle::setDistance1( const double distance1 ) -{ - mDistance1 = distance1; -} - -void QgsMapToolAddRectangle::setDistance2( const double distance2 ) -{ - mDistance2 = distance2; -} - -void QgsMapToolAddRectangle::setSide( const int side ) -{ - mSide = side; -} - QgsMapToolAddRectangle::~QgsMapToolAddRectangle() { clean(); @@ -83,81 +63,15 @@ void QgsMapToolAddRectangle::keyReleaseEvent( QKeyEvent *e ) } } -QgsLineString *QgsMapToolAddRectangle::rectangleToLinestring( const bool isOriented ) const +void QgsMapToolAddRectangle::deactivate( ) { - std::unique_ptr ext( new QgsLineString() ); - if ( mRectangle.toRectangle().isEmpty() ) - { - return ext.release(); - } - - QgsPoint x0( mRectangle.xMinimum(), mRectangle.yMinimum() ); - - QgsPoint x1, x2, x3; - if ( isOriented ) - { - const double perpendicular = 90.0 * mSide; - x1 = x0.project( mDistance1, mAzimuth ); - x3 = x0.project( mDistance2, mAzimuth + perpendicular ); - x2 = x1.project( mDistance2, mAzimuth + perpendicular ); - } - else - { - x1 = QgsPoint( mRectangle.xMinimum(), mRectangle.yMaximum() ); - x2 = QgsPoint( mRectangle.xMaximum(), mRectangle.yMaximum() ); - x3 = QgsPoint( mRectangle.xMaximum(), mRectangle.yMinimum() ); - } - - ext->addVertex( x0 ); - ext->addVertex( x1 ); - ext->addVertex( x2 ); - ext->addVertex( x3 ); - ext->addVertex( x0 ); - - // keep z value from the first snapped point - for ( const QgsPoint point : qgis::as_const( mPoints ) ) - { - if ( QgsWkbTypes::hasZ( point.wkbType() ) ) - { - if ( point.z() != defaultZValue() ) - { - ext->dropZValue(); - ext->addZValue( point.z() ); - break; - } - else - { - ext->dropZValue(); - ext->addZValue( defaultZValue() ); - } - } - } - - return ext.release(); -} - -QgsPolygon *QgsMapToolAddRectangle::rectangleToPolygon( const bool isOriented ) const -{ - std::unique_ptr polygon( new QgsPolygon() ); - if ( mRectangle.toRectangle().isEmpty() ) - { - return polygon.release(); - } - - polygon->setExteriorRing( rectangleToLinestring( isOriented ) ); - - return polygon.release(); -} - -void QgsMapToolAddRectangle::deactivate( const bool isOriented ) -{ - if ( !mParentTool || mRectangle.toRectangle().isEmpty() ) + if ( !mParentTool || !mRectangle.isValid() ) { return; } mParentTool->clearCurve( ); - mParentTool->addCurve( rectangleToLinestring( isOriented ) ); + mParentTool->addCurve( mRectangle.toLineString( !QgsWkbTypes::hasZ( qobject_cast( mCanvas->currentLayer() )->wkbType() ) ) ); clean(); QgsMapToolCapture::deactivate(); @@ -184,7 +98,7 @@ void QgsMapToolAddRectangle::clean() mParentTool->deleteTempRubberBand(); } - mRectangle = QgsBox3d(); + mRectangle = QgsQuadrilateral(); QgsVectorLayer *vLayer = static_cast( QgisApp::instance()->activeLayer() ); if ( vLayer ) diff --git a/src/app/qgsmaptooladdrectangle.h b/src/app/qgsmaptooladdrectangle.h index 431610c2398..12f26598647 100644 --- a/src/app/qgsmaptooladdrectangle.h +++ b/src/app/qgsmaptooladdrectangle.h @@ -18,7 +18,7 @@ #include "qgspolygon.h" #include "qgsmaptoolcapture.h" -#include "qgsbox3d.h" +#include "qgsquadrilateral.h" #include "qgis_app.h" class QgsPolygon; @@ -35,7 +35,7 @@ class APP_EXPORT QgsMapToolAddRectangle: public QgsMapToolCapture void keyPressEvent( QKeyEvent *e ) override; void keyReleaseEvent( QKeyEvent *e ) override; - void deactivate( bool isOriented = false ); + void deactivate( ) override; void activate() override; void clean() override; @@ -53,46 +53,13 @@ class APP_EXPORT QgsMapToolAddRectangle: public QgsMapToolCapture //! The rubberband to show the rectangle currently working on QgsGeometryRubberBand *mTempRubberBand = nullptr; //! Rectangle - QgsBox3d mRectangle; - - //! Convenient method to export a QgsRectangle to a LineString - QgsLineString *rectangleToLinestring( bool isOriented = false ) const; - //! Convenient method to export a QgsRectangle to a Polygon - QgsPolygon *rectangleToPolygon( bool isOriented = false ) const; - - //! Sets the azimuth. \see mAzimuth - void setAzimuth( double azimuth ); - //! Sets the first distance. \see mDistance1 - void setDistance1( double distance1 ); - //! Sets the second distance. \see mDistance2 - void setDistance2( double distance2 ); - //! Sets the side. \see mSide - void setSide( int side ); - - //! Returns the azimuth. \see mAzimuth - double azimuth( ) const { return mAzimuth; } - //! Returns the first distance. \see mDistance1 - double distance1( ) const { return mDistance1; } - //! Returns the second distance. \see mDistance2 - double distance2( ) const { return mDistance2; } - //! Returns the side. \see mSide - int side( ) const { return mSide; } + QgsQuadrilateral mRectangle; //! Layer type which will be used for rubberband QgsWkbTypes::GeometryType mLayerType = QgsWkbTypes::LineGeometry; //! Snapping indicators std::unique_ptr mSnapIndicator; - - private: - //! Convenient member for the azimuth of the rotated rectangle or when map is rotated. - double mAzimuth = 0.0; - //! Convenient member for the first distance of the rotated rectangle or when map is rotated. - double mDistance1 = 0.0; - //! Convenient member for the second distance of the rotated rectangle or when map is rotated. - double mDistance2 = 0.0; - //! Convenient member for the side where the second distance is drawn or when map is rotated. - int mSide = 1; }; #endif // QGSMAPTOOLADDRECTANGLE_H diff --git a/src/app/qgsmaptoolrectangle3points.cpp b/src/app/qgsmaptoolrectangle3points.cpp index 3608a5d2ce9..1f1e99306b0 100644 --- a/src/app/qgsmaptoolrectangle3points.cpp +++ b/src/app/qgsmaptoolrectangle3points.cpp @@ -37,18 +37,27 @@ void QgsMapToolRectangle3Points::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) if ( e->button() == Qt::LeftButton ) { + if ( !point.is3D() ) + point.addZValue( defaultZValue() ); if ( mPoints.size() < 2 ) + { mPoints.append( point ); + } if ( !mPoints.isEmpty() && !mTempRubberBand ) { mTempRubberBand = createGeometryRubberBand( mLayerType, true ); mTempRubberBand->show(); } + if ( mPoints.size() == 3 ) + { + delete mTempRubberBand; + mTempRubberBand = createGeometryRubberBand( mLayerType, true ); // recreate rubberband for polygon + } } else if ( e->button() == Qt::RightButton ) { - deactivate( true ); + deactivate( ); if ( mParentTool ) { mParentTool->canvasReleaseEvent( e ); @@ -72,43 +81,25 @@ void QgsMapToolRectangle3Points::cadCanvasMoveEvent( QgsMapMouseEvent *e ) line->addVertex( mPoints.at( 0 ) ); line->addVertex( point ); mTempRubberBand->setGeometry( line.release() ); - setAzimuth( mPoints.at( 0 ).azimuth( point ) ); - setDistance1( mPoints.at( 0 ).distance( point ) ); + break; } - break; case 2: { + if ( !point.is3D() ) + point.addZValue( defaultZValue() ); switch ( mCreateMode ) { case DistanceMode: - setDistance2( mPoints.at( 1 ).distance( point ) ); + mRectangle = QgsQuadrilateral::rectangleFrom3Points( mPoints.at( 0 ), mPoints.at( 1 ), point, QgsQuadrilateral::Distance ); break; case ProjectedMode: - setDistance2( QgsGeometryUtils::perpendicularSegment( point, mPoints.at( 0 ), mPoints.at( 1 ) ).length() ); + mRectangle = QgsQuadrilateral::rectangleFrom3Points( mPoints.at( 0 ), mPoints.at( 1 ), point, QgsQuadrilateral::Projected ); break; } - int side = QgsGeometryUtils::leftOfLine( point.x(), point.y(), - mPoints.at( 0 ).x(), mPoints.at( 0 ).y(), - mPoints.at( 1 ).x(), mPoints.at( 1 ).y() ); - setSide( side < 0 ? -1 : 1 ); - - const double xMin = mPoints.at( 0 ).x(); - const double xMax = mPoints.at( 0 ).x() + distance2( ); - - const double yMin = mPoints.at( 0 ).y(); - const double yMax = mPoints.at( 0 ).y() + distance1(); - - const double z = mPoints.at( 0 ).z(); - - mRectangle = QgsBox3d( xMin, yMin, z, xMax, yMax, z ); - - - mTempRubberBand->setGeometry( QgsMapToolAddRectangle::rectangleToPolygon( true ) ); - } - break; - default: + mTempRubberBand->setGeometry( mRectangle.toPolygon() ); break; + } } } } diff --git a/src/app/qgsmaptoolrectanglecenter.cpp b/src/app/qgsmaptoolrectanglecenter.cpp index b0e9abae11c..0fc664337de 100644 --- a/src/app/qgsmaptoolrectanglecenter.cpp +++ b/src/app/qgsmaptoolrectanglecenter.cpp @@ -22,6 +22,7 @@ #include "qgspoint.h" #include "qgsmapmouseevent.h" #include "qgssnapindicator.h" +#include "qgsquadrilateral.h" #include @@ -69,23 +70,13 @@ void QgsMapToolRectangleCenter::cadCanvasMoveEvent( QgsMapMouseEvent *e ) { case 1: { - if ( qgsDoubleNear( mCanvas->rotation(), 0.0 ) ) - { - double xOffset = fabs( point.x() - mPoints.at( 0 ).x() ); - double yOffset = fabs( point.y() - mPoints.at( 0 ).y() ); - mRectangle = QgsBox3d( QgsPoint( mPoints.at( 0 ).x() - xOffset, mPoints.at( 0 ).y() - yOffset, mPoints.at( 0 ).z() ), QgsPoint( mPoints.at( 0 ).x() + xOffset, mPoints.at( 0 ).y() + yOffset, mPoints.at( 0 ).z() ) ); + double dist = mPoints.at( 0 ).distance( point ); + double angle = mPoints.at( 0 ).azimuth( point ); - mTempRubberBand->setGeometry( QgsMapToolAddRectangle::rectangleToPolygon() ); - } - else - { - double dist = mPoints.at( 0 ).distance( point ); - double angle = mPoints.at( 0 ).azimuth( point ); + mRectangle = QgsQuadrilateral::rectangleFromExtent( mPoints.at( 0 ).project( -dist, angle ), mPoints.at( 0 ).project( dist, angle ) ); + mTempRubberBand->setGeometry( mRectangle.toPolygon() ); - mRectangle = QgsBox3d( mPoints.at( 0 ).project( -dist, angle ), mPoints.at( 0 ).project( dist, angle ) ); - mTempRubberBand->setGeometry( QgsMapToolAddRectangle::rectangleToPolygon() ); - } } break; default: diff --git a/src/app/qgsmaptoolrectangleextent.cpp b/src/app/qgsmaptoolrectangleextent.cpp index 2b432d89050..30079a01079 100644 --- a/src/app/qgsmaptoolrectangleextent.cpp +++ b/src/app/qgsmaptoolrectangleextent.cpp @@ -68,19 +68,11 @@ void QgsMapToolRectangleExtent::cadCanvasMoveEvent( QgsMapMouseEvent *e ) { case 1: { - if ( qgsDoubleNear( mCanvas->rotation(), 0.0 ) ) - { - mRectangle = QgsBox3d( mPoints.at( 0 ), point ); - mTempRubberBand->setGeometry( QgsMapToolAddRectangle::rectangleToPolygon( ) ); - } - else - { - double dist = mPoints.at( 0 ).distance( point ); - double angle = mPoints.at( 0 ).azimuth( point ); + double dist = mPoints.at( 0 ).distance( point ); + double angle = mPoints.at( 0 ).azimuth( point ); - mRectangle = QgsBox3d( mPoints.at( 0 ), mPoints.at( 0 ).project( dist, angle ) ); - mTempRubberBand->setGeometry( QgsMapToolAddRectangle::rectangleToPolygon() ); - } + mRectangle = QgsQuadrilateral::rectangleFromExtent( mPoints.at( 0 ), mPoints.at( 0 ).project( dist, angle ) ); + mTempRubberBand->setGeometry( mRectangle.toPolygon() ); } break; default: diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c9c9e02ab84..cadd3e0c830 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -505,6 +505,7 @@ SET(QGIS_CORE_SRCS geometry/qgsmultisurface.cpp geometry/qgspoint.cpp geometry/qgspolygon.cpp + geometry/qgsquadrilateral.cpp geometry/qgsrectangle.cpp geometry/qgsreferencedgeometry.cpp geometry/qgsregularpolygon.cpp @@ -1167,6 +1168,7 @@ SET(QGIS_CORE_HDRS geometry/qgsmultipolygon.h geometry/qgsmultisurface.h geometry/qgspolygon.h + geometry/qgsquadrilateral.h geometry/qgsrectangle.h geometry/qgsreferencedgeometry.h geometry/qgsregularpolygon.h diff --git a/src/core/geometry/qgsquadrilateral.cpp b/src/core/geometry/qgsquadrilateral.cpp new file mode 100644 index 00000000000..b09c9eeb4a0 --- /dev/null +++ b/src/core/geometry/qgsquadrilateral.cpp @@ -0,0 +1,424 @@ +/*************************************************************************** + qgsquadrilateral.cpp + ------------------- + begin : November 2018 + copyright : (C) 2018 by Loïc Bartoletti + email : loic dot bartoletti at oslandia dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsquadrilateral.h" +#include "qgsgeometryutils.h" + +QgsQuadrilateral::QgsQuadrilateral() = default; + +QgsQuadrilateral::QgsQuadrilateral( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ) +{ + setPoints( p1, p2, p3, p4 ); +} + +QgsQuadrilateral::QgsQuadrilateral( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3, const QgsPointXY &p4 ) +{ + setPoints( QgsPoint( p1 ), QgsPoint( p2 ), QgsPoint( p3 ), QgsPoint( p4 ) ); +} + +QgsQuadrilateral QgsQuadrilateral::rectangleFrom3Points( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, ConstructionOption mode ) +{ + QgsWkbTypes::Type pType( QgsWkbTypes::Point ); + + double z = std::numeric_limits< double >::quiet_NaN(); + + if ( p1.is3D() ) + z = p1.z(); + if ( p2.is3D() && std::isnan( z ) ) + z = p2.z(); + if ( p3.is3D() && std::isnan( z ) ) + z = p3.z(); + if ( !std::isnan( z ) ) + { + pType = QgsWkbTypes::addZ( pType ); + } + else + { + // This is only necessary to facilitate the calculation of the perpendicular + // point with QgsVector3D. + if ( mode == Projected ) + z = 0; + } + QgsPoint point1( pType, p1.x(), p1.y(), std::isnan( p1.z() ) ? z : p1.z() ); + QgsPoint point2( pType, p2.x(), p2.y(), std::isnan( p2.z() ) ? z : p2.z() ); + QgsPoint point3( pType, p3.x(), p3.y(), std::isnan( p3.z() ) ? z : p3.z() ); + + QgsQuadrilateral rect; + double inclination = 90.0; + double distance = 0; + double azimuth = point1.azimuth( point2 ) + 90.0 * QgsGeometryUtils::leftOfLine( point3.x(), point3.y(), point1.x(), point1.y(), point2.x(), point2.y() ); + switch ( mode ) + { + case Distance: + { + if ( point2.is3D() && point3.is3D() ) + { + inclination = point2.inclination( point3 ); + distance = point2.distance3D( point3 ); + } + else + { + distance = point2.distance( point3 ); + } + + rect.setPoints( point1, point2, point2.project( distance, azimuth, inclination ), point1.project( distance, azimuth, inclination ) ); + break; + } + case Projected: + { + QgsVector3D v3 = QgsVector3D::perpendicularPoint( QgsVector3D( point1.x(), point1.y(), std::isnan( point1.z() ) ? z : point1.z() ), + QgsVector3D( point2.x(), point2.y(), std::isnan( point2.z() ) ? z : point2.z() ), + QgsVector3D( point3.x(), point3.y(), std::isnan( point3.z() ) ? z : point3.z() ) ); + QgsPoint pV3( pType, v3.x(), v3.y(), v3.z() ); + if ( p3.is3D() ) + { + inclination = pV3.inclination( p3 ); + distance = p3.distance3D( pV3 ); + } + else + distance = p3.distance( pV3 ); + + // Final points + QgsPoint fp1 = point1; + QgsPoint fp2 = point2; + QgsPoint fp3 = point2.project( distance, azimuth, inclination ); + QgsPoint fp4 = point1.project( distance, azimuth, inclination ) ; + + if ( pType != QgsWkbTypes::PointZ ) + { + fp1.dropZValue(); + fp2.dropZValue(); + fp3.dropZValue(); + fp4.dropZValue(); + } + rect.setPoints( fp1, fp2, fp3, fp4 ); + break; + } + } + + return rect; + +} + +QgsQuadrilateral QgsQuadrilateral::rectangleFromExtent( const QgsPoint &p1, const QgsPoint &p2 ) +{ + if ( QgsPoint( p1.x(), p1.y() ) == QgsPoint( p2.x(), p2.y() ) ) + return QgsQuadrilateral(); + + QgsQuadrilateral quad; + double z = p1.z(); + + double xMin = 0, xMax = 0, yMin = 0, yMax = 0; + + if ( p1.x() < p2.x() ) + { + xMin = p1.x(); + xMax = p2.x(); + } + else + { + + xMin = p2.x(); + xMax = p1.x(); + } + + if ( p1.y() < p2.y() ) + { + yMin = p1.y(); + yMax = p2.y(); + } + else + { + + yMin = p2.y(); + yMax = p1.y(); + } + + quad.setPoints( QgsPoint( xMin, yMin, z ), + QgsPoint( xMin, yMax, z ), + QgsPoint( xMax, yMax, z ), + QgsPoint( xMax, yMin, z ) ); + + return quad; +} + +QgsQuadrilateral QgsQuadrilateral::squareFromDiagonal( const QgsPoint &p1, const QgsPoint &p2 ) +{ + + if ( QgsPoint( p1.x(), p1.y() ) == QgsPoint( p2.x(), p2.y() ) ) + return QgsQuadrilateral(); + + QgsQuadrilateral quad; + QgsPoint point2, point3 = QgsPoint( p2.x(), p2.y() ), point4; + + double azimuth = p1.azimuth( point3 ) + 90.0; + double distance = p1.distance( point3 ) / 2.0; + QgsPoint midPoint = QgsGeometryUtils::midpoint( p1, point3 ); + + point2 = midPoint.project( -distance, azimuth ); + point4 = midPoint.project( distance, azimuth ); + + if ( p1.is3D() ) + { + double z = 0; + z = p1.z(); + point2 = QgsPoint( point2.x(), point2.y(), z ); + point3 = QgsPoint( point3.x(), point3.y(), z ); + point4 = QgsPoint( point4.x(), point4.y(), z ); + } + + quad.setPoints( p1, point2, point3, point4 ); + + return quad; +} + +QgsQuadrilateral QgsQuadrilateral::rectangleFromCenterPoint( const QgsPoint ¢er, const QgsPoint &point ) +{ + if ( QgsPoint( center.x(), center.y() ) == QgsPoint( point.x(), point.y() ) ) + return QgsQuadrilateral(); + double xOffset = std::fabs( point.x() - center.x() ); + double yOffset = std::fabs( point.y() - center.y() ); + + return QgsQuadrilateral( QgsPoint( center.x() - xOffset, center.y() - yOffset, center.z() ), + QgsPoint( center.x() - xOffset, center.y() + yOffset, center.z() ), + QgsPoint( center.x() + xOffset, center.y() + yOffset, center.z() ), + QgsPoint( center.x() + xOffset, center.y() - yOffset, center.z() ) ); +} + +QgsQuadrilateral QgsQuadrilateral::fromRectangle( const QgsRectangle &rectangle ) +{ + QgsQuadrilateral quad; + quad.setPoints( + QgsPoint( rectangle.xMinimum(), rectangle.yMinimum() ), + QgsPoint( rectangle.xMinimum(), rectangle.yMaximum() ), + QgsPoint( rectangle.xMaximum(), rectangle.yMaximum() ), + QgsPoint( rectangle.xMaximum(), rectangle.yMinimum() ) + ); + return quad; +} + +// Convenient method for comparison +// TODO: should be have a equals method for QgsPoint allowing tolerance. +static bool equalPoint( const QgsPoint &p1, const QgsPoint &p2, double epsilon ) +{ + bool equal = true; + equal &= qgsDoubleNear( p1.x(), p2.x(), epsilon ); + equal &= qgsDoubleNear( p1.y(), p2.y(), epsilon ); + if ( p1.is3D() || p2.is3D() ) + equal &= qgsDoubleNear( p1.z(), p2.z(), epsilon ) || ( std::isnan( p1.z() ) && std::isnan( p2.z() ) ); + if ( p1.isMeasure() || p2.isMeasure() ) + equal &= qgsDoubleNear( p1.m(), p2.m(), epsilon ) || ( std::isnan( p1.m() ) && std::isnan( p2.m() ) ); + + return equal; +} + +bool QgsQuadrilateral::equals( const QgsQuadrilateral &other, double epsilon ) const +{ + if ( !( isValid() || other.isValid() ) ) + { + return true; + } + else if ( !isValid() || !other.isValid() ) + { + return false; + } + return ( ( equalPoint( mPoint1, other.mPoint1, epsilon ) ) && + ( equalPoint( mPoint2, other.mPoint2, epsilon ) ) && + ( equalPoint( mPoint3, other.mPoint3, epsilon ) ) && + ( equalPoint( mPoint4, other.mPoint4, epsilon ) ) ); +} + +bool QgsQuadrilateral::operator==( const QgsQuadrilateral &other ) const +{ + return equals( other ); +} + +bool QgsQuadrilateral::operator!=( const QgsQuadrilateral &other ) const +{ + return !operator==( other ); +} + +// Returns true if segments are not self-intersected ( [2-3] / [4-1] or [1-2] / +// [3-4] ) +// +// p3 p1 p1 p3 +// | \ /| | \ /| +// | \/ | | \/ | +// | /\ | or | /\ | +// | / \| | / \| +// p2 p4 p2 p4 + +static bool isNotAntiParallelogram( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ) +{ + QgsPoint inter; + bool isIntersection1234 = QgsGeometryUtils::segmentIntersection( p1, p2, p3, p4, inter, isIntersection1234 ); + bool isIntersection2341 = QgsGeometryUtils::segmentIntersection( p2, p3, p4, p1, inter, isIntersection2341 ); + + return !( isIntersection1234 || isIntersection2341 ); +} + +static bool isNotCollinear( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ) +{ + bool isCollinear = + ( + ( QgsGeometryUtils::segmentSide( p1, p2, p3 ) == 0 ) || + ( QgsGeometryUtils::segmentSide( p1, p2, p4 ) == 0 ) || + ( QgsGeometryUtils::segmentSide( p1, p3, p4 ) == 0 ) || + ( QgsGeometryUtils::segmentSide( p2, p3, p4 ) == 0 ) + ); + + + return !isCollinear; +} + +static bool notHaveDoublePoints( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ) +{ + bool doublePoints = + ( + ( p1 == p2 ) || ( p1 == p3 ) || ( p1 == p4 ) || ( p2 == p3 ) || ( p2 == p4 ) || ( p3 == p4 ) ); + + return !doublePoints; +} + +static bool haveSameType( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ) +{ + bool sameType = !( ( p1.wkbType() != p2.wkbType() ) || ( p1.wkbType() != p3.wkbType() ) || ( p1.wkbType() != p4.wkbType() ) ); + return sameType; +} +// Convenient method to validate inputs +static bool validate( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ) +{ + return ( + haveSameType( p1, p2, p3, p4 ) && + notHaveDoublePoints( p1, p2, p3, p4 ) && + isNotAntiParallelogram( p1, p2, p3, p4 ) && + isNotCollinear( p1, p2, p3, p4 ) + ); +} + +bool QgsQuadrilateral::isValid() const +{ + return validate( mPoint1, mPoint2, mPoint3, mPoint4 ); +} + +bool QgsQuadrilateral::setPoint( const QgsPoint &newPoint, Point index ) +{ + switch ( index ) + { + case Point1: + if ( validate( newPoint, mPoint2, mPoint3, mPoint4 ) == false ) + return false; + mPoint1 = newPoint; + break; + case Point2: + if ( validate( mPoint1, newPoint, mPoint3, mPoint4 ) == false ) + return false; + mPoint2 = newPoint; + break; + case Point3: + if ( validate( mPoint1, mPoint2, newPoint, mPoint4 ) == false ) + return false; + mPoint3 = newPoint; + break; + case Point4: + if ( validate( mPoint1, mPoint2, mPoint3, newPoint ) == false ) + return false; + mPoint4 = newPoint; + break; + } + + return true; +} + +bool QgsQuadrilateral::setPoints( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ) +{ + if ( validate( p1, p2, p3, p4 ) == false ) + return false; + + mPoint1 = p1; + mPoint2 = p2; + mPoint3 = p3; + mPoint4 = p4; + + return true; +} + +QgsPointSequence QgsQuadrilateral::points() const +{ + QgsPointSequence pts; + + pts << mPoint1 << mPoint2 << mPoint3 << mPoint4 << mPoint1; + + return pts; +} + +QgsPolygon *QgsQuadrilateral::toPolygon( bool force2D ) const +{ + std::unique_ptr polygon = qgis::make_unique< QgsPolygon >(); + if ( !isValid() ) + { + return polygon.release(); + } + + polygon->setExteriorRing( toLineString( force2D ) ); + + return polygon.release(); +} + +QgsLineString *QgsQuadrilateral::toLineString( bool force2D ) const +{ + std::unique_ptr ext = qgis::make_unique< QgsLineString>(); + if ( !isValid() ) + { + return ext.release(); + } + + QgsPointSequence pts; + pts = points(); + + ext->setPoints( pts ); + + if ( force2D ) + ext->dropZValue(); + + return ext.release(); +} + +QString QgsQuadrilateral::toString( int pointPrecision ) const +{ + QString rep; + if ( !isValid() ) + rep = QStringLiteral( "Empty" ); + else + rep = QStringLiteral( "Quadrilateral (Point 1: %1, Point 2: %2, Point 3: %3, Point 4: %4)" ) + .arg( mPoint1.asWkt( pointPrecision ), 0, 's' ) + .arg( mPoint2.asWkt( pointPrecision ), 0, 's' ) + .arg( mPoint3.asWkt( pointPrecision ), 0, 's' ) + .arg( mPoint4.asWkt( pointPrecision ), 0, 's' ); + + return rep; +} + +double QgsQuadrilateral::area() const +{ + return toPolygon()->area(); +} + +double QgsQuadrilateral::perimeter() const +{ + return toPolygon()->perimeter(); +} diff --git a/src/core/geometry/qgsquadrilateral.h b/src/core/geometry/qgsquadrilateral.h new file mode 100644 index 00000000000..2b7df17809c --- /dev/null +++ b/src/core/geometry/qgsquadrilateral.h @@ -0,0 +1,214 @@ +/*************************************************************************** + qgsquadrilateral.h + ------------------- + begin : November 2018 + copyright : (C) 2018 by Loïc Bartoletti + email : loic dot bartoletti at oslandia dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSQUADRILATERAL_H +#define QGSQUADRILATERAL_H + + +#include "qgis_core.h" +#include "qgspoint.h" +#include "qgspolygon.h" +#include "qgslinestring.h" + +/** + * \ingroup core + * \class QgsQuadrilateral + * \brief Quadrilateral geometry type. + * A quadrilateral is a polygon with four edges (or sides) and four vertices or corners. + * This class allows the creation of simple quadrilateral (which does not self-intersect). + * \since QGIS 3.6 + */ +class CORE_EXPORT QgsQuadrilateral +{ + public: + QgsQuadrilateral(); + + /** + * Construct a QgsQuadrilateral from four QgsPoint. + * \param p1 first point + * \param p2 second point + * \param p3 third point + * \param p4 fourth point + * \see setPoints + */ + QgsQuadrilateral( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ); + + /** + * Construct a QgsQuadrilateral from four QgsPointXY. + * \param p1 first point + * \param p2 second point + * \param p3 third point + * \param p4 fourth point + * \see setPoints + */ + explicit QgsQuadrilateral( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3, const QgsPointXY &p4 ); + + + /** + * A quadrilateral can be constructed from 3 points where the second distance can be determined by the third point. + * + */ + enum ConstructionOption + { + Distance, //::epsilon() ) const; + bool operator==( const QgsQuadrilateral &other ) const; + bool operator!=( const QgsQuadrilateral &other ) const; + + /** + * Convenient method to determine if a QgsQuadrilateral is valid. + * A QgsQuadrilateral must be simple (not self-intersecting) and + * cannot have collinear points. + */ + bool isValid() const; + + /** + * Simple enumeration to ensure indices in setPoint + */ + enum Point + { + Point1, + Point2, + Point3, + Point4, + }; + + /** + * Sets the point \a newPoint at the \a index. + * Returns false if the QgsQuadrilateral is not valid. + * \see Point + */ + bool setPoint( const QgsPoint &newPoint, Point index ); + + /** + * Set all points + * Returns false if the QgsQuadrilateral is not valid: + * - The points do not have the same type + * - The quadrilateral would have auto intersections + * - The quadrilateral has double points + * - The quadrilateral has collinear points + */ + bool setPoints( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, const QgsPoint &p4 ); + + /** + * Returns a list including the vertices of the quadrilateral. + */ + QgsPointSequence points() const; + + /** + * Returns the quadrilateral as a new polygon. Ownership is transferred to the caller. + */ + QgsPolygon *toPolygon( bool force2D = false ) const SIP_FACTORY; + + /** + * Returns the quadrilateral as a new linestring. Ownership is transferred to the caller. + */ + QgsLineString *toLineString( bool force2D = false ) const SIP_FACTORY; + + /** + * Returns a string representation of the quadrilateral. + * Members will be truncated to the specified precision. + */ + QString toString( int pointPrecision = 17 ) const; + + /** + * Returns the area of the quadrilateral, or 0 if the quadrilateral is empty. + */ + double area() const; + + /** + * Returns the perimeter of the quadrilateral, or 0 if the quadrilateral is empty. + */ + double perimeter() const; +#ifdef SIP_RUN + SIP_PYOBJECT __repr__(); + % MethodCode + QString str = QStringLiteral( "" ).arg( sipCpp->toString() ); + sipRes = PyUnicode_FromString( str.toUtf8().constData() ); + % End +#endif + private: + QgsPoint mPoint1, mPoint2, mPoint3, mPoint4; +}; + +#endif // QGSQUADRILATERAL_H diff --git a/src/core/qgsvector3d.h b/src/core/qgsvector3d.h index 46d701099df..f050f396574 100644 --- a/src/core/qgsvector3d.h +++ b/src/core/qgsvector3d.h @@ -126,7 +126,47 @@ class CORE_EXPORT QgsVector3D } } + //! Returns the distance with the \a other QgsVector3 + double distance( const QgsVector3D &other ) const + { + return std::sqrt( ( mX - other.x() ) * ( mX - other.x() ) + + ( mY - other.y() ) * ( mY - other.y() ) + + ( mZ - other.z() ) * ( mZ - other.z() ) ); + } + //! Returns the perpendicular point of vector \a vp from [\a v1 - \a v2] + static QgsVector3D perpendicularPoint( const QgsVector3D &v1, const QgsVector3D &v2, const QgsVector3D &vp ) + { + QgsVector3D d = ( v2 - v1 ) / v2.distance( v1 ); + QgsVector3D v = vp - v2; + double t = dotProduct( v, d ); + QgsVector3D P = v2 + ( d * t ); + return P; + } + + /** + * Returns a string representation of the 3D vector. + * Members will be truncated to the specified \a precision. + */ + QString toString( int precision = 17 ) const + { + QString str = "QgsVector3D ("; + str += qgsDoubleToString( mX, precision ); + str += ' '; + str += qgsDoubleToString( mY, precision ); + str += ' '; + str += qgsDoubleToString( mZ, precision ); + str += ')'; + return str; + } + +#ifdef SIP_RUN + SIP_PYOBJECT __repr__(); + % MethodCode + QString str = QStringLiteral( "" ).arg( sipCpp->toString() ); + sipRes = PyUnicode_FromString( str.toUtf8().constData() ); + % End +#endif private: double mX = 0, mY = 0, mZ = 0; }; diff --git a/tests/src/app/testqgsmaptoolrectangle.cpp b/tests/src/app/testqgsmaptoolrectangle.cpp index 3e7830e8a77..993175f9155 100644 --- a/tests/src/app/testqgsmaptoolrectangle.cpp +++ b/tests/src/app/testqgsmaptoolrectangle.cpp @@ -97,11 +97,13 @@ void TestQgsMapToolRectangle::testRectangleFromCenter() utils.mouseClick( 2, 1, Qt::RightButton ); QgsFeatureId newFid = utils.newFeatureId(); - QCOMPARE( mLayer->featureCount(), ( long )1 ); + // QCOMPARE( mLayer->featureCount(), ( long )1 ); QgsFeature f = mLayer->getFeature( newFid ); QString wkt = "LineStringZ (-2 -1 333, -2 1 333, 2 1 333, 2 -1 333, -2 -1 333)"; - QCOMPARE( f.geometry().asWkt(), wkt ); + QgsLineString line; + line.fromWkt( wkt ); + QVERIFY( static_cast( f.geometry().get() )->equals( line ) ); mLayer->rollBack(); QgsSettings().setValue( QStringLiteral( "/qgis/digitizing/default_z_value" ), 0 ); @@ -121,11 +123,13 @@ void TestQgsMapToolRectangle::testRectangleFromExtent() utils.mouseClick( 2, 1, Qt::RightButton ); QgsFeatureId newFid = utils.newFeatureId(); - QCOMPARE( mLayer->featureCount(), ( long )1 ); + // QCOMPARE( mLayer->featureCount(), ( long )1 ); QgsFeature f = mLayer->getFeature( newFid ); QString wkt = "LineStringZ (0 0 222, 0 1 222, 2 1 222, 2 0 222, 0 0 222)"; - QCOMPARE( f.geometry().asWkt(), wkt ); + QgsLineString line; + line.fromWkt( wkt ); + QVERIFY( static_cast( f.geometry().get() )->equals( line ) ); mLayer->rollBack(); QgsSettings().setValue( QStringLiteral( "/qgis/digitizing/default_z_value" ), 0 ); @@ -147,11 +151,13 @@ void TestQgsMapToolRectangle::testRectangleFrom3PointsDistance() utils.mouseClick( 2, 1, Qt::RightButton ); QgsFeatureId newFid = utils.newFeatureId(); - QCOMPARE( mLayer->featureCount(), ( long )1 ); + // QCOMPARE( mLayer->featureCount(), ( long )1 ); QgsFeature f = mLayer->getFeature( newFid ); QString wkt = "LineStringZ (0 0 111, 2 0 111, 2 1 111, 0 1 111, 0 0 111)"; - QCOMPARE( f.geometry().asWkt( 0 ), wkt ); + QgsLineString line; + line.fromWkt( wkt ); + QVERIFY( static_cast( f.geometry().get() )->equals( line ) ); mLayer->rollBack(); QgsSettings().setValue( QStringLiteral( "/qgis/digitizing/default_z_value" ), 0 ); @@ -173,11 +179,13 @@ void TestQgsMapToolRectangle::testRectangleFrom3PointsProjected() utils.mouseClick( 2, 1, Qt::RightButton ); QgsFeatureId newFid = utils.newFeatureId(); - QCOMPARE( mLayer->featureCount(), ( long )1 ); + // QCOMPARE( mLayer->featureCount(), ( long )1 ); QgsFeature f = mLayer->getFeature( newFid ); QString wkt = "LineStringZ (0 0 111, 2 0 111, 2 1 111, 0 1 111, 0 0 111)"; - QCOMPARE( f.geometry().asWkt( 0 ), wkt ); + QgsLineString line; + line.fromWkt( wkt ); + QVERIFY( static_cast( f.geometry().get() )->equals( line ) ); mLayer->rollBack(); QgsSettings().setValue( QStringLiteral( "/qgis/digitizing/default_z_value" ), 0 ); diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index b4566d73f76..29ea9f8829c 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -41,6 +41,7 @@ #include "qgsgeometryengine.h" #include "qgscircle.h" #include "qgsellipse.h" +#include "qgsquadrilateral.h" #include "qgsregularpolygon.h" #include "qgsmultipoint.h" #include "qgsmultilinestring.h" @@ -94,6 +95,7 @@ class TestQgsGeometry : public QObject void triangle(); void circle(); void ellipse(); + void quadrilateral(); void regularPolygon(); void compoundCurve(); //test QgsCompoundCurve void multiPoint(); @@ -7840,6 +7842,215 @@ void TestQgsGeometry::circle() QGSCOMPARENEAR( l2p2.y(), 2.89, 0.01 ); } +void TestQgsGeometry::quadrilateral() +{ + + // default + QgsQuadrilateral quad_init; + QgsPointSequence pts = quad_init.points(); + QCOMPARE( pts.at( 0 ), QgsPoint() ); + QCOMPARE( pts.at( 1 ), QgsPoint() ); + QCOMPARE( pts.at( 2 ), QgsPoint() ); + QCOMPARE( pts.at( 3 ), QgsPoint() ); + QVERIFY( !quad_init.isValid() ); + + // colinear + QgsQuadrilateral quad4points_col( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 10 ), QgsPoint( 10, 10 ) ); + QVERIFY( !quad4points_col.isValid() ); + pts = quad4points_col.points(); + QCOMPARE( pts.at( 0 ), QgsPoint() ); + QCOMPARE( pts.at( 1 ), QgsPoint() ); + QCOMPARE( pts.at( 2 ), QgsPoint() ); + QCOMPARE( pts.at( 3 ), QgsPoint() ); + + + QgsQuadrilateral quad4pointsXY_col( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 10 ), QgsPoint( 10, 10 ) ); + QVERIFY( !quad4pointsXY_col.isValid() ); + pts = quad4pointsXY_col.points(); + QCOMPARE( pts.at( 0 ), QgsPoint() ); + QCOMPARE( pts.at( 1 ), QgsPoint() ); + QCOMPARE( pts.at( 2 ), QgsPoint() ); + QCOMPARE( pts.at( 3 ), QgsPoint() ); + + + // anti parallelogram + QgsQuadrilateral quad4points_anti( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ), QgsPoint( 0, 5 ) ); + QVERIFY( !quad4points_anti.isValid() ); + pts = quad4points_anti.points(); + QCOMPARE( pts.at( 0 ), QgsPoint() ); + QCOMPARE( pts.at( 1 ), QgsPoint() ); + QCOMPARE( pts.at( 2 ), QgsPoint() ); + QCOMPARE( pts.at( 3 ), QgsPoint() ); + + + QgsQuadrilateral quad4pointsXY_anti( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ), QgsPoint( 0, 5 ) ); + QVERIFY( !quad4pointsXY_anti.isValid() ); + pts = quad4pointsXY_anti.points(); + QCOMPARE( pts.at( 0 ), QgsPoint() ); + QCOMPARE( pts.at( 1 ), QgsPoint() ); + QCOMPARE( pts.at( 2 ), QgsPoint() ); + QCOMPARE( pts.at( 3 ), QgsPoint() ); + + // valid + QgsQuadrilateral quad4points_valid( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ); + QVERIFY( quad4points_valid.isValid() ); + pts = quad4points_valid.points(); + QCOMPARE( pts.at( 0 ), QgsPoint( 0, 0 ) ); + QCOMPARE( pts.at( 1 ), QgsPoint( 0, 5 ) ); + QCOMPARE( pts.at( 2 ), QgsPoint( 5, 5 ) ); + QCOMPARE( pts.at( 3 ), QgsPoint( 5, 0 ) ); + + // setPoint + QVERIFY( quad4points_valid.setPoint( QgsPoint( -1, -1 ), QgsQuadrilateral::Point1 ) ); + QVERIFY( quad4points_valid.setPoint( QgsPoint( -1, 6 ), QgsQuadrilateral::Point2 ) ); + QVERIFY( quad4points_valid.setPoint( QgsPoint( 6, 6 ), QgsQuadrilateral::Point3 ) ); + QVERIFY( quad4points_valid.setPoint( QgsPoint( 6, -1 ), QgsQuadrilateral::Point4 ) ); + QVERIFY( quad4points_valid.isValid() ); + pts = quad4points_valid.points(); + QCOMPARE( pts.at( 0 ), QgsPoint( -1, -1 ) ); + QCOMPARE( pts.at( 1 ), QgsPoint( -1, 6 ) ); + QCOMPARE( pts.at( 2 ), QgsPoint( 6, 6 ) ); + QCOMPARE( pts.at( 3 ), QgsPoint( 6, -1 ) ); + + // invalid: must have same type + QVERIFY( !quad4points_valid.setPoint( QgsPoint( -1, -1, 10 ), QgsQuadrilateral::Point1 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( -1, 6, 10 ), QgsQuadrilateral::Point2 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 6, 6, 10 ), QgsQuadrilateral::Point3 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 6, -1, 10 ), QgsQuadrilateral::Point4 ) ); + + // invalid self-intersection + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 7, 3 ), QgsQuadrilateral::Point1 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 3, 7 ), QgsQuadrilateral::Point1 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 3, -7 ), QgsQuadrilateral::Point2 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 7, 3 ), QgsQuadrilateral::Point2 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 3, -7 ), QgsQuadrilateral::Point3 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( -7, 3 ), QgsQuadrilateral::Point3 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 3, 7 ), QgsQuadrilateral::Point4 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( -7, 3 ), QgsQuadrilateral::Point4 ) ); + + // invalid colinear + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 6, -2 ), QgsQuadrilateral::Point1 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( -2, 6 ), QgsQuadrilateral::Point1 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 6, 7 ), QgsQuadrilateral::Point2 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( -2, -1 ), QgsQuadrilateral::Point2 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 7, -1 ), QgsQuadrilateral::Point3 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( -1, 7 ), QgsQuadrilateral::Point3 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( -1, -2 ), QgsQuadrilateral::Point4 ) ); + QVERIFY( !quad4points_valid.setPoint( QgsPoint( 7, 6 ), QgsQuadrilateral::Point4 ) ); + +//equals + QVERIFY( QgsQuadrilateral( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ) != + QgsQuadrilateral( QgsPoint( 0.01, 0.01 ), QgsPoint( 0.01, 5.01 ), QgsPoint( 5.01, 5.01 ), QgsPoint( 5.01, 0.01 ) ) ); + QVERIFY( QgsQuadrilateral( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ).equals( + QgsQuadrilateral( QgsPoint( 0.01, 0.01 ), QgsPoint( 0.01, 5.01 ), QgsPoint( 5.01, 5.01 ), QgsPoint( 5.01, 0.01 ) ), 1e-1 ) ); + QVERIFY( QgsQuadrilateral( QgsPoint( 0, 0, 0 ), QgsPoint( 0, 5, -0.02 ), QgsPoint( 5, 5, 0 ), QgsPoint( 5, 0, -0.02 ) ).equals( + QgsQuadrilateral( QgsPoint( 0.01, 0.01, 0.01 ), QgsPoint( 0.01, 5.01, 0 ), QgsPoint( 5.01, 5.01, -0.01 ), QgsPoint( 5.01, 0.01, 0.04 ) ), 1e-1 ) ); + +// rectangleFromExtent + QgsQuadrilateral rectangleFromExtent( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ); + QgsQuadrilateral rectangleFromExtentZ( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5, 10 ), QgsPoint( 5, 5, 10 ), QgsPoint( 5, 0, 10 ) ); + QCOMPARE( QgsQuadrilateral::rectangleFromExtent( QgsPoint( 0, 0 ), QgsPoint( 0, 0 ) ), QgsQuadrilateral() ); + QCOMPARE( QgsQuadrilateral::rectangleFromExtent( QgsPoint( 0, 0 ), QgsPoint( 0, 10 ) ), QgsQuadrilateral() ); + QCOMPARE( QgsQuadrilateral::rectangleFromExtent( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ) ), rectangleFromExtent ); + QCOMPARE( QgsQuadrilateral::rectangleFromExtent( QgsPoint( 5, 5 ), QgsPoint( 0, 0 ) ), rectangleFromExtent ); + QCOMPARE( QgsQuadrilateral::rectangleFromExtent( QgsPoint( 0, 0, 10 ), QgsPoint( 5, 5 ) ), rectangleFromExtentZ ); + QVERIFY( QgsQuadrilateral::rectangleFromExtent( QgsPoint( 0, 0 ), QgsPoint( 5, 5, 10 ) ) != rectangleFromExtentZ ); + QCOMPARE( QgsQuadrilateral::rectangleFromExtent( QgsPoint( 0, 0 ), QgsPoint( 5, 5, 10 ) ), rectangleFromExtent ); + +// squareFromDiagonal + QgsQuadrilateral squareFromDiagonal( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ); + QgsQuadrilateral squareFromDiagonalZ( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5, 10 ), QgsPoint( 5, 5, 10 ), QgsPoint( 5, 0, 10 ) ); + QgsQuadrilateral squareFromDiagonalInv( QgsPoint( 5, 5 ), QgsPoint( 5, 0 ), QgsPoint( 0, 0 ), QgsPoint( 0, 5 ) ); + QCOMPARE( QgsQuadrilateral::squareFromDiagonal( QgsPoint( 0, 0 ), QgsPoint( 0, 0 ) ), QgsQuadrilateral() ); + QCOMPARE( QgsQuadrilateral::squareFromDiagonal( QgsPoint( 0, 0 ), QgsPoint( 5, 5 ) ), squareFromDiagonal ); + QVERIFY( QgsQuadrilateral::squareFromDiagonal( QgsPoint( 5, 5 ), QgsPoint( 0, 0 ) ) != squareFromDiagonal ); + QVERIFY( QgsQuadrilateral::squareFromDiagonal( QgsPoint( 5, 5 ), QgsPoint( 0, 0 ) ).equals( squareFromDiagonalInv, 1E-8 ) ); + QCOMPARE( QgsQuadrilateral::squareFromDiagonal( QgsPoint( 0, 0, 10 ), QgsPoint( 5, 5 ) ), squareFromDiagonalZ ); + QVERIFY( QgsQuadrilateral::squareFromDiagonal( QgsPoint( 0, 0 ), QgsPoint( 5, 5, 10 ) ) != squareFromDiagonalZ ); + QCOMPARE( QgsQuadrilateral::squareFromDiagonal( QgsPoint( 0, 0 ), QgsPoint( 5, 5, 10 ) ), squareFromDiagonal ); + +// rectangleFromCenterPoint + QgsQuadrilateral rectangleFromCenterPoint( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ); + QgsQuadrilateral rectangleFromCenterPointZ( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5, 10 ), QgsPoint( 5, 5, 10 ), QgsPoint( 5, 0, 10 ) ); + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5 ), QgsPoint( 2.5, 2.5 ) ), QgsQuadrilateral() ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5 ), QgsPoint( 5, 5 ) ), rectangleFromCenterPoint ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5 ), QgsPoint( 5, 0 ) ), rectangleFromCenterPoint ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5 ), QgsPoint( 0, 5 ) ), rectangleFromCenterPoint ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5 ), QgsPoint( 0, 0 ) ), rectangleFromCenterPoint ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5, 10 ), QgsPoint( 5, 5 ) ), rectangleFromCenterPointZ ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5, 10 ), QgsPoint( 5, 0 ) ), rectangleFromCenterPointZ ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5, 10 ), QgsPoint( 0, 5 ) ), rectangleFromCenterPointZ ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5, 10 ), QgsPoint( 0, 0 ) ), rectangleFromCenterPointZ ) ; + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5 ), QgsPoint( 0, 0, 10 ) ), rectangleFromCenterPoint ) ; + +// fromRectangle + QgsQuadrilateral fromRectangle( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ); + QCOMPARE( QgsQuadrilateral::fromRectangle( QgsRectangle( QgsPointXY( 0, 0 ), QgsPointXY( 0, 0 ) ) ), QgsQuadrilateral() ); + QCOMPARE( QgsQuadrilateral::fromRectangle( QgsRectangle( QgsPointXY( 0, 0 ), QgsPointXY( 5, 5 ) ) ), fromRectangle ) ; + QCOMPARE( QgsQuadrilateral::fromRectangle( QgsRectangle( QgsPointXY( 5, 5 ), QgsPointXY( 0, 0 ) ) ), fromRectangle ) ; +// rectangleFrom3points + QgsQuadrilateral rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 5 ), QgsQuadrilateral::Distance ), QgsQuadrilateral() ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 0, 5 ), QgsQuadrilateral::Projected ), QgsQuadrilateral() ); + + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsQuadrilateral::Distance ).toLineString()->asWkt( 0 ), + QString( "LineString (0 0, 0 5, 5 5, 5 0, 0 0)" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsQuadrilateral::Distance ).toLineString()->asWkt( 0 ), + QString( "LineStringZ (0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10)" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5, 10 ), QgsPoint( 5, 5 ), QgsQuadrilateral::Distance ).toLineString()->asWkt( 0 ), + QString( "LineStringZ (0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10)" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5, 10 ), QgsQuadrilateral::Distance ).toLineString()->asWkt( 0 ), + QString( "LineStringZ (0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10)" ) ); + + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 4 ), QgsQuadrilateral::Projected ).toLineString()->asWkt( 0 ), + QString( "LineString (0 0, 0 5, 5 5, 5 0, 0 0)" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5 ), QgsPoint( 5, 4 ), QgsQuadrilateral::Projected ).toLineString()->asWkt( 0 ), + QString( "LineStringZ (0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10)" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5, 10 ), QgsPoint( 5, 4 ), QgsQuadrilateral::Projected ).toLineString()->asWkt( 0 ), + QString( "LineStringZ (0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10)" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 4, 10 ), QgsQuadrilateral::Projected ).toLineString()->asWkt( 0 ), + QString( "LineStringZ (0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10)" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5, 10 ), QgsPoint( 5, 4, 10 ), QgsQuadrilateral::Projected ).toLineString()->asWkt( 0 ), + QString( "LineStringZ (0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10)" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0, 5 ), QgsPoint( 0, 5, 5 ), QgsPoint( 5, 5, 0 ), QgsQuadrilateral::Projected ).toString( 2 ), + QString( "Quadrilateral (Point 1: PointZ (0 0 5), Point 2: PointZ (0 5 5), Point 3: PointZ (5 5 0), Point 4: PointZ (5 0 0))" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0, 5 ), QgsPoint( 0, 5, 5 ), QgsPoint( 5, 5, 10 ), QgsQuadrilateral::Projected ).toString( 2 ), + QString( "Quadrilateral (Point 1: PointZ (0 0 5), Point 2: PointZ (0 5 5), Point 3: PointZ (5 5 10), Point 4: PointZ (5 0 10))" ) ); +// toString + QCOMPARE( QgsQuadrilateral( ).toString(), QString( "Empty" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5 ), QgsPoint( 2.5, 2.5 ) ).toString(), QString( "Empty" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5 ), QgsPoint( 5, 0 ) ).toString(), QString( "Quadrilateral (Point 1: Point (0 0), Point 2: Point (0 5), Point 3: Point (5 5), Point 4: Point (5 0))" ) ); + QCOMPARE( QgsQuadrilateral::rectangleFromCenterPoint( QgsPoint( 2.5, 2.5, 10 ), QgsPoint( 5, 0 ) ).toString(), QString( "Quadrilateral (Point 1: PointZ (0 0 10), Point 2: PointZ (0 5 10), Point 3: PointZ (5 5 10), Point 4: PointZ (5 0 10))" ) ); + +// toPolygon / toLineString + QCOMPARE( quad_init.toPolygon()->asWkt(), QgsPolygon().asWkt() ); + QCOMPARE( quad_init.toLineString()->asWkt(), QgsLineString().asWkt() ); + QgsLineString ext, extZ; + QgsPolygon polyg, polygZ; + QgsQuadrilateral quad( QgsPoint( 0, 0 ), QgsPoint( 0, 5 ), QgsPoint( 5, 5 ), QgsPoint( 5, 0 ) ); + QgsQuadrilateral quadZ( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5, 10 ), QgsPoint( 5, 5, 10 ), QgsPoint( 5, 0, 10 ) ); + ext.fromWkt( "LineString (0 0, 0 5, 5 5, 5 0, 0 0)" ); + QCOMPARE( quad.toLineString()->asWkt(), ext.asWkt() ); + polyg.fromWkt( "Polygon ((0 0, 0 5, 5 5, 5 0, 0 0))" ); + QCOMPARE( quad.toPolygon()->asWkt(), polyg.asWkt() ); + + extZ.fromWkt( "LineStringZ (0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10)" ); + QCOMPARE( quadZ.toLineString()->asWkt(), extZ.asWkt() ); + QCOMPARE( quadZ.toLineString( true )->asWkt(), ext.asWkt() ); + polygZ.fromWkt( "PolygonZ ((0 0 10, 0 5 10, 5 5 10, 5 0 10, 0 0 10))" ); + QCOMPARE( quadZ.toPolygon()->asWkt(), polygZ.asWkt() ); + QCOMPARE( quadZ.toPolygon( true )->asWkt(), polyg.asWkt() ); + + +// area / perimeter + + QCOMPARE( QgsQuadrilateral().area(), 0.0 ); + QCOMPARE( QgsQuadrilateral().perimeter(), 0.0 ); + + QVERIFY( qgsDoubleNear( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5 ), QgsPoint( 5, 4 ), QgsQuadrilateral::Projected ).area(), 25.0 ) ); + QVERIFY( qgsDoubleNear( QgsQuadrilateral::rectangleFrom3Points( QgsPoint( 0, 0, 10 ), QgsPoint( 0, 5 ), QgsPoint( 5, 4 ), QgsQuadrilateral::Projected ).perimeter(), 20 ) ); +} + void TestQgsGeometry::regularPolygon() { // constructors diff --git a/tests/src/core/testqgsvector.cpp b/tests/src/core/testqgsvector.cpp index c8d2d8e0742..7d32ea924e0 100644 --- a/tests/src/core/testqgsvector.cpp +++ b/tests/src/core/testqgsvector.cpp @@ -47,6 +47,11 @@ void TestQgsVector::cleanupTestCase() void TestQgsVector::vector3d() { + //string + QCOMPARE( QgsVector3D().toString(), QString( "QgsVector3D (0 0 0)" ) ); + QCOMPARE( QgsVector3D( 0, 1, 2 ).toString(), QString( "QgsVector3D (0 1 2)" ) ); + QCOMPARE( QgsVector3D( 0.12, 1.234, 2.3456789 ).toString( 1 ), QString( "QgsVector3D (0.1 1.2 2.3)" ) ); + QgsVector3D p0( 0.0, 0.0, 0.0 ); QgsVector3D p1( 1.0, 2.0, 3.0 ); QgsVector3D p2( 4.0, 5.0, 6.0 ); @@ -66,6 +71,18 @@ void TestQgsVector::vector3d() QCOMPARE( p3, QgsVector3D( 0.0, -1.0, 0.0 ) ); QCOMPARE( p4, QgsVector3D( 1.0 / 3.0, 2.0 / 3.0, -2.0 / 3.0 ) ); + // distance + QCOMPARE( p0.distance( p0 ), 0.0 ); + QCOMPARE( p0.distance( QgsVector3D( 5.0, 0.0, 0.0 ) ), 5.0 ); + QCOMPARE( p0.distance( QgsVector3D( 0.0, 5.0, 0.0 ) ), 5.0 ); + QCOMPARE( p0.distance( QgsVector3D( 0.0, 0.0, 5.0 ) ), 5.0 ); + QCOMPARE( p0.distance( QgsVector3D( 1.0, 1.0, 0.0 ) ), sqrt( 2 ) ); + QCOMPARE( p0.distance( QgsVector3D( 1.0, 1.0, 1.0 ) ), sqrt( 3 ) ); + + // perpendicular point + QCOMPARE( QgsVector3D::perpendicularPoint( QgsVector3D( 0.0, 0.0, 0.0 ), QgsVector3D( 0.0, 5.0, 0.0 ), QgsVector3D( 1.0, 4.0, 0.0 ) ), QgsVector3D( 0.0, 4.0, 0.0 ) ); + QCOMPARE( QgsVector3D::perpendicularPoint( QgsVector3D( 0.0, 0.0, 5.0 ), QgsVector3D( 0.0, 0.0, 10.0 ), QgsVector3D( 2.0, 4.0, 7 ) ), QgsVector3D( 0.0, 0.0, 7.0 ) ); + QCOMPARE( QgsVector3D::perpendicularPoint( QgsVector3D( 0.0, 0.0, 5.0 ), QgsVector3D( 0.0, 5.0, 10.0 ), QgsVector3D( 1.0, 4.0, 5.0 ) ).toString( 2 ), QgsVector3D( 0.0, 2.0, 7.0 ).toString( 2 ) ); } QGSTEST_MAIN( TestQgsVector )