geometry: Add polyhedralsurface support

This commit is contained in:
Jean Felder 2024-08-08 15:32:53 +02:00 committed by Loïc Bartoletti
parent 3295d7e803
commit 371eca55e8
14 changed files with 3883 additions and 1 deletions

View File

@ -1101,7 +1101,7 @@ if (WITH_CORE AND WITH_BINDINGS)
include(SIPMacros)
set(SIP_INCLUDES ${PYQT_SIP_DIR} ${CMAKE_SOURCE_DIR}/python)
set(SIP_CONCAT_PARTS 22)
set(SIP_CONCAT_PARTS 24)
if (NOT BINDINGS_GLOBAL_INSTALL)
set(Python_SITEARCH ${QGIS_DATA_DIR}/python)

View File

@ -0,0 +1,5 @@
# The following has been generated automatically from src/core/geometry/qgspolyhedralsurface.h
try:
QgsPolyhedralSurface.__group__ = ['geometry']
except NameError:
pass

View File

@ -0,0 +1,302 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/geometry/qgspolyhedralsurface.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsPolyhedralSurface: QgsSurface
{
%Docstring(signature="appended")
Polyhedral surface geometry type.
A polyhedral surface is a collection of polygons which share common boundary segments.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgspolyhedralsurface.h"
%End
public:
QgsPolyhedralSurface();
QgsPolyhedralSurface( const QgsPolyhedralSurface &p );
QgsPolyhedralSurface( const QgsMultiPolygon *multiPolygon );
%Docstring
Creates a polyhedral surface from a multiPolygon.
%End
public:
virtual bool fuzzyEqual( const QgsAbstractGeometry &other, double epsilon = 1e-8 ) const /HoldGIL/;
virtual bool fuzzyDistanceEqual( const QgsAbstractGeometry &other, double epsilon = 1e-8 ) const /HoldGIL/;
virtual bool operator==( const QgsAbstractGeometry &other ) const;
virtual bool operator!=( const QgsAbstractGeometry &other ) const;
~QgsPolyhedralSurface();
virtual QString geometryType() const /HoldGIL/;
int dimension() const final /HoldGIL/;
virtual QgsPolyhedralSurface *clone() const /Factory/;
virtual void clear();
virtual bool fromWkb( QgsConstWkbPtr &wkb );
virtual bool fromWkt( const QString &wkt );
virtual int wkbSize( QgsAbstractGeometry::WkbFlags flags = QgsAbstractGeometry::WkbFlags() ) const;
virtual QByteArray asWkb( QgsAbstractGeometry::WkbFlags flags = QgsAbstractGeometry::WkbFlags() ) const;
virtual QString asWkt( int precision = 17 ) const;
virtual QDomElement asGml2( QDomDocument &doc, int precision = 17, const QString &ns = "gml", QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) const;
virtual QDomElement asGml3( QDomDocument &doc, int precision = 17, const QString &ns = "gml", QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) const;
virtual QString asKml( int precision = 17 ) const;
virtual void normalize() /HoldGIL/;
virtual double area() const /HoldGIL/;
virtual double perimeter() const /HoldGIL/;
virtual QgsAbstractGeometry *boundary() const /Factory/;
virtual QgsPolyhedralSurface *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/;
virtual QgsPolyhedralSurface *simplifyByDistance( double tolerance ) const /Factory/;
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
virtual bool boundingBoxIntersects( const QgsBox3D &box3d ) const /HoldGIL/;
int numPatches() const /HoldGIL/;
%Docstring
Returns the number of patches contained with the polyhedral surface.
.. seealso:: :py:func:`patchN`
%End
SIP_PYOBJECT patchN( int i ) /HoldGIL,TypeHint="QgsPolygon"/;
%Docstring
Retrieves a patch from the polyhedral surface. The first patch has index 0.
:raises IndexError: if no patch with the specified index exists.
.. seealso:: :py:func:`numPatches`
%End
%MethodCode
if ( a0 < 0 || a0 >= sipCpp->numPatches() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return sipConvertFromType( const_cast< QgsPolygon * >( sipCpp->patchN( a0 ) ), sipType_QgsPolygon, NULL );
}
%End
virtual void setPatches( const QVector<QgsPolygon *> &patches /Transfer/ );
%Docstring
Sets all patches, transferring ownership to the polyhedral surface.
%End
virtual void addPatch( QgsPolygon *patch /Transfer/ );
%Docstring
Adds a patch to the geometry, transferring ownership to the polyhedral surface.
%End
bool removePatch( int ringIndex );
%Docstring
Removes a patch from the polyhedral surface. The first patch has index 0.
The corresponding patch is removed from the polyhedral surface and deleted.
:raises IndexError: if no patch with the specified index exists.
%End
%MethodCode
if ( a0 < 0 || a0 >= sipCpp->numPatches() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return PyBool_FromLong( sipCpp->removePatch( a0 ) );
}
%End
virtual QPainterPath asQPainterPath() const;
virtual void draw( QPainter &p ) const;
virtual void transform( const QgsCoordinateTransform &ct, Qgis::TransformDirection d = Qgis::TransformDirection::Forward, bool transformZ = false ) throw( QgsCsException );
virtual void transform( const QTransform &t, double zTranslate = 0.0, double zScale = 1.0, double mTranslate = 0.0, double mScale = 1.0 );
virtual bool insertVertex( QgsVertexId position, const QgsPoint &vertex );
virtual bool moveVertex( QgsVertexId position, const QgsPoint &newPos );
virtual bool deleteVertex( QgsVertexId position );
virtual QgsCoordinateSequence coordinateSequence() const;
virtual int nCoordinates() const;
virtual int vertexNumberFromVertexId( QgsVertexId id ) const;
virtual bool isEmpty() const /HoldGIL/;
virtual double closestSegment( const QgsPoint &pt, QgsPoint &segmentPt /Out/, QgsVertexId &vertexAfter /Out/, int *leftOf /Out/ = 0, double epsilon = 4 * DBL_EPSILON ) const;
virtual bool nextVertex( QgsVertexId &id, QgsPoint &vertex /Out/ ) const;
virtual void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex /Out/, QgsVertexId &nextVertex /Out/ ) const;
bool hasCurvedSegments() const final;
virtual QgsAbstractGeometry *segmentize( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/;
%Docstring
Returns a geometry without curves. Caller takes ownership
:param tolerance: segmentation tolerance
:param toleranceType: maximum segmentation angle or maximum difference between approximation and curve
%End
virtual double vertexAngle( QgsVertexId vertex ) const;
%Docstring
Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
:param vertex: the vertex id
:return: rotation in radians, clockwise from north
%End
virtual int vertexCount( int part = 0, int ring = 0 ) const;
virtual int ringCount( int part = 0 ) const /HoldGIL/;
virtual int partCount() const /HoldGIL/;
virtual QgsPoint vertexAt( QgsVertexId id ) const;
virtual double segmentLength( QgsVertexId startVertex ) const;
virtual bool addZValue( double zValue = 0 );
virtual bool addMValue( double mValue = 0 );
virtual bool dropZValue();
virtual bool dropMValue();
virtual void swapXy();
virtual QgsMultiSurface *toCurveType() const /Factory/;
virtual bool transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback = 0 );
QgsMultiPolygon *toMultiPolygon() const /Factory/;
%Docstring
Converts a polyhedral surface to a multipolygon.
Caller takes ownership.
%End
virtual QgsPolyhedralSurface *createEmptyWithSameType() const /Factory/;
SIP_PYOBJECT __repr__();
%MethodCode
QString wkt = sipCpp->asWkt();
if ( wkt.length() > 1000 )
wkt = wkt.left( 1000 ) + QStringLiteral( "..." );
QString str = QStringLiteral( "<QgsPolyhedralSurface: %1>" ).arg( wkt );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
Py_ssize_t __len__() const;
%Docstring
Returns the number of patches within the polyhedral surface.
%End
%MethodCode
sipRes = sipCpp->numPatches();
%End
SIP_PYOBJECT __getitem__( int index ) /TypeHint="QgsPolygon"/;
%Docstring
Returns the geometry at the specified ``index``.
Indexes can be less than 0, in which case they correspond to geometries from the end of the collect. E.g. an index of -1
corresponds to the last geometry in the collection.
:raises IndexError: if no geometry with the specified ``index`` exists.
%End
%MethodCode
const int count = sipCpp->numPatches();
if ( a0 < -count || a0 >= count )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else if ( a0 >= 0 )
{
return sipConvertFromType( sipCpp->patchN( a0 ), sipType_QgsPolygon, NULL );
}
else
{
return sipConvertFromType( sipCpp->patchN( count + a0 ), sipType_QgsPolygon, NULL );
}
%End
protected:
virtual int childCount() const;
virtual QgsAbstractGeometry *childGeometry( int index ) const;
virtual int compareToSameClass( const QgsAbstractGeometry *other ) const;
virtual QgsBox3D calculateBoundingBox3D() const;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/geometry/qgspolyhedralsurface.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -353,6 +353,7 @@
%Include auto_generated/geometry/qgsorientedbox3d.sip
%Include auto_generated/geometry/qgspoint.sip
%Include auto_generated/geometry/qgspolygon.sip
%Include auto_generated/geometry/qgspolyhedralsurface.sip
%Include auto_generated/geometry/qgsquadrilateral.sip
%Include auto_generated/geometry/qgsrectangle.sip
%Include auto_generated/geometry/qgsreferencedgeometry.sip

