Skip to content

Commit 0af1ce4

Browse files
authoredNov 30, 2018
Merge pull request #8571 from elpaso/bugfix-20601-rastercalc-duplicated-layer-names
Fix rastercalc duplicated layer names
2 parents e546129 + 50e5414 commit 0af1ce4

File tree

8 files changed

+170
-37
lines changed

8 files changed

+170
-37
lines changed
 

‎python/analysis/auto_generated/raster/qgsrastercalculator.sip.in

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ Represents an individual raster layer/band number entry within a raster calculat
2323
%End
2424
public:
2525

26+
static QVector<QgsRasterCalculatorEntry> rasterEntries();
27+
%Docstring
28+
Creates a list of raster entries from the current project.
29+
30+
If there is more than one layer with the same data source
31+
only one of them is added to the list, duplicate names are
32+
also handled by appending an _n integer to the base name.
33+
34+
:return: the list of raster entries form the current project
35+
36+
.. versionadded:: 3.6
37+
%End
38+
2639
QString ref;
2740

2841
QgsRasterLayer *raster;

‎python/plugins/processing/algs/qgis/ui/RasterCalculatorWidgets.py

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@
2828
import re
2929
import json
3030

31+
from qgis.utils import iface
3132
from qgis.PyQt import uic
33+
from qgis.PyQt.QtCore import Qt
3234
from qgis.PyQt.QtGui import QTextCursor
3335
from qgis.PyQt.QtWidgets import (QLineEdit, QPushButton, QLabel,
34-
QComboBox, QSpacerItem, QSizePolicy)
36+
QComboBox, QSpacerItem, QSizePolicy,
37+
QListWidgetItem)
3538

3639
from qgis.core import (QgsProcessingUtils,
3740
QgsProcessingParameterDefinition,
@@ -44,9 +47,10 @@
4447
from processing.tools import dataobjects
4548
from processing.tools.system import userFolder
4649

47-
4850
from processing.gui.wrappers import InvalidParameterValue
4951

52+
from qgis.analysis import QgsRasterCalculatorEntry
53+
5054
pluginPath = os.path.dirname(__file__)
5155
WIDGET_ADD_NEW, BASE_ADD_NEW = uic.loadUiType(
5256
os.path.join(pluginPath, 'AddNewExpressionDialog.ui'))
@@ -186,8 +190,20 @@ def fillPredefined(self):
186190
def setList(self, options):
187191
self.options = options
188192
self.listWidget.clear()
189-
for opt in options.keys():
190-
self.listWidget.addItem(opt)
193+
entries = QgsRasterCalculatorEntry.rasterEntries()
194+
195+
def _find_source(name):
196+
for entry in entries:
197+
if entry.ref == name:
198+
return entry.raster.source()
199+
return ''
200+
201+
for name in options.keys():
202+
item = QListWidgetItem(name, self.listWidget)
203+
tooltip = _find_source(name)
204+
if tooltip:
205+
item.setData(Qt.ToolTipRole, tooltip)
206+
self.listWidget.addItem(item)
191207

192208
def setValue(self, value):
193209
self.text.setPlainText(value)
@@ -201,30 +217,28 @@ class ExpressionWidgetWrapper(WidgetWrapper):
201217
def _panel(self, options):
202218
return ExpressionWidget(options)
203219

220+
def _get_options(self):
221+
entries = QgsRasterCalculatorEntry.rasterEntries()
222+
options = {}
223+
for entry in entries:
224+
options[entry.ref] = entry.ref
225+
return options
226+
204227
def createWidget(self):
205228
if self.dialogType == DIALOG_STANDARD:
206-
layers = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
207-
options = {}
208-
for lyr in layers:
209-
for n in range(lyr.bandCount()):
210-
name = '{:s}@{:d}'.format(lyr.name(), n + 1)
211-
options[name] = name
212-
return self._panel(options)
229+
if iface is not None and iface.layerTreeView() is not None and iface.layerTreeView().layerTreeModel() is not None:
230+
iface.layerTreeView().layerTreeModel().dataChanged.connect(self.refresh)
231+
return self._panel(self._get_options())
213232
elif self.dialogType == DIALOG_BATCH:
214233
return QLineEdit()
215234
else:
216235
layers = self.dialog.getAvailableValuesOfType([QgsProcessingParameterRasterLayer], [QgsProcessingOutputRasterLayer])
217236
options = {self.dialog.resolveValueDescription(lyr): "{}@1".format(self.dialog.resolveValueDescription(lyr)) for lyr in layers}
218-
return self._panel(options)
237+
self.widget = self._panel(options)
238+
return self.widget
219239

220-
def refresh(self):
221-
# TODO: check if avoid code duplication with self.createWidget
222-
layers = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance())
223-
options = {}
224-
for lyr in layers:
225-
for n in range(lyr.bandCount()):
226-
options[lyr.name()] = '{:s}@{:d}'.format(lyr.name(), n + 1)
227-
self.widget.setList(options)
240+
def refresh(self, *args):
241+
self.widget.setList(self._get_options())
228242

