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