QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
474 preserveCircular &= curve->hasCurvedSegments();
475
476 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
477 {
478 features = mLayer->getSelectedFeatures();
479 }
480 else //else consider all the feature that intersect the bounding box of the split line
481 {
482 bBox = curve->boundingBox();
483
484 if ( bBox.isEmpty() )
485 {
486 //if the bbox is a line, try to make a square out of it
487 if ( bBox.width() == 0.0 && bBox.height() > 0 )
488 {
489 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
490 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
491 }
492 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
493 {
494 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
495 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
496 }
497 else
498 {
499 //If we have a single point, we still create a non-null box
500 double bufferDistance = 0.000001;
501 if ( mLayer->crs().isGeographic() )
502 bufferDistance = 0.00000001;
503 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
504 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
505 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
506 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
507 }
508 }
509
510 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
511 }
512
514
515 const int fieldCount = mLayer->fields().count();
516
517 QgsFeature feat;
518 while ( features.nextFeature( feat ) )
519 {
520 if ( !feat.hasGeometry() )
521 {
522 continue;
523 }
524 QVector<QgsGeometry> newGeometries;
525 QgsPointSequence featureTopologyTestPoints;
526 const QgsGeometry originalGeom = feat.geometry();
527 QgsGeometry featureGeom = originalGeom;
528 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
529 topologyTestPoints.append( featureTopologyTestPoints );
530 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
531 {
532 //find largest geometry and give that to the original feature
533 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
534 double featureGeomSize = size( featureGeom );
535
536 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [&size]( const QgsGeometry &a, const QgsGeometry &b ) -> bool {
537 return size( a ) < size( b );
538 } );
539
540 if ( size( *largestNewFeature ) > featureGeomSize )
541 {
542 QgsGeometry copy = *largestNewFeature;
543 *largestNewFeature = featureGeom;
544 featureGeom = copy;
545 }
546
547 //change this geometry
548 mLayer->changeGeometry( feat.id(), featureGeom );
549
550 //update any attributes for original feature which are set to GeometryRatio split policy
551 QgsAttributeMap attributeMap;
552 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
553 {
554 const QgsField field = mLayer->fields().at( fieldIdx );
555 switch ( field.splitPolicy() )
556 {
560 break;
561
563 {
564 if ( field.isNumeric() )
565 {
566 const double originalValue = feat.attribute( fieldIdx ).toDouble();
567
568 double originalSize = 0;
569
570 switch ( originalGeom.type() )
571 {
575 originalSize = 0;
576 break;
578 originalSize = originalGeom.length();
579 break;
581 originalSize = originalGeom.area();
582 break;
583 }
584
585 double newSize = 0;
586 switch ( featureGeom.type() )
587 {
591 newSize = 0;
592 break;
594 newSize = featureGeom.length();
595 break;
597 newSize = featureGeom.area();
598 break;
599 }
600
601 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
602 }
603 break;
604 }
605 }
606 }
607
608 if ( !attributeMap.isEmpty() )
609 {
610 mLayer->changeAttributeValues( feat.id(), attributeMap );
611 }
612
613 //insert new features
614 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
615 {
616 QgsAttributeMap attributeMap;
617 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
618 {
619 const QgsField field = mLayer->fields().at( fieldIdx );
620 // respect field split policy
621 switch ( field.splitPolicy() )
622 {
624 //do nothing - default values ​​are determined
625 break;
626
628 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
629 break;
630
632 {
633 if ( !field.isNumeric() )
634 {
635 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
636 }
637 else
638 {
639 const double originalValue = feat.attribute( fieldIdx ).toDouble();
640
641 double originalSize = 0;
642
643 switch ( originalGeom.type() )
644 {
648 originalSize = 0;
649 break;
651 originalSize = originalGeom.length();
652 break;
654 originalSize = originalGeom.area();
655 break;
656 }
657
658 double newSize = 0;
659 switch ( geom.type() )
660 {
664 newSize = 0;
665 break;
667 newSize = geom.length();
668 break;
670 newSize = geom.area();
671 break;
672 }
673
674 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
675 }
676 break;
677 }
678
680 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
681 break;
682 }
683 }
684
685 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
686 }
687
688 if ( topologicalEditing )
689 {
690 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
691 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
692 {
693 addTopologicalPoints( *topol_it );
694 }
695 }
696 ++numberOfSplitFeatures;
697 }
698 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
699 {
700 returnCode = splitFunctionReturn;
701 }
702 }
703
704 if ( !featuresDataToAdd.isEmpty() )
705 {
706 // finally create and add all bits of geometries cut off the original geometries
707 // (this is much faster than creating features one by one)
708 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
709 mLayer->addFeatures( featuresListToAdd );
710 }
711
712 if ( numberOfSplitFeatures == 0 )
713 {
715 }
716
717 return returnCode;
718}
719
720Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
721{
723 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
724 {
725 l << QgsPoint( *it );
726 }
727 return splitParts( l, topologicalEditing );
728}
729
731{
732 if ( !mLayer->isSpatial() )
734
735 double xMin, yMin, xMax, yMax;
736 QgsRectangle bBox; //bounding box of the split line
737 int numberOfSplitParts = 0;
738
740
741 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
742 {
743 fit = mLayer->getSelectedFeatures();
744 }
745 else //else consider all the feature that intersect the bounding box of the split line
746 {
747 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
748 {
749 bBox.setXMinimum( xMin );
750 bBox.setYMinimum( yMin );
751 bBox.setXMaximum( xMax );
752 bBox.setYMaximum( yMax );
753 }
754 else
755 {
757 }
758
759 if ( bBox.isEmpty() )
760 {
761 //if the bbox is a line, try to make a square out of it
762 if ( bBox.width() == 0.0 && bBox.height() > 0 )
763 {
764 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
765 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
766 }
767 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
768 {
769 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
770 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
771 }
772 else
773 {
774 //If we have a single point, we still create a non-null box
775 double bufferDistance = 0.000001;
776 if ( mLayer->crs().isGeographic() )
777 bufferDistance = 0.00000001;
778 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
779 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
780 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
781 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
782 }
783 }
784
785 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
786 }
787
788 QgsFeature feat;
789 while ( fit.nextFeature( feat ) )
790 {
791 QgsGeometry featureGeom = feat.geometry();
792
793 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
794 QVector<QgsGeometry> resultCollection;
795 QgsPointSequence topologyTestPoints;
796 for ( QgsGeometry part : geomCollection )
797 {
798 QVector<QgsGeometry> newGeometries;
799 QgsPointSequence partTopologyTestPoints;
800
801 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
802
803 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
804 {
805 for ( int i = 0; i < newGeometries.size(); ++i )
806 {
807 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
808 }
809
810 topologyTestPoints.append( partTopologyTestPoints );
811
812 ++numberOfSplitParts;
813 }
814 // Note: For multilinestring layers, when the split line does not intersect the feature part,
815 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
816 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened || splitFunctionReturn == Qgis::GeometryOperationResult::InvalidBaseGeometry )
817 {
818 // Add part as is
819 resultCollection.append( part );
820 }
821 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
822 {
823 return splitFunctionReturn;
824 }
825 }
826
827 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
828 mLayer->changeGeometry( feat.id(), newGeom );
829
830 if ( topologicalEditing )
831 {
832 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
833 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
834 {
835 addTopologicalPoints( *topol_it );
836 }
837 }
838 }
839 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
840 {
841 //There is a selection but no feature has been split.
842 //Maybe user forgot that only the selected features are split
844 }
845
847}
848
849
851{
852 if ( !mLayer->isSpatial() )
853 return 1;
854
855 if ( geom.isNull() )
856 {
857 return 1;
858 }
859
860 bool pointsAdded = false;
861
863 while ( it != geom.vertices_end() )
864 {
865 if ( addTopologicalPoints( *it ) == 0 )
866 {
867 pointsAdded = true;
868 }
869 ++it;
870 }
871
872 return pointsAdded ? 0 : 2;
873}
874
876{
877 if ( !mLayer->isSpatial() )
878 return 1;
879
880 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
881
882 //work with a tolerance because coordinate projection may introduce some rounding
883 double threshold = getTopologicalSearchRadius( mLayer );
884
885 QgsRectangle searchRect( p, p, false );
886 searchRect.grow( threshold );
887
888 QgsFeature f;
889 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( searchRect ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ).setNoAttributes() );
890
891 bool pointsAdded = false;
892 while ( fit.nextFeature( f ) )
893 {
894 QgsGeometry geom = f.geometry();
895 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
896 {
897 pointsAdded = true;
898 mLayer->changeGeometry( f.id(), geom );
899 }
900 }
901
902 return pointsAdded ? 0 : 2;
903}
904
906{
907 if ( !mLayer->isSpatial() )
908 return 1;
909
910 if ( ps.isEmpty() )
911 {
912 return 1;
913 }
914
915 bool pointsAdded = false;
916
917 QgsPointSequence::const_iterator it = ps.constBegin();
918 while ( it != ps.constEnd() )
919 {
920 if ( addTopologicalPoints( *it ) == 0 )
921 {
922 pointsAdded = true;
923 }
924 ++it;
925 }
926
927 return pointsAdded ? 0 : 2;
928}
929
934
936 const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage
937)
938{
939 errorMessage.clear();
940
941 if ( mergeFeatureIds.isEmpty() )
942 {
943 errorMessage = QObject::tr( "List of features to merge is empty" );
944 return false;
945 }
946
947 QgsAttributeMap newAttributes;
948 for ( int i = 0; i < mergeAttributes.count(); ++i )
949 {
950 QVariant val = mergeAttributes.at( i );
951
952 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider
953 && mLayer->dataProvider()
954 && mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
955
956 // convert to destination data type
957 QString errorMessageConvertCompatible;
958 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
959 {
960 if ( errorMessage.isEmpty() )
961 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 );
962 }
963 newAttributes[i] = val;
964 }
965
966 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
967
968 // Delete other features but the target feature
969 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
970 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
971 {
972 if ( *feature_it != targetFeatureId )
973 mLayer->deleteFeature( *feature_it );
974 }
975
976 // Modify target feature or create a new one if invalid
977 QgsGeometry mergeGeometry = unionGeometry;
978 if ( targetFeatureId == FID_NULL )
979 {
980 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
981 mLayer->addFeature( mergeFeature );
982 }
983 else
984 {
985 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
986 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
987 }
988
989 mLayer->endEditCommand();
990
991 mLayer->triggerRepaint();
992
993 return true;
994}
995
996bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
997{
998 if ( list.empty() )
999 {
1000 return false;
1001 }
1002
1003 xmin = std::numeric_limits<double>::max();
1004 xmax = -std::numeric_limits<double>::max();
1005 ymin = std::numeric_limits<double>::max();
1006 ymax = -std::numeric_limits<double>::max();
1007
1008 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
1009 {
1010 if ( it->x() < xmin )
1011 {
1012 xmin = it->x();
1013 }
1014 if ( it->x() > xmax )
1015 {
1016 xmax = it->x();
1017 }
1018 if ( it->y() < ymin )
1019 {
1020 ymin = it->y();
1021 }
1022 if ( it->y() > ymax )
1023 {
1024 ymax = it->y();
1025 }
1026 }
1027
1028 return true;
1029}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2121
@ AddPartSelectedGeometryNotFound
The selected geometry cannot be found.
Definition qgis.h:2131
@ InvalidInputGeometryType
The input geometry (ring, part, split line, etc.) has not the correct geometry type.
Definition qgis.h:2125
@ Success
Operation succeeded.
Definition qgis.h:2122
@ AddRingNotInExistingFeature
The input ring doesn't have any existing ring to fit into.
Definition qgis.h:2137
@ AddRingNotClosed
The input ring is not closed.
Definition qgis.h:2134
@ NothingHappened
Nothing happened, without any error.
Definition qgis.h:2123
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition qgis.h:2124
@ LayerNotEditable
Cannot edit layer.
Definition qgis.h:2129
@ Feet
Imperial feet.
Definition qgis.h:5173
@ Meters
Meters.
Definition qgis.h:5171
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2278
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2276
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
Definition qgis.h:4026
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
Definition qgis.h:4027
@ DefaultValue
Use default field value.
Definition qgis.h:4024
@ Duplicate
Duplicate original value.
Definition qgis.h:4025
@ 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:1785
VectorEditResult
Specifies the result of a vector layer edit operation.
Definition qgis.h:1897
@ EmptyGeometry
Edit operation resulted in an empty geometry.
Definition qgis.h:1899
@ Success
Edit operation was successful.
Definition qgis.h:1898
@ FetchFeatureFailed
Unable to fetch requested feature.
Definition qgis.h:1901
@ EditFailed
Edit operation failed.
Definition qgis.h:1900
@ InvalidLayer
Edit failed due to invalid layer.
Definition qgis.h:1902
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:754
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:6975
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