I found a problem with exporting the vector layer with: "Replace all selected raw field values by displayed values" in the PyQGIS script.
The source layer contains a value map for attribute "value_map"
:
PyQGIS script:
# -*- coding: utf-8 -*-
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (QgsProcessing,
QgsFeatureSink,
QgsVectorFileWriter,
QgsProcessingException,
QgsProcessingAlgorithm,
QgsVectorFileWriter,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink)
from qgis import processing
from qgis.utils import iface
class ExampleProcessingAlgorithm(QgsProcessingAlgorithm):
INPUT = 'INPUT'
def tr(self, string):
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return ExampleProcessingAlgorithm()
def name(self):
return 'myscript'
def displayName(self):
return self.tr('My Script')
def group(self):
return self.tr('Example scripts')
def groupId(self):
return 'examplescripts'
def shortHelpString(self):
return self.tr("Example algorithm short description")
def initAlgorithm(self, config=None):
# We add the input vector features source. It can have any kind of
# geometry.
self.addParameter(
QgsProcessingParameterFeatureSource(
self.INPUT,
self.tr('Input layer'),
[QgsProcessing.TypeVectorAnyGeometry]
)
)
def processAlgorithm(self, parameters, context, feedback):
layer = iface.activeLayer()
feedback.pushInfo(str(layer))
QgsVectorFileWriter.writeAsVectorFormat(
layer=layer,
fileName='C://Users//001-PC//Desktop//output_layer.json',
fileEncoding="utf-8",
driverName="GEOJSON",
fieldValueConverter=QgsVectorFileWriter.FieldValueConverter()
)
return {}
How can I export layer values with a values map? My script does not work with:
fieldValueConverter = QgsVectorFileWriter.FieldValueConverter()
Exported JSON (script result):
{
"type": "FeatureCollection",
"name": "output_layer",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::5514" } },
"features": [
{ "type": "Feature", "properties": { "fid": 1, "text": "aaa", "value_map": 1 }, "geometry": { "type": "Point", "coordinates": [ -553020.462518186424859, -1170227.009075149893761 ] } },
{ "type": "Feature", "properties": { "fid": 2, "text": "sadasd", "value_map": 2 }, "geometry": { "type": "Point", "coordinates": [ -553025.836183290113695, -1170234.612239605281502 ] } },
{ "type": "Feature", "properties": { "fid": 3, "text": "sdasdsad", "value_map": 3 }, "geometry": { "type": "Point", "coordinates": [ -553018.004352234653197, -1170230.038907602196559 ] } }
]
}
Source layer value map setting:
2 Answers 2
The official doc for QgsVectorFileWriter.FieldValueConverter
tells you that it's an "Interface to convert raw field values to their user-friendly value"
The interface has 3 methods, clone
, convert
and fieldDefinition
.
For convert
,
Default implementation will return provided value unmodified.
For fieldDefinition
,
Default implementation will return provided field unmodified.
So, the solution is to create your own class base on QgsVectorFileWriter.FieldValueConverter
to get the expected behaviour. In Python tests in QGIS repo, there is an example TestFieldValueConverter
implemented https://github.com/qgis/QGIS/blob/master/tests/src/python/test_qgsvectorfilewriter.py#L68 and called from https://github.com/qgis/QGIS/blob/master/tests/src/python/test_qgsvectorfilewriter.py#L533-L569
I've made an example that take valueMap
info and report them in the export correctly. I've tested below that should more or less do the job as I did not try it extensively. I just manage valueMap
here but you will need more code to manage other types of fields when converting from fields values to display values. In current code, I did not try in particular to manage NULL
values. I've also used writeAsVectorFormatV2
as writeAsVectorFormat
different ways to export are deprecated although always working at the moment.
class CustomFieldValueConverter(QgsVectorFileWriter.FieldValueConverter):
def __init__(self, layer):
QgsVectorFileWriter.FieldValueConverter.__init__(self)
self.layer = layer
def fieldDefinition(self, field):
idx = self.layer.fields().indexFromName(field.name())
editorWidget = self.layer.editorWidgetSetup(idx)
if editorWidget.type() == 'ValueMap':
return QgsField(field.name(), QVariant.String)
else:
return self.layer.fields()[idx]
def convert(self, idx, value):
editorWidget = self.layer.editorWidgetSetup(idx)
# print(editorWidget.config(), editorWidget.type(), editorWidget.isNull())
if editorWidget.type() == 'ValueMap':
valueMap = editorWidget.config()['map']
dictValueMapWithKeyValueSwapped = {v: k for d in valueMap for k, v in d.items()}
return dictValueMapWithKeyValueSwapped.get(str(value))
else:
return value
layer = iface.activeLayer()
myoptions = QgsVectorFileWriter.SaveVectorOptions()
myconverter = CustomFieldValueConverter(layer)
myoptions.fieldValueConverter = myconverter
myoptions.driverName = 'GeoJSON'
context = QgsProject.instance().transformContext()
mypath = '/tmp/output.geojson'
write_result, error_message = QgsVectorFileWriter.writeAsVectorFormatV2(layer, mypath, context, myoptions)
print(write_result, error_message)
Edit:
A more generic version that should work for all fields. Do not hesitate to make comment about it (only tested with a ValueMap
sample on my side)
class CustomFieldValueConverter(QgsVectorFileWriter.FieldValueConverter):
def __init__(self, layer):
QgsVectorFileWriter.FieldValueConverter.__init__(self)
self.__formattersAllowList = ["KeyValue", "List", "ValueRelation", "ValueMap"]
self.layer = layer
self.__mFormatters = {}
self.__mConfig = {}
self.__mCaches= {}
for i, field in enumerate(layer.fields()):
setup = field.editorWidgetSetup()
fieldFormatter = QgsApplication.fieldFormatterRegistry().fieldFormatter(setup.type())
if fieldFormatter.id() in self.__formattersAllowList:
self.__mFormatters[i] = fieldFormatter
keysValue = setup.config()['map']
dict_target = {}
for j in keysValue:
dict_target.update(j)
self.__mConfig[i] = {"map": dict_target}
def fieldDefinition(self, field):
if not self.layer:
return field
idx = self.layer.fields().indexFromName(field.name())
editorWidget = self.layer.editorWidgetSetup(idx)
if idx in self.__mFormatters:
return QgsField(field.name(), QVariant.String)
else:
return field
def convert(self, idx, value):
formatter = self.__mFormatters.get(idx)
if formatter is None:
return value
if self.__mCaches.get(idx) is not None:
cache = self.__mCaches.get(idx)
else:
cache = formatter.createCache(self.layer, idx, self.__mConfig.get(idx))
self.__mCaches[idx] = cache
return formatter.representValue(self.layer, idx, self.__mConfig.get(idx), cache, value);
def clone(self):
return FieldValueConverter(self)
layer = iface.activeLayer()
myoptions = QgsVectorFileWriter.SaveVectorOptions()
myconverter = CustomFieldValueConverter(layer)
myoptions.fieldValueConverter = myconverter
myoptions.driverName = 'GeoJSON'
context = QgsProject.instance().transformContext()
mypath = '/tmp/output.geojson'
write_result, error_message = QgsVectorFileWriter.writeAsVectorFormatV2(layer, mypath, context, myoptions)
print(write_result, error_message)
-
Thank you for your answer, your soulution works well.Wenceslauw– Wenceslauw2021年03月15日 06:58:42 +00:00Commented Mar 15, 2021 at 6:58
-
Hi @ThomasG77, I am trying to make your solution work with a Value Relation type, but I get errors considering the 'map' keyword. I searched a bunch, but can't figure out how to make this work. Any help would be greatly appreciated :)Horizen– Horizen2021年12月08日 22:16:00 +00:00Commented Dec 8, 2021 at 22:16
I used @ThomasG77 solution and modified my script. Script is working well now:
# -*- coding: utf-8 -*-
from qgis.PyQt.QtCore import *
from qgis.core import (QgsProcessing,
QgsField,
QgsFeatureSink,
QgsVectorFileWriter,
QgsProcessingException,
QgsProcessingAlgorithm,
QgsVectorFileWriter,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink)
from qgis import processing
from qgis.utils import iface
class CustomFieldValueConverter(QgsVectorFileWriter.FieldValueConverter):
def __init__(self, layer):
QgsVectorFileWriter.FieldValueConverter.__init__(self)
self.layer = layer
def fieldDefinition(self, field):
idx = self.layer.fields().indexFromName(field.name())
editorWidget = self.layer.editorWidgetSetup(idx)
if editorWidget.type() == 'ValueMap':
return QgsField(field.name(), QVariant.String)
else:
return self.layer.fields()[idx]
def convert(self, idx, value):
editorWidget = self.layer.editorWidgetSetup(idx)
# print(editorWidget.config(), editorWidget.type(), editorWidget.isNull())
if editorWidget.type() == 'ValueMap':
valueMap = editorWidget.config()['map']
dictValueMapWithKeyValueSwapped = {v: k for d in valueMap for k, v in d.items()}
return dictValueMapWithKeyValueSwapped.get(str(value))
else:
return value
class ExampleProcessingAlgorithm(QgsProcessingAlgorithm):
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
def tr(self, string):
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return ExampleProcessingAlgorithm()
def name(self):
return 'myscript'
def displayName(self):
return self.tr('My Script')
def group(self):
return self.tr('Example scripts')
def groupId(self):
return 'examplescripts'
def shortHelpString(self):
return self.tr("Example algorithm short description")
def initAlgorithm(self, config=None):
# We add the input vector features source. It can have any kind of
# geometry.
self.addParameter(
QgsProcessingParameterFeatureSource(
self.INPUT,
self.tr('Input layer'),
[QgsProcessing.TypeVectorAnyGeometry]
)
)
def processAlgorithm(self, parameters, context, feedback):
layer = iface.activeLayer()
myconverter = CustomFieldValueConverter(layer)
QgsVectorFileWriter.writeAsVectorFormat(
layer=layer,
fileName='C://Users//001-PC//Desktop//output_layer.json',
fileEncoding="utf-8",
driverName="GEOJSON",
fieldValueConverter=myconverter
)
return {}
Explore related questions
See similar questions with these tags.