229243
def setValue(self, value):
230244
if self.dialogType == DIALOG_STANDARD:

‎src/analysis/raster/qgsrastercalculator.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "qgsrasterprojector.h"
2626
#include "qgsfeedback.h"
2727
#include "qgsogrutils.h"
28+
#include "qgsproject.h"
2829

2930
#include <QFile>
3031

@@ -349,3 +350,56 @@ QString QgsRasterCalculator::lastError() const
349350
{
350351
return mLastError;
351352
}
353+
354+
QVector<QgsRasterCalculatorEntry> QgsRasterCalculatorEntry::rasterEntries()
355+
{
356+
QVector<QgsRasterCalculatorEntry> availableEntries;
357+
const QMap<QString, QgsMapLayer *> &layers = QgsProject::instance()->mapLayers();
358+
359+
auto uniqueRasterBandIdentifier = [ & ]( QgsRasterCalculatorEntry & entry ) -> bool
360+
{
361+
unsigned int i( 1 );
362+
entry.ref = QStringLiteral( "%1@%2" ).arg( entry.raster->name() ).arg( entry.bandNumber );
363+
while ( true )
364+
{
365+
bool unique( true );
366+
for ( const auto &ref : qgis::as_const( availableEntries ) )
367+
{
368+
// Safety belt
369+
if ( !( entry.raster && ref.raster ) )
370+
continue;
371+
// Check if a layer with the same data source was already added to the list
372+
if ( ref.raster->publicSource() == entry.raster->publicSource() )
373+
return false;
374+
// If same name but different source
375+
if ( ref.ref == entry.ref )
376+
{
377+
unique = false;
378+
entry.ref = QStringLiteral( "%1_%2@%3" ).arg( entry.raster->name() ).arg( i++ ).arg( entry.bandNumber );
379+
}
380+
}
381+
if ( unique )
382+
return true;
383+
}
384+
};
385+
386+
QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
387+
for ( ; layerIt != layers.constEnd(); ++layerIt )
388+
{
389+
QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layerIt.value() );
390+
if ( rlayer && rlayer->dataProvider() && rlayer->dataProvider()->name() == QLatin1String( "gdal" ) )
391+
{
392+
//get number of bands
393+
for ( int i = 0; i < rlayer->bandCount(); ++i )
394+
{
395+
QgsRasterCalculatorEntry entry;
396+
entry.raster = rlayer;
397+
entry.bandNumber = i + 1;
398+
if ( ! uniqueRasterBandIdentifier( entry ) )
399+
continue;
400+
availableEntries.push_back( entry );
401+
}
402+
}
403+
}
404+
return availableEntries;
405+
}

‎src/analysis/raster/qgsrastercalculator.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ class ANALYSIS_EXPORT QgsRasterCalculatorEntry
4040

4141
public:
4242

