Select to view content in your preferred language

Calculate From and To Endpoint IDs (from one of several point feature classes)

1914
17
Jump to solution
03-01-2024 12:46 PM
NathanGEOregon
Occasional Contributor

We are still using the Geometric Network in ArcMap, along with Attribute Assistant (AA). We are in the process of exploring a transition to the Trace Network and adopting Attribute Rules in Pro.

We have been using a couple of AA rules to populate a FromID and ToID field with the IDs of the endpoints of a line segment (Sewer Mains) from several possible point feature classes (manholes, nodes, etc).

The From Junction Field  (and similar To Junction Field) in AA method made this pretty straightforward, as it would select whatever point feature happened to be at the start or endpoint and grab the associated ID, regardless of what feature class the point happened to be from.

Is there a similar way to use junctions in Arcade to supply From and To IDs to a line from several possible point feature classes? It seems if you're only working with a single point feature class, it would be pretty straightforward (https://www.youtube.com/watch?v=ueckrlhto0Q) but for multiple point feature classes, it seems you'd need to create a feature variable for each point feature class, and parse through them like "If there is a manhole point at the From location, grab the ID, Otherwise check if there is a node at the From location and grab the ID" etc.

0 Kudos
2 Solutions

Accepted Solutions
RhettZufelt
MVP Notable Contributor

To make it a bit easier to get a grasp of, I have modified the code to use a results dictionary without the interim dict.  This should make it easier to follow:

 

 

function get_fs(cls_name){
  return Decode(cls_name,
      "sdInlet",FeatureSetByName($datastore, "sdInlet", fields, true),
      "sdOutlet",FeatureSetByName($datastore, "sdOutlet", fields, true),
      "sdCleanOut",FeatureSetByName($datastore, "sdCleanOut", fields, true),
      "sdManhole",FeatureSetByName($datastore, "sdManhole", fields, true),
      "sdDischargePoint",FeatureSetByName($datastore, "sdDischargePoint", fields, true),
      "sdFitting",FeatureSetByName($datastore, "sdFitting", fields, true),
      "sdNetworkStructure",FeatureSetByName($datastore, "sdNetworkStructure", fields, true),
      null)
}
function IsEmptyButBetter(data) {
    if (IsEmpty(data)) return true;
    for (var x in data) return false;
    return true;
}

var fields = ['FacilityID']
var fclist = ['sdManhole','sdInlet','sdOutlet','sdCleanOut', 'sdFitting', 'sdDischargePoint', 'sdNetworkStructure'];
var line_shape = Geometry($feature)
var spRef = line_shape['spatialReference']

// Get the origin and end points of the line
var orig_x = line_shape['paths'][0][0].x
var orig_y = line_shape['paths'][0][0].y
var orig_point = Point({x: orig_x, y: orig_y, spatialReference: spRef})

var end_x = line_shape['paths'][-1][-1].x
var end_y = line_shape['paths'][-1][-1].y
var end_point = Point({x: end_x, y: end_y, spatialReference: spRef})

var umh = 'NA'
var dmh = 'NA'
for (var p in fclist){
    var point_fs = get_fs(fclist[p]);

    //find point intersecting origin
    var origIntx = first(Intersects(orig_point, point_fs));

    //find point intersecting end
    var endIntx = first(Intersects(end_point, point_fs));

    if (!IsEmptyButBetter(origIntx)) {
        var umh = origIntx['FacilityID']
      }


    if (!IsEmptyButBetter(endIntx)) {
        var dmh = endIntx['FacilityID']
      }


}

return {
    "result": {
        "attributes": {
            "UpMH": umh, 
            "DownMH": dmh,
            "FACILITYID": umh + "-" + dmh
        }
    }

}

 

 

UpMH, DownMH, and FACILITYID are the column names in the main line FC.

R_

View solution in original post

NathanGEOregon
Occasional Contributor

@RhettZufelt this is fantastic, works like a charm! I was getting a "null geometry" error when validating in the Expression Builder. I simplified the the lines related grabbing the origin and endpoints of the line, which seemed to resolve the error.

var orig_point = line_shape.paths[0][0]

var end_point = line_shape.paths[-1][-1]

 

View solution in original post

0 Kudos
17 Replies
NathanGEOregon
Occasional Contributor

Thanks-

I did see that.

The Calculate Attributes by Junction points to a single feature class (Manholes) as possible Main endpoints. So it appears that if the endpoint could be one of many point feature classes (Manhole, Node, Valve, etc) then you would need to call in each point feature class and loop through them. I was trying to avoid this, as the Arcade code would start to get pretty bloated.

0 Kudos
avonmoos
Regular Contributor

Did you end up figuring this out for many points? Example code? Thanks!

0 Kudos
avonmoos
Regular Contributor

@RhettZufelt any ideas?

0 Kudos
RhettZufelt
MVP Notable Contributor

Have not really looked into this yet as the Trace network is so limited, really has no use case for us at the moment. (at least as long as ArcMap is still going)

I have a post here about doing something similar with Python that could be converted pretty easy with some of the AA rules that @MikeMillerGIS posted above.

Would have to experiment with the points coming from different dataset and see just how much of a performance hit that causes.

I originally set this up for offline use, so my nightly script just combines the manholes and cleanouts (MH's, CO's, Inlets, Outlets, etc. for the Storm layers) into a single point featureclass with the FacilityID and record number.  Then, I just have my script check the intersection with this layer at the upstream and downstream ends of the line.

Since all I need is the attributes from the points that intersect, this has been working great.

Figure when I move to Pro for this, will just modify this Arcade code to intersect against this same 'combined' featureclass as it keeps things simple.  Then I may test performance hits comparing this to actually intersecting the start/end points with multiple featureclasses.

R_

avonmoos
Regular Contributor

Thanks for the info Rhett!

0 Kudos
RhettZufelt
MVP Notable Contributor

@avonmoos 

With the help of @MikeMillerGIS I was able to come up with something along these lines.

Set up for my Storm data, the 'endpoints' of a gravity main can be a Manhole, Inlet, Outlet, Cleanout, Discharge Point, Network Structure, or Fitting all of which are in their own featureclass.

Though, have not tested it extensively, should be a good starting point as it is working as expected.

 

function get_fs(cls_name){
  return Decode(cls_name,
      "sdInlet",FeatureSetByName($datastore, "sdInlet", fields, true),
      "sdOutlet",FeatureSetByName($datastore, "sdOutlet", fields, true),
      "sdCleanOut",FeatureSetByName($datastore, "sdCleanOut", fields, true),
      "sdManhole",FeatureSetByName($datastore, "sdManhole", fields, true),
      "sdDischargePoint",FeatureSetByName($datastore, "sdDischargePoint", fields, true),
      "sdFitting",FeatureSetByName($datastore, "sdFitting", fields, true),
      "sdNetworkStructure",FeatureSetByName($datastore, "sdNetworkStructure", fields, true),
      null)
}
function IsEmptyButBetter(data) {
    if (IsEmpty(data)) return true;
    for (var x in data) return false;
    return true;
}

var fields = ['FacilityID']
var fclist = ['sdManhole','sdInlet','sdOutlet','sdCleanOut', 'sdFitting', 'sdDischargePoint', 'sdNetworkStructure'];
var line_shape = Geometry($feature)
var spRef = line_shape['spatialReference']

// Get the origin and end points of the line
var orig_x = line_shape['paths'][0][0].x
var orig_y = line_shape['paths'][0][0].y
var orig_point = Point({x: orig_x, y: orig_y, spatialReference: spRef})

var end_x = line_shape['paths'][-1][-1].x
var end_y = line_shape['paths'][-1][-1].y
var end_point = Point({x: end_x, y: end_y, spatialReference: spRef})
var attributes = {}

var umh = 'NA'
var dmh = 'NA'
for (var p in fclist){
    var point_fs = get_fs(fclist[p]);

    //find point intersecting origin
    var origIntx = first(Intersects(orig_point, point_fs));

    //find point intersecting end
    var endIntx = first(Intersects(end_point, point_fs));

    if (!IsEmptyButBetter(origIntx)) {
        attributes['UpMH'] = origIntx['FacilityID']
        var umh = attributes['UpMH']
      }


    if (!IsEmptyButBetter(endIntx)) {
        attributes['DownMH'] = endIntx['FacilityID']
        var dmh = attributes['DownMH']
      }

    //add FacilityID to attribute results
    if (!IsEmptyButBetter(attributes)) {
          attributes['FacilityID'] = umh + "-" + dmh
        }
    
    var result = {}
    if (!IsEmptyButBetter(attributes)) {
        result['result'] = {
            'attributes': attributes
        }
    }
}
return result

 

This will find the FacilityID from the upstream and downstream points if exist, populate the UpMH and DownMH fields in the gravity main feature class as well as update the gravity main FacilityID to UpMH-DownMH regardless of which point FC it is in.

R_

NathanGEOregon
Occasional Contributor

Thanks for doing the heavy lifting! This looks like what I had in mind!

0 Kudos
MikeMillerGIS
Esri Frequent Contributor

We are still working on these tools, but take a look at this toolbox. 

https://github.com/Esri/arcade-expressions/blob/master/attribute_assistant/AttributeAssistant.atbx

We are working on GP tools to create the arcade for you.  Lots to do still(hence not really sharing them yet), but they are functioning.  

https://github.com/Esri/arcade-expressions/blob/master/attribute_assistant/readme.md

NOTE: The output is a csv that you need to import, so it does not apply the AR 

0 Kudos