Skip to content

Commit 6d38894

Browse files
authored
Merge pull request #5024 from nyalldawson/build-vrt
Restore gdal build vrt alg, plus improvements
2 parents fdee6ea + 8139786 commit 6d38894

File tree

8 files changed

+203
-82
lines changed

8 files changed

+203
-82
lines changed

python/plugins/processing/algs/gdal/GdalAlgorithmProvider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
from .AssignProjection import AssignProjection
3737
from .aspect import aspect
38+
from .buildvrt import buildvrt
3839
from .ColorRelief import ColorRelief
3940
from .tri import tri
4041
from .warp import warp
@@ -44,7 +45,6 @@
4445
# from .translate import translate
4546
# from .pct2rgb import pct2rgb
4647
# from .merge import merge
47-
# from .buildvrt import buildvrt
4848
# from .polygonize import polygonize
4949
# from .gdaladdo import gdaladdo
5050
# from .ClipByExtent import ClipByExtent
@@ -144,14 +144,14 @@ def loadAlgorithms(self):
144144
# information(),
145145
AssignProjection(),
146146
aspect(),
147+
buildvrt(),
147148
ColorRelief(),
148149
tri(),
149150
warp(),
150151
# translate(),
151152
# rgb2pct(),
152153
# pct2rgb(),
153154
# merge(),
154-
# buildvrt(),
155155
# polygonize(),
156156
# gdaladdo(),
157157
# ClipByExtent(),

python/plugins/processing/algs/gdal/buildvrt.py

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@
2929

3030
from qgis.PyQt.QtGui import QIcon
3131

32-
from qgis.core import QgsProcessingUtils
32+
from qgis.core import (QgsProcessing,
33+
QgsProperty,
34+
QgsProcessingParameterMultipleLayers,
35+
QgsProcessingParameterEnum,
36+
QgsProcessingParameterBoolean,
37+
QgsProcessingParameterRasterDestination,
38+
QgsProcessingOutputLayerDefinition,
39+
QgsProcessingUtils)
3340
from processing.algs.gdal.GdalAlgorithm import GdalAlgorithm
34-
from processing.core.outputs import OutputRaster
35-
from processing.core.parameters import ParameterBoolean
36-
from processing.core.parameters import ParameterMultipleInput
37-
from processing.core.parameters import ParameterSelection
3841
from processing.algs.gdal.GdalUtils import GdalUtils
39-
from processing.tools import dataobjects
4042

4143
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
4244

@@ -55,15 +57,27 @@ def __init__(self):
5557
super().__init__()
5658

5759
def initAlgorithm(self, config=None):
58-
self.addParameter(ParameterMultipleInput(self.INPUT,
59-
self.tr('Input layers'), dataobjects.TYPE_RASTER))
60-
self.addParameter(ParameterSelection(self.RESOLUTION,
61-
self.tr('Resolution'), self.RESOLUTION_OPTIONS, 0))
62-
self.addParameter(ParameterBoolean(self.SEPARATE,
63-
self.tr('Layer stack'), True))
64-
self.addParameter(ParameterBoolean(self.PROJ_DIFFERENCE,
65-
self.tr('Allow projection difference'), False))
66-
self.addOutput(OutputRaster(buildvrt.OUTPUT, self.tr('Virtual')))
60+
61+
class ParameterVrtDestination(QgsProcessingParameterRasterDestination):
62+
63+
def __init__(self, name, description):
64+
super().__init__(name, description)
65+
66+
def type(self):
67+
return 'vrt_destination'
68+
69+
def defaultFileExtension(self):
70+
return 'vrt'
71+
72+
self.addParameter(QgsProcessingParameterMultipleLayers(self.INPUT,
73+
self.tr('Input layers'), QgsProcessing.TypeRaster))
74+
self.addParameter(QgsProcessingParameterEnum(self.RESOLUTION,
75+
self.tr('Resolution'), options=self.RESOLUTION_OPTIONS, defaultValue=0))
76+
self.addParameter(QgsProcessingParameterBoolean(self.SEPARATE,
77+
self.tr('Layer stack'), defaultValue=True))
78+
self.addParameter(QgsProcessingParameterBoolean(self.PROJ_DIFFERENCE,
79+
self.tr('Allow projection difference'), defaultValue=False))
80+
self.addParameter(ParameterVrtDestination(self.OUTPUT, self.tr('Virtual')))
6781

