Skip to content

Commit f65c845

Browse files
committed
[processing][GRASS] Correctly handle input vector layers with |layername= param
Fixes #20277
1 parent 82ac04d commit f65c845

11 files changed

+156
-8
lines changed

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

+31-8
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@
6262
QgsProcessingParameterFolderDestination,
6363
QgsProcessingOutputHtml,
6464
QgsProcessingUtils,
65-
QgsVectorLayer)
65+
QgsVectorLayer,
66+
QgsProviderRegistry)
6667
from qgis.utils import iface
6768
from osgeo import ogr
6869

@@ -110,12 +111,15 @@ def __init__(self, descriptionfile):
110111
self.params = []
111112
self.hardcodedStrings = []
112113
self.inputLayers = []
114+
self.commands = []
115+
self.outputCommands = []
116+
self.exportedLayers = {}
113117
self.descriptionFile = descriptionfile
114118

115119
# Default GRASS parameters
116120
self.region = None
117121
self.cellSize = None
118-
self.snaptTolerance = None
122+
self.snapTolerance = None
119123
self.outputType = None
120124
self.minArea = None
121125
self.alignToResolution = None
@@ -801,7 +805,18 @@ def loadVectorLayerFromParameter(self, name, parameters, context, feedback, exte
801805
:param external: use v.external (v.in.ogr if False).
802806
"""
803807
layer = self.parameterAsVectorLayer(parameters, name, context)
804-
if layer is None or layer.dataProvider().name() != 'ogr':
808+
809+
is_ogr_disk_based_layer = layer is not None and layer.dataProvider().name() == 'ogr'
810+
if is_ogr_disk_based_layer:
811+
# we only support direct reading of disk based ogr layers -- not ogr postgres layers, etc
812+
source_parts = QgsProviderRegistry.instance().decodeUri('ogr', layer.source())
813+
if not source_parts.get('path'):
814+
is_ogr_disk_based_layer = False
815+
elif source_parts.get('layerId'):
816+
# no support for directly reading layers by id in grass
817+
is_ogr_disk_based_layer = False
818+
819+
if not is_ogr_disk_based_layer:
805820
# parameter is not a vector layer or not an OGR layer - try to convert to a source compatible with
806821
# grass OGR inputs and extract selection if required
807822
path = self.parameterAsCompatibleSourceLayerPath(parameters, name, context,
@@ -827,28 +842,36 @@ def loadVectorLayer(self, name, layer, external=False, feedback=None):
827842
external = ProcessingConfig.getSetting(
828843
Grass7Utils.GRASS_USE_VEXTERNAL)
829844

845+
source_parts = QgsProviderRegistry.instance().decodeUri('ogr', layer.source())
846+
file_path = source_parts.get('path')
847+
layer_name = source_parts.get('layerName')
848+
830849
# safety check: we can only use external for ogr layers which support random read
831850
if external:
832-
feedback.pushInfo('Attempting to use v.external for direct layer read')
851+
if feedback is not None:
852+
feedback.pushInfo('Attempting to use v.external for direct layer read')
833853
ds = ogr.Open(file_path)
834854
if ds is not None:
835855
ogr_layer = ds.GetLayer()
836856
if ogr_layer is None or not ogr_layer.TestCapability(ogr.OLCRandomRead):
837-
feedback.reportError('Cannot use v.external: layer does not support random read')
857+
if feedback is not None:
858+
feedback.reportError('Cannot use v.external: layer does not support random read')
838859
external = False
839860
else:
840-
feedback.reportError('Cannot use v.external: error reading layer')
861+
if feedback is not None:
862+
feedback.reportError('Cannot use v.external: error reading layer')
841863
external = False
842864

843865
self.inputLayers.append(layer)
844866
self.setSessionProjectionFromLayer(layer)
845867
destFilename = 'vector_{}'.format(os.path.basename(getTempFilename()))
846868
self.exportedLayers[name] = destFilename
847-
command = '{0}{1}{2} input="{3}" output="{4}" --overwrite -o'.format(
869+
command = '{0}{1}{2} input="{3}"{4} output="{5}" --overwrite -o'.format(
848870
'v.external' if external else 'v.in.ogr',
849871
' min_area={}'.format(self.minArea) if not external else '',
850872
' snap={}'.format(self.snapTolerance) if not external else '',
851-
os.path.normpath(layer.source()),
873+
os.path.normpath(file_path),
874+
' layer="{}"'.format(layer_name) if layer_name else '',
852875
destFilename)
853876
self.commands.append(command)
854877

python/plugins/processing/tests/Grass7AlgorithmsVectorTest.py

+63
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import shutil
3232
import os
3333
import tempfile
34+
import re
3435

3536
from qgis.core import (QgsVectorLayer,
3637
QgsApplication,
@@ -48,6 +49,9 @@
4849
from processing.algs.grass7.Grass7Utils import Grass7Utils
4950

5051

52+
testDataPath = os.path.join(os.path.dirname(__file__), 'testdata')
53+
54+
5155
class TestGrass7AlgorithmsVectorTest(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest):
5256

5357
@classmethod
@@ -241,6 +245,65 @@ def testOutputToGeopackage(self):
241245

242246
QgsProject.instance().removeMapLayer(layer)
243247

248+
def testVectorLayerInput(self):
249+
alg = QgsApplication.processingRegistry().createAlgorithmById('grass7:v.buffer')
250+
self.assertIsNotNone(alg)
251+
self.assertFalse(alg.commands)
252+
253+
def get_command(alg):
254+
command = alg.commands[-1]
255+
command = re.sub(r'output=".*?"', 'output="###"', command)
256+
command = command.replace(testDataPath, 'testdata')
257+
return command
258+
259+
# GML source
260+
source = os.path.join(testDataPath, 'points.gml')
261+
vl = QgsVectorLayer(source)
262+
self.assertTrue(vl.isValid())
263+
alg.loadVectorLayer('test_layer', vl, external=False)
264+
self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/points.gml" output="###" --overwrite -o')
265+
# try with external -- not support for GML, so should fall back to v.in.ogr
266+
alg.loadVectorLayer('test_layer', vl, external=True)
267+
self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/points.gml" output="###" --overwrite -o')
268+
269+
# SHP source
270+
source = os.path.join(testDataPath, 'lines_z.shp')
271+
vl = QgsVectorLayer(source)
272+
self.assertTrue(vl.isValid())
273+
alg.loadVectorLayer('test_layer', vl, external=False)
274+
self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/lines_z.shp" output="###" --overwrite -o')
275+
# try with external -- should work for shapefile
276+
alg.loadVectorLayer('test_layer', vl, external=True)
277+
self.assertEqual(get_command(alg), 'v.external input="testdata/lines_z.shp" output="###" --overwrite -o')
278+
279+
# GPKG source
280+
source = os.path.join(testDataPath, 'custom/pol.gpkg')
281+
vl = QgsVectorLayer(source + '|layername=pol2')
282+
self.assertTrue(vl.isValid())
283+
alg.loadVectorLayer('test_layer', vl, external=False)
284+
self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/custom/pol.gpkg" layer="pol2" output="###" --overwrite -o')
285+
# try with external -- should work for Geopackage (although grass itself tends to crash here!)
286+
alg.loadVectorLayer('test_layer', vl, external=True)
287+
self.assertEqual(get_command(alg), 'v.external input="testdata/custom/pol.gpkg" layer="pol2" output="###" --overwrite -o')
288+
289+
# different layer
290+
source = os.path.join(testDataPath, 'custom/pol.gpkg')
291+
vl = QgsVectorLayer(source + '|layername=pol3')
292+
self.assertTrue(vl.isValid())
293+
alg.loadVectorLayer('test_layer', vl, external=False)
294+
self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/custom/pol.gpkg" layer="pol3" output="###" --overwrite -o')
295+
alg.loadVectorLayer('test_layer', vl, external=True)
296+
self.assertEqual(get_command(alg), 'v.external input="testdata/custom/pol.gpkg" layer="pol3" output="###" --overwrite -o')
297+
298+
# GPKG no layer: you get what you get and you don't get upset
299+
source = os.path.join(testDataPath, 'custom/pol.gpkg')
300+
vl = QgsVectorLayer(source)
301+
self.assertTrue(vl.isValid())
302+
alg.loadVectorLayer('test_layer', vl, external=False)
303+
self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/custom/pol.gpkg" output="###" --overwrite -o')
304+
alg.loadVectorLayer('test_layer', vl, external=True)
305+
self.assertEqual(get_command(alg), 'v.external input="testdata/custom/pol.gpkg" output="###" --overwrite -o')
306+
244307

245308
if __name__ == '__main__':
246309
nose2.main()
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PROJCS["Hotine_Oblique_Mercator_Azimuth_Center",GEOGCS["GCS_bessel",DATUM["D_unknown",SPHEROID["Bessel_1841",6377397.155,299.1528128]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Hotine_Oblique_Mercator_Azimuth_Center"],PARAMETER["latitude_of_center",46.95240555555556],PARAMETER["longitude_of_center",7.439583333333333],PARAMETER["azimuth",90],PARAMETER["scale_factor",1],PARAMETER["false_easting",2600000],PARAMETER["false_northing",1200000],UNIT["Meter",1]]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PROJCS["Hotine_Oblique_Mercator_Azimuth_Center",GEOGCS["GCS_bessel",DATUM["D_unknown",SPHEROID["Bessel_1841",6377397.155,299.1528128]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Hotine_Oblique_Mercator_Azimuth_Center"],PARAMETER["latitude_of_center",46.95240555555556],PARAMETER["longitude_of_center",7.439583333333333],PARAMETER["azimuth",90],PARAMETER["scale_factor",1],PARAMETER["false_easting",2600000],PARAMETER["false_northing",1200000],UNIT["Meter",1]]
Binary file not shown.
Binary file not shown.

python/plugins/processing/tests/testdata/grass7_algorithms_vector_tests.yaml

+60
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ tests:
3232
compare:
3333
geometry:
3434
precision: 7
35+
ignore_crs_check: true
3536

3637
- algorithm: grass7:v.rast.stats
3738
name: V.rast.stats
@@ -271,3 +272,62 @@ tests:
271272
raster_output:
272273
hash: 7aa8e68b697e1558e6621fa23b5f1f01a5826649ffc31fd255e709a1
273274
type: rasterhash
275+
276+
- algorithm: grass7:v.buffer
277+
name: Buffer with layername
278+
params:
279+
-c: false
280+
-s: false
281+
-t: false
282+
GRASS_MIN_AREA_PARAMETER: 0.0001
283+
GRASS_OUTPUT_TYPE_PARAMETER: 0
284+
GRASS_SNAP_TOLERANCE_PARAMETER: -1.0
285+
GRASS_VECTOR_DSCO: ''
286+
GRASS_VECTOR_LCO: ''
287+
angle: 0.0
288+
cats: ''
289+
distance: 10.0
290+
input:
291+
name: custom/pol.gpkg|layername=pol3
292+
type: vector
293+
scale: 1.0
294+
tolerance: 0.01
295+
type:
296+
- 0
297+
- 1
298+
- 4
299+
where: ''
300+
results:
301+
output:
302+
name: expected/grass7/buffer_polys_layer3.shp
303+
type: vector
304+
305+
- algorithm: grass7:v.buffer
306+
name: Buffer with layername 2
307+
params:
308+
-c: false
309+
-s: false
310+
-t: false
311+
GRASS_MIN_AREA_PARAMETER: 0.0001
312+
GRASS_OUTPUT_TYPE_PARAMETER: 0
313+
GRASS_SNAP_TOLERANCE_PARAMETER: -1.0
314+
GRASS_VECTOR_DSCO: ''
315+
GRASS_VECTOR_LCO: ''
316+
angle: 0.0
317+
cats: ''
318+
distance: 10.0
319+
input:
320+
name: custom/pol.gpkg|layername=pol2
321+
type: vector
322+
scale: 1.0
323+
tolerance: 0.01
324+
type:
325+
- 0
326+
- 1
327+
- 4
328+
where: ''
329+
results:
330+
output:
331+
name: expected/grass7/buffer_polys_layer2.shp
332+
type: vector
333+

0 commit comments

Comments
 (0)