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