Merge pull request #8571 from elpaso/bugfix-20601-rastercalc-duplicated-layer-names

Fix rastercalc duplicated layer names
This commit is contained in:
Alessandro Pasotti 2018-11-30 10:17:38 +01:00 committed by GitHub
commit 0af1ce40a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 170 additions and 37 deletions

View File

@ -23,6 +23,19 @@ Represents an individual raster layer/band number entry within a raster calculat
%End
public:
static QVector<QgsRasterCalculatorEntry> rasterEntries();
%Docstring
Creates a list of raster entries from the current project.
If there is more than one layer with the same data source
only one of them is added to the list, duplicate names are
also handled by appending an _n integer to the base name.
:return: the list of raster entries form the current project
.. versionadded:: 3.6
%End
QString ref;
QgsRasterLayer *raster;

View File

@ -28,10 +28,13 @@ from functools import partial
import re
import json
from qgis.utils import iface
from qgis.PyQt import uic
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QTextCursor
from qgis.PyQt.QtWidgets import (QLineEdit, QPushButton, QLabel,
QComboBox, QSpacerItem, QSizePolicy)
QComboBox, QSpacerItem, QSizePolicy,
QListWidgetItem)
from qgis.core import (QgsProcessingUtils,
QgsProcessingParameterDefinition,
@ -44,9 +47,10 @@ from processing.gui.BatchInputSelectionPanel import BatchInputSelectionPanel
from processing.tools import dataobjects
from processing.tools.system import userFolder
from processing.gui.wrappers import InvalidParameterValue
from qgis.analysis import QgsRasterCalculatorEntry
pluginPath = os.path.dirname(__file__)
WIDGET_ADD_NEW, BASE_ADD_NEW = uic.loadUiType(
os.path.join(pluginPath, 'AddNewExpressionDialog.ui'))
@ -186,8 +190,20 @@ class ExpressionWidget(BASE, WIDGET):
def setList(self, options):
self.options = options
self.listWidget.clear()
for opt in options.keys():
self.listWidget.addItem(opt)
entries = QgsRasterCalculatorEntry.rasterEntries()
def _find_source(name):
for entry in entries:
if entry.ref == name:
return entry.raster.source()
return ''
for name in options.keys():
item = QListWidgetItem(name, self.listWidget)
tooltip = _find_source(name)
if tooltip:
item.setData(Qt.ToolTipRole, tooltip)
self.listWidget.addItem(item)
def setValue(self, value):
self.text.setPlainText(value)
@ -201,30 +217,28 @@ class ExpressionWidgetWrapper(WidgetWrapper):
def _panel(self, options):
return ExpressionWidget(options)
def _get_options(self):
entries = QgsRasterCalculatorEntry.rasterEntries()
options = {}
for entry in entries:
options[entry.ref] = entry.ref
return options
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
layers = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
options = {}
for lyr in layers:
for n in range(lyr.bandCount()):
name = '{:s}@{:d}'.format(lyr.name(), n + 1)
options[name] = name
return self._panel(options)
if iface is not None and iface.layerTreeView() is not None and iface.layerTreeView().layerTreeModel() is not None:
iface.layerTreeView().layerTreeModel().dataChanged.connect(self.refresh)
return self._panel(self._get_options())
elif self.dialogType == DIALOG_BATCH:
return QLineEdit()
else:
layers = self.dialog.getAvailableValuesOfType([QgsProcessingParameterRasterLayer], [QgsProcessingOutputRasterLayer])
options = {self.dialog.resolveValueDescription(lyr): "{}@1".format(self.dialog.resolveValueDescription(lyr)) for lyr in layers}
return self._panel(options)
self.widget = self._panel(options)
return self.widget
def refresh(self):
# TODO: check if avoid code duplication with self.createWidget
layers = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance())
options = {}
for lyr in layers:
for n in range(lyr.bandCount()):
options[lyr.name()] = '{:s}@{:d}'.format(lyr.name(), n + 1)
self.widget.setList(options)
def refresh(self, *args):
self.widget.setList(self._get_options())
def setValue(self, value):
if self.dialogType == DIALOG_STANDARD:

View File

