1

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.

Buffer

Here are examples of the results using @FelixIP's steps below.

In areas where the buffers do not overlap it works great: enter image description here

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.

enter image description here

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

Results I have using Emil's script. enter image description here

4
  • 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. Sep 20, 2019 at 16:48
  • Do features in your centerline data always begin or end at the intersect points? Sep 20, 2019 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, 2019 at 20:17
  • What level license do you have? Sep 24, 2019 at 23:04

3 Answers 3

2

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.AddGeometryAttributes_management(Input_Features="MPART", Geometry_Properties="CENTROID", Length_Unit="", Area_Unit="", Coordinate_System="")
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:

enter image description here

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

enter image description here

6
  • 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, 2019 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, 2019 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, 2019 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, 2019 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, 2019 at 20:00
1

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
  3. Split your selected dissolve line by your points (Advanced license needed)
  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:
                    oids.add (oid)
        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: enter image description here

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

4
  • 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, 2019 at 21:16
  • @Mercator posting some sample data would help. Sep 26, 2019 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, 2019 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, 2019 at 15:46
0

Using the steps and script posted by @EmilBrundage 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.

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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