diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 02cc5816ceff..400d11e29257 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -16,6 +16,7 @@ ADD_SUBDIRECTORY(roadgraph) ADD_SUBDIRECTORY(zonal_statistics) ADD_SUBDIRECTORY(georeferencer) ADD_SUBDIRECTORY(gps_importer) +ADD_SUBDIRECTORY(topology) IF (WITH_SPATIALITE) ADD_SUBDIRECTORY(offline_editing) diff --git a/src/plugins/topology/CMakeLists.txt b/src/plugins/topology/CMakeLists.txt new file mode 100644 index 000000000000..b48db8d46c6b --- /dev/null +++ b/src/plugins/topology/CMakeLists.txt @@ -0,0 +1,62 @@ + +######################################################## +# Files + +SET (topol_SRCS + topol.cpp + rulesDialog.cpp + checkDock.cpp + topolError.cpp + topolTest.cpp + dockModel.cpp +) + +SET (topol_UIS + rulesDialog.ui + checkDock.ui +) + +SET (topol_MOC_HDRS + topol.h + rulesDialog.h + checkDock.h + topolTest.h + dockModel.h + geosFunctions.h +) + +SET (topol_RCCS topol.qrc) + +######################################################## +# Build + +QT4_WRAP_UI (topol_UIS_H ${topol_UIS}) + +QT4_WRAP_CPP (topol_MOC_SRCS ${topol_MOC_HDRS}) + +QT4_ADD_RESOURCES(topol_RCC_SRCS ${topol_RCCS}) + +ADD_LIBRARY (topolplugin MODULE ${topol_SRCS} ${topol_MOC_SRCS} ${topol_RCC_SRCS} ${topol_UIS_H}) + +INCLUDE_DIRECTORIES( + ${CMAKE_BINARY_DIR}/src/ui + ${CMAKE_CURRENT_BINARY_DIR} + ${GEOS_INCLUDE_DIR} + ../../core ../../core/raster ../../core/renderer ../../core/symbology + ../../gui + .. +) + +TARGET_LINK_LIBRARIES(topolplugin + qgis_core + qgis_gui +) + + +######################################################## +# Install + +INSTALL(TARGETS topolplugin + RUNTIME DESTINATION ${QGIS_PLUGIN_DIR} + LIBRARY DESTINATION ${QGIS_PLUGIN_DIR}) + diff --git a/src/plugins/topology/checkDock.cpp b/src/plugins/topology/checkDock.cpp new file mode 100644 index 000000000000..51b47ced9c99 --- /dev/null +++ b/src/plugins/topology/checkDock.cpp @@ -0,0 +1,458 @@ +/*************************************************************************** + checkDock.cpp + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 + +#include "checkDock.h" + +#include +#include +#include +//#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include "../../app/qgisapp.h" + +#include "topolTest.h" +#include "rulesDialog.h" +#include "dockModel.h" +//#include "geosFunctions.h" + +//class QgisInterface; + +checkDock::checkDock( QgisInterface* qIface, QWidget* parent ) + : QDockWidget( parent ), Ui::checkDock() +{ + mTest = new topolTest( qIface ); + setupUi( this ); + + // hide the fix-related stuff, needs more work + qgsInterface = qIface; + mFixButton->hide(); + mFixBox->hide(); + + mErrorListModel = new DockModel( mErrorList, parent ); + mErrorTableView->setModel( mErrorListModel ); + mErrorTableView->setSelectionBehavior( QAbstractItemView::SelectRows ); + mErrorTableView->verticalHeader()->setDefaultSectionSize( 20 ); + + mLayerRegistry = QgsMapLayerRegistry::instance(); + mConfigureDialog = new rulesDialog( mLayerRegistry->mapLayers().keys(), mTest->testMap(), qIface, parent ); + mTestTable = mConfigureDialog->testTable(); + + mValidateExtentButton->setIcon( QIcon( ":/topology/validateExtent.png" ) ); + mValidateAllButton->setIcon( QIcon( ":/topology/validateAll.png" ) ); + mConfigureButton->setIcon( QIcon( ":/topology/configureRules.png" ) ); + + +// mQgisApp = QgisApp::instance(); + QgsMapCanvas* canvas = qIface->mapCanvas();// mQgisApp->mapCanvas(); + mRBFeature1 = new QgsRubberBand( canvas ); + mRBFeature2 = new QgsRubberBand( canvas ); + mRBConflict = new QgsRubberBand( canvas ); + + mRBFeature1->setColor( "blue" ); + mRBFeature2->setColor( "green" ); + mRBConflict->setColor( "red" ); + + mRBFeature1->setWidth( 5 ); + mRBFeature2->setWidth( 5 ); + mRBConflict->setWidth( 5 ); + + mVMConflict = 0; + mVMFeature1 = 0; + mVMFeature2 = 0; + + connect( mConfigureButton, SIGNAL( clicked() ), this, SLOT( configure() ) ); + connect( mValidateAllButton, SIGNAL( clicked() ), this, SLOT( validateAll() ) ); + //connect( mValidateSelectedButton, SIGNAL( clicked() ), this, SLOT( validateSelected() ) ); + connect( mValidateExtentButton, SIGNAL( clicked() ), this, SLOT( validateExtent() ) ); + connect( mToggleRubberbands, SIGNAL( clicked() ), this, SLOT( toggleErrorMarkers() ) ); + + connect( mFixButton, SIGNAL( clicked() ), this, SLOT( fix() ) ); + connect( mErrorTableView, SIGNAL( clicked( const QModelIndex & ) ), this, SLOT( errorListClicked( const QModelIndex & ) ) ); + + connect( mLayerRegistry, SIGNAL( layerWasAdded( QgsMapLayer* ) ), mConfigureDialog, SLOT( addLayer( QgsMapLayer* ) ) ); + connect( mLayerRegistry, SIGNAL( layerWillBeRemoved( QString ) ), mConfigureDialog, SLOT( removeLayer( QString ) ) ); + connect( mLayerRegistry, SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( parseErrorListByLayer( QString ) ) ); + + connect( this, SIGNAL( visibilityChanged( bool ) ), this, SLOT( updateRubberBands( bool ) ) ); +} + +checkDock::~checkDock() +{ + delete mRBConflict, mRBFeature1, mRBFeature2; + delete mConfigureDialog; + delete mErrorListModel; + + QList::const_iterator it; + for ( it = mRbErrorMarkers.begin(); it != mRbErrorMarkers.end(); ++it ) + { + QgsRubberBand* rb = *it; + rb->reset(); + delete rb; + } + + clearVertexMarkers(); + + // delete errors in list + deleteErrors(); +} + +void checkDock::clearVertexMarkers() +{ + if ( mVMConflict ) + { + delete mVMConflict; + mVMConflict = 0; + } + if ( mVMFeature1 ) + { + delete mVMFeature1; + mVMFeature1 = 0; + } + if ( mVMFeature2 ) + { + delete mVMFeature2; + mVMFeature2 = 0; + } +} + +void checkDock::updateRubberBands( bool visible ) +{ + if ( !visible ) + { + mRBConflict->reset(); + mRBFeature1->reset(); + mRBFeature2->reset(); + + clearVertexMarkers(); + } +} + +void checkDock::deleteErrors() +{ + QList::Iterator it = mErrorList.begin(); + for ( ; it != mErrorList.end(); ++it ) + delete *it; + + mErrorList.clear(); + mErrorListModel->resetModel(); +} + +void checkDock::parseErrorListByLayer( QString layerId ) +{ + QgsVectorLayer* layer = ( QgsVectorLayer* )mLayerRegistry->mapLayers()[layerId]; + QList::Iterator it = mErrorList.begin(); + QList::Iterator end = mErrorList.end(); + + while ( it != mErrorList.end() ) + { + FeatureLayer fl1 = ( *it )->featurePairs().first(); + FeatureLayer fl2 = ( *it )->featurePairs()[1]; + if ( fl1.layer == layer || fl2.layer == layer ) + { + it = mErrorList.erase( it ); + } + else + ++it; + } + + mErrorListModel->resetModel(); + mComment->setText( QString( "No errors were found" ) ); +} + +void checkDock::parseErrorListByFeature( int featureId ) +{ + QList::Iterator it = mErrorList.begin(); + QList::Iterator end = mErrorList.end(); + + while ( it != mErrorList.end() ) + { + FeatureLayer fl1 = ( *it )->featurePairs().first(); + FeatureLayer fl2 = ( *it )->featurePairs()[1]; + if ( fl1.feature.id() == featureId || fl2.feature.id() == featureId ) + { + it = mErrorList.erase( it ); + } + else + ++it; + } + + mComment->setText( QString( "No errors were found" ) ); + mErrorListModel->resetModel(); +} + +void checkDock::configure() +{ + mConfigureDialog->show(); +} + +void checkDock::errorListClicked( const QModelIndex& index ) +{ + int row = index.row(); + QgsRectangle r = mErrorList[row]->boundingBox(); + r.scale( 1.5 ); + QgsMapCanvas* canvas = qgsInterface->mapCanvas(); + canvas->setExtent( r ); + canvas->refresh(); + + mFixBox->clear(); + mFixBox->addItems( mErrorList[row]->fixNames() ); + mFixBox->setCurrentIndex( mFixBox->findText( "Select automatic fix" ) ); + + QgsFeature f; + QgsGeometry* g; + FeatureLayer fl = mErrorList[row]->featurePairs().first(); + if ( !fl.layer ) + { + std::cout << "invalid layer 1\n"; + return; + } + + //fl1.layer->getFeatures( QgsFeatureRequest().setFilterFid( fl1.feature.id() ) ).nextFeature( f1 ); + + fl.layer->getFeatures( QgsFeatureRequest().setFilterFid( fl.feature.id() ) ).nextFeature( f ); + g = f.geometry(); + if ( !g ) + { + std::cout << "invalid geometry 1\n" << std::flush; + QMessageBox::information( this, "Topology test", "Feature not found in the layer.\nThe layer has probably changed.\nRun topology check again." ); + return; + } + + clearVertexMarkers(); + + // use vertex marker when highlighting a point + // and rubber band otherwise + if ( g->type() == QGis::Point ) + { + mVMFeature1 = new QgsVertexMarker( canvas ); + mVMFeature1->setIconType( QgsVertexMarker::ICON_X ); + mVMFeature1->setPenWidth( 5 ); + mVMFeature1->setIconSize( 5 ); + mVMFeature1->setColor( "blue" ); + mVMFeature1->setCenter( g->asPoint() ); + } + else + mRBFeature1->setToGeometry( g, fl.layer ); + + fl = mErrorList[row]->featurePairs()[1]; + if ( !fl.layer ) + { + std::cout << "invalid layer 2\n"; + return; + } + + + fl.layer->getFeatures( QgsFeatureRequest().setFilterFid( fl.feature.id() ) ).nextFeature( f ); + g = f.geometry(); + if ( !g ) + { + std::cout << "invalid geometry 2\n" << std::flush; + QMessageBox::information( this, "Topology test", "Feature not found in the layer.\nThe layer has probably changed.\nRun topology check again." ); + return; + } + + if ( g->type() == QGis::Point ) + { + mVMFeature2 = new QgsVertexMarker( canvas ); + mVMFeature2->setIconType( QgsVertexMarker::ICON_BOX ); + mVMFeature2->setPenWidth( 5 ); + mVMFeature2->setIconSize( 5 ); + mVMFeature2->setColor( "green" ); + mVMFeature2->setCenter( g->asPoint() ); + } + else + mRBFeature2->setToGeometry( g, fl.layer ); + + if ( !mErrorList[row]->conflict() ) + { + std::cout << "invalid conflict\n" << std::flush; + return; + } + + if ( mErrorList[row]->conflict()->type() == QGis::Point ) + { + mVMConflict = new QgsVertexMarker( canvas ); + mVMConflict->setIconType( QgsVertexMarker::ICON_BOX ); + mVMConflict->setPenWidth( 5 ); + mVMConflict->setIconSize( 5 ); + mVMConflict->setColor( "red" ); + mVMConflict->setCenter( mErrorList[row]->conflict()->asPoint() ); + } + else + mRBConflict->setToGeometry( mErrorList[row]->conflict(), fl.layer ); +} + +void checkDock::fix() +{ + int row = mErrorTableView->currentIndex().row(); + QString fixName = mFixBox->currentText(); + + if ( row == -1 ) + return; + + mRBFeature1->reset(); + mRBFeature2->reset(); + mRBConflict->reset(); + + clearVertexMarkers(); + + if ( mErrorList[row]->fix( fixName ) ) + { + mErrorList.removeAt( row ); + mErrorListModel->resetModel(); + //parseErrorListByFeature(); + mComment->setText( QString( "%1 errors were found" ).arg( mErrorList.count() ) ); + qgsInterface->mapCanvas()->refresh(); + } + else + QMessageBox::information( this, "Topology fix error", "Fixing failed!" ); +} + +void checkDock::runTests( ValidateType type ) +{ + for ( int i = 0; i < mTestTable->rowCount(); ++i ) + { + QString testName = mTestTable->item( i, 0 )->text(); + QString toleranceStr = mTestTable->item( i, 3 )->text(); + QString layer1Str = mTestTable->item( i, 4 )->text(); + QString layer2Str = mTestTable->item( i, 5 )->text(); + + // test if layer1 is in the registry + if ( !(( QgsVectorLayer* )mLayerRegistry->mapLayers().contains( layer1Str ) ) ) + { + std::cout << "CheckDock: layer " << layer1Str.toStdString() << " not found in registry!" << std::flush; + return; + } + + QgsVectorLayer* layer1 = ( QgsVectorLayer* )mLayerRegistry->mapLayers()[layer1Str]; + QgsVectorLayer* layer2 = 0; + + if (( QgsVectorLayer* )mLayerRegistry->mapLayers().contains( layer2Str ) ) + layer2 = ( QgsVectorLayer* )mLayerRegistry->mapLayers()[layer2Str]; + + QProgressDialog progress( testName, "Abort", 0, layer1->featureCount(), this ); + progress.setWindowModality( Qt::WindowModal ); + + connect( &progress, SIGNAL( canceled() ), mTest, SLOT( setTestCancelled() ) ); + connect( mTest, SIGNAL( progress( int ) ), &progress, SLOT( setValue( int ) ) ); + // run the test + + ErrorList errors = mTest->runTest( testName, layer1, layer2, type, toleranceStr.toDouble() ); + + QList::Iterator it; + + QgsRubberBand* rb = 0; + for ( it = errors.begin(); it != errors.end(); ++it ) + { + TopolError* te = *it; + te->conflict(); + + QSettings settings; + if ( te->conflict()->type() == QGis::Polygon ) + { + rb = new QgsRubberBand( qgsInterface->mapCanvas(), true ); + } + else + { + rb = new QgsRubberBand( qgsInterface->mapCanvas(), te->conflict()->type() ); + } + rb->setColor( "red" ); + rb->setWidth( 4 ); + rb->setToGeometry( te->conflict(), layer1 ); + rb->show(); + mRbErrorMarkers << rb; + } + disconnect( &progress, SIGNAL( canceled() ), mTest, SLOT( setTestCancelled() ) ); + disconnect( mTest, SIGNAL( progress( int ) ), &progress, SLOT( setValue( int ) ) ); + mErrorList << errors; + } + mMarkersVisible = true; + mErrorListModel->resetModel(); +} + +void checkDock::validate( ValidateType type ) +{ + mErrorList.clear(); + + QList::const_iterator it; + for ( it = mRbErrorMarkers.begin(); it != mRbErrorMarkers.end(); ++it ) + { + QgsRubberBand* rb = *it; + rb->reset(); + delete rb; + } + + mRbErrorMarkers.clear(); + + runTests( type ); + mComment->setText( QString( "%1 errors were found" ).arg( mErrorList.count() ) ); + + mRBFeature1->reset(); + mRBFeature2->reset(); + mRBConflict->reset(); + clearVertexMarkers(); + + mErrorTableView->resizeColumnsToContents(); +} + +void checkDock::validateExtent() +{ + validate( ValidateExtent ); +} + +void checkDock::validateAll() +{ + validate( ValidateAll ); +} + +void checkDock::validateSelected() +{ + validate( ValidateSelected ); +} + +void checkDock::toggleErrorMarkers() +{ + QList::const_iterator it; + for ( it = mRbErrorMarkers.begin(); it != mRbErrorMarkers.end(); ++it ) + { + QgsRubberBand* rb = *it; + if ( mMarkersVisible == true ) + { + rb->hide(); + } + else + { + rb->show(); + } + } + mMarkersVisible = !mMarkersVisible; + +} diff --git a/src/plugins/topology/checkDock.h b/src/plugins/topology/checkDock.h new file mode 100644 index 000000000000..e70c88fa1893 --- /dev/null +++ b/src/plugins/topology/checkDock.h @@ -0,0 +1,146 @@ +/*************************************************************************** + checkDock.h + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 CHECKDOCK_H +#define CHECKDOCK_H + +#include + +#include +#include +//#include +#include "qgsspatialindex.h" +//#include + +#include "ui_checkDock.h" +#include "rulesDialog.h" +#include "topolError.h" +#include "topolTest.h" +#include "dockModel.h" + +class QgsMapLayerRegistry; +class QgsRubberBand; +class QgsVertexMarker; +class QgisApp; +class QgisInterface; +class checkDock; + +class checkDock : public QDockWidget, public Ui::checkDock +{ + Q_OBJECT + + public: + /** + * Constructor + * @param qIface pointer to QgisInterface instance that is passed to the rulesDialog + * @param parent parent object + */ + checkDock( QgisInterface* qIface, QWidget *parent = 0 ); + ~checkDock(); + + private slots: + /** + * Launches the configuration dialog + */ + void configure(); + /** + * Launches fixing routine + */ + void fix(); + /** + * Validates the whole layer + */ + void validateAll(); + /** + * Validates the current extent + */ + void validateExtent(); + /** + * Validates only selected features + */ + void validateSelected(); + /** + * toggles the visibility of rubber band error markers + */ + void toggleErrorMarkers(); + /** + * Handles error selection + * @param index clicked index in the table + */ + void errorListClicked( const QModelIndex& index ); + /** + * Deletes allocated errors' data + */ + void deleteErrors(); + /** + * Filters all errors involving features from specified layer + * @param layerId layer ID + */ + void parseErrorListByLayer( QString layerId ); + /** + * Clears rubberbands when window is hidden + * @param visible true if the window is visible + */ + void updateRubberBands( bool visible ); + + + private: + rulesDialog* mConfigureDialog; + QgisApp* mQgisApp; + + QgsRubberBand* mRBConflict; + QgsRubberBand* mRBFeature1; + QgsRubberBand* mRBFeature2; + QgsVertexMarker* mVMConflict; + QgsVertexMarker* mVMFeature1; + QgsVertexMarker* mVMFeature2; + QList mRbErrorMarkers; + bool mMarkersVisible; + + ErrorList mErrorList; + DockModel* mErrorListModel; + + QgisInterface* qgsInterface; + + //pointer to topology tests table + QTableWidget* mTestTable; + + topolTest* mTest; + QgsMapLayerRegistry* mLayerRegistry; + + /** + * Runs tests from the test table + * @param type validation type - what features to check + */ + void runTests( ValidateType type ); + /** + * Validates topology + * @param type validation type - what features to check + */ + void validate( ValidateType type ); + /** + * Filters all errors involving specified feature + * @param featureId feature ID + */ + void parseErrorListByFeature( int featureId ); + /** + * Deletes vertex markers + */ + void clearVertexMarkers(); +}; + +#endif diff --git a/src/plugins/topology/checkDock.ui b/src/plugins/topology/checkDock.ui new file mode 100644 index 000000000000..fc73dde993f8 --- /dev/null +++ b/src/plugins/topology/checkDock.ui @@ -0,0 +1,89 @@ + + + checkDock + + + + 0 + 0 + 436 + 439 + + + + Topology Checker + + + + + + + + + + + Validate All + + + + + + + Validate Extent + + + + + + + + + Toggle Error Markers + + + + + + + + + + Topology not checked yet + + + + + + + + + Configure + + + + + + + + Select automatic fix + + + + + + + + Fix! + + + + + + + + + + + + + diff --git a/src/plugins/topology/configureRules.png b/src/plugins/topology/configureRules.png new file mode 100644 index 000000000000..483559183097 Binary files /dev/null and b/src/plugins/topology/configureRules.png differ diff --git a/src/plugins/topology/dockModel.cpp b/src/plugins/topology/dockModel.cpp new file mode 100644 index 000000000000..4ebac6f83eb7 --- /dev/null +++ b/src/plugins/topology/dockModel.cpp @@ -0,0 +1,125 @@ +/*************************************************************************** + dockModel.cpp + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 "dockModel.h" +#include "topolError.h" + +DockModel::DockModel( ErrorList& theErrorList, QObject *parent = 0 ) : mErrorlist( theErrorList ) +{ + mHeader << "Error" << "Layer" << "Feature ID"; + +} + +int DockModel::rowCount( const QModelIndex &parent ) const +{ + return mErrorlist.count(); +} + +int DockModel::columnCount( const QModelIndex &parent ) const +{ + return 3; +} + +QVariant DockModel::headerData( int section, Qt::Orientation orientation, int role ) const +{ + if ( role == Qt::DisplayRole ) + { + if ( orientation == Qt::Vertical ) //row + { + return QVariant( section ); + } + else + { + return mHeader[section]; + } + } + else return QVariant(); +} + +QVariant DockModel::data( const QModelIndex &index, int role ) const +{ + if ( !index.isValid() || ( role != Qt::TextAlignmentRole && role != Qt::DisplayRole && role != Qt::EditRole ) ) + return QVariant(); + + int row = index.row(); +// if(!row) +// { +// return QVariant(); +// } + int column = index.column(); + + if ( role == Qt::TextAlignmentRole ) + { + if ( column ) + return QVariant( Qt::AlignRight ); + else + return QVariant( Qt::AlignLeft ); + } + + QVariant val; + switch ( column ) + { + case 0: + val = mErrorlist[row]->name(); + break; + case 1: + if ( !mErrorlist[row]->featurePairs().first().layer ) + val = QString( "Unkown" ); + else + val = mErrorlist[row]->featurePairs().first().layer->name(); + break; + case 2: + val = mErrorlist[row]->featurePairs().first().feature.id(); + break; + default: + val = QVariant(); + } + + if ( val.isNull() ) + { + return QVariant(); + } + + // convert to QString from some other representation + // this prevents displaying greater numbers in exponential format + return val.toString(); +} + +bool DockModel::setData( const QModelIndex &index, const QVariant &value, int role ) +{ + return false; +} + +Qt::ItemFlags DockModel::flags( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return Qt::ItemIsEnabled; + + Qt::ItemFlags flags = QAbstractItemModel::flags( index ); + return flags; +} + +void DockModel::resetModel() +{ + reset(); +} + +void DockModel::reload( const QModelIndex &index1, const QModelIndex &index2 ) + +{ + emit dataChanged( index1, index2 ); +} diff --git a/src/plugins/topology/dockModel.h b/src/plugins/topology/dockModel.h new file mode 100644 index 000000000000..798bf7d20e2c --- /dev/null +++ b/src/plugins/topology/dockModel.h @@ -0,0 +1,91 @@ +/*************************************************************************** + dockModel.h + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 DOCKMODEL_H +#define DOCKMODEL_H + +#include +#include +#include + +#include "topolError.h" + +class DockModel: public QAbstractTableModel +{ +Q_OBJECT + +public: + /** + * Constructor + * @param theErrorList reference to the ErrorList where errors will be stored + * @param parent parent object + */ + DockModel(ErrorList& theErrorList, QObject *parent); + /** + * Returns header data + * @param section required section + * @param orientation horizontal or vertical orientation + * @param role data role + */ + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /** + * Returns data on the given index + * @param index model index + * @param role data role + */ + virtual QVariant data(const QModelIndex &index, int role) const; + /** + * Updates data on given index + * @param index model index + * @param value new data value + * @param role data role + */ + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + /** + * Returns item flags for the index + * @param index model index + */ + Qt::ItemFlags flags(const QModelIndex &index) const; + + /** + * Returns the number of rows + * @param parent parent index + */ + int rowCount(const QModelIndex &parent) const; + /** + * Returns the number of columns + * @param parent parent index + */ + int columnCount(const QModelIndex &parent) const; + + /** + * Reloads the model data between indices + * @param index1 start index + * @param index2 end index + */ + void reload(const QModelIndex &index1, const QModelIndex &index2); + /** + * Resets the model + */ + void resetModel(); + +private: + ErrorList& mErrorlist; + QList mHeader; +}; + +#endif diff --git a/src/plugins/topology/geosFunctions.h b/src/plugins/topology/geosFunctions.h new file mode 100644 index 000000000000..21d6506f3d85 --- /dev/null +++ b/src/plugins/topology/geosFunctions.h @@ -0,0 +1,123 @@ +#ifndef GEOSFUNCTIONS_H +#define GEOSFUNCTIONS_H + +#include +#include + +class GEOSException +{ + public: + GEOSException( const char *theMsg ) + { + if ( strcmp( theMsg, "Unknown exception thrown" ) == 0 && lastMsg ) + { + delete [] theMsg; + char *aMsg = new char[strlen( lastMsg )+1]; + strcpy( aMsg, lastMsg ); + msg = aMsg; + } + else + { + msg = theMsg; + lastMsg = msg; + } + } + + // copy constructor + GEOSException( const GEOSException &rhs ) + { + *this = rhs; + } + + ~GEOSException() + { + if ( lastMsg == msg ) + lastMsg = NULL; + delete [] msg; + } + + const char *what() + { + return msg; + } + + private: + const char *msg; + static const char *lastMsg; +}; + +/** + * Checks whether two geometries touch each other + * @param g1 first geometry + * @param g2 second geometry + */ +bool geosTouches(QgsGeometry* g1, QgsGeometry* g2) +{ + try + { + if (1 == GEOSTouches(g1->asGeos(), g2->asGeos())) + return true; + } + catch (GEOSException &e) + { + return false; + } +} + +/** + * Checks whether two geometries are identical in all respect(duplicates) + * @param g1 first geometry + * @param g2 second geometry + */ +bool geosEquals(QgsGeometry* g1, QgsGeometry* g2) +{ + try + { + if (1 == GEOSEquals(g1->asGeos(), g2->asGeos())) + return true; + } + catch (GEOSException &e) + { + return false; + } +} + + + +/** + * Checks whether two geometries overlap + * @param g1 first geometry + * @param g2 second geometry + */ +bool geosOverlaps(QgsGeometry* g1, QgsGeometry* g2) +{ + try + { + if (1 == GEOSOverlaps(g1->asGeos(), g2->asGeos())) + return true; + } + catch (GEOSException &e) + { + return false; + } +} + +/** + * Checks whether the first geometry contains the second geometry + * @param g1 first geometry + * @param g2 second geometry + */ +bool geosContains(QgsGeometry* g1, QgsGeometry* g2) +{ + try + { + if (1 == GEOSContains(g1->asGeos(), g2->asGeos())) + return true; + } + catch (GEOSException &e) + { + return false; + } +} + +#endif diff --git a/src/plugins/topology/rulesDialog.cpp b/src/plugins/topology/rulesDialog.cpp new file mode 100644 index 000000000000..e47ac2940472 --- /dev/null +++ b/src/plugins/topology/rulesDialog.cpp @@ -0,0 +1,274 @@ +/*************************************************************************** + rulesDialog.cpp + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 + +#include +#include +//#include +//#include +#include +#include + +#include +#include +#include +#include + +//#include "../../app/qgisapp.h" + +#include "rulesDialog.h" +#include "topolTest.h" + +rulesDialog::rulesDialog( QList layerList, QMap testMap, QgisInterface* theQgisIface, QWidget *parent ) + : QDialog( parent ), Ui::rulesDialog() +{ + setupUi( this ); + + mQgisIface = theQgisIface; + + //setHorizontalHeaderItems(); + mTestTable->hideColumn( 4 ); + mTestTable->hideColumn( 5 ); + + mTestConfMap = testMap; + mTestTable->setSelectionBehavior( QAbstractItemView::SelectRows ); + mTestBox->addItems( mTestConfMap.keys() ); + + QgsMapLayerRegistry* layerRegistry = QgsMapLayerRegistry::instance(); + + for ( int i = 0; i < layerList.size(); ++i ) + { + // add layer ID to the layerId list + mLayerIds << layerList[i]; + + //QgsVectorLayer* v1 = ( QgsVectorLayer* )layerRegistry->mapLayers()[layerList[i]]; + + // add layer name to the layer combo boxes + mLayer1Box->addItem((( QgsVectorLayer* )layerRegistry->mapLayers()[layerList[i]] )->name() ); + mLayer2Box->addItem((( QgsVectorLayer* )layerRegistry->mapLayers()[layerList[i]] )->name() ); + } + + connect( mAddTestButton, SIGNAL( clicked() ), this, SLOT( addTest() ) ); + connect( mAddTestButton, SIGNAL( clicked() ), mTestTable, SLOT( resizeColumnsToContents() ) ); + // attempt to add new test when Ok clicked + connect( buttonBox, SIGNAL( accepted() ), this, SLOT( addTest() ) ); + connect( mDeleteTestButton, SIGNAL( clicked() ), this, SLOT( deleteTest() ) ); + connect( mTestBox, SIGNAL( currentIndexChanged( const QString& ) ), this, SLOT( showControls( const QString& ) ) ); + mTestBox->setCurrentIndex( 4 ); + + //this resets this plugin up if a project is loaded + connect( mQgisIface->mainWindow(), SIGNAL( projectRead() ), this, SLOT( projectRead() ) ); + projectRead(); +} + +rulesDialog::~rulesDialog() +{ +} + +void rulesDialog::setHorizontalHeaderItems() +{ + QStringList labels; + labels << "Test" << "Layer #1" << "Layer #2" << "Tolerance" << "" << ""; + mTestTable->setHorizontalHeaderLabels( labels ); +} + +void rulesDialog::readTest( int index, QgsMapLayerRegistry* layerRegistry ) +{ + QString testName; + QString layer1Id; + QString layer2Id; + QString tolerance; + QgsProject* project = QgsProject::instance(); + QString postfix = QString( "%1" ).arg( index ); + + testName = project->readEntry( "Topol", "/testname_" + postfix, "" ); + tolerance = project->readEntry( "Topol", "/tolerance_" + postfix, "" ); + layer1Id = project->readEntry( "Topol", "/layer1_" + postfix, "" ); + layer2Id = project->readEntry( "Topol", "/layer2_" + postfix, "" ); + + QgsVectorLayer* l1; + if ( !( QgsVectorLayer* )layerRegistry->mapLayers().contains( layer1Id ) ) + return; + + l1 = ( QgsVectorLayer* )layerRegistry->mapLayers()[layer1Id]; + if ( !l1 ) + return; + + QString layer1Name = l1->name(); + QString layer2Name; + QgsVectorLayer* l2; + + if ( mTestConfMap[testName].useSecondLayer ) + { + if ( !( QgsVectorLayer* )layerRegistry->mapLayers().contains( layer2Id ) ) + return; + else + { + l2 = ( QgsVectorLayer* )layerRegistry->mapLayers()[layer2Id]; + layer2Name = l2->name(); + } + } + else + layer2Name = "No layer"; + + int row = index; + mTestTable->insertRow( row ); + + QTableWidgetItem* newItem; + newItem = new QTableWidgetItem( testName ); + newItem->setFlags( newItem->flags() & ~Qt::ItemIsEditable ); + mTestTable->setItem( row, 0, newItem ); + + newItem = new QTableWidgetItem( layer1Name ); + newItem->setFlags( newItem->flags() & ~Qt::ItemIsEditable ); + mTestTable->setItem( row, 1, newItem ); + + newItem = new QTableWidgetItem( layer2Name ); + newItem->setFlags( newItem->flags() & ~Qt::ItemIsEditable ); + mTestTable->setItem( row, 2, newItem ); + + if ( mTestConfMap[testName].useTolerance ) + newItem = new QTableWidgetItem( tolerance ); + else + newItem = new QTableWidgetItem( QString( "No tolerance" ) ); + + newItem->setFlags( newItem->flags() & ~Qt::ItemIsEditable ); + mTestTable->setItem( row, 3, newItem ); + + // add layer ids to hidden columns + newItem = new QTableWidgetItem( layer1Id ); + mTestTable->setItem( row, 4, newItem ); + newItem = new QTableWidgetItem( layer2Id ); + mTestTable->setItem( row, 5, newItem ); +} + +void rulesDialog::projectRead() +{ + QgsMapLayerRegistry* layerRegistry = QgsMapLayerRegistry::instance(); + int testCount = QgsProject::instance()->readNumEntry( "Topol", "/testCount" ); + mTestTable->clearContents(); + + for ( int i = 0; i < testCount; ++i ) + readTest( i, layerRegistry ); +} + +void rulesDialog::showControls( const QString& testName ) +{ + mLayer2Box->setVisible( mTestConfMap[testName].useSecondLayer ); + + bool useTolerance = mTestConfMap[testName].useTolerance; + mToleranceBox->setVisible( useTolerance ); + mToleranceLabel->setVisible( useTolerance ); +} + +void rulesDialog::addLayer( QgsMapLayer* layer ) +{ + mLayerIds << layer->id(); + + // add layer name to the layer combo boxes + mLayer1Box->addItem( layer->name() ); + mLayer2Box->addItem( layer->name() ); +} + +void rulesDialog::removeLayer( QString layerId ) +{ + int index = mLayerIds.indexOf( layerId ); + + mLayerIds.removeAt( index ); + // + 1 for "No layer" string + mLayer1Box->removeItem( index + 1 ); + mLayer2Box->removeItem( index + 1 ); + + // TODO: Maybe tell the dock that we have no layers under + //if (mLayer1Box->size() == 1) do something +} + +void rulesDialog::addTest() +{ + //sanity checks + QString test = mTestBox->currentText(); + QString layer1 = mLayer1Box->currentText(); + if ( layer1 == "No layer" ) + return; + + QString layer2 = mLayer2Box->currentText(); + if ( layer2 == "No layer" && mTestConfMap[test].useSecondLayer ) + return; + + int row = mTestTable->rowCount(); + mTestTable->insertRow( row ); + + QTableWidgetItem* newItem; + newItem = new QTableWidgetItem( test ); + mTestTable->setItem( row, 0, newItem ); + newItem = new QTableWidgetItem( layer1 ); + mTestTable->setItem( row, 1, newItem ); + + if ( mTestConfMap[test].useSecondLayer ) + newItem = new QTableWidgetItem( layer2 ); + else + newItem = new QTableWidgetItem( "No layer" ); + + mTestTable->setItem( row, 2, newItem ); + + if ( mTestConfMap[test].useTolerance ) + newItem = new QTableWidgetItem( QString( "%1" ).arg( mToleranceBox->value() ) ); + else + newItem = new QTableWidgetItem( QString( "No tolerance" ) ); + + mTestTable->setItem( row, 3, newItem ); + + QString layer1ID, layer2ID; + // add layer ids to hidden columns + // -1 for "No layer" string + if ( mTestConfMap[test].useSecondLayer ) + layer2ID = mLayerIds[mLayer2Box->currentIndex() - 1]; + else + layer2ID = "No layer"; + + layer1ID = mLayerIds[mLayer1Box->currentIndex() - 1]; + + //TODO: use setItemData (or something like that) instead of hidden columns + newItem = new QTableWidgetItem( layer1ID ); + mTestTable->setItem( row, 4, newItem ); + newItem = new QTableWidgetItem( layer2ID ); + mTestTable->setItem( row, 5, newItem ); + + // save state to the project file..... + QString postfix = QString( "%1" ).arg( row ); + QgsProject* project = QgsProject::instance(); + + project->writeEntry( "Topol", "/testCount", row + 1 ); + project->writeEntry( "Topol", "/testname_" + postfix, test ); + project->writeEntry( "Topol", "/tolerance_" + postfix, QString( "%1" ).arg( mToleranceBox->value() ) ); + project->writeEntry( "Topol", "/layer1_" + postfix, layer1ID ); + project->writeEntry( "Topol", "/layer2_" + postfix, layer2ID ); + + // reset controls to default + mTestBox->setCurrentIndex( 0 ); + mLayer1Box->setCurrentIndex( 0 ); + mLayer2Box->setCurrentIndex( 0 ); + mToleranceBox->setValue( 0 ); +} + +void rulesDialog::deleteTest() +{ + int row = mTestTable->currentRow(); + if ( 0 <= row && row < mTestTable->rowCount() ) + mTestTable->removeRow( row ); +} diff --git a/src/plugins/topology/rulesDialog.h b/src/plugins/topology/rulesDialog.h new file mode 100644 index 000000000000..d5c351b69072 --- /dev/null +++ b/src/plugins/topology/rulesDialog.h @@ -0,0 +1,100 @@ +/*************************************************************************** + rulesDialog.h + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 RULESDIALOG_H_ +#define RULESDIALOG_H_ + +#include + +#include + +#include "ui_rulesDialog.h" +#include "topolTest.h" + +class QgisInterface; +class QgsMapLayerRegistry; + +class rulesDialog : public QDialog, public Ui::rulesDialog +{ + Q_OBJECT + + public: + /* + * Constructor + * @param layerList List of layer IDs + * @param testMap maps test names to test routines + * @param theQgisIface pointer to a QgisInterface instance + * @param parent parent widget + */ + rulesDialog( QList layerList, QMap testMap, QgisInterface* theQgisIface, QWidget *parent ); + ~rulesDialog(); + /* + * Returns pointer to the test table + */ + QTableWidget* testTable() { return mTestTable; } + /* + * Returns pointer to the test combobox + */ + QComboBox* testBox() { return mTestBox; } + + private: + QMap mTestConfMap; + QList mLayerIds; + QgisInterface* mQgisIface; + + /* + * Reads a test from the project + * @param index test index + * @param layerRegistry pointer to a QgsMapLayerRegistry instance + */ + void readTest( int index, QgsMapLayerRegistry* layerRegistry ); + /* + * Sets the horizontal header for tet table + */ + void setHorizontalHeaderItems(); + + private slots: + /* + * Shows or hides controls according to test settings + * @param testName name of the test + */ + void showControls( const QString& testName ); + /* + * Adds test to the table + */ + void addTest(); + /* + * Deletes test from the table + */ + void deleteTest(); + /* + * Reads tests from the project + */ + void projectRead(); + /* + * Adds layer to layer comboboxes + * @param layer layer pointer + */ + void addLayer( QgsMapLayer* layer ); + /* + * Deletes layer to layer comboboxes + * @param layerId layer ID + */ + void removeLayer( QString layerId ); +}; + +#endif diff --git a/src/plugins/topology/rulesDialog.ui b/src/plugins/topology/rulesDialog.ui new file mode 100644 index 000000000000..1e2267f1e50d --- /dev/null +++ b/src/plugins/topology/rulesDialog.ui @@ -0,0 +1,198 @@ + + + rulesDialog + + + + 0 + 0 + 739 + 545 + + + + Test settings + + + + + + + + Current Rules + + + + + + + + Rule + + + + + Layer #1 + + + + + Layer #2 + + + + + Tolerance + + + + + Layer1ID + + + + + Layer2ID + + + + + + + + + + QComboBox::AdjustToContents + + + + + + + QComboBox::AdjustToContents + + + + No layer + + + + + + + + QComboBox::AdjustToContents + + + + No layer + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Tolerance + + + + + + + + + + + + + + Add New Rule + + + + + + + Delete Rule + + + + + + + Qt::Horizontal + + + + 198 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + buttonBox + accepted() + rulesDialog + accept() + + + 506 + 369 + + + 289 + 194 + + + + + buttonBox + rejected() + rulesDialog + reject() + + + 506 + 369 + + + 289 + 194 + + + + + diff --git a/src/plugins/topology/topol.cpp b/src/plugins/topology/topol.cpp new file mode 100644 index 000000000000..5dc7e9a2c662 --- /dev/null +++ b/src/plugins/topology/topol.cpp @@ -0,0 +1,178 @@ +/*************************************************************************** + topol.cpp + TOPOLogy checker + ------------------- + begin : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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. * + * * + ***************************************************************************/ + +// QGIS Specific includes +#include +#include +#include +#include + +// Qt4 Related Includes +#include +#include +#include +#include + +#include "topol.h" +#include "checkDock.h" + +static const char * const sIdent = "$Id: plugin.cpp 8053 2008-01-26 13:59:53Z timlinux $"; +static const QString sName = QObject::tr( "Topology Checker" ); +static const QString sDescription = QObject::tr( "A Plugin for finding topological errors in vector layers" ); +static const QString sCategory = QObject::tr( "Vector" ); +static const QString sPluginVersion = QObject::tr( "Version 0.1" ); +static const QgisPlugin::PLUGINTYPE sPluginType = QgisPlugin::UI; +static const QString sPluginIcon = ":/topology/topol.png"; + +////////////////////////////////////////////////////////////////////// +// +// THE FOLLOWING METHODS ARE MANDATORY FOR ALL PLUGINS +// +////////////////////////////////////////////////////////////////////// + +/** + * Constructor for the plugin. The plugin is passed a pointer + * an interface object that provides access to exposed functions in QGIS. + * @param theQGisInterface - Pointer to the QGIS interface object + */ +Topol::Topol( QgisInterface * theQgisInterface ): + QgisPlugin( sName, sDescription, sCategory, sPluginVersion, sPluginType ), + mQGisIface( theQgisInterface ) +{ + mDock = 0; +} + +Topol::~Topol() +{ +} + +/* + * Initialize the GUI interface for the plugin - this is only called once when the plugin is + * added to the plugin registry in the QGIS application. + */ +void Topol::initGui() +{ + mQActionPointer = new QAction( QIcon( ":/topology/topol.png" ), tr( "TopologyChecker" ), this ); + //mQActionPointer = new QAction( QIcon(), tr( "Topology Checker" ), this ); + mQActionPointer->setCheckable( true ); + + + // Create the action for tool + //mQActionPointer = new QAction(QIcon(":/topol_c/topol.png"),tr("Topology Checker"), this); + // Set the what's this text + mQActionPointer->setWhatsThis( tr( "Topology Checker for vector layer" ) ); + // Connect the action to the run + connect( mQActionPointer, SIGNAL( triggered() ), this, SLOT( showOrHide() ) ); + // Add the icon to the toolbar + mQGisIface->addToolBarIcon( mQActionPointer ); + mQGisIface->addPluginToMenu( tr( "&Topol" ), mQActionPointer ); + //run(); +} +//method defined in interface +void Topol::help() +{ + //implement me! +} + +void Topol::showOrHide() +{ + if ( !mDock ) + run(); + else + if ( mQActionPointer->isChecked() ) + mDock->show(); + else + mDock->hide(); +} + +// Slot called when the menu item is triggered +// If you created more menu items / toolbar buttons in initiGui, you should +// create a separate handler for each action - this single run() method will +// not be enough +void Topol::run() +{ + mDock = new checkDock( mQGisIface ); + mQGisIface->addDockWidget( Qt::RightDockWidgetArea, mDock ); + connect( mDock, SIGNAL( visibilityChanged( bool ) ), mQActionPointer, SLOT( setChecked( bool ) ) ); + //mDock->show(); +} + +// Unload the plugin by cleaning up the GUI +void Topol::unload() +{ + // remove the GUI + mQGisIface->removePluginMenu( "&Topol", mQActionPointer ); + mQGisIface->removeToolBarIcon( mQActionPointer ); + delete mQActionPointer; +} + +////////////////////////////////////////////////////////////////////////// +// +// +// THE FOLLOWING CODE IS AUTOGENERATED BY THE PLUGIN BUILDER SCRIPT +// YOU WOULD NORMALLY NOT NEED TO MODIFY THIS, AND YOUR PLUGIN +// MAY NOT WORK PROPERLY IF YOU MODIFY THIS INCORRECTLY +// +// +////////////////////////////////////////////////////////////////////////// + + +/** + * Required extern functions needed for every plugin + * These functions can be called prior to creating an instance + * of the plugin class + */ +// Class factory to return a new instance of the plugin class +QGISEXTERN QgisPlugin * classFactory( QgisInterface * theQgisInterfacePointer ) +{ + return new Topol( theQgisInterfacePointer ); +} +// Return the name of the plugin - note that we do not user class members as +// the class may not yet be insantiated when this method is called. +QGISEXTERN QString name() +{ + return sName; +} + +// Return the description +QGISEXTERN QString description() +{ + return sDescription; +} + +// Return the type (either UI or MapLayer plugin) +QGISEXTERN int type() +{ + return sPluginType; +} + +// Return the version number for the plugin +QGISEXTERN QString version() +{ + return sPluginVersion; +} + +QGISEXTERN QString icon() +{ + return sPluginIcon; +} + +// Delete ourself +QGISEXTERN void unload( QgisPlugin * thePluginPointer ) +{ + delete thePluginPointer; +} diff --git a/src/plugins/topology/topol.h b/src/plugins/topology/topol.h new file mode 100644 index 000000000000..fd56e45ebd0a --- /dev/null +++ b/src/plugins/topology/topol.h @@ -0,0 +1,111 @@ +/*************************************************************************** + topol.h + ------------------- + begin : Jan 21, 2004 + copyright : (C) 2004 by Tim Sutton + email : tim@linfiniti.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. * + * * + ***************************************************************************/ + /* $Id: plugin.h 7796 2007-12-16 22:11:38Z homann $ */ +/*************************************************************************** + * QGIS Programming conventions: + * + * mVariableName - a class level member variable + * sVariableName - a static class level member variable + * variableName() - accessor for a class member (no 'get' in front of name) + * setVariableName() - mutator for a class member (prefix with 'set') + * + * Additional useful conventions: + * + * theVariableName - a method parameter (prefix with 'the') + * myVariableName - a locally declared variable within a method ('my' prefix) + * + * DO: Use mixed case variable names - myVariableName + * DON'T: separate variable names using underscores: my_variable_name (NO!) + * + * **************************************************************************/ +#ifndef TOPOL_H +#define TOPOL_H + +//QT4 includes +#include + +//QGIS includes +#include "../qgisplugin.h" + +//forward declarations +class QAction; +class QToolBar; + +class QgisInterface; +class checkDock; + +/** +* \class Plugin +* \brief [name] plugin for QGIS +* [description] +*/ +class Topol:public QObject, public QgisPlugin +{ + Q_OBJECT; + public: + + ////////////////////////////////////////////////////////////////////// + // + // MANDATORY PLUGIN METHODS FOLLOW + // + ////////////////////////////////////////////////////////////////////// + + /** + * Constructor for a plugin. The QgisInterface pointer is passed by + * QGIS when it attempts to instantiate the plugin. + * @param theInterface Pointer to the QgisInterface object. + */ + Topol(QgisInterface * theInterface); + //! Destructor + virtual ~Topol(); + +public slots: + //! init the gui + virtual void initGui(); + //! Create and show the dialog box + void run(); + //! Show/hide the dialog box + void showOrHide(); + //! unload the plugin + void unload(); + //! show the help document + void help(); + +private: + + //////////////////////////////////////////////////////////////////// + // + // MANDATORY PLUGIN PROPERTY DECLARATIONS ..... + // + //////////////////////////////////////////////////////////////////// + + int mPluginType; + //! Pointer to the QGIS interface object + QgisInterface *mQGisIface; + //!pointer to the qaction for this plugin + QAction * mQActionPointer; + checkDock* mDock; + + //////////////////////////////////////////////////////////////////// + // + // ADD YOUR OWN PROPERTY DECLARATIONS AFTER THIS POINT..... + // + //////////////////////////////////////////////////////////////////// +}; + +#endif //Topol_H diff --git a/src/plugins/topology/topol.png b/src/plugins/topology/topol.png new file mode 100644 index 000000000000..25f809cd94b8 Binary files /dev/null and b/src/plugins/topology/topol.png differ diff --git a/src/plugins/topology/topol.qrc b/src/plugins/topology/topol.qrc new file mode 100644 index 000000000000..90433f8a239c --- /dev/null +++ b/src/plugins/topology/topol.qrc @@ -0,0 +1,8 @@ + + + topol.png + configureRules.png + validateAll.png + validateExtent.png + + diff --git a/src/plugins/topology/topolError.cpp b/src/plugins/topology/topolError.cpp new file mode 100644 index 000000000000..69ba8404b7b9 --- /dev/null +++ b/src/plugins/topology/topolError.cpp @@ -0,0 +1,229 @@ +/*************************************************************************** + topolError.h + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 "topolError.h" + +//TODO: tell dock to parse errorlist when feature is deleted +bool TopolError::fix( QString fixName ) +{ + std::cout << "fix: \"" << fixName.toStdString() << "\"\n"; + ( this->*mFixMap[fixName] )(); +} + +bool TopolError::fixMove( FeatureLayer fl1, FeatureLayer fl2 ) +{ + bool ok; + QgsFeature f1, f2; + + ok = fl1.layer->getFeatures( QgsFeatureRequest().setFilterFid( fl1.feature.id() ) ).nextFeature( f1 ); + ok = ok && fl2.layer->getFeatures(( QgsFeatureRequest().setFilterFid( fl2.feature.id() ) ) ).nextFeature( f2 ); + + if ( !ok ) + return false; + + + QgsGeometry* g2, *g1 = f1.geometry(); + // 0 means success + if ( !f1.geometry()->makeDifference( f2.geometry() ) ) + return fl1.layer->changeGeometry( f1.id(), f1.geometry() ); + + return false; +} + +bool TopolError::fixMoveFirst() +{ + return fixMove( mFeaturePairs.first(), mFeaturePairs[1] ); +} + +bool TopolError::fixMoveSecond() +{ + return fixMove( mFeaturePairs[1], mFeaturePairs.first() ); +} + +bool TopolError::fixUnion( FeatureLayer fl1, FeatureLayer fl2 ) +{ + bool ok; + QgsFeature f1, f2; + + ok = fl1.layer->getFeatures( QgsFeatureRequest().setFilterFid( fl1.feature.id() ) ).nextFeature( f1 ); + ok = ok && fl2.layer->getFeatures(( QgsFeatureRequest().setFilterFid( fl2.feature.id() ) ) ).nextFeature( f2 ); + + if ( !ok ) + return false; + + QgsGeometry* g = f1.geometry()->combine( f2.geometry() ); + if ( !g ) + return false; + + if ( fl2.layer->deleteFeature( f2.id() ) ) + return fl1.layer->changeGeometry( f1.id(), g ); + + return false; +} + +bool TopolError::fixSnap() +{ + bool ok; + QgsFeature f1, f2; + FeatureLayer fl = mFeaturePairs[1]; + ok = fl.layer->getFeatures(( QgsFeatureRequest().setFilterFid( fl.feature.id() ) ) ).nextFeature( f2 ); + fl = mFeaturePairs.first(); + ok = ok && fl.layer->getFeatures( QgsFeatureRequest().setFilterFid( fl.feature.id() ) ).nextFeature( f1 ); + + if ( !ok ) + return false; + + QgsGeometry* ge = f1.geometry(); + + QgsPolyline line = ge->asPolyline(); + line.last() = mConflict->asPolyline().last(); + + QgsGeometry* newG = QgsGeometry::fromPolyline( line ); + bool ret = fl.layer->changeGeometry( f1.id(), newG ); + delete newG; + + return ret; +} + +bool TopolError::fixUnionFirst() +{ + return fixUnion( mFeaturePairs.first(), mFeaturePairs[1] ); +} + +bool TopolError::fixUnionSecond() +{ + return fixUnion( mFeaturePairs[1], mFeaturePairs.first() ); +} + +bool TopolError::fixDeleteFirst() +{ + FeatureLayer fl = mFeaturePairs.first(); + return fl.layer->deleteFeature( fl.feature.id() ); +} + +bool TopolError::fixDeleteSecond() +{ + FeatureLayer fl = mFeaturePairs[1]; + return fl.layer->deleteFeature( fl.feature.id() ); +} + +TopolError::TopolError( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ): + mFeaturePairs( theFeaturePairs ), + mBoundingBox( theBoundingBox ), + mConflict( theConflict ) +{ + mFixMap["Select automatic fix"] = &TopolError::fixDummy; +} + +TopolErrorIntersection::TopolErrorIntersection( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "intersecting geometries"; + + mFixMap["Move blue feature"] = &TopolErrorIntersection::fixMoveFirst; + mFixMap["Move red feature"] = &TopolErrorIntersection::fixMoveSecond; + mFixMap["Delete blue feature"] = &TopolErrorIntersection::fixDeleteFirst; + mFixMap["Delete red feature"] = &TopolErrorIntersection::fixDeleteSecond; + + // allow union only when both features have the same geometry type + if ( theFeaturePairs.first().feature.geometry()->type() == theFeaturePairs[1].feature.geometry()->type() ) + { + mFixMap["Union to blue feature"] = &TopolErrorIntersection::fixUnionFirst; + mFixMap["Union to red feature"] = &TopolErrorIntersection::fixUnionSecond; + } +} + +TopolErrorClose::TopolErrorClose( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "features too close"; + + mFixMap["Move blue feature"] = &TopolErrorClose::fixMoveFirst; + mFixMap["Move red feature"] = &TopolErrorClose::fixMoveSecond; + mFixMap["Snap to segment"] = &TopolErrorClose::fixSnap; +} + +TopolErrorCovered::TopolErrorCovered( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "point not covered by segment"; + mFixMap["Delete point"] = &TopolErrorCovered::fixDeleteFirst; +} + +TopolErrorShort::TopolErrorShort( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "segment too short"; + mFixMap["Delete feature"] = &TopolErrorShort::fixDeleteFirst; +} + +TopolErrorValid::TopolErrorValid( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "invalid geometry"; + mFixMap["Delete feature"] = &TopolErrorValid::fixDeleteFirst; +} + +TopolErrorDangle::TopolErrorDangle( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "dangling end"; + mFixMap["Delete feature"] = &TopolErrorDangle::fixDeleteFirst; +} + +TopolErrorDuplicates::TopolErrorDuplicates( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "duplicate geometry"; + //mFixMap["Delete feature"] = &TopolErrorDuplicates::fixDeleteFirst; +} + +TopolErrorPseudos::TopolErrorPseudos( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "pseudo node"; + //mFixMap["Delete feature"] = &TopolErrorDuplicates::fixDeleteFirst; +} + +TopolErrorOverlaps::TopolErrorOverlaps( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "overlaps"; + //mFixMap["Delete feature"] = &TopolErrorDuplicates::fixDeleteFirst; +} + +TopolErrorGaps::TopolErrorGaps( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "gaps"; + //mFixMap["Delete feature"] = &TopolErrorDuplicates::fixDeleteFirst; +} + +TopolErrorPointNotCoveredByLineEnds::TopolErrorPointNotCoveredByLineEnds( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "point not covered"; +} + +TopolErrorLineEndsNotCoveredByPoints::TopolErrorLineEndsNotCoveredByPoints( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "line ends not covered by point"; +} + +TopolErrorPointNotInPolygon::TopolErrorPointNotInPolygon( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "point not in polygon"; +} + +TopolErrorPolygonContainsPoint::TopolErrorPolygonContainsPoint( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "polygon does not contain point"; +} + +TopolErroMultiPart::TopolErroMultiPart( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ) : TopolError( theBoundingBox, theConflict, theFeaturePairs ) +{ + mName = "multipart feature"; +} diff --git a/src/plugins/topology/topolError.h b/src/plugins/topology/topolError.h new file mode 100644 index 000000000000..9d3a8070c78b --- /dev/null +++ b/src/plugins/topology/topolError.h @@ -0,0 +1,233 @@ +/*************************************************************************** + topolError.h + TOPOLogy checker + ------------------- + begin : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 TOPOLERROR_H +#define TOPOLERROR_H + +#include +#include +#include + +class TopolError; +typedef QList ErrorList; +typedef bool ( TopolError::*fixFunction )(); + +class FeatureLayer +{ + public: + FeatureLayer() : + layer( 0 ), feature( QgsFeature() ) {}; + /** + * Constructor + * @param theLayer layer pointer + * @param theFeature QgsFeature + */ + FeatureLayer( QgsVectorLayer* theLayer, QgsFeature theFeature ) : + layer( theLayer ), feature( theFeature ) {}; + + QgsVectorLayer* layer; + QgsFeature feature; +}; + +class TopolError +{ + protected: + QString mName; + QgsRectangle mBoundingBox; + QgsGeometry* mConflict; + QList mFeaturePairs; + QMap mFixMap; + + /** + * A dummy fix - does nothing + */ + bool fixDummy() { return false; } + /** + * Snaps to a feature + */ + bool fixSnap(); + /** + * Moves first feature + */ + bool fixMoveFirst(); + /** + * Moves second feature + */ + bool fixMoveSecond(); + /** + * Unions features to the first + */ + bool fixUnionFirst(); + /** + * Unions features to the first + */ + bool fixUnionSecond(); + /** + * Deletes first feature + */ + bool fixDeleteFirst(); + /** + * Deletes second feature + */ + bool fixDeleteSecond(); + + //helper fix functions + + /** + * Makes geometry difference + * @param fl1 first FeatureLayer pair + * @param fl2 second FeatureLayer pair + */ + bool fixMove( FeatureLayer fl1, FeatureLayer fl2 ); + /** + * Unions features to the first one + * @param fl1 first FeatureLayer pair + * @param fl2 second FeatureLayer pair + */ + bool fixUnion( FeatureLayer fl1, FeatureLayer fl2 ); + + public: + /** + * Constructor + * @param theBoundingBox bounding box of the two features + * @param theConflict geometry representation of the conflict + * @param theFeaturePairs FeatureLayer pairs of the two features + */ + TopolError( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); + + /** + * Destructor + */ + virtual ~TopolError() { delete mConflict; } + /** + * Runs fixing function + * @param fixName name of the fix + */ + virtual bool fix( QString fixName ); + /** + * Returns error's name + */ + virtual QString name() { return mName; } + /** + * Returns topology conflict + */ + virtual QgsGeometry* conflict() { return mConflict; } + /** + * Returns bounding box of the error + */ + virtual QgsRectangle boundingBox() { return mBoundingBox; } + /** + * Returns FeatureLayer pairs from the error + */ + virtual QList featurePairs() { return mFeaturePairs; } + /** + * Returns the names of posible fixes + */ + virtual QStringList fixNames() { return mFixMap.keys(); } +}; + +class TopolErrorIntersection : public TopolError +{ + public: + TopolErrorIntersection( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorClose : public TopolError +{ + public: + TopolErrorClose( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorCovered : public TopolError +{ + public: + TopolErrorCovered( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorShort : public TopolError +{ + public: + TopolErrorShort( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorValid : public TopolError +{ + public: + TopolErrorValid( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorDangle : public TopolError +{ + public: + TopolErrorDangle( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorDuplicates : public TopolError +{ + public: + TopolErrorDuplicates( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorPseudos : public TopolError +{ + public: + TopolErrorPseudos( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorOverlaps : public TopolError +{ + public: + TopolErrorOverlaps( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorGaps : public TopolError +{ + public: + TopolErrorGaps( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorPointNotCoveredByLineEnds : public TopolError +{ + public: + TopolErrorPointNotCoveredByLineEnds( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorLineEndsNotCoveredByPoints : public TopolError +{ + public: + TopolErrorLineEndsNotCoveredByPoints( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorPointNotInPolygon : public TopolError +{ + public: + TopolErrorPointNotInPolygon( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErrorPolygonContainsPoint : public TopolError +{ + public: + TopolErrorPolygonContainsPoint( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +class TopolErroMultiPart : public TopolError +{ + public: + TopolErroMultiPart( QgsRectangle theBoundingBox, QgsGeometry* theConflict, QList theFeaturePairs ); +}; + +#endif diff --git a/src/plugins/topology/topolTest.cpp b/src/plugins/topology/topolTest.cpp new file mode 100644 index 000000000000..0e7729dad400 --- /dev/null +++ b/src/plugins/topology/topolTest.cpp @@ -0,0 +1,1700 @@ +/*************************************************************************** + topolTest.cpp + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 "topolTest.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "geosFunctions.h" + +topolTest::topolTest( QgisInterface* qgsIface ) +{ + theQgsInterface = qgsIface; + mTestCancelled = false; + + // one layer tests + mTopologyRuleMap["must not have invalid geometries"].f = &topolTest::checkValid; + mTopologyRuleMap["must not have invalid geometries"].useSecondLayer = false; + mTopologyRuleMap["must not have invalid geometries"].useTolerance = false; + mTopologyRuleMap["must not have invalid geometries"].useSpatialIndex = false; + + //mTopologyRuleMap["segments must have minimum length"].f = &topolTest::checkSegmentLength; + //mTopologyRuleMap["segments must have minimum length"].useTolerance = true; + //mTopologyRuleMap["segments must have minimum length"].useSecondLayer = false; + //mTopologyRuleMap["segments must have minimum length"].useSpatialIndex = false; + + mTopologyRuleMap["must not have dangles"].f = &topolTest::checkDanglingLines; + mTopologyRuleMap["must not have dangles"].useSecondLayer = false; + mTopologyRuleMap["must not have dangles"].useTolerance = false; + mTopologyRuleMap["must not have dangles"].useSpatialIndex = false; + + mTopologyRuleMap["must not have duplicates"].f = &topolTest::checkDuplicates; + mTopologyRuleMap["must not have duplicates"].useTolerance = false; + mTopologyRuleMap["must not have duplicates"].useSecondLayer = false; + mTopologyRuleMap["must not have duplicates"].useSpatialIndex = true; + + mTopologyRuleMap["must not have pseudos"].f = &topolTest::checkPseudos; + mTopologyRuleMap["must not have pseudos"].useTolerance = false; + mTopologyRuleMap["must not have pseudos"].useSecondLayer = false; + mTopologyRuleMap["must not have pseudos"].useSpatialIndex = false; + + mTopologyRuleMap["must not overlap"].f = &topolTest::checkOverlaps; + mTopologyRuleMap["must not overlap"].useTolerance = false; + mTopologyRuleMap["must not overlap"].useSecondLayer = false; + mTopologyRuleMap["must not overlap"].useSpatialIndex = true; + + mTopologyRuleMap["must not have gaps"].f = &topolTest::checkGaps; + mTopologyRuleMap["must not have gaps"].useTolerance = false; + mTopologyRuleMap["must not have gaps"].useSecondLayer = false; + mTopologyRuleMap["must not have gaps"].useSpatialIndex = false; + + mTopologyRuleMap["Must not have multi-part geometries"].f = &topolTest::checkMultipart; + mTopologyRuleMap["Must not have multi-part geometries"].useSecondLayer = false; + mTopologyRuleMap["Must not have multi-part geometries"].useTolerance = false; + mTopologyRuleMap["Must not have multi-part geometries"].useSpatialIndex = false; + + // two layer tests + mTopologyRuleMap["must not overlap with"].f = &topolTest::checkOverlapWithLayer; + mTopologyRuleMap["must not overlap with"].useSecondLayer = true; + mTopologyRuleMap["must not overlap with"].useTolerance = false; + mTopologyRuleMap["must not overlap with"].useSpatialIndex = true; + + mTopologyRuleMap["points must be covered by segments"].f = &topolTest::checkPointCoveredBySegment; + mTopologyRuleMap["points must be covered by segments"].useSecondLayer = true; + mTopologyRuleMap["points must be covered by segments"].useTolerance = false; + mTopologyRuleMap["points must be covered by segments"].useSpatialIndex = true; + + //mTopologyRuleMap["features must not be closer than tolerance"].f = &topolTest::checkCloseFeature; + //mTopologyRuleMap["features must not be closer than tolerance"].useSecondLayer = true; + //mTopologyRuleMap["features must not be closer than tolerance"].useTolerance = false; + //mTopologyRuleMap["features must not be closer than tolerance"].useSpatialIndex = false; + + mTopologyRuleMap["Ponts must be covered by endpoints of line"].f = &topolTest::checkPointCoveredByLineEnds; + mTopologyRuleMap["Ponts must be covered by endpoints of line"].useSecondLayer = true; + mTopologyRuleMap["Ponts must be covered by endpoints of line"].useTolerance = false; + mTopologyRuleMap["Ponts must be covered by endpoints of line"].useSpatialIndex = true; + + mTopologyRuleMap["Line ends must be covered by Points"].f = &topolTest::checkyLineEndsCoveredByPoints; + mTopologyRuleMap["Line ends must be covered by Points"].useSecondLayer = true; + mTopologyRuleMap["Line ends must be covered by Points"].useTolerance = false; + mTopologyRuleMap["Line ends must be covered by Points"].useSpatialIndex = true; + + mTopologyRuleMap["Points must be inside polygon"].f = &topolTest::checkPointInPolygon; + mTopologyRuleMap["Points must be inside polygon"].useSecondLayer = true; + mTopologyRuleMap["Points must be inside polygon"].useTolerance = false; + mTopologyRuleMap["Points must be inside polygon"].useSpatialIndex = true; + + mTopologyRuleMap["Polygon must contain point"].f = &topolTest::checkPolygonContainsPoint; + mTopologyRuleMap["Polygon must contain point"].useSecondLayer = true; + mTopologyRuleMap["Polygon must contain point"].useTolerance = false; + mTopologyRuleMap["Polygon must contain point"].useSpatialIndex = true; + + + +} + +topolTest::~topolTest() +{ + QMap::Iterator lit = mLayerIndexes.begin(); + for ( ; lit != mLayerIndexes.end(); ++lit ) + delete *lit; +} + +void topolTest::setTestCancelled() +{ + mTestCancelled = true; +} + +bool topolTest::testCancelled() +{ + if ( mTestCancelled ) + { + mTestCancelled = false; + return true; + } + + return false; +} + +ErrorList topolTest::checkCloseFeature( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ) +{ + ErrorList errorList; + QgsSpatialIndex* index = 0; + + bool badG1 = false, badG2 = false; + bool skipItself = layer1 == layer2; + + int i = 0; + QList::Iterator it; + QList::ConstIterator FeatureListEnd = mFeatureList1.end(); + for ( it = mFeatureList1.begin(); it != FeatureListEnd; ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + + if ( testCancelled() ) + break; + + QgsGeometry* g1 = it->feature.geometry(); + QgsRectangle bb = g1->boundingBox(); + + // increase bounding box by tolerance + QgsRectangle frame( bb.xMinimum() - tolerance, bb.yMinimum() - tolerance, bb.xMaximum() + tolerance, bb.yMaximum() + tolerance ); + + QList crossingIds; + crossingIds = index->intersects( frame ); + + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + + for ( ; cit != crossingIdsEnd; ++cit ) + { + QgsFeature& f = mFeatureMap2[*cit].feature; + QgsGeometry* g2 = f.geometry(); + + // skip itself, when invoked with the same layer + if ( skipItself && f.id() == it->feature.id() ) + continue; + + if ( !g2 || !g2->asGeos() ) + { + badG2 = true; + continue; + } + + if ( !g1 || !g1->asGeos() ) + { + badG1 = true; + continue; + + } + + if ( g1->distance( *g2 ) < tolerance ) + { + QgsRectangle r = g2->boundingBox(); + r.combineExtentWith( &bb ); + + QList fls; + FeatureLayer fl; + fl.feature = f; + fl.layer = layer2; + fls << *it << fl; + QgsGeometry* conflict = new QgsGeometry( *g2 ); + TopolErrorClose* err = new TopolErrorClose( r, conflict, fls ); + //TopolErrorClose* err = new TopolErrorClose(r, g2, fls); + + errorList << err; + } + } + } + + if ( badG2 ) + std::cout << "g2 or g2->asGeos() == NULL in close\n" << std::flush; + + if ( badG1 ) + std::cout << "g1 or g1->asGeos() == NULL in close\n" << std::flush; + + return errorList; +} + +ErrorList topolTest::checkDanglingLines( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + Q_UNUSED( layer2 ); + + int i = 0; + ErrorList errorList; + QgsFeature f; + + if ( layer1->geometryType() != QGis::Line ) + { + return errorList; + } + + QList::Iterator it; + QList::ConstIterator FeatureListEnd = mFeatureList1.end(); + + qDebug() << mFeatureList1.count(); + + QgsPoint startPoint; + QgsPoint endPoint; + + std::multimap endVerticesMap; + + for ( it = mFeatureList1.begin(); it != FeatureListEnd; ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + + if ( testCancelled() ) + break; + + QgsGeometry* g1 = it->feature.geometry(); + + if ( !g1 ) + { + std::cout << "g2 == NULL in pseudo line test\n" << std::flush; + continue; + } + + if ( !g1->asGeos() ) + { + std::cout << "g2->asGeos() == NULL in pseudo line test\n" << std::flush; + continue; + } + + if ( g1->isMultipart() ) + { + QgsMultiPolyline lines = g1->asMultiPolyline(); + for ( int m = 0; m < lines.count(); m++ ) + { + QgsPolyline line = lines[m]; + startPoint = line[0]; + endPoint = line[line.size() - 1]; + + endVerticesMap.insert( std::pair( startPoint, it->feature.id() ) ); + endVerticesMap.insert( std::pair( endPoint, it->feature.id() ) ); + + } + } + else + { + QgsPolyline polyline = g1->asPolyline(); + startPoint = polyline[0]; + endPoint = polyline[polyline.size()-1]; + endVerticesMap.insert( std::pair( startPoint, it->feature.id() ) ); + endVerticesMap.insert( std::pair( endPoint, it->feature.id() ) ); + } + } + + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + + for ( std::multimap::iterator pointIt = endVerticesMap.begin(), end = endVerticesMap.end(); pointIt != end; pointIt = endVerticesMap.upper_bound( pointIt->first ) ) + { + QgsPoint p = pointIt->first; + QgsFeatureId k = pointIt->second; + + int repetitions = endVerticesMap.count( p ); + + //QgsGeometry* extentPoly = + if ( repetitions == 1 ) + { + + QgsGeometry* conflictGeom = QgsGeometry::fromPoint( p ); + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + } + + QgsRectangle bBox = conflictGeom->boundingBox(); + QgsFeature feat; + + FeatureLayer ftrLayer1; + //need to fetch attributes?? being safe side by fetching.. + layer1->getFeatures( QgsFeatureRequest().setFilterFid( k ) ).nextFeature( feat ); + ftrLayer1.feature = feat; + ftrLayer1.layer = layer1; + + QList errorFtrLayers; + errorFtrLayers << ftrLayer1 << ftrLayer1; + + TopolErrorDangle* err = new TopolErrorDangle( bBox, conflictGeom, errorFtrLayers ); + errorList << err; + + } + } + + return errorList; +} + +ErrorList topolTest::checkDuplicates( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + Q_UNUSED( layer2 ); + //TODO: multilines - check all separate pieces + int i = 0; + ErrorList errorList; + + QList* duplicateIds = new QList(); + + QgsSpatialIndex* index = mLayerIndexes[layer1->id()]; + + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + + QList::Iterator it; + QList::ConstIterator FeatureListEnd = mFeatureList1.end(); + for ( it = mFeatureList1.begin(); it != FeatureListEnd; ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + + int currentId = it->feature.id(); + + if ( duplicateIds->contains( currentId ) ) + { + //is already a duplicate geometry..skip.. + continue; + } + + if ( testCancelled() ) + break; + + QgsGeometry* g1 = it->feature.geometry(); + QgsRectangle bb = g1->boundingBox(); + + QList crossingIds; + crossingIds = index->intersects( bb ); + + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + + bool duplicate = false; + + for ( ; cit != crossingIdsEnd; ++cit ) + { + duplicate = false; + // skip itself + if ( mFeatureMap2[*cit].feature.id() == it->feature.id() ) + continue; + + QgsGeometry* g2 = mFeatureMap2[*cit].feature.geometry(); + if ( !g2 ) + { + std::cout << "g2 == NULL in dangling line test\n" << std::flush; + continue; + } + + if ( !g2->asGeos() ) + { + std::cout << "g2->asGeos() == NULL in dangling line test\n" << std::flush; + continue; + } + + if (g1->equals(g2)) + { + duplicate = true; + duplicateIds->append( mFeatureMap2[*cit].feature.id() ); + } + + if ( duplicate ) + { + + + QList fls; + fls << *it << *it; + QgsGeometry* conflict = new QgsGeometry( *g1 ); + + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflict)) + { + continue; + } + if(canvasExtentPoly->crosses(conflict)) + { + conflict = conflict->intersection(canvasExtentPoly); + } + } + + TopolErrorDuplicates* err = new TopolErrorDuplicates( bb, conflict, fls ); + + errorList << err; + } + + } + + } + + return errorList; + +} + +ErrorList topolTest::checkOverlaps( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + Q_UNUSED( layer2 ); + int i = 0; + ErrorList errorList; + + // could be enabled for lines and points too + // so duplicate rule may be removed? + + if ( layer1->geometryType() != QGis::Polygon ) + { + return errorList; + } + + QList* duplicateIds = new QList(); + + QgsSpatialIndex* index; + index = mLayerIndexes[layer1->id()]; + + if ( !index ) + { + qDebug() << "no index present"; + return errorList; + } + + QMap::Iterator it; + QMap::ConstIterator FeatureListEnd = mFeatureMap2.end(); + for ( it = mFeatureMap2.begin(); it != FeatureListEnd; ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + + int currentId = it->feature.id(); + + if ( duplicateIds->contains( currentId ) ) + { + //is already a duplicate geometry..skip.. + continue; + } + + if ( testCancelled() ) + break; + + QgsGeometry* g1 = it->feature.geometry(); + QgsRectangle bb = g1->boundingBox(); + + QList crossingIds; + crossingIds = index->intersects( bb ); + + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + + bool duplicate = false; + + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + for ( ; cit != crossingIdsEnd; ++cit ) + { + duplicate = false; + // skip itself + if ( mFeatureMap2[*cit].feature.id() == it->feature.id() ) + continue; + + QgsGeometry* g2 = mFeatureMap2[*cit].feature.geometry(); + if ( !g2 ) + { + std::cout << "g2 == NULL in dangling line test\n" << std::flush; + continue; + } + + if ( !g2->asGeos() ) + { + std::cout << "g2->asGeos() == NULL in dangling line test\n" << std::flush; + continue; + } + + if (g1->overlaps(g2)) + { + duplicate = true; + duplicateIds->append( mFeatureMap2[*cit].feature.id() ); + } + + if ( duplicate ) + { + QList fls; + fls << *it << *it; + QgsGeometry* conflictGeom = g1->intersection( g2 ); + + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + if(canvasExtentPoly->crosses(conflictGeom)) + { + conflictGeom = conflictGeom->intersection(canvasExtentPoly); + } + } + + TopolErrorOverlaps* err = new TopolErrorOverlaps( bb, conflictGeom, fls ); + + errorList << err; + } + + } + + } + + return errorList; +} + +ErrorList topolTest::checkGaps( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + Q_UNUSED( layer2 ); + + int i = 0; + ErrorList errorList; + + // could be enabled for lines and points too + // so duplicate rule may be removed? + + if ( layer1->geometryType() != QGis::Polygon ) + { + return errorList; + } + + QList::Iterator it; + QgsGeometry* g1; + + QList geomList; + + qDebug() << mFeatureList1.count() << " features in list!"; + QList::ConstIterator FeatureListEnd = mFeatureList1.end(); + for ( it = mFeatureList1.begin(); it != FeatureListEnd; ++it ) + { + qDebug() << "reading features-" << i; + + if ( !( ++i % 100 ) ) + { + emit progress( i ); + } + + if ( testCancelled() ) + { + break; + } + + g1 = it->feature.geometry(); + + if ( !g1 ) + { + continue; + } + + if ( !g1->asGeos() ) + { + continue; + } + geomList.push_back( GEOSGeom_clone( g1->asGeos() ) ); + } + + GEOSGeometry** geomArray = new GEOSGeometry*[geomList.size()]; + for ( int i = 0; i < geomList.size(); ++i ) + { + //qDebug() << "filling geometry array-" << i; + geomArray[i] = geomList.at( i ); + } + + qDebug() << "creating geometry collection-"; + + if ( geomList.size() == 0 ) + { + //qDebug() << "geometry list is empty!"; + return errorList; + } + + GEOSGeometry* collection = 0; + collection = GEOSGeom_createCollection( GEOS_MULTIPOLYGON, geomArray, geomList.size() ); + + + qDebug() << "performing cascaded union..might take time..-"; + GEOSGeometry* unionGeom = GEOSUnionCascaded( collection ); + //delete[] geomArray; + + QgsGeometry test; + test.fromGeos( unionGeom ); + + + qDebug() << "wktmerged - " << test.exportToWkt(); + + QString extentWkt = test.boundingBox().asWktPolygon(); + QgsGeometry* extentGeom = QgsGeometry::fromWkt( extentWkt ); + QgsGeometry* bufferExtent = extentGeom->buffer( 2, 3 ); + + qDebug() << "extent wkt - " << bufferExtent->exportToWkt(); + + QgsGeometry* diffGeoms = bufferExtent->difference( &test ); + + qDebug() << "difference gometry - " << diffGeoms->exportToWkt() ; + + QList geomColl = diffGeoms->asGeometryCollection(); + + //QList geomColl = test.asGeometryCollection(); + + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + for ( int i = 1; i < geomColl.count() ; ++i ) + { + QgsGeometry* conflictGeom = geomColl[i]; + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + if(canvasExtentPoly->crosses(conflictGeom)) + { + conflictGeom = conflictGeom->intersection(canvasExtentPoly); + } + } + QgsRectangle bBox = conflictGeom->boundingBox(); + QgsFeature feat; + FeatureLayer ftrLayer1; + ftrLayer1.layer = layer1; + QList errorFtrLayers; + errorFtrLayers << ftrLayer1 << ftrLayer1; + TopolErrorGaps* err = new TopolErrorGaps( bBox, conflictGeom, errorFtrLayers ); + errorList << err; + } + + return errorList; +} + +ErrorList topolTest::checkPseudos( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + Q_UNUSED( layer2 ); + + int i = 0; + ErrorList errorList; + QgsFeature f; + + if ( layer1->geometryType() != QGis::Line ) + { + return errorList; + } + + QList::Iterator it; + QList::ConstIterator FeatureListEnd = mFeatureList1.end(); + + qDebug() << mFeatureList1.count(); + + QgsPoint startPoint; + QgsPoint endPoint; + + std::multimap endVerticesMap; + + for ( it = mFeatureList1.begin(); it != FeatureListEnd; ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + + if ( testCancelled() ) + break; + + QgsGeometry* g1 = it->feature.geometry(); + + if ( !g1 ) + { + std::cout << "g2 == NULL in pseudo line test\n" << std::flush; + continue; + } + + if ( !g1->asGeos() ) + { + std::cout << "g2->asGeos() == NULL in pseudo line test\n" << std::flush; + continue; + } + + if ( g1->isMultipart() ) + { + QgsMultiPolyline lines = g1->asMultiPolyline(); + for ( int m = 0; m < lines.count(); m++ ) + { + QgsPolyline line = lines[m]; + startPoint = line[0]; + endPoint = line[line.size() - 1]; + + endVerticesMap.insert( std::pair( startPoint, it->feature.id() ) ); + endVerticesMap.insert( std::pair( endPoint, it->feature.id() ) ); + + } + } + else + { + QgsPolyline polyline = g1->asPolyline(); + startPoint = polyline[0]; + endPoint = polyline[polyline.size()-1]; + endVerticesMap.insert( std::pair( startPoint, it->feature.id() ) ); + endVerticesMap.insert( std::pair( endPoint, it->feature.id() ) ); + } + } + + + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + + for ( std::multimap::iterator pointIt = endVerticesMap.begin(), end = endVerticesMap.end(); pointIt != end; pointIt = endVerticesMap.upper_bound( pointIt->first ) ) + { + QgsPoint p = pointIt->first; + QgsFeatureId k = pointIt->second; + + int repetitions = endVerticesMap.count( p ); + + if ( repetitions == 2 ) + { + QgsGeometry* conflictGeom = QgsGeometry::fromPoint( p ); + + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + } + + QgsRectangle bBox = conflictGeom->boundingBox(); + QgsFeature feat; + + FeatureLayer ftrLayer1; + //need to fetch attributes?? being safe side by fetching.. + layer1->getFeatures( QgsFeatureRequest().setFilterFid( k ) ).nextFeature( feat ); + ftrLayer1.feature = feat; + ftrLayer1.layer = layer1; + + QList errorFtrLayers; + errorFtrLayers << ftrLayer1 << ftrLayer1; + + TopolErrorPseudos* err = new TopolErrorPseudos( bBox, conflictGeom, errorFtrLayers ); + errorList << err; + + } + } + + return errorList; +} + +ErrorList topolTest::checkValid( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + Q_UNUSED( layer1 ); + Q_UNUSED( layer2 ); + + int i = 0; + ErrorList errorList; + QgsFeature f; + + QList::Iterator it; + + for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( ++i ); + if ( testCancelled() ) + break; + + QgsGeometry* g = it->feature.geometry(); + if ( !g ) + { + std::cout << "validity test: invalid QgsGeometry pointer\n" << std::flush; + continue; + } + + if ( !g->asGeos() ) + continue; + + if ( !GEOSisValid( g->asGeos() ) ) + { + QgsRectangle r = g->boundingBox(); + QList fls; + fls << *it << *it; + + QgsGeometry* conflict = new QgsGeometry( *g ); + TopolErrorValid* err = new TopolErrorValid( r, conflict, fls ); + errorList << err; + } + } + + return errorList; +} + + +ErrorList topolTest::checkPointCoveredBySegment( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + + int i = 0; + + ErrorList errorList; + + if ( layer1->geometryType() != QGis::Point ) + { + return errorList; + } + if ( layer2->geometryType() == QGis::Point ) + { + return errorList; + } + + QgsSpatialIndex* index = mLayerIndexes[layer2->id()]; + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + + QList::Iterator it; + for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + + if ( testCancelled() ) + break; + + QgsGeometry* g1 = it->feature.geometry(); + QgsRectangle bb = g1->boundingBox(); + + QList crossingIds; + crossingIds = index->intersects( bb ); + + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + + bool touched = false; + + for ( ; cit != crossingIdsEnd; ++cit ) + { + QgsFeature& f = mFeatureMap2[*cit].feature; + QgsGeometry* g2 = f.geometry(); + + if ( !g2 || !g2->asGeos() ) + { + std::cout << "g2 or g2->asGeos() == NULL in covered\n" << std::flush; + continue; + } + + // test if point touches other geometry + if ( geosTouches( g1, g2 ) ) + { + touched = true; + break; + } + } + + if ( !touched ) + { + QgsGeometry* conflictGeom = new QgsGeometry( *g1 ); + + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + } + + QList fls; + fls << *it << *it; + //bb.scale(10); + + TopolErrorCovered* err = new TopolErrorCovered( bb, conflictGeom, fls ); + + errorList << err; + } + } + + return errorList; +} + +ErrorList topolTest::checkSegmentLength( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ) +{ + Q_UNUSED( layer2 ); + + int i = 0; + ErrorList errorList; + QgsFeature f; + + + QList::Iterator it; + QList::ConstIterator FeatureListEnd = mFeatureList1.end(); + + QgsPolygon pol; + + QgsMultiPolygon mpol; + QgsPolyline segm; + QgsPolyline ls; + QgsMultiPolyline mls; + QList fls; + TopolErrorShort* err; + double distance; + + for ( it = mFeatureList1.begin(); it != FeatureListEnd; ++it ) + { + if ( !( ++i % 100 ) ) + { + emit progress( i ); + } + + if ( testCancelled() ) + { + break; + } + + QgsGeometry* g1 = it->feature.geometry(); + + + // switching by type here, because layer can contain both single and multi version geometries + switch ( g1->wkbType() ) + { + case QGis::WKBLineString: + case QGis::WKBLineString25D: + ls = g1->asPolyline(); + + + for ( int i = 1; i < ls.size(); ++i ) + { + distance = sqrt( ls[i-1].sqrDist( ls[i] ) ); + if ( distance < tolerance ) + { + fls.clear(); + fls << *it << *it; + segm.clear(); + segm << ls[i-1] << ls[i]; + QgsGeometry* conflict = QgsGeometry::fromPolyline( segm ); + err = new TopolErrorShort( g1->boundingBox(), conflict, fls ); + //err = new TopolErrorShort(g1->boundingBox(), QgsGeometry::fromPolyline(segm), fls); + errorList << err; + //break on getting the first error + break; + } + } + break; + + case QGis::WKBPolygon: + case QGis::WKBPolygon25D: + pol = g1->asPolygon(); + + for ( int i = 0; i < pol.size(); ++i ) + { + for ( int j = 1; j < pol[i].size(); ++j ) + { + distance = sqrt( pol[i][j-1].sqrDist( pol[i][j] ) ); + if ( distance < tolerance ) + { + fls.clear(); + fls << *it << *it; + segm.clear(); + segm << pol[i][j-1] << pol[i][j]; + QgsGeometry* conflict = QgsGeometry::fromPolyline( segm ); + err = new TopolErrorShort( g1->boundingBox(), conflict, fls ); + errorList << err; + //break on getting the first error + break; + } + } + } + + break; + + case QGis::WKBMultiLineString: + case QGis::WKBMultiLineString25D: + mls = g1->asMultiPolyline(); + + for ( int k = 0; k < mls.size(); ++k ) + { + QgsPolyline& ls = mls[k]; + for ( int i = 1; i < ls.size(); ++i ) + { + distance = sqrt( ls[i-1].sqrDist( ls[i] ) ); + if ( distance < tolerance ) + { + fls.clear(); + fls << *it << *it; + segm.clear(); + segm << ls[i-1] << ls[i]; + QgsGeometry* conflict = QgsGeometry::fromPolyline( segm ); + err = new TopolErrorShort( g1->boundingBox(), conflict, fls ); + errorList << err; + //break on getting the first error + break; + } + } + } + break; + + case QGis::WKBMultiPolygon: + case QGis::WKBMultiPolygon25D: + mpol = g1->asMultiPolygon(); + + for ( int k = 0; k < mpol.size(); ++k ) + { + QgsPolygon& pol = mpol[k]; + for ( int i = 0; i < pol.size(); ++i ) + { + for ( int j = 1; j < pol[i].size(); ++j ) + { + distance = pol[i][j-1].sqrDist( pol[i][j] ); + if ( distance < tolerance ) + { + fls.clear(); + fls << *it << *it; + segm.clear(); + segm << pol[i][j-1] << pol[i][j]; + QgsGeometry* conflict = QgsGeometry::fromPolyline( segm ); + err = new TopolErrorShort( g1->boundingBox(), conflict, fls ); + errorList << err; + //break on getting the first error + break; + } + } + } + } + break; + + default: + continue; + } + } + + return errorList; +} + +ErrorList topolTest::checkOverlapWithLayer( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + + int i = 0; + ErrorList errorList; + + bool skipItself = layer1 == layer2; + QgsSpatialIndex* index = mLayerIndexes[layer2->id()]; + + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + + QList::Iterator it; + QList::ConstIterator FeatureListEnd = mFeatureList1.end(); + for ( it = mFeatureList1.begin(); it != FeatureListEnd; ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + + if ( testCancelled() ) + break; + + QgsGeometry* g1 = it->feature.geometry(); + QgsRectangle bb = g1->boundingBox(); + + QList crossingIds; + crossingIds = index->intersects( bb ); + + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + for ( ; cit != crossingIdsEnd; ++cit ) + { + QgsFeature& f = mFeatureMap2[*cit].feature; + QgsGeometry* g2 = f.geometry(); + + // skip itself, when invoked with the same layer + if ( skipItself && f.id() == it->feature.id() ) + continue; + + if ( !g2 ) + { + std::cout << "no second geometry\n"; + continue; + } + + if ( g1->overlaps( g2 )) + { + QgsRectangle r = bb; + QgsRectangle r2 = g2->boundingBox(); + r.combineExtentWith( &r2 ); + + QgsGeometry* conflictGeom = g1->intersection( g2 ); + // could this for some reason return NULL? + if ( !conflictGeom ) + { + continue; + } + + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + if(canvasExtentPoly->crosses(conflictGeom)) + { + conflictGeom = conflictGeom->intersection(canvasExtentPoly); + } + } + + //c = new QgsGeometry; + + QList fls; + FeatureLayer fl; + fl.feature = f; + fl.layer = layer2; + fls << *it << fl; + TopolErrorIntersection* err = new TopolErrorIntersection( r, conflictGeom, fls ); + + errorList << err; + } + } + } + return errorList; + } + + + +ErrorList topolTest::checkPointCoveredByLineEnds( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + + int i = 0; + ErrorList errorList; + + + if ( layer1->geometryType() != QGis::Point ) + { + return errorList; + } + + if ( layer2->geometryType() != QGis::Line ) + { + return errorList; + } + + QgsSpatialIndex* index = mLayerIndexes[layer2->id()]; + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + + QList::Iterator it; + for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + if ( testCancelled() ) + break; + QgsGeometry* g1 = it->feature.geometry(); + QgsRectangle bb = g1->boundingBox(); + QList crossingIds; + crossingIds = index->intersects( bb ); + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + bool touched = false; + for ( ; cit != crossingIdsEnd; ++cit ) + { + QgsFeature& f = mFeatureMap2[*cit].feature; + QgsGeometry* g2 = f.geometry(); + if ( !g2 || !g2->asGeos() ) + { + std::cout << "g2 or g2->asGeos() == NULL in covered\n" << std::flush; + continue; + } + QgsGeometry* startPoint = QgsGeometry::fromPoint( g2->asPolyline().first() ); + QgsGeometry* endPoint = QgsGeometry::fromPoint( g2->asPolyline().last() ); + + if ( g1->intersects( startPoint ) || g1->intersects( endPoint ) ) + { + touched = true; + break; + } + } + if ( !touched ) + { + QgsGeometry* conflictGeom = new QgsGeometry( *g1 ); + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + } + + QList fls; + fls << *it << *it; + //bb.scale(10); + + TopolErrorPointNotCoveredByLineEnds* err = new TopolErrorPointNotCoveredByLineEnds( bb, conflictGeom, fls ); + errorList << err; + } + } + return errorList; + +} + +ErrorList topolTest::checkyLineEndsCoveredByPoints(double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent) +{ + Q_UNUSED( tolerance ); + + int i = 0; + ErrorList errorList; + + + if ( layer1->geometryType() != QGis::Line ) + { + return errorList; + } + + if ( layer2->geometryType() != QGis::Point ) + { + return errorList; + } + + QgsSpatialIndex* index = mLayerIndexes[layer2->id()]; + + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + QList::Iterator it; + for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + if ( testCancelled() ) + break; + QgsGeometry* g1 = it->feature.geometry(); + + QgsGeometry* startPoint = QgsGeometry::fromPoint( g1->asPolyline().first() ); + QgsGeometry* endPoint = QgsGeometry::fromPoint( g1->asPolyline().last() ); + + QgsRectangle bb = g1->boundingBox(); + QList crossingIds; + crossingIds = index->intersects( bb ); + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + bool touched = false; + + bool touchStartPoint = false; + bool touchEndPoint = false; + + for ( ; cit != crossingIdsEnd; ++cit ) + { + QgsFeature& f = mFeatureMap2[*cit].feature; + QgsGeometry* g2 = f.geometry(); + if ( !g2 || !g2->asGeos() ) + { + std::cout << "g2 or g2->asGeos() == NULL in covered\n" << std::flush; + continue; + } + + + if (g2->intersects(startPoint)) + { + touchStartPoint = true; + } + + if(g2->intersects(endPoint)) + { + touchEndPoint = true ; + } + + if(touchStartPoint && touchEndPoint) + { + touched = true; + break; + } + + } + if ( !touched ) + { + QgsGeometry* conflictGeom = new QgsGeometry( *g1 ); + + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + if(canvasExtentPoly->crosses(conflictGeom)) + { + conflictGeom = conflictGeom->intersection(canvasExtentPoly); + } + } + QList fls; + fls << *it << *it; + //bb.scale(10); + + + TopolErrorLineEndsNotCoveredByPoints* err = new TopolErrorLineEndsNotCoveredByPoints( bb, conflictGeom, fls ); + errorList << err; + } + } + return errorList; + +} + +ErrorList topolTest::checkPointInPolygon( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + + int i = 0; + ErrorList errorList; + + if ( layer1->geometryType() != QGis::Point ) + { + return errorList; + } + + if ( layer2->geometryType() != QGis::Polygon ) + { + return errorList; + } + + QgsSpatialIndex* index = mLayerIndexes[layer2->id()]; + + QgsGeometry* canvasExtentPoly = QgsGeometry::fromWkt(theQgsInterface->mapCanvas()->extent().asWktPolygon()); + + QList::Iterator it; + for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + if ( testCancelled() ) + break; + QgsGeometry* g1 = it->feature.geometry(); + QgsRectangle bb = g1->boundingBox(); + QList crossingIds; + crossingIds = index->intersects( bb ); + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + bool touched = false; + for ( ; cit != crossingIdsEnd; ++cit ) + { + QgsFeature& f = mFeatureMap2[*cit].feature; + QgsGeometry* g2 = f.geometry(); + if ( !g2 || !g2->asGeos() ) + { + std::cout << "g2 or g2->asGeos() == NULL in covered\n" << std::flush; + continue; + } + if ( g2->contains( g1 ) ) + { + touched = true; + break; + } + } + if ( !touched ) + { + QgsGeometry* conflictGeom = new QgsGeometry( *g1 ); + + if(isExtent) + { + if(canvasExtentPoly->disjoint(conflictGeom)) + { + continue; + } + } + + QList fls; + fls << *it << *it; + //bb.scale(10); + + TopolErrorPointNotInPolygon* err = new TopolErrorPointNotInPolygon( bb, conflictGeom, fls ); + errorList << err; + } + } + return errorList; + +} + + +ErrorList topolTest::checkPolygonContainsPoint( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + + int i = 0; + ErrorList errorList; + + if ( layer1->geometryType() != QGis::Polygon ) + { + return errorList; + } + + if ( layer2->geometryType() != QGis::Point ) + { + return errorList; + } + + QgsSpatialIndex* index = mLayerIndexes[layer2->id()]; + + QList::Iterator it; + for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + if ( testCancelled() ) + break; + QgsGeometry* g1 = it->feature.geometry(); + QgsRectangle bb = g1->boundingBox(); + QList crossingIds; + crossingIds = index->intersects( bb ); + QList::Iterator cit = crossingIds.begin(); + QList::ConstIterator crossingIdsEnd = crossingIds.end(); + bool touched = false; + for ( ; cit != crossingIdsEnd; ++cit ) + { + QgsFeature& f = mFeatureMap2[*cit].feature; + QgsGeometry* g2 = f.geometry(); + if ( !g2 || !g2->asGeos() ) + { + std::cout << "g2 or g2->asGeos() == NULL in covered\n" << std::flush; + continue; + } + if ( g1->contains( g2 ) ) + { + touched = true; + break; + } + } + if ( !touched ) + { + QList fls; + fls << *it << *it; + //bb.scale(10); + QgsGeometry* conflict = new QgsGeometry( *g1 ); + TopolErrorPolygonContainsPoint* err = new TopolErrorPolygonContainsPoint( bb, conflict, fls ); + errorList << err; + } + } + return errorList; +} + +ErrorList topolTest::checkMultipart( double tolerance, QgsVectorLayer *layer1, QgsVectorLayer *layer2, bool isExtent ) +{ + Q_UNUSED( tolerance ); + Q_UNUSED( layer2 ); + Q_UNUSED( layer1 ); + int i = 0; + ErrorList errorList; + QList::Iterator it; + for ( it = mFeatureList1.begin(); it != mFeatureList1.end(); ++it ) + { + if ( !( ++i % 100 ) ) + emit progress( ++i ); + if ( testCancelled() ) + break; + QgsGeometry* g = it->feature.geometry(); + if ( !g ) + { + std::cout << "validity test: invalid QgsGeometry pointer\n" << std::flush; + continue; + } + if ( !g->asGeos() ) + continue; + if ( g->isMultipart() ) + { + QgsRectangle r = g->boundingBox(); + QList fls; + fls << *it << *it; + QgsGeometry* conflict = new QgsGeometry( *g ); + TopolErroMultiPart* err = new TopolErroMultiPart( r, conflict, fls ); + errorList << err; + } + } + return errorList; +} + +void topolTest::fillFeatureMap( QgsVectorLayer* layer, QgsRectangle extent ) +{ + QgsFeatureIterator fit; + if ( extent.isEmpty() ) + { + fit = layer->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ) ); + } + else + { + fit = layer->getFeatures( QgsFeatureRequest() + .setFilterRect( extent ) + .setFlags( QgsFeatureRequest::ExactIntersect ) + .setSubsetOfAttributes( QgsAttributeList() ) ); + } + + QgsFeature f; + + while ( fit.nextFeature( f ) ) + { + if ( f.geometry() ) + { + mFeatureMap2[f.id()] = FeatureLayer( layer, f ); + } + } +} + +void topolTest::fillFeatureList( QgsVectorLayer* layer, QgsRectangle extent ) +{ + QgsFeatureIterator fit; + if ( extent.isEmpty() ) + { + fit = layer->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ) ); + } + else + { + fit = layer->getFeatures( QgsFeatureRequest() + .setFilterRect( extent ) + .setFlags( QgsFeatureRequest::ExactIntersect ) + .setSubsetOfAttributes( QgsAttributeList() ) ); + } + + QgsFeature f; + + while ( fit.nextFeature( f ) ) + { + if ( f.geometry() ) + { + mFeatureList1 << FeatureLayer( layer, f ); + } + } + +} + +QgsSpatialIndex* topolTest::createIndex( QgsVectorLayer* layer, QgsRectangle extent ) +{ + QgsSpatialIndex* index = new QgsSpatialIndex(); + + QgsFeatureIterator fit; + if ( extent.isEmpty() ) + { + fit = layer->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ) ); + } + else + { + fit = layer->getFeatures( QgsFeatureRequest() + .setFilterRect( extent ) + .setFlags( QgsFeatureRequest::ExactIntersect ) + .setSubsetOfAttributes( QgsAttributeList() ) ); + } + + + int i = 0; + QgsFeature f; + while ( fit.nextFeature( f ) ) + { + if ( !( ++i % 100 ) ) + emit progress( i ); + + if ( testCancelled() ) + { + delete index; + return 0; + } + + if ( f.geometry() ) + { + index->insertFeature( f ); + mFeatureMap2[f.id()] = FeatureLayer( layer, f ); + } + } + + return index; +} + +ErrorList topolTest::runTest( QString testName, QgsVectorLayer* layer1, QgsVectorLayer* layer2, ValidateType type, double tolerance ) +{ + std::cout << testName.toStdString(); + ErrorList errors; + + if ( !layer1 ) + { + std::cout << "First layer not found in registry!\n" << std::flush; + return errors; + } + + if ( !layer2 && mTopologyRuleMap[testName].useSecondLayer ) + { + std::cout << "Second layer not found in registry!\n" << std::flush; + return errors; + } + + QString secondLayerId; + mFeatureList1.clear(); + mFeatureMap2.clear(); + + //checking if new features are not + //being recognised due to indexing not being upto date + + mLayerIndexes.clear(); + + if ( mTopologyRuleMap[testName].useSecondLayer ) + { + // validate all features or current extent + QgsRectangle extent; + if ( type == ValidateExtent ) + { + extent = theQgsInterface->mapCanvas()->extent(); + } + else + { + extent = QgsRectangle(); + } + + fillFeatureList(layer1,extent); + //fillFeatureMap( layer1, extent ); + + QString secondLayerId = layer2->id(); + + if ( !mLayerIndexes.contains( layer2->id() ) ) + { + mLayerIndexes[layer2->id()] = createIndex( layer2, extent ); + } + } + else + { + // validate all features or current extent + QgsRectangle extent; + if ( type == ValidateExtent ) + { + extent = theQgsInterface->mapCanvas()->extent(); + if ( mTopologyRuleMap[testName].useSpatialIndex ) + { + mLayerIndexes[layer1->id()] = createIndex( layer1, theQgsInterface->mapCanvas()->extent() ); + } + else + { + fillFeatureList( layer1, extent ); + } + } + else + { + if ( mTopologyRuleMap[testName].useSpatialIndex ) + { + if ( !mLayerIndexes.contains( layer1->id() ) ) + { + + mLayerIndexes[layer1->id()] = createIndex( layer1, QgsRectangle() ); + } + } + else + { + fillFeatureList( layer1, QgsRectangle() ); + } + } + + } + + //call test routine + bool isValidatingExtent; + if( type == ValidateExtent ) + { + isValidatingExtent = true; + } + else + { + isValidatingExtent = false; + } + + return ( this->*( mTopologyRuleMap[testName].f ) )( tolerance, layer1, layer2, isValidatingExtent ); +} diff --git a/src/plugins/topology/topolTest.h b/src/plugins/topology/topolTest.h new file mode 100644 index 000000000000..1dcf10fbcac4 --- /dev/null +++ b/src/plugins/topology/topolTest.h @@ -0,0 +1,280 @@ +/*************************************************************************** + topolTest.h + TOPOLogy checker + ------------------- + date : May 2009 + copyright : Vita Cizek + email : weetya (at) gmail.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 TOPOLTEST_H +#define TOPOLTEST_H + +#include + +#include +#include +#include "qgsspatialindex.h" + +#include "topolError.h" + +class topolTest; +class QgisInterface; +class WKTReader; + +enum ValidateType { ValidateAll, ValidateExtent, ValidateSelected }; + +typedef ErrorList( topolTest::*testFunction )( double, QgsVectorLayer*, QgsVectorLayer*,bool ); + +class TopologyRule +{ + public: + bool useSecondLayer; + bool useTolerance; + bool useSpatialIndex; + testFunction f; + + /** + * Constructor + * initializes the test to use both layers and not to use the tolerance + */ + TopologyRule() + { + useSecondLayer = true; + useTolerance = false; + f = 0; + } +}; + +/** + helper class to pass as comparator to map,set etc.. + */ +class PointComparer +{ + public: + bool operator()( QgsPoint p1, QgsPoint p2 )const + { + if ( p1.x() < p2.x() ) + { + return true; + } + + if ( p1.x() == p2.x() ) + { + if ( p1.y() < p2.y() ) + { + return true; + } + } + + return false; + } +}; + + +class topolTest: public QObject +{ + Q_OBJECT + + public: + topolTest( QgisInterface* qgsIface ); + ~topolTest(); + + /** + * Returns copy of the test map + */ + QMap testMap() { return mTopologyRuleMap; } + /** + * Runs the test and returns all found errors + * @param testName name of the test + * @param layer1 pointer to the first layer + * @param layer2 pointer to the second layer + * @param type type what features to validate + * @param tolerance possible tolerance + */ + ErrorList runTest( QString testName, QgsVectorLayer* layer1, QgsVectorLayer* layer2, ValidateType type, double tolerance ); + + /** + * Checks for intersections of the two layers + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 pointer to the second layer + */ + ErrorList checkOverlapWithLayer( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2 , bool isExtent); + /** + * Checks for self-intersections in the layer + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + ErrorList checkSelfIntersections( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + /** + * Checks for features that are too close + * @param tolerance allowed tolerance + * @param layer1 pointer to the first layer + * @param layer2 pointer to the second layer + */ + ErrorList checkCloseFeature( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + /** + * Checks for short segments + * @param tolerance tolerance - not used + * @param layer1 pointer to the first layer + * @param layer2 pointer to the second layer + */ + ErrorList checkSegmentLength( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + /** + * Checks for dangling lines + * @param tolerance allowed tolerance + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + ErrorList checkDanglingLines( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2 , bool isExtent); + /** + * Checks for points not covered by any segment + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 pointer to the second layer + */ + ErrorList checkPointCoveredBySegment( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + /** + * Checks for invalid geometries + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + ErrorList checkValid( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + + /** + * Checks for duplicate geometries + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + ErrorList checkDuplicates( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + + /** + * Checks for pseudo nodes + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + ErrorList checkPseudos( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + + /** + * Checks for overlaps of polygons from same layer + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + ErrorList checkOverlaps( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + + /** + * Checks for gaps among polygons from same layer + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + ErrorList checkGaps( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + + /** + * Checks for points form one layer that are not covered by line ends form another layer + * @param tolerance not used + * @param layer1 pointer to the first point layer + * @param layer2 pointer to the second line layer + */ + ErrorList checkPointCoveredByLineEnds( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + + /** + * Checks for points that are not inside any polygons from another layer + * @param tolerance not used + * @param layer1 pointer to the first point layer + * @param layer2 pointer to the second polygon layer + */ + ErrorList checkPointInPolygon( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2, bool isExtent ); + + /** + * Checks for polygons that do not contain any points form another layer + * @param tolerance not used + * @param layer1 pointer to the first polygon layer + * @param layer2 pointer to the second point layer + */ + ErrorList checkPolygonContainsPoint( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2,bool isExtent ); + + /** + * Checks for multipart features + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + ErrorList checkMultipart( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2,bool isExtent ); + + /** + * Checks for line features that do not have both ends covered by points from another layer + * @param tolerance not used + * @param layer1 pointer to the first linelayer + * @param layer2 pointer to the second point layer + */ + ErrorList checkyLineEndsCoveredByPoints( double tolerance, QgsVectorLayer* layer1, QgsVectorLayer* layer2,bool isExtent ); + + + public slots: + /** + * Checks for invalid geometries + * @param tolerance not used + * @param layer1 pointer to the first layer + * @param layer2 not used + */ + void setTestCancelled(); + + private: + QMap mLayerIndexes; + QMap mTopologyRuleMap; + + QList mFeatureList1; + QMap mFeatureMap2; + + QgisInterface* theQgsInterface; + bool mTestCancelled; + + /** + * Builds spatial index for the layer + * @param layer pointer to the layer + */ + QgsSpatialIndex* createIndex( QgsVectorLayer* layer, QgsRectangle extent ); + + /** + * Fills the feature list with features from the layer + * @param layer pointer to the layer + * @param extent of the layer to add features + */ + void fillFeatureList( QgsVectorLayer* layer, QgsRectangle extent ); + + /** + * Fills the feature map with features from the layer + * @param layer pointer to the layer + * @param extent of the layer to create index + */ + void fillFeatureMap( QgsVectorLayer* layer, QgsRectangle extent ); + + /** + * Returns true if the test was cancelled + */ + bool testCancelled(); + + signals: + /** + * Informs progress dialog about current status + * @param value process status + */ + void progress( int value ); +}; + +#endif diff --git a/src/plugins/topology/validateAll.png b/src/plugins/topology/validateAll.png new file mode 100644 index 000000000000..e16603f52a7e Binary files /dev/null and b/src/plugins/topology/validateAll.png differ diff --git a/src/plugins/topology/validateExtent.png b/src/plugins/topology/validateExtent.png new file mode 100644 index 000000000000..96290f06677c Binary files /dev/null and b/src/plugins/topology/validateExtent.png differ