QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
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
30
31bool QgsGeometrySelfIntersectionCheckError::handleChanges( const QList<QgsGeometryCheck::Change> &changes )
32{
34 return false;
35
36 for ( const QgsGeometryCheck::Change &change : changes )
37 {
38 if ( change.vidx.vertex == mIntersection.segment1 || change.vidx.vertex == mIntersection.segment1 + 1 || change.vidx.vertex == mIntersection.segment2 || change.vidx.vertex == mIntersection.segment2 + 1 )
39 {
40 return false;
41 }
42 else if ( change.vidx.vertex >= 0 )
43 {
44 if ( change.vidx.vertex < mIntersection.segment1 )
45 {
46 mIntersection.segment1 += change.type == QgsGeometryCheck::ChangeAdded ? 1 : -1;
47 }
48 if ( change.vidx.vertex < mIntersection.segment2 )
49 {
50 mIntersection.segment2 += change.type == QgsGeometryCheck::ChangeAdded ? 1 : -1;
51 }
52 }
53 }
54 return true;
55}
56
58{
60 // Static cast since this should only get called if isEqual == true
62 mIntersection.point = err->mIntersection.point;
63}
64
65void QgsGeometrySelfIntersectionCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
66{
67 QgsFeaturePool *featurePool = featurePools[error->layerId()];
68 QgsFeature feature;
69 if ( !featurePool->getFeature( error->featureId(), feature ) )
70 {
71 error->setObsolete();
72 return;
73 }
74
75 QgsGeometry featureGeom = feature.geometry();
76 QgsAbstractGeometry *geom = featureGeom.get();
77
78 const QgsVertexId vidx = error->vidx();
79
80 // Check if ring still exists
81 if ( !vidx.isValid( geom ) )
82 {
83 error->setObsolete();
84 return;
85 }
86
87 const QgsGeometryCheckErrorSingle *singleError = static_cast<const QgsGeometryCheckErrorSingle *>( error );
88 const QgsGeometryUtils::SelfIntersection &inter = static_cast<const QgsGeometrySelfIntersectionCheckError *>( singleError->singleError() )->intersection();
89 // Check if error still applies
90 bool ringIsClosed = false;
91 const int nVerts = QgsGeometryCheckerUtils::polyLineSize( geom, vidx.part, vidx.ring, &ringIsClosed );
92 if ( nVerts == 0 || inter.segment1 >= nVerts || inter.segment2 >= nVerts )
93 {
94 error->setObsolete();
95 return;
96 }
97 const QgsPoint p1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment1 ) );
98 const QgsPoint q1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment2 ) );
99 const QgsPoint p2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment1 + 1 ) % nVerts ) );
100 const QgsPoint q2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment2 + 1 ) % nVerts ) );
101 QgsPoint s;
102 bool intersection = false;
103 if ( !QgsGeometryUtils::segmentIntersection( p1, p2, q1, q2, s, intersection, mContext->tolerance ) )
104 {
105 error->setObsolete();
106 return;
107 }
108
109 // Fix with selected method
110 if ( method == NoChange )
111 {
112 error->setFixed( method );
113 }
114 else if ( method == ToMultiObject || method == ToSingleObjects )
115 {
116 // Extract rings
117 QgsPointSequence ring1, ring2;
118 bool ring1EndsWithS = false;
119 bool ring2EndsWithS = false;
120 for ( int i = 0; i < nVerts; ++i )
121 {
122 if ( i <= inter.segment1 || i >= inter.segment2 + 1 )
123 {
124 ring1.append( geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, i ) ) );
125 ring1EndsWithS = false;
126 if ( i == inter.segment2 + 1 )
127 {
128 ring2.append( s );
129 ring2EndsWithS = true;
130 }
131 }
132 else
133 {
134 ring2.append( geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, i ) ) );
135 ring2EndsWithS = true;
136 if ( i == inter.segment1 + 1 )
137 {
138 ring1.append( s );
139 ring1EndsWithS = false;
140 }
141 }
142 }
143 if ( nVerts == inter.segment2 + 1 )
144 {
145 ring2.append( s );
146 ring2EndsWithS = true;
147 }
148 if ( ringIsClosed || ring1EndsWithS )
149 ring1.append( ring1.front() ); // Ensure ring is closed
150 if ( ringIsClosed || ring2EndsWithS )
151 ring2.append( ring2.front() ); // Ensure ring is closed
152
153 if ( ring1.size() < 3 + ( ringIsClosed || ring1EndsWithS ) || ring2.size() < 3 + ( ringIsClosed || ring2EndsWithS ) )
154 {
155 error->setFixFailed( tr( "Resulting geometry is degenerate" ) );
156 return;
157 }
158 std::unique_ptr<QgsLineString> ringGeom1 = std::make_unique<QgsLineString>();
159 ringGeom1->setPoints( ring1 );
160 std::unique_ptr<QgsLineString> ringGeom2 = std::make_unique<QgsLineString>();
161 ringGeom2->setPoints( ring2 );
162
164 // If is a polygon...
165 if ( QgsCurvePolygon *poly = qgsgeometry_cast<QgsCurvePolygon *>( part ) )
166 {
167 // If self-intersecting ring is an interior ring, create separate holes
168 if ( vidx.ring > 0 )
169 {
170 poly->removeInteriorRing( vidx.ring );
171 poly->addInteriorRing( ringGeom1.release() );
172 poly->addInteriorRing( ringGeom2.release() );
173 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, vidx ) );
174 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 2 ) ) );
175 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeAdded, QgsVertexId( vidx.part, poly->ringCount() - 1 ) ) );
176 feature.setGeometry( featureGeom );
177 featurePool->updateFeature( feature );
178 }
179 else
180 {
181 // If ring is exterior, build two polygons, and reassign interiors as necessary
182 poly->setExteriorRing( ringGeom1.release() );
183
184 // If original feature was a linear polygon, also create the new part as a linear polygon
185 std::unique_ptr<QgsCurvePolygon> poly2 = qgsgeometry_cast<QgsPolygon *>( part ) ? std::make_unique<QgsPolygon>() : std::make_unique<QgsCurvePolygon>();
186 poly2->setExteriorRing( ringGeom2.release() );
187
188 // Reassing interiors as necessary
189 std::unique_ptr<QgsGeometryEngine> geomEnginePoly1( QgsGeometry::createGeometryEngine( poly, mContext->tolerance ) );
190 std::unique_ptr<QgsGeometryEngine> geomEnginePoly2( QgsGeometry::createGeometryEngine( poly2.get(), mContext->tolerance ) );
191 for ( int n = poly->numInteriorRings(), i = n - 1; i >= 0; --i )
192 {
193 if ( !geomEnginePoly1->contains( poly->interiorRing( i ) ) )
194 {
195 if ( geomEnginePoly2->contains( poly->interiorRing( i ) ) )
196 {
197 poly2->addInteriorRing( qgsgeometry_cast<QgsCurve *>( poly->interiorRing( i )->clone() ) );
198 // No point in adding ChangeAdded changes, since the entire poly2 is added anyways later on
199 }
200 poly->removeInteriorRing( i );
201 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeRemoved, QgsVertexId( vidx.part, 1 + i ) ) );
202 }
203 }
204
205 if ( method == ToMultiObject )
206 {
207 // If is already a geometry collection, just add the new polygon.
208 if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
209 {
210 collection->addGeometry( poly2.release() );
211 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) );
212 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geom->partCount() - 1 ) ) );
213 feature.setGeometry( featureGeom );
214 featurePool->updateFeature( feature );
215 }
216 // Otherwise, create multipolygon
217 else
218 {
219 std::unique_ptr<QgsMultiPolygon> multiPoly = std::make_unique<QgsMultiPolygon>();
220 multiPoly->addGeometry( poly->clone() );
221 multiPoly->addGeometry( poly2.release() );
222 feature.setGeometry( QgsGeometry( std::move( multiPoly ) ) );
223 featurePool->updateFeature( feature );
224 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) );
225 }
226 }
227 else // if ( method == ToSingleObjects )
228 {
229 QgsFeature newFeature;
230 newFeature.setAttributes( feature.attributes() );
231 newFeature.setGeometry( QgsGeometry( std::move( poly2 ) ) );
232 feature.setGeometry( featureGeom );
233 featurePool->updateFeature( feature );
234 featurePool->addFeature( newFeature );
235 changes[error->layerId()][feature.id()].append( Change( ChangeRing, ChangeChanged, QgsVertexId( vidx.part, vidx.ring ) ) );
236 changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) );
237 }
238 }
239 }
240 else if ( qgsgeometry_cast<QgsCurve *>( part ) )
241 {
242 if ( method == ToMultiObject )
243 {
244 if ( QgsGeometryCollection *geomCollection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
245 {
246 geomCollection->removeGeometry( vidx.part );
247 geomCollection->addGeometry( ringGeom1.release() );
248 geomCollection->addGeometry( ringGeom2.release() );
249 feature.setGeometry( featureGeom );
250 featurePool->updateFeature( feature );
251 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) );
252 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 2 ) ) );
253 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) );
254 }
255 else
256 {
257 std::unique_ptr<QgsMultiCurve> multiCurve = std::make_unique<QgsMultiLineString>();
258 multiCurve->addGeometry( ringGeom1.release() );
259 multiCurve->addGeometry( ringGeom2.release() );
260 feature.setGeometry( QgsGeometry( std::move( multiCurve ) ) );
261 featurePool->updateFeature( feature );
262 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged ) );
263 }
264 }
265 else // if(method == ToSingleObjects)
266 {
267 if ( QgsGeometryCollection *geomCollection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
268 {
269 geomCollection->removeGeometry( vidx.part );
270 geomCollection->addGeometry( ringGeom1.release() );
271 feature.setGeometry( featureGeom );
272 featurePool->updateFeature( feature );
273 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeRemoved, QgsVertexId( vidx.part ) ) );
274 changes[error->layerId()][feature.id()].append( Change( ChangePart, ChangeAdded, QgsVertexId( geomCollection->partCount() - 1 ) ) );
275 }
276 else
277 {
278 feature.setGeometry( QgsGeometry( std::move( ringGeom1 ) ) );
279 featurePool->updateFeature( feature );
280 changes[error->layerId()][feature.id()].append( Change( ChangeFeature, ChangeChanged, QgsVertexId( vidx.part ) ) );
281 }
282 QgsFeature newFeature;
283 newFeature.setAttributes( feature.attributes() );
284 newFeature.setGeometry( QgsGeometry( std::move( ringGeom2 ) ) );
285 featurePool->addFeature( newFeature );
286 changes[error->layerId()][newFeature.id()].append( Change( ChangeFeature, ChangeAdded ) );
287 }
288 }
289 error->setFixed( method );
290 }
291 else
292 {
293 error->setFixFailed( tr( "Unknown method" ) );
294 }
295}
296
298{
299 static const QStringList methods = QStringList()
300 << tr( "Split feature into a multi-object feature" )
301 << tr( "Split feature into multiple single-object features" )
302 << tr( "No action" );
303 return methods;
304}
305
306QList<QgsSingleGeometryCheckError *> QgsGeometrySelfIntersectionCheck::processGeometry( const QgsGeometry &geometry ) const
307{
308 QList<QgsSingleGeometryCheckError *> errors;
309 const QgsAbstractGeometry *geom = geometry.constGet();
310 for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
311 {
312 for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing )
313 {
315 {
316 errors.append( new QgsGeometrySelfIntersectionCheckError( this, geometry, QgsGeometry( inter.point.clone() ), QgsVertexId( iPart, iRing ), inter ) );
317 }
318 }
319 }
320 return errors;
321}
322
324QList<Qgis::GeometryType> QgsGeometrySelfIntersectionCheck::factoryCompatibleGeometryTypes()
325{
327}
328
329bool QgsGeometrySelfIntersectionCheck::factoryIsCompatible( QgsVectorLayer *layer )
330{
331 return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
332}
333
334QString QgsGeometrySelfIntersectionCheck::factoryDescription()
335{
336 return tr( "Self intersection" );
337}
338
339QgsGeometryCheck::Flags QgsGeometrySelfIntersectionCheck::factoryFlags()
340{
342}
343
344QString QgsGeometrySelfIntersectionCheck::factoryId()
345{
346 return QStringLiteral( "QgsGeometrySelfIntersectionCheck" );
347}
348
349QgsGeometryCheck::CheckType QgsGeometrySelfIntersectionCheck::factoryCheckType()
350{
352}
@ 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:58
QgsAttributes attributes
Definition qgsfeature.h:67
QgsFeatureId id
Definition qgsfeature.h:66
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QgsGeometry geometry
Definition qgsfeature.h:69
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
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 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...
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.
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.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
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