Skip to content

Commit

Permalink
Implement zoom to feature
Browse files Browse the repository at this point in the history
  • Loading branch information
m-kuhn committed Oct 15, 2018
1 parent 4988382 commit dc2c78f
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/app/qgisapp.cpp
Expand Up @@ -932,7 +932,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
{
mInfoBar->pushWarning( tr( "Geometry Validation" ), message );
} );
mGeometryValidationDock = new QgsGeometryValidationDock( tr( "Geometry Validation" ) );
mGeometryValidationDock = new QgsGeometryValidationDock( tr( "Geometry Validation" ), mMapCanvas );
mGeometryValidationModel = new QgsGeometryValidationModel( mGeometryValidationService.get(), mGeometryValidationDock );
connect( this, &QgisApp::activeLayerChanged, mGeometryValidationModel, [this]( QgsMapLayer * layer )
{
Expand Down
37 changes: 33 additions & 4 deletions src/app/qgsgeometryvalidationdock.cpp
Expand Up @@ -15,20 +15,23 @@ email : matthias@opengis.ch

#include "qgsgeometryvalidationdock.h"
#include "qgsgeometryvalidationmodel.h"
#include "qgsmapcanvas.h"

#include <QButtonGroup>

QgsGeometryValidationDock::QgsGeometryValidationDock( const QString &title, QWidget *parent, Qt::WindowFlags flags )
QgsGeometryValidationDock::QgsGeometryValidationDock( const QString &title, QgsMapCanvas *mapCanvas, QWidget *parent, Qt::WindowFlags flags )
: QgsDockWidget( title, parent, flags )
, mMapCanvas( mapCanvas )
{
setupUi( this );

connect( mNextButton, &QPushButton::clicked, this, &QgsGeometryValidationDock::gotoNextError );
connect( mPreviousButton, &QPushButton::clicked, this, &QgsGeometryValidationDock::gotoPreviousError );
connect( mZoomToProblemButton, &QPushButton::clicked, this, &QgsGeometryValidationDock::zoomToProblem );
connect( mZoomToFeatureButton, &QPushButton::clicked, this, &QgsGeometryValidationDock::zoomToFeature );

onCurrentErrorChanged( QModelIndex(), QModelIndex() );
connect( mMapCanvas, &QgsMapCanvas::currentLayerChanged, this, &QgsGeometryValidationDock::updateLayerTransform );
connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsGeometryValidationDock::updateLayerTransform );
connect( mMapCanvas, &QgsMapCanvas::transformContextChanged, this, &QgsGeometryValidationDock::updateLayerTransform );
}

QgsGeometryValidationModel *QgsGeometryValidationDock::geometryValidationModel() const
Expand Down Expand Up @@ -59,16 +62,41 @@ void QgsGeometryValidationDock::gotoPreviousError()
void QgsGeometryValidationDock::zoomToProblem()
{
mLastZoomToAction = ZoomToProblem;
if ( !currentIndex().isValid() )
return;

QgsRectangle problemExtent = currentIndex().data( QgsGeometryValidationModel::ProblemExtentRole ).value<QgsRectangle>();
QgsRectangle mapExtent = mLayerTransform.transform( problemExtent );
mMapCanvas->zoomToFeatureExtent( mapExtent );
}

void QgsGeometryValidationDock::zoomToFeature()
{
mLastZoomToAction = ZoomToFeature;
// mErrorListView->currentIndex().data( )
if ( !currentIndex().isValid() )
return;

QgsRectangle featureExtent = currentIndex().data( QgsGeometryValidationModel::FeatureExtentRole ).value<QgsRectangle>();
QgsRectangle mapExtent = mLayerTransform.transform( featureExtent );
mMapCanvas->zoomToFeatureExtent( mapExtent );
}

void QgsGeometryValidationDock::updateLayerTransform()
{
if ( !mMapCanvas->currentLayer() )
return;

mLayerTransform = QgsCoordinateTransform( mMapCanvas->currentLayer()->crs(), mMapCanvas->mapSettings().destinationCrs(), mMapCanvas->mapSettings().transformContext() );
}

QModelIndex QgsGeometryValidationDock::currentIndex() const
{
return mErrorListView->selectionModel()->currentIndex();
}

void QgsGeometryValidationDock::onCurrentErrorChanged( const QModelIndex &current, const QModelIndex &previous )
{
Q_UNUSED( previous )
mNextButton->setEnabled( current.isValid() && current.row() < mGeometryValidationModel->rowCount() - 1 );
mPreviousButton->setEnabled( current.isValid() && current.row() > 0 );

Expand All @@ -83,5 +111,6 @@ void QgsGeometryValidationDock::onCurrentErrorChanged( const QModelIndex &curren

case ZoomToFeature:
zoomToFeature();
break;
}
}
8 changes: 7 additions & 1 deletion src/app/qgsgeometryvalidationdock.h
Expand Up @@ -18,7 +18,9 @@ email : matthias@opengis.ch

#include "ui_qgsgeometryvalidationdockbase.h"
#include "qgsdockwidget.h"
#include "qgscoordinatetransform.h"

class QgsMapCanvas;
class QgsGeometryValidationModel;

/**
Expand All @@ -29,7 +31,7 @@ class QgsGeometryValidationDock : public QgsDockWidget, public Ui_QgsGeometryVal
Q_OBJECT

public:
QgsGeometryValidationDock( const QString &title, QWidget *parent = nullptr, Qt::WindowFlags flags = nullptr );
QgsGeometryValidationDock( const QString &title, QgsMapCanvas *mapCanvas, QWidget *parent = nullptr, Qt::WindowFlags flags = nullptr );

QgsGeometryValidationModel *geometryValidationModel() const;
void setGeometryValidationModel( QgsGeometryValidationModel *geometryValidationModel );
Expand All @@ -40,6 +42,7 @@ class QgsGeometryValidationDock : public QgsDockWidget, public Ui_QgsGeometryVal
void gotoPreviousError();
void zoomToProblem();
void zoomToFeature();
void updateLayerTransform();

private:
enum ZoomToAction
Expand All @@ -50,6 +53,9 @@ class QgsGeometryValidationDock : public QgsDockWidget, public Ui_QgsGeometryVal
ZoomToAction mLastZoomToAction = ZoomToFeature;
QgsGeometryValidationModel *mGeometryValidationModel = nullptr;
QButtonGroup *mZoomToButtonGroup = nullptr;
QgsMapCanvas *mMapCanvas = nullptr;
QgsCoordinateTransform mLayerTransform;
QModelIndex currentIndex() const;
};

#endif // QGSGEOMETRYVALIDATIONPANEL_H
34 changes: 32 additions & 2 deletions src/app/qgsgeometryvalidationmodel.cpp
Expand Up @@ -11,7 +11,8 @@ QgsGeometryValidationModel::QgsGeometryValidationModel( QgsGeometryValidationSer
{
connect( mGeometryValidationService, &QgsGeometryValidationService::geometryCheckCompleted, this, &QgsGeometryValidationModel::onGeometryCheckCompleted );
connect( mGeometryValidationService, &QgsGeometryValidationService::geometryCheckStarted, this, &QgsGeometryValidationModel::onGeometryCheckStarted );
connect( mGeometryValidationService, &QgsGeometryValidationService::topologyChecksUpdated, this, &QgsGeometryValidationModel::onTopologyChecksUpdated, Qt::QueuedConnection );
connect( mGeometryValidationService, &QgsGeometryValidationService::topologyChecksUpdated, this, &QgsGeometryValidationModel::onTopologyChecksUpdated );
connect( mGeometryValidationService, &QgsGeometryValidationService::topologyChecksCleared, this, &QgsGeometryValidationModel::onTopologyChecksCleared );
}

QModelIndex QgsGeometryValidationModel::index( int row, int column, const QModelIndex &parent ) const
Expand Down Expand Up @@ -61,6 +62,18 @@ QVariant QgsGeometryValidationModel::data( const QModelIndex &index, int role )

return tr( "%1: %2" ).arg( featureTitle, topologyError->description() );
}

case FeatureExtentRole:
{
const QgsFeatureId fid = topologyError->featureId();
const QgsFeature feature = mCurrentLayer->getFeature( fid ); // TODO: this should be cached!
return feature.geometry().boundingBox();
}

case ProblemExtentRole:
{
return topologyError->affectedAreaBBox();
}
}
}
else
Expand Down Expand Up @@ -195,7 +208,7 @@ void QgsGeometryValidationModel::onTopologyChecksUpdated( QgsVectorLayer *layer,

if ( layer == currentLayer() )
{
const int oldRowCount = rowCount( QModelIndex() );
const int oldRowCount = rowCount();
beginInsertRows( QModelIndex(), oldRowCount, oldRowCount + errors.size() );
}

Expand All @@ -207,6 +220,23 @@ void QgsGeometryValidationModel::onTopologyChecksUpdated( QgsVectorLayer *layer,
}
}

void QgsGeometryValidationModel::onTopologyChecksCleared( QgsVectorLayer *layer )
{
auto &topologyLayerErrors = mTopologyErrorStorage[layer];
if ( topologyLayerErrors.empty() )
return;

if ( layer == currentLayer() )
{
beginRemoveRows( QModelIndex(), mErrorStorage.size(), rowCount() - 1 );
}
topologyLayerErrors.clear();
if ( layer == currentLayer() )
{
endRemoveRows();
}
}

int QgsGeometryValidationModel::errorsForFeature( QgsVectorLayer *layer, QgsFeatureId fid )
{
const auto &layerErrors = mErrorStorage[layer];
Expand Down
1 change: 1 addition & 0 deletions src/app/qgsgeometryvalidationmodel.h
Expand Up @@ -36,6 +36,7 @@ class QgsGeometryValidationModel : public QAbstractItemModel
void onGeometryCheckCompleted( QgsVectorLayer *layer, QgsFeatureId fid, const QList<std::shared_ptr<QgsSingleGeometryCheckError> > &errors );
void onGeometryCheckStarted( QgsVectorLayer *layer, QgsFeatureId fid );
void onTopologyChecksUpdated( QgsVectorLayer *layer, const QList<std::shared_ptr<QgsGeometryCheckError> > &errors );
void onTopologyChecksCleared( QgsVectorLayer *layer );

private:
struct FeatureErrors
Expand Down
37 changes: 31 additions & 6 deletions src/app/qgsgeometryvalidationservice.cpp
Expand Up @@ -196,11 +196,24 @@ void QgsGeometryValidationService::processFeature( QgsVectorLayer *layer, QgsFea

void QgsGeometryValidationService::triggerTopologyChecks( QgsVectorLayer *layer )
{
emit topologyChecksCleared( layer );

QFutureWatcher<void> *futureWatcher = mLayerCheckStates[layer].topologyCheckFutureWatcher;
if ( futureWatcher )
{
// TODO: kill!!
delete futureWatcher;
// Make sure no more checks are started first
futureWatcher->cancel();

// Tell all checks to stop asap
const auto feedbacks = mLayerCheckStates[layer].topologyCheckFeedbacks;
for ( QgsFeedback *feedback : feedbacks )
{
if ( feedback )
feedback->cancel();
}

// The future watcher will take care of deleting
mLayerCheckStates[layer].topologyCheckFeedbacks.clear();
}

QgsFeatureIds checkFeatureIds = layer->editBuffer()->changedGeometries().keys().toSet();
Expand All @@ -209,7 +222,6 @@ void QgsGeometryValidationService::triggerTopologyChecks( QgsVectorLayer *layer
// TODO: ownership of these objects...
QgsVectorLayerFeaturePool *featurePool = new QgsVectorLayerFeaturePool( layer );
QList<QgsGeometryCheckError *> &allErrors = mLayerCheckStates[layer].topologyCheckErrors;
QgsFeedback *feedback = new QgsFeedback();
QMap<QString, QgsFeatureIds> layerIds;
layerIds.insert( layer->id(), checkFeatureIds );
QgsGeometryCheck::LayerFeatureIds layerFeatureIds( layerIds );
Expand All @@ -219,12 +231,19 @@ void QgsGeometryValidationService::triggerTopologyChecks( QgsVectorLayer *layer

const QList<QgsGeometryCheck *> checks = mLayerCheckStates[layer].topologyChecks;

QFuture<void> future = QtConcurrent::map( checks, [featurePools, &allErrors, feedback, layerFeatureIds, layer, this]( const QgsGeometryCheck * check )
QMap<const QgsGeometryCheck *, QgsFeedback *> feedbacks;
for ( QgsGeometryCheck *check : checks )
feedbacks.insert( check, new QgsFeedback() );

mLayerCheckStates[layer].topologyCheckFeedbacks = feedbacks.values();

QFuture<void> future = QtConcurrent::map( checks, [featurePools, &allErrors, layerFeatureIds, layer, feedbacks, this]( const QgsGeometryCheck * check )
{
// Watch out with the layer pointer in here. We are running in a thread, so we do not want to actually use it
// except for using its address to report the error.
QList<QgsGeometryCheckError *> errors;
QStringList messages; // Do we really need these?
QgsFeedback *feedback = feedbacks.value( check );

check->collectErrors( featurePools, errors, messages, feedback, layerFeatureIds );
QgsReadWriteLocker errorLocker( mTopologyCheckLock, QgsReadWriteLocker::Write );
Expand All @@ -235,18 +254,24 @@ void QgsGeometryValidationService::triggerTopologyChecks( QgsVectorLayer *layer
{
sharedErrors.append( std::shared_ptr<QgsGeometryCheckError>( error ) );
}
emit topologyChecksUpdated( layer, sharedErrors );
if ( !feedback->isCanceled() )
emit topologyChecksUpdated( layer, sharedErrors );

errorLocker.unlock();
} );

futureWatcher = new QFutureWatcher<void>();
futureWatcher->setFuture( future );

connect( futureWatcher, &QFutureWatcherBase::finished, this, [&allErrors, layer, this]()
connect( futureWatcher, &QFutureWatcherBase::finished, this, [&allErrors, layer, feedbacks, futureWatcher, this]()
{
QgsReadWriteLocker errorLocker( mTopologyCheckLock, QgsReadWriteLocker::Read );
layer->setAllowCommit( allErrors.empty() );
errorLocker.unlock();
qDeleteAll( feedbacks.values() );
futureWatcher->deleteLater();
if ( mLayerCheckStates[layer].topologyCheckFutureWatcher == futureWatcher )
mLayerCheckStates[layer].topologyCheckFutureWatcher = nullptr;
} );

mLayerCheckStates[layer].topologyCheckFutureWatcher = futureWatcher;
Expand Down
6 changes: 4 additions & 2 deletions src/app/qgsgeometryvalidationservice.h
Expand Up @@ -30,7 +30,7 @@ class QgsGeometryCheck;
class QgsSingleGeometryCheck;
class QgsSingleGeometryCheckError;
class QgsGeometryCheckError;

class QgsFeedback;

/**
* This service connects to all layers in a project and triggers validation
Expand Down Expand Up @@ -68,6 +68,7 @@ class QgsGeometryValidationService : public QObject
void geometryCheckStarted( QgsVectorLayer *layer, QgsFeatureId fid );
void geometryCheckCompleted( QgsVectorLayer *layer, QgsFeatureId fid, const QList<std::shared_ptr<QgsSingleGeometryCheckError>> &errors );
void topologyChecksUpdated( QgsVectorLayer *layer, const QList<std::shared_ptr<QgsGeometryCheckError> > &errors );
void topologyChecksCleared( QgsVectorLayer *layer );

void warning( const QString &message );

Expand All @@ -92,8 +93,9 @@ class QgsGeometryValidationService : public QObject
struct VectorCheckState
{
QList< QgsSingleGeometryCheck * > singleFeatureChecks;
QList< QgsGeometryCheck * > topologyChecks;
QList< QgsGeometryCheck *> topologyChecks;
QFutureWatcher<void> *topologyCheckFutureWatcher = nullptr;
QList<QgsFeedback *> topologyCheckFeedbacks; // will be deleted when topologyCheckFutureWatcher is delteed
QList<QgsGeometryCheckError *> topologyCheckErrors;
};

Expand Down

0 comments on commit dc2c78f

Please sign in to comment.