6882
def name(self):
6983
return 'buildvirtualraster'
@@ -80,25 +94,34 @@ def group(self):
8094
def getConsoleCommands(self, parameters, context, feedback):
8195
arguments = []
8296
arguments.append('-resolution')
83-
arguments.append(self.RESOLUTION_OPTIONS[self.getParameterValue(self.RESOLUTION)])
84-
if self.getParameterValue(buildvrt.SEPARATE):
97+
arguments.append(self.RESOLUTION_OPTIONS[self.parameterAsEnum(parameters, self.RESOLUTION, context)])
98+
if self.parameterAsBool(parameters, buildvrt.SEPARATE, context):
8599
arguments.append('-separate')
86-
if self.getParameterValue(buildvrt.PROJ_DIFFERENCE):
100+
if self.parameterAsBool(parameters, buildvrt.PROJ_DIFFERENCE, context):
87101
arguments.append('-allow_projection_difference')
88102
# Always write input files to a text file in case there are many of them and the
89103
# length of the command will be longer then allowed in command prompt
90104
listFile = os.path.join(QgsProcessingUtils.tempFolder(), 'buildvrtInputFiles.txt')
91105
with open(listFile, 'w') as f:
92-
f.write(self.getParameterValue(buildvrt.INPUT).replace(';', '\n'))
106+
layers = []
107+
for l in self.parameterAsLayerList(parameters, self.INPUT, context):
108+
layers.append(l.source())
109+
f.write('\n'.join(layers))
93110
arguments.append('-input_file_list')
94111
arguments.append(listFile)
95-
out = self.getOutputValue(buildvrt.OUTPUT)
112+
113+
out = self.parameterAsOutputLayer(parameters, self.OUTPUT, context)
96114
# Ideally the file extensions should be limited to just .vrt but I'm not sure how
97115
# to do it simply so instead a check is performed.
98116
_, ext = os.path.splitext(out)
99117
if not ext.lower() == '.vrt':
100-
out = out.replace(ext, '.vrt')
101-
self.setOutputValue(self.OUTPUT, out)
118+
out = out[:-len(ext)] + '.vrt'
119+
if isinstance(parameters[self.OUTPUT], QgsProcessingOutputLayerDefinition):
120+
output_def = QgsProcessingOutputLayerDefinition(parameters[self.OUTPUT])
121+
output_def.sink = QgsProperty.fromValue(out)
122+
self.setOutputValue(self.OUTPUT, output_def)
123+
else:
124+
self.setOutputValue(self.OUTPUT, out)
102125
arguments.append(out)
103126

104127
return ['gdalbuildvrt', GdalUtils.escapeAndJoin(arguments)]

python/plugins/processing/gui/MultipleInputDialog.py

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,17 @@
2929

3030
import os
3131

32-
from qgis.core import QgsSettings
32+
from qgis.core import (QgsSettings,
33+
QgsProcessing,
34+
QgsVectorFileWriter,
35+
QgsProviderRegistry,
36+
QgsProcessingModelChildParameterSource)
3337
from qgis.PyQt import uic
3438
from qgis.PyQt.QtCore import Qt
3539
from qgis.PyQt.QtCore import QByteArray
36-
from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QPushButton, QDialogButtonBox
40+
from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QPushButton, QDialogButtonBox, QFileDialog
3741
from qgis.PyQt.QtGui import QStandardItemModel, QStandardItem
42+
from processing.tools import dataobjects
3843