@ -25,6 +25,7 @@
#include "qgsrasterprojector.h"
#include "qgsfeedback.h"
#include "qgsogrutils.h"
#include "qgsproject.h"
#include <QFile>
@ -349,3 +350,56 @@ QString QgsRasterCalculator::lastError() const
{
return mLastError;
}
QVector<QgsRasterCalculatorEntry> QgsRasterCalculatorEntry::rasterEntries()
{
QVector<QgsRasterCalculatorEntry> availableEntries;
const QMap<QString, QgsMapLayer *> &layers = QgsProject::instance()->mapLayers();
auto uniqueRasterBandIdentifier = [ & ]( QgsRasterCalculatorEntry & entry ) -> bool
{
unsigned int i( 1 );
entry.ref = QStringLiteral( "%1@%2" ).arg( entry.raster->name() ).arg( entry.bandNumber );
while ( true )
{
bool unique( true );
for ( const auto &ref : qgis::as_const( availableEntries ) )
{
// Safety belt
if ( !( entry.raster && ref.raster ) )
continue;
// Check if a layer with the same data source was already added to the list
if ( ref.raster->publicSource() == entry.raster->publicSource() )
return false;
// If same name but different source
if ( ref.ref == entry.ref )
{
unique = false;
entry.ref = QStringLiteral( "%1_%2@%3" ).arg( entry.raster->name() ).arg( i++ ).arg( entry.bandNumber );
}
}
if ( unique )
return true;
}
};
QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
for ( ; layerIt != layers.constEnd(); ++layerIt )
{
QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layerIt.value() );
if ( rlayer && rlayer->dataProvider() && rlayer->dataProvider()->name() == QLatin1String( "gdal" ) )
{
//get number of bands
for ( int i = 0; i < rlayer->bandCount(); ++i )
{
QgsRasterCalculatorEntry entry;
entry.raster = rlayer;
entry.bandNumber = i + 1;
if ( ! uniqueRasterBandIdentifier( entry ) )
continue;
availableEntries.push_back( entry );
}
}
}
return availableEntries;
}

View File

@ -40,6 +40,18 @@ class ANALYSIS_EXPORT QgsRasterCalculatorEntry
public:
/**
* Creates a list of raster entries from the current project.
*
* If there is more than one layer with the same data source
* only one of them is added to the list, duplicate names are
* also handled by appending an _n integer to the base name.
*
* \return the list of raster entries form the current project
* \since QGIS 3.6
*/
static QVector<QgsRasterCalculatorEntry> rasterEntries();
/**
* Name of entry.
*/

View File

@ -5681,7 +5681,7 @@ void QgisApp::showRasterCalculator()
if ( d.exec() == QDialog::Accepted )
{
//invoke analysis library
QgsRasterCalculator rc( d.formulaString(), d.outputFile(), d.outputFormat(), d.outputRectangle(), d.outputCrs(), d.numberOfColumns(), d.numberOfRows(), d.rasterEntries() );
QgsRasterCalculator rc( d.formulaString(), d.outputFile(), d.outputFormat(), d.outputRectangle(), d.outputCrs(), d.numberOfColumns(), d.numberOfRows(), QgsRasterCalculatorEntry::rasterEntries() );
QProgressDialog p( tr( "Calculating raster expression…" ), tr( "Abort" ), 0, 0 );
p.setWindowModality( Qt::WindowModal );

View File

@ -153,6 +153,7 @@ QVector<QgsRasterCalculatorEntry> QgsRasterCalcDialog::rasterEntries() const
return entries;
}
void QgsRasterCalcDialog::setExtentSize( int width, int height, QgsRectangle bbox )
{
mNColumnsSpinBox->setValue( width );
@ -164,14 +165,14 @@ void QgsRasterCalcDialog::setExtentSize( int width, int height, QgsRectangle bbo
mExtentSizeSet = true;
}
void QgsRasterCalcDialog::insertAvailableRasterBands()
{
const QMap<QString, QgsMapLayer *> &layers = QgsProject::instance()->mapLayers();
QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
for ( ; layerIt != layers.constEnd(); ++layerIt )
mAvailableRasterBands = QgsRasterCalculatorEntry::rasterEntries().toList();
mRasterBandsListWidget->clear();
for ( const auto &entry : qgis::as_const( mAvailableRasterBands ) )
{
QgsRasterLayer *rlayer = dynamic_cast<QgsRasterLayer *>( layerIt.value() );
QgsRasterLayer *rlayer = entry.raster;
if ( rlayer && rlayer->dataProvider() && rlayer->dataProvider()->name() == QLatin1String( "gdal" ) )
{
if ( !mExtentSizeSet ) //set bounding box / resolution of output to the values of the first possible input layer
@ -179,16 +180,9 @@ void QgsRasterCalcDialog::insertAvailableRasterBands()
setExtentSize( rlayer->width(), rlayer->height(), rlayer->extent() );
mCrsSelector->setCrs( rlayer->crs() );
}
//get number of bands
for ( int i = 0; i < rlayer->bandCount(); ++i )
{
QgsRasterCalculatorEntry entry;
entry.raster = rlayer;
entry.bandNumber = i + 1;
entry.ref = rlayer->name() + '@' + QString::number( i + 1 );
mAvailableRasterBands.push_back( entry );
mRasterBandsListWidget->addItem( entry.ref );
}
QListWidgetItem *item = new QListWidgetItem( entry.ref, mRasterBandsListWidget );
item->setData( Qt::ToolTipRole, rlayer->publicSource() );
mRasterBandsListWidget->addItem( item );
}
}
}

