Skip to content

Commit

Permalink
[FEATURE] fill ring tool: by pressing shift an existing ring is filled
Browse files Browse the repository at this point in the history
  • Loading branch information
jef-n committed Jul 20, 2017
1 parent 0fbc189 commit e7744bd
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 55 deletions.
170 changes: 115 additions & 55 deletions src/app/qgsmaptoolfillring.cpp
Expand Up @@ -22,8 +22,8 @@
#include "qgsattributedialog.h"
#include "qgisapp.h"
#include "qgsvectorlayerutils.h"
#include <QMouseEvent>

#include <QMouseEvent>
#include <limits>

QgsMapToolFillRing::QgsMapToolFillRing( QgsMapCanvas *canvas )
Expand Down Expand Up @@ -52,38 +52,49 @@ void QgsMapToolFillRing::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
return;
}

//add point to list and to rubber band
if ( e->button() == Qt::LeftButton )
if ( e->button() == Qt::LeftButton && QApplication::keyboardModifiers() == Qt::ShiftModifier && !isCapturing() )
{
// left button with shift fills an existing ring
}
else if ( e->button() == Qt::LeftButton )
{
// add point to list and to rubber band

int error = addVertex( e->mapPoint() );
if ( error == 1 )
{
//current layer is not a vector layer
// current layer is not a vector layer
return;
}
else if ( error == 2 )
{
//problem with coordinate transformation
// problem with coordinate transformation
emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), QgsMessageBar::WARNING );
return;
}

startCapturing();
return;
}
else if ( e->button() == Qt::RightButton )
else if ( e->button() != Qt::RightButton || !isCapturing() )
{
if ( !isCapturing() )
return;
return;
}

QgsGeometry g;
QgsFeatureId fid;

if ( isCapturing() )
{
deleteTempRubberBand();

closePolygon();

vlayer->beginEditCommand( tr( "Ring added and filled" ) );

QList< QgsPointXY > pointList = points();

QgsFeatureId modifiedFid;
int addRingReturnCode = vlayer->addRing( pointList, &modifiedFid );
int addRingReturnCode = vlayer->addRing( pointList, &fid );
if ( addRingReturnCode != 0 )
{
QString errorMessage;
Expand Down Expand Up @@ -114,65 +125,114 @@ void QgsMapToolFillRing::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
}
emit messageEmitted( tr( "could not add ring since %1." ).arg( errorMessage ), QgsMessageBar::CRITICAL );
vlayer->destroyEditCommand();

return;
}
else

g = QgsGeometry::fromPolygon( QgsPolygon() << pointList.toVector() );
}
else
{
vlayer->beginEditCommand( tr( "Ring filled" ) );

g = ringUnderPoint( e->mapPoint(), fid );

if ( fid == -1 )
{
// find parent feature and get it attributes
double xMin, xMax, yMin, yMax;
QgsRectangle bBox;
emit messageEmitted( tr( "No ring found to fill." ), QgsMessageBar::CRITICAL );
vlayer->destroyEditCommand();
return;
}
}

xMin = std::numeric_limits<double>::max();
xMax = -std::numeric_limits<double>::max();
yMin = std::numeric_limits<double>::max();
yMax = -std::numeric_limits<double>::max();
QgsRectangle bBox = g.boundingBox();

Q_FOREACH ( const QgsPointXY &point, pointList )
{
xMin = qMin( xMin, point.x() );
xMax = qMax( xMax, point.x() );
yMin = qMin( yMin, point.y() );
yMax = qMax( yMax, point.y() );
}
QgsExpressionContext context = vlayer->createExpressionContext();

QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) );

