diff --git a/python/plugins/processing/algs/grass7/Grass7Algorithm.py b/python/plugins/processing/algs/grass7/Grass7Algorithm.py index e0d181da671..2a74b7bfc42 100644 --- a/python/plugins/processing/algs/grass7/Grass7Algorithm.py +++ b/python/plugins/processing/algs/grass7/Grass7Algorithm.py @@ -62,7 +62,8 @@ from qgis.core import (Qgis, QgsProcessingParameterFolderDestination, QgsProcessingOutputHtml, QgsProcessingUtils, - QgsVectorLayer) + QgsVectorLayer, + QgsProviderRegistry) from qgis.utils import iface from osgeo import ogr @@ -110,12 +111,15 @@ class Grass7Algorithm(QgsProcessingAlgorithm): self.params = [] self.hardcodedStrings = [] self.inputLayers = [] + self.commands = [] + self.outputCommands = [] + self.exportedLayers = {} self.descriptionFile = descriptionfile # Default GRASS parameters self.region = None self.cellSize = None - self.snaptTolerance = None + self.snapTolerance = None self.outputType = None self.minArea = None self.alignToResolution = None @@ -801,7 +805,18 @@ class Grass7Algorithm(QgsProcessingAlgorithm): :param external: use v.external (v.in.ogr if False). """ layer = self.parameterAsVectorLayer(parameters, name, context) - if layer is None or layer.dataProvider().name() != 'ogr': + + is_ogr_disk_based_layer = layer is not None and layer.dataProvider().name() == 'ogr' + if is_ogr_disk_based_layer: + # we only support direct reading of disk based ogr layers -- not ogr postgres layers, etc + source_parts = QgsProviderRegistry.instance().decodeUri('ogr', layer.source()) + if not source_parts.get('path'): + is_ogr_disk_based_layer = False + elif source_parts.get('layerId'): + # no support for directly reading layers by id in grass + is_ogr_disk_based_layer = False + + if not is_ogr_disk_based_layer: # parameter is not a vector layer or not an OGR layer - try to convert to a source compatible with # grass OGR inputs and extract selection if required path = self.parameterAsCompatibleSourceLayerPath(parameters, name, context, @@ -827,28 +842,36 @@ class Grass7Algorithm(QgsProcessingAlgorithm): external = ProcessingConfig.getSetting( Grass7Utils.GRASS_USE_VEXTERNAL) + source_parts = QgsProviderRegistry.instance().decodeUri('ogr', layer.source()) + file_path = source_parts.get('path') + layer_name = source_parts.get('layerName') + # safety check: we can only use external for ogr layers which support random read if external: - feedback.pushInfo('Attempting to use v.external for direct layer read') + if feedback is not None: + feedback.pushInfo('Attempting to use v.external for direct layer read') ds = ogr.Open(file_path) if ds is not None: ogr_layer = ds.GetLayer() if ogr_layer is None or not ogr_layer.TestCapability(ogr.OLCRandomRead): - feedback.reportError('Cannot use v.external: layer does not support random read') + if feedback is not None: + feedback.reportError('Cannot use v.external: layer does not support random read') external = False else: - feedback.reportError('Cannot use v.external: error reading layer') + if feedback is not None: + feedback.reportError('Cannot use v.external: error reading layer') external = False self.inputLayers.append(layer) self.setSessionProjectionFromLayer(layer) destFilename = 'vector_{}'.format(os.path.basename(getTempFilename())) self.exportedLayers[name] = destFilename - command = '{0}{1}{2} input="{3}" output="{4}" --overwrite -o'.format( + command = '{0}{1}{2} input="{3}"{4} output="{5}" --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()), + os.path.normpath(file_path), + ' layer="{}"'.format(layer_name) if layer_name else '', destFilename) self.commands.append(command) diff --git a/python/plugins/processing/tests/Grass7AlgorithmsVectorTest.py b/python/plugins/processing/tests/Grass7AlgorithmsVectorTest.py index c24037139e7..44a31e91164 100644 --- a/python/plugins/processing/tests/Grass7AlgorithmsVectorTest.py +++ b/python/plugins/processing/tests/Grass7AlgorithmsVectorTest.py @@ -31,6 +31,7 @@ import nose2 import shutil import os import tempfile +import re from qgis.core import (QgsVectorLayer, QgsApplication, @@ -48,6 +49,9 @@ from qgis.testing import ( from processing.algs.grass7.Grass7Utils import Grass7Utils +testDataPath = os.path.join(os.path.dirname(__file__), 'testdata') + + class TestGrass7AlgorithmsVectorTest(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest): @classmethod @@ -241,6 +245,65 @@ class TestGrass7AlgorithmsVectorTest(unittest.TestCase, AlgorithmsTestBase.Algor QgsProject.instance().removeMapLayer(layer) + def testVectorLayerInput(self): + alg = QgsApplication.processingRegistry().createAlgorithmById('grass7:v.buffer') + self.assertIsNotNone(alg) + self.assertFalse(alg.commands) + + def get_command(alg): + command = alg.commands[-1] + command = re.sub(r'output=".*?"', 'output="###"', command) + command = command.replace(testDataPath, 'testdata') + return command + + # GML source + source = os.path.join(testDataPath, 'points.gml') + vl = QgsVectorLayer(source) + self.assertTrue(vl.isValid()) + alg.loadVectorLayer('test_layer', vl, external=False) + self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/points.gml" output="###" --overwrite -o') + # try with external -- not support for GML, so should fall back to v.in.ogr + alg.loadVectorLayer('test_layer', vl, external=True) + self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/points.gml" output="###" --overwrite -o') + + # SHP source + source = os.path.join(testDataPath, 'lines_z.shp') + vl = QgsVectorLayer(source) + self.assertTrue(vl.isValid()) + alg.loadVectorLayer('test_layer', vl, external=False) + self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/lines_z.shp" output="###" --overwrite -o') + # try with external -- should work for shapefile + alg.loadVectorLayer('test_layer', vl, external=True) + self.assertEqual(get_command(alg), 'v.external input="testdata/lines_z.shp" output="###" --overwrite -o') + + # GPKG source + source = os.path.join(testDataPath, 'custom/pol.gpkg') + vl = QgsVectorLayer(source + '|layername=pol2') + self.assertTrue(vl.isValid()) + alg.loadVectorLayer('test_layer', vl, external=False) + self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/custom/pol.gpkg" layer="pol2" output="###" --overwrite -o') + # try with external -- should work for Geopackage (although grass itself tends to crash here!) + alg.loadVectorLayer('test_layer', vl, external=True) + self.assertEqual(get_command(alg), 'v.external input="testdata/custom/pol.gpkg" layer="pol2" output="###" --overwrite -o') + + # different layer + source = os.path.join(testDataPath, 'custom/pol.gpkg') + vl = QgsVectorLayer(source + '|layername=pol3') + self.assertTrue(vl.isValid()) + alg.loadVectorLayer('test_layer', vl, external=False) + self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/custom/pol.gpkg" layer="pol3" output="###" --overwrite -o') + alg.loadVectorLayer('test_layer', vl, external=True) + self.assertEqual(get_command(alg), 'v.external input="testdata/custom/pol.gpkg" layer="pol3" output="###" --overwrite -o') + + # GPKG no layer: you get what you get and you don't get upset + source = os.path.join(testDataPath, 'custom/pol.gpkg') + vl = QgsVectorLayer(source) + self.assertTrue(vl.isValid()) + alg.loadVectorLayer('test_layer', vl, external=False) + self.assertEqual(get_command(alg), 'v.in.ogr min_area=None snap=None input="testdata/custom/pol.gpkg" output="###" --overwrite -o') + alg.loadVectorLayer('test_layer', vl, external=True) + self.assertEqual(get_command(alg), 'v.external input="testdata/custom/pol.gpkg" output="###" --overwrite -o') + if __name__ == '__main__': nose2.main() diff --git a/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.dbf b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.dbf new file mode 100644 index 00000000000..f1fff3443fe Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.dbf differ diff --git a/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.prj b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.prj new file mode 100644 index 00000000000..5107c57a536 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.prj @@ -0,0 +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]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.shp b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.shp new file mode 100644 index 00000000000..a413777a6b9 Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.shp differ diff --git a/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.shx b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.shx new file mode 100644 index 00000000000..371f61b25e4 Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer2.shx differ diff --git a/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.dbf b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.dbf new file mode 100644 index 00000000000..f1fff3443fe Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.dbf differ diff --git a/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.prj b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.prj new file mode 100644 index 00000000000..5107c57a536 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.prj @@ -0,0 +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]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.shp b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.shp new file mode 100644 index 00000000000..91ee2dc0f5c Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.shp differ diff --git a/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.shx b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.shx new file mode 100644 index 00000000000..f5ee8e12503 Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/grass7/buffer_polys_layer3.shx differ diff --git a/python/plugins/processing/tests/testdata/grass7_algorithms_vector_tests.yaml b/python/plugins/processing/tests/testdata/grass7_algorithms_vector_tests.yaml index cbe5ebb4186..37d3a6986c9 100644 --- a/python/plugins/processing/tests/testdata/grass7_algorithms_vector_tests.yaml +++ b/python/plugins/processing/tests/testdata/grass7_algorithms_vector_tests.yaml @@ -32,6 +32,7 @@ tests: compare: geometry: precision: 7 + ignore_crs_check: true - algorithm: grass7:v.rast.stats name: V.rast.stats @@ -271,3 +272,62 @@ tests: raster_output: hash: 7aa8e68b697e1558e6621fa23b5f1f01a5826649ffc31fd255e709a1 type: rasterhash + + - algorithm: grass7:v.buffer + name: Buffer with layername + params: + -c: false + -s: false + -t: false + GRASS_MIN_AREA_PARAMETER: 0.0001 + GRASS_OUTPUT_TYPE_PARAMETER: 0 + GRASS_SNAP_TOLERANCE_PARAMETER: -1.0 + GRASS_VECTOR_DSCO: '' + GRASS_VECTOR_LCO: '' + angle: 0.0 + cats: '' + distance: 10.0 + input: + name: custom/pol.gpkg|layername=pol3 + type: vector + scale: 1.0 + tolerance: 0.01 + type: + - 0 + - 1 + - 4 + where: '' + results: + output: + name: expected/grass7/buffer_polys_layer3.shp + type: vector + + - algorithm: grass7:v.buffer + name: Buffer with layername 2 + params: + -c: false + -s: false + -t: false + GRASS_MIN_AREA_PARAMETER: 0.0001 + GRASS_OUTPUT_TYPE_PARAMETER: 0 + GRASS_SNAP_TOLERANCE_PARAMETER: -1.0 + GRASS_VECTOR_DSCO: '' + GRASS_VECTOR_LCO: '' + angle: 0.0 + cats: '' + distance: 10.0 + input: + name: custom/pol.gpkg|layername=pol2 + type: vector + scale: 1.0 + tolerance: 0.01 + type: + - 0 + - 1 + - 4 + where: '' + results: + output: + name: expected/grass7/buffer_polys_layer2.shp + type: vector +