/*************************************************************************** qgsvectorlayereditutils.cpp --------------------- begin : Dezember 2012 copyright : (C) 2012 by Martin Dobias email : wonder dot sk at gmail dot com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qgsvectorlayereditutils.h" #include "qgsvectordataprovider.h" #include "qgsfeatureiterator.h" #include "qgsgeometrycache.h" #include "qgsvectorlayereditbuffer.h" #include "qgslinestring.h" #include "qgslogger.h" #include "qgspointv2.h" #include "qgsgeometryfactory.h" #include "qgis.h" #include "qgswkbtypes.h" #include QgsVectorLayerEditUtils::QgsVectorLayerEditUtils( QgsVectorLayer* layer ) : L( layer ) { } bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex ) { if ( !L->hasGeometryType() ) return false; QgsGeometry geometry; if ( !cache()->geometry( atFeatureId, geometry ) ) { // it's not in cache: let's fetch it from layer QgsFeature f; if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.hasGeometry() ) return false; // geometry not found geometry = f.geometry(); } geometry.insertVertex( x, y, beforeVertex ); L->editBuffer()->changeGeometry( atFeatureId, geometry ); return true; } bool QgsVectorLayerEditUtils::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex ) { QgsPointV2 p( x, y ); return moveVertex( p, atFeatureId, atVertex ); } bool QgsVectorLayerEditUtils::moveVertex( const QgsPointV2& p, QgsFeatureId atFeatureId, int atVertex ) { if ( !L->hasGeometryType() ) return false; QgsGeometry geometry; if ( !cache()->geometry( atFeatureId, geometry ) ) { // it's not in cache: let's fetch it from layer QgsFeature f; if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( atFeatureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.hasGeometry() ) return false; // geometry not found geometry = f.geometry(); } geometry.moveVertex( p, atVertex ); L->editBuffer()->changeGeometry( atFeatureId, geometry ); return true; } QgsVectorLayer::EditResult QgsVectorLayerEditUtils::deleteVertex( QgsFeatureId featureId, int vertex ) { if ( !L->hasGeometryType() ) return QgsVectorLayer::InvalidLayer; QgsGeometry geometry; if ( !cache()->geometry( featureId, geometry ) ) { // it's not in cache: let's fetch it from layer QgsFeature f; if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.hasGeometry() ) return QgsVectorLayer::FetchFeatureFailed; // geometry not found geometry = f.geometry(); } if ( !geometry.deleteVertex( vertex ) ) return QgsVectorLayer::EditFailed; if ( geometry.geometry() && geometry.geometry()->nCoordinates() == 0 ) { //last vertex deleted, set geometry to null geometry.setGeometry( nullptr ); } L->editBuffer()->changeGeometry( featureId, geometry ); return !geometry.isEmpty() ? QgsVectorLayer::Success : QgsVectorLayer::EmptyGeometry; } int QgsVectorLayerEditUtils::addRing( const QList& ring, const QgsFeatureIds& targetFeatureIds, QgsFeatureId* modifiedFeatureId ) { QgsLineString* ringLine = new QgsLineString(); QgsPointSequence ringPoints; QList::const_iterator ringIt = ring.constBegin(); for ( ; ringIt != ring.constEnd(); ++ringIt ) { ringPoints.append( QgsPointV2( ringIt->x(), ringIt->y() ) ); } ringLine->setPoints( ringPoints ); return addRing( ringLine, targetFeatureIds, modifiedFeatureId ); } int QgsVectorLayerEditUtils::addRing( QgsCurve* ring, const QgsFeatureIds& targetFeatureIds, QgsFeatureId* modifiedFeatureId ) { if ( !L->hasGeometryType() ) { delete ring; return 5; } int addRingReturnCode = 5; //default: return code for 'ring not inserted' QgsFeature f; QgsFeatureIterator fit; if ( !targetFeatureIds.isEmpty() ) { //check only specified features fit = L->getFeatures( QgsFeatureRequest().setFilterFids( targetFeatureIds ) ); } else { //check all intersecting features QgsRectangle bBox = ring->boundingBox(); fit = L->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) ); } //find first valid feature we can add the ring to while ( fit.nextFeature( f ) ) { if ( !f.hasGeometry() ) continue; //add ring takes ownership of ring, and deletes it if there's an error QgsGeometry g = f.geometry(); addRingReturnCode = g.addRing( static_cast< QgsCurve* >( ring->clone() ) ); if ( addRingReturnCode == 0 ) { L->editBuffer()->changeGeometry( f.id(), g ); if ( modifiedFeatureId ) *modifiedFeatureId = f.id(); //setModified( true, true ); break; } } delete ring; return addRingReturnCode; } int QgsVectorLayerEditUtils::addPart( const QList &points, QgsFeatureId featureId ) { QgsPointSequence l; for ( QList::const_iterator it = points.constBegin(); it != points.constEnd(); ++it ) { l << QgsPointV2( *it ); } return addPart( l, featureId ); } int QgsVectorLayerEditUtils::addPart( const QgsPointSequence &points, QgsFeatureId featureId ) { if ( !L->hasGeometryType() ) return 6; QgsGeometry geometry; bool firstPart = false; if ( !cache()->geometry( featureId, geometry ) ) // maybe it's in cache { // it's not in cache: let's fetch it from layer QgsFeature f; if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) ) return 6; //not found if ( !f.hasGeometry() ) { //no existing geometry, so adding first part to null geometry firstPart = true; } else { geometry = f.geometry(); } } int errorCode = geometry.addPart( points, L->geometryType() ) ; if ( errorCode == 0 ) { if ( firstPart && QgsWkbTypes::isSingleType( L->wkbType() ) && L->dataProvider()->doesStrictFeatureTypeCheck() ) { //convert back to single part if required by layer geometry.convertToSingleType(); } L->editBuffer()->changeGeometry( featureId, geometry ); } return errorCode; } int QgsVectorLayerEditUtils::addPart( QgsCurve* ring, QgsFeatureId featureId ) { if ( !L->hasGeometryType() ) return 6; QgsGeometry geometry; bool firstPart = false; if ( !cache()->geometry( featureId, geometry ) ) // maybe it's in cache { // it's not in cache: let's fetch it from layer QgsFeature f; if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) ) return 6; //not found if ( !f.hasGeometry() ) { //no existing geometry, so adding first part to null geometry firstPart = true; } else { geometry = f.geometry(); } } int errorCode = geometry.addPart( ring, L->geometryType() ) ; if ( errorCode == 0 ) { if ( firstPart && QgsWkbTypes::isSingleType( L->wkbType() ) && L->dataProvider()->doesStrictFeatureTypeCheck() ) { //convert back to single part if required by layer geometry.convertToSingleType(); } L->editBuffer()->changeGeometry( featureId, geometry ); } return errorCode; } int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx, double dy ) { if ( !L->hasGeometryType() ) return 1; QgsGeometry geometry; if ( !cache()->geometry( featureId, geometry ) ) // maybe it's in cache { // it's not in cache: let's fetch it from layer QgsFeature f; if ( !L->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( f ) || !f.hasGeometry() ) return 1; //geometry not found geometry = f.geometry(); } int errorCode = geometry.translate( dx, dy ); if ( errorCode == 0 ) { L->editBuffer()->changeGeometry( featureId, geometry ); } return errorCode; } int QgsVectorLayerEditUtils::splitFeatures( const QList& splitLine, bool topologicalEditing ) { if ( !L->hasGeometryType() ) return 4; QgsFeatureList newFeatures; //store all the newly created features double xMin, yMin, xMax, yMax; QgsRectangle bBox; //bounding box of the split line int returnCode = 0; int splitFunctionReturn; //return code of QgsGeometry::splitGeometry int numberOfSplittedFeatures = 0; QgsFeatureIterator features; const QgsFeatureIds selectedIds = L->selectedFeaturesIds(); if ( !selectedIds.isEmpty() ) //consider only the selected features if there is a selection { features = L->selectedFeaturesIterator(); } else //else consider all the feature that intersect the bounding box of the split line { if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) == 0 ) { bBox.setXMinimum( xMin ); bBox.setYMinimum( yMin ); bBox.setXMaximum( xMax ); bBox.setYMaximum( yMax ); } else { return 1; } if ( bBox.isEmpty() ) { //if the bbox is a line, try to make a square out of it if ( bBox.width() == 0.0 && bBox.height() > 0 ) { bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 ); bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 ); } else if ( bBox.height() == 0.0 && bBox.width() > 0 ) { bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 ); bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 ); } else { //If we have a single point, we still create a non-null box double bufferDistance = 0.000001; if ( L->crs().isGeographic() ) bufferDistance = 0.00000001; bBox.setXMinimum( bBox.xMinimum() - bufferDistance ); bBox.setXMaximum( bBox.xMaximum() + bufferDistance ); bBox.setYMinimum( bBox.yMinimum() - bufferDistance ); bBox.setYMaximum( bBox.yMaximum() + bufferDistance ); } } features = L->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) ); } QgsFeature feat; while ( features.nextFeature( feat ) ) { if ( !feat.hasGeometry() ) { continue; } QList newGeometries; QList topologyTestPoints; QgsGeometry* newGeometry = nullptr; QgsGeometry featureGeom = feat.geometry(); splitFunctionReturn = featureGeom.splitGeometry( splitLine, newGeometries, topologicalEditing, topologyTestPoints ); if ( splitFunctionReturn == 0 ) { //change this geometry L->editBuffer()->changeGeometry( feat.id(), featureGeom ); //insert new features for ( int i = 0; i < newGeometries.size(); ++i ) { newGeometry = newGeometries.at( i ); QgsFeature newFeature; newFeature.setGeometry( *newGeometry ); //use default value where possible for primary key (e.g. autoincrement), //and use the value from the original (split) feature if not primary key QgsAttributes newAttributes = feat.attributes(); Q_FOREACH ( int pkIdx, L->dataProvider()->pkAttributeIndexes() ) { const QVariant defaultValue = L->dataProvider()->defaultValueClause( pkIdx ); if ( !defaultValue.isNull() ) { newAttributes[ pkIdx ] = defaultValue; } else //try with NULL { newAttributes[ pkIdx ] = QVariant(); } } newFeature.setAttributes( newAttributes ); newFeatures.append( newFeature ); } if ( topologicalEditing ) { QList::const_iterator topol_it = topologyTestPoints.constBegin(); for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it ) { addTopologicalPoints( *topol_it ); } } ++numberOfSplittedFeatures; } else if ( splitFunctionReturn > 1 ) //1 means no split but also no error { returnCode = splitFunctionReturn; } } if ( numberOfSplittedFeatures == 0 && !selectedIds.isEmpty() ) { //There is a selection but no feature has been split. //Maybe user forgot that only the selected features are split returnCode = 4; } //now add the new features to this vectorlayer L->editBuffer()->addFeatures( newFeatures ); return returnCode; } int QgsVectorLayerEditUtils::splitParts( const QList& splitLine, bool topologicalEditing ) { if ( !L->hasGeometryType() ) return 4; double xMin, yMin, xMax, yMax; QgsRectangle bBox; //bounding box of the split line int returnCode = 0; int splitFunctionReturn; //return code of QgsGeometry::splitGeometry int numberOfSplittedParts = 0; QgsFeatureIterator fit; if ( L->selectedFeatureCount() > 0 ) //consider only the selected features if there is a selection { fit = L->selectedFeaturesIterator(); } else //else consider all the feature that intersect the bounding box of the split line { if ( boundingBoxFromPointList( splitLine, xMin, yMin, xMax, yMax ) == 0 ) { bBox.setXMinimum( xMin ); bBox.setYMinimum( yMin ); bBox.setXMaximum( xMax ); bBox.setYMaximum( yMax ); } else { return 1; } if ( bBox.isEmpty() ) { //if the bbox is a line, try to make a square out of it if ( bBox.width() == 0.0 && bBox.height() > 0 ) { bBox.setXMinimum( bBox.xMinimum() - bBox.height() / 2 ); bBox.setXMaximum( bBox.xMaximum() + bBox.height() / 2 ); } else if ( bBox.height() == 0.0 && bBox.width() > 0 ) { bBox.setYMinimum( bBox.yMinimum() - bBox.width() / 2 ); bBox.setYMaximum( bBox.yMaximum() + bBox.width() / 2 ); } else { //If we have a single point, we still create a non-null box double bufferDistance = 0.000001; if ( L->crs().isGeographic() ) bufferDistance = 0.00000001; bBox.setXMinimum( bBox.xMinimum() - bufferDistance ); bBox.setXMaximum( bBox.xMaximum() + bufferDistance ); bBox.setYMinimum( bBox.yMinimum() - bufferDistance ); bBox.setYMaximum( bBox.yMaximum() + bufferDistance ); } } fit = L->getFeatures( QgsFeatureRequest().setFilterRect( bBox ).setFlags( QgsFeatureRequest::ExactIntersect ) ); } int addPartRet = 0; QgsFeature feat; while ( fit.nextFeature( feat ) ) { QList newGeometries; QList topologyTestPoints; QgsGeometry featureGeom = feat.geometry(); splitFunctionReturn = featureGeom.splitGeometry( splitLine, newGeometries, topologicalEditing, topologyTestPoints ); if ( splitFunctionReturn == 0 ) { //add new parts if ( !newGeometries.isEmpty() ) featureGeom.convertToMultiType(); for ( int i = 0; i < newGeometries.size(); ++i ) { addPartRet = featureGeom.addPart( newGeometries.at( i ) ); if ( addPartRet ) break; } // For test only: Exception already thrown here... // feat.geometry()->asWkb(); if ( !addPartRet ) { L->editBuffer()->changeGeometry( feat.id(), featureGeom ); } else { // Test addPartRet switch ( addPartRet ) { case 1: QgsDebugMsg( "Not a multipolygon" ); break; case 2: QgsDebugMsg( "Not a valid geometry" ); break; case 3: QgsDebugMsg( "New polygon ring" ); break; } } L->editBuffer()->changeGeometry( feat.id(), featureGeom ); if ( topologicalEditing ) { QList::const_iterator topol_it = topologyTestPoints.constBegin(); for ( ; topol_it != topologyTestPoints.constEnd(); ++topol_it ) { addTopologicalPoints( *topol_it ); } } ++numberOfSplittedParts; } else if ( splitFunctionReturn > 1 ) //1 means no split but also no error { returnCode = splitFunctionReturn; } qDeleteAll( newGeometries ); } if ( numberOfSplittedParts == 0 && L->selectedFeatureCount() > 0 && returnCode == 0 ) { //There is a selection but no feature has been split. //Maybe user forgot that only the selected features are split returnCode = 4; } return returnCode; } int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsGeometry& geom ) { if ( !L->hasGeometryType() ) return 1; if ( geom.isEmpty() ) { return 1; } int returnVal = 0; QgsWkbTypes::Type wkbType = geom.wkbType(); switch ( wkbType ) { //line case QgsWkbTypes::LineString25D: case QgsWkbTypes::LineString: { QgsPolyline theLine = geom.asPolyline(); QgsPolyline::const_iterator line_it = theLine.constBegin(); for ( ; line_it != theLine.constEnd(); ++line_it ) { if ( addTopologicalPoints( *line_it ) != 0 ) { returnVal = 2; } } break; } //multiline case QgsWkbTypes::MultiLineString25D: case QgsWkbTypes::MultiLineString: { QgsMultiPolyline theMultiLine = geom.asMultiPolyline(); QgsPolyline currentPolyline; for ( int i = 0; i < theMultiLine.size(); ++i ) { QgsPolyline::const_iterator line_it = currentPolyline.constBegin(); for ( ; line_it != currentPolyline.constEnd(); ++line_it ) { if ( addTopologicalPoints( *line_it ) != 0 ) { returnVal = 2; } } } break; } //polygon case QgsWkbTypes::Polygon25D: case QgsWkbTypes::Polygon: { QgsPolygon thePolygon = geom.asPolygon(); QgsPolyline currentRing; for ( int i = 0; i < thePolygon.size(); ++i ) { currentRing = thePolygon.at( i ); QgsPolyline::const_iterator line_it = currentRing.constBegin(); for ( ; line_it != currentRing.constEnd(); ++line_it ) { if ( addTopologicalPoints( *line_it ) != 0 ) { returnVal = 2; } } } break; } //multipolygon case QgsWkbTypes::MultiPolygon25D: case QgsWkbTypes::MultiPolygon: { QgsMultiPolygon theMultiPolygon = geom.asMultiPolygon(); QgsPolygon currentPolygon; QgsPolyline currentRing; for ( int i = 0; i < theMultiPolygon.size(); ++i ) { currentPolygon = theMultiPolygon.at( i ); for ( int j = 0; j < currentPolygon.size(); ++j ) { currentRing = currentPolygon.at( j ); QgsPolyline::const_iterator line_it = currentRing.constBegin(); for ( ; line_it != currentRing.constEnd(); ++line_it ) { if ( addTopologicalPoints( *line_it ) != 0 ) { returnVal = 2; } } } } break; } default: break; } return returnVal; } int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsPoint& p ) { if ( !L->hasGeometryType() ) return 1; QMultiMap snapResults; //results from the snapper object //we also need to snap to vertex to make sure the vertex does not already exist in this geometry QMultiMap vertexSnapResults; QList filteredSnapResults; //we filter out the results that are on existing vertices //work with a tolerance because coordinate projection may introduce some rounding double threshold = 0.0000001; if ( L->crs().mapUnits() == QgsUnitTypes::DistanceMeters ) { threshold = 0.001; } else if ( L->crs().mapUnits() == QgsUnitTypes::DistanceFeet ) { threshold = 0.0001; } if ( L->snapWithContext( p, threshold, snapResults, QgsSnapper::SnapToSegment ) != 0 ) { return 2; } QMultiMap::const_iterator snap_it = snapResults.constBegin(); QMultiMap::const_iterator vertex_snap_it; for ( ; snap_it != snapResults.constEnd(); ++snap_it ) { //test if p is already a vertex of this geometry. If yes, don't insert it bool vertexAlreadyExists = false; if ( L->snapWithContext( p, threshold, vertexSnapResults, QgsSnapper::SnapToVertex ) != 0 ) { continue; } vertex_snap_it = vertexSnapResults.constBegin(); for ( ; vertex_snap_it != vertexSnapResults.constEnd(); ++vertex_snap_it ) { if ( snap_it.value().snappedAtGeometry == vertex_snap_it.value().snappedAtGeometry ) { vertexAlreadyExists = true; } } if ( !vertexAlreadyExists ) { filteredSnapResults.push_back( *snap_it ); } } insertSegmentVerticesForSnap( filteredSnapResults ); return 0; } int QgsVectorLayerEditUtils::insertSegmentVerticesForSnap( const QList& snapResults ) { if ( !L->hasGeometryType() ) return 1; int returnval = 0; QgsPoint layerPoint; QList::const_iterator it = snapResults.constBegin(); for ( ; it != snapResults.constEnd(); ++it ) { if ( it->snappedVertexNr == -1 ) // segment snap { layerPoint = it->snappedVertex; if ( !insertVertex( layerPoint.x(), layerPoint.y(), it->snappedAtGeometry, it->afterVertexNr ) ) { returnval = 3; } } } return returnval; } int QgsVectorLayerEditUtils::boundingBoxFromPointList( const QList& list, double& xmin, double& ymin, double& xmax, double& ymax ) const { if ( list.size() < 1 ) { return 1; } xmin = std::numeric_limits::max(); xmax = -std::numeric_limits::max(); ymin = std::numeric_limits::max(); ymax = -std::numeric_limits::max(); for ( QList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it ) { if ( it->x() < xmin ) { xmin = it->x(); } if ( it->x() > xmax ) { xmax = it->x(); } if ( it->y() < ymin ) { ymin = it->y(); } if ( it->y() > ymax ) { ymax = it->y(); } } return 0; }