Select to view content in your preferred language

How to automatically update features with arcade calculated expressions

564
6
Jump to solution
10-18-2024 03:19 AM
JamesPoeschel
Frequent Contributor

I have features with calculated expressions that take values from nearby points and produce a shared final score. The issue I am facing is that when an additional point is created or one point is edited, all the other points need to be selected individually to be updated. In the picture below, there are four black points for each leg of an intersection. After each point is created and their forms are completed, the user has to go back and click each point individually to reupdate each point once again. Since any change to one point affects all 4.

JamesPoeschel_0-1729246138443.png

 

I am wondering if there is a way to add to the arcade code to automatically update. Or any other workarounds that would solve this problem.

 

Tags (1)
0 Kudos
1 Solution

Accepted Solutions
MikeMillerGIS
Esri Frequent Contributor

Please post code as a code snippet:

  • Code Comments:
    • Use $FeatureSet
      • I think you need to use $FeatureSet, as an attribute rule profile cannot use $layer
      • var matchingProj = Filter($layer, "Project = '" + currentProj + "' AND Intersection = '" + currentIntx + "'");
      • to
      • var matchingProj = Filter($featureSet, "Project = '" + currentProj + "' AND Intersection = '" + currentIntx + "'");
    • For the sql, best to use the @Variable syntax
      • "Project = '" + currentProj + "' AND Intersection = '" + currentIntx + "'");
      • "Project = @currentProj AND Intersection = @currentIntx");
    • I am confused by the Distinct call to get the leg directions and then more filtering of the data.  I think this could be simplified.  
      • for (var feat in matchingProj){
        	if (IsNull(feat.LegDirection)){
        		continue
        	}
        	var sumDenominator = 0;
            for (var field in fieldsAndScores) {
        		if (!IsEmpty(feat[field])) {
        			sumDenominator += fieldsAndScores[field];
        		}
        	}        
        
            totalSumDenominator += sumDenominator;
            
        }

 

What you want to do is get all the OIDs or GlobalID of the features you want to update.  You are looping over them, so store them into a list/array.

When done, convert that list into a list of dictionaries, with the globalid and an attributes keyword, with the field you wish to update in it

Since this is an attribute rule, it always fires in the database, so it will work no matter what client does the edits.  Some client are not aware of the return edit payload from apply edits(instead of the edit just returning success, it returns the others features that were edited so it can do a refresh).  So if the client is not reading the return edits, you just need to refresh to see the other updates.

The return edit statement you want is the Updates:

return {
    'edit': [{
        'className': 'b_edit_dict',
        'updates': [{
            'globalID': '{7EBAB596-E9DB-40D8-9756-B2EBED2500B7}',
            'attributes': {
                'field_name': 22
            }
        }]
    }]
}

 

View solution in original post

0 Kudos
6 Replies
MikeMillerGIS
Esri Frequent Contributor

Using an attribute rule, yes you can.  You can find all points in a distance and trigger an edit on that feature, so its attribute rule fire or calculate the value and set it on that feature.

You want to look at the return edits keywords:

https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/attribute-rule-dictionary-k...

0 Kudos
JamesPoeschel
Frequent Contributor

Hi Mike, thank you very much for pointing me in the right direction.
Looks like I need to use the example in the update section, but I need a global or objectID:


return {
    "result": $feature.assetid,
    "edit": [{
        "className": "electricdistributionassembly",
        "updates": [{
            "objectID": feature_objectid,
            "associationType": 'container'
        }]
    }]
}

To make sure, this would work in AGOL field maps editor?  Would I just pull the current objectID within the code?
To further clarify also, other calculated fields update automatically, like the "Freight Access Leg Direction Score" shown in the picture. But this field does not since its calculation involves other points.

 

My code is below in case it helps. It looks around for other features, but as I mentioned it requires me to update each point after one point is modified.
please forgive my lack of coding skill. 

// get the current project and intersection name
var currentProj = $feature.Project;
var currentIntx = $feature.Intersection;

// Filter through the other points that have the same project and intersection name
var matchingProj = Filter($layer, "Project = '" + currentProj + "' AND Intersection = '" + currentIntx + "'");

// get list of all different cardinal directions within the filtered points
var allDirections = Distinct(matchingProj, "LegDirection");

// set initial total denmoniator of scoring ratio to 0
var totalSumDenominator = 0;

// set highest possible score values of different fields
var fieldsAndScores = {
    Freight_VerticalClearance: 2,
    Freight_CornerTurningRadii: 1,
    Freight_TruckTurningMovement: 2,
};

// loop through each direction within the filter to get the total denominator
for (var f in allDirections) {
    var currentDir = f.LegDirection;
    var filteredFeatures = Filter(matchingProj, "LegDirection = '" + currentDir + "'");

    for (var feat in filteredFeatures) {
        var sumDenominator = 0;
        for (var field in fieldsAndScores) {
            if (!IsEmpty(feat[field])) {
                sumDenominator += fieldsAndScores[field];
            }
        }        

        totalSumDenominator += sumDenominator;
    }
}

