Find which side of line point falls on

795
3
Jump to solution
06-09-2023 08:49 AM
RobertChaney
Occasional Contributor

I've converted Dimension Annotation to points and have spatially joined the points with parcel lines based on a Match Option, Within a Distance of 30 feet.  This allowed me to get the Dimension Annotation TextString field into the parcel line attributes, which I can then use for labeling purposes.  To further this process, I'd like to know if there's a way to know which side of the parcel line the point falls on, so that I could take advantage of using the label position field that's part of the parcel fabric.

ArcGIS Pro ver. 3.1.2

Data is currently in a FGDB

Thanks,

Robert

 

0 Kudos
1 Solution

Accepted Solutions
RobertChaney
Occasional Contributor

@MikeMillerGIS was kind enough to modify his arcade expression which was originally suggested by HungGi.

Mike's expression (listed below) was able to do what I needed and will help out going forward with working on migrating dimension annotation to labels.  All I needed to do was replace the "LineFC" with my line feature class, then insert the expression in the Calculate Field on my point feature class.  I can use this info to assign the label position so that the label remains consistent with the side of the parcel line where the annotation was located.

// The Road Centerline feature set
var line_class = FeatureSetByName($datastore, "LineFC", ["OBJECTID"], true);


// *************       Functions            *************

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

    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 return_side_of_Line(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  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
    }
}


// ************* 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 side_line = return_side_of_Line($feature, closest_line);
if (side_line == null) return
return side_line

  

View solution in original post

3 Replies
HungGi
by
New Contributor III

Hi @RobertChaney ,

The following link might lead you onto the path to finding your solution: https://github.com/Esri/arcade-expressions/blob/master/attribute_rule_calculation/GetAddressFromCent...

If you look at the attribute rule code on line 114, there is a function that returns which side of the line the point falls on. The result is pushed to a field for the result. I know it's more for address and centreline but i'm pretty sure you might be able to go through the code and extract what you need to figure out which side the point falls on for the parcel line.

Hope this helps! 

Cheers!
Hung G.
AmirBar-Maor
Esri Regular Contributor

@RobertChaney 

Great to see that you are improving efficiency by moving away from annotation to labels.

From looking around you can use the arcpy method ' queryPointAndDistance' 

AmirBarMaor_0-1686558015487.png

It is documented here:

https://pro.arcgis.com/en/pro-app/latest/arcpy/classes/polyline.htm

You can also view this code sample copy - pasted from  here:

pnt = [x[0] for x in arcpy.da.SearchCursor("point",["SHAPE@"])][0]
line = [x[0] for x in arcpy.da.SearchCursor("line",["SHAPE@"])][0]
line.queryPointAndDistance(pnt)

#output
(<PointGeometry object at 0x27b71d30[0x27b71860]>, 212.1234, 23.1234, False)

 

Another place to look at is this community post which concludes the use of the same method.

Once you have it working it might be good to share with others that run into the same issue and want to leverage the LabelPostion field.

0 Kudos
RobertChaney
Occasional Contributor

@MikeMillerGIS was kind enough to modify his arcade expression which was originally suggested by HungGi.

Mike's expression (listed below) was able to do what I needed and will help out going forward with working on migrating dimension annotation to labels.  All I needed to do was replace the "LineFC" with my line feature class, then insert the expression in the Calculate Field on my point feature class.  I can use this info to assign the label position so that the label remains consistent with the side of the parcel line where the annotation was located.

// The Road Centerline feature set
var line_class = FeatureSetByName($datastore, "LineFC", ["OBJECTID"], true);


// *************       Functions            *************

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

    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 return_side_of_Line(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  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
    }
}


// ************* 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 side_line = return_side_of_Line($feature, closest_line);
if (side_line == null) return
return side_line