Skip to content

Commit 349f6ea

Browse files
authored
Merge pull request #4732 from nyalldawson/alg
[FEATURE] Subdivide algorithm for geometries
2 parents b3da171 + fbd1d00 commit 349f6ea

14 files changed

+518
-42
lines changed

python/core/geometry/qgsgeometry.sip

+27-1
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,23 @@ Returns the smallest convex polygon that contains all the points in the geometry
782782
If ``edgesOnly`` is true than line string boundary geometries will be returned
783783
instead of polygons.
784784
An empty geometry will be returned if the diagram could not be calculated.
785+
.. versionadded:: 3.0
786+
:rtype: QgsGeometry
787+
%End
788+
789+
QgsGeometry subdivide( int maxNodes = 256 ) const;
790+
%Docstring
791+
Subdivides the geometry. The returned geometry will be a collection containing subdivided parts
792+
from the original geometry, where no part has more then the specified maximum number of nodes (``maxNodes``).
793+
794+
This is useful for dividing a complex geometry into less complex parts, which are better able to be spatially
795+
indexed and faster to perform further operations such as intersects on. The returned geometry parts may
796+
not be valid and may contain self-intersections.
797+
798+
The minimum allowed value for ``maxNodes`` is 8.
799+
800+
Curved geometries will be segmentized before subdivision.
801+
785802
.. versionadded:: 3.0
786803
:rtype: QgsGeometry
787804
%End
@@ -828,6 +845,16 @@ Returns a geometry representing the points shared by this geometry and other.
828845
:rtype: QgsGeometry
829846
%End
830847

848+
QgsGeometry clipped( const QgsRectangle &rectangle );
849+
%Docstring
850+
Clips the geometry using the specified ``rectangle``.
851+
852+
Performs a fast, non-robust intersection between the geometry and
853+
a ``rectangle``. The returned geometry may be invalid.
854+
.. versionadded:: 3.0
855+
:rtype: QgsGeometry
856+
%End
857+
831858
QgsGeometry combine( const QgsGeometry &geometry ) const;
832859
%Docstring
833860
Returns a geometry representing all the points in this geometry and other (a
@@ -1129,7 +1156,6 @@ Ring 0 is outer ring and can't be deleted.
11291156
.. versionadded:: 2.10
11301157
%End
11311158

1132-
11331159
void draw( QPainter &p ) const;
11341160
%Docstring
11351161
Draws the geometry onto a QPainter

python/plugins/processing/algs/qgis/MultipartToSingleparts.py

+24-13
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@
2929

3030
from qgis.PyQt.QtGui import QIcon
3131

32-
from qgis.core import QgsWkbTypes, QgsProcessingUtils
32+
from qgis.core import (QgsWkbTypes,
33+
QgsProcessingUtils,
34+
QgsProcessingParameterFeatureSource,
35+
QgsProcessingParameterFeatureSink,
36+
QgsProcessingOutputVectorLayer)
3337

3438
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
3539
from processing.core.parameters import ParameterVector
@@ -51,8 +55,11 @@ def group(self):
5155

5256
def __init__(self):
5357
super().__init__()
54-
self.addParameter(ParameterVector(self.INPUT, self.tr('Input layer')))
55-
self.addOutput(OutputVector(self.OUTPUT, self.tr('Single parts')))
58+
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
59+
self.tr('Input layer')))
60+
61+
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Single parts')))
62+
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Single parts')))
5663

5764
def name(self):
5865
return 'multiparttosingleparts'
@@ -61,28 +68,32 @@ def displayName(self):
6168
return self.tr('Multipart to singleparts')
6269

6370
def processAlgorithm(self, parameters, context, feedback):
64-
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context)
65-
geomType = QgsWkbTypes.singleType(layer.wkbType())
71+
source = self.parameterAsSource(parameters, self.INPUT, context)
72+
geomType = QgsWkbTypes.singleType(source.wkbType())
73+
74+
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
75+
source.fields(), geomType, source.sourceCrs())
6676

67-
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), geomType, layer.crs(),
68-
context)
77+
features = source.getFeatures()
78+
total = 100.0 / source.featureCount()
6979

70-
features = QgsProcessingUtils.getFeatures(layer, context)
71-
total = 100.0 / QgsProcessingUtils.featureCount(layer, context)
7280
for current, f in enumerate(features):
81+
if feedback.isCanceled():
82+
break
83+
7384
input_geometry = f.geometry()
7485
if input_geometry:
7586
if input_geometry.isMultipart():
7687
for g in input_geometry.asGeometryCollection():
7788
output_feature = f
7889
output_feature.setGeometry(g)
79-
writer.addFeature(output_feature)
90+
sink.addFeature(output_feature)
8091
else:
81-
writer.addFeature(f)
92+
sink.addFeature(f)
8293
else:
8394
#input feature with null geometry
84-
writer.addFeature(f)
95+
sink.addFeature(f)
8596

8697
feedback.setProgress(int(current * total))
8798

