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