QGIS API Documentation 4.1.0-Master (d6fb7a379fb)
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
138
139static Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ptr< QgsCurve > &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureIds *modifiedFeatureIds, bool firstOne = true )
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 return success ? Qgis::GeometryOperationResult::Success : addRingReturnCode;
211}
212
214double QgsVectorLayerEditUtils::getTopologicalSearchRadius( const QgsVectorLayer *layer )
215{
216 double threshold = layer->geometryOptions()->geometryPrecision();
217
218 if ( qgsDoubleNear( threshold, 0.0 ) )
219 {
220 threshold = 1e-8;
221
222 if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
223 {
224 threshold = 0.001;
225 }
226 else if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
227 {
228 threshold = 0.0001;
229 }
230 }
231 return threshold;
232}
233
234void QgsVectorLayerEditUtils::addTopologicalPointsToLayers( const QgsGeometry &geom, QgsVectorLayer *vlayer, const QList<QgsMapLayer *> &layers, const QString &toolName )
235{
236 QgsFeatureRequest request = QgsFeatureRequest().setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 );
237 QgsFeature f;
238
239 for ( QgsMapLayer *layer : layers )
240 {
241 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
242 if ( vectorLayer && vectorLayer->isEditable() && vectorLayer->isSpatial() && ( vectorLayer->geometryType() == Qgis::GeometryType::Line || vectorLayer->geometryType() == Qgis::GeometryType::Polygon ) )
243 {
244 // boundingBox() is cached, it doesn't matter calling it in the loop
245 QgsRectangle bbox = geom.boundingBox();
246 QgsCoordinateTransform ct;
247 if ( vectorLayer->crs() != vlayer->crs() )
248 {
249 ct = QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() );
250 try
251 {
252 bbox = ct.transformBoundingBox( bbox );
253 }
254 catch ( QgsCsException & )
255 {
256 QgsDebugError( u"Bounding box transformation failed, skipping topological points for layer %1"_s.arg( vlayer->id() ) );
257 continue;
258 }
259 }
260 bbox.grow( getTopologicalSearchRadius( vectorLayer ) );
261 request.setFilterRect( bbox );
262
263 // 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
264 if ( !vectorLayer->getFeatures( request ).nextFeature( f ) )
265 continue;
266
267 vectorLayer->beginEditCommand( QObject::tr( "Topological points added by '%1'" ).arg( toolName ) );
268
269 int returnValue = 2;
270 if ( vectorLayer->crs() != vlayer->crs() )
271 {
272 try
273 {
274 // transform digitized geometry from vlayer crs to vectorLayer crs and add topological points
275 QgsGeometry transformedGeom( geom );
276 transformedGeom.transform( ct );
277 returnValue = vectorLayer->addTopologicalPoints( transformedGeom );
278 }
279 catch ( QgsCsException & )
280 {
281 QgsDebugError( u"transformation to vectorLayer coordinate failed"_s );
282 }
283 }
284 else
285 {
286 returnValue = vectorLayer->addTopologicalPoints( geom );
287 }
288
289 if ( returnValue == 0 )
290 {
291 vectorLayer->endEditCommand();
292 }
293 else
294 {
295 // the layer was not modified, leave the undo buffer intact
296 vectorLayer->destroyEditCommand();
297 }
298 }
299 }
300}
302
303Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
304{
306 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
307 {
308 l << QgsPoint( *it );
309 }
310 return addRing( l, targetFeatureIds, modifiedFeatureId );
311}
312
314{
315 QgsLineString *ringLine = new QgsLineString( ring );
316 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
317}
318
320{
321 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
322 if ( modifiedFeatureId )
323 {
324 QgsFeatureIds modifiedFeatureIds;
325 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, &modifiedFeatureIds, true );
326 if ( modifiedFeatureId && !modifiedFeatureIds.empty() )
327 *modifiedFeatureId = *modifiedFeatureIds.begin();
328 return result;
329 }
330 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
331}
332
334{
335 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
336 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
337}
338
339
341{
343 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
344 {
345 l << QgsPoint( *it );
346 }
347 return addPart( l, featureId );
348}
349
351{
352 if ( !mLayer->isSpatial() )
354
355 QgsGeometry geometry;
356 bool firstPart = false;
357 QgsFeature f;
358 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
360
361 if ( !f.hasGeometry() )
362 {
363 //no existing geometry, so adding first part to null geometry
364 firstPart = true;
365 }
366 else
367 {
368 geometry = f.geometry();
369 }
370
371 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
373 {
374 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
375 {
376 //convert back to single part if required by layer
377 geometry.convertToSingleType();
378 }
379 mLayer->changeGeometry( featureId, geometry );
380 }
381 return errorCode;
382}
383
385{
386 if ( !mLayer->isSpatial() )
388
389 QgsGeometry geometry;
390 bool firstPart = false;
391 QgsFeature f;
392 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
394
395 if ( !f.hasGeometry() )
396 {
397 //no existing geometry, so adding first part to null geometry
398 firstPart = true;
399 }
400 else
401 {
402 geometry = f.geometry();
403 if ( mLayer->geometryType() == Qgis::GeometryType::Polygon && ring->orientation() != geometry.polygonOrientation() )
404 {
405 ring = ring->reversed();
406 }
407 }
408 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( ring, mLayer->wkbType() );
409
411 {
412 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
413 {
414 //convert back to single part if required by layer
415 geometry.convertToSingleType();
416 }
417 mLayer->changeGeometry( featureId, geometry );
418 }
419 return errorCode;
420}
421
422// TODO QGIS 5.0 -- this should return Qgis::GeometryOperationResult
423int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
424{
425 if ( !mLayer->isSpatial() )
426 return 1;
427
428 QgsFeature f;
429 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
430 return 1; //geometry not found
431
432 QgsGeometry geometry = f.geometry();
433
434 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
436 {
437 mLayer->changeGeometry( featureId, geometry );
438 }
439 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
440}
441
442Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
443{
445 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
446 {
447 l << QgsPoint( *it );
448 }
449 return splitFeatures( l, topologicalEditing );
450}
451
453{
454 QgsLineString lineString( splitLine );
455 QgsPointSequence topologyTestPoints;
456 bool preserveCircular = false;
457 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
458}
459
460Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
461{
462 if ( !mLayer->isSpatial() )
464
465 QgsRectangle bBox; //bounding box of the split line
467 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
468 int numberOfSplitFeatures = 0;
469
470 QgsFeatureIterator features;
471 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
472
473 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
474 {
475 features = mLayer->getSelectedFeatures();
476 }
477 else //else consider all the feature that intersect the bounding box of the split line
478 {
479 bBox = curve->boundingBox();
480
481 if ( bBox.isEmpty() )
482 {
483 //if the bbox is a line, try to make a square out of it
484 if ( bBox.width() == 0.0 && bBox.height() > 0 )
485 {
486 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
487 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
488 }
489 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
490 {
491 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
492 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
493 }
494 else
495 {
496 //If we have a single point, we still create a non-null box
497 double bufferDistance = 0.000001;
498 if ( mLayer->crs().isGeographic() )
499 bufferDistance = 0.00000001;
500 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
501 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
502 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
503 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
504 }
505 }
506
507 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
508 }
509
511
512 const int fieldCount = mLayer->fields().count();
513 const bool splitCurveContainsCurves = curve->hasCurvedSegments();
514
515 QgsFeature feat;
516 while ( features.nextFeature( feat ) )
517 {
518 if ( !feat.hasGeometry() )
519 {
520 continue;
521 }
522 QVector<QgsGeometry> newGeometries;
523 QgsPointSequence featureTopologyTestPoints;
524 const QgsGeometry originalGeom = feat.geometry();
525 QgsGeometry featureGeom = originalGeom;
526
527 // For the current geometry, make sure preserveCircular is not forced, unless
528 // the input param is true and one of the involved geometries contains curves
529 bool preserveCircularForGeom = preserveCircular;
530 preserveCircularForGeom &= ( splitCurveContainsCurves || featureGeom.constGet()->hasCurvedSegments() );
531 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircularForGeom, topologicalEditing, featureTopologyTestPoints );
532
533 topologyTestPoints.append( featureTopologyTestPoints );
534 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
535 {
536 //find largest geometry and give that to the original feature
537 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
538 double featureGeomSize = size( featureGeom );
539
540 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [&size]( const QgsGeometry &a, const QgsGeometry &b ) -> bool {
541 return size( a ) < size( b );
542 } );
543
544 if ( size( *largestNewFeature ) > featureGeomSize )
545 {
546 QgsGeometry copy = *largestNewFeature;
547 *largestNewFeature = featureGeom;
548 featureGeom = copy;
549 }
550
551 //change this geometry
552 mLayer->changeGeometry( feat.id(), featureGeom );
553
554 //update any attributes for original feature which are set to GeometryRatio split policy
555 QgsAttributeMap attributeMap;
556 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
557 {
558 const QgsField field = mLayer->fields().at( fieldIdx );
559 switch ( field.splitPolicy() )
560 {
564 break;
565
567 {
568 if ( field.isNumeric() )
569 {
570 const double originalValue = feat.attribute( fieldIdx ).toDouble();
571
572 double originalSize = 0;
573
574 switch ( originalGeom.type() )
575 {
579 originalSize = 0;
580 break;
582 originalSize = originalGeom.length();
583 break;
585 originalSize = originalGeom.area();
586 break;
587 }
588
589 double newSize = 0;
590 switch ( featureGeom.type() )
591 {
595 newSize = 0;
596 break;
598 newSize = featureGeom.length();
599 break;
601 newSize = featureGeom.area();
602 break;
603 }
604
605 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
606 }
607 break;
608 }
609 }
610 }
611
612 if ( !attributeMap.isEmpty() )
613 {
614 mLayer->changeAttributeValues( feat.id(), attributeMap );
615 }
616
617 //insert new features
618 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
619 {
620 QgsAttributeMap attributeMap;
621 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
622 {
623 const QgsField field = mLayer->fields().at( fieldIdx );
624 // respect field split policy
625 switch ( field.splitPolicy() )
626 {
628 //do nothing - default values ​​are determined
629 break;
630
632 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
633 break;
634
636 {
637 if ( !field.isNumeric() )
638 {
639 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
640 }
641 else
642 {
643 const double originalValue = feat.attribute( fieldIdx ).toDouble();
644
645 double originalSize = 0;
646
647 switch ( originalGeom.type() )
648 {
652 originalSize = 0;
653 break;
655 originalSize = originalGeom.length();
656 break;
658 originalSize = originalGeom.area();
659 break;
660 }
661
662 double newSize = 0;
663 switch ( geom.type() )
664 {
668 newSize = 0;
669 break;
671 newSize = geom.length();
672 break;
674 newSize = geom.area();
675 break;
676 }
677
678 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
679 }
680 break;
681 }
682
684 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
685 break;
686 }
687 }
688
689 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
690 }
691
692 if ( topologicalEditing )
693 {
694 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
695 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
696 {
697 addTopologicalPoints( *topol_it );
698 }
699 }
700 ++numberOfSplitFeatures;
701 }
702 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
703 {
704 returnCode = splitFunctionReturn;
705 }
706 }
707
708 if ( !featuresDataToAdd.isEmpty() )
709 {
710 // finally create and add all bits of geometries cut off the original geometries
711 // (this is much faster than creating features one by one)
712 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
713 mLayer->addFeatures( featuresListToAdd );
714 }
715
716 if ( numberOfSplitFeatures == 0 )
717 {
719 }
720
721 return returnCode;
722}
723
724Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
725{
727 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
728 {
729 l << QgsPoint( *it );
730 }
731 return splitParts( l, topologicalEditing );
732}
733
735{
736 if ( !mLayer->isSpatial() )
738
739 double xMin, yMin, xMax, yMax;
740 QgsRectangle bBox; //bounding box of the split line
741 int numberOfSplitParts = 0;
742
744
745 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
746 {
747 fit = mLayer->getSelectedFeatures();
748 }
749 else //else consider all the feature that intersect the bounding box of the split line
750 {
751 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
752 {
753 bBox.setXMinimum( xMin );
754 bBox.setYMinimum( yMin );
755 bBox.setXMaximum( xMax );
756 bBox.setYMaximum( yMax );
757 }
758 else
759 {
761 }
762
763 if ( bBox.isEmpty() )
764 {
765 //if the bbox is a line, try to make a square out of it
766 if ( bBox.width() == 0.0 && bBox.height() > 0 )
767 {
768 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
769 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
770 }
771 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
772 {
773 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
774 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
775 }
776 else
777 {
778 //If we have a single point, we still create a non-null box
779 double bufferDistance = 0.000001;
780 if ( mLayer->crs().isGeographic() )
781 bufferDistance = 0.00000001;
782 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
783 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
784 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
785 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
786 }
787 }
788
789 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
790 }
791
792 QgsFeature feat;
793 while ( fit.nextFeature( feat ) )
794 {
795 QgsGeometry featureGeom = feat.geometry();
796
797 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
798 QVector<QgsGeometry> resultCollection;
799 QgsPointSequence topologyTestPoints;
800 for ( QgsGeometry part : geomCollection )
801 {
802 QVector<QgsGeometry> newGeometries;
803 QgsPointSequence partTopologyTestPoints;
804
805 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
806
807 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
808 {
809 for ( int i = 0; i < newGeometries.size(); ++i )
810 {
811 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
812 }
813
814 topologyTestPoints.append( partTopologyTestPoints );
815
816 ++numberOfSplitParts;
817 }
818 // Note: For multilinestring layers, when the split line does not intersect the feature part,
819 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
820 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened || splitFunctionReturn == Qgis::GeometryOperationResult::InvalidBaseGeometry )
821 {
822 // Add part as is
823 resultCollection.append( part );
824 }
825 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
826 {
827 return splitFunctionReturn;
828 }
829 }
830
831 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
832 mLayer->changeGeometry( feat.id(), newGeom );
833
834 if ( topologicalEditing )
835 {
836 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
837 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
838 {
839 addTopologicalPoints( *topol_it );
840 }
841 }
842 }
843 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
844 {
845 //There is a selection but no feature has been split.
846 //Maybe user forgot that only the selected features are split
848 }
849
851}
852
853
855{
856 if ( !mLayer->isSpatial() )
857 return 1;
858
859 if ( geom.isNull() )
860 {
861 return 1;
862 }
863
864 bool pointsAdded = false;
865
867 while ( it != geom.vertices_end() )
868 {
869 if ( addTopologicalPoints( *it ) == 0 )
870 {
871 pointsAdded = true;
872 }
873 ++it;
874 }
875
876 return pointsAdded ? 0 : 2;
877}
878
880{
881 if ( !mLayer->isSpatial() )
882 return 1;
883
884 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
885
886 //work with a tolerance because coordinate projection may introduce some rounding
887 double threshold = getTopologicalSearchRadius( mLayer );
888
889 QgsRectangle searchRect( p, p, false );
890 searchRect.grow( threshold );
891
892 QgsFeature f;
893 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( searchRect ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ).setNoAttributes() );
894
895 bool pointsAdded = false;
896 while ( fit.nextFeature( f ) )
897 {
898 QgsGeometry geom = f.geometry();
899 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
900 {
901 pointsAdded = true;
902 mLayer->changeGeometry( f.id(), geom );
903 }
904 }
905
906 return pointsAdded ? 0 : 2;
907}
908
910{
911 if ( !mLayer->isSpatial() )
912 return 1;
913
914 if ( ps.isEmpty() )
915 {
916 return 1;
917 }
918
919 bool pointsAdded = false;
920
921 QgsPointSequence::const_iterator it = ps.constBegin();
922 while ( it != ps.constEnd() )
923 {
924 if ( addTopologicalPoints( *it ) == 0 )
925 {
926 pointsAdded = true;
927 }
928 ++it;
929 }
930
931 return pointsAdded ? 0 : 2;
932}
933
938
940 const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage
941)
942{
943 errorMessage.clear();
944
945 if ( mergeFeatureIds.isEmpty() )
946 {
947 errorMessage = QObject::tr( "List of features to merge is empty" );
948 return false;
949 }
950
951 QgsAttributeMap newAttributes;
952 for ( int i = 0; i < mergeAttributes.count(); ++i )
953 {
954 QVariant val = mergeAttributes.at( i );
955
956 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider
957 && mLayer->dataProvider()
958 && mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
959
960 // convert to destination data type
961 QString errorMessageConvertCompatible;
962 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
963 {
964 if ( errorMessage.isEmpty() )
965 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 );
966 }
967 newAttributes[i] = val;
968 }
969
970 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
971
972 // Delete other features but the target feature
973 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
974 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
975 {
976 if ( *feature_it != targetFeatureId )
977 mLayer->deleteFeature( *feature_it );
978 }
979
980 // Modify target feature or create a new one if invalid
981 QgsGeometry mergeGeometry = unionGeometry;
982 if ( targetFeatureId == FID_NULL )
983 {
984 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
985 mLayer->addFeature( mergeFeature );
986 }
987 else
988 {
989 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
990 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
991 }
992
993 mLayer->endEditCommand();
994
995 mLayer->triggerRepaint();
996
997 return true;
998}
999
1000bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
1001{
1002 if ( list.empty() )
1003 {
1004 return false;
1005 }
1006
1007 xmin = std::numeric_limits<double>::max();
1008 xmax = -std::numeric_limits<double>::max();
1009 ymin = std::numeric_limits<double>::max();
1010 ymax = -std::numeric_limits<double>::max();
1011
1012 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
1013 {
1014 if ( it->x() < xmin )
1015 {
1016 xmin = it->x();
1017 }
1018 if ( it->x() > xmax )
1019 {
1020 xmax = it->x();
1021 }
1022 if ( it->y() < ymin )
1023 {
1024 ymin = it->y();
1025 }
1026 if ( it->y() > ymax )
1027 {
1028 ymax = it->y();
1029 }
1030 }
1031
1032 return true;
1033}
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:5385
@ Meters
Meters.
Definition qgis.h:5383
@ 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: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 & 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:68
QgsGeometry geometry
Definition qgsfeature.h:71
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.
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.
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:7222
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