QGIS API Documentation 4.1.0-Master (26185ffb827)
Loading...
Searching...
No Matches
qgsvectorlayereditutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorlayereditutils.cpp
3 ---------------------
4 begin : Dezember 2012
5 copyright : (C) 2012 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
16
17#include <limits>
18
19#include "qgis.h"
20#include "qgsabstractgeometry.h"
21#include "qgsfeatureiterator.h"
22#include "qgsgeometryoptions.h"
23#include "qgslinestring.h"
24#include "qgslogger.h"
25#include "qgspoint.h"
30#include "qgsvectorlayer.h"
32#include "qgsvectorlayerutils.h"
33#include "qgswkbtypes.h"
34
35#include <QString>
36
37using namespace Qt::StringLiterals;
38
42
43bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
44{
45 if ( !mLayer->isSpatial() )
46 return false;
47
48 QgsFeature f;
49 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
50 return false; // geometry not found
51
52 QgsGeometry geometry = f.geometry();
53
54 geometry.insertVertex( x, y, beforeVertex );
55
56 mLayer->changeGeometry( atFeatureId, geometry );
57 return true;
58}
59
60bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
61{
62 if ( !mLayer->isSpatial() )
63 return false;
64
65 QgsFeature f;
66 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
67 return false; // geometry not found
68
69 QgsGeometry geometry = f.geometry();
70
71 geometry.insertVertex( point, beforeVertex );
72
73 mLayer->changeGeometry( atFeatureId, geometry );
74 return true;
75}
76
77bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
78{
79 QgsPoint p( x, y );
80 return moveVertex( p, atFeatureId, atVertex );
81}
82
83bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
84{
85 if ( !mLayer->isSpatial() )
86 return false;
87
88 QgsFeature f;
89 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
90 return false; // geometry not found
91
92 QgsGeometry geometry = f.geometry();
93
94 // If original point is not 3D but destination yes, check if it can be promoted
95 if ( p.is3D() && !geometry.constGet()->is3D() && QgsWkbTypes::hasZ( mLayer->wkbType() ) )
96 {
98 return false;
99 }
100
101 // If original point has not M-value but destination yes, check if it can be promoted
102 if ( p.isMeasure() && !geometry.constGet()->isMeasure() && QgsWkbTypes::hasM( mLayer->wkbType() ) )
103 {
105 return false;
106 }
107
108 if ( !geometry.moveVertex( p, atVertex ) )
109 return false;
110
111 return mLayer->changeGeometry( atFeatureId, geometry );
112}
113
115{
116 if ( !mLayer->isSpatial() )
118
119 QgsFeature f;
120 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
121 return Qgis::VectorEditResult::FetchFeatureFailed; // geometry not found
122
123 QgsGeometry geometry = f.geometry();
124
125 if ( !geometry.deleteVertex( vertex ) )
127
128 if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
129 {
130 //last vertex deleted, set geometry to null
131 geometry.set( nullptr );
132 }
133
134 mLayer->changeGeometry( featureId, geometry );
136}
137
139{
140 if ( !mLayer->isSpatial() )
142
143 QgsFeature f;
144 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
146
147 QgsGeometry geometry = f.geometry();
148
149 if ( !geometry.deleteVertices( vertices ) )
151
152 if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
153 {
154 // Last vertex deleted, set geometry to null
155 geometry.set( nullptr );
156 }
157
158 mLayer->changeGeometry( featureId, geometry );
160}
161
162static Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ptr< QgsCurve > &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureIds *modifiedFeatureIds, bool firstOne = true )
163{
164 if ( !layer || !layer->isSpatial() )
165 {
167 }
168
169 if ( !ring )
170 {
172 }
173
174 if ( !ring->isClosed() )
175 {
177 }
178
179 if ( !layer->isValid() || !layer->editBuffer() || !layer->dataProvider() )
180 {
182 }
183
184 Qgis::GeometryOperationResult addRingReturnCode = Qgis::GeometryOperationResult::AddRingNotInExistingFeature; //default: return code for 'ring not inserted'
185 QgsFeature f;
186
188 if ( !targetFeatureIds.isEmpty() )
189 {
190 //check only specified features
191 fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) );
192 }
193 else
194 {
195 //check all intersecting features
196 QgsRectangle bBox = ring->boundingBox();
197 fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
198 }
199
200 //find valid features we can add the ring to
201 bool success = false;
202 while ( fit.nextFeature( f ) )
203 {
204 if ( !f.hasGeometry() )
205 continue;
206
207 //add ring takes ownership of ring, and deletes it if there's an error
208 QgsGeometry g = f.geometry();
209
210 if ( ring->orientation() != g.polygonOrientation() )
211 {
212 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
213 }
214 else
215 {
216 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->reversed() ) );
217 }
218 if ( addRingReturnCode == Qgis::GeometryOperationResult::Success )
219 {
220 success = true;
221 layer->changeGeometry( f.id(), g );
222 if ( modifiedFeatureIds )
223 {
224 modifiedFeatureIds->insert( f.id() );
225 if ( firstOne )
226 {
227 break;
228 }
229 }
230 }
231 }
232
233 return success ? Qgis::GeometryOperationResult::Success : addRingReturnCode;
234}
235
237double QgsVectorLayerEditUtils::getTopologicalSearchRadius( const QgsVectorLayer *layer )
238{
239 double threshold = layer->geometryOptions()->geometryPrecision();
240
241 if ( qgsDoubleNear( threshold, 0.0 ) )
242 {
243 threshold = 1e-8;
244
245 if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
246 {
247 threshold = 0.001;
248 }
249 else if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
250 {
251 threshold = 0.0001;
252 }
253 }
254 return threshold;
255}
256
257void QgsVectorLayerEditUtils::addTopologicalPointsToLayers( const QgsGeometry &geom, QgsVectorLayer *vlayer, const QList<QgsMapLayer *> &layers, const QString &toolName )
258{
259 QgsFeatureRequest request = QgsFeatureRequest().setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 );
260 QgsFeature f;
261
262 for ( QgsMapLayer *layer : layers )
263 {
264 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
265 if ( vectorLayer && vectorLayer->isEditable() && vectorLayer->isSpatial() && ( vectorLayer->geometryType() == Qgis::GeometryType::Line || vectorLayer->geometryType() == Qgis::GeometryType::Polygon ) )
266 {
267 // boundingBox() is cached, it doesn't matter calling it in the loop
268 QgsRectangle bbox = geom.boundingBox();
269 QgsCoordinateTransform ct;
270 if ( vectorLayer->crs() != vlayer->crs() )
271 {
272 ct = QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() );
273 try
274 {
275 bbox = ct.transformBoundingBox( bbox );
276 }
277 catch ( QgsCsException & )
278 {
279 QgsDebugError( u"Bounding box transformation failed, skipping topological points for layer %1"_s.arg( vlayer->id() ) );
280 continue;
281 }
282 }
283 bbox.grow( getTopologicalSearchRadius( vectorLayer ) );
284 request.setFilterRect( bbox );
285
286 // 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
287 if ( !vectorLayer->getFeatures( request ).nextFeature( f ) )
288 continue;
289
290 vectorLayer->beginEditCommand( QObject::tr( "Topological points added by '%1'" ).arg( toolName ) );
291
292 int returnValue = 2;
293 if ( vectorLayer->crs() != vlayer->crs() )
294 {
295 try
296 {
297 // transform digitized geometry from vlayer crs to vectorLayer crs and add topological points
298 QgsGeometry transformedGeom( geom );
299 transformedGeom.transform( ct );
300 returnValue = vectorLayer->addTopologicalPoints( transformedGeom );
301 }
302 catch ( QgsCsException & )
303 {
304 QgsDebugError( u"transformation to vectorLayer coordinate failed"_s );
305 }
306 }
307 else
308 {
309 returnValue = vectorLayer->addTopologicalPoints( geom );
310 }
311
312 if ( returnValue == 0 )
313 {
314 vectorLayer->endEditCommand();
315 }
316 else
317 {
318 // the layer was not modified, leave the undo buffer intact
319 vectorLayer->destroyEditCommand();
320 }
321 }
322 }
323}
325
326Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
327{
329 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
330 {
331 l << QgsPoint( *it );
332 }
333 return addRing( l, targetFeatureIds, modifiedFeatureId );
334}
335
337{
338 QgsLineString *ringLine = new QgsLineString( ring );
339 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
340}
341
343{
344 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
345 if ( modifiedFeatureId )
346 {
347 QgsFeatureIds modifiedFeatureIds;
348 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, &modifiedFeatureIds, true );
349 if ( modifiedFeatureId && !modifiedFeatureIds.empty() )
350 *modifiedFeatureId = *modifiedFeatureIds.begin();
351 return result;
352 }
353 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
354}
355
357{
358 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
359 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
360}
361
362
364{
366 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
367 {
368 l << QgsPoint( *it );
369 }
370 return addPart( l, featureId );
371}
372
374{
375 if ( !mLayer->isSpatial() )
377
378 QgsGeometry geometry;
379 bool firstPart = false;
380 QgsFeature f;
381 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
383
384 if ( !f.hasGeometry() )
385 {
386 //no existing geometry, so adding first part to null geometry
387 firstPart = true;
388 }
389 else
390 {
391 geometry = f.geometry();
392 }
393
394 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
396 {
397 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
398 {
399 //convert back to single part if required by layer
400 geometry.convertToSingleType();
401 }
402 mLayer->changeGeometry( featureId, geometry );
403 }
404 return errorCode;
405}
406
408{
409 if ( !mLayer->isSpatial() )
411
412 QgsGeometry geometry;
413 bool firstPart = false;
414 QgsFeature f;
415 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
417
418 if ( !f.hasGeometry() )
419 {
420 //no existing geometry, so adding first part to null geometry
421 firstPart = true;
422 }
423 else
424 {
425 geometry = f.geometry();
426 if ( mLayer->geometryType() == Qgis::GeometryType::Polygon && ring->orientation() != geometry.polygonOrientation() )
427 {
428 ring = ring->reversed();
429 }
430 }
431 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( ring, mLayer->wkbType() );
432
434 {
435 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
436 {
437 //convert back to single part if required by layer
438 geometry.convertToSingleType();
439 }
440 mLayer->changeGeometry( featureId, geometry );
441 }
442 return errorCode;
443}
444
445// TODO QGIS 5.0 -- this should return Qgis::GeometryOperationResult
446int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
447{
448 if ( !mLayer->isSpatial() )
449 return 1;
450
451 QgsFeature f;
452 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
453 return 1; //geometry not found
454
455 QgsGeometry geometry = f.geometry();
456
457 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
459 {
460 mLayer->changeGeometry( featureId, geometry );
461 }
462 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
463}
464
465Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
466{
468 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
469 {
470 l << QgsPoint( *it );
471 }
472 return splitFeatures( l, topologicalEditing );
473}
474
476{
477 QgsLineString lineString( splitLine );
478 QgsPointSequence topologyTestPoints;
479 bool preserveCircular = false;
480 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
481}
482
483Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
484{
485 if ( !mLayer->isSpatial() )
487
488 QgsRectangle bBox; //bounding box of the split line
490 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
491 int numberOfSplitFeatures = 0;
492
493 QgsFeatureIterator features;
494 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
495
496 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
497 {
498 features = mLayer->getSelectedFeatures();
499 }
500 else //else consider all the feature that intersect the bounding box of the split line
501 {
502 bBox = curve->boundingBox();
503
504 if ( bBox.isEmpty() )
505 {
506 //if the bbox is a line, try to make a square out of it
507 if ( bBox.width() == 0.0 && bBox.height() > 0 )
508 {
509 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
510 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
511 }
512 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
513 {
514 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
515 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
516 }
517 else
518 {
519 //If we have a single point, we still create a non-null box
520 double bufferDistance = 0.000001;
521 if ( mLayer->crs().isGeographic() )
522 bufferDistance = 0.00000001;
523 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
524 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
525 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
526 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
527 }
528 }
529
530 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
531 }
532
534
535 const int fieldCount = mLayer->fields().count();
536 const bool splitCurveContainsCurves = curve->hasCurvedSegments();
537
538 QgsFeature feat;
539 while ( features.nextFeature( feat ) )
540 {
541 if ( !feat.hasGeometry() )
542 {
543 continue;
544 }
545 QVector<QgsGeometry> newGeometries;
546 QgsPointSequence featureTopologyTestPoints;
547 const QgsGeometry originalGeom = feat.geometry();
548 QgsGeometry featureGeom = originalGeom;
549
550 // For the current geometry, make sure preserveCircular is not forced, unless
551 // the input param is true and one of the involved geometries contains curves
552 bool preserveCircularForGeom = preserveCircular;
553 preserveCircularForGeom &= ( splitCurveContainsCurves || featureGeom.constGet()->hasCurvedSegments() );
554 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircularForGeom, topologicalEditing, featureTopologyTestPoints );
555
556 topologyTestPoints.append( featureTopologyTestPoints );
557 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
558 {
559 //find largest geometry and give that to the original feature
560 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
561 double featureGeomSize = size( featureGeom );
562
563 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [&size]( const QgsGeometry &a, const QgsGeometry &b ) -> bool {
564 return size( a ) < size( b );
565 } );
566
567 if ( size( *largestNewFeature ) > featureGeomSize )
568 {
569 QgsGeometry copy = *largestNewFeature;
570 *largestNewFeature = featureGeom;
571 featureGeom = copy;
572 }
573
574 //change this geometry
575 mLayer->changeGeometry( feat.id(), featureGeom );
576
577 //update any attributes for original feature which are set to GeometryRatio split policy
578 QgsAttributeMap attributeMap;
579 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
580 {
581 const QgsField field = mLayer->fields().at( fieldIdx );
582 switch ( field.splitPolicy() )
583 {
587 break;
588
590 {
591 if ( field.isNumeric() )
592 {
593 const double originalValue = feat.attribute( fieldIdx ).toDouble();
594
595 double originalSize = 0;
596
597 switch ( originalGeom.type() )
598 {
602 originalSize = 0;
603 break;
605 originalSize = originalGeom.length();
606 break;
608 originalSize = originalGeom.area();
609 break;
610 }
611
612 double newSize = 0;
613 switch ( featureGeom.type() )
614 {
618 newSize = 0;
619 break;
621 newSize = featureGeom.length();
622 break;
624 newSize = featureGeom.area();
625 break;
626 }
627
628 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
629 }
630 break;
631 }
632 }
633 }
634
635 if ( !attributeMap.isEmpty() )
636 {
637 mLayer->changeAttributeValues( feat.id(), attributeMap );
638 }
639
640 //insert new features
641 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
642 {
643 QgsAttributeMap attributeMap;
644 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
645 {
646 const QgsField field = mLayer->fields().at( fieldIdx );
647 // respect field split policy
648 switch ( field.splitPolicy() )
649 {
651 //do nothing - default values ​​are determined
652 break;
653
655 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
656 break;
657
659 {
660 if ( !field.isNumeric() )
661 {
662 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
663 }
664 else
665 {
666 const double originalValue = feat.attribute( fieldIdx ).toDouble();
667
668 double originalSize = 0;
669
670 switch ( originalGeom.type() )
671 {
675 originalSize = 0;
676 break;
678 originalSize = originalGeom.length();
679 break;
681 originalSize = originalGeom.area();
682 break;
683 }
684
685 double newSize = 0;
686 switch ( geom.type() )
687 {
691 newSize = 0;
692 break;
694 newSize = geom.length();
695 break;
697 newSize = geom.area();
698 break;
699 }
700
701 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
702 }
703 break;
704 }
705
707 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
708 break;
709 }
710 }
711
712 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
713 }
714
715 if ( topologicalEditing )
716 {
717 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
718 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
719 {
720 addTopologicalPoints( *topol_it );
721 }
722 }
723 ++numberOfSplitFeatures;
724 }
725 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
726 {
727 returnCode = splitFunctionReturn;
728 }
729 }
730
731 if ( !featuresDataToAdd.isEmpty() )
732 {
733 // finally create and add all bits of geometries cut off the original geometries
734 // (this is much faster than creating features one by one)
735 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
736 mLayer->addFeatures( featuresListToAdd );
737 }
738
739 if ( numberOfSplitFeatures == 0 )
740 {
742 }
743
744 return returnCode;
745}
746
747Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
748{
750 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
751 {
752 l << QgsPoint( *it );
753 }
754 return splitParts( l, topologicalEditing );
755}
756
758{
759 if ( !mLayer->isSpatial() )
761
762 double xMin, yMin, xMax, yMax;
763 QgsRectangle bBox; //bounding box of the split line
764 int numberOfSplitParts = 0;
765
767
768 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
769 {
770 fit = mLayer->getSelectedFeatures();
771 }
772 else //else consider all the feature that intersect the bounding box of the split line
773 {
774 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
775 {
776 bBox.setXMinimum( xMin );
777 bBox.setYMinimum( yMin );
778 bBox.setXMaximum( xMax );
779 bBox.setYMaximum( yMax );
780 }
781 else
782 {
784 }
785
786 if ( bBox.isEmpty() )
787 {
788 //if the bbox is a line, try to make a square out of it
789 if ( bBox.width() == 0.0 && bBox.height() > 0 )
790 {
791 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
792 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
793 }
794 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
795 {
796 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
797 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
798 }
799 else
800 {
801 //If we have a single point, we still create a non-null box
802 double bufferDistance = 0.000001;
803 if ( mLayer->crs().isGeographic() )
804 bufferDistance = 0.00000001;
805 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
806 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
807 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
808 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
809 }
810 }
811
812 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
813 }
814
815 QgsFeature feat;
816 while ( fit.nextFeature( feat ) )
817 {
818 QgsGeometry featureGeom = feat.geometry();
819
820 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
821 QVector<QgsGeometry> resultCollection;
822 QgsPointSequence topologyTestPoints;
823 for ( QgsGeometry part : geomCollection )
824 {
825 QVector<QgsGeometry> newGeometries;
826 QgsPointSequence partTopologyTestPoints;
827
828 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
829
830 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
831 {
832 for ( int i = 0; i < newGeometries.size(); ++i )
833 {
834 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
835 }
836
837 topologyTestPoints.append( partTopologyTestPoints );
838
839 ++numberOfSplitParts;
840 }
841 // Note: For multilinestring layers, when the split line does not intersect the feature part,
842 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
843 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened || splitFunctionReturn == Qgis::GeometryOperationResult::InvalidBaseGeometry )
844 {
845 // Add part as is
846 resultCollection.append( part );
847 }
848 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
849 {
850 return splitFunctionReturn;
851 }
852 }
853
854 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
855 mLayer->changeGeometry( feat.id(), newGeom );
856
857 if ( topologicalEditing )
858 {
859 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
860 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
861 {
862 addTopologicalPoints( *topol_it );
863 }
864 }
865 }
866 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
867 {
868 //There is a selection but no feature has been split.
869 //Maybe user forgot that only the selected features are split
871 }
872
874}
875
876
878{
879 if ( !mLayer->isSpatial() )
880 return 1;
881
882 if ( geom.isNull() )
883 {
884 return 1;
885 }
886
887 bool pointsAdded = false;
888
890 while ( it != geom.vertices_end() )
891 {
892 if ( addTopologicalPoints( *it ) == 0 )
893 {
894 pointsAdded = true;
895 }
896 ++it;
897 }
898
899 return pointsAdded ? 0 : 2;
900}
901
903{
904 if ( !mLayer->isSpatial() )
905 return 1;
906
907 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
908
909 //work with a tolerance because coordinate projection may introduce some rounding
910 double threshold = getTopologicalSearchRadius( mLayer );
911
912 QgsRectangle searchRect( p, p, false );
913 searchRect.grow( threshold );
914
915 QgsFeature f;
916 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( searchRect ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ).setNoAttributes() );
917
918 bool pointsAdded = false;
919 while ( fit.nextFeature( f ) )
920 {
921 QgsGeometry geom = f.geometry();
922 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
923 {
924 pointsAdded = true;
925 mLayer->changeGeometry( f.id(), geom );
926 }
927 }
928
929 return pointsAdded ? 0 : 2;
930}
931
933{
934 if ( !mLayer->isSpatial() )
935 return 1;
936
937 if ( ps.isEmpty() )
938 {
939 return 1;
940 }
941
942 bool pointsAdded = false;
943
944 QgsPointSequence::const_iterator it = ps.constBegin();
945 while ( it != ps.constEnd() )
946 {
947 if ( addTopologicalPoints( *it ) == 0 )
948 {
949 pointsAdded = true;
950 }
951 ++it;
952 }
953
954 return pointsAdded ? 0 : 2;
955}
956
961
963 const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage
964)
965{
966 errorMessage.clear();
967
968 if ( mergeFeatureIds.isEmpty() )
969 {
970 errorMessage = QObject::tr( "List of features to merge is empty" );
971 return false;
972 }
973
974 QgsAttributeMap newAttributes;
975 for ( int i = 0; i < mergeAttributes.count(); ++i )
976 {
977 QVariant val = mergeAttributes.at( i );
978
979 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider
980 && mLayer->dataProvider()
981 && mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
982
983 // convert to destination data type
984 QString errorMessageConvertCompatible;
985 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
986 {
987 if ( errorMessage.isEmpty() )
988 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 );
989 }
990 newAttributes[i] = val;
991 }
992
993 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
994
995 // Delete other features but the target feature
996 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
997 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
998 {
999 if ( *feature_it != targetFeatureId )
1000 mLayer->deleteFeature( *feature_it );
1001 }
1002
1003 // Modify target feature or create a new one if invalid
1004 QgsGeometry mergeGeometry = unionGeometry;
1005 if ( targetFeatureId == FID_NULL )
1006 {
1007 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
1008 mLayer->addFeature( mergeFeature );
1009 }
1010 else
1011 {
1012 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
1013 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
1014 }
1015
1016 mLayer->endEditCommand();
1017
1018 mLayer->triggerRepaint();
1019
1020 return true;
1021}
1022
1023bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
1024{
1025 if ( list.empty() )
1026 {
1027 return false;
1028 }
1029
1030 xmin = std::numeric_limits<double>::max();
1031 xmax = -std::numeric_limits<double>::max();
1032 ymin = std::numeric_limits<double>::max();
1033 ymax = -std::numeric_limits<double>::max();
1034
1035 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
1036 {
1037 if ( it->x() < xmin )
1038 {
1039 xmin = it->x();
1040 }
1041 if ( it->x() > xmax )
1042 {
1043 xmax = it->x();
1044 }
1045 if ( it->y() < ymin )
1046 {
1047 ymin = it->y();
1048 }
1049 if ( it->y() > ymax )
1050 {
1051 ymax = it->y();
1052 }
1053 }
1054
1055 return true;
1056}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2162
@ AddPartSelectedGeometryNotFound
The selected geometry cannot be found.
Definition qgis.h:2172
@ InvalidInputGeometryType
The input geometry (ring, part, split line, etc.) has not the correct geometry type.
Definition qgis.h:2166
@ Success
Operation succeeded.
Definition qgis.h:2163
@ AddRingNotInExistingFeature
The input ring doesn't have any existing ring to fit into.
Definition qgis.h:2178
@ AddRingNotClosed
The input ring is not closed.
Definition qgis.h:2175
@ NothingHappened
Nothing happened, without any error.
Definition qgis.h:2164
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition qgis.h:2165
@ LayerNotEditable
Cannot edit layer.
Definition qgis.h:2170
@ Feet
Imperial feet.
Definition qgis.h:5419
@ Meters
Meters.
Definition qgis.h:5417
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2331
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2329
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
Definition qgis.h:4093
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
Definition qgis.h:4094
@ DefaultValue
Use default field value.
Definition qgis.h:4091
@ Duplicate
Duplicate original value.
Definition qgis.h:4092
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
@ Unknown
Unknown types.
Definition qgis.h:383
@ Null
No geometry.
Definition qgis.h:384
@ Provider
Field originates from the underlying data provider of the vector layer.
Definition qgis.h:1826
VectorEditResult
Specifies the result of a vector layer edit operation.
Definition qgis.h:1938
@ EmptyGeometry
Edit operation resulted in an empty geometry.
Definition qgis.h:1940
@ Success
Edit operation was successful.
Definition qgis.h:1939
@ FetchFeatureFailed
Unable to fetch requested feature.
Definition qgis.h:1942
@ EditFailed
Edit operation failed.
Definition qgis.h:1941
@ InvalidLayer
Edit failed due to invalid layer.
Definition qgis.h:1943
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.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
A vector of attributes.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
Definition qgscurve.cpp:291
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 & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:63
QgsGeometry geometry
Definition qgsfeature.h:66
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Qgis::FieldDomainSplitPolicy splitPolicy() const
Returns the field's split policy, which indicates how field values should be handled during a split o...
Definition qgsfield.cpp:764
bool isNumeric
Definition qgsfield.h:59
double geometryPrecision() const
The precision in which geometries on this layer should be saved.
A geometry is the spatial representation of a feature.
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.
bool deleteVertices(const QSet< int > &atVertices)
Deletes vertices at the given positions (first number is index 0).
void set(QgsAbstractGeometry *geometry)
Sets the underlying geometry store.
QgsAbstractGeometry::vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult addPartV2(const QVector< QgsPointXY > &points, Qgis::WkbType wkbType=Qgis::WkbType::Unknown)
Adds a new part to a the geometry.
Qgis::GeometryOperationResult translate(double dx, double dy, double dz=0.0, double dm=0.0)
Translates this geometry by dx, dy, dz and dm.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitGeometry(const QVector< QgsPointXY > &splitLine, QVector< QgsGeometry > &newGeometries, bool topological, QVector< QgsPointXY > &topologyTestPoints, bool splitFeature=true)
Splits this geometry according to a given line.
bool moveVertex(double x, double y, int atVertex)
Moves the vertex at the given position number and item (first number is index 0) to the given coordin...
QgsAbstractGeometry::vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
Line string geometry type, with support for z-dimension and m-values.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
QString id
Definition qgsmaplayer.h:86
QgsCoordinateTransformContext transformContext() const
Returns the layer data provider coordinate transform context or a default transform context if the la...
Represents a 2D point.
Definition qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double yMaximum
static const QgsSettingsEntryDouble * settingsDigitizingDefaultMValue
Settings entry digitizing default m value.
static const QgsSettingsEntryDouble * settingsDigitizingDefaultZValue
Settings entry digitizing default z value.
Represents a default, "not-specified" value for a feature attribute.
int translateFeature(QgsFeatureId featureId, double dx, double dy)
Translates feature by dx, dy.
Qgis::VectorEditResult deleteVertices(QgsFeatureId featureId, const QSet< int > &vertices)
Deletes a set of vertices from a feature.
bool mergeFeatures(const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage)
Merge features into a single one.
QgsVectorLayerEditUtils(QgsVectorLayer *layer)
bool insertVertex(double x, double y, QgsFeatureId atFeatureId, int beforeVertex)
Insert a new vertex before the given vertex number, in the given ring, item (first number is index 0)...
Q_DECL_DEPRECATED Qgis::GeometryOperationResult addPart(const QVector< QgsPointXY > &ring, QgsFeatureId featureId)
Adds a new part polygon to a multipart feature.
Qgis::VectorEditResult deleteVertex(QgsFeatureId featureId, int vertex)
Deletes a vertex from a feature.
Qgis::GeometryOperationResult addRingV2(QgsCurve *ring, const QgsFeatureIds &targetFeatureIds=QgsFeatureIds(), QgsFeatureIds *modifiedFeatureIds=nullptr)
Adds a ring to polygon/multipolygon features.
int addTopologicalPoints(const QgsGeometry &geom)
Adds topological points for every vertex of the geometry.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitParts(const QVector< QgsPointXY > &splitLine, bool topologicalEditing=false)
Splits parts cut by the given line.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitFeatures(const QVector< QgsPointXY > &splitLine, bool topologicalEditing=false)
Splits features cut by the given line.
bool moveVertex(double x, double y, QgsFeatureId atFeatureId, int atVertex)
Moves the vertex at the given position number, ring and item (first number is index 0),...
Q_DECL_DEPRECATED Qgis::GeometryOperationResult addRing(const QVector< QgsPointXY > &ring, const QgsFeatureIds &targetFeatureIds=QgsFeatureIds(), QgsFeatureId *modifiedFeatureId=nullptr)
Adds a ring to polygon/multipolygon features.
Encapsulate geometry and attributes for new features, to be passed to createFeatures.
QList< QgsVectorLayerUtils::QgsFeatureData > QgsFeaturesDataList
Alias for list of QgsFeatureData.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
static QgsFeatureList createFeatures(const QgsVectorLayer *layer, const QgsFeaturesDataList &featuresData, QgsExpressionContext *context=nullptr)
Creates a set of new features ready for insertion into a layer.
Represents a vector layer which manages a vector based dataset.
bool isEditable() const final
Returns true if the provider is in editing mode.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
void endEditCommand()
Finish edit command and add it to undo/redo stack.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
QgsGeometryOptions * geometryOptions() const
Configuration and logic to apply automatically on any edit happening on this layer.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
int addTopologicalPoints(const QgsGeometry &geom)
Adds topological points for every vertex of the geometry.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
static Q_INVOKABLE bool isSingleType(Qgis::WkbType type)
Returns true if the WKB type is a single type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7257
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:59