# Variable Buffer on Street Centerline using ArcGIS

I have a street centerline file where all road segments have a functional class of Arterial (A), Collector (C), Local (L). I also have a point file of each intersection.

I would like to do a crash analysis using 150' buffers at each intersection. The trick is that the buffer I want is not just a uniform 150' buffer off the intersection point. I want to buffer the centerlines for a specified length from the intersection point based on the functional class: A = 400', C = 250', L = 150'.

For example where an Arterial (A) intersects a Collector (C), I want a 150' buffer of a 400' line segment from the intersection point for the Arterial roadway and a 150' buffer of a 150' segment for the Local Roadway. The image shows what I am trying to get at.

I'm thinking I need to first find a way to create line features of lines with of the specified length coded with the functional class. That's where I'm stuck. Then it would be just a simple matter of buffering that result.

Anyone have any thoughts? I have ArcGIS 10.6.1, Network Analyst and Spatial Analyst.

Here are examples of the results using FelixIP steps below.

In areas where the buffers do not overlap it works great:

For intersections that are complex or composed of dual lines it does not work too well. Currently looking for a way to simplify these areas.

Here is a good example of the final buffer I'm trying to achieve:

Results I have using Emil's script.

• Do you need the buffers, or you you need to clip your roads by the distances indicated? If you need the buffers what width do you need it to be? From your pic it looks like about 100 feet. – Emil Brundage Sep 20 '19 at 16:48
• Do features in your centerline data always begin or end at the intersect points? – Emil Brundage Sep 20 '19 at 19:35
• It's kind of hard to explain, but looking at what was recently posted may help visualize it. My centerline data does not always begin or end at intersection points, but I can dissolve and planarize to get that. I need buffers (width varies by type of road) of the centerlines. The length of the centerline to buffer also varies by type of road. – Mercator Sep 24 '19 at 20:17
• What level license do you have? – Emil Brundage Sep 24 '19 at 23:04

Very tough indeed. So try this on roads dissolved by CLASS:

``````arcpy.Intersect_analysis(in_features="ROADS_Dissolved #", out_feature_class="C:/SCRATCH/SCRATCH.gdb/MPART", join_attributes="ALL", cluster_tolerance="-1 Unknown", output_type="POINT")
arcpy.Buffer_analysis(in_features="MPART", out_feature_class="C:/SCRATCH/SCRATCH.gdb/BUFFERS", buffer_distance_or_field="100 Meters", line_side="FULL", line_end_type="ROUND", dissolve_option="NONE", dissolve_field="", method="PLANAR")
``````

Make sure to use 0.5*largest of your lengths as buffer radius. Remove all fields from buffers but keep centroids' coordinates.

``````arcpy.Intersect_analysis(in_features="ROADS_Dissolved #;BUFFERS #", out_feature_class="C:/SCRATCH/SCRATCH.gdb/SEGMENTS", join_attributes="ALL", cluster_tolerance="-1 Unknown", output_type="INPUT")
arcpy.AddField_management(in_table="SEGMENTS", field_name="LENGTH", field_type="LONG", field_precision="", field_scale="", field_length="", field_alias="", field_is_nullable="NULLABLE", field_is_required="NON_REQUIRED", field_domain="")
``````

Transfer desired length to newly created field LENGTH. Start editing SEGMENTS, select them all and planarise using relevant tool from editor toolbar.

Run this expression on any numeric field in SEGMENTS:

``````def getSegment (shp,x,y,L):
pG=shp.lastPoint;pG.X=x;pG.Y=y
dist=shp.measureOnLine(pG)
return dist
shorten=shp.segmentAlongLine (0,L/2)
return shorten
#--------------------
getSegment( !Shape!, !CENTROID_X!, !CENTROID_Y!, !LENGTH! )
``````

Select all segments where result is >0 and flip them at once by using FLIP tool!. It will guarante that all segments will head away from intersection points:

Clear selection, Run above expression after removing 1st return line on field Shape:

• Thanks for posting this. I started running through the process, but wasn't wasn't quite successful. Couple of questions. At the beginning when doing the dissolve, do I want the Create-Multipart and Un-split options checked on or off? – Mercator Sep 23 '19 at 16:09
• You also mention to "Make sure to use 0.5*largest of your lengths as buffer radius". Are you referring to the largest length before or after the dissolve? – Mercator Sep 23 '19 at 16:17
• And last question. I'm not quite following what you mean in the last step "Run above expression after removing 1st return line on field Shape". – Mercator Sep 23 '19 at 16:19
• A) doesn't matter, because you planarize later b) something big but not causing buffer overlaps, 400 in case shown c) can you see return statements in expression posted? – FelixIP Sep 23 '19 at 19:09
• Thanks for your clarification. I was able to get successful results for the most part. Unfortunately my street centerline geography may be too detailed. If the the intersections are far enough apart it works great. However, the centerlines have dual lines (or complex intersections) in cases causing buffers to overlap. I'll post images of this above. I think with some cleanup of the MPART features, I may get what I want. – Mercator Sep 24 '19 at 20:00

This is a process that is best done through automation, and an advanced license helps. The basic work flow:

Divide your lines up for by their categories. For each category:

1. Select lines within desired line distance of points, such as A = 400
2. Dissolve selection with multipart
4. Select split lines intersecting points
5. Switch your selection and delete features - they don't touch your points
6. Feature Vertices to Points - both ends - your remaining dissolved lines. You'll be finding their end points to determine if they need to be flipped. Each line in your dissolved line feature class needs to start at the intersect point (Advanced license needed)
7. use and search cursor and geometry objects on your dissolved line feature class to determine which line features don't have their start point intersecting your intersection point feature class. These will need to be flipped with Flip Line (Standard license needed)
8. Create a new line feature class. Iterate through your dissolve line feature class with a search cursor. insert new features using polyline geometry objects and `segmentAlongLine`.

Here's an example with an input line feature class, an input intersect point feature class, a distance in meters, and an output feature class:

``````#input point intersections feature class
inPnts = r"C:\Users\E1B8\Desktop\E1B8\Workspace\Workspace.gdb\TestPoints"
#input line feature class
inLines = r"C:\Users\E1B8\Desktop\E1B8\Workspace\Workspace.gdb\TestLines"
#distance of line from intersection in meters
distMeters = 100
#output line feature class
outFc = r"C:\Users\E1B8\Desktop\E1B8\Workspace\Workspace.gdb\OutFc"

print "importing"
import arcpy
import os

def ClipLinesByPointAndDistance (inPnts, inLines, distMeters, outFc):
print ("getting scratch workspace")
workspace = arcpy.env.scratchGDB
garbage = []

try:
print ("creating line layer")
lnLyr = "linLyr"
i = 0
while arcpy.Exists (lnLyr):
lnLyr = "linLyr{}".format (i)
i += 1
lnLyr = arcpy.MakeFeatureLayer_management (inLines, lnLyr) [0]
garbage += [lnLyr]

print ("selecting lines within {} meters of points".format (distMeters))
sel = arcpy.SelectLayerByLocation_management (lnLyr, "", inPnts,
"{} meters".format (distMeters))

print ("{} features selected".format (arcpy.GetCount_management (lnLyr) [0]))

print ("dissolving lines")
disFc = arcpy.CreateUniqueName ("dis", workspace)
disFc = arcpy.Dissolve_management (lnLyr, disFc) [0]
garbage += [disFc]

print ("splitting lines at points")
spLnFc = arcpy.CreateUniqueName ("spLn", workspace)
spLnFc = arcpy.SplitLineAtPoint_management (disFc, inPnts,
spLnFc, "1 feet") [0]

print ("creating split lines layer")
spLyr = "spLyr"
i = 0
while arcpy.Exists (spLyr):
spLyr = "spLyr{}".format (i)
i += 1
spLyr = arcpy.MakeFeatureLayer_management (spLnFc, spLyr) [0]
garbage += [spLyr]

print ("selecting lines by points")
sel = arcpy.SelectLayerByLocation_management (spLyr, "", inPnts,
"1 feet")
print ("{} features selected".format (arcpy.GetCount_management (spLyr) [0]))

print ("switching selection")
arcpy.SelectLayerByAttribute_management(spLyr, "SWITCH_SELECTION")

if arcpy.Describe (spLyr).FIDSet:
print ("deleting rows")
with arcpy.da.UpdateCursor (spLyr, "*") as curs:
for row in curs: curs.deleteRow ()

print ("clearning selection")
arcpy.SelectLayerByAttribute_management(spLyr, "CLEAR_SELECTION")

print ("line vertices to points")
l2pFc = arcpy.CreateUniqueName ("l2p", workspace)
arcpy.FeatureVerticesToPoints_management (spLyr, l2pFc, "BOTH_ENDS")
garbage += [l2pFc]

