Auto Snapping with Attribute rules during Editing

10-01-2021 08:04 AM
Esri Contributor
2 2 1,311

AmirBar-Maor  from the parcel fabrics team wrote a cool attribute rule that allows you to auto-snap to the feature near by after creating or updating a feature. 

In 2.7, we added the ability to assign an attribute rule to the shape field, which will allow the Arcade expression to return a geometry to update the $feature geometry. This opens up all sorts of workflows, including the one Amir built, which is the snapping. 

I thought it the attribute rules community will enjoy this


MVP Frequent Contributor

Interesting. One problem I see is that he snaps to the first point, not to the closest. But doing it this way keeps the rule shorter.

I recently did the opposite: Snapping a point shape to the closest polyline.


function closest_feature(test_feature, compare_feature_set) {
  // returns the feature of compare_feature_set that is closest to test_feature
  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

function project_orthogonally(point_geometry, line_geometry) {
  // returns a projection of the point_geometry onto the line_geometry
  // only start and end point of the line_geometry are considered!
  var p = point_geometry
  var r0 = line_geometry.paths[0][0]
  var r1 = line_geometry.paths[0][-1]
  var ux = r1.x - r0.x
  var uy = r1.y - r0.y
  var lambda = ((p.x-r0.x)*ux + (p.y-r0.y)*uy) / (ux*ux + uy*uy)
  var new_p = Point({"x": r0.x + lambda * ux, "y": r0.y + lambda * uy, "spatialReference": p.spatialReference})
  // if new_p is on the line defined by r0 and r1 but not on the actual line_geometry, snap it to the closest end point
  if(Disjoint(new_p, line_geometry)) {
    new_p = IIF(Distance(r0, p) < Distance(r1, p), r0, r1)
  return new_p

function snap_point_to_polyline(point_geometry, polyline_geometry) {
  // returns a point that is snapped to the polyline_geometry
  // create feature set of the polyline's segments
  var sr = point_geometry.spatialReference
  var vertices = polyline_geometry.paths[0]
  var fs_segments = {"fields": [], "spatialReference": sr, "geometryType": "esriGeometryPolyline", "features": []}
  for(var s=0; s<Count(vertices)-1; s++) {
    var p0 = vertices[s]
    var p1 = vertices[s+1]
    Push(fs_segments.features, {"geometry": {"paths": [[ [p0.x, p0.y], [p1.x, p1.y] ]], "spatialReference": sr}})
  fs_segments = FeatureSet(Text(fs_segments))
  // project point_geometry onto the closest segment
  var closest_segment_geo = Geometry(closest_feature(point_geometry, fs_segments))
  return project_orthogonally(point_geometry, closest_segment_geo ) 

var fs_lines = FeatureSetByName($datastore, "Lines")
// optionally, buffer
fs_lines = Intersects(fs_lines, Buffer($feature, 100))

var closest_line_geometry = Geometry(closest_feature($feature, fs_lines))
return snap_point_to_polyline(Geometry($feature), closest_line_geometry)



Notable Contributor

Regarding this blurb:

In 2.7, we added the ability to assign an attribute rule to the shape field, which will allow the Arcade expression to return a geometry to update the $feature geometry.

Is there any chance that's a typo? If I understand correctly, we can edit the geometry in version 2.6.


Screenshots of Pro 2.6.8:




About the Author
Software Engineer, Author of several GIS books and Software Engineering Content Creator on YouTube and podcast