Esri / arcade-expressions

ArcGIS Arcade expression templates for all supported profiles in the ArcGIS platform.
Apache License 2.0
278 stars 99 forks source link

update variable section #37

Closed ted-howard closed 3 years ago

ted-howard commented 3 years ago
lingal1968 commented 6 months ago

I just joined GitHub and this may not be the right place. I have been working with the get address from centerline code and it is working with my data. However, I would like to pull additional fields from the centerline to populate fields in my address point layer. Some of those fields would be contingent on the side of the road the point is on (ESN, County, etc.). Do you know where I can find some sample code for that?

ted-howard commented 6 months ago

@lingal1968 pulling additional fields from the centerline is simple. Request the fields you want here: https://github.com/Esri/arcade-expressions/blob/186c6efb6ccddbd787dd0dbb4eab4951f2679c41/attribute_rule_calculation/GetAddressFromCenterline.js#L17

And then include them in the payload here: https://github.com/Esri/arcade-expressions/blob/186c6efb6ccddbd787dd0dbb4eab4951f2679c41/attribute_rule_calculation/GetAddressFromCenterline.js#L214-L217

Determining county etc is more complex. I would point you to the Address Data Management solution which includes a lot of complex attribute rules for this sort of thing. Some of the examples are found in this repo here

lingal1968 commented 6 months ago

I was overthinking it. Thank you.

As for the county, esn, etc. I can’t figure out how to pull the data from the road centerline based on the side_of_line value of either right or left. The fields are in the centerline layer, (ex: CountyLeft and CountyRight) but only one would copy to the structure based on which side of the road it's on. I just don't know how to filter based on that calculated value.

I've looked at the Address Data Management and I can't find anything that would help.

I can't thank you enough for the guidance. Have a good evening.

Get Outlook for iOShttps://aka.ms/o0ukef


