QGIS API Documentation 3.41.0-Master (45a0abf3bec)
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
19#include "qgsfeatureiterator.h"
21#include "qgslinestring.h"
22#include "qgslogger.h"
23#include "qgspoint.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"
32
33#include <limits>
34
35
40
41bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
42{
43 if ( !mLayer->isSpatial() )
44 return false;
45
46 QgsFeature f;
47 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
48 return false; // geometry not found
49
50 QgsGeometry geometry = f.geometry();
51
52 geometry.insertVertex( x, y, beforeVertex );
53
54 mLayer->changeGeometry( atFeatureId, geometry );
55 return true;
56}
57
58bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
59{
60 if ( !mLayer->isSpatial() )
61 return false;
62
63 QgsFeature f;
64 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
65 return false; // geometry not found
66
67 QgsGeometry geometry = f.geometry();
68
69 geometry.insertVertex( point, beforeVertex );
70
71 mLayer->changeGeometry( atFeatureId, geometry );
72 return true;
73}
74
75bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
76{
77 QgsPoint p( x, y );
78 return moveVertex( p, atFeatureId, atVertex );
79}
80
81bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
82{
83 if ( !mLayer->isSpatial() )
84 return false;
85
86 QgsFeature f;
87 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
88 return false; // geometry not found
89
90 QgsGeometry geometry = f.geometry();
91
92 // If original point is not 3D but destination yes, check if it can be promoted
93 if ( p.is3D() && !geometry.constGet()->is3D() && QgsWkbTypes::hasZ( mLayer->wkbType() ) )
94 {
96 return false;
97 }
98
99 // If original point has not M-value but destination yes, check if it can be promoted
100 if ( p.isMeasure() && !geometry.constGet()->isMeasure() && QgsWkbTypes::hasM( mLayer->wkbType() ) )
101 {
103 return false;
104 }
105
106 if ( !geometry.moveVertex( p, atVertex ) )
107 return false;
108
109 return mLayer->changeGeometry( atFeatureId, geometry );
110}
111
113{
114 if ( !mLayer->isSpatial() )
116
117 QgsFeature f;
118 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
119 return Qgis::VectorEditResult::FetchFeatureFailed; // geometry not found
120
121 QgsGeometry geometry = f.geometry();
122
123 if ( !geometry.deleteVertex( vertex ) )
125
126 if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
127 {
128 //last vertex deleted, set geometry to null
129 geometry.set( nullptr );
130 }
131
132 mLayer->changeGeometry( featureId, geometry );
134}
135
136
137static
138Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ptr< QgsCurve > &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureIds *modifiedFeatureIds, bool firstOne = true )
139{
140
141 if ( !layer || !layer->isSpatial() )
142 {
144 }
145
146 if ( !ring )
147 {
149 }
150
151 if ( !ring->isClosed() )
152 {
154 }
155
156 if ( !layer->isValid() || !layer->editBuffer() || !layer->dataProvider() )
157 {
159 }
160
161 Qgis::GeometryOperationResult addRingReturnCode = Qgis::GeometryOperationResult::AddRingNotInExistingFeature; //default: return code for 'ring not inserted'
162 QgsFeature f;
163
165 if ( !targetFeatureIds.isEmpty() )
166 {
167 //check only specified features
168 fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) );
169 }
170 else
171 {
172 //check all intersecting features
173 QgsRectangle bBox = ring->boundingBox();
174 fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
175 }
176
177 //find valid features we can add the ring to
178 bool success = false;
179 while ( fit.nextFeature( f ) )
180 {
181 if ( !f.hasGeometry() )
182 continue;
183
184 //add ring takes ownership of ring, and deletes it if there's an error
185 QgsGeometry g = f.geometry();
186
187 if ( ring->orientation() != g.polygonOrientation() )
188 {
189 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
190 }
191 else
192 {
193 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->reversed() ) );
194 }
195 if ( addRingReturnCode == Qgis::GeometryOperationResult::Success )
196 {
197 success = true;
198 layer->changeGeometry( f.id(), g );
199 if ( modifiedFeatureIds )
200 {
201 modifiedFeatureIds->insert( f.id() );
202 if ( firstOne )
203 {
204 break;
205 }
206 }
207
208 }
209 }
210
211 return success ? Qgis::GeometryOperationResult::Success : addRingReturnCode;
212}
213
214Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
215{
217 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
218 {
219 l << QgsPoint( *it );
220 }
221 return addRing( l, targetFeatureIds, modifiedFeatureId );
222}
223
225{
226 QgsLineString *ringLine = new QgsLineString( ring );
227 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
228}
229
231{
232 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
233 if ( modifiedFeatureId )
234 {
235 QgsFeatureIds *modifiedFeatureIds = new QgsFeatureIds;
236 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, true );
237 *modifiedFeatureId = *modifiedFeatureIds->begin();
238 return result;
239 }
240 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
241}
242
244{
245
246 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
247 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
248}
249
250
251
253{
255 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
256 {
257 l << QgsPoint( *it );
258 }
259 return addPart( l, featureId );
260}
261
263{
264 if ( !mLayer->isSpatial() )
266
267 QgsGeometry geometry;
268 bool firstPart = false;
269 QgsFeature f;
270 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
272
273 if ( !f.hasGeometry() )
274 {
275 //no existing geometry, so adding first part to null geometry
276 firstPart = true;
277 }
278 else
279 {
280 geometry = f.geometry();
281 }
282
283 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
285 {
286 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
288 {
289 //convert back to single part if required by layer
290 geometry.convertToSingleType();
291 }
292 mLayer->changeGeometry( featureId, geometry );
293 }
294 return errorCode;
295}
296
298{
299
300 if ( !mLayer->isSpatial() )
302
303 QgsGeometry geometry;
304 bool firstPart = false;
305 QgsFeature f;
306 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
308
309 if ( !f.hasGeometry() )
310 {
311 //no existing geometry, so adding first part to null geometry
312 firstPart = true;
313 }
314 else
315 {
316 geometry = f.geometry();
317 if ( ring->orientation() != geometry.polygonOrientation() )
318 {
319 ring = ring->reversed();
320 }
321 }
322 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( ring, mLayer->wkbType() );
323
325 {
326 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
328 {
329 //convert back to single part if required by layer
330 geometry.convertToSingleType();
331 }
332 mLayer->changeGeometry( featureId, geometry );
333 }
334 return errorCode;
335}
336
337// TODO QGIS 4.0 -- this should return Qgis::GeometryOperationResult
338int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
339{
340 if ( !mLayer->isSpatial() )
341 return 1;
342
343 QgsFeature f;
344 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
345 return 1; //geometry not found
346
347 QgsGeometry geometry = f.geometry();
348
349 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
351 {
352 mLayer->changeGeometry( featureId, geometry );
353 }
354 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
355}
356
357Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
358{
359
361 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
362 {
363 l << QgsPoint( *it );
364 }
365 return splitFeatures( l, topologicalEditing );
366}
367
369{
370 QgsLineString lineString( splitLine );
371 QgsPointSequence topologyTestPoints;
372 bool preserveCircular = false;
373 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
374}
375
376Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
377{
378 if ( !mLayer->isSpatial() )
380
381 QgsRectangle bBox; //bounding box of the split line
383 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
384 int numberOfSplitFeatures = 0;
385
386 QgsFeatureIterator features;
387 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
388
389 // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
390 preserveCircular &= curve->hasCurvedSegments();
391
392 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
393 {
394 features = mLayer->getSelectedFeatures();
395 }
396 else //else consider all the feature that intersect the bounding box of the split line
397 {
398
399 bBox = curve->boundingBox();
400
401 if ( bBox.isEmpty() )
402 {
403 //if the bbox is a line, try to make a square out of it
404 if ( bBox.width() == 0.0 && bBox.height() > 0 )
405 {
406 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
407 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
408 }
409 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
410 {
411 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
412 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
413 }
414 else
415 {
416 //If we have a single point, we still create a non-null box
417 double bufferDistance = 0.000001;
418 if ( mLayer->crs().isGeographic() )
419 bufferDistance = 0.00000001;
420 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
421 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
422 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
423 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
424 }
425 }
426
427 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
428 }
429
431
432 const int fieldCount = mLayer->fields().count();
433
434 QgsFeature feat;
435 while ( features.nextFeature( feat ) )
436 {
437 if ( !feat.hasGeometry() )
438 {
439 continue;
440 }
441 QVector<QgsGeometry> newGeometries;
442 QgsPointSequence featureTopologyTestPoints;
443 const QgsGeometry originalGeom = feat.geometry();
444 QgsGeometry featureGeom = originalGeom;
445 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
446 topologyTestPoints.append( featureTopologyTestPoints );
447 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
448 {
449 //change this geometry
450 mLayer->changeGeometry( feat.id(), featureGeom );
451
452 //update any attributes for original feature which are set to GeometryRatio split policy
453 QgsAttributeMap attributeMap;
454 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
455 {
456 const QgsField field = mLayer->fields().at( fieldIdx );
457 switch ( field.splitPolicy() )
458 {
462 break;
463
465 {
466 if ( field.isNumeric() )
467 {
468 const double originalValue = feat.attribute( fieldIdx ).toDouble();
469
470 double originalSize = 0;
471
472 switch ( originalGeom.type() )
473 {
477 originalSize = 0;
478 break;
480 originalSize = originalGeom.length();
481 break;
483 originalSize = originalGeom.area();
484 break;
485 }
486
487 double newSize = 0;
488 switch ( featureGeom.type() )
489 {
493 newSize = 0;
494 break;
496 newSize = featureGeom.length();
497 break;
499 newSize = featureGeom.area();
500 break;
501 }
502
503 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
504 }
505 break;
506 }
507 }
508 }
509
510 if ( !attributeMap.isEmpty() )
511 {
512 mLayer->changeAttributeValues( feat.id(), attributeMap );
513 }
514
515 //insert new features
516 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
517 {
518 QgsAttributeMap attributeMap;
519 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
520 {
521 const QgsField field = mLayer->fields().at( fieldIdx );
522 // respect field split policy
523 switch ( field.splitPolicy() )
524 {
526 //do nothing - default values ​​are determined
527 break;
528
530 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
531 break;
532
534 {
535 if ( !field.isNumeric() )
536 {
537 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
538 }
539 else
540 {
541 const double originalValue = feat.attribute( fieldIdx ).toDouble();
542
543 double originalSize = 0;
544
545 switch ( originalGeom.type() )
546 {
550 originalSize = 0;
551 break;
553 originalSize = originalGeom.length();
554 break;
556 originalSize = originalGeom.area();
557 break;
558 }
559
560 double newSize = 0;
561 switch ( geom.type() )
562 {
566 newSize = 0;
567 break;
569 newSize = geom.length();
570 break;
572 newSize = geom.area();
573 break;
574 }
575
576 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
577 }
578 break;
579 }
580
582 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
583 break;
584 }
585 }
586
587 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
588 }
589
590 if ( topologicalEditing )
591 {
592 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
593 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
594 {
595 addTopologicalPoints( *topol_it );
596 }
597 }
598 ++numberOfSplitFeatures;
599 }
600 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
601 {
602 returnCode = splitFunctionReturn;
603 }
604 }
605
606 if ( !featuresDataToAdd.isEmpty() )
607 {
608 // finally create and add all bits of geometries cut off the original geometries
609 // (this is much faster than creating features one by one)
610 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
611 mLayer->addFeatures( featuresListToAdd );
612 }
613
614 if ( numberOfSplitFeatures == 0 )
615 {
617 }
618
619 return returnCode;
620}
621
622Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
623{
625 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
626 {
627 l << QgsPoint( *it );
628 }
629 return splitParts( l, topologicalEditing );
630}
631
633{
634 if ( !mLayer->isSpatial() )
636
637 double xMin, yMin, xMax, yMax;
638 QgsRectangle bBox; //bounding box of the split line
639 int numberOfSplitParts = 0;
640
642
643 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
644 {
645 fit = mLayer->getSelectedFeatures();
646 }
647 else //else consider all the feature that intersect the bounding box of the split line
648 {
649 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
650 {
651 bBox.setXMinimum( xMin );
652 bBox.setYMinimum( yMin );
653 bBox.setXMaximum( xMax );
654 bBox.setYMaximum( yMax );
655 }
656 else
657 {
659 }
660
661 if ( bBox.isEmpty() )
662 {
663 //if the bbox is a line, try to make a square out of it
664 if ( bBox.width() == 0.0 && bBox.height() > 0 )
665 {
666 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
667 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
668 }
669 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
670 {
671 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
672 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
673 }
674 else
675 {
676 //If we have a single point, we still create a non-null box
677 double bufferDistance = 0.000001;
678 if ( mLayer->crs().isGeographic() )
679 bufferDistance = 0.00000001;
680 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
681 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
682 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
683 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
684 }
685 }
686
687 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
688 }
689
690 QgsFeature feat;
691 while ( fit.nextFeature( feat ) )
692 {
693 QgsGeometry featureGeom = feat.geometry();
694
695 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
696 QVector<QgsGeometry> resultCollection;
697 QgsPointSequence topologyTestPoints;
698 for ( QgsGeometry part : geomCollection )
699 {
700 QVector<QgsGeometry> newGeometries;
701 QgsPointSequence partTopologyTestPoints;
702
703 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
704
705 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
706 {
707 for ( int i = 0; i < newGeometries.size(); ++i )
708 {
709 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
710 }
711
712 topologyTestPoints.append( partTopologyTestPoints );
713
714 ++numberOfSplitParts;
715 }
716 // Note: For multilinestring layers, when the split line does not intersect the feature part,
717 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
718 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened ||
720 {
721 // Add part as is
722 resultCollection.append( part );
723 }
724 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
725 {
726 return splitFunctionReturn;
727 }
728 }
729
730 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
731 mLayer->changeGeometry( feat.id(), newGeom ) ;
732
733 if ( topologicalEditing )
734 {
735 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
736 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
737 {
738 addTopologicalPoints( *topol_it );
739 }
740 }
741
742 }
743 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
744 {
745 //There is a selection but no feature has been split.
746 //Maybe user forgot that only the selected features are split
748 }
749
751}
752
753
755{
756 if ( !mLayer->isSpatial() )
757 return 1;
758
759 if ( geom.isNull() )
760 {
761 return 1;
762 }
763
764 bool pointsAdded = false;
765
767 while ( it != geom.vertices_end() )
768 {
769 if ( addTopologicalPoints( *it ) == 0 )
770 {
771 pointsAdded = true;
772 }
773 ++it;
774 }
775
776 return pointsAdded ? 0 : 2;
777}
778
780{
781 if ( !mLayer->isSpatial() )
782 return 1;
783
784 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
785
786 //work with a tolerance because coordinate projection may introduce some rounding
787 double threshold = mLayer->geometryOptions()->geometryPrecision();
788
789 if ( qgsDoubleNear( threshold, 0.0 ) )
790 {
791 threshold = 1e-8;
792
793 if ( mLayer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
794 {
795 threshold = 0.001;
796 }
797 else if ( mLayer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
798 {
799 threshold = 0.0001;
800 }
801 }
802
803 QgsRectangle searchRect( p, p, false );
804 searchRect.grow( threshold );
805
806 QgsFeature f;
808 .setFilterRect( searchRect )
810 .setNoAttributes() );
811
812 bool pointsAdded = false;
813 while ( fit.nextFeature( f ) )
814 {
815 QgsGeometry geom = f.geometry();
816 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
817 {
818 pointsAdded = true;
819 mLayer->changeGeometry( f.id(), geom );
820 }
821 }
822
823 return pointsAdded ? 0 : 2;
824}
825
827{
828 if ( !mLayer->isSpatial() )
829 return 1;
830
831 if ( ps.isEmpty() )
832 {
833 return 1;
834 }
835
836 bool pointsAdded = false;
837
838 QgsPointSequence::const_iterator it = ps.constBegin();
839 while ( it != ps.constEnd() )
840 {
841 if ( addTopologicalPoints( *it ) == 0 )
842 {
843 pointsAdded = true;
844 }
845 ++it;
846 }
847
848 return pointsAdded ? 0 : 2;
849}
850
855
856bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
857{
858 errorMessage.clear();
859
860 if ( mergeFeatureIds.isEmpty() )
861 {
862 errorMessage = QObject::tr( "List of features to merge is empty" );
863 return false;
864 }
865
866 QgsAttributeMap newAttributes;
867 for ( int i = 0; i < mergeAttributes.count(); ++i )
868 {
869 QVariant val = mergeAttributes.at( i );
870
871 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider &&
872 mLayer->dataProvider() &&
873 mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
874
875 // convert to destination data type
876 QString errorMessageConvertCompatible;
877 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
878 {
879 if ( errorMessage.isEmpty() )
880 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 );
881 }
882 newAttributes[ i ] = val;
883 }
884
885 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
886
887 // Delete other features but the target feature
888 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
889 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
890 {
891 if ( *feature_it != targetFeatureId )
892 mLayer->deleteFeature( *feature_it );
893 }
894
895 // Modify target feature or create a new one if invalid
896 QgsGeometry mergeGeometry = unionGeometry;
897 if ( targetFeatureId == FID_NULL )
898 {
899 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
900 mLayer->addFeature( mergeFeature );
901 }
902 else
903 {
904 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
905 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
906 }
907
908 mLayer->endEditCommand();
909
910 mLayer->triggerRepaint();
911
912 return true;
913}
914
915bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
916{
917 if ( list.empty() )
918 {
919 return false;
920 }
921
922 xmin = std::numeric_limits<double>::max();
923 xmax = -std::numeric_limits<double>::max();
924 ymin = std::numeric_limits<double>::max();
925 ymax = -std::numeric_limits<double>::max();
926
927 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
928 {
929 if ( it->x() < xmin )
930 {
931 xmin = it->x();
932 }
933 if ( it->x() > xmax )
934 {
935 xmax = it->x();
936 }
937 if ( it->y() < ymin )
938 {
939 ymin = it->y();
940 }
941 if ( it->y() > ymax )
942 {
943 ymax = it->y();
944 }
945 }
946
947 return true;
948}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:1889
@ AddPartSelectedGeometryNotFound
The selected geometry cannot be found.
@ InvalidInputGeometryType
The input geometry (ring, part, split line, etc.) has not the correct geometry type.
@ Success
Operation succeeded.
@ AddRingNotInExistingFeature
The input ring doesn't have any existing ring to fit into.
@ AddRingNotClosed
The input ring is not closed.
@ NothingHappened
Nothing happened, without any error.
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
@ LayerNotEditable
Cannot edit layer.
@ Feet
Imperial feet.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
@ DefaultValue
Use default field value.
@ Duplicate
Duplicate original value.
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
@ Provider
Field originates from the underlying data provider of the vector layer.
VectorEditResult
Specifies the result of a vector layer edit operation.
Definition qgis.h:1665
@ EmptyGeometry
Edit operation resulted in an empty geometry.
@ Success
Edit operation was successful.
@ FetchFeatureFailed
Unable to fetch requested feature.
@ EditFailed
Edit operation failed.
@ InvalidLayer
Edit failed due to invalid layer.
The vertex_iterator class provides 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.
Abstract base class for curved geometry type.
Definition qgscurve.h:35
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
Definition qgscurve.cpp:286
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.
This class wraps a request for features to a vector layer (or directly its vector data provider).
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:53
QString typeName() const
Gets the field type.
Definition qgsfield.cpp:161
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition qgsfield.cpp:473
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:755
bool isNumeric
Definition qgsfield.h:56
int count
Definition qgsfields.h:50
Qgis::FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
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.
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:83
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
A class to represent 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() const
Returns the x minimum value (left side of rectangle).
void setYMinimum(double y)
Set the minimum y value.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
void setXMinimum(double x)
Set the minimum x value.
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
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.
bool isEmpty() const
Returns true if the rectangle has no area.
double height() const
Returns the height of the rectangle.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
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.
virtual bool doesStrictFeatureTypeCheck() const
Returns true if the provider is strict about the type of inserted features (e.g.
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index.
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 data sets.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
bool addFeatures(QgsFeatureList &features, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a list of features to the sink.
Q_INVOKABLE bool deleteFeature(QgsFeatureId fid, QgsVectorLayer::DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
void endEditCommand()
Finish edit command and add it to undo/redo stack.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
QgsGeometryOptions * geometryOptions() const
Configuration and logic to apply automatically on any edit happening on this layer.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
QgsFeatureIterator getSelectedFeatures(QgsFeatureRequest request=QgsFeatureRequest()) const
Returns an iterator of the selected features.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
Q_INVOKABLE bool changeAttributeValues(QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues=QgsAttributeMap(), bool skipDefaultValues=false, QgsVectorLayerToolsContext *context=nullptr)
Changes attributes' values for a feature (but does not immediately commit the changes).
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 bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static bool isSingleType(Qgis::WkbType type)
Returns true if the WKB type is a single type.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5958
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