Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsvectorlayereditutils.cpp
3 : : ---------------------
4 : : begin : Dezember 2012
5 : : copyright : (C) 2012 by Martin Dobias
6 : : email : wonder dot sk at gmail dot com
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 : : #include "qgsvectorlayereditutils.h"
16 : :
17 : : #include "qgsvectordataprovider.h"
18 : : #include "qgsfeatureiterator.h"
19 : : #include "qgsvectorlayereditbuffer.h"
20 : : #include "qgslinestring.h"
21 : : #include "qgslogger.h"
22 : : #include "qgspoint.h"
23 : : #include "qgsgeometryfactory.h"
24 : : #include "qgis.h"
25 : : #include "qgswkbtypes.h"
26 : : #include "qgsvectorlayerutils.h"
27 : : #include "qgsvectorlayer.h"
28 : : #include "qgsgeometryoptions.h"
29 : : #include "qgsabstractgeometry.h"
30 : :
31 : : #include <limits>
32 : :
33 : :
34 : 0 : QgsVectorLayerEditUtils::QgsVectorLayerEditUtils( QgsVectorLayer *layer )
35 : 0 : : mLayer( layer )
36 : : {
37 : 0 : }
38 : :
39 : 0 : bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
40 : : {
41 : 0 : if ( !mLayer->isSpatial() )
42 : 0 : return false;
43 : :
44 : 0 : QgsFeature f;
45 : 0 : if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
46 : 0 : return false; // geometry not found
47 : :
48 : 0 : QgsGeometry geometry = f.geometry();
49 : :
50 : 0 : geometry.insertVertex( x, y, beforeVertex );
51 : :
52 : 0 : mLayer->changeGeometry( atFeatureId, geometry );
53 : 0 : return true;
54 : 0 : }
55 : :
56 : 0 : bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
57 : : {
58 : 0 : if ( !mLayer->isSpatial() )
59 : 0 : return false;
60 : :
61 : 0 : QgsFeature f;
62 : 0 : if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
63 : 0 : return false; // geometry not found
64 : :
65 : 0 : QgsGeometry geometry = f.geometry();
66 : :
67 : 0 : geometry.insertVertex( point, beforeVertex );
68 : :
69 : 0 : mLayer->changeGeometry( atFeatureId, geometry );
70 : 0 : return true;
71 : 0 : }
72 : :
73 : 0 : bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
74 : : {
75 : 0 : QgsPoint p( x, y );
76 : 0 : return moveVertex( p, atFeatureId, atVertex );
77 : 0 : }
78 : :
79 : 0 : bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
80 : : {
81 : 0 : if ( !mLayer->isSpatial() )
82 : 0 : return false;
83 : :
84 : 0 : QgsFeature f;
85 : 0 : if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
86 : 0 : return false; // geometry not found
87 : :
88 : 0 : QgsGeometry geometry = f.geometry();
89 : :
90 : 0 : geometry.moveVertex( p, atVertex );
91 : :
92 : 0 : mLayer->changeGeometry( atFeatureId, geometry );
93 : 0 : return true;
94 : 0 : }
95 : :
96 : :
97 : 0 : QgsVectorLayer::EditResult QgsVectorLayerEditUtils::deleteVertex( QgsFeatureId featureId, int vertex )
98 : : {
99 : 0 : if ( !mLayer->isSpatial() )
100 : 0 : return QgsVectorLayer::InvalidLayer;
101 : :
102 : 0 : QgsFeature f;
103 : 0 : if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
104 : 0 : return QgsVectorLayer::FetchFeatureFailed; // geometry not found
105 : :
106 : 0 : QgsGeometry geometry = f.geometry();
107 : :
108 : 0 : if ( !geometry.deleteVertex( vertex ) )
109 : 0 : return QgsVectorLayer::EditFailed;
110 : :
111 : 0 : if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
112 : : {
113 : : //last vertex deleted, set geometry to null
114 : 0 : geometry.set( nullptr );
115 : 0 : }
116 : :
117 : 0 : mLayer->changeGeometry( featureId, geometry );
118 : 0 : return !geometry.isNull() ? QgsVectorLayer::Success : QgsVectorLayer::EmptyGeometry;
119 : 0 : }
120 : :
121 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
122 : : {
123 : 0 : QgsPointSequence l;
124 : 0 : for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
125 : : {
126 : 0 : l << QgsPoint( *it );
127 : 0 : }
128 : 0 : return addRing( l, targetFeatureIds, modifiedFeatureId );
129 : 0 : }
130 : :
131 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::addRing( const QgsPointSequence &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
132 : : {
133 : 0 : QgsLineString *ringLine = new QgsLineString( ring );
134 : 0 : return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
135 : 0 : }
136 : :
137 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::addRing( QgsCurve *ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
138 : : {
139 : 0 : if ( !mLayer->isSpatial() )
140 : : {
141 : 0 : delete ring;
142 : 0 : return QgsGeometry::AddRingNotInExistingFeature;
143 : : }
144 : :
145 : 0 : QgsGeometry::OperationResult addRingReturnCode = QgsGeometry::AddRingNotInExistingFeature; //default: return code for 'ring not inserted'
146 : 0 : QgsFeature f;
147 : :
148 : 0 : QgsFeatureIterator fit;
149 : 0 : if ( !targetFeatureIds.isEmpty() )
150 : : {
151 : : //check only specified features
152 : 0 : fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) );
153 : 0 : }
154 : : else
155 : : {
156 : : //check all intersecting features
157 : 0 : QgsRectangle bBox = ring->boundingBox();
158 : 0 : fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
159 : : }
160 : :
161 : : //find first valid feature we can add the ring to
162 : 0 : while ( fit.nextFeature( f ) )
163 : : {
164 : 0 : if ( !f.hasGeometry() )
165 : 0 : continue;
166 : :
167 : : //add ring takes ownership of ring, and deletes it if there's an error
168 : 0 : QgsGeometry g = f.geometry();
169 : :
170 : 0 : addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
171 : 0 : if ( addRingReturnCode == 0 )
172 : 0 : if ( addRingReturnCode == QgsGeometry::Success )
173 : : {
174 : 0 : mLayer->changeGeometry( f.id(), g );
175 : 0 : if ( modifiedFeatureId )
176 : 0 : *modifiedFeatureId = f.id();
177 : :
178 : : //setModified( true, true );
179 : 0 : break;
180 : : }
181 : 0 : }
182 : :
183 : 0 : delete ring;
184 : 0 : return addRingReturnCode;
185 : 0 : }
186 : :
187 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::addPart( const QVector<QgsPointXY> &points, QgsFeatureId featureId )
188 : : {
189 : 0 : QgsPointSequence l;
190 : 0 : for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
191 : : {
192 : 0 : l << QgsPoint( *it );
193 : 0 : }
194 : 0 : return addPart( l, featureId );
195 : 0 : }
196 : :
197 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::addPart( const QgsPointSequence &points, QgsFeatureId featureId )
198 : : {
199 : 0 : if ( !mLayer->isSpatial() )
200 : 0 : return QgsGeometry::OperationResult::AddPartSelectedGeometryNotFound;
201 : :
202 : 0 : QgsGeometry geometry;
203 : 0 : bool firstPart = false;
204 : 0 : QgsFeature f;
205 : 0 : if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
206 : 0 : return QgsGeometry::OperationResult::AddPartSelectedGeometryNotFound; //not found
207 : :
208 : 0 : if ( !f.hasGeometry() )
209 : : {
210 : : //no existing geometry, so adding first part to null geometry
211 : 0 : firstPart = true;
212 : 0 : }
213 : : else
214 : : {
215 : 0 : geometry = f.geometry();
216 : : }
217 : :
218 : 0 : QgsGeometry::OperationResult errorCode = geometry.addPart( points, mLayer->geometryType() );
219 : 0 : if ( errorCode == QgsGeometry::Success )
220 : : {
221 : 0 : if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
222 : 0 : && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
223 : : {
224 : : //convert back to single part if required by layer
225 : 0 : geometry.convertToSingleType();
226 : 0 : }
227 : 0 : mLayer->changeGeometry( featureId, geometry );
228 : 0 : }
229 : 0 : return errorCode;
230 : 0 : }
231 : :
232 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::addPart( QgsCurve *ring, QgsFeatureId featureId )
233 : : {
234 : 0 : if ( !mLayer->isSpatial() )
235 : 0 : return QgsGeometry::AddPartSelectedGeometryNotFound;
236 : :
237 : 0 : QgsGeometry geometry;
238 : 0 : bool firstPart = false;
239 : 0 : QgsFeature f;
240 : 0 : if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
241 : 0 : return QgsGeometry::AddPartSelectedGeometryNotFound;
242 : :
243 : 0 : if ( !f.hasGeometry() )
244 : : {
245 : : //no existing geometry, so adding first part to null geometry
246 : 0 : firstPart = true;
247 : 0 : }
248 : : else
249 : : {
250 : 0 : geometry = f.geometry();
251 : : }
252 : :
253 : 0 : QgsGeometry::OperationResult errorCode = geometry.addPart( ring, mLayer->geometryType() );
254 : 0 : if ( errorCode == QgsGeometry::Success )
255 : : {
256 : 0 : if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
257 : 0 : && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
258 : : {
259 : : //convert back to single part if required by layer
260 : 0 : geometry.convertToSingleType();
261 : 0 : }
262 : 0 : mLayer->changeGeometry( featureId, geometry );
263 : 0 : }
264 : 0 : return errorCode;
265 : 0 : }
266 : :
267 : :
268 : 0 : int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
269 : : {
270 : 0 : if ( !mLayer->isSpatial() )
271 : 0 : return 1;
272 : :
273 : 0 : QgsFeature f;
274 : 0 : if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
275 : 0 : return 1; //geometry not found
276 : :
277 : 0 : QgsGeometry geometry = f.geometry();
278 : :
279 : 0 : int errorCode = geometry.translate( dx, dy );
280 : 0 : if ( errorCode == 0 )
281 : : {
282 : 0 : mLayer->changeGeometry( featureId, geometry );
283 : 0 : }
284 : 0 : return errorCode;
285 : 0 : }
286 : :
287 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
288 : : {
289 : 0 : QgsPointSequence l;
290 : 0 : for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
291 : : {
292 : 0 : l << QgsPoint( *it );
293 : 0 : }
294 : 0 : return splitFeatures( l, topologicalEditing );
295 : 0 : }
296 : :
297 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsPointSequence &splitLine, bool topologicalEditing )
298 : : {
299 : 0 : QgsLineString lineString( splitLine );
300 : 0 : QgsPointSequence topologyTestPoints;
301 : 0 : bool preserveCircular = false;
302 : 0 : return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
303 : 0 : }
304 : :
305 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
306 : : {
307 : 0 : if ( !mLayer->isSpatial() )
308 : 0 : return QgsGeometry::InvalidBaseGeometry;
309 : :
310 : 0 : QgsRectangle bBox; //bounding box of the split line
311 : 0 : QgsGeometry::OperationResult returnCode = QgsGeometry::OperationResult::Success;
312 : : QgsGeometry::OperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
313 : 0 : int numberOfSplitFeatures = 0;
314 : :
315 : 0 : QgsFeatureIterator features;
316 : 0 : const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
317 : :
318 : : // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
319 : 0 : preserveCircular &= curve->hasCurvedSegments();
320 : :
321 : 0 : if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
322 : : {
323 : 0 : features = mLayer->getSelectedFeatures();
324 : 0 : }
325 : : else //else consider all the feature that intersect the bounding box of the split line
326 : : {
327 : :
328 : 0 : bBox = curve->boundingBox();
329 : :
330 : 0 : if ( bBox.isEmpty() )
331 : : {
332 : : //if the bbox is a line, try to make a square out of it
333 : 0 : if ( bBox.width() == 0.0 && bBox.height() > 0 )
334 : : {
335 : 0 : bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
336 : 0 : bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
337 : 0 : }
338 : 0 : else if ( bBox.height() == 0.0 && bBox.width() > 0 )
339 : : {
340 : 0 : bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
341 : 0 : bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
342 : 0 : }
343 : : else
344 : : {
345 : : //If we have a single point, we still create a non-null box
346 : 0 : double bufferDistance = 0.000001;
347 : 0 : if ( mLayer->crs().isGeographic() )
348 : 0 : bufferDistance = 0.00000001;
349 : 0 : bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
350 : 0 : bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
351 : 0 : bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
352 : 0 : bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
353 : : }
354 : 0 : }
355 : :
356 : 0 : features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
357 : : }
358 : :
359 : 0 : QgsVectorLayerUtils::QgsFeaturesDataList featuresDataToAdd;
360 : :
361 : 0 : QgsFeature feat;
362 : 0 : while ( features.nextFeature( feat ) )
363 : : {
364 : 0 : if ( !feat.hasGeometry() )
365 : : {
366 : 0 : continue;
367 : : }
368 : 0 : QVector<QgsGeometry> newGeometries;
369 : 0 : QgsPointSequence featureTopologyTestPoints;
370 : 0 : QgsGeometry featureGeom = feat.geometry();
371 : 0 : splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
372 : 0 : topologyTestPoints.append( featureTopologyTestPoints );
373 : 0 : if ( splitFunctionReturn == QgsGeometry::OperationResult::Success )
374 : : {
375 : : //change this geometry
376 : 0 : mLayer->changeGeometry( feat.id(), featureGeom );
377 : :
378 : : //insert new features
379 : 0 : QgsAttributeMap attributeMap = feat.attributes().toMap();
380 : 0 : for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
381 : : {
382 : 0 : featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
383 : : }
384 : :
385 : 0 : if ( topologicalEditing )
386 : : {
387 : 0 : QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
388 : 0 : for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
389 : : {
390 : 0 : addTopologicalPoints( *topol_it );
391 : 0 : }
392 : 0 : }
393 : 0 : ++numberOfSplitFeatures;
394 : 0 : }
395 : 0 : else if ( splitFunctionReturn != QgsGeometry::OperationResult::Success && splitFunctionReturn != QgsGeometry::NothingHappened ) // i.e. no split but no error occurred
396 : : {
397 : 0 : returnCode = splitFunctionReturn;
398 : 0 : }
399 : 0 : }
400 : :
401 : 0 : if ( !featuresDataToAdd.isEmpty() )
402 : : {
403 : : // finally create and add all bits of geometries cut off the original geometries
404 : : // (this is much faster than creating features one by one)
405 : 0 : QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
406 : 0 : mLayer->addFeatures( featuresListToAdd );
407 : 0 : }
408 : :
409 : 0 : if ( numberOfSplitFeatures == 0 )
410 : : {
411 : 0 : returnCode = QgsGeometry::OperationResult::NothingHappened;
412 : 0 : }
413 : :
414 : 0 : return returnCode;
415 : 0 : }
416 : :
417 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
418 : : {
419 : 0 : QgsPointSequence l;
420 : 0 : for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
421 : : {
422 : 0 : l << QgsPoint( *it );
423 : 0 : }
424 : 0 : return splitParts( l, topologicalEditing );
425 : 0 : }
426 : :
427 : 0 : QgsGeometry::OperationResult QgsVectorLayerEditUtils::splitParts( const QgsPointSequence &splitLine, bool topologicalEditing )
428 : : {
429 : 0 : if ( !mLayer->isSpatial() )
430 : 0 : return QgsGeometry::InvalidBaseGeometry;
431 : :
432 : : double xMin, yMin, xMax, yMax;
433 : 0 : QgsRectangle bBox; //bounding box of the split line
434 : 0 : QgsGeometry::OperationResult returnCode = QgsGeometry::OperationResult::Success;
435 : : QgsGeometry::OperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
436 : 0 : int numberOfSplitParts = 0;
437 : :
438 : 0 : QgsFeatureIterator fit;
439 : :
440 : 0 : if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
441 : : {
442 : 0 : fit = mLayer->getSelectedFeatures();
443 : 0 : }
444 : : else //else consider all the feature that intersect the bounding box of the split line
445 : : {
446 : 0 : if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
447 : : {
448 : 0 : bBox.setXMinimum( xMin );
449 : 0 : bBox.setYMinimum( yMin );
450 : 0 : bBox.setXMaximum( xMax );
451 : 0 : bBox.setYMaximum( yMax );
452 : 0 : }
453 : : else
454 : : {
455 : 0 : return QgsGeometry::OperationResult::InvalidInputGeometryType;
456 : : }
457 : :
458 : 0 : if ( bBox.isEmpty() )
459 : : {
460 : : //if the bbox is a line, try to make a square out of it
461 : 0 : if ( bBox.width() == 0.0 && bBox.height() > 0 )
462 : : {
463 : 0 : bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
464 : 0 : bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
465 : 0 : }
466 : 0 : else if ( bBox.height() == 0.0 && bBox.width() > 0 )
467 : : {
468 : 0 : bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
469 : 0 : bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
470 : 0 : }
471 : : else
472 : : {
473 : : //If we have a single point, we still create a non-null box
474 : 0 : double bufferDistance = 0.000001;
475 : 0 : if ( mLayer->crs().isGeographic() )
476 : 0 : bufferDistance = 0.00000001;
477 : 0 : bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
478 : 0 : bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
479 : 0 : bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
480 : 0 : bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
481 : : }
482 : 0 : }
483 : :
484 : 0 : fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
485 : : }
486 : :
487 : 0 : QgsFeature feat;
488 : 0 : while ( fit.nextFeature( feat ) )
489 : : {
490 : 0 : QVector<QgsGeometry> newGeometries;
491 : 0 : QgsPointSequence topologyTestPoints;
492 : 0 : QgsGeometry featureGeom = feat.geometry();
493 : 0 : splitFunctionReturn = featureGeom.splitGeometry( splitLine, newGeometries, topologicalEditing, topologyTestPoints, false );
494 : :
495 : 0 : if ( splitFunctionReturn == QgsGeometry::OperationResult::Success && !newGeometries.isEmpty() )
496 : : {
497 : 0 : QgsGeometry newGeom( newGeometries.at( 0 ) );
498 : 0 : newGeom.convertToMultiType();
499 : :
500 : 0 : for ( int i = 1; i < newGeometries.size(); ++i )
501 : : {
502 : 0 : QgsGeometry part = newGeometries.at( i );
503 : 0 : part.convertToSingleType();
504 : 0 : newGeom.addPart( part );
505 : 0 : }
506 : :
507 : 0 : mLayer->changeGeometry( feat.id(), newGeom );
508 : :
509 : 0 : if ( topologicalEditing )
510 : : {
511 : 0 : QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
512 : 0 : for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
513 : : {
514 : 0 : addTopologicalPoints( *topol_it );
515 : 0 : }
516 : 0 : }
517 : 0 : ++numberOfSplitParts;
518 : 0 : }
519 : 0 : else if ( splitFunctionReturn != QgsGeometry::OperationResult::Success && splitFunctionReturn != QgsGeometry::OperationResult::NothingHappened )
520 : : {
521 : 0 : returnCode = splitFunctionReturn;
522 : 0 : }
523 : 0 : }
524 : :
525 : 0 : if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 && returnCode == QgsGeometry::Success )
526 : : {
527 : : //There is a selection but no feature has been split.
528 : : //Maybe user forgot that only the selected features are split
529 : 0 : returnCode = QgsGeometry::OperationResult::NothingHappened;
530 : 0 : }
531 : :
532 : 0 : return returnCode;
533 : 0 : }
534 : :
535 : :
536 : 0 : int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsGeometry &geom )
537 : : {
538 : 0 : if ( !mLayer->isSpatial() )
539 : 0 : return 1;
540 : :
541 : 0 : if ( geom.isNull() )
542 : : {
543 : 0 : return 1;
544 : : }
545 : :
546 : 0 : int returnVal = 0;
547 : :
548 : 0 : QgsAbstractGeometry::vertex_iterator it = geom.vertices_begin();
549 : 0 : while ( it != geom.vertices_end() )
550 : : {
551 : 0 : if ( addTopologicalPoints( *it ) != 0 )
552 : : {
553 : 0 : returnVal = 2;
554 : 0 : }
555 : 0 : ++it;
556 : : }
557 : :
558 : 0 : return returnVal;
559 : 0 : }
560 : :
561 : 0 : int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsPoint &p )
562 : : {
563 : 0 : if ( !mLayer->isSpatial() )
564 : 0 : return 1;
565 : :
566 : 0 : double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
567 : :
568 : : //work with a tolerance because coordinate projection may introduce some rounding
569 : 0 : double threshold = mLayer->geometryOptions()->geometryPrecision();
570 : :
571 : 0 : if ( qgsDoubleNear( threshold, 0.0 ) )
572 : : {
573 : 0 : threshold = 0.0000001;
574 : :
575 : 0 : if ( mLayer->crs().mapUnits() == QgsUnitTypes::DistanceMeters )
576 : : {
577 : 0 : threshold = 0.001;
578 : 0 : }
579 : 0 : else if ( mLayer->crs().mapUnits() == QgsUnitTypes::DistanceFeet )
580 : : {
581 : 0 : threshold = 0.0001;
582 : 0 : }
583 : 0 : }
584 : :
585 : 0 : QgsRectangle searchRect( p.x() - threshold, p.y() - threshold,
586 : 0 : p.x() + threshold, p.y() + threshold );
587 : 0 : double sqrSnappingTolerance = threshold * threshold;
588 : :
589 : 0 : QgsFeature f;
590 : 0 : QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest()
591 : 0 : .setFilterRect( searchRect )
592 : 0 : .setFlags( QgsFeatureRequest::ExactIntersect )
593 : 0 : .setNoAttributes() );
594 : :
595 : 0 : QMap<QgsFeatureId, QgsGeometry> features;
596 : 0 : QMap<QgsFeatureId, int> segments;
597 : :
598 : 0 : while ( fit.nextFeature( f ) )
599 : : {
600 : : int afterVertex;
601 : 0 : QgsPointXY snappedPoint;
602 : 0 : double sqrDistSegmentSnap = f.geometry().closestSegmentWithContext( p, snappedPoint, afterVertex, nullptr, segmentSearchEpsilon );
603 : 0 : if ( sqrDistSegmentSnap < sqrSnappingTolerance )
604 : : {
605 : 0 : segments[f.id()] = afterVertex;
606 : 0 : features[f.id()] = f.geometry();
607 : 0 : }
608 : : }
609 : :
610 : 0 : if ( segments.isEmpty() )
611 : 0 : return 2;
612 : :
613 : 0 : bool pointsAdded = false;
614 : 0 : for ( QMap<QgsFeatureId, int>::const_iterator it = segments.constBegin(); it != segments.constEnd(); ++it )
615 : : {
616 : 0 : QgsFeatureId fid = it.key();
617 : 0 : int segmentAfterVertex = it.value();
618 : 0 : QgsGeometry geom = features[fid];
619 : :
620 : : int atVertex, beforeVertex, afterVertex;
621 : : double sqrDistVertexSnap;
622 : 0 : geom.closestVertex( p, atVertex, beforeVertex, afterVertex, sqrDistVertexSnap );
623 : :
624 : 0 : if ( sqrDistVertexSnap < sqrSnappingTolerance )
625 : 0 : continue; // the vertex already exists - do not insert it
626 : :
627 : 0 : if ( !mLayer->insertVertex( p, fid, segmentAfterVertex ) )
628 : : {
629 : 0 : QgsDebugMsg( QStringLiteral( "failed to insert topo point" ) );
630 : 0 : }
631 : : else
632 : : {
633 : 0 : pointsAdded = true;
634 : : }
635 : 0 : }
636 : :
637 : 0 : return pointsAdded ? 0 : 2;
638 : 0 : }
639 : :
640 : 0 : int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsPointSequence &ps )
641 : : {
642 : 0 : if ( !mLayer->isSpatial() )
643 : 0 : return 1;
644 : :
645 : 0 : if ( ps.isEmpty() )
646 : : {
647 : 0 : return 1;
648 : : }
649 : :
650 : 0 : bool pointsAdded = false;
651 : :
652 : 0 : QgsPointSequence::const_iterator it = ps.constBegin();
653 : 0 : while ( it != ps.constEnd() )
654 : : {
655 : 0 : if ( addTopologicalPoints( *it ) == 0 )
656 : : {
657 : 0 : pointsAdded = true;
658 : 0 : }
659 : 0 : ++it;
660 : : }
661 : :
662 : 0 : return pointsAdded ? 0 : 2;
663 : 0 : }
664 : :
665 : 0 : int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsPointXY &p )
666 : : {
667 : 0 : return addTopologicalPoints( QgsPoint( p ) );
668 : 0 : }
669 : :
670 : :
671 : 0 : bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
672 : : {
673 : 0 : if ( list.empty() )
674 : : {
675 : 0 : return false;
676 : : }
677 : :
678 : 0 : xmin = std::numeric_limits<double>::max();
679 : 0 : xmax = -std::numeric_limits<double>::max();
680 : 0 : ymin = std::numeric_limits<double>::max();
681 : 0 : ymax = -std::numeric_limits<double>::max();
682 : :
683 : 0 : for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
684 : : {
685 : 0 : if ( it->x() < xmin )
686 : : {
687 : 0 : xmin = it->x();
688 : 0 : }
689 : 0 : if ( it->x() > xmax )
690 : : {
691 : 0 : xmax = it->x();
692 : 0 : }
693 : 0 : if ( it->y() < ymin )
694 : : {
695 : 0 : ymin = it->y();
696 : 0 : }
697 : 0 : if ( it->y() > ymax )
698 : : {
699 : 0 : ymax = it->y();
700 : 0 : }
701 : 0 : }
702 : :
703 : 0 : return true;
704 : 0 : }
|