More on Mapping with pyshp, Github, & GeoJSON

More Details

Recently I gave a short talk to a room of non-GIS-y Python developers, demonstrating how they could make web maps using Github (with no GIS experience necessary!).

Part of that process involved converting shapefiles into GeoJSON. The code for the demo is available on Github. For anyone interested: read on for a little more on the how & why I touched on briefly during my talk.

Mapping? In my GitHub? ...yes.

It's true. This past summer, GitHub announced support for web maps directly inside a GitHub repository (or gist!). It's really simple: just push any geoJSON file up to GitHub, and when you view that file on GitHub, your map renders automagically.

The "Catch"

Not really a catch, but a stumbling point for many "traditional" GIS users: a lot of GIS professionals still use clunky spatial data formats (e.g., the "Shapefiles" mentioned in my talk). Until very recently, the most popular GIS Deskop software - ESRI ArcGIS - did not offer an easy way to transform shapefiles, etc, into geoJSON data.

Never fear! Open Source is here.

Luckily, there are tools to transform spatial data, and some are both free & open source. GDAL/OGR is the canonical example, but it can be a little front-heavy when it comes to installation & configuration. Since this demo was working with shapefiles, I went back to my favorite little pure-Python library: "pyshp".

Magic: the __geo_interface__ property

The nice thing about popular open formats like GeoJSON is that lots of people tend to use them, and eventually standardized protocols around when and how we use said formats get built. In GeoJSON's case, there is a widely-accepted protocol in the form of the __geo_interface__ Python property. This handy property is supported by a number of tools (even ESRI's arcpy is using it!), including pyshp. With a tiny bit of Python, it makes transformation of coordinate data into GeoJSON-friendly strings super simple:

Here, sf is a "Reader" object, which I passed the path to the shapefile I want to work on. (reference for full code)

for r in sf.shapeRecords():
    coords = r.shape.__geo_interface__['coordinates']
    # (...)
    geom = r.shape.__geo_interface__

Key take-away: The shapeRecords() method returns geometry (that is, coordinates that can be used to "draw" a shape or point) for each feature in our shapefile.

Those geometries each have shape and record attributes. And given that, here's the magic - each shape has a __geo_interface__ property!

Using that, we can grab JSON-friendly values, ready to load into a geoJSON file:

# r.shape.__geo_interface__ for a given sample 'r'
{'coordinates': (-100.86541381018779, 33.85341020379265), 'type': 'Point'}

From there, writing those JSON-friendly geometries into a new geoJSON file is a piece of cake. Check out the whole thing in the demo script on GitHub.

By @Sara Safavi