QGIS API Documentation 4.3.0-Master (7d9941090cd)
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 "qgscurvepolygon.h"
22#include "qgsfeatureiterator.h"
23#include "qgsgeometryoptions.h"
24#include "qgslinestring.h"
25#include "qgslogger.h"
26#include "qgspoint.h"
31#include "qgsvectorlayer.h"
33#include "qgsvectorlayerutils.h"
34#include "qgswkbtypes.h"
35
36#include <QString>
37
38using namespace Qt::StringLiterals;
39
43
44bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
45{
46 if ( !mLayer->isSpatial() )
47 return false;
48
49 QgsFeature f;
50 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
51 return false; // geometry not found
52
53 QgsGeometry geometry = f.geometry();
54
55 geometry.insertVertex( x, y, beforeVertex );
56
57 mLayer->changeGeometry( atFeatureId, geometry );
58 return true;
59}
60
61bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
62{
63 if ( !mLayer->isSpatial() )
64 return false;
65
66 QgsFeature f;
67 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
68 return false; // geometry not found
69
70 QgsGeometry geometry = f.geometry();
71
72 geometry.insertVertex( point, beforeVertex );
73
74 mLayer->changeGeometry( atFeatureId, geometry );
75 return true;
76}
77
78bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
79{
80 QgsPoint p( x, y );
81 return moveVertex( p, atFeatureId, atVertex );
82}
83
84bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
85{
86 if ( !mLayer->isSpatial() )
87 return false;
88
89 QgsFeature f;
90 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
91 return false; // geometry not found
92
93 QgsGeometry geometry = f.geometry();
94
95 // If original point is not 3D but destination yes, check if it can be promoted
96 if ( p.is3D() && !geometry.constGet()->is3D() && QgsWkbTypes::hasZ( mLayer->wkbType() ) )
97 {
99 return false;
100 }
101
102 // If original point has not M-value but destination yes, check if it can be promoted
103 if ( p.isMeasure() && !geometry.constGet()->isMeasure() && QgsWkbTypes::hasM( mLayer->wkbType() ) )
104 {
106 return false;
107 }
108
109 if ( !geometry.moveVertex( p, atVertex ) )
110 return false;
111
112 return mLayer->changeGeometry( atFeatureId, geometry );
113}
114
116{
117 return deleteVertices( featureId, { vertex } );
118}
119
121{
122 if ( !mLayer->isSpatial() )
124
125 QgsFeature f;
126 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
128
129 QgsGeometry geometry = f.geometry();
130
131 if ( !geometry.deleteVertices( vertices ) )
133
134 if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
135 {
136 // Last vertex deleted, set geometry to null
137 geometry.set( nullptr );
138 }
139
140 mLayer->changeGeometry( featureId, geometry );
142}
143
144static Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ptr< QgsCurve > &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureIds *modifiedFeatureIds, bool firstOne = true )
145{
146 if ( !layer || !layer->isSpatial() )
147 {
149 }
150
151 if ( !ring )
152 {
154 }
155
156 if ( !ring->isClosed() )
157 {
159 }
160
161 if ( !layer->isValid() || !layer->editBuffer() || !layer->dataProvider() )
162 {
164 }
165
166 Qgis::GeometryOperationResult addRingReturnCode = Qgis::GeometryOperationResult::AddRingNotInExistingFeature; //default: return code for 'ring not inserted'
167 QgsFeature f;
168
170 if ( !targetFeatureIds.isEmpty() )
171 {
172 //check only specified features
173 fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) );
174 }
175 else
176 {
177 //check all intersecting features
178 QgsRectangle bBox = ring->boundingBox();
179 fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
180 }
181
182 //find valid features we can add the ring to
183 bool success = false;
184 while ( fit.nextFeature( f ) )
185 {
186 if ( !f.hasGeometry() )
187 continue;
188
189 //add ring takes ownership of ring, and deletes it if there's an error
190 QgsGeometry g = f.geometry();
191
192 if ( ring->orientation() != g.polygonOrientation() )
193 {
194 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
195 }
196 else
197 {
198 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->reversed() ) );
199 }
200 if ( addRingReturnCode == Qgis::GeometryOperationResult::Success )
201 {
202 success = true;
203 layer->changeGeometry( f.id(), g );
204 if ( modifiedFeatureIds )
205 {
206 modifiedFeatureIds->insert( f.id() );
207 if ( firstOne )
208 {
209 break;
210 }
211 }
212 }
213 }
214
215 return success ? Qgis::GeometryOperationResult::Success : addRingReturnCode;
216}
217
219double QgsVectorLayerEditUtils::getTopologicalSearchRadius( const QgsVectorLayer *layer )
220{
221 double threshold = layer->geometryOptions()->geometryPrecision();
222
223 if ( qgsDoubleNear( threshold, 0.0 ) )
224 {
225 threshold = 1e-8;
226
227 if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
228 {
229 threshold = 0.001;
230 }
231 else if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
232 {
233 threshold = 0.0001;
234 }
235 }
236 return threshold;
237}
238
239void QgsVectorLayerEditUtils::addTopologicalPointsToLayers( const QgsGeometry &geom, QgsVectorLayer *vlayer, const QList<QgsMapLayer *> &layers, const QString &toolName )
240{
241 QgsFeatureRequest request = QgsFeatureRequest().setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 );
242 QgsFeature f;
243
244 for ( QgsMapLayer *layer : layers )
245 {
246 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
247 if ( vectorLayer && vectorLayer->isEditable() && vectorLayer->isSpatial() && ( vectorLayer->geometryType() == Qgis::GeometryType::Line || vectorLayer->geometryType() == Qgis::GeometryType::Polygon ) )
248 {
249 // boundingBox() is cached, it doesn't matter calling it in the loop
250 QgsRectangle bbox = geom.boundingBox();
251 QgsCoordinateTransform ct;
252 if ( vectorLayer->crs() != vlayer->crs() )
253 {
254 ct = QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() );
255 try
256 {
257 bbox = ct.transformBoundingBox( bbox );
258 }
259 catch ( QgsCsException & )
260 {
261 QgsDebugError( u"Bounding box transformation failed, skipping topological points for layer %1"_s.arg( vlayer->id() ) );
262 continue;
263 }
264 }
265 bbox.grow( getTopologicalSearchRadius( vectorLayer ) );
266 request.setFilterRect( bbox );
267
268 // 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
269 if ( !vectorLayer->getFeatures( request ).nextFeature( f ) )
270 continue;
271
272 vectorLayer->beginEditCommand( QObject::tr( "Topological points added by '%1'" ).arg( toolName ) );
273
274 int returnValue = 2;
275 if ( vectorLayer->crs() != vlayer->crs() )
276 {
277 try
278 {
279 // transform digitized geometry from vlayer crs to vectorLayer crs and add topological points
280 QgsGeometry transformedGeom( geom );
281 transformedGeom.transform( ct );
282 returnValue = vectorLayer->addTopologicalPoints( transformedGeom );
283 }
284 catch ( QgsCsException & )
285 {
286 QgsDebugError( u"transformation to vectorLayer coordinate failed"_s );
287 }
288 }
289 else
290 {
291 returnValue = vectorLayer->addTopologicalPoints( geom );
292 }
293
294 if ( returnValue == 0 )
295 {
296 vectorLayer->endEditCommand();
297 }
298 else
299 {
300 // the layer was not modified, leave the undo buffer intact
301 vectorLayer->destroyEditCommand();
302 }
303 }
304 }
305}
307
308Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
309{
311 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
312 {
313 l << QgsPoint( *it );
314 }
315 return addRing( l, targetFeatureIds, modifiedFeatureId );
316}
317
319{
320 QgsLineString *ringLine = new QgsLineString( ring );
321 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
322}
323
325{
326 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
327 if ( modifiedFeatureId )
328 {
329 QgsFeatureIds modifiedFeatureIds;
330 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, &modifiedFeatureIds, true );
331 if ( modifiedFeatureId && !modifiedFeatureIds.empty() )
332 *modifiedFeatureId = *modifiedFeatureIds.begin();
333 return result;
334 }
335 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
336}
337
339{
340 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
341 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
342}
343
344
346{
348 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
349 {
350 l << QgsPoint( *it );
351 }
352 return addPart( l, featureId );
353}
354
356{
357 if ( !mLayer->isSpatial() )
359
360 QgsGeometry geometry;
361 bool firstPart = false;
362 QgsFeature f;
363 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
365
366 if ( !f.hasGeometry() )
367 {
368 //no existing geometry, so adding first part to null geometry
369 firstPart = true;
370 }
371 else
372 {
373 geometry = f.geometry();
374 }
375
376 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
378 {
379 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
380 {
381 //convert back to single part if required by layer
382 geometry.convertToSingleType();
383 }
384 mLayer->changeGeometry( featureId, geometry );
385 }
386 return errorCode;
387}
388
390{
391 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
392
393 if ( !mLayer->isSpatial() )
395
396 QgsGeometry geometry;
397 bool firstPart = false;
398 QgsFeature f;
399 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
401
402 if ( !f.hasGeometry() )
403 {
404 //no existing geometry, so adding first part to null geometry
405 firstPart = true;
406 }
407 else
408 {
409 geometry = f.geometry();
410 if ( mLayer->geometryType() == Qgis::GeometryType::Polygon && uniquePtrRing->orientation() != geometry.polygonOrientation() )
411 {
412 uniquePtrRing.reset( uniquePtrRing->reversed() );
413 }
414 }
415 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( uniquePtrRing.release(), mLayer->wkbType() );
416
418 {
419 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
420 {
421 //convert back to single part if required by layer
422 geometry.convertToSingleType();
423 }
424 mLayer->changeGeometry( featureId, geometry );
425 }
426 return errorCode;
427}
428
430{
431 std::unique_ptr<QgsCurvePolygon> uniquePtrPoly( polygon );
432
433 if ( !mLayer->isSpatial() )
435
436 if ( mLayer->geometryType() != Qgis::GeometryType::Polygon )
438
439 QgsGeometry geometry;
440 bool firstPart = false;
441 QgsFeature f;
442 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
444
445 if ( !f.hasGeometry() )
446 {
447 //no existing geometry, so adding first part to null geometry
448 firstPart = true;
449 }
450 else
451 {
452 geometry = f.geometry();
453 switch ( geometry.polygonOrientation() )
454 {
456 polygon->forceClockwise();
457 break;
459 polygon->forceCounterClockwise();
460 break;
462 break;
463 }
464 }
465 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( uniquePtrPoly.release(), mLayer->wkbType() );
466
468 {
469 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
470 {
471 //convert back to single part if required by layer
472 geometry.convertToSingleType();
473 }
474 mLayer->changeGeometry( featureId, geometry );
475 }
476 return errorCode;
477}
478
479// TODO QGIS 5.0 -- this should return Qgis::GeometryOperationResult
480int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
481{
482 if ( !mLayer->isSpatial() )
483 return 1;
484
485 QgsFeature f;
486 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
487 return 1; //geometry not found
488
489 QgsGeometry geometry = f.geometry();
490
491 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
493 {
494 mLayer->changeGeometry( featureId, geometry );
495 }
496 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
497}
498
499Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
500{
502 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
503 {
504 l << QgsPoint( *it );
505 }
506 return splitFeatures( l, topologicalEditing );
507}
508
510{
511 QgsLineString lineString( splitLine );
512 QgsPointSequence topologyTestPoints;
513 bool preserveCircular = false;
514 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
515}
516
517Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
518{
519 if ( !mLayer->isSpatial() )
521
522 QgsRectangle bBox; //bounding box of the split line
524 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
525 int numberOfSplitFeatures = 0;
526
527 QgsFeatureIterator features;
528 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
529
530 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
531 {
532 features = mLayer->getSelectedFeatures();
533 }
534 else //else consider all the feature that intersect the bounding box of the split line
535 {
536 bBox = curve->boundingBox();
537
538 if ( bBox.isEmpty() )
539 {
540 //if the bbox is a line, try to make a square out of it
541 if ( bBox.width() == 0.0 && bBox.height() > 0 )
542 {
543 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
544 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
545 }
546 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
547 {
548 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
549 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
550 }
551 else
552 {
553 //If we have a single point, we still create a non-null box
554 double bufferDistance = 0.000001;
555 if ( mLayer->crs().isGeographic() )
556 bufferDistance = 0.00000001;
557 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
558 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
559 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
560 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
561 }
562 }
563
564 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
565 }
566
568
569 const int fieldCount = mLayer->fields().count();
570 const bool splitCurveContainsCurves = curve->hasCurvedSegments();
571
572 QgsFeature feat;
573 while ( features.nextFeature( feat ) )
574 {
575 if ( !feat.hasGeometry() )
576 {
577 continue;
578 }
579 QVector<QgsGeometry> newGeometries;
580 QgsPointSequence featureTopologyTestPoints;
581 const QgsGeometry originalGeom = feat.geometry();
582 QgsGeometry featureGeom = originalGeom;
583
584 // For the current geometry, make sure preserveCircular is not forced, unless
585 // the input param is true and one of the involved geometries contains curves
586 bool preserveCircularForGeom = preserveCircular;
587 preserveCircularForGeom &= ( splitCurveContainsCurves || featureGeom.constGet()->hasCurvedSegments() );
588 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircularForGeom, topologicalEditing, featureTopologyTestPoints );
589
590 topologyTestPoints.append( featureTopologyTestPoints );
591 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
592 {
593 //find largest geometry and give that to the original feature
594 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
595 double featureGeomSize = size( featureGeom );
596
597 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [&size]( const QgsGeometry &a, const QgsGeometry &b ) -> bool {
598 return size( a ) < size( b );
599 } );
600
601 if ( size( *largestNewFeature ) > featureGeomSize )
602 {
603 QgsGeometry copy = *largestNewFeature;
604 *largestNewFeature = featureGeom;
605 featureGeom = copy;
606 }
607
608 //change this geometry
609 mLayer->changeGeometry( feat.id(), featureGeom );
610
611 //update any attributes for original feature which are set to GeometryRatio split policy
612 QgsAttributeMap attributeMap;
613 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
614 {
615 const QgsField field = mLayer->fields().at( fieldIdx );
616 switch ( field.splitPolicy() )
617 {
621 break;
622
624 {
625 if ( field.isNumeric() )
626 {
627 const double originalValue = feat.attribute( fieldIdx ).toDouble();
628
629 double originalSize = 0;
630
631 switch ( originalGeom.type() )
632 {
636 originalSize = 0;
637 break;
639 originalSize = originalGeom.length();
640 break;
642 originalSize = originalGeom.area();
643 break;
644 }
645
646 double newSize = 0;
647 switch ( featureGeom.type() )
648 {
652 newSize = 0;
653 break;
655 newSize = featureGeom.length();
656 break;
658 newSize = featureGeom.area();
659 break;
660 }
661
662 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
663 }
664 break;
665 }
666 }
667 }
668
669 if ( !attributeMap.isEmpty() )
670 {
671 mLayer->changeAttributeValues( feat.id(), attributeMap );
672 }
673
674 //insert new features
675 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
676 {
677 QgsAttributeMap attributeMap;
678 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
679 {
680 const QgsField field = mLayer->fields().at( fieldIdx );
681 // respect field split policy
682 switch ( field.splitPolicy() )
683 {
685 //do nothing - default values ​​are determined
686 break;
687
689 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
690 break;
691
693 {
694 if ( !field.isNumeric() )
695 {
696 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
697 }
698 else
699 {
700 const double originalValue = feat.attribute( fieldIdx ).toDouble();
701
702 double originalSize = 0;
703
704 switch ( originalGeom.type() )
705 {
709 originalSize = 0;
710 break;
712 originalSize = originalGeom.length();
713 break;
715 originalSize = originalGeom.area();
716 break;
717 }
718
719 double newSize = 0;
720 switch ( geom.type() )
721 {
725 newSize = 0;
726 break;
728 newSize = geom.length();
729 break;
731 newSize = geom.area();
732 break;
733 }
734
735 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
736 }
737 break;
738 }
739
741 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
742 break;
743 }
744 }
745
746 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
747 }
748
749 if ( topologicalEditing )
750 {
751 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
752 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
753 {
754 addTopologicalPoints( *topol_it );
755 }
756 }
757 ++numberOfSplitFeatures;
758 }
759 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
760 {
761 returnCode = splitFunctionReturn;
762 }
763 }
764
765 if ( !featuresDataToAdd.isEmpty() )
766 {
767 // finally create and add all bits of geometries cut off the original geometries
768 // (this is much faster than creating features one by one)
769 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
770 mLayer->addFeatures( featuresListToAdd );
771 }
772
773 if ( numberOfSplitFeatures == 0 )
774 {
776 }
777
778 return returnCode;
779}
780
781Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
782{
784 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
785 {
786 l << QgsPoint( *it );
787 }
788 return splitParts( l, topologicalEditing );
789}
790
792{
793 if ( !mLayer->isSpatial() )
795
796 double xMin, yMin, xMax, yMax;
797 QgsRectangle bBox; //bounding box of the split line
798 int numberOfSplitParts = 0;
799
801
802 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
803 {
804 fit = mLayer->getSelectedFeatures();
805 }
806 else //else consider all the feature that intersect the bounding box of the split line
807 {
808 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
809 {
810 bBox.setXMinimum( xMin );
811 bBox.setYMinimum( yMin );
812 bBox.setXMaximum( xMax );
813 bBox.setYMaximum( yMax );
814 }
815 else
816 {
818 }
819
820 if ( bBox.isEmpty() )
821 {
822 //if the bbox is a line, try to make a square out of it
823 if ( bBox.width() == 0.0 && bBox.height() > 0 )
824 {
825 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
826 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
827 }
828 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
829 {
830 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
831 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
832 }
833 else
834 {
835 //If we have a single point, we still create a non-null box
836 double bufferDistance = 0.000001;
837 if ( mLayer->crs().isGeographic() )
838 bufferDistance = 0.00000001;
839 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
840 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
841 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
842 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
843 }
844 }
845
846 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
847 }
848
849 QgsFeature feat;
850 while ( fit.nextFeature( feat ) )
851 {
852 QgsGeometry featureGeom = feat.geometry();
853
854 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
855 QVector<QgsGeometry> resultCollection;
856 QgsPointSequence topologyTestPoints;
857 for ( QgsGeometry part : geomCollection )
858 {
859 QVector<QgsGeometry> newGeometries;
860 QgsPointSequence partTopologyTestPoints;
861
862 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
863
864 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
865 {
866 for ( int i = 0; i < newGeometries.size(); ++i )
867 {
868 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
869 }
870
871 topologyTestPoints.append( partTopologyTestPoints );
872
873 ++numberOfSplitParts;
874 }
875 // Note: For multilinestring layers, when the split line does not intersect the feature part,
876 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
877 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened || splitFunctionReturn == Qgis::GeometryOperationResult::InvalidBaseGeometry )
878 {
879 // Add part as is
880 resultCollection.append( part );
881 }
882 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
883 {
884 return splitFunctionReturn;
885 }
886 }
887
888 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
889 mLayer->changeGeometry( feat.id(), newGeom );
890
891 if ( topologicalEditing )
892 {
893 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
894 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
895 {
896 addTopologicalPoints( *topol_it );
897 }
898 }
899 }
900 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
901 {
902 //There is a selection but no feature has been split.
903 //Maybe user forgot that only the selected features are split
905 }
906
908}
909
910
912{
913 if ( !mLayer->isSpatial() )
914 return 1;
915
916 if ( geom.isNull() )
917 {
918 return 1;
919 }
920
921 bool pointsAdded = false;
922
924 while ( it != geom.vertices_end() )
925 {
926 if ( addTopologicalPoints( *it ) == 0 )
927 {
928 pointsAdded = true;
929 }
930 ++it;
931 }
932
933 return pointsAdded ? 0 : 2;
934}
935
937{
938 if ( !mLayer->isSpatial() )
939 return 1;
940
941 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
942
943 //work with a tolerance because coordinate projection may introduce some rounding
944 double threshold = getTopologicalSearchRadius( mLayer );
945
946 QgsRectangle searchRect( p, p, false );
947 searchRect.grow( threshold );
948
949 QgsFeature f;
950 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( searchRect ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ).setNoAttributes() );
951
952 bool pointsAdded = false;
953 while ( fit.nextFeature( f ) )
954 {
955 QgsGeometry geom = f.geometry();
956 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
957 {
958 pointsAdded = true;
959 mLayer->changeGeometry( f.id(), geom );
960 }
961 }
962
963 return pointsAdded ? 0 : 2;
964}
965
967{
968 if ( !mLayer->isSpatial() )
969 return 1;
970
971 if ( ps.isEmpty() )
972 {
973 return 1;
974 }
975
976 bool pointsAdded = false;
977
978 QgsPointSequence::const_iterator it = ps.constBegin();
979 while ( it != ps.constEnd() )
980 {
981 if ( addTopologicalPoints( *it ) == 0 )
982 {
983 pointsAdded = true;
984 }
985 ++it;
986 }
987
988 return pointsAdded ? 0 : 2;
989}
990
995
997 const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage
998)
999{
1000 errorMessage.clear();
1001
1002 if ( mergeFeatureIds.isEmpty() )
1003 {
1004 errorMessage = QObject::tr( "List of features to merge is empty" );
1005 return false;
1006 }
1007
1008 QgsAttributeMap newAttributes;
1009 for ( int i = 0; i < mergeAttributes.count(); ++i )
1010 {
1011 QVariant val = mergeAttributes.at( i );
1012
1013 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider
1014 && mLayer->dataProvider()
1015 && mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
1016
1017 // convert to destination data type
1018 QString errorMessageConvertCompatible;
1019 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
1020 {
1021 if ( errorMessage.isEmpty() )
1022 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 );
1023 }
1024 newAttributes[i] = val;
1025 }
1026
1027 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
1028
1029 // Delete other features but the target feature
1030 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
1031 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
1032 {
1033 if ( *feature_it != targetFeatureId )
1034 mLayer->deleteFeature( *feature_it );
1035 }
1036
1037 // Modify target feature or create a new one if invalid
1038 QgsGeometry mergeGeometry = unionGeometry;
1039 if ( targetFeatureId == FID_NULL )
1040 {
1041 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
1042 mLayer->addFeature( mergeFeature );
1043 }
1044 else
1045 {
1046 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
1047 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
1048 }
1049
1050 mLayer->endEditCommand();
1051
1052 mLayer->triggerRepaint();
1053
1054 return true;
1055}
1056
1057bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
1058{
1059 if ( list.empty() )
1060 {
1061 return false;
1062 }
1063
1064 xmin = std::numeric_limits<double>::max();
1065 xmax = -std::numeric_limits<double>::max();
1066 ymin = std::numeric_limits<double>::max();
1067 ymax = -std::numeric_limits<double>::max();
1068
1069 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
1070 {
1071 if ( it->x() < xmin )
1072 {
1073 xmin = it->x();
1074 }
1075 if ( it->x() > xmax )
1076 {
1077 xmax = it->x();
1078 }
1079 if ( it->y() < ymin )
1080 {
1081 ymin = it->y();
1082 }
1083 if ( it->y() > ymax )
1084 {
1085 ymax = it->y();
1086 }
1087 }
1088
1089 return true;
1090}
@ NoOrientation
Unknown orientation or sentinel value.
Definition qgis.h:3651
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3650
@ Clockwise
Clockwise direction.
Definition qgis.h:3649
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2192
@ AddPartSelectedGeometryNotFound
The selected geometry cannot be found.
Definition qgis.h:2202
@ InvalidInputGeometryType
The input geometry (ring, part, split line, etc.) has not the correct geometry type.
Definition qgis.h:2196
@ Success
Operation succeeded.
Definition qgis.h:2193
@ AddRingNotInExistingFeature
The input ring doesn't have any existing ring to fit into.
Definition qgis.h:2208
@ AddRingNotClosed
The input ring is not closed.
Definition qgis.h:2205
@ NothingHappened
Nothing happened, without any error.
Definition qgis.h:2194
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition qgis.h:2195
@ LayerNotEditable
Cannot edit layer.
Definition qgis.h:2200
@ Feet
Imperial feet.
Definition qgis.h:5488
@ Meters
Meters.
Definition qgis.h:5486
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2362
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2360
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
Definition qgis.h:4128
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
Definition qgis.h:4129
@ DefaultValue
Use default field value.
Definition qgis.h:4126
@ Duplicate
Duplicate original value.
Definition qgis.h:4127
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
@ Unknown
Unknown types.
Definition qgis.h:383
@ Null
No geometry.
Definition qgis.h:384
@ Provider
Field originates from the underlying data provider of the vector layer.
Definition qgis.h:1856
VectorEditResult
Specifies the result of a vector layer edit operation.
Definition qgis.h:1968
@ EmptyGeometry
Edit operation resulted in an empty geometry.
Definition qgis.h:1970
@ Success
Edit operation was successful.
Definition qgis.h:1969
@ FetchFeatureFailed
Unable to fetch requested feature.
Definition qgis.h:1972
@ EditFailed
Edit operation failed.
Definition qgis.h:1971
@ InvalidLayer
Edit failed due to invalid layer.
Definition qgis.h:1973
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.
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.
Curve polygon geometry type.
void forceCounterClockwise()
Forces the polygon to respect the exterior ring is counter-clockwise, interior rings are clockwise co...
void forceClockwise()
Forces the polygon to respect the exterior ring is clockwise, interior rings are counter-clockwise co...
Abstract base class for curved geometry type.
Definition qgscurve.h:36
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:60
QgsFeatureId id
Definition qgsfeature.h:63
QgsGeometry geometry
Definition qgsfeature.h:66
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:56
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:769
bool isNumeric
Definition qgsfield.h:59
double geometryPrecision() const
The precision in which geometries on this layer should be saved.
A geometry is the spatial representation of a feature.
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.
bool deleteVertices(const QSet< int > &atVertices)
Deletes vertices at the given positions (first number is index 0).
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:90
QString id
Definition qgsmaplayer.h:86
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:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
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.
Qgis::VectorEditResult deleteVertices(QgsFeatureId featureId, const QSet< int > &vertices)
Deletes a set of vertices from a feature.
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.
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:7462
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:71