Skip to content

Commit a687744

Browse files
authoredMay 15, 2018
Merge pull request #6996 from alexbruy/processing-grass-formats
[processing] allow GRASS algorithms to save vectors in any GDAL-supported format
2 parents 0383a14 + 67965ca commit a687744

File tree

2 files changed

+129
-29
lines changed

2 files changed

+129
-29
lines changed
 

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

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class Grass7Algorithm(QgsProcessingAlgorithm):
8989
GRASS_REGION_ALIGN_TO_RESOLUTION = 'GRASS_REGION_ALIGN_TO_RESOLUTION'
9090
GRASS_RASTER_FORMAT_OPT = 'GRASS_RASTER_FORMAT_OPT'
9191
GRASS_RASTER_FORMAT_META = 'GRASS_RASTER_FORMAT_META'
92+
GRASS_VECTOR_DSCO = 'GRASS_VECTOR_DSCO'
93+
GRASS_VECTOR_LCO = 'GRASS_VECTOR_LCO'
9294

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

289291
if vectorOutputs:
292+
# Add an optional output type
290293
param = QgsProcessingParameterEnum(self.GRASS_OUTPUT_TYPE_PARAMETER,
291294
self.tr('v.out.ogr output type'),
292295
self.OUTPUT_TYPES)
293296
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
294297
self.params.append(param)
295298