View File

@ -0,0 +1,5 @@
# The following has been generated automatically from src/core/geometry/qgspolyhedralsurface.h
try:
QgsPolyhedralSurface.__group__ = ['geometry']
except NameError:
pass

View File

@ -0,0 +1,302 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/geometry/qgspolyhedralsurface.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsPolyhedralSurface: QgsSurface
{
%Docstring(signature="appended")
Polyhedral surface geometry type.
A polyhedral surface is a collection of polygons which share common boundary segments.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgspolyhedralsurface.h"
%End
public:
QgsPolyhedralSurface();
QgsPolyhedralSurface( const QgsPolyhedralSurface &p );
QgsPolyhedralSurface( const QgsMultiPolygon *multiPolygon );
%Docstring
Creates a polyhedral surface from a multiPolygon.
%End
public:
virtual bool fuzzyEqual( const QgsAbstractGeometry &other, double epsilon = 1e-8 ) const /HoldGIL/;
virtual bool fuzzyDistanceEqual( const QgsAbstractGeometry &other, double epsilon = 1e-8 ) const /HoldGIL/;
virtual bool operator==( const QgsAbstractGeometry &other ) const;
virtual bool operator!=( const QgsAbstractGeometry &other ) const;
~QgsPolyhedralSurface();
virtual QString geometryType() const /HoldGIL/;
int dimension() const final /HoldGIL/;
virtual QgsPolyhedralSurface *clone() const /Factory/;
virtual void clear();
virtual bool fromWkb( QgsConstWkbPtr &wkb );
virtual bool fromWkt( const QString &wkt );
virtual int wkbSize( QgsAbstractGeometry::WkbFlags flags = QgsAbstractGeometry::WkbFlags() ) const;
virtual QByteArray asWkb( QgsAbstractGeometry::WkbFlags flags = QgsAbstractGeometry::WkbFlags() ) const;
virtual QString asWkt( int precision = 17 ) const;
virtual QDomElement asGml2( QDomDocument &doc, int precision = 17, const QString &ns = "gml", QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) const;
virtual QDomElement asGml3( QDomDocument &doc, int precision = 17, const QString &ns = "gml", QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) const;
virtual QString asKml( int precision = 17 ) const;
virtual void normalize() /HoldGIL/;
virtual double area() const /HoldGIL/;
virtual double perimeter() const /HoldGIL/;
virtual QgsAbstractGeometry *boundary() const /Factory/;
virtual QgsPolyhedralSurface *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const /Factory/;
virtual QgsPolyhedralSurface *simplifyByDistance( double tolerance ) const /Factory/;
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
virtual bool boundingBoxIntersects( const QgsBox3D &box3d ) const /HoldGIL/;
int numPatches() const /HoldGIL/;
%Docstring
Returns the number of patches contained with the polyhedral surface.
.. seealso:: :py:func:`patchN`
%End
SIP_PYOBJECT patchN( int i ) /HoldGIL,TypeHint="QgsPolygon"/;
%Docstring
Retrieves a patch from the polyhedral surface. The first patch has index 0.
:raises IndexError: if no patch with the specified index exists.
.. seealso:: :py:func:`numPatches`
%End
%MethodCode
if ( a0 < 0 || a0 >= sipCpp->numPatches() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return sipConvertFromType( const_cast< QgsPolygon * >( sipCpp->patchN( a0 ) ), sipType_QgsPolygon, NULL );
}
%End
virtual void setPatches( const QVector<QgsPolygon *> &patches /Transfer/ );
%Docstring
Sets all patches, transferring ownership to the polyhedral surface.
%End
virtual void addPatch( QgsPolygon *patch /Transfer/ );
%Docstring
Adds a patch to the geometry, transferring ownership to the polyhedral surface.
%End
bool removePatch( int ringIndex );
%Docstring
Removes a patch from the polyhedral surface. The first patch has index 0.
The corresponding patch is removed from the polyhedral surface and deleted.
:raises IndexError: if no patch with the specified index exists.
%End
%MethodCode
if ( a0 < 0 || a0 >= sipCpp->numPatches() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return PyBool_FromLong( sipCpp->removePatch( a0 ) );
}
%End
virtual QPainterPath asQPainterPath() const;
virtual void draw( QPainter &p ) const;
virtual void transform( const QgsCoordinateTransform &ct, Qgis::TransformDirection d = Qgis::TransformDirection::Forward, bool transformZ = false ) throw( QgsCsException );
virtual void transform( const QTransform &t, double zTranslate = 0.0, double zScale = 1.0, double mTranslate = 0.0, double mScale = 1.0 );
virtual bool insertVertex( QgsVertexId position, const QgsPoint &vertex );
virtual bool moveVertex( QgsVertexId position, const QgsPoint &newPos );
virtual bool deleteVertex( QgsVertexId position );
virtual QgsCoordinateSequence coordinateSequence() const;
virtual int nCoordinates() const;
virtual int vertexNumberFromVertexId( QgsVertexId id ) const;
virtual bool isEmpty() const /HoldGIL/;
virtual double closestSegment( const QgsPoint &pt, QgsPoint &segmentPt /Out/, QgsVertexId &vertexAfter /Out/, int *leftOf /Out/ = 0, double epsilon = 4 * DBL_EPSILON ) const;
virtual bool nextVertex( QgsVertexId &id, QgsPoint &vertex /Out/ ) const;
virtual void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex /Out/, QgsVertexId &nextVertex /Out/ ) const;
bool hasCurvedSegments() const final;
virtual QgsAbstractGeometry *segmentize( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/;
%Docstring
Returns a geometry without curves. Caller takes ownership
:param tolerance: segmentation tolerance
:param toleranceType: maximum segmentation angle or maximum difference between approximation and curve
%End
virtual double vertexAngle( QgsVertexId vertex ) const;
%Docstring
Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
:param vertex: the vertex id
:return: rotation in radians, clockwise from north
%End
virtual int vertexCount( int part = 0, int ring = 0 ) const;
virtual int ringCount( int part = 0 ) const /HoldGIL/;
virtual int partCount() const /HoldGIL/;
virtual QgsPoint vertexAt( QgsVertexId id ) const;
virtual double segmentLength( QgsVertexId startVertex ) const;
virtual bool addZValue( double zValue = 0 );
virtual bool addMValue( double mValue = 0 );
virtual bool dropZValue();
virtual bool dropMValue();
virtual void swapXy();
virtual QgsMultiSurface *toCurveType() const /Factory/;
virtual bool transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback = 0 );
QgsMultiPolygon *toMultiPolygon() const /Factory/;
%Docstring
Converts a polyhedral surface to a multipolygon.
Caller takes ownership.
%End
virtual QgsPolyhedralSurface *createEmptyWithSameType() const /Factory/;
SIP_PYOBJECT __repr__();
%MethodCode
QString wkt = sipCpp->asWkt();
if ( wkt.length() > 1000 )
wkt = wkt.left( 1000 ) + QStringLiteral( "..." );
QString str = QStringLiteral( "<QgsPolyhedralSurface: %1>" ).arg( wkt );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
int __len__() const;
%Docstring
Returns the number of patches within the polyhedral surface.
%End
%MethodCode
sipRes = sipCpp->numPatches();
%End
SIP_PYOBJECT __getitem__( int index ) /TypeHint="QgsPolygon"/;
%Docstring
Returns the geometry at the specified ``index``.
Indexes can be less than 0, in which case they correspond to geometries from the end of the collect. E.g. an index of -1
corresponds to the last geometry in the collection.
:raises IndexError: if no geometry with the specified ``index`` exists.
%End
%MethodCode
const int count = sipCpp->numPatches();
if ( a0 < -count || a0 >= count )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else if ( a0 >= 0 )
{
return sipConvertFromType( sipCpp->patchN( a0 ), sipType_QgsPolygon, NULL );
}
else
{
return sipConvertFromType( sipCpp->patchN( count + a0 ), sipType_QgsPolygon, NULL );
}
%End
protected:
virtual int childCount() const;
virtual QgsAbstractGeometry *childGeometry( int index ) const;
virtual int compareToSameClass( const QgsAbstractGeometry *other ) const;
virtual QgsBox3D calculateBoundingBox3D() const;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/geometry/qgspolyhedralsurface.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -353,6 +353,7 @@
%Include auto_generated/geometry/qgsorientedbox3d.sip
%Include auto_generated/geometry/qgspoint.sip
%Include auto_generated/geometry/qgspolygon.sip
%Include auto_generated/geometry/qgspolyhedralsurface.sip
%Include auto_generated/geometry/qgsquadrilateral.sip
%Include auto_generated/geometry/qgsrectangle.sip
%Include auto_generated/geometry/qgsreferencedgeometry.sip

