Select to view content in your preferred language

Creating an attribute rule to populate two fields in a line feature class

960
7
Jump to solution
04-03-2023 10:49 AM
Labels (2)
JustinBernard
New Contributor II

Hello, 

I wonder if anyone can help me out with some arcade/attribute rules, or point me to some resources? Or if this is even possible?

I have a line feature class that contains pipes, and I have 3 point feature classes. In the line feature class, I have two fields - Form Node and To Node. These fields hold the NodeID for whatever node the linework is snapped to. 


When i digitize a line, I would manually update these fields with the NodeID from the Node layer the line is snapped to.

I want to automate the process so when I snap the line to the first node, The "From Field" will be populated, and when I snap to the second node, the "To Node" field will be populated. Is this possible using Arcade and Attribute rules? 

Thanks for the help and any code samples provided. 

Cheers,

Justin


Tags (2)
0 Kudos
1 Solution

Accepted Solutions
JohannesLindner
MVP Frequent Contributor

This is easily achievable with Attribute Rules.

If your point feature classes are in the same database as the line feature class, and the point id field has the same name in all point fcs, you can use this expression.

Be sure to change the names of the point fcs in lines 14-16, the name of the point id field in lines 34 and 39, and the from and to field names in line 44.

// Calculation Attribute Rule on the line fc
// field: leave empty!
// triggers: insert, update

// if we're updateing and the geometry didn't change, abort
if($editcontext.editType == "UPDATE") {
    var cur_geom = Geometry($feature)
    var prev_geom = Geometry($originalfeature)
    if(Equals(cur_geom, prev_geom)) { return }
}

// load the point fcs
var point_fcs = [
    FeaturesetByName($datastore, "PointFC1"),
    FeaturesetByName($datastore, "PointFC2"),
    FeaturesetByName($datastore, "PointFC3"),
]


// get the line's start and end point
var start_point = Geometry($feature).paths[0][0]
var end_point = Geometry($feature).paths[-1][-1]

// find the ids of the points intersecting the line's start and end point
var start_id = null
var end_id = null

for(var p in point_fcs) {
    // get the points intersecting the line
    var intersecting_points = Intersects($feature, point_fcs[p])
    // if start_id is still null, try to get the id of the first point intersecting the line's start point
    if(start_id == null) {
        var candidate = First(Intersects(start_point, intersecting_points))
        if(candidate != null) { start_id = candidate.PointID }
    }
    // if end_id is still null, try to get the id of the first point intersecting the line's end point
    if(end_id == null) {
        var candidate = First(Intersects(end_point, intersecting_points))
        if(candidate != null) { end_id = candidate.PointID }
    }
}

return {
    result: {attributes: {FromNode: start_id, ToNode: end_id}}
}

Have a great day!
Johannes

View solution in original post

7 Replies
JohannesLindner
MVP Frequent Contributor

This is easily achievable with Attribute Rules.

If your point feature classes are in the same database as the line feature class, and the point id field has the same name in all point fcs, you can use this expression.

Be sure to change the names of the point fcs in lines 14-16, the name of the point id field in lines 34 and 39, and the from and to field names in line 44.

// Calculation Attribute Rule on the line fc
// field: leave empty!
// triggers: insert, update

// if we're updateing and the geometry didn't change, abort
if($editcontext.editType == "UPDATE") {
    var cur_geom = Geometry($feature)
    var prev_geom = Geometry($originalfeature)
    if(Equals(cur_geom, prev_geom)) { return }
}

// load the point fcs
var point_fcs = [
    FeaturesetByName($datastore, "PointFC1"),
    FeaturesetByName($datastore, "PointFC2"),
    FeaturesetByName($datastore, "PointFC3"),
]


// get the line's start and end point
var start_point = Geometry($feature).paths[0][0]
var end_point = Geometry($feature).paths[-1][-1]

// find the ids of the points intersecting the line's start and end point
var start_id = null
var end_id = null

for(var p in point_fcs) {
    // get the points intersecting the line
    var intersecting_points = Intersects($feature, point_fcs[p])
    // if start_id is still null, try to get the id of the first point intersecting the line's start point
    if(start_id == null) {
        var candidate = First(Intersects(start_point, intersecting_points))
        if(candidate != null) { start_id = candidate.PointID }
    }
    // if end_id is still null, try to get the id of the first point intersecting the line's end point
    if(end_id == null) {
        var candidate = First(Intersects(end_point, intersecting_points))
        if(candidate != null) { end_id = candidate.PointID }
    }
}

return {
    result: {attributes: {FromNode: start_id, ToNode: end_id}}
}

Have a great day!
Johannes
JustinBernard
New Contributor II

Ahh! 

Thanks so much Johannes!