QgsFeature f;
if ( fit.nextFeature( f ) )
{
//create QgsFeature with wkb representation
QgsFeature ft = QgsVectorLayerUtils::createFeature( vlayer, g, f.attributes().toMap(), &context );

bool res = false;
if ( QApplication::keyboardModifiers() == Qt::ControlModifier )
{
res = vlayer->addFeature( ft );
}
else
{
QgsAttributeDialog *dialog = new QgsAttributeDialog( vlayer, &ft, false, nullptr, true );
dialog->setMode( QgsAttributeForm::AddFeatureMode );
res = dialog->exec(); // will also add the feature
}

bBox.setXMinimum( xMin );
bBox.setYMinimum( yMin );
bBox.setXMaximum( xMax );
bBox.setYMaximum( yMax );
if ( res )
{
vlayer->endEditCommand();
}
else
{
vlayer->destroyEditCommand();
}
}

QgsExpressionContext context = vlayer->createExpressionContext();
if ( isCapturing() )
stopCapturing();
}

QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterFid( modifiedFid ) );
// TODO refactor - shamelessly copied from QgsMapToolDeleteRing::ringUnderPoint
QgsGeometry QgsMapToolFillRing::ringUnderPoint( const QgsPointXY &p, QgsFeatureId &fid )
{
//check if we operate on a vector layer
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() );

QgsFeature f;
if ( fit.nextFeature( f ) )
{
QgsGeometry g = QgsGeometry::fromPolygon( QgsPolygon() << pointList.toVector() );
//There is no clean way to find if we are inside the ring of a feature,
//so we iterate over all the features visible in the canvas
//If several rings are found at this position, the smallest one is chosen,
//in order to be able to delete a ring inside another ring
double area = std::numeric_limits<double>::max();

//create QgsFeature with wkb representation
QgsFeature ft = QgsVectorLayerUtils::createFeature( vlayer, g, f.attributes().toMap(), &context );
QgsGeometry ringGeom;
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( toLayerCoordinates( vlayer, mCanvas->extent() ) ) );

bool res = false;
if ( QApplication::keyboardModifiers() == Qt::ControlModifier )
{
res = vlayer->addFeature( ft );
}
else
{
QgsAttributeDialog *dialog = new QgsAttributeDialog( vlayer, &ft, false, nullptr, true );
dialog->setMode( QgsAttributeForm::AddFeatureMode );
res = dialog->exec(); // will also add the feature
}
QgsFeature f;
while ( fit.nextFeature( f ) )
{
QgsGeometry g = f.geometry();
if ( g.isNull() )
continue;

if ( res )
{
vlayer->endEditCommand();
}
else
QgsMultiPolygon pol;
if ( g.wkbType() == QgsWkbTypes::Polygon || g.wkbType() == QgsWkbTypes::Polygon25D )
{
pol = QgsMultiPolygon() << g.asPolygon();
}
else
{
pol = g.asMultiPolygon();
}

for ( int i = 0; i < pol.size() ; ++i )
{
//for each part
if ( pol[i].size() > 1 )
{
for ( int j = 1; j < pol[i].size(); ++j )
{
vlayer->destroyEditCommand();
QgsPolygon tempPol = QgsPolygon() << pol[i][j];
QgsGeometry tempGeom = QgsGeometry::fromPolygon( tempPol );
if ( tempGeom.area() < area && tempGeom.contains( &p ) )
{
fid = f.id();
area = tempGeom.area();
ringGeom = tempGeom;
}
}
}
}
stopCapturing();
}
return ringGeom;
}
7 changes: 7 additions & 0 deletions src/app/qgsmaptoolfillring.h
Expand Up @@ -27,4 +27,11 @@ class APP_EXPORT QgsMapToolFillRing: public QgsMapToolCapture
QgsMapToolFillRing( QgsMapCanvas *canvas );
virtual ~QgsMapToolFillRing();
void cadCanvasReleaseEvent( QgsMapMouseEvent *e ) override;

private:

/** Return the geometry of the ring under the point p and sets fid to the feature id
*/
QgsGeometry ringUnderPoint( const QgsPointXY &p, QgsFeatureId &fid );

};

0 comments on commit e7744bd

Please sign in to comment.