View File

@ -902,6 +902,7 @@ set(QGIS_CORE_SRCS
geometry/qgsorientedbox3d.cpp
geometry/qgspoint.cpp
geometry/qgspolygon.cpp
geometry/qgspolyhedralsurface.cpp
geometry/qgsquadrilateral.cpp
geometry/qgsrectangle.cpp
geometry/qgsreferencedgeometry.cpp
@ -1491,6 +1492,7 @@ set(QGIS_CORE_HDRS
geometry/qgsorientedbox3d.h
geometry/qgspoint.h
geometry/qgspolygon.h
geometry/qgspolyhedralsurface.h
geometry/qgsquadrilateral.h
geometry/qgsrectangle.h
geometry/qgsreferencedgeometry.h

View File

@ -0,0 +1,996 @@
/***************************************************************************
qgspolyhedralsurface.cpp
---------------------
begin : August 2024
copyright : (C) 2024 by Jean Felder
email : jean dot felder 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 "qgspolyhedralsurface.h"
#include "qgsapplication.h"
#include "qgscurve.h"
#include "qgsfeedback.h"
#include "qgsgeometryutils.h"
#include "qgslinestring.h"
#include "qgslogger.h"
#include "qgsmultipolygon.h"
#include "qgsmultisurface.h"
#include "qgspolygon.h"
#include "qgsvertexid.h"
#include "qgswkbptr.h"
#include "qgsmultilinestring.h"
#include <QPainter>
#include <QPainterPath>
#include <memory>
#include <nlohmann/json.hpp>
QgsPolyhedralSurface::QgsPolyhedralSurface()
{
mWkbType = Qgis::WkbType::PolyhedralSurface;
}
QgsPolyhedralSurface::QgsPolyhedralSurface( const QgsMultiPolygon *multiPolygon )
{
if ( multiPolygon->isEmpty() )
{
return;
}
mPatches.reserve( multiPolygon->numGeometries() );
for ( int i = 0; i < multiPolygon->numGeometries(); ++i )
{
mPatches.append( multiPolygon->polygonN( i )->clone() );
}
setZMTypeFromSubGeometry( multiPolygon->polygonN( 0 ), Qgis::WkbType::PolyhedralSurface );
}
QgsPolyhedralSurface::~QgsPolyhedralSurface()
{
QgsPolyhedralSurface::clear();
}
QgsPolyhedralSurface *QgsPolyhedralSurface::createEmptyWithSameType() const
{
auto result = std::make_unique< QgsPolyhedralSurface >();
result->mWkbType = mWkbType;
return result.release();
}
QString QgsPolyhedralSurface::geometryType() const
{
return QStringLiteral( "PolyhedralSurface" );
}
int QgsPolyhedralSurface::dimension() const
{
return 2;
}
QgsPolyhedralSurface::QgsPolyhedralSurface( const QgsPolyhedralSurface &p )
: QgsSurface( p )
{
mWkbType = p.mWkbType;
mPatches.reserve( p.mPatches.size() );
for ( const QgsPolygon *patch : p.mPatches )
{
mPatches.push_back( patch->clone() );
}
mBoundingBox = p.mBoundingBox;
mHasCachedValidity = p.mHasCachedValidity;
mValidityFailureReason = p.mValidityFailureReason;
}
// cppcheck-suppress operatorEqVarError
QgsPolyhedralSurface &QgsPolyhedralSurface::operator=( const QgsPolyhedralSurface &p )
{
if ( &p != this )
{
QgsSurface::operator=( p );
mPatches.reserve( p.mPatches.size() );
for ( const QgsPolygon *patch : p.mPatches )
{
mPatches.push_back( patch->clone() );
}
}
return *this;
}
QgsPolyhedralSurface *QgsPolyhedralSurface::clone() const
{
return new QgsPolyhedralSurface( *this );
}
void QgsPolyhedralSurface::clear()
{
mWkbType = Qgis::WkbType::PolyhedralSurface;
qDeleteAll( mPatches );
mPatches.clear();
clearCache();
}
bool QgsPolyhedralSurface::fromWkb( QgsConstWkbPtr &wkbPtr )
{
clear();
if ( !wkbPtr )
{
return false;
}
Qgis::WkbType type = wkbPtr.readHeader();
if ( QgsWkbTypes::flatType( type ) != Qgis::WkbType::PolyhedralSurface )
{
return false;
}
mWkbType = type;
int nPatches;
wkbPtr >> nPatches;
std::unique_ptr< QgsPolygon > currentPatch;
for ( int i = 0; i < nPatches; ++i )
{
Qgis::WkbType polygonType = wkbPtr.readHeader();
wkbPtr -= 1 + sizeof( int );
Qgis::WkbType flatPolygonType = QgsWkbTypes::flatType( polygonType );
if ( flatPolygonType == Qgis::WkbType::Polygon )
{
currentPatch.reset( new QgsPolygon() );
}
else
{
return false;
}
currentPatch->fromWkb( wkbPtr ); // also updates wkbPtr
mPatches.append( currentPatch.release() );
}
return true;
}
bool QgsPolyhedralSurface::fromWkt( const QString &wkt )
{
clear();
QPair<Qgis::WkbType, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
if ( QgsWkbTypes::geometryType( parts.first ) != Qgis::GeometryType::Polygon )
return false;
mWkbType = parts.first;
QString secondWithoutParentheses = parts.second;
secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
secondWithoutParentheses.isEmpty() )
return true;
QString defaultChildWkbType = QStringLiteral( "Polygon%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
for ( const QString &childWkt : blocks )
{
QPair<Qgis::WkbType, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
if ( QgsWkbTypes::flatType( childParts.first ) == Qgis::WkbType::Polygon )
{
mPatches.append( new QgsPolygon() );
}
else
{
clear();
return false;
}
if ( !mPatches.back()->fromWkt( childWkt ) )
{
clear();
return false;
}
}
return true;
}
QgsBox3D QgsPolyhedralSurface::calculateBoundingBox3D() const
{
if ( mPatches.empty() )
{
return QgsBox3D();
}
QgsBox3D bbox = mPatches.at( 0 )->boundingBox3D();
for ( int i = 1; i < mPatches.size(); ++i )
{
QgsBox3D polygonBox = mPatches.at( i )->boundingBox3D();
bbox.combineWith( polygonBox );
}
return bbox;
}
int QgsPolyhedralSurface::wkbSize( QgsAbstractGeometry::WkbFlags flags ) const
{
int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
for ( const QgsPolygon *patch : mPatches )
{
binarySize += patch->wkbSize( flags );
}
return binarySize;
}
QByteArray QgsPolyhedralSurface::asWkb( WkbFlags flags ) const
{
QByteArray wkbArray;
wkbArray.resize( QgsPolyhedralSurface::wkbSize( flags ) );
QgsWkbPtr wkb( wkbArray );
wkb << static_cast<char>( QgsApplication::endian() );
wkb << static_cast<quint32>( wkbType() );
wkb << static_cast<quint32>( mPatches.size() );
for ( const QgsPolygon *patch : mPatches )
{
wkb << patch->asWkb( flags );
}
return wkbArray;
}
QString QgsPolyhedralSurface::asWkt( int precision ) const
{
QString wkt = wktTypeStr();
if ( isEmpty() )
wkt += QLatin1String( " EMPTY" );
else
{
wkt += QLatin1String( " (" );
for ( const QgsPolygon *patch : mPatches )
{
QString childWkt = patch->asWkt( precision );
if ( qgsgeometry_cast<const QgsPolygon *>( patch ) )
{
// Type names of linear geometries are omitted
childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
}
wkt += childWkt + ',';
}
if ( wkt.endsWith( ',' ) )
{
wkt.chop( 1 ); // Remove last ','
}
wkt += ')';
}
return wkt;
}
QDomElement QgsPolyhedralSurface::asGml2( QDomDocument &, int, const QString &, const AxisOrder ) const
{
QgsDebugError( QStringLiteral( "gml version 2 does not support PolyhedralSurface geometry" ) );
return QDomElement();
}
QDomElement QgsPolyhedralSurface::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
{
QDomElement elemPolyhedralSurface = doc.createElementNS( ns, QStringLiteral( "PolyhedralSurface" ) );
if ( isEmpty() )
return elemPolyhedralSurface;
QDomElement elemPolygonPatches = doc.createElementNS( ns, QStringLiteral( "polygonPatches" ) );
for ( QgsPolygon *patch : mPatches )
{
QDomElement elemPolygonPatch = patch->asGml3( doc, precision, ns, axisOrder );
elemPolygonPatch.setTagName( "PolygonPatch" );
elemPolygonPatches.appendChild( elemPolygonPatch );
}
elemPolyhedralSurface.appendChild( elemPolygonPatches );
return elemPolyhedralSurface;
}
json QgsPolyhedralSurface::asJsonObject( int precision ) const
{
// GeoJSON format does not support PolyhedralSurface geometry
// Return a multipolygon instead;
std::unique_ptr<QgsMultiPolygon> multiPolygon( toMultiPolygon() );
return multiPolygon->asJsonObject( precision );
}
QString QgsPolyhedralSurface::asKml( int ) const
{
QgsDebugError( QStringLiteral( "kml format does not support PolyhedralSurface geometry" ) );
return QString( "" );
}
void QgsPolyhedralSurface::normalize()
{
for ( QgsPolygon *patch : mPatches )
{
QgsCurve *exteriorRing = patch->exteriorRing();
if ( !exteriorRing )
continue;
exteriorRing->normalize();
if ( patch->numInteriorRings() > 0 )
{
QVector<QgsCurve *> interiorRings;
for ( int i = 0, n = patch->numInteriorRings(); i < n; ++i )
{
interiorRings.push_back( patch->interiorRing( i )->clone() );
}
// sort rings
std::sort( interiorRings.begin(), interiorRings.end(), []( const QgsCurve * a, const QgsCurve * b )
{
return a->compareTo( b ) > 0;
} );
patch->removeInteriorRings();
for ( QgsCurve *curve : interiorRings )
patch->addInteriorRing( curve );
}
}
}
double QgsPolyhedralSurface::area() const
{
// sum area of patches
double area = 0.0;
for ( const QgsPolygon *patch : mPatches )
{
area += patch->area();
}
return area;
}
double QgsPolyhedralSurface::perimeter() const
{
// sum perimeter of patches
double perimeter = 0.0;
for ( const QgsPolygon *patch : mPatches )
{
perimeter += patch->perimeter();
}
return perimeter;
}
QgsAbstractGeometry *QgsPolyhedralSurface::boundary() const
{
std::unique_ptr< QgsMultiLineString > multiLine( new QgsMultiLineString() );
multiLine->reserve( mPatches.size() );
for ( QgsPolygon *polygon : mPatches )
{
std::unique_ptr<QgsAbstractGeometry> polygonBoundary( polygon->boundary() );
if ( QgsLineString *lineStringBoundary = qgsgeometry_cast< QgsLineString * >( polygonBoundary.get() ) )
{
multiLine->addGeometry( lineStringBoundary->clone() );
}
else if ( QgsMultiLineString *multiLineStringBoundary = qgsgeometry_cast< QgsMultiLineString * >( polygonBoundary.get() ) )
{
for ( int j = 0; j < multiLineStringBoundary->numGeometries(); ++j )
{
multiLine->addGeometry( multiLineStringBoundary->geometryN( j )->clone() );
}
}
}
if ( multiLine->numGeometries() == 0 )
{
return nullptr;
}
return multiLine.release();
}
QgsPolyhedralSurface *QgsPolyhedralSurface::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
{
std::unique_ptr< QgsPolyhedralSurface > surface( createEmptyWithSameType() );
for ( QgsPolygon *patch : mPatches )
{
// exterior ring
std::unique_ptr<QgsCurve> exteriorRing( static_cast< QgsCurve *>( patch->exteriorRing()->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
if ( !exteriorRing )
{
return nullptr;
}
std::unique_ptr<QgsPolygon> gridifiedPatch = std::make_unique<QgsPolygon>();
gridifiedPatch->setExteriorRing( exteriorRing.release() );
//interior rings
for ( int i = 0, n = patch->numInteriorRings(); i < n; ++i )
{
QgsCurve *interiorRing = patch->interiorRing( i );
if ( !interiorRing )
continue;
std::unique_ptr<QgsCurve> gridifiedInterior( static_cast< QgsCurve * >( interiorRing->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
if ( gridifiedInterior )
gridifiedPatch->addInteriorRing( gridifiedInterior.release() );
}
surface->addPatch( gridifiedPatch.release() );
}
return surface.release();
}
QgsPolyhedralSurface *QgsPolyhedralSurface::simplifyByDistance( double tolerance ) const
{
if ( isEmpty() )
return nullptr;
std::unique_ptr< QgsPolyhedralSurface > simplifiedGeom = std::make_unique< QgsPolyhedralSurface >();
for ( QgsPolygon *polygon : mPatches )
{
std::unique_ptr<QgsCurvePolygon> polygonSimplified( polygon->simplifyByDistance( tolerance ) );
simplifiedGeom->addPatch( polygonSimplified->surfaceToPolygon() );
}
return simplifiedGeom.release();
}
bool QgsPolyhedralSurface::removeDuplicateNodes( double epsilon, bool useZValues )
{
bool result = false;
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
if ( patch->removeDuplicateNodes( epsilon, useZValues ) )
{
result = true;
}
}
return result;
}
bool QgsPolyhedralSurface::boundingBoxIntersects( const QgsBox3D &box3d ) const
{
// if we already have the bounding box calculated, then this check is trivial!
if ( !mBoundingBox.isNull() )
{
return mBoundingBox.intersects( box3d );
}
// loop through each patch and test the bounding box intersection.
// This gives us a chance to use optimisations which may be present on the individual
// ring geometry subclasses, and at worst it will cause a calculation of the bounding box
// of each individual patch geometry which we would have to do anyway... (and these
// bounding boxes are cached, so would be reused without additional expense)
for ( const QgsPolygon *patch : mPatches )
{
if ( patch->boundingBoxIntersects( box3d ) )
return true;
}
// even if we don't intersect the bounding box of any rings, we may still intersect the
// bounding box of the overall polygon (we are considering worst case scenario here and
// the polygon is invalid, with rings outside the exterior ring!)
// so here we fall back to the non-optimised base class check which has to first calculate
// the overall bounding box of the polygon..
return QgsSurface::boundingBoxIntersects( box3d );
}
void QgsPolyhedralSurface::setPatches( const QVector<QgsPolygon *> &patches )
{
qDeleteAll( mPatches );
mPatches.clear();
for ( QgsPolygon *patch : patches )
{
addPatch( patch );
}
clearCache();
}
void QgsPolyhedralSurface::addPatch( QgsPolygon *patch )
{
if ( !patch )
return;
if ( mPatches.empty() )
{
setZMTypeFromSubGeometry( patch, Qgis::WkbType::PolyhedralSurface );
}
// Ensure dimensionality of patch matches polyhedral surface
if ( !is3D() )
patch->dropZValue();
else if ( !patch->is3D() )
patch->addZValue();
if ( !isMeasure() )
patch->dropMValue();
else if ( !patch->isMeasure() )
patch->addMValue();
mPatches.append( patch );
clearCache();
}
bool QgsPolyhedralSurface::removePatch( int patchIndex )
{
if ( patchIndex < 0 || patchIndex >= mPatches.size() )
{
return false;
}
delete mPatches.takeAt( patchIndex );
clearCache();
return true;
}
QPainterPath QgsPolyhedralSurface::asQPainterPath() const
{
QPainterPath painterPath;
for ( const QgsPolygon *patch : mPatches )
{
QPainterPath patchPath = patch->asQPainterPath();
patchPath.closeSubpath();
painterPath.addPath( patchPath );
}
return painterPath;
}
void QgsPolyhedralSurface::draw( QPainter &p ) const
{
if ( mPatches.empty() )
return;
for ( const QgsPolygon *patch : mPatches )
{
patch->draw( p );
}
}
void QgsPolyhedralSurface::transform( const QgsCoordinateTransform &ct, Qgis::TransformDirection d, bool transformZ )
{
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->transform( ct, d, transformZ );
}
clearCache();
}
void QgsPolyhedralSurface::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
{
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->transform( t, zTranslate, zScale, mTranslate, mScale );
}
clearCache();
}
QgsCoordinateSequence QgsPolyhedralSurface::coordinateSequence() const
{
QgsCoordinateSequence sequence;
for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
{
QgsCoordinateSequence polyCoords = polygon->coordinateSequence();
QgsCoordinateSequence::const_iterator cIt = polyCoords.constBegin();
for ( ; cIt != polyCoords.constEnd(); ++cIt )
{
sequence.push_back( *cIt );
}
}
return sequence;
}
int QgsPolyhedralSurface::nCoordinates() const
{
int count = 0;
for ( const QgsPolygon *patch : mPatches )
{
count += patch->nCoordinates();
}
return count;
}
int QgsPolyhedralSurface::vertexNumberFromVertexId( QgsVertexId id ) const
{
if ( id.part < 0 || id.part >= partCount() )
return -1;
int number = 0;
for ( int i = 0; i < mPatches.count(); ++i )
{
if ( id.part == i )
{
int partNumber = mPatches.at( i )->vertexNumberFromVertexId( QgsVertexId( 0, id.ring, id.vertex ) );
if ( partNumber == -1 )
{
return -1;
}
return number + partNumber;
}
else
{
number += mPatches.at( i )->nCoordinates();
}
}
return -1; // should not happen
}
bool QgsPolyhedralSurface::isEmpty() const
{
return mPatches.isEmpty();
}
double QgsPolyhedralSurface::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
{
QVector<QgsPolygon *> segmentList = mPatches;
return QgsGeometryUtils::closestSegmentFromComponents( segmentList, QgsGeometryUtils::Part, pt, segmentPt, vertexAfter, leftOf, epsilon );
}
bool QgsPolyhedralSurface::nextVertex( QgsVertexId &vId, QgsPoint &vertex ) const
{
if ( vId.part < 0 )
{
vId.part = 0;
vId.ring = -1;
vId.vertex = -1;
}
if ( isEmpty() || vId.part >= partCount() )
{
return false;
}
QgsPolygon *patch = mPatches[vId.part];
if ( patch->nextVertex( vId, vertex ) )
{
return true;
}
++vId.part;
vId.ring = 0;
vId.vertex = -1;
if ( vId.part >= partCount() )
{
return false;
}
patch = mPatches[vId.part];
return patch->nextVertex( vId, vertex );
}
void QgsPolyhedralSurface::adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) const
{
if ( vertex.part < 0 || vertex.part >= partCount() )
{
previousVertex = QgsVertexId();
nextVertex = QgsVertexId();
return;
}
QgsPolygon *patch = mPatches[vertex.ring];
patch->adjacentVertices( QgsVertexId( 0, 0, vertex.vertex ), previousVertex, nextVertex );
return;
}
bool QgsPolyhedralSurface::insertVertex( QgsVertexId vId, const QgsPoint &vertex )
{
if ( vId.part < 0 || vId.part >= partCount() )
{
return false;
}
QgsPolygon *patch = mPatches.at( vId.part );
bool success = patch->insertVertex( QgsVertexId( 0, vId.ring, vId.vertex ), vertex );
if ( success )
{
clearCache();
}
return success;
}
bool QgsPolyhedralSurface::moveVertex( QgsVertexId vId, const QgsPoint &newPos )
{
if ( vId.part < 0 || vId.part >= partCount() )
{
return false;
}
QgsPolygon *patch = mPatches.at( vId.part );
bool success = patch->moveVertex( QgsVertexId( 0, vId.ring, vId.vertex ), newPos );
if ( success )
{
clearCache();
}
return success;
}
bool QgsPolyhedralSurface::deleteVertex( QgsVertexId vId )
{
if ( vId.part < 0 || vId.part >= partCount() )
{
return false;
}
QgsPolygon *patch = mPatches.at( vId.part );
bool success = patch->deleteVertex( QgsVertexId( 0, vId.ring, vId.vertex ) );
if ( success )
{
// if the patch has lost its exterior ring, remove it
if ( !patch->exteriorRing() )
{
delete mPatches.takeAt( vId.part );
}
clearCache();
}
return success;
}
bool QgsPolyhedralSurface::hasCurvedSegments() const
{
return false;
}
QgsAbstractGeometry *QgsPolyhedralSurface::segmentize( double tolerance, SegmentationToleranceType toleranceType ) const
{
// This is only used by curves
Q_UNUSED( tolerance )
Q_UNUSED( toleranceType )
return clone();
}
double QgsPolyhedralSurface::vertexAngle( QgsVertexId vertex ) const
{
if ( vertex.part < 0 || vertex.part >= partCount() )
{
return 0.0;
}
QgsPolygon *patch = mPatches[vertex.part];
return patch->vertexAngle( QgsVertexId( 0, vertex.ring, vertex.vertex ) );
}
int QgsPolyhedralSurface::vertexCount( int part, int ring ) const
{
if ( part < 0 || part >= partCount() )
{
return 0;
}
QgsPolygon *patchPolygon = mPatches[part];
QgsCurve *ringCurve = ring == 0 ? patchPolygon->exteriorRing() : patchPolygon->interiorRing( ring - 1 );
if ( ringCurve )
{
return ringCurve->vertexCount();
}
return 0;
}
int QgsPolyhedralSurface::ringCount( int part ) const
{
if ( part < 0 || part >= partCount() )
return 0;
return mPatches[part]->ringCount();
}
int QgsPolyhedralSurface::partCount() const
{
return mPatches.size();
}
QgsPoint QgsPolyhedralSurface::vertexAt( QgsVertexId id ) const
{
if ( id.part < 0 || id.part >= partCount() )
return QgsPoint();
return mPatches[id.part]->vertexAt( id );
}
double QgsPolyhedralSurface::segmentLength( QgsVertexId startVertex ) const
{
if ( startVertex.part < 0 || startVertex.part >= partCount() )
{
return 0.0;
}
const QgsPolygon *patch = mPatches[startVertex.part];
return patch->segmentLength( QgsVertexId( 0, startVertex.ring, startVertex.vertex ) );
}
bool QgsPolyhedralSurface::addZValue( double zValue )
{
if ( QgsWkbTypes::hasZ( mWkbType ) )
{
return false;
}
mWkbType = QgsWkbTypes::addZ( mWkbType );
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->addZValue( zValue );
}
clearCache();
return true;
}
bool QgsPolyhedralSurface::addMValue( double mValue )
{
if ( QgsWkbTypes::hasM( mWkbType ) )
{
return false;
}
mWkbType = QgsWkbTypes::addM( mWkbType );
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->addMValue( mValue );
}
clearCache();
return true;
}
bool QgsPolyhedralSurface::dropZValue()
{
if ( !is3D() )
{
return false;
}
mWkbType = QgsWkbTypes::dropZ( mWkbType );
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->dropZValue();
}
clearCache();
return true;
}
bool QgsPolyhedralSurface::dropMValue()
{
if ( !isMeasure() )
{
return false;
}
mWkbType = QgsWkbTypes::dropM( mWkbType );
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->dropMValue();
}
clearCache();
return true;
}
void QgsPolyhedralSurface::swapXy()
{
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->swapXy();
}
clearCache();
}
QgsMultiSurface *QgsPolyhedralSurface::toCurveType() const
{
std::unique_ptr<QgsMultiSurface> multiSurface = std::make_unique< QgsMultiSurface >();
multiSurface->reserve( mPatches.size() );
for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
{
multiSurface->addGeometry( polygon->clone() );
}
return multiSurface.release();
}
bool QgsPolyhedralSurface::transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback )
{
if ( !transformer )
return false;
bool res = true;
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
res = patch->transform( transformer );
if ( feedback && feedback->isCanceled() )
res = false;
if ( !res )
break;
}
clearCache();
return res;
}
QgsMultiPolygon *QgsPolyhedralSurface::toMultiPolygon() const
{
std::unique_ptr<QgsMultiPolygon> multiPolygon = std::make_unique< QgsMultiPolygon >();
multiPolygon->reserve( mPatches.size() );
for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
{
multiPolygon->addGeometry( polygon->clone() );
}
return multiPolygon.release();
}
void QgsPolyhedralSurface::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
{
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->filterVertices( filter );
}
clearCache();
}
void QgsPolyhedralSurface::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
{
for ( QgsPolygon *patch : std::as_const( mPatches ) )
{
patch->transformVertices( transform );
}
clearCache();
}
int QgsPolyhedralSurface::childCount() const
{
return mPatches.count();
}
QgsAbstractGeometry *QgsPolyhedralSurface::childGeometry( int index ) const
{
return mPatches.at( index );
}
int QgsPolyhedralSurface::compareToSameClass( const QgsAbstractGeometry *other ) const
{
const QgsPolyhedralSurface *otherPolySurface = qgsgeometry_cast<const QgsPolyhedralSurface *>( other );
if ( !otherPolySurface )
return -1;
const int nPatches1 = mPatches.size();
const int nPatches2 = otherPolySurface->mPatches.size();
if ( nPatches1 < nPatches2 )
{
return -1;
}
if ( nPatches1 > nPatches2 )
{
return 1;
}
for ( int i = 0; i < nPatches1; i++ )
{
const int polygonComp = mPatches.at( i )->compareTo( otherPolySurface->mPatches.at( i ) );
if ( polygonComp != 0 )
{
return polygonComp;
}
}
return 0;
}

View File

@ -0,0 +1,376 @@
/***************************************************************************
qgspolyhedralsurface.h
-------------------
begin : August 2024
copyright : (C) 2024 by Jean Felder
email : jean dot felder 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 QGSPOLYHEDRALSURFACE_H
#define QGSPOLYHEDRALSURFACE_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgssurface.h"
#include "qgspolygon.h"
#include "qgsmultipolygon.h"
/**
* \ingroup core
* \class QgsPolyhedralSurface
* \brief Polyhedral surface geometry type.
*
* A polyhedral surface is a collection of polygons which share common boundary segments.
*
* \since QGIS 3.40
*/
class CORE_EXPORT QgsPolyhedralSurface: public QgsSurface
{
public:
QgsPolyhedralSurface();
QgsPolyhedralSurface( const QgsPolyhedralSurface &p );
/**
* Creates a polyhedral surface from a multiPolygon.
*/
QgsPolyhedralSurface( const QgsMultiPolygon *multiPolygon );
QgsPolyhedralSurface &operator=( const QgsPolyhedralSurface &p );
#ifndef SIP_RUN
private:
bool fuzzyHelper( const QgsAbstractGeometry &other, double epsilon, bool useDistance ) const
{
const QgsPolyhedralSurface *otherPolygon = qgsgeometry_cast< const QgsPolyhedralSurface * >( &other );
if ( !otherPolygon )
return false;
//run cheap checks first
if ( mWkbType != otherPolygon->mWkbType )
return false;
if ( mPatches.count() != otherPolygon->mPatches.count() )
return false;
for ( int i = 0; i < mPatches.count(); ++i )
{
if ( ( !mPatches.at( i ) && otherPolygon->mPatches.at( i ) ) ||
( mPatches.at( i ) && !otherPolygon->mPatches.at( i ) ) )
return false;
if ( useDistance )
{
if ( mPatches.at( i ) && otherPolygon->mPatches.at( i ) &&
!( *mPatches.at( i ) ).fuzzyDistanceEqual( *otherPolygon->mPatches.at( i ), epsilon ) )
return false;
}
else
{
if ( mPatches.at( i ) && otherPolygon->mPatches.at( i ) &&
!( *mPatches.at( i ) ).fuzzyEqual( *otherPolygon->mPatches.at( i ), epsilon ) )
return false;
}
}
return true;
}
#endif
public:
bool fuzzyEqual( const QgsAbstractGeometry &other, double epsilon = 1e-8 ) const override SIP_HOLDGIL
{
return fuzzyHelper( other, epsilon, false );
}
bool fuzzyDistanceEqual( const QgsAbstractGeometry &other, double epsilon = 1e-8 ) const override SIP_HOLDGIL
{
return fuzzyHelper( other, epsilon, true );
}
bool operator==( const QgsAbstractGeometry &other ) const override
{
return fuzzyEqual( other, 1e-8 );
}
bool operator!=( const QgsAbstractGeometry &other ) const override
{
return !operator==( other );
}
~QgsPolyhedralSurface() override;
QString geometryType() const override SIP_HOLDGIL;
int dimension() const final SIP_HOLDGIL;
QgsPolyhedralSurface *clone() const override SIP_FACTORY;
void clear() override;
bool fromWkb( QgsConstWkbPtr &wkb ) override;
bool fromWkt( const QString &wkt ) override;
int wkbSize( QgsAbstractGeometry::WkbFlags flags = QgsAbstractGeometry::WkbFlags() ) const override;
QByteArray asWkb( QgsAbstractGeometry::WkbFlags flags = QgsAbstractGeometry::WkbFlags() ) const override;
QString asWkt( int precision = 17 ) const override;
QDomElement asGml2( QDomDocument &doc, int precision = 17, const QString &ns = "gml", QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) const override;
QDomElement asGml3( QDomDocument &doc, int precision = 17, const QString &ns = "gml", QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) const override;
json asJsonObject( int precision = 17 ) const override SIP_SKIP;
QString asKml( int precision = 17 ) const override;
void normalize() override SIP_HOLDGIL;
//surface interface
double area() const override SIP_HOLDGIL;
double perimeter() const override SIP_HOLDGIL;
QgsAbstractGeometry *boundary() const override SIP_FACTORY;
QgsPolyhedralSurface *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0, bool removeRedundantPoints = false ) const override SIP_FACTORY;
QgsPolyhedralSurface *simplifyByDistance( double tolerance ) const override SIP_FACTORY;
bool removeDuplicateNodes( double epsilon = 4 * std::numeric_limits<double>::epsilon(), bool useZValues = false ) override;
bool boundingBoxIntersects( const QgsBox3D &box3d ) const override SIP_HOLDGIL;
/**
* Returns the number of patches contained with the polyhedral surface.
*
* \see patchN()
*/
int numPatches() const SIP_HOLDGIL
{
return mPatches.size();
}
#ifndef SIP_RUN
/**
* Retrieves a patch from the polyhedral surface. The first patch has index 0.
*
* \see numPatches()
*/
const QgsPolygon *patchN( int i ) const
{
if ( i < 0 || i >= mPatches.size() )
{
return nullptr;
}
return mPatches.at( i );
}
/**
* Retrieves a patch from the polyhedral surface. The first patch has index 0.
*
* \see numPatches()
*/
QgsPolygon *patchN( int i )
{
if ( i < 0 || i >= mPatches.size() )
{
return nullptr;
}
return mPatches.at( i );
}
#else
/**
* Retrieves a patch from the polyhedral surface. The first patch has index 0.
*
* \throws IndexError if no patch with the specified index exists.
*
* \see numPatches()
*/
SIP_PYOBJECT patchN( int i ) SIP_HOLDGIL SIP_TYPEHINT( QgsPolygon );
% MethodCode
if ( a0 < 0 || a0 >= sipCpp->numPatches() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return sipConvertFromType( const_cast< QgsPolygon * >( sipCpp->patchN( a0 ) ), sipType_QgsPolygon, NULL );
}
% End
#endif
/**
* Sets all patches, transferring ownership to the polyhedral surface.
*/
virtual void setPatches( const QVector<QgsPolygon *> &patches SIP_TRANSFER );
/**
* Adds a patch to the geometry, transferring ownership to the polyhedral surface.
*/
virtual void addPatch( QgsPolygon *patch SIP_TRANSFER );
#ifndef SIP_RUN
/**
* Removes a patch from the polyhedral surface. The first patch has index 0.
* The corresponding patch is removed from the polyhedral surface and deleted. If a patch was successfully removed
* the function will return TRUE.
*
*/
bool removePatch( int patchIndex );
#else
/**
* Removes a patch from the polyhedral surface. The first patch has index 0.
* The corresponding patch is removed from the polyhedral surface and deleted.
*
* \throws IndexError if no patch with the specified index exists.
*
*/
bool removePatch( int ringIndex );
% MethodCode
if ( a0 < 0 || a0 >= sipCpp->numPatches() )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
return PyBool_FromLong( sipCpp->removePatch( a0 ) );
}
% End
#endif
QPainterPath asQPainterPath() const override;
void draw( QPainter &p ) const override;
void transform( const QgsCoordinateTransform &ct, Qgis::TransformDirection d = Qgis::TransformDirection::Forward, bool transformZ = false ) override SIP_THROW( QgsCsException );
void transform( const QTransform &t, double zTranslate = 0.0, double zScale = 1.0, double mTranslate = 0.0, double mScale = 1.0 ) override;
bool insertVertex( QgsVertexId position, const QgsPoint &vertex ) override;
bool moveVertex( QgsVertexId position, const QgsPoint &newPos ) override;
bool deleteVertex( QgsVertexId position ) override;
QgsCoordinateSequence coordinateSequence() const override;
int nCoordinates() const override;
int vertexNumberFromVertexId( QgsVertexId id ) const override;
bool isEmpty() const override SIP_HOLDGIL;
double closestSegment( const QgsPoint &pt, QgsPoint &segmentPt SIP_OUT, QgsVertexId &vertexAfter SIP_OUT, int *leftOf SIP_OUT = nullptr, double epsilon = 4 * std::numeric_limits<double>::epsilon() ) const override;
bool nextVertex( QgsVertexId &id, QgsPoint &vertex SIP_OUT ) const override;
void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex SIP_OUT, QgsVertexId &nextVertex SIP_OUT ) const override;
bool hasCurvedSegments() const final;
/**
* Returns a geometry without curves. Caller takes ownership
* \param tolerance segmentation tolerance
* \param toleranceType maximum segmentation angle or maximum difference between approximation and curve
*/
QgsAbstractGeometry *segmentize( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const override SIP_FACTORY;
/**
* Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
* \param vertex the vertex id
* \returns rotation in radians, clockwise from north
*/
double vertexAngle( QgsVertexId vertex ) const override;
int vertexCount( int part = 0, int ring = 0 ) const override;
int ringCount( int part = 0 ) const override SIP_HOLDGIL;
int partCount() const override SIP_HOLDGIL;
QgsPoint vertexAt( QgsVertexId id ) const override;
double segmentLength( QgsVertexId startVertex ) const override;
bool addZValue( double zValue = 0 ) override;
bool addMValue( double mValue = 0 ) override;
bool dropZValue() override;
bool dropMValue() override;
void swapXy() override;
QgsMultiSurface *toCurveType() const override SIP_FACTORY;
bool transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback = nullptr ) override;
/**
* Converts a polyhedral surface to a multipolygon.
* Caller takes ownership.
*/
QgsMultiPolygon *toMultiPolygon() const SIP_FACTORY;
#ifndef SIP_RUN
void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override;
void transformVertices( const std::function< QgsPoint( const QgsPoint & ) > &transform ) override;
/**
* Cast the \a geom to a QgsPolyhedralSurface.
* Should be used by qgsgeometry_cast<QgsPolyhedralSurface *>( geometry ).
*
* \note Not available in Python. Objects will be automatically be converted to the appropriate target type.
*/
inline static const QgsPolyhedralSurface *cast( const QgsAbstractGeometry *geom )
{
if ( geom && QgsWkbTypes::flatType( geom->wkbType() ) == Qgis::WkbType::PolyhedralSurface )
return static_cast<const QgsPolyhedralSurface *>( geom );
return nullptr;
}
#endif
QgsPolyhedralSurface *createEmptyWithSameType() const override SIP_FACTORY;
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
QString wkt = sipCpp->asWkt();
if ( wkt.length() > 1000 )
wkt = wkt.left( 1000 ) + QStringLiteral( "..." );
QString str = QStringLiteral( "<QgsPolyhedralSurface: %1>" ).arg( wkt );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
/**
* Returns the number of patches within the polyhedral surface.
*/
int __len__() const;
% MethodCode
sipRes = sipCpp->numPatches();
% End
/**
* Returns the geometry at the specified ``index``.
*
* Indexes can be less than 0, in which case they correspond to geometries from the end of the collect. E.g. an index of -1
* corresponds to the last geometry in the collection.
*
* \throws IndexError if no geometry with the specified ``index`` exists.
*/
SIP_PYOBJECT __getitem__( int index ) SIP_TYPEHINT( QgsPolygon );
% MethodCode
const int count = sipCpp->numPatches();
if ( a0 < -count || a0 >= count )
{
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else if ( a0 >= 0 )
{
return sipConvertFromType( sipCpp->patchN( a0 ), sipType_QgsPolygon, NULL );
}
else
{
return sipConvertFromType( sipCpp->patchN( count + a0 ), sipType_QgsPolygon, NULL );
}
% End
#endif
protected:
int childCount() const override;
QgsAbstractGeometry *childGeometry( int index ) const override;
int compareToSameClass( const QgsAbstractGeometry *other ) const override;
QgsBox3D calculateBoundingBox3D() const override;
private:
QVector< QgsPolygon * > mPatches;
};
// clazy:excludeall=qstring-allocations
#endif // QGSPOLYHEDRALSURFACE_H