I'll try out the code and let you kmow how it went. 

Cheers,

Justin

0 Kudos
JustinBernard
New Contributor II

The code worked beautifully. I  wish i could give you a coffee or something! 

Thank you so much! 🙂

0 Kudos
HungGi
by
New Contributor III

This is great! I was able to implement this into my workflow!

Thank you @JohannesLindner for the code. It works when there are existing points to snap to. What if a segment is drawn first and the point is added later on, how do I go about updating the "From Node" or "To Node" of that line? I'm assuming that an attribute rule needs to be added to the point feature class. I was able get the segment, but I'm drawing a blank as to how I need to figure out whether the point is at the start or end of the line segment and then populate the respective "From Node" or "To Node" in the line. Am I at least on the right path?

// Calculation Attribute Rule on the point fc
// field: leave empty!
// triggers: insert, update

// if we're updating and the geometry didn't change, abort
if($editcontext.editType == "UPDATE") {
  var cur_geom = Geometry($feature)
  var prev_geom = Geometry($originalfeature)
  if (Equals(cur_geom, prev_geom)) {
    return
  }
}
// load the line fcs - SEG_ID, NODE_FROM, NODE_TO are only required
var line_fcs = [FeaturesetByName($datastore, "LineFC", ["SEG_ID","NODE_FROM","NODE_TO"], false)]

// get current point
var point_ID = Geometry($feature)

//find the ids of the segment intersecting the point
var segment_id = null
var node_from_id = null
var node_to_id = null

for (var p in line_fcs) {
  // get the lines intersecting the point
  var intersecting_lines = Intersects($feature, line_fcs[p])
  // if segment_id is null, try to get the id of the intersecting line
  if (segment_id == null) {
    var candidate = First(Intersects(point_ID, intersecting_lines))
    if (candidate !=  null) {
      segment_id = candidate.SEG_ID
      node_from_id = candidate.NODE_FROM
      node_to_id = candidate.NODE_TO
    }
  }
}

 

Cheers!
Hung G.
0 Kudos
JohannesLindner
MVP Frequent Contributor

I'm assuming that an attribute rule needs to be added to the point feature class

Correct.

 

The easiest way is to remove lines 5-10 from the rule on the line fc and add this rule to the point fc:

// Calculation Attribute Rule on the point fc
// field: leave empty
// triggers: insert, update
// exclude from application evaluation

var lines = FeaturesetByName($datastore, "TestLines")
var i_lines = Intersects($feature, lines)
var updates = []
for(var i_line in i_lines) {
    Push(updates, {globalID: i_line.GlobalID})
}
return {edit: [{className: "TestLines", updates: updates}]}

 

This issues an update request to all intersecting line features, so their rule will be executed and they find the intersecting points on their own.

This will slow things down in bulk updates if you have big point and/or line fcs. The point rule will do the intersect, then each intersected line will do the intersect again. Also, the line rule will now calculate the fields in each update, not just the geometry updates.

 

A better way would be to leave the line fc rule as it is and check for intersection with start or end point in the point fc. You can get the first element of an array with the index 0, the last element with the index -1. A line geometry is an array (multipart) of arrays (parts) of points. See lines 10 & 11.

// Calculation Attribute Rule on the point fc
// field: leave empty
// triggers: insert, update
// exclude from application evaluation

var lines = FeaturesetByName($datastore, "TestLines")
var i_lines = Intersects($feature, lines)
var updates = []
for(var i_line in i_lines) {
    var start_point = Geometry(i_line).paths[0][0]
    var end_point = Geometry(i_line).paths[-1][-1]
    if(Intersects($feature, start_point)) {
        Push(updates, {globalID: i_line.GlobalID, attributes: {IntegerField1: $feature.OBJECTID}})
    }
    if(Intersects($feature, end_point)) {
        Push(updates, {globalID: i_line.GlobalID, attributes: {IntegerField2: $feature.OBJECTID}})
    }
}
return {edit: [{className: "TestLines", updates: updates}]}

 

 

Be sure to change to your fields and table names! I used IntegerField1, IntegerField2, and OBJECTID.


Have a great day!
Johannes
HungGi
by
New Contributor III

Thank you so much @JohannesLindner!!

I ended up going with your second approach as I felt it was exactly what I was going for. The first approach like you said can slow things down if there are bulk updates going on.

Looking back at my code, i wasn't even close.

Cheers!

Hung

Cheers!
Hung G.
0 Kudos
Adam_Bourque
Occasional Contributor

Is this code possible to do without attribute rules / offline local geodatabase? I am wanting to do the same thing for existing web layer in AGOL. Just wanting to ensure if there is a way to use arcade expression without having to export data, calculate offline and republish.

0 Kudos