QGIS API Documentation 3.41.0-Master (d2aaa9c6e02)
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
215double QgsVectorLayerEditUtils::getTopologicalSearchRadius( const QgsVectorLayer *layer )
216{
217 double threshold = layer->geometryOptions()->geometryPrecision();
218
219 if ( qgsDoubleNear( threshold, 0.0 ) )
220 {
221 threshold = 1e-8;
222
223 if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
224 {
225 threshold = 0.001;
226 }
227 else if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
228 {
229 threshold = 0.0001;
230 }
231 }
232 return threshold;
233}
235
236Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
237{
239 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
240 {
241 l << QgsPoint( *it );
242 }
243 return addRing( l, targetFeatureIds, modifiedFeatureId );
244}
245
247{
248 QgsLineString *ringLine = new QgsLineString( ring );
249 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
250}
251
253{
254 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
255 if ( modifiedFeatureId )
256 {
257 QgsFeatureIds modifiedFeatureIds;
258 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, &modifiedFeatureIds, true );
259 if ( modifiedFeatureId && !modifiedFeatureIds.empty() )
260 *modifiedFeatureId = *modifiedFeatureIds.begin();
261 return result;
262 }
263 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
264}
265
267{
268
269 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
270 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
271}
272
273
274
276{
278 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
279 {
280 l << QgsPoint( *it );
281 }
282 return addPart( l, featureId );
283}
284
286{
287 if ( !mLayer->isSpatial() )
289
290 QgsGeometry geometry;
291 bool firstPart = false;
292 QgsFeature f;
293 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
295
296 if ( !f.hasGeometry() )
297 {
298 //no existing geometry, so adding first part to null geometry
299 firstPart = true;
300 }
301 else
302 {
303 geometry = f.geometry();
304 }
305
306 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
308 {
309 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
311 {
312 //convert back to single part if required by layer
313 geometry.convertToSingleType();
314 }
315 mLayer->changeGeometry( featureId, geometry );
316 }
317 return errorCode;
318}
319
321{
322
323 if ( !mLayer->isSpatial() )
325
326 QgsGeometry geometry;
327 bool firstPart = false;
328 QgsFeature f;
329 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
331
332 if ( !f.hasGeometry() )
333 {
334 //no existing geometry, so adding first part to null geometry
335 firstPart = true;
336 }
337 else
338 {
339 geometry = f.geometry();
340 if ( ring->orientation() != geometry.polygonOrientation() )
341 {
342 ring = ring->reversed();
343 }
344 }
345 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( ring, mLayer->wkbType() );
346
348 {
349 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
351 {
352 //convert back to single part if required by layer
353 geometry.convertToSingleType();
354 }
355 mLayer->changeGeometry( featureId, geometry );
356 }
357 return errorCode;
358}
359
360// TODO QGIS 4.0 -- this should return Qgis::GeometryOperationResult
361int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
362{
363 if ( !mLayer->isSpatial() )
364 return 1;
365
366 QgsFeature f;
367 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
368 return 1; //geometry not found
369
370 QgsGeometry geometry = f.geometry();
371
372 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
374 {
375 mLayer->changeGeometry( featureId, geometry );
376 }
377 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
378}
379
380Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
381{
382
384 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
385 {
386 l << QgsPoint( *it );
387 }
388 return splitFeatures( l, topologicalEditing );
389}
390
392{
393 QgsLineString lineString( splitLine );
394 QgsPointSequence topologyTestPoints;
395 bool preserveCircular = false;
396 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
397}
398
399Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
400{
401 if ( !mLayer->isSpatial() )
403
404 QgsRectangle bBox; //bounding box of the split line
406 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
407 int numberOfSplitFeatures = 0;
408
409 QgsFeatureIterator features;
410 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
411
412 // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
413 preserveCircular &= curve->hasCurvedSegments();
414
415 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
416 {
417 features = mLayer->getSelectedFeatures();
418 }
419 else //else consider all the feature that intersect the bounding box of the split line
420 {
421
422 bBox = curve->boundingBox();
423
424 if ( bBox.isEmpty() )
425 {
426 //if the bbox is a line, try to make a square out of it
427 if ( bBox.width() == 0.0 && bBox.height() > 0 )
428 {
429 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
430 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
431 }
432 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
433 {
434 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
435 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
436 }
437 else
438 {
439 //If we have a single point, we still create a non-null box
440 double bufferDistance = 0.000001;
441 if ( mLayer->crs().isGeographic() )
442 bufferDistance = 0.00000001;
443 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
444 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
445 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
446 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
447 }
448 }
449
450 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
451 }
452
454
455 const int fieldCount = mLayer->fields().count();
456
457 QgsFeature feat;
458 while ( features.nextFeature( feat ) )
459 {
460 if ( !feat.hasGeometry() )
461 {
462 continue;
463 }
464 QVector<QgsGeometry> newGeometries;
465 QgsPointSequence featureTopologyTestPoints;
466 const QgsGeometry originalGeom = feat.geometry();
467 QgsGeometry featureGeom = originalGeom;
468 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
469 topologyTestPoints.append( featureTopologyTestPoints );
470 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
471 {
472 //find largest geometry and give that to the original feature
473 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
474 double featureGeomSize = size( featureGeom );
475
476 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [ &size ]( const QgsGeometry & a, const QgsGeometry & b ) -> bool
477 {
478 return size( a ) < size( b );
479 } );
480
481 if ( size( *largestNewFeature ) > featureGeomSize )
482 {
483 QgsGeometry copy = *largestNewFeature;
484 *largestNewFeature = featureGeom;
485 featureGeom = copy;
486 }
487
488 //change this geometry
489 mLayer->changeGeometry( feat.id(), featureGeom );
490
491 //update any attributes for original feature which are set to GeometryRatio split policy
492 QgsAttributeMap attributeMap;
493 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
494 {
495 const QgsField field = mLayer->fields().at( fieldIdx );
496 switch ( field.splitPolicy() )
497 {
501 break;
502
504 {
505 if ( field.isNumeric() )
506 {
507 const double originalValue = feat.attribute( fieldIdx ).toDouble();
508
509 double originalSize = 0;
510
511 switch ( originalGeom.type() )
512 {
516 originalSize = 0;
517 break;
519 originalSize = originalGeom.length();
520 break;
522 originalSize = originalGeom.area();
523 break;
524 }
525
526 double newSize = 0;
527 switch ( featureGeom.type() )
528 {
532 newSize = 0;
533 break;
535 newSize = featureGeom.length();
536 break;
538 newSize = featureGeom.area();
539 break;
540 }
541
542 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
543 }
544 break;
545 }
546 }
547 }
548
549 if ( !attributeMap.isEmpty() )
550 {
551 mLayer->changeAttributeValues( feat.id(), attributeMap );
552 }
553
554 //insert new features
555 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
556 {
557 QgsAttributeMap attributeMap;
558 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
559 {
560 const QgsField field = mLayer->fields().at( fieldIdx );
561 // respect field split policy
562 switch ( field.splitPolicy() )
563 {
565 //do nothing - default values ​​are determined
566 break;
567
569 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
570 break;
571
573 {
574 if ( !field.isNumeric() )
575 {
576 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
577 }
578 else
579 {
580 const double originalValue = feat.attribute( fieldIdx ).toDouble();
581
582 double originalSize = 0;
583
584 switch ( originalGeom.type() )
585 {
589 originalSize = 0;
590 break;
592 originalSize = originalGeom.length();
593 break;
595 originalSize = originalGeom.area();
596 break;
597 }
598
599 double newSize = 0;
600 switch ( geom.type() )
601 {
605 newSize = 0;
606 break;
608 newSize = geom.length();
609 break;
611 newSize = geom.area();
612 break;
613 }
614
615 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
616 }
617 break;
618 }
619
621 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
622 break;
623 }
624 }
625
626 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
627 }
628
629 if ( topologicalEditing )
630 {
631 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
632 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
633 {
634 addTopologicalPoints( *topol_it );
635 }
636 }
637 ++numberOfSplitFeatures;
638 }
639 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
640 {
641 returnCode = splitFunctionReturn;
642 }
643 }
644
645 if ( !featuresDataToAdd.isEmpty() )
646 {
647 // finally create and add all bits of geometries cut off the original geometries
648 // (this is much faster than creating features one by one)
649 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
650 mLayer->addFeatures( featuresListToAdd );
651 }
652
653 if ( numberOfSplitFeatures == 0 )
654 {
656 }
657
658 return returnCode;
659}
660
661Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
662{
664 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
665 {
666 l << QgsPoint( *it );
667 }
668 return splitParts( l, topologicalEditing );
669}
670
672{
673 if ( !mLayer->isSpatial() )
675
676 double xMin, yMin, xMax, yMax;
677 QgsRectangle bBox; //bounding box of the split line
678 int numberOfSplitParts = 0;
679
681
682 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
683 {
684 fit = mLayer->getSelectedFeatures();
685 }
686 else //else consider all the feature that intersect the bounding box of the split line
687 {
688 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
689 {
690 bBox.setXMinimum( xMin );
691 bBox.setYMinimum( yMin );
692 bBox.setXMaximum( xMax );
693 bBox.setYMaximum( yMax );
694 }
695 else
696 {
698 }
699
700 if ( bBox.isEmpty() )
701 {
702 //if the bbox is a line, try to make a square out of it
703 if ( bBox.width() == 0.0 && bBox.height() > 0 )
704 {
705 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
706 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
707 }
708 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
709 {
710 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
711 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
712 }
713 else
714 {
715 //If we have a single point, we still create a non-null box
716 double bufferDistance = 0.000001;
717 if ( mLayer->crs().isGeographic() )
718 bufferDistance = 0.00000001;
719 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
720 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
721 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
722 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
723 }
724 }
725
726 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
727 }
728
729 QgsFeature feat;
730 while ( fit.nextFeature( feat ) )
731 {
732 QgsGeometry featureGeom = feat.geometry();
733
734 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
735 QVector<QgsGeometry> resultCollection;
736 QgsPointSequence topologyTestPoints;
737 for ( QgsGeometry part : geomCollection )
738 {
739 QVector<QgsGeometry> newGeometries;
740 QgsPointSequence partTopologyTestPoints;
741
742 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
743
744 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
745 {
746 for ( int i = 0; i < newGeometries.size(); ++i )
747 {
748 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
749 }
750
751 topologyTestPoints.append( partTopologyTestPoints );
752
753 ++numberOfSplitParts;
754 }
755 // Note: For multilinestring layers, when the split line does not intersect the feature part,
756 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
757 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened ||
759 {
760 // Add part as is
761 resultCollection.append( part );
762 }
763 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
764 {
765 return splitFunctionReturn;
766 }
767 }
768
769 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
770 mLayer->changeGeometry( feat.id(), newGeom ) ;
771
772 if ( topologicalEditing )
773 {
774 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
775 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
776 {
777 addTopologicalPoints( *topol_it );
778 }
779 }
780
781 }
782 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
783 {
784 //There is a selection but no feature has been split.
785 //Maybe user forgot that only the selected features are split
787 }
788
790}
791
792
794{
795 if ( !mLayer->isSpatial() )
796 return 1;
797
798 if ( geom.isNull() )
799 {
800 return 1;
801 }
802
803 bool pointsAdded = false;
804
806 while ( it != geom.vertices_end() )
807 {
808 if ( addTopologicalPoints( *it ) == 0 )
809 {
810 pointsAdded = true;
811 }
812 ++it;
813 }
814
815 return pointsAdded ? 0 : 2;
816}
817
819{
820 if ( !mLayer->isSpatial() )
821 return 1;
822
823 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
824
825 //work with a tolerance because coordinate projection may introduce some rounding
826 double threshold = getTopologicalSearchRadius( mLayer );
827
828 QgsRectangle searchRect( p, p, false );
829 searchRect.grow( threshold );
830
831 QgsFeature f;
833 .setFilterRect( searchRect )
835 .setNoAttributes() );
836
837 bool pointsAdded = false;
838 while ( fit.nextFeature( f ) )
839 {
840 QgsGeometry geom = f.geometry();
841 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
842 {
843 pointsAdded = true;
844 mLayer->changeGeometry( f.id(), geom );
845 }
846 }
847
848 return pointsAdded ? 0 : 2;
849}
850
852{
853 if ( !mLayer->isSpatial() )
854 return 1;
855
856 if ( ps.isEmpty() )
857 {
858 return 1;
859 }
860
861 bool pointsAdded = false;
862
863 QgsPointSequence::const_iterator it = ps.constBegin();
864 while ( it != ps.constEnd() )
865 {
866 if ( addTopologicalPoints( *it ) == 0 )
867 {
868 pointsAdded = true;
869 }
870 ++it;
871 }
872
873 return pointsAdded ? 0 : 2;
874}
875
880
881bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
882{
883 errorMessage.clear();
884
885 if ( mergeFeatureIds.isEmpty() )
886 {
887 errorMessage = QObject::tr( "List of features to merge is empty" );
888 return false;
889 }
890
891 QgsAttributeMap newAttributes;
892 for ( int i = 0; i < mergeAttributes.count(); ++i )
893 {
894 QVariant val = mergeAttributes.at( i );
895
896 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider &&
897 mLayer->dataProvider() &&
898 mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
899
900 // convert to destination data type
901 QString errorMessageConvertCompatible;
902 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
903 {
904 if ( errorMessage.isEmpty() )
905 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 );
906 }
907 newAttributes[ i ] = val;
908 }
909
910 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
911
912 // Delete other features but the target feature
913 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
914 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
915 {
916 if ( *feature_it != targetFeatureId )
917 mLayer->deleteFeature( *feature_it );
918 }
919
920 // Modify target feature or create a new one if invalid
921 QgsGeometry mergeGeometry = unionGeometry;
922 if ( targetFeatureId == FID_NULL )
923 {
924 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
925 mLayer->addFeature( mergeFeature );
926 }
927 else
928 {
929 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
930 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
931 }
932
933 mLayer->endEditCommand();
934
935 mLayer->triggerRepaint();
936
937 return true;
938}
939
940bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
941{
942 if ( list.empty() )
943 {
944 return false;
945 }
946
947 xmin = std::numeric_limits<double>::max();
948 xmax = -std::numeric_limits<double>::max();
949 ymin = std::numeric_limits<double>::max();
950 ymax = -std::numeric_limits<double>::max();
951
952 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
953 {
954 if ( it->x() < xmin )
955 {
956 xmin = it->x();
957 }
958 if ( it->x() > xmax )
959 {
960 xmax = it->x();
961 }
962 if ( it->y() < ymin )
963 {
964 ymin = it->y();
965 }
966 if ( it->y() > ymax )
967 {
968 ymax = it->y();
969 }
970 }
971
972 return true;
973}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:1952
@ 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:1728
@ 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
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
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.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
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:6091
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