ArcPy or ArcObject code to find the closest point on a line.

9743
18
Jump to solution
11-24-2015 09:39 PM
George_GShi
New Contributor

Hi All,

Can you give me some hints (code snippet in Arcpy or ArcObject) on:

            for a given point to find the closest point location on a line feature.

Thanks in advance!

George

geoshi@gmail.com

0 Kudos
1 Solution

Accepted Solutions
DarrenWiens2
MVP Honored Contributor

What license level is available?

View solution in original post

0 Kudos
18 Replies
DarrenWiens2
MVP Honored Contributor

What license level is available?

0 Kudos
George_GShi
New Contributor

ArcEditor (or "standard" ).

0 Kudos
RebeccaStrauch__GISP
MVP Emeritus

Near—Help | ArcGIS for Desktop  Is a good place to start if you have the advanced level (which is why Darren asked).   There are several samples of using python code.

if you don't have advanced, then it will take a bit more work.

DanPatterson_Retired
MVP Emeritus

For a python approach, simply

Assume a start location at the origin, and projected coordinates (values have been translated to the origin to simplify the visuals and the math)

>>> import numpy as np
>>> origin = np.array([0,0],dtype="float64")
>>> dests = np.array([[1,2],[-3,3],[-4,-4],[1,1],[5,-5]],dtype="float64")
>>> deltas = dests - origin
>>> distances = np.hypot(deltas[:,0], deltas[:,1])
>>> min_dist = np.min(distances)
>>> wh = np.where(distances == min_dist)
>>> closest = dests[wh[0]]
>>> closest
array([[ 1.,  1.]])
>>>

line (02) that is the origin

line (03 ) the destinations of the points constituting the polyline

line (04) get the coordinate differences)

line (05) calculate the distances, note the np.hypot uses dy,dx as inputs

line (06) find the minimum distance

line(07) find where the min distance is

line(08) use the position of the min distance to slice the original destinations.  ( note wh returns a tuple and you want the first element)

the closest point...obviously is 1,1

see other purely arcpy options.

DarrenWiens2
MVP Honored Contributor

I'm not sure, but I believe this will only get you the closest vertex, not the closest location on a line (which doesn't necessarily mean a vertex). You need to do some trig in between each vertex. But, I could be missing some of the numpy magic going on here.

0 Kudos
DanPatterson_Retired
MVP Emeritus

I left that part out ... but I will post about it soon.  In the interim, George found your solution with the wrong license level worked for him.

0 Kudos
DarrenWiens2
MVP Honored Contributor

Well, I don't believe I answered the question, but maybe thinking about license-levels did the trick...? Anyhow, I'd be interested to see what this looks like through numpy.

0 Kudos
DanPatterson_Retired
MVP Emeritus

will do, the example I gave was for non-sequential points... caught it too late.  But if you want to experiment  for sequential points consider a polyline consisting of  points a,b,c,d and a source point X

  • you just find the closest point on the line from your source point... lets say 'c' wins and 'b' and 'd' are closer than 'a'
  • you now have two potential segments to examine b-c and c-d (this could be multiple, but lets keep it simple.
  • project X onto the candidate segments (the geometry stuff) to get the point on the segment and the distance to X from the segments (could be multiple)
  • the segment from above that has  shortest distance is the correct line segment and you have the intersection point and closest point onto the polyline

I have simplified some checks, but you can incorporate extent checks to see which segments of the polyline contain space that X can project on to.  Remember, this is projection onto a segment and not a imaginary line extending from it.  So soon, it is an interesting comparison of numpy solution and Near tool

0 Kudos
RichardFairhurst
MVP Honored Contributor

Dan:

I was trying to use your code, since I want to find the minimum distance to the actual vertice points in a polyline with the onMouseDownMap function of an addin tool.  Here is the code I tried:

class SplitLineToolClass(object):
    """Implementation for SplitLine_addin.SplitLinetool (Tool)"""
    def __init__(self):
        self.enabled = False
        self.cursor = 3 # Crosshairs
       
    def onMouseDownMap(self, x, y, button, shift):
        # Pass if not a Left Mouse Button Click
        if button != 1:
            pass
            return
    addressLines = pythonaddins.GetSelectedTOCLayerOrDataFrame()
    ## Code that verifies a polyline layer is selected in the TOC
    desc=arcpy.Describe(addressLines)
    sr = arcpy.SpatialReference(desc.spatialReference.factoryCode)
    origin = np.array([x,y],dtype="float64")
    dests = arcpy.da.FeatureClassToNumPyArray(addressLines, ["SHAPE@X","SHAPE@Y"], "", sr, explode_to_points=True)
    deltas = dests - origin 
    distances = np.hypot(deltas[:,0], deltas[:,1]) 
    min_dist = np.min(distances)
    wh = np.where(distances == min_dist) 
    closest = dests[wh[0]]
    print("Closest Vertice = {0}".format(closest))

However, my adaptation of your code is throwing an error on the line that subtracts the two arrays (deltas = dests - origin on line 18 of the code above).  Here is the error:

Traceback (most recent call last):

  File ..., line ..., in onMouseDownMap

    deltas = dests - origin

TypeError: unsupported operand type(s) for -: 'numpy.ndarray' and 'numpy.ndarray'

When I added print lines for the origin and dest arrays, it showed that the Origin array created by line 16 is a list with a space a number a double space and a number, while the Dest array was a list of tuples containing two numbers separated by a comma and a space.  What is the best way to get the two arrays compatible for line 18?

Here is how the arrays printed:

Origin Point = [ 6305489.12520918  2169986.76545799]

Destination Points = [(6305087.957613736, 2169963.2243613005)

(6305515.50958015, 2169962.629874304)

...

(6305742.797839478, 2169860.239002958)]

When I tried the "SHAPE@XY" field instead the result seemed worse:

Destination Point = [([6305087.957613736, 2169963.2243613005],)

([6305515.50958015, 2169962.629874304],)

...

([6305742.797839478, 2169860.239002958],)]

0 Kudos