Skip to content

Commit

Permalink
Merge pull request #6996 from alexbruy/processing-grass-formats
Browse files Browse the repository at this point in the history
[processing] allow GRASS algorithms to save vectors in any GDAL-supported format
  • Loading branch information
alexbruy committed May 15, 2018
2 parents 0383a14 + 67965ca commit a687744
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 29 deletions.
79 changes: 54 additions & 25 deletions python/plugins/processing/algs/grass7/Grass7Algorithm.py
Expand Up @@ -89,6 +89,8 @@ class Grass7Algorithm(QgsProcessingAlgorithm):
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'
GRASS_VECTOR_DSCO = 'GRASS_VECTOR_DSCO'
GRASS_VECTOR_LCO = 'GRASS_VECTOR_LCO'

OUTPUT_TYPES = ['auto', 'point', 'line', 'area']
QGIS_OUTPUT_TYPES = {QgsProcessing.TypeVectorAnyGeometry: 'auto',
Expand Down Expand Up @@ -287,12 +289,31 @@ def defineCharacteristicsFromFile(self):
self.params.append(param)

if vectorOutputs:
# Add an optional output type
param = QgsProcessingParameterEnum(self.GRASS_OUTPUT_TYPE_PARAMETER,
self.tr('v.out.ogr output type'),
self.OUTPUT_TYPES)
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)

# Add a DSCO parameter for format export
param = QgsProcessingParameterString(
self.GRASS_VECTOR_DSCO,
self.tr('v.out.ogr output data source options (dsco)'),
multiLine=True, optional=True
)
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)

# Add a LCO parameter for format export
param = QgsProcessingParameterString(
self.GRASS_VECTOR_LCO,
self.tr('v.out.ogr output layer options (lco)'),
multiLine=True, optional=True
)
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.params.append(param)

def getDefaultCellSize(self):
"""
Determine a default cell size from all the raster layers.
Expand Down Expand Up @@ -502,7 +523,9 @@ def processCommand(self, parameters, context, feedback, delOutputs=False):
self.GRASS_OUTPUT_TYPE_PARAMETER,
self.GRASS_REGION_ALIGN_TO_RESOLUTION,
self.GRASS_RASTER_FORMAT_OPT,
self.GRASS_RASTER_FORMAT_META]:
self.GRASS_RASTER_FORMAT_META,
self.GRASS_VECTOR_DSCO,
self.GRASS_VECTOR_LCO]:
continue

# Raster and vector layers
Expand Down Expand Up @@ -629,9 +652,6 @@ def vectorOutputType(self, parameters, context):

def processOutputs(self, parameters, context, feedback):
"""Prepare the GRASS v.out.ogr commands"""
# TODO: support multiple raster formats.
# TODO: support multiple vector formats.

# Determine general vector output type
self.vectorOutputType(parameters, context)

Expand Down Expand Up @@ -820,16 +840,19 @@ def loadVectorLayer(self, name, layer, external=False):
destFilename)
self.commands.append(command)

def exportVectorLayerFromParameter(self, name, parameters, context):
def exportVectorLayerFromParameter(self, name, parameters, context, layer=None, nocats=False):
"""
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.
Creates a dedicated command to export a vector from
a QgsProcessingParameter.
:param name: name of the parameter.
:param context: parameters context.
:param layer: for vector with multiples layers, exports only one layer.
:param nocats: do not export GRASS categories.
"""
fileName = os.path.normpath(
self.parameterAsOutputLayer(parameters, name, context))
grassName = '{}{}'.format(name, self.uniqueSuffix)

# Find if there is a dataType
dataType = self.outType
if self.outType == 'auto':
Expand All @@ -839,28 +862,34 @@ def exportVectorLayerFromParameter(self, name, parameters, context):
if layerType in self.QGIS_OUTPUT_TYPES:
dataType = self.QGIS_OUTPUT_TYPES[layerType]

grassName = '{}{}'.format(name, self.uniqueSuffix)
self.exportVectorLayer(grassName, fileName, dataType)
outFormat = QgsVectorFileWriter.driverForExtension(os.path.splitext(fileName)[1]).replace(' ', '_')
dsco = self.parameterAsString(parameters, self.GRASS_VECTOR_DSCO, context)
lco = self.parameterAsString(parameters, self.GRASS_VECTOR_LCO, context)
self.exportVectorLayer(grassName, fileName, layer, nocats, dataType, outFormat, dsco, lco)

def exportVectorLayer(self, grassName, fileName, dataType='auto', layer=None, nocats=False):
def exportVectorLayer(self, grassName, fileName, layer=None, nocats=False, dataType='auto',
outFormat='GPKG', dsco=None, lco=None):
"""
Creates a dedicated command to export a vector from
temporary GRASS DB into a file via ogr.
: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.
temporary GRASS DB into a file via OGR.
:param grassName: name of the vector to export.
:param fileName: file path of vector layer.
:param dataType: export only this type of data.
:param layer: for vector with multiples layers, exports only one layer.
:param nocats: do not export GRASS categories.
:param outFormat: file format for export.
:param dsco: datasource creation options for format.
:param lco: layer creation options for format.
"""
for cmd in [self.commands, self.outputCommands]:
cmd.append(
'v.out.ogr{0} type={1} {2} input="{3}" output="{4}" {5}'.format(
' -c' if nocats else '',
dataType,
'v.out.ogr{0} type="{1}" input="{2}" output="{3}" format="{4}" {5}{6}{7} --overwrite'.format(
'' if nocats else ' -c',
dataType, grassName, fileName,
outFormat,
'layer={}'.format(layer) if layer else '',
grassName,
fileName,
'format=ESRI_Shapefile --overwrite'
' dsco="{}"'.format(dsco) if dsco else '',
' lco="{}"'.format(lco) if lco else ''
)
)

Expand Down
79 changes: 75 additions & 4 deletions python/plugins/processing/tests/Grass7AlgorithmsVectorTest.py
Expand Up @@ -95,17 +95,25 @@ def testMemoryLayerInput(self):

temp_file = os.path.join(self.temp_dir, 'grass_output.shp')
parameters = {'input': 'testmem',
'cats': '',
'where': '',
'type': [0, 1, 4],
'distance': 1,
'minordistance': None,
'angle': 0,
'column': None,
'scale': 1,
'tolerance': 0.01,
'-s': False,
'-c': False,
'-t': False,
'output': temp_file,
'GRASS_SNAP_TOLERANCE_PARAMETER': -1, 'GRASS_MIN_AREA_PARAMETER': 0.0001,
'GRASS_OUTPUT_TYPE_PARAMETER': 0}
'GRASS_REGION_PARAMETER': None,
'GRASS_SNAP_TOLERANCE_PARAMETER': -1,
'GRASS_MIN_AREA_PARAMETER': 0.0001,
'GRASS_OUTPUT_TYPE_PARAMETER': 0,
'GRASS_VECTOR_DSCO': '',
'GRASS_VECTOR_LCO': ''}
feedback = QgsProcessingFeedback()

results, ok = alg.run(parameters, context, feedback)
Expand Down Expand Up @@ -146,17 +154,25 @@ def testFeatureSourceInput(self):
self.assertIsNotNone(alg)
temp_file = os.path.join(self.temp_dir, 'grass_output_sel.shp')
parameters = {'input': QgsProcessingFeatureSourceDefinition('testmem', True),
'cats': '',
'where': '',
'type': [0, 1, 4],
'distance': 1,
'minordistance': None,
'angle': 0,
'column': None,
'scale': 1,
'tolerance': 0.01,
'-s': False,
'-c': False,
'-t': False,
'output': temp_file,
'GRASS_SNAP_TOLERANCE_PARAMETER': -1, 'GRASS_MIN_AREA_PARAMETER': 0.0001,
'GRASS_OUTPUT_TYPE_PARAMETER': 0}
'GRASS_REGION_PARAMETER': None,
'GRASS_SNAP_TOLERANCE_PARAMETER': -1,
'GRASS_MIN_AREA_PARAMETER': 0.0001,
'GRASS_OUTPUT_TYPE_PARAMETER': 0,
'GRASS_VECTOR_DSCO': '',
'GRASS_VECTOR_LCO': ''}
feedback = QgsProcessingFeedback()

results, ok = alg.run(parameters, context, feedback)
Expand All @@ -170,6 +186,61 @@ def testFeatureSourceInput(self):

QgsProject.instance().removeMapLayer(layer)

def testOutputToGeopackage(self):
# create a memory layer and add to project and context
layer = QgsVectorLayer("Point?crs=epsg:3857&field=fldtxt:string&field=fldint:integer",
"testmem", "memory")
self.assertTrue(layer.isValid())
pr = layer.dataProvider()
f = QgsFeature()
f.setAttributes(["test", 123])
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(100, 200)))
f2 = QgsFeature()
f2.setAttributes(["test2", 457])
f2.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(110, 200)))
self.assertTrue(pr.addFeatures([f, f2]))
self.assertEqual(layer.featureCount(), 2)
QgsProject.instance().addMapLayer(layer)
context = QgsProcessingContext()
context.setProject(QgsProject.instance())

alg = QgsApplication.processingRegistry().createAlgorithmById('grass7:v.buffer')
self.assertIsNotNone(alg)

temp_file = os.path.join(self.temp_dir, 'grass_output.gpkg')
parameters = {'input': 'testmem',
'cats': '',
'where': '',
'type': [0, 1, 4],
'distance': 1,
'minordistance': None,
'angle': 0,
'column': None,
'scale': 1,
'tolerance': 0.01,
'-s': False,
'-c': False,
'-t': False,
'output': temp_file,
'GRASS_REGION_PARAMETER': None,
'GRASS_SNAP_TOLERANCE_PARAMETER': -1,
'GRASS_MIN_AREA_PARAMETER': 0.0001,
'GRASS_OUTPUT_TYPE_PARAMETER': 0,
'GRASS_VECTOR_DSCO': '',
'GRASS_VECTOR_LCO': ''}
feedback = QgsProcessingFeedback()

results, ok = alg.run(parameters, context, feedback)
self.assertTrue(ok)
self.assertTrue(os.path.exists(temp_file))

# make sure that layer has correct features
res = QgsVectorLayer(temp_file, 'res')
self.assertTrue(res.isValid())
self.assertEqual(res.featureCount(), 2)

QgsProject.instance().removeMapLayer(layer)


if __name__ == '__main__':
nose2.main()

0 comments on commit a687744

Please sign in to comment.