QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 std::unique_ptr< QgsLineString > ringGeom1 = std::make_unique< QgsLineString >();
164 ringGeom1->setPoints( ring1 );
165 std::unique_ptr< QgsLineString >ringGeom2 = std::make_unique< QgsLineString >();
166 ringGeom2->setPoints( ring2 );
167
169 // If is a polygon...
170 if ( QgsCurvePolygon *poly = qgsgeometry_cast<QgsCurvePolygon *>( part ) )
171 {
172 // If self-intersecting ring is an interior ring, create separate holes
173 if ( vidx.ring > 0 )
174 {
175 poly->removeInteriorRing( vidx.ring );
176 poly->addInteriorRing( ringGeom1.release() );
177 poly->addInteriorRing( ringGeom2.release() );
178 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, vidx ) );
179 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 2 ) ) );
180 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 1 ) ) );
181 feature.setGeometry( featureGeom );
182 featurePool->updateFeature( feature );
183 }
184 else
185 {
186 // If ring is exterior, build two polygons, and reassign interiors as necessary
187 poly->setExteriorRing( ringGeom1.release() );
188
189 // If original feature was a linear polygon, also create the new part as a linear polygon
190 std::unique_ptr< QgsCurvePolygon > poly2 = qgsgeometry_cast<QgsPolygon *>( part ) ? std::make_unique< QgsPolygon> () : std::make_unique< QgsCurvePolygon >();
191 poly2->setExteriorRing( ringGeom2.release() );
192
193 // Reassing interiors as necessary
194 std::unique_ptr< QgsGeometryEngine > geomEnginePoly1 = QgsGeometryCheckerUtils::createGeomEngine( poly, mContext->tolerance );
195 std::unique_ptr< QgsGeometryEngine > geomEnginePoly2 = QgsGeometryCheckerUtils::createGeomEngine( poly2.get(), mContext->tolerance );
196 for ( int n = poly->numInteriorRings(), i = n - 1; i >= 0; --i )
197 {
198 if ( !geomEnginePoly1->contains( poly->interiorRing( i ) ) )
199 {
200 if ( geomEnginePoly2->contains( poly->interiorRing( i ) ) )
201 {
202 poly2->addInteriorRing( qgsgeometry_cast<QgsCurve *>( poly->interiorRing( i )->clone() ) );
203 // No point in adding ChangeAdded changes, since the entire poly2 is added anyways later on
204 }
205 poly->removeInteriorRing( i );
206 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, QgsVertexId( vidx.part, 1 + i ) ) );
207 }
208 }
209
210 if ( method == ToMultiObject )
211 {
212 // If is already a geometry collection, just add the new polygon.
213 if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
214 {
215 collection->addGeometry( poly2.release() );
216 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) );
217 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geom->partCount() - 1 ) ) );
218 feature.setGeometry( featureGeom );
219 featurePool->updateFeature( feature );
220 }
221 // Otherwise, create multipolygon
222 else
223 {
224 std::unique_ptr< QgsMultiPolygon > multiPoly = std::make_unique< QgsMultiPolygon >();
225 multiPoly->addGeometry( poly->clone() );
226 multiPoly->addGeometry( poly2.release() );
227 feature.setGeometry( QgsGeometry( std::move( multiPoly ) ) );
228 featurePool->updateFeature( feature );
229 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) );
230 }
231 }
232 else // if ( method == ToSingleObjects )
233 {
234 QgsFeature newFeature;
235 newFeature.setAttributes( feature.attributes() );
236 newFeature.setGeometry( QgsGeometry( std::move( poly2 ) ) );
237 feature.setGeometry( featureGeom );
238 featurePool->updateFeature( feature );
239 featurePool->addFeature( newFeature );
240 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) );
241 changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) );
242 }
243 }
244 }
245 else if ( qgsgeometry_cast<QgsCurve *>( part ) )
246 {
247 if ( method == ToMultiObject )
248 {
249 if ( QgsGeometryCollection *geomCollection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
250 {
251 geomCollection->removeGeometry( vidx.part );
252 geomCollection->addGeometry( ringGeom1.release() );
253 geomCollection->addGeometry( ringGeom2.release() );
254 feature.setGeometry( featureGeom );
255 featurePool->updateFeature( feature );
256 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) );
257 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 2 ) ) );
258 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) );
259 }
260 else
261 {
262 std::unique_ptr< QgsMultiCurve > multiCurve = std::make_unique< QgsMultiLineString >();
263 multiCurve->addGeometry( ringGeom1.release() );
264 multiCurve->addGeometry( ringGeom2.release() );
265 feature.setGeometry( QgsGeometry( std::move( multiCurve ) ) );
266 featurePool->updateFeature( feature );
267 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) );
268 }
269 }
270 else // if(method == ToSingleObjects)
271 {
272 if ( QgsGeometryCollection *geomCollection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
273 {
274 geomCollection->removeGeometry( vidx.part );
275 geomCollection->addGeometry( ringGeom1.release() );
276 feature.setGeometry( featureGeom );
277 featurePool->updateFeature( feature );
278 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) );
279 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) );
280 }
281 else
282 {
283 feature.setGeometry( QgsGeometry( std::move( ringGeom1 ) ) );
284 featurePool->updateFeature( feature );
285 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged, QgsVertexId( vidx.part ) ) );
286 }
287 QgsFeature newFeature;
288 newFeature.setAttributes( feature.attributes() );
289 newFeature.setGeometry( QgsGeometry( std::move( ringGeom2 ) ) );
290 featurePool->addFeature( newFeature );
291 changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) );
292 }
293 }
294 error->setFixed( method );
295 }
296 else
297 {
298 error->setFixFailed( tr( "Unknown method" ) );
299 }
300}
301
303{
304 static const QStringList methods = QStringList()
305 << tr( "Split feature into a multi-object feature" )
306 << tr( "Split feature into multiple single-object features" )
307 << tr( "No action" );
308 return methods;
309}
310
311QList<QgsSingleGeometryCheckError *> QgsGeometrySelfIntersectionCheck::processGeometry( const QgsGeometry &geometry ) const
312{
313 QList<QgsSingleGeometryCheckError *> errors;
314 const QgsAbstractGeometry *geom = geometry.constGet();
315 for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
316 {
317 for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing )
318 {
320 {
321 errors.append( new QgsGeometrySelfIntersectionCheckError( this, geometry, QgsGeometry( inter.point.clone() ), QgsVertexId( iPart, iRing ), inter ) );
322 }
323 }
324 }
325 return errors;
326}
327
329QList<Qgis::GeometryType> QgsGeometrySelfIntersectionCheck::factoryCompatibleGeometryTypes()
330{
332}
333
334bool QgsGeometrySelfIntersectionCheck::factoryIsCompatible( QgsVectorLayer *layer )
335{
336 return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
337}
338
339QString QgsGeometrySelfIntersectionCheck::factoryDescription()
340{
341 return tr( "Self intersection" );
342}
343
344QgsGeometryCheck::Flags QgsGeometrySelfIntersectionCheck::factoryFlags()
345{
347}
348
349QString QgsGeometrySelfIntersectionCheck::factoryId()
350{
351 return QStringLiteral( "QgsGeometrySelfIntersectionCheck" );
352}
353
354QgsGeometryCheck::CheckType QgsGeometrySelfIntersectionCheck::factoryCheckType()
355{
357}
@ Polygon
Polygons.
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.
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.
QFlags< Flag > Flags
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.
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)
Compute the intersection between two segments.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
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:30
bool isValid() const
Returns true if the vertex id is valid.
Definition: qgsvertexid.h:45
int part
Part number.
Definition: qgsvertexid.h:88
int ring
Ring number.
Definition: qgsvertexid.h:91