Attribute Rules: Update only on geometry change

2173
7
11-19-2019 05:47 AM
Status: Open
ThomasColson
MVP Frequent Contributor

With the following attribute rule

 

var fsPoly = Intersects(fsPolyBoundary, Geometry($feature))
var loc = First (fsPoly)

var name  = """"
if (loc == null)
   return  {""errorMessage"": ""Transformers must be created in a substation.""}
else 
   name =  loc.LOC_NAME

return name + "" - "" + $feature.LOC_NAME;",,,False,True,False,,,,"{""type"":""PropertySet"",""propertySetItems"":[]}"‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

We can update a field name based on its relationship "within" another polygon. 

If I want the field calculated on insert or edit, I do this: 

Problem is, what if the user makes an attribute edit, not a shape edit? The rule still fires (and takes a performance hit). 

 

With SQL triggers, I can restrict them to only fire if the shape is new or updated, and ignore any attribute updates. 


The same functionality should exist for attribute rules. 

7 Comments
SylvainHotte2

Here some code I did that might help you. I have a function (AttributeHasChangedExcept) that check if any attribute has changed except those pass in the array (you could have an empty array if you want). 

I think in your case the array would be empty, meaning the if the rule was triggered it is because of a geometry change...in that case you could do the rest of your code...maybe it is not exactly what you need but it could be adapted

//check if a value is in the array case insentitive
function IsValInArray(val, array){
    for (var i in array){
        if (upper(val)== upper(array[i])){
            return true
        }
    }
    return false
}

//check if attribute of feature are the same of previous
//Except the one in the ignore array
function AttributeHasChangedExcept(attrIgnore){
    
    //get and transform all attribute to dictionary
    var dictCurrent = Dictionary(text($feature))["attributes"]
    
    var hasChanged = [false,""]
    
    //Search each attribute to see if value was changed
    for (var attr in dictCurrent){
        //if not in the ignoreed attribute
        if (!IsValInArray(attr, attrIgnore)){
            var curVal = $feature[attr]            
            var prevVal = $originalFeature[attr]
            if (curVal != prevVal){
                hasChanged = [true,attr]
            }
        }
    }    
    return hasChanged
}

//Subord Assign cannot be updated except for:
// - Segment_ID
// - Set to Historical
// - Geometry (move)

//If original was SUBORD Assigned
if ($originalFeature.ADDRTYPE == "SUBORD" && $originalFeature.ADDRSTATUS == "AS"){
    var ignoredAttribute = ["RD_SEGMENT_ID", "ROAD_NAME_ID", "ADDRTYPE", "SUBTYPE", "CREATE_BY", "CREATE_DATE", "MODIFIED_BY", "MODIFIED_DATE", "OBJECTID", "GLOBALID"]
    //Filter 1 : check if attributes have changed
    var check = AttributeHasChangedExcept(ignoredAttribute)
    if (check[0]){
        //They cannot be changed
        return {"errorMessage":"Subord Assigned cannot be updated except for segment_id, Historical or point moved :" + check[1] + " has changed"}
    }
    
    //Filter 2 : New Address Type can only be Subord or Hist --> error
    if (! ($feature.ADDRTYPE == "SUBORD" || $feature.ADDRTYPE == "HIST")){
        return {"errorMessage":"Subord Assigned cannot be updated except for segment_id, Historical or point moved : point must be Subord or Historical"}
    }
    
    //we don't check geometry because change is allowed
    return true
}

//If not the rule does not apply to this case 
return true

 

TWickiSLU

I make use of "$originalFeature" to check if the area of the geomery has changed. If not, I skip the calculation and return the original value. However, the rule still fires, so I'm not sure if this helps performance.

 

 

// Calculate attribute "AREA" if geometry changes 
var origGeometry = $originalFeature['Shape.STArea()']; 
var newGeometry = $feature['Shape.STArea()']; 

if (origGeometry != newGeometry){
    return Round(Area($feature, 'square-meters'),0)
} else {
    return $feature.AREA
}

 

 

 

Bud
by

Migrated from a duplicate idea:


I have calculation attribute rules that only needs to be run if the geometry is edited.

Example:

  • If a new feature is created or if an existing feature's geometry is edited, then:
  • Redefine the M-Values by setting them to the cumulative length of the line.

It's not necessary to run those kinds of calculation attribute rules if only the non-spatial fields get edited. And there are scenarios where unnecessarily running the attribute rules causes slow performance.

Could an optional setting be added to calculation attribute rules that would let us only run the rule if the geometry has been changed?

Bud
by
LCTechLogin

This would be wonderful for the datasets that need to have a date field for the last geometry update - but if those don't happen often, to auto fill, since it will most likely be forgotten!  But this workflow won't work right now since attribute changes can trigger it too. 

JohannesLindner

@LCTechLogin 

You should be able to do something like this:

 

// calculation attribute rule
// field: LastGeometryEdit (Date)
// triggers: Insert, Update

// geometry changed if we're inserting a new feature or if the old and new geometry aren't equal
var geometry_changed = $editcontext.editType == "INSERT" || !Equals(Geometry($originalfeature), Geometry($feature))

// if geometry changed, return current dtae and time, else return field value
return IIf(geometry_changed, Now(), $feature.LastGeometryEdit)

 

 

This could also be somewhat used as workaround for the original idea. But of course, the rule will still fire if you edit an attribute, so there will be some perfomance hit, especially if you edit large chunks at once.

var geometry_changed = $editcontext.editType == "INSERT" || !Equals(Geometry($originalfeature), Geometry($feature))

// no geometry edit? return the current field value
if(!geometry_changed) { return $feature.WATERSHED }

// your code (this will only get executed if geometry_changed is true)
var fsPolyBoundary = ...
var fsPoly = ...
var loc = ...
...
...
return name
ChelseaRozek

Would love to have this built in as one of the check boxes. I have a lot of rules and don't want to have to copy this code into each one. It's also not a great solution since the rule fires anyways.