Skip to content

Commit 1c11a91

Browse files
committedApr 28, 2021
Add QgsGeometry::normalize()
A port of the equivalent method from GEOS, but with added support for curved geometries and M values Reorganizes the geometry into a normalized form (or "canonical" form). Polygon rings will be rearranged so that their starting vertex is the lower left and ring orientation follows the right hand rule, collections are ordered by geometry type, and other normalization techniques are applied. The resultant geometry will be geometrically equivalent to the original geometry.
1 parent e3a73a1 commit 1c11a91

24 files changed

+194
-32
lines changed
 

‎python/core/auto_generated/geometry/qgsabstractgeometry.sip.in

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,17 @@ For instance, a polygon geometry will have a boundary consisting of the linestri
179179
.. versionadded:: 3.0
180180
%End
181181

182+
virtual void normalize() = 0;
183+
%Docstring
184+
Reorganizes the geometry into a normalized form (or "canonical" form).
185+
186+
Polygon rings will be rearranged so that their starting vertex is the lower left and ring orientation follows the
187+
right hand rule, collections are ordered by geometry type, and other normalization techniques are applied. The
188+
resultant geometry will be geometrically equivalent to the original geometry.
189+
190+
.. versionadded:: 3.20
191+
%End
192+
182193

183194
virtual bool fromWkb( QgsConstWkbPtr &wkb ) = 0;
184195
%Docstring

‎python/core/auto_generated/geometry/qgscircularstring.sip.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ Appends the contents of another circular ``string`` to the end of this circular
203203

204204
virtual bool transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback = 0 );
205205

206+
void scroll( int firstVertexIndex ) final;
206207

207208

208209
virtual QgsCircularString *createEmptyWithSameType() const /Factory/;
@@ -222,7 +223,6 @@ Appends the contents of another circular ``string`` to the end of this circular
222223
int compareToSameClass( const QgsAbstractGeometry *other ) const final;
223224
virtual QgsRectangle calculateBoundingBox() const;
224225

225-
void scroll( int firstVertexIndex ) final;
226226

227227
};
228228

‎python/core/auto_generated/geometry/qgscompoundcurve.sip.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ Appends first point if not already closed.
187187

188188
virtual bool transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback = 0 );
189189

190+
void scroll( int firstVertexIndex ) final;
190191

191192

192193
virtual QgsCompoundCurve *createEmptyWithSameType() const /Factory/;
@@ -206,7 +207,6 @@ Appends first point if not already closed.
206207
int compareToSameClass( const QgsAbstractGeometry *other ) const final;
207208
virtual QgsRectangle calculateBoundingBox() const;
208209

209-
void scroll( int firstVertexIndex ) final;
210210

211211
};
212212

‎python/core/auto_generated/geometry/qgscurve.sip.in

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ Returns a geometry without curves. Caller takes ownership
191191

192192
virtual QgsCurve *toCurveType() const /Factory/;
193193

194+
void normalize() final /HoldGIL/;
194195

195196
virtual QgsRectangle boundingBox() const;
196197

@@ -284,18 +285,6 @@ Returns the curve's orientation, e.g. clockwise or counter-clockwise.
284285
.. versionadded:: 3.6
285286
%End
286287

287-
288-
289-
protected:
290-
291-
virtual void clearCache() const;
292-
293-
294-
virtual int childCount() const;
295-
296-
virtual QgsPoint childPoint( int index ) const;
297-
298-
299288
virtual void scroll( int firstVertexIndex ) = 0;
300289
%Docstring
301290
Scrolls the curve vertices so that they start with the vertex at the given index.
@@ -316,6 +305,17 @@ Scrolls the curve vertices so that they start with the vertex at the given index
316305

317306

318307

308+
protected:
309+
310+
virtual void clearCache() const;
311+
312+
313+
virtual int childCount() const;
314+
315+
virtual QgsPoint childPoint( int index ) const;
316+
317+
318+
319319
};
320320

