More adventures in overlay: creating a street intersection list

If you work in local government, your GIS shop is most likely responsible for maintaining a street centerline database.  And if you’re the keeper of the street centerline database, you’ve probably had requests to create a street intersection list—a point feature class with attributes that list the names of the cross-streets. Typical uses for a street intersection list is inventory, inspection, and management of signs and other assets, as well as for input to E911 systems.  They are also useful for quality assurance of the street centerline database.

Creating a street intersection list is an interesting exercise in overlaying mixed feature types, in this case points and lines, as well as dealing with one-to-many relationships, so I thought it worthwhile to work through the steps as part of this series of blogs about overlay.


You can download the data and models used in this post here.


The basic strategy is to first create a feature class of points where streets intersect. Then, using some sort of spatial search, construct a list of all the street names connected to each point. The last step can be done using the Spatial Join tool (in previous posts, I showed how to use Spatial Join and its Field Map parameter to create such an attribute, and I’ll use the same technique here).

For my first attempt at creating a point feature class, I used the Feature Vertices To Point tool, which seems to be the favorite starting point according to most message boards. Feature Vertices To Point creates a point for the first and last vertex of every street feature. The problem with this method is that it resulted in many more points than I needed. For example:

  • One point is created for each line connected at an intersection. That means for a four-way intersections, four identical points are created where only one is needed. These extra points just get in the way and can slow down processing.
  • Points are created at the ends of dead-end streets, and I’m not interested in dead-ends since they aren’t intersections—there’s only one connected street.
  • Finally, points are created where two street features with the same name connect. This splitting of street features is typically due to an attribute change, such as a disconnect in address ranges as shown here, or a change in road class or some other non-name attribute. The term I use for these intersections is ‘pseudo-junction’. Since the street name doesn’t change at these pseudo-junctions, I’m not interested in them either. (The split is hardly ever due to a change in street name—except here in Redlands where we seem to have lots of streets that change names for no apparent reason.)

Although the Feature Vertices To Point method gave me a starting set of points, I wanted a method that gave me a smaller starting set and less artifacts (dead-ends and pseudo-junctions).

Unsplitting lines

The first step is to run the Unsplit Line tool using the street name field for the Dissolve Field parameter. Unsplit Line merges connected line features with the same street name, as shown below, where the red points represent the endpoints of the line features. The output has far fewer line segments then the input, and pseudo-junctions are removed.

Create intersections using the Intersect tool

The output of Unsplit Line is input to the Intersect tool, as shown in the model snippet below. Intersect creates points where line segments touch.

  • When Intersect is run with one input feature class, the feature class is intersected with itself. You don’t need to (or want to) input the same feature class twice.
  • The Output type is POINT, meaning that the output features are points created where two streets intersect. The default value for Output type is INPUT, and if you specify INPUT, the output will be an empty line feature class.
  • In this instance, the JoinAttributes is set to ONLY_FID, meaning that no attributes from the input features will be carried to the output feature class.

Removing duplicates

The output of Intersect is a feature class with duplicate points at every intersection. Note that there are no points created for dead-ends, nor are there any pseudo-junctions.  To remove the duplicates, keeping only one of the duplicate points, use the Delete Identical tool as shown in the model snippet below.  For the Fields(s) parameter in Delete Identical, use the Shape field.  This deletes all but one of the points that share the same x,y location. (The Delete Identical tool requires an Advanced license.  If you don’t have Advanced, use the Collect Events tool instead.)

Finding the intersecting streets with Spatial Join

The next step is to use the Spatial Join tool to create an attribute on the intersection points that lists all the streets that connect to the intersection.

This usage of Spatial Join depends upon defining a new output field (“Names”) in the Field Map of Join Features parameter. The first thing to do is to remove all the input fields from the Field Map control by highlighting the field and clicking the remove button. Then follow these steps, all illustrated below.

  1. Right-click within the Field Map control and choose Add Output Field. The Output Field Properties dialog box opens.
  2. In the Output Field Properties dialog box, name the field “Names”.   Do not use the same name as your input street name field (the one used in the Unsplit Lines tool). Choose Text for the Type of field, make its Length big enough to hold the concatenated field names (I chose 80).  Choose Join for the Merge Rule and provide a delimiter ‘ & ‘  (space/ampersand/space). Click OK.
  3. The field map will show this new output field. You now have to tell it what fields you want to join by right-clicking Names and choosing Add Input Field. This opens the Add Input dialog box.
  4. In the Add Input dialog box, select the street name field (FULLNAME)  and click OK. The field map will now show the new field along with the input field that will create the values for the new field.
  5. For the Join Operation parameter, choose JOIN_ONE_TO_ONE.  For the Match Option, choose INTERSECT or WITHIN_A_DISTANCE.  For either option, you can specify a Search Radius.  One meter is plenty.

