Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add an explicit "Add Layers" button to elevation profile dock
This provides a user-friendly why of adding new layers to a plot -
clicking it will show a filtered list of possible layers which
can be added to the plot, but which currently aren't in the plot.

(I.e it will include all raster layers from the project which
aren't marked as having elevation data.)

Selecting layers will cause them to automatically be marked as
having elevation data and immediately added to the plot.
  • Loading branch information
nyalldawson committed May 23, 2023
1 parent d0f61c9 commit 29f0d47
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 1 deletion.
16 changes: 16 additions & 0 deletions python/core/auto_generated/qgselevationutils.sip.in
Expand Up @@ -28,6 +28,22 @@ Calculates the elevation range for a ``project``.

This method considers the elevation (or z) range available from layers contained within the project and
returns the maximal combined elevation range of these layers.
%End

static bool canEnableElevationForLayer( QgsMapLayer *layer );
%Docstring
Returns ``True`` if elevation can be enabled for a map ``layer``.

.. versionadded:: 3.32
%End

static bool enableElevationForLayer( QgsMapLayer *layer );
%Docstring
Automatically enables elevation for a map ``layer``, using reasonable defaults.

Returns ``True`` if the elevation was enabled successfully.

.. versionadded:: 3.32
%End

};
Expand Down
125 changes: 125 additions & 0 deletions src/app/elevation/qgselevationprofilewidget.cpp
Expand Up @@ -20,6 +20,8 @@
#include "qgselevationprofilecanvas.h"
#include "qgsdockablewidgethelper.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayerelevationproperties.h"
#include "qgsmaplayermodel.h"
#include "qgsmaptoolprofilecurve.h"
#include "qgsmaptoolprofilecurvefromfeature.h"
#include "qgsrubberband.h"
Expand Down Expand Up @@ -47,6 +49,8 @@
#include "qgselevationprofiletoolmeasure.h"
#include "qgssettingsentryimpl.h"
#include "qgssettingstree.h"
#include "qgsmaplayerproxymodel.h"
#include "qgselevationutils.h"


#include <QToolBar>
Expand All @@ -61,6 +65,71 @@ const QgsSettingsEntryDouble *QgsElevationProfileWidget::settingTolerance = new
const QgsSettingsEntryBool *QgsElevationProfileWidget::settingShowLayerTree = new QgsSettingsEntryBool( QStringLiteral( "show-layer-tree" ), QgsSettingsTree::sTreeElevationProfile, true, QStringLiteral( "Whether the layer tree should be shown for elevation profile plots" ) );
const QgsSettingsEntryBool *QgsElevationProfileWidget::settingLockAxis = new QgsSettingsEntryBool( QStringLiteral( "lock-axis-ratio" ), QgsSettingsTree::sTreeElevationProfile, false, QStringLiteral( "Whether the the distance and elevation axis scales are locked to each other" ) );

//
// QgsElevationProfileLayersDialog
//

QgsElevationProfileLayersDialog::QgsElevationProfileLayersDialog( QWidget *parent )
: QDialog( parent )
{
setupUi( this );
QgsGui::enableAutoGeometryRestore( this );

mFilterLineEdit->setShowClearButton( true );
mFilterLineEdit->setShowSearchIcon( true );

mModel = new QgsMapLayerProxyModel( listMapLayers );
listMapLayers->setModel( mModel );
const QModelIndex firstLayer = mModel->index( 0, 0 );
listMapLayers->selectionModel()->select( firstLayer, QItemSelectionModel::Select );

connect( listMapLayers, &QListView::doubleClicked, this, &QgsElevationProfileLayersDialog::accept );

connect( mFilterLineEdit, &QLineEdit::textChanged, mModel, &QgsMapLayerProxyModel::setFilterString );
connect( mCheckBoxVisibleLayers, &QCheckBox::toggled, this, &QgsElevationProfileLayersDialog::filterVisible );

mFilterLineEdit->setFocus();
}

void QgsElevationProfileLayersDialog::setVisibleLayers( const QList<QgsMapLayer *> &layers )
{
mVisibleLayers = layers;
}

void QgsElevationProfileLayersDialog::setHiddenLayers( const QList<QgsMapLayer *> &layers )
{
mModel->setExceptedLayerList( layers );
}

QList< QgsMapLayer *> QgsElevationProfileLayersDialog::selectedLayers() const
{
QList< QgsMapLayer * > layers;

const QModelIndexList selection = listMapLayers->selectionModel()->selectedIndexes();
for ( const QModelIndex &index : selection )
{
const QModelIndex sourceIndex = mModel->mapToSource( index );
if ( !sourceIndex.isValid() )
{
continue;
}

QgsMapLayer *layer = mModel->sourceLayerModel()->layerFromIndex( sourceIndex );
if ( layer )
layers << layer;
}
return layers;
}

