Spider Diagrams

Recently I had a need to generate a spider diagram for my map.  Spider diagrams, also called desire lines for business scenarios, are a series of rays drawn from a central point to related points.  The result shows you the actual area of influence for each central point.

spiderdiagram

Some good examples would be store locations and customers that visit them, library locations and patrons’ home locations, or in my case fire station locations and incidents they responded to. 

ArcGIS Desktop has a Create Spider Diagram tool, but only if you have a Business Analyst license.  Since I don’t have that license, it does not help me out so I decided to create my spider diagram script … and share with you!

# ---------------------------------------------------------------------------
# spider.py
# Created on: 9/15/2016
# By: Michael A. Carson
# Description: Creates spider diagram lines from two point feature classes.
# Note: You need one point feature class for the center points and another
#       for the related points.  Both point feature classes must have a field
#       that identifies the center point ID to relate them.
# ---------------------------------------------------------------------------

# Import modules
import arcpy

# Local variables
cenfc = "C:/spider_diagram/data.gdb/fire_stations"
cenfld = "STAT"
relfc = "C:/spider_diagram/data.gdb/fire_incidents"
relfld = "STATION"
spiderfc = "C:/spider_diagram/data.gdb/spider_lines"

# Read in the ID values and coordinates of the center points
print "Reading in center point IDs and coordinates..."
cendict = {}
for row in arcpy.da.SearchCursor(cenfc, [cenfld, "SHAPE@XY"]):
  cendict[row[0]] = row[1]

# Read in the ID values and coordinates of the related points and create line geometry objects
print "Reading in related point IDs and coordinates..."
linelist = []
for row in arcpy.da.SearchCursor(relfc, [relfld, "SHAPE@XY"]):
  relxy = row[1]
  cenxy = cendict[row[0]]
  polyline = arcpy.Polyline(arcpy.Array([arcpy.Point(cenxy[0], cenxy[1]), arcpy.Point(relxy[0], relxy[1])]))
  linelist.append([row[0], polyline])

# Setup line feature for spider lines
print "Setting up line feature for spider lines..."
sr = arcpy.Describe(cenfc).spatialReference
tempfc = "in_memory/tempfc"
arcpy.CreateFeatureclass_management(tempfc.split("/")[0], tempfc.split("/")[1], "POLYLINE", "", "", "", sr)
field = arcpy.ListFields(cenfc, cenfld)
fielddict = {"SmallInteger": "SHORT", "Integer": "LONG", "Single": "FLOAT", "Double": "DOUBLE", "String": "TEXT", "Date": "DATE", "BLOB": "BLOB"}
fieldtype = fielddict.get(field[0].type)
if fieldtype == "TEXT":
  arcpy.AddField_management(tempfc, cenfld, fieldtype, "", "", str(field[0].length))
else:
  arcpy.AddField_management(tempfc, cenfld, fieldtype, "", "", "", "", "NULLABLE")

# Add spider lines
print "Adding spider lines..."
cursor = arcpy.da.InsertCursor(tempfc, [cenfld, "SHAPE@"])
for line in linelist:
  cursor.insertRow(line)
del cursor

# Save spider lines to spider line feature
print "Saving spider lines..."
arcpy.env.overwriteOutput = True
arcpy.CopyFeatures_management(tempfc, spiderfc)
arcpy.Delete_management(tempfc)

# All done
print "All done!"

Use the scroll bar above to move the code over.  I tend to code long lines of code!

I included print statements so when you run it, it will let you know what step it is on.  This python script is a little involved, so I will step you through it so you know what is going on.

The script requires that you have two point feature classes, one representing the center points (in my case fire stations) and the other the related points (in my case the incidents each fire station responded to).  Also, each of the point feature classes must have a field in them that contains values that relate the two (in my case fire station numbers).  You set the center point feature class, cenfc, and the field name that identifies the points, cenfld.  You also set the related point feature class, relfc, and the field name that identifies what center point it is related to, relfld.  Lastly, you set the output line feature class, spiderfc, for the spider diagram lines.  My example uses features in a geodatabase, but it also will work if you use shapefiles.

