Hi all,
Arcade expressions are a great way to non-permanently add information to records.
I was wondering if there's a way to grab the output of an Arcade expression for use in a Python script? Basically I want to treat is as a field in its own right.
My use-case that I have in mind is concatenating several fields.
For example, Genus, species, common name are all separate fields but are often concatenated for display. I don't need it concatenated in my main data, but for this particular workflow (labelling things), I'd like to have that information.
Thanks!
Solved! Go to Solution.
Arcade expressions are only evaluated on command, e.g.
That means:
For example, Genus, species, common name are all separate fields but are often concatenated for display. I don't need it concatenated in my main data, but for this particular workflow (labelling things), I'd like to have that information.
If this is just for labelling, you can just use an expression in the Label Class. You can only access teh $feature, for performance reasons the Arcade Labeling Profile doesn't allow the FeatureSetBy*() functions. But what you want could be as simple as this:
var genus = "Genus"// $feature.Genus
var species = "Species"// $feature.Species
var name = "Common Name"// $feature.CommonName
return `${genus} ${species} (${name})`
I did some more work on it and I found a workaround. It's not great, but it gets the job done and does not slow down the workflow significantly (it added about one second to the workflow).
Basically, you grab the expression, create a new field, use the expression to calculate the field, and then delete it at the end of the workflow. This will only work if you have an active map with the data in it, since we're basically just accessing the lyr file.
I'm hoping someone finds this and makes good use of it in the future.
Code:
FC = #whatever feature class. Must be a layer in your map for this to work
#since we're basically accessing a lyr file.
nameField1 = arcpy.GetParameterAsText(0) #"expression/expression0"
#This next line makes this case insensitive. Get rid of the .lower() if unnecessary.
if arcpy.Describe(FC).dataType == "FeatureLayer":
#Look up expression if it's being referenced
if "expression/" in nameField1.lower():
if (nameField1.lower() != "expression/"): #E.g. "expression/expression1"
nameField1 = nameField1.split("/")[1] #Split to get the expression name\
#to search in the dictionary
'''Grab cim information'''
cim_lyr= FC.getDefinition('V2') #get CIM definition
cim_lyr=cim_lyr.popupInfo #Get pop-up information
#FC = FC.dataSource #I access the datasource for part of my workflow, so you may be able to ignore this
if cim_lyr != None:
cim_lyr= cim_lyr.expressionInfos #List of expressions in the popup info
expDict= {} #Empty dictionary to match input with the list of expressions
for f in cim_lyr:
expDict[f.name.lower()]= f.expression #Match expression name with the actual expression\
#e.g. {expression0 : "'Feature' $feature.FEAT_TYPE"}
if nameField1.lower() in expDict:
arcpy.management.CalculateField(FC, "ExpressionDELETE", expDict[nameField1.lower()], 'ARCADE') #Create and calculate field
nameField1= "ExpressionDELETE"
#Grab the first expression out of all of them
#This is kind of lazy because I should manually call the first in the list, but this works
elif nameField1.lower() == "expression/":
cim_lyr= FC.getDefinition('V2')
cim_lyr=cim_lyr.popupInfo
#FC = FC.dataSource #See note above
if cim_lyr != None:
cim_lyr= cim_lyr.expressionInfos
arcpy.management.CalculateField(FC, 'ExpressionDELETE', cim_lyr[0].expression, 'ARCADE')
nameField1= 'ExpressionDELETE'
#code
#code
#code
#Delete the field so it's not ugly to look at. May cause issues; see my notes outside the code.
arcpy.management.DeleteField(FC, 'ExpressionDELETE')
This is will break if you're running it on more than one feature class at a time multiple times in a session, but for one feature class, it will go forever.
An idea that is relevant to this post was posted here.
Arcade expressions are only evaluated on command, e.g.
That means:
For example, Genus, species, common name are all separate fields but are often concatenated for display. I don't need it concatenated in my main data, but for this particular workflow (labelling things), I'd like to have that information.
If this is just for labelling, you can just use an expression in the Label Class. You can only access teh $feature, for performance reasons the Arcade Labeling Profile doesn't allow the FeatureSetBy*() functions. But what you want could be as simple as this:
var genus = "Genus"// $feature.Genus
var species = "Species"// $feature.Species
var name = "Common Name"// $feature.CommonName
return `${genus} ${species} (${name})`
Hi Johannes, thanks for responding.
That is good information to know.
I should have been more clear in my original post; it's for labelling a derivative product so I might be up the creek without a paddle here.
I might make an Idea about this.
I did some more work on it and I found a workaround. It's not great, but it gets the job done and does not slow down the workflow significantly (it added about one second to the workflow).
Basically, you grab the expression, create a new field, use the expression to calculate the field, and then delete it at the end of the workflow. This will only work if you have an active map with the data in it, since we're basically just accessing the lyr file.
I'm hoping someone finds this and makes good use of it in the future.
Code:
FC = #whatever feature class. Must be a layer in your map for this to work
#since we're basically accessing a lyr file.
nameField1 = arcpy.GetParameterAsText(0) #"expression/expression0"
#This next line makes this case insensitive. Get rid of the .lower() if unnecessary.
if arcpy.Describe(FC).dataType == "FeatureLayer":
#Look up expression if it's being referenced
if "expression/" in nameField1.lower():
if (nameField1.lower() != "expression/"): #E.g. "expression/expression1"
nameField1 = nameField1.split("/")[1] #Split to get the expression name\
#to search in the dictionary
'''Grab cim information'''
cim_lyr= FC.getDefinition('V2') #get CIM definition
cim_lyr=cim_lyr.popupInfo #Get pop-up information
#FC = FC.dataSource #I access the datasource for part of my workflow, so you may be able to ignore this
if cim_lyr != None:
cim_lyr= cim_lyr.expressionInfos #List of expressions in the popup info
expDict= {} #Empty dictionary to match input with the list of expressions
for f in cim_lyr:
expDict[f.name.lower()]= f.expression #Match expression name with the actual expression\
#e.g. {expression0 : "'Feature' $feature.FEAT_TYPE"}
if nameField1.lower() in expDict:
arcpy.management.CalculateField(FC, "ExpressionDELETE", expDict[nameField1.lower()], 'ARCADE') #Create and calculate field
nameField1= "ExpressionDELETE"
#Grab the first expression out of all of them
#This is kind of lazy because I should manually call the first in the list, but this works
elif nameField1.lower() == "expression/":
cim_lyr= FC.getDefinition('V2')
cim_lyr=cim_lyr.popupInfo
#FC = FC.dataSource #See note above
if cim_lyr != None:
cim_lyr= cim_lyr.expressionInfos
arcpy.management.CalculateField(FC, 'ExpressionDELETE', cim_lyr[0].expression, 'ARCADE')
nameField1= 'ExpressionDELETE'
#code
#code
#code
#Delete the field so it's not ugly to look at. May cause issues; see my notes outside the code.
arcpy.management.DeleteField(FC, 'ExpressionDELETE')
This is will break if you're running it on more than one feature class at a time multiple times in a session, but for one feature class, it will go forever.
An idea that is relevant to this post was posted here.