Using ArcMap's near tool, I have 'projected' the vertices of a trail onto a corresponding line (river), in order to find the closest point on the river to each trail vertex. However, as you can see in the screenshot, not all the trail points (from the green line) have found corresponding near points on the river... I tried first with no search radius and again with a search radius of 2000 m (the distance between the river and the trail in the area is under 300 m).

How can I find all the corresponding near points along the river? enter image description here

  • What version of ArcGIS do you use? Does your data have a projected or geographic coordinate system? Are they shapefiles or gdb feature classes?
    – GISGe
    Jan 22, 2018 at 12:09
  • Should have mentioned: Version 10.5, projected coordinate system, and they are shapefiles.
    – maps_spam
    Jan 22, 2018 at 14:00
  • Can't help but think a solution lies in identifying inflection points as discussed here. But not sure how to progress.
    – Hornbydd
    Jan 22, 2018 at 15:47

2 Answers 2


Using the "Near" Tool may not be the best option in this case. From the help file:

The distance between any two features is calculated as the shortest separation between them, that is, where the two features are closest to each other.

So, using your example, it looks like the reason the points along the river (to the north of your screen shot), are not being picked up because the distance to the vertices to the east and west are closer. Also bear in mind the proximity tools in ArcMap use a set of rules to define distance:

Rule 1: The distance between two points is the straight line connecting the points

Rule 2: Distance from a point to a polyline is the perpendicular or the closest vertex

Rule 3: Distance between polylines is determined by line segment vertices

This is summarised in the following table - again from the help file: enter image description here

In order to try and fix your issue, you could try running "Point Distance" Tool. This tool needs an "Advanced License", it will create a table with Matching the FIDs between two features. You inputs need to be point features, so you will need to convert the vertices of the river and trail to points. From the tool's help file:

The results are recorded in the output table containing the following information:

INPUT_FID: The feature ID of the input features

NEAR_FID: The feature ID of the near features

DISTANCE: The distance from the input to near feature.The value of this field is in the linear unit of the input features coordinate system.

You could then decide some rules regarding distances and calculate the MIN, MAX and MEAN distances between the point features to select those points meeting your needs.

There may be better answers, but this is where I would start.

  • Oh yeah, this makes sense! I was trying to get the perpendicular from the point to the river using the near tool and had reviewed the documentation for Near before but it didn't occur to me, somehow that those 'blank' parts of the river are farther and thus the near points don't show up on them. From the visual it just seems intuitively that some of the 'blank' part of the river are actually be closer to some of the trail vertices. I'm going to look into your suggestions.
    – maps_spam
    Jan 22, 2018 at 14:18
  • @maps_spam Maybe try densifying the vertices in the river feature. Eyeballing the curve it looks like there aren't many. Which would explain why visually it seems there may be discrepancies in your output. Jan 22, 2018 at 16:46
  • I'm not trying to find the nearest vertex on the river though, only the nearest point on the river - should be anywhere on the river, not necessarily a vertex.
    – maps_spam
    Jan 22, 2018 at 16:52
  • Sorry I should have clarified. By densifying the number of vertices on the river you increase the chances of one of the proximity tools identifying a point along the river. That is, if you decide to go with one of the proximity tools. Jan 22, 2018 at 17:27
  • How would that work? I meant earlier that I don't think the Near tool only seeks out vertices to identify as Near points; it can also find (or in this case, 'create', in a sense) Near features (points) on the line in the absence of defined vertices.
    – maps_spam
    Jan 22, 2018 at 19:51

While researching the answer I came across a potential solution from Tom Dilts. He created a Tool in Model Builder which will create perpendicular station lines from a a series of points along a line. His Tool is available to download from here: Station Lines Arc10.0.

Citation for the use of the tool is:

Preferred citation: Dilts, T.E. (2015) Station Lines for ArcGIS 10.1. University of Nevada Reno. Available at: http://www.arcgis.com/home/item.html?id=3f05ef15203e4a3ca88fc1d92347512b

