Spatial Pixel

Making Design Computable

SlippyMapper is a module in the Spatial Pixel libraries that helps you draw interactive Slippy Maps in Processing's Python Mode.

The most basic functionality renders tiled maps like those on OpenStreetMap and Google Maps. In combination with other Spatial Pixel libraries, you can overlay geographic data (e.g. KML or GeoJSON), make multiple interactive maps, and annotate and render them with the simplicity and flexibility that Processing.py offers.

Installing SlippyMapper

SlippyMapper is part of the Spatial Pixel computational toolkit, hosted on GitHub.

First, ensure you have Python Mode version 3027 or later installed.

With a Processing sketch open, click on the mode selector and select “Add Mode…” Ensure you have Python Mode 3027 installed. If not, click “Update.”

Python Mode 3027 or later required

This version introduces the ability to host a site-packages subfolder in your sketchbook’s libraries folder for Processing libraries written in Python. Thanks to Jonathan Feinberg for the quick response to the feature request!

Download the Spatial Pixel libraries from GitHub

Download the zip file from the latest release here: https://github.com/awmartin/spatialpixel/releases

Unzip the archive you downloaded into your Processing sketchbook, under libraries → site-packages. On a Mac, it should look like this:

If you don’t see a site-packages in your libraries folder after installing Python Mode 3027 or later, you can just create the folder yourself. Then unzip the spatialpixel zip package into that folder. You should be good to go!

Confirm you can load the libraries

In a new Python Mode sketch, attempt to import the spatialpixel module:

import spatialpixel

If it doesn’t give you an error, you’re ready to start using SlippyMapper!

Your First SlippyMapper Map!

Start with a new empty sketch, and import the slippymapper library.

import spatialpixel.mapping.slippymapper as slippymapper

def setup():
    size(512, 512)

def draw():
    background(255)

The module slippymapper contains a class called SlippyMapper. This is the class we’ll use to create the tiled map object. Its constructor takes a few arguments, in order:

  • Latitude of the map’s center
  • Longitude of the map’s center
  • Zoom value between 1 and 18

Here’s an example:

import spatialpixel.mapping.slippymapper as slippymapper

def setup():
    size(512, 512)
    city = slippymapper.SlippyMapper(51.5074, -0.1278, 11)

def draw():
    background(255)

Now you need to render the map, which starts the process of progressively downloading the map tiles. Also, draw the map at the same time, which helps the library proceed with its process of downloading and rendering. You’ll also need to declare the london variable as global so you can use it in draw().

import spatialpixel.mapping.slippymapper as slippymapper

def setup():
    size(512, 512)
    global london
    london = slippymapper.SlippyMapper(51.5074, -0.1278, 11)
    london.render()

def draw():
    background(255)
    london.draw()

The simplest kind of map rendered by SlippyMapper.

Let’s take a look at the line that creates the composite map image:

london = slippymapper.SlippyMapper(51.5074, -0.1278, 11)

The first two arguments are the latitude and longitude of the center of the map, respectively. The third argument is the zoom value, an integer between 1 and 18, which is the standard range for such tiled maps.

You can even add a second city to the mix:

import spatialpixel.mapping.slippymapper as slippymapper

def setup():
    size(1024, 512)

    global london
    london = slippymapper.SlippyMapper(51.5074, -0.1278, 11)
    london.render()

    global paris
    paris = slippymapper.SlippyMapper(48.855901, 2.347745, 11)
    paris.render()

def draw():
    background(255)
    london.draw()

    translate(512, 0)
    paris.draw()

London and Paris via SlippyMapper + Toner (Stamen Design)

Rendering Style

The rendering style you’re seeing is called “toner,” rendered by Stamen Design in San Francisco. They’ve made the map tiles open to everyone, following the common “Z/X/Y” standard map server tile format.

SlippyMapper includes a number of public map tile servers with different styles. You can see all the available ones here.

Just add another argument to the constructor to change the style. The acceptable values are the keys of the tile_servers dictionary: 'toner', 'toner-lite', 'toner-hybrid', etc. Try this:

import spatialpixel.mapping.slippymapper as slippymapper

def setup():
    size(512, 512)
    global london
    london = slippymapper.SlippyMapper(51.5074, -0.1278, 11, 'carto-dark')
    london.render()

def draw():
    background(255)
    london.draw()

Also try 'terrain', 'mapnik', and 'openstreetmap' for different rendering styles.

Bigger Maps

By default, the SlippyMapper object produces a map of 512 pixels square, twice the dimensions of a standard map tile image (256 pixels). You can add two more arguments to the SlippyMapper constructor to set this size:

import spatialpixel.mapping.slippymapper as slippymapper

nyc = slippymapper.SlippyMapper(40.715, -73.999, 12, "carto-light", 1000, 800)

This makes a map 1000 pixels wide and 800 pixels tall. This is helpful when you need to render more detail or a larger area.

Markers

Adding marker annotations to SlippyMapper is done by calling the addMarker() method. The first two arguments are always the latitude and longitude of the marker, and the third is a value that encapsulates what you want draw.

import spatialpixel.mapping.slippymapper as slippymapper

def setup():
    size(1000, 800, P2D)
    global nyc
    nyc = slippymapper.SlippyMapper(40.715, -73.999, 12, 'carto-light', width, height)

    nyc.addMarker(40.808238, -73.959277, "Avery GSAPP")
    nyc.addMarker(40.689220, -74.044359, "Statue of Liberty")
    nyc.addMarker(40.660212, -73.968962, "Prospect Park")

    nyc.render()

