Skip to content

Commit e12621c

Browse files
committed
Add API method to remove duplicate nodes from geometries
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a degenerate geometry. By default, z values are not considered when detecting duplicate nodes. E.g. two nodes with the same x and y coordinate but different z values will still be considered duplicate and one will be removed. If useZValues is true, then the z values are also tested and nodes with the same x and y but different z will be maintained. Note that duplicate nodes are not tested between different parts of a multipart geometry. E.g. a multipoint geometry with overlapping points will not be changed by this method. The function will return true if nodes were removed, or false if no duplicate nodes were found. Includes unit tests and a processing algorithm which exposes this functionality.
1 parent 0b452ca commit e12621c

34 files changed

Lines changed: 720 additions & 2 deletions

python/core/geometry/qgsabstractgeometry.sip

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,29 @@ Returns the centroid of the geometry
436436
:rtype: QgsAbstractGeometry
437437
%End
438438

439+
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) = 0;
440+
%Docstring
441+
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a
442+
degenerate geometry.
443+
444+
The ``epsilon`` parameter specifies the tolerance for coordinates when determining that
445+
vertices are identical.
446+
447+
By default, z values are not considered when detecting duplicate nodes. E.g. two nodes
448+
with the same x and y coordinate but different z values will still be considered
449+
duplicate and one will be removed. If ``useZValues`` is true, then the z values are
450+
also tested and nodes with the same x and y but different z will be maintained.
451+
452+
Note that duplicate nodes are not tested between different parts of a multipart geometry. E.g.
453+
a multipoint geometry with overlapping points will not be changed by this method.
454+
455+
The function will return true if nodes were removed, or false if no duplicate nodes
456+
were found.
457+
458+
.. versionadded:: 3.0
459+
:rtype: bool
460+
%End
461+
439462
virtual double vertexAngle( QgsVertexId vertex ) const = 0;
440463
%Docstring
441464
Returns approximate angle at a vertex. This is usually the average angle between adjacent

python/core/geometry/qgscircularstring.sip

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ class QgsCircularString: QgsCurve
8383

8484
virtual QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
8585

86+
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
87+
88+
8689
virtual void draw( QPainter &p ) const;
8790

8891
virtual void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,

python/core/geometry/qgscompoundcurve.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ class QgsCompoundCurve: QgsCurve
7979

8080
virtual QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
8181

82+
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
83+
8284

8385
int nCurves() const;
8486
%Docstring

python/core/geometry/qgscurvepolygon.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class QgsCurvePolygon: QgsSurface
6767

6868
virtual QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
6969

70+
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
71+
7072

7173
int numInteriorRings() const;
7274
%Docstring

python/core/geometry/qgsgeometry.sip

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,29 @@ Returns true if WKB of the geometry is of WKBMulti* type
688688
:rtype: QgsGeometry
689689
%End
690690

691+
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
692+
%Docstring
693+
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a
694+
degenerate geometry.
695+
696+
The ``epsilon`` parameter specifies the tolerance for coordinates when determining that
697+
vertices are identical.
698+
699+
By default, z values are not considered when detecting duplicate nodes. E.g. two nodes
700+
with the same x and y coordinate but different z values will still be considered
701+
duplicate and one will be removed. If ``useZValues`` is true, then the z values are
702+
also tested and nodes with the same x and y but different z will be maintained.
703+
704+
Note that duplicate nodes are not tested between different parts of a multipart geometry. E.g.
705+
a multipoint geometry with overlapping points will not be changed by this method.
706+
707+
The function will return true if nodes were removed, or false if no duplicate nodes
708+
were found.
709+
710+
.. versionadded:: 3.0
711+
:rtype: bool
712+
%End
713+
691714
bool intersects( const QgsRectangle &r ) const;
692715
%Docstring
693716
Tests for intersection with a rectangle (uses GEOS)

python/core/geometry/qgsgeometrycollection.sip

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ class QgsGeometryCollection: QgsAbstractGeometry
5252
virtual void clear();
5353

5454
virtual QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
55+
56+
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
57+
5558
virtual QgsAbstractGeometry *boundary() const /Factory/;
5659

5760
virtual void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex /Out/, QgsVertexId &nextVertex /Out/ ) const;

python/core/geometry/qgslinestring.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ Closes the line string by appending the first point to the end of the line, if i
179179

180180
virtual QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
181181

182+
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
183+
182184

183185
virtual bool fromWkb( QgsConstWkbPtr &wkb );
184186

python/core/geometry/qgspoint.sip

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@ class QgsPoint: QgsAbstractGeometry
339339
virtual QgsPoint *clone() const /Factory/;
340340

341341
virtual QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
342+
343+
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
344+
342345
virtual void clear();
343346

344347
virtual bool fromWkb( QgsConstWkbPtr &wkb );
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ line_duplicate_nodes.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>2</gml:X><gml:Y>0</gml:Y></gml:coord>
10+
<gml:coord><gml:X>3</gml:X><gml:Y>3</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:line_duplicate_nodes fid="lines.2">
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 3,2 3,3 3,3</gml:coordinates></gml:LineString></ogr:geometryProperty>
17+
</ogr:line_duplicate_nodes>
18+
</gml:featureMember>
19+
</ogr:FeatureCollection>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="line_duplicate_nodes" type="ogr:line_duplicate_nodes_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="line_duplicate_nodes_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:LineStringPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
</xs:sequence>
20+
</xs:extension>
21+
</xs:complexContent>
22+
</xs:complexType>
23+
</xs:schema>

0 commit comments

Comments
 (0)