mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-22 00:06:12 -05:00
geometry: Add polyhedralsurface support
This commit is contained in:
parent
3295d7e803
commit
371eca55e8
@ -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)
|
||||
|
5
python/PyQt6/core/auto_additions/qgspolyhedralsurface.py
Normal file
5
python/PyQt6/core/auto_additions/qgspolyhedralsurface.py
Normal file
@ -0,0 +1,5 @@
|
||||
# The following has been generated automatically from src/core/geometry/qgspolyhedralsurface.h
|
||||
try:
|
||||
QgsPolyhedralSurface.__group__ = ['geometry']
|
||||
except NameError:
|
||||
pass
|
@ -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 *
|
||||
************************************************************************/
|
@ -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
|
||||
|
5
python/core/auto_additions/qgspolyhedralsurface.py
Normal file
5
python/core/auto_additions/qgspolyhedralsurface.py
Normal file
@ -0,0 +1,5 @@
|
||||
# The following has been generated automatically from src/core/geometry/qgspolyhedralsurface.h
|
||||
try:
|
||||
QgsPolyhedralSurface.__group__ = ['geometry']
|
||||
except NameError:
|
||||
pass
|
302
python/core/auto_generated/geometry/qgspolyhedralsurface.sip.in
Normal file
302
python/core/auto_generated/geometry/qgspolyhedralsurface.sip.in
Normal 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 *
|
||||
************************************************************************/
|
@ -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
|
||||
|
@ -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
|
||||
|
996
src/core/geometry/qgspolyhedralsurface.cpp
Normal file
996
src/core/geometry/qgspolyhedralsurface.cpp
Normal 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;
|
||||
}
|
376
src/core/geometry/qgspolyhedralsurface.h
Normal file
376
src/core/geometry/qgspolyhedralsurface.h
Normal 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
|
@ -28,6 +28,7 @@ set(TESTS
|
||||
testqgspoint.cpp
|
||||
testqgspointxy.cpp
|
||||
testqgspolygon.cpp
|
||||
testqgspolyhedralsurface.cpp
|
||||
testqgsquadrilateral.cpp
|
||||
testqgsrectangle.cpp
|
||||
testqgsregularpolygon.cpp
|
||||
|
1696
tests/src/core/geometry/testqgspolyhedralsurface.cpp
Normal file
1696
tests/src/core/geometry/testqgspolyhedralsurface.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
|
194
tests/src/python/test_qgspolyhedralsurface.py
Normal file
194
tests/src/python/test_qgspolyhedralsurface.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user