def draw():
    background(255)
    nyc.draw()

The great part about this is that you can use regular Python code to parse data files, like CSV files, and add markers en masse. For example, download this file of subway entrances from the MTA, then add it as a file in a new sketch (i.e. include in its “data” folder). And try this:

import spatialpixel.mapping.slippymapper as slippymapper
import csv

def setup():
    size(1000, 800, P2D)

    global nyc
    nyc = slippymapper.SlippyMapper(40.715, -73.899, 11, 'carto-dark', width, height)

    with open('StationEntrances.csv') as f:
        reader = csv.reader(f)
        header = reader.next()

        for row in reader:
            latitude = float(row[3])
            longitude = float(row[4])
            nyc.addMarker(latitude, longitude)

    nyc.render()

def draw():
    background(255)
    nyc.draw()

The addMarker() method in this case accepts just two arguments, the latitude and longitude of the marker. By default, it draws a small white circle (with a black outline) at the given location.

Adding an image (from Processing’s loadImage function) will draw the image instead.

import spatialpixel.mapping.slippymapper as slippymapper
import csv

def setup():
    size(1000, 800, P2D)

    pin = loadImage("https://s3.amazonaws.com/spatialpixel/maps/map-pin-10px.png")

    global nyc
    nyc = slippymapper.SlippyMapper(40.715, -73.899, 11, 'carto-dark', width, height)

    with open('StationEntrances.csv') as f:
        reader = csv.reader(f)
        header = reader.next()

        for row in reader:
            latitude = float(row[3])
            longitude = float(row[4])
            nyc.addMarker(latitude, longitude, pin)

    nyc.render()

def draw():
    background(255)
    nyc.draw()

More about how to annotate maps with addMarker() will come in a subsequent post. It will cover different customizations as well as interactivity.

Google Driving Directions

The Spatial Pixel library includes a convenience module for requesting and rendering Google Directions and rendering them on a map.

import spatialpixel.mapping.slippymapper as slippymapper
import spatialpixel.google.directions as directions

def setup():
    size(1000, 800, P2D)

    global nyc
    nyc = slippymapper.SlippyMapper(40.768, -73.977, 12, 'carto-light', width, height)

    apiKey = ''   # Enter your Google Directions API key (optional)
    newmuseum = (40.722361, -73.991991)
    gsapp = (40.808451, -73.960301)
    museumvisit = directions.SlippyLayer(apiKey, gsapp, newmuseum, 'bicycling')
    nyc.addLayer(museumvisit)

    nyc.render()

def draw():
    background(255)
    nyc.draw()

A bicycling route from Columbia University to the New Museum? No way!

The style is "carto-light"

Note that the Google Directions rendering is a bit incomplete. The data coming from Google is correct, but some legs of a route might render oddly, crossing bodies of water or appear unusually jagged. It’s just a limitation of the rendering engine that I’m still working on.

Drawing Arbitrary Geometry

The challenge in drawing custom geometry over a map is really just about converting longitude and latitude to x- and y- coordinates in the pixel-space of the canvas. Processing treats everything as x- and y- coordinates.

SlippyMapper provides methods that do this for you, namely lonToX and latToY. Give them a longitude or latitude, and they will return x or y values, respectively, for points in pixel space relative to the upper-left corner of the map. Once you have these points, it’s easier to draw new geometry from them.

For example, let’s say you wanted to draw lines between all the major airports in the NYC area:

import spatialpixel.mapping.slippymapper as slippymapper

def setup():
    size(1000, 600, P2D)

    global nyc
    nyc = slippymapper.SlippyMapper(40.715, -73.985, 11, 'toner-lite', width, height)
    nyc.render()

    global jfk_x, jfk_y
    jfk_x = nyc.lonToX(-73.7781)
    jfk_y = nyc.latToY(40.6413)

    global lga_x, lga_y
    lga_x = nyc.lonToX(-73.8740)
    lga_y = nyc.latToY(40.7769)

    global ewr_x, ewr_y
    ewr_x = nyc.lonToX(-74.1745)
    ewr_y = nyc.latToY(40.6895)

def draw():
    background(255)
    nyc.draw()

    stroke(255, 0, 0)
    strokeWeight(2)
    line(jfk_x, jfk_y, ewr_x, ewr_y)
    line(ewr_x, ewr_y, lga_x, lga_y)
    line(lga_x, lga_y, jfk_x, jfk_y)

A "toner-lite" styled map with lines drawn between major airports in NYC.

There are other modules in the Spatial Pixel library that can be used with Slippy Mapper, notably KML and GeoJSON parsers that render the geometries those geographic formats contain. More about that later.

About SpatialPixel

Spatial Pixel is a website about making computational design more accessible to designers. The code talked about here is hosted on GitHub at https://github.com/awmartin/spatialpixel. Please email me at william.martin@anomalus.com if you have feature requests, want to contribute, have work you’ve done with this library, or just want to start a conversation!

I'm Allan William Martin, a product manager, computational designer, and software engineer in New York City. I work at Pivotal on Cloud Foundry, a cloud-native application platform. I've taught at the Yale School of Architecture, New York Institute of Technology, and General Assembly.

This post was published on by .