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

935 lines
42 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""
***************************************************************************
Grass7Algorithm.py
---------------------
Date : February 2015
Copyright : (C) 2014-2015 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Victor Olaya'
__date__ = 'February 2015'
__copyright__ = '(C) 2012-2015, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import sys
import os
import re
import uuid
import importlib
2016-04-22 10:38:48 +02:00
from qgis.PyQt.QtCore import QCoreApplication, QUrl
from qgis.core import (Qgis,
QgsRasterLayer,
QgsApplication,
QgsMapLayer,
QgsProcessingUtils,
2017-10-22 10:25:09 +02:00
QgsProcessing,
2017-05-16 15:21:41 +10:00
QgsMessageLog,
QgsVectorFileWriter,
2017-05-16 16:36:00 +10:00
QgsProcessingAlgorithm,
QgsProcessingParameterDefinition,
QgsProcessingException,
QgsProcessingParameterExtent,
QgsProcessingParameterEnum,
QgsProcessingParameterNumber,
QgsProcessingParameterString,
2017-10-22 10:25:09 +02:00
QgsProcessingParameterField,
QgsProcessingParameterPoint,
QgsProcessingParameterBoolean,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterMultipleLayers,
QgsProcessingParameterVectorDestination,
QgsProcessingParameterRasterDestination,
QgsProcessingParameterFileDestination,
2017-12-27 20:13:05 +01:00
QgsProcessingParameterFile,
QgsProcessingParameterFolderDestination,
QgsProcessingOutputFolder,
QgsProcessingOutputVectorLayer,
QgsProcessingOutputRasterLayer,
QgsProcessingOutputHtml,
QgsProcessingUtils)
from qgis.utils import iface
from processing.core.ProcessingConfig import ProcessingConfig
from processing.core.parameters import (getParameterFromString)
from .Grass7Utils import Grass7Utils
#from processing.tools import dataobjects, system
from processing.tools.system import isWindows, getTempFilename
pluginPath = os.path.normpath(os.path.join(
os.path.split(os.path.dirname(__file__))[0], os.pardir))
class Grass7Algorithm(QgsProcessingAlgorithm):
GRASS_OUTPUT_TYPE_PARAMETER = 'GRASS_OUTPUT_TYPE_PARAMETER'
GRASS_MIN_AREA_PARAMETER = 'GRASS_MIN_AREA_PARAMETER'
GRASS_SNAP_TOLERANCE_PARAMETER = 'GRASS_SNAP_TOLERANCE_PARAMETER'
GRASS_REGION_EXTENT_PARAMETER = 'GRASS_REGION_PARAMETER'
GRASS_REGION_CELLSIZE_PARAMETER = 'GRASS_REGION_CELLSIZE_PARAMETER'
2017-10-22 10:25:09 +02:00
GRASS_REGION_ALIGN_TO_RESOLUTION = 'GRASS_REGION_ALIGN_TO_RESOLUTION'
GRASS_RASTER_FORMAT_OPT = 'GRASS_RASTER_FORMAT_OPT'
GRASS_RASTER_FORMAT_META = 'GRASS_RASTER_FORMAT_META'
OUTPUT_TYPES = ['auto', 'point', 'line', 'area']
2017-10-22 10:25:09 +02:00
QGIS_OUTPUT_TYPES = {QgsProcessing.TypeVectorAnyGeometry: 'auto',
QgsProcessing.TypeVectorPoint: 'point',
QgsProcessing.TypeVectorLine: 'line',
QgsProcessing.TypeVectorPolygon: 'area'}
def __init__(self, descriptionfile):
super().__init__()
self._name = ''
self._display_name = ''
self._group = ''
self._groupId = ''
self.groupIdRegex = re.compile('^[^\s\(]+')
self.grass7Name = ''
self.params = []
self.hardcodedStrings = []
2017-10-22 10:25:09 +02:00
self.inputLayers = []
self.descriptionFile = descriptionfile
# Default GRASS parameters
self.region = None
self.cellSize = None
self.snaptTolerance = None
self.outputType = None
self.minArea = None
self.alignToResolution = None
2017-10-22 10:25:09 +02:00
# Load parameters from a description file
self.defineCharacteristicsFromFile()
self.numExportedLayers = 0
# Do we need this anymore?
2017-03-04 16:23:36 +01:00
self.uniqueSuffix = str(uuid.uuid4()).replace('-', '')
# Use the ext mechanism
name = self.name().replace('.', '_')
try:
2017-10-22 10:25:09 +02:00
self.module = importlib.import_module(
'processing.algs.grass7.ext.{}'.format(name))
except ImportError:
self.module = None
def createInstance(self):
return self.__class__(self.descriptionFile)
2017-10-22 10:25:09 +02:00
def name(self):
return self._name
def displayName(self):
return self._display_name
def group(self):
return self._group
def groupId(self):
return self._groupId
def icon(self):
return QgsApplication.getThemeIcon("/providerGrass.svg")
def svgIconPath(self):
return QgsApplication.iconPath("providerGrass.svg")
def flags(self):
# TODO - maybe it's safe to background thread this?
return super().flags() | QgsProcessingAlgorithm.FlagNoThreading
def tr(self, string, context=''):
if context == '':
context = self.__class__.__name__
return QCoreApplication.translate(context, string)
2017-10-22 10:25:09 +02:00
def helpUrl(self):
helpPath = Grass7Utils.grassHelpPath()
if helpPath == '':
return None
if os.path.exists(helpPath):
return QUrl.fromLocalFile(os.path.join(helpPath, '{}.html'.format(self.grass7Name))).toString()
else:
return helpPath + '{}.html'.format(self.grass7Name)
def initAlgorithm(self, config=None):
"""
Algorithm initialization
"""
for p in self.params:
# We use createOutput argument for automatic output creation
res = self.addParameter(p, True)
# File destinations are not automatically added as outputs
2017-10-22 10:25:09 +02:00
if (isinstance(p, QgsProcessingParameterFileDestination)
and p.defaultFileExtension().lower() == 'html'):
self.addOutput(QgsProcessingOutputHtml(p.name(), p.description()))
def defineCharacteristicsFromFile(self):
"""
Create algorithm parameters and outputs from a text file.
"""
with open(self.descriptionFile) as lines:
# First line of the file is the Grass algorithm name
line = lines.readline().strip('\n').strip()
self.grass7Name = line
# Second line if the algorithm name in Processing
line = lines.readline().strip('\n').strip()
self._name = line
self._display_name = QCoreApplication.translate("GrassAlgorithm", line)
if " - " not in self._name:
self._name = self.grass7Name + " - " + self._name
self._display_name = self.grass7Name + " - " + self._display_name
self._name = self._name[:self._name.find(' ')].lower()
# Read the grass group
line = lines.readline().strip('\n').strip()
self._group = QCoreApplication.translate("GrassAlgorithm", line)
self._groupId = self.groupIdRegex.search(line).group(0).lower()
hasRasterOutput = False
hasRasterInput = False
hasVectorInput = False
vectorOutputs = False
# Then you have parameters/output definition
line = lines.readline().strip('\n').strip()
while line != '':
try:
line = line.strip('\n').strip()
if line.startswith('Hardcoded'):
self.hardcodedStrings.append(line[len('Hardcoded|'):])
parameter = getParameterFromString(line)
if parameter is not None:
self.params.append(parameter)
if isinstance(parameter, QgsProcessingParameterVectorLayer):
hasVectorInput = True
elif isinstance(parameter, QgsProcessingParameterRasterLayer):
hasRasterInput = True
elif isinstance(parameter, QgsProcessingParameterMultipleLayers):
if parameter.layerType() < 3 or parameter.layerType() == 5:
hasVectorInput = True
elif parameter.layerType() == 3:
hasRasterInput = True
elif isinstance(parameter, QgsProcessingParameterVectorDestination):
vectorOutputs = True
elif isinstance(parameter, QgsProcessingParameterRasterDestination):
hasRasterOutput = True
line = lines.readline().strip('\n').strip()
except Exception as e:
QgsMessageLog.logMessage(self.tr('Could not open GRASS GIS 7 algorithm: {0}\n{1}').format(self.descriptionFile, line), self.tr('Processing'), Qgis.Critical)
raise e
param = QgsProcessingParameterExtent(
self.GRASS_REGION_EXTENT_PARAMETER,
self.tr('GRASS GIS 7 region extent'),
optional=True
)
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)
2017-10-22 10:25:09 +02:00
if hasRasterOutput or hasRasterInput:
# Add a cellsize parameter
param = QgsProcessingParameterNumber(
self.GRASS_REGION_CELLSIZE_PARAMETER,
self.tr('GRASS GIS 7 region cellsize (leave 0 for default)'),
type=QgsProcessingParameterNumber.Double,
minValue=0.0, maxValue=sys.float_info.max + 1, defaultValue=0.0
)
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)
2017-10-22 10:25:09 +02:00
if hasRasterOutput:
# Add a createopt parameter for format export
param = QgsProcessingParameterString(
self.GRASS_RASTER_FORMAT_OPT,
self.tr('Output Rasters format options (createopt)'),
multiLine=True, optional=True
)
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)
# Add a metadata parameter for format export
param = QgsProcessingParameterString(
self.GRASS_RASTER_FORMAT_META,
self.tr('Output Rasters format metadata options (metaopt)'),
multiLine=True, optional=True
)
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)
if hasVectorInput:
param = QgsProcessingParameterNumber(self.GRASS_SNAP_TOLERANCE_PARAMETER,
self.tr('v.in.ogr snap tolerance (-1 = no snap)'),
type=QgsProcessingParameterNumber.Double,
minValue=-1.0, maxValue=sys.float_info.max + 1,
defaultValue=-1.0)
2017-05-16 16:36:00 +10:00
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)
param = QgsProcessingParameterNumber(self.GRASS_MIN_AREA_PARAMETER,
self.tr('v.in.ogr min area'),
type=QgsProcessingParameterNumber.Double,
minValue=0.0, maxValue=sys.float_info.max + 1,
defaultValue=0.0001)
2017-05-16 16:36:00 +10:00
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)
2017-10-22 10:25:09 +02:00
if vectorOutputs:
param = QgsProcessingParameterEnum(self.GRASS_OUTPUT_TYPE_PARAMETER,
self.tr('v.out.ogr output type'),
self.OUTPUT_TYPES)
2017-05-16 16:36:00 +10:00
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)
2017-10-22 10:25:09 +02:00
def getDefaultCellSize(self):
2017-09-08 17:47:24 +02:00
"""
Determine a default cell size from all the raster layers.
"""
cellsize = 0.0
2017-10-22 10:25:09 +02:00
layers = [l for l in self.inputLayers if isinstance(l, QgsRasterLayer)]
for layer in layers:
cellsize = max(layer.rasterUnitsPerPixelX(), cellsize)
2017-10-22 10:25:09 +02:00
if cellsize == 0.0:
cellsize = 100.0
2017-10-22 10:25:09 +02:00
return cellsize
def grabDefaultGrassParameters(self, parameters, context):
"""
Imports default GRASS parameters (EXTENT, etc) into
object attributes for faster retrieving.
"""
# GRASS region extent
2017-10-22 10:25:09 +02:00
self.region = self.parameterAsExtent(parameters,
self.GRASS_REGION_EXTENT_PARAMETER,
context)
# GRASS cell size
if self.parameterDefinition(self.GRASS_REGION_CELLSIZE_PARAMETER):
self.cellSize = self.parameterAsDouble(parameters,
self.GRASS_REGION_CELLSIZE_PARAMETER,
context)
# GRASS snap tolerance
self.snapTolerance = self.parameterAsDouble(parameters,
2017-10-22 10:25:09 +02:00
self.GRASS_SNAP_TOLERANCE_PARAMETER,
context)
# GRASS min area
self.minArea = self.parameterAsDouble(parameters,
self.GRASS_MIN_AREA_PARAMETER,
context)
# GRASS output type
self.outputType = self.parameterAsString(parameters,
self.GRASS_OUTPUT_TYPE_PARAMETER,
context)
# GRASS align to resolution
self.alignToResolution = self.parameterAsBool(parameters,
self.GRASS_REGION_ALIGN_TO_RESOLUTION,
context)
2017-10-22 10:25:09 +02:00
def processAlgorithm(self, parameters, context, feedback):
if isWindows():
path = Grass7Utils.grassPath()
if path == '':
raise QgsProcessingException(
self.tr('GRASS GIS 7 folder is not configured. Please '
'configure it before running GRASS GIS 7 algorithms.'))
2016-01-02 16:14:41 +01:00
# Create brand new commands lists
self.commands = []
self.outputCommands = []
self.exportedLayers = {}
# If GRASS session has been created outside of this algorithm then
# get the list of layers loaded in GRASS otherwise start a new
# session
existingSession = Grass7Utils.sessionRunning
if existingSession:
self.exportedLayers = Grass7Utils.getSessionLayers()
else:
Grass7Utils.startGrassSession()
# Handle default GRASS parameters
self.grabDefaultGrassParameters(parameters, context)
2017-10-22 10:25:09 +02:00
# Handle ext functions for inputs/command/outputs
for fName in ['Inputs', 'Command', 'Outputs']:
fullName = 'process{}'.format(fName)
if self.module and hasattr(self.module, fullName):
getattr(self.module, fullName)(self, parameters, context)
else:
getattr(self, fullName)(parameters, context)
# Run GRASS
loglines = []
2016-01-14 12:19:31 +01:00
loglines.append(self.tr('GRASS GIS 7 execution commands'))
for line in self.commands:
feedback.pushCommandInfo(line)
loglines.append(line)
if ProcessingConfig.getSetting(Grass7Utils.GRASS_LOG_COMMANDS):
QgsMessageLog.logMessage("\n".join(loglines), self.tr('Processing'), Qgis.Info)
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.endGrassSession()
# Return outputs map
outputs = {}
2017-10-22 10:25:09 +02:00
for out in self.outputDefinitions():
outName = out.name()
if outName in parameters:
outputs[outName] = parameters[outName]
2017-10-22 10:25:09 +02:00
if isinstance(out, QgsProcessingOutputHtml):
self.convertToHtml(parameters[outName])
return outputs
2017-05-17 08:12:43 +10:00
def processInputs(self, parameters, context):
"""Prepare the GRASS import commands"""
inputs = [p for p in self.parameterDefinitions()
if isinstance(p, (QgsProcessingParameterVectorLayer,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterMultipleLayers))]
for param in inputs:
paramName = param.name()
2017-10-22 10:25:09 +02:00
if not paramName in parameters:
continue
# Handle Null parameter
if parameters[paramName] is None:
2017-10-22 10:25:09 +02:00
continue
elif isinstance(parameters[paramName], str) and len(parameters[paramName]) == 0:
continue
# Raster inputs needs to be imported into temp GRASS DB
if isinstance(param, QgsProcessingParameterRasterLayer):
2017-10-22 10:25:09 +02:00
if paramName not in self.exportedLayers:
self.loadRasterLayerFromParameter(
paramName, parameters, context)
# Vector inputs needs to be imported into temp GRASS DB
elif isinstance(param, QgsProcessingParameterVectorLayer):
2017-10-22 10:25:09 +02:00
if paramName not in self.exportedLayers:
# Attribute tables are also vector inputs
if QgsProcessing.TypeFile in param.dataTypes():
self.loadAttributeTableFromParameter(
paramName, parameters, context)
else:
self.loadVectorLayerFromParameter(
paramName, parameters, context, None)
2017-10-22 10:25:09 +02:00
# For multiple inputs, process each layer
elif isinstance(param, QgsProcessingParameterMultipleLayers):
layers = self.parameterAsLayerList(parameters, paramName, context)
for idx, layer in enumerate(layers):
layerName = '{}_{}'.format(paramName, idx)
2017-10-22 10:25:09 +02:00
# Add a raster layer
if layer.type() == QgsMapLayer.RasterLayer:
self.loadRasterLayer(layerName, layer)
2017-10-22 10:25:09 +02:00
# Add a vector layer
elif layer.type() == QgsMapLayer.VectorLayer:
self.loadVectorLayer(layerName, layer, None)
2017-10-22 10:25:09 +02:00
self.postInputs()
2017-10-22 10:25:09 +02:00
def postInputs(self):
"""
After layer imports, we need to update some internal parameters
"""
# If projection has not already be set, use the project
self.setSessionProjectionFromProject()
# Build GRASS region
2017-09-07 11:25:02 +02:00
if self.region.isEmpty():
2017-10-22 10:25:09 +02:00
self.region = QgsProcessingUtils.combineLayerExtents(self.inputLayers)
command = 'g.region n={} s={} e={} w={}'.format(
self.region.yMaximum(), self.region.yMinimum(),
2017-09-07 11:25:02 +02:00
self.region.xMaximum(), self.region.xMinimum()
)
2017-09-08 17:47:24 +02:00
# Handle cell size
if self.parameterDefinition(self.GRASS_REGION_CELLSIZE_PARAMETER):
if self.cellSize:
cellSize = self.cellSize
else:
2017-10-22 10:25:09 +02:00
cellSize = self.getDefaultCellSize()
command += ' res={}'.format(cellSize)
2017-10-22 10:25:09 +02:00
2017-09-08 17:47:24 +02:00
# Handle align to resolution
if self.alignToResolution:
command += ' -a'
# Add the default parameters commands
self.commands.append(command)
2017-10-22 10:25:09 +02:00
2018-02-18 15:49:19 +01:00
QgsMessageLog.logMessage(self.tr('processInputs end. Commands: {}').format(self.commands), 'Grass7', Qgis.Info)
2017-10-22 10:25:09 +02:00
def processCommand(self, parameters, context, delOutputs=False):
"""
Prepare the GRASS algorithm command
:param parameters:
2017-10-22 10:25:09 +02:00
:param context:
:param delOutputs: do not add outputs to commands.
"""
noOutputs = [o for o in self.parameterDefinitions() if o not in self.destinationParameterDefinitions()]
command = '{} '.format(self.grass7Name)
command += '{}'.join(self.hardcodedStrings)
# Add algorithm command
for param in noOutputs:
paramName = param.name()
value = None
2017-10-22 10:25:09 +02:00
# Exclude default GRASS parameters
if paramName in [self.GRASS_REGION_CELLSIZE_PARAMETER,
self.GRASS_REGION_EXTENT_PARAMETER,
self.GRASS_MIN_AREA_PARAMETER,
self.GRASS_SNAP_TOLERANCE_PARAMETER,
self.GRASS_OUTPUT_TYPE_PARAMETER,
self.GRASS_REGION_ALIGN_TO_RESOLUTION,
self.GRASS_RASTER_FORMAT_OPT,
self.GRASS_RASTER_FORMAT_META]:
continue
2017-10-22 10:25:09 +02:00
# Raster and vector layers
if isinstance(param, (QgsProcessingParameterRasterLayer,
QgsProcessingParameterVectorLayer)):
if paramName in self.exportedLayers:
value = self.exportedLayers[paramName]
else:
value = self.parameterAsCompatibleSourceLayerPath(
parameters, paramName, context,
QgsVectorFileWriter.supportedFormatExtensions()
)
# MultipleLayers
elif isinstance(param, QgsProcessingParameterMultipleLayers):
layers = self.parameterAsLayerList(parameters, paramName, context)
values = []
for idx in range(len(layers)):
layerName = '{}_{}'.format(paramName, idx)
values.append(self.exportedLayers[layerName])
value = ','.join(values)
# For booleans, we just add the parameter name
elif isinstance(param, QgsProcessingParameterBoolean):
if self.parameterAsBool(parameters, paramName, context):
command += ' {}'.format(paramName)
2017-12-28 18:28:18 +01:00
# For Extents, remove if the value is null
elif isinstance(param, QgsProcessingParameterExtent):
if self.parameterAsExtent(parameters, paramName, context):
value = self.parameterAsString(parameters, paramName, context)
# For enumeration, we need to grab the string value
elif isinstance(param, QgsProcessingParameterEnum):
# Handle multiple values
if param.allowMultiple():
indexes = self.parameterAsEnums(parameters, paramName, context)
else:
indexes = [self.parameterAsEnum(parameters, paramName, context)]
2017-12-28 18:28:18 +01:00
if indexes:
value = '"{}"'.format(','.join([param.options()[i] for i in indexes]))
# For strings, we just translate as string
elif isinstance(param, QgsProcessingParameterString):
2017-10-22 10:25:09 +02:00
data = self.parameterAsString(parameters, paramName, context)
# if string is empty, we don't add it
if len(data) > 0:
value = '"{}"'.format(
self.parameterAsString(parameters, paramName, context)
)
# For fields, we just translate as string
elif isinstance(param, QgsProcessingParameterField):
2017-12-27 11:52:01 +01:00
value = ','.join(
self.parameterAsFields(parameters, paramName, context)
)
2017-12-27 20:13:05 +01:00
elif isinstance(param, QgsProcessingParameterFile):
2017-12-28 18:28:18 +01:00
if self.parameterAsString(parameters, paramName, context):
value = '"{}"'.format(
self.parameterAsString(parameters, paramName, context)
)
elif isinstance(param, QgsProcessingParameterPoint):
if self.parameterAsString(parameters, paramName, context):
# parameter specified, evaluate as point
# TODO - handle CRS transform
point = self.parameterAsPoint(parameters, paramName, context)
value = '{},{}'.format(point.x(), point.y())
# For numbers, we translate as a string
elif isinstance(param, (QgsProcessingParameterNumber,
QgsProcessingParameterPoint)):
value = self.parameterAsString(parameters, paramName, context)
# For everything else, we assume that it is a string
else:
value = '"{}"'.format(
self.parameterAsString(parameters, paramName, context)
)
if value:
command += ' {}={}'.format(paramName, value)
# Handle outputs
2017-10-22 10:25:09 +02:00
if not delOutputs:
for out in self.destinationParameterDefinitions():
# We exclude hidden parameters
if out.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
2017-10-22 10:25:09 +02:00
outName = out.name()
# For File destination
if isinstance(out, QgsProcessingParameterFileDestination):
# for HTML reports, we need to redirect stdout
if out.defaultFileExtension().lower() == 'html':
command += ' > "{}"'.format(
self.parameterAsFileOutput(
parameters, outName, context)
)
else:
command += ' {}="{}"'.format(
outName,
self.parameterAsFileOutput(
parameters, outName, context))
# For folders destination
elif isinstance(out, QgsProcessingParameterFolderDestination):
# We need to add a unique temporary basename
uniqueBasename = outName + self.uniqueSuffix
command += ' {}={}'.format(outName, uniqueBasename)
2017-10-22 10:25:09 +02:00
else:
# We add an output name to make sure it is unique if the session
# uses this algorithm several times.
#value = self.parameterAsOutputLayer(parameters, outName, context)
uniqueOutputName = outName + self.uniqueSuffix
command += ' {}={}'.format(outName, uniqueOutputName)
2017-10-22 10:25:09 +02:00
# Add output file to exported layers, to indicate that
# they are present in GRASS
self.exportedLayers[outName] = uniqueOutputName
command += ' --overwrite'
self.commands.append(command)
2018-02-18 15:49:19 +01:00
QgsMessageLog.logMessage(self.tr('processCommands end. Commands: {}').format(self.commands), 'Grass7', Qgis.Info)
2017-10-22 10:25:09 +02:00
def vectorOutputType(self, parameters, context):
"""Determine vector output types for outputs"""
self.outType = 'auto'
if self.parameterDefinition(self.GRASS_OUTPUT_TYPE_PARAMETER):
typeidx = self.parameterAsEnum(parameters,
self.GRASS_OUTPUT_TYPE_PARAMETER,
context)
self.outType = ('auto' if typeidx
is None else self.OUTPUT_TYPES[typeidx])
def processOutputs(self, parameters, context):
"""Prepare the GRASS v.out.ogr commands"""
# TODO: support multiple raster formats.
# TODO: support multiple vector formats.
2017-10-22 10:25:09 +02:00
# Determine general vector output type
self.vectorOutputType(parameters, context)
for out in self.destinationParameterDefinitions():
outName = out.name()
if isinstance(out, QgsProcessingParameterRasterDestination):
2017-10-22 10:25:09 +02:00
self.exportRasterLayerFromParameter(outName, parameters, context)
elif isinstance(out, QgsProcessingParameterVectorDestination):
self.exportVectorLayerFromParameter(outName, parameters, context)
elif isinstance(out, QgsProcessingParameterFolderDestination):
self.exportRasterLayersIntoDirectory(outName, parameters, context)
2017-10-22 10:25:09 +02:00
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.
2017-10-22 10:25:09 +02:00
"""
layer = self.parameterAsRasterLayer(parameters, name, context)
self.loadRasterLayer(name, layer, external, band)
2017-12-29 17:13:30 +01:00
def loadRasterLayer(self, name, layer, external=True, band=1, destName=None):
"""
Creates a dedicated command to load a raster into
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.
2017-12-29 17:13:30 +01:00
:param destName: force the destination name of the raster.
"""
2017-10-22 10:25:09 +02:00
self.inputLayers.append(layer)
self.setSessionProjectionFromLayer(layer)
2017-12-29 17:13:30 +01:00
if not destName:
destName = 'rast_{}'.format(os.path.basename(getTempFilename()))
self.exportedLayers[name] = destName
2017-10-22 10:25:09 +02:00
command = '{0} input="{1}" {2}output="{3}" --overwrite -o'.format(
'r.external' if external else 'r.in.gdal',
os.path.normpath(layer.source()),
2017-10-22 10:25:09 +02:00
'band={} '.format(band) if band else '',
2017-12-29 17:13:30 +01:00
destName)
2017-10-22 10:25:09 +02:00
self.commands.append(command)
2017-10-22 10:25:09 +02:00
def exportRasterLayerFromParameter(self, name, parameters, context, colorTable=True):
"""
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 dict.
:param context: Algorithm context.
2017-10-22 10:25:09 +02:00
:param colorTable: preserve color Table.
"""
fileName = os.path.normpath(
self.parameterAsOutputLayer(parameters, name, context))
2017-10-22 10:25:09 +02:00
grassName = '{}{}'.format(name, self.uniqueSuffix)
outFormat = Grass7Utils.getRasterFormatFromFilename(fileName)
createOpt = self.parameterAsString(parameters, self.GRASS_RASTER_FORMAT_OPT, context)
metaOpt = self.parameterAsString(parameters, self.GRASS_RASTER_FORMAT_META, context)
self.exportRasterLayer(grassName, fileName, colorTable, outFormat, createOpt, metaOpt)
def exportRasterLayer(self, grassName, fileName,
colorTable=True, outFormat='GTiff',
createOpt=None,
metaOpt=None):
2017-10-22 10:25:09 +02:00
"""
Creates a dedicated command to export a raster from
temporary GRASS DB into a file via gdal.
:param grassName: name of the raster to export.
:param fileName: file path of raster layer.
2017-10-22 10:25:09 +02:00
:param colorTable: preserve color Table.
:param outFormat: file format for export.
:param createOpt: creation options for format.
:param metatOpt: metadata options for export.
2017-10-22 10:25:09 +02:00
"""
if not createOpt:
if outFormat in Grass7Utils.GRASS_RASTER_FORMATS_CREATEOPTS:
createOpt = Grass7Utils.GRASS_RASTER_FORMATS_CREATEOPTS[outFormat]
2017-10-22 10:25:09 +02:00
for cmd in [self.commands, self.outputCommands]:
# Adjust region to layer before exporting
cmd.append('g.region raster={}'.format(grassName))
cmd.append(
2017-12-27 20:13:05 +01:00
'r.out.gdal -t -m{0} input="{1}" output="{2}" format="{3}" {4}{5} --overwrite'.format(
'' if colorTable else ' -c',
2017-10-22 10:25:09 +02:00
grassName, fileName,
outFormat,
' createopt="{}"'.format(createOpt) if createOpt else '',
' metaopt="{}"'.format(metaOpt) if metaOpt else ''
2017-10-22 10:25:09 +02:00
)
)
2017-12-29 17:13:30 +01:00
def exportRasterLayersIntoDirectory(self, name, parameters, context, colorTable=True, wholeDB=False):
"""
Creates a dedicated loop command to export rasters from
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.
2017-12-29 17:13:30 +01:00
:param wholeDB: export every raster layer from the GRASSDB
"""
# Grab directory name and temporary basename
outDir = os.path.normpath(
self.parameterAsString(parameters, name, context))
2017-12-29 17:13:30 +01:00
basename = ''
if not wholeDB:
basename = name + self.uniqueSuffix
# Add a loop export from the basename
for cmd in [self.commands, self.outputCommands]:
2017-12-27 20:13:05 +01:00
# TODO Windows support
# TODO Format/options support
cmd.append("for r in $(g.list type=rast pattern='{}*'); do".format(basename))
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"'
)
)
cmd.append("done")
def loadVectorLayerFromParameter(self, name, parameters, context, external=False):
2017-10-22 10:25:09 +02:00
"""
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).
2017-10-22 10:25:09 +02:00
"""
layer = self.parameterAsVectorLayer(parameters, name, context)
self.loadVectorLayer(name, layer, external)
def loadVectorLayer(self, name, layer, external=False):
2017-10-22 10:25:09 +02:00
"""
Creates a dedicated command to load a vector into
temporary GRASS DB.
:param name: name of the parameter
:param layer: QgsMapLayer for the vector layer.
2017-10-22 10:25:09 +02:00
:param external: use v.external (v.in.ogr if False).
"""
# TODO: support selections
# TODO: support multiple input formats
if external is None:
external = ProcessingConfig.getSetting(
Grass7Utils.GRASS_USE_VEXTERNAL)
self.inputLayers.append(layer)
self.setSessionProjectionFromLayer(layer)
destFilename = 'vector_{}'.format(os.path.basename(getTempFilename()))
2017-10-22 10:25:09 +02:00
self.exportedLayers[name] = destFilename
command = '{0}{1}{2} input="{3}" output="{4}" --overwrite -o'.format(
'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 '',
os.path.normpath(layer.source()),
2017-10-22 10:25:09 +02:00
destFilename)
self.commands.append(command)
def exportVectorLayerFromParameter(self, name, parameters, context):
"""
Creates a dedicated command to export a raster from
temporary GRASS DB into a file via gdal.
:param grassName: name of the parameter
:param fileName: file path of raster layer
:param colorTable: preserve color Table.
"""
fileName = os.path.normpath(
self.parameterAsOutputLayer(parameters, name, context))
2017-10-22 10:25:09 +02:00
# Find if there is a dataType
dataType = self.outType
if self.outType == 'auto':
parameter = self.parameterDefinition(name)
if parameter:
layerType = parameter.dataType()
if layerType in self.QGIS_OUTPUT_TYPES:
dataType = self.QGIS_OUTPUT_TYPES[layerType]
grassName = '{}{}'.format(name, self.uniqueSuffix)
self.exportVectorLayer(grassName, fileName, dataType)
def exportVectorLayer(self, grassName, fileName, dataType='auto', layer=None, nocats=False):
"""
Creates a dedicated command to export a vector from
temporary GRASS DB into a file via ogr.
2017-12-28 18:28:18 +01:00
:param grassName: name of the parameter.
:param fileName: file path of raster layer.
:param dataType: GRASS data type for exporting data.
:param layer: In GRASS a vector can have multiple layers.
:param nocats: Also export features without category if True.
2017-10-22 10:25:09 +02:00
"""
for cmd in [self.commands, self.outputCommands]:
cmd.append(
2017-10-22 19:03:39 +02:00
'v.out.ogr{0} type={1} {2} input="{3}" output="{4}" {5}'.format(
2017-12-28 18:28:18 +01:00
' -c' if nocats else '',
2017-10-22 10:25:09 +02:00
dataType,
2017-10-22 19:03:39 +02:00
'layer={}'.format(layer) if layer else '',
2017-10-22 10:25:09 +02:00
grassName,
fileName,
'format=ESRI_Shapefile --overwrite'
)
)
def loadAttributeTableFromParameter(self, name, parameters, context):
"""
Creates a dedicated command to load an attribute table
into the temporary GRASS DB.
:param name: name of the parameter
:param parameters: Parameters of the algorithm.
:param context: Processing context
"""
table = self.parameterAsVectorLayer(parameters, name, context)
self.loadAttributeTable(name, table)
def loadAttributeTable(self, name, layer, destName=None):
"""
Creates a dedicated command to load an attribute table
into the temporary GRASS DB.
:param name: name of the input parameter.
:param layer: a layer object to import from.
:param destName: force the name for the table into GRASS DB.
"""
self.inputLayers.append(layer)
if not destName:
destName = 'table_{}'.format(os.path.basename(getTempFilename()))
self.exportedLayers[name] = destName
command = 'db.in.ogr --overwrite input="{0}" output="{1}"'.format(
os.path.normpath(layer.source()), destName)
self.commands.append(command)
def exportAttributeTable(self, grassName, fileName, outFormat='CSV', layer=1):
"""
Creates a dedicated command to export an attribute
table from the temporary GRASS DB into a file via ogr.
:param grassName: name of the parameter.
:param fileName: file path of raster layer.
:param outFormat: file format for export.
:param layer: In GRASS a vector can have multiple layers.
"""
for cmd in [self.commands, self.outputCommands]:
cmd.append(
'db.out.ogr input="{0}" output="{1}" layer={2} format={3} --overwrite'.format(
grassName, fileName, layer, outFormat
)
)
def setSessionProjectionFromProject(self):
"""
Set the projection from the project.
We creates a PROJ4 definition which is transmitted to Grass
"""
2016-05-06 12:02:59 +02:00
if not Grass7Utils.projectionSet and iface:
2016-01-20 16:39:13 +02:00
proj4 = iface.mapCanvas().mapSettings().destinationCrs().toProj4()
command = 'g.proj -c proj4="{}"'.format(proj4)
self.commands.append(command)
Grass7Utils.projectionSet = True
def setSessionProjectionFromLayer(self, layer):
"""
Set the projection from a QgsVectorLayer.
We creates a PROJ4 definition which is transmitted to Grass
"""
if not Grass7Utils.projectionSet:
proj4 = str(layer.crs().toProj4())
command = 'g.proj -c proj4="{}"'.format(proj4)
self.commands.append(command)
Grass7Utils.projectionSet = True
2017-10-22 10:25:09 +02:00
def convertToHtml(self, fileName):
# Read HTML contents
lines = []
html = False
with open(fileName, 'r', encoding='utf-8') as f:
lines = f.readlines()
if len(lines) > 1 and '<html>' not in lines[0]:
# Then write into the HTML file
with open(fileName, 'w', encoding='utf-8') as f:
f.write('<html><head>')
f.write('<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head>')
f.write('<body><p>')
for line in lines:
f.write('{}</br>'.format(line))
f.write('</p></body></html>')
def canExecute(self):
message = Grass7Utils.checkGrassIsInstalled()
return not message, message
2017-05-16 15:21:41 +10:00
def checkParameterValues(self, parameters, context):
if self.module:
if hasattr(self.module, 'checkParameterValuesBeforeExecuting'):
func = getattr(self.module, 'checkParameterValuesBeforeExecuting')
2017-10-22 10:25:09 +02:00
#return func(self, parameters, context), None
return None, func(self, parameters, context)
2017-05-16 15:21:41 +10:00
return super(Grass7Algorithm, self).checkParameterValues(parameters, context)