polygon gemetry method angleAndDistanceTo used against another polygon geometry?

1409
7
02-06-2019 01:41 PM
JennyHuang3
New Contributor III

hello!!

i don't have license for near analysis and am trying to use arcpy to create a tool that accepts two polygon feature classes (input and source) and then for each geometry in the input feature, calculates the closest distance and angle to the source geometry. i was thinking of using spatial join first then search cursor on the input feature then use angleAndDistsanceTo method but it only accepts point geometry as input. So i'm wondering if there's a way around this? other solutions to get the distance/angle output is definitely appreciated as well.

thank you!

0 Kudos
7 Replies
DanPatterson_Retired
MVP Emeritus

To give you an idea of what would be involved to code it... consider the following

import numpy as np

def pnt_on_poly(pnt, poly):
    """Find closest point location on a polygon/polyline.

    Parameters
    ----------
    pnt : 1D ndarray array
        XY pair representing the point coordinates.
    poly : 2D ndarray array
        A sequence of XY pairs in clockwise order is expected.  The first and
        last points may or may not be duplicates, signifying sequence closure.

    Returns
    -------
    A list of [x, y, distance] for the intersection point on the line

    Notes
    -----
    e_dist is represented by _e_2d and pnt_on_seg by its equivalent below.

    This may be as simple as finding the closest point on the edge, but if
    needed, an orthogonal projection onto a polygon/line edge will be done.
    This situation arises when the distance to two sequential points is the
    same
    """
    def _e_2d_(a, p):
        """ array points to point distance... mini e_dist"""
        diff = a - p[np.newaxis, :]
        return np.sqrt(np.einsum('ij,ij->i', diff, diff))
    #
    def _pnt_on_seg_(seg, pnt):
        """mini pnt_on_seg function normally required by pnt_on_poly"""
        x0, y0, x1, y1, dx, dy = *pnt, *seg[0], *(seg[1] - seg[0])
        dist_ = dx*dx + dy*dy  # squared length
        u = ((x0 - x1)*dx + (y0 - y1)*dy)/dist_
        u = max(min(u, 1), 0)  # u must be between 0 and 1
        xy = np.array([dx, dy])*u + [x1, y1]
        return xy
    #
    def _line_dir_(orig, dest):
        """mini line direction function"""
        orig = np.atleast_2d(orig)
        dest = np.atleast_2d(dest)
        dxy = dest - orig
        ang = np.degrees(np.arctan2(dxy[:, 1], dxy[:, 0]))
        return ang
    #
    pnt = np.asarray(pnt)
    poly = np.asarray(poly)
    if np.all(poly[0] == poly[-1]):  # strip off any duplicate
        poly = poly[:-1]
    # ---- determine the distances
    d = _e_2d_(poly, pnt)  # abbreviated edist =>  d = e_dist(poly, pnt)
    key = np.argsort(d)[0]         # dist = d[key]
    if key == 0:
        seg = np.vstack((poly[-1:], poly[:3]))
    elif (key + 1) >= len(poly):
        seg = np.vstack((poly[-2:], poly[:1]))
    else:
        seg = poly[key-1:key+2]    # grab the before and after closest
    n1 = _pnt_on_seg_(seg[:-1], pnt)  # abbreviated pnt_on_seg
    d1 = np.linalg.norm(n1 - pnt)
    n2 = _pnt_on_seg_(seg[1:], pnt)   # abbreviated pnt_on_seg
    d2 = np.linalg.norm(n2 - pnt)    
    if d1 <= d2:
        dest = [n1[0], n1[1]]
        ang = _line_dir_(pnt, dest)
        return [n1[0], n1[1], np.asscalar(d1), np.asscalar(ang)]
    else:
        dest = [n2[0], n2[1]]
        ang = _line_dir_(pnt, dest)
        return [n2[0], n2[1], np.asscalar(d2), np.asscalar(ang)]


a = np.array([[0, 0], [0, 100], [100, 100], [100, 80], [20, 80],
               [20, 20], [100, 20], [100, 0], [0, 0]])  #  the letter C

pnt = [150, 150]

pnt_on_poly(pnt, a) # ---- [100, 100, 70.71067811865476, -135.0]

# ---- point on polygon (100, 100), distance 70.7, angle -135

but you would have to do a general sort of the closest point in one polygon to the extent of the other, then limit your candidate points before doing the calculation.  One gains a greater appreciation of what goes on behinds the scenes for such a 'simple' question

JennyHuang3
New Contributor III

Thanks for the quick response Dan! I was really hoping to find a solution that's a lot simpler, something that's built-in in arcpy, hidden somewhere that i haven't found.

but thanks anyways, i guess that's why near analysis is only included in the advanced license

0 Kudos
DanPatterson_Retired
MVP Emeritus

Yes... the extra cost is sometimes not "extra" depending on what you want to do

Good luck, I am sure you can cobble a workaround or revisit the importance of your desired requirements

0 Kudos
DanPatterson_Retired
MVP Emeritus

I do have some workarounds... like a 'near-ish' tool in

https://www.arcgis.com/home/item.html?id=f96ede37dcd04c2e96dc903a4ce26244 

depending on the data you are using, it is a good substitute especially if you can deal with a 'close enough' scenario, rather than "I need the exact closest location on the polygon"

Have a look... tools are for ArcGIS Pro only though.  I do most of my work in numpy and python and use the tools in Pro to put a 'shell' around the calculations obviating the need for an advanced license (although I have one, so I can check my results )

JennyHuang3
New Contributor III

Sweet. Much appreciated Dan!

0 Kudos
DuncanHornby
MVP Notable Contributor

Jenny,

You could try exploding the polygon vertices into a point layer and then use angleAndDistanceTo?

Be aware that this approach would not find the closest point on the polygon boundary. You could reduce that issue by densifying the boundary first.

JennyHuang3
New Contributor III

that's an awesome idea! thank you Duncan!

0 Kudos