321321
/************************************************************************

‎python/core/auto_generated/geometry/qgscurvepolygon.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Curve polygon geometry type
5858

5959
virtual QString asKml( int precision = 17 ) const;
6060

61+
void normalize() final /HoldGIL/;
6162

6263
virtual double area() const /HoldGIL/;
6364

‎python/core/auto_generated/geometry/qgsgeometry.sip.in

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,17 @@ The ``method`` argument dictates which validator to utilize.
20272027
The ``flags`` parameter indicates optional flags which control the type of validity checking performed.
20282028

20292029
.. versionadded:: 1.5
2030+
%End
2031+
2032+
void normalize();
2033+
%Docstring
2034+
Reorganizes the geometry into a normalized form (or "canonical" form).
2035+
2036+
Polygon rings will be rearranged so that their starting vertex is the lower left and ring orientation follows the
2037+
right hand rule, collections are ordered by geometry type, and other normalization techniques are applied. The
2038+
resultant geometry will be geometrically equivalent to the original geometry.
2039+
2040+
.. versionadded:: 3.20
20302041
%End
20312042

20322043
static QgsGeometry unaryUnion( const QVector<QgsGeometry> &geometries );

‎python/core/auto_generated/geometry/qgsgeometrycollection.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ An IndexError will be raised if no geometry with the specified index exists.
150150
}
151151
%End
152152

153+
void normalize() final /HoldGIL/;
153154
virtual void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, bool transformZ = false ) throw( QgsCsException );
154155

155156
virtual void transform( const QTransform &t, double zTranslate = 0.0, double zScale = 1.0, double mTranslate = 0.0, double mScale = 1.0 );

‎python/core/auto_generated/geometry/qgslinestring.sip.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ of the curve.
553553

554554
virtual bool transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback = 0 );
555555

556+
void scroll( int firstVertexIndex ) final;
556557

557558

558559
virtual QgsLineString *createEmptyWithSameType() const /Factory/;
@@ -652,7 +653,6 @@ corresponds to the last point in the line.
652653
int compareToSameClass( const QgsAbstractGeometry *other ) const final;
653654
virtual QgsRectangle calculateBoundingBox() const;
654655

655-
void scroll( int firstVertexIndex ) final;
656656

657657
};
658658

‎python/core/auto_generated/geometry/qgspoint.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ Example
349349

350350
QgsPoint operator-( QgsVector v ) const /HoldGIL/;
351351

352+
void normalize() final /HoldGIL/;
352353
virtual bool isEmpty() const /HoldGIL/;
353354

354355
virtual QgsRectangle boundingBox() const /HoldGIL/;

‎src/core/geometry/qgsabstractgeometry.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,17 @@ class CORE_EXPORT QgsAbstractGeometry
235235
*/
236236
virtual QgsAbstractGeometry *boundary() const = 0 SIP_FACTORY;
237237

238+
/**
239+
* Reorganizes the geometry into a normalized form (or "canonical" form).
240+
*
241+
* Polygon rings will be rearranged so that their starting vertex is the lower left and ring orientation follows the
242+
* right hand rule, collections are ordered by geometry type, and other normalization techniques are applied. The
243+
* resultant geometry will be geometrically equivalent to the original geometry.
244+
*
245+
* \since QGIS 3.20
246+
*/
247+
virtual void normalize() = 0;
248+
238249
//import
239250

