Skip to content

Commit d10aaf4

Browse files
author
Médéric RIBREUX
committed
Support multiple output file raster formats:
* A new createopt textbox has been added to the parameters dialog for algorithms which exports to raster files. * A new metaopt textbox has also been added to the Algorithm parameters dialog. * Raster file format is detected from output filename extension. * GdalUtils has been improved to correctly detect raster formats supported for creation. * QFileDialog for output rasters now display only file filters for supported output raster file formats.
1 parent aa17df1 commit d10aaf4

File tree

8 files changed

+133
-22
lines changed

8 files changed

+133
-22
lines changed

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

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class GdalUtils(object):
6161
GDAL_HELP_PATH = 'GDAL_HELP_PATH'
6262

6363
supportedRasters = None
64+
supportedOutputRasters = None
6465

6566
@staticmethod
6667
def runGdal(commands, feedback=None):
@@ -135,7 +136,10 @@ def getSupportedRasters():
135136
gdal.AllRegister()
136137

137138
GdalUtils.supportedRasters = {}
139+
GdalUtils.supportedOutputRasters = {}
138140
GdalUtils.supportedRasters['GTiff'] = ['tif']
141+
GdalUtils.supportedOutputRasters['GTiff'] = ['tif']
142+
139143
for i in range(gdal.GetDriverCount()):
140144
driver = gdal.GetDriver(i)
141145
if driver is None:
@@ -146,18 +150,31 @@ def getSupportedRasters():
146150
or metadata[gdal.DCAP_RASTER] != 'YES':
147151
continue
148152

149-
# ===================================================================
150-
# if gdal.DCAP_CREATE not in metadata \
151-
# or metadata[gdal.DCAP_CREATE] != 'YES':
152-
# continue
153-
# ===================================================================
154153
if gdal.DMD_EXTENSION in metadata:
155154
extensions = metadata[gdal.DMD_EXTENSION].split('/')
156155
if extensions:
157156
GdalUtils.supportedRasters[shortName] = extensions
157+
# Only creatable rasters can be referenced in output rasters
158+
if ((gdal.DCAP_CREATE in metadata
159+
and metadata[gdal.DCAP_CREATE] == 'YES')
160+
or (gdal.DCAP_CREATECOPY in metadata
161+
and metadata[gdal.DCAP_CREATECOPY] == 'YES')):
162+
GdalUtils.supportedOutputRasters[shortName] = extensions
158163

159164
return GdalUtils.supportedRasters
160165

166+
@staticmethod
167+
def getSupportedOutputRasters():
168+
if not gdalAvailable:
169+
return {}
170+
171+
if GdalUtils.supportedOutputRasters is not None:
172+
return GdalUtils.supportedOutputRasters
173+
else:
174+
GdalUtils.getSupportedRasters()
175+
176+
return GdalUtils.supportedOutputRasters
177+
161178
@staticmethod
162179
def getSupportedRasterExtensions():
163180
allexts = ['tif']
@@ -167,6 +184,15 @@ def getSupportedRasterExtensions():
167184
allexts.append(ext)
168185
return allexts
169186

187+
@staticmethod
188+
def getSupportedOutputRasterExtensions():
189+
allexts = ['tif']
190+
for exts in list(GdalUtils.getSupportedOutputRasters().values()):
191+
for ext in exts:
192+
if ext not in allexts and ext != '':
193+
allexts.append(ext)
194+
return allexts
195+
170196
@staticmethod
171197
def getVectorDriverFromFileName(filename):
172198
ext = os.path.splitext(filename)[1]

