QGIS API Documentation 4.3.0-Master (0cfde48c85b)
Loading...
Searching...
No Matches
qgsvectorlayereditutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorlayereditutils.cpp
3 ---------------------
4 begin : Dezember 2012
5 copyright : (C) 2012 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
16
17#include <limits>
18
19#include "qgis.h"
20#include "qgsabstractgeometry.h"
21#include "qgscurvepolygon.h"
22#include "qgsfeatureiterator.h"
23#include "qgsgeometryoptions.h"
24#include "qgsgeos.h"
25#include "qgslinestring.h"
26#include "qgslogger.h"
27#include "qgspoint.h"
32#include "qgsvectorlayer.h"
34#include "qgsvectorlayerutils.h"
35#include "qgswkbtypes.h"
36
37#include <QString>
38
39using namespace Qt::StringLiterals;
40
44
45bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
46{
47 if ( !mLayer->isSpatial() )
48 return false;
49
50 QgsFeature f;
51 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
52 return false; // geometry not found
53
54 QgsGeometry geometry = f.geometry();
55
56 geometry.insertVertex( x, y, beforeVertex );
57
58 mLayer->changeGeometry( atFeatureId, geometry );
59 return true;
60}
61
62bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
63{
64 if ( !mLayer->isSpatial() )
65 return false;
66
67 QgsFeature f;
68 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
69 return false; // geometry not found
70
71 QgsGeometry geometry = f.geometry();
72
73 geometry.insertVertex( point, beforeVertex );
74
75 mLayer->changeGeometry( atFeatureId, geometry );
76 return true;
77}
78
79bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
80{
81 QgsPoint p( x, y );
82 return moveVertex( p, atFeatureId, atVertex );
83}
84
85bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
86{
87 if ( !mLayer->isSpatial() )
88 return false;
89
90 QgsFeature f;
91 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
92 return false; // geometry not found
93
94 QgsGeometry geometry = f.geometry();
95
96 // If original point is not 3D but destination yes, check if it can be promoted
97 if ( p.is3D() && !geometry.constGet()->is3D() && QgsWkbTypes::hasZ( mLayer->wkbType() ) )
98 {
100 return false;
101 }
102
103 // If original point has not M-value but destination yes, check if it can be promoted
104 if ( p.isMeasure() && !geometry.constGet()->isMeasure() && QgsWkbTypes::hasM( mLayer->wkbType() ) )
105 {
107 return false;
108 }
109
110 if ( !geometry.moveVertex( p, atVertex ) )
111 return false;
112
113 return mLayer->changeGeometry( atFeatureId, geometry );
114}
115
117{
118 return deleteVertices( featureId, { vertex } );
119}
120
122{
123 if ( !mLayer->isSpatial() )
125
126 QgsFeature f;
127 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
129
130 QgsGeometry geometry = f.geometry();
131
132 if ( !geometry.deleteVertices( vertices ) )
134
135 if ( geometry.constGet() && geometry.constGet()->nCoordinates() == 0 )
136 {
137 // Last vertex deleted, set geometry to null
138 geometry.set( nullptr );
139 }
140
141 mLayer->changeGeometry( featureId, geometry );
143}
144
145static Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ptr< QgsCurve > &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureIds *modifiedFeatureIds, bool firstOne = true )
146{
147 if ( !layer || !layer->isSpatial() )
148 {
150 }
151
152 if ( !ring )
153 {
155 }
156
157 if ( !ring->isClosed() )
158 {
160 }
161
162 if ( !layer->isValid() || !layer->editBuffer() || !layer->dataProvider() )
163 {
165 }
166
167 Qgis::GeometryOperationResult addRingReturnCode = Qgis::GeometryOperationResult::AddRingNotInExistingFeature; //default: return code for 'ring not inserted'
168 QgsFeature f;
169
171 if ( !targetFeatureIds.isEmpty() )
172 {
173 //check only specified features
174 fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) );
175 }
176 else
177 {
178 //check all intersecting features
179 QgsRectangle bBox = ring->boundingBox();
180 fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
181 }
182
183 //find valid features we can add the ring to
184 bool success = false;
185 while ( fit.nextFeature( f ) )
186 {
187 if ( !f.hasGeometry() )
188 continue;
189
190 //add ring takes ownership of ring, and deletes it if there's an error
191 QgsGeometry g = f.geometry();
192
193 if ( ring->orientation() != g.polygonOrientation() )
194 {
195 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) );
196 }
197 else
198 {
199 addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->reversed() ) );
200 }
201 if ( addRingReturnCode == Qgis::GeometryOperationResult::Success )
202 {
203 success = true;
204 layer->changeGeometry( f.id(), g );
205 if ( modifiedFeatureIds )
206 {
207 modifiedFeatureIds->insert( f.id() );
208 if ( firstOne )
209 {
210 break;
211 }
212 }
213 }
214 }
215
216 return success ? Qgis::GeometryOperationResult::Success : addRingReturnCode;
217}
218
220double QgsVectorLayerEditUtils::getTopologicalSearchRadius( const QgsVectorLayer *layer )
221{
222 double threshold = layer->geometryOptions()->geometryPrecision();
223
224 if ( qgsDoubleNear( threshold, 0.0 ) )
225 {
226 threshold = 1e-8;
227
228 if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Meters )
229 {
230 threshold = 0.001;
231 }
232 else if ( layer->crs().mapUnits() == Qgis::DistanceUnit::Feet )
233 {
234 threshold = 0.0001;
235 }
236 }
237 return threshold;
238}
239
240void QgsVectorLayerEditUtils::addTopologicalPointsToLayers( const QgsGeometry &geom, QgsVectorLayer *vlayer, const QList<QgsMapLayer *> &layers, const QString &toolName )
241{
242 QgsFeatureRequest request = QgsFeatureRequest().setNoAttributes().setFlags( Qgis::FeatureRequestFlag::NoGeometry ).setLimit( 1 );
243 QgsFeature f;
244
245 for ( QgsMapLayer *layer : layers )
246 {
247 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
248 if ( vectorLayer && vectorLayer->isEditable() && vectorLayer->isSpatial() && ( vectorLayer->geometryType() == Qgis::GeometryType::Line || vectorLayer->geometryType() == Qgis::GeometryType::Polygon ) )
249 {
250 // boundingBox() is cached, it doesn't matter calling it in the loop
251 QgsRectangle bbox = geom.boundingBox();
252 QgsCoordinateTransform ct;
253 if ( vectorLayer->crs() != vlayer->crs() )
254 {
255 ct = QgsCoordinateTransform( vlayer->crs(), vectorLayer->crs(), vectorLayer->transformContext() );
256 try
257 {
258 bbox = ct.transformBoundingBox( bbox );
259 }
260 catch ( QgsCsException & )
261 {
262 QgsDebugError( u"Bounding box transformation failed, skipping topological points for layer %1"_s.arg( vlayer->id() ) );
263 continue;
264 }
265 }
266 bbox.grow( getTopologicalSearchRadius( vectorLayer ) );
267 request.setFilterRect( bbox );
268
269 // We check that there is actually at least one feature intersecting our geometry in the layer to avoid creating an empty edit command and calling costly addTopologicalPoint
270 if ( !vectorLayer->getFeatures( request ).nextFeature( f ) )
271 continue;
272
273 vectorLayer->beginEditCommand( QObject::tr( "Topological points added by '%1'" ).arg( toolName ) );
274
275 int returnValue = 2;
276 if ( vectorLayer->crs() != vlayer->crs() )
277 {
278 try
279 {
280 // transform digitized geometry from vlayer crs to vectorLayer crs and add topological points
281 QgsGeometry transformedGeom( geom );
282 transformedGeom.transform( ct );
283 returnValue = vectorLayer->addTopologicalPoints( transformedGeom );
284 }
285 catch ( QgsCsException & )
286 {
287 QgsDebugError( u"transformation to vectorLayer coordinate failed"_s );
288 }
289 }
290 else
291 {
292 returnValue = vectorLayer->addTopologicalPoints( geom );
293 }
294
295 if ( returnValue == 0 )
296 {
297 vectorLayer->endEditCommand();
298 }
299 else
300 {
301 // the layer was not modified, leave the undo buffer intact
302 vectorLayer->destroyEditCommand();
303 }
304 }
305 }
306}
308
309Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addRing( const QVector<QgsPointXY> &ring, const QgsFeatureIds &targetFeatureIds, QgsFeatureId *modifiedFeatureId )
310{
312 for ( QVector<QgsPointXY>::const_iterator it = ring.constBegin(); it != ring.constEnd(); ++it )
313 {
314 l << QgsPoint( *it );
315 }
316 return addRing( l, targetFeatureIds, modifiedFeatureId );
317}
318
320{
321 QgsLineString *ringLine = new QgsLineString( ring );
322 return addRing( ringLine, targetFeatureIds, modifiedFeatureId );
323}
324
326{
327 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
328 if ( modifiedFeatureId )
329 {
330 QgsFeatureIds modifiedFeatureIds;
331 Qgis::GeometryOperationResult result = staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, &modifiedFeatureIds, true );
332 if ( modifiedFeatureId && !modifiedFeatureIds.empty() )
333 *modifiedFeatureId = *modifiedFeatureIds.begin();
334 return result;
335 }
336 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, nullptr, true );
337}
338
340{
341 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
342 return staticAddRing( mLayer, uniquePtrRing, targetFeatureIds, modifiedFeatureIds, false );
343}
344
345
347{
349 for ( QVector<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd(); ++it )
350 {
351 l << QgsPoint( *it );
352 }
353 return addPart( l, featureId );
354}
355
357{
358 if ( !mLayer->isSpatial() )
360
361 QgsGeometry geometry;
362 bool firstPart = false;
363 QgsFeature f;
364 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
366
367 if ( !f.hasGeometry() )
368 {
369 //no existing geometry, so adding first part to null geometry
370 firstPart = true;
371 }
372 else
373 {
374 geometry = f.geometry();
375 }
376
377 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( points, mLayer->wkbType() );
379 {
380 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
381 {
382 //convert back to single part if required by layer
383 geometry.convertToSingleType();
384 }
385 mLayer->changeGeometry( featureId, geometry );
386 }
387 return errorCode;
388}
389
391{
392 std::unique_ptr<QgsCurve> uniquePtrRing( ring );
393
394 if ( !mLayer->isSpatial() )
396
397 QgsGeometry geometry;
398 bool firstPart = false;
399 QgsFeature f;
400 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
402
403 if ( !f.hasGeometry() )
404 {
405 //no existing geometry, so adding first part to null geometry
406 firstPart = true;
407 }
408 else
409 {
410 geometry = f.geometry();
411 if ( mLayer->geometryType() == Qgis::GeometryType::Polygon && uniquePtrRing->orientation() != geometry.polygonOrientation() )
412 {
413 uniquePtrRing.reset( uniquePtrRing->reversed() );
414 }
415 }
416 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( uniquePtrRing.release(), mLayer->wkbType() );
417
419 {
420 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
421 {
422 //convert back to single part if required by layer
423 geometry.convertToSingleType();
424 }
425 mLayer->changeGeometry( featureId, geometry );
426 }
427 return errorCode;
428}
429
431{
432 std::unique_ptr<QgsCurvePolygon> uniquePtrPoly( polygon );
433
434 if ( !mLayer->isSpatial() )
436
437 if ( mLayer->geometryType() != Qgis::GeometryType::Polygon )
439
440 QgsGeometry geometry;
441 bool firstPart = false;
442 QgsFeature f;
443 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) )
445
446 if ( !f.hasGeometry() )
447 {
448 //no existing geometry, so adding first part to null geometry
449 firstPart = true;
450 }
451 else
452 {
453 geometry = f.geometry();
454 switch ( geometry.polygonOrientation() )
455 {
457 polygon->forceClockwise();
458 break;
460 polygon->forceCounterClockwise();
461 break;
463 break;
464 }
465 }
466 Qgis::GeometryOperationResult errorCode = geometry.addPartV2( uniquePtrPoly.release(), mLayer->wkbType() );
467
469 {
470 if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) && mLayer->dataProvider()->doesStrictFeatureTypeCheck() )
471 {
472 //convert back to single part if required by layer
473 geometry.convertToSingleType();
474 }
475 mLayer->changeGeometry( featureId, geometry );
476 }
477 return errorCode;
478}
479
480// TODO QGIS 5.0 -- this should return Qgis::GeometryOperationResult
481int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
482{
483 if ( !mLayer->isSpatial() )
484 return 1;
485
486 QgsFeature f;
487 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setNoAttributes() ).nextFeature( f ) || !f.hasGeometry() )
488 return 1; //geometry not found
489
490 QgsGeometry geometry = f.geometry();
491
492 Qgis::GeometryOperationResult errorCode = geometry.translate( dx, dy );
494 {
495 mLayer->changeGeometry( featureId, geometry );
496 }
497 return errorCode == Qgis::GeometryOperationResult::Success ? 0 : 1;
498}
499
500Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
501{
503 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
504 {
505 l << QgsPoint( *it );
506 }
507 return splitFeatures( l, topologicalEditing );
508}
509
511{
512 QgsLineString lineString( splitLine );
513 QgsPointSequence topologyTestPoints;
514 bool preserveCircular = false;
515 return splitFeatures( &lineString, topologyTestPoints, preserveCircular, topologicalEditing );
516}
517
518Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
519{
520 if ( !mLayer->isSpatial() )
522
523 QgsRectangle bBox; //bounding box of the split line
525 Qgis::GeometryOperationResult splitFunctionReturn; //return code of QgsGeometry::splitGeometry
526 int numberOfSplitFeatures = 0;
527
528 QgsFeatureIterator features;
529 const QgsFeatureIds selectedIds = mLayer->selectedFeatureIds();
530
531 if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection
532 {
533 features = mLayer->getSelectedFeatures();
534 }
535 else //else consider all the feature that intersect the bounding box of the split line
536 {
537 bBox = curve->boundingBox();
538
539 if ( bBox.isEmpty() )
540 {
541 //if the bbox is a line, try to make a square out of it
542 if ( bBox.width() == 0.0 && bBox.height() > 0 )
543 {
544 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
545 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
546 }
547 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
548 {
549 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
550 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
551 }
552 else
553 {
554 //If we have a single point, we still create a non-null box
555 double bufferDistance = 0.000001;
556 if ( mLayer->crs().isGeographic() )
557 bufferDistance = 0.00000001;
558 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
559 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
560 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
561 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
562 }
563 }
564
565 features = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
566 }
567
569
570 const int fieldCount = mLayer->fields().count();
571#if !( GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 ) )
572 const bool splitCurveContainsCurves = curve->hasCurvedSegments();
573#endif
574
575 QgsFeature feat;
576 while ( features.nextFeature( feat ) )
577 {
578 if ( !feat.hasGeometry() )
579 {
580 continue;
581 }
582 QVector<QgsGeometry> newGeometries;
583 QgsPointSequence featureTopologyTestPoints;
584 const QgsGeometry originalGeom = feat.geometry();
585 QgsGeometry featureGeom = originalGeom;
586
587#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
588 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints );
589#else
590 // For the current geometry, make sure preserveCircular is not forced, unless
591 // the input param is true and one of the involved geometries contains curves
592 bool preserveCircularForGeom = preserveCircular;
593 preserveCircularForGeom &= ( splitCurveContainsCurves || featureGeom.constGet()->hasCurvedSegments() );
594 splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircularForGeom, topologicalEditing, featureTopologyTestPoints );
595#endif
596
597 topologyTestPoints.append( featureTopologyTestPoints );
598 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success )
599 {
600 //find largest geometry and give that to the original feature
601 std::function<double( const QgsGeometry & )> size = mLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
602 double featureGeomSize = size( featureGeom );
603
604 QVector<QgsGeometry>::iterator largestNewFeature = std::max_element( newGeometries.begin(), newGeometries.end(), [&size]( const QgsGeometry &a, const QgsGeometry &b ) -> bool {
605 return size( a ) < size( b );
606 } );
607
608 if ( size( *largestNewFeature ) > featureGeomSize )
609 {
610 QgsGeometry copy = *largestNewFeature;
611 *largestNewFeature = featureGeom;
612 featureGeom = copy;
613 }
614
615 //change this geometry
616 mLayer->changeGeometry( feat.id(), featureGeom );
617
618 //update any attributes for original feature which are set to GeometryRatio split policy
619 QgsAttributeMap attributeMap;
620 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
621 {
622 const QgsField field = mLayer->fields().at( fieldIdx );
623 switch ( field.splitPolicy() )
624 {
628 break;
629
631 {
632 if ( field.isNumeric() )
633 {
634 const double originalValue = feat.attribute( fieldIdx ).toDouble();
635
636 double originalSize = 0;
637
638 switch ( originalGeom.type() )
639 {
643 originalSize = 0;
644 break;
646 originalSize = originalGeom.length();
647 break;
649 originalSize = originalGeom.area();
650 break;
651 }
652
653 double newSize = 0;
654 switch ( featureGeom.type() )
655 {
659 newSize = 0;
660 break;
662 newSize = featureGeom.length();
663 break;
665 newSize = featureGeom.area();
666 break;
667 }
668
669 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
670 }
671 break;
672 }
673 }
674 }
675
676 if ( !attributeMap.isEmpty() )
677 {
678 mLayer->changeAttributeValues( feat.id(), attributeMap );
679 }
680
681 //insert new features
682 for ( const QgsGeometry &geom : std::as_const( newGeometries ) )
683 {
684 QgsAttributeMap attributeMap;
685 for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx )
686 {
687 const QgsField field = mLayer->fields().at( fieldIdx );
688 // respect field split policy
689 switch ( field.splitPolicy() )
690 {
692 //do nothing - default values ​​are determined
693 break;
694
696 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
697 break;
698
700 {
701 if ( !field.isNumeric() )
702 {
703 attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) );
704 }
705 else
706 {
707 const double originalValue = feat.attribute( fieldIdx ).toDouble();
708
709 double originalSize = 0;
710
711 switch ( originalGeom.type() )
712 {
716 originalSize = 0;
717 break;
719 originalSize = originalGeom.length();
720 break;
722 originalSize = originalGeom.area();
723 break;
724 }
725
726 double newSize = 0;
727 switch ( geom.type() )
728 {
732 newSize = 0;
733 break;
735 newSize = geom.length();
736 break;
738 newSize = geom.area();
739 break;
740 }
741
742 attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue );
743 }
744 break;
745 }
746
748 attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() );
749 break;
750 }
751 }
752
753 featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap );
754 }
755
756 if ( topologicalEditing )
757 {
758 QgsPointSequence::const_iterator topol_it = featureTopologyTestPoints.constBegin();
759 for ( ; topol_it != featureTopologyTestPoints.constEnd(); ++topol_it )
760 {
761 addTopologicalPoints( *topol_it );
762 }
763 }
764 ++numberOfSplitFeatures;
765 }
766 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success && splitFunctionReturn != Qgis::GeometryOperationResult::NothingHappened ) // i.e. no split but no error occurred
767 {
768 returnCode = splitFunctionReturn;
769 }
770 }
771
772 if ( !featuresDataToAdd.isEmpty() )
773 {
774 // finally create and add all bits of geometries cut off the original geometries
775 // (this is much faster than creating features one by one)
776 QgsFeatureList featuresListToAdd = QgsVectorLayerUtils::createFeatures( mLayer, featuresDataToAdd );
777 mLayer->addFeatures( featuresListToAdd );
778 }
779
780 if ( numberOfSplitFeatures == 0 )
781 {
783 }
784
785 return returnCode;
786}
787
788Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
789{
791 for ( QVector<QgsPointXY>::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it )
792 {
793 l << QgsPoint( *it );
794 }
795 return splitParts( l, topologicalEditing );
796}
797
799{
800 if ( !mLayer->isSpatial() )
802
803 double xMin, yMin, xMax, yMax;
804 QgsRectangle bBox; //bounding box of the split line
805 int numberOfSplitParts = 0;
806
808
809 if ( mLayer->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
810 {
811 fit = mLayer->getSelectedFeatures();
812 }
813 else //else consider all the feature that intersect the bounding box of the split line
814 {
815 if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) )
816 {
817 bBox.setXMinimum( xMin );
818 bBox.setYMinimum( yMin );
819 bBox.setXMaximum( xMax );
820 bBox.setYMaximum( yMax );
821 }
822 else
823 {
825 }
826
827 if ( bBox.isEmpty() )
828 {
829 //if the bbox is a line, try to make a square out of it
830 if ( bBox.width() == 0.0 && bBox.height() > 0 )
831 {
832 bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
833 bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
834 }
835 else if ( bBox.height() == 0.0 && bBox.width() > 0 )
836 {
837 bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
838 bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
839 }
840 else
841 {
842 //If we have a single point, we still create a non-null box
843 double bufferDistance = 0.000001;
844 if ( mLayer->crs().isGeographic() )
845 bufferDistance = 0.00000001;
846 bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
847 bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
848 bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
849 bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
850 }
851 }
852
853 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ) );
854 }
855
856 QgsFeature feat;
857 while ( fit.nextFeature( feat ) )
858 {
859 QgsGeometry featureGeom = feat.geometry();
860
861 const QVector<QgsGeometry> geomCollection = featureGeom.asGeometryCollection();
862 QVector<QgsGeometry> resultCollection;
863 QgsPointSequence topologyTestPoints;
864 for ( QgsGeometry part : geomCollection )
865 {
866 QVector<QgsGeometry> newGeometries;
867 QgsPointSequence partTopologyTestPoints;
868
869 const Qgis::GeometryOperationResult splitFunctionReturn = part.splitGeometry( splitLine, newGeometries, topologicalEditing, partTopologyTestPoints, false );
870
871 if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success && !newGeometries.isEmpty() )
872 {
873 for ( int i = 0; i < newGeometries.size(); ++i )
874 {
875 resultCollection.append( newGeometries.at( i ).asGeometryCollection() );
876 }
877
878 topologyTestPoints.append( partTopologyTestPoints );
879
880 ++numberOfSplitParts;
881 }
882 // Note: For multilinestring layers, when the split line does not intersect the feature part,
883 // QgsGeometry::splitGeometry returns InvalidBaseGeometry instead of NothingHappened
884 else if ( splitFunctionReturn == Qgis::GeometryOperationResult::NothingHappened || splitFunctionReturn == Qgis::GeometryOperationResult::InvalidBaseGeometry )
885 {
886 // Add part as is
887 resultCollection.append( part );
888 }
889 else if ( splitFunctionReturn != Qgis::GeometryOperationResult::Success )
890 {
891 return splitFunctionReturn;
892 }
893 }
894
895 QgsGeometry newGeom = QgsGeometry::collectGeometry( resultCollection );
896 mLayer->changeGeometry( feat.id(), newGeom );
897
898 if ( topologicalEditing )
899 {
900 QgsPointSequence::const_iterator topol_it = topologyTestPoints.constBegin();
901 for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
902 {
903 addTopologicalPoints( *topol_it );
904 }
905 }
906 }
907 if ( numberOfSplitParts == 0 && mLayer->selectedFeatureCount() > 0 )
908 {
909 //There is a selection but no feature has been split.
910 //Maybe user forgot that only the selected features are split
912 }
913
915}
916
917
919{
920 if ( !mLayer->isSpatial() )
921 return 1;
922
923 if ( geom.isNull() )
924 {
925 return 1;
926 }
927
928 bool pointsAdded = false;
929
931 while ( it != geom.vertices_end() )
932 {
933 if ( addTopologicalPoints( *it ) == 0 )
934 {
935 pointsAdded = true;
936 }
937 ++it;
938 }
939
940 return pointsAdded ? 0 : 2;
941}
942
944{
945 if ( !mLayer->isSpatial() )
946 return 1;
947
948 double segmentSearchEpsilon = mLayer->crs().isGeographic() ? 1e-12 : 1e-8;
949
950 //work with a tolerance because coordinate projection may introduce some rounding
951 double threshold = getTopologicalSearchRadius( mLayer );
952
953 QgsRectangle searchRect( p, p, false );
954 searchRect.grow( threshold );
955
956 QgsFeature f;
957 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterRect( searchRect ).setFlags( Qgis::FeatureRequestFlag::ExactIntersect ).setNoAttributes() );
958
959 bool pointsAdded = false;
960 while ( fit.nextFeature( f ) )
961 {
962 QgsGeometry geom = f.geometry();
963 if ( geom.addTopologicalPoint( p, threshold, segmentSearchEpsilon ) )
964 {
965 pointsAdded = true;
966 mLayer->changeGeometry( f.id(), geom );
967 }
968 }
969
970 return pointsAdded ? 0 : 2;
971}
972
974{
975 if ( !mLayer->isSpatial() )
976 return 1;
977
978 if ( ps.isEmpty() )
979 {
980 return 1;
981 }
982
983 bool pointsAdded = false;
984
985 QgsPointSequence::const_iterator it = ps.constBegin();
986 while ( it != ps.constEnd() )
987 {
988 if ( addTopologicalPoints( *it ) == 0 )
989 {
990 pointsAdded = true;
991 }
992 ++it;
993 }
994
995 return pointsAdded ? 0 : 2;
996}
997
1002
1004 const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage
1005)
1006{
1007 errorMessage.clear();
1008
1009 if ( mergeFeatureIds.isEmpty() )
1010 {
1011 errorMessage = QObject::tr( "List of features to merge is empty" );
1012 return false;
1013 }
1014
1015 QgsAttributeMap newAttributes;
1016 for ( int i = 0; i < mergeAttributes.count(); ++i )
1017 {
1018 QVariant val = mergeAttributes.at( i );
1019
1020 bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == Qgis::FieldOrigin::Provider
1021 && mLayer->dataProvider()
1022 && mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
1023
1024 // convert to destination data type
1025 QString errorMessageConvertCompatible;
1026 if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
1027 {
1028 if ( errorMessage.isEmpty() )
1029 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 );
1030 }
1031 newAttributes[i] = val;
1032 }
1033
1034 mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
1035
1036 // Delete other features but the target feature
1037 QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
1038 for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
1039 {
1040 if ( *feature_it != targetFeatureId )
1041 mLayer->deleteFeature( *feature_it );
1042 }
1043
1044 // Modify target feature or create a new one if invalid
1045 QgsGeometry mergeGeometry = unionGeometry;
1046 if ( targetFeatureId == FID_NULL )
1047 {
1048 QgsFeature mergeFeature = QgsVectorLayerUtils::createFeature( mLayer, mergeGeometry, newAttributes );
1049 mLayer->addFeature( mergeFeature );
1050 }
1051 else
1052 {
1053 mLayer->changeGeometry( targetFeatureId, mergeGeometry );
1054 mLayer->changeAttributeValues( targetFeatureId, newAttributes );
1055 }
1056
1057 mLayer->endEditCommand();
1058
1059 mLayer->triggerRepaint();
1060
1061 return true;
1062}
1063
1064bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
1065{
1066 if ( list.empty() )
1067 {
1068 return false;
1069 }
1070
1071 xmin = std::numeric_limits<double>::max();
1072 xmax = -std::numeric_limits<double>::max();
1073 ymin = std::numeric_limits<double>::max();
1074 ymax = -std::numeric_limits<double>::max();
1075
1076 for ( QgsPointSequence::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
1077 {
1078 if ( it->x() < xmin )
1079 {
1080 xmin = it->x();
1081 }
1082 if ( it->x() > xmax )
1083 {
1084 xmax = it->x();
1085 }
1086 if ( it->y() < ymin )
1087 {
1088 ymin = it->y();
1089 }
1090 if ( it->y() > ymax )
1091 {
1092 ymax = it->y();
1093 }
1094 }
1095
1096 return true;
1097}
@ NoOrientation
Unknown orientation or sentinel value.
Definition qgis.h:3673
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3672
@ Clockwise
Clockwise direction.
Definition qgis.h:3671
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2212
@ AddPartSelectedGeometryNotFound
The selected geometry cannot be found.
Definition qgis.h:2222
@ InvalidInputGeometryType
The input geometry (ring, part, split line, etc.) has not the correct geometry type.
Definition qgis.h:2216
@ Success
Operation succeeded.
Definition qgis.h:2213
@ AddRingNotInExistingFeature
The input ring doesn't have any existing ring to fit into.
Definition qgis.h:2228
@ AddRingNotClosed
The input ring is not closed.
Definition qgis.h:2225
@ NothingHappened
Nothing happened, without any error.
Definition qgis.h:2214
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition qgis.h:2215
@ LayerNotEditable
Cannot edit layer.
Definition qgis.h:2220
@ Feet
Imperial feet.
Definition qgis.h:5571
@ Meters
Meters.
Definition qgis.h:5569
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2382
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2380
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
Definition qgis.h:4172
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
Definition qgis.h:4173
@ DefaultValue
Use default field value.
Definition qgis.h:4170
@ Duplicate
Duplicate original value.
Definition qgis.h:4171
@ Point
Points.
Definition qgis.h:400
@ Line
Lines.
Definition qgis.h:401
@ Polygon
Polygons.
Definition qgis.h:402
@ Unknown
Unknown types.
Definition qgis.h:403
@ Null
No geometry.
Definition qgis.h:404
@ Provider
Field originates from the underlying data provider of the vector layer.
Definition qgis.h:1876
VectorEditResult
Specifies the result of a vector layer edit operation.
Definition qgis.h:1988
@ EmptyGeometry
Edit operation resulted in an empty geometry.
Definition qgis.h:1990
@ Success
Edit operation was successful.
Definition qgis.h:1989
@ FetchFeatureFailed
Unable to fetch requested feature.
Definition qgis.h:1992
@ EditFailed
Edit operation failed.
Definition qgis.h:1991
@ InvalidLayer
Edit failed due to invalid layer.
Definition qgis.h:1993
The vertex_iterator class provides an STL-style iterator for vertices.
virtual bool addZValue(double zValue=0)=0
Adds a z-dimension to the geometry, initialized to a preset value.
bool isMeasure() const
Returns true if the geometry contains m values.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual bool addMValue(double mValue=0)=0
Adds a measure to the geometry, initialized to a preset value.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
A vector of attributes.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Curve polygon geometry type.
void forceCounterClockwise()
Forces the polygon to respect the exterior ring is counter-clockwise, interior rings are clockwise co...
void forceClockwise()
Forces the polygon to respect the exterior ring is clockwise, interior rings are counter-clockwise co...
Abstract base class for curved geometry type.
Definition qgscurve.h:36
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:63
QgsGeometry geometry
Definition qgsfeature.h:66
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Qgis::FieldDomainSplitPolicy splitPolicy() const
Returns the field's split policy, which indicates how field values should be handled during a split o...
Definition qgsfield.cpp:769
bool isNumeric
Definition qgsfield.h:59
double geometryPrecision() const
The precision in which geometries on this layer should be saved.
A geometry is the spatial representation of a feature.
double length() const
Returns the planar, 2-dimensional length of geometry.
bool addTopologicalPoint(const QgsPoint &point, double snappingTolerance=1e-8, double segmentSearchEpsilon=1e-12)
Adds a vertex to the segment which intersect point but don't already have a vertex there.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool insertVertex(double x, double y, int beforeVertex)
Insert a new vertex before the given vertex index, ring and item (first number is index 0) If the req...
bool convertToSingleType()
Converts multi type geometry into single type geometry e.g.
Qgis::GeometryOperationResult addRing(const QVector< QgsPointXY > &ring)
Adds a new ring to this geometry.
Qgis::GeometryType type
double area() const
Returns the planar, 2-dimensional area of the geometry.
Qgis::AngularDirection polygonOrientation() const
Returns the orientation of the polygon.
bool deleteVertices(const QSet< int > &atVertices)
Deletes vertices at the given positions (first number is index 0).
void set(QgsAbstractGeometry *geometry)
Sets the underlying geometry store.
QgsAbstractGeometry::vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult addPartV2(const QVector< QgsPointXY > &points, Qgis::WkbType wkbType=Qgis::WkbType::Unknown)
Adds a new part to a the geometry.
Qgis::GeometryOperationResult translate(double dx, double dy, double dz=0.0, double dm=0.0)
Translates this geometry by dx, dy, dz and dm.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitGeometry(const QVector< QgsPointXY > &splitLine, QVector< QgsGeometry > &newGeometries, bool topological, QVector< QgsPointXY > &topologyTestPoints, bool splitFeature=true)
Splits this geometry according to a given line.
bool moveVertex(double x, double y, int atVertex)
Moves the vertex at the given position number and item (first number is index 0) to the given coordin...
QgsAbstractGeometry::vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
Line string geometry type, with support for z-dimension and m-values.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
QString id
Definition qgsmaplayer.h:86
QgsCoordinateTransformContext transformContext() const
Returns the layer data provider coordinate transform context or a default transform context if the la...
Represents a 2D point.
Definition qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double yMaximum
static const QgsSettingsEntryDouble * settingsDigitizingDefaultMValue
Settings entry digitizing default m value.
static const QgsSettingsEntryDouble * settingsDigitizingDefaultZValue
Settings entry digitizing default z value.
Represents a default, "not-specified" value for a feature attribute.
int translateFeature(QgsFeatureId featureId, double dx, double dy)
Translates feature by dx, dy.
Qgis::VectorEditResult deleteVertices(QgsFeatureId featureId, const QSet< int > &vertices)
Deletes a set of vertices from a feature.
bool mergeFeatures(const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage)
Merge features into a single one.
QgsVectorLayerEditUtils(QgsVectorLayer *layer)
bool insertVertex(double x, double y, QgsFeatureId atFeatureId, int beforeVertex)
Insert a new vertex before the given vertex number, in the given ring, item (first number is index 0)...
Q_DECL_DEPRECATED Qgis::GeometryOperationResult addPart(const QVector< QgsPointXY > &ring, QgsFeatureId featureId)
Adds a new part polygon to a multipart feature.
Qgis::VectorEditResult deleteVertex(QgsFeatureId featureId, int vertex)
Deletes a vertex from a feature.
Qgis::GeometryOperationResult addRingV2(QgsCurve *ring, const QgsFeatureIds &targetFeatureIds=QgsFeatureIds(), QgsFeatureIds *modifiedFeatureIds=nullptr)
Adds a ring to polygon/multipolygon features.
int addTopologicalPoints(const QgsGeometry &geom)
Adds topological points for every vertex of the geometry.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitParts(const QVector< QgsPointXY > &splitLine, bool topologicalEditing=false)
Splits parts cut by the given line.
Q_DECL_DEPRECATED Qgis::GeometryOperationResult splitFeatures(const QVector< QgsPointXY > &splitLine, bool topologicalEditing=false)
Splits features cut by the given line.
bool moveVertex(double x, double y, QgsFeatureId atFeatureId, int atVertex)
Moves the vertex at the given position number, ring and item (first number is index 0),...
Q_DECL_DEPRECATED Qgis::GeometryOperationResult addRing(const QVector< QgsPointXY > &ring, const QgsFeatureIds &targetFeatureIds=QgsFeatureIds(), QgsFeatureId *modifiedFeatureId=nullptr)
Adds a ring to polygon/multipolygon features.
Encapsulate geometry and attributes for new features, to be passed to createFeatures.
QList< QgsVectorLayerUtils::QgsFeatureData > QgsFeaturesDataList
Alias for list of QgsFeatureData.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
static QgsFeatureList createFeatures(const QgsVectorLayer *layer, const QgsFeaturesDataList &featuresData, QgsExpressionContext *context=nullptr)
Creates a set of new features ready for insertion into a layer.
Represents a vector layer which manages a vector based dataset.
bool isEditable() const final
Returns true if the provider is in editing mode.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
void endEditCommand()
Finish edit command and add it to undo/redo stack.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
QgsGeometryOptions * geometryOptions() const
Configuration and logic to apply automatically on any edit happening on this layer.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
int addTopologicalPoints(const QgsGeometry &geom)
Adds topological points for every vertex of the geometry.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
static Q_INVOKABLE bool isSingleType(Qgis::WkbType type)
Returns true if the WKB type is a single type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7725
QVector< QgsPoint > QgsPointSequence
QMap< int, QVariant > QgsAttributeMap
QList< QgsFeature > QgsFeatureList
#define FID_NULL
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugError(str)
Definition qgslogger.h:71