Distance between points along a polyline

20321
24
02-01-2015 05:36 AM
JodyCamille
New Contributor II

I have points marking tagged fish locations along a river. How do I measure the distance between each of those successive points?

0 Kudos
24 Replies
DanPatterson_Retired
MVP Emeritus

You don't specify the format of your data...but lets assume that the points are in sequential order in something like a shapefile's table.  You can create a new field in your table (ie Dist type double etc) and use one of the code blocks specifying a Python parser and copying

dist_between(!Shape!)  or  dist_cumu(!Shape!)

depending upon whether you need inter-point distance or cumulative distance.  (Note...haven't tested in a long time)

EDIT   this will give you the point to point distance, if you points form part of the polyline, convert the polyline to points first

otherwise you will get the Euclidean distance between the points and not the points along the polyline

for interpoint distance

I have added a function to produce points along a polyline based upon an increment if that will help

''' input shape field: returns distance between points
dist_between(!Shape!)    #enter into the expression box'''
x0 = 0.0;  y0 = 0.0;  distance = 0.0
def dist_between(shape):
  global x0;  global y0;  global distance
  x = shape.firstpoint.X;  y = shape.firstpoint.Y
  if x0 == 0.0 and y0 == 0.0:
    x0 = x; y0 = y
  distance = math.sqrt((x - x0)**2 + (y - y0)**2)
  x0 = x;  y0 = y
  return distance

for cumulative distance

''' input shape field: returns cumulative distance between points
dist_cumu(!Shape!)    #enter into the expression box'''
x0 = 0.0;  y0 = 0.0;  distance = 0.0
def dist_cumu(shape):
  global x0;  global y0;  global distance
  x = shape.firstpoint.X;  y = shape.firstpoint.Y
  if x0 == 0.0 and y0 == 0.0:
    x0 = x; y0 = y
  distance += math.sqrt((x - x0)**2 + (y - y0)**2)
  x0 = x;  y0 = y
  return distance

# create points along polyline example
''' input shape field, value (distance or decimal fraction, 0-1), use_fraction (True/False), XorY (X or Y):
    returns a point x meters or x  decimal fraction along a line, user specifies whether X or Y coordinates
pnt_along(!Shape!, 100, False, 'X')    #eg.  calculate X coordinate 100 m from start point '''
def pnt_along(shape, value, use_fraction, XorY):
  XorY = XorY.upper()
  if use_fraction and (value > 1.0):
    value = value/100.0
  if shape.type == "polygon":
    shape = shape.boundary()
  pnt = shape.positionAlongLine(value,use_fraction)
  if XorY == 'X':  return pnt.centroid.X
  else:  return pnt.centroid.Y
JodyCamille
New Contributor II

I appreciate your comments, Dan.

I have a point shapefile of tagged fish locations. I snapped those points to a polyline shapefile of the rivers that the fish migrated on. Should I convert the river (polyline) shapefile to a point file and calculate your code on a distance field in that newly converted point file?

Thank you,

Jody

0 Kudos
XanderBakker
Esri Esteemed Contributor

Did you try the code I posted? It does not require you to snap your points to the river...

0 Kudos
DanPatterson_Retired
MVP Emeritus

No...as indicated, the interpoint distance examples I suggested would give you the Euclidean distance between the points, I think you want points to be along the polyline after they are snapped, then distance determined along the polyline...I would go with Xander's solution...just change the input files and run his script in a script editor

0 Kudos
NguyenTien_Viet
New Contributor

Hi Dan

What should I do if the polyline is complicated  (ie: a grid) and want to measure the shortest distance between 2 points along that grid? I am using ArcGIS 10.2 and cannot use Nework Analyst.

0 Kudos
MohamedMagdy
New Contributor III

If these points located along river line you can use linear referenceing tool to measure the route length along river

ArcGIS Help 10.1

0 Kudos
XanderBakker
Esri Esteemed Contributor

In case you have a featureclass with the river (1 line) and a point featureclass with the fishing locations, then you should snap those points to the line, obtain the position of the snapped points on the line, define the distance from the start of the river, sort the location on distance from start and determine the distance between consecutive points. In python this would look like this:

import arcpy

fc_pnt = r"C:\Forum\Fishing\test.gdb\fishingpoints"
fc_line = r"C:\Forum\Fishing\test.gdb\river"
fld_diststart = "DistStart"
fld_distprevious = "DistPrev"

# add fields to point featureclass in case they do not exist
add_flds = [fld_diststart, fld_distprevious]
for fld in add_flds:
    if len(arcpy.ListFields(fc_pnt, wild_card=fld)) == 0:
        arcpy.AddField_management(fc_pnt, fld, "DOUBLE")

# get first (only?) polyline from lines featureclass
polyline = arcpy.da.SearchCursor(fc_line, ("SHAPE@",)).next()[0]

dct = {}
flds = ("SHAPE@", "OID@", fld_diststart)
with arcpy.da.SearchCursor(fc_pnt, flds) as curs:
    for row in curs:
        pnt = row[0]
        oid = row[1]
        diststart = polyline.queryPointAndDistance(pnt, False)
        dct[oid] = diststart[1]
del curs, row

# sort on distance and determine distance between points
dct2 = {}
cnt = 0
for oid, diststart in sorted(dct.items(), key=lambda x: x[1]):
    cnt += 1
    if cnt == 1:
        distbetween = 0
        distprev = diststart
    else:
        distbetween = diststart - distprev
        distprev = diststart
    dct2[oid] = distbetween

# update the points fc
flds = ("OID@", fld_diststart, fld_distprevious)
with arcpy.da.UpdateCursor(fc_pnt, flds) as curs:
    for row in curs:
        oid = row[0]
        row[1] = dct[oid]
        row[2] = dct2[oid]
        curs.updateRow(row)
del curs, row

The result will look like this:

Fishing.png

KirstenLawrence
New Contributor III

This looks like a good script for my needs right now. I have many points that are associated with many different lines. They don't necessarily fall on the lines but I know which line they should be associated with by a unique line  ID. It seems I can use your script above but I need to somehow find the right line for the right points. Seems like maybe I should set up a cursor first for the lines that accesses the unique line ID field, and below set up a cursor for the points that accesses the unique line ID field that is attached to the points, and when those values match, then do the distance calculations? The data is multidimensional, where I have groups of points associated with a vehicle, and there are time values for each point as well, that essentially represent trips along each line. I've already sorted on the vehicle ID, the line ID, the time and the coords and now want to calculate the distance, and ultimately the speed of the vehicle between each point.

0 Kudos
RichardFairhurst
MVP Honored Contributor

Your are wrong on how you should approach this problem.  Embedded cursors are virtually never the solution to any problem.  Use a dictionary to make the match between the line IDs and the points and then process one cursor normally.  The larger your datasets get the embedded cursors slow down exponentially, while Dictionaries have a linear performance impact.  So for 10000 points on 100 lines embedded cursors will perform at least 100 times slower than a dictionary lookup.

Although I have not tested the code below, I believe it will work.  The main part I am unsure of is the sort of dct that creates dct2.  In any case, once all bugs are worked out this code will perform wonderfully.

import arcpy  
  
fc_pnt = r"C:\Forum\Fishing\test.gdb\fishingpoints"  
fc_line = r"C:\Forum\Fishing\test.gdb\river"  
fld_diststart = "DistStart"  
fld_distprevious = "DistPrev"  
  
# add fields to point featureclass in case they do not exist  
add_flds = [fld_diststart, fld_distprevious]  
for fld in add_flds:  
    if len(arcpy.ListFields(fc_pnt, wild_card=fld)) == 0:  
        arcpy.AddField_management(fc_pnt, fld, "DOUBLE")  

fields = ["LINE_ID", "SHAPE@"]

# Create a dictionary of lines with their geometry
valueDict = {r[0]:(r[1:]) for r in arcpy.da.SearchCursor(fc_line, fields)}
  
dct = {}  
flds = ["LINE_ID", "SHAPE@", "OID@", fld_diststart]
with arcpy.da.SearchCursor(fc_pnt, flds) as curs:  
    for row in curs:
        line = row[0]
        if line in valueDict:
            polyline = valueDict[line]
            pnt = row[1]  
            oid = row[2]  
            diststart = polyline.queryPointAndDistance(pnt, False)  
            dct[(line, oid)] = diststart[1]
del curs, row  

# sort on distance and determine distance between points  
dct2 = {}  
cnt = 0
prevline = -1  
for line_oid, diststart in sorted(dct.items(), key=lambda x: x[1]):  
    cnt += 1  
    if cnt == 1:  
        distbetween = 0  
        distprev = diststart
        prevline = line_oid[0] 
    elif line_oid[0] != prevline: 
        distbetween = 0  
        distprev = diststart
        prevline = line_oid[0] 
    else:  
        distbetween = diststart - distprev  
        distprev = diststart  
    dct2[line_oid] = distbetween  
  
# update the points fc  
flds = ("LINE_ID", "OID@", fld_diststart, fld_distprevious)  
with arcpy.da.UpdateCursor(fc_pnt, flds) as curs:  
    for row in curs:  
        line = row[0]
        oid = row[1]  
        if (line, oid) in dct:
            row[2] = dct[(line, oid)]  
            row[3] = dct2[(line, oid)]  
            curs.updateRow(row)  
del curs, row