240251
/**

‎src/core/geometry/qgscircularstring.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
163163
double yAt( int index ) const override SIP_HOLDGIL;
164164

165165
bool transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback = nullptr ) override;
166+
void scroll( int firstVertexIndex ) final;
166167

167168
#ifndef SIP_RUN
168169
void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override;
@@ -201,7 +202,6 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
201202

202203
int compareToSameClass( const QgsAbstractGeometry *other ) const final;
203204
QgsRectangle calculateBoundingBox() const override;
204-
void scroll( int firstVertexIndex ) final;
205205

206206
private:
207207
QVector<double> mX;

‎src/core/geometry/qgscompoundcurve.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
148148
double yAt( int index ) const override SIP_HOLDGIL;
149149

150150
bool transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback = nullptr ) override;
151+
void scroll( int firstVertexIndex ) final;
151152

152153
#ifndef SIP_RUN
153154
void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override;
@@ -186,7 +187,6 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
186187

187188
int compareToSameClass( const QgsAbstractGeometry *other ) const final;
188189
QgsRectangle calculateBoundingBox() const override;
189-
void scroll( int firstVertexIndex ) final;
190190

191191
private:
192192
QVector< QgsCurve * > mCurves;

‎src/core/geometry/qgscurve.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,33 @@ QgsCurve *QgsCurve::toCurveType() const
199199
return clone();
200200
}
201201

202+
void QgsCurve::normalize()
203+
{
204+
if ( isEmpty() )
205+
return;
206+
207+
if ( !isClosed() )
208+
{
209+
return;
210+
}
211+
212+
int minCoordinateIndex = 0;
213+
QgsPoint minCoord;
214+
int i = 0;
215+
for ( auto it = vertices_begin(); it != vertices_end(); ++it )
216+
{
217+
const QgsPoint vertex = *it;
218+
if ( minCoord.isEmpty() || minCoord.compareTo( &vertex ) > 0 )
219+
{
220+
minCoord = vertex;
221+
minCoordinateIndex = i;
222+
}
223+
i++;
224+
}
225+
226+
scroll( minCoordinateIndex );
227+
}
228+
202229
QgsRectangle QgsCurve::boundingBox() const
203230
{
204231
if ( mBoundingBox.isNull() )

‎src/core/geometry/qgscurve.h

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class CORE_EXPORT QgsCurve: public QgsAbstractGeometry SIP_ABSTRACT
176176
int partCount() const override;
177177
QgsPoint vertexAt( QgsVertexId id ) const override;
178178
QgsCurve *toCurveType() const override SIP_FACTORY;
179+
void normalize() final SIP_HOLDGIL;
179180

180181
QgsRectangle boundingBox() const override;
181182
bool isValid( QString &error SIP_OUT, int flags = 0 ) const override;
@@ -262,6 +263,19 @@ class CORE_EXPORT QgsCurve: public QgsAbstractGeometry SIP_ABSTRACT
262263
*/
263264
Orientation orientation() const;
264265

266+
/**
267+
* Scrolls the curve vertices so that they start with the vertex at the given index.
268+
*
269+
* \warning This should only be called on closed curves, or the shape of the curve will be altered and
270+
* the result is undefined.
271+
*
272+
* \warning The \a firstVertexIndex must correspond to a segment vertex and not a curve point or the result
273+
* is undefined.
274+
*
275+
* \since QGIS 3.20
276+
*/
277+
virtual void scroll( int firstVertexIndex ) = 0;
278+
265279
#ifndef SIP_RUN
266280

267281
/**
@@ -305,20 +319,6 @@ class CORE_EXPORT QgsCurve: public QgsAbstractGeometry SIP_ABSTRACT
305319

306320
int childCount() const override;
307321
QgsPoint childPoint( int index ) const override;
308-
309-
/**
310-
* Scrolls the curve vertices so that they start with the vertex at the given index.
311-
*
312-
* \warning This should only be called on closed curves, or the shape of the curve will be altered and
313-
* the result is undefined.
314-
*
315-
* \warning The \a firstVertexIndex must correspond to a segment vertex and not a curve point or the result
316-
* is undefined.
317-
*
318-
* \since QGIS 3.20
319-
*/
320-
virtual void scroll( int firstVertexIndex ) = 0;
321-
322322
#ifndef SIP_RUN
323323

