diff --git a/images/themes/default/mActionDeleteHole.png b/images/themes/default/mActionDeleteHole.png new file mode 100644 index 00000000000..6785481d757 Binary files /dev/null and b/images/themes/default/mActionDeleteHole.png differ diff --git a/images/themes/default/mActionDeletePart.png b/images/themes/default/mActionDeletePart.png new file mode 100644 index 00000000000..ba90095aae0 Binary files /dev/null and b/images/themes/default/mActionDeletePart.png differ diff --git a/images/themes/default/mActionSimplify.png b/images/themes/default/mActionSimplify.png new file mode 100644 index 00000000000..3262a732da4 Binary files /dev/null and b/images/themes/default/mActionSimplify.png differ diff --git a/python/core/qgsgeometry.sip b/python/core/qgsgeometry.sip index 29f4146a10b..7d12d7da4ba 100644 --- a/python/core/qgsgeometry.sip +++ b/python/core/qgsgeometry.sip @@ -289,5 +289,16 @@ not disjoint with existing polygons of the feature*/ // TODO: destruction of created geometries?? QList asGeometryCollection() /Factory/; + /** delete a hole in polygon or multipolygon. + Ring 0 is outer ring and can't be deleted. + @return TRUE on success + @note added in version 1.2 */ + bool deleteHole( int ringNum, int partNum = 0 ); + + /** delete part identified by the part number + @return TRUE on success + @note added in version 1.2 */ + bool deletePart( int partNum ); + }; // class QgsGeometry diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 7d4bb9dc246..c865596e7fb 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -27,12 +27,15 @@ SET(QGIS_APP_SRCS qgsmaptooladdisland.cpp qgsmaptooladdring.cpp qgsmaptoolcapture.cpp + qgsmaptooldeletehole.cpp + qgsmaptooldeletepart.cpp qgsmaptooldeletevertex.cpp qgsmaptooledit.cpp qgsmaptoolidentify.cpp qgsmaptoolmovefeature.cpp qgsmaptoolmovevertex.cpp qgsmaptoolselect.cpp + qgsmaptoolsimplify.cpp qgsmaptoolsplitfeatures.cpp qgsmaptoolvertexedit.cpp qgsmeasuredialog.cpp @@ -115,6 +118,7 @@ SET (QGIS_APP_MOC_HDRS qgsmaptooladdring.h qgsmaptoolmovefeature.h qgsmaptoolselect.h + qgsmaptoolsimplify.h qgsmeasuretool.h qgsmeasuredialog.h diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 83e82a25142..c174f68fe86 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -157,6 +157,8 @@ #include "qgsmaptooladdisland.h" #include "qgsmaptooladdring.h" #include "qgsmaptooladdvertex.h" +#include "qgsmaptooldeletehole.h" +#include "qgsmaptooldeletepart.h" #include "qgsmaptooldeletevertex.h" #include "qgsmaptoolidentify.h" #include "qgsmaptoolmovefeature.h" @@ -166,6 +168,7 @@ #include "qgsmaptoolsplitfeatures.h" #include "qgsmaptoolvertexedit.h" #include "qgsmaptoolzoom.h" +#include "qgsmaptoolsimplify.h" #include "qgsmeasuretool.h" // @@ -465,6 +468,9 @@ QgisApp::~QgisApp() delete mMapTools.mVertexMove; delete mMapTools.mVertexDelete; delete mMapTools.mAddRing; + delete mMapTools.mSimplifyFeature; + delete mMapTools.mDeleteHole; + delete mMapTools.mDeletePart; delete mMapTools.mAddIsland; delete mPythonConsole; @@ -667,6 +673,22 @@ void QgisApp::createActions() connect( mActionAddIsland, SIGNAL( triggered() ), this, SLOT( addIsland() ) ); mActionAddIsland->setEnabled( false ); + mActionSimplifyFeature = new QAction( getThemeIcon( "mActionSimplify.png" ), tr( "Simplify Feature" ), this ); + mActionSimplifyFeature->setStatusTip( tr( "Simplify Feature" ) ); + connect( mActionSimplifyFeature, SIGNAL( triggered() ), this, SLOT( simplifyFeature() ) ); + mActionSimplifyFeature->setEnabled( false ); + + mActionDeleteHole = new QAction( getThemeIcon( "mActionDeleteHole.png" ), tr( "Delete Hole" ), this ); + mActionDeleteHole->setStatusTip( tr( "Delete Hole" ) ); + connect( mActionDeleteHole, SIGNAL( triggered() ), this, SLOT( deleteHole() ) ); + mActionDeleteHole->setEnabled( false ); + + mActionDeletePart = new QAction( getThemeIcon( "mActionDeletePart.png" ), tr( "Delete Part" ), this ); + mActionDeletePart->setStatusTip( tr( "Delete Part" ) ); + connect( mActionDeletePart, SIGNAL( triggered() ), this, SLOT( deletePart() ) ); + mActionDeletePart->setEnabled( false ); + + // View Menu Items mActionPan = new QAction( getThemeIcon( "mActionPan.png" ), tr( "Pan Map" ), this ); @@ -990,6 +1012,12 @@ void QgisApp::createActionGroups() mMapToolGroup->addAction( mActionAddRing ); mActionAddIsland->setCheckable( true ); mMapToolGroup->addAction( mActionAddIsland ); + mActionSimplifyFeature->setCheckable( true ); + mMapToolGroup->addAction( mActionSimplifyFeature ); + mActionDeleteHole->setCheckable( true ); + mMapToolGroup->addAction( mActionDeleteHole ); + mActionDeletePart->setCheckable( true ); + mMapToolGroup->addAction( mActionDeletePart ); } void QgisApp::createMenus() @@ -1073,9 +1101,15 @@ void QgisApp::createMenus() mEditMenu->addAction( mActionAddRing ); mEditMenu->addAction( mActionAddIsland ); + mActionEditSeparator2 = mEditMenu->addSeparator(); + + mEditMenu->addAction( mActionSimplifyFeature ); + mEditMenu->addAction( mActionDeleteHole ); + mEditMenu->addAction( mActionDeletePart ); + if ( layout == QDialogButtonBox::GnomeLayout || layout == QDialogButtonBox::MacLayout ) { - mActionEditSeparator2 = mEditMenu->addSeparator(); + mActionEditSeparator3 = mEditMenu->addSeparator(); mEditMenu->addAction( mActionOptions ); mEditMenu->addAction( mActionCustomProjection ); } @@ -1267,6 +1301,16 @@ void QgisApp::createToolBars() mDigitizeToolBar->addAction( mActionCopyFeatures ); mDigitizeToolBar->addAction( mActionPasteFeatures ); mToolbarMenu->addAction( mDigitizeToolBar->toggleViewAction() ); + + mAdvancedDigitizeToolBar = addToolBar( tr( "Advanced Digitizing" ) ); + mAdvancedDigitizeToolBar->setIconSize( myIconSize ); + mAdvancedDigitizeToolBar->setObjectName( "Advanced Digitizing" ); + mAdvancedDigitizeToolBar->addAction( mActionSimplifyFeature ); + mAdvancedDigitizeToolBar->addAction( mActionDeleteHole ); + mAdvancedDigitizeToolBar->addAction( mActionDeletePart ); + mToolbarMenu->addAction( mAdvancedDigitizeToolBar->toggleViewAction() ); + + // // Map Navigation Toolbar mMapNavToolBar = addToolBar( tr( "Map Navigation" ) ); @@ -1606,6 +1650,12 @@ void QgisApp::createCanvas() mMapTools.mAddRing = new QgsMapToolAddRing( mMapCanvas ); mMapTools.mAddRing->setAction( mActionAddRing ); mMapTools.mAddIsland = new QgsMapToolAddIsland( mMapCanvas ); + mMapTools.mSimplifyFeature = new QgsMapToolSimplify( mMapCanvas ); + mMapTools.mSimplifyFeature->setAction( mActionSimplifyFeature ); + mMapTools.mDeleteHole = new QgsMapToolDeleteHole( mMapCanvas ); + mMapTools.mDeleteHole->setAction( mActionDeleteHole ); + mMapTools.mDeletePart = new QgsMapToolDeletePart( mMapCanvas ); + mMapTools.mDeletePart->setAction( mActionDeletePart ); //ensure that non edit tool is initialised or we will get crashes in some situations mNonEditMapTool = mMapTools.mPan; } @@ -3979,6 +4029,21 @@ void QgisApp::moveFeature() mMapCanvas->setMapTool( mMapTools.mMoveFeature ); } +void QgisApp::simplifyFeature() +{ + mMapCanvas->setMapTool( mMapTools.mSimplifyFeature ); +} + +void QgisApp::deleteHole() +{ + mMapCanvas->setMapTool( mMapTools.mDeleteHole ); +} + +void QgisApp::deletePart() +{ + mMapCanvas->setMapTool( mMapTools.mDeletePart ); +} + void QgisApp::splitFeatures() { mMapCanvas->setMapTool( mMapTools.mSplitFeatures ); @@ -5215,11 +5280,13 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) { mActionCapturePoint->setEnabled( true ); mActionCapturePoint->setVisible( true ); + mActionDeletePart->setEnabled( true ); } else { mActionCapturePoint->setEnabled( false ); mActionCapturePoint->setVisible( false ); + mActionDeletePart->setEnabled( false ); } mActionCaptureLine->setEnabled( false ); mActionCapturePolygon->setEnabled( false ); @@ -5231,6 +5298,9 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionAddRing->setEnabled( false ); mActionAddIsland->setEnabled( false ); mActionSplitFeatures->setEnabled( false ); + mActionSimplifyFeature->setEnabled( false ); + mActionDeleteHole->setEnabled( false ); + if ( vlayer->isEditable() && dprovider->capabilities() & QgsVectorDataProvider::ChangeGeometries ) { mActionMoveVertex->setEnabled( true ); @@ -5244,12 +5314,16 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionCaptureLine->setEnabled( true ); mActionCaptureLine->setVisible( true ); mActionSplitFeatures->setEnabled( true ); + mActionSimplifyFeature->setEnabled( true ); + mActionDeletePart->setEnabled( true ); } else { mActionCaptureLine->setEnabled( false ); mActionCaptureLine->setVisible( false ); mActionSplitFeatures->setEnabled( false ); + mActionSimplifyFeature->setEnabled( false ); + mActionDeletePart->setEnabled( false ); } mActionCapturePoint->setEnabled( false ); mActionCapturePolygon->setEnabled( false ); @@ -5257,6 +5331,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionCapturePolygon->setVisible( false ); mActionAddRing->setEnabled( false ); mActionAddIsland->setEnabled( false ); + mActionDeleteHole->setEnabled( false ); } else if ( vlayer->geometryType() == QGis::Polygon ) { @@ -5267,6 +5342,9 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionAddRing->setEnabled( true ); mActionAddIsland->setEnabled( true ); mActionSplitFeatures->setEnabled( true ); + mActionSimplifyFeature->setEnabled( true ); + mActionDeleteHole->setEnabled( true ); + mActionDeletePart->setEnabled( true ); } else { @@ -5275,6 +5353,9 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionAddRing->setEnabled( false ); mActionAddIsland->setEnabled( false ); mActionSplitFeatures->setEnabled( false ); + mActionSimplifyFeature->setEnabled( false ); + mActionDeleteHole->setEnabled( false ); + mActionDeletePart->setEnabled( false ); } mActionCapturePoint->setEnabled( false ); mActionCaptureLine->setEnabled( false ); diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 7d9ddf2e1bd..9c181380dff 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -42,6 +42,8 @@ class QgisAppInterface; class QgsClipboard; class QgsComposer; class QgsHelpViewer; +class QgsFeature; + class QgsLegend; class QgsMapCanvas; class QgsMapLayer; @@ -63,7 +65,6 @@ class QgsVectorLayer; #include "qgsconfig.h" #include "qgspoint.h" - /*! \class QgisApp * \brief Main window for the Qgis application */ @@ -221,6 +222,9 @@ class QgisApp : public QMainWindow QAction *actionMoveVertex() { return mActionMoveVertex; } QAction *actionAddRing() { return mActionAddRing; } QAction *actionAddIsland() { return mActionAddIsland; } + QAction *actionSimplifyFeature() { return mActionSimplifyFeature; } + QAction *actionDeleteHole() { return mActionDeleteHole; } + QAction *actionDeletePart() { return mActionDeletePart; } QAction *actionEditSeparator2() { return mActionEditSeparator2; } QAction *actionPan() { return mActionPan; } @@ -318,6 +322,7 @@ class QgisApp : public QMainWindow QToolBar *layerToolBar() { return mLayerToolBar; } QToolBar *mapNavToolToolBar() { return mMapNavToolBar; } QToolBar *digitizeToolBar() { return mDigitizeToolBar; } + QToolBar *advancedDigitizeToolBar() { return mAdvancedDigitizeToolBar; } QToolBar *attributesToolBar() { return mAttributesToolBar; } QToolBar *pluginToolBar() { return mPluginToolBar; } QToolBar *helpToolBar() { return mHelpToolBar; } @@ -495,6 +500,12 @@ class QgisApp : public QMainWindow void addRing(); //! activates the add island tool void addIsland(); + //! simplifies feature + void simplifyFeature(); + //! deletes hole in polygon + void deleteHole(); + //! deletes part of polygon + void deletePart(); //! activates the selection tool void select(); @@ -666,6 +677,7 @@ class QgisApp : public QMainWindow QToolBar *mLayerToolBar; QToolBar *mMapNavToolBar; QToolBar *mDigitizeToolBar; + QToolBar *mAdvancedDigitizeToolBar; QToolBar *mAttributesToolBar; QToolBar *mPluginToolBar; QToolBar *mHelpToolBar; @@ -701,6 +713,10 @@ class QgisApp : public QMainWindow QAction *mActionAddRing; QAction *mActionAddIsland; QAction *mActionEditSeparator2; + QAction *mActionSimplifyFeature; + QAction *mActionDeleteHole; + QAction *mActionDeletePart; + QAction *mActionEditSeparator3; QAction *mActionPan; QAction *mActionZoomIn; @@ -816,6 +832,9 @@ class QgisApp : public QMainWindow QgsMapTool* mVertexDelete; QgsMapTool* mAddRing; QgsMapTool* mAddIsland; + QgsMapTool* mSimplifyFeature; + QgsMapTool* mDeleteHole; + QgsMapTool* mDeletePart; } mMapTools; QgsMapTool *mNonEditMapTool; @@ -921,6 +940,7 @@ class QgisApp : public QMainWindow QgsPythonUtils* mPythonUtils; static QgisApp *smInstance; + }; #endif diff --git a/src/app/qgsmaptooldeletehole.cpp b/src/app/qgsmaptooldeletehole.cpp new file mode 100644 index 00000000000..0cbbece7549 --- /dev/null +++ b/src/app/qgsmaptooldeletehole.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + qgsmaptooldeletehole.h - delete a hole from polygon + --------------------- + begin : April 2009 + copyright : (C) 2009 by Richard Kostecky + email : csf dot kostej at mail 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 "qgsmaptooldeletehole.h" + +#include "qgsmapcanvas.h" +#include "qgsvertexmarker.h" +#include "qgsvectorlayer.h" + +#include +#include + +QgsMapToolDeleteHole::QgsMapToolDeleteHole( QgsMapCanvas* canvas ) + : QgsMapToolVertexEdit( canvas ), mCross( 0 ) +{ +} + +QgsMapToolDeleteHole::~QgsMapToolDeleteHole() +{ + delete mCross; +} + +void QgsMapToolDeleteHole::canvasMoveEvent( QMouseEvent * e ) +{ + //nothing to do +} + +void QgsMapToolDeleteHole::canvasPressEvent( QMouseEvent * e ) +{ + delete mCross; + mCross = 0; + + mRecentSnappingResults.clear(); + //do snap -> new recent snapping results + if ( mSnapper.snapToCurrentLayer( e->pos(), mRecentSnappingResults, QgsSnapper::SnapToVertex ) != 0 ) + { + //error + } + + if ( mRecentSnappingResults.size() > 0 ) + { + QgsPoint markerPoint = mRecentSnappingResults.begin()->snappedVertex; + + //show vertex marker + mCross = new QgsVertexMarker( mCanvas ); + mCross->setIconType( QgsVertexMarker::ICON_X ); + mCross->setCenter( markerPoint ); + } + else + { + displaySnapToleranceWarning(); + } +} + +void QgsMapToolDeleteHole::canvasReleaseEvent( QMouseEvent * e ) +{ + delete mCross; + mCross = 0; + + QgsMapLayer* currentLayer = mCanvas->currentLayer(); + if ( !currentLayer ) + return; + + QgsVectorLayer* vlayer = dynamic_cast( currentLayer ); + if ( !vlayer ) + return; + + + if ( mRecentSnappingResults.size() > 0 ) + { + QList::iterator sr_it = mRecentSnappingResults.begin(); + for ( ; sr_it != mRecentSnappingResults.end(); ++sr_it ) + { + deleteHole( sr_it->snappedAtGeometry, sr_it->snappedVertexNr, vlayer); + } + } +} + + +void QgsMapToolDeleteHole::deleteHole( int fId, int beforeVertexNr, QgsVectorLayer* vlayer) +{ + QgsFeature f; + vlayer->featureAtId( fId, f ); + + QgsGeometry* g = f.geometry(); + QGis::WkbType wkbtype = g->wkbType(); + int ringNum, partNum = 0; + + if (wkbtype == QGis::WKBPolygon || wkbtype == QGis::WKBPolygon25D) + { + ringNum = ringNumInPolygon( g, beforeVertexNr ); + } + else if (wkbtype == QGis::WKBMultiPolygon || wkbtype == QGis::WKBMultiPolygon25D) + { + ringNum = ringNumInMultiPolygon( g, beforeVertexNr, partNum ); + } + else + return; + + if (g->deleteHole( ringNum, partNum )) + { + vlayer->deleteFeature( fId ); + vlayer->addFeature(f); + mCanvas->refresh(); + } + +} + +int QgsMapToolDeleteHole::ringNumInPolygon( QgsGeometry* g, int vertexNr ) +{ + QgsPolygon polygon = g->asPolygon(); + for (int ring = 0; ring < polygon.count(); ring++) + { + if (vertexNr < polygon[ring].count()) + return ring; + + vertexNr -= polygon[ring].count(); + } + return -1; +} + +int QgsMapToolDeleteHole::ringNumInMultiPolygon( QgsGeometry* g, int vertexNr, int& partNum ) +{ + QgsMultiPolygon mpolygon = g->asMultiPolygon(); + for (int part = 0; part < mpolygon.count(); part++) + { + const QgsPolygon& polygon = mpolygon[part]; + for (int ring = 0; ring < polygon.count(); ring++) + { + if (vertexNr < polygon[ring].count()) + { + partNum = part; + return ring; + } + + vertexNr -= polygon[ring].count(); + } + } + return -1; +} + + +void QgsMapToolDeleteHole::deactivate() +{ + delete mCross; + mCross = 0; + + QgsMapTool::deactivate(); +} diff --git a/src/app/qgsmaptooldeletehole.h b/src/app/qgsmaptooldeletehole.h new file mode 100644 index 00000000000..7bfd5c91be2 --- /dev/null +++ b/src/app/qgsmaptooldeletehole.h @@ -0,0 +1,53 @@ +/*************************************************************************** + qgsmaptooldeletehole.h - delete a hole from polygon + --------------------- + begin : April 2009 + copyright : (C) 2009 by Richard Kostecky + email : csf dot kostej at mail 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. * + * * + ***************************************************************************/ + +#ifndef QGSMAPTOOLDELETEHOLE_H +#define QGSMAPTOOLDELETEHOLE_H + +#include "qgsmaptoolvertexedit.h" +#include + +class QgsVertexMarker; +/**Map tool to delete vertices from line/polygon features*/ + +class QgsMapToolDeleteHole: public QgsMapToolVertexEdit +{ + public: + QgsMapToolDeleteHole( QgsMapCanvas* canvas ); + virtual ~QgsMapToolDeleteHole(); + + void canvasMoveEvent( QMouseEvent * e ); + + void canvasPressEvent( QMouseEvent * e ); + + void canvasReleaseEvent( QMouseEvent * e ); + + //! called when map tool is being deactivated + void deactivate(); + + private: + QgsVertexMarker* mCross; + + //! delete hole from the geometry + void deleteHole( int fId, int beforeVertexNr, QgsVectorLayer* vlayer); + + //! return ring number in polygon + int ringNumInPolygon( QgsGeometry* g, int vertexNr ); + + //! return ring number in multipolygon and set parNum to index of the part + int ringNumInMultiPolygon( QgsGeometry* g, int vertexNr, int& partNum ); +}; + +#endif diff --git a/src/app/qgsmaptooldeletepart.cpp b/src/app/qgsmaptooldeletepart.cpp new file mode 100644 index 00000000000..0f6b1378bb1 --- /dev/null +++ b/src/app/qgsmaptooldeletepart.cpp @@ -0,0 +1,178 @@ +/*************************************************************************** + qgsmaptooldeletepart.cpp - delete a part from multipart geometry + --------------------- + begin : April 2009 + copyright : (C) 2009 by Richard Kostecky + email : csf dot kostej at mail 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 "qgsmaptooldeletepart.h" + +#include "qgsmapcanvas.h" +#include "qgsvertexmarker.h" +#include "qgsvectorlayer.h" + +#include +#include + +QgsMapToolDeletePart::QgsMapToolDeletePart( QgsMapCanvas* canvas ) + : QgsMapToolVertexEdit( canvas ), mCross( 0 ) +{ +} + +QgsMapToolDeletePart::~QgsMapToolDeletePart() +{ + delete mCross; +} + +void QgsMapToolDeletePart::canvasMoveEvent( QMouseEvent * e ) +{ + //nothing to do +} + +void QgsMapToolDeletePart::canvasPressEvent( QMouseEvent * e ) +{ + delete mCross; + mCross = 0; + + mRecentSnappingResults.clear(); + //do snap -> new recent snapping results + if ( mSnapper.snapToCurrentLayer( e->pos(), mRecentSnappingResults, QgsSnapper::SnapToVertex ) != 0 ) + { + //error + } + + if ( mRecentSnappingResults.size() > 0 ) + { + QgsPoint markerPoint = mRecentSnappingResults.begin()->snappedVertex; + + //show vertex marker + mCross = new QgsVertexMarker( mCanvas ); + mCross->setIconType( QgsVertexMarker::ICON_X ); + mCross->setCenter( markerPoint ); + } + else + { + displaySnapToleranceWarning(); + } +} + +void QgsMapToolDeletePart::canvasReleaseEvent( QMouseEvent * e ) +{ + delete mCross; + mCross = 0; + + QgsMapLayer* currentLayer = mCanvas->currentLayer(); + if ( !currentLayer ) + return; + + QgsVectorLayer* vlayer = dynamic_cast( currentLayer ); + if ( !vlayer ) + return; + + if ( mRecentSnappingResults.size() > 0 ) + { + QList::iterator sr_it = mRecentSnappingResults.begin(); + for ( ; sr_it != mRecentSnappingResults.end(); ++sr_it ) + { + deletePart( sr_it->snappedAtGeometry, sr_it->snappedVertexNr, vlayer); + } + } + +} + + +void QgsMapToolDeletePart::deletePart( int fId, int beforeVertexNr, QgsVectorLayer* vlayer) +{ + QgsFeature f; + vlayer->featureAtId( fId, f ); + + // find out the part number + QgsGeometry* g = f.geometry(); + if ( !g->isMultipart() ) + { + QMessageBox::information(mCanvas, tr("Delete part"), tr("This isn't a multipart geometry.")); + return; + } + + int partNum = partNumberOfVertex( g, beforeVertexNr ); + + if (g->deletePart( partNum )) + { + vlayer->deleteFeature( fId ); + vlayer->addFeature(f); + mCanvas->refresh(); + } + else + { + QMessageBox::information(mCanvas, tr("Delete part"), tr("Couldn't remove the selected part.")); + } + +} + +int QgsMapToolDeletePart::partNumberOfVertex( QgsGeometry* g, int beforeVertexNr ) +{ + int part; + + switch ( g->wkbType() ) + { + case QGis::WKBMultiPoint25D: + case QGis::WKBMultiPoint: + if ( beforeVertexNr < g->asMultiPoint().count() ) + return beforeVertexNr; + else + return -1; + + case QGis::WKBMultiLineString25D: + case QGis::WKBMultiLineString: + { + QgsMultiPolyline mline = g->asMultiPolyline(); + for (part = 0; part < mline.count(); part++) + { + if (beforeVertexNr < mline[part].count()) + return part; + + beforeVertexNr -= mline[part].count(); + } + return -1; // not found + } + + case QGis::WKBMultiPolygon25D: + case QGis::WKBMultiPolygon: + { + QgsMultiPolygon mpolygon = g->asMultiPolygon(); + for (part = 0; part < mpolygon.count(); part++) // go through the polygons + { + const QgsPolygon& polygon = mpolygon[part]; + for (int ring = 0; ring < polygon.count(); ring++) // go through the rings + { + if (beforeVertexNr < polygon[ring].count()) + return part; + + beforeVertexNr -= polygon[ring].count(); + } + } + return -1; // not found + } + + default: + return -1; + } +} + + +void QgsMapToolDeletePart::deactivate() +{ + delete mCross; + mCross = 0; + + QgsMapTool::deactivate(); +} + diff --git a/src/app/qgsmaptooldeletepart.h b/src/app/qgsmaptooldeletepart.h new file mode 100644 index 00000000000..7035a6d592c --- /dev/null +++ b/src/app/qgsmaptooldeletepart.h @@ -0,0 +1,50 @@ +/*************************************************************************** + qgsmaptooldeletepart.h - delete a part from multipart geometry + --------------------- + begin : April 2009 + copyright : (C) 2009 by Richard Kostecky + email : csf dot kostej at mail 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. * + * * + ***************************************************************************/ + +#ifndef QGSMAPTOOLDELETEPART_H +#define QGSMAPTOOLDELETEPART_H + +#include "qgsmaptoolvertexedit.h" + +class QgsVertexMarker; + +/**Map tool to delete vertices from line/polygon features*/ +class QgsMapToolDeletePart: public QgsMapToolVertexEdit +{ + public: + QgsMapToolDeletePart( QgsMapCanvas* canvas ); + virtual ~QgsMapToolDeletePart(); + + void canvasMoveEvent( QMouseEvent * e ); + + void canvasPressEvent( QMouseEvent * e ); + + void canvasReleaseEvent( QMouseEvent * e ); + + //! called when map tool is being deactivated + void deactivate(); + + private: + QgsVertexMarker* mCross; + + //! delete part of a geometry + void deletePart( int fId, int beforeVertexNr, QgsVectorLayer* vlayer); + + //! find out part number of geometry given the snapped vertex number + int partNumberOfVertex( QgsGeometry* g, int beforeVertexNr ); + +}; + +#endif diff --git a/src/app/qgsmaptoolsimplify.cpp b/src/app/qgsmaptoolsimplify.cpp new file mode 100644 index 00000000000..ed20be04767 --- /dev/null +++ b/src/app/qgsmaptoolsimplify.cpp @@ -0,0 +1,381 @@ +/*************************************************************************** + qgsmaptoolsimplify.cpp - simplify vector layer features + --------------------- + begin : April 2009 + copyright : (C) 2009 by Richard Kostecky + email : csf dot kostej at mail 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 "qgsmaptoolsimplify.h" + +#include "qgsgeometry.h" +#include "qgsmapcanvas.h" +#include "qgsrubberband.h" +#include "qgsvectorlayer.h" +#include "qgstolerance.h" + +#include + +#include + +QgsSimplifyDialog::QgsSimplifyDialog(QWidget* parent) + : QDialog( parent ) +{ + setupUi(this); + connect( horizontalSlider, SIGNAL( valueChanged( int ) ), + this, SLOT( valueChanged( int ) ) ); + connect( okButton, SIGNAL(clicked()), + this, SLOT(simplify())); + +} + +void QgsSimplifyDialog::valueChanged(int value) +{ + emit toleranceChanged(value); +} + +void QgsSimplifyDialog::simplify() +{ + emit storeSimplified(); +} + +void QgsSimplifyDialog::setRange(int minValue, int maxValue) +{ + horizontalSlider->setMinimum(minValue); + horizontalSlider->setMaximum(maxValue); + + // let's have 20 page steps + horizontalSlider->setPageStep( (maxValue - minValue) / 20 ); +} + + +QgsMapToolSimplify::QgsMapToolSimplify( QgsMapCanvas* canvas ) + : QgsMapToolEdit( canvas), mRubberBand( 0 ) +{ + mSimplifyDialog = new QgsSimplifyDialog( canvas->topLevelWidget() ); + connect( mSimplifyDialog, SIGNAL( toleranceChanged( int ) ), + this, SLOT( toleranceChanged( int ) ) ); + connect( mSimplifyDialog, SIGNAL( storeSimplified() ), + this, SLOT(storeSimplified())); + connect( mSimplifyDialog, SIGNAL(finished(int)), + this, SLOT(removeRubberBand()) ); +} + +QgsMapToolSimplify::~QgsMapToolSimplify() +{ + removeRubberBand(); + delete mSimplifyDialog; +} + + +void QgsMapToolSimplify::toleranceChanged(int tolerance) +{ + mTolerance = double(tolerance)/toleranceDivider; + + // create a copy of selected feature and do the simplification + QgsFeature f = mSelectedFeature; + QgsSimplifyFeature::simplifyLine(f, mTolerance); + mRubberBand->setToGeometry(f.geometry(), false); +} + + +void QgsMapToolSimplify::storeSimplified() +{ + QgsVectorLayer * vlayer = currentVectorLayer(); + QgsSimplifyFeature::simplifyLine(mSelectedFeature, mTolerance); + // TODO(md): change geometry of feature instead of delete+add + vlayer->deleteFeature( mSelectedFeature.id() ); + vlayer->addFeature(mSelectedFeature); + + mCanvas->refresh(); +} + +int QgsMapToolSimplify::calculateDivider(double num) +{ + double tmp = num; + int i = 1; + while (tmp < 1) + { + tmp = tmp*10; + i = i *10; + } + return i; +} + +void QgsMapToolSimplify::calculateSliderBoudaries() +{ + double minTolerance, maxTolerance; + + double tol = 0.0000001; + bool found = false; + bool isLine = mSelectedFeature.geometry()->type() == QGis::Line; + QVector pts = getPointList(mSelectedFeature); + int size = pts.size(); + if (size == 0 || (isLine && size < 2) || (!isLine && size < 3) ) + { + return; + } + + // calculate min + while (!found) + { + if (QgsSimplifyFeature::simplifyPoints(pts, tol).size() < size) + { + found = true; + minTolerance = tol/2; + } else { + tol = tol * 2; + } + } + + found = false; + int requiredCnt = (isLine ? 2 : 3); + // calculate max + while (!found) + { + if (QgsSimplifyFeature::simplifyPoints(pts, tol).size() < requiredCnt + 1) + { +//TODO: fix for polygon + found = true; + maxTolerance = tol; + } else { + tol = tol * 2; + } + } + toleranceDivider = calculateDivider(minTolerance); + // set min and max + mSimplifyDialog->setRange( int(minTolerance * toleranceDivider), + int(maxTolerance * toleranceDivider) ); +} + + +void QgsMapToolSimplify::canvasPressEvent( QMouseEvent * e ) +{ + QgsVectorLayer * vlayer = currentVectorLayer(); + QgsPoint layerCoords = mCanvas->getCoordinateTransform()->toMapPoint( e->pos().x(), e->pos().y() ); + + double r = QgsTolerance::vertexSearchRadius(vlayer, mCanvas->mapRenderer()); + QgsRectangle selectRect = QgsRectangle( layerCoords.x() - r, layerCoords.y() - r, + layerCoords.x() + r, layerCoords.y() + r); + vlayer->select( QgsAttributeList(), selectRect, true ); + + QgsGeometry* geometry = QgsGeometry::fromPoint( layerCoords ); + double minDistance = 10000000; + double currentDistance; + QgsFeature f; + + mSelectedFeature.setValid(FALSE); + + while (vlayer->nextFeature(f)) + { + currentDistance = geometry->distance( *(f.geometry()) ); + if ( currentDistance < minDistance ) + { + minDistance = currentDistance; + mSelectedFeature = f; + } + } + + // delete previous rubberband (if any) + removeRubberBand(); + + if (mSelectedFeature.isValid()) + { + mRubberBand = new QgsRubberBand(mCanvas); + mRubberBand->setToGeometry(mSelectedFeature.geometry(), false); + mRubberBand->setColor(Qt::red); + mRubberBand->setWidth(2); + mRubberBand->show(); + //calculate boudaries for slidebar + calculateSliderBoudaries(); + + // show dialog as a non-modal window + mSimplifyDialog->show(); + } +} + +void QgsMapToolSimplify::removeRubberBand() +{ + delete mRubberBand; + mRubberBand = 0; +} + +void QgsMapToolSimplify::deactivate() +{ + if (mSimplifyDialog->isVisible()) + mSimplifyDialog->close(); + removeRubberBand(); + QgsMapTool::deactivate(); +} + + +QVector QgsMapToolSimplify::getPointList(QgsFeature& f) +{ + QgsGeometry* line = f.geometry(); + if ((line->type() != QGis::Line && line->type() != QGis::Polygon ) || line->isMultipart()) + { + return QVector(); + } + if ((line->type() == QGis::Line)) + { + return line->asPolyline(); + } + else + { + if (line->asPolygon().size() > 1) + { + return QVector(); + } + return line->asPolygon()[0]; + } + +} + + + + + +bool QgsSimplifyFeature::simplifyLine(QgsFeature& lineFeature, double tolerance) +{ + QgsGeometry* line = lineFeature.geometry(); + if (line->type() != QGis::Line) + { + return FALSE; + } + + QVector resultPoints = simplifyPoints(line->asPolyline(), tolerance); + lineFeature.setGeometry( QgsGeometry::fromPolyline( resultPoints ) ); + return TRUE; +} + + +//TODO: change to correct structure after +bool QgsSimplifyFeature::simplifyPartOfLine(QgsFeature& lineFeature, int fromVertexNr, int toVertexNr, double tolerance) +{ + QgsGeometry* line = lineFeature.geometry(); + if (line->type() != QGis::Line) + { + return FALSE; + } + + QVector resultPoints = simplifyPoints(line->asPolyline(), tolerance); + lineFeature.setGeometry( QgsGeometry::fromPolyline( resultPoints ) ); + return TRUE; +} + + + +QVector QgsSimplifyFeature::simplifyPoints (const QVector& pts, double tolerance) +{ + // Douglas-Peucker simplification algorithm + + int anchor = 0; + int floater = pts.size() - 1; + + QList stack; + StackEntry temporary; + StackEntry entry = {anchor, floater}; + stack.append(entry); + + QSet keep; + double anchorX; + double anchorY; + double seg_len; + double max_dist; + int farthest; + double dist_to_seg; + double vecX; + double vecY; + + while (!stack.empty()) + { + temporary = stack.takeLast(); + anchor = temporary.anchor; + floater = temporary.floater; + // initialize line segment + if (pts[floater] != pts[anchor]) + { + anchorX = pts[floater].x() - pts[anchor].x(); + anchorY = pts[floater].y() - pts[anchor].y(); + seg_len = sqrt(anchorX * anchorX + anchorY * anchorY); + // get the unit vector + anchorX /= seg_len; + anchorY /= seg_len; + } + else + { + anchorX = anchorY = seg_len = 0.0; + } + // inner loop: + max_dist = 0.0; + farthest = anchor + 1; + for (int i = anchor + 1; i < floater; i++) + { + dist_to_seg = 0.0; + // compare to anchor + vecX = pts[i].x() - pts[anchor].x(); + vecY = pts[i].y() - pts[anchor].y(); + seg_len = sqrt( vecX * vecX + vecY * vecY ); + // dot product: + double proj = vecX * anchorX + vecY * anchorY; + if (proj < 0.0) + { + dist_to_seg = seg_len; + } + else + { + // compare to floater + vecX = pts[i].x() - pts[floater].x(); + vecY = pts[i].y() - pts[floater].y(); + seg_len = sqrt( vecX * vecX + vecY *vecY ); + // dot product: + proj = vecX * (-anchorX) + vecY * (-anchorY); + if (proj < 0.0) + { + dist_to_seg = seg_len; + } + else + { // calculate perpendicular distance to line (pythagorean theorem): + dist_to_seg = sqrt(fabs(seg_len * seg_len - proj * proj)); + } + if (max_dist < dist_to_seg) + { + max_dist = dist_to_seg; + farthest = i; + } + } + } + if (max_dist <= tolerance) + { // # use line segment + keep.insert(anchor); + keep.insert(floater); + } + else + { + StackEntry s = {anchor, farthest}; + stack.append(s); + + StackEntry r = {farthest, floater}; + stack.append(r); + } + } + + QList keep2 = keep.toList(); + qSort(keep2); + + QVector result; + int position; + while (!keep2.empty()) + { + position = keep2.takeFirst(); + result.append(pts[position]); + } + return result; +} diff --git a/src/app/qgsmaptoolsimplify.h b/src/app/qgsmaptoolsimplify.h new file mode 100644 index 00000000000..d16d005c31e --- /dev/null +++ b/src/app/qgsmaptoolsimplify.h @@ -0,0 +1,111 @@ +/*************************************************************************** + qgsmaptoolsimplify.h - simplify vector layer features + --------------------- + begin : April 2009 + copyright : (C) 2009 by Richard Kostecky + email : csf dot kostej at mail 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. * + * * + ***************************************************************************/ + +#ifndef QGSMAPTOOLSIMPLIFY_H +#define QGSMAPTOOLSIMPLIFY_H + +#include "qgsmaptooledit.h" +#include "ui_qgssimplifytolerancedialog.h" + +#include +#include "qgsfeature.h" + +class QgsRubberBand; + + +class QgsSimplifyDialog : public QDialog, private Ui::SimplifyLineDialog +{ + Q_OBJECT + +public: + QgsSimplifyDialog( QWidget* parent = NULL ); + + void setRange(int minValue, int maxValue); + +signals: + void toleranceChanged( int tol ); + void storeSimplified(); + +private slots: + void valueChanged( int value ); + void simplify(); +}; + + +/**Map tool to add vertices to line/polygon features*/ +class QgsMapToolSimplify: public QgsMapToolEdit +{ + Q_OBJECT + +public: + QgsMapToolSimplify( QgsMapCanvas* canvas ); + virtual ~QgsMapToolSimplify(); + + void canvasPressEvent( QMouseEvent * e ); + + //! called when map tool is being deactivated + void deactivate(); + +public slots: + void removeRubberBand(); + +private: + + int calculateDivider(double num); + + void calculateSliderBoudaries(); + + QVector getPointList(QgsFeature& f); + + // data + + QgsSimplifyDialog* mSimplifyDialog; + + QgsRubberBand* mRubberBand; + + QgsFeature mSelectedFeature; + + int toleranceDivider; + + double mTolerance; + +private slots: + void toleranceChanged(int tolerance); + void storeSimplified(); + +}; + +/** + Implementation of Douglas-Peucker simplification algorithm. + */ +class QgsSimplifyFeature +{ + struct StackEntry { + int anchor; + int floater; + }; + +public: + /** simplify line feature with specified tolerance. Returns TRUE on success */ + static bool simplifyLine(QgsFeature &lineFeature, double tolerance); + /** simplify a part of line feature specified by range of vertices with given tolerance. Returns TRUE on success */ + static bool simplifyPartOfLine(QgsFeature &lineFeature, int fromVertexNr, int toVertexNr, double tolerance); + /** simplify a line given by a vector of points and tolerance. Returns simplified vector of points */ + static QVector simplifyPoints (const QVector& pts, double tolerance); + + +}; + +#endif diff --git a/src/core/qgsgeometry.cpp b/src/core/qgsgeometry.cpp index 74c4d382640..2d952c14469 100644 --- a/src/core/qgsgeometry.cpp +++ b/src/core/qgsgeometry.cpp @@ -5540,3 +5540,117 @@ QList QgsGeometry::asGeometryCollection() return geomCollection; } + + +bool QgsGeometry::deleteHole( int ringNum, int partNum ) +{ + if (ringNum <= 0 || partNum < 0) + return FALSE; + + switch ( wkbType() ) + { + case QGis::WKBPolygon25D: + case QGis::WKBPolygon: + { + if (partNum != 0) + return FALSE; + + QgsPolygon polygon = asPolygon(); + if ( ringNum >= polygon.count() ) + return FALSE; + + polygon.remove( ringNum ); + + QgsGeometry* g2 = QgsGeometry::fromPolygon( polygon ); + *this = *g2; + delete g2; + return TRUE; + } + + case QGis::WKBMultiPolygon25D: + case QGis::WKBMultiPolygon: + { + QgsMultiPolygon mpolygon = asMultiPolygon(); + + if (partNum >= mpolygon.count()) + return FALSE; + + if ( ringNum >= mpolygon[partNum].count() ) + return FALSE; + + mpolygon[partNum].remove( ringNum ); + + QgsGeometry* g2 = QgsGeometry::fromMultiPolygon( mpolygon ); + *this = *g2; + delete g2; + return TRUE; + } + + default: + return FALSE; // only makes sense with polygons and multipolygons + } +} + + +bool QgsGeometry::deletePart( int partNum ) +{ + if (partNum < 0) + return FALSE; + + switch ( wkbType() ) + { + case QGis::WKBMultiPoint25D: + case QGis::WKBMultiPoint: + { + QgsMultiPoint mpoint = asMultiPoint(); + + if (partNum >= mpoint.size() || mpoint.size() == 1) + return FALSE; + + mpoint.remove( partNum ); + + QgsGeometry* g2 = QgsGeometry::fromMultiPoint( mpoint ); + *this = *g2; + delete g2; + break; + } + + case QGis::WKBMultiLineString25D: + case QGis::WKBMultiLineString: + { + QgsMultiPolyline mline = asMultiPolyline(); + + if (partNum >= mline.size() || mline.size() == 1) + return FALSE; + + mline.remove( partNum ); + + QgsGeometry* g2 = QgsGeometry::fromMultiPolyline( mline ); + *this = *g2; + delete g2; + break; + } + + case QGis::WKBMultiPolygon25D: + case QGis::WKBMultiPolygon: + { + QgsMultiPolygon mpolygon = asMultiPolygon(); + + if (partNum >= mpolygon.size() || mpolygon.size() == 1) + return FALSE; + + mpolygon.remove( partNum ); + + QgsGeometry* g2 = QgsGeometry::fromMultiPolygon( mpolygon ); + *this = *g2; + delete g2; + break; + } + + default: + // single part geometries are ignored + return FALSE; + } + + return TRUE; +} diff --git a/src/core/qgsgeometry.h b/src/core/qgsgeometry.h index 526d8315c95..82ca278a6db 100644 --- a/src/core/qgsgeometry.h +++ b/src/core/qgsgeometry.h @@ -332,6 +332,18 @@ class CORE_EXPORT QgsGeometry @note added in version 1.1 */ QList asGeometryCollection(); + /** delete a hole in polygon or multipolygon. + Ring 0 is outer ring and can't be deleted. + @return TRUE on success + @note added in version 1.2 */ + bool deleteHole( int ringNum, int partNum = 0 ); + + /** delete part identified by the part number + @return TRUE on success + @note added in version 1.2 */ + bool deletePart( int partNum ); + + private: // Private variables diff --git a/src/ui/qgssimplifytolerancedialog.ui b/src/ui/qgssimplifytolerancedialog.ui new file mode 100644 index 00000000000..7f39f0307c0 --- /dev/null +++ b/src/ui/qgssimplifytolerancedialog.ui @@ -0,0 +1,59 @@ + + + SimplifyLineDialog + + + + 0 + 0 + 431 + 54 + + + + Simplify line tolerance + + + + + + Set tolerance + + + + + + + Qt::Horizontal + + + + + + + OK + + + + + + + + + okButton + clicked() + SimplifyLineDialog + accept() + + + 390 + 24 + + + 236 + 30 + + + + +