QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 "qgsvectordataprovider.h"
18 #include "qgsgeometrycache.h"
20 #include "qgslogger.h"
21 
22 #include <limits>
23 
24 
26  : L( layer )
27 {
28 }
29 
30 bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
31 {
32  if ( !L->hasGeometryType() )
33  return false;
34 
35  QgsGeometry geometry;
36  if ( !cache()->geometry( atFeatureId, geometry ) )
37  {
38  // it's not in cache: let's fetch it from layer
39  QgsFeature f;
40  if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.geometry() )
41  return false; // geometry not found
42 
43  geometry = *f.geometry();
44  }
45 
46  geometry.insertVertex( x, y, beforeVertex );
47 
48  L->editBuffer()->changeGeometry( atFeatureId, &geometry );
49  return true;
50 }
51 
52 
53 bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
54 {
55  if ( !L->hasGeometryType() )
56  return false;
57 
58  QgsGeometry geometry;
59  if ( !cache()->geometry( atFeatureId, geometry ) )
60  {
61  // it's not in cache: let's fetch it from layer
62  QgsFeature f;
63  if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.geometry() )
64  return false; // geometry not found
65 
66  geometry = *f.geometry();
67  }
68 
69  geometry.moveVertex( x, y, atVertex );
70 
71  L->editBuffer()->changeGeometry( atFeatureId, &geometry );
72  return true;
73 }
74 
75 
76 bool QgsVectorLayerEditUtils::deleteVertex( QgsFeatureId atFeatureId, int atVertex )
77 {
78  if ( !L->hasGeometryType() )
79  return false;
80 
81  QgsGeometry geometry;
82  if ( !cache()->geometry( atFeatureId, geometry ) )
83  {
84  // it's not in cache: let's fetch it from layer
85  QgsFeature f;
86  if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.geometry() )
87  return false; // geometry not found
88 
89  geometry = *f.geometry();
90  }
91 
92  if ( !geometry.deleteVertex( atVertex ) )
93  return false;
94 
95  L->editBuffer()->changeGeometry( atFeatureId, &geometry );
96  return true;
97 }
98 
99 
100 int QgsVectorLayerEditUtils::addRing( const QList<QgsPoint>& ring )
101 {
102  if ( !L->hasGeometryType() )
103  return 5;
104 
105  int addRingReturnCode = 5; //default: return code for 'ring not inserted'
106  double xMin, yMin, xMax, yMax;
107  QgsRectangle bBox;
108 
109  if ( boundingBoxFromPointList( ring, xMin, yMin, xMax, yMax ) == 0 )
110  {
111  bBox.setXMinimum( xMin ); bBox.setYMinimum( yMin );
112  bBox.setXMaximum( xMax ); bBox.setYMaximum( yMax );
113  }
114  else
115  {
116  return 3; //ring not valid
117  }
118 
119  QgsFeatureIterator fit = L->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
120 
121  QgsFeature f;
122  while ( fit.nextFeature( f ) )
123  {
124  addRingReturnCode = f.geometry()->addRing( ring );
125  if ( addRingReturnCode == 0 )
126  {
127  L->editBuffer()->changeGeometry( f.id(), f.geometry() );
128 
129  //setModified( true, true );
130  break;
131  }
132  }
133 
134  return addRingReturnCode;
135 }
136 
137 
138 int QgsVectorLayerEditUtils::addPart( const QList<QgsPoint> &points, QgsFeatureId featureId )
139 {
140  if ( !L->hasGeometryType() )
141  return 6;
142 
143  QgsGeometry geometry;
144  if ( !cache()->geometry( featureId, geometry ) ) // maybe it's in cache
145  {
146  // it's not in cache: let's fetch it from layer
147  QgsFeature f;
148  if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.geometry() )
149  return 6; //geometry not found
150 
151  geometry = *f.geometry();
152  }
153 
154  int errorCode = geometry.addPart( points, L->geometryType() );
155  if ( errorCode == 0 )
156  {
157  L->editBuffer()->changeGeometry( featureId, &geometry );
158  }
159  return errorCode;
160 }
161 
162 
163 
164 int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy )
165 {
166  if ( !L->hasGeometryType() )
167  return 1;
168 
169  QgsGeometry geometry;
170  if ( !cache()->geometry( featureId, geometry ) ) // maybe it's in cache
171  {
172  // it's not in cache: let's fetch it from layer
173  QgsFeature f;
174  if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.geometry() )
175  return 1; //geometry not found
176 
177  geometry = *f.geometry();
178  }
179 
180  int errorCode = geometry.translate( dx, dy );
181  if ( errorCode == 0 )
182  {
183  L->editBuffer()->changeGeometry( featureId, &geometry );
184  }
185  return errorCode;
186 }
187 
188 
189 int QgsVectorLayerEditUtils::splitFeatures( const QList<QgsPoint>& splitLine, bool topologicalEditing )
190 {
191  if ( !L->hasGeometryType() )
192  return 4;
193 
194  QgsFeatureList newFeatures; //store all the newly created features
195  double xMin, yMin, xMax, yMax;
196  QgsRectangle bBox; //bounding box of the split line
197  int returnCode = 0;
198  int splitFunctionReturn; //return code of QgsGeometry::splitGeometry
199  int numberOfSplittedFeatures = 0;
200 
201  QgsFeatureIterator features;
202  const QgsFeatureIds selectedIds = L->selectedFeaturesIds();
203 
204  if ( selectedIds.size() > 0 ) //consider only the selected features if there is a selection
205  {
206  features = L->selectedFeaturesIterator();
207  }
208  else //else consider all the feature that intersect the bounding box of the split line
209  {
210  if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) == 0 )
211  {
212  bBox.setXMinimum( xMin ); bBox.setYMinimum( yMin );
213  bBox.setXMaximum( xMax ); bBox.setYMaximum( yMax );
214  }
215  else
216  {
217  return 1;
218  }
219 
220  if ( bBox.isEmpty() )
221  {
222  //if the bbox is a line, try to make a square out of it
223  if ( bBox.width() == 0.0 && bBox.height() > 0 )
224  {
225  bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
226  bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
227  }
228  else if ( bBox.height() == 0.0 && bBox.width() > 0 )
229  {
230  bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
231  bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
232  }
233  else
234  {
235  //If we have a single point, we still create a non-null box
236  double bufferDistance = 0.000001;
237  if ( L->crs().geographicFlag() )
238  bufferDistance = 0.00000001;
239  bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
240  bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
241  bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
242  bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
243  }
244  }
245 
246  features = L->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
247  }
248 
249  QgsFeature feat;
250  while ( features.nextFeature( feat ) )
251  {
252  if ( !feat.geometry() )
253  {
254  continue;
255  }
256  QList<QgsGeometry*> newGeometries;
257  QList<QgsPoint> topologyTestPoints;
258  QgsGeometry* newGeometry = 0;
259  splitFunctionReturn = feat.geometry()->splitGeometry( splitLine, newGeometries, topologicalEditing, topologyTestPoints );
260  if ( splitFunctionReturn == 0 )
261  {
262  //change this geometry
263  L->editBuffer()->changeGeometry( feat.id(), feat.geometry() );
264 
265  //insert new features
266  for ( int i = 0; i < newGeometries.size(); ++i )
267  {
268  newGeometry = newGeometries.at( i );
269  QgsFeature newFeature;
270  newFeature.setGeometry( newGeometry );
271 
272  //use default value where possible for primary key (e.g. autoincrement),
273  //and use the value from the original (split) feature if not primary key
274  QgsAttributes newAttributes = feat.attributes();
275  foreach ( int pkIdx, L->dataProvider()->pkAttributeIndexes() )
276  {
277  const QVariant defaultValue = L->dataProvider()->defaultValue( pkIdx );
278  if ( !defaultValue.isNull() )
279  {
280  newAttributes[ pkIdx ] = defaultValue;
281  }
282  else //try with NULL
283  {
284  newAttributes[ pkIdx ] = QVariant();
285  }
286  }
287 
288  newFeature.setAttributes( newAttributes );
289 
290  newFeatures.append( newFeature );
291  }
292 
293  if ( topologicalEditing )
294  {
295  QList<QgsPoint>::const_iterator topol_it = topologyTestPoints.constBegin();
296  for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
297  {
298  addTopologicalPoints( *topol_it );
299  }
300  }
301  ++numberOfSplittedFeatures;
302  }
303  else if ( splitFunctionReturn > 1 ) //1 means no split but also no error
304  {
305  returnCode = splitFunctionReturn;
306  }
307  }
308 
309  if ( numberOfSplittedFeatures == 0 && selectedIds.size() > 0 )
310  {
311  //There is a selection but no feature has been split.
312  //Maybe user forgot that only the selected features are split
313  returnCode = 4;
314  }
315 
316 
317  //now add the new features to this vectorlayer
318  L->editBuffer()->addFeatures( newFeatures );
319 
320  return returnCode;
321 }
322 
323 int QgsVectorLayerEditUtils::splitParts( const QList<QgsPoint>& splitLine, bool topologicalEditing )
324 {
325  if ( !L->hasGeometryType() )
326  return 4;
327 
328  double xMin, yMin, xMax, yMax;
329  QgsRectangle bBox; //bounding box of the split line
330  int returnCode = 0;
331  int splitFunctionReturn; //return code of QgsGeometry::splitGeometry
332  int numberOfSplittedParts = 0;
333 
334  QgsFeatureIterator fit;
335 
336  if ( L->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection
337  {
338  fit = L->selectedFeaturesIterator();
339  }
340  else //else consider all the feature that intersect the bounding box of the split line
341  {
342  if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) == 0 )
343  {
344  bBox.setXMinimum( xMin ); bBox.setYMinimum( yMin );
345  bBox.setXMaximum( xMax ); bBox.setYMaximum( yMax );
346  }
347  else
348  {
349  return 1;
350  }
351 
352  if ( bBox.isEmpty() )
353  {
354  //if the bbox is a line, try to make a square out of it
355  if ( bBox.width() == 0.0 && bBox.height() > 0 )
356  {
357  bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 );
358  bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 );
359  }
360  else if ( bBox.height() == 0.0 && bBox.width() > 0 )
361  {
362  bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 );
363  bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 );
364  }
365  else
366  {
367  //If we have a single point, we still create a non-null box
368  double bufferDistance = 0.000001;
369  if ( L->crs().geographicFlag() )
370  bufferDistance = 0.00000001;
371  bBox.setXMinimum( bBox.xMinimum() - bufferDistance );
372  bBox.setXMaximum( bBox.xMaximum() + bufferDistance );
373  bBox.setYMinimum( bBox.yMinimum() - bufferDistance );
374  bBox.setYMaximum( bBox.yMaximum() + bufferDistance );
375  }
376  }
377 
378  fit = L->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) );
379  }
380 
381  int addPartRet = 0;
382 
383  QgsFeature feat;
384  while ( fit.nextFeature( feat ) )
385  {
386  QList<QgsGeometry*> newGeometries;
387  QList<QgsPoint> topologyTestPoints;
388  splitFunctionReturn = feat.geometry()->splitGeometry( splitLine, newGeometries, topologicalEditing, topologyTestPoints );
389  if ( splitFunctionReturn == 0 )
390  {
391  //add new parts
392  for ( int i = 0; i < newGeometries.size(); ++i )
393  {
394  addPartRet = feat.geometry()->addPart( newGeometries.at( i ) );
395  if ( addPartRet )
396  break;
397  }
398 
399  // For test only: Exception already thrown here...
400  // feat.geometry()->asWkb();
401 
402  if ( !addPartRet )
403  {
404  L->editBuffer()->changeGeometry( feat.id(), feat.geometry() );
405  }
406  else
407  {
408  // Test addPartRet
409  switch ( addPartRet )
410  {
411  case 1:
412  QgsDebugMsg( "Not a multipolygon" );
413  break;
414 
415  case 2:
416  QgsDebugMsg( "Not a valid geometry" );
417  break;
418 
419  case 3:
420  QgsDebugMsg( "New polygon ring" );
421  break;
422  }
423  }
424  L->editBuffer()->changeGeometry( feat.id(), feat.geometry() );
425 
426  if ( topologicalEditing )
427  {
428  QList<QgsPoint>::const_iterator topol_it = topologyTestPoints.constBegin();
429  for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it )
430  {
431  addTopologicalPoints( *topol_it );
432  }
433  }
434  ++numberOfSplittedParts;
435  }
436  else if ( splitFunctionReturn > 1 ) //1 means no split but also no error
437  {
438  returnCode = splitFunctionReturn;
439  }
440 
441  qDeleteAll( newGeometries );
442  }
443 
444  if ( numberOfSplittedParts == 0 && L->selectedFeatureCount() > 0 && returnCode == 0 )
445  {
446  //There is a selection but no feature has been split.
447  //Maybe user forgot that only the selected features are split
448  returnCode = 4;
449  }
450 
451  return returnCode;
452 }
453 
454 
456 {
457  if ( !L->hasGeometryType() )
458  return 1;
459 
460  if ( !geom )
461  {
462  return 1;
463  }
464 
465  int returnVal = 0;
466 
467  QGis::WkbType wkbType = geom->wkbType();
468 
469  switch ( wkbType )
470  {
471  //line
473  case QGis::WKBLineString:
474  {
475  QgsPolyline theLine = geom->asPolyline();
476  QgsPolyline::const_iterator line_it = theLine.constBegin();
477  for ( ; line_it != theLine.constEnd(); ++line_it )
478  {
479  if ( addTopologicalPoints( *line_it ) != 0 )
480  {
481  returnVal = 2;
482  }
483  }
484  break;
485  }
486 
487  //multiline
490  {
491  QgsMultiPolyline theMultiLine = geom->asMultiPolyline();
492  QgsPolyline currentPolyline;
493 
494  for ( int i = 0; i < theMultiLine.size(); ++i )
495  {
496  QgsPolyline::const_iterator line_it = currentPolyline.constBegin();
497  for ( ; line_it != currentPolyline.constEnd(); ++line_it )
498  {
499  if ( addTopologicalPoints( *line_it ) != 0 )
500  {
501  returnVal = 2;
502  }
503  }
504  }
505  break;
506  }
507 
508  //polygon
509  case QGis::WKBPolygon25D:
510  case QGis::WKBPolygon:
511  {
512  QgsPolygon thePolygon = geom->asPolygon();
513  QgsPolyline currentRing;
514 
515  for ( int i = 0; i < thePolygon.size(); ++i )
516  {
517  currentRing = thePolygon.at( i );
518  QgsPolyline::const_iterator line_it = currentRing.constBegin();
519  for ( ; line_it != currentRing.constEnd(); ++line_it )
520  {
521  if ( addTopologicalPoints( *line_it ) != 0 )
522  {
523  returnVal = 2;
524  }
525  }
526  }
527  break;
528  }
529 
530  //multipolygon
533  {
534  QgsMultiPolygon theMultiPolygon = geom->asMultiPolygon();
535  QgsPolygon currentPolygon;
536  QgsPolyline currentRing;
537 
538  for ( int i = 0; i < theMultiPolygon.size(); ++i )
539  {
540  currentPolygon = theMultiPolygon.at( i );
541  for ( int j = 0; j < currentPolygon.size(); ++j )
542  {
543  currentRing = currentPolygon.at( j );
544  QgsPolyline::const_iterator line_it = currentRing.constBegin();
545  for ( ; line_it != currentRing.constEnd(); ++line_it )
546  {
547  if ( addTopologicalPoints( *line_it ) != 0 )
548  {
549  returnVal = 2;
550  }
551  }
552  }
553  }
554  break;
555  }
556  default:
557  break;
558  }
559  return returnVal;
560 }
561 
562 
564 {
565  if ( !L->hasGeometryType() )
566  return 1;
567 
568  QMultiMap<double, QgsSnappingResult> snapResults; //results from the snapper object
569  //we also need to snap to vertex to make sure the vertex does not already exist in this geometry
570  QMultiMap<double, QgsSnappingResult> vertexSnapResults;
571 
572  QList<QgsSnappingResult> filteredSnapResults; //we filter out the results that are on existing vertices
573 
574  //work with a tolerance because coordinate projection may introduce some rounding
575  double threshold = 0.0000001;
576  if ( L->crs().mapUnits() == QGis::Meters )
577  {
578  threshold = 0.001;
579  }
580  else if ( L->crs().mapUnits() == QGis::Feet )
581  {
582  threshold = 0.0001;
583  }
584 
585 
586  if ( L->snapWithContext( p, threshold, snapResults, QgsSnapper::SnapToSegment ) != 0 )
587  {
588  return 2;
589  }
590 
591  QMultiMap<double, QgsSnappingResult>::const_iterator snap_it = snapResults.constBegin();
592  QMultiMap<double, QgsSnappingResult>::const_iterator vertex_snap_it;
593  for ( ; snap_it != snapResults.constEnd(); ++snap_it )
594  {
595  //test if p is already a vertex of this geometry. If yes, don't insert it
596  bool vertexAlreadyExists = false;
597  if ( L->snapWithContext( p, threshold, vertexSnapResults, QgsSnapper::SnapToVertex ) != 0 )
598  {
599  continue;
600  }
601 
602  vertex_snap_it = vertexSnapResults.constBegin();
603  for ( ; vertex_snap_it != vertexSnapResults.constEnd(); ++vertex_snap_it )
604  {
605  if ( snap_it.value().snappedAtGeometry == vertex_snap_it.value().snappedAtGeometry )
606  {
607  vertexAlreadyExists = true;
608  }
609  }
610 
611  if ( !vertexAlreadyExists )
612  {
613  filteredSnapResults.push_back( *snap_it );
614  }
615  }
616  insertSegmentVerticesForSnap( filteredSnapResults );
617  return 0;
618 }
619 
620 
621 int QgsVectorLayerEditUtils::insertSegmentVerticesForSnap( const QList<QgsSnappingResult>& snapResults )
622 {
623  if ( !L->hasGeometryType() )
624  return 1;
625 
626  int returnval = 0;
627  QgsPoint layerPoint;
628 
629  QList<QgsSnappingResult>::const_iterator it = snapResults.constBegin();
630  for ( ; it != snapResults.constEnd(); ++it )
631  {
632  if ( it->snappedVertexNr == -1 ) // segment snap
633  {
634  layerPoint = it->snappedVertex;
635  if ( !insertVertex( layerPoint.x(), layerPoint.y(), it->snappedAtGeometry, it->afterVertexNr ) )
636  {
637  returnval = 3;
638  }
639  }
640  }
641  return returnval;
642 }
643 
644 
645 
646 
647 int QgsVectorLayerEditUtils::boundingBoxFromPointList( const QList<QgsPoint>& list, double& xmin, double& ymin, double& xmax, double& ymax ) const
648 {
649  if ( list.size() < 1 )
650  {
651  return 1;
652  }
653 
658 
659  for ( QList<QgsPoint>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
660  {
661  if ( it->x() < xmin )
662  {
663  xmin = it->x();
664  }
665  if ( it->x() > xmax )
666  {
667  xmax = it->x();
668  }
669  if ( it->y() < ymin )
670  {
671  ymin = it->y();
672  }
673  if ( it->y() > ymax )
674  {
675  ymax = it->y();
676  }
677  }
678 
679  return 0;
680 }