324324
/**

‎src/core/geometry/qgscurvepolygon.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,27 @@ QString QgsCurvePolygon::asKml( int precision ) const
464464
return kml;
465465
}
466466

467+
void QgsCurvePolygon::normalize()
468+
{
469+
// normalize rings
470+
if ( mExteriorRing )
471+
mExteriorRing->normalize();
472+
473+
for ( QgsCurve *ring : std::as_const( mInteriorRings ) )
474+
{
475+
ring->normalize();
476+
}
477+
478+
// sort rings
479+
std::sort( mInteriorRings.begin(), mInteriorRings.end(), []( const QgsCurve * a, const QgsCurve * b )
480+
{
481+
return a->compareTo( b ) > 0;
482+
} );
483+
484+
// normalize ring orientation
485+
forceRHR();
486+
}
487+
467488
double QgsCurvePolygon::area() const
468489
{
469490
if ( !mExteriorRing )

‎src/core/geometry/qgscurvepolygon.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
5858
QDomElement asGml3( QDomDocument &doc, int precision = 17, const QString &ns = "gml", QgsAbstractGeometry::AxisOrder axisOrder = QgsAbstractGeometry::AxisOrder::XY ) const override;
5959
json asJsonObject( int precision = 17 ) const override SIP_SKIP;
6060
QString asKml( int precision = 17 ) const override;
61+
void normalize() final SIP_HOLDGIL;
6162

6263
//surface interface
6364
double area() const override SIP_HOLDGIL;

‎src/core/geometry/qgsgeometry.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2717,6 +2717,17 @@ void QgsGeometry::validateGeometry( QVector<QgsGeometry::Error> &errors, const V
27172717
}
27182718
}
27192719

2720+
void QgsGeometry::normalize()
2721+
{
2722+
if ( !d->geometry )
2723+
{
2724+
return;
2725+
}
2726+
2727+
detach();
2728+
d->geometry->normalize();
2729+
}
2730+
27202731
bool QgsGeometry::isGeosValid( const QgsGeometry::ValidityFlags flags ) const
27212732
{
27222733
if ( !d->geometry )

‎src/core/geometry/qgsgeometry.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,6 +2140,17 @@ class CORE_EXPORT QgsGeometry
21402140
*/
21412141
void validateGeometry( QVector<QgsGeometry::Error> &errors SIP_OUT, ValidationMethod method = ValidatorQgisInternal, QgsGeometry::ValidityFlags flags = QgsGeometry::ValidityFlags() ) const;
21422142

2143+
/**
2144+
* Reorganizes the geometry into a normalized form (or "canonical" form).
2145+
*
2146+
* Polygon rings will be rearranged so that their starting vertex is the lower left and ring orientation follows the
2147+
* right hand rule, collections are ordered by geometry type, and other normalization techniques are applied. The
2148+
* resultant geometry will be geometrically equivalent to the original geometry.
2149+
*
2150+
* \since QGIS 3.20
2151+
*/
2152+
void normalize();
2153+
21432154
/**
21442155
* Compute the unary union on a list of \a geometries. May be faster than an iterative union on a set of geometries.
21452156
* The returned geometry will be fully noded, i.e. a node will be created at every common intersection of the

‎src/core/geometry/qgsgeometrycollection.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,18 @@ bool QgsGeometryCollection::removeGeometry( int nr )
291291
return true;
292292
}
293293

294+
void QgsGeometryCollection::normalize()
295+
{
296+
for ( QgsAbstractGeometry *geometry : std::as_const( mGeometries ) )
297+
{
298+
geometry->normalize();
299+
}
300+
std::sort( mGeometries.begin(), mGeometries.end(), []( const QgsAbstractGeometry * a, const QgsAbstractGeometry * b )
301+
{
302+
return a->compareTo( b ) > 0;
303+
} );
304+
}
305+
294306
int QgsGeometryCollection::dimension() const
295307
{
296308
int maxDim = 0;

‎src/core/geometry/qgsgeometrycollection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
179179
% End
180180
#endif
181181

182+
void normalize() final SIP_HOLDGIL;
182183
void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, bool transformZ = false ) override SIP_THROW( QgsCsException );
183184
void transform( const QTransform &t, double zTranslate = 0.0, double zScale = 1.0, double mTranslate = 0.0, double mScale = 1.0 ) override;
184185

0 commit comments

Comments
 (0)
Please sign in to comment.