The setup of the Tool is a little tricky, and you have to manually place the inputs and outputs into Model Builder. I have taken the model and created a *.PYT which simplifies the entry of a lot of the inputs. The code for the Python Toolbox is below. However, there are some limitations, the main being the input and outputs of the Tool need to be in a Directory...NOT a Geodatabase. The Tool will throw all sorts of errors if even one input / output is linked to a Geodatabase.

Also needs an Advanced License

The way I would see your workflow:

  1. Convert the vertices of your Trail or River to Points - these points are the input of the tool
  2. Run the tool using the converted vertices as the point file and whatever line file you generated them from as the input line. Choose a distance that the Station Lines will be created to (in map units - try use a distance longer than the maximum distance between the river and trail).
  3. The output will be a series of lines which run perpendicular to the input line and point. Intersect these lines with the Trail or River (depending on what you used as the input) to get the point at which the corresponding point is perpendicular. When you run the intersect choose the output feature type of the intersect as "Point".
  4. You can use a spatial join to evaluate which points connect to which lines and intersect at which point.

Below please find the examples of the Inputs and Output.

Replication of your example

Below please see how the tool appears (1 - the original tool from the link and the circle...as the PYT appears)

enter image description here

The output: enter image description here

Intersecting, green dots are the corresponding points: enter image description here

Finally the Python Toolbox Code - Copy into a text file and saves as a *.PYT and "Add a Toolbox" in ArcMap. Just note, this code is as is, I took it straight from the source, I have no claim to it and full rights and credit goes to the author. I simply made it a Toolbox so I could use it easier.

Name:        Create Perpendicular Lines

Author:      Tom Dilts

Created:     24 Jan2018
Copyright:   None

    Adapted from the Toolbox Authored by Tom Dilts
    Preferred citation: Dilts, T.E. (2015) Station Lines for ArcGIS 10.1. University of Nevada Reno. Available at: http://www.arcgis.com/home/item.html?id=3f05ef15203e4a3ca88fc1d92347512b

# import modules
import arcpy

'''Do not change the name of this class. It will break the toolbox.'''
class Toolbox(object):
    def __init__(self):
        '''Define toolbox properties (the toolbox anme is the .pyt filename).'''
        self.label = "Create Station Lines"
        self.alias = ""

        # List of tool classes associated with this toolbox
        self.tools = [Tool]

