We have a requirement to get the nearest line feature with respect to point feature using arcade expression in ArcGIS pro attribute rule.
(or)
Can we get the perpendicular distance between the Line Feature and Point Feature in ArcGIS pro using attribute rule (arcade expression)
Any quick help on the above requirement would be much appreciated.
Thanks in advance...!
@Anonymous User
Solved! Go to Solution.
// Calculation Attribute Rule for your point FC
// Field: NearestLineObjectID
// Triggers: Insert (, Update)
// Get your line FC
var line_fs = FeatureSetByName($datastore, "FullNameOfLineFeatureclass", ["ObjectID"], true)
// If you have many lines, just select those that are within a certain distance of your point
// I tested it without this line and around 10.000 line features and it took a good time to compute. With this line it was done almost instantly.
// Choose a distance in which a line feature will certainly be!
line_fs = Intersects(line_fs, Buffer($feature, 100, "Meters"))
// Cycle through the line features and find the nearest one
var min_dist = 999999 // arbitrary large number
var nearest_line_oid = null
var geo = Geometry($feature)
for(var line in line_fs) {
var line_geo = Geometry(line)
var dist = Distance(geo, line_geo)
if(dist < min_dist) {
min_dist = dist
nearest_line_oid = line.ObjectID
}
}
// Return the ObjectID of the nearest line feature
return nearest_line_oid
This will only work when you insert or update point features. If you insert or edit a line feature that would place it nearer to a point than another line, you have to update the point!
Or, you could add an Attribute Rule to the line featureclass that updates the point featureclass when you edit a line. Let me know if you need help with that.
@HusseinNasser2: This should probably be moved to the Attribute Rules group.
Hello - I recently stumbled upon this post and I am looking at doing what you say 'can be done' which is to have an attribute for the line class that will update the point class with the closest feature. Do you happen to have an example of that?
// Calculation Attribute Rule for your point FC
// Field: NearestLineObjectID
// Triggers: Insert (, Update)
// Get your line FC
var line_fs = FeatureSetByName($datastore, "FullNameOfLineFeatureclass", ["ObjectID"], true)
// If you have many lines, just select those that are within a certain distance of your point
// I tested it without this line and around 10.000 line features and it took a good time to compute. With this line it was done almost instantly.
// Choose a distance in which a line feature will certainly be!
line_fs = Intersects(line_fs, Buffer($feature, 100, "Meters"))
// Cycle through the line features and find the nearest one
var min_dist = 999999 // arbitrary large number
var nearest_line_oid = null
var geo = Geometry($feature)
for(var line in line_fs) {
var line_geo = Geometry(line)
var dist = Distance(geo, line_geo)
if(dist < min_dist) {
min_dist = dist
nearest_line_oid = line.ObjectID
}
}
// Return the ObjectID of the nearest line feature
return nearest_line_oid
This will only work when you insert or update point features. If you insert or edit a line feature that would place it nearer to a point than another line, you have to update the point!
Or, you could add an Attribute Rule to the line featureclass that updates the point featureclass when you edit a line. Let me know if you need help with that.
@HusseinNasser2: This should probably be moved to the Attribute Rules group.
Thanks @JohannesLindner
code working as expected ✌️
Hello - I recently stumbled upon this post and I am looking at doing what you say 'can be done' which is to have an attribute for the line class that will update the point class with the closest feature. Do you happen to have an example of that?
Sure. It gets a little more complicated, that's why I didn't post it in the original answer.
Setup:
// Calculation Attribute Rule on TestPoints
// Triggers: Insert, Update
// Field: IntegerField
// returns a field value of the closest TestLines feature
// This is the code from the original answer, slighlty modified to return the whole feature, not just a field value
// also, I made it into a function to keep the code below cleaner
function get_closest_feature(test_feature, compare_feature_set) {
var min_distance = 9999999
var closest_feature = null
for(var f in compare_feature_set) {
var d = Distance(test_feature, f)
if(d < min_distance) {
min_distance = d
closest_feature = f
}
}
return closest_feature
}
// only run if this is a new feature or the geometry got updated
if($editcontext.editType == "INSERT" || !Equals(Geometry($feature), Geometry($originalfeature))) {
// load TestLines
var lines = FeatureSetByName($datastore, "TestLines", ["IntegerField"], false)
// find the closest TestLines feature
var close_lines = Intersects(lines, Buffer($feature, 100, "Meters"))
var closest_line = get_closest_feature($feature, close_lines)
// return its field value
if(closest_line != null) {
return closest_line.IntegerField
}
// there is no TestLines feature within the specified buffer distance
return null
}
// Calculation Attribute Rule on TestLines
// Triggers: Insert, Update, Delete
// Field: empty
// Exclude from application evaluation
// Edits the IntegerField values of nearby TestPoints features
function get_closest_feature(test_feature, compare_feature_set) {
var min_distance = 9999999
var closest_feature = null
for(var f in compare_feature_set) {
var d = Distance(test_feature, f)
if(d < min_distance) {
min_distance = d
closest_feature = f
}
}
return closest_feature
}
// load features
var points = FeatureSetByName($datastore, "TestPoints", ["GlobalID"], false) // we edit the point using their GlobalID as identifier
var lines = FeatureSetByName($datastore, "TestLines", ["IntegerField"], false)
// if we're deleting this line, we have to remove it from the lines feature set,
// because at this point, it's still there
if($editcontext.editType == "DELETE") {
var gid = $feature.GlobalID
lines = Filter(lines, "GlobalID <> @gid")
}
// create array with updates to TestPoints
var update_array = []
// create array to keep track of already visited points
// we have to recalcluate all points that were nearby the old geometry and all points that are nearby the new geometry
// some/many points will fall in both categories, but we don't need to check them twice.
// if there are many points, this will save some time.
var visited = []
// we need to check all points close to the old and the new geometries.
var geometries = [Geometry($feature), Geometry($originalfeature)]
for(var g in geometries) {
// when we insert a new line, Geometry($originalfeature) is null and fails the code below.
// in that case, just continue with the next geometry.
if(geometries[g] == null) { continue }
// find all points nearby the geometry, remove the points we already checked
var points_in_buffer = Intersects(points, Buffer(geometries[g], 100, "Meters"))
if(Count(visited) > 0) {
points_in_buffer = Filter(points_in_buffer, "GlobalID NOT IN @visited")
}
// recalculate each point
for(var p in points_in_buffer) {
var new_value = null // default is null (in case there is no nearby line feature)
var close_lines = Intersects(lines, Buffer(p, 100, "Meters"))
var closest_line = get_closest_feature(p, close_lines)
if(closest_line != null) {
new_value = closest_line.IntegerField
}
// we identify the point feature that we want to update by its GlobalID and specify its new attributes in a dictionary.
Push(update_array, {"globalID": p.GlobalID, "attributes": {"IntegerField": new_value}})
// hey, we already checked this point, no need to do it again for the next geometry!
Push(visited, p.GlobalID)
}
}
// instead of returning a singular field value, we can also return a dictionary.
// this dictionary can contain edits that we want to make to other feature classes.
// documentation for the dictionary keywords can be found here:
// https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/attribute-rule-dictionary-keywords.htm
return {
"edit": [
{
"className": "TestPoints", // we want to edit TestPoints
"updates": update_array // we want to apply all the new IntegerField values that we calculated above
}
]
}
Inserting TestPoints
Moving TestPoints
Inserting TestLines
Moving TestLines
Deleting TestLines
As I said in the original answer, be mindful of the buffer distance you chose. The point in the north east can't find line 1, because my buffer distance is too small.
This is excellent - thank you so much!
are you saying that the perpendicular distance to the line isn't the shortest distance?
Actually not, i need to get the shortest distance, which is provided by @JohannesLindner .
so moved