View File

@ -28,6 +28,7 @@ set(TESTS
testqgspoint.cpp
testqgspointxy.cpp
testqgspolygon.cpp
testqgspolyhedralsurface.cpp
testqgsquadrilateral.cpp
testqgsrectangle.cpp
testqgsregularpolygon.cpp

File diff suppressed because it is too large Load Diff

View File

@ -233,6 +233,7 @@ ADD_PYTHON_TEST(PyQgsPointCloudRgbRenderer test_qgspointcloudrgbrenderer.py)
ADD_PYTHON_TEST(PyQgsPointClusterRenderer test_qgspointclusterrenderer.py)
ADD_PYTHON_TEST(PyQgsPointDisplacementRenderer test_qgspointdisplacementrenderer.py)
ADD_PYTHON_TEST(PyQgsPolygon test_qgspolygon.py)
ADD_PYTHON_TEST(PyQgsPolyhedralSurface test_qgspolyhedralsurface.py)
ADD_PYTHON_TEST(PyQgsProcessExecutablePt1 test_qgsprocessexecutable_pt1.py)
ADD_PYTHON_TEST(PyQgsProcessExecutablePt2 test_qgsprocessexecutable_pt2.py)
ADD_PYTHON_TEST(PyQgsProcessingInPlace test_qgsprocessinginplace.py)

View File

@ -0,0 +1,194 @@
"""QGIS Unit tests for QgsPolyhedralSurface
.. note:: 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.
"""
__author__ = 'Jean Felder'
__date__ = '12/08/2024'
__copyright__ = 'Copyright 2024, The QGIS Project'
import qgis # NOQA
from qgis.core import (
QgsLineString, QgsPoint, QgsPolygon, QgsPolyhedralSurface, QgsWkbTypes)
import unittest
from qgis.testing import start_app, QgisTestCase
start_app()
class TestQgsPolyhedralSurface(QgisTestCase):
def test_constructor(self):
surface = QgsPolyhedralSurface()
self.assertTrue(surface.isEmpty())
self.assertEqual(surface.numPatches(), 0)
self.assertFalse(surface.is3D())
self.assertFalse(surface.isMeasure())
self.assertEqual(surface.wkbType(), QgsWkbTypes.Type.PolyhedralSurface)
self.assertEqual(surface.wktTypeStr(), "PolyhedralSurface")
self.assertEqual(surface.geometryType(), "PolyhedralSurface")
self.assertEqual(surface.dimension(), 2)
def test_wkt(self):
# 2D
surface = QgsPolyhedralSurface()
surface.fromWkt('POLYHEDRALSURFACE (((0 0,0 1,1 1,1 0,0 0)))')
self.assertFalse(surface.isEmpty())
self.assertEqual(surface.numPatches(), 1)
self.assertFalse(surface.is3D())
self.assertFalse(surface.isMeasure())
self.assertEqual(surface.wkbType(), QgsWkbTypes.Type.PolyhedralSurface)
surface2 = QgsPolyhedralSurface()
surface2.fromWkt(surface.asWkt())
self.assertEqual(surface, surface2)
# 3D
surfaceZ = QgsPolyhedralSurface()
surfaceZ.fromWkt('POLYHEDRALSURFACE Z (((0 0 0,0 1 0,1 1 0,0 0 0)))')
self.assertFalse(surfaceZ.isEmpty())
self.assertEqual(surfaceZ.numPatches(), 1)
self.assertTrue(surfaceZ.is3D())
self.assertFalse(surfaceZ.isMeasure())
self.assertEqual(surfaceZ.wkbType(), QgsWkbTypes.Type.PolyhedralSurfaceZ)
surfaceZ2 = QgsPolyhedralSurface()
surfaceZ2.fromWkt(surfaceZ.asWkt())
self.assertEqual(surfaceZ, surfaceZ2)
# Measure
surfaceM = QgsPolyhedralSurface()
surfaceM.fromWkt('POLYHEDRALSURFACE M (((0 0 3,0 1 3,1 1 3,0 0 3)))')
self.assertFalse(surfaceM.isEmpty())
self.assertEqual(surfaceM.numPatches(), 1)
self.assertFalse(surfaceM.is3D())
self.assertTrue(surfaceM.isMeasure())
self.assertEqual(surfaceM.wkbType(), QgsWkbTypes.Type.PolyhedralSurfaceM)
surfaceM2 = QgsPolyhedralSurface()
surfaceM2.fromWkt(surfaceM.asWkt())
self.assertEqual(surfaceM, surfaceM2)
# ZM
surfaceZM = QgsPolyhedralSurface()
surfaceZM.fromWkt('POLYHEDRALSURFACE ZM '
'(((0 0 1 2,0 1 1 2,1 1 1 2,0 0 1 2)),'
'((10 10 0 0,10 11 0 0,11 11 0 0,10 10 0 0)))')
self.assertFalse(surfaceZM.isEmpty())
self.assertEqual(surfaceZM.numPatches(), 2)
self.assertTrue(surfaceZM.is3D())
self.assertTrue(surfaceZM.isMeasure())
self.assertEqual(surfaceZM.wkbType(), QgsWkbTypes.Type.PolyhedralSurfaceZM)
surfaceZM2 = QgsPolyhedralSurface()
surfaceZM2.fromWkt(surfaceZM.asWkt())
self.assertEqual(surfaceZM, surfaceZM2)
def test_patch(self):
surface = QgsPolyhedralSurface()
self.assertTrue(surface.isEmpty())
self.assertEqual(surface.numPatches(), 0)
self.assertFalse(surface.is3D())
self.assertFalse(surface.isMeasure())
patch1 = QgsPolygon()
patchExterior1 = QgsLineString(
[QgsPoint(0, 0), QgsPoint(0, 10), QgsPoint(10, 10), QgsPoint(10, 0),
QgsPoint(0, 0)])
patch1.setExteriorRing(patchExterior1)
patchInteriorRing = QgsLineString(
[QgsPoint(1, 1), QgsPoint(1, 9), QgsPoint(9, 9), QgsPoint(9, 1),
QgsPoint(1, 1)])
patch1.addInteriorRing(patchInteriorRing)
surface.addPatch(patch1)
self.assertEqual(surface.numPatches(), 1)
self.assertFalse(surface.is3D())
self.assertFalse(surface.isMeasure())
patch2 = QgsPolygon()
patchExterior2 = QgsLineString(
[QgsPoint(10, 0), QgsPoint(10, 10), QgsPoint(20, 10), QgsPoint(20, 0),
QgsPoint(10, 0)])
patch2.setExteriorRing(patchExterior2)
surface.addPatch(patch2)
self.assertEqual(surface.numPatches(), 2)
self.assertFalse(surface.is3D())
self.assertFalse(surface.isMeasure())
surface.clear()
self.assertEqual(surface.numPatches(), 0)
self.assertFalse(surface.is3D())
self.assertFalse(surface.isMeasure())
def test_len(self):
surface1 = QgsPolyhedralSurface()
self.assertEqual(surface1.numPatches(), 0)
self.assertEqual(len(surface1), 0)
patch1 = QgsPolygon()
patchExterior1 = QgsLineString(
[QgsPoint(0, 0), QgsPoint(0, 10), QgsPoint(10, 10), QgsPoint(10, 0),
QgsPoint(0, 0)])
patch1.setExteriorRing(patchExterior1)
patchInteriorRing = QgsLineString(
[QgsPoint(1, 1), QgsPoint(1, 9), QgsPoint(9, 9), QgsPoint(9, 1),
QgsPoint(1, 1)])
patch1.addInteriorRing(patchInteriorRing)
surface1.addPatch(patch1)
self.assertEqual(surface1.numPatches(), 1)
self.assertEqual(len(surface1), 1)
surface2 = QgsPolyhedralSurface()
surface2.fromWkt('POLYHEDRALSURFACE ZM '
'(((0 0 1 2,0 1 1 2,1 1 1 2,0 0 1 2)),'
'((10 10 0 0,10 11 0 0,11 11 0 0,10 10 0 0)))')
self.assertTrue(surface2.numPatches(), 2)
self.assertTrue(len(surface2), 2)
def test_getitem(self):
surface = QgsPolyhedralSurface()
with self.assertRaises(IndexError):
surface[0]
with self.assertRaises(IndexError):
surface[-1]
patch1 = QgsPolygon()
patchExterior1 = QgsLineString(
[QgsPoint(0, 0), QgsPoint(0, 10), QgsPoint(10, 10), QgsPoint(10, 0),
QgsPoint(0, 0)])
patch1.setExteriorRing(patchExterior1)
patchInteriorRing = QgsLineString(
[QgsPoint(1, 1), QgsPoint(1, 9), QgsPoint(9, 9), QgsPoint(9, 1),
QgsPoint(1, 1)])
patch1.addInteriorRing(patchInteriorRing)
surface.addPatch(patch1)
self.assertEqual(surface.numPatches(), 1)
self.assertEqual(surface[0], patch1)
self.assertEqual(surface[-1], patch1)
with self.assertRaises(IndexError):
surface[1]
with self.assertRaises(IndexError):
surface[-2]
patch2 = QgsPolygon()
patchExterior2 = QgsLineString(
[QgsPoint(10, 0), QgsPoint(10, 10), QgsPoint(20, 10), QgsPoint(20, 0),
QgsPoint(10, 0)])
patch2.setExteriorRing(patchExterior2)
surface.addPatch(patch2)
self.assertEqual(surface.numPatches(), 2)
self.assertEqual(surface[0], patch1)
self.assertEqual(surface[1], patch2)
self.assertEqual(surface[-1], patch2)
self.assertEqual(surface[-2], patch1)
with self.assertRaises(IndexError):
surface[2]
with self.assertRaises(IndexError):
surface[-3]
if __name__ == '__main__':
unittest.main()