The next step is to read in all the center point IDs and coordinates.  We do this in a loop using SearchCursor to pull out each point’s item value and x,y coordinate.  A dictionary, cendict, is created for each point ID, row[0], with their associated coordinate, row[1].  We need this in dictionary format when we create each line that runs from the center point to a related point.  If you are curious what the dictionary looks like, here is what mine looks like for my six fire station points (fire station 11 to 16):

{11: (6468490.929039478, 1889364.6638042927),
 12: (6457601.408495724, 1881725.9950265437),
 13: (6458487.647536889, 1893885.4716918021),
 14: (6459933.538345903, 1887955.772593215),
 15: (6464624.778963402, 1883811.9232693762),
 16: (6467188.506447479, 1895612.4928440452)}

Note each fire station number is a key in the dictionary, and each has an associated x,y coordinate for their location.

Next a line list, linelist, must be built using the related points which will be used later to create the spider lines.  We do this in a loop once again using SearchCursor on the related points.  As the script loops through, it grabs each related point’s coordinate, relxy from row[1], and the associated center point ID from the relfld field value, row[0], which is used to grab and set the associated center point coordinate, cenxy, using the cendict dictionary.  For example, if one of the related points is associated with fire station 11, it will grab 11’s coordinate from the dictionary.  Pretty cool, eh?

In that same loop polyline geometry is created for each spider line using a point array.  Basically the straight line’s start coordinate is the center point x,y (cenxy[0],cenxy[1]) and the end coordinate is the related point x,y (relxy[0],relxy[1]).  Lastly, the center point ID and the polyline geometry is added to the linelist.  The linelist will be used later to create all the spider lines.

Next an empty line feature class is setup for the spider lines.  A Describe is done on the center point feature class, cenfc, to get at it’s spatial reference.  This spatial reference, sr, will be used when the line feature class is created.  Note that the line feature class will be created in memory as tempfc.  This will allow the script to run a lot faster when adding all the spider lines.  The CreateFeatureclass_management tool is used to create the temporary line feature class in memory.

Next a field needs to be added to the temporary line feature class.  This field will store the center point ID that each spider line will connect to.  Why do we want to do this?  Well if you wanted to color the spider lines as the same color as their center point, they will look much better when symbolized AND you would know which spider line relates to which center point when things get crowded!  Also you could relate the lines back to the center points to get at the lengths of the lines if you wanted to do some kind of summary of distances for each center point.  Note that a field dictionary, fielddict, is created for the different types of fields and naming conventions.  We have to do this because ListFields gives us one field type name when we ask for the field type of a field and then have to use another name when we create the same type of field using the add field tool!  For example, if your center point ID was a “String” field, then when it is created in the spider feature class we have to use the word “TEXT” instead of “String”.  I don’t know why ESRI did that, but there you go.  The fielddict is used to look up the word we need to use to create the field type, then the AddField_management tool is used to add the field.  Note that if the field type is “TEXT”, the field’s length is needed when created.  All others do not require it.

Next the spider lines are added to the temporary line feature class using cursors.  For each line value in the linelist, cursor is used to insert each line’s center point ID and geometry (adding the line to the line feature class and attributing it).  Once done, the cursor is closed by deleting it.

And finally, the CopyFeatures_management tool is used to copy the spider lines in the temporary line feature class, tempfc, to the spider line feature class, spiderfc.  Then the temporary line feature class is deleted from memory.  Note that the overwrite output environment setting is set to True, so if the spider line feature class already exists, it will just be replaced.

And here is my result after I symbolize the fire stations and spider lines:

spiderdiagram2

Kind of looks like exploding fireworks!

I hope this script will help you out with your spider diagram needs.  No need to buy a license just to create a spider diagram!  Enjoy. -mike

7 thoughts on “Spider Diagrams

  1. I did this several years ago using Business Analyst. The county fire department was looking at changing the service area for a station. We made the calls customers and the stations stores. The spider diagram created a clear picture. The real fun was when we calculated drive times for all calls using the existing and proposed boundaries. Adding them up, the result showed the opposite of what everyone expected. We checked it carefully, and it was right. Score one for GIS.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s