In my last post, I described how to take a shapefile and plot the outlines of the geometries in the shapefile. But the power of shapefiles is in the records (the data) associated with each shape. One common way of presenting shapefile data is to plot the shapefile geometry as polygons that are colored by some value of data. So as a prelude to doing just that, this post will cover how to plot polygons using the shapely and descartes libraries. As always, my code is up on my github page.
The two python libraries that I’ll be using are shapely (for constructing a polygon) and descartes (for adding a polygon to a plot). So step 0 is to go install those! I’ll also be using the numpy and matplotlib libraries, but you probably already have those.
Though the documentation for shapely has some nice sample source code, I wrote my own script, simple_polygons.py, to get to know the libraries better. In this approach, there are two steps to building a polygon from scratch: constructing the points that define the polygon’s shape and then mapping those points into a polygon structure. The first step doesn’t require any special functions, just standard numpy. The second step uses the shapely.geometry.Polygon class to build a polygon from a list of coordinates.
There are limitations for valid polygons, but virtually any shape can be constructed, like the following pacman:
The first step is to build the list of coordinates defining the exterior points (the outer circle) and a list of interior points to exclude from the polygon (the eyeball). Starting with the exterior points, I calculate the x and y coordinates of unit circle from 0.25pi to 7/4pi (0 to 2pi would map a whole circle rather than a pacman):
theta = np.linspace(0.25*3.14,1.75*3.14,80) # add random perturbation max_rough=0.05 pert=max_rough * np.random.rand(len(theta)) x = np.cos(theta)+pert y = np.sin(theta)+pert
I also add a random, small perturbation to each x-y position to add a bit of roughness to the outer pacman edge, because I wanted some small scale roughness more similar to the shapefiles I’d be plotting later. Next, I build a python list of all those x-y points. This list, ext, is the list of exterior points that I’ll give to shapely:
# build the list of points ext = list() # loop over x,y, add each point to list for itheta in range(len(theta)): ext.append((x[itheta],y[itheta])) ext.append((0,0)) # add 0 point
At the end, I add the 0,0 point, otherwise the start and end points on the circle would connect to each other and I’d get a pacman that was punched in the face:
That takes care of the exterior points, and making the list of interior points is similar. This list, inter, will be a list of points that define interior geometries to exclude from the polygon:
# build eyeball interior points theta=np.linspace(0,2*3.14,30) x = 0.1*np.cos(theta)+0.2 y = 0.1*np.sin(theta)+0.7 inter = list() for itheta in range(len(theta)): inter.append((x[itheta],y[itheta])) inter.append((x,y))
Now that we have the list of exterior and interior points, you just give that to shapely’s polygon function (shapely.geometry.Polygon):
polygon = Polygon(ext,[inter[::-1]])
Two things about passing Polygon the interior list: (1) you can actually pass Polygon a list of lists to define multiple areas to exclude from the polygon, so you have to add the brackets around inter and (2) I haven’t quite figured out the [::-1] that the shapely documentation includes. I know that generally, [::-1] will take all the elements of a list and reverse them, but why does Polygon need the points in reverse? No idea. Without it, I only get an outer edge defining the eyeball:
I would love to get some information on why Polygon needs the reversed list, so leave me a note in the comments if you know why.
Regardless, the next step is to add that polygon structure to a plot, with a straightforward use of matplotlib.pyplot (imported as plt) and descartes.patch.PolygonPatch:
# initialize figure and axes fig = plt.figure() ax = fig.add_axes((0.1,0.1,0.8,0.8)) # put the patch on the plot patch = PolygonPatch(polygon, facecolor=[0,0,0.5], edgecolor=[1,1,1], alpha=1.0) ax.add_patch(patch) # new axes plt.xlim([-1.5, 1.5]) plt.ylim([-1.5,1.5]) ax.set_aspect(1) plt.show()
PolygonPatch’s arguments are pretty self explanatory: facecolor and edgecolor set the colors for the fill and edge of the polygon. Conveniently, facecolor and edgecolor can be specified as RGB values, which I’ll take advantage of for plotting shapefile records in my next post. It can also accept any of the kwargs available to matplotlib.patches.Polygon class (like the transparency,alpha, between 0 and 1).
So that’s it! Pretty easy! And in some ways it is even easier to plot polygons from a shapefile, since pyshp imports shapefile coordinates as a list and you can just give that list directly to Polygon… more on that in the next post.