class Tool(object):
    def __init__(self):
        '''Define the tool (tool name is the class name).'''
        self.label = "Station Lines"
        self.description = "Creates station lines (perpendicular lines) at user-specified points along a polyline.  Station lines are a useful first step for hydraulic analysis of rivers"
        self.canRunInBackground = False

    def getParameterInfo(self):
        '''parameter definitions for GUI'''
        param0 = arcpy.Parameter(
            displayName = "Input Line File",
            name = "Input Line",
            datatype = "GPFeatureLayer",
            parameterType = "Required",
            direction = "Input")

        param0.filter.list = ['Polyline']
        param0.value = ""

        param1 = arcpy.Parameter(
            displayName = "Input Converted Vertices",
            name = "Input Points",
            datatype = "GPFeatureLayer",
            parameterType = "Required",
            direction = "Input")

        param1.filter.list = ['Point']
        param1.value = ""

        param2 = arcpy.Parameter(
            displayName = "Input Transect Distance",
            name = "Input Transect Distance",
            datatype = "GPDouble",
            parameterType = "Required",
            direction = "Input")
        param2.value = '1000'

        param3 = arcpy.Parameter(
            displayName = "Output File",
            name = "Output",
            datatype = "DEShapefile",
            parameterType = "Required",
            direction = "OutPut")

        param4 = arcpy.Parameter(
            displayName = "Working Directory",
            name = "Working Directory",
            datatype = "DEFolder",
            parameterType = "Required",
            direction = "Input")

        params = [param0, param1, param2, param3, param4]

        return params

    def isLicensed(self):
        '''Set whether tool is licensed to execute.'''
        return True

    def updateParameters(self, parameters):
        '''Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed.'''


    def updateMessages(self, parameters):
        '''Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation.'''

    def execute(self, parameters, messages):
        '''The source code of the tool.'''

        Input_Line_Shapefile = parameters[0].valueAsText
        Input_Points = parameters[1].valueAsText
        Trasect_Distance = parameters[2].valueAsText
        Output_Station_Lines= parameters[3].valueAsText

        WorkingDir = parameters[4].valueAsText

        arcpy.AddMessage (Input_Line_Shapefile + " Added to Inputs")
        arcpy.AddMessage (Input_Points + " Added to Inputs")
        arcpy.AddMessage (Trasect_Distance + " Added to Inputs")
        arcpy.AddMessage (Output_Station_Lines + " Added to Inputs")

        Output_Features = Input_Points
        points = Output_Features
        Trial_FeatureVerticesToPoint = points

        splitline_shp = WorkingDir + "splitline.shp"
        Output_Feature_Class = WorkingDir + "azimuth.shp"

        copy_Select = Output_Feature_Class
        copy_Select__2_ = copy_Select
        splitline_shp__6_ = copy_Select__2_
        splitline_shp__5_ = splitline_shp__6_

        spatjoin_shp = WorkingDir + "spatjoin.shp"
        Left = WorkingDir + "spatjoin_BearingDistanceToLi.shp"
        Right = WorkingDir + "spatjoin_BearingDistanceToLi1.shp"
        mergedata = WorkingDir + "merge.shp"

# Process: Add XY Coordinates
        arcpy.AddMessage ("XY Added")

