Skip to content

Commit

Permalink
Improve GRASS provider:
Browse files Browse the repository at this point in the history
- Improve GRASS detection for all OS.
- Use GRASS --exec command.
- Unified GRASS batch job method for all OS (easier to maintain).
- Handle MS-Windows codepages (for data only, if you have a username with special characters, it will not work).
- Better support for filepath normalization.
- add -m option to r.out.gdal.
  • Loading branch information
Médéric Ribreux committed Nov 4, 2017
1 parent c63df20 commit 6c81895
Show file tree
Hide file tree
Showing 2 changed files with 268 additions and 200 deletions.
69 changes: 39 additions & 30 deletions python/plugins/processing/algs/grass7/Grass7Algorithm.py
Expand Up @@ -266,11 +266,8 @@ def getDefaultCellSize(self):
cellsize = 0.0
layers = [l for l in self.inputLayers if isinstance(l, QgsRasterLayer)]

# Use this function to calculate cell size
def cz(l, cellsize): return max(cellsize, (l.extent().xMaximum() - l.extent().xMinimum()) / l.width())

for layer in layers:
cellsize = cz(layer, cellsize)
cellsize = max(layer.rasterUnitsPerPixelX(), cellsize)

if cellsize == 0.0:
cellsize = 100.0
Expand Down Expand Up @@ -328,7 +325,7 @@ def processAlgorithm(self, parameters, context, feedback):
if existingSession:
self.exportedLayers = Grass7Utils.getSessionLayers()
else:
Grass7Utils.startGrass7Session()
Grass7Utils.startGrassSession()

# Handle default GRASS parameters
self.grabDefaultGrassParameters(parameters, context)
Expand All @@ -350,17 +347,16 @@ def processAlgorithm(self, parameters, context, feedback):
if ProcessingConfig.getSetting(Grass7Utils.GRASS_LOG_COMMANDS):
QgsMessageLog.logMessage("\n".join(loglines), self.tr('Processing'), QgsMessageLog.INFO)

Grass7Utils.executeGrass7(self.commands, feedback, self.outputCommands)
Grass7Utils.executeGrass(self.commands, feedback, self.outputCommands)

# If the session has been created outside of this algorithm, add
# the new GRASS GIS 7 layers to it otherwise finish the session
if existingSession:
Grass7Utils.addSessionLayers(self.exportedLayers)
else:
Grass7Utils.endGrass7Session()
Grass7Utils.endGrassSession()

# Return outputs map
QgsMessageLog.logMessage('outputDefinitions: {}'.format(self.outputDefinitions()), 'Grass7', QgsMessageLog.INFO)
outputs = {}
for out in self.outputDefinitions():
outName = out.name()
Expand All @@ -369,7 +365,6 @@ def processAlgorithm(self, parameters, context, feedback):
if isinstance(out, QgsProcessingOutputHtml):
self.convertToHtml(parameters[outName])

QgsMessageLog.logMessage('processAlgorithm end. outputs: {}'.format(outputs), 'Grass7', QgsMessageLog.INFO)
return outputs

def processInputs(self, parameters, context):
Expand Down Expand Up @@ -582,30 +577,35 @@ def processOutputs(self, parameters, context):
elif isinstance(out, QgsProcessingParameterFolderDestination):
self.exportRasterLayersIntoDirectory(outName, parameters, context)

QgsMessageLog.logMessage('processOutputs. Commands: {}'.format(self.commands), 'Grass7', QgsMessageLog.INFO)

def loadRasterLayerFromParameter(self, name, parameters, context, external=True, band=1):
"""
Creates a dedicated command to load a raster into
the temporary GRASS DB.
:param name: name of the parameter.
:param parameters: algorithm parameters dict.
:param context: algorithm context.
:param external: True if using r.external.
:param band: imports only specified band. None for all bands.
"""
layer = self.parameterAsRasterLayer(parameters, name, context)
self.loadRasterLayer(name, layer, external, band)

