Arcade expression for getting shortest distance between line feature to point feature

2844
8
Jump to solution
02-09-2021 10:04 PM
sayamshivakumar
New Contributor II

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 

0 Kudos
2 Solutions

Accepted Solutions
JohannesLindner
MVP Frequent Contributor
// 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.


Have a great day!
Johannes

View solution in original post

cwlee27
New Contributor II

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?  

View solution in original post

8 Replies
JohannesLindner
MVP Frequent Contributor
// 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.


Have a great day!
Johannes
sayamshivakumar
New Contributor II

Thanks @JohannesLindner  
code working as expected ✌️ 

0 Kudos
cwlee27
New Contributor II

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?  

JohannesLindner
MVP Frequent Contributor

Sure. It gets a little more complicated, that's why I didn't post it in the original answer.

 

Setup:

  • Point feature class TestPoints
  • Line feature class TestLines
  • both have a field "IntegerField"
  • TestPoints.IntegerField has to be the same value as the closest feature of TestLines
  • This has to work when inserting points, updating points, inserting lines, updating lines, and deleting lines

 

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

JohannesLindner_1-1647595674741.png

Moving TestPoints

JohannesLindner_2-1647595697842.png

Inserting TestLines

JohannesLindner_3-1647595756738.png

Moving TestLines

JohannesLindner_4-1647595794841.png

Deleting TestLines

JohannesLindner_5-1647595814511.png

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.


Have a great day!
Johannes
0 Kudos
cwlee27
New Contributor II

This is excellent - thank you so much!

0 Kudos
DanPatterson
MVP Esteemed Contributor

are you saying that the perpendicular distance to the line isn't the shortest distance?


... sort of retired...
0 Kudos
sayamshivakumar
New Contributor II

Actually not, i need to get the shortest distance, which is provided by @JohannesLindner .

0 Kudos
DanPatterson
MVP Esteemed Contributor

so moved


... sort of retired...
0 Kudos