# Process: Add Field (3)
        arcpy.AddField_management(Output_Features, "Dist", "LONG", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

# Process: Calculate Field (3)
        arcpy.CalculateField_management(points, "Dist", Trasect_Distance, "VB", "")
        arcpy.AddMessage ("Calcs Done")
# Process: Split Line At Vertices
        arcpy.SplitLine_management(Input_Line_Shapefile, splitline_shp)
        arcpy.AddMessage ("Line split")
#Azimuth added
        Input_Line_Features = splitline_shp
        Output_Feature_Class = WorkingDir + "azimuth.shp"

# Local variables:
        Converted_Graphics_7 = Input_Line_Features
        Converted_Graphics_7__2_ = Converted_Graphics_7
        Converted_Graphics_7__3_ = Converted_Graphics_7__2_
        Converted_Graphics_7__4_ = Converted_Graphics_7__3_
        Converted_Graphics_7__6_ = Converted_Graphics_7__4_
        Converted_Graphics_7__9_ = Converted_Graphics_7__6_
        Converted_Graphics_7__5_ = Converted_Graphics_7__9_
        Converted_Graphics_7__8_ = Converted_Graphics_7__5_
        Converted_Graphics_7__10_ = Converted_Graphics_7__8_
        Converted_Graphics_7__7_ = Converted_Graphics_7__10_
        Converted_Graphics_7__13_ = Converted_Graphics_7__7_
        Converted_Graphics_7__12_ = Converted_Graphics_7__13_
        Converted_Graphics_7__15_ = Converted_Graphics_7__12_
        Converted_Graphics_7__11_ = Converted_Graphics_7__15_
        Converted_Graphics_7__17_ = Converted_Graphics_7__11_
        Converted_Graphics_7__16_ = Converted_Graphics_7__17_
        Converted_Graphics_7__19_ = Converted_Graphics_7__16_
        Output_Layer = "copy_Layer"
        Export_Output_3_Layer = Output_Layer
        Export_Output_3_Layer__3_ = Export_Output_3_Layer
        Export_Output_3_Layer__4_ = Export_Output_3_Layer__3_
        Export_Output_3_Layer__2_ = Export_Output_3_Layer__4_
        Export_Output_3_Layer__6_ = Export_Output_3_Layer__2_
        Export_Output_3_Layer__5_ = Export_Output_3_Layer__6_
        Export_Output_3_Layer__7_ = Export_Output_3_Layer__5_
        Export_Output_3_Layer__9_ = Export_Output_3_Layer__7_
        Export_Output_3_Layer__11_ = Export_Output_3_Layer__9_
        Export_Output_3_Layer__8_ = Export_Output_3_Layer__11_
        Output_stuff = Export_Output_3_Layer__8_
        copy_Layer = Output_stuff

# Process: Add Field
        arcpy.AddField_management(Input_Line_Features, "StartX", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

# Process: Add Field (2)
        arcpy.AddField_management(Converted_Graphics_7, "StartY", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

# Process: Add Field (3)
        arcpy.AddField_management(Converted_Graphics_7__2_, "EndX", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Add Field (4)
        arcpy.AddField_management(Converted_Graphics_7__3_, "EndY", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Calculate Field
        arcpy.CalculateField_management(Converted_Graphics_7__4_, "StartX", "!Shape!.firstpoint.X", "PYTHON_9.3", "")

        # Process: Calculate Field (2)
        arcpy.CalculateField_management(Converted_Graphics_7__6_, "StartY", "!shape.firstpoint.y!", "PYTHON_9.3", "")

        # Process: Calculate Field (3)
        arcpy.CalculateField_management(Converted_Graphics_7__9_, "EndX", "!shape.lastpoint.x!", "PYTHON_9.3", "")

        # Process: Calculate Field (4)
        arcpy.CalculateField_management(Converted_Graphics_7__5_, "EndY", "!shape.lastpoint.y!", "PYTHON_9.3", "")

        # Process: Add Field (5)
        arcpy.AddField_management(Converted_Graphics_7__8_, "rad", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Calculate Field (5)
        arcpy.CalculateField_management(Converted_Graphics_7__10_, "rad", "Atn(( [StartX] - [EndX]) / ( [StartY] - [EndY] ))", "VB", "")

        # Process: Add Field (6)
        arcpy.AddField_management(Converted_Graphics_7__7_, "Angle", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Calculate Field (6)
        arcpy.CalculateField_management(Converted_Graphics_7__13_, "Angle", "[rad] * (180 / 3.14159265)", "VB", "")

        # Process: Add Field (7)
        arcpy.AddField_management(Converted_Graphics_7__12_, "EastWest", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Calculate Field (7)
        arcpy.CalculateField_management(Converted_Graphics_7__15_, "EastWest", "[StartX] - [EndX]", "VB", "")

        # Process: Add Field (8)
        arcpy.AddField_management(Converted_Graphics_7__11_, "NorthSouth", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Calculate Field (8)
        arcpy.CalculateField_management(Converted_Graphics_7__17_, "NorthSouth", "[StartY] - [EndY]", "VB", "")

        # Process: Add Field (9)
        arcpy.AddField_management(Converted_Graphics_7__16_, "Azimuth", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Make Feature Layer

        # Process: Select Layer By Attribute
        arcpy.SelectLayerByAttribute_management(Output_Layer, "NEW_SELECTION", "\"NorthSouth\" > 0")

        # Process: Calculate Field (9)
        arcpy.CalculateField_management(Export_Output_3_Layer, "Azimuth", "[Angle] + 180", "VB", "")

        # Process: Select Layer By Attribute (2)
        arcpy.SelectLayerByAttribute_management(Export_Output_3_Layer__3_, "NEW_SELECTION", "\"NorthSouth\" < 0 and \"EastWest\" > 0")

        # Process: Calculate Field (10)
        arcpy.CalculateField_management(Export_Output_3_Layer__4_, "Azimuth", "[Angle] + 360", "VB", "")

        # Process: Select Layer By Attribute (3)
        arcpy.SelectLayerByAttribute_management(Export_Output_3_Layer__2_, "NEW_SELECTION", "\"NorthSouth\" < 0 and \"EastWest\" < 0")

        # Process: Calculate Field (11)
        arcpy.CalculateField_management(Export_Output_3_Layer__6_, "Azimuth", "[Angle]", "VB", "")

        # Process: Select Layer By Attribute (4)
        arcpy.SelectLayerByAttribute_management(Export_Output_3_Layer__5_, "NEW_SELECTION", "\"NorthSouth\" = 0 and \"EastWest\" > 0")

        # Process: Calculate Field (12)
        arcpy.CalculateField_management(Export_Output_3_Layer__7_, "Azimuth", "270", "VB", "")

        # Process: Select Layer By Attribute (5)
        arcpy.SelectLayerByAttribute_management(Export_Output_3_Layer__9_, "NEW_SELECTION", "\"NorthSouth\" = 0 and \"EastWest\" < 0")

        # Process: Calculate Field (13)
        arcpy.CalculateField_management(Export_Output_3_Layer__11_, "Azimuth", "90", "VB", "")

        # Process: Delete Field
        arcpy.DeleteField_management(Export_Output_3_Layer__8_, "StartX;StartY;EndX;EndY;rad;Angle;EastWest;NorthSouth")

        # Process: Select Layer By Attribute (6)
        arcpy.SelectLayerByAttribute_management(Output_stuff, "NEW_SELECTION", "\"Azimuth\" < 365")

        # Process: Select
        arcpy.Select_analysis(copy_Layer, Output_Feature_Class, "Azimuth <= 365")
        arcpy.AddMessage ("Azimuth calculated")
        # Process: Add Field
        arcpy.AddField_management(Output_Feature_Class, "left", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Add Field (2)
        arcpy.AddField_management(copy_Select, "right", "DOUBLE", "", "", "", "", "NON_NULLABLE", "NON_REQUIRED", "")

        # Process: Calculate Field
        arcpy.CalculateField_management(copy_Select__2_, "right", "[Azimuth] + 90", "VB", "")

        # Process: Calculate Field (2)
        arcpy.CalculateField_management(splitline_shp__6_, "left", "[Azimuth] - 90", "VB", "")

        # Process: Spatial Join
        arcpy.SpatialJoin_analysis(Trial_FeatureVerticesToPoint, splitline_shp__5_, spatjoin_shp, "JOIN_ONE_TO_ONE", "KEEP_ALL", "OBJECTID \"OBJECTID\" true true false 10 Long 0 10 ,First,#,vertex,OBJECTID,-1,-1;POINT_X \"POINT_X\" true true false 0 Double 0 0 ,First,#,vertex,POINT_X,-1,-1;POINT_Y \"POINT_Y\" true true false 0 Double 0 0 ,First,#,vertex,POINT_Y,-1,-1;POINT_Z \"POINT_Z\" true true false 0 Double 0 0 ,First,#,vertex,POINT_Z,-1,-1;POINT_M \"POINT_M\" true true false 0 Double 0 0 ,First,#,vertex,POINT_M,-1,-1;Dist \"Dist\" true true false 0 Long 0 0 ,First,#,vertex,Dist,-1,-1;Id \"Id\" true true false 10 Long 0 10 ,First,#,C:\\temp\\azimuth.shp,Id,-1,-1;NEAR_FID \"NEAR_FID\" true true false 10 Long 0 10 ,First,#,C:\\temp\\azimuth.shp,NEAR_FID,-1,-1;NEAR_DIST \"NEAR_DIST\" true true false 19 Double 0 0 ,First,#,C:\\temp\\azimuth.shp,NEAR_DIST,-1,-1;NEAR_X \"NEAR_X\" true true false 19 Double 0 0 ,First,#,C:\\temp\\azimuth.shp,NEAR_X,-1,-1;NEAR_Y \"NEAR_Y\" true true false 19 Double 0 0 ,First,#,C:\\temp\\azimuth.shp,NEAR_Y,-1,-1;Azimuth \"Azimuth\" true true false 19 Double 0 0 ,First,#,C:\\temp\\azimuth.shp,Azimuth,-1,-1;left \"left\" true true false 19 Double 0 0 ,First,#,C:\\temp\\azimuth.shp,left,-1,-1;right \"right\" true true false 19 Double 0 0 ,First,#,C:\\temp\\azimuth.shp,right,-1,-1", "INTERSECT", "", "")

        # Process: Bearing Distance To Line
        arcpy.BearingDistanceToLine_management(spatjoin_shp, Left, "POINT_X", "POINT_Y", "Dist", "9001", "left", "9102", "0", "TARGET_FID", "PROJCS['Hartebeeshoek94_Lo31',GEOGCS['GCS_Hartebeesthoek_1994',DATUM['D_Hartebeesthoek_1994',SPHEROID['WGS_1984',6378137.0,298.257223563]],PRIMEM['Greenwich',0.0],UNIT['Degree',0.0174532925199433]],PROJECTION['Transverse_Mercator'],PARAMETER['False_Easting',0.0],PARAMETER['False_Northing',0.0],PARAMETER['Central_Meridian',31.0],PARAMETER['Scale_Factor',1.0],PARAMETER['Latitude_Of_Origin',0.0],UNIT['Meter',1.0]];-5623200 -10002100 10000;-100000 10000;-100000 10000;0.001;0.001;0.001;IsHighPrecision")

        # Process: Bearing Distance To Line (2)
        arcpy.BearingDistanceToLine_management(spatjoin_shp, Right, "POINT_X", "POINT_Y", "Dist", "9001", "right", "9102", "0", "TARGET_FID", "PROJCS['Hartebeeshoek94_Lo31',GEOGCS['GCS_Hartebeesthoek_1994',DATUM['D_Hartebeesthoek_1994',SPHEROID['WGS_1984',6378137.0,298.257223563]],PRIMEM['Greenwich',0.0],UNIT['Degree',0.0174532925199433]],PROJECTION['Transverse_Mercator'],PARAMETER['False_Easting',0.0],PARAMETER['False_Northing',0.0],PARAMETER['Central_Meridian',31.0],PARAMETER['Scale_Factor',1.0],PARAMETER['Latitude_Of_Origin',0.0],UNIT['Meter',1.0]];-5623200 -10002100 10000;-100000 10000;-100000 10000;0.001;0.001;0.001;IsHighPrecision")

        # Process: Merge
        # Process: Merge
        arcpy.Merge_management(WorkingDir + "spatjoin_BearingDistanceToLi.shp" + ";" + WorkingDir + "spatjoin_BearingDistanceToLi1.shp", mergedata, "POINT_X \"POINT_X\" true true false 0 Double 0 0 ,First,#,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi.shp,POINT_X,-1,-1,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi1.shp,POINT_X,-1,-1;POINT_Y \"POINT_Y\" true true false 0 Double 0 0 ,First,#,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi.shp,POINT_Y,-1,-1,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi1.shp,POINT_Y,-1,-1;Dist \"Dist\" true true false 0 Double 0 0 ,First,#,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi.shp,Dist,-1,-1,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi1.shp,Dist,-1,-1;left \"left\" true true false 0 Double 0 0 ,First,#,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi.shp,left,-1,-1;TARGET_FID \"TARGET_FID\" true true false 0 Double 0 0 ,First,#,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi.shp,TARGET_FID,-1,-1,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi1.shp,TARGET_FID,-1,-1;right \"right\" true true false 0 Double 0 0 ,First,#,C:\\temp\\DDP\\spatjoin_BearingDistanceToLi1.shp,right,-1,-1")

        # Process: Dissolve
        arcpy.Dissolve_management(mergedata, Output_Station_Lines, "TARGET_FID", "", "MULTI_PART", "DISSOLVE_LINES")


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.