4

As part of a project, I have to extract X, Y coordinates from points in a shapefile, and classify each point as North or South, or East or West.

To do so, I created new fields:

  • a field to store the X coordinates
  • a field to store the Y coordinates
  • a field to store the North/South classification for each point based on the Y coordinates
  • a field to store the East/West classification for each point based on the X coordinates

I wrote code to add these new fields, and I wrote code to delete these new fields to make the script re-runnable. All fields are added just fine, but only some fields are deleted. If I run the script again, all the fields are added again as extras (i.e. XCoord [original]...XCoord1[extras]...XCoord2[extras], etc.), and again, only some fields are deleted, and so on.

This is must be a problem with my delete code, but I'm not sure where I've gone wrong exactly. Is it the placement of my code within the script, or syntax, or something else?

My current placement of addition and deletion code is as below, before and after my mainline, respectively. I have tried including it within the mainline, but to no avail. I have also tried adding the deletion code first, and then the addition code.

# import libraries
import os
from qgis.PyQt.QtCore import QVariant

# Make shapefile the working layer.
currLayer = qgis.utils.iface.activeLayer()

caps = currLayer.dataProvider().capabilities()
currLayer.updateFields()

# Add the coordinate and classification attributes.
if caps & QgsVectorDataProvider.AddAttributes:

# I put the attributes on new lines for readability on GIS stack exchange.
    res = currLayer.dataProvider().addAttributes([
                QgsField("XCoord", QVariant.String),
                QgsField("YCoord", QVariant.String),
                QgsField("NSHemi", QVariant.String)
                QgsField("EWHemi", QVariant.String)])

# Commit the addition of fields.
currLayer.updateFields()

############################################################
### Main line, which works just fine ###
############################################################

caps = currLayer.dataProvider().capabilities()
currLayer.updateFields()

# Delete the classification and coordinate attributes from the shapefile.
if caps & QgsVectorDataProvider.DeleteAttributes:

    # Get the field names from the current layer.
    fields = currLayer.fields()

    # Determine their index numbers.
    idxX = fields.indexFromName('XCoord')
    idxY = fields.indexFromName('YCoord')
    idxNSHemi = fields.indexFromName('NSHemi')
    idxEWHemi = fields.indexFromName('EWHemi')

    # If the index number is -1, that means the index search
    # didn't find the attribute. If index number is greater
    # than -1, the position is returned.

    if idxX > -1:
        res = currLayer.dataProvider().deleteAttributes([idxX])
    if idxY > -1:
        res = currLayer.dataProvider().deleteAttributes([idxY])
    if idxNSHemi > -1:
        res = currLayer.dataProvider().deleteAttributes([idxNSHemi])
    if idxEWHemi > -1:
        res = currLayer.dataProvider().deleteAttributes([idxEWHemi])

# Commit the removal of fields.
currLayer.updateFields()

1 Answer 1

6

The problem is very logical once you realise that field indexes are a dynamic thing. You are retrieving field indexes by their name in advance, then attempting to use those indexes when deleting fields one by one. The problem is that, immediately after the first field has been deleted, the indexes have dynamically changed, so the remaining previously calculated indexes are no longer valid!

So, if you want to delete attributes one by one, You will need to retrieve the field index from the field name at each step immediately before deletion, and also call updateFields() after each step.

*As a side note, I think you should use a decimal numeric type for X and Y fields.

currLayer = qgis.utils.iface.activeLayer()

caps = currLayer.dataProvider().capabilities()
currLayer.updateFields()

# Add the coordinate and classification attributes.
if caps & QgsVectorDataProvider.AddAttributes:

# I put the attributes on new lines for readability on GIS stack exchange.
    res = currLayer.dataProvider().addAttributes([
            QgsField("XCoord", QVariant.Double),
            QgsField("YCoord", QVariant.Double),
            QgsField("NSHemi", QVariant.String),
            QgsField("EWHemi", QVariant.String)])

# Commit the addition of fields.
currLayer.updateFields()

caps = currLayer.dataProvider().capabilities()

# Delete the classification and coordinate attributes from the shapefile.
if caps & QgsVectorDataProvider.DeleteAttributes:
    # If the index number is -1, that means the index search
    # didn't find the attribute. If index number is greater
    # than -1, the position is returned.
    idxX = currLayer.fields().indexFromName('XCoord')
    if idxX > -1:
        res = currLayer.dataProvider().deleteAttributes([idxX])
        print(res)# Check if operation returns True or False
        currLayer.updateFields()
    idxY = currLayer.fields().indexFromName('YCoord')
    if idxY > -1:
        res = currLayer.dataProvider().deleteAttributes([idxY])
        print(res)# Check if operation returns True or False
        currLayer.updateFields()
    idxNSHemi = currLayer.fields().indexFromName('NSHemi')
    if idxNSHemi > -1:
        res = currLayer.dataProvider().deleteAttributes([idxNSHemi])
        print(res)# Check if operation returns True or False
        currLayer.updateFields()
    idxEWHemi = currLayer.fields().indexFromName('EWHemi')
    if idxEWHemi > -1:
        res = currLayer.dataProvider().deleteAttributes([idxEWHemi])
        print(res)# Check if operation returns True or False
        currLayer.updateFields()

However, a much simpler way would be to pass a list of indexes to the deleteAttributes() method so that the fields will be deleted in one go.

E.g.

caps = currLayer.dataProvider().capabilities()

# Delete the classification and coordinate attributes from the shapefile.
if caps & QgsVectorDataProvider.DeleteAttributes:
    fld_names = ['XCoord', 'YCoord', 'NSHemi', 'EWHemi']
    indexes = [i for i in [currLayer.fields().lookupField(n) for n in fld_names] if i > -1]
    print(indexes)
    res = currLayer.dataProvider().deleteAttributes(indexes)
    print(res)# Check if operation returns True or False
    currLayer.updateFields()
2
  • The OP could also get radixes once and delete in reverse sorted radix order, so that the radixes don't change.
    – Vince
    Nov 19, 2022 at 13:21
  • 1
    Ah okay, that makes sense. I knew I was missing something straightforward. Thank you for your explanations and for your suggestions, I appreciate it!
    – Mommo_Mia
    Nov 20, 2022 at 0:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.