[processing] correctly handle case when layer source is not a plain path

in the virtual vector algorithm (fix #29336)
This commit is contained in:
Alexander Bruy 2019-09-20 12:52:56 +03:00
parent cebdc8dded
commit 3c13fbfeb5
3 changed files with 53 additions and 142 deletions

View File

@ -2,7 +2,7 @@
"""
***************************************************************************
__init__.py
Datasources2Vrt.py
---------------------
Date : May 2015
Copyright : (C) 2015 by Luigi Pirelli
@ -21,27 +21,20 @@ __author__ = 'Luigi Pirelli'
__date__ = 'May 2015'
__copyright__ = '(C) 2015, Luigi Pirelli'
import codecs
import xml.sax.saxutils
import warnings
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
from osgeo import ogr
from qgis.core import (QgsProcessingFeedback,
from qgis.core import (QgsProcessing,
QgsProcessingException,
QgsProcessingParameterMultipleLayers,
QgsProcessingParameterBoolean,
QgsProcessing,
QgsProcessingParameterVectorDestination,
QgsProcessingOutputString,
QgsProcessingException)
)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.algs.gdal.GdalUtils import GdalUtils
class Datasources2Vrt(QgisAlgorithm):
INPUT = 'INPUT'
UNIONED = 'UNIONED'
OUTPUT = 'OUTPUT'
VRT_STRING = 'VRT_STRING'
@ -51,6 +44,12 @@ class Datasources2Vrt(QgisAlgorithm):
def groupId(self):
return 'vectorgeneral'
def name(self):
return 'buildvirtualvector'
def displayName(self):
return self.tr('Build virtual vector')
def __init__(self):
super().__init__()
@ -82,151 +81,44 @@ class Datasources2Vrt(QgisAlgorithm):
self.addOutput(QgsProcessingOutputString(self.VRT_STRING,
self.tr('Virtual string')))
def name(self):
return 'buildvirtualvector'
def displayName(self):
return self.tr('Build virtual vector')
def processAlgorithm(self, parameters, context, feedback):
input_layers = self.parameterAsLayerList(parameters, self.INPUT, context)
unioned = self.parameterAsBoolean(parameters, self.UNIONED, context)
vrtPath = self.parameterAsOutputLayer(parameters, self.OUTPUT, context)
vrtString = self.mergeDataSources2Vrt(input_layers,
vrtPath,
union=unioned,
relative=False,
schema=False,
feedback=feedback)
return {self.OUTPUT: vrtPath, self.VRT_STRING: vrtString}
def mergeDataSources2Vrt(self, dataSources, outFile, union=False, relative=False,
schema=False, feedback=None):
'''Function to do the work of merging datasources in a single vrt format
@param data_sources: Array of path strings
@param outfile: the output vrt file to generate
@param relative: Write relative flag. DOES NOT relativise paths. They have to be already relative
@param schema: Schema flag
@return: vrt in string format
'''
if feedback is None:
feedback = QgsProcessingFeedback()
vrt = '<OGRVRTDataSource>'
if union:
if unioned:
vrt += '<OGRVRTUnionLayer name="UnionedLayer">'
total = 100.0 / len(dataSources) if dataSources else 1
for current, layer in enumerate(dataSources):
total = 100.0 / len(input_layers) if input_layers else 0
for current, layer in enumerate(input_layers):
if feedback.isCanceled():
break
uri = layer.source()
basePath = uri.split('|')[0]
layerName = GdalUtils.ogrLayerName(uri)
vrt += '<OGRVRTLayer name="{}">'.format(self.escape(layerName))
vrt += '<SrcDataSource>{}</SrcDataSource>'.format(self.escape(basePath))
vrt += '<SrcLayer>{}</SrcLayer>'.format(self.escape(layerName))
vrt += '</OGRVRTLayer>'
feedback.setProgress(int(current * total))
inFile = layer.source()
srcDS = ogr.Open(inFile, 0)
if srcDS is None:
raise QgsProcessingException(
self.tr('Invalid datasource: {}'.format(inFile)))
if schema:
inFile = '@dummy@'
for layer in srcDS:
layerDef = layer.GetLayerDefn()
layerName = layerDef.GetName()
vrt += '<OGRVRTLayer name="{}">'.format(self.XmlEsc(layerName))
vrt += '<SrcDataSource relativeToVRT="{}" shared="{}">{}</SrcDataSource>'.format(1 if relative else 0, not schema, self.XmlEsc(inFile))
if schema:
vrt += '<SrcLayer>@dummy@</SrcLayer>'
else:
vrt += '<SrcLayer>{}</SrcLayer>'.format(self.XmlEsc(layerName))
vrt += '<GeometryType>{}</GeometryType>'.format(self.GeomType2Name(layerDef.GetGeomType()))
crs = layer.GetSpatialRef()
if crs is not None:
vrt += '<LayerSRS>{}</LayerSRS>'.format(self.XmlEsc(crs.ExportToWkt()))
# Process all the fields.
for fieldIdx in range(layerDef.GetFieldCount()):
fieldDef = layerDef.GetFieldDefn(fieldIdx)
vrt += '<Field name="{}" type="{}"'.format(self.XmlEsc(fieldDef.GetName()), self.fieldType2Name(fieldDef.GetType()))
if not schema:
vrt += ' src="{}"'.format(self.XmlEsc(fieldDef.GetName()))
if fieldDef.GetWidth() > 0:
vrt += ' width="{}"'.format(fieldDef.GetWidth())
if fieldDef.GetPrecision() > 0:
vrt += ' precision="{}"'.format(fieldDef.GetPrecision())
vrt += '/>'
vrt += '</OGRVRTLayer>'
srcDS.Destroy()
if union:
if unioned:
vrt += '</OGRVRTUnionLayer>'
vrt += '</OGRVRTDataSource>'
#TODO: pretty-print XML
with open(vrtPath, 'w', encoding='utf-8') as f:
f.write(vrt)
if outFile is not None:
with codecs.open(outFile, 'w') as f:
f.write(vrt)
return {self.OUTPUT: vrtPath, self.VRT_STRING: vrt}
return vrt
def GeomType2Name(self, geomType):
if geomType == ogr.wkbUnknown:
return 'wkbUnknown'
elif geomType == ogr.wkbPoint:
return 'wkbPoint'
elif geomType == ogr.wkbLineString:
return 'wkbLineString'
elif geomType == ogr.wkbPolygon:
return 'wkbPolygon'
elif geomType == ogr.wkbMultiPoint:
return 'wkbMultiPoint'
elif geomType == ogr.wkbMultiLineString:
return 'wkbMultiLineString'
elif geomType == ogr.wkbMultiPolygon:
return 'wkbMultiPolygon'
elif geomType == ogr.wkbGeometryCollection:
return 'wkbGeometryCollection'
elif geomType == ogr.wkbNone:
return 'wkbNone'
elif geomType == ogr.wkbLinearRing:
return 'wkbLinearRing'
else:
return 'wkbUnknown'
def fieldType2Name(self, fieldType):
if fieldType == ogr.OFTInteger:
return 'Integer'
elif fieldType == ogr.OFTString:
return 'String'
elif fieldType == ogr.OFTReal:
return 'Real'
elif fieldType == ogr.OFTStringList:
return 'StringList'
elif fieldType == ogr.OFTIntegerList:
return 'IntegerList'
elif fieldType == ogr.OFTRealList:
return 'RealList'
elif fieldType == ogr.OFTBinary:
return 'Binary'
elif fieldType == ogr.OFTDate:
return 'Date'
elif fieldType == ogr.OFTTime:
return 'Time'
elif fieldType == ogr.OFTDateTime:
return 'DateTime'
else:
return 'String'
def XmlEsc(self, text):
return xml.sax.saxutils.escape(text)
def escape(self, text):
text = text.replace('&', '&amp;')
text = text.replace('<', '&lt;')
text = text.replace('>', '&gt;')
text = text.replace('"', '&quot;')
text = text.replace('\'', '&apos;')
return text

View File

@ -0,0 +1 @@
<OGRVRTDataSource><OGRVRTUnionLayer name="UnionedLayer"><OGRVRTLayer name="lines"><SrcDataSource>/home/alex/devel/qgis/python/plugins/processing/tests/testdata/lines.gml</SrcDataSource><SrcLayer>lines</SrcLayer></OGRVRTLayer><OGRVRTLayer name="multilines"><SrcDataSource>/home/alex/devel/qgis/python/plugins/processing/tests/testdata/multilines.gml</SrcDataSource><SrcLayer>multilines</SrcLayer></OGRVRTLayer></OGRVRTUnionLayer></OGRVRTDataSource>

View File

@ -1876,5 +1876,23 @@ tests:
__all__:
precision: 7
- algorithm: qgis:buildvirtualvector
name: Build virtual vector
params:
INPUT:
params:
- name: lines.gml|layername=lines
type: vector
- name: multilines.gml|layername=multilines
type: vector
type: multi
UNIONED: true
results:
OUTPUT:
name: expected/vrt_lines.vrt
type: vector
compare:
ignore_crs_check: true
# See ../README.md for a description of the file format