QGIS API Documentation 3.99.0-Master (51df526a401)
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}
234
235void QgsVectorLayerEditUtils::addTopologicalPointsToLayers( const QgsGeometry &geom, QgsVectorLayer *vlayer, const QList<QgsMapLayer *> &layers, const QString &toolName )
236{
238 QgsFeature f;
239
240 for ( QgsMapLayer *layer : layers )
241 {
242 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
243 if ( vectorLayer && vectorLayer->isEditable() && vectorLayer->isSpatial() && ( vectorLayer->geometryType() == Qgis::GeometryType::Line || vectorLayer->geometryType() == Qgis::GeometryType::Polygon ) )
244 {
245 // boundingBox() is cached, it doesn't matter calling it in the loop
246 QgsRectangle bbox = geom.boundingBox();
248 if ( vectorLayer->crs() != vlayer->crs() )
249 {
250 ct = QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() );
251 try
252 {
253 bbox = ct.transformBoundingBox( bbox );
254 }
255 catch ( QgsCsException & )
256 {
257 QgsDebugError( QStringLiteral( "Bounding box transformation failed, skipping topological points for layer %1" ).arg( vlayer->id() ) );
258 continue;
259 }
260 }
261 bbox.grow( getTopologicalSearchRadius( vectorLayer ) );
262 request.setFilterRect( bbox );
263
264 // We check that there is actually at least one feature intersecting our geometry in the layer to avoid creating an empty edit command and calling costly addTopologicalPoint
265 if ( !vectorLayer->getFeatures( request ).nextFeature( f ) )
266 continue;
267
268 vectorLayer->beginEditCommand( QObject::tr( "Topological points added by '%1'" ).arg( toolName ) );
269
270 int returnValue = 2;
271 if ( vectorLayer->crs() != vlayer->crs() )
272 {
273 try
274 {
275 // transform digitized geometry from vlayer crs to vectorLayer crs and add topological points
276 QgsGeometry transformedGeom( geom );
277 transformedGeom.transform( ct );
278 returnValue = vectorLayer->addTopologicalPoints( transformedGeom );
279 }
280 catch ( QgsCsException & )
281 {
282 QgsDebugError( QStringLiteral( "transformation to vectorLayer coordinate failed" ) );
283 }
284 }
285 else
286 {
287 returnValue = vectorLayer->addTopologicalPoints( geom );
288 }
289
290 if ( returnValue == 0 )
291 {
292 vectorLayer->endEditCommand();
293 }
294 else
295 {
296 // the layer was not modified, leave the undo buffer intact
297 vectorLayer->destroyEditCommand();
298 }
299 }
300 }
301}
303
304Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
305{
307 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
308 {
309 l << QgsPoint( *it );
310 }
311 return addRing( l, targetFeatureIds, modifiedFeatureId );
312}
313
315{
316 QgsLineString *ringLine = new QgsLineString( ring );
317 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
318}
319
321{
322 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
323 if ( modifiedFeatureId )
324 {
325 QgsFeatureIds modifiedFeatureIds;
326 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, &modifiedFeatureIds, true );
327 if ( modifiedFeatureId && !modifiedFeatureIds.empty() )
328 *modifiedFeatureId = *modifiedFeatureIds.begin();
329 return result;
330 }
331 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
332}
333
335{
336
337 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
338 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
339}
340
341
342
344{
346 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
347 {
348 l << QgsPoint( *it );
349 }
350 return addPart( l, featureId );
351}
352
354{
355 if ( !mLayer->isSpatial() )
357
358 QgsGeometry geometry;
359 bool firstPart = false;
360 QgsFeature f;
361 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
363
364 if ( !f.hasGeometry() )
365 {
366 //no existing geometry, so adding first part to null geometry
367 firstPart = true;
368 }
369 else
370 {
371 geometry = f.geometry();
372 }
373
374 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
376 {
377 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
379 {
380 //convert back to single part if required by layer
381 geometry.convertToSingleType();
382 }
383 mLayer->changeGeometry( featureId, geometry );
384 }
385 return errorCode;
386}
387
389{
390
391 if ( !mLayer->isSpatial() )
393
394 QgsGeometry geometry;
395 bool firstPart = false;
396 QgsFeature f;
397 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
399
400 if ( !f.hasGeometry() )
401 {
402 //no existing geometry, so adding first part to null geometry
403 firstPart = true;
404 }
405 else
406 {
407 geometry = f.geometry();
408 if ( ring->orientation() != geometry.polygonOrientation() )
409 {
410 ring = ring->reversed();
411 }
412 }
413 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( ring, mLayer->wkbType() );
414
416 {
417 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
419 {
420 //convert back to single part if required by layer
421 geometry.convertToSingleType();
422 }
423 mLayer->changeGeometry( featureId, geometry );
424 }
425 return errorCode;
426}
427
428// TODO QGIS 4.0 -- this should return Qgis::GeometryOperationResult
429int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
430{
431 if ( !mLayer->isSpatial() )
432 return 1;
433
434 QgsFeature f;
435 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
436 return 1; //geometry not found
437
438 QgsGeometry geometry = f.geometry();
439
440 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
442 {
443 mLayer->changeGeometry( featureId, geometry );
444 }
445 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
446}
447
448Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
449{
450
452 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
453 {
454 l << QgsPoint( *it );
455 }
456 return splitFeatures( l, topologicalEditing );
457}
458
460{
461 QgsLineString lineString( splitLine );
462 QgsPointSequence topologyTestPoints;
463 bool preserveCircular = false;
464 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
465}
466
467Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
468{
469 if ( !mLayer->isSpatial() )
471
472 QgsRectangle bBox; //bounding box of the split line
474 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
475 int numberOfSplitFeatures = 0;
476
477 QgsFeatureIterator features;
478 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
479
480 // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
481 preserveCircular &= curve->hasCurvedSegments();
482
483 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
484 {
485 features = mLayer->getSelectedFeatures();
486 }
487 else //else consider all the feature that intersect the bounding box of the split line
488 {
489
490 bBox = curve->boundingBox();
491
492 if ( bBox.isEmpty() )
493 {
494 //if the bbox is a line, try to make a square out of it
495 if ( bBox.width() == 0.0 && bBox.height() > 0 )
496 {
497 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
498 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
499 }
500 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
501 {
502 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
503 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
504 }
505 else
506 {
507 //If we have a single point, we still create a non-null box
508 double bufferDistance = 0.000001;
509 if ( mLayer->crs().isGeographic() )
510 bufferDistance = 0.00000001;
511 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
512 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
513 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
514 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
515 }
516 }
517
518 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
519 }
520
522
523 const int fieldCount = mLayer->fields().count();
524
525 QgsFeature feat;
526 while ( features.nextFeature( feat ) )
527 {
528 if ( !feat.hasGeometry() )
529 {
530 continue;
531 }
532 QVector<QgsGeometry> newGeometries;
533 QgsPointSequence featureTopologyTestPoints;
534 const QgsGeometry originalGeom = feat.geometry();
535 QgsGeometry featureGeom = originalGeom;
536 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
537 topologyTestPoints.append( featureTopologyTestPoints );
538 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
539 {
540 //find largest geometry and give that to the original feature
541 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
542 double featureGeomSize = size( featureGeom );
543
544 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [ &size ]( const QgsGeometry & a, const QgsGeometry & b ) -> bool
545 {
546 return size( a ) < size( b );
547 } );
548
549 if ( size( *largestNewFeature ) > featureGeomSize )
550 {
551 QgsGeometry copy = *largestNewFeature;
552 *largestNewFeature = featureGeom;
553 featureGeom = copy;
554 }
555
556 //change this geometry
557 mLayer->changeGeometry( feat.id(), featureGeom );
558
559 //update any attributes for original feature which are set to GeometryRatio split policy
560 QgsAttributeMap attributeMap;
561 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
562 {
563 const QgsField field = mLayer->fields().at( fieldIdx );
564 switch ( field.splitPolicy() )
565 {
569 break;
570
572 {
573 if ( field.isNumeric() )
574 {
575 const double originalValue = feat.attribute( fieldIdx ).toDouble();
576
577 double originalSize = 0;
578
579 switch ( originalGeom.type() )
580 {
584 originalSize = 0;
585 break;
587 originalSize = originalGeom.length();
588 break;
590 originalSize = originalGeom.area();
591 break;
592 }
593
594 double newSize = 0;
595 switch ( featureGeom.type() )
596 {
600 newSize = 0;
601 break;
603 newSize = featureGeom.length();
604 break;
606 newSize = featureGeom.area();
607 break;
608 }
609
610 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
611 }
612 break;
613 }
614 }
615 }
616
617 if ( !attributeMap.isEmpty() )
618 {
619 mLayer->changeAttributeValues( feat.id(), attributeMap );
620 }
621
622 //insert new features
623 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
624 {
625 QgsAttributeMap attributeMap;
626 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
627 {
628 const QgsField field = mLayer->fields().at( fieldIdx );
629 // respect field split policy
630 switch ( field.splitPolicy() )
631 {
633 //do nothing - default values ​​are determined
634 break;
635
637 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
638 break;
639
641 {
642 if ( !field.isNumeric() )
643 {
644 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
645 }
646 else
647 {
648 const double originalValue = feat.attribute( fieldIdx ).toDouble();
649
650 double originalSize = 0;
651
652 switch ( originalGeom.type() )
653 {
657 originalSize = 0;
658 break;
660 originalSize = originalGeom.length();
661 break;
663 originalSize = originalGeom.area();
664 break;
665 }
666
667 double newSize = 0;
668 switch ( geom.type() )
669 {
673 newSize = 0;
674 break;
676 newSize = geom.length();
677 break;
679 newSize = geom.area();
680 break;
681 }
682
683 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
684 }
685 break;
686 }
687
689 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
690 break;
691 }
692 }
693
694 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
695 }
696
697 if ( topologicalEditing )
698 {
699 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
700 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
701 {
702 addTopologicalPoints( *topol_it );
703 }
704 }
705 ++numberOfSplitFeatures;
706 }
707 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
708 {
709 returnCode = splitFunctionReturn;
710 }
711 }
712
713 if ( !featuresDataToAdd.isEmpty() )
714 {
715 // finally create and add all bits of geometries cut off the original geometries
716 // (this is much faster than creating features one by one)
717 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
718 mLayer->addFeatures( featuresListToAdd );
719 }
720
721 if ( numberOfSplitFeatures == 0 )
722 {
724 }
725
726 return returnCode;
727}
728
729Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
730{
732 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
733 {
734 l << QgsPoint( *it );
735 }
736 return splitParts( l, topologicalEditing );
737}
738
740{
741 if ( !mLayer->isSpatial() )
743
744 double xMin, yMin, xMax, yMax;
745 QgsRectangle bBox; //bounding box of the split line
746 int numberOfSplitParts = 0;
747
749
750 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
751 {
752 fit = mLayer->getSelectedFeatures();
753 }
754 else //else consider all the feature that intersect the bounding box of the split line
755 {
756 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
757 {
758 bBox.setXMinimum( xMin );
759 bBox.setYMinimum( yMin );
760 bBox.setXMaximum( xMax );
761 bBox.setYMaximum( yMax );
762 }
763 else
764 {
766 }
767
768 if ( bBox.isEmpty() )
769 {
770 //if the bbox is a line, try to make a square out of it
771 if ( bBox.width() == 0.0 && bBox.height() > 0 )
772 {
773 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
774 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
775 }
776 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
777 {
778 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
779 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
780 }
781 else
782 {
783 //If we have a single point, we still create a non-null box
784 double bufferDistance = 0.000001;
785 if ( mLayer->crs().isGeographic() )
786 bufferDistance = 0.00000001;
787 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
788 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
789 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
790 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
791 }
792 }
793
794 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
795 }
796
797 QgsFeature feat;
798 while ( fit.nextFeature( feat ) )
799 {
800 QgsGeometry featureGeom = feat.geometry();
801
802 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
803 QVector<QgsGeometry> resultCollection;
804 QgsPointSequence topologyTestPoints;
805 for ( QgsGeometry part : geomCollection )
806 {
807 QVector<QgsGeometry> newGeometries;
808 QgsPointSequence partTopologyTestPoints;
809
810 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
811
812 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
813 {
814 for ( int i = 0; i < newGeometries.size(); ++i )
815 {
816 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
817 }
818
819 topologyTestPoints.append( partTopologyTestPoints );
820
821 ++numberOfSplitParts;
822 }
823 // Note: For multilinestring layers, when the split line does not intersect the feature part,
824 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
825 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened ||
827 {
828 // Add part as is
829 resultCollection.append( part );
830 }
831 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
832 {
833 return splitFunctionReturn;
834 }
835 }
836
837 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
838 mLayer->changeGeometry( feat.id(), newGeom ) ;
839
840 if ( topologicalEditing )
841 {
842 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
843 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
844 {
845 addTopologicalPoints( *topol_it );
846 }
847 }
848
849 }
850 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
851 {
852 //There is a selection but no feature has been split.
853 //Maybe user forgot that only the selected features are split
855 }
856
858}
859
860
862{
863 if ( !mLayer->isSpatial() )
864 return 1;
865
866 if ( geom.isNull() )
867 {
868 return 1;
869 }
870
871 bool pointsAdded = false;
872
874 while ( it != geom.vertices_end() )
875 {
876 if ( addTopologicalPoints( *it ) == 0 )
877 {
878 pointsAdded = true;
879 }
880 ++it;
881 }
882
883 return pointsAdded ? 0 : 2;
884}
885
887{
888 if ( !mLayer->isSpatial() )
889 return 1;
890
891 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
892
893 //work with a tolerance because coordinate projection may introduce some rounding
894 double threshold = getTopologicalSearchRadius( mLayer );
895
896 QgsRectangle searchRect( p, p, false );
897 searchRect.grow( threshold );
898
899 QgsFeature f;
901 .setFilterRect( searchRect )
903 .setNoAttributes() );
904
905 bool pointsAdded = false;
906 while ( fit.nextFeature( f ) )
907 {
908 QgsGeometry geom = f.geometry();
909 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
910 {
911 pointsAdded = true;
912 mLayer->changeGeometry( f.id(), geom );
913 }
914 }
915
916 return pointsAdded ? 0 : 2;
917}
918
920{
921 if ( !mLayer->isSpatial() )
922 return 1;
923
924 if ( ps.isEmpty() )
925 {
926 return 1;
927 }
928
929 bool pointsAdded = false;
930
931 QgsPointSequence::const_iterator it = ps.constBegin();
932 while ( it != ps.constEnd() )
933 {
934 if ( addTopologicalPoints( *it ) == 0 )
935 {
936 pointsAdded = true;
937 }
938 ++it;
939 }
940
941 return pointsAdded ? 0 : 2;
942}
943
948
949bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
950{
951 errorMessage.clear();
952
953 if ( mergeFeatureIds.isEmpty() )
954 {
955 errorMessage = QObject::tr( "List of features to merge is empty" );
956 return false;
957 }
958
959 QgsAttributeMap newAttributes;
960 for ( int i = 0; i < mergeAttributes.count(); ++i )
961 {
962 QVariant val = mergeAttributes.at( i );
963
964 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider &&
965 mLayer->dataProvider() &&
966 mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
967
968 // convert to destination data type
969 QString errorMessageConvertCompatible;
970 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
971 {
972 if ( errorMessage.isEmpty() )
973 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 );
974 }
975 newAttributes[ i ] = val;
976 }
977
978 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
979
980 // Delete other features but the target feature
981 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
982 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
983 {
984 if ( *feature_it != targetFeatureId )
985 mLayer->deleteFeature( *feature_it );
986 }
987
988 // Modify target feature or create a new one if invalid
989 QgsGeometry mergeGeometry = unionGeometry;
990 if ( targetFeatureId == FID_NULL )
991 {
992 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
993 mLayer->addFeature( mergeFeature );
994 }
995 else
996 {
997 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
998 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
999 }
1000
1001 mLayer->endEditCommand();
1002
1003 mLayer->triggerRepaint();
1004
1005 return true;
1006}
1007
1008bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
1009{
1010 if ( list.empty() )
1011 {
1012 return false;
1013 }
1014
1015 xmin = std::numeric_limits<double>::max();
1016 xmax = -std::numeric_limits<double>::max();
1017 ymin = std::numeric_limits<double>::max();
1018 ymax = -std::numeric_limits<double>::max();
1019
1020 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
1021 {
1022 if ( it->x() < xmin )
1023 {
1024 xmin = it->x();
1025 }
1026 if ( it->x() > xmax )
1027 {
1028 xmax = it->x();
1029 }
1030 if ( it->y() < ymin )
1031 {
1032 ymin = it->y();
1033 }
1034 if ( it->y() > ymax )
1035 {
1036 ymax = it->y();
1037 }
1038 }
1039
1040 return true;
1041}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2005
@ 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.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ 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:1781
@ 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 an STL-style iterator for vertices.
virtual bool addZValue(double zValue=0)=0
Adds a z-dimension to the geometry, initialized to a preset value.
bool isMeasure() const
Returns true if the geometry contains m values.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
A vector of attributes.
Handles coordinate transforms between two coordinate systems.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
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.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setLimit(long long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
QString typeName() const
Gets the field type.
Definition qgsfield.cpp:162
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition qgsfield.cpp:474
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:761
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.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult addPartV2(const QVector< QgsPointXY > &points, Qgis::WkbType wkbType=Qgis::WkbType::Unknown)
Adds a new part to a the geometry.
Qgis::GeometryOperationResult translate(double dx, double dy, double dz=0.0, double dm=0.0)
Translates this geometry by dx, dy, dz and dm.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitGeometry(const QVector< QgsPointXY > &splitLine, QVector< QgsGeometry > &newGeometries, bool topological, QVector< QgsPointXY > &topologyTestPoints, bool splitFeature=true)
Splits this geometry according to a given line.
bool moveVertex(double x, double y, int atVertex)
Moves the vertex at the given position number and item (first number is index 0) to the given coordin...
QgsAbstractGeometry::vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
Line string geometry type, with support for z-dimension and m-values.
Base class for all map layer types.
Definition qgsmaplayer.h:78
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:85
QString id
Definition qgsmaplayer.h:81
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
QgsCoordinateTransformContext transformContext() const
Returns the layer data provider coordinate transform context or a default transform context if the la...
Represents a 2D point.
Definition qgspointxy.h:60
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double yMaximum
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 dataset.
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.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
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.
int addTopologicalPoints(const QgsGeometry &geom)
Adds topological points for every vertex of the geometry.
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 Q_INVOKABLE bool isSingleType(Qgis::WkbType type)
Returns true if the WKB type is a single type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6392
QVector< QgsPoint > QgsPointSequence
QMap< int, QVariant > QgsAttributeMap
QList< QgsFeature > QgsFeatureList
#define FID_NULL
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugError(str)
Definition qgslogger.h:40