Select to view content in your preferred language

Arcade expressions to add, update in edit property

5445
4
Jump to solution
05-18-2022 10:58 PM
HNTZK46
Emerging Contributor

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.

Tags (2)
0 Kudos
1 Solution

Accepted Solutions
JohannesLindner
MVP Frequent Contributor
  • If you're triggering on both Insert and Update, you have to either create 2 rules or take care of your current edit type. In your case, when you move a point, you both update the existing polygon's location and also create a new polygon.
  • your updates array is wrong, I'm surprised that it actually works.

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
        }
    ]
}

 


Have a great day!
Johannes

View solution in original post

4 Replies
JohannesLindner
MVP Frequent Contributor
  • If you're triggering on both Insert and Update, you have to either create 2 rules or take care of your current edit type. In your case, when you move a point, you both update the existing polygon's location and also create a new polygon.
  • your updates array is wrong, I'm surprised that it actually works.

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
        }
    ]
}

 


Have a great day!
Johannes
HNTZK46
Emerging Contributor

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. 

 

0 Kudos
JohannesLindner
MVP Frequent Contributor

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:

GitHub - Esri/arcade-expressions: ArcGIS Arcade expression templates for all supported profiles in t...

 

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...


Have a great day!
Johannes
0 Kudos
HNTZK46
Emerging Contributor

Thanks a lot for the information!

0 Kudos