QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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  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  QgsPoint p1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment1 ) );
103  QgsPoint q1 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, inter.segment2 ) );
104  QgsPoint p2 = geom->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( inter.segment1 + 1 ) % nVerts ) );
105  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 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  {
327  for ( const QgsGeometryUtils::SelfIntersection &inter : QgsGeometryUtils::selfIntersections( geom, iPart, iRing, mContext->tolerance ) )
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 nullptr;
355 }
356 
357 QString QgsGeometrySelfIntersectionCheck::factoryId()
358 {
359  return QStringLiteral( "QgsGeometrySelfIntersectionCheck" );
360 }
361 
362 QgsGeometryCheck::CheckType QgsGeometrySelfIntersectionCheck::factoryCheckType()
363 {
365 }
QgsFeatureId id
Definition: qgsfeature.h:64
virtual void setExteriorRing(QgsCurve *ring)
Sets the exterior ring of the polygon.
virtual bool removeGeometry(int nr)
Removes a geometry from the collection.
void setPoints(const QgsPointSequence &points)
Resets the line string to match the specified list of points.
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.
static QVector< SelfIntersection > selfIntersections(const QgsAbstractGeometry *geom, int part, int ring, double tolerance)
Find self intersections in a polyline.
void update(const QgsSingleGeometryCheckError *other) override
Update this error with the information from other.
static QgsAbstractGeometry * getGeomPart(QgsAbstractGeometry *geom, int partIdx)
bool isValid() const
Returns true if the vertex id is valid.
Something has been added.
virtual void addInteriorRing(QgsCurve *ring)
Adds an interior ring to the geometry (takes ownership)
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
virtual bool isEqual(const QgsSingleGeometryCheckError *other) const
Check if this error is equal to other.
Multi line string geometry collection.
Curve polygon geometry type.
CheckType
The type of a check.
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...
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:127
QgsGeometrySelfIntersectionCheckError(const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, QgsVertexId vertexId, const QgsGeometryUtils::SelfIntersection &intersection)
bool isEqual(const QgsSingleGeometryCheckError *other) const override
Check if this error is equal to other.
void setObsolete()
Set the error status to obsolete.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
bool handleChanges(const QList< QgsGeometryCheck::Change > &changes) override
Apply a list of changes.
bool removeInteriorRing(int ringIndex)
Removes an interior ring from the polygon.
virtual void updateFeature(QgsFeature &feature)=0
Updates a feature in this pool.
void setFixFailed(const QString &reason)
Set the error status to failed and specify the reason for failure.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
virtual bool handleChanges(const QList< QgsGeometryCheck::Change > &changes)
Apply a list of changes.
QList< QgsSingleGeometryCheckError * > processGeometry(const QgsGeometry &geometry) const override
Check the geometry for errors.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
QgsSingleGeometryCheckError * singleError() const
The underlying single error.
Utility class for identifying a unique vertex within a geometry.
Geometry collection.
int ringCount(int part=0) const override
Returns the number of rings of which this geometry is built.
bool getFeature(QgsFeatureId id, QgsFeature &feature)
Retrieves the feature with the specified id into feature.
Multi curve geometry collection.
Definition: qgsmulticurve.h:29
QStringList resolutionMethods() const override
Returns a list of descriptions for available resolutions for errors.
virtual bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=nullptr)
Adds a single feature to the sink.
virtual int ringCount(int part=0) const =0
Returns the number of rings of which this geometry is built.
Abstract base class for all geometries.
const QString & layerId() const
The id of the layer on which this error has been detected.
Wraps a QgsSingleGeometryError into a standard QgsGeometryCheckError.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
An error from a QgsSingleGeometryCheck.
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.
QVector< QgsPoint > QgsPointSequence
int partCount() const override
Returns count of parts contained in the geometry.
QMap< QString, QMap< QgsFeatureId, QList< QgsGeometryCheck::Change > > > Changes
A collection of changes.
Multi polygon geometry collection.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
A feature pool is based on a vector layer and caches features.
void fixError(const QMap< QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap< QString, int > &mergeAttributeIndices, Changes &changes) const override
Fix the error error with the specified method.
QgsCurve * clone() const override=0
Clones the geometry by performing a deep copy.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
Descripts a change to fix a geometry.
void setFixed(int method)
Set the status to fixed and specify the method that has been used to fix the error.
void setGeometry(const QgsGeometry &geometry)
Set the feature&#39;s geometry.
Definition: qgsfeature.cpp:137
QgsFeatureId featureId() const
The id of the feature on which this error has been detected.
QgsGeometry geometry
Definition: qgsfeature.h:67
QgsCurvePolygon * clone() const override
Clones the geometry by performing a deep copy.
This represents an error reported by a geometry check.
static std::unique_ptr< QgsGeometryEngine > createGeomEngine(const QgsAbstractGeometry *geometry, double tolerance)
Polygon geometry type.
Definition: qgspolygon.h:31
virtual QgsPoint vertexAt(QgsVertexId id) const =0
Returns the point corresponding to a specified vertex id.
Represents a vector layer which manages a vector based data sets.
virtual void update(const QgsSingleGeometryCheckError *other)
Update this error with the information from other.
const QgsVertexId & vidx() const
The id of the affected vertex.
const QgsGeometryUtils::SelfIntersection & intersection() const
QgsAttributes attributes
Definition: qgsfeature.h:65
virtual int partCount() const =0
Returns count of parts contained in the geometry.
The check controls individual nodes.
virtual bool addGeometry(QgsAbstractGeometry *g)
Adds a geometry and takes ownership. Returns true in case of success.