QGIS API Documentation 3.99.0-Master (d270888f95f)
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
43
44bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
45{
46 if ( !mLayer->isSpatial() )
47 return false;
48
49 QgsFeature f;
50 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
51 return false; // geometry not found
52
53 QgsGeometry geometry = f.geometry();
54
55 geometry.insertVertex( x, y, beforeVertex );
56
57 mLayer->changeGeometry( atFeatureId, geometry );
58 return true;
59}
60
61bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
62{
63 if ( !mLayer->isSpatial() )
64 return false;
65
66 QgsFeature f;
67 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
68 return false; // geometry not found
69
70 QgsGeometry geometry = f.geometry();
71
72 geometry.insertVertex( point, beforeVertex );
73
74 mLayer->changeGeometry( atFeatureId, geometry );
75 return true;
76}
77
78bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
79{
80 QgsPoint p( x, y );
81 return moveVertex( p, atFeatureId, atVertex );
82}
83
84bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
85{
86 if ( !mLayer->isSpatial() )
87 return false;
88
89 QgsFeature f;
90 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
91 return false; // geometry not found
92
93 QgsGeometry geometry = f.geometry();
94
95 // If original point is not 3D but destination yes, check if it can be promoted
96 if ( p.is3D() && !geometry.constGet()->is3D() && QgsWkbTypes::hasZ( mLayer->wkbType() ) )
97 {
99 return false;
100 }
101
102 // If original point has not M-value but destination yes, check if it can be promoted
103 if ( p.isMeasure() && !geometry.constGet()->isMeasure() && QgsWkbTypes::hasM( mLayer->wkbType() ) )
104 {
106 return false;
107 }
108
109 if ( !geometry.moveVertex( p, atVertex ) )
110 return false;
111
112 return mLayer->changeGeometry( atFeatureId, geometry );
113}
114
116{
117 if ( !mLayer->isSpatial() )
119
120 QgsFeature f;
121 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
122 return Qgis::VectorEditResult::FetchFeatureFailed; // geometry not found
123
124 QgsGeometry geometry = f.geometry();
125
126 if ( !geometry.deleteVertex( vertex ) )
128
129 if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
130 {
131 //last vertex deleted, set geometry to null
132 geometry.set( nullptr );
133 }
134
135 mLayer->changeGeometry( featureId, geometry );
137}
138
139
140static
141Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ptr< QgsCurve > &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureIds *modifiedFeatureIds, bool firstOne = true )
142{
143
144 if ( !layer || !layer->isSpatial() )
145 {
147 }
148
149 if ( !ring )
150 {
152 }
153
154 if ( !ring->isClosed() )
155 {
157 }
158
159 if ( !layer->isValid() || !layer->editBuffer() || !layer->dataProvider() )
160 {
162 }
163
164 Qgis::GeometryOperationResult addRingReturnCode = Qgis::GeometryOperationResult::AddRingNotInExistingFeature; //default: return code for 'ring not inserted'
165 QgsFeature f;
166
168 if ( !targetFeatureIds.isEmpty() )
169 {
170 //check only specified features
171 fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) );
172 }
173 else
174 {
175 //check all intersecting features
176 QgsRectangle bBox = ring->boundingBox();
177 fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
178 }
179
180 //find valid features we can add the ring to
181 bool success = false;
182 while ( fit.nextFeature( f ) )
183 {
184 if ( !f.hasGeometry() )
185 continue;
186
187 //add ring takes ownership of ring, and deletes it if there's an error
188 QgsGeometry g = f.geometry();
189
190 if ( ring->orientation() != g.polygonOrientation() )
191 {
192 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
193 }
194 else
195 {
196 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->reversed() ) );
197 }
198 if ( addRingReturnCode == Qgis::GeometryOperationResult::Success )
199 {
200 success = true;
201 layer->changeGeometry( f.id(), g );
202 if ( modifiedFeatureIds )
203 {
204 modifiedFeatureIds->insert( f.id() );
205 if ( firstOne )
206 {
207 break;
208 }
209 }
210
211 }
212 }
213
214 return success ? Qgis::GeometryOperationResult::Success : addRingReturnCode;
215}
216
218double QgsVectorLayerEditUtils::getTopologicalSearchRadius( const QgsVectorLayer *layer )
219{
220 double threshold = layer->geometryOptions()->geometryPrecision();
221
222 if ( qgsDoubleNear( threshold, 0.0 ) )
223 {
224 threshold = 1e-8;
225
226 if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
227 {
228 threshold = 0.001;
229 }
230 else if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
231 {
232 threshold = 0.0001;
233 }
234 }
235 return threshold;
236}
237
238void QgsVectorLayerEditUtils::addTopologicalPointsToLayers( const QgsGeometry &geom, QgsVectorLayer *vlayer, const QList<QgsMapLayer *> &layers, const QString &toolName )
239{
240 QgsFeatureRequest request = QgsFeatureRequest().setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 );
241 QgsFeature f;
242
243 for ( QgsMapLayer *layer : layers )
244 {
245 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
246 if ( vectorLayer && vectorLayer->isEditable() && vectorLayer->isSpatial() && ( vectorLayer->geometryType() == Qgis::GeometryType::Line || vectorLayer->geometryType() == Qgis::GeometryType::Polygon ) )
247 {
248 // boundingBox() is cached, it doesn't matter calling it in the loop
249 QgsRectangle bbox = geom.boundingBox();
250 QgsCoordinateTransform ct;
251 if ( vectorLayer->crs() != vlayer->crs() )
252 {
253 ct = QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() );
254 try
255 {
256 bbox = ct.transformBoundingBox( bbox );
257 }
258 catch ( QgsCsException & )
259 {
260 QgsDebugError( u"Bounding box transformation failed, skipping topological points for layer %1"_s.arg( vlayer->id() ) );
261 continue;
262 }
263 }
264 bbox.grow( getTopologicalSearchRadius( vectorLayer ) );
265 request.setFilterRect( bbox );
266
267 // 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
268 if ( !vectorLayer->getFeatures( request ).nextFeature( f ) )
269 continue;
270
271 vectorLayer->beginEditCommand( QObject::tr( "Topological points added by '%1'" ).arg( toolName ) );
272
273 int returnValue = 2;
274 if ( vectorLayer->crs() != vlayer->crs() )
275 {
276 try
277 {
278 // transform digitized geometry from vlayer crs to vectorLayer crs and add topological points
279 QgsGeometry transformedGeom( geom );
280 transformedGeom.transform( ct );
281 returnValue = vectorLayer->addTopologicalPoints( transformedGeom );
282 }
283 catch ( QgsCsException & )
284 {
285 QgsDebugError( u"transformation to vectorLayer coordinate failed"_s );
286 }
287 }
288 else
289 {
290 returnValue = vectorLayer->addTopologicalPoints( geom );
291 }
292
293 if ( returnValue == 0 )
294 {
295 vectorLayer->endEditCommand();
296 }
297 else
298 {
299 // the layer was not modified, leave the undo buffer intact
300 vectorLayer->destroyEditCommand();
301 }
302 }
303 }
304}
306
307Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
308{
310 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
311 {
312 l << QgsPoint( *it );
313 }
314 return addRing( l, targetFeatureIds, modifiedFeatureId );
315}
316
318{
319 QgsLineString *ringLine = new QgsLineString( ring );
320 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
321}
322
324{
325 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
326 if ( modifiedFeatureId )
327 {
328 QgsFeatureIds modifiedFeatureIds;
329 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, &modifiedFeatureIds, true );
330 if ( modifiedFeatureId && !modifiedFeatureIds.empty() )
331 *modifiedFeatureId = *modifiedFeatureIds.begin();
332 return result;
333 }
334 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
335}
336
338{
339
340 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
341 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
342}
343
344
345
347{
349 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
350 {
351 l << QgsPoint( *it );
352 }
353 return addPart( l, featureId );
354}
355
357{
358 if ( !mLayer->isSpatial() )
360
361 QgsGeometry geometry;
362 bool firstPart = false;
363 QgsFeature f;
364 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
366
367 if ( !f.hasGeometry() )
368 {
369 //no existing geometry, so adding first part to null geometry
370 firstPart = true;
371 }
372 else
373 {
374 geometry = f.geometry();
375 }
376
377 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
379 {
380 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
381 && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
382 {
383 //convert back to single part if required by layer
384 geometry.convertToSingleType();
385 }
386 mLayer->changeGeometry( featureId, geometry );
387 }
388 return errorCode;
389}
390
392{
393
394 if ( !mLayer->isSpatial() )
396
397 QgsGeometry geometry;
398 bool firstPart = false;
399 QgsFeature f;
400 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
402
403 if ( !f.hasGeometry() )
404 {
405 //no existing geometry, so adding first part to null geometry
406 firstPart = true;
407 }
408 else
409 {
410 geometry = f.geometry();
411 if ( mLayer->geometryType() == Qgis::GeometryType::Polygon && ring->orientation() != geometry.polygonOrientation() )
412 {
413 ring = ring->reversed();
414 }
415 }
416 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( ring, mLayer->wkbType() );
417
419 {
420 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
421 && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
422 {
423 //convert back to single part if required by layer
424 geometry.convertToSingleType();
425 }
426 mLayer->changeGeometry( featureId, geometry );
427 }
428 return errorCode;
429}
430
431// TODO QGIS 5.0 -- this should return Qgis::GeometryOperationResult
432int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
433{
434 if ( !mLayer->isSpatial() )
435 return 1;
436
437 QgsFeature f;
438 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
439 return 1; //geometry not found
440
441 QgsGeometry geometry = f.geometry();
442
443 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
445 {
446 mLayer->changeGeometry( featureId, geometry );
447 }
448 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
449}
450
451Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
452{
453
455 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
456 {
457 l << QgsPoint( *it );
458 }
459 return splitFeatures( l, topologicalEditing );
460}
461
463{
464 QgsLineString lineString( splitLine );
465 QgsPointSequence topologyTestPoints;
466 bool preserveCircular = false;
467 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
468}
469
470Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
471{
472 if ( !mLayer->isSpatial() )
474
475 QgsRectangle bBox; //bounding box of the split line
477 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
478 int numberOfSplitFeatures = 0;
479
480 QgsFeatureIterator features;
481 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
482
483 // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
484 preserveCircular &= curve->hasCurvedSegments();
485
486 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
487 {
488 features = mLayer->getSelectedFeatures();
489 }
490 else //else consider all the feature that intersect the bounding box of the split line
491 {
492
493 bBox = curve->boundingBox();
494
495 if ( bBox.isEmpty() )
496 {
497 //if the bbox is a line, try to make a square out of it
498 if ( bBox.width() == 0.0 && bBox.height() > 0 )
499 {
500 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
501 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
502 }
503 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
504 {
505 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
506 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
507 }
508 else
509 {
510 //If we have a single point, we still create a non-null box
511 double bufferDistance = 0.000001;
512 if ( mLayer->crs().isGeographic() )
513 bufferDistance = 0.00000001;
514 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
515 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
516 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
517 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
518 }
519 }
520
521 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
522 }
523
525
526 const int fieldCount = mLayer->fields().count();
527
528 QgsFeature feat;
529 while ( features.nextFeature( feat ) )
530 {
531 if ( !feat.hasGeometry() )
532 {
533 continue;
534 }
535 QVector<QgsGeometry> newGeometries;
536 QgsPointSequence featureTopologyTestPoints;
537 const QgsGeometry originalGeom = feat.geometry();
538 QgsGeometry featureGeom = originalGeom;
539 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
540 topologyTestPoints.append( featureTopologyTestPoints );
541 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
542 {
543 //find largest geometry and give that to the original feature
544 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
545 double featureGeomSize = size( featureGeom );
546
547 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [ &size ]( const QgsGeometry & a, const QgsGeometry & b ) -> bool
548 {
549 return size( a ) < size( b );
550 } );
551
552 if ( size( *largestNewFeature ) > featureGeomSize )
553 {
554 QgsGeometry copy = *largestNewFeature;
555 *largestNewFeature = featureGeom;
556 featureGeom = copy;
557 }
558
559 //change this geometry
560 mLayer->changeGeometry( feat.id(), featureGeom );
561
562 //update any attributes for original feature which are set to GeometryRatio split policy
563 QgsAttributeMap attributeMap;
564 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
565 {
566 const QgsField field = mLayer->fields().at( fieldIdx );
567 switch ( field.splitPolicy() )
568 {
572 break;
573
575 {
576 if ( field.isNumeric() )
577 {
578 const double originalValue = feat.attribute( fieldIdx ).toDouble();
579
580 double originalSize = 0;
581
582 switch ( originalGeom.type() )
583 {
587 originalSize = 0;
588 break;
590 originalSize = originalGeom.length();
591 break;
593 originalSize = originalGeom.area();
594 break;
595 }
596
597 double newSize = 0;
598 switch ( featureGeom.type() )
599 {
603 newSize = 0;
604 break;
606 newSize = featureGeom.length();
607 break;
609 newSize = featureGeom.area();
610 break;
611 }
612
613 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
614 }
615 break;
616 }
617 }
618 }
619
620 if ( !attributeMap.isEmpty() )
621 {
622 mLayer->changeAttributeValues( feat.id(), attributeMap );
623 }
624
625 //insert new features
626 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
627 {
628 QgsAttributeMap attributeMap;
629 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
630 {
631 const QgsField field = mLayer->fields().at( fieldIdx );
632 // respect field split policy
633 switch ( field.splitPolicy() )
634 {
636 //do nothing - default values ​​are determined
637 break;
638
640 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
641 break;
642
644 {
645 if ( !field.isNumeric() )
646 {
647 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
648 }
649 else
650 {
651 const double originalValue = feat.attribute( fieldIdx ).toDouble();
652
653 double originalSize = 0;
654
655 switch ( originalGeom.type() )
656 {
660 originalSize = 0;
661 break;
663 originalSize = originalGeom.length();
664 break;
666 originalSize = originalGeom.area();
667 break;
668 }
669
670 double newSize = 0;
671 switch ( geom.type() )
672 {
676 newSize = 0;
677 break;
679 newSize = geom.length();
680 break;
682 newSize = geom.area();
683 break;
684 }
685
686 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
687 }
688 break;
689 }
690
692 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
693 break;
694 }
695 }
696
697 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
698 }
699
700 if ( topologicalEditing )
701 {
702 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
703 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
704 {
705 addTopologicalPoints( *topol_it );
706 }
707 }
708 ++numberOfSplitFeatures;
709 }
710 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
711 {
712 returnCode = splitFunctionReturn;
713 }
714 }
715
716 if ( !featuresDataToAdd.isEmpty() )
717 {
718 // finally create and add all bits of geometries cut off the original geometries
719 // (this is much faster than creating features one by one)
720 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
721 mLayer->addFeatures( featuresListToAdd );
722 }
723
724 if ( numberOfSplitFeatures == 0 )
725 {
727 }
728
729 return returnCode;
730}
731
732Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
733{
735 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
736 {
737 l << QgsPoint( *it );
738 }
739 return splitParts( l, topologicalEditing );
740}
741
743{
744 if ( !mLayer->isSpatial() )
746
747 double xMin, yMin, xMax, yMax;
748 QgsRectangle bBox; //bounding box of the split line
749 int numberOfSplitParts = 0;
750
752
753 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
754 {
755 fit = mLayer->getSelectedFeatures();
756 }
757 else //else consider all the feature that intersect the bounding box of the split line
758 {
759 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
760 {
761 bBox.setXMinimum( xMin );
762 bBox.setYMinimum( yMin );
763 bBox.setXMaximum( xMax );
764 bBox.setYMaximum( yMax );
765 }
766 else
767 {
769 }
770
771 if ( bBox.isEmpty() )
772 {
773 //if the bbox is a line, try to make a square out of it
774 if ( bBox.width() == 0.0 && bBox.height() > 0 )
775 {
776 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
777 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
778 }
779 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
780 {
781 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
782 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
783 }
784 else
785 {
786 //If we have a single point, we still create a non-null box
787 double bufferDistance = 0.000001;
788 if ( mLayer->crs().isGeographic() )
789 bufferDistance = 0.00000001;
790 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
791 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
792 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
793 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
794 }
795 }
796
797 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
798 }
799
800 QgsFeature feat;
801 while ( fit.nextFeature( feat ) )
802 {
803 QgsGeometry featureGeom = feat.geometry();
804
805 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
806 QVector<QgsGeometry> resultCollection;
807 QgsPointSequence topologyTestPoints;
808 for ( QgsGeometry part : geomCollection )
809 {
810 QVector<QgsGeometry> newGeometries;
811 QgsPointSequence partTopologyTestPoints;
812
813 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
814
815 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
816 {
817 for ( int i = 0; i < newGeometries.size(); ++i )
818 {
819 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
820 }
821
822 topologyTestPoints.append( partTopologyTestPoints );
823
824 ++numberOfSplitParts;
825 }
826 // Note: For multilinestring layers, when the split line does not intersect the feature part,
827 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
828 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened ||
830 {
831 // Add part as is
832 resultCollection.append( part );
833 }
834 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
835 {
836 return splitFunctionReturn;
837 }
838 }
839
840 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
841 mLayer->changeGeometry( feat.id(), newGeom ) ;
842
843 if ( topologicalEditing )
844 {
845 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
846 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
847 {
848 addTopologicalPoints( *topol_it );
849 }
850 }
851
852 }
853 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
854 {
855 //There is a selection but no feature has been split.
856 //Maybe user forgot that only the selected features are split
858 }
859
861}
862
863
865{
866 if ( !mLayer->isSpatial() )
867 return 1;
868
869 if ( geom.isNull() )
870 {
871 return 1;
872 }
873
874 bool pointsAdded = false;
875
877 while ( it != geom.vertices_end() )
878 {
879 if ( addTopologicalPoints( *it ) == 0 )
880 {
881 pointsAdded = true;
882 }
883 ++it;
884 }
885
886 return pointsAdded ? 0 : 2;
887}
888
890{
891 if ( !mLayer->isSpatial() )
892 return 1;
893
894 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
895
896 //work with a tolerance because coordinate projection may introduce some rounding
897 double threshold = getTopologicalSearchRadius( mLayer );
898
899 QgsRectangle searchRect( p, p, false );
900 searchRect.grow( threshold );
901
902 QgsFeature f;
903 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest()
904 .setFilterRect( searchRect )
906 .setNoAttributes() );
907
908 bool pointsAdded = false;
909 while ( fit.nextFeature( f ) )
910 {
911 QgsGeometry geom = f.geometry();
912 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
913 {
914 pointsAdded = true;
915 mLayer->changeGeometry( f.id(), geom );
916 }
917 }
918
919 return pointsAdded ? 0 : 2;
920}
921
923{
924 if ( !mLayer->isSpatial() )
925 return 1;
926
927 if ( ps.isEmpty() )
928 {
929 return 1;
930 }
931
932 bool pointsAdded = false;
933
934 QgsPointSequence::const_iterator it = ps.constBegin();
935 while ( it != ps.constEnd() )
936 {
937 if ( addTopologicalPoints( *it ) == 0 )
938 {
939 pointsAdded = true;
940 }
941 ++it;
942 }
943
944 return pointsAdded ? 0 : 2;
945}
946
951
952bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
953{
954 errorMessage.clear();
955
956 if ( mergeFeatureIds.isEmpty() )
957 {
958 errorMessage = QObject::tr( "List of features to merge is empty" );
959 return false;
960 }
961
962 QgsAttributeMap newAttributes;
963 for ( int i = 0; i < mergeAttributes.count(); ++i )
964 {
965 QVariant val = mergeAttributes.at( i );
966
967 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider &&
968 mLayer->dataProvider() &&
969 mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
970
971 // convert to destination data type
972 QString errorMessageConvertCompatible;
973 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
974 {
975 if ( errorMessage.isEmpty() )
976 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 );
977 }
978 newAttributes[ i ] = val;
979 }
980
981 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
982
983 // Delete other features but the target feature
984 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
985 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
986 {
987 if ( *feature_it != targetFeatureId )
988 mLayer->deleteFeature( *feature_it );
989 }
990
991 // Modify target feature or create a new one if invalid
992 QgsGeometry mergeGeometry = unionGeometry;
993 if ( targetFeatureId == FID_NULL )
994 {
995 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
996 mLayer->addFeature( mergeFeature );
997 }
998 else
999 {
1000 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
1001 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
1002 }
1003
1004 mLayer->endEditCommand();
1005
1006 mLayer->triggerRepaint();
1007
1008 return true;
1009}
1010
1011bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
1012{
1013 if ( list.empty() )
1014 {
1015 return false;
1016 }
1017
1018 xmin = std::numeric_limits<double>::max();
1019 xmax = -std::numeric_limits<double>::max();
1020 ymin = std::numeric_limits<double>::max();
1021 ymax = -std::numeric_limits<double>::max();
1022
1023 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
1024 {
1025 if ( it->x() < xmin )
1026 {
1027 xmin = it->x();
1028 }
1029 if ( it->x() > xmax )
1030 {
1031 xmax = it->x();
1032 }
1033 if ( it->y() < ymin )
1034 {
1035 ymin = it->y();
1036 }
1037 if ( it->y() > ymax )
1038 {
1039 ymax = it->y();
1040 }
1041 }
1042
1043 return true;
1044}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2100
@ AddPartSelectedGeometryNotFound
The selected geometry cannot be found.
Definition qgis.h:2110
@ InvalidInputGeometryType
The input geometry (ring, part, split line, etc.) has not the correct geometry type.
Definition qgis.h:2104
@ Success
Operation succeeded.
Definition qgis.h:2101
@ AddRingNotInExistingFeature
The input ring doesn't have any existing ring to fit into.
Definition qgis.h:2116
@ AddRingNotClosed
The input ring is not closed.
Definition qgis.h:2113
@ NothingHappened
Nothing happened, without any error.
Definition qgis.h:2102
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition qgis.h:2103
@ LayerNotEditable
Cannot edit layer.
Definition qgis.h:2108
@ Feet
Imperial feet.
Definition qgis.h:5088
@ Meters
Meters.
Definition qgis.h:5086
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2256
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2254
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
Definition qgis.h:3968
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
Definition qgis.h:3969
@ DefaultValue
Use default field value.
Definition qgis.h:3966
@ Duplicate
Duplicate original value.
Definition qgis.h:3967
@ Point
Points.
Definition qgis.h:366
@ Line
Lines.
Definition qgis.h:367
@ Polygon
Polygons.
Definition qgis.h:368
@ Unknown
Unknown types.
Definition qgis.h:369
@ Null
No geometry.
Definition qgis.h:370
@ Provider
Field originates from the underlying data provider of the vector layer.
Definition qgis.h:1764
VectorEditResult
Specifies the result of a vector layer edit operation.
Definition qgis.h:1876
@ EmptyGeometry
Edit operation resulted in an empty geometry.
Definition qgis.h:1878
@ Success
Edit operation was successful.
Definition qgis.h:1877
@ FetchFeatureFailed
Unable to fetch requested feature.
Definition qgis.h:1880
@ EditFailed
Edit operation failed.
Definition qgis.h:1879
@ InvalidLayer
Edit failed due to invalid layer.
Definition qgis.h:1881
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:287
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:766
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.
Q_INVOKABLE 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:6900
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