Home · Blog · Games · Resume

2022-02-06
OpenStreetMap with a Pen Plotter

I wrote some Rust code to render GeoJSON data to SVG for use with a pen plotter.

┌─────────────┐ ┌───────┐ ┌───┐ ┌───────┐
│OpenStreetMap├─►GeoJSON├─►SVG├─►Printer│
└─────────────┘ └───────┘ └───┘ └───────┘


Along the way I learned about OpenStreetMap, the Overpass API, the Overpass query language (OverpassQL), and a web-based interface for quickly trying out OverpassQL queries. I also wrote some trivial and some nontrivial geometry algorithms for point/segment/polygon collision, intersection, cropping, and crosshatching.

# Background

## Pen Plotters

I recently bought an AxiDraw V3/A3. AxiDraw is a line of pen plotters sold by Evil Mad Scientist.

The AxiDraw V3/A3 pen plotter is a numerical control machine with a large drawing area (16.93 × 11.69 inches), a surprisingly fast arm (15 in per second), and an unusually high precision (2032 steps per inch).

A pen plotter comes with general-purpose mount which you can tighten around any kind of writing implement.

I suspect you could mount something more exotic (watercolor, laser cutter, knife) if you wanted to. I mostly use pens and markers.

Pen plotters have been around since at least 1959. They generally inspect to be fed low-level instructions such as ‘go here’, ‘pen down’, ‘go there’, ‘pen up’, etc.

A modern pen plotter such as the AxiDraw V3/A3 comes with utilities for interpreting SVGs, reordering instructions for faster printing, estimating print time, controlling print speed to avoid ink blots, etc.

## OpenStreetMap

OpenStreetMap began in the mid-2000s as an effort to create a Wikipedia-style repository of map data. It comprises a backend database for storing map data (locations, paths, areas, and associated information), a series of APIs for querying that information, a general-purpose web frontend for viewing map data (think Google Maps or MapQuest), and a large set of tools for inserting, editing, and retrieving data.

## Overpass API

The Overpass API is an API for querying OpenStreetMap geodata. The easiest way to understand it is to

1. visit overpass-turbo.eu,
2. enter an example query such as (node(51.249,7.148,51.251,7.152);<;);out meta;
3. press ▶️ Run, and
4. press 🔎 to center the preview window around the returned data.

The example query above is written in Overpass QL, the Overpass query language. I find it a bit complex, but I understand that it’s very powerful.

# Work

## Generative Art

When I first started to use the pen plotter, I began by developing a set of low-level libraries in Rust for manipulating points, line segments and open/closed polygons in a vector format. I made some generative art with simple lines and curves:

(Most of this stuff is inspired by Sol LeWitt.)

This was a really good starting point. I began to rewrite some of my low-level libraries for more ergonomic usage (simpler construction of points and rectangles relative to each other, automatic centering, predictable cropping behavior) and began to squash floating-point bugs. I also started to write more complex generative art which required computing bounding boxes, line/line collisions, and other geometry problems.

Absolutely every problem I needed to solve here had a solution on Wikipedia (line-line intersection, line segment intersection). Vector graphics are well-understood and well-trodden territory.

## Dealing with Geodata

Eventually I began to try to print OpenStreetMap data instead. I learned that plotting unprocessed lat/long data results in a squashed, stretched map, like so:

This is because points in geographic databases are generally stored as (latitude, longitude) (or, sometimes, (longitude, latitude)) pairs.

Because of issues with map projection and especially ESPG:3857 (the specific spherical mercator projection OpenStreetMap uses), areas further from the equator become more distorted.

I ended up building some utilities to rotate, scale, shift, and project the vector data. I often found that I would pull down map data, render a plot, and later decide that I wanted to zoom in a bit, or pan over a bit.

## Layers and tags

Next I realized I wanted to use colored pens / colored markers to draw multicolor prints. It seems natural that streets might be one color, water another, etc.

OpenStreetMap tags all features with tags, which areconvention-driven key/value pairs. These might annotate some line as a highway or as a footway, or they might annotate an area as a park vs. a garden, etc.

I began to pull down geodata and process each element as a polygon, multipolygon, linestring, etc. while preserving the tags associated with that original bit of geodata. This let me apply colors, crosshatching, etc. first, then apply color and separate into layers near the end.

## Crosshatching

I wrote a crosshatcher to transform a closed polygon into a set of straight lines which fill its interior with crosshatching at some spacing and angle.

Crosshatching is computational tricky – given some angle and distance, you have to find the set of all lines which could pass through the polygon and truncate each line into one or more line segments. Because the polygon isn’t necessarily convex, a given line might intersect the polygon zero, one, or more than one times. It might intersect the polygon along an edge, or precisely at a vertex. A crosshatch line might run along one of the edges of the polygon.

You can probably see in the print above that this algorithm used to fail whenever a crosshatch line was unfortunate enough to meet its surrounding polygon at a vertex rather than along an edge.

## Deduplication

When plotting transit data, I found that the pen plotter spent a lot of time retracing its path. Transit data is very layered – the train going uptown, the same train going uptown express, the same train going downtown, etc. all occupy the same points and lines in space. When the pen plotter retraces its steps, the ink smears and bleeds in an ugly way.

Deduplication is another surprisingly hard problem. I ended up quantizing each point to a fine grid, hashing the data in order to deduplicate it, and then restoring the data to its original position.

Quantization is determined by the size of the underlying grid. If you make the grid too coarse, it mangles curved input data into blocky output data.

# Conclusion

This is a pretty fun project. I ended up with a a very fast piece of software which can convert OpenStreetMap GeoJSON data into an SVG suitable for plotting. It’s easy to dial in an address or an area of land, toggle on and off layers, and produce a large, detailed print as a piece of art or a gift.

I plan to add some more interesting features – crosshatching is only one way to fill an area with a line, for example. I also think it would be really fun to plot data which has been stretched or transformed in some obviously mathematical way (projected onto a hyperbolic plane so that things get more dense further from the center, for example).