Select to view content in your preferred language

Accessing Arcade expressions in popups (ArcGIS Pro) for use in python script

1146
4
Jump to solution
05-18-2022 11:33 AM
AlfredBaldenweck
MVP Regular Contributor

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!

2 Solutions

Accepted Solutions
JohannesLindner
MVP Frequent Contributor

Arcade expressions are only evaluated on command, e.g.

  • when you open a popup
  • when you trigger an attribute rule
  • when you use Arcade in the CalculateField
  • when you show a feature with Arcade in its label or symbology on the map

 

That means:

  • You can get and set Arcade expressions with the CIM module.
  • You cannot get the expressions' outputs with Python.

 

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})`

JohannesLindner_0-1652937343344.png

 


Have a great day!
Johannes

View solution in original post

0 Kudos
AlfredBaldenweck
MVP Regular Contributor

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.

  • This can also be avoided by just not deleting that calculated field, but that's kind of ugly to look at.
  • Close and re-open Pro if you need to run whatever you're doing on the same multiple features multiple times to reset whatever issue is causing this.

An idea that is relevant to this post was posted here.

View solution in original post

0 Kudos
4 Replies
AlfredBaldenweck
MVP Regular Contributor

As an update to this post.

I have managed to get into the CIM definition as mentioned here. I am able to see the expression, but am not sure yet how to access the output per record in the table. Would appreciate any tips.

0 Kudos
JohannesLindner
MVP Frequent Contributor

Arcade expressions are only evaluated on command, e.g.

  • when you open a popup
  • when you trigger an attribute rule
  • when you use Arcade in the CalculateField
  • when you show a feature with Arcade in its label or symbology on the map

 

That means:

  • You can get and set Arcade expressions with the CIM module.
  • You cannot get the expressions' outputs with Python.

 

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})`

JohannesLindner_0-1652937343344.png

 


Have a great day!
Johannes
0 Kudos
AlfredBaldenweck
MVP Regular Contributor

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.

0 Kudos
AlfredBaldenweck
MVP Regular Contributor

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.

  • This can also be avoided by just not deleting that calculated field, but that's kind of ugly to look at.
  • Close and re-open Pro if you need to run whatever you're doing on the same multiple features multiple times to reset whatever issue is causing this.

An idea that is relevant to this post was posted here.

0 Kudos