43+
/**
44+
* Creates a list of raster entries from the current project.
45+
*
46+
* If there is more than one layer with the same data source
47+
* only one of them is added to the list, duplicate names are
48+
* also handled by appending an _n integer to the base name.
49+
*
50+
* \return the list of raster entries form the current project
51+
* \since QGIS 3.6
52+
*/
53+
static QVector<QgsRasterCalculatorEntry> rasterEntries();
54+
4355
/**
4456
* Name of entry.
4557
*/

‎src/app/qgisapp.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5681,7 +5681,7 @@ void QgisApp::showRasterCalculator()
56815681
if ( d.exec() == QDialog::Accepted )
56825682
{
56835683
//invoke analysis library
5684-
QgsRasterCalculator rc( d.formulaString(), d.outputFile(), d.outputFormat(), d.outputRectangle(), d.outputCrs(), d.numberOfColumns(), d.numberOfRows(), d.rasterEntries() );
5684+
QgsRasterCalculator rc( d.formulaString(), d.outputFile(), d.outputFormat(), d.outputRectangle(), d.outputCrs(), d.numberOfColumns(), d.numberOfRows(), QgsRasterCalculatorEntry::rasterEntries() );
56855685

56865686
QProgressDialog p( tr( "Calculating raster expression…" ), tr( "Abort" ), 0, 0 );
56875687
p.setWindowModality( Qt::WindowModal );

‎src/app/qgsrastercalcdialog.cpp

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ QVector<QgsRasterCalculatorEntry> QgsRasterCalcDialog::rasterEntries() const
153153
return entries;
154154
}
155155

156+
156157
void QgsRasterCalcDialog::setExtentSize( int width, int height, QgsRectangle bbox )
157158
{
158159
mNColumnsSpinBox->setValue( width );
@@ -164,31 +165,24 @@ void QgsRasterCalcDialog::setExtentSize( int width, int height, QgsRectangle bbo
164165
mExtentSizeSet = true;
165166
}
166167

168+
167169
void QgsRasterCalcDialog::insertAvailableRasterBands()
168170
{
169-
const QMap<QString, QgsMapLayer *> &layers = QgsProject::instance()->mapLayers();
170-
QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
171-
172-
for ( ; layerIt != layers.constEnd(); ++layerIt )
171+
mAvailableRasterBands = QgsRasterCalculatorEntry::rasterEntries().toList();
172+
mRasterBandsListWidget->clear();
173+
for ( const auto &entry : qgis::as_const( mAvailableRasterBands ) )
173174
{
174-
QgsRasterLayer *rlayer = dynamic_cast<QgsRasterLayer *>( layerIt.value() );
175+
QgsRasterLayer *rlayer = entry.raster;
175176
if ( rlayer && rlayer->dataProvider() && rlayer->dataProvider()->name() == QLatin1String( "gdal" ) )
176177
{
177178
if ( !mExtentSizeSet ) //set bounding box / resolution of output to the values of the first possible input layer
178179
{
179180
setExtentSize( rlayer->width(), rlayer->height(), rlayer->extent() );
180181
mCrsSelector->setCrs( rlayer->crs() );
181182
}
182-
//get number of bands
183-
for ( int i = 0; i < rlayer->bandCount(); ++i )
184-
{
185-
QgsRasterCalculatorEntry entry;
186-
entry.raster = rlayer;
187-
entry.bandNumber = i + 1;
188-
entry.ref = rlayer->name() + '@' + QString::number( i + 1 );
189-
mAvailableRasterBands.push_back( entry );
190-
mRasterBandsListWidget->addItem( entry.ref );
191-
}
183+
QListWidgetItem *item = new QListWidgetItem( entry.ref, mRasterBandsListWidget );
184+
item->setData( Qt::ToolTipRole, rlayer->publicSource() );
185+
mRasterBandsListWidget->addItem( item );
192186
}
193187
}
194188
}

‎src/app/qgsrastercalcdialog.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class APP_EXPORT QgsRasterCalcDialog: public QDialog, private Ui::QgsRasterCalcD
2828
{
2929
Q_OBJECT
3030
public:
31+
32+
/**
33+
* Constructor for raster calculator dialog
34+
* \param rasterLayer main raster layer, will be used for default extent and projection
35+
* \param parent widget
36+
* \param f window flags
37+
*/
3138
QgsRasterCalcDialog( QgsRasterLayer *rasterLayer = nullptr, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr );
3239

3340
QString formulaString() const;
@@ -43,7 +50,12 @@ class APP_EXPORT QgsRasterCalcDialog: public QDialog, private Ui::QgsRasterCalcD
4350
//! Number of pixels in y-direction
4451
int numberOfRows() const;
4552

46-
QVector<QgsRasterCalculatorEntry> rasterEntries() const;
53+
/**
54+
* Extract raster layer information from the current project
55+
* \return a vector of raster entries from the current project
56+
* \deprecated since QGIS 3.6 use QgsRasterCalculatorEntry::rasterEntries() instead
57+
*/
58+
Q_DECL_DEPRECATED QVector<QgsRasterCalculatorEntry> rasterEntries() const SIP_DEPRECATED;
4759

4860
private slots:
4961
void mRasterBandsListWidget_itemDoubleClicked( QListWidgetItem *item );
@@ -102,6 +114,8 @@ class APP_EXPORT QgsRasterCalcDialog: public QDialog, private Ui::QgsRasterCalcD
102114
QList<QgsRasterCalculatorEntry> mAvailableRasterBands;
103115

104116
bool mExtentSizeSet = false;
117+
118+
friend class TestQgsRasterCalcDialog;
105119
};
106120

107121
#endif // QGSRASTERCALCDIALOG_H

‎tests/src/analysis/testqgsrastercalculator.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class TestQgsRasterCalculator : public QObject
5858
void toString();
5959
void findNodes();
6060

61+
void testRasterEntries();
62+
6163
private:
6264

6365
QgsRasterLayer *mpLandsatRasterLayer = nullptr;
@@ -554,6 +556,36 @@ void TestQgsRasterCalculator::findNodes()
554556

555557
}
556558