View File

@ -28,6 +28,13 @@ class APP_EXPORT QgsRasterCalcDialog: public QDialog, private Ui::QgsRasterCalcD
{
Q_OBJECT
public:
/**
* Constructor for raster calculator dialog
* \param rasterLayer main raster layer, will be used for default extent and projection
* \param parent widget
* \param f window flags
*/
QgsRasterCalcDialog( QgsRasterLayer *rasterLayer = nullptr, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr );
QString formulaString() const;
@ -43,7 +50,12 @@ class APP_EXPORT QgsRasterCalcDialog: public QDialog, private Ui::QgsRasterCalcD
//! Number of pixels in y-direction
int numberOfRows() const;
QVector<QgsRasterCalculatorEntry> rasterEntries() const;
/**
* Extract raster layer information from the current project
* \return a vector of raster entries from the current project
* \deprecated since QGIS 3.6 use QgsRasterCalculatorEntry::rasterEntries() instead
*/
Q_DECL_DEPRECATED QVector<QgsRasterCalculatorEntry> rasterEntries() const SIP_DEPRECATED;
private slots:
void mRasterBandsListWidget_itemDoubleClicked( QListWidgetItem *item );
@ -102,6 +114,8 @@ class APP_EXPORT QgsRasterCalcDialog: public QDialog, private Ui::QgsRasterCalcD
QList<QgsRasterCalculatorEntry> mAvailableRasterBands;
bool mExtentSizeSet = false;
friend class TestQgsRasterCalcDialog;
};
#endif // QGSRASTERCALCDIALOG_H

View File

@ -58,6 +58,8 @@ class TestQgsRasterCalculator : public QObject
void toString();
void findNodes();
void testRasterEntries();
private:
QgsRasterLayer *mpLandsatRasterLayer = nullptr;
@ -554,6 +556,36 @@ void TestQgsRasterCalculator::findNodes()
}
void TestQgsRasterCalculator::testRasterEntries()
{
// Create some test layers
QList<QgsMapLayer *> layers;
QgsRasterLayer *rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/dem.tif", QStringLiteral( "dem" ) );
layers << rlayer;
// Duplicate name, same source
rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/dem.tif", QStringLiteral( "dem" ) );
layers << rlayer;
// Duplicated name different source
rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/dem_int16.tif", QStringLiteral( "dem" ) );
layers << rlayer;
// Different name and different source
rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/slope.tif", QStringLiteral( "slope" ) );
layers << rlayer ;
// Different name and same source
rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/slope.tif", QStringLiteral( "slope2" ) );
layers << rlayer ;
QgsProject::instance()->addMapLayers( layers );
QVector<QgsRasterCalculatorEntry> availableRasterBands = QgsRasterCalculatorEntry::rasterEntries();
QMap<QString, QgsRasterCalculatorEntry> entryMap;
for ( const auto &rb : qgis::as_const( availableRasterBands ) )
{
entryMap[rb.ref] = rb;
}
QStringList keys( entryMap.keys() );
keys.sort();
QCOMPARE( keys.join( ',' ), QStringLiteral( "dem@1,dem_1@1,landsat@1,landsat_4326@1,slope2@1" ) );
}
void TestQgsRasterCalculator::errors( )
{
QgsRasterCalculatorEntry entry1;