QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
qgsgeometryselfintersectioncheck.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgeometryselfintersectioncheck.cpp
3 ---------------------
4 begin : September 2015
5 copyright : (C) 2014 by Sandro Mani / Sourcepole AG
6 email : smani at sourcepole dot ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
18#include "qgspolygon.h"
19#include "qgslinestring.h"
20#include "qgsgeometryengine.h"
21#include "qgsmultipolygon.h"
22#include "qgsmultilinestring.h"
23#include "qgsgeometryutils.h"
24#include "qgsfeaturepool.h"
25
27{
31}
32
33bool QgsGeometrySelfIntersectionCheckError::handleChanges( const QList<QgsGeometryCheck::Change> &changes )
34{
36 return false;
37
38 for ( const QgsGeometryCheck::Change &change : changes )
39 {
40 if ( change.vidx.vertex == mIntersection.segment1 ||
41 change.vidx.vertex == mIntersection.segment1 + 1 ||
42 change.vidx.vertex == mIntersection.segment2 ||
43 change.vidx.vertex == mIntersection.segment2 + 1 )
44 {
45 return false;
46 }
47 else if ( change.vidx.vertex >= 0 )
48 {
49 if ( change.vidx.vertex < mIntersection.segment1 )
50 {
51 mIntersection.segment1 += change.type == QgsGeometryCheck::ChangeAdded ? 1 : -1;
52 }
53 if ( change.vidx.vertex < mIntersection.segment2 )
54 {
55 mIntersection.segment2 += change.type == QgsGeometryCheck::ChangeAdded ? 1 : -1;
56 }
57 }
58 }
59 return true;
60}
61
63{
65 // Static cast since this should only get called if isEqual == true
67 mIntersection.point = err->mIntersection.point;
68}
69
70void QgsGeometrySelfIntersectionCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
71{
72 QgsFeaturePool *featurePool = featurePools[ error->layerId() ];
73 QgsFeature feature;
74 if ( !featurePool->getFeature( error->featureId(), feature ) )
75 {
76 error->setObsolete();
77 return;
78 }
79
80 QgsGeometry featureGeom = feature.geometry();
81 QgsAbstractGeometry *geom = featureGeom.get();
82
83 const QgsVertexId vidx = error->vidx();
84
85 // Check if ring still exists
86 if ( !vidx.isValid( geom ) )
87 {
88 error->setObsolete();
89 return;
90 }
91
92 const QgsGeometryCheckErrorSingle *singleError = static_cast<const QgsGeometryCheckErrorSingle *>( error );
93 const QgsGeometryUtils::SelfIntersection &inter = static_cast<const QgsGeometrySelfIntersectionCheckError *>( singleError->singleError() )->intersection();
94 // Check if error still applies
95 bool ringIsClosed = false;
96 const int nVerts = QgsGeometryCheckerUtils::polyLineSize( geom, vidx.part, vidx.ring, &ringIsClosed );
97 if ( nVerts == 0 || inter.segment1 >= nVerts || inter.segment2 >= nVerts )
98 {
99 error->setObsolete();
100 return;
101 }
102 const QgsPoint p1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment1 ) );
103 const QgsPoint q1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment2 ) );
104 const QgsPoint p2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment1 + 1 ) % nVerts ) );
105 const QgsPoint q2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment2 + 1 ) % nVerts ) );
106 QgsPoint s;
107 bool intersection = false;
108 if ( !QgsGeometryUtils::segmentIntersection( p1, p2, q1, q2, s, intersection, mContext->tolerance ) )
109 {
110 error->setObsolete();
111 return;
112 }
113
114 // Fix with selected method
115 if ( method == NoChange )
116 {
117 error->setFixed( method );
118 }
119 else if ( method == ToMultiObject || method == ToSingleObjects )
120 {
121 // Extract rings
122 QgsPointSequence ring1, ring2;
123 bool ring1EndsWithS = false;
124 bool ring2EndsWithS = false;
125 for ( int i = 0; i < nVerts; ++i )
126 {
127 if ( i <= inter.segment1 || i >= inter.segment2 + 1 )
128 {
129 ring1.append( geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, i ) ) );
130 ring1EndsWithS = false;
131 if ( i == inter.segment2 + 1 )
132 {
133 ring2.append( s );
134 ring2EndsWithS = true;
135 }
136 }
137 else
138 {
139 ring2.append( geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, i ) ) );
140 ring2EndsWithS = true;
141 if ( i == inter.segment1 + 1 )
142 {
143 ring1.append( s );
144 ring1EndsWithS = false;
145 }
146 }
147 }
148 if ( nVerts == inter.segment2 + 1 )
149 {
150 ring2.append( s );
151 ring2EndsWithS = true;
152 }
153 if ( ringIsClosed || ring1EndsWithS )
154 ring1.append( ring1.front() ); // Ensure ring is closed
155 if ( ringIsClosed || ring2EndsWithS )
156 ring2.append( ring2.front() ); // Ensure ring is closed
157
158 if ( ring1.size() < 3 + ( ringIsClosed || ring1EndsWithS ) || ring2.size() < 3 + ( ringIsClosed || ring2EndsWithS ) )
159 {
160 error->setFixFailed( tr( "Resulting geometry is degenerate" ) );
161 return;
162 }
163 QgsLineString *ringGeom1 = new QgsLineString();
164 ringGeom1->setPoints( ring1 );
165 QgsLineString *ringGeom2 = new QgsLineString();
166 ringGeom2->setPoints( ring2 );
167
169 // If is a polygon...
170 if ( dynamic_cast<QgsCurvePolygon *>( part ) )
171 {
172 QgsCurvePolygon *poly = static_cast<QgsCurvePolygon *>( part );
173 // If self-intersecting ring is an interior ring, create separate holes
174 if ( vidx.ring > 0 )
175 {
176 poly->removeInteriorRing( vidx.ring );
177 poly->addInteriorRing( ringGeom1 );
178 poly->addInteriorRing( ringGeom2 );
179 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, vidx ) );
180 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 2 ) ) );
181 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 1 ) ) );
182 feature.setGeometry( featureGeom );
183 featurePool->updateFeature( feature );
184 }
185 else
186 {
187 // If ring is exterior, build two polygons, and reassign interiors as necessary
188 poly->setExteriorRing( ringGeom1 );
189
190 // If original feature was a linear polygon, also create the new part as a linear polygon
191 QgsCurvePolygon *poly2 = dynamic_cast<QgsPolygon *>( part ) ? new QgsPolygon() : new QgsCurvePolygon();
192 poly2->setExteriorRing( ringGeom2 );
193
194 // Reassing interiors as necessary
195 std::unique_ptr< QgsGeometryEngine > geomEnginePoly1 = QgsGeometryCheckerUtils::createGeomEngine( poly, mContext->tolerance );
196 std::unique_ptr< QgsGeometryEngine > geomEnginePoly2 = QgsGeometryCheckerUtils::createGeomEngine( poly2, mContext->tolerance );
197 for ( int n = poly->numInteriorRings(), i = n - 1; i >= 0; --i )
198 {
199 if ( !geomEnginePoly1->contains( poly->interiorRing( i ) ) )
200 {
201 if ( geomEnginePoly2->contains( poly->interiorRing( i ) ) )
202 {
203 poly2->addInteriorRing( static_cast<QgsCurve *>( poly->interiorRing( i )->clone() ) );
204 // No point in adding ChangeAdded changes, since the entire poly2 is added anyways later on
205 }
206 poly->removeInteriorRing( i );
207 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, QgsVertexId( vidx.part, 1 + i ) ) );
208 }
209 }
210
211 if ( method == ToMultiObject )
212 {
213 // If is already a geometry collection, just add the new polygon.
214 if ( dynamic_cast<QgsGeometryCollection *>( geom ) )
215 {
216 static_cast<QgsGeometryCollection *>( geom )->addGeometry( poly2 );
217 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) );
218 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geom->partCount() - 1 ) ) );
219 feature.setGeometry( featureGeom );
220 featurePool->updateFeature( feature );
221 }
222 // Otherwise, create multipolygon
223 else
224 {
225 QgsMultiPolygon *multiPoly = new QgsMultiPolygon();
226 multiPoly->addGeometry( poly->clone() );
227 multiPoly->addGeometry( poly2 );
228 feature.setGeometry( QgsGeometry( multiPoly ) );
229 featurePool->updateFeature( feature );
230 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) );
231 }
232 }
233 else // if ( method == ToSingleObjects )
234 {
235 QgsFeature newFeature;
236 newFeature.setAttributes( feature.attributes() );
237 newFeature.setGeometry( QgsGeometry( poly2 ) );
238 feature.setGeometry( featureGeom );
239 featurePool->updateFeature( feature );
240 featurePool->addFeature( newFeature );
241 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) );
242 changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) );
243 }
244 }
245 }
246 else if ( dynamic_cast<QgsCurve *>( part ) )
247 {
248 if ( method == ToMultiObject )
249 {
250 if ( dynamic_cast<QgsGeometryCollection *>( geom ) )
251 {
252 QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom );
253 geomCollection->removeGeometry( vidx.part );
254 geomCollection->addGeometry( ringGeom1 );
255 geomCollection->addGeometry( ringGeom2 );
256 feature.setGeometry( featureGeom );
257 featurePool->updateFeature( feature );
258 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) );
259 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 2 ) ) );
260 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) );
261 }
262 else
263 {
264 QgsMultiCurve *geomCollection = new QgsMultiLineString();
265 geomCollection->addGeometry( ringGeom1 );
266 geomCollection->addGeometry( ringGeom2 );
267 feature.setGeometry( QgsGeometry( geomCollection ) );
268 featurePool->updateFeature( feature );
269 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) );
270 }
271 }
272 else // if(method == ToSingleObjects)
273 {
274 if ( dynamic_cast<QgsGeometryCollection *>( geom ) )
275 {
276 QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom );
277 geomCollection->removeGeometry( vidx.part );
278 geomCollection->addGeometry( ringGeom1 );
279 feature.setGeometry( featureGeom );
280 featurePool->updateFeature( feature );
281 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) );
282 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) );
283 }
284 else
285 {
286 feature.setGeometry( QgsGeometry( ringGeom1 ) );
287 featurePool->updateFeature( feature );
288 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged, QgsVertexId( vidx.part ) ) );
289 }
290 QgsFeature newFeature;
291 newFeature.setAttributes( feature.attributes() );
292 newFeature.setGeometry( QgsGeometry( ringGeom2 ) );
293 featurePool->addFeature( newFeature );
294 changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) );
295 }
296 }
297 else
298 {
299 delete ringGeom1;
300 delete ringGeom2;
301 }
302 error->setFixed( method );
303 }
304 else
305 {
306 error->setFixFailed( tr( "Unknown method" ) );
307 }
308}
309
311{
312 static const QStringList methods = QStringList()
313 << tr( "Split feature into a multi-object feature" )
314 << tr( "Split feature into multiple single-object features" )
315 << tr( "No action" );
316 return methods;
317}
318
319QList<QgsSingleGeometryCheckError *> QgsGeometrySelfIntersectionCheck::processGeometry( const QgsGeometry &geometry ) const
320{
321 QList<QgsSingleGeometryCheckError *> errors;
322 const QgsAbstractGeometry *geom = geometry.constGet();
323 for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
324 {
325 for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing )
326 {
328 {
329 errors.append( new QgsGeometrySelfIntersectionCheckError( this, geometry, QgsGeometry( inter.point.clone() ), QgsVertexId( iPart, iRing ), inter ) );
330 }
331 }
332 }
333 return errors;
334}
335
337QList<Qgis::GeometryType> QgsGeometrySelfIntersectionCheck::factoryCompatibleGeometryTypes()
338{
339 return {Qgis::GeometryType::Line, Qgis::GeometryType::Polygon};
340}
341
342bool QgsGeometrySelfIntersectionCheck::factoryIsCompatible( QgsVectorLayer *layer )
343{
344 return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
345}
346
347QString QgsGeometrySelfIntersectionCheck::factoryDescription()
348{
349 return tr( "Self intersection" );
350}
351
352QgsGeometryCheck::Flags QgsGeometrySelfIntersectionCheck::factoryFlags()
353{
354 return QgsGeometryCheck::Flags();
355}
356
357QString QgsGeometrySelfIntersectionCheck::factoryId()
358{
359 return QStringLiteral( "QgsGeometrySelfIntersectionCheck" );
360}
361
362QgsGeometryCheck::CheckType QgsGeometrySelfIntersectionCheck::factoryCheckType()
363{
365}
Abstract base class for all geometries.
virtual int ringCount(int part=0) const =0
Returns the number of rings of which this geometry is built.
virtual QgsPoint vertexAt(QgsVertexId id) const =0
Returns the point corresponding to a specified vertex id.
virtual int partCount() const =0
Returns count of parts contained in the geometry.
Curve polygon geometry type.
int ringCount(int part=0) const override SIP_HOLDGIL
Returns the number of rings of which this geometry is built.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
QgsCurvePolygon * clone() const override
Clones the geometry by performing a deep copy.
virtual void setExteriorRing(QgsCurve *ring)
Sets the exterior ring of the polygon.
virtual void addInteriorRing(QgsCurve *ring)
Adds an interior ring to the geometry (takes ownership)
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with the curve polygon.
bool removeInteriorRing(int ringIndex)
Removes an interior ring from the polygon.
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
QgsCurve * clone() const override=0
Clones the geometry by performing a deep copy.
A feature pool is based on a vector layer and caches features.
virtual void updateFeature(QgsFeature &feature)=0
Updates a feature in this pool.
bool getFeature(QgsFeatureId id, QgsFeature &feature)
Retrieves the feature with the specified id into feature.
virtual bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags())
Adds a single feature to the sink.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsAttributes attributes
Definition: qgsfeature.h:65
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:160
QgsGeometry geometry
Definition: qgsfeature.h:67
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:167
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
const double tolerance
The tolerance to allow for in geometry checks.
Wraps a QgsSingleGeometryError into a standard QgsGeometryCheckError.
QgsSingleGeometryCheckError * singleError() const
The underlying single error.
This represents an error reported by a geometry check.
const QgsVertexId & vidx() const
The id of the affected vertex.
QgsFeatureId featureId() const
The id of the feature on which this error has been detected.
void setFixed(int method)
Set the status to fixed and specify the method that has been used to fix the error.
void setFixFailed(const QString &reason)
Set the error status to failed and specify the reason for failure.
void setObsolete()
Set the error status to obsolete.
const QString & layerId() const
The id of the layer on which this error has been detected.
QMap< QString, QMap< QgsFeatureId, QList< QgsGeometryCheck::Change > > > Changes
A collection of changes.
const QgsGeometryCheckContext * mContext
@ ChangeRing
This change happens on ring level.
@ ChangeFeature
This change happens on feature level.
@ ChangePart
This change happens on part level.
CheckType
The type of a check.
@ FeatureNodeCheck
The check controls individual nodes.
@ ChangeChanged
Something has been updated.
@ ChangeAdded
Something has been added.
@ ChangeRemoved
Something has been removed.
static std::unique_ptr< QgsGeometryEngine > createGeomEngine(const QgsAbstractGeometry *geometry, double tolerance)
static QgsAbstractGeometry * getGeomPart(QgsAbstractGeometry *geom, int partIdx)
static int polyLineSize(const QgsAbstractGeometry *geom, int iPart, int iRing, bool *isClosed=nullptr)
Returns the number of points in a polyline, accounting for duplicate start and end point if the polyl...
Geometry collection.
virtual bool removeGeometry(int nr)
Removes a geometry from the collection.
virtual bool addGeometry(QgsAbstractGeometry *g)
Adds a geometry and takes ownership. Returns true in case of success.
int partCount() const override
Returns count of parts contained in the geometry.
const QgsGeometryUtils::SelfIntersection & intersection() const
bool handleChanges(const QList< QgsGeometryCheck::Change > &changes) override
Apply a list of changes.
bool isEqual(const QgsSingleGeometryCheckError *other) const override
Check if this error is equal to other.
void update(const QgsSingleGeometryCheckError *other) override
Update this error with the information from other.
void fixError(const QMap< QString, QgsFeaturePool * > &featurePools, QgsGeometryCheckError *error, int method, const QMap< QString, int > &mergeAttributeIndices, Changes &changes) const override
Fixes the error error with the specified method.
QList< QgsSingleGeometryCheckError * > processGeometry(const QgsGeometry &geometry) const override
Check the geometry for errors.
Q_DECL_DEPRECATED QStringList resolutionMethods() const override
Returns a list of descriptions for available resolutions for errors.
static QVector< SelfIntersection > selfIntersections(const QgsAbstractGeometry *geom, int part, int ring, double tolerance)
Find self intersections in a polyline.
static bool segmentIntersection(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false) SIP_HOLDGIL
Compute the intersection between two segments.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
void setPoints(size_t size, const double *x, const double *y, const double *z=nullptr, const double *m=nullptr)
Resets the line string to match the specified point data.
Multi curve geometry collection.
Definition: qgsmulticurve.h:30
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Multi line string geometry collection.
Multi polygon geometry collection.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Polygon geometry type.
Definition: qgspolygon.h:34
An error from a QgsSingleGeometryCheck.
virtual bool isEqual(const QgsSingleGeometryCheckError *other) const
Check if this error is equal to other.
virtual bool handleChanges(const QList< QgsGeometryCheck::Change > &changes)
Apply a list of changes.
virtual void update(const QgsSingleGeometryCheckError *other)
Update this error with the information from other.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
QVector< QgsPoint > QgsPointSequence
Descripts a change to fix a geometry.
Utility class for identifying a unique vertex within a geometry.
Definition: qgsvertexid.h:31
int part
Part number.
Definition: qgsvertexid.h:89
int ring
Ring number.
Definition: qgsvertexid.h:92
bool isValid() const SIP_HOLDGIL
Returns true if the vertex id is valid.
Definition: qgsvertexid.h:46