5

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":

enter image description here

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:

enter image description here

Taras
35.8k5 gold badges77 silver badges152 bronze badges
asked Mar 11, 2021 at 14:24

2 Answers 2

8

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)
Alexandre Neto
14.5k2 gold badges61 silver badges86 bronze badges
answered Mar 12, 2021 at 9:52
2
  • Thank you for your answer, your soulution works well. Commented 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 :) Commented Dec 8, 2021 at 22:16
0

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 {}
ThomasG77
31.7k1 gold badge56 silver badges96 bronze badges
answered Mar 15, 2021 at 7:00

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.