From: Ted Howard @.> Sent: Tuesday, April 30, 2024 7:27:20 PM To: Esri/arcade-expressions @.> Cc: Linda Gallion @.>; Mention @.> Subject: Re: [Esri/arcade-expressions] update variable section (#37)

@lingal1968https://github.com/lingal1968 pulling additional fields from the centerline is simple. Request the fields you want here: https://github.com/Esri/arcade-expressions/blob/186c6efb6ccddbd787dd0dbb4eab4951f2679c41/attribute_rule_calculation/GetAddressFromCenterline.js#L17

And then include them in the payload here: https://github.com/Esri/arcade-expressions/blob/186c6efb6ccddbd787dd0dbb4eab4951f2679c41/attribute_rule_calculation/GetAddressFromCenterline.js#L214-L217

Determining county etc is more complex. I would point you to the Address Data Managementhttps://doc.arcgis.com/en/arcgis-solutions/latest/reference/introduction-to-address-data-management.htm solution which includes a lot of complex attribute rules for this sort of thing. Some of the examples are found in this repo herehttps://github.com/Esri/arcade-expressions/tree/186c6efb6ccddbd787dd0dbb4eab4951f2679c41/Industry/AddressManagement

— Reply to this email directly, view it on GitHubhttps://github.com/Esri/arcade-expressions/pull/37#issuecomment-2087765085, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BIGH3WF75GBDM7PM6IBQY73ZAAZGRAVCNFSM42Z4QGPKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBYG43TMNJQHA2Q. You are receiving this because you were mentioned.Message ID: @.***>

ted-howard commented 6 months ago

if those fields are in centerline then all you need is to inspect the return from closest_point_info https://github.com/Esri/arcade-expressions/blob/186c6efb6ccddbd787dd0dbb4eab4951f2679c41/attribute_rule_calculation/GetAddressFromCenterline.js#L195

data['lineSide'] will tell you left or right

lingal1968 commented 6 months ago

I’m sorry. I don’t understand. Could you provide a sample?

Get Outlook for iOShttps://aka.ms/o0ukef


From: Ted Howard @.> Sent: Tuesday, April 30, 2024 7:53:09 PM To: Esri/arcade-expressions @.> Cc: Linda Gallion @.>; Mention @.> Subject: Re: [Esri/arcade-expressions] update variable section (#37)

if those fields are in centerline then all you need is to inspect the return from closest_point_info https://github.com/Esri/arcade-expressions/blob/186c6efb6ccddbd787dd0dbb4eab4951f2679c41/attribute_rule_calculation/GetAddressFromCenterline.js#L195

data['lineSide'] will tell you left or right

— Reply to this email directly, view it on GitHubhttps://github.com/Esri/arcade-expressions/pull/37#issuecomment-2087785944, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BIGH3WANT3SIYA4OVMVQ3J3ZAA4HLAVCNFSM42Z4QGPKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBYG43TQNJZGQ2A. You are receiving this because you were mentioned.Message ID: @.***>

ted-howard commented 6 months ago

Assuming request CountyLeft and CountyRight fields for the centerline


// return result to update attributes of $feature
return {
    "result": {
        "attributes":
            Dictionary(
                percent_field, percent_along,
                offdir_field, data["lineSide"],
                address_field, address_num,
                fullname_field, closest_line[fullname],
                county, Iif(data["lineSide"] == "left", CountyLeft, CountyRight)
            )
    }
}
lingal1968 commented 6 months ago

Perfect. Thank you.

Get Outlook for iOShttps://aka.ms/o0ukef


From: Ted Howard @.> Sent: Tuesday, April 30, 2024 8:05:32 PM To: Esri/arcade-expressions @.> Cc: Linda Gallion @.>; Mention @.> Subject: Re: [Esri/arcade-expressions] update variable section (#37)

Assuming request CountyLeft and CountyRight fields for the centerline

// return result to update attributes of $feature return { "result": { "attributes": Dictionary( percent_field, percent_along, offdir_field, data["lineSide"], address_field, address_num, fullname_field, closest_line[fullname] county, Iif(data["lineSide"] == "left", CountyLeft, CountyRight) ) } }

— Reply to this email directly, view it on GitHubhttps://github.com/Esri/arcade-expressions/pull/37#issuecomment-2087795693, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BIGH3WHSWNUGFA6QGLUFG2LZAA5VZAVCNFSM42Z4QGPKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBYG43TSNJWHEZQ. You are receiving this because you were mentioned.Message ID: @.***>

lingal1968 commented 6 months ago

Good afternoon. It’s recognizing the fields as text and returning CountyLeft instead of the value of the field. I’ve tried a few ways but I’m not having any luck. What should I change to make it recognize this as a field? – Also, I’m not having any luck adding the additional fields in the results section – it gives me a “wrong number of arguments”. I’ve attached the whole code. The msagpredir_field is the additional field I am trying to add (just the first of several I’ll need to include). Any guidance is greatly appreciated!

// This rule will calculate an address number for a point based on nearest road centerline

// Centerline attribute field names var fromleft = "fromleft" var toleft = "toleft" var fromright = "fromright" var toright = "toright" var fullname = "msagcomple" var msagpredir = "msagpredir" var countyleft = "countyleft" var countyrt = "countyrt" var commleft = "commleft" var commright = "commright" var esnleft = "esnleft" var esnright = "esnright" var zipleft = "zipleft" var zipright = "zipright"

// Site Address Point fields to calculate var percent_field = "PERCENTALONG" // percent along centerline where site address lies var offdir_field = "OFFDIR" // side of centerline where site address lies var fullname_field = "msagcomple" // name of closest centerline var address_field = "address" // address number calculated based on where site address lies var county_field = "county" var msagcommun_field = "msagcommun" var esn_field = "esn" var zipcode_field = "zipcode" var msagpredir_field = "msagpredir"

// The Road Centerline feature set var line_class = FeatureSetByName($datastore, "regional_branch.sde.WCTCOGRoadCenterlines", [fromleft, toleft, fromright, toright, fullname, msagpredir, countyleft, countyrt, commleft, commright, esnleft, esnright, zipleft, zipright], true);

// Functions

function find_closest_line() { // Find closest line segment to $feature. Limit search to specific radius. var candidates = Intersects(line_class, Buffer($feature, 1000, "feet")); //var candidates = line_class;

var shortest = [1e10, null];
for (var line in candidates) {
    var d = Distance($feature, line)
    if (d < shortest[0]) shortest = [d, line]
}
return shortest[-1]

}

function closest_point_info(point_feature, line_feature) { /* finds the closest point on line_feature from point_feature

    Args:
        point_feature: Point Geometry
        line_feature: Line Geometry

    Returns: dictionary
        {distance: number,    // distance from point_feature to closest point
         coordinates: array,  // the coordinate pair of the closest point
         isVertex: bool,      // if the closest point is a vertex of line_feature
         lineSide: text}      // side of the line that point_feature is on based

*/

var point_feature = Geometry(point_feature);
var line_feature = Geometry(line_feature);
var vertices = line_feature["paths"]
var x = point_feature["x"];
var y = point_feature["y"];

// Loop through each part of the geometry and each segment, tracking the shortest distance
var shortest = [1e10];
for (var i in vertices) {
    var part = vertices[i];
    var previous = part[0];
    for (var j = 1; j < Count(part); j++) {
        var current = part[j];
        var result = pDistance(x, y, previous["x"], previous["y"], current["x"], current["y"]);
        if (result[0] < shortest[0]) shortest = result
        previous = current;
    }

}

// Couldn't find anything
if (Count(shortest) == 1) return null

return {"distance": shortest[0],
        "coordinates": shortest[1],
        "isVertex": shortest[2],
        "lineSide": shortest[3]}

}

function pDistance(x, y, x1, y1, x2, y2) { // adopted from https://stackoverflow.com/a/6853926 var A = x - x1; var B = y - y1; var C = x2 - x1; var D = y2 - y1;

var dot = A C + B D; var len_sq = C C + D D; var param = -1; if (len_sq != 0) //in case of 0 length line param = dot / len_sq;

var xx, yy; var is_vertex = true; if (param < 0) { xx = x1; yy = y1; } else if (param > 1) { xx = x2; yy = y2; } else { is_vertex = false; xx = x1 + param C; yy = y1 + param D; }

var dx = x - xx; var dy = y - yy; return [Sqrt(dx dx + dy dy), [xx, yy], is_vertex, side_of_line(x,y,x1,y1,x2,y2)]; }

function side_of_line(x, y, x1, y1, x2, y2) { // get side of line segment that a point (x, y) is on based on the direction of segment [[x1, y1], [x2, y2]] // adopted from https://math.stackexchange.com/a/274728 var d = (x - x1) (y2 - y1) - (y - y1) (x2 - x1) if (d < 0) { return 'left' } else if (d > 0) { return 'right' } else { return null } }

function intersect_distance_along(intersect_geometry, line, unit){ // Loop through the segments of the line. Handle multipart geometries. var distance_along_line = 0; for (var part in Geometry(line).paths) { var segment = Geometry(line).paths[part];

    // Loop through the points in the segment
    for (var i in segment) {
        if (i == 0) continue;

        // Construct a 2-point line segment from the current and previous point
        var first_point = segment[i-1];
        var second_point = segment[i]
        var two_point_line = Polyline({'paths': [[[first_point.x, first_point.y], [second_point.x, second_point.y]]], 'spatialReference' : first_point.spatialReference});

        // Test if the  point intersects the 2-point line segment
        if (Intersects(intersect_geometry, two_point_line)) {
            // Construct a 2-point line segment using the previous point and the address point
            var last_segment = Polyline({'paths': [[[first_point.x, first_point.y], [intersect_geometry.x, intersect_geometry.y]]], 'spatialReference' : first_point.spatialReference});
            // Add to the total distance along the line and break the loop
            distance_along_line += Length(last_segment, unit);
            return distance_along_line
        }
        // Add to the toal distance along the line
        distance_along_line += Length(two_point_line, unit);
    }
}

return null;

}

function create_point(coordinates, spatial_ref) { // create point geometry from coordinates [x, y] return Point({"x": coordinates[0], "y": coordinates[1], "spatialReference": spatial_ref}) }

function get_addr_num(road, percent_along, dir) { // This function will return the address number of the new site address point // It determines this based on the from and to address range on the intersecting road and the direction of the offset // If direction is null, defaults to left offset var addr_num = null; if (IsEmpty(dir)) dir = 'left'; var from = road[fromleft]; var to = road[toleft]; if (Lower(dir) == 'right') { var from = road[fromright]; var to = road[toright]; } if (from == null || to == null) return null; var val = percent_along * (to - from); var addr_num = 0;

if ((Floor(val) % 2) == 0) addr_num = Floor(val);
else if ((Ceil(val) % 2) == 0) addr_num = Ceil(val);
else addr_num = Floor(val) - 1;

return from + addr_num;

}

// ***** End Functions Section **

// find closest line to $feature var closest_line = find_closest_line(); if (closest_line == null) return

// find info about the closest point on the closest line to $feature var data = closest_point_info($feature, closest_line); if (data == null) return

// calculate the distance along of closest point var closest_point = create_point(data["coordinates"], Geometry($feature)["spatialReference"]) var distance_along = intersect_distance_along(closest_point, closest_line, "feet") if (distance_along == null) return { "errorMessage": "could not calculate distance along" } var percent_along = distance_along / Length(closest_line, "feet")

// calculate address number var address_num = get_addr_num(closest_line, percent_along, data["lineSide"])

// return result to update attributes of $feature return { "result": { "attributes": Dictionary( percent_field, percent_along, offdir_field, data["lineSide"], address_field, address_num, fullname_field, closest_line[fullname], county_field, Iif(data["lineSide"] == "left", countyleft, countyrt), msagcommun_field, Iif(data["lineSide"] == "left", commleft, commright), zipcode_field, Iif(data["lineSide"] == "left", zipleft, zipright), esn_field, Iif(data["lineSide"] == "left", esnleft, esnright) ) } }

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Ted Howard @.> Sent: Tuesday, April 30, 2024 8:06 PM To: Esri/arcade-expressions @.> Cc: Linda Gallion @.>; Mention @.> Subject: Re: [Esri/arcade-expressions] update variable section (#37)

Assuming request CountyLeft and CountyRight fields for the centerline

// return result to update attributes of $feature

return {

"result": {

    "attributes":

        Dictionary(

            percent_field, percent_along,

            offdir_field, data["lineSide"],

            address_field, address_num,

            fullname_field, closest_line[fullname]

            county, Iif(data["lineSide"] == "left", CountyLeft, CountyRight)

        )

}

}

— Reply to this email directly, view it on GitHubhttps://github.com/Esri/arcade-expressions/pull/37#issuecomment-2087795693, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BIGH3WHSWNUGFA6QGLUFG2LZAA5VZAVCNFSM42Z4QGPKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBYG43TSNJWHEZQ. You are receiving this because you were mentioned.Message ID: @.**@.>>

lingal1968 commented 6 months ago

I emailed too quickly. I got the right/left fields to work. I am unable to add additional fields from the road centerline layer. I want to copy not just the [fullname] but also, [msagpredir] and others into fields in my structure layer with the same names -– it gives me a “wrong number of arguments”. What am I doing wrong?

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Linda Gallion Sent: Wednesday, May 1, 2024 2:12 PM To: Esri/arcade-expressions @.>; Esri/arcade-expressions @.> Cc: Mention @.***> Subject: RE: [Esri/arcade-expressions] update variable section (#37)

Good afternoon. It’s recognizing the fields as text and returning CountyLeft instead of the value of the field. I’ve tried a few ways but I’m not having any luck. What should I change to make it recognize this as a field? – Also, I’m not having any luck adding the additional fields in the results section – it gives me a “wrong number of arguments”. I’ve attached the whole code. The msagpredir_field is the additional field I am trying to add (just the first of several I’ll need to include). Any guidance is greatly appreciated!

// This rule will calculate an address number for a point based on nearest road centerline

// Centerline attribute field names var fromleft = "fromleft" var toleft = "toleft" var fromright = "fromright" var toright = "toright" var fullname = "msagcomple" var msagpredir = "msagpredir" var countyleft = "countyleft" var countyrt = "countyrt" var commleft = "commleft" var commright = "commright" var esnleft = "esnleft" var esnright = "esnright" var zipleft = "zipleft" var zipright = "zipright"

// Site Address Point fields to calculate var percent_field = "PERCENTALONG" // percent along centerline where site address lies var offdir_field = "OFFDIR" // side of centerline where site address lies var fullname_field = "msagcomple" // name of closest centerline var address_field = "address" // address number calculated based on where site address lies var county_field = "county" var msagcommun_field = "msagcommun" var esn_field = "esn" var zipcode_field = "zipcode" var msagpredir_field = "msagpredir"

// The Road Centerline feature set var line_class = FeatureSetByName($datastore, "regional_branch.sde.WCTCOGRoadCenterlines", [fromleft, toleft, fromright, toright, fullname, msagpredir, countyleft, countyrt, commleft, commright, esnleft, esnright, zipleft, zipright], true);

// Functions

function find_closest_line() { // Find closest line segment to $feature. Limit search to specific radius. var candidates = Intersects(line_class, Buffer($feature, 1000, "feet")); //var candidates = line_class;

var shortest = [1e10, null];
for (var line in candidates) {
    var d = Distance($feature, line)
    if (d < shortest[0]) shortest = [d, line]
}
return shortest[-1]

}

function closest_point_info(point_feature, line_feature) { /* finds the closest point on line_feature from point_feature

    Args:
        point_feature: Point Geometry
        line_feature: Line Geometry

    Returns: dictionary
        {distance: number,    // distance from point_feature to closest point
         coordinates: array,  // the coordinate pair of the closest point
         isVertex: bool,      // if the closest point is a vertex of line_feature
         lineSide: text}      // side of the line that point_feature is on based

*/

var point_feature = Geometry(point_feature);
var line_feature = Geometry(line_feature);
var vertices = line_feature["paths"]
var x = point_feature["x"];
var y = point_feature["y"];

// Loop through each part of the geometry and each segment, tracking the shortest distance
var shortest = [1e10];
for (var i in vertices) {
    var part = vertices[i];
    var previous = part[0];
    for (var j = 1; j < Count(part); j++) {
        var current = part[j];
        var result = pDistance(x, y, previous["x"], previous["y"], current["x"], current["y"]);
        if (result[0] < shortest[0]) shortest = result
        previous = current;
    }

}

// Couldn't find anything
if (Count(shortest) == 1) return null

return {"distance": shortest[0],
        "coordinates": shortest[1],
        "isVertex": shortest[2],
        "lineSide": shortest[3]}

}

function pDistance(x, y, x1, y1, x2, y2) { // adopted from https://stackoverflow.com/a/6853926 var A = x - x1; var B = y - y1; var C = x2 - x1; var D = y2 - y1;

var dot = A C + B D; var len_sq = C C + D D; var param = -1; if (len_sq != 0) //in case of 0 length line param = dot / len_sq;

var xx, yy; var is_vertex = true; if (param < 0) { xx = x1; yy = y1; } else if (param > 1) { xx = x2; yy = y2; } else { is_vertex = false; xx = x1 + param C; yy = y1 + param D; }

var dx = x - xx; var dy = y - yy; return [Sqrt(dx dx + dy dy), [xx, yy], is_vertex, side_of_line(x,y,x1,y1,x2,y2)]; }

function side_of_line(x, y, x1, y1, x2, y2) { // get side of line segment that a point (x, y) is on based on the direction of segment [[x1, y1], [x2, y2]] // adopted from https://math.stackexchange.com/a/274728 var d = (x - x1) (y2 - y1) - (y - y1) (x2 - x1) if (d < 0) { return 'left' } else if (d > 0) { return 'right' } else { return null } }

function intersect_distance_along(intersect_geometry, line, unit){ // Loop through the segments of the line. Handle multipart geometries. var distance_along_line = 0; for (var part in Geometry(line).paths) { var segment = Geometry(line).paths[part];

    // Loop through the points in the segment
    for (var i in segment) {
        if (i == 0) continue;

        // Construct a 2-point line segment from the current and previous point
        var first_point = segment[i-1];
        var second_point = segment[i]
        var two_point_line = Polyline({'paths': [[[first_point.x, first_point.y], [second_point.x, second_point.y]]], 'spatialReference' : first_point.spatialReference});

        // Test if the  point intersects the 2-point line segment
        if (Intersects(intersect_geometry, two_point_line)) {
            // Construct a 2-point line segment using the previous point and the address point
            var last_segment = Polyline({'paths': [[[first_point.x, first_point.y], [intersect_geometry.x, intersect_geometry.y]]], 'spatialReference' : first_point.spatialReference});
            // Add to the total distance along the line and break the loop
            distance_along_line += Length(last_segment, unit);
            return distance_along_line
        }
        // Add to the toal distance along the line
        distance_along_line += Length(two_point_line, unit);
    }
}

return null;

}

function create_point(coordinates, spatial_ref) { // create point geometry from coordinates [x, y] return Point({"x": coordinates[0], "y": coordinates[1], "spatialReference": spatial_ref}) }

function get_addr_num(road, percent_along, dir) { // This function will return the address number of the new site address point // It determines this based on the from and to address range on the intersecting road and the direction of the offset // If direction is null, defaults to left offset var addr_num = null; if (IsEmpty(dir)) dir = 'left'; var from = road[fromleft]; var to = road[toleft]; if (Lower(dir) == 'right') { var from = road[fromright]; var to = road[toright]; } if (from == null || to == null) return null; var val = percent_along * (to - from); var addr_num = 0;

if ((Floor(val) % 2) == 0) addr_num = Floor(val);
else if ((Ceil(val) % 2) == 0) addr_num = Ceil(val);
else addr_num = Floor(val) - 1;

return from + addr_num;

}

// ***** End Functions Section **

// find closest line to $feature var closest_line = find_closest_line(); if (closest_line == null) return

// find info about the closest point on the closest line to $feature var data = closest_point_info($feature, closest_line); if (data == null) return

// calculate the distance along of closest point var closest_point = create_point(data["coordinates"], Geometry($feature)["spatialReference"]) var distance_along = intersect_distance_along(closest_point, closest_line, "feet") if (distance_along == null) return { "errorMessage": "could not calculate distance along" } var percent_along = distance_along / Length(closest_line, "feet")

// calculate address number var address_num = get_addr_num(closest_line, percent_along, data["lineSide"])

// return result to update attributes of $feature return { "result": { "attributes": Dictionary( percent_field, percent_along, offdir_field, data["lineSide"], address_field, address_num, fullname_field, closest_line[fullname], county_field, Iif(data["lineSide"] == "left", countyleft, countyrt), msagcommun_field, Iif(data["lineSide"] == "left", commleft, commright), zipcode_field, Iif(data["lineSide"] == "left", zipleft, zipright), esn_field, Iif(data["lineSide"] == "left", esnleft, esnright) ) } }

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Ted Howard @.**@.>> Sent: Tuesday, April 30, 2024 8:06 PM To: Esri/arcade-expressions @.**@.>> Cc: Linda Gallion @.**@.>>; Mention @.**@.>> Subject: Re: [Esri/arcade-expressions] update variable section (#37)

Assuming request CountyLeft and CountyRight fields for the centerline

// return result to update attributes of $feature

return {

"result": {

    "attributes":

        Dictionary(

            percent_field, percent_along,

            offdir_field, data["lineSide"],

            address_field, address_num,

            fullname_field, closest_line[fullname]

            county, Iif(data["lineSide"] == "left", CountyLeft, CountyRight)

        )

}

}

— Reply to this email directly, view it on GitHubhttps://github.com/Esri/arcade-expressions/pull/37#issuecomment-2087795693, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BIGH3WHSWNUGFA6QGLUFG2LZAA5VZAVCNFSM42Z4QGPKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBYG43TSNJWHEZQ. You are receiving this because you were mentioned.Message ID: @.**@.>>

lingal1968 commented 6 months ago

Disregard. I figured this out as well. Thank you for all of your assistance!

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Linda Gallion Sent: Wednesday, May 1, 2024 3:48 PM To: Esri/arcade-expressions @.>; Esri/arcade-expressions @.> Cc: Mention @.***> Subject: RE: [Esri/arcade-expressions] update variable section (#37)

I emailed too quickly. I got the right/left fields to work. I am unable to add additional fields from the road centerline layer. I want to copy not just the [fullname] but also, [msagpredir] and others into fields in my structure layer with the same names -– it gives me a “wrong number of arguments”. What am I doing wrong?

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Linda Gallion Sent: Wednesday, May 1, 2024 2:12 PM To: Esri/arcade-expressions @.**@.>>; Esri/arcade-expressions @.**@.>> Cc: Mention @.**@.>> Subject: RE: [Esri/arcade-expressions] update variable section (#37)

Good afternoon. It’s recognizing the fields as text and returning CountyLeft instead of the value of the field. I’ve tried a few ways but I’m not having any luck. What should I change to make it recognize this as a field? – Also, I’m not having any luck adding the additional fields in the results section – it gives me a “wrong number of arguments”. I’ve attached the whole code. The msagpredir_field is the additional field I am trying to add (just the first of several I’ll need to include). Any guidance is greatly appreciated!

// This rule will calculate an address number for a point based on nearest road centerline

// Centerline attribute field names var fromleft = "fromleft" var toleft = "toleft" var fromright = "fromright" var toright = "toright" var fullname = "msagcomple" var msagpredir = "msagpredir" var countyleft = "countyleft" var countyrt = "countyrt" var commleft = "commleft" var commright = "commright" var esnleft = "esnleft" var esnright = "esnright" var zipleft = "zipleft" var zipright = "zipright"

// Site Address Point fields to calculate var percent_field = "PERCENTALONG" // percent along centerline where site address lies var offdir_field = "OFFDIR" // side of centerline where site address lies var fullname_field = "msagcomple" // name of closest centerline var address_field = "address" // address number calculated based on where site address lies var county_field = "county" var msagcommun_field = "msagcommun" var esn_field = "esn" var zipcode_field = "zipcode" var msagpredir_field = "msagpredir"

// The Road Centerline feature set var line_class = FeatureSetByName($datastore, "regional_branch.sde.WCTCOGRoadCenterlines", [fromleft, toleft, fromright, toright, fullname, msagpredir, countyleft, countyrt, commleft, commright, esnleft, esnright, zipleft, zipright], true);

// Functions

function find_closest_line() { // Find closest line segment to $feature. Limit search to specific radius. var candidates = Intersects(line_class, Buffer($feature, 1000, "feet")); //var candidates = line_class;

var shortest = [1e10, null];
for (var line in candidates) {
    var d = Distance($feature, line)
    if (d < shortest[0]) shortest = [d, line]
}
return shortest[-1]

}

function closest_point_info(point_feature, line_feature) { /* finds the closest point on line_feature from point_feature

    Args:
        point_feature: Point Geometry
        line_feature: Line Geometry

    Returns: dictionary
        {distance: number,    // distance from point_feature to closest point
         coordinates: array,  // the coordinate pair of the closest point
         isVertex: bool,      // if the closest point is a vertex of line_feature
         lineSide: text}      // side of the line that point_feature is on based

*/

var point_feature = Geometry(point_feature);
var line_feature = Geometry(line_feature);
var vertices = line_feature["paths"]
var x = point_feature["x"];
var y = point_feature["y"];

// Loop through each part of the geometry and each segment, tracking the shortest distance
var shortest = [1e10];
for (var i in vertices) {
    var part = vertices[i];
    var previous = part[0];
    for (var j = 1; j < Count(part); j++) {
        var current = part[j];
        var result = pDistance(x, y, previous["x"], previous["y"], current["x"], current["y"]);
        if (result[0] < shortest[0]) shortest = result
        previous = current;
    }

}

// Couldn't find anything
if (Count(shortest) == 1) return null

return {"distance": shortest[0],
        "coordinates": shortest[1],
        "isVertex": shortest[2],
        "lineSide": shortest[3]}

}

function pDistance(x, y, x1, y1, x2, y2) { // adopted from https://stackoverflow.com/a/6853926 var A = x - x1; var B = y - y1; var C = x2 - x1; var D = y2 - y1;

var dot = A C + B D; var len_sq = C C + D D; var param = -1; if (len_sq != 0) //in case of 0 length line param = dot / len_sq;

var xx, yy; var is_vertex = true; if (param < 0) { xx = x1; yy = y1; } else if (param > 1) { xx = x2; yy = y2; } else { is_vertex = false; xx = x1 + param C; yy = y1 + param D; }

var dx = x - xx; var dy = y - yy; return [Sqrt(dx dx + dy dy), [xx, yy], is_vertex, side_of_line(x,y,x1,y1,x2,y2)]; }

function side_of_line(x, y, x1, y1, x2, y2) { // get side of line segment that a point (x, y) is on based on the direction of segment [[x1, y1], [x2, y2]] // adopted from https://math.stackexchange.com/a/274728 var d = (x - x1) (y2 - y1) - (y - y1) (x2 - x1) if (d < 0) { return 'left' } else if (d > 0) { return 'right' } else { return null } }

function intersect_distance_along(intersect_geometry, line, unit){ // Loop through the segments of the line. Handle multipart geometries. var distance_along_line = 0; for (var part in Geometry(line).paths) { var segment = Geometry(line).paths[part];

    // Loop through the points in the segment
    for (var i in segment) {
        if (i == 0) continue;

        // Construct a 2-point line segment from the current and previous point
        var first_point = segment[i-1];
        var second_point = segment[i]
        var two_point_line = Polyline({'paths': [[[first_point.x, first_point.y], [second_point.x, second_point.y]]], 'spatialReference' : first_point.spatialReference});

        // Test if the  point intersects the 2-point line segment
        if (Intersects(intersect_geometry, two_point_line)) {
            // Construct a 2-point line segment using the previous point and the address point
            var last_segment = Polyline({'paths': [[[first_point.x, first_point.y], [intersect_geometry.x, intersect_geometry.y]]], 'spatialReference' : first_point.spatialReference});
            // Add to the total distance along the line and break the loop
            distance_along_line += Length(last_segment, unit);
            return distance_along_line
        }
        // Add to the toal distance along the line
        distance_along_line += Length(two_point_line, unit);
    }
}

return null;

}

function create_point(coordinates, spatial_ref) { // create point geometry from coordinates [x, y] return Point({"x": coordinates[0], "y": coordinates[1], "spatialReference": spatial_ref}) }

function get_addr_num(road, percent_along, dir) { // This function will return the address number of the new site address point // It determines this based on the from and to address range on the intersecting road and the direction of the offset // If direction is null, defaults to left offset var addr_num = null; if (IsEmpty(dir)) dir = 'left'; var from = road[fromleft]; var to = road[toleft]; if (Lower(dir) == 'right') { var from = road[fromright]; var to = road[toright]; } if (from == null || to == null) return null; var val = percent_along * (to - from); var addr_num = 0;

if ((Floor(val) % 2) == 0) addr_num = Floor(val);
else if ((Ceil(val) % 2) == 0) addr_num = Ceil(val);
else addr_num = Floor(val) - 1;

return from + addr_num;

}

// ***** End Functions Section **

// find closest line to $feature var closest_line = find_closest_line(); if (closest_line == null) return

// find info about the closest point on the closest line to $feature var data = closest_point_info($feature, closest_line); if (data == null) return

// calculate the distance along of closest point var closest_point = create_point(data["coordinates"], Geometry($feature)["spatialReference"]) var distance_along = intersect_distance_along(closest_point, closest_line, "feet") if (distance_along == null) return { "errorMessage": "could not calculate distance along" } var percent_along = distance_along / Length(closest_line, "feet")

// calculate address number var address_num = get_addr_num(closest_line, percent_along, data["lineSide"])

// return result to update attributes of $feature return { "result": { "attributes": Dictionary( percent_field, percent_along, offdir_field, data["lineSide"], address_field, address_num, fullname_field, closest_line[fullname], county_field, Iif(data["lineSide"] == "left", countyleft, countyrt), msagcommun_field, Iif(data["lineSide"] == "left", commleft, commright), zipcode_field, Iif(data["lineSide"] == "left", zipleft, zipright), esn_field, Iif(data["lineSide"] == "left", esnleft, esnright) ) } }

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Ted Howard @.**@.>> Sent: Tuesday, April 30, 2024 8:06 PM To: Esri/arcade-expressions @.**@.>> Cc: Linda Gallion @.**@.>>; Mention @.**@.>> Subject: Re: [Esri/arcade-expressions] update variable section (#37)

Assuming request CountyLeft and CountyRight fields for the centerline

// return result to update attributes of $feature

return {

"result": {

    "attributes":

        Dictionary(

            percent_field, percent_along,

            offdir_field, data["lineSide"],

            address_field, address_num,

            fullname_field, closest_line[fullname]

            county, Iif(data["lineSide"] == "left", CountyLeft, CountyRight)

        )

}

}

— Reply to this email directly, view it on GitHubhttps://github.com/Esri/arcade-expressions/pull/37#issuecomment-2087795693, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BIGH3WHSWNUGFA6QGLUFG2LZAA5VZAVCNFSM42Z4QGPKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBYG43TSNJWHEZQ. You are receiving this because you were mentioned.Message ID: @.**@.>>

lingal1968 commented 6 months ago

I have another pretty big coding problem. I do the 9-1-1 addressing for an 18-county area and have to deal with existing addresses (which aren’t always accurate). Geocoding new addresses is not an option as I have to work within the constraints of existing flawed addressing. I’m not sure where to even start. Is this something you’ve seen? Can you point me to some sample code that I can work with or someone who specializes in this complexity of coding? I’ve spelled out what I want to achieve below but I’m just not sure if it’s even possible. Any guidance you can provide is greatly appreciated.

Auto assign addresses using existing structure points and road centerlines before trying to geocode. This is the order to follow when determining a new address:

  1. Are there address points on either side of the new structure point?
    • Calculate a straight line from each structure point location to the related road centerline and assign that address house number value to that location (disregard the road “to/from” values).
    • Subtract the low address house number from the high address house number and save as “Difference”.
    • Measure the linear distance between the two points (created in a.) on the road centerline and save as “Distance Between Existing”.
    • Measure the linear distance between the new structure point and the existing low address and save as “Distance From”.
    • Divide “Distance Between Existing” by “Difference” and save as “Divisor”.
    • Finally, divide “Distance From” by “Divisor” and add the total to the low address (round).
    • Ensure value is correct parity.
  2. Is there an address point on either side of the new structure point? If Higher:
    • Calculate a straight line from the structure point location to the related road centerline and assign that address house number value to that location (disregard the road high range value).
    • Subtract the low road range from the existing address house number and save as “Difference”.
    • Measure the linear distance between the point (created in a.) and the low end of the road and save as “Distance Between Existing”.
    • Measure the linear distance between the new structure point and the low end of the road and save as “Distance From”.
    • Divide “Distance Between Existing” by “Difference” and save as “Divisor”.
    • Finally, divide “Distance From” by “Divisor” and add the total to the low range of the road (round).
    • Ensure value is correct parity.
  3. If Lower:
    • Calculate a straight line from the structure point location to the related road centerline and assign that address house number value to that location (disregard the road low range value).
    • Subtract the low address from the high road range and save as “Difference”.
    • Measure the linear distance between the point (created in a.) and the high end of the road and save as “Distance Between Existing”.
    • Measure the linear distance between the new structure point and the high end of the road and save as “Distance From”.
    • Divide “Distance Between Existing” by “Difference” and save as “Divisor”.
    • Finally, divide “Distance From” by “Divisor” and subtract the total from the high range of the road (round and remove negative).
    • Ensure value is correct parity.
  4. Lastly, if there are no existing address points on the road segment, geocode the address using the centerline values.

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Linda Gallion Sent: Wednesday, May 1, 2024 3:55 PM To: Esri/arcade-expressions @.>; Esri/arcade-expressions @.> Cc: Mention @.***> Subject: RE: [Esri/arcade-expressions] update variable section (#37)

Disregard. I figured this out as well. Thank you for all of your assistance!

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Linda Gallion Sent: Wednesday, May 1, 2024 3:48 PM To: Esri/arcade-expressions @.**@.>>; Esri/arcade-expressions @.**@.>> Cc: Mention @.**@.>> Subject: RE: [Esri/arcade-expressions] update variable section (#37)

I emailed too quickly. I got the right/left fields to work. I am unable to add additional fields from the road centerline layer. I want to copy not just the [fullname] but also, [msagpredir] and others into fields in my structure layer with the same names -– it gives me a “wrong number of arguments”. What am I doing wrong?

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Linda Gallion Sent: Wednesday, May 1, 2024 2:12 PM To: Esri/arcade-expressions @.**@.>>; Esri/arcade-expressions @.**@.>> Cc: Mention @.**@.>> Subject: RE: [Esri/arcade-expressions] update variable section (#37)

Good afternoon. It’s recognizing the fields as text and returning CountyLeft instead of the value of the field. I’ve tried a few ways but I’m not having any luck. What should I change to make it recognize this as a field? – Also, I’m not having any luck adding the additional fields in the results section – it gives me a “wrong number of arguments”. I’ve attached the whole code. The msagpredir_field is the additional field I am trying to add (just the first of several I’ll need to include). Any guidance is greatly appreciated!

// This rule will calculate an address number for a point based on nearest road centerline

// Centerline attribute field names var fromleft = "fromleft" var toleft = "toleft" var fromright = "fromright" var toright = "toright" var fullname = "msagcomple" var msagpredir = "msagpredir" var countyleft = "countyleft" var countyrt = "countyrt" var commleft = "commleft" var commright = "commright" var esnleft = "esnleft" var esnright = "esnright" var zipleft = "zipleft" var zipright = "zipright"

// Site Address Point fields to calculate var percent_field = "PERCENTALONG" // percent along centerline where site address lies var offdir_field = "OFFDIR" // side of centerline where site address lies var fullname_field = "msagcomple" // name of closest centerline var address_field = "address" // address number calculated based on where site address lies var county_field = "county" var msagcommun_field = "msagcommun" var esn_field = "esn" var zipcode_field = "zipcode" var msagpredir_field = "msagpredir"

// The Road Centerline feature set var line_class = FeatureSetByName($datastore, "regional_branch.sde.WCTCOGRoadCenterlines", [fromleft, toleft, fromright, toright, fullname, msagpredir, countyleft, countyrt, commleft, commright, esnleft, esnright, zipleft, zipright], true);

// Functions

function find_closest_line() { // Find closest line segment to $feature. Limit search to specific radius. var candidates = Intersects(line_class, Buffer($feature, 1000, "feet")); //var candidates = line_class;

var shortest = [1e10, null];
for (var line in candidates) {
    var d = Distance($feature, line)
    if (d < shortest[0]) shortest = [d, line]
}
return shortest[-1]

}

function closest_point_info(point_feature, line_feature) { /* finds the closest point on line_feature from point_feature

    Args:
        point_feature: Point Geometry
        line_feature: Line Geometry

    Returns: dictionary
        {distance: number,    // distance from point_feature to closest point
         coordinates: array,  // the coordinate pair of the closest point
         isVertex: bool,      // if the closest point is a vertex of line_feature
         lineSide: text}      // side of the line that point_feature is on based

*/

var point_feature = Geometry(point_feature);
var line_feature = Geometry(line_feature);
var vertices = line_feature["paths"]
var x = point_feature["x"];
var y = point_feature["y"];

// Loop through each part of the geometry and each segment, tracking the shortest distance
var shortest = [1e10];
for (var i in vertices) {
    var part = vertices[i];
    var previous = part[0];
    for (var j = 1; j < Count(part); j++) {
        var current = part[j];
        var result = pDistance(x, y, previous["x"], previous["y"], current["x"], current["y"]);
        if (result[0] < shortest[0]) shortest = result
        previous = current;
    }

}

// Couldn't find anything
if (Count(shortest) == 1) return null

return {"distance": shortest[0],
        "coordinates": shortest[1],
        "isVertex": shortest[2],
        "lineSide": shortest[3]}

}

function pDistance(x, y, x1, y1, x2, y2) { // adopted from https://stackoverflow.com/a/6853926 var A = x - x1; var B = y - y1; var C = x2 - x1; var D = y2 - y1;

var dot = A C + B D; var len_sq = C C + D D; var param = -1; if (len_sq != 0) //in case of 0 length line param = dot / len_sq;

var xx, yy; var is_vertex = true; if (param < 0) { xx = x1; yy = y1; } else if (param > 1) { xx = x2; yy = y2; } else { is_vertex = false; xx = x1 + param C; yy = y1 + param D; }

var dx = x - xx; var dy = y - yy; return [Sqrt(dx dx + dy dy), [xx, yy], is_vertex, side_of_line(x,y,x1,y1,x2,y2)]; }

function side_of_line(x, y, x1, y1, x2, y2) { // get side of line segment that a point (x, y) is on based on the direction of segment [[x1, y1], [x2, y2]] // adopted from https://math.stackexchange.com/a/274728 var d = (x - x1) (y2 - y1) - (y - y1) (x2 - x1) if (d < 0) { return 'left' } else if (d > 0) { return 'right' } else { return null } }

function intersect_distance_along(intersect_geometry, line, unit){ // Loop through the segments of the line. Handle multipart geometries. var distance_along_line = 0; for (var part in Geometry(line).paths) { var segment = Geometry(line).paths[part];

    // Loop through the points in the segment
    for (var i in segment) {
        if (i == 0) continue;

        // Construct a 2-point line segment from the current and previous point
        var first_point = segment[i-1];
        var second_point = segment[i]
        var two_point_line = Polyline({'paths': [[[first_point.x, first_point.y], [second_point.x, second_point.y]]], 'spatialReference' : first_point.spatialReference});

        // Test if the  point intersects the 2-point line segment
        if (Intersects(intersect_geometry, two_point_line)) {
            // Construct a 2-point line segment using the previous point and the address point
            var last_segment = Polyline({'paths': [[[first_point.x, first_point.y], [intersect_geometry.x, intersect_geometry.y]]], 'spatialReference' : first_point.spatialReference});
            // Add to the total distance along the line and break the loop
            distance_along_line += Length(last_segment, unit);
            return distance_along_line
        }
        // Add to the toal distance along the line
        distance_along_line += Length(two_point_line, unit);
    }
}

return null;

}

function create_point(coordinates, spatial_ref) { // create point geometry from coordinates [x, y] return Point({"x": coordinates[0], "y": coordinates[1], "spatialReference": spatial_ref}) }

function get_addr_num(road, percent_along, dir) { // This function will return the address number of the new site address point // It determines this based on the from and to address range on the intersecting road and the direction of the offset // If direction is null, defaults to left offset var addr_num = null; if (IsEmpty(dir)) dir = 'left'; var from = road[fromleft]; var to = road[toleft]; if (Lower(dir) == 'right') { var from = road[fromright]; var to = road[toright]; } if (from == null || to == null) return null; var val = percent_along * (to - from); var addr_num = 0;

if ((Floor(val) % 2) == 0) addr_num = Floor(val);
else if ((Ceil(val) % 2) == 0) addr_num = Ceil(val);
else addr_num = Floor(val) - 1;

return from + addr_num;

}

// ***** End Functions Section **

// find closest line to $feature var closest_line = find_closest_line(); if (closest_line == null) return

// find info about the closest point on the closest line to $feature var data = closest_point_info($feature, closest_line); if (data == null) return

// calculate the distance along of closest point var closest_point = create_point(data["coordinates"], Geometry($feature)["spatialReference"]) var distance_along = intersect_distance_along(closest_point, closest_line, "feet") if (distance_along == null) return { "errorMessage": "could not calculate distance along" } var percent_along = distance_along / Length(closest_line, "feet")

// calculate address number var address_num = get_addr_num(closest_line, percent_along, data["lineSide"])

// return result to update attributes of $feature return { "result": { "attributes": Dictionary( percent_field, percent_along, offdir_field, data["lineSide"], address_field, address_num, fullname_field, closest_line[fullname], county_field, Iif(data["lineSide"] == "left", countyleft, countyrt), msagcommun_field, Iif(data["lineSide"] == "left", commleft, commright), zipcode_field, Iif(data["lineSide"] == "left", zipleft, zipright), esn_field, Iif(data["lineSide"] == "left", esnleft, esnright) ) } }

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Ted Howard @.**@.>> Sent: Tuesday, April 30, 2024 8:06 PM To: Esri/arcade-expressions @.**@.>> Cc: Linda Gallion @.**@.>>; Mention @.**@.>> Subject: Re: [Esri/arcade-expressions] update variable section (#37)

Assuming request CountyLeft and CountyRight fields for the centerline

// return result to update attributes of $feature

return {

"result": {

    "attributes":

        Dictionary(

            percent_field, percent_along,

            offdir_field, data["lineSide"],

            address_field, address_num,

            fullname_field, closest_line[fullname]

            county, Iif(data["lineSide"] == "left", CountyLeft, CountyRight)

        )

}

}

— Reply to this email directly, view it on GitHubhttps://github.com/Esri/arcade-expressions/pull/37#issuecomment-2087795693, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BIGH3WHSWNUGFA6QGLUFG2LZAA5VZAVCNFSM42Z4QGPKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBYG43TSNJWHEZQ. You are receiving this because you were mentioned.Message ID: @.**@.>>

ted-howard commented 6 months ago

Seems achievable with existing data by using a batch attribute rule, though it will take a lot of work. Again, I can only point you to the Address Data Management solution. You could also ask Arcade questions on esri community. https://community.esri.com/t5/attribute-rules/ct-p/attribute-rules

lingal1968 commented 6 months ago

Ok. Thank you.

Linda Gallion, 9-1-1 GIS Specialist West Central TX COG 3702 Loop 322 Abilene, TX 79602 (325) 672-8544 From: Ted Howard @.> Sent: Thursday, May 2, 2024 11:09 AM To: Esri/arcade-expressions @.> Cc: Linda Gallion @.>; Mention @.> Subject: Re: [Esri/arcade-expressions] update variable section (#37)

Seems achievable with existing data by using a batch attribute rule, though it will take a lot of work. Again, I can only point you to the Address Data Management solution. You could also ask Arcade questions on esri community. https://community.esri.com/t5/attribute-rules/ct-p/attribute-rules

— Reply to this email directly, view it on GitHubhttps://github.com/Esri/arcade-expressions/pull/37#issuecomment-2090903548, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BIGH3WFFHD6JXVWXZ63E7DTZAJQKBAVCNFSM42Z4QGPKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBZGA4TAMZVGQ4A. You are receiving this because you were mentioned.Message ID: @.**@.>>

MikeMillerGIS commented 6 months ago

Please do not comment on this merged PR anymore. I would suggest you open a new issue if you are looking for help with an attribute rule.