QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 {
28  return QgsSingleGeometryCheckError::isEqual( other ) &&
31 }
32 
33 bool 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
66  const QgsGeometrySelfIntersectionCheckError *err = static_cast<const QgsGeometrySelfIntersectionCheckError *>( other );
67  mIntersection.point = err->mIntersection.point;
68 }
69 
70 void 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 
319 QList<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 
337 QList<QgsWkbTypes::GeometryType> QgsGeometrySelfIntersectionCheck::factoryCompatibleGeometryTypes()
338 {
340 }
341 
342 bool QgsGeometrySelfIntersectionCheck::factoryIsCompatible( QgsVectorLayer *layer )
343 {
344  return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
345 }
346 
347 QString QgsGeometrySelfIntersectionCheck::factoryDescription()
348 {
349  return tr( "Self intersection" );
350 }
351 
352 QgsGeometryCheck::Flags QgsGeometrySelfIntersectionCheck::factoryFlags()
353 {
354  return QgsGeometryCheck::Flags();
355 }
356 
357 QString QgsGeometrySelfIntersectionCheck::factoryId()
358 {
359  return QStringLiteral( "QgsGeometrySelfIntersectionCheck" );
360 }
361 
362 QgsGeometryCheck::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.
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)
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
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:153
QgsGeometry geometry
Definition: qgsfeature.h:67
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:163
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 QString & layerId() const
The id of the layer on which this error has been detected.
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.
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:125
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:44
void setPoints(const QgsPointSequence &points)
Resets the line string to match the specified list of points.
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 QgsWkbTypes::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