GitSpatial

A Spatial API for your GitHub-hosted GeoJSON

by Jason Sanford / @JCSanford

What is GeoJSON?

A simple text format for representing geographic data

Points

{
    "type": "Point",
    "coordinates": [
        -104.998531,
        39.753459
    ]
}

LineStrings

{
    "type": "LineString",
    "coordinates": [
        [-104.998507, 39.753776],
        [-104.997611, 39.753079],
        [-104.998818, 39.752147],
        [-104.999682, 39.752825]
    ]
}

Polygons

{
    "type": "Polygon",
    "coordinates": [
        [
            [-104.998521, 39.753770],
            [-104.997625, 39.753077],
            [-104.998824, 39.752166],
            [-104.999696, 39.752830],
            [-104.998521, 39.753770]
        ]
    ]
}

Features

Join Attributes to Geometry

{
    "type": "Feature",
    "properties": {
        "name": "Wynkoop Brewing Company",
        "floors": 3,
        "beers_on_tap": [
            "Rail Yard Ale",
            "Rocky Mountain Oyster Stout"
        ]
    },
    "geometry": {
        "type": "Point",
        "coordinates": [
            -104.998531,
            39.753459
        ]
    }
}

GitHub automatically Renders GeoJSON Files

GitHub automatically Renders GeoJSON Files

That's cool, but ...

What if:

  • I have a lot of features. I can't show them all on the map at the same time.
  • I only care about features in a specific area.
  • I want to find the nearest feature to me.

Build an API!

I want:

  • to perform bounding box queries
  • to perform point + radius queries
  • updates to my GeoJSON (commits) to be reflected in the API almost immediately

A Bounding Box Query

Show me all the parks in the current map window

http://gitspatial.com/api/v1/:github_user/:github_repo/:file_name?bbox=:min_lng,:min_lat,:max_lng,:max_lat
http://gitspatial.com/api/v1/JasonSanford/mecklenburg-gis-opendata/parks?bbox=-80.8633,35.2071,-80.8158,35.2488

A Point + Radius Query

Show me the schools within 4000 meters of this point

http://gitspatial.com/api/v1/:github_user/:github_repo/:file_name?lat=:latitude&lon=:longitude&distance=:distance
http://gitspatial.com/api/v1/JasonSanford/mecklenburg-gis-opendata/schools.geojson?lat=35.256&lon=-80.809&distance=4000

The Response

A GeoJSON FeatureCollection we can use to display on a web map or consume in an app

{
    "count": 1000,
    "total_count": 1254,
    "type": "FeatureCollection",
    "features": [
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-80.838923, 35.220129]
            },
            "type": "Feature",
            "properties": {
                "city": "CHARLOTTE",
                "address": "800 EAST 3RD ST",
                "type": "NEIGHBORHOOD PARK",
                "name": "MARSHALL NEIGHBORHOOD PARK"
            },
            "id": 31902
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-80.845156, 35.391193]
            },
            "type": "Feature",
            "properties": {
                "city": "HUNTERSVILLE",
                "address": "",
                "type": "COMMUNITY PARK",
                "name": "HUNTERSVILLE ATHLETIC COMMUNITY PARK"
            },
            "id": 31901
        }
    ]
}

A dense feature set - utility poles for a city

We can update with each map pan

The Geo Hello World

Where is the nearest cofee shop? (from Wynkoop)

http://gitspatial.com/api/v1/JasonSanford/colorado-osm/cafes.geojson?lat=39.75345&lon=-104.99853&distance=300
{
    "count": 1,
    "total_count": 1,
    "type": "FeatureCollection",
    "features": [
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-105.0009146, 39.751674]
            },
            "type": "Feature",
            "properties": {
                "amenity": "cafe",
                "name": "Tattered Cover"
            }
        }
    ]
}

How it Works

Authorize Access

After access is granted we'll use the GitHub API to grab all of your public repos.

Sync Some Repos

Some of your repos won't have any GeoJSON in them. We'll only bother syncing the ones you tell us to.

Add a Service Hook

  • The GitHub API is used to add a WebHook URL
  • GitHub will POST relevant data here whenever you push changes to your repo.
  • We'll update your features if you've made any changes to synced feature sets.

Sync Some Feature Sets

Again, some of your files aren't GeoJSON. We'll only bother syncing the ones you tell us to.

Feature Set Details

Once a feature set is synced, you'll see page that lets you preview your features and gives some example API calls for both bounding box and point + radius queries.

Creating Features

  • During syncing, loop through the array of features and insert into the database
  • GeoJSON geometry is converted to GeoDjango (GEOS) geometry objects
  • Other properties are just stored as a dumb string and inserted as text
import json

from django.contrib.gis.geos import GEOSGeometry

from models import Feature

# We have a feature dict in context that looks like:
feature = {
    "type": "Feature",
    "properties": {
        "name": "Wynkoop Brewing Company",
        "floors": 3,
        "beers_on_tap": [
            "Rail Yard Ale",
            "Rocky Mountain Oyster Stout"
        ]
    },
    "geometry": {
        "type": "Point",
        "coordinates": [-104.998531, 39.753459]
    }
}

geom = GEOSGeometry(json.dumps(feature['geometry']))
properties = json.dumps(feature['properties'])

feature = Feature(geom=geom, properties=properties)
feature.save()

How it Really Works

Django

  • I use Django for my day job - Quickest way to get up and running
  • Using GeoDjango we can do fancy spatial queries very easily.
from django.contrib.gis.geos.point import Point
from django.contrib.gis.measure import D

lat = request.GET['lat']
lon = request.GET['lon']
distance = request.GET['distance']
point = Point(x=lon, y=lat, srid=4326)

Feature.objects.filter(geom__distance_lte=(point, D(m=distance))).geojson()

Python Social Auth

  • Super-easy 3rd party authentication/authorization
  • Lots of different providers: GitHub, Facebook, Google, Twitter, many many more ...
  • Piggybacks Django's built-in auth backend
  • Stores necessary access keys in the database so API request can be made on behalf of the user

Celery

  • GitSpatial does lots of background processing
  • We need a way to do work outside of the request/response cycle
  • It could take a while to sync lots of repos for a user - We need a way to queue those up

Postgres/PostGIS

An extension to PostgreSQL that adds spatial indexes and hundreds of functions that let you do fancy things like:

  • How many bars are within 100m of this point?
  • How many structures are within a specific flood zone?
  • What's the distance between these two objects?
  • What percentage of residents of Denver live within 1 mile of a grocery store?

Thanks!