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.
This commit is contained in:
Nyall Dawson 2023-05-22 11:48:27 +10:00
parent d0f61c9a52
commit 29f0d479d0
8 changed files with 329 additions and 1 deletions

View File

@ -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
};

View File

@ -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"
@ -47,6 +49,8 @@
#include "qgselevationprofiletoolmeasure.h"
#include "qgssettingsentryimpl.h"
#include "qgssettingstree.h"
#include "qgsmaplayerproxymodel.h"
#include "qgselevationutils.h"
#include <QToolBar>
@ -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 )
@ -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 );
@ -386,6 +460,53 @@ 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;
@ -393,6 +514,10 @@ void QgsElevationProfileWidget::updateCanvasLayers()
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;
}

View File

@ -23,6 +23,7 @@
#include "qgsgeometry.h"
#include "qobjectuniqueptr.h"
#include "qgselevationprofilelayertreeview.h"
#include "ui_qgselevationprofileaddlayersdialogbase.h"
#include <QWidgetAction>
#include <QElapsedTimer>
@ -50,6 +51,7 @@ class QLabel;
class QgsProfilePoint;
class QgsSettingsEntryDouble;
class QgsSettingsEntryBool;
class QgsMapLayerProxyModel;
class QgsAppElevationProfileLayerTreeView : public QgsElevationProfileLayerTreeView
{
@ -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
@ -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 );

View File

@ -16,7 +16,7 @@
#include "qgselevationutils.h"
#include "qgsproject.h"
#include "qgsmaplayerelevationproperties.h"
#include "qgsrasterlayerelevationproperties.h"
QgsDoubleRange QgsElevationUtils::calculateZRangeForProject( QgsProject *project )
{
@ -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;
}

View File

@ -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 );
};

View File

@ -420,6 +420,11 @@ void QgsElevationProfileLayerTreeView::populateInitialLayers( QgsProject *projec
}
}
QgsElevationProfileLayerTreeProxyModel *QgsElevationProfileLayerTreeView::proxyModel()
{
return mProxyModel;
}
void QgsElevationProfileLayerTreeView::resizeEvent( QResizeEvent *event )
{
header()->setMinimumSectionSize( viewport()->width() );

View File

@ -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;

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsElevationProfileAddLayersDialogBase</class>
<widget class="QDialog" name="QgsElevationProfileAddLayersDialogBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>270</width>
<height>194</height>
</rect>
</property>
<property name="windowTitle">
<string>Add Layers to Elevation Profile</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="0">
<widget class="QListView" name="listMapLayers">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QgsFilterLineEdit" name="mFilterLineEdit">
<property name="placeholderText">
<string>Search</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="mCheckBoxVisibleLayers">
<property name="toolTip">
<string>If checked, only layers visible within the map will be listed</string>
</property>
<property name="text">
<string>Show visible layers only</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsFilterLineEdit</class>
<extends>QLineEdit</extends>
<header>qgsfilterlineedit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mFilterLineEdit</tabstop>
<tabstop>listMapLayers</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QgsElevationProfileAddLayersDialogBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QgsElevationProfileAddLayersDialogBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>