void QgsElevationProfileLayersDialog::filterVisible( bool enabled )
{
if ( enabled )
mModel->setLayerAllowlist( mVisibleLayers );
else
mModel->setLayerAllowlist( QList< QgsMapLayer * >() );
}


QgsElevationProfileWidget::QgsElevationProfileWidget( const QString &name )
: QWidget( nullptr )
, mCanvasName( name )
Expand Down Expand Up @@ -103,6 +172,11 @@ QgsElevationProfileWidget::QgsElevationProfileWidget( const QString &name )

mCanvas->setTool( mIdentifyTool );

QAction *addLayerAction = new QAction( tr( "Add Layers" ), this );
addLayerAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionAddLayer.svg" ) ) );
connect( addLayerAction, &QAction::triggered, this, &QgsElevationProfileWidget::addLayers );
toolBar->addAction( addLayerAction );

QAction *showLayerTree = new QAction( tr( "Show Layer Tree" ), this );
showLayerTree->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconLayerTree.svg" ) ) );
showLayerTree->setCheckable( true );
Expand Down Expand Up @@ -386,13 +460,64 @@ void QgsElevationProfileWidget::cancelJobs()
mCanvas->cancelJobs();
}

void QgsElevationProfileWidget::addLayers()
{
QgsElevationProfileLayersDialog addDialog( this );
const QMap<QString, QgsMapLayer *> allMapLayers = QgsProject::instance()->mapLayers();

// The add layers dialog should only show layers which CAN have elevation, yet currently don't
// have it enabled. So collect layers which don't match this criteria now for filtering out.
QList< QgsMapLayer * > layersWhichAlreadyHaveElevationOrCannotHaveElevation;
for ( auto it = allMapLayers.constBegin(); it != allMapLayers.constEnd(); ++it )
{
if ( !QgsElevationUtils::canEnableElevationForLayer( it.value() ) || it.value()->elevationProperties()->hasElevation() )
{
layersWhichAlreadyHaveElevationOrCannotHaveElevation << it.value();
continue;
}
}
addDialog.setHiddenLayers( layersWhichAlreadyHaveElevationOrCannotHaveElevation );

addDialog.setVisibleLayers( mMainCanvas->layers( true ) );

if ( addDialog.exec() == QDialog::Accepted )
{
const QList<QgsMapLayer *> layers = addDialog.selectedLayers();
QList< QgsMapLayer * > updatedLayers;
if ( !layers.empty() )
{
for ( QgsMapLayer *layer : layers )
{
if ( QgsElevationUtils::enableElevationForLayer( layer ) )
updatedLayers << layer;
}

mLayerTreeView->proxyModel()->invalidate();
for ( QgsMapLayer *layer : std::as_const( updatedLayers ) )
{
if ( QgsLayerTreeLayer *node = mLayerTree->findLayer( layer ) )
{
node->setItemVisibilityChecked( true );
}
}

updateCanvasLayers();
scheduleUpdate();
}
}
}

