TransWikia.com

QGIS iface.addVectorLayer() results in incorrect field length

Geographic Information Systems Asked by staf on May 18, 2021

I ambuilding a python plugin for QGIS. As part of the plugin, I do:
iface.addVectorLayer(geoj, self.selectedLayer, 'ogr')

This is basically adding a layer to the project, with
geoj: a geojson string,
self.selectedLayer: the name I want to give it, and
‘ogr’: the driver.

I get a layer in my project with most field lengths set to zero:
enter image description here

This is a problem, because when I then try to export the loaded layer as a shapefile or something, I get the following error:
*Export to vector file failed.
Error: Feature write errors:
Error converting value ((1:0)) for attribute field photos_captions: String of length 5 exceeds maximum field length (1)
*

And a complete QGIS Crash!

Do I need to specify some more parameters to be able to load the geojson in my project as a valid layer with correctly defined fields?

Here are the relevant sections of code:

 def getGeoJsonFromSelectedLayer(self):

        if self.selectedLayer:
            url = "https://api.fulcrumapp.com/api/v2/query"

            tableSelector = f"SELECT * FROM "{self.selectedLayer}""
            querystring = {"q": tableSelector, "format": "geojson", "headers": "false",
                           "metadata": "false", "arrays": "false", "page": "1", "per_page": "20000"}

            headers = {
                "Accept": "application/json",
                "X-ApiToken": self.API_TOKEN
            }

            response = requests.request(
                "GET", url, headers=headers, params=querystring)

            # For Debugging:
            iface.messageBar().pushMessage(response.text)

            self.createLayerFromGeojson(response.text)

        else:
            iface.messageBar().pushMessage("No Layer Selected")

    def createLayerFromGeojson(self, geoj):
        # if there are features in the list
        if len(geoj) > 0:
                       
            # add the layer to the list of layers
            iface.addVectorLayer(geoj, self.selectedLayer, 'ogr')

        else:
            iface.messageBar().pushMessage("No features found in the geoJSON")

One Answer

To answer your question and to add some code to my comment :

I writed a snippet of code based on this answer :

import re
    
geoj_layer = QgsVectorLayer(geoj, '', 'ogr')
    refactor = []
field_error = []
for field in geoj_layer.fields(): # creation of a list with the field and field type of the layer.
    refactor_field = {}
    refactor_field['expression']=str(""")+field.name()+str(""")
    refactor_field['name']=field.name()
    refactor_field['precision']=field.precision()
    if field.typeName() == "IntegerList": # if the field as a not well handled type
        field_error.append(field.name()[:10])
        refactor_field['length']=0
        refactor_field['type']=10
    else :
        refactor_field['type']=field.type()
    refactor.append(refactor_field)

output_path = path + 'new_layer.shp'
# I'm using refactorfield algorithm from Qgis to create a new layer with some modification on specified fields.
processing.run("qgis:refactorfields", {'INPUT':geoj,'FIELDS_MAPPING':refactor,'OUTPUT':output_path})
iface.addVectorLayer(output_path,'', 'ogr')

# Modification of the value for a better value
geoj_layer = QgsVectorLayer(output_path, '', 'ogr')
for field_name in field_error:
    for f in geoj_layer.getFeatures() :
        attrs= f.attributes()
        value = attrs[geoj_layer.fields().indexFromName(field_name)]
        string = re.split(',|:|)', value)
        new_value = ''
        for elem in string[1:-1] :
            if elem == string[1:-1][-1] :
                new_value = new_value +str(elem)
            else :
                new_value = new_value +str(elem) + ", "
        geoj_layer.startEditing()
        geoj_layer.changeAttributeValue(f.id(), geoj_layer.fields().indexFromName(field_name), new_value )
        geoj_layer.commitChanges()
        geoj_layer.triggerRepaint()

This code will create a new shapefile with a field "photos_cap" as a String. Shapefile only accept 10 characters for field name.You'll get a value of 0 instead of (1:0).

Edit after the author's comment

You can still use refactorfield on a geojson and create another geojson with the column and value you want.

Example :

This is my geojson with the IntegerList :
enter image description here

I can obtain this kind of result in a temporary layer : enter image description here

Instead of calling the geojson from your api with iface.addVectorLayer you can call it with QgsVectorLayer so it doesn't appear in the canvas. You can then use refactorfield to change the fields' type of your layer and save the result in a temporary layer (I use tempfile to save the layer in a temporary directory).

With this code, only one layer appear in the canvas, a temporary layer without IntegerList :

import re, tempfile
tf = tempfile.TemporaryDirectory()

def format_to_string(value):
    if isinstance(value, str) :
        string = re.split(',|:|)', value)
        new_value = ''
        for elem in string[1:-1] :
            if elem == string[1:-1][-1] :
                new_value = new_value +str(elem)
            else :
                new_value = new_value +str(elem) + ", "
    if isinstance(value, list) :
        string = ""
        for elem in value:
            string += elem
            if elem != value[-1] :
                string = string + ', '
        new_value = string
    return (new_value)

path = tf.name + "/"

geoj = "/home/Documents/geojson/champ_listint.geojson"

geoj_layer = QgsVectorLayer(geoj, '', 'ogr')
refactor = []
field_error = []
for field in geoj_layer.fields(): # creation of a list with the field and field type of the layer.
    refactor_field = {}
    refactor_field['expression']=str(""")+field.name()+str(""")
    refactor_field['name']=field.name()
    refactor_field['precision']=field.precision()
    if field.typeName() == "IntegerList": # if the field as a not well handled type
        field_error.append(field.name())
        refactor_field['length']=0
        refactor_field['type']=10
    elif field.typeName() == "StringList": # if the field as a not well handled type
        field_error.append(field.name())
        refactor_field['length']=0
        refactor_field['type']=10
    else :
        refactor_field['type']=field.type()
    refactor.append(refactor_field)

output_path = path + 'new_layer.geojson'
# I'm using refactorfield algorithm from Qgis to create a new layer with some modification on specified fields.
processing.run("qgis:refactorfields", {'INPUT':geoj,'FIELDS_MAPPING':refactor,'OUTPUT':output_path})
iface.addVectorLayer(output_path,'', 'ogr')

# Modification of the value for a better value
geoj_layer_2 = QgsVectorLayer(output_path, '', 'ogr')
for field_name in field_error:
    for f in geoj_layer.getFeatures() :
        attrs= f.attributes()
        value = attrs[geoj_layer.fields().indexFromName(field_name)]
        new_value = format_to_string(value)
        geoj_layer_2.startEditing()
        geoj_layer_2.changeAttributeValue(f.id(), geoj_layer.fields().indexFromName(field_name), new_value )
        geoj_layer_2.commitChanges()
        geoj_layer_2.triggerRepaint()

enter image description here

Correct answer by JULESG on May 18, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP