Skip to content

Commit

Permalink
[processing][GRASS] Correctly handle input vector layers with |layern…
Browse files Browse the repository at this point in the history
…ame= param

Fixes #20277
  • Loading branch information
nyalldawson committed Nov 1, 2018
1 parent 82ac04d commit f65c845
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 8 deletions.
39 changes: 31 additions & 8 deletions python/plugins/processing/algs/grass7/Grass7Algorithm.py
Expand Up @@ -62,7 +62,8 @@
QgsProcessingParameterFolderDestination,
QgsProcessingOutputHtml,
QgsProcessingUtils,
QgsVectorLayer)
QgsVectorLayer,
QgsProviderRegistry)
from qgis.utils import iface
from osgeo import ogr

Expand Down Expand Up @@ -110,12 +111,15 @@ def __init__(self, descriptionfile):
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
Expand Down Expand Up @@ -801,7 +805,18 @@ def loadVectorLayerFromParameter(self, name, parameters, context, feedback, exte
: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,
Expand All @@ -827,28 +842,36 @@ def loadVectorLayer(self, name, layer, external=False, feedback=None):
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)

Expand Down
63 changes: 63 additions & 0 deletions python/plugins/processing/tests/Grass7AlgorithmsVectorTest.py
Expand Up @@ -31,6 +31,7 @@
import shutil
import os
import tempfile
import re

from qgis.core import (QgsVectorLayer,
QgsApplication,
Expand All @@ -48,6 +49,9 @@
from processing.algs.grass7.Grass7Utils import Grass7Utils


testDataPath = os.path.join(os.path.dirname(__file__), 'testdata')


class TestGrass7AlgorithmsVectorTest(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest):

@classmethod
Expand Down Expand Up @@ -241,6 +245,65 @@ def testOutputToGeopackage(self):

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()
Binary file not shown.
@@ -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]]
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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]]
Binary file not shown.
Binary file not shown.
Expand Up @@ -32,6 +32,7 @@ tests:
compare:
geometry:
precision: 7
ignore_crs_check: true

- algorithm: grass7:v.rast.stats
name: V.rast.stats
Expand Down Expand Up @@ -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

0 comments on commit f65c845

Please sign in to comment.