Refactoring of rectangle maptools

Adds a new geometry class QgsQuadrilateral, for 4 sided geometries.
This commit is contained in:
lbartoletti 2019-01-15 00:24:16 +01:00 committed by Nyall Dawson
parent 046bec48bc
commit edadcb773f
15 changed files with 1179 additions and 186 deletions

View File

@ -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 &center, 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( "<QgsQuadrilateral: %1>" ).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 *
************************************************************************/

View File

@ -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( "<QgsVector3D: %1>" ).arg( sipCpp->toString() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
};
/************************************************************************

View File

@ -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

View File

@ -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<QgsLineString> 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<QgsPolygon> 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<QgsVectorLayer *>( mCanvas->currentLayer() )->wkbType() ) ) );
clean();
QgsMapToolCapture::deactivate();
@ -184,7 +98,7 @@ void QgsMapToolAddRectangle::clean()
mParentTool->deleteTempRubberBand();
}
mRectangle = QgsBox3d();
mRectangle = QgsQuadrilateral();
QgsVectorLayer *vLayer = static_cast<QgsVectorLayer *>( QgisApp::instance()->activeLayer() );
if ( vLayer )

View File

@ -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<QgsSnapIndicator> 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

View File

@ -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;
}
}
}
}

View File

@ -22,6 +22,7 @@
#include "qgspoint.h"
#include "qgsmapmouseevent.h"
#include "qgssnapindicator.h"
#include "qgsquadrilateral.h"
#include <memory>
@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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 &center, 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<QgsPolygon> 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<QgsLineString> 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();
}

View File

@ -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, //<! Second distance is equal to the distance between 2nd and 3rd point
Projected, //<! Second distance is equal to the distance of the perpendicualr projection of the 3rd point on the segment or its extension.
};
/**
* 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
* \see ConstructionOption
*/
static QgsQuadrilateral rectangleFrom3Points( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, ConstructionOption mode );
/**
* Construct a QgsQuadrilateral as a rectangle from an extent, defined by
* two opposite corner points.
* Z is taken from point \a p1.
* \param p1 first point
* \param p2 second point
*/
static QgsQuadrilateral rectangleFromExtent( const QgsPoint &p1, const QgsPoint &p2 );
#ifndef SIP_RUN
/**
* Alias for rectangleFromDiagonal
*/
static constexpr auto &rectangleFromDiagonal = rectangleFromExtent;
#endif
/**
* Construct a QgsQuadrilateral as a square from a diagonal.
* Z is taken from point \a p1.
* \param p1 first point
* \param p2 second point
*/
static QgsQuadrilateral squareFromDiagonal( const QgsPoint &p1, const QgsPoint &p2 );
/**
* Construct a QgsQuadrilateral as a rectangle from center point \a center
* and another point \a point.
* Z is taken from \a center point.
* \param center center point
* \param point corner point
*/
static QgsQuadrilateral rectangleFromCenterPoint( const QgsPoint &center, const QgsPoint &point );
/**
* Construct a QgsQuadrilateral as a rectangle from a QgsRectangle.
* \param rectangle rectangle
*/
static QgsQuadrilateral fromRectangle( const QgsRectangle &rectangle );
// TODO:
// Rhombus
/**
* 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
*/
bool equals( const QgsQuadrilateral &other, double epsilon = 4 * std::numeric_limits<double>::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( "<QgsQuadrilateral: %1>" ).arg( sipCpp->toString() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif
private:
QgsPoint mPoint1, mPoint2, mPoint3, mPoint4;
};
#endif // QGSQUADRILATERAL_H

View File

@ -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( "<QgsVector3D: %1>" ).arg( sipCpp->toString() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif
private:
double mX = 0, mY = 0, mZ = 0;
};

View File

@ -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<QgsLineString *>( 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<QgsLineString *>( 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<QgsLineString *>( 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<QgsLineString *>( f.geometry().get() )->equals( line ) );
mLayer->rollBack();
QgsSettings().setValue( QStringLiteral( "/qgis/digitizing/default_z_value" ), 0 );

View File

@ -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

View File

@ -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 )