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