From 723d597a78e5c8f5158b8fc6e82050dac25ab9b6 Mon Sep 17 00:00:00 2001 From: vinayan Date: Wed, 12 Dec 2012 20:14:14 +0530 Subject: [PATCH] [Feature] Topology Cheker Plugin. added files for topology plugin modified cmakelists to add topology plugin folder Conflicts: src/plugins/CMakeLists.txt fixed issues in duplicate rule where zooming was not possible..renamed 'Test' to 'Rule' fixed minimum segment length rule renamed rules to meaningful descriptions Added new rule "Must not have pseudos" for line vector layers. Renamed rules to more understandable descriptions. Handled WKB::MultilineStrings in line shapefiles in hanging lines rule that caused crashes. "segments must have minimum length" rule now only checks if the geometry contains at least one short segment added new Rule "Must Not Have Gaps" removed unwanted indexing for some rules. prepare commit run. test commit modified 'must not have gaps' rule. updated calls to new vector api more changes based on new api fixed "must not have gaps rule" fixed extent issues changed dangle rule implementation More rules added -checkPointCoveredbyLineEnds -checkPointInPolygon new rules added -point must be inside polygon -polygon must contain point -geometry must not be multipart added rule 'checkyLineEndsCoveredByPoints' renamed some rules commented 'closer than tolerance rules' -performance issues changed colors of error markers changed visualization style of errors changed rule paramters validateExtent fixed to show only errors from within canvas extent removed rule - polygons must contain feature removed direct calls to geos api prepare commit run fixed missing icon in plugin manager icons added. validateSelected option removed --- src/plugins/CMakeLists.txt | 1 + src/plugins/topology/CMakeLists.txt | 62 + src/plugins/topology/checkDock.cpp | 458 ++++++ src/plugins/topology/checkDock.h | 146 ++ src/plugins/topology/checkDock.ui | 89 ++ src/plugins/topology/configureRules.png | Bin 0 -> 3902 bytes src/plugins/topology/dockModel.cpp | 125 ++ src/plugins/topology/dockModel.h | 91 ++ src/plugins/topology/geosFunctions.h | 123 ++ src/plugins/topology/rulesDialog.cpp | 274 ++++ src/plugins/topology/rulesDialog.h | 100 ++ src/plugins/topology/rulesDialog.ui | 198 +++ src/plugins/topology/topol.cpp | 178 +++ src/plugins/topology/topol.h | 111 ++ src/plugins/topology/topol.png | Bin 0 -> 2159 bytes src/plugins/topology/topol.qrc | 8 + src/plugins/topology/topolError.cpp | 229 +++ src/plugins/topology/topolError.h | 233 ++++ src/plugins/topology/topolTest.cpp | 1700 +++++++++++++++++++++++ src/plugins/topology/topolTest.h | 280 ++++ src/plugins/topology/validateAll.png | Bin 0 -> 2985 bytes src/plugins/topology/validateExtent.png | Bin 0 -> 3284 bytes 22 files changed, 4406 insertions(+) create mode 100644 src/plugins/topology/CMakeLists.txt create mode 100644 src/plugins/topology/checkDock.cpp create mode 100644 src/plugins/topology/checkDock.h create mode 100644 src/plugins/topology/checkDock.ui create mode 100644 src/plugins/topology/configureRules.png create mode 100644 src/plugins/topology/dockModel.cpp create mode 100644 src/plugins/topology/dockModel.h create mode 100644 src/plugins/topology/geosFunctions.h create mode 100644 src/plugins/topology/rulesDialog.cpp create mode 100644 src/plugins/topology/rulesDialog.h create mode 100644 src/plugins/topology/rulesDialog.ui create mode 100644 src/plugins/topology/topol.cpp create mode 100644 src/plugins/topology/topol.h create mode 100644 src/plugins/topology/topol.png create mode 100644 src/plugins/topology/topol.qrc create mode 100644 src/plugins/topology/topolError.cpp create mode 100644 src/plugins/topology/topolError.h create mode 100644 src/plugins/topology/topolTest.cpp create mode 100644 src/plugins/topology/topolTest.h create mode 100644 src/plugins/topology/validateAll.png create mode 100644 src/plugins/topology/validateExtent.png 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 0000000000000000000000000000000000000000..4835591830972560db8496f01d4349a1c309d176 GIT binary patch literal 3902 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 M07*qoM6N<$f`V>wS^xk5 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..25f809cd94b8983c3674df8227aeb0c9b1a651aa GIT binary patch literal 2159 zcmV-#2$1)QP)o!Qx${hj^&&S%frv*-MFS4b)GM+qT%1B*Iz=#T^i0An>~BB?sBM9=&An(Ei8nH#OOk8rKrHbz?7?3uUdkF zf*KgFT)9G6SeSIX-OGRrP55$2DYNSxY2*rD0%Timk&%(|ehLcId-*J;*Cuu@ zC~6ZI&x=@42y!$LFRt4w}b9%aM&Dp{uZ#=AE>Tg(YOEM;){dH z&dw$}I=X>*`}XbJcGSS~-4NK#+|Q|9jRGs)tgBZPItV2pg{cYC*|B2>PNz}ns;Vl| z)6;oj;fvJHTy=lg`>3%&>k{w?yQ|x#h5s*Q*B?>#Z9r26UAuLotOQ2 zrX4)f1*$44q?BFi9yagNZ7g@vYTQNV(R255`yhU%v&$aT`8A3>|~u z`9HT9^p5=R9=@Yb4nj>8M9lCSEci3z}|?6-IaN+#QBDd^EL0{t2AB z4_q}c?rBiHJIwee@Ugdz{o~&4&ft&0m-FDu6VP`U3>oKj_x=aGd#$m5>h~ao*WLRU z@HhGJ=^+T|50QyJ3E^MBd)9%J@IT8WWAFi@@&NbXwa3e|;7*w?fCxFm18V!?o`6 zP)aEN(%5(H1p!?kJsE0scz&bNrG2bc0~Y&U#g?^|n(vJKxuRN7`VK`eI&UoQW9PO? zJzAwpt6QGKzUO5&tXRsmYu6|(EhYQthy0>XXKb1ViGOH~Y5=W~(<(TLvND>F&*Tvv z9$wGnbUKNPkLPrmifi?emdC0qlbqJw78XKG(ll+arfCE0b~~1hiE!ami{o04yw1~! zi49-@Y&IKf*Q~*H_CvUNwZ*ZTBa0A1D2nomqA1H}%$Ol3PoAtwDLME-7Fp{PaKtZ0 zCcWfw0JZWa1N)CQwGSLP&=|xj%b{BzkK;5~79dGg)zkwA4k&SPad!>L$;li(e3)qo z)6qi*!T4DZ?p;b;!onBl^G%Bs6ciY%q9D)m%nvGIx7!Qm&!0aeJw3hAwOGp1rDPn) z!MWxHbO>zROZ5*>Q3AJ0jD7ic#_jM~7F7HfMN!Di%%tuG1*oj7Bq}DBbC%xdse8Oj zW|o)Mb-iCqOpNKmGkU@Vwrt)ENkL$-85#eepBSl^JCLrL#!pr)Ff0mb)tHtrou$8B zLQ+x^fq{YK=H{|y{RRpv9O$dx^DgXtWD!E>Wo2ci1xiaxQEeLfPlv*-?~SeOx?xl; z27FL=BjMJ$hp`Xp4!sA#$Vt$t3lh+ezr&{88%aw`1HfvvQX4)6_lmb#Wx{bsLRHmm z!-o%_lb4rgv)OF*jJmG#*ysofdXL3DZ_AGcM_8aIk z*to|JzUC2+rxH?1gb)KXP21yiI){gbhN7yfC@wC>*8fqQssDtKe%_D#O+G9eW2}Lf zy$55T@UDN87Opqb5<-jt_5&fL<-*_y?^fboo!kkp%>lO?=B2}wUwhAYQ!_09DdicU z0)T#xdM~({#?OM4hrr$mwk?8};-KifPeZo-dU)g{Sa%Zo{v6I8fh8kh^K)>esqa-h zlmEOBch?%A&KllQ`aU1^i27I&!F6DxB8^^nb zz_>(sJP8JmZQ4(}0j{gE8*pYnS`~`aSZCJiJqqJWuAQ&bsFb24_C!x;}`0N8%Jj`hQW~%)F*VWG=A^n_@ z8gf*^pW=;f-wd@+U@%s0&%O+c{%*|c)1HD)4mEjN`vtzE=ww*7)3~QM{mOVpbsx2V zU@%7OnJeLTIjnyYoDS2X4+k(n(kdgrUHAnY+H6|v;QFh2r;5= zv4;m3z}5j<*Pc-Q10mfy)3ayKj=;HqfPgF^L>N3wz$L|4Q^zoR=s+%CzRdOO*ST@y z2Ko8m6RrhX zFK_B4S*_O8qeqYSnmBP{qxPJf9BH*$zi#vYl)SY8$(p9QjvqhX;4he*oE&Mh*<8Sr ze)(sbr&3jQqoOEFX3d&KY;3Hmsw!DoS+zTN?nFwt$?bNhK2*Q}LWp=x(^7;GBiwGc lO$c$Krlw|_l=2Y3{{Ss^iXT(km_z^o002ovPDHLkV1o3N8p!|v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e16603f52a7eaa58b092a2bfae142183858a86b3 GIT binary patch literal 2985 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 literal 0 HcmV?d00001 diff --git a/src/plugins/topology/validateExtent.png b/src/plugins/topology/validateExtent.png new file mode 100644 index 0000000000000000000000000000000000000000..96290f06677ca22aa65cfd77919ffe946ead91bb GIT binary patch literal 3284 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 S;50S>0000