QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgsvectorlayereditutils.cpp
Go to the documentation of this file.
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 ***************************************************************************/
16
17#include <limits>
18
19#include "qgis.h"
20#include "qgsabstractgeometry.h"
21#include "qgsfeatureiterator.h"
22#include "qgsgeometryoptions.h"
23#include "qgslinestring.h"
24#include "qgslogger.h"
25#include "qgspoint.h"
30#include "qgsvectorlayer.h"
32#include "qgsvectorlayerutils.h"
33#include "qgswkbtypes.h"
34
39
40bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
41{
42 if ( !mLayer->isSpatial() )
43 return false;
44
45 QgsFeature f;
46 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
47 return false; // geometry not found
48
49 QgsGeometry geometry = f.geometry();
50
51 geometry.insertVertex( x, y, beforeVertex );
52
53 mLayer->changeGeometry( atFeatureId, geometry );
54 return true;
55}
56
57bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
58{
59 if ( !mLayer->isSpatial() )
60 return false;
61
62 QgsFeature f;
63 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
64 return false; // geometry not found
65
66 QgsGeometry geometry = f.geometry();
67
68 geometry.insertVertex( point, beforeVertex );
69
70 mLayer->changeGeometry( atFeatureId, geometry );
71 return true;
72}
73
74bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
75{
76 QgsPoint p( x, y );
77 return moveVertex( p, atFeatureId, atVertex );
78}
79
80bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
81{
82 if ( !mLayer->isSpatial() )
83 return false;
84
85 QgsFeature f;
86 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
87 return false; // geometry not found
88
89 QgsGeometry geometry = f.geometry();
90
91 // If original point is not 3D but destination yes, check if it can be promoted
92 if ( p.is3D() && !geometry.constGet()->is3D() && QgsWkbTypes::hasZ( mLayer->wkbType() ) )
93 {
95 return false;
96 }
97
98 // If original point has not M-value but destination yes, check if it can be promoted
99 if ( p.isMeasure() && !geometry.constGet()->isMeasure() && QgsWkbTypes::hasM( mLayer->wkbType() ) )
100 {
102 return false;
103 }
104
105 if ( !geometry.moveVertex( p, atVertex ) )
106 return false;
107
108 return mLayer->changeGeometry( atFeatureId, geometry );
109}
110
112{
113 if ( !mLayer->isSpatial() )
115
116 QgsFeature f;
117 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
118 return Qgis::VectorEditResult::FetchFeatureFailed; // geometry not found
119
120 QgsGeometry geometry = f.geometry();
121
122 if ( !geometry.deleteVertex( vertex ) )
124
125 if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
126 {
127 //last vertex deleted, set geometry to null
128 geometry.set( nullptr );
129 }
130
131 mLayer->changeGeometry( featureId, geometry );
133}
134
135
136static
137Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ptr< QgsCurve > &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureIds *modifiedFeatureIds, bool firstOne = true )
138{
139
140 if ( !layer || !layer->isSpatial() )
141 {
143 }
144
145 if ( !ring )
146 {
148 }
149
150 if ( !ring->isClosed() )
151 {
153 }
154
155 if ( !layer->isValid() || !layer->editBuffer() || !layer->dataProvider() )
156 {
158 }
159
160 Qgis::GeometryOperationResult addRingReturnCode = Qgis::GeometryOperationResult::AddRingNotInExistingFeature; //default: return code for 'ring not inserted'
161 QgsFeature f;
162
164 if ( !targetFeatureIds.isEmpty() )
165 {
166 //check only specified features
167 fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) );
168 }
169 else
170 {
171 //check all intersecting features
172 QgsRectangle bBox = ring->boundingBox();
173 fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
174 }
175
176 //find valid features we can add the ring to
177 bool success = false;
178 while ( fit.nextFeature( f ) )
179 {
180 if ( !f.hasGeometry() )
181 continue;
182
183 //add ring takes ownership of ring, and deletes it if there's an error
184 QgsGeometry g = f.geometry();
185
186 if ( ring->orientation() != g.polygonOrientation() )
187 {
188 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
189 }
190 else
191 {
192 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->reversed() ) );
193 }
194 if ( addRingReturnCode == Qgis::GeometryOperationResult::Success )
195 {
196 success = true;
197 layer->changeGeometry( f.id(), g );
198 if ( modifiedFeatureIds )
199 {
200 modifiedFeatureIds->insert( f.id() );
201 if ( firstOne )
202 {
203 break;
204 }
205 }
206
207 }
208 }
209
210 return success ? Qgis::GeometryOperationResult::Success : addRingReturnCode;
211}
212
214double QgsVectorLayerEditUtils::getTopologicalSearchRadius( const QgsVectorLayer *layer )
215{
216 double threshold = layer->geometryOptions()->geometryPrecision();
217
218 if ( qgsDoubleNear( threshold, 0.0 ) )
219 {
220 threshold = 1e-8;
221
222 if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
223 {
224 threshold = 0.001;
225 }
226 else if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
227 {
228 threshold = 0.0001;
229 }
230 }
231 return threshold;
232}
233
234void QgsVectorLayerEditUtils::addTopologicalPointsToLayers( const QgsGeometry &geom, QgsVectorLayer *vlayer, const QList<QgsMapLayer *> &layers, const QString &toolName )
235{
236 QgsFeatureRequest request = QgsFeatureRequest().setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 );
237 QgsFeature f;
238
239 for ( QgsMapLayer *layer : layers )
240 {
241 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
242 if ( vectorLayer && vectorLayer->isEditable() && vectorLayer->isSpatial() && ( vectorLayer->geometryType() == Qgis::GeometryType::Line || vectorLayer->geometryType() == Qgis::GeometryType::Polygon ) )
243 {
244 // boundingBox() is cached, it doesn't matter calling it in the loop
245 QgsRectangle bbox = geom.boundingBox();
246 QgsCoordinateTransform ct;
247 if ( vectorLayer->crs() != vlayer->crs() )
248 {
249 ct = QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() );
250 try
251 {
252 bbox = ct.transformBoundingBox( bbox );
253 }
254 catch ( QgsCsException & )
255 {
256 QgsDebugError( QStringLiteral( "Bounding box transformation failed, skipping topological points for layer %1" ).arg( vlayer->id() ) );
257 continue;
258 }
259 }
260 bbox.grow( getTopologicalSearchRadius( vectorLayer ) );
261 request.setFilterRect( bbox );
262
263 // We check that there is actually at least one feature intersecting our geometry in the layer to avoid creating an empty edit command and calling costly addTopologicalPoint
264 if ( !vectorLayer->getFeatures( request ).nextFeature( f ) )
265 continue;
266
267 vectorLayer->beginEditCommand( QObject::tr( "Topological points added by '%1'" ).arg( toolName ) );
268
269 int returnValue = 2;
270 if ( vectorLayer->crs() != vlayer->crs() )
271 {
272 try
273 {
274 // transform digitized geometry from vlayer crs to vectorLayer crs and add topological points
275 QgsGeometry transformedGeom( geom );
276 transformedGeom.transform( ct );
277 returnValue = vectorLayer->addTopologicalPoints( transformedGeom );
278 }
279 catch ( QgsCsException & )
280 {
281 QgsDebugError( QStringLiteral( "transformation to vectorLayer coordinate failed" ) );
282 }
283 }
284 else
285 {
286 returnValue = vectorLayer->addTopologicalPoints( geom );
287 }
288
289 if ( returnValue == 0 )
290 {
291 vectorLayer->endEditCommand();
292 }
293 else
294 {
295 // the layer was not modified, leave the undo buffer intact
296 vectorLayer->destroyEditCommand();
297 }
298 }
299 }
300}
302
303Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
304{
306 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
307 {
308 l << QgsPoint( *it );
309 }
310 return addRing( l, targetFeatureIds, modifiedFeatureId );
311}
312
314{
315 QgsLineString *ringLine = new QgsLineString( ring );
316 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
317}
318
320{
321 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
322 if ( modifiedFeatureId )
323 {
324 QgsFeatureIds modifiedFeatureIds;
325 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, &modifiedFeatureIds, true );
326 if ( modifiedFeatureId && !modifiedFeatureIds.empty() )
327 *modifiedFeatureId = *modifiedFeatureIds.begin();
328 return result;
329 }
330 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
331}
332
334{
335
336 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
337 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
338}
339
340
341
343{
345 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
346 {
347 l << QgsPoint( *it );
348 }
349 return addPart( l, featureId );
350}
351
353{
354 if ( !mLayer->isSpatial() )
356
357 QgsGeometry geometry;
358 bool firstPart = false;
359 QgsFeature f;
360 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
362
363 if ( !f.hasGeometry() )
364 {
365 //no existing geometry, so adding first part to null geometry
366 firstPart = true;
367 }
368 else
369 {
370 geometry = f.geometry();
371 }
372
373 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
375 {
376 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
377 && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
378 {
379 //convert back to single part if required by layer
380 geometry.convertToSingleType();
381 }
382 mLayer->changeGeometry( featureId, geometry );
383 }
384 return errorCode;
385}
386
388{
389
390 if ( !mLayer->isSpatial() )
392
393 QgsGeometry geometry;
394 bool firstPart = false;
395 QgsFeature f;
396 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
398
399 if ( !f.hasGeometry() )
400 {
401 //no existing geometry, so adding first part to null geometry
402 firstPart = true;
403 }
404 else
405 {
406 geometry = f.geometry();
407 if ( ring->orientation() != geometry.polygonOrientation() )
408 {
409 ring = ring->reversed();
410 }
411 }
412 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( ring, mLayer->wkbType() );
413
415 {
416 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
417 && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
418 {
419 //convert back to single part if required by layer
420 geometry.convertToSingleType();
421 }
422 mLayer->changeGeometry( featureId, geometry );
423 }
424 return errorCode;
425}
426
427// TODO QGIS 4.0 -- this should return Qgis::GeometryOperationResult
428int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
429{
430 if ( !mLayer->isSpatial() )
431 return 1;
432
433 QgsFeature f;
434 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
435 return 1; //geometry not found
436
437 QgsGeometry geometry = f.geometry();
438
439 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
441 {
442 mLayer->changeGeometry( featureId, geometry );
443 }
444 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
445}
446
447Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
448{
449
451 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
452 {
453 l << QgsPoint( *it );
454 }
455 return splitFeatures( l, topologicalEditing );
456}
457
459{
460 QgsLineString lineString( splitLine );
461 QgsPointSequence topologyTestPoints;
462 bool preserveCircular = false;
463 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
464}
465
466Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
467{
468 if ( !mLayer->isSpatial() )
470
471 QgsRectangle bBox; //bounding box of the split line
473 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
474 int numberOfSplitFeatures = 0;
475
476 QgsFeatureIterator features;
477 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
478
479 // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
480 preserveCircular &= curve->hasCurvedSegments();
481
482 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
483 {
484 features = mLayer->getSelectedFeatures();
485 }
486 else //else consider all the feature that intersect the bounding box of the split line
487 {
488
489 bBox = curve->boundingBox();
490
491 if ( bBox.isEmpty() )
492 {
493 //if the bbox is a line, try to make a square out of it
494 if ( bBox.width() == 0.0 && bBox.height() > 0 )
495 {
496 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
497 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
498 }
499 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
500 {
501 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
502 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
503 }
504 else
505 {
506 //If we have a single point, we still create a non-null box
507 double bufferDistance = 0.000001;
508 if ( mLayer->crs().isGeographic() )
509 bufferDistance = 0.00000001;
510 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
511 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
512 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
513 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
514 }
515 }
516
517 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
518 }
519
521
522 const int fieldCount = mLayer->fields().count();
523
524 QgsFeature feat;
525 while ( features.nextFeature( feat ) )
526 {
527 if ( !feat.hasGeometry() )
528 {
529 continue;
530 }
531 QVector<QgsGeometry> newGeometries;
532 QgsPointSequence featureTopologyTestPoints;
533 const QgsGeometry originalGeom = feat.geometry();
534 QgsGeometry featureGeom = originalGeom;
535 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
536 topologyTestPoints.append( featureTopologyTestPoints );
537 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
538 {
539 //find largest geometry and give that to the original feature
540 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
541 double featureGeomSize = size( featureGeom );
542
543 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [ &size ]( const QgsGeometry & a, const QgsGeometry & b ) -> bool
544 {
545 return size( a ) < size( b );
546 } );
547
548 if ( size( *largestNewFeature ) > featureGeomSize )
549 {
550 QgsGeometry copy = *largestNewFeature;
551 *largestNewFeature = featureGeom;
552 featureGeom = copy;
553 }
554
555 //change this geometry
556 mLayer->changeGeometry( feat.id(), featureGeom );
557
558 //update any attributes for original feature which are set to GeometryRatio split policy
559 QgsAttributeMap attributeMap;
560 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
561 {
562 const QgsField field = mLayer->fields().at( fieldIdx );
563 switch ( field.splitPolicy() )
564 {
568 break;
569
571 {
572 if ( field.isNumeric() )
573 {
574 const double originalValue = feat.attribute( fieldIdx ).toDouble();
575
576 double originalSize = 0;
577
578 switch ( originalGeom.type() )
579 {
583 originalSize = 0;
584 break;
586 originalSize = originalGeom.length();
587 break;
589 originalSize = originalGeom.area();
590 break;
591 }
592
593 double newSize = 0;
594 switch ( featureGeom.type() )
595 {
599 newSize = 0;
600 break;
602 newSize = featureGeom.length();
603 break;
605 newSize = featureGeom.area();
606 break;
607 }
608
609 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
610 }
611 break;
612 }
613 }
614 }
615
616 if ( !attributeMap.isEmpty() )
617 {
618 mLayer->changeAttributeValues( feat.id(), attributeMap );
619 }
620
621 //insert new features
622 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
623 {
624 QgsAttributeMap attributeMap;
625 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
626 {
627 const QgsField field = mLayer->fields().at( fieldIdx );
628 // respect field split policy
629 switch ( field.splitPolicy() )
630 {
632 //do nothing - default values ​​are determined
633 break;
634
636 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
637 break;
638
640 {
641 if ( !field.isNumeric() )
642 {
643 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
644 }
645 else
646 {
647 const double originalValue = feat.attribute( fieldIdx ).toDouble();
648
649 double originalSize = 0;
650
651 switch ( originalGeom.type() )
652 {
656 originalSize = 0;
657 break;
659 originalSize = originalGeom.length();
660 break;
662 originalSize = originalGeom.area();
663 break;
664 }
665
666 double newSize = 0;
667 switch ( geom.type() )
668 {
672 newSize = 0;
673 break;
675 newSize = geom.length();
676 break;
678 newSize = geom.area();
679 break;
680 }
681
682 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
683 }
684 break;
685 }
686
688 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
689 break;
690 }
691 }
692
693 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
694 }
695
696 if ( topologicalEditing )
697 {
698 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
699 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
700 {
701 addTopologicalPoints( *topol_it );
702 }
703 }
704 ++numberOfSplitFeatures;
705 }
706 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
707 {
708 returnCode = splitFunctionReturn;
709 }
710 }
711
712 if ( !featuresDataToAdd.isEmpty() )
713 {
714 // finally create and add all bits of geometries cut off the original geometries
715 // (this is much faster than creating features one by one)
716 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
717 mLayer->addFeatures( featuresListToAdd );
718 }
719
720 if ( numberOfSplitFeatures == 0 )
721 {
723 }
724
725 return returnCode;
726}
727
728Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
729{
731 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
732 {
733 l << QgsPoint( *it );
734 }
735 return splitParts( l, topologicalEditing );
736}
737
739{
740 if ( !mLayer->isSpatial() )
742
743 double xMin, yMin, xMax, yMax;
744 QgsRectangle bBox; //bounding box of the split line
745 int numberOfSplitParts = 0;
746
748
749 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
750 {
751 fit = mLayer->getSelectedFeatures();
752 }
753 else //else consider all the feature that intersect the bounding box of the split line
754 {
755 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
756 {
757 bBox.setXMinimum( xMin );
758 bBox.setYMinimum( yMin );
759 bBox.setXMaximum( xMax );
760 bBox.setYMaximum( yMax );
761 }
762 else
763 {
765 }
766
767 if ( bBox.isEmpty() )
768 {
769 //if the bbox is a line, try to make a square out of it
770 if ( bBox.width() == 0.0 && bBox.height() > 0 )
771 {
772 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
773 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
774 }
775 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
776 {
777 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
778 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
779 }
780 else
781 {
782 //If we have a single point, we still create a non-null box
783 double bufferDistance = 0.000001;
784 if ( mLayer->crs().isGeographic() )
785 bufferDistance = 0.00000001;
786 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
787 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
788 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
789 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
790 }
791 }
792
793 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
794 }
795
796 QgsFeature feat;
797 while ( fit.nextFeature( feat ) )
798 {
799 QgsGeometry featureGeom = feat.geometry();
800
801 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
802 QVector<QgsGeometry> resultCollection;
803 QgsPointSequence topologyTestPoints;
804 for ( QgsGeometry part : geomCollection )
805 {
806 QVector<QgsGeometry> newGeometries;
807 QgsPointSequence partTopologyTestPoints;
808
809 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
810
811 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
812 {
813 for ( int i = 0; i < newGeometries.size(); ++i )
814 {
815 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
816 }
817
818 topologyTestPoints.append( partTopologyTestPoints );
819
820 ++numberOfSplitParts;
821 }
822 // Note: For multilinestring layers, when the split line does not intersect the feature part,
823 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
824 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened ||
826 {
827 // Add part as is
828 resultCollection.append( part );
829 }
830 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
831 {
832 return splitFunctionReturn;
833 }
834 }
835
836 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
837 mLayer->changeGeometry( feat.id(), newGeom ) ;
838
839 if ( topologicalEditing )
840 {
841 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
842 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
843 {
844 addTopologicalPoints( *topol_it );
845 }
846 }
847
848 }
849 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
850 {
851 //There is a selection but no feature has been split.
852 //Maybe user forgot that only the selected features are split
854 }
855
857}
858
859
861{
862 if ( !mLayer->isSpatial() )
863 return 1;
864
865 if ( geom.isNull() )
866 {
867 return 1;
868 }
869
870 bool pointsAdded = false;
871
873 while ( it != geom.vertices_end() )
874 {
875 if ( addTopologicalPoints( *it ) == 0 )
876 {
877 pointsAdded = true;
878 }
879 ++it;
880 }
881
882 return pointsAdded ? 0 : 2;
883}
884
886{
887 if ( !mLayer->isSpatial() )
888 return 1;
889
890 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
891
892 //work with a tolerance because coordinate projection may introduce some rounding
893 double threshold = getTopologicalSearchRadius( mLayer );
894
895 QgsRectangle searchRect( p, p, false );
896 searchRect.grow( threshold );
897
898 QgsFeature f;
899 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest()
900 .setFilterRect( searchRect )
902 .setNoAttributes() );
903
904 bool pointsAdded = false;
905 while ( fit.nextFeature( f ) )
906 {
907 QgsGeometry geom = f.geometry();
908 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
909 {
910 pointsAdded = true;
911 mLayer->changeGeometry( f.id(), geom );
912 }
913 }
914
915 return pointsAdded ? 0 : 2;
916}
917
919{
920 if ( !mLayer->isSpatial() )
921 return 1;
922
923 if ( ps.isEmpty() )
924 {
925 return 1;
926 }
927
928 bool pointsAdded = false;
929
930 QgsPointSequence::const_iterator it = ps.constBegin();
931 while ( it != ps.constEnd() )
932 {
933 if ( addTopologicalPoints( *it ) == 0 )
934 {
935 pointsAdded = true;
936 }
937 ++it;
938 }
939
940 return pointsAdded ? 0 : 2;
941}
942
947
948bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
949{
950 errorMessage.clear();
951
952 if ( mergeFeatureIds.isEmpty() )
953 {
954 errorMessage = QObject::tr( "List of features to merge is empty" );
955 return false;
956 }
957
958 QgsAttributeMap newAttributes;
959 for ( int i = 0; i < mergeAttributes.count(); ++i )
960 {
961 QVariant val = mergeAttributes.at( i );
962
963 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider &&
964 mLayer->dataProvider() &&
965 mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
966
967 // convert to destination data type
968 QString errorMessageConvertCompatible;
969 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
970 {
971 if ( errorMessage.isEmpty() )
972 errorMessage = QObject::tr( "Could not store value '%1' in field of type %2: %3" ).arg( mergeAttributes.at( i ).toString(), mLayer->fields().at( i ).typeName(), errorMessageConvertCompatible );
973 }
974 newAttributes[ i ] = val;
975 }
976
977 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
978
979 // Delete other features but the target feature
980 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
981 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
982 {
983 if ( *feature_it != targetFeatureId )
984 mLayer->deleteFeature( *feature_it );
985 }
986
987 // Modify target feature or create a new one if invalid
988 QgsGeometry mergeGeometry = unionGeometry;
989 if ( targetFeatureId == FID_NULL )
990 {
991 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
992 mLayer->addFeature( mergeFeature );
993 }
994 else
995 {
996 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
997 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
998 }
999
1000 mLayer->endEditCommand();
1001
1002 mLayer->triggerRepaint();
1003
1004 return true;
1005}
1006
1007bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
1008{
1009 if ( list.empty() )
1010 {
1011 return false;
1012 }
1013
1014 xmin = std::numeric_limits<double>::max();
1015 xmax = -std::numeric_limits<double>::max();
1016 ymin = std::numeric_limits<double>::max();
1017 ymax = -std::numeric_limits<double>::max();
1018
1019 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
1020 {
1021 if ( it->x() < xmin )
1022 {
1023 xmin = it->x();
1024 }
1025 if ( it->x() > xmax )
1026 {
1027 xmax = it->x();
1028 }
1029 if ( it->y() < ymin )
1030 {
1031 ymin = it->y();
1032 }
1033 if ( it->y() > ymax )
1034 {
1035 ymax = it->y();
1036 }
1037 }
1038
1039 return true;
1040}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2042
@ AddPartSelectedGeometryNotFound
The selected geometry cannot be found.
Definition qgis.h:2052
@ InvalidInputGeometryType
The input geometry (ring, part, split line, etc.) has not the correct geometry type.
Definition qgis.h:2046
@ Success
Operation succeeded.
Definition qgis.h:2043
@ AddRingNotInExistingFeature
The input ring doesn't have any existing ring to fit into.
Definition qgis.h:2058
@ AddRingNotClosed
The input ring is not closed.
Definition qgis.h:2055
@ NothingHappened
Nothing happened, without any error.
Definition qgis.h:2044
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition qgis.h:2045
@ LayerNotEditable
Cannot edit layer.
Definition qgis.h:2050
@ Feet
Imperial feet.
Definition qgis.h:5016
@ Meters
Meters.
Definition qgis.h:5014
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2198
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2196
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
Definition qgis.h:3909
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
Definition qgis.h:3910
@ DefaultValue
Use default field value.
Definition qgis.h:3907
@ Duplicate
Duplicate original value.
Definition qgis.h:3908
@ Point
Points.
Definition qgis.h:359
@ Line
Lines.
Definition qgis.h:360
@ Polygon
Polygons.
Definition qgis.h:361
@ Unknown
Unknown types.
Definition qgis.h:362
@ Null
No geometry.
Definition qgis.h:363
@ Provider
Field originates from the underlying data provider of the vector layer.
Definition qgis.h:1706
VectorEditResult
Specifies the result of a vector layer edit operation.
Definition qgis.h:1818
@ EmptyGeometry
Edit operation resulted in an empty geometry.
Definition qgis.h:1820
@ Success
Edit operation was successful.
Definition qgis.h:1819
@ FetchFeatureFailed
Unable to fetch requested feature.
Definition qgis.h:1822
@ EditFailed
Edit operation failed.
Definition qgis.h:1821
@ InvalidLayer
Edit failed due to invalid layer.
Definition qgis.h:1823
The vertex_iterator class provides an STL-style iterator for vertices.
virtual bool addZValue(double zValue=0)=0
Adds a z-dimension to the geometry, initialized to a preset value.
bool isMeasure() const
Returns true if the geometry contains m values.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
A vector of attributes.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
Definition qgscurve.cpp:287
virtual QgsCurve * reversed() const =0
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:54
Qgis::FieldDomainSplitPolicy splitPolicy() const
Returns the field's split policy, which indicates how field values should be handled during a split o...
Definition qgsfield.cpp:762
bool isNumeric
Definition qgsfield.h:57
double geometryPrecision() const
The precision in which geometries on this layer should be saved.
A geometry is the spatial representation of a feature.
bool deleteVertex(int atVertex)
Deletes the vertex at the given position number and item (first number is index 0).
double length() const
Returns the planar, 2-dimensional length of geometry.
bool addTopologicalPoint(const QgsPoint &point, double snappingTolerance=1e-8, double segmentSearchEpsilon=1e-12)
Adds a vertex to the segment which intersect point but don't already have a vertex there.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
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.
bool insertVertex(double x, double y, int beforeVertex)
Insert a new vertex before the given vertex index, ring and item (first number is index 0) If the req...
bool convertToSingleType()
Converts multi type geometry into single type geometry e.g.
Qgis::GeometryOperationResult addRing(const QVector< QgsPointXY > &ring)
Adds a new ring to this geometry.
Qgis::GeometryType type
double area() const
Returns the planar, 2-dimensional area of the geometry.
Qgis::AngularDirection polygonOrientation() const
Returns the orientation of the polygon.
void set(QgsAbstractGeometry *geometry)
Sets the underlying geometry store.
QgsAbstractGeometry::vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult addPartV2(const QVector< QgsPointXY > &points, Qgis::WkbType wkbType=Qgis::WkbType::Unknown)
Adds a new part to a the geometry.
Qgis::GeometryOperationResult translate(double dx, double dy, double dz=0.0, double dm=0.0)
Translates this geometry by dx, dy, dz and dm.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitGeometry(const QVector< QgsPointXY > &splitLine, QVector< QgsGeometry > &newGeometries, bool topological, QVector< QgsPointXY > &topologyTestPoints, bool splitFeature=true)
Splits this geometry according to a given line.
bool moveVertex(double x, double y, int atVertex)
Moves the vertex at the given position number and item (first number is index 0) to the given coordin...
QgsAbstractGeometry::vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
Line string geometry type, with support for z-dimension and m-values.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:87
QString id
Definition qgsmaplayer.h:83
QgsCoordinateTransformContext transformContext() const
Returns the layer data provider coordinate transform context or a default transform context if the la...
Represents a 2D point.
Definition qgspointxy.h:60
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double yMaximum
static const QgsSettingsEntryDouble * settingsDigitizingDefaultMValue
Settings entry digitizing default m value.
static const QgsSettingsEntryDouble * settingsDigitizingDefaultZValue
Settings entry digitizing default z value.
Represents a default, "not-specified" value for a feature attribute.
int translateFeature(QgsFeatureId featureId, double dx, double dy)
Translates feature by dx, dy.
bool mergeFeatures(const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage)
Merge features into a single one.
QgsVectorLayerEditUtils(QgsVectorLayer *layer)
bool insertVertex(double x, double y, QgsFeatureId atFeatureId, int beforeVertex)
Insert a new vertex before the given vertex number, in the given ring, item (first number is index 0)...
Q_DECL_DEPRECATED Qgis::GeometryOperationResult addPart(const QVector< QgsPointXY > &ring, QgsFeatureId featureId)
Adds a new part polygon to a multipart feature.
Qgis::VectorEditResult deleteVertex(QgsFeatureId featureId, int vertex)
Deletes a vertex from a feature.
Qgis::GeometryOperationResult addRingV2(QgsCurve *ring, const QgsFeatureIds &targetFeatureIds=QgsFeatureIds(), QgsFeatureIds *modifiedFeatureIds=nullptr)
Adds a ring to polygon/multipolygon features.
int addTopologicalPoints(const QgsGeometry &geom)
Adds topological points for every vertex of the geometry.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitParts(const QVector< QgsPointXY > &splitLine, bool topologicalEditing=false)
Splits parts cut by the given line.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitFeatures(const QVector< QgsPointXY > &splitLine, bool topologicalEditing=false)
Splits features cut by the given line.
bool moveVertex(double x, double y, QgsFeatureId atFeatureId, int atVertex)
Moves the vertex at the given position number, ring and item (first number is index 0),...
Q_DECL_DEPRECATED Qgis::GeometryOperationResult addRing(const QVector< QgsPointXY > &ring, const QgsFeatureIds &targetFeatureIds=QgsFeatureIds(), QgsFeatureId *modifiedFeatureId=nullptr)
Adds a ring to polygon/multipolygon features.
Encapsulate geometry and attributes for new features, to be passed to createFeatures.
QList< QgsVectorLayerUtils::QgsFeatureData > QgsFeaturesDataList
Alias for list of QgsFeatureData.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
static QgsFeatureList createFeatures(const QgsVectorLayer *layer, const QgsFeaturesDataList &featuresData, QgsExpressionContext *context=nullptr)
Creates a set of new features ready for insertion into a layer.
Represents a vector layer which manages a vector based dataset.
bool isEditable() const final
Returns true if the provider is in editing mode.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
void endEditCommand()
Finish edit command and add it to undo/redo stack.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
QgsGeometryOptions * geometryOptions() const
Configuration and logic to apply automatically on any edit happening on this layer.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
int addTopologicalPoints(const QgsGeometry &geom)
Adds topological points for every vertex of the geometry.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
Q_INVOKABLE bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
static Q_INVOKABLE bool isSingleType(Qgis::WkbType type)
Returns true if the WKB type is a single type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
QVector< QgsPoint > QgsPointSequence
QMap< int, QVariant > QgsAttributeMap
QList< QgsFeature > QgsFeatureList
#define FID_NULL
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugError(str)
Definition qgslogger.h:57