QGIS API Documentation 3.36.0-Maidenhead (09951dc0acf)
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
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
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( Qgis::FeatureRequestFlag::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 if ( ring->orientation() != g.polygonOrientation() )
187 {
188 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
189 }
190 else
191 {
192 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->reversed() ) );
193 }
194 if ( addRingReturnCode == Qgis::GeometryOperationResult::Success )
195 {
196 layer->changeGeometry( f.id(), g );
197 if ( modifiedFeatureIds )
198 {
199 modifiedFeatureIds->insert( f.id() );
200 if ( firstOne )
201 {
202 break;
203 }
204 }
205
206 }
207 }
208
209 return addRingReturnCode;
210}
211
212Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
213{
215 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
216 {
217 l << QgsPoint( *it );
218 }
219 return addRing( l, targetFeatureIds, modifiedFeatureId );
220}
221
223{
224 QgsLineString *ringLine = new QgsLineString( ring );
225 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
226}
227
229{
230 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
231 if ( modifiedFeatureId )
232 {
233 QgsFeatureIds *modifiedFeatureIds = new QgsFeatureIds;
234 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, true );
235 *modifiedFeatureId = *modifiedFeatureIds->begin();
236 return result;
237 }
238 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
239}
240
242{
243
244 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
245 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
246}
247
248
249
251{
253 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
254 {
255 l << QgsPoint( *it );
256 }
257 return addPart( l, featureId );
258}
259
261{
262 if ( !mLayer->isSpatial() )
264
265 QgsGeometry geometry;
266 bool firstPart = false;
267 QgsFeature f;
268 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
270
271 if ( !f.hasGeometry() )
272 {
273 //no existing geometry, so adding first part to null geometry
274 firstPart = true;
275 }
276 else
277 {
278 geometry = f.geometry();
279 }
280
281 Qgis::GeometryOperationResult errorCode = geometry.addPart( points, mLayer->geometryType() );
283 {
284 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
286 {
287 //convert back to single part if required by layer
288 geometry.convertToSingleType();
289 }
290 mLayer->changeGeometry( featureId, geometry );
291 }
292 return errorCode;
293}
294
296{
297
298 if ( !mLayer->isSpatial() )
300
301 QgsGeometry geometry;
302 bool firstPart = false;
303 QgsFeature f;
304 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
306
307 if ( !f.hasGeometry() )
308 {
309 //no existing geometry, so adding first part to null geometry
310 firstPart = true;
311 }
312 else
313 {
314 geometry = f.geometry();
315 if ( ring->orientation() != geometry.polygonOrientation() )
316 {
317 ring = ring->reversed();
318 }
319 }
320 Qgis::GeometryOperationResult errorCode = geometry.addPart( ring, mLayer->geometryType() );
321
323 {
324 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
326 {
327 //convert back to single part if required by layer
328 geometry.convertToSingleType();
329 }
330 mLayer->changeGeometry( featureId, geometry );
331 }
332 return errorCode;
333}
334
335// TODO QGIS 4.0 -- this should return Qgis::GeometryOperationResult
336int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
337{
338 if ( !mLayer->isSpatial() )
339 return 1;
340
341 QgsFeature f;
342 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
343 return 1; //geometry not found
344
345 QgsGeometry geometry = f.geometry();
346
347 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
349 {
350 mLayer->changeGeometry( featureId, geometry );
351 }
352 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
353}
354
355Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
356{
357
359 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
360 {
361 l << QgsPoint( *it );
362 }
363 return splitFeatures( l, topologicalEditing );
364}
365
367{
368 QgsLineString lineString( splitLine );
369 QgsPointSequence topologyTestPoints;
370 bool preserveCircular = false;
371 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
372}
373
374Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
375{
376 if ( !mLayer->isSpatial() )
378
379 QgsRectangle bBox; //bounding box of the split line
381 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
382 int numberOfSplitFeatures = 0;
383
384 QgsFeatureIterator features;
385 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
386
387 // deactivate preserving circular if the curve contains only straight segments to avoid transforming Polygon to CurvePolygon
388 preserveCircular &= curve->hasCurvedSegments();
389
390 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
391 {
392 features = mLayer->getSelectedFeatures();
393 }
394 else //else consider all the feature that intersect the bounding box of the split line
395 {
396
397 bBox = curve->boundingBox();
398
399 if ( bBox.isEmpty() )
400 {
401 //if the bbox is a line, try to make a square out of it
402 if ( bBox.width() == 0.0 && bBox.height() > 0 )
403 {
404 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
405 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
406 }
407 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
408 {
409 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
410 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
411 }
412 else
413 {
414 //If we have a single point, we still create a non-null box
415 double bufferDistance = 0.000001;
416 if ( mLayer->crs().isGeographic() )
417 bufferDistance = 0.00000001;
418 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
419 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
420 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
421 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
422 }
423 }
424
425 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
426 }
427
429
430 const int fieldCount = mLayer->fields().count();
431
432 QgsFeature feat;
433 while ( features.nextFeature( feat ) )
434 {
435 if ( !feat.hasGeometry() )
436 {
437 continue;
438 }
439 QVector<QgsGeometry> newGeometries;
440 QgsPointSequence featureTopologyTestPoints;
441 const QgsGeometry originalGeom = feat.geometry();
442 QgsGeometry featureGeom = originalGeom;
443 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
444 topologyTestPoints.append( featureTopologyTestPoints );
445 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
446 {
447 //change this geometry
448 mLayer->changeGeometry( feat.id(), featureGeom );
449
450 //update any attributes for original feature which are set to GeometryRatio split policy
451 QgsAttributeMap attributeMap;
452 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
453 {
454 const QgsField field = mLayer->fields().at( fieldIdx );
455 switch ( field.splitPolicy() )
456 {
460 break;
461
463 {
464 if ( field.isNumeric() )
465 {
466 const double originalValue = feat.attribute( fieldIdx ).toDouble();
467
468 double originalSize = 0;
469
470 switch ( originalGeom.type() )
471 {
475 originalSize = 0;
476 break;
478 originalSize = originalGeom.length();
479 break;
481 originalSize = originalGeom.area();
482 break;
483 }
484
485 double newSize = 0;
486 switch ( featureGeom.type() )
487 {
491 newSize = 0;
492 break;
494 newSize = featureGeom.length();
495 break;
497 newSize = featureGeom.area();
498 break;
499 }
500
501 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
502 }
503 break;
504 }
505 }
506 }
507
508 if ( !attributeMap.isEmpty() )
509 {
510 mLayer->changeAttributeValues( feat.id(), attributeMap );
511 }
512
513 //insert new features
514 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
515 {
516 QgsAttributeMap attributeMap;
517 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
518 {
519 const QgsField field = mLayer->fields().at( fieldIdx );
520 // respect field split policy
521 switch ( field.splitPolicy() )
522 {
524 // TODO!!!
525
526 break;
527
529 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
530 break;
531
533 {
534 if ( !field.isNumeric() )
535 {
536 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
537 }
538 else
539 {
540 const double originalValue = feat.attribute( fieldIdx ).toDouble();
541
542 double originalSize = 0;
543
544 switch ( originalGeom.type() )
545 {
549 originalSize = 0;
550 break;
552 originalSize = originalGeom.length();
553 break;
555 originalSize = originalGeom.area();
556 break;
557 }
558
559 double newSize = 0;
560 switch ( geom.type() )
561 {
565 newSize = 0;
566 break;
568 newSize = geom.length();
569 break;
571 newSize = geom.area();
572 break;
573 }
574
575 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
576 }
577 break;
578 }
579
581 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
582 break;
583 }
584 }
585
586 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
587 }
588
589 if ( topologicalEditing )
590 {
591 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
592 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
593 {
594 addTopologicalPoints( *topol_it );
595 }
596 }
597 ++numberOfSplitFeatures;
598 }
599 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
600 {
601 returnCode = splitFunctionReturn;
602 }
603 }
604
605 if ( !featuresDataToAdd.isEmpty() )
606 {
607 // finally create and add all bits of geometries cut off the original geometries
608 // (this is much faster than creating features one by one)
609 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
610 mLayer->addFeatures( featuresListToAdd );
611 }
612
613 if ( numberOfSplitFeatures == 0 )
614 {
616 }
617
618 return returnCode;
619}
620
621Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
622{
624 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
625 {
626 l << QgsPoint( *it );
627 }
628 return splitParts( l, topologicalEditing );
629}
630
632{
633 if ( !mLayer->isSpatial() )
635
636 double xMin, yMin, xMax, yMax;
637 QgsRectangle bBox; //bounding box of the split line
638 int numberOfSplitParts = 0;
639
641
642 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
643 {
644 fit = mLayer->getSelectedFeatures();
645 }
646 else //else consider all the feature that intersect the bounding box of the split line
647 {
648 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
649 {
650 bBox.setXMinimum( xMin );
651 bBox.setYMinimum( yMin );
652 bBox.setXMaximum( xMax );
653 bBox.setYMaximum( yMax );
654 }
655 else
656 {
658 }
659
660 if ( bBox.isEmpty() )
661 {
662 //if the bbox is a line, try to make a square out of it
663 if ( bBox.width() == 0.0 && bBox.height() > 0 )
664 {
665 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
666 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
667 }
668 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
669 {
670 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
671 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
672 }
673 else
674 {
675 //If we have a single point, we still create a non-null box
676 double bufferDistance = 0.000001;
677 if ( mLayer->crs().isGeographic() )
678 bufferDistance = 0.00000001;
679 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
680 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
681 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
682 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
683 }
684 }
685
686 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
687 }
688
689 QgsFeature feat;
690 while ( fit.nextFeature( feat ) )
691 {
692 QgsGeometry featureGeom = feat.geometry();
693
694 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
695 QVector<QgsGeometry> resultCollection;
696 QgsPointSequence topologyTestPoints;
697 for ( QgsGeometry part : geomCollection )
698 {
699 QVector<QgsGeometry> newGeometries;
700 QgsPointSequence partTopologyTestPoints;
701
702 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
703
704 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
705 {
706 for ( int i = 0; i < newGeometries.size(); ++i )
707 {
708 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
709 }
710
711 topologyTestPoints.append( partTopologyTestPoints );
712
713 ++numberOfSplitParts;
714 }
715 // Note: For multilinestring layers, when the split line does not intersect the feature part,
716 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
717 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened ||
719 {
720 // Add part as is
721 resultCollection.append( part );
722 }
723 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
724 {
725 return splitFunctionReturn;
726 }
727 }
728
729 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
730 mLayer->changeGeometry( feat.id(), newGeom ) ;
731
732 if ( topologicalEditing )
733 {
734 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
735 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
736 {
737 addTopologicalPoints( *topol_it );
738 }
739 }
740
741 }
742 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
743 {
744 //There is a selection but no feature has been split.
745 //Maybe user forgot that only the selected features are split
747 }
748
750}
751
752
754{
755 if ( !mLayer->isSpatial() )
756 return 1;
757
758 if ( geom.isNull() )
759 {
760 return 1;
761 }
762
763 bool pointsAdded = false;
764
766 while ( it != geom.vertices_end() )
767 {
768 if ( addTopologicalPoints( *it ) == 0 )
769 {
770 pointsAdded = true;
771 }
772 ++it;
773 }
774
775 return pointsAdded ? 0 : 2;
776}
777
779{
780 if ( !mLayer->isSpatial() )
781 return 1;
782
783 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
784
785 //work with a tolerance because coordinate projection may introduce some rounding
786 double threshold = mLayer->geometryOptions()->geometryPrecision();
787
788 if ( qgsDoubleNear( threshold, 0.0 ) )
789 {
790 threshold = 0.0000001;
791
792 if ( mLayer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
793 {
794 threshold = 0.001;
795 }
796 else if ( mLayer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
797 {
798 threshold = 0.0001;
799 }
800 }
801
802 QgsRectangle searchRect( p.x() - threshold, p.y() - threshold,
803 p.x() + threshold, p.y() + threshold );
804 double sqrSnappingTolerance = threshold * threshold;
805
806 QgsFeature f;
808 .setFilterRect( searchRect )
810 .setNoAttributes() );
811
812 QMap<QgsFeatureId, QgsGeometry> features;
813 QMap<QgsFeatureId, int> segments;
814
815 while ( fit.nextFeature( f ) )
816 {
817 int afterVertex;
818 QgsPointXY snappedPoint;
819 double sqrDistSegmentSnap = f.geometry().closestSegmentWithContext( p, snappedPoint, afterVertex, nullptr, segmentSearchEpsilon );
820 if ( sqrDistSegmentSnap < sqrSnappingTolerance )
821 {
822 segments[f.id()] = afterVertex;
823 features[f.id()] = f.geometry();
824 }
825 }
826
827 if ( segments.isEmpty() )
828 return 2;
829
830 bool pointsAdded = false;
831 for ( QMap<QgsFeatureId, int>::const_iterator it = segments.constBegin(); it != segments.constEnd(); ++it )
832 {
833 QgsFeatureId fid = it.key();
834 int segmentAfterVertex = it.value();
835 QgsGeometry geom = features[fid];
836
837 int atVertex, beforeVertex, afterVertex;
838 double sqrDistVertexSnap;
839 geom.closestVertex( p, atVertex, beforeVertex, afterVertex, sqrDistVertexSnap );
840
841 if ( sqrDistVertexSnap < sqrSnappingTolerance )
842 continue; // the vertex already exists - do not insert it
843
844 if ( !mLayer->insertVertex( p, fid, segmentAfterVertex ) )
845 {
846 QgsDebugError( QStringLiteral( "failed to insert topo point" ) );
847 }
848 else
849 {
850 pointsAdded = true;
851 }
852 }
853
854 return pointsAdded ? 0 : 2;
855}
856
858{
859 if ( !mLayer->isSpatial() )
860 return 1;
861
862 if ( ps.isEmpty() )
863 {
864 return 1;
865 }
866
867 bool pointsAdded = false;
868
869 QgsPointSequence::const_iterator it = ps.constBegin();
870 while ( it != ps.constEnd() )
871 {
872 if ( addTopologicalPoints( *it ) == 0 )
873 {
874 pointsAdded = true;
875 }
876 ++it;
877 }
878
879 return pointsAdded ? 0 : 2;
880}
881
886
887bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
888{
889 errorMessage.clear();
890
891 if ( mergeFeatureIds.isEmpty() )
892 {
893 errorMessage = QObject::tr( "List of features to merge is empty" );
894 return false;
895 }
896
897 QgsAttributeMap newAttributes;
898 for ( int i = 0; i < mergeAttributes.count(); ++i )
899 {
900 QVariant val = mergeAttributes.at( i );
901
902 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
903 mLayer->dataProvider() &&
904 mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
905
906 // convert to destination data type
907 QString errorMessageConvertCompatible;
908 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
909 {
910 if ( errorMessage.isEmpty() )
911 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 );
912 }
913 newAttributes[ i ] = val;
914 }
915
916 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
917
918 // Delete other features but the target feature
919 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
920 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
921 {
922 if ( *feature_it != targetFeatureId )
923 mLayer->deleteFeature( *feature_it );
924 }
925
926 // Modify target feature or create a new one if invalid
927 QgsGeometry mergeGeometry = unionGeometry;
928 if ( targetFeatureId == FID_NULL )
929 {
930 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
931 mLayer->addFeature( mergeFeature );
932 }
933 else
934 {
935 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
936 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
937 }
938
939 mLayer->endEditCommand();
940
941 mLayer->triggerRepaint();
942
943 return true;
944}
945
946bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
947{
948 if ( list.empty() )
949 {
950 return false;
951 }
952
953 xmin = std::numeric_limits<double>::max();
954 xmax = -std::numeric_limits<double>::max();
955 ymin = std::numeric_limits<double>::max();
956 ymax = -std::numeric_limits<double>::max();
957
958 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
959 {
960 if ( it->x() < xmin )
961 {
962 xmin = it->x();
963 }
964 if ( it->x() > xmax )
965 {
966 xmax = it->x();
967 }
968 if ( it->y() < ymin )
969 {
970 ymin = it->y();
971 }
972 if ( it->y() > ymax )
973 {
974 ymax = it->y();
975 }
976 }
977
978 return true;
979}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:1596
@ 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.
@ Feet
Imperial feet.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
@ 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.
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
VectorEditResult
Flags which control feature selection behavior.
Definition qgis.h:1386
@ 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 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.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
A vector of attributes.
Abstract base class for curved geometry type.
Definition qgscurve.h:35
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.
This class wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:56
QgsFeatureId id
Definition qgsfeature.h:64
QgsGeometry geometry
Definition qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
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: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
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.
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
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.
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.
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.
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.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:81
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
A class to represent a 2D point.
Definition qgspointxy.h:60
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
A rectangle specified with double values.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
void setYMinimum(double y)
Set the minimum y value.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
void setXMinimum(double x)
Set the minimum x value.
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
bool isEmpty() const
Returns true if the rectangle has no area.
double height() const
Returns the height of the rectangle.
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 hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static bool isSingleType(Qgis::WkbType type)
Returns true if the WKB type is a single type.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5144
QVector< QgsPoint > QgsPointSequence
QMap< int, QVariant > QgsAttributeMap
QList< QgsFeature > QgsFeatureList
Definition qgsfeature.h:917
#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:38