Field Calculating Geometry Attributes in ArcGIS API for Python

781
2
Jump to solution
09-09-2021 04:53 AM
AnneSantaMaria
New Contributor III

Good Morning,

I am trying to calculate geometry attributes (polygon extents, point centroids, areas etc) on a feature service hosted in our stand alone portal using notebooks and the ArcGIS API for Python. Our data is branch versioned and hosted on an enterprise portal. What I cannot figure out is how to field calculate things like geometry, extents, and point centroids in different coordinates. It seems the syntax on the API reference page doesn't work when trying to work with data accessed and assigned to a spatially enable data frame. Am I using the spatially enable data frame incorrectly or is there a different way this should be computed? 

This is what I would like to be able to do:

1. Calculate area (this can also be done using attribute rules)

2. Calculate the XY min/max extents of polygons in decimal degrees (not the spatial reference of the item)

3. Calculate centroids in decimal degrees.

Here is the code below. My problem is the feature set/spatially enable data frame is a dictionary and it appears the geometry module doesn't work the same with those items. The error I receive below: 

AttributeError: 'dict' object has no attribute 'area'

 

 

from arcgis.gis import GIS
#import modules
from arcgis.features import *
from arcgis.geometry import *
import os
import datetime

#access portal and pull the feature service and layer for analysis
gis = GIS('home')
search_result = gis.content.search('NotebookTesting', 'Feature Layer')
item = search_result[0]
item_layers = item.layers
feature_layer = item_layers[0]
#check if editing and updating are enabled
feature_layer.properties.capabilities 
#query the layer for items that meet criteria ie area is null
fset = feature_layer.query(where = 'AreaSqFeet IS NULL') 
flayer_rows = fset.sdf
fset.sdf
for index, row in flayer_rows.iterrows():
   if row ['Area_SqFeet'] == None:
           fset.features[index].geometry.area # here is where I think I would calculate area, centroids and extents
          feature_layer.edit_features(updates = fset.features)

 

 

 

 

0 Kudos
1 Solution

Accepted Solutions
jcarlson
MVP Notable Contributor

In short, yes, you're not quite using the spatially enabled dataframe correctly. Accomplishing your goal is quite simple, but you have to create an intermediate GeoSeriesAccessor object. This class allows you to perform operations like area, extent, etc., on an entire series of shapes, which avoids needing to iterate over your dataframe. You can also use the project_as method to get those outputs into degrees.

 

# Add this to your import statements
from arcgis.features import GeoSeriesAccessor

# Grab the layer directly, without as many intermediate outputs
# Using content.get avoids potentially inconsistent results from a search
lyr = gis.content.get('itemID').layers[0]

# Query features directly to a dataframe
df = lyr.query(where = 'AreaSqFeet IS NULL', as_df=True)

# Create GeoSeriesAccessor
gsa = GeoSeriesAccessor(df['SHAPE'])

# Use GSA to calculate fields
df['area_field'] = gsa.area
df['centroid_dd'] = gsa.centroid
df['extent_field'] = gsa.extent

 

 Mind you, centroid and extent return tuples, so you may need to do some extra work to reformat that as needed for your fields. It might look something like this:

 

new_col_list = ['xmin','ymin','xmax','ymax']
for n,col in enumerate(new_col_list):
    df[col] = df['extent'].apply(lambda extent: extent[n])

df.drop(columns='extent', inplace=True)

 

 If you have trouble projecting your dataframe / geoseriesaccessor, as I sometimes do, note that you can also specify a different spatial reference to be output from your query itself, by specifying out_sr=* as a parameter.

EDIT: I forgot to mention, when you're done, you can submit your edits in one go, rather than as part of an interative process. edit_features takes a featureset for the updates parameter, and you can convert your dataframe directly to a featureset once you've performed your calculations.

lyr.edit_features(updates = df.spatial.to_featureset())

 

- Josh Carlson
Kendall County GIS

View solution in original post

0 Kudos
2 Replies
jcarlson
MVP Notable Contributor

In short, yes, you're not quite using the spatially enabled dataframe correctly. Accomplishing your goal is quite simple, but you have to create an intermediate GeoSeriesAccessor object. This class allows you to perform operations like area, extent, etc., on an entire series of shapes, which avoids needing to iterate over your dataframe. You can also use the project_as method to get those outputs into degrees.

 

# Add this to your import statements
from arcgis.features import GeoSeriesAccessor

# Grab the layer directly, without as many intermediate outputs
# Using content.get avoids potentially inconsistent results from a search
lyr = gis.content.get('itemID').layers[0]

# Query features directly to a dataframe
df = lyr.query(where = 'AreaSqFeet IS NULL', as_df=True)

# Create GeoSeriesAccessor
gsa = GeoSeriesAccessor(df['SHAPE'])

# Use GSA to calculate fields
df['area_field'] = gsa.area
df['centroid_dd'] = gsa.centroid
df['extent_field'] = gsa.extent

 

 Mind you, centroid and extent return tuples, so you may need to do some extra work to reformat that as needed for your fields. It might look something like this:

 

new_col_list = ['xmin','ymin','xmax','ymax']
for n,col in enumerate(new_col_list):
    df[col] = df['extent'].apply(lambda extent: extent[n])

df.drop(columns='extent', inplace=True)

 

 If you have trouble projecting your dataframe / geoseriesaccessor, as I sometimes do, note that you can also specify a different spatial reference to be output from your query itself, by specifying out_sr=* as a parameter.

EDIT: I forgot to mention, when you're done, you can submit your edits in one go, rather than as part of an interative process. edit_features takes a featureset for the updates parameter, and you can convert your dataframe directly to a featureset once you've performed your calculations.

lyr.edit_features(updates = df.spatial.to_featureset())

 

- Josh Carlson
Kendall County GIS
0 Kudos
AnneSantaMaria
New Contributor III

Thank you so much for helping. I would have never thought to look at the geoaccessorseries object. I was able to work through the scripting component and run the script until actually applying the updates. The edits cannot be applied "True curve update not allowed." Error code 400.  Which is weird because the service does Support True Curves : set to true. That may be something with our data/server and nothing to do with the script BUT again thank you! The script works like a charm. 

0 Kudos