Where can you build an Accessory Dwelling Unit (ADU) in Ann Arbor?

Mar 29, 2020

Over the weeknd, I built a map showing where you can build an Accessory Dwelling Unit (ADU) in Ann Arbor. It maps all of Ann Arbor’s parcels and highlights the ones that are zoned correctly.

My goal was for this map to still work 10 years from now. So I built it entirely without external dependencies. That ended up being a bit harder than I thought it would be.

You could make this a lot more quickly and easily if you didn’t add that constraint. Loading the shapefile in QGIS and then exporting an image would be pretty speedy. Or you could load the dataset directly into Mapbox and publish a map there.


There’s a huge set of geo tools out there and they are a tangled mess. But it’s getting better. I can’t step through how to set it all up, but if you want to do something similar, I’m coming into this project with PostGIS and GDAL installed on my Mac with homebrew.


I knew everything would have to fit in on static site – no servers. Third-party services are out. (A) I’m not about to spend $5 a month on this little side project. And (B) I love Mapbox but they will probably make a lot of cash pivoting to Mars satellite mapping or something (good on them) and then my tiny map would just stop working.

My orignal thought was to use Leaflet. It’s been around 10 years and it’s going to be around in 10 years. Solid like a rock. Good choice.

But then I’d have to fire up Mapbox Studio Classic and fight with it in the latest MacOS. If I wanted to add any interactivity, that would be a pain involving remembering how to do UTFGrid tiles.

I’m suspicious that vector renderers like Mabpox GL JS won’t function in browsers in 10 years but bdon convinced me that either GL JS or Tangram are probably fine. Start the clock.


It’s hard to navigate without a labeled base street map. The best open source for those is OpenStreetMap. And the best way to get OpenStreetMap data on your computer in managable little bundles is the minutely custom OSM extracts from Protomaps. I just drew a rough box in Washtenaw County and went from there.

To get the OSM data onto a map, you need to render it out into vector tiles. Conveniently, Tilemaker has a brew formula.

The configuration is a bit mysterious. You need a decent understanding of OSM tagging to do antything custom, and the actual configuration happens in JSON and … lua? I’m not saying lua is a bad decision, but I’ve never worked with it. Happily, they include a default configuration that probably has what you need out of the box.


The Washtenaw County parcel data is a little tricky to get at. Property taxes fund a huge part of our local government and you’d think the data on who owns what (and at what valuation) would be more public. There is a nice online map viewer, or you can FOIA the data. I work at a company that focuses on parcel data so I was able to skip ahead on this one.


Ann Arbor has an open data portal of sorts and it’s got a zoning shapefile. I loaded that into PostGIS using ogr2ogr:

ogr2ogr -f PostgreSQL pg:'user=matth host=localhost port=5432' -nln mi_washtenaw_zoning -s_srs 'epsg:2253' -t_srs 'epsg:4326' -nlt GEOMETRY -lco precision=NO /Users/matth/Downloads/AA_Zoning_Districts/AA_Zoning_Districts.shp

And thenjoined it up to the parcel data:

create index mi_washtenaw_zoning_geom_idx
on mi_washtenaw_zoning
using gist(wkb_geometry);

update mi_washtenaw p
set zoning = zoningclas
from mi_washtenaw_zoning b
where st_intersects(st_centroid(p.wkb_geometry), b.wkb_geometry);

Rendering parcel tiles

I used ogr2ogr to export just the Ann Arbor parcels from Washtenaw County. you can add in a little sql query to get just the data you need:

ogr2ogr -f GeoJSON mi_washtenaw_parcels.geojson pg:'user=matthhost=localhost port=5432' mi_washtenaw -sql "select * from mi_washtenaw where city='ann-arbor'"

Ann Arbor has about 30,000 properties. That’s way too many to show all of them on the map at once with GeoJSON, so I knew I’d need to render them out as vector tiles.

I used tippecanoe to turn the GeoJSON into vector tiles. There are a lot of setting to play with. Here are a few that were relevant here:

  • output-to-directory, because I would be hosting the tiles instead of uploading them to mapbox
  • include to only include certain attributes in the data. Mapbox doesn’t like rendering tiles larger than 512kb, so I had to slim things down.
  • simplify-only-low-zooms to garauntee that at least the closest zoom level would have the full parcel detail
  • no-tiny-polygon-reduction, otherwise I got jagged triangles and other polygons instead of parcels
  • no-tile-compression, the web server would handle this
tippecanoe --output-to-directory=parcel_tiles mi_washtenaw_parcels.geojson --read-parallel --include=address --include=zoning --include=ll_gissqft --no-tile-compression -f --coalesce-densest-as-needed --detect-shared-borders --no-tiny-polygon-reduction --simplify-only-low-zooms

Putting it together

Time to put everything on a Mapbox Map! I followed their standard setup instructions, but I downloaded the JS and CSS assets to my working directory instead of using the CDN versions.

There were a couple gotchas, and I found solutions to several in klokantech’s mapbox-gl-js-offline-example repo. The biggest one was that the fonts I needed for road labels had to to be rendered to PBF to be used in Mapbox GL JS (apparently this isn’t the case with Tangram). I skipped those and used the fonts in kloantech’s example.

Then, I added in layers for the parcels with a nice subtle styling, plus a layer that used filters to select only the parcels that match the ADU criteria.

The map looked good, but it was hard to tell where Ann Arbor ended. I downloaded the City boundary shapefile from the data portal and saved it as GeoJSON using QGIS, but that didn’t look great as a simple line.

I used this codepen plus Turf.js for inspiration on how to turn it into a mask, where the areas outside the City would be appear only faintly. In the process, I managed to add all the Township islands – they’ll be back in a future map.

You can see the final product here, and the code is here.