Hello, I am looking for some tips to complete Arcade expression for calculation rule of Attribute Rules that does as follows:
1. Create a buffer around a created point feature
2. Update a buffer location accordingly when a point feature's location is updated
3. If there is an intersect between a point feature and another polygon feature (say "Park"), get attribute values from another polygon class to populate fields of a point featureclass
Problems:
1. When a new point is created, a buffer is not created and also fields not populated with extracted values
2. When existing point is moved, Buffer is updated its location based on the updated point and also fields are populated successfully with the extracted values. But an additional buffer feature is also created in the buffer polygon feature class.
my code is below:
var bufferedGeometry = buffer($feature,500)
var globalId = $feature.globalid
var fs = FeatureSetbyName($datastore, "PolygonClass")
var bf = filter(fs, "PointGuid = @globalId")
if (count(bf) == 0) return $feature.NAME;
var bufferedFeature = first(bf)
var fsPark = FeatureSetByName($datastore, "Park", ["*"])
var fsParkIntersect = Intersects(fsPark, Geometry($feature))
var ParkAtt = First(fsParkIntersect)
if (ParkAtt == null)
return {"errorMessage": "Point must be created in Park."}
else
return {
"result": {
"attributes" : {
"NAME" : "Point" + ParkAtt.NAME,
"ParkNAME" : ParkAtt.NAME,
"ParkID" : ParkAtt.ParkID
}
},
"edit": [
{
"className" : "PolygonClass",
"adds" : [
{
"attributes": {
"NAME": $feature.NAME,
"PointGuid": $feature.GlobalID
},
"geometry": bufferedGeometry
}
],
"updates" : [
{
"GlobalID" : bufferedFeature.globalId,
"NAME": $feature.NAME,
"geometry": bufferedGeometry
}
]
}
]
}
I created the above code based on the code found here https://www.esri.com/arcgis-blog/products/arcgis-pro/data-management/advanced-gdb-attribute-rules-ed...
Thanks in advance for your help.
Solved! Go to Solution.
For documentation on the Attribute Rule return keywords, go here: https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/attribute-rule-dictionary-k...
You also have to think about what happens if you manually delete a buffer polygon. Then you will get ExceptionErrors, because your code doesn't find the feature, so bufferedFeature will be null, and then you try to call attributes on null, which doesn't work. So you have to include null checks, just like you did for the park feature.
Personally, I find it much easier to create and fill the adds, updates, anad deletes array in my code and just use those arrays in the return dict. Makes it much more readable and also easier to write, because you don't have to care about the brackets that much.
So, with all that (switch behavior dependent on edit mode, do null checks, fill the edit arrays inside the code blocks), we end up with something like this:
// determine our edit mode ("INSERT", "UPDATE", or "DELETE")
var mode = $editcontext.editType
// return early if we're updating the $feature and the geometry didn't change
var geometry_is_unchanged = Equals(Geometry($feature), Geometry($originalfeature))
if(mode == "UPDATE" && geometry_is_unchanged) {
return
}
// get park feature
var fsPark = FeatureSetByName($datastore, "Parks", ["NAME", "ParkID"], false)
var fsParkIntersect = Intersects(fsPark, Geometry($feature))
var park = First(fsParkIntersect)
// return error if $feature doesn't intersect a park
if (park == null)
return {"errorMessage": "Point must be in Park."}
// create the result dict
// we do this here, because we need some of the fields in the edit arrays
var result = {
"attributes": {
"NAME": "Point " + park.NAME,
"ParkNAME": park.NAME,
"ParkID": park.ParkID
}
}
// initialize the edit arrays for adds, updates, and deletes
var adds = []
var updates = []
var deletes = []
// depending on edit type, fill the corresponding array
var bufferedGeometry = Buffer($feature, 500)
if(mode != "INSERT") {
// get the buffer polygon (we don't need that if we're inserting)
var globalId = $feature.GlobalID
var fsBuffers = FeatureSetbyName($datastore, "TestPolygons", ["GlobalID"], false)
var bufferedFeature = First(Filter(fsBuffers, "PointGuid = @globalId"))
if(mode == "UPDATE") {
if(bufferedFeature == null) {
// somehow, there is no buffer polygon (eg we deleted it).
// in that case, use the insert code
mode = "INSERT"
} else {
var update = {
"globalID": bufferedFeature.GlobalID,
"geometry": bufferedGeometry
"attributes": {"NAME": result.NAME}
}
Push(updates, update)
}
}
if(mode == "DELETE") {
if(bufferedFeature == null) {
// somehow, there is no buffer polygon (eg we deleted it).
// in that case, we don't need to edit the buffer polygons,
// we can just return
return
} else {
var delete = {"globalID": bufferedFeature.GlobalID}
Push(deletes, delete)
}
}
}
if(mode == "INSERT") {
var add = {
"geometry": bufferedGeometry,
"attributes": {
"PointGuid": $feature.GlobalID,
"NAME": result.NAME
}
}
Push(adds, add)
}
// return
return {
"result": result,
"edit": [
{
"className": "TestPolygons",
"adds": adds,
"updates": updates,
"deletes": deletes
}
]
}
For documentation on the Attribute Rule return keywords, go here: https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/attribute-rule-dictionary-k...
You also have to think about what happens if you manually delete a buffer polygon. Then you will get ExceptionErrors, because your code doesn't find the feature, so bufferedFeature will be null, and then you try to call attributes on null, which doesn't work. So you have to include null checks, just like you did for the park feature.
Personally, I find it much easier to create and fill the adds, updates, anad deletes array in my code and just use those arrays in the return dict. Makes it much more readable and also easier to write, because you don't have to care about the brackets that much.
So, with all that (switch behavior dependent on edit mode, do null checks, fill the edit arrays inside the code blocks), we end up with something like this:
// determine our edit mode ("INSERT", "UPDATE", or "DELETE")
var mode = $editcontext.editType
// return early if we're updating the $feature and the geometry didn't change
var geometry_is_unchanged = Equals(Geometry($feature), Geometry($originalfeature))
if(mode == "UPDATE" && geometry_is_unchanged) {
return
}
// get park feature
var fsPark = FeatureSetByName($datastore, "Parks", ["NAME", "ParkID"], false)
var fsParkIntersect = Intersects(fsPark, Geometry($feature))
var park = First(fsParkIntersect)
// return error if $feature doesn't intersect a park
if (park == null)
return {"errorMessage": "Point must be in Park."}
// create the result dict
// we do this here, because we need some of the fields in the edit arrays
var result = {
"attributes": {
"NAME": "Point " + park.NAME,
"ParkNAME": park.NAME,
"ParkID": park.ParkID
}
}
// initialize the edit arrays for adds, updates, and deletes
var adds = []
var updates = []
var deletes = []
// depending on edit type, fill the corresponding array
var bufferedGeometry = Buffer($feature, 500)
if(mode != "INSERT") {
// get the buffer polygon (we don't need that if we're inserting)
var globalId = $feature.GlobalID
var fsBuffers = FeatureSetbyName($datastore, "TestPolygons", ["GlobalID"], false)
var bufferedFeature = First(Filter(fsBuffers, "PointGuid = @globalId"))
if(mode == "UPDATE") {
if(bufferedFeature == null) {
// somehow, there is no buffer polygon (eg we deleted it).
// in that case, use the insert code
mode = "INSERT"
} else {
var update = {
"globalID": bufferedFeature.GlobalID,
"geometry": bufferedGeometry
"attributes": {"NAME": result.NAME}
}
Push(updates, update)
}
}
if(mode == "DELETE") {
if(bufferedFeature == null) {
// somehow, there is no buffer polygon (eg we deleted it).
// in that case, we don't need to edit the buffer polygons,
// we can just return
return
} else {
var delete = {"globalID": bufferedFeature.GlobalID}
Push(deletes, delete)
}
}
}
if(mode == "INSERT") {
var add = {
"geometry": bufferedGeometry,
"attributes": {
"PointGuid": $feature.GlobalID,
"NAME": result.NAME
}
}
Push(adds, add)
}
// return
return {
"result": result,
"edit": [
{
"className": "TestPolygons",
"adds": adds,
"updates": updates,
"deletes": deletes
}
]
}
Thank you very much for the solution! As you mentioned, I created two rules for each insert and update, then it works.
Also thanks for the link about the dictionary keywords. Just wondering if you could tell me any tutorial site or online free documents of Arcade or Attribute Rules that I can access? I would like to learn Arcade and Attribute Rules more but I cannot find any tutorial or training resource for beginner like me.
For getting started:
Getting Started | ArcGIS Arcade | ArcGIS Developer
Your Arcade Questions Answered (esri.com)
Attribute Rules in Arcade (From Scratch) - Demo Th... - Esri Community
Introduction to Attribute Rules
Useful documentation (I have this bookmarked):
Function Reference | ArcGIS Arcade | ArcGIS Developer
Code examples:
Some videos:
Attribute Rules Videos - Esri Community
And of course the Attribute Rules - Esri Community for asking all of your questions about Attribute Rules.
Sadly, there's no group for general Arcade yet; the jury is out on whether to post those in the ArcGIS Pro group or the AGOL group...
Thanks a lot for the information!