def loadRasterLayer(self, name, layer, external=True, band=1):
"""
Creates a dedicated command to load a raster into
temporary GRASS DB.
:layerKey: name of the parameter
:layerSrc: file path of raster layer
:external: use r.external (r.in.gdal if False).
:band: import only this band (if None, all bands are imported).
the temporary GRASS DB.
:param name: name of the parameter.
:param layer: QgsMapLayer for the raster layer.
:param external: True if using r.external.
:param band: imports only specified band. None for all bands.
"""
self.inputLayers.append(layer)
self.setSessionProjectionFromLayer(layer)
destFilename = 'a' + os.path.basename(getTempFilename())
self.exportedLayers[name] = destFilename
command = '{0} input="{1}" {2}output="{3}" --overwrite -o'.format(
'r.external' if external else 'r.in.gdal',
layer.source(),
os.path.normpath(layer.source()),
'band={} '.format(band) if band else '',
destFilename)
self.commands.append(command)
Expand All @@ -615,11 +615,12 @@ def exportRasterLayerFromParameter(self, name, parameters, context, colorTable=T
Creates a dedicated command to export a raster from
temporary GRASS DB into a file via gdal.
:param name: name of the parameter.
:param parameters: Algorithm parameters list.
:param parameters: Algorithm parameters dict.
:param context: Algorithm context.
:param colorTable: preserve color Table.
"""
fileName = self.parameterAsOutputLayer(parameters, name, context)
fileName = os.path.normpath(
self.parameterAsOutputLayer(parameters, name, context))
grassName = '{}{}'.format(name, self.uniqueSuffix)
self.exportRasterLayer(grassName, fileName, colorTable)

Expand All @@ -635,7 +636,7 @@ def exportRasterLayer(self, grassName, fileName, colorTable=True):
# Adjust region to layer before exporting
cmd.append('g.region raster={}'.format(grassName))
cmd.append(
'r.out.gdal{0} {3} input="{1}" output="{2}"'.format(
'r.out.gdal -m{0} {3} input="{1}" output="{2}"'.format(
' -t' if colorTable else '',
grassName, fileName,
'--overwrite -c createopt="TFW=YES,COMPRESS=LZW"'
Expand All @@ -645,21 +646,23 @@ def exportRasterLayer(self, grassName, fileName, colorTable=True):
def exportRasterLayersIntoDirectory(self, name, parameters, context, colorTable=True):
"""
Creates a dedicated loop command to export rasters from
temporary GRASS DB into a directory gdal.
:param name: name of the parameter
:param fileName: file path of raster layer
temporary GRASS DB into a directory via gdal.
:param name: name of the output directory parameter.
:param parameters: Algorithm parameters dict.
:param context: Algorithm context.
:param colorTable: preserve color Table.
"""
# Grab directory name and temporary basename
outDir = self.parameterAsString(parameters, name, context)
outDir = os.path.normpath(
self.parameterAsString(parameters, name, context))
basename = name + self.uniqueSuffix

# Add a loop export from the basename
for cmd in [self.commands, self.outputCommands]:
# Adjust region to layer before exporting
# TODO: Does-it works under MS-Windows or MacOSX?
cmd.append("for r in $(g.list type=rast pattern='{}*'); do".format(basename))
cmd.append(" r.out.gdal{0} input=${{r}} output={1}/${{r}}.tif {2}".format(
cmd.append(" r.out.gdal -m{0} input=${{r}} output={1}/${{r}}.tif {2}".format(
' -t' if colorTable else '', outDir,
'--overwrite -c createopt="TFW=YES,COMPRESS=LZW"'
)
Expand All @@ -668,6 +671,12 @@ def exportRasterLayersIntoDirectory(self, name, parameters, context, colorTable=

def loadVectorLayerFromParameter(self, name, parameters, context, external=None):
"""
Creates a dedicated command to load a vector into
the temporary GRASS DB.
:param name: name of the parameter
:param parameters: Parameters of the algorithm.
:param context: Processing context
:param external: use v.external (v.in.ogr if False).
"""
layer = self.parameterAsVectorLayer(parameters, name, context)
self.loadVectorLayer(name, layer, external)
Expand All @@ -677,8 +686,7 @@ def loadVectorLayer(self, name, layer, external=None):
Creates a dedicated command to load a vector into
temporary GRASS DB.
:param name: name of the parameter
:param parameters: Parameters of the algorithm.
:param context: Processing context
:param layer: QgsMapLayer for the vector layer.
:param external: use v.external (v.in.ogr if False).
"""
# TODO: support selections
Expand All @@ -694,7 +702,7 @@ def loadVectorLayer(self, name, layer, external=None):
'v.external' if external else 'v.in.ogr',
' min_area={}'.format(self.minArea) if not external else '',
' snap={}'.format(self.snapTolerance) if not external else '',
layer.source(),
os.path.normpath(layer.source()),
destFilename)
self.commands.append(command)

Expand All @@ -706,7 +714,8 @@ def exportVectorLayerFromParameter(self, name, parameters, context):
:param fileName: file path of raster layer
:param colorTable: preserve color Table.
"""
fileName = self.parameterAsOutputLayer(parameters, name, context)
fileName = os.path.normpath(
self.parameterAsOutputLayer(parameters, name, context))
# Find if there is a dataType
dataType = self.outType
if self.outType == 'auto':
Expand Down Expand Up @@ -778,7 +787,7 @@ def convertToHtml(self, fileName):
f.write('</p></body></html>')

def canExecute(self):
message = Grass7Utils.checkGrass7IsInstalled()
message = Grass7Utils.checkGrassIsInstalled()
return not message, message

def checkParameterValues(self, parameters, context):
Expand Down

0 comments on commit 6c81895

Please sign in to comment.