QGIS API Documentation  3.37.0-Master (a5b4d9743e8)
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 "qgsunsetattributevalue.h"
18 #include "qgsvectordataprovider.h"
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"
31 #include "qgssettingsentryimpl.h"
32 
33 #include <limits>
34 
35 
37  : mLayer( layer )
38 {
39 }
40 
41 bool 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 
58 bool 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 
75 bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
76 {
77  QgsPoint p( x, y );
78  return moveVertex( p, atFeatureId, atVertex );
79 }
80 
81 bool 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 
137 static
138 Qgis::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 
164  QgsFeatureIterator fit;
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 
212 Qgis::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 
250 Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addPart( const QVector<QgsPointXY> &points, QgsFeatureId featureId )
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() );
282  if ( errorCode == Qgis::GeometryOperationResult::Success )
283  {
284  if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
285  && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
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 
322  if ( errorCode == Qgis::GeometryOperationResult::Success )
323  {
324  if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() )
325  && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
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
336 int 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 );
348  if ( errorCode == Qgis::GeometryOperationResult::Success )
349  {
350  mLayer->changeGeometry( featureId, geometry );
351  }
352  return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
353 }
354 
355 Qgis::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 
374 Qgis::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 
621 Qgis::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 
640  QgsFeatureIterator fit;
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 = 1e-8;
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, p, false );
803  searchRect.grow( threshold );
804 
805  QgsFeature f;
807  .setFilterRect( searchRect )
809  .setNoAttributes() );
810 
811  bool pointsAdded = false;
812  while ( fit.nextFeature( f ) )
813  {
814  QgsGeometry geom = f.geometry();
815  if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
816  {
817  pointsAdded = true;
818  mLayer->changeGeometry( f.id(), geom );
819  }
820  }
821 
822  return pointsAdded ? 0 : 2;
823 }
824 
826 {
827  if ( !mLayer->isSpatial() )
828  return 1;
829 
830  if ( ps.isEmpty() )
831  {
832  return 1;
833  }
834 
835  bool pointsAdded = false;
836 
837  QgsPointSequence::const_iterator it = ps.constBegin();
838  while ( it != ps.constEnd() )
839  {
840  if ( addTopologicalPoints( *it ) == 0 )
841  {
842  pointsAdded = true;
843  }
844  ++it;
845  }
846 
847  return pointsAdded ? 0 : 2;
848 }
849 
851 {
852  return addTopologicalPoints( QgsPoint( p ) );
853 }
854 
855 bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
856 {
857  errorMessage.clear();
858 
859  if ( mergeFeatureIds.isEmpty() )
860  {
861  errorMessage = QObject::tr( "List of features to merge is empty" );
862  return false;
863  }
864 
865  QgsAttributeMap newAttributes;
866  for ( int i = 0; i < mergeAttributes.count(); ++i )
867  {
868  QVariant val = mergeAttributes.at( i );
869 
870  bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
871  mLayer->dataProvider() &&
872  mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
873 
874  // convert to destination data type
875  QString errorMessageConvertCompatible;
876  if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
877  {
878  if ( errorMessage.isEmpty() )
879  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 );
880  }
881  newAttributes[ i ] = val;
882  }
883 
884  mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
885 
886  // Delete other features but the target feature
887  QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
888  for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
889  {
890  if ( *feature_it != targetFeatureId )
891  mLayer->deleteFeature( *feature_it );
892  }
893 
894  // Modify target feature or create a new one if invalid
895  QgsGeometry mergeGeometry = unionGeometry;
896  if ( targetFeatureId == FID_NULL )
897  {
898  QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
899  mLayer->addFeature( mergeFeature );
900  }
901  else
902  {
903  mLayer->changeGeometry( targetFeatureId, mergeGeometry );
904  mLayer->changeAttributeValues( targetFeatureId, newAttributes );
905  }
906 
907  mLayer->endEditCommand();
908 
909  mLayer->triggerRepaint();
910 
911  return true;
912 }
913 
914 bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
915 {
916  if ( list.empty() )
917  {
918  return false;
919  }
920 
921  xmin = std::numeric_limits<double>::max();
922  xmax = -std::numeric_limits<double>::max();
923  ymin = std::numeric_limits<double>::max();
924  ymax = -std::numeric_limits<double>::max();
925 
926  for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
927  {
928  if ( it->x() < xmin )
929  {
930  xmin = it->x();
931  }
932  if ( it->x() > xmax )
933  {
934  xmax = it->x();
935  }
936  if ( it->y() < ymin )
937  {
938  ymin = it->y();
939  }
940  if ( it->y() > ymax )
941  {
942  ymax = it->y();
943  }
944  }
945 
946  return true;
947 }
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
Specifies the result of a vector layer edit operation.
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.
Definition: qgsattributes.h:59
Q_GADGET Qgis::DistanceUnit mapUnits
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
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:162
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.
Q_GADGET bool isNull
Definition: qgsgeometry.h:164
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
Definition: qgsgeometry.h:165
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...
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:81
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:83
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
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:159
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:149
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:236
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:164
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:154
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:307
bool isEmpty() const
Returns true if the rectangle has no area.
Definition: qgsrectangle.h:492
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:243
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).
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.
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.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
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.
Definition: qgswkbtypes.h:973
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1023
static bool isSingleType(Qgis::WkbType type)
Returns true if the WKB type is a single type.
Definition: qgswkbtypes.h:748
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5172
QVector< QgsPoint > QgsPointSequence
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:39
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:917
#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