print ("creating line to point layer")
l2pLyr = "l2pLyr"
i = 0
while arcpy.Exists (l2pLyr):
l2pLyr = "l2pLyr{}".format (i)
i += 1
l2pLyr = arcpy.MakeFeatureLayer_management (l2pFc, l2pLyr) [0]
garbage += [l2pLyr]

print ("selecting vertices intersecting points")
sel = arcpy.SelectLayerByLocation_management (l2pLyr, "", inPnts,
"1 feet")
print ("{} features selected".format (arcpy.GetCount_management (l2pLyr) [0]))

print ("switching selection")
arcpy.SelectLayerByAttribute_management(l2pLyr, "SWITCH_SELECTION")

print ("getting endpoints")
endPnts = set ()
with arcpy.da.SearchCursor (l2pLyr, "SHAPE@XY") as curs:
for xy, in curs: endPnts.add (xy)
print ("{} end points".format (len (endPnts)))

print ("getting spatial reference")
sr = arcpy.Describe (spLyr).spatialReference

print ("adding lines to new feature class")
oids = set ()
with arcpy.da.SearchCursor (spLyr, ["SHAPE@", "OID@"]) as curs:
for geom, oid in curs:
lastPnt = geom.lastPoint
if not (lastPnt.X, lastPnt.Y) in endPnts:
if oids:
print ("selecting lines to flip")
sql = "{} in {}".format (arcpy.Describe (spLyr).OIDFieldName,
tuple (oids))
sel = arcpy.SelectLayerByAttribute_management (spLyr, "", sql)
print ("{} features selected".format (arcpy.GetCount_management (spLyr) [0]))

print ("flipping lines")
arcpy.FlipLine_edit (spLyr)

print ("clearning selection")
arcpy.SelectLayerByAttribute_management(spLyr, "CLEAR_SELECTION")

print ("creating output feature class")
outPath, outName = os.path.split (outFc)
outName = arcpy.ValidateTableName (outName, outPath)
outFc = arcpy.CreateUniqueName (outName, outPath)
outName = os.path.basename (outFc)
outFc = arcpy.CreateFeatureclass_management (outPath, outName, "POLYLINE",
spatial_reference = inLines) [0]

dist = sr.metersPerUnit * distMeters
with arcpy.da.InsertCursor (outFc, "SHAPE@") as iCurs:
with arcpy.da.SearchCursor (spLyr, "SHAPE@") as sCurs:
for geom, in sCurs:
row = (geom.segmentAlongLine (0, dist),)
iCurs.insertRow (row)

print ("cleaning up")
for trash in garbage:
arcpy.Delete_management (trash)

print ("created:")
print (outFc)

except Exception as e:
print ("error!")
print ("cleaning up")
for trash in garbage:
arcpy.Delete_management (trash)
raise e

ClipLinesByPointAndDistance (inPnts, inLines, distMeters, outFc)
``````

Results:

The rest of the process involves buffering and dissolving line outputs.

• Emil, thanks so much for posting all of this. This looks to be exactly what I'm looking for. The only hitch I ran into so far is that the output feature class is only created in one direction from the intersecting point. I suspect this might be because our center-lines represent both directions of travel with just one line. I'll post a screen capture in my original post if you want to see it. – Mercator Sep 26 '19 at 21:16
• @Mercator posting some sample data would help. – Emil Brundage Sep 26 '19 at 22:08
• I placed a copy of the GDB (zipped) on our my FTP site: ftp.cityofmadison.com/MPO/Dan/GeoStack. Username: mpoftp Password: Maps4you! – Mercator Sep 27 '19 at 15:42
• FAC2004 is the Roadway Classification. OneWay is the direction of travel: F (from), T (to), B (both). Thanks much for willing to take a look, – Mercator Sep 27 '19 at 15:46

Using the steps and script posted by Emil has worked (for the most part). I added one extra step because my centerlines are modeled different.

I ran the script on the first category of lines to get the first result which are the lines on one side of the intersection. The step I then added was to make a copy of the first category of lines, Flip the direction of all, then run the script on those to get the lines on the other side of the intersection.

At first look, all appeared correct, but on close inspection I found that some line segments did not get created in the correct direction. In the second step, some of the segments were created in the same direction as the first set. Here is a screen capture highlighting those areas. I'm trying to figure out what is different about those few line segments. Odd thing is, it is mostly roads running north-south that are not correct.