299+
# Add a DSCO parameter for format export
300+
param = QgsProcessingParameterString(
301+
self.GRASS_VECTOR_DSCO,
302+
self.tr('v.out.ogr output data source options (dsco)'),
303+
multiLine=True, optional=True
304+
)
305+
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
306+
self.params.append(param)
307+
308+
# Add a LCO parameter for format export
309+
param = QgsProcessingParameterString(
310+
self.GRASS_VECTOR_LCO,
311+
self.tr('v.out.ogr output layer options (lco)'),
312+
multiLine=True, optional=True
313+
)
314+
param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
315+
self.params.append(param)
316+
296317
def getDefaultCellSize(self):
297318
"""
298319
Determine a default cell size from all the raster layers.
@@ -502,7 +523,9 @@ def processCommand(self, parameters, context, feedback, delOutputs=False):
502523
self.GRASS_OUTPUT_TYPE_PARAMETER,
503524
self.GRASS_REGION_ALIGN_TO_RESOLUTION,
504525
self.GRASS_RASTER_FORMAT_OPT,
505-
self.GRASS_RASTER_FORMAT_META]:
526+
self.GRASS_RASTER_FORMAT_META,
527+
self.GRASS_VECTOR_DSCO,
528+
self.GRASS_VECTOR_LCO]:
506529
continue
507530

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

630653
def processOutputs(self, parameters, context, feedback):
631654
"""Prepare the GRASS v.out.ogr commands"""
632-
# TODO: support multiple raster formats.
633-
# TODO: support multiple vector formats.
634-
635655
# Determine general vector output type
636656
self.vectorOutputType(parameters, context)
637657

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

823-
def exportVectorLayerFromParameter(self, name, parameters, context):
843+
def exportVectorLayerFromParameter(self, name, parameters, context, layer=None, nocats=False):
824844
"""
825-
Creates a dedicated command to export a raster from
826-
temporary GRASS DB into a file via gdal.
827-
:param grassName: name of the parameter
828-
:param fileName: file path of raster layer
829-
:param colorTable: preserve color Table.
845+
Creates a dedicated command to export a vector from
846+
a QgsProcessingParameter.
847+
:param name: name of the parameter.
848+
:param context: parameters context.
849+
:param layer: for vector with multiples layers, exports only one layer.
850+
:param nocats: do not export GRASS categories.
830851
"""
831852
fileName = os.path.normpath(
832853
self.parameterAsOutputLayer(parameters, name, context))
854+
grassName = '{}{}'.format(name, self.uniqueSuffix)
855+
833856
# Find if there is a dataType
834857
dataType = self.outType
835858
if self.outType == 'auto':
@@ -839,28 +862,34 @@ def exportVectorLayerFromParameter(self, name, parameters, context):
839862
if layerType in self.QGIS_OUTPUT_TYPES:
840863
dataType = self.QGIS_OUTPUT_TYPES[layerType]
841864

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

845-
def exportVectorLayer(self, grassName, fileName, dataType='auto', layer=None, nocats=False):
870+
def exportVectorLayer(self, grassName, fileName, layer=None, nocats=False, dataType='auto',
871+
outFormat='GPKG', dsco=None, lco=None):
846872
"""
847873
Creates a dedicated command to export a vector from
848-
temporary GRASS DB into a file via ogr.
849-
:param grassName: name of the parameter.
850-
:param fileName: file path of raster layer.
851-
:param dataType: GRASS data type for exporting data.
852-
:param layer: In GRASS a vector can have multiple layers.
853-
:param nocats: Also export features without category if True.
874+
temporary GRASS DB into a file via OGR.
875+
:param grassName: name of the vector to export.
876+
:param fileName: file path of vector layer.
877+
:param dataType: export only this type of data.
878+
:param layer: for vector with multiples layers, exports only one layer.
879+
:param nocats: do not export GRASS categories.
880+
:param outFormat: file format for export.
881+
:param dsco: datasource creation options for format.
882+
:param lco: layer creation options for format.
854883
"""
855884
for cmd in [self.commands, self.outputCommands]:
856885
cmd.append(
857-
'v.out.ogr{0} type={1} {2} input="{3}" output="{4}" {5}'.format(
858-
' -c' if nocats else '',
859-
dataType,
886+
'v.out.ogr{0} type="{1}" input="{2}" output="{3}" format="{4}" {5}{6}{7} --overwrite'.format(
887+
'' if nocats else ' -c',
888+
dataType, grassName, fileName,
889+
outFormat,
860890
'layer={}'.format(layer) if layer else '',
861-
grassName,
862-
fileName,
863-
'format=ESRI_Shapefile --overwrite'
891+
' dsco="{}"'.format(dsco) if dsco else '',
892+
' lco="{}"'.format(lco) if lco else ''
864893
)
865894
)
866895

‎python/plugins/processing/tests/Grass7AlgorithmsVectorTest.py

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,25 @@ def testMemoryLayerInput(self):
9595

9696
temp_file = os.path.join(self.temp_dir, 'grass_output.shp')
9797
parameters = {'input': 'testmem',
98+
'cats': '',
99+
'where': '',
98100
'type': [0, 1, 4],
99101
'distance': 1,
102+
'minordistance': None,
100103
'angle': 0,
104+
'column': None,
101105
'scale': 1,
102106
'tolerance': 0.01,
103107
'-s': False,
104108
'-c': False,
105109
'-t': False,
106110
'output': temp_file,
107-
'GRASS_SNAP_TOLERANCE_PARAMETER': -1, 'GRASS_MIN_AREA_PARAMETER': 0.0001,
108-
'GRASS_OUTPUT_TYPE_PARAMETER': 0}
111+
'GRASS_REGION_PARAMETER': None,
112+
'GRASS_SNAP_TOLERANCE_PARAMETER': -1,
113+
'GRASS_MIN_AREA_PARAMETER': 0.0001,
114+
'GRASS_OUTPUT_TYPE_PARAMETER': 0,
115+
'GRASS_VECTOR_DSCO': '',
116+
'GRASS_VECTOR_LCO': ''}
109117
feedback = QgsProcessingFeedback()
110118

111119
results, ok = alg.run(parameters, context, feedback)
@@ -146,17 +154,25 @@ def testFeatureSourceInput(self):
146154
self.assertIsNotNone(alg)
147155
temp_file = os.path.join(self.temp_dir, 'grass_output_sel.shp')
148156
parameters = {'input': QgsProcessingFeatureSourceDefinition('testmem', True),
157+
'cats': '',
158+
'where': '',
149159
'type': [0, 1, 4],
150160
'distance': 1,
161+
'minordistance': None,
151162
'angle': 0,
163+
'column': None,
152164
'scale': 1,
153165
'tolerance': 0.01,
154166
'-s': False,
155167
'-c': False,
156168
'-t': False,
157169
'output': temp_file,
158-
'GRASS_SNAP_TOLERANCE_PARAMETER': -1, 'GRASS_MIN_AREA_PARAMETER': 0.0001,
159-
'GRASS_OUTPUT_TYPE_PARAMETER': 0}
170+
'GRASS_REGION_PARAMETER': None,
171+
'GRASS_SNAP_TOLERANCE_PARAMETER': -1,
172+
'GRASS_MIN_AREA_PARAMETER': 0.0001,
173+
'GRASS_OUTPUT_TYPE_PARAMETER': 0,
174+
'GRASS_VECTOR_DSCO': '',
175+
'GRASS_VECTOR_LCO': ''}
160176
feedback = QgsProcessingFeedback()
161177

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

171187
QgsProject.instance().removeMapLayer(layer)
172188

189+
def testOutputToGeopackage(self):
190+
# create a memory layer and add to project and context
191+
layer = QgsVectorLayer("Point?crs=epsg:3857&field=fldtxt:string&field=fldint:integer",
192+
"testmem", "memory")
193+
self.assertTrue(layer.isValid())
194+
pr = layer.dataProvider()
195+
f = QgsFeature()
196+
f.setAttributes(["test", 123])
197+
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(100, 200)))
198+
f2 = QgsFeature()
199+
f2.setAttributes(["test2", 457])
200+
f2.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(110, 200)))
201+
self.assertTrue(pr.addFeatures([f, f2]))
202+
self.assertEqual(layer.featureCount(), 2)
203+
QgsProject.instance().addMapLayer(layer)
204+
context = QgsProcessingContext()
205+
context.setProject(QgsProject.instance())
206+
207+
alg = QgsApplication.processingRegistry().createAlgorithmById('grass7:v.buffer')
208+
self.assertIsNotNone(alg)
209+
210+
temp_file = os.path.join(self.temp_dir, 'grass_output.gpkg')
211+
parameters = {'input': 'testmem',
212+
'cats': '',
213+
'where': '',
214+
'type': [0, 1, 4],
215+
'distance': 1,
216+
'minordistance': None,
217+
'angle': 0,
218+
'column': None,
219+
'scale': 1,
220+
'tolerance': 0.01,
221+
'-s': False,
222+
'-c': False,
223+
'-t': False,
224+
'output': temp_file,
225+
'GRASS_REGION_PARAMETER': None,
226+
'GRASS_SNAP_TOLERANCE_PARAMETER': -1,
227+
'GRASS_MIN_AREA_PARAMETER': 0.0001,
228+
'GRASS_OUTPUT_TYPE_PARAMETER': 0,
229+
'GRASS_VECTOR_DSCO': '',
230+
'GRASS_VECTOR_LCO': ''}
231+
feedback = QgsProcessingFeedback()
232+
233+
results, ok = alg.run(parameters, context, feedback)
234+
self.assertTrue(ok)
235+
self.assertTrue(os.path.exists(temp_file))
236+
237+
# make sure that layer has correct features
238+
res = QgsVectorLayer(temp_file, 'res')
239+
self.assertTrue(res.isValid())
240+
self.assertEqual(res.featureCount(), 2)
241+
242+
QgsProject.instance().removeMapLayer(layer)
243+
173244

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

0 commit comments

Comments
 (0)
Please sign in to comment.