void QgsElevationProfileWidget::updateCanvasLayers()
{
QList<QgsMapLayer *> layers;
const QList< QgsMapLayer * > layerOrder = mLayerTree->layerOrder();
layers.reserve( layerOrder.size() );
for ( QgsMapLayer *layer : layerOrder )
{
// safety check. maybe elevation properties have been disabled externally.
if ( !layer->elevationProperties() || !layer->elevationProperties()->hasElevation() )
continue;

if ( mLayerTree->findLayer( layer )->isVisible() )
layers << layer;
}
Expand Down
23 changes: 23 additions & 0 deletions src/app/elevation/qgselevationprofilewidget.h
Expand Up @@ -23,6 +23,7 @@
#include "qgsgeometry.h"
#include "qobjectuniqueptr.h"
#include "qgselevationprofilelayertreeview.h"
#include "ui_qgselevationprofileaddlayersdialogbase.h"

#include <QWidgetAction>
#include <QElapsedTimer>
Expand Down Expand Up @@ -50,6 +51,7 @@ class QLabel;
class QgsProfilePoint;
class QgsSettingsEntryDouble;
class QgsSettingsEntryBool;
class QgsMapLayerProxyModel;

class QgsAppElevationProfileLayerTreeView : public QgsElevationProfileLayerTreeView
{
Expand All @@ -63,6 +65,26 @@ class QgsAppElevationProfileLayerTreeView : public QgsElevationProfileLayerTreeV
void contextMenuEvent( QContextMenuEvent *event ) override;
};

class QgsElevationProfileLayersDialog: public QDialog, private Ui::QgsElevationProfileAddLayersDialogBase
{
Q_OBJECT

public:
QgsElevationProfileLayersDialog( QWidget *parent = nullptr );
void setVisibleLayers( const QList<QgsMapLayer *> &layers );
void setHiddenLayers( const QList<QgsMapLayer *> &layers );
QList< QgsMapLayer * > selectedLayers() const;

private slots:

void filterVisible( bool enabled );

private:

QgsMapLayerProxyModel *mModel = nullptr;
QList< QgsMapLayer * > mVisibleLayers;
};

class QgsElevationProfileWidget : public QWidget
{
Q_OBJECT
Expand Down Expand Up @@ -93,6 +115,7 @@ class QgsElevationProfileWidget : public QWidget
void toggleDockModeRequested( bool docked );

private slots:
void addLayers();
void updateCanvasLayers();
void onTotalPendingJobsCountChanged( int count );
void setProfileCurve( const QgsGeometry &curve, bool resetView );
Expand Down
36 changes: 35 additions & 1 deletion src/core/qgselevationutils.cpp
Expand Up @@ -16,7 +16,7 @@
#include "qgselevationutils.h"
#include "qgsproject.h"
#include "qgsmaplayerelevationproperties.h"

#include "qgsrasterlayerelevationproperties.h"

QgsDoubleRange QgsElevationUtils::calculateZRangeForProject( QgsProject *project )
{
Expand Down Expand Up @@ -54,3 +54,37 @@ QgsDoubleRange QgsElevationUtils::calculateZRangeForProject( QgsProject *project
std::isnan( max ) ? std::numeric_limits< double >::max() : max );
}

bool QgsElevationUtils::canEnableElevationForLayer( QgsMapLayer *layer )
{
return static_cast< bool >( layer->elevationProperties() );
}

bool QgsElevationUtils::enableElevationForLayer( QgsMapLayer *layer )
{
switch ( layer->type() )
{
case Qgis::LayerType::Raster:
{
if ( QgsRasterLayerElevationProperties *properties = qobject_cast<QgsRasterLayerElevationProperties * >( layer->elevationProperties() ) )
{
properties->setEnabled( true );
// This could potentially be made smarter, eg by checking the data type of bands. But that's likely overkill..!
properties->setBandNumber( 1 );
return true;
}
break;
}

// can't automatically enable elevation for these layer types
case Qgis::LayerType::Vector:
case Qgis::LayerType::Plugin:
case Qgis::LayerType::Mesh:
case Qgis::LayerType::VectorTile:
case Qgis::LayerType::Annotation:
case Qgis::LayerType::PointCloud:
case Qgis::LayerType::Group:
break;
}
return false;
}

16 changes: 16 additions & 0 deletions src/core/qgselevationutils.h
Expand Up @@ -40,6 +40,22 @@ class CORE_EXPORT QgsElevationUtils
*/
static QgsDoubleRange calculateZRangeForProject( QgsProject *project );

/**
* Returns TRUE if elevation can be enabled for a map \a layer.
*
* \since QGIS 3.32
*/
static bool canEnableElevationForLayer( QgsMapLayer *layer );

/**
* Automatically enables elevation for a map \a layer, using reasonable defaults.
*
* Returns TRUE if the elevation was enabled successfully.
*
* \since QGIS 3.32
*/
static bool enableElevationForLayer( QgsMapLayer *layer );

};


Expand Down
5 changes: 5 additions & 0 deletions src/gui/elevation/qgselevationprofilelayertreeview.cpp
Expand Up @@ -420,6 +420,11 @@ void QgsElevationProfileLayerTreeView::populateInitialLayers( QgsProject *projec
}
}

QgsElevationProfileLayerTreeProxyModel *QgsElevationProfileLayerTreeView::proxyModel()
{
return mProxyModel;
}

void QgsElevationProfileLayerTreeView::resizeEvent( QResizeEvent *event )
{
header()->setMinimumSectionSize( viewport()->width() );
Expand Down
7 changes: 7 additions & 0 deletions src/gui/elevation/qgselevationprofilelayertreeview.h
Expand Up @@ -129,6 +129,13 @@ class GUI_EXPORT QgsElevationProfileLayerTreeView : public QTreeView
*/
void populateInitialLayers( QgsProject *project );

/**
* Returns the view's proxy model.
*
* \since QGIS 3.32
*/
QgsElevationProfileLayerTreeProxyModel *proxyModel();

protected:

void resizeEvent( QResizeEvent *event ) override;
Expand Down

0 comments on commit 29f0d47

Please sign in to comment.