The output of Spatial Join is shown below.  For each point, the intersecting streets are found in the Names attribute, each street separated by the delimiter (an ampersand ‘ & ‘ in this case).  The Spatial Join tool creates the Join_Count attribute for you.  This is the count of intersecting streets.

Exploring the results

The output of Intersect is a multipoint feature class, meaning that one feature can contain more than one point.  The majority of the intersections have just one point, but if a feature contains more than one point, it means that the streets intersect more than once, as shown in the figure to the left.

To find features with more than one point, use the Add Field tool to add an integer field, and use the Calculate Field tool with the following expression to find the number of points in the multipoint:


Any feature with two or more parts is a loop. The figure to the right shows the Calculate Field dialog. I named my field NumberOfIntersections. To display loops, I select intersections where NumberOfIntersections is greater than one.

Finding potential errors

The intersection features can help you find potential errors in the street features. For example, the map below shows an apartment complex where all the streets are named SUNRISE. This may or may not be a street name coding error, but to find these intersections and review them, you need a better way then visually scanning the table looking for issues.

To facilitate finding errors, I calculated a new field, StreetCount, containing the number of unique street names at an intersection.

The expression for Calculate Field is:


The getLength() routine code is as follows:

def getLength(inNames):
  # Create a list (array) by splitting the string on its delimiter
  asList = inNames.split(" & ")

  # Use the set operator to create a new list with unique names
  uniqueList = set(asList)

  # Return the number of entries in the set
  return len(uniqueList)

To find potential issues, select records where StreetCount is not equal to Join_Count, as shown below.  Join_Count is the number of intersecting features while StreetCount is the number of unique street names. It they’re not equal, then there are multiple features with the same name at the intersection and I need to investigate further.

Ramps and limited access highways

You probably don’t want to create intersections where surface streets cross limited access highways, or where ramps connect limited access highways and surface streets.  You can avoid processing these streets by selecting just those centerlines you’re interested in, as shown in the model snippet below.

This entry was posted in Analysis & Geoprocessing and tagged , . Bookmark the permalink.