// Add all values of the Freight Score for all features in the filter to get the numerator
var totalIntFreightScore = Sum(matchingProj, "Freight_AcessLegDirScore");

// if there is a score, divide the total score divided by total possible score
if (totalSumDenominator > 0) {
    return Round(((totalIntFreightScore / totalSumDenominator) * 100), 2);
} else {
    return 0; // Default value if the denominator is zero
}

0 Kudos
MikeMillerGIS
Esri Frequent Contributor

Please post code as a code snippet:

  • Code Comments:
    • Use $FeatureSet
      • I think you need to use $FeatureSet, as an attribute rule profile cannot use $layer
      • var matchingProj = Filter($layer, "Project = '" + currentProj + "' AND Intersection = '" + currentIntx + "'");
      • to
      • var matchingProj = Filter($featureSet, "Project = '" + currentProj + "' AND Intersection = '" + currentIntx + "'");
    • For the sql, best to use the @Variable syntax
      • "Project = '" + currentProj + "' AND Intersection = '" + currentIntx + "'");
      • "Project = @currentProj AND Intersection = @currentIntx");
    • I am confused by the Distinct call to get the leg directions and then more filtering of the data.  I think this could be simplified.  
      • for (var feat in matchingProj){
        	if (IsNull(feat.LegDirection)){
        		continue
        	}
        	var sumDenominator = 0;
            for (var field in fieldsAndScores) {
        		if (!IsEmpty(feat[field])) {
        			sumDenominator += fieldsAndScores[field];
        		}
        	}        
        
            totalSumDenominator += sumDenominator;
            
        }

 

What you want to do is get all the OIDs or GlobalID of the features you want to update.  You are looping over them, so store them into a list/array.

When done, convert that list into a list of dictionaries, with the globalid and an attributes keyword, with the field you wish to update in it

Since this is an attribute rule, it always fires in the database, so it will work no matter what client does the edits.  Some client are not aware of the return edit payload from apply edits(instead of the edit just returning success, it returns the others features that were edited so it can do a refresh).  So if the client is not reading the return edits, you just need to refresh to see the other updates.

The return edit statement you want is the Updates:

return {
    'edit': [{
        'className': 'b_edit_dict',
        'updates': [{
            'globalID': '{7EBAB596-E9DB-40D8-9756-B2EBED2500B7}',
            'attributes': {
                'field_name': 22
            }
        }]
    }]
}

 

0 Kudos
JamesPoeschel
Frequent Contributor

Thank you, that is a big help. I think I'm getting much closer now. I don't get an error when in the code tester, but when editing a feature in the web map, it wont let me update the layer.

JamesPoeschel_0-1729523277940.png

 

var currentProj = $feature.Project;
var currentIntx = $feature.Intersection;
var matchingProj = Filter($featureSet, "Project = @currentProj AND Intersection = @currentIntx");

var totalSumDenominator = 0;

var fieldsAndScores = {
    Freight_VerticalClearance: 2,
    Freight_CornerTurningRadii: 1,
    Freight_TruckTurningMovement: 2,
    Freight_OnStreetParking: 1,
    Freight_DesignatedFreightRout: 2
};

var listOID = [];
var index = 0; 
var attributesList = []; // Initialize an empty array for dictionaries

for (var feat in matchingProj){
    listOID[index] = feat.OBJECTID;
    index += 1;  // Increment the index

	if (IsNan(feat.LegDirection)){
		continue
	}
	var sumDenominator = 0;
    for (var field in fieldsAndScores) {
		if (!IsEmpty(feat[field])) {
			sumDenominator += fieldsAndScores[field];
		}
	}        

    totalSumDenominator += sumDenominator;
    
}

Console("List of OIDs: " + listOID);

// Add all values of the IntPedADAScore for all features in the filter
var totalIntFreightScore = Sum(matchingProj, "Freight_AcessLegDirScore");
    
// if (totalSumDenominator > 0) {
//     var FinalScore = Round(((totalIntFreightScore / totalSumDenominator) * 100), 2);
//     return FinalScore;
// } else {
//     return 0; // Default value if the denominator is zero
// }

var FinalScore = Round(((totalIntFreightScore / totalSumDenominator) * 100), 2);

// Convert the listOID into a list of dictionaries
for (var oid in listOID) {
    var attrDict = {
        "OBJECTID": listOID[oid],
        "Freight_AccessTotalScore": FinalScore
    };
    
    // Use the length of attributesList to append the new dictionary
    attributesList[Count(attributesList)] = attrDict;  
}

Console("List of Attributes: " + attributesList);

return {
    'edit': [{
        'className': FeatureSetById($map, "19130248331-layer-8"),
        'updates': [{
            attributesList
        }]
    }]
}
0 Kudos
MikeMillerGIS
Esri Frequent Contributor
You are creating an attribute rule, right? That looks like you are using a form calculation rule, which does not support DML(editing other features)
0 Kudos
JamesPoeschel
Frequent Contributor

To be honest, I am not 100% sure. I am pretty new. This is a calculated expression being applied to a form within field maps. Perhaps I should have posted this in the AGOL forum? Or is what I am trying to do not possible.

0 Kudos