diff --git a/python/core/core.sip b/python/core/core.sip index 93400610e75..73bf6b4c9c5 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -370,5 +370,6 @@ %Include geometry/qgspointv2.sip %Include geometry/qgspolygon.sip %Include geometry/qgssurface.sip +%Include geometry/qgstriangle.sip %Include geometry/qgswkbtypes.sip %Include geometry/qgswkbptr.sip diff --git a/python/core/geometry/qgsgeometryutils.sip b/python/core/geometry/qgsgeometryutils.sip index f1bf6b89925..8fad0714915 100644 --- a/python/core/geometry/qgsgeometryutils.sip +++ b/python/core/geometry/qgsgeometryutils.sip @@ -67,4 +67,10 @@ class QgsGeometryUtils static QgsPointV2 midpoint (const QgsPointV2& pt1, const QgsPointV2& pt2); + static double gradient( const QgsPointV2& pt1, const QgsPointV2& pt2 ); + + static void coefficients(const QgsPointV2 &pt1, const QgsPointV2 &pt2, double& a /Out/, double& b /Out/, double& c /Out/); + + static QgsLineString perpendicularSegment( const QgsPointV2& p, const QgsPointV2& s1, const QgsPointV2& s2 ); + }; diff --git a/python/core/geometry/qgstriangle.sip b/python/core/geometry/qgstriangle.sip new file mode 100644 index 00000000000..fc8d250fc7b --- /dev/null +++ b/python/core/geometry/qgstriangle.sip @@ -0,0 +1,63 @@ +class QgsTriangle : public QgsPolygonV2 +{ +%TypeHeaderCode +#include +%End + + public: + QgsTriangle(); + QgsTriangle( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3 ); + explicit QgsTriangle( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3 ); + explicit QgsTriangle( const QPointF p1, const QPointF p2, const QPointF p3 ); + + // inherited: bool operator==( const QgsTriangle& other ) const; + // inherited: bool operator!=( const QgsTriangle& other ) const; + + virtual QString geometryType() const; + virtual QgsTriangle* clone() const /Factory/; + void clear(); + + virtual bool fromWkb( QgsConstWkbPtr& wkbPtr ); + bool fromWkt( const QString &wkt ); + // inherited: QString asWkt( int precision = 17 ) const; + // inherited: QDomElement asGML2( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const; + // inherited: QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const; + // inherited: QString asJSON( int precision = 17 ) const; + + QgsPolygonV2* surfaceToPolygon() const /Factory/; + QgsAbstractGeometry* toCurveType() const /Factory/; + + //overridden to handle LineString25D rings + virtual void setExteriorRing( QgsCurve* ring /Transfer/ ); + virtual QgsAbstractGeometry* boundary() const /Factory/; + // inherited: double pointDistanceToBoundary( double x, double y ) const; + QgsPointV2 vertexAt( int atVertex ) const; + + void addInteriorRing( QgsCurve* ring /Transfer/ ); // NOTE: no interior ring for triangle. + bool deleteVertex( QgsVertexId position ); + bool insertVertex( QgsVertexId position, const QgsPointV2 &vertex ); + bool moveVertex( QgsVertexId vId, const QgsPointV2& newPos ); + + QVector lengths() const; + QVector angles() const; + + bool isIsocele( double lengthTolerance = 0.0001 ) const; + bool isEquilateral( double lengthTolerance = 0.0001 ) const; + bool isRight( double angleTolerance = 0.0001 ) const; + bool isScalene( double lengthTolerance = 0.0001 ) const; + + QVector altitudes( ) const; + QVector medians( ) const; + QVector bisectors( double lengthTolerance = 0.0001 ) const; + + QgsTriangle medial( ) const; + QgsPointV2 orthocenter( double lengthTolerance = 0.0001 ) const; + QgsPointV2 circumscribedCenter( ) const; + double circumscribedRadius( ) const; + // TODO: + // QgsCircle circumscribedCircle ( ) const; // need QgsCircle (from CADDigitize.CADCircle) + QgsPointV2 inscribedCenter( ) const; + double inscribedRadius( ) const; + // TODO: + // QgsCircle inscribedCircle ( ) const; // need QgsCircle (from CADDigitize.CADCircle) +}; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d8db0ed1e15..9a6a878936e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -383,6 +383,7 @@ SET(QGIS_CORE_SRCS geometry/qgsmultisurface.cpp geometry/qgspointv2.cpp geometry/qgspolygon.cpp + geometry/qgstriangle.cpp geometry/qgswkbptr.cpp geometry/qgswkbtypes.cpp @@ -922,6 +923,7 @@ SET(QGIS_CORE_HDRS geometry/qgsmultisurface.h geometry/qgspointv2.h geometry/qgspolygon.h + geometry/qgstriangle.h geometry/qgssurface.h geometry/qgswkbptr.h geometry/qgswkbtypes.h diff --git a/src/core/geometry/qgsgeometryutils.cpp b/src/core/geometry/qgsgeometryutils.cpp index 7649674b0a8..e8e90be2164 100644 --- a/src/core/geometry/qgsgeometryutils.cpp +++ b/src/core/geometry/qgsgeometryutils.cpp @@ -21,6 +21,7 @@ email : marco.hugentobler at sourcepole dot com #include "qgslinestring.h" #include "qgswkbptr.h" +#include #include #include #include @@ -834,6 +835,79 @@ QgsPointV2 QgsGeometryUtils::midpoint( const QgsPointV2 &pt1, const QgsPointV2 & return QgsPointV2( pType, x, y, z, m ); } +double QgsGeometryUtils::gradient( const QgsPointV2 &pt1, const QgsPointV2 &pt2 ) +{ + double delta_x = pt2.x() - pt1.x(); + double delta_y = pt2.y() - pt1.y(); + if ( qgsDoubleNear( delta_x, 0.0 ) ) + { + return INFINITY; + } + + return delta_y / delta_x; +} + +void QgsGeometryUtils::coefficients( const QgsPointV2 &pt1, const QgsPointV2 &pt2, double &a, double &b, double &c ) +{ + if ( qgsDoubleNear( pt1.x(), pt2.x() ) ) + { + a = 1; + b = 0; + c = -pt1.x(); + } + else if ( qgsDoubleNear( pt1.y(), pt2.y() ) ) + { + a = 0; + b = 1; + c = -pt1.y(); + } + else + { + a = pt1.y() - pt2.y(); + b = pt2.x() - pt1.x(); + c = pt1.x() * pt2.y() - pt1.y() * pt2.x(); + } + +} + +QgsLineString QgsGeometryUtils::perpendicularSegment( const QgsPointV2 &p, const QgsPointV2 &s1, const QgsPointV2 &s2 ) +{ + QgsLineString line; + QgsPointV2 p2; + + if ( ( p == s1 ) || ( p == s2 ) ) + { + return line; + } + + double a, b, c; + coefficients( s1, s2, a, b, c ); + + if ( qgsDoubleNear( a, 0 ) ) + { + p2 = QgsPointV2( p.x(), s1.y() ); + } + else if ( qgsDoubleNear( b, 0 ) ) + { + p2 = QgsPointV2( s1.x(), p.y() ); + } + else + { + double y = ( -c - a * p.x() ) / b; + double m = gradient( s1, s2 ); + double d2 = 1 + m * m; + double H = p.y() - y; + double dx = m * H / d2; + double dy = m * dx; + p2 = QgsPointV2( p.x() + dx, y + dy ); + } + + line.addVertex( p ); + line.addVertex( p2 ); + + return line; +} + double QgsGeometryUtils::lineAngle( double x1, double y1, double x2, double y2 ) { double at = atan2( y2 - y1, x2 - x1 ); diff --git a/src/core/geometry/qgsgeometryutils.h b/src/core/geometry/qgsgeometryutils.h index 0cf039d87ee..b3647985824 100644 --- a/src/core/geometry/qgsgeometryutils.h +++ b/src/core/geometry/qgsgeometryutils.h @@ -291,6 +291,33 @@ class CORE_EXPORT QgsGeometryUtils */ static QgsPointV2 midpoint( const QgsPointV2 &pt1, const QgsPointV2 &pt2 ); + /** Return the gradient of a line defined by points \a pt1 and \a pt2. + * @param pt1 first point. + * @param pt2 second point. + * @return The gradient of this linear entity, or infinity if vertical + * @note added in QGIS 3.0 + */ + static double gradient( const QgsPointV2 &pt1, const QgsPointV2 &pt2 ); + + /** Return the coefficients (a, b, c for equation "ax + by + c = 0") of a line defined by points \a pt1 and \a pt2. + * @param pt1 first point. + * @param pt2 second point. + * @param a Output parameter, a coefficient of the equation. + * @param b Output parameter, b coefficient of the equation. + * @param c Output parameter, c coefficient of the equation. + * @note added in QGIS 3.0 + */ + static void coefficients( const QgsPointV2 &pt1, const QgsPointV2 &pt2, double &a, double &b, double &c ); + + /** + * @brief Create a perpendicular line segment from p to segment [s1, s2] + * @param p The point + * @param s1 The segment start point + * @param s2 The segment end point + * @return A line (segment) from p to perpendicular point on segment [s1, s2] + */ + static QgsLineString perpendicularSegment( const QgsPointV2 &p, const QgsPointV2 &s1, const QgsPointV2 &s2 ); + //! @note not available in Python bindings enum ComponentType { diff --git a/src/core/geometry/qgslinestring.h b/src/core/geometry/qgslinestring.h index 4243e455d77..4d8b0fb7d86 100644 --- a/src/core/geometry/qgslinestring.h +++ b/src/core/geometry/qgslinestring.h @@ -210,6 +210,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve void fromWkbPoints( QgsWkbTypes::Type type, const QgsConstWkbPtr &wkb ); friend class QgsPolygonV2; + friend class QgsTriangle; }; diff --git a/src/core/geometry/qgstriangle.cpp b/src/core/geometry/qgstriangle.cpp new file mode 100644 index 00000000000..bd838b747fd --- /dev/null +++ b/src/core/geometry/qgstriangle.cpp @@ -0,0 +1,529 @@ +/*************************************************************************** + qgstriangle.cpp + ------------------- + begin : January 2017 + copyright : (C) 2017 by Loïc Bartoletti + email : lbartoletti at tuxfamily dot org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgstriangle.h" +#include "qgsgeometryutils.h" +#include "qgslinestring.h" +#include "qgswkbptr.h" + +#include + +QgsTriangle::QgsTriangle() + : QgsPolygonV2() +{ + mWkbType = QgsWkbTypes::Triangle; +} + +QgsTriangle::QgsTriangle( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3 ) +{ + mWkbType = QgsWkbTypes::Triangle; + + if ( !validateGeom( p1, p2, p3 ) ) + { + return; + } + QgsLineString *ext = new QgsLineString(); + ext->setPoints( QgsPointSequence() << p1 << p2 << p3 << p1 ); + setExteriorRing( ext ); + +} + +QgsTriangle::QgsTriangle( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3 ) + : QgsPolygonV2() +{ + mWkbType = QgsWkbTypes::Triangle; + QgsPointV2 pt1( p1 ); + QgsPointV2 pt2( p2 ); + QgsPointV2 pt3( p3 ); + + if ( !validateGeom( pt1, pt2, pt3 ) ) + { + return; + } + QgsLineString *ext = new QgsLineString(); + ext->setPoints( QgsPointSequence() << pt1 << pt2 << pt3 << pt1 ); + setExteriorRing( ext ); +} + +QgsTriangle::QgsTriangle( const QPointF p1, const QPointF p2, const QPointF p3 ) + : QgsPolygonV2() +{ + mWkbType = QgsWkbTypes::Triangle; + QgsPointV2 pt1( p1 ); + QgsPointV2 pt2( p2 ); + QgsPointV2 pt3( p3 ); + + if ( !validateGeom( pt1, pt2, pt3 ) ) + { + return; + } + QgsLineString *ext = new QgsLineString(); + ext->setPoints( QgsPointSequence() << pt1 << pt2 << pt3 << pt1 ); + setExteriorRing( ext ); +} + +void QgsTriangle::clear() +{ + QgsCurvePolygon::clear(); + mWkbType = QgsWkbTypes::Triangle; +} + +QgsTriangle *QgsTriangle::clone() const +{ + return new QgsTriangle( *this ); +} + +bool QgsTriangle::fromWkb( QgsConstWkbPtr &wkbPtr ) +{ + clear(); + if ( !wkbPtr ) + { + return false; + } + + QgsWkbTypes::Type type = wkbPtr.readHeader(); + if ( QgsWkbTypes::flatType( type ) != QgsWkbTypes::Triangle ) + { + return false; + } + mWkbType = type; + + QgsWkbTypes::Type ringType; + switch ( mWkbType ) + { + case QgsWkbTypes::TriangleZ: + ringType = QgsWkbTypes::LineStringZ; + break; + case QgsWkbTypes::TriangleM: + ringType = QgsWkbTypes::LineStringM; + break; + case QgsWkbTypes::TriangleZM: + ringType = QgsWkbTypes::LineStringZM; + break; + default: + ringType = QgsWkbTypes::LineString; + break; + } + + int nRings; + wkbPtr >> nRings; + if ( nRings > 1 ) + { + return false; + } + + QgsLineString *line = new QgsLineString(); + line->fromWkbPoints( ringType, wkbPtr ); + if ( !mExteriorRing ) + { + mExteriorRing = line; + } + + return true; +} + +bool QgsTriangle::fromWkt( const QString &wkt ) +{ + + clear(); + + QPair parts = QgsGeometryUtils::wktReadBlock( wkt ); + + if ( QgsWkbTypes::geometryType( parts.first ) != QgsWkbTypes::PolygonGeometry ) + return false; + + mWkbType = parts.first; + + QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? "Z" : QString(), isMeasure() ? "M" : QString() ); + + Q_FOREACH ( const QString &childWkt, QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType ) ) + { + QPair childParts = QgsGeometryUtils::wktReadBlock( childWkt ); + + QgsWkbTypes::Type flatCurveType = QgsWkbTypes::flatType( childParts.first ); + if ( flatCurveType == QgsWkbTypes::LineString ) + mInteriorRings.append( new QgsLineString() ); + else + { + clear(); + return false; + } + if ( !mInteriorRings.back()->fromWkt( childWkt ) ) + { + clear(); + return false; + } + } + + if ( mInteriorRings.isEmpty() ) + { + clear(); + return false; + } + mExteriorRing = mInteriorRings.at( 0 ); + mInteriorRings.removeFirst(); + + //scan through rings and check if dimensionality of rings is different to CurvePolygon. + //if so, update the type dimensionality of the CurvePolygon to match + bool hasZ = false; + bool hasM = false; + if ( mExteriorRing ) + { + hasZ = hasZ || mExteriorRing->is3D(); + hasM = hasM || mExteriorRing->isMeasure(); + } + if ( hasZ ) + addZValue( 0 ); + if ( hasM ) + addMValue( 0 ); + + return true; +} + +QgsPolygonV2 *QgsTriangle::surfaceToPolygon() const +{ + return toPolygon(); +} + +QgsAbstractGeometry *QgsTriangle::toCurveType() const +{ + std::unique_ptr curvePolygon( new QgsCurvePolygon() ); + curvePolygon->setExteriorRing( mExteriorRing->clone() ); + + return curvePolygon.release(); +} + +void QgsTriangle::addInteriorRing( QgsCurve *ring ) +{ + Q_UNUSED( ring ); + return; +} + +bool QgsTriangle::deleteVertex( QgsVertexId position ) +{ + Q_UNUSED( position ); + return false; +} + +bool QgsTriangle::insertVertex( QgsVertexId position, const QgsPointV2 &vertex ) +{ + Q_UNUSED( position ); + Q_UNUSED( vertex ); + return false; +} +#include +bool QgsTriangle::moveVertex( QgsVertexId vId, const QgsPointV2 &newPos ) +{ + if ( !mExteriorRing || vId.part != 0 || vId.ring != 0 || vId.vertex < 0 || vId.vertex > 4 ) + { + return false; + } + + if ( vId.vertex == 4 ) + { + vId.vertex = 0; + } + + QgsPointV2 p1( vId.vertex == 0 ? newPos : vertexAt( 0 ) ); + QgsPointV2 p2( vId.vertex == 1 ? newPos : vertexAt( 1 ) ); + QgsPointV2 p3( vId.vertex == 2 ? newPos : vertexAt( 2 ) ); + + if ( !validateGeom( p1, p2, p3 ) ) + { + return false; + } + + QgsCurve *ring = mExteriorRing; + int n = ring->numPoints(); + bool success = ring->moveVertex( vId, newPos ); + if ( success ) + { + // If first or last vertex is moved, also move the last/first vertex + if ( vId.vertex == 0 ) + ring->moveVertex( QgsVertexId( vId.part, vId.ring, n - 1 ), newPos ); + clearCache(); + } + return success; +} + +void QgsTriangle::setExteriorRing( QgsCurve *ring ) +{ + if ( !ring ) + { + return; + } + + if ( ring->hasCurvedSegments() ) + { + //need to segmentize ring as polygon does not support curves + QgsCurve *line = ring->segmentize(); + delete ring; + ring = line; + } + + if ( ( ring->numPoints() > 4 ) || ( ring->numPoints() < 3 ) ) + { + return; + } + else if ( ring->numPoints() == 4 ) + { + if ( !ring->isClosed() ) + { + return; + } + } + else if ( ring->numPoints() == 3 ) + { + if ( ring->isClosed() ) + { + return; + } + QgsLineString *lineString = dynamic_cast< QgsLineString *>( ring ); + if ( lineString && !lineString->isClosed() ) + { + lineString->close(); + } + ring = lineString; + } + + if ( !validateGeom( ring->vertexAt( QgsVertexId( 0, 0, 0 ) ), ring->vertexAt( QgsVertexId( 0, 0, 1 ) ), ring->vertexAt( QgsVertexId( 0, 0, 2 ) ) ) ) + { + return; + } + + delete mExteriorRing; + + mExteriorRing = ring; + + //set proper wkb type + setZMTypeFromSubGeometry( ring, QgsWkbTypes::Triangle ); + + clearCache(); +} + +QgsAbstractGeometry *QgsTriangle::boundary() const +{ + if ( !mExteriorRing ) + return nullptr; + + return mExteriorRing->clone(); +} + +QgsPointV2 QgsTriangle::vertexAt( int atVertex ) const +{ + QgsVertexId id( 0, 0, atVertex ); + return mExteriorRing->vertexAt( id ); +} + +QVector QgsTriangle::lengths() const +{ + QVector lengths; + lengths.append( vertexAt( 0 ).distance( vertexAt( 1 ) ) ); + lengths.append( vertexAt( 1 ).distance( vertexAt( 2 ) ) ); + lengths.append( vertexAt( 2 ).distance( vertexAt( 0 ) ) ); + + return lengths; +} + +QVector QgsTriangle::angles() const +{ + QVector angles; + double ax, ay, bx, by, cx, cy; + + ax = vertexAt( 0 ).x(); + ay = vertexAt( 0 ).y(); + bx = vertexAt( 1 ).x(); + by = vertexAt( 1 ).y(); + cx = vertexAt( 2 ).x(); + cy = vertexAt( 2 ).y(); + + double a1 = fmod( QgsGeometryUtils::angleBetweenThreePoints( cx, cy, ax, ay, bx, by ), M_PI ); + double a2 = fmod( QgsGeometryUtils::angleBetweenThreePoints( ax, ay, bx, by, cx, cy ), M_PI ); + double a3 = fmod( QgsGeometryUtils::angleBetweenThreePoints( bx, by, cx, cy, ax, ay ), M_PI ); + + angles.append( ( a1 > M_PI / 2 ? a1 - M_PI / 2 : a1 ) ); + angles.append( ( a2 > M_PI / 2 ? a2 - M_PI / 2 : a2 ) ); + angles.append( ( a3 > M_PI / 2 ? a3 - M_PI / 2 : a3 ) ); + + return angles; +} + +bool QgsTriangle::isIsocele( double lengthTolerance ) const +{ + QVector sides = lengths(); + bool ab_bc = qgsDoubleNear( sides.at( 0 ), sides.at( 1 ), lengthTolerance ); + bool bc_ca = qgsDoubleNear( sides.at( 1 ), sides.at( 2 ), lengthTolerance ); + bool ca_ab = qgsDoubleNear( sides.at( 2 ), sides.at( 0 ), lengthTolerance ); + + return ( ab_bc || bc_ca || ca_ab ); +} + +bool QgsTriangle::isEquilateral( double lengthTolerance ) const +{ + QVector sides = lengths(); + bool ab_bc = qgsDoubleNear( sides.at( 0 ), sides.at( 1 ), lengthTolerance ); + bool bc_ca = qgsDoubleNear( sides.at( 1 ), sides.at( 2 ), lengthTolerance ); + bool ca_ab = qgsDoubleNear( sides.at( 2 ), sides.at( 0 ), lengthTolerance ); + + return ( ab_bc && bc_ca && ca_ab ); +} + +bool QgsTriangle::isRight( double angleTolerance ) const +{ + QVector a = angles(); + QVector::iterator ita = a.begin(); + while ( ita != a.end() ) + { + if ( qgsDoubleNear( *ita, M_PI / 2.0, angleTolerance ) ) + return true; + ita++; + } + return false; +} + +bool QgsTriangle::isScalene( double lengthTolerance ) const +{ + return !isIsocele( lengthTolerance ); +} + +QVector QgsTriangle::altitudes() const +{ + QVector alt; + alt.append( QgsGeometryUtils::perpendicularSegment( vertexAt( 0 ), vertexAt( 2 ), vertexAt( 1 ) ) ); + alt.append( QgsGeometryUtils::perpendicularSegment( vertexAt( 1 ), vertexAt( 0 ), vertexAt( 2 ) ) ); + alt.append( QgsGeometryUtils::perpendicularSegment( vertexAt( 2 ), vertexAt( 0 ), vertexAt( 1 ) ) ); + + return alt; +} + +QVector QgsTriangle::medians() const +{ + QVector med; + + QgsLineString med1; + QgsLineString med2; + QgsLineString med3; + med1.setPoints( QgsPointSequence() << vertexAt( 0 ) << QgsGeometryUtils::midpoint( vertexAt( 1 ), vertexAt( 2 ) ) ); + med2.setPoints( QgsPointSequence() << vertexAt( 1 ) << QgsGeometryUtils::midpoint( vertexAt( 0 ), vertexAt( 2 ) ) ); + med3.setPoints( QgsPointSequence() << vertexAt( 2 ) << QgsGeometryUtils::midpoint( vertexAt( 0 ), vertexAt( 1 ) ) ); + med.append( med1 ); + med.append( med2 ); + med.append( med3 ); + + return med; +} + +QVector QgsTriangle::bisectors( double lengthTolerance ) const +{ + QVector bis; + QgsLineString bis1; + QgsLineString bis2; + QgsLineString bis3; + QgsPointV2 incenter = inscribedCenter(); + QgsPointV2 out; + + QgsGeometryUtils::segmentIntersection( vertexAt( 0 ), incenter, vertexAt( 1 ), vertexAt( 2 ), out, lengthTolerance ); + bis1.setPoints( QgsPointSequence() << vertexAt( 0 ) << out ); + + QgsGeometryUtils::segmentIntersection( vertexAt( 1 ), incenter, vertexAt( 0 ), vertexAt( 2 ), out, lengthTolerance ); + bis2.setPoints( QgsPointSequence() << vertexAt( 1 ) << out ); + + QgsGeometryUtils::segmentIntersection( vertexAt( 2 ), incenter, vertexAt( 0 ), vertexAt( 1 ), out, lengthTolerance ); + bis3.setPoints( QgsPointSequence() << vertexAt( 2 ) << out ); + + bis.append( bis1 ); + bis.append( bis2 ); + bis.append( bis3 ); + + return bis; +} + +QgsTriangle QgsTriangle::medial() const +{ + QgsPointV2 p1, p2, p3; + p1 = QgsGeometryUtils::midpoint( vertexAt( 0 ), vertexAt( 1 ) ); + p2 = QgsGeometryUtils::midpoint( vertexAt( 1 ), vertexAt( 2 ) ); + p3 = QgsGeometryUtils::midpoint( vertexAt( 2 ), vertexAt( 0 ) ); + return QgsTriangle( p1, p2, p3 ); +} + +QgsPointV2 QgsTriangle::orthocenter( double lengthTolerance ) const +{ + QVector alt = altitudes(); + QgsPointV2 ortho; + QgsGeometryUtils::segmentIntersection( alt.at( 0 ).pointN( 0 ), alt.at( 0 ).pointN( 1 ), alt.at( 1 ).pointN( 0 ), alt.at( 1 ).pointN( 1 ), ortho, lengthTolerance ); + + return ortho; +} + +QgsPointV2 QgsTriangle::circumscribedCenter() const +{ + double r, x, y; + QgsGeometryUtils::circleCenterRadius( vertexAt( 0 ), vertexAt( 1 ), vertexAt( 2 ), r, x, y ); + return QgsPointV2( x, y ); +} + +double QgsTriangle::circumscribedRadius() const +{ + double r, x, y; + QgsGeometryUtils::circleCenterRadius( vertexAt( 0 ), vertexAt( 1 ), vertexAt( 2 ), r, x, y ); + return r; +} + +/* +QgsCircle QgsTriangle::circumscribedCircle() const +{ + +}*/ + +QgsPointV2 QgsTriangle::inscribedCenter() const +{ + + QVector l = lengths(); + double x = ( l.at( 0 ) * vertexAt( 2 ).x() + + l.at( 1 ) * vertexAt( 0 ).x() + + l.at( 2 ) * vertexAt( 1 ).x() ) / perimeter(); + double y = ( l.at( 0 ) * vertexAt( 2 ).y() + + l.at( 1 ) * vertexAt( 0 ).y() + + l.at( 2 ) * vertexAt( 1 ).y() ) / perimeter(); + + return QgsPointV2( x, y ); +} + +double QgsTriangle::inscribedRadius() const +{ + return ( 2.0 * area() / perimeter() ); +} + +bool QgsTriangle::validateGeom( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3 ) +{ + if ( ( ( p1 == p2 ) || ( p1 == p3 ) || ( p2 == p3 ) ) || qgsDoubleNear( QgsGeometryUtils::leftOfLine( p1.x(), p1.y(), p2.x(), p2.y(), p3.x(), p3.y() ), 0.0 ) ) + { + return false; + } + + return true; +} + +/* +QgsCircle QgsTriangle::inscribedCircle() const +{ + +}*/ + + + diff --git a/src/core/geometry/qgstriangle.h b/src/core/geometry/qgstriangle.h new file mode 100644 index 00000000000..fffb0942ded --- /dev/null +++ b/src/core/geometry/qgstriangle.h @@ -0,0 +1,322 @@ +/*************************************************************************** + qgstriangle.h + ------------------- + begin : January 2017 + copyright : (C) 2017 by Loïc Bartoletti + email : lbartoletti at tuxfamily dot org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 QGSTRIANGLE_H +#define QGSTRIANGLE_H + +#include "qgis_core.h" +#include "qgspolygon.h" +#include "qgslinestring.h" + +/** \ingroup core + * \class QgsTriangle + * \brief Triangle geometry type. + * \note added in QGIS 3.0 + */ +class CORE_EXPORT QgsTriangle : public QgsPolygonV2 +{ + public: + QgsTriangle(); + + /** Construct a QgsTriangle from three QgsPointV2. + * An empty triangle is returned if there are identical points or if the points are collinear. + * @param p1 first point + * @param p2 second point + * @param p3 third point + */ + QgsTriangle( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3 ); + + /** Construct a QgsTriangle from three QgsPoint. + * An empty triangle is returned if there are identical points or if the points are collinear. + * @param p1 first point + * @param p2 second point + * @param p3 third point + */ + explicit QgsTriangle( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3 ); + + /** Construct a QgsTriangle from three QPointF. + * An empty triangle is returned if there are identical points or if the points are collinear. + * @param p1 first point + * @param p2 second point + * @param p3 third point + */ + explicit QgsTriangle( const QPointF p1, const QPointF p2, const QPointF p3 ); + + // inherited: bool operator==( const QgsTriangle& other ) const; + // inherited: bool operator!=( const QgsTriangle& other ) const; + + virtual QString geometryType() const override { return QStringLiteral( "Triangle" ); } + virtual QgsTriangle *clone() const override; + void clear() override; + + virtual bool fromWkb( QgsConstWkbPtr &wkbPtr ) override; + + bool fromWkt( const QString &wkt ) override; + + // inherited: QString asWkt( int precision = 17 ) const; + // inherited: QDomElement asGML2( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const; + // inherited: QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const; + // inherited: QString asJSON( int precision = 17 ) const; + + QgsPolygonV2 *surfaceToPolygon() const override; + + QgsAbstractGeometry *toCurveType() const override; + + //! Inherited method not used. You cannot add an interior ring into a triangle. + void addInteriorRing( QgsCurve *ring ) override; + + /** Inherited method not used. You cannot add an interior ring into a triangle. + * @note not available in Python bindings + */ + void setInteriorRings( const QList< QgsCurve *> &rings ) = delete; + //! Inherited method not used. You cannot delete or insert a vertex directly. Returns always false. + bool deleteVertex( QgsVertexId position ) override; + //! Inherited method not used. You cannot delete or insert a vertex directly. Returns always false. + bool insertVertex( QgsVertexId position, const QgsPointV2 &vertex ) override; + bool moveVertex( QgsVertexId vId, const QgsPointV2 &newPos ) override; + + virtual void setExteriorRing( QgsCurve *ring ) override; + + virtual QgsAbstractGeometry *boundary() const override; + + // inherited: double pointDistanceToBoundary( double x, double y ) const; + + /** + * Returns coordinates of a vertex. + * @param atVertex index of the vertex + * @return Coordinates of the vertex or QgsPointV2(0,0) on error (\a atVertex < 0 or > 3). + */ + QgsPointV2 vertexAt( int atVertex ) const; + + /** + * Returns the three lengths of the triangle. + * @return Lengths of triangle ABC where [AB] is at 0, [BC] is at 1, [CA] is at 2 + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * tri.lengths() + * # [5.0, 5.0, 7.0710678118654755] + * \endcode + */ + QVector lengths() const; + + /** + * Returns the three angles of the triangle. + * @return Angles in radians of triangle ABC where angle BAC is at 0, angle ABC is at 1, angle BCA is at 2 + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * [math.degrees(i) for i in tri.angles()] + * # [45.0, 90.0, 45.0] + * \endcode + */ + QVector angles() const; + + /** + * Is the triangle isocele (two sides with the same length)? + * @param lengthTolerance The tolerance to use + * @return True or False + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * tri.lengths() + * # [5.0, 5.0, 7.0710678118654755] + * tri.isIsocele() + * # True + * # length of [AB] == length of [BC] + * \endcode + */ + bool isIsocele( double lengthTolerance = 0.0001 ) const; + + /** + * Is the triangle equilateral (three sides with the same length)? + * @param lengthTolerance The tolerance to use + * @return True or False + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 10, 10 ), QgsPointV2( 16, 10 ), QgsPointV2( 13, 15.1962 ) ) + * tri.lengths() + * # [6.0, 6.0000412031918575, 6.0000412031918575] + * tri.isEquilateral() + * # True + * # All lengths are close to 6.0 + * \endcode + */ + bool isEquilateral( double lengthTolerance = 0.0001 ) const; + + /** + * Is the triangle right-angled? + * @param angleTolerance The tolerance to use + * @return True or False + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * [math.degrees(i) for i in tri.angles()] + * # [45.0, 90.0, 45.0] + * tri.isRight() + * # True + * # angle of ABC == 90 + * \endcode + */ + bool isRight( double angleTolerance = 0.0001 ) const; + + /** + * Is the triangle scalene (all sides have differen lengths)? + * @param lengthTolerance The tolerance to use + * @return True or False + * @return True or False + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 7.2825, 4.2368 ), QgsPointV2( 13.0058, 3.3218 ), QgsPointV2( 9.2145, 6.5242 ) ) + * tri.lengths() + * # [5.795980321740233, 4.962793714229921, 2.994131386562721] + * tri.isScalene() + * # True + * # All lengths are different + * \endcode + */ + bool isScalene( double lengthTolerance = 0.0001 ) const; + + /** + * An altitude is a segment (defined by a QgsLineString) from a vertex to the opposite side (or, if necessary, to the extension of the opposite side). + * @return Three altitudes from this triangle + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * [alt.asWkt() for alt in tri.altitudes()] + * # ['LineString (0 0, 0 5)', 'LineString (0 5, 2.5 2.5)', 'LineString (5 5, 0 5)'] + * \endcode + */ + QVector altitudes( ) const; + + /** + * A median is a segment (defined by a QgsLineString) from a vertex to the midpoint of the opposite side. + * @return Three medians from this triangle + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * [med.asWkt() for med in tri.medians()] + * # ['LineString (0 0, 2.5 5)', 'LineString (0 5, 2.5 2.5)', 'LineString (5 5, 0 2.5)'] + * \endcode + */ + QVector medians( ) const; + + /** + * The segment (defined by a QgsLineString) returned bisect the angle of a vertex to the opposite side. + * @param lengthTolerance The tolerance to use + * @return Three angle bisector from this triangle + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * [bis.asWkt() for bis in tri.bisectors()] + * # ['LineString (0 0, 2.07106781186547462 5)', 'LineString (0 5, 2.5 2.5)', 'LineString (5 5, 0 2.92893218813452538)'] + * \endcode + */ + QVector bisectors( double lengthTolerance = 0.0001 ) const; + + /** + * Medial (or midpoint) triangle of a triangle ABC is the triangle with vertices at the midpoints of the triangle's sides. + * @return The medial from this triangle + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * tri.medial().asWkt() + * # 'Triangle ((0 2.5, 2.5 5, 2.5 2.5, 0 2.5))' + * \endcode + */ + QgsTriangle medial( ) const; + + /** + * An orthocenter is the point of intersection of the altitudes of a triangle. + * @param lengthTolerance The tolerance to use + * @return The orthocenter of the triangle. + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * tri.orthocenter().asWkt() + * # 'Point (0 5)' + * \endcode + */ + QgsPointV2 orthocenter( double lengthTolerance = 0.0001 ) const; + + /** + * Center of the circumscribed circle of the triangle. + * @return The center of the circumscribed circle of the triangle + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * tri.circumscribedCenter().asWkt() + * # 'Point (2.5 2.5)' + * \endcode + */ + QgsPointV2 circumscribedCenter( ) const; + + /** + * Radius of the circumscribed circle of the triangle. + * @return The radius of the circumscribed circle of the triangle + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * tri.circumscribedRadius() + * # 3.5355339059327378 + * \endcode + */ + double circumscribedRadius( ) const; + + // TODO: + // QgsCircle circumscribedCircle ( ) const; // need QgsCircle (from CADDigitize.CADCircle) + + /** + * Center of the inscribed circle of the triangle. + * @return The center of the inscribed circle of the triangle + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * tri.inscribedCenter().asWkt() + * # 'Point (1.46446609406726225 3.53553390593273775)' + * \endcode + */ + QgsPointV2 inscribedCenter( ) const; + + /** + * Radius of the inscribed circle of the triangle. + * @return The radius of the inscribed circle of the triangle + * * Example: + * \code{.py} + * tri = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ) + * tri.inscribedRadius() + * # 1.4644660940672622 + * \endcode + */ + double inscribedRadius( ) const; + + // TODO: + // QgsCircle inscribedCircle ( ) const; // need QgsCircle (from CADDigitize.CADCircle) + private: + + /** + * @brief Convenient method checking the validity of geometry (no duplicate point(s), no colinearity). + * @param p1 first point + * @param p2 second point + * @param p3 third point + * @return True if the points can create a triangle, otherwise false. + * @note not available in Python bindings + */ + bool validateGeom( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3 ); + +}; +#endif // QGSTRIANGLE_H diff --git a/src/core/geometry/qgswkbtypes.cpp b/src/core/geometry/qgswkbtypes.cpp index d499611fb5c..76324dc2495 100644 --- a/src/core/geometry/qgswkbtypes.cpp +++ b/src/core/geometry/qgswkbtypes.cpp @@ -56,6 +56,11 @@ const QMap QgsWkbTypes::ENTRIES { PolygonM, wkbEntry( QStringLiteral( "PolygonM" ), false, MultiPolygonM, PolygonM, Polygon, PolygonGeometry, false, true ) }, { PolygonZM, wkbEntry( QStringLiteral( "PolygonZM" ), false, MultiPolygonZM, PolygonZM, Polygon, PolygonGeometry, true, true ) }, { Polygon25D, wkbEntry( QStringLiteral( "Polygon25D" ), false, MultiPolygon25D, Polygon25D, Polygon, PolygonGeometry, true, false ) }, + //triangle + { Triangle, wkbEntry( QStringLiteral( "Triangle" ), false, Unknown, Triangle, Triangle, PolygonGeometry, false, false ) }, + { TriangleZ, wkbEntry( QStringLiteral( "TriangleZ" ), false, Unknown, TriangleZ, Triangle, PolygonGeometry, true, false ) }, + { TriangleM, wkbEntry( QStringLiteral( "TriangleM" ), false, Unknown, TriangleM, Triangle, PolygonGeometry, false, true ) }, + { TriangleZM, wkbEntry( QStringLiteral( "TriangleZM" ), false, Unknown, TriangleZM, Triangle, PolygonGeometry, true, true ) }, //curvepolygon { CurvePolygon, wkbEntry( QStringLiteral( "CurvePolygon" ), false, MultiSurface, CurvePolygon, CurvePolygon, PolygonGeometry, false, false ) }, { CurvePolygonZ, wkbEntry( QStringLiteral( "CurvePolygonZ" ), false, MultiSurfaceZ, CurvePolygonZ, CurvePolygon, PolygonGeometry, true, false ) }, diff --git a/src/core/geometry/qgswkbtypes.h b/src/core/geometry/qgswkbtypes.h index 0b155756f45..ee2ddbdc59d 100644 --- a/src/core/geometry/qgswkbtypes.h +++ b/src/core/geometry/qgswkbtypes.h @@ -68,6 +68,7 @@ class CORE_EXPORT QgsWkbTypes Point = 1, LineString = 2, Polygon = 3, + Triangle = 17, MultiPoint = 4, MultiLineString = 5, MultiPolygon = 6, @@ -81,6 +82,7 @@ class CORE_EXPORT QgsWkbTypes PointZ = 1001, LineStringZ = 1002, PolygonZ = 1003, + TriangleZ = 1017, MultiPointZ = 1004, MultiLineStringZ = 1005, MultiPolygonZ = 1006, @@ -93,6 +95,7 @@ class CORE_EXPORT QgsWkbTypes PointM = 2001, LineStringM = 2002, PolygonM = 2003, + TriangleM = 2017, MultiPointM = 2004, MultiLineStringM = 2005, MultiPolygonM = 2006, @@ -114,6 +117,7 @@ class CORE_EXPORT QgsWkbTypes CurvePolygonZM = 3010, MultiCurveZM = 3011, MultiSurfaceZM = 3012, + TriangleZM = 3017, Point25D = 0x80000001, LineString25D, Polygon25D, @@ -201,6 +205,22 @@ class CORE_EXPORT QgsWkbTypes case MultiPolygonZM: return PolygonZM; + case Triangle: + // case MultiTriangle: + return Triangle; + + case TriangleZ: + // case MultiTriangleZ: + return TriangleZ; + + case TriangleM: + // case MultiTriangleM: + return TriangleM; + + case TriangleZM: + // case MultiTriangleZM: + return TriangleZM; + case CircularString: return CircularString; @@ -259,6 +279,7 @@ class CORE_EXPORT QgsWkbTypes case Polygon25D: case MultiPolygon25D: return Polygon25D; + } return Unknown; } @@ -273,6 +294,10 @@ class CORE_EXPORT QgsWkbTypes switch ( type ) { case Unknown: + case Triangle: + case TriangleZ: + case TriangleM: + case TriangleZM: return Unknown; case GeometryCollection: @@ -422,6 +447,12 @@ class CORE_EXPORT QgsWkbTypes case Polygon25D: return Polygon; + case Triangle: + case TriangleZ: + case TriangleM: + case TriangleZM: + return Triangle; + case MultiPoint: case MultiPointZ: case MultiPointM: @@ -523,6 +554,7 @@ class CORE_EXPORT QgsWkbTypes case Point: case LineString: case Polygon: + case Triangle: case CircularString: case CompoundCurve: case CurvePolygon: @@ -530,18 +562,21 @@ class CORE_EXPORT QgsWkbTypes case PointZ: case LineStringZ: case PolygonZ: + case TriangleZ: case CircularStringZ: case CompoundCurveZ: case CurvePolygonZ: case PointM: case LineStringM: case PolygonM: + case TriangleM: case CircularStringM: case CompoundCurveM: case CurvePolygonM: case PointZM: case LineStringZM: case PolygonZM: + case TriangleZM: case CircularStringZM: case CompoundCurveZM: case CurvePolygonZM: @@ -662,12 +697,16 @@ class CORE_EXPORT QgsWkbTypes case Polygon: case MultiPolygon: + case Triangle: case PolygonZ: + case TriangleZ: case MultiPolygonZ: case PolygonM: + case TriangleM: case MultiPolygonM: case PolygonZM: case MultiPolygonZM: + case TriangleZM: case Polygon25D: case MultiPolygon25D: case CurvePolygon: @@ -719,6 +758,7 @@ class CORE_EXPORT QgsWkbTypes case PointZ: case LineStringZ: case PolygonZ: + case TriangleZ: case MultiPointZ: case MultiLineStringZ: case MultiPolygonZ: @@ -731,6 +771,7 @@ class CORE_EXPORT QgsWkbTypes case PointZM: case LineStringZM: case PolygonZM: + case TriangleZM: case MultiPointZM: case MultiLineStringZM: case MultiPolygonZM: @@ -766,6 +807,7 @@ class CORE_EXPORT QgsWkbTypes case PointM: case LineStringM: case PolygonM: + case TriangleM: case MultiPointM: case MultiLineStringM: case MultiPolygonM: @@ -778,6 +820,7 @@ class CORE_EXPORT QgsWkbTypes case PointZM: case LineStringZM: case PolygonZM: + case TriangleZM: case MultiPointZM: case MultiLineStringZM: case MultiPolygonZM: diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index d0676ee9642..102be0d8286 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -45,6 +45,7 @@ #include "qgsgeometrycollection.h" #include "qgspointv2.h" #include "qgspolygon.h" +#include "qgstriangle.h" #include "qgsmultipoint.h" #include "qgsmultilinestring.h" #include "qgscurvepolygon.h" @@ -2190,6 +2191,33 @@ static QVariant fcnMakePolygon( const QVariantList &values, const QgsExpressionC return QVariant::fromValue( QgsGeometry( polygon ) ); } +static QVariant fcnMakeTriangle( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent ) +{ + std::unique_ptr tr( new QgsTriangle() ); + std::unique_ptr lineString( new QgsLineString() ); + lineString->clear(); + + Q_FOREACH ( const QVariant &value, values ) + { + QgsGeometry geom = getGeometry( value, parent ); + if ( geom.isNull() ) + return QVariant(); + + if ( geom.type() != QgsWkbTypes::PointGeometry || geom.isMultipart() ) + return QVariant(); + + QgsPointV2 *point = dynamic_cast< QgsPointV2 * >( geom.geometry() ); + if ( !point ) + return QVariant(); + + lineString->addVertex( *point ); + } + + tr->setExteriorRing( lineString.release() ); + + return QVariant::fromValue( QgsGeometry( tr.release() ) ); +} + static QVariant pointAt( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent ) // helper function { FEAT_FROM_CONTEXT( context, f ); @@ -3999,6 +4027,10 @@ const QList &QgsExpression::Functions() << new StaticFunction( QStringLiteral( "make_point_m" ), 3, fcnMakePointM, QStringLiteral( "GeometryGroup" ) ) << new StaticFunction( QStringLiteral( "make_line" ), -1, fcnMakeLine, QStringLiteral( "GeometryGroup" ) ) << new StaticFunction( QStringLiteral( "make_polygon" ), -1, fcnMakePolygon, QStringLiteral( "GeometryGroup" ) ) + << new StaticFunction( QStringLiteral( "make_triangle" ), ParameterList() << Parameter( QStringLiteral( "geometry" ) ) + << Parameter( QStringLiteral( "geometry" ) ) + << Parameter( QStringLiteral( "geometry" ) ), + fcnMakeTriangle, QStringLiteral( "GeometryGroup" ) ) << new StaticFunction( QStringLiteral( "$x_at" ), 1, fcnXat, QStringLiteral( "GeometryGroup" ), QString(), true, QSet(), false, QStringList() << QStringLiteral( "xat" ) << QStringLiteral( "x_at" ) ) << new StaticFunction( QStringLiteral( "$y_at" ), 1, fcnYat, QStringLiteral( "GeometryGroup" ), QString(), true, QSet(), false, QStringList() << QStringLiteral( "yat" ) << QStringLiteral( "y_at" ) ) << new StaticFunction( QStringLiteral( "x_min" ), 1, fcnXMin, QStringLiteral( "GeometryGroup" ), QString(), false, QSet(), false, QStringList() << QStringLiteral( "xmin" ) ) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 74590e5af36..aaa6fa6de8f 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -771,6 +771,11 @@ class TestQgsExpression: public QObject QTest::newRow( "make_polygon" ) << "geom_to_wkt(make_polygon(geom_from_wkt('LINESTRING( 0 0, 0 1, 1 1, 1 0, 0 0 )')))" << false << QVariant( "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))" ); QTest::newRow( "make_polygon rings" ) << "geom_to_wkt(make_polygon(geom_from_wkt('LINESTRING( 0 0, 0 1, 1 1, 1 0, 0 0 )'),geom_from_wkt('LINESTRING( 0.1 0.1, 0.1 0.2, 0.2 0.2, 0.2 0.1, 0.1 0.1 )'),geom_from_wkt('LINESTRING( 0.8 0.8, 0.8 0.9, 0.9 0.9, 0.9 0.8, 0.8 0.8 )')))" << false << QVariant( "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0),(0.1 0.1, 0.1 0.2, 0.2 0.2, 0.2 0.1, 0.1 0.1),(0.8 0.8, 0.8 0.9, 0.9 0.9, 0.9 0.8, 0.8 0.8))" ); + QTest::newRow( "make_triangle not geom" ) << "geom_to_wkt(make_triangle(make_point(2,4), 'g', make_point(3,5)))" << true << QVariant(); + QTest::newRow( "make_triangle null" ) << "geom_to_wkt(make_triangle(make_point(2,4), NULL, make_point(3,5)))" << false << QVariant(); + QTest::newRow( "make_triangle duplicated point" ) << "geom_to_wkt(make_triangle(make_point(2,4), make_point(2,4), make_point(3,5)))" << false << QVariant( "Triangle ()" ); + QTest::newRow( "make_triangle colinear points" ) << "geom_to_wkt(make_triangle(make_point(0,1), make_point(0,2), make_point(0,3)))" << false << QVariant( "Triangle ()" ); + QTest::newRow( "make_triangle" ) << "geom_to_wkt(make_triangle(make_point(0,0), make_point(5,5), make_point(0,10)))" << false << QVariant( "Triangle ((0 0, 5 5, 0 10, 0 0))" ); QTest::newRow( "x point" ) << "x(make_point(2.2,4.4))" << false << QVariant( 2.2 ); QTest::newRow( "y point" ) << "y(make_point(2.2,4.4))" << false << QVariant( 4.4 ); QTest::newRow( "z point" ) << "z(make_point(2.2,4.4,6.6))" << false << QVariant( 6.6 ); diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index f050736abfb..1789543163e 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -34,6 +34,7 @@ #include "qgspointv2.h" #include "qgslinestring.h" #include "qgspolygon.h" +#include "qgstriangle.h" #include "qgsmultipoint.h" #include "qgsmultilinestring.h" #include "qgsmultipolygon.h" @@ -70,6 +71,7 @@ class TestQgsGeometry : public QObject void point(); //test QgsPointV2 void lineString(); //test QgsLineString void polygon(); //test QgsPolygonV2 + void triangle(); void compoundCurve(); //test QgsCompoundCurve void multiPoint(); void multiLineString(); @@ -3181,6 +3183,434 @@ void TestQgsGeometry::polygon() } +void TestQgsGeometry::triangle() +{ + //test constructor + QgsTriangle t1; + QVERIFY( t1.isEmpty() ); + QCOMPARE( t1.numInteriorRings(), 0 ); + QCOMPARE( t1.nCoordinates(), 0 ); + QCOMPARE( t1.ringCount(), 0 ); + QCOMPARE( t1.partCount(), 0 ); + QVERIFY( !t1.is3D() ); + QVERIFY( !t1.isMeasure() ); + QCOMPARE( t1.wkbType(), QgsWkbTypes::Triangle ); + QCOMPARE( t1.wktTypeStr(), QString( "Triangle" ) ); + QCOMPARE( t1.geometryType(), QString( "Triangle" ) ); + QCOMPARE( t1.dimension(), 2 ); + QVERIFY( !t1.hasCurvedSegments() ); + QCOMPARE( t1.area(), 0.0 ); + QCOMPARE( t1.perimeter(), 0.0 ); + QVERIFY( !t1.exteriorRing() ); + QVERIFY( !t1.interiorRing( 0 ) ); + + //set exterior ring + + //try with no ring + QgsLineString *ext = 0; + t1.setExteriorRing( ext ); + QVERIFY( t1.isEmpty() ); + QCOMPARE( t1.numInteriorRings(), 0 ); + QCOMPARE( t1.nCoordinates(), 0 ); + QCOMPARE( t1.ringCount(), 0 ); + QCOMPARE( t1.partCount(), 0 ); + QVERIFY( !t1.exteriorRing() ); + QVERIFY( !t1.interiorRing( 0 ) ); + QCOMPARE( t1.wkbType(), QgsWkbTypes::Triangle ); + + //valid exterior ring + ext = new QgsLineString(); + ext->setPoints( QgsPointSequence() << QgsPointV2( 0, 0 ) << QgsPointV2( 0, 10 ) << QgsPointV2( 10, 10 ) + << QgsPointV2( 0, 0 ) ); + QVERIFY( ext->isClosed() ); + t1.setExteriorRing( ext ); + QVERIFY( !t1.isEmpty() ); + QCOMPARE( t1.numInteriorRings(), 0 ); + QCOMPARE( t1.nCoordinates(), 4 ); + QCOMPARE( t1.ringCount(), 1 ); + QCOMPARE( t1.partCount(), 1 ); + QVERIFY( !t1.is3D() ); + QVERIFY( !t1.isMeasure() ); + QCOMPARE( t1.wkbType(), QgsWkbTypes::Triangle ); + QCOMPARE( t1.wktTypeStr(), QString( "Triangle" ) ); + QCOMPARE( t1.geometryType(), QString( "Triangle" ) ); + QCOMPARE( t1.dimension(), 2 ); + QVERIFY( !t1.hasCurvedSegments() ); + QCOMPARE( t1.area(), 50.0 ); + QGSCOMPARENEAR( t1.perimeter(), 34.1421, 0.001 ); + QVERIFY( t1.exteriorRing() ); + QVERIFY( !t1.interiorRing( 0 ) ); + + //retrieve exterior ring and check + QCOMPARE( *( static_cast< const QgsLineString * >( t1.exteriorRing() ) ), *ext ); + + //set new ExteriorRing + ext = new QgsLineString(); + ext->setPoints( QgsPointSequence() << QgsPointV2( 0, 10 ) << QgsPointV2( 5, 5 ) << QgsPointV2( 10, 10 ) + << QgsPointV2( 0, 10 ) ); + QVERIFY( ext->isClosed() ); + t1.setExteriorRing( ext ); + QVERIFY( !t1.isEmpty() ); + QCOMPARE( t1.numInteriorRings(), 0 ); + QCOMPARE( t1.nCoordinates(), 4 ); + QCOMPARE( t1.ringCount(), 1 ); + QCOMPARE( t1.partCount(), 1 ); + QVERIFY( !t1.is3D() ); + QVERIFY( !t1.isMeasure() ); + QCOMPARE( t1.wkbType(), QgsWkbTypes::Triangle ); + QCOMPARE( t1.wktTypeStr(), QString( "Triangle" ) ); + QCOMPARE( t1.geometryType(), QString( "Triangle" ) ); + QCOMPARE( t1.dimension(), 2 ); + QVERIFY( !t1.hasCurvedSegments() ); + QCOMPARE( t1.area(), 25.0 ); + QGSCOMPARENEAR( t1.perimeter(), 24.1421, 0.001 ); + QVERIFY( t1.exteriorRing() ); + QVERIFY( !t1.interiorRing( 0 ) ); + QCOMPARE( *( static_cast< const QgsLineString * >( t1.exteriorRing() ) ), *ext ); + + //test that a non closed exterior ring will be automatically closed + QgsTriangle t2; + ext = new QgsLineString(); + ext->setPoints( QgsPointSequence() << QgsPointV2( 0, 0 ) << QgsPointV2( 0, 10 ) << QgsPointV2( 10, 10 ) ); + QVERIFY( !ext->isClosed() ); + t2.setExteriorRing( ext ); + QVERIFY( !t2.isEmpty() ); + QVERIFY( t2.exteriorRing()->isClosed() ); + QCOMPARE( t2.nCoordinates(), 4 ); + + // invalid number of points + ext = new QgsLineString(); + t2.clear(); + ext->setPoints( QgsPointSequence() << QgsPointV2( 0, 0 ) << QgsPointV2( 0, 10 ) ); + t2.setExteriorRing( ext ); + QVERIFY( t2.isEmpty() ); + + ext = new QgsLineString(); + t2.clear(); + ext->setPoints( QgsPointSequence() << QgsPointV2( 0, 0 ) << QgsPointV2( 0, 10 ) << QgsPointV2( 10, 10 ) << QgsPointV2( 5, 10 ) << QgsPointV2( 8, 10 ) ); + t2.setExteriorRing( ext ); + QVERIFY( t2.isEmpty() ); + + // invalid exterior ring + ext = new QgsLineString(); + t2.clear(); + ext->setPoints( QgsPointSequence() << QgsPointV2( 0, 0 ) << QgsPointV2( 0, 10 ) << QgsPointV2( 10, 10 ) << QgsPointV2( 5, 10 ) ); + t2.setExteriorRing( ext ); + QVERIFY( t2.isEmpty() ); + + // circular ring + QgsCircularString *circularRing = new QgsCircularString(); + t2.clear(); + circularRing->setPoints( QgsPointSequence() << QgsPointV2( 0, 0 ) << QgsPointV2( 0, 10 ) << QgsPointV2( 10, 10 ) ); + QVERIFY( circularRing->hasCurvedSegments() ); + t2.setExteriorRing( circularRing ); + QVERIFY( t2.isEmpty() ); + + //constructor with 3 points + // double points + QgsTriangle t3( QgsPointV2( 0, 0 ), QgsPointV2( 0, 0 ), QgsPointV2( 10, 10 ) ); + QVERIFY( t3.isEmpty() ); + QCOMPARE( t3.numInteriorRings(), 0 ); + QCOMPARE( t3.nCoordinates(), 0 ); + QCOMPARE( t3.ringCount(), 0 ); + QCOMPARE( t3.partCount(), 0 ); + QVERIFY( !t3.is3D() ); + QVERIFY( !t3.isMeasure() ); + QCOMPARE( t3.wkbType(), QgsWkbTypes::Triangle ); + QCOMPARE( t3.wktTypeStr(), QString( "Triangle" ) ); + QCOMPARE( t3.geometryType(), QString( "Triangle" ) ); + QCOMPARE( t3.dimension(), 2 ); + QVERIFY( !t3.hasCurvedSegments() ); + QCOMPARE( t3.area(), 0.0 ); + QCOMPARE( t3.perimeter(), 0.0 ); + QVERIFY( !t3.exteriorRing() ); + QVERIFY( !t3.interiorRing( 0 ) ); + + // colinear + t3 = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 0, 10 ) ); + QVERIFY( t3.isEmpty() ); + QCOMPARE( t3.numInteriorRings(), 0 ); + QCOMPARE( t3.nCoordinates(), 0 ); + QCOMPARE( t3.ringCount(), 0 ); + QCOMPARE( t3.partCount(), 0 ); + QVERIFY( !t3.is3D() ); + QVERIFY( !t3.isMeasure() ); + QCOMPARE( t3.wkbType(), QgsWkbTypes::Triangle ); + QCOMPARE( t3.wktTypeStr(), QString( "Triangle" ) ); + QCOMPARE( t3.geometryType(), QString( "Triangle" ) ); + QCOMPARE( t3.dimension(), 2 ); + QVERIFY( !t3.hasCurvedSegments() ); + QCOMPARE( t3.area(), 0.0 ); + QCOMPARE( t3.perimeter(), 0.0 ); + QVERIFY( !t3.exteriorRing() ); + QVERIFY( !t3.interiorRing( 0 ) ); + + t3 = QgsTriangle( QgsPointV2( 0, 0 ), QgsPointV2( 0, 10 ), QgsPointV2( 10, 10 ) ); + QVERIFY( !t3.isEmpty() ); + QCOMPARE( t3.numInteriorRings(), 0 ); + QCOMPARE( t3.nCoordinates(), 4 ); + QCOMPARE( t3.ringCount(), 1 ); + QCOMPARE( t3.partCount(), 1 ); + QVERIFY( !t3.is3D() ); + QVERIFY( !t3.isMeasure() ); + QCOMPARE( t3.wkbType(), QgsWkbTypes::Triangle ); + QCOMPARE( t3.wktTypeStr(), QString( "Triangle" ) ); + QCOMPARE( t3.geometryType(), QString( "Triangle" ) ); + QCOMPARE( t3.dimension(), 2 ); + QVERIFY( !t3.hasCurvedSegments() ); + QCOMPARE( t3.area(), 50.0 ); + QGSCOMPARENEAR( t3.perimeter(), 34.1421, 0.001 ); + QVERIFY( t3.exteriorRing() ); + QVERIFY( !t3.interiorRing( 0 ) ); + + // clone + QgsTriangle *t4 = t3.clone(); + QCOMPARE( t3, *t4 ); + delete t4; + + // constructor from QgsPoint and QPointF + QgsTriangle t_qgspoint = QgsTriangle( QgsPoint( 0, 0 ), QgsPoint( 0, 10 ), QgsPoint( 10, 10 ) ); + QVERIFY( t3 == t_qgspoint ); + QgsTriangle t_pointf = QgsTriangle( QPointF( 0, 0 ), QPointF( 0, 10 ), QPointF( 10, 10 ) ); + QVERIFY( t3 == t_pointf ); + + // fromWkt + QgsTriangle t5; + ext = new QgsLineString(); + ext->setPoints( QgsPointSequence() << QgsPointV2( QgsWkbTypes::PointZM, 0, 0, 1, 5 ) + << QgsPointV2( QgsWkbTypes::PointZM, 0, 10, 2, 6 ) << QgsPointV2( QgsWkbTypes::PointZM, 10, 10, 3, 7 ) ); + t5.setExteriorRing( ext ); + QString wkt = t5.asWkt(); + QVERIFY( !wkt.isEmpty() ); + QgsTriangle t6; + QVERIFY( t6.fromWkt( wkt ) ); + QCOMPARE( t5, t6 ); + + // conversion + QgsPolygonV2 p1; + ext = new QgsLineString(); + ext->setPoints( QgsPointSequence() << QgsPointV2( QgsWkbTypes::PointZM, 0, 0, 1, 5 ) + << QgsPointV2( QgsWkbTypes::PointZM, 0, 10, 2, 6 ) << QgsPointV2( QgsWkbTypes::PointZM, 10, 10, 3, 7 ) ); + p1.setExteriorRing( ext ); + //toPolygon + std::unique_ptr< QgsPolygonV2 > poly( t5.toPolygon() ); + QCOMPARE( *poly, p1 ); + //surfaceToPolygon + std::unique_ptr< QgsPolygonV2 > surface( t5.surfaceToPolygon() ); + QCOMPARE( *surface, p1 ); + + //bad WKT + QVERIFY( !t6.fromWkt( "Point()" ) ); + QVERIFY( t6.isEmpty() ); + QVERIFY( !t6.exteriorRing() ); + QCOMPARE( t6.numInteriorRings(), 0 ); + QVERIFY( !t6.is3D() ); + QVERIFY( !t6.isMeasure() ); + QCOMPARE( t6.wkbType(), QgsWkbTypes::Triangle ); + + // WKB + QByteArray wkb = t5.asWkb(); + t6.clear(); + QgsConstWkbPtr wkb16ptr5( wkb ); + t6.fromWkb( wkb16ptr5 ); + QCOMPARE( t5.wkbType(), QgsWkbTypes::TriangleZM ); + QCOMPARE( t5, t6 ); + + //bad WKB - check for no crash + t6.clear(); + QgsConstWkbPtr nullPtr( nullptr, 0 ); + QVERIFY( !t6.fromWkb( nullPtr ) ); + QCOMPARE( t6.wkbType(), QgsWkbTypes::Triangle ); + QgsPointV2 point( 1, 2 ); + QByteArray wkbPoint = point.asWkb(); + QgsConstWkbPtr wkbPointPtr( wkbPoint ); + QVERIFY( !t6.fromWkb( wkbPointPtr ) ); + QCOMPARE( t6.wkbType(), QgsWkbTypes::Triangle ); + + // lengths and angles + QgsTriangle t7( QgsPointV2( 0, 0 ), QgsPointV2( 0, 5 ), QgsPointV2( 5, 5 ) ); + + QVector a_tested, a_t7 = t7.angles(); + a_tested.append( M_PI / 4.0 ); + a_tested.append( M_PI / 2.0 ); + a_tested.append( M_PI / 4.0 ); + QGSCOMPARENEAR( a_tested.at( 0 ), a_t7.at( 0 ), 0.0001 ); + QGSCOMPARENEAR( a_tested.at( 1 ), a_t7.at( 1 ), 0.0001 ); + QGSCOMPARENEAR( a_tested.at( 2 ), a_t7.at( 2 ), 0.0001 ); + + QVector l_tested, l_t7 = t7.lengths(); + l_tested.append( 5 ); + l_tested.append( 5 ); + l_tested.append( sqrt( 5 * 5 + 5 * 5 ) ); + QGSCOMPARENEAR( l_tested.at( 0 ), l_t7.at( 0 ), 0.0001 ); + QGSCOMPARENEAR( l_tested.at( 1 ), l_t7.at( 1 ), 0.0001 ); + QGSCOMPARENEAR( l_tested.at( 2 ), l_t7.at( 2 ), 0.0001 ); + + // type of triangle + QVERIFY( t7.isRight() ); + QVERIFY( t7.isIsocele() ); + QVERIFY( !t7.isScalene() ); + QVERIFY( !t7.isEquilateral() ); + + QgsTriangle t8( QgsPointV2( 7.2825, 4.2368 ), QgsPointV2( 13.0058, 3.3218 ), QgsPointV2( 9.2145, 6.5242 ) ); + // angles in radians 58.8978;31.1036;89.9985 + // length 5.79598;4.96279;2.99413 + QVERIFY( t8.isRight() ); + QVERIFY( !t8.isIsocele() ); + QVERIFY( t8.isScalene() ); + QVERIFY( !t8.isEquilateral() ); + + QgsTriangle t9( QgsPointV2( 10, 10 ), QgsPointV2( 16, 10 ), QgsPointV2( 13, 15.1962 ) ); + QVERIFY( !t9.isRight() ); + QVERIFY( t9.isIsocele() ); + QVERIFY( !t9.isScalene() ); + QVERIFY( t9.isEquilateral() ); + + // vertex + QCOMPARE( t9.vertexAt( -1 ), QgsPointV2( 0, 0 ) ); + QCOMPARE( t9.vertexAt( 0 ), QgsPointV2( 10, 10 ) ); + QCOMPARE( t9.vertexAt( 1 ), QgsPointV2( 16, 10 ) ); + QCOMPARE( t9.vertexAt( 2 ), QgsPointV2( 13, 15.1962 ) ); + QCOMPARE( t9.vertexAt( 3 ), QgsPointV2( 10, 10 ) ); + QCOMPARE( t9.vertexAt( 4 ), QgsPointV2( 0, 0 ) ); + + // altitudes + QgsTriangle t10( QgsPointV2( 20, 2 ), QgsPointV2( 16, 6 ), QgsPointV2( 26, 2 ) ); + QVector alt = t10.altitudes(); + QGSCOMPARENEARPOINT( alt.at( 0 ).pointN( 1 ), QgsPointV2( 20.8276, 4.0690 ), 0.0001 ); + QGSCOMPARENEARPOINT( alt.at( 1 ).pointN( 1 ), QgsPointV2( 16, 2 ), 0.0001 ); + QGSCOMPARENEARPOINT( alt.at( 2 ).pointN( 1 ), QgsPointV2( 23, -1 ), 0.0001 ); + + // orthocenter + QCOMPARE( QgsPointV2( 16, -8 ), t10.orthocenter() ); + QCOMPARE( QgsPointV2( 0, 5 ), t7.orthocenter() ); + QGSCOMPARENEARPOINT( QgsPointV2( 13, 11.7321 ), t9.orthocenter(), 0.0001 ); + + // circumscribed circle + QCOMPARE( QgsPointV2( 2.5, 2.5 ), t7.circumscribedCenter() ); + QGSCOMPARENEAR( 3.5355, t7.circumscribedRadius(), 0.0001 ); + QCOMPARE( QgsPointV2( 23, 9 ), t10.circumscribedCenter() ); + QGSCOMPARENEAR( 7.6158, t10.circumscribedRadius(), 0.0001 ); + QGSCOMPARENEARPOINT( QgsPointV2( 13, 11.7321 ), t9.circumscribedCenter(), 0.0001 ); + QGSCOMPARENEAR( 3.4641, t9.circumscribedRadius(), 0.0001 ); + + // inscribed circle + QGSCOMPARENEARPOINT( QgsPointV2( 1.4645, 3.5355 ), t7.inscribedCenter(), 0.001 ); + QGSCOMPARENEAR( 1.4645, t7.inscribedRadius(), 0.0001 ); + QGSCOMPARENEARPOINT( QgsPointV2( 20.4433, 3.0701 ), t10.inscribedCenter(), 0.001 ); + QGSCOMPARENEAR( 1.0701, t10.inscribedRadius(), 0.0001 ); + QGSCOMPARENEARPOINT( QgsPointV2( 13, 11.7321 ), t9.inscribedCenter(), 0.0001 ); + QGSCOMPARENEAR( 1.7321, t9.inscribedRadius(), 0.0001 ); + + // medians + QVector med = t7.medians(); + QCOMPARE( med.at( 0 ).pointN( 0 ), t7.vertexAt( 0 ) ); + QGSCOMPARENEARPOINT( med.at( 0 ).pointN( 1 ), QgsPointV2( 2.5, 5 ), 0.0001 ); + QCOMPARE( med.at( 1 ).pointN( 0 ), t7.vertexAt( 1 ) ); + QGSCOMPARENEARPOINT( med.at( 1 ).pointN( 1 ), QgsPointV2( 2.5, 2.5 ), 0.0001 ); + QCOMPARE( med.at( 2 ).pointN( 0 ), t7.vertexAt( 2 ) ); + QGSCOMPARENEARPOINT( med.at( 2 ).pointN( 1 ), QgsPointV2( 0, 2.5 ), 0.0001 ); + med.clear(); + + med = t10.medians(); + QCOMPARE( med.at( 0 ).pointN( 0 ), t10.vertexAt( 0 ) ); + QGSCOMPARENEARPOINT( med.at( 0 ).pointN( 1 ), QgsPointV2( 21, 4 ), 0.0001 ); + QCOMPARE( med.at( 1 ).pointN( 0 ), t10.vertexAt( 1 ) ); + QGSCOMPARENEARPOINT( med.at( 1 ).pointN( 1 ), QgsPointV2( 23, 2 ), 0.0001 ); + QCOMPARE( med.at( 2 ).pointN( 0 ), t10.vertexAt( 2 ) ); + QGSCOMPARENEARPOINT( med.at( 2 ).pointN( 1 ), QgsPointV2( 18, 4 ), 0.0001 ); + med.clear(); + alt.clear(); + + med = t9.medians(); + alt = t9.altitudes(); + QGSCOMPARENEARPOINT( med.at( 0 ).pointN( 0 ), alt.at( 0 ).pointN( 0 ), 0.0001 ); + QGSCOMPARENEARPOINT( med.at( 0 ).pointN( 1 ), alt.at( 0 ).pointN( 1 ), 0.0001 ); + QGSCOMPARENEARPOINT( med.at( 1 ).pointN( 0 ), alt.at( 1 ).pointN( 0 ), 0.0001 ); + QGSCOMPARENEARPOINT( med.at( 1 ).pointN( 1 ), alt.at( 1 ).pointN( 1 ), 0.0001 ); + QGSCOMPARENEARPOINT( med.at( 2 ).pointN( 0 ), alt.at( 2 ).pointN( 0 ), 0.0001 ); + QGSCOMPARENEARPOINT( med.at( 2 ).pointN( 1 ), alt.at( 2 ).pointN( 1 ), 0.0001 ); + + // medial + QCOMPARE( t7.medial(), QgsTriangle( QgsPointV2( 0, 2.5 ), QgsPointV2( 2.5, 5 ), QgsPointV2( 2.5, 2.5 ) ) ); + QCOMPARE( t9.medial(), QgsTriangle( QgsGeometryUtils::midpoint( t9.vertexAt( 0 ), t9.vertexAt( 1 ) ), + QgsGeometryUtils::midpoint( t9.vertexAt( 1 ), t9.vertexAt( 2 ) ), + QgsGeometryUtils::midpoint( t9.vertexAt( 2 ), t9.vertexAt( 0 ) ) ) ); + + // bisectors + QVector bis = t7.bisectors(); + QCOMPARE( bis.at( 0 ).pointN( 0 ), t7.vertexAt( 0 ) ); + QGSCOMPARENEARPOINT( bis.at( 0 ).pointN( 1 ), QgsPointV2( 2.0711, 5 ), 0.0001 ); + QCOMPARE( bis.at( 1 ).pointN( 0 ), t7.vertexAt( 1 ) ); + QGSCOMPARENEARPOINT( bis.at( 1 ).pointN( 1 ), QgsPointV2( 2.5, 2.5 ), 0.0001 ); + QCOMPARE( bis.at( 2 ).pointN( 0 ), t7.vertexAt( 2 ) ); + QGSCOMPARENEARPOINT( bis.at( 2 ).pointN( 1 ), QgsPointV2( 0, 2.9289 ), 0.0001 ); + + // "deleted" method + QgsTriangle t11( QgsPointV2( 0, 0 ), QgsPointV2( 100, 100 ), QgsPointV2( 0, 200 ) ); + ext->setPoints( QgsPointSequence() << QgsPointV2( 5, 5 ) + << QgsPointV2( 50, 50 ) << QgsPointV2( 0, 25 ) + << QgsPointV2( 5, 5 ) ); + t11.addInteriorRing( ext ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((0 0, 100 100, 0 200, 0 0))" ) ); + + /* QList lc; + lc.append(ext); + t11.setInteriorRings( lc ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((0 0, 100 100, 0 200, 0 0))" ) );*/ + + QgsVertexId id( 0, 0, 1 ); + QVERIFY( !t11.deleteVertex( id ) ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((0 0, 100 100, 0 200, 0 0))" ) ); + QVERIFY( !t11.insertVertex( id, QgsPointV2( 5, 5 ) ) ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((0 0, 100 100, 0 200, 0 0))" ) ); + + //move vertex + QgsPointV2 pt1( 5, 5 ); + // invalid part + id.part = -1; + QVERIFY( !t11.moveVertex( id, pt1 ) ); + id.part = 1; + QVERIFY( !t11.moveVertex( id, pt1 ) ); + // invalid ring + id.part = 0; + id.ring = -1; + QVERIFY( !t11.moveVertex( id, pt1 ) ); + id.ring = 1; + QVERIFY( !t11.moveVertex( id, pt1 ) ); + id.ring = 0; + id.vertex = -1; + QVERIFY( !t11.moveVertex( id, pt1 ) ); + id.vertex = 5; + QVERIFY( !t11.moveVertex( id, pt1 ) ); + + // valid vertex + id.vertex = 0; + QVERIFY( t11.moveVertex( id, pt1 ) ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((5 5, 100 100, 0 200, 5 5))" ) ); + pt1 = QgsPointV2(); + QVERIFY( t11.moveVertex( id, pt1 ) ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((0 0, 100 100, 0 200, 0 0))" ) ); + id.vertex = 4; + pt1 = QgsPointV2( 5, 5 ); + QVERIFY( t11.moveVertex( id, pt1 ) ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((5 5, 100 100, 0 200, 5 5))" ) ); + pt1 = QgsPointV2(); + QVERIFY( t11.moveVertex( id, pt1 ) ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((0 0, 100 100, 0 200, 0 0))" ) ); + id.vertex = 1; + pt1 = QgsPointV2( 5, 5 ); + QVERIFY( t11.moveVertex( id, pt1 ) ); + QCOMPARE( t11.asWkt(), QString( "Triangle ((0 0, 5 5, 0 200, 0 0))" ) ); + // colinear + pt1 = QgsPointV2( 0, 100 ); + QVERIFY( !t11.moveVertex( id, pt1 ) ); + // duplicate point + pt1 = QgsPointV2( 0, 0 ); + QVERIFY( !t11.moveVertex( id, pt1 ) ); + +} + void TestQgsGeometry::compoundCurve() { //test that area of a compound curve ring is equal to a closed linestring with the same vertices diff --git a/tests/src/core/testqgsgeometryutils.cpp b/tests/src/core/testqgsgeometryutils.cpp index 72ab7fa53b3..a980f4388f1 100644 --- a/tests/src/core/testqgsgeometryutils.cpp +++ b/tests/src/core/testqgsgeometryutils.cpp @@ -52,6 +52,9 @@ class TestQgsGeometryUtils: public QObject void testSqrDistToLine(); void testAngleThreePoints(); void testMidPoint(); + void testGradient(); + void testCoefficients(); + void testPerpendicularSegment(); }; @@ -549,5 +552,86 @@ void TestQgsGeometryUtils::testMidPoint() QCOMPARE( QgsGeometryUtils::midpoint( p1, QgsPointV2( QgsWkbTypes::PointZM, 2, 2, 2, 2 ) ), QgsPointV2( QgsWkbTypes::PointZM, 3, 4, 1, 1 ) ); } +void TestQgsGeometryUtils::testGradient() +{ + QVERIFY( QgsGeometryUtils::gradient( QgsPointV2( 4, 6 ), QgsPointV2( 4, 8 ) ) == INFINITY ); + QGSCOMPARENEAR( QgsGeometryUtils::gradient( QgsPointV2( 2, 8 ), QgsPointV2( 3, 20 ) ), 12, 0.00000001 ); + QGSCOMPARENEAR( QgsGeometryUtils::gradient( QgsPointV2( 2, -88 ), QgsPointV2( 4, -4 ) ), 42, 0.00000001 ); + QGSCOMPARENEAR( QgsGeometryUtils::gradient( QgsPointV2( 4, 6 ), QgsPointV2( 8, 6 ) ), 0, 0.00000001 ); +} + +void TestQgsGeometryUtils::testCoefficients() +{ + double a, b, c; + + // pt1.x == pt2.x + QgsGeometryUtils::coefficients( QgsPointV2( 4, 6 ), QgsPointV2( 4, 8 ), a, b, c ); + QGSCOMPARENEAR( a, 1, 0.00000001 ); + QGSCOMPARENEAR( b, 0, 0.00000001 ); + QGSCOMPARENEAR( c, -4, 0.00000001 ); + + // pt1.y == pt2.y + QgsGeometryUtils::coefficients( QgsPointV2( 6, 4 ), QgsPointV2( 8, 4 ), a, b, c ); + QGSCOMPARENEAR( a, 0, 0.00000001 ); + QGSCOMPARENEAR( b, 1, 0.00000001 ); + QGSCOMPARENEAR( c, -4, 0.00000001 ); + + // else + QgsGeometryUtils::coefficients( QgsPointV2( 6, 4 ), QgsPointV2( 4, 8 ), a, b, c ); + QGSCOMPARENEAR( a, -4, 0.00000001 ); + QGSCOMPARENEAR( b, -2, 0.00000001 ); + QGSCOMPARENEAR( c, 32, 0.00000001 ); + QgsGeometryUtils::coefficients( QgsPointV2( -4, -2 ), QgsPointV2( 4, 2 ), a, b, c ); + QGSCOMPARENEAR( a, -4, 0.00000001 ); + QGSCOMPARENEAR( b, 8, 0.00000001 ); + QGSCOMPARENEAR( c, 0, 0.00000001 ); +} +void TestQgsGeometryUtils::testPerpendicularSegment() +{ + QgsPointV2 p1( 3, 13 ); + QgsPointV2 s1( 2, 3 ); + QgsPointV2 s2( 7, 11 ); + + QgsLineString line_r = QgsGeometryUtils::perpendicularSegment( p1, s1, s2 ); + + // default case + QgsLineString line; + line.addVertex( p1 ); + line.addVertex( QgsPointV2( 6.7753, 10.6404 ) ); + QGSCOMPARENEARPOINT( line.pointN( 0 ), line_r.pointN( 0 ), 0.0001 ); + QGSCOMPARENEARPOINT( line.pointN( 1 ), line_r.pointN( 1 ), 0.0001 ); + + // perpendicular line don't intersect segment + line.clear(); + p1 = QgsPointV2( 11, 11 ); + line_r = QgsGeometryUtils::perpendicularSegment( p1, s1, s2 ); + line.addVertex( p1 ); + line.addVertex( QgsPointV2( 8.1236, 12.7978 ) ); + QGSCOMPARENEARPOINT( line.pointN( 0 ), line_r.pointN( 0 ), 0.0001 ); + QGSCOMPARENEARPOINT( line.pointN( 1 ), line_r.pointN( 1 ), 0.0001 ); + + // horizontal + s1 = QgsPointV2( -3, 3 ); + s2 = QgsPointV2( 2, 3 ); + line.clear(); + p1 = QgsPointV2( 3, 13 ); + line_r = QgsGeometryUtils::perpendicularSegment( p1, s1, s2 ); + line.addVertex( p1 ); + line.addVertex( QgsPointV2( 3, 3 ) ); + QCOMPARE( line.pointN( 0 ), line_r.pointN( 0 ) ); + QCOMPARE( line.pointN( 1 ), line_r.pointN( 1 ) ); + + // vertical + s1 = QgsPointV2( 3, 13 ); + s2 = QgsPointV2( 3, 3 ); + line.clear(); + p1 = QgsPointV2( -7, 8 ); + line_r = QgsGeometryUtils::perpendicularSegment( p1, s1, s2 ); + line.addVertex( p1 ); + line.addVertex( QgsPointV2( 3, 8 ) ); + QCOMPARE( line.pointN( 0 ), line_r.pointN( 0 ) ); + QCOMPARE( line.pointN( 1 ), line_r.pointN( 1 ) ); +} + QGSTEST_MAIN( TestQgsGeometryUtils ) #include "testqgsgeometryutils.moc"