3944
pluginPath = os.path.split(os.path.dirname(__file__))[0]
4045
WIDGET, BASE = uic.loadUiType(
@@ -43,9 +48,11 @@
4348

4449
class MultipleInputDialog(BASE, WIDGET):
4550

46-
def __init__(self, options, selectedoptions=None):
51+
def __init__(self, options, selectedoptions=None, datatype=None):
4752
super(MultipleInputDialog, self).__init__(None)
4853
self.setupUi(self)
54+
self.datatype = datatype
55+
self.model = None
4956

5057
self.lstLayers.setSelectionMode(QAbstractItemView.NoSelection)
5158

@@ -68,6 +75,11 @@ def __init__(self, options, selectedoptions=None):
6875
self.btnToggleSelection = QPushButton(self.tr('Toggle selection'))
6976
self.buttonBox.addButton(self.btnToggleSelection,
7077
QDialogButtonBox.ActionRole)
78+
if self.datatype is not None:
79+
btnAddFile = QPushButton(self.tr('Add file(s)…'))
80+
btnAddFile.clicked.connect(self.addFiles)
81+
self.buttonBox.addButton(btnAddFile,
82+
QDialogButtonBox.ActionRole)
7183

7284
self.btnSelectAll.clicked.connect(lambda: self.selectAll(True))
7385
self.btnClearSelection.clicked.connect(lambda: self.selectAll(False))
@@ -76,22 +88,34 @@ def __init__(self, options, selectedoptions=None):
7688
self.settings = QgsSettings()
7789
self.restoreGeometry(self.settings.value("/Processing/multipleInputDialogGeometry", QByteArray()))
7890

91+
self.lstLayers.setSelectionMode(QAbstractItemView.ExtendedSelection)
7992
self.populateList()
8093
self.finished.connect(self.saveWindowGeometry)
8194

8295
def saveWindowGeometry(self):
8396
self.settings.setValue("/Processing/multipleInputDialogGeometry", self.saveGeometry())
8497

8598
def populateList(self):
86-
model = QStandardItemModel()
99+
self.model = QStandardItemModel()
87100
for value, text in self.options:
88101
item = QStandardItem(text)
89102
item.setData(value, Qt.UserRole)
90103
item.setCheckState(Qt.Checked if value in self.selectedoptions else Qt.Unchecked)
91104
item.setCheckable(True)
92-
model.appendRow(item)
105+
self.model.appendRow(item)
93106

94-
self.lstLayers.setModel(model)
107+
# add extra options (e.g. manually added layers)
108+
for t in [o for o in self.selectedoptions if not isinstance(o, int)]:
109+
if isinstance(t, QgsProcessingModelChildParameterSource):
110+
item = QStandardItem(t.staticValue())
111+
else:
112+
item = QStandardItem(t)
113+
item.setData(item.text(), Qt.UserRole)
114+
item.setCheckState(Qt.Checked)
115+
item.setCheckable(True)
116+
self.model.appendRow(item)
117+
118+
self.lstLayers.setModel(self.model)
95119

96120
def accept(self):
97121
self.selectedoptions = []
@@ -106,15 +130,56 @@ def reject(self):
106130
self.selectedoptions = None
107131
QDialog.reject(self)
108132

133+
def getItemsToModify(self):
134+
items = []
135+
if len(self.lstLayers.selectedIndexes()) > 1:
136+
for i in self.lstLayers.selectedIndexes():
137+
items.append(self.model.itemFromIndex(i))
138+
else:
139+
for i in range(self.model.rowCount()):
140+
items.append(self.model.item(i))
141+
return items
142+
109143
def selectAll(self, value):
110-
model = self.lstLayers.model()
111-
for i in range(model.rowCount()):
112-
item = model.item(i)
144+
for item in self.getItemsToModify():
113145
item.setCheckState(Qt.Checked if value else Qt.Unchecked)
114146

115147
def toggleSelection(self):
116-
model = self.lstLayers.model()
117-
for i in range(model.rowCount()):
118-
item = model.item(i)
148+
for item in self.getItemsToModify():
119149
checked = item.checkState() == Qt.Checked
120150
item.setCheckState(Qt.Unchecked if checked else Qt.Checked)
151+
152+
def getFileFilter(self, datatype):
153+
"""
154+
Returns a suitable file filter pattern for the specified parameter definition
155+
:param param:
156+
:return:
157+
"""
158+
if datatype == QgsProcessing.TypeRaster:
159+
return QgsProviderRegistry.instance().fileRasterFilters()
160+
elif datatype == QgsProcessing.TypeFile:
161+
return self.tr('All files (*.*)')
162+
else:
163+
exts = QgsVectorFileWriter.supportedFormatExtensions()
164+
for i in range(len(exts)):
165+
exts[i] = self.tr('{0} files (*.{1})').format(exts[i].upper(), exts[i].lower())
166+
return self.tr('All files (*.*)') + ';;' + ';;'.join(exts)
167+
168+
def addFiles(self):
169+
filter = self.getFileFilter(self.datatype)
170+
171+
settings = QgsSettings()
172+
path = str(settings.value('/Processing/LastInputPath'))
173+
174+
ret, selected_filter = QFileDialog.getOpenFileNames(self, self.tr('Select file(s)'),
175+
path, filter)
176+
if ret:
177+
files = list(ret)
178+
settings.setValue('/Processing/LastInputPath',
179+
os.path.dirname(str(files[0])))
180+
for filename in files:
181+
item = QStandardItem(filename)
182+
item.setData(filename, Qt.UserRole)
183+
item.setCheckState(Qt.Checked)
184+
item.setCheckable(True)
185+
self.model.appendRow(item)

python/plugins/processing/gui/MultipleInputPanel.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828

2929
import os
3030

31+
from qgis.core import QgsProcessing
3132
from qgis.PyQt import uic
3233
from qgis.PyQt.QtCore import pyqtSignal
33-
34+
''
3435
from processing.gui.MultipleInputDialog import MultipleInputDialog
3536
from processing.gui.MultipleFileInputDialog import MultipleFileInputDialog
3637

@@ -63,10 +64,10 @@ def setSelectedItems(self, selected):
6364
self.tr('{0} elements selected').format(len(self.selectedoptions)))
6465

6566
def showSelectionDialog(self):
66-
if self.datatype is None:
67-
dlg = MultipleInputDialog(self.options, self.selectedoptions)
68-
else:
67+
if self.datatype == QgsProcessing.TypeFile:
6968
dlg = MultipleFileInputDialog(self.selectedoptions)
69+
else:
70+
dlg = MultipleInputDialog(self.options, self.selectedoptions, datatype=self.datatype)
7071
dlg.exec_()
7172
if dlg.selectedoptions is not None:
7273
self.selectedoptions = dlg.selectedoptions
@@ -76,12 +77,15 @@ def showSelectionDialog(self):
7677

7778
def updateForOptions(self, options):
7879
selectedoptions = []
79-
selected = [self.options[i] for i in self.selectedoptions]
80+
selected = [self.options[i] if isinstance(i, int) else i for i in self.selectedoptions]
8081
for sel in selected:
81-
try:
82-
idx = options.index(sel)
83-
selectedoptions.append(idx)
84-
except ValueError:
85-
pass
82+
if isinstance(sel, int):
83+
try:
84+
idx = options.index(sel)
85+
selectedoptions.append(idx)
86+
except ValueError:
87+
pass
88+
else:
89+
selectedoptions.append(sel)
8690
self.options = options
8791
self.setSelectedItems(selectedoptions)

0 commit comments

Comments
 (0)