Leave a Reply


  1. djc7265019 says:

    This is great! My supervisor asked me yesterday to find him traffic signals within intersections which contain county roads. Doesn’t appear I can do this with 9.3. Can’t hardly wait until the city upgrades me to 10.

    City of tallahassee
    Public Works

  2. thanhtinhnguyen says:

    Impressive! I have been using GIS for 7 years now and do the spatial join on a daily basic but I didn’t know about the join attribute options in the “Finding Intersecting streets with spatial join” ! I always have to do it a very very long way.
    I have a question, maybe you will have a better way to do it. I need to extract sections of streets. What I have is a spreadsheet with hundreds of street names and from intersection 1 to intersection 2. What need to produce is the polylines of street split at the from intersection 1 to intersection 2
    Thank you very much,

  3. Dale Honeycutt says:

    In response to Serena’s comment above, I wrote up a methodology to extract sections of streets between two intersections. I shared it on my account as a geoprocessing sample. The sample is a zip file that contains a PDF describing this methodology. Here’s the link to the item.

    • hherrmann says:

      This was a huge help up to the last page or so. The interation process….it didn’t seem complete. I was never able to figure it out. I’m just a lowly old Specialist though so that may be why. Otherwise, nice share. :)

  4. linzey.davis13 says:

    Everything else was fantastic by the way! Helped me out so much, this was something my boss and I talked about and I was able to figure out so much faster! Thank you very much!
    One little request though, could you possibly explain a litter further on the Ramps and Highways that you have touched on here?

  5. Dale Honeycutt says:

    I’m assuming you’re talking about that last bit where we leave out ramps and highways. To be honest, I didn’t think of this until I was about to post the blog — the data I was working with didn’t have any interstate highways (limited access) or ramps connecting surface streets to the interstates. So I kinda threw this in. When finding street intersections, I figured that in most cases. you don’t want to create an intersection point where a ramp connects to a surface street or interstate, nor do you want an intersection where a surface street crosses over an interstate. So, we just leave interstates and ramps out of the processing. This is done at the beginning of the model where the Make Feature Layer tool is used with the road centerlines as input. Note the Expression parameter — it’s selecting all streets with a ROADCLASS of “Major Arterial”, “Minor Arterial” and so forth (but not selecting ramps or limited access highways) (alternatively, remove ramps and limited access highways from the selection). Most street datasets have some sort of ROADCLASS attribute like this (this dataset is from our Local Government data model and it has a ROADCLASS attribute). The output of Make Feature Layer is all features except ramps and interstate highways. This output is fed to the Unsplit Line tool and everything proceeds as outlined above.

    This is an optional step — it may be that you want intersections with ramps and limited access highways in your intersection list. That is, you’ll get intersections like “REDLANDS BLVD & INTERSTATE 10″ and “REDLANDS BLVD & RAMP”

    Hope this helps

  6. shitijmehta says:

    Very nice blog indeed. Simpler way to find the street intersection list.

  7. recurvata says:

    That is really cool. Great post.

  8. saraemani says:

    Thank You very much for sharing your knowledge. I have to create an intersections list from Centerline network, and was able to do so by following the above guidelines.

  9. zhangiben says:

    I was very excited to use your method to process the street intersections but stuck at the Spatial Join. I have tried many times to join all the street names at intersections in the attribute table but could only get one street name in the table. I carefully followed your five steps in your illustration and retraced many times and still didn’t work. What do you think I did wrong? I am using the trial version of desktop 10.1.

    Thank you,

    • Dale Honeycutt says:

      I have no clue what you might be doing wrong. Perhaps you can send me a small subset of your data to my email address. My profile page has my email address. I also sent you an email at your registered address.

      • Dale Honeycutt says:

        We discovered that Wei’s centerline shapefile had a bizarre issue with the field containing the street name (STREET_NAME). The workaround was to create a new text field STREET_NAME2 and copy the contents of STREET_NAME into it using the Calculate Field tool. STREET_NAME2 was then used in Spatial Join.

        I’m still investigating what’s wrong with the shapefile. Just to clarify, shapefiles are an open format and there are third-party software packages that create shapefiles. Unfortunately, these other packages sometimes don’t create correct shapefiles. The theory I’m working on is that Wei’s shapefile was created by non-Esri software and the .dbf file has an improper character encoding setting. Copying the shapefile to a file geodatabase feature class doesn’t solve the problem, nor does copying it to another shapefile (copying usually solves most of these problems). Only copying the contents to a new field solves it. There doesn’t appear to be any escape characters in the field contents (sometimes text fields have a “\n” or “\r” — carriage return or line feed characters — as the last character). Sigh.

        • says:

          I had a similar issue as Wei, but my mistake was allowing the intersection points to be populated with the attribute STREET_NAME, same as my centerline file. The Spatial Join simply populated “Names” with the STREET_VALUE from each intersection point. I deleted the STREET_NAME attribute from the intersection points via Open Attribute Table before starting the Spatial Join tool. The Spatial Join then successfully populated “Names” with the STREET_NAME value from each intersecting centerline. And, terrific blog, Dale!

          • Dale Honeycutt says:

            You NAILED it! It looks like it’s a bug (?) with Spatial Join — if you have the same field names in the target and join features, then Spatial Join gets confused and won’t perform the join merge rule properly. I’m still investigating… there may be a valid reason for this behavior (not willing to call it a bug yet — behavior like this usually has a reason).

  10. langdonms says:

    Thank you for the well written post and tutorial! Just what I needed for gathering our cross-street data for centerlines.

    • langdonms says:

      I especially appreciate the python notations in the followup tutorial Finding Street Segments. As I am learning it is great to read explanations rather than “here’s the code answer, copy/paste it.”

  11. zyanj2002 says:

    very detailed ,thanks for your share

  12. Mike Onzay says:

    I’m stuck on the spatial join. I’ve copied the toolbox to a different location and I’m trying to use my data. At the point where it asks for the field map of join features I keep getting a list of available fields from “E:\10_1ResourceCenter\Blogs\StreetIntersectionList\ToolData\Test.gdb\

    I cannot figure out how to see my own fields names from my layer.

    • Dale Honeycutt says:

      Open the model in ModelBuilder, then open the Spatial Join tool. In the Spatial Join tool dialog, right-click in the Field Map control and click “Reset”. If this doesn’t work, remove the Spatial Join tool and re-add it.

      The issue is that field map controls remember their previous settings. Some call this a feature, some call this a bug.

  13. Mike Onzay says:

    Deleting the spatial join tool and re-adding it proved to be the best option.

  14. says:

    Thank you Dale for this amazing article. I am calculating intersection densities (among other street connectivity indicators) for different cities and the post has been most helpful. The level of detail and the simple steps you use make this so easy.

  15. innisfilgis1 says:

    These instructions are fantastic! Very clear and readable.

    However I am trying to actually create a layer with street intersections for our road network. I used the intersection tool but it creates the multi-point layer, and I don’t have the proper license for the feature to point tool. I was wondering if there was a work-around? Or suggestions for a different method?


  16. deanvdh says:

    Thank you for the post. Helped me with a current project.

    Is there a way to create a “from” and “to” attribute from a line segment. I used “Feature Vertices To Points” tool to create a point at the mid of the line segment. I now need to populate the two fields ( “from” and “to”) with the name/s of the intersecting road/s?

    I have tried to use the network analysis tool but it does not work on my computer. Any advice will be greatly appreciated.

    From from South Africa

  17. bizzym says:

    Followed the procedure and have my intersection points with street names concatenated in one field. Is there a way to do this but create output that has separated fields? What I have is a street layer that has a PreDirectional (N,S,E,W), StreetName (the base name for the street), and the PostType(Rd, St, Dr, Ln, Ave, etc). What I need is a table that has Dir1,St1,Type1,Dir2,St2,Type2, … up to 4. The order of the streets is not important so long as the numbers are all for the same street record. I can’t have E 4th St intersect with S Maple Ave and end up with E 4th Ave for Dir1,St1,Type1 and S Maple St for Dir2,St2,Type2 because it went alphabetical on me.

    Any help would be great!!

  18. lissajo64 says:

    Would the output be something that could work in a geocoder? I have tried something like this before and the only way I could see to do it was to create a layer with a concatonated field:
    Streetname & Streetname2, then append Streetname2 & Streetname to the same layer so that if the street was entered either way it would be found. I am wondering if there is an easier way to do this or maybe it is a way to configure the geocoder that may make it possible to not have to add the appended part.
    Hope this makes sense…Thanks!

  19. andyboat says:

    I am trying to replicate the entire process with arcpy( scripting) but I don’t seem to get the field mapping part to work. The merge rule that I define doesn’t get applied and the output field just come out unchanged. Any help would be appreciated on how to replicate the finding intersecting streets with spatial join and doing the field mapping portion.

    Thanks in advance for your help

  20. cdurand says:

    Some 10 years ago we had a consultant create a tool for this task. I just dug it up and it still works! It seems to handle duplicates and cul-de-sacs without any trouble. It requires input of a .shp file and outputs an intersection point file with 4 fields, Road1, Road2 and X/Y. There’s very little setup to get it to run but it’s pretty simple and I could write it up if anyone thought it would be worthwhile.

  21. dpdeegan says:

    We did this to get names for street segments that humans care about: X St from Z St to W St.
    The output field I had to works with contained lots of duplicates and extraneous ‘. I wanted to get consistent strings so:

    def uniqueString(instring,delim):
    import re
    inlist = re.split(delim, instring.replace("'" , "")) #re.split returns a list from a string with delimiters
    outlist = [] #need a blank list for the next step
    instring = ""
    for item in inlist:
    if item not in outlist:#if it isn't in the list, add it. first item is always added because the list is blank
    outstring = " to ".join(map(str , outlist)) #make a delimited string from a list. the map(str, list) is important
    return outstring

    Luckily, we don’t need from/to to overlap at the start/end of segments from the original line, all streets are two way.
    The function could be modified to output a list, and do something like ToField = row[1] = list[0] FromFields = row[2] = list(1) in an update cursor. The catch is… sometimes there are 3 roads at an intersection. The simple SegmentName text field solution is all we need.
    I don’t use the set(list) option, the ‘if not item in outlist’ is more lines but seem to use it a lot.

    • dpdeegan says:

      Ooops, the code tag doesn’t keep the indentation. Let’s try that again…

      def uniqueString(instring,delim):
      import re
      inlist = re.split(delim, instring.replace(“‘” , “”))
      outlist = []
      instring = “”
      for item in inlist:
      if item not in outlist:
      outstring = ” to “.join(map(str , outlist))
      return outstring

  22. hherrmann says:

    One thing I noticed about this that messed up the end process that I was trying to get to, (a street crossroads listing), is that using the unsplit tool resulted in changing the direction of my streets. I needed it to be the same in order to have a 1st cross street, 2nd cross street, etc. So I wanted to keep the direction the same. I accomplished this by using the dissolve too instead.

  23. chen0315 says:

    I try to download the data model, but it says i don’t have permission to access the resource. Why is that?

  24. drewdowling says:

    This model is great, it saved me hours. Thank you so much.