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