Select to view content in your preferred language

Writing attributes from a related table to a feature layer

338
3
10-02-2024 01:52 PM
RemyShipman
New Contributor

I don't have a whole lot of python experience and I have been trying to get this script working for a while now and it seems like I'm close to getting it to work, but at this point I feel as if I'm banging my head against the wall. 

 

So, I have a hosted feature layer on my organization's Enterprise. It has two layers, called Private Trees and Public trees and a table called Tree Pruning Schedule that is related to the Public Trees layer. I'm trying to get the script to write attributes from the table into the layer itself. I followed this post for the most part, but my issue is once it gets to the end I get "Exception: 'list' object has no attribute 'attributes'". And I'm not sure how to fix it. Any help would be greatly appreciated!

 

from arcgis import GIS
gis = GIS("home")
from arcgis.features import FeatureLayer
import pandas as pd
from arcgis.features import FeatureCollection

# Call feature service and make variables for the Public Trees layer
treeinventory = gis.content.get("ID")
trees = treeinventory.layers[1]
trees_sedf = pd.DataFrame.spatial.from_layer(trees)
trees_fset = trees.query()
trees_features = trees_fset.features

# Make variable for the Pruning Table
table_url = 'org url'
table_lyr = FeatureLayer(table_url)
table_sedf = pd.DataFrame.spatial.from_layer(table_lyr)
table_fset = table_lyr.query()
table_features = table_fset.features

# Set up dataframes, drop duplicates besides most recent, merge both data frame tables
df = table_sedf.sort_values('tree_id', ascending=False)
df = df.drop_duplicates(subset="tree_id")
joined_rows = pd.merge(left = trees_sedf, right = df, how = 'inner', on = 'tree_id')

# Loop through dataframes and update Trees to match Pruning Table
def update(trees, table):
    for tree_id in joined_rows['tree_id']:
        try:
            tree_feature = [f for f in trees if f.attributes['tree_id'] == tree_id][0]
            tree_feature.attributes['prunesched'] = table.attributes['pruningschedule']
            trees.edit_features(updates=[tree_feature])
        except Exception as e:
            print(f"Could not update {tree_id}. Exception: {e}")
            continue
            
update(trees_features, table_features)

 

3 Replies
Clubdebambos
Frequent Contributor

Hi @RemyShipman 

If you print tree_feature before applying the edit_features(updates) what does the printout look like for tree_feature?

The edit_features(updates=) is expecting a list of dictionaries or in your case, a list containing one dictionary that describes the update for the feature on each iteration.

~ learn.finaldraftmapping.com
0 Kudos
JakeSkinner
Esri Esteemed Contributor

Hi @RemyShipman, you might be able to do this a little easier with search and update cursors.  Below is an example:

import arcpy
from arcgis import GIS

# Variables
portalURL = 'https://portal.esri.com/portal'
username = 'portaladmin'
password = '**********'
itemID = '7dd52c5ef86b44efaaac764294abf163'

# Connect to Portal
print("Connecting to Portal")
gis = GIS(portalURL, username, password)
arcpy.SignInToPortal(portalURL, username, password)

# Reference services
print("Referencing services")
treeinventory = gis.content.get(itemID)
treeLyr = treeinventory.layers[0]
treeTbl = treeinventory.tables[0]

# Create dictionary of schedule
print("Creating dictionary of schedule")
schedDict = {}
with arcpy.da.SearchCursor(treeTbl.url, ['tree_id', 'pruningschedule']) as cursor:
    for row in cursor:
        schedDict[row[0]] = row[1]
del cursor

# Updating tree lyr
print("Updating tree")
with arcpy.da.UpdateCursor(treeLyr.url, ['tree_id', 'prunesched']) as cursor:
    for row in cursor:
        try:
            row[1] = schedDict[row[0]]
            cursor.updateRow(row)
        except KeyError:
            pass
del cursor

print("Finished")
Clubdebambos
Frequent Contributor

To accompany the great ArcPy solution from @JakeSkinner here is an option using the ArcGIS API for Python in case ArcPy is not an option.

 

from arcgis import GIS

## Access ArcGIS Online
agol = GIS("home")

################################################################################
## USER INPUT REQUIRED #########################################################

## get the Feature Service as an Item object
trees_fs = agol.content.get("FS_ITEM_ID")

match_field_lyr = "FIELD_NAME" # Matching ID from layer
match_field_tbl = "FIELD_NAME" # Matching ID from table
fld2update = "FIELD_NAME"
tbl_fld = "FIELD_NAME"

## NOTE: You also need to put in the layer and table index below

################################################################################

## get the Feature Layer to update as a FeatureLayer object
trees_fl = trees_fs.layers[1] # get the correct layer

## get the attributes for each tree
## returns the OID, MatchField, FieldToUpdate
trees_features = trees_fl.query(
    out_fields = f"{match_field_lyr},{fld2update}",
    return_geometry = False
).features

## get the table as a Table object
trees_tbl = trees_fs.tables[0] # get the correct table

## get the attribute from the table
## return OID, MatchField, FieldToGetDataFrom
trees_records = trees_tbl.query(
    out_fields = f"{match_field_tbl},{tbl_fld}"
).features

## create a dictionary from trees_records for lookup based on MatchField
lookup = {f.attributes[match_field_lyr]: f.attributes[tbl_fld] for f in trees_records}

## iterate over trees_features and update the FieldToUpdate based on REG_NO using the lookup dictionary
for f in trees_features:
    reg_no = f.attributes[match_field_tbl]
    if reg_no in lookup:
        f.attributes[fld2update] = lookup[reg_no]
        ## you could call edit_features here too instead of below
        ## although will be significantly slower with this approach
        #trees_fl.edit_features(
        #    updates=[f]
        #)


## update the FeatureLayer
trees_fl.edit_features(
    updates=trees_features
)

 

~ learn.finaldraftmapping.com