88-
del writer
99+
return {self.OUTPUT: dest_id}

python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
# from .Delaunay import Delaunay
6767
# from .VoronoiPolygons import VoronoiPolygons
6868
# from .DensifyGeometries import DensifyGeometries
69-
# from .MultipartToSingleparts import MultipartToSingleparts
69+
from .MultipartToSingleparts import MultipartToSingleparts
7070
# from .SimplifyGeometries import SimplifyGeometries
7171
# from .LinesToPolygons import LinesToPolygons
7272
# from .PolygonsToLines import PolygonsToLines
@@ -206,7 +206,7 @@ def getAlgs(self):
206206
# ReprojectLayer(), ExportGeometryInfo(), Centroids(),
207207
# Delaunay(), VoronoiPolygons(), SimplifyGeometries(),
208208
# DensifyGeometries(), DensifyGeometriesInterval(),
209-
# MultipartToSingleparts(), SinglePartsToMultiparts(),
209+
# , SinglePartsToMultiparts(),
210210
# PolygonsToLines(), LinesToPolygons(), ExtractNodes(),
211211
# ConvexHull(), FixedDistanceBuffer(),
212212
# VariableDistanceBuffer(), Dissolve(), Difference(),
@@ -271,7 +271,8 @@ def getAlgs(self):
271271
ExtentFromLayer(),
272272
ExtractByExpression(),
273273
GridPolygon(),
274-
Merge()
274+
Merge(),
275+
MultipartToSingleparts()
275276
]
276277

277278
if hasPlotly:

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

+11-11
Original file line numberDiff line numberDiff line change
@@ -389,17 +389,17 @@ tests:
389389
# name: expected/merge_lines.gml
390390
# type: vector
391391
#
392-
# - algorithm: qgis:multiparttosingleparts
393-
# name: Multiparts to singleparts
394-
# params:
395-
# INPUT:
396-
# name: multilines.gml
397-
# type: vector
398-
# results:
399-
# OUTPUT:
400-
# name: expected/multi_to_single.gml
401-
# type: vector
402-
#
392+
- algorithm: qgis:multiparttosingleparts
393+
name: Multiparts to singleparts
394+
params:
395+
INPUT:
396+
name: multilines.gml
397+
type: vector
398+
results:
399+
OUTPUT:
400+
name: expected/multi_to_single.gml
401+
type: vector
402+
403403
- algorithm: qgis:boundingboxes
404404
name: Bounding boxes for lines
405405
params:

src/core/geometry/qgsgeometry.cpp

+31-7
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,30 @@ QgsGeometry QgsGeometry::delaunayTriangulation( double tolerance, bool edgesOnly
16551655
return geos.delaunayTriangulation( tolerance, edgesOnly );
16561656
}
16571657

1658+
QgsGeometry QgsGeometry::subdivide( int maxNodes ) const
1659+
{
1660+
if ( !d->geometry )
1661+
{
1662+
return QgsGeometry();
1663+
}
1664+
1665+
const QgsAbstractGeometry *geom = d->geometry;
1666+
std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
1667+
if ( QgsWkbTypes::isCurvedType( d->geometry->wkbType() ) )
1668+
{
1669+
segmentizedCopy.reset( d->geometry->segmentize() );
1670+
geom = segmentizedCopy.get();
1671+
}
1672+
1673+
QgsGeos geos( geom );
1674+
QgsAbstractGeometry *result = geos.subdivide( maxNodes );
1675+
if ( !result )
1676+
{
1677+
return QgsGeometry();
1678+
}
1679+
return QgsGeometry( result );
1680+
}
1681+
16581682
QgsGeometry QgsGeometry::interpolate( double distance ) const
16591683
{
16601684
if ( !d->geometry )
@@ -2089,17 +2113,17 @@ void QgsGeometry::mapToPixel( const QgsMapToPixel &mtp )
20892113
}
20902114
}
20912115

2092-
#if 0
2093-
void QgsGeometry::clip( const QgsRectangle &rect )
2116+
QgsGeometry QgsGeometry::clipped( const QgsRectangle &rectangle )
20942117
{
2095-
if ( d->geometry )
2118+
if ( !d->geometry || rectangle.isNull() || rectangle.isEmpty() )
20962119
{
2097-
detach();
2098-
d->geometry->clip( rect );
2099-
removeWkbGeos();
2120+
return QgsGeometry();
21002121
}
2122+
2123+
QgsGeos geos( d->geometry );
2124+
QgsAbstractGeometry *resultGeom = geos.clip( rectangle );
2125+
return QgsGeometry( resultGeom );
21012126
}
2102-
#endif
21032127

21042128
void QgsGeometry::draw( QPainter &p ) const
21052129
{

src/core/geometry/qgsgeometry.h

+25-7
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,22 @@ class CORE_EXPORT QgsGeometry
733733
*/
734734
QgsGeometry delaunayTriangulation( double tolerance = 0.0, bool edgesOnly = false ) const;
735735

736+
/**
737+
* Subdivides the geometry. The returned geometry will be a collection containing subdivided parts
738+
* from the original geometry, where no part has more then the specified maximum number of nodes (\a maxNodes).
739+
*
740+
* This is useful for dividing a complex geometry into less complex parts, which are better able to be spatially
741+
* indexed and faster to perform further operations such as intersects on. The returned geometry parts may
742+
* not be valid and may contain self-intersections.
743+
*
744+
* The minimum allowed value for \a maxNodes is 8.
745+
*
746+
* Curved geometries will be segmentized before subdivision.
747+
*
748+
* \since QGIS 3.0
749+
*/
750+
QgsGeometry subdivide( int maxNodes = 256 ) const;
751+
736752
/**
737753
* Return interpolated point on line at distance
738754
* \since QGIS 1.9
@@ -765,6 +781,15 @@ class CORE_EXPORT QgsGeometry
765781
//! Returns a geometry representing the points shared by this geometry and other.
766782
QgsGeometry intersection( const QgsGeometry &geometry ) const;
767783

784+
/**
785+
* Clips the geometry using the specified \a rectangle.
786+
*
787+
* Performs a fast, non-robust intersection between the geometry and
788+
* a \a rectangle. The returned geometry may be invalid.
789+
* \since QGIS 3.0
790+
*/
791+
QgsGeometry clipped( const QgsRectangle &rectangle );
792+
768793
/** Returns a geometry representing all the points in this geometry and other (a
769794
* union geometry operation).
770795
* \note this operation is not called union since its a reserved word in C++.
@@ -999,13 +1024,6 @@ class CORE_EXPORT QgsGeometry
9991024
*/
10001025
void mapToPixel( const QgsMapToPixel &mtp );
10011026

1002-
// not implemented for 2.10
1003-
/* Clips the geometry using the specified rectangle
1004-
* \param rect clip rectangle
1005-
* \since QGIS 2.10
1006-
*/
1007-
// void clip( const QgsRectangle& rect );
1008-
10091027
/** Draws the geometry onto a QPainter
10101028
* \param p destination QPainter
10111029
* \since QGIS 2.10

src/core/geometry/qgsgeometryfactory.cpp

+36
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,39 @@ QgsAbstractGeometry *QgsGeometryFactory::geomFromWkbType( QgsWkbTypes::Type t )
264264
return nullptr;
265265
}
266266
}
267+
268+
std::unique_ptr<QgsGeometryCollection> QgsGeometryFactory::createCollectionOfType( QgsWkbTypes::Type t )
269+
{
270+
QgsWkbTypes::Type type = QgsWkbTypes::flatType( QgsWkbTypes::multiType( t ) );
271+
std::unique_ptr< QgsGeometryCollection > collect;
272+
switch ( type )
273+
{
274+
case QgsWkbTypes::MultiPoint:
275+
collect.reset( new QgsMultiPointV2() );
276+
break;
277+
case QgsWkbTypes::MultiLineString:
278+
collect.reset( new QgsMultiLineString() );
279+
break;
280+
case QgsWkbTypes::MultiCurve:
281+
collect.reset( new QgsMultiCurve() );
282+
break;
283+
case QgsWkbTypes::MultiPolygon:
284+
collect.reset( new QgsMultiPolygonV2() );
285+
break;
286+
case QgsWkbTypes::MultiSurface:
287+
collect.reset( new QgsMultiSurface() );
288+
break;
289+
case QgsWkbTypes::GeometryCollection:
290+
collect.reset( new QgsGeometryCollection() );
291+
break;
292+
default:
293+
// should not be possible
294+
return nullptr;
295+
}
296+
if ( QgsWkbTypes::hasM( t ) )
297+
collect->addMValue();
298+
if ( QgsWkbTypes::hasZ( t ) )
299+
collect->addZValue();
300+
301+
return collect;
302+
}

src/core/geometry/qgsgeometryfactory.h

+8
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020

2121
#include "qgis_core.h"
2222
#include <QString>
23+
#include <memory>
2324

2425
class QgsAbstractGeometry;
2526
class QgsLineString;
2627
class QgsConstWkbPtr;
2728
class QgsRectangle;
29+
class QgsGeometryCollection;
2830

2931
//compatibility with old classes
3032
#include "qgspointxy.h"
@@ -70,6 +72,12 @@ class CORE_EXPORT QgsGeometryFactory
7072
//! Return empty geometry from wkb type
7173
static QgsAbstractGeometry *geomFromWkbType( QgsWkbTypes::Type t );
7274

75+
/**
76+
* Returns a new geometry collection matching a specified WKB \a type. For instance, if
77+
* type is PolygonM the returned geometry will be a QgsMultiPolygonV2 with M values.
78+
*/
79+
static std::unique_ptr< QgsGeometryCollection > createCollectionOfType( QgsWkbTypes::Type type );
80+
7381
private:
7482
static QgsLineString *linestringFromPolyline( const QgsPolyline &polyline );
7583
};

0 commit comments

Comments
 (0)