python/plugins/processing/algs/grass7/Grass7Algorithm.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ class Grass7Algorithm(QgsProcessingAlgorithm):
8686
GRASS_REGION_EXTENT_PARAMETER = 'GRASS_REGION_PARAMETER'
8787
GRASS_REGION_CELLSIZE_PARAMETER = 'GRASS_REGION_CELLSIZE_PARAMETER'
8888
GRASS_REGION_ALIGN_TO_RESOLUTION = 'GRASS_REGION_ALIGN_TO_RESOLUTION'
89+
GRASS_RASTER_FORMAT_OPT = 'GRASS_RASTER_FORMAT_OPT'
90+
GRASS_RASTER_FORMAT_META = 'GRASS_RASTER_FORMAT_META'
8991

9092
OUTPUT_TYPES = ['auto', 'point', 'line', 'area']
9193
QGIS_OUTPUT_TYPES = {QgsProcessing.TypeVectorAnyGeometry: 'auto',
@@ -227,6 +229,7 @@ def defineCharacteristicsFromFile(self):
227229
self.params.append(param)
228230

229231
if hasRasterOutput:
232+
# Add a cellsize parameter
230233
param = QgsProcessingParameterNumber(
231234
self.GRASS_REGION_CELLSIZE_PARAMETER,
232235
self.tr('GRASS GIS 7 region cellsize (leave 0 for default)'),
@@ -236,6 +239,24 @@ def defineCharacteristicsFromFile(self):
236239
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
237240
self.params.append(param)
238241

242+
# Add a createopt parameter for format export
243+
param = QgsProcessingParameterString(
244+
self.GRASS_RASTER_FORMAT_OPT,
245+
self.tr('Output Rasters format options (createopt)'),
246+
multiLine=True, optional=True
247+
)
248+
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
249+
self.params.append(param)
250+
251+
# Add a metadata parameter for format export
252+
param = QgsProcessingParameterString(
253+
self.GRASS_RASTER_FORMAT_META,
254+
self.tr('Output Rasters format metadata options (metaopt)'),
255+
multiLine=True, optional=True
256+
)
257+
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
258+
self.params.append(param)
259+
239260
if hasVectorInput:
240261
param = QgsProcessingParameterNumber(self.GRASS_SNAP_TOLERANCE_PARAMETER,
241262
self.tr('v.in.ogr snap tolerance (-1 = no snap)'),
@@ -459,7 +480,9 @@ def processCommand(self, parameters, context, delOutputs=False):
459480
self.GRASS_MIN_AREA_PARAMETER,
460481
self.GRASS_SNAP_TOLERANCE_PARAMETER,
461482
self.GRASS_OUTPUT_TYPE_PARAMETER,
462-
self.GRASS_REGION_ALIGN_TO_RESOLUTION]:
483+
self.GRASS_REGION_ALIGN_TO_RESOLUTION,
484+
self.GRASS_RASTER_FORMAT_OPT,
485+
self.GRASS_RASTER_FORMAT_META]:
463486
continue
464487

465488
# Raster and vector layers
@@ -622,24 +645,39 @@ def exportRasterLayerFromParameter(self, name, parameters, context, colorTable=T
622645
fileName = os.path.normpath(
623646
self.parameterAsOutputLayer(parameters, name, context))
624647
grassName = '{}{}'.format(name, self.uniqueSuffix)
625-
self.exportRasterLayer(grassName, fileName, colorTable)
648+
outFormat = Grass7Utils.getRasterFormatFromFilename(fileName)
649+
createOpt = self.parameterAsString(parameters, self.GRASS_RASTER_FORMAT_OPT, context)
650+
metaOpt = self.parameterAsString(parameters, self.GRASS_RASTER_FORMAT_META, context)
651+
self.exportRasterLayer(grassName, fileName, colorTable, outFormat, createOpt, metaOpt)
626652

627-
def exportRasterLayer(self, grassName, fileName, colorTable=True):
653+
def exportRasterLayer(self, grassName, fileName,
654+
colorTable=True, outFormat='GTiff',
655+
createOpt=None,
656+
metaOpt=None):
628657
"""
629658
Creates a dedicated command to export a raster from
630659
temporary GRASS DB into a file via gdal.
631-
:param grassName: name of the parameter
632-
:param fileName: file path of raster layer
660+
:param grassName: name of the raster to export.
661+
:param fileName: file path of raster layer.
633662
:param colorTable: preserve color Table.
663+
:param outFormat: file format for export.
664+
:param createOpt: creation options for format.
665+
:param metatOpt: metadata options for export.
634666
"""
667+
if not createOpt:
668+
if outFormat in Grass7Utils.GRASS_RASTER_FORMATS_CREATEOPTS:
669+
createOpt = Grass7Utils.GRASS_RASTER_FORMATS_CREATEOPTS[outFormat]
670+
635671
for cmd in [self.commands, self.outputCommands]:
636672
# Adjust region to layer before exporting
637673
cmd.append('g.region raster={}'.format(grassName))
638674
cmd.append(
639-
'r.out.gdal -m{0} {3} input="{1}" output="{2}"'.format(
675+
'r.out.gdal -c -m{0} input="{1}" output="{2}" format="{3}" {4}{5} --overwrite'.format(
640676
' -t' if colorTable else '',
641677
grassName, fileName,
642-
'--overwrite -c createopt="TFW=YES,COMPRESS=LZW"'
678+
outFormat,
679+
' createopt="{}"'.format(createOpt) if createOpt else '',
680+
' metaopt="{}"'.format(metaOpt) if metaOpt else ''
643681
)
644682
)
645683

python/plugins/processing/algs/grass7/Grass7AlgorithmProvider.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
from .Grass7Algorithm import Grass7Algorithm
3939
from processing.tools.system import isWindows, isMac
4040
#from .nviz7 import nviz7
41-
from processing.algs.gdal.GdalUtils import GdalUtils
4241

4342
pluginPath = os.path.normpath(os.path.join(
4443
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -146,11 +145,7 @@ def supportedOutputVectorLayerExtensions(self):
146145
return QgsVectorFileWriter.supportedFormatExtensions()
147146

148147
def supportedOutputRasterLayerExtensions(self):
149-
# We use the same extensions than GDAL because:
150-
# - GRASS is also using GDAL for raster imports.
151-
# - Chances that GRASS is compiled with another version of
152-
# GDAL than QGIS are very limited!
153-
return GdalUtils.getSupportedRasterExtensions()
148+
return Grass7Utils.getSupportedOutputRasterExtensions()
154149

155150
def canBeActivated(self):
156151
return not bool(Grass7Utils.checkGrass7IsInstalled())

python/plugins/processing/algs/grass7/Grass7Utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from processing.core.ProcessingConfig import ProcessingConfig
4141
from processing.tools.system import userFolder, isWindows, isMac, mkdir
4242
from processing.tests.TestData import points
43+
from processing.algs.gdal.GdalUtils import GdalUtils
4344

4445

4546
class Grass7Utils(object):
@@ -55,6 +56,13 @@ class Grass7Utils(object):
5556
GRASS_HELP_PATH = 'GRASS_HELP_PATH'
5657
GRASS_USE_VEXTERNAL = 'GRASS_USE_VEXTERNAL'
5758

59+
# TODO Review all default options formats
60+
GRASS_RASTER_FORMATS_CREATEOPTS = {
61+
'GTiff': 'TFW=YES,COMPRESS=LZW',
62+
'PNG': 'ZLEVEL=9',
63+
'WEBP': 'QUALITY=85'
64+
}
65+
5866
sessionRunning = False
5967
sessionLayers = {}
6068
projectionSet = False
@@ -515,3 +523,27 @@ def grassHelpPath():
515523
else:
516524
# GRASS not available!
517525
return 'https://grass.osgeo.org/grass72/manuals/'
526+
527+
@staticmethod
528+
def getSupportedOutputRasterExtensions():
529+
# We use the same extensions than GDAL because:
530+
# - GRASS is also using GDAL for raster imports.
531+
# - Chances that GRASS is compiled with another version of
532+
# GDAL than QGIS are very limited!
533+
return GdalUtils.getSupportedOutputRasterExtensions()
534+
535+
@staticmethod
536+
def getRasterFormatFromFilename(filename):
537+
"""
538+
Returns Raster format name from a raster filename.
539+
:param filename: The name with extension of the raster.
540+
:return: The Gdal short format name for extension.
541+
"""
542+
ext = os.path.splitext(filename)[1].lower()
543+
ext = ext.lstrip('.')
544+
supported = GdalUtils.getSupportedRasters()
545+
for name in list(supported.keys()):
546+
exts = supported[name]
547+
if ext in exts:
548+
return name
549+
return 'GTiff'

python/plugins/processing/algs/grass7/TODO.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ QGIS3 Processing Port
1717
* TODO Improve unit tests.
1818
* TODO Use prepareAlgorithm for algorithm preparation.
1919
* TODO Support ParameterTable.
20-
* TODO Support multiple output raster formats.
2120
* TODO Support multiple output vector formats.
2221
* TODO Try to use v.external.out on simple algorithms.
2322
* TODO Add an optional/advanced 'format option' textbox if vector output is detected.
@@ -231,6 +230,10 @@ QGIS3 Processing Port
231230
* v_net_steiner.py
232231
* v_net_visibility.py
233232

233+
* DONE Support multiple output file raster formats.
234+
* DONE Add an optional/advanced 'format option' textbox if raster output is detected.
235+
* DONE Detext file format from extension.
236+
* DONE Improve GdalUtils to report raster formats that can be created with GDAL.
234237
* DONE Add GRASS 7.2 new algorithms.
235238
* DONE Remove r.aspect => r.slope.aspect.
236239
* DONE Remove r.median.

python/plugins/processing/gui/ParameterGuiUtils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ def getFileFilter(param):
5959
elif param.type() == 'raster':
6060
return QgsProviderRegistry.instance().fileRasterFilters()
6161
elif param.type() == 'rasterDestination':
62-
exts = dataobjects.getSupportedOutputRasterLayerExtensions()
63-
for i in range(len(exts)):
64-
exts[i] = tr('{0} files (*.{1})', 'QgsProcessingParameterRasterDestination').format(exts[i].upper(), exts[i].lower())
62+
exts = dataobjects.getSupportedOutputRasterFilters()
6563
return ';;'.join(exts) + ';;' + tr('All files (*.*)')
6664
elif param.type() == 'table':
6765
exts = ['csv', 'dbf']

python/plugins/processing/gui/wrappers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def getFileName(self, initial_value=''):
220220
else:
221221
path = ''
222222

223+
# TODO: should use selectedFilter argument for default file format
223224
filename, selected_filter = QFileDialog.getOpenFileName(self.widget, self.tr('Select file'),
224225
path, getFileFilter(self.param))
225226
if filename:

python/plugins/processing/tools/dataobjects.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,24 @@ def getSupportedOutputRasterLayerExtensions():
117117
return allexts
118118

119119

120+
def getSupportedOutputRasterFilters():
121+
"""
122+
Return a list of file filters for supported raster formats.
123+
Supported formats come from Gdal.
124+
:return: a list of strings for Qt file filters.
125+
"""
126+
allFilters = []
127+
supported = GdalUtils.getSupportedOutputRasters()
128+
formatList = sorted(supported.keys())
129+
# Place GTiff as the first format
130+
if 'GTiff' in formatList:
131+
formatList.pop(formatList.index('GTiff'))
132+
formatList.insert(0, 'GTiff')
133+
for f in formatList:
134+
allFilters.append('{0} files (*.{1})'.format(f, ' *.'.join(supported[f])))
135+
return allFilters
136+
137+
120138
def load(fileName, name=None, crs=None, style=None, isRaster=False):
121139
"""Loads a layer/table into the current project, given its file.
122140
"""

0 commit comments

Comments
 (0)