QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
qgsvectorlayereditutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorlayereditutils.cpp
3 ---------------------
4 begin : Dezember 2012
5 copyright : (C) 2012 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
16
19#include "qgsfeatureiterator.h"
21#include "qgslinestring.h"
22#include "qgslogger.h"
23#include "qgspoint.h"
24#include "qgis.h"
25#include "qgswkbtypes.h"
26#include "qgsvectorlayerutils.h"
27#include "qgsvectorlayer.h"
28#include "qgsgeometryoptions.h"
29#include "qgsabstractgeometry.h"
32
33#include <limits>
34
35
37 : mLayer( layer )
38{
39}
40
41bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
42{
43 if ( !mLayer->isSpatial() )
44 return false;
45
46 QgsFeature f;
47 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
48 return false; // geometry not found
49
50 QgsGeometry geometry = f.geometry();
51
52 geometry.insertVertex( x, y, beforeVertex );
53
54 mLayer->changeGeometry( atFeatureId, geometry );
55 return true;
56}
57
58bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
59{
60 if ( !mLayer->isSpatial() )
61 return false;
62
63 QgsFeature f;
64 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
65 return false; // geometry not found
66
67 QgsGeometry geometry = f.geometry();
68
69 geometry.insertVertex( point, beforeVertex );
70
71 mLayer->changeGeometry( atFeatureId, geometry );
72 return true;
73}
74
75bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
76{
77 QgsPoint p( x, y );
78 return moveVertex( p, atFeatureId, atVertex );
79}
80
81bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
82{
83 if ( !mLayer->isSpatial() )
84 return false;
85
86 QgsFeature f;
87 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
88 return false; // geometry not found
89
90 QgsGeometry geometry = f.geometry();
91
92 // If original point is not 3D but destination yes, check if it can be promoted
93 if ( p.is3D() && !geometry.constGet()->is3D() && QgsWkbTypes::hasZ( mLayer->wkbType() ) )
94 {
96 return false;
97 }
98
99 // If original point has not M-value but destination yes, check if it can be promoted
100 if ( p.isMeasure() && !geometry.constGet()->isMeasure() && QgsWkbTypes::hasM( mLayer->wkbType() ) )
101 {
103 return false;
104 }
105
106 if ( !geometry.moveVertex( p, atVertex ) )
107 return false;
108
109 return mLayer->changeGeometry( atFeatureId, geometry );
110}
111
113{
114 if ( !mLayer->isSpatial() )
116
117 QgsFeature f;
118 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
119 return Qgis::VectorEditResult::FetchFeatureFailed; // geometry not found
120
121 QgsGeometry geometry = f.geometry();
122
123 if ( !geometry.deleteVertex( vertex ) )
125
126 if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
127 {
128 //last vertex deleted, set geometry to null
129 geometry.set( nullptr );
130 }
131
132 mLayer->changeGeometry( featureId, geometry );
134}
135
136
137static
138Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ptr< QgsCurve > &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureIds *modifiedFeatureIds, bool firstOne = true )
139{
140
141 if ( !layer || !layer->isSpatial() )
142 {
144 }
145
146 if ( !ring )
147 {
149 }
150
151 if ( !ring->isClosed() )
152 {
154 }
155
156 if ( !layer->isValid() || !layer->editBuffer() || !layer->dataProvider() )
157 {
159 }
160
161 Qgis::GeometryOperationResult addRingReturnCode = Qgis::GeometryOperationResult::AddRingNotInExistingFeature; //default: return code for 'ring not inserted'
162 QgsFeature f;
163
165 if ( !targetFeatureIds.isEmpty() )
166 {
167 //check only specified features
168 fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) );
169 }
170 else
171 {
172 //check all intersecting features
173 QgsRectangle bBox = ring->boundingBox();
174 fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
175 }
176
177 //find first valid feature we can add the ring to
178 while ( fit.nextFeature( f ) )
179 {
180 if ( !f.hasGeometry() )
181 continue;
182
183 //add ring takes ownership of ring, and deletes it if there's an error
184 QgsGeometry g = f.geometry();
185
186 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
187 if ( addRingReturnCode == Qgis::GeometryOperationResult::Success )
188 {
189 layer->changeGeometry( f.id(), g );
190 if ( modifiedFeatureIds )
191 {
192 modifiedFeatureIds->insert( f.id() );
193 if ( firstOne )
194 {
195 break;
196 }
197 }
198
199 }
200 }
201
202 return addRingReturnCode;
203}
204
205Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
206{
208 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
209 {
210 l << QgsPoint( *it );
211 }
212 return addRing( l, targetFeatureIds, modifiedFeatureId );
213}
214
216{
217 QgsLineString *ringLine = new QgsLineString( ring );
218 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
219}
220
222{
223 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
224 if ( modifiedFeatureId )
225 {
226 QgsFeatureIds *modifiedFeatureIds = new QgsFeatureIds;
227 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, true );
228 *modifiedFeatureId = *modifiedFeatureIds->begin();
229 return result;
230 }
231 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
232}
233
235{
236
237 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
238 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
239}
240
241
242
244{
246 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
247 {
248 l << QgsPoint( *it );
249 }
250 return addPart( l, featureId );
251}
252
254{
255 if ( !mLayer->isSpatial() )
257
258 QgsGeometry geometry;
259 bool firstPart = false;
260 QgsFeature f;
261 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
263
264 if ( !f.hasGeometry() )
265 {
266 //no existing geometry, so adding first part to null geometry
267 firstPart = true;
268 }
269 else
270 {
271 geometry = f.geometry();
272 }
273
274 Qgis::GeometryOperationResult errorCode = geometry.addPart( points, mLayer->geometryType() );
276 {
277 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
279 {
280 //convert back to single part if required by layer
281 geometry.convertToSingleType();
282 }
283 mLayer->changeGeometry( featureId, geometry );
284 }
285 return errorCode;
286}
287
289{
290 if ( !mLayer->isSpatial() )
292
293 QgsGeometry geometry;
294 bool firstPart = false;
295 QgsFeature f;
296 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
298
299 if ( !f.hasGeometry() )
300 {
301 //no existing geometry, so adding first part to null geometry
302 firstPart = true;
303 }
304 else
305 {
306 geometry = f.geometry();
307 }
308
309 Qgis::GeometryOperationResult errorCode = geometry.addPart( ring, mLayer->geometryType() );
311 {
312 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
314 {
315 //convert back to single part if required by layer
316 geometry.convertToSingleType();
317 }
318 mLayer->changeGeometry( featureId, geometry );
319 }
320 return errorCode;
321}
322
323// TODO QGIS 4.0 -- this should return Qgis::GeometryOperationResult
324int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
325{
326 if ( !mLayer->isSpatial() )
327 return 1;
328
329 QgsFeature f;
330 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
331 return 1; //geometry not found
332
333 QgsGeometry geometry = f.geometry();
334
335 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
337 {
338 mLayer->changeGeometry( featureId, geometry );
339 }
340 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
341}
342
343Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
344{
346 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
347 {
348 l << QgsPoint( *it );
349 }
350 return splitFeatures( l, topologicalEditing );
351}
352
354{
355 QgsLineString lineString( splitLine );
356 QgsPointSequence topologyTestPoints;
357 bool preserveCircular = false;
358 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
359}
360
361Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
362{
363 if ( !mLayer->isSpatial() )
365
366 QgsRectangle bBox; //bounding box of the split line
368 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
369 int numberOfSplitFeatures = 0;
370
371 QgsFeatureIterator features;
372 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
373
374 // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
375 preserveCircular &= curve->hasCurvedSegments();
376
377 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
378 {
379 features = mLayer->getSelectedFeatures();
380 }
381 else //else consider all the feature that intersect the bounding box of the split line
382 {
383
384 bBox = curve->boundingBox();
385
386 if ( bBox.isEmpty() )
387 {
388 //if the bbox is a line, try to make a square out of it
389 if ( bBox.width() == 0.0 && bBox.height() > 0 )
390 {
391 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
392 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
393 }
394 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
395 {
396 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
397 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
398 }
399 else
400 {
401 //If we have a single point, we still create a non-null box
402 double bufferDistance = 0.000001;
403 if ( mLayer->crs().isGeographic() )
404 bufferDistance = 0.00000001;
405 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
406 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
407 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
408 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
409 }
410 }
411
412 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
413 }
414
416
417 const int fieldCount = mLayer->fields().count();
418
419 QgsFeature feat;
420 while ( features.nextFeature( feat ) )
421 {
422 if ( !feat.hasGeometry() )
423 {
424 continue;
425 }
426 QVector<QgsGeometry> newGeometries;
427 QgsPointSequence featureTopologyTestPoints;
428 const QgsGeometry originalGeom = feat.geometry();
429 QgsGeometry featureGeom = originalGeom;
430 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
431 topologyTestPoints.append( featureTopologyTestPoints );
432 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
433 {
434 //change this geometry
435 mLayer->changeGeometry( feat.id(), featureGeom );
436
437 //update any attributes for original feature which are set to GeometryRatio split policy
438 QgsAttributeMap attributeMap;
439 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
440 {
441 const QgsField field = mLayer->fields().at( fieldIdx );
442 switch ( field.splitPolicy() )
443 {
447 break;
448
450 {
451 if ( field.isNumeric() )
452 {
453 const double originalValue = feat.attribute( fieldIdx ).toDouble();
454
455 double originalSize = 0;
456
457 switch ( originalGeom.type() )
458 {
459 case Qgis::GeometryType::Point:
460 case Qgis::GeometryType::Unknown:
461 case Qgis::GeometryType::Null:
462 originalSize = 0;
463 break;
464 case Qgis::GeometryType::Line:
465 originalSize = originalGeom.length();
466 break;
467 case Qgis::GeometryType::Polygon:
468 originalSize = originalGeom.area();
469 break;
470 }
471
472 double newSize = 0;
473 switch ( featureGeom.type() )
474 {
475 case Qgis::GeometryType::Point:
476 case Qgis::GeometryType::Unknown:
477 case Qgis::GeometryType::Null:
478 newSize = 0;
479 break;
480 case Qgis::GeometryType::Line:
481 newSize = featureGeom.length();
482 break;
483 case Qgis::GeometryType::Polygon:
484 newSize = featureGeom.area();
485 break;
486 }
487
488 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
489 }
490 break;
491 }
492 }
493 }
494
495 if ( !attributeMap.isEmpty() )
496 {
497 mLayer->changeAttributeValues( feat.id(), attributeMap );
498 }
499
500 //insert new features
501 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
502 {
503 QgsAttributeMap attributeMap;
504 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
505 {
506 const QgsField field = mLayer->fields().at( fieldIdx );
507 // respect field split policy
508 switch ( field.splitPolicy() )
509 {
511 // TODO!!!
512
513 break;
514
516 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
517 break;
518
520 {
521 if ( !field.isNumeric() )
522 {
523 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
524 }
525 else
526 {
527 const double originalValue = feat.attribute( fieldIdx ).toDouble();
528
529 double originalSize = 0;
530
531 switch ( originalGeom.type() )
532 {
533 case Qgis::GeometryType::Point:
534 case Qgis::GeometryType::Unknown:
535 case Qgis::GeometryType::Null:
536 originalSize = 0;
537 break;
538 case Qgis::GeometryType::Line:
539 originalSize = originalGeom.length();
540 break;
541 case Qgis::GeometryType::Polygon:
542 originalSize = originalGeom.area();
543 break;
544 }
545
546 double newSize = 0;
547 switch ( geom.type() )
548 {
549 case Qgis::GeometryType::Point:
550 case Qgis::GeometryType::Unknown:
551 case Qgis::GeometryType::Null:
552 newSize = 0;
553 break;
554 case Qgis::GeometryType::Line:
555 newSize = geom.length();
556 break;
557 case Qgis::GeometryType::Polygon:
558 newSize = geom.area();
559 break;
560 }
561
562 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
563 }
564 break;
565 }
566
568 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
569 break;
570 }
571 }
572
573 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
574 }
575
576 if ( topologicalEditing )
577 {
578 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
579 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
580 {
581 addTopologicalPoints( *topol_it );
582 }
583 }
584 ++numberOfSplitFeatures;
585 }
586 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
587 {
588 returnCode = splitFunctionReturn;
589 }
590 }
591
592 if ( !featuresDataToAdd.isEmpty() )
593 {
594 // finally create and add all bits of geometries cut off the original geometries
595 // (this is much faster than creating features one by one)
596 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
597 mLayer->addFeatures( featuresListToAdd );
598 }
599
600 if ( numberOfSplitFeatures == 0 )
601 {
603 }
604
605 return returnCode;
606}
607
608Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
609{
611 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
612 {
613 l << QgsPoint( *it );
614 }
615 return splitParts( l, topologicalEditing );
616}
617
619{
620 if ( !mLayer->isSpatial() )
622
623 double xMin, yMin, xMax, yMax;
624 QgsRectangle bBox; //bounding box of the split line
625 int numberOfSplitParts = 0;
626
628
629 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
630 {
631 fit = mLayer->getSelectedFeatures();
632 }
633 else //else consider all the feature that intersect the bounding box of the split line
634 {
635 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
636 {
637 bBox.setXMinimum( xMin );
638 bBox.setYMinimum( yMin );
639 bBox.setXMaximum( xMax );
640 bBox.setYMaximum( yMax );
641 }
642 else
643 {
645 }
646
647 if ( bBox.isEmpty() )
648 {
649 //if the bbox is a line, try to make a square out of it
650 if ( bBox.width() == 0.0 && bBox.height() > 0 )
651 {
652 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
653 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
654 }
655 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
656 {
657 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
658 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
659 }
660 else
661 {
662 //If we have a single point, we still create a non-null box
663 double bufferDistance = 0.000001;
664 if ( mLayer->crs().isGeographic() )
665 bufferDistance = 0.00000001;
666 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
667 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
668 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
669 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
670 }
671 }
672
673 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
674 }
675
676 QgsFeature feat;
677 while ( fit.nextFeature( feat ) )
678 {
679 QgsGeometry featureGeom = feat.geometry();
680
681 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
682 QVector<QgsGeometry> resultCollection;
683 QgsPointSequence topologyTestPoints;
684 for ( QgsGeometry part : geomCollection )
685 {
686 QVector<QgsGeometry> newGeometries;
687 QgsPointSequence partTopologyTestPoints;
688
689 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
690
691 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
692 {
693 for ( int i = 0; i < newGeometries.size(); ++i )
694 {
695 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
696 }
697
698 topologyTestPoints.append( partTopologyTestPoints );
699
700 ++numberOfSplitParts;
701 }
702 // Note: For multilinestring layers, when the split line does not intersect the feature part,
703 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
704 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened ||
706 {
707 // Add part as is
708 resultCollection.append( part );
709 }
710 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
711 {
712 return splitFunctionReturn;
713 }
714 }
715
716 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
717 mLayer->changeGeometry( feat.id(), newGeom ) ;
718
719 if ( topologicalEditing )
720 {
721 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
722 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
723 {
724 addTopologicalPoints( *topol_it );
725 }
726 }
727
728 }
729 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
730 {
731 //There is a selection but no feature has been split.
732 //Maybe user forgot that only the selected features are split
734 }
735
737}
738
739
741{
742 if ( !mLayer->isSpatial() )
743 return 1;
744
745 if ( geom.isNull() )
746 {
747 return 1;
748 }
749
750 bool pointsAdded = false;
751
753 while ( it != geom.vertices_end() )
754 {
755 if ( addTopologicalPoints( *it ) == 0 )
756 {
757 pointsAdded = true;
758 }
759 ++it;
760 }
761
762 return pointsAdded ? 0 : 2;
763}
764
766{
767 if ( !mLayer->isSpatial() )
768 return 1;
769
770 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
771
772 //work with a tolerance because coordinate projection may introduce some rounding
773 double threshold = mLayer->geometryOptions()->geometryPrecision();
774
775 if ( qgsDoubleNear( threshold, 0.0 ) )
776 {
777 threshold = 0.0000001;
778
779 if ( mLayer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
780 {
781 threshold = 0.001;
782 }
783 else if ( mLayer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
784 {
785 threshold = 0.0001;
786 }
787 }
788
789 QgsRectangle searchRect( p.x() - threshold, p.y() - threshold,
790 p.x() + threshold, p.y() + threshold );
791 double sqrSnappingTolerance = threshold * threshold;
792
793 QgsFeature f;
795 .setFilterRect( searchRect )
797 .setNoAttributes() );
798
799 QMap<QgsFeatureId, QgsGeometry> features;
800 QMap<QgsFeatureId, int> segments;
801
802 while ( fit.nextFeature( f ) )
803 {
804 int afterVertex;
805 QgsPointXY snappedPoint;
806 double sqrDistSegmentSnap = f.geometry().closestSegmentWithContext( p, snappedPoint, afterVertex, nullptr, segmentSearchEpsilon );
807 if ( sqrDistSegmentSnap < sqrSnappingTolerance )
808 {
809 segments[f.id()] = afterVertex;
810 features[f.id()] = f.geometry();
811 }
812 }
813
814 if ( segments.isEmpty() )
815 return 2;
816
817 bool pointsAdded = false;
818 for ( QMap<QgsFeatureId, int>::const_iterator it = segments.constBegin(); it != segments.constEnd(); ++it )
819 {
820 QgsFeatureId fid = it.key();
821 int segmentAfterVertex = it.value();
822 QgsGeometry geom = features[fid];
823
824 int atVertex, beforeVertex, afterVertex;
825 double sqrDistVertexSnap;
826 geom.closestVertex( p, atVertex, beforeVertex, afterVertex, sqrDistVertexSnap );
827
828 if ( sqrDistVertexSnap < sqrSnappingTolerance )
829 continue; // the vertex already exists - do not insert it
830
831 if ( !mLayer->insertVertex( p, fid, segmentAfterVertex ) )
832 {
833 QgsDebugError( QStringLiteral( "failed to insert topo point" ) );
834 }
835 else
836 {
837 pointsAdded = true;
838 }
839 }
840
841 return pointsAdded ? 0 : 2;
842}
843
845{
846 if ( !mLayer->isSpatial() )
847 return 1;
848
849 if ( ps.isEmpty() )
850 {
851 return 1;
852 }
853
854 bool pointsAdded = false;
855
856 QgsPointSequence::const_iterator it = ps.constBegin();
857 while ( it != ps.constEnd() )
858 {
859 if ( addTopologicalPoints( *it ) == 0 )
860 {
861 pointsAdded = true;
862 }
863 ++it;
864 }
865
866 return pointsAdded ? 0 : 2;
867}
868
870{
871 return addTopologicalPoints( QgsPoint( p ) );
872}
873
874bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
875{
876 errorMessage.clear();
877
878 if ( mergeFeatureIds.isEmpty() )
879 {
880 errorMessage = QObject::tr( "List of features to merge is empty" );
881 return false;
882 }
883
884 QgsAttributeMap newAttributes;
885 for ( int i = 0; i < mergeAttributes.count(); ++i )
886 {
887 QVariant val = mergeAttributes.at( i );
888
889 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
890 mLayer->dataProvider() &&
891 mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
892
893 // convert to destination data type
894 QString errorMessageConvertCompatible;
895 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
896 {
897 if ( errorMessage.isEmpty() )
898 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 );
899 }
900 newAttributes[ i ] = val;
901 }
902
903 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
904
905 // Delete other features but the target feature
906 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
907 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
908 {
909 if ( *feature_it != targetFeatureId )
910 mLayer->deleteFeature( *feature_it );
911 }
912
913 // Modify target feature or create a new one if invalid
914 QgsGeometry mergeGeometry = unionGeometry;
915 if ( targetFeatureId == FID_NULL )
916 {
917 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
918 mLayer->addFeature( mergeFeature );
919 }
920 else
921 {
922 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
923 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
924 }
925
926 mLayer->endEditCommand();
927
928 mLayer->triggerRepaint();
929
930 return true;
931}
932
933bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
934{
935 if ( list.empty() )
936 {
937 return false;
938 }
939
940 xmin = std::numeric_limits<double>::max();
941 xmax = -std::numeric_limits<double>::max();
942 ymin = std::numeric_limits<double>::max();
943 ymax = -std::numeric_limits<double>::max();
944
945 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
946 {
947 if ( it->x() < xmin )
948 {
949 xmin = it->x();
950 }
951 if ( it->x() > xmax )
952 {
953 xmax = it->x();
954 }
955 if ( it->y() < ymin )
956 {
957 ymin = it->y();
958 }
959 if ( it->y() > ymax )
960 {
961 ymax = it->y();
962 }
963 }
964
965 return true;
966}
GeometryOperationResult
Success or failure of a geometry operation.
Definition: qgis.h:1424
@ AddPartSelectedGeometryNotFound
The selected geometry cannot be found.
@ InvalidInputGeometryType
The input geometry (ring, part, split line, etc.) has not the correct geometry type.
@ Success
Operation succeeded.
@ AddRingNotInExistingFeature
The input ring doesn't have any existing ring to fit into.
@ AddRingNotClosed
The input ring is not closed.
@ NothingHappened
Nothing happened, without any error.
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
@ LayerNotEditable
Cannot edit layer.
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
@ DefaultValue
Use default field value.
@ Duplicate
Duplicate original value.
VectorEditResult
Flags which control feature selection behavior.
Definition: qgis.h:1214
@ EmptyGeometry
Edit operation resulted in an empty geometry.
@ Success
Edit operation was successful.
@ FetchFeatureFailed
Unable to fetch requested feature.
@ EditFailed
Edit operation failed.
@ InvalidLayer
Edit failed due to invalid layer.
The vertex_iterator class provides 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 is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
bool isMeasure() const SIP_HOLDGIL
Returns true if the geometry contains m values.
A vector of attributes.
Definition: qgsattributes.h:59
Q_GADGET Qgis::DistanceUnit mapUnits
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
QgsRectangle boundingBox() const override
Returns the minimal bounding box for the geometry.
Definition: qgscurve.cpp:238
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:230
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:335
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
QString typeName() const
Gets the field type.
Definition: qgsfield.cpp:150
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition: qgsfield.cpp:452
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:729
Q_GADGET bool isNumeric
Definition: qgsfield.h:56
@ OriginProvider
Field comes from the underlying data provider of the vector layer (originIndex = index in provider's ...
Definition: qgsfields.h:51
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Definition: qgsfields.cpp:189
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
Definition: qgsfields.cpp:197
double geometryPrecision() const
The precision in which geometries on this layer should be saved.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
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.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
QgsPointXY closestVertex(const QgsPointXY &point, int &closestVertexIndex, int &previousVertexIndex, int &nextVertexIndex, double &sqrDist) const
Returns the vertex closest to the given point, the corresponding vertex index, squared distance snap ...
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
QgsAbstractGeometry * get()
Returns a modifiable (non-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
Definition: qgsgeometry.h:167
double area() const
Returns the planar, 2-dimensional area of the geometry.
Qgis::GeometryOperationResult addPart(const QVector< QgsPointXY > &points, Qgis::GeometryType geomType=Qgis::GeometryType::Unknown)
Adds a new part to a the geometry.
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.
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...
double closestSegmentWithContext(const QgsPointXY &point, QgsPointXY &minDistPoint, int &nextVertexIndex, int *leftOrRightOfSegment=nullptr, double epsilon=DEFAULT_SEGMENT_EPSILON) const
Searches for the closest segment of geometry to the given point.
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.
Definition: qgslinestring.h:45
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
bool isValid
Definition: qgsmaplayer.h:81
A class to represent a 2D point.
Definition: qgspointxy.h:59
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
static const QgsSettingsEntryDouble * settingsDigitizingDefaultMValue
Settings entry digitizing default m value.
static const QgsSettingsEntryDouble * settingsDigitizingDefaultZValue
Settings entry digitizing default z value.
Represents a default, "not-specified" value for a feature attribute.
virtual bool doesStrictFeatureTypeCheck() const
Returns true if the provider is strict about the type of inserted features (e.g.
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index.
int translateFeature(QgsFeatureId featureId, double dx, double dy)
Translates feature by dx, dy.
bool mergeFeatures(const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage)
Merge features into a single one.
QgsVectorLayerEditUtils(QgsVectorLayer *layer)
bool insertVertex(double x, double y, QgsFeatureId atFeatureId, int beforeVertex)
Insert a new vertex before the given vertex number, in the given ring, item (first number is index 0)...
Q_DECL_DEPRECATED Qgis::GeometryOperationResult addPart(const QVector< QgsPointXY > &ring, QgsFeatureId featureId)
Adds a new part polygon to a multipart feature.
Qgis::VectorEditResult deleteVertex(QgsFeatureId featureId, int vertex)
Deletes a vertex from a feature.
Qgis::GeometryOperationResult addRingV2(QgsCurve *ring, const QgsFeatureIds &targetFeatureIds=QgsFeatureIds(), QgsFeatureIds *modifiedFeatureIds=nullptr)
Adds a ring to polygon/multipolygon features.
int addTopologicalPoints(const QgsGeometry &geom)
Adds topological points for every vertex of the geometry.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitParts(const QVector< QgsPointXY > &splitLine, bool topologicalEditing=false)
Splits parts cut by the given line.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitFeatures(const QVector< QgsPointXY > &splitLine, bool topologicalEditing=false)
Splits features cut by the given line.
bool moveVertex(double x, double y, QgsFeatureId atFeatureId, int atVertex)
Moves the vertex at the given position number, ring and item (first number is index 0),...
Q_DECL_DEPRECATED Qgis::GeometryOperationResult addRing(const QVector< QgsPointXY > &ring, const QgsFeatureIds &targetFeatureIds=QgsFeatureIds(), QgsFeatureId *modifiedFeatureId=nullptr)
Adds a ring to polygon/multipolygon features.
Encapsulate geometry and attributes for new features, to be passed to createFeatures.
QList< QgsVectorLayerUtils::QgsFeatureData > QgsFeaturesDataList
Alias for list of QgsFeatureData.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
static QgsFeatureList createFeatures(const QgsVectorLayer *layer, const QgsFeaturesDataList &featuresData, QgsExpressionContext *context=nullptr)
Creates a set of new features ready for insertion into a layer.
Represents a vector layer which manages a vector based data sets.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
bool deleteFeature(QgsFeatureId fid, DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
bool insertVertex(double x, double y, QgsFeatureId atFeatureId, int beforeVertex)
Inserts a new vertex before the given vertex number, in the given ring, item (first number is index 0...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
bool addFeatures(QgsFeatureList &features, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a list of features to the sink.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
void endEditCommand()
Finish edit command and add it to undo/redo stack.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
bool changeAttributeValues(QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues=QgsAttributeMap(), bool skipDefaultValues=false)
Changes attributes' values for a feature (but does not immediately commit the changes).
QgsGeometryOptions * geometryOptions() const
Configuration and logic to apply automatically on any edit happening on this layer.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
QgsFeatureIterator getSelectedFeatures(QgsFeatureRequest request=QgsFeatureRequest()) const
Returns an iterator of the selected features.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
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 bool isSingleType(Qgis::WkbType type) SIP_HOLDGIL
Returns true if the WKB type is a single type.
Definition: qgswkbtypes.h:749
static bool hasZ(Qgis::WkbType type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:977
static bool hasM(Qgis::WkbType type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1027
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3988
QVector< QgsPoint > QgsPointSequence
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:42
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:920
#define FID_NULL
Definition: qgsfeatureid.h:29
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
const QgsField & field
Definition: qgsfield.h:554
#define QgsDebugError(str)
Definition: qgslogger.h:38