ArcPy Solution for Copy Parallel

This question has been marked as a duplicate multiple times now. Please read the entire question before flagging it as a duplicate. The previous "duplicates" for questions about copy parallel do not produce the same result as copy parallel tool.

I am looking for an arcpy process that EXACTLY replicates the copy parallel editor tool in ArcGIS 10.3. This is not the same thing as simply offsetting lines with arcpy. That is simple, but it does not replicate the copy parallel tool.

I am using ArcMap with a STANDARD licence. Advanced licence functions won't work as a solution (buffer with flat endcaps)

As an example, I have run the script from this answer https://gis.stackexchange.com/a/181545/56638. I used an offset of x = 20 and y = -20 which produces the red lines in the image below (yellow line is original line).

Then I used the copy parallel tool and set the offset to 20. That produced the green lines in the image. These lines are exactly 20m from the original line.

The problem is that when offsetting with the arcpy solution, the angle of the line is not considered. Therefore, the red lines (produced using arcpy method) are more than 20m from the original line. If you want the offset line to be a certain distance from the original line, you would need to know the angle of the line and calculate the distances to offset x and y based on trig. I am struggling to come up with a solution for this.

Another example with the arcpy solution, with offset set to x = 20 and y = 0. This demonstrates the problems that bends cause and highlights the need to take into account the angle or bearing of the original line.

This is the code that I have tried so far:

tmpwrkspace = r'C:\Users\jasbal\Documents\ArcGIS\Default.gdb'
tempFeatureClass = tmpwrkspace + "\temp_rline"
tempFeatureClass2 = tmpwrkspace + "\temp_lline"
fc_line = r'C:\Users\jasbal\Documents\ArcGIS\Default.gdb\RWP_Rev0_4'
if arcpy.Exists(tempFeatureClass):
arcpy.Delete_management(tempFeatureClass)
arcpy.CopyFeatures_management(fc_line, tempFeatureClass)
if arcpy.Exists(tempFeatureClass2):
arcpy.Delete_management(tempFeatureClass2)
arcpy.CopyFeatures_management(fc_line, tempFeatureClass2)
xOffset = 20
yOffset = -20
xOffset2 = -20
yOffset2 = 20
with arcpy.da.UpdateCursor(tempFeatureClass, ["SHAPE@XY"]) as cursor:
for row in cursor:
cursor.updateRow([[row[0][0] + xOffset, row[0][1] + yOffset]])
del cursor
with arcpy.da.UpdateCursor(tempFeatureClass2, ["SHAPE@XY"]) as cursor:
for row in cursor:
cursor.updateRow([[row[0][0] + xOffset2, row[0][1] + yOffset2]])
del cursor


My idea is to calculate the angle of the line and to use that in a trig function to calculate x and y offset based on the distance I want to be from the original line.

Something like this:

distance = arcpy.GetParameterAsText(0)
angle = "Angle Field" obtained with search cursor
xOffset = distance/cos(angle)
yOffset = distance * tan(angle)


I am not sure if I have my trig correct. I need some advice on this part.

I am not looking for a complete code snippet for copy parallel. The only part I need to figure out is how to calculate the offsets correctly for a set distance from the original line based on the line angle.

• Have you considered replicating the solution in this answer gis.stackexchange.com/a/25950/64785 ? – Midavalo May 16 '17 at 5:33
• The basic answer is there is no arcpy Copy Parallel - this has been asked many times, and that is the only real answer to that. It could probably be replicated based on the answer in my comment above - have you tried that? Can you include a code snippet showing what you've tried? – Midavalo May 16 '17 at 5:35
• I do not have an advanced license so that does not provide the solution I need. I am editing my question now. – jbalk May 16 '17 at 5:35
• There is an ArcGIS Idea asking for an Arcpy Copy Parallel function. It has a status of "Reviewed" (whatever that means) geonet.esri.com/ideas/5691 – Midavalo May 16 '17 at 5:36
• Have you looked at gis.stackexchange.com/questions/224637/… ? I know it says "ArcGIS Pro" but the solution may work with desktop. – Midavalo May 16 '17 at 5:42

After trying a few different methods to achieve a solution, I have settled on a modified version of the code found here: https://gis.stackexchange.com/a/229386/56638

Here is the full code from FelixIP's answer:

import arcpy, math
infc=r'..\SCRARCH\clone.shp'

def CopyParallel(plyP,sLength):
part=plyP.getPart(0)
lArray=arcpy.Array();rArray=arcpy.Array()
for ptX in part:
dL=plyP.measureOnLine(ptX)
ptX0=plyP.positionAlongLine (dL-0.01).firstPoint
ptX1=plyP.positionAlongLine (dL+0.01).firstPoint
dX=float(ptX1.X)-float(ptX0.X)
dY=float(ptX1.Y)-float(ptX0.Y)
lenV=math.hypot(dX,dY)
sX=-dY*sLength/lenV;sY=dX*sLength/lenV
leftP=arcpy.Point(ptX.X+sX,ptX.Y+sY)
rightP=arcpy.Point(ptX.X-sX, ptX.Y-sY)
array = arcpy.Array([lArray, rArray])
section=arcpy.Polyline(array)
return section

with arcpy.da.UpdateCursor(infc,("Shape@","Width")) as cursor:
for shp,w in cursor:
twoLines=CopyParallel(shp,w)
cursor.updateRow((twoLines,w))


This code create two parallel lines, one on either side of the original line. I only need to offset my line in one direction, so I modified the code as follows:

Make sure you run this on a copy of your data. It does not preserve the original input lines.

import arcpy, math
infc=r'C:\temp\test.shp'


Create two functions, one for left, and one for right side of line:

Left side:

def CopyParallelL(plyP,sLength):
part=plyP.getPart(0)
lArray=arcpy.Array()
for ptX in part:
dL=plyP.measureOnLine(ptX)
ptX0=plyP.positionAlongLine (dL-0.01).firstPoint
ptX1=plyP.positionAlongLine (dL+0.01).firstPoint
dX=float(ptX1.X)-float(ptX0.X)
dY=float(ptX1.Y)-float(ptX0.Y)
lenV=math.hypot(dX,dY)
sX=-dY*sLength/lenV;sY=dX*sLength/lenV
leftP=arcpy.Point(ptX.X+sX,ptX.Y+sY)
array = arcpy.Array([lArray])
section=arcpy.Polyline(array)
return section


Right:

def CopyParallelR(plyP,sLength):
part=plyP.getPart(0)
rArray=arcpy.Array()
for ptX in part:
dL=plyP.measureOnLine(ptX)
ptX0=plyP.positionAlongLine (dL-0.01).firstPoint
ptX1=plyP.positionAlongLine (dL+0.01).firstPoint
dX=float(ptX1.X)-float(ptX0.X)
dY=float(ptX1.Y)-float(ptX0.Y)
lenV=math.hypot(dX,dY)
sX=-dY*sLength/lenV;sY=dX*sLength/lenV
rightP=arcpy.Point(ptX.X-sX, ptX.Y-sY)
array = arcpy.Array([rArray])
section=arcpy.Polyline(array)
return section


Run the update cursor and call the appropriate function:

with arcpy.da.UpdateCursor(infc,("Shape@","Width")) as cursor:
for shp,w in cursor:
LeftLine=CopyParallelL(shp,w)
cursor.updateRow((LeftLine,w))


Here is an example output using the left side function: