If you are using python for spatial processing, it is sometimes useful to plot your data. In these cases, it may also be helpful to render a simple map to help locate the data. This notebook will show you how plot a shapefile using pyplot for this purpose.
If you don't have pyshp
or pyproj
in your jupyter environement, install them now:
In [ ]:
!pip install pyshp pyproj
This notebook uses the 1:110m Natural Earth countries shapefile.
After loading the shapefile, we also fetch the index of the "MAPCOLOR7" field so that we can paint different countries different colors.
You can also use your own shapefile - just copy it to the same folder as this notebook, and update the filename passed to shapefile.Reader
. You may also need to update mapcolor_idx
field to reference an attribute that exists in your shapefile.
In [18]:
import shapefile
sf = shapefile.Reader("ne_110m_admin_0_countries/ne_110m_admin_0_countries.shp")
mapcolor_idx = [field[0] for field in sf.fields].index("MAPCOLOR7")-1
mapcolor_map = [
"#000000",
"#fbb4ae",
"#b3cde3",
"#ccebc5",
"#decbe4",
"#fed9a6",
"#ffffcc",
"#e5d8bd",
]
The countries shapefile is in the WGS 84 projetion (EPSG:4326
). We will reproject to Web Mercator (EPSG:3857
) to demonstrate how reprojection. To do this, we construct a Transformer
using pyproj
.
At the same time, set up the rendering bounds. For simplicity, define the bounds in lat/lon and reproject to meters. Note that pyplot
expects bounds in minx,maxx,miny,maxy
order, while pyproj
transform works on in x,y
pairs.
In [19]:
import pyproj
transformer = pyproj.Transformer.from_crs('EPSG:4326', 'EPSG:3857', always_xy=True)
BOUNDS = [-180, 180, -75, 80]
BOUNDS[0],BOUNDS[2] = transformer.transform(BOUNDS[0],BOUNDS[2])
BOUNDS[1],BOUNDS[3] = transformer.transform(BOUNDS[1],BOUNDS[3])
To display the shapefile, iterate through the shapes in the shapefile. Fetch the mapcolor attribute for each shape and use it to determine the fill color. Collect the points for each shape, and transform them to EPSG:3857
using the transformer constructed above. Plot each shape with pyplot using fill
for the fill and plot
for the outline.
Finaly, set the bounds on the plot.
In [22]:
%matplotlib notebook
import matplotlib.pyplot as plt
for shape in sf.shapeRecords():
for i in range(len(shape.shape.parts)):
fillcolor=mapcolor_map[shape.record[mapcolor_idx]]
i_start = shape.shape.parts[i]
if i==len(shape.shape.parts)-1:
i_end = len(shape.shape.points)
else:
i_end = shape.shape.parts[i+1]
points = list(transformer.itransform(shape.shape.points[i_start:i_end]))
x = [i[0] for i in points]
y = [i[1] for i in points]
#Poly Fill
plt.fill(x,y, facecolor=fillcolor, alpha=0.8)
#Poly line
plt.plot(x,y, color='#000000', alpha=1, linewidth=1)
ax = plt.axis(BOUNDS)