559+
void TestQgsRasterCalculator::testRasterEntries()
560+
{
561+
// Create some test layers
562+
QList<QgsMapLayer *> layers;
563+
QgsRasterLayer *rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/dem.tif", QStringLiteral( "dem" ) );
564+
layers << rlayer;
565+
// Duplicate name, same source
566+
rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/dem.tif", QStringLiteral( "dem" ) );
567+
layers << rlayer;
568+
// Duplicated name different source
569+
rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/dem_int16.tif", QStringLiteral( "dem" ) );
570+
layers << rlayer;
571+
// Different name and different source
572+
rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/slope.tif", QStringLiteral( "slope" ) );
573+
layers << rlayer ;
574+
// Different name and same source
575+
rlayer = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/analysis/slope.tif", QStringLiteral( "slope2" ) );
576+
layers << rlayer ;
577+
QgsProject::instance()->addMapLayers( layers );
578+
QVector<QgsRasterCalculatorEntry> availableRasterBands = QgsRasterCalculatorEntry::rasterEntries();
579+
QMap<QString, QgsRasterCalculatorEntry> entryMap;
580+
for ( const auto &rb : qgis::as_const( availableRasterBands ) )
581+
{
582+
entryMap[rb.ref] = rb;
583+
}
584+
QStringList keys( entryMap.keys() );
585+
keys.sort();
586+
QCOMPARE( keys.join( ',' ), QStringLiteral( "dem@1,dem_1@1,landsat@1,landsat_4326@1,slope2@1" ) );
587+
}
588+
557589
void TestQgsRasterCalculator::errors( )
558590
{
559591
QgsRasterCalculatorEntry entry1;

0 commit comments

Comments
 (0)
Please sign in to comment.