Introduction

Recently I came across this post by Ben Connor Barrie of Damn Arbor about a proposed food truck ordinance. According to the article, these are the highlights:

(1) The mobile food vending service shall not be located in any required setback, any sight distance triangle, or required buffer. Mobile food vendors may be allowed in any office (O) commercial and industrial districts and the parking district. Mobile food vendors shall be prohibited in any residential district and the D1 and D2 districts.

(8) There shall be a minimum 200-foot separation from any residential use or residentially zoned district. This measurement shall be taken from the property line to property line at the closest point.

The conclusion is that:

…the ordinance would only allow food trucks (and food carts) in areas zoned Office, Commercial, Industrial or Parking, but only in places that are more than 200ft from a residential area. This means goodbye to our beloved Ricewood in its current location. More importantly, it would ban food trucks in most Office, Commercial, Industrial, and Parking zoned parcels.

This would apparently make our beloved Ricewood illegal in its current location. What else would it make illegal? Where would food trucks be legal and illegal under this ordinance? This is an interesting problem that we can solve with civic data provided by the City and the County. Let’s give it a shot!

Objective

What we need to prove is whether or not there are any areas zoned Office, Commercial, Industrial, or Parking that are more than 200 feet away from any areas zoned Residential. If there aren’t, then this ordinance effectively outlaws food trucks. If there are, we would like to figure out exactly where food trucks are allowed to be. For the first pass we’ll only look at which parcels are inside or outside of the 200-ft buffer (that is: if part of a food truck parcel is within the 200-ft buffer zone, then we assume that entire parcel can’t have food trucks). For the second pass, though, we’ll actually be able to tell exactly where a food truck is permitted, even if the buffer zone ends halfway through a parcel.

We also have to make some assumptions about which zones the ordinance is talking about. Now, I’m not a lawyer, and I assume the Ordinance Revisions Committee will discuss this, but for the sake of this write-up, I’m considering the following zones to allow food trucks per the ordinance (I have affectionately abbreviated these as COMP zones):

  • O - Office
  • ORL - Office/Research/Limited Industrial
  • P - Parking
  • C1 - Local business
  • C1A - Campus Business
  • C1A/R - Campus Business/Residential
  • C2A - Central Business
  • C2A/R - Central Business/Residential
  • C2B - Business Service
  • C2B/R - Business Service/Residential
  • C3 - Fringe Commercial
  • M1 - Limited Industrial
  • M1A - Limited Light Industrial
  • M2 - Heavy Industrial

I have no idea if this list is correct, and I’m a bit unsure about some of the mixed zones (e.g. C1A/R). The ordinance just mentions commercial and residential zones, and I’m not sure what the mixed use zones are considered in this case. But this gives us something to work off of and gives us a best-case scenario, I think.

I’m considering the following zones to be where food trucks are prohibited within 200 feet:

  • R1A - Single-Family Dwelling
  • R1B - single-Family Dwelling
  • R1C - Single-Family Dwelling
  • R1D - Single-Family Dwelling
  • R2A - Two-Family Dwelling
  • R2B - Two-Family Dwelling
  • R3 - Townhouse Dwelling
  • R4A - Multiple-Family Dwelling
  • R4B - Multiple-Family Dwelling
  • R4C - Multiple-Family Dwelling
  • R4D - Multiple-Family Dwelling
  • R6 - Mobile Home Park

Weapons of Choice

Because I’m a fan of open source tools (and I’m guessing ESRI is stupid expenisve), that’s what I used. Specifically:

  • QGIS, for quick sandboxing and ad hoc visualization
  • PostGIS, for the actual parcel/zone computations
  • ogr2ogr, for inserting Shapefile data into PostGIS

Getting Started

We’re interested in seeing which COMP zones are within 200 feet of a residential zone, which means we’re going to need a parcel map with zones attached to each parcel. This is our first road block, because while the City of Ann Arbor provides a lot of useful GIS data sets, they unfortunately do not provide a parcel map with zones.

But they do provide a parcel map of Washtenaw County and a map of zoning districts. We should be able to use this information to figure out which zone a parcel is in by checking to see which piece of zoning geometry overlaps a given parcel. Our weapon of choice for this is PostGIS, which is a set of GIS extensions for the powerful Postgres database.

Map Projections

Before we go further, we should talk about projections (flat Earthers, feel free to skip ahead. Everyone else, this is for you.) I’ll preface this by saying that I am by no means a mathematician or a geographer, but that never stopped me before.

If you’ve used a globe to find things before, you might be familiar with degrees, minutes, and seconds. For example, City Hall is located at 42°16’54.2” N 83°44’42.8” W. So if you start from the equator and go 42 degrees, 16 minutes, 54.2 seconds north, and if you start from the prime meridian (which runs through Greenwich, England because of course it does) and go 83 degrees, 44 minutes, 42.8 seconds west, you’ll wind up right on top of City Hall wondering why 400 people showed up to complain about building more housing. This is called a Spatial Reference System, or SRS. Spatial reference systems are necessary to interpret coordinates on a sphere or a plane; without an SRS, coordinates are meaningless. More specifically, there are Geographic Coordinate Systems (GCS) and Projected Coordinate Systems (PCS). GCSs measure distances with an angular unit (e.g degrees) and PCSs measure distances with a linear unit (e.g. meters).

There are thousands of different SRSs and each one has a unique identifier called an SRID. Some are well-known (to geographers) and used frequently, like EPSG:3426 (a.k.a. World Geodetic System 84, WGS84). This representation is the one GPS uses. The City releases its Shapefiles in SRID 900916, which is the State Plane Coordinate System of 1983. It measures distances in linear feet.

“But wait!”, you say. “The Earth is an oblate spheroid! How do we transform degrees into coordinates on a plane?” I’m glad you asked! A projection takes points on a sphere and maps them down onto a plane (with math). There’s an issue, though. Spheres and spheroids, like the Earth, don’t project perfectly onto a plane, so there’s always some kind of distortion (for comparison, try flattening an orange peel without tearing it). But different projections introduce different amounts of distortion, and there are ways to get projections that are perfectly suitable for the type of mapping you need to do. Each SRS has information on how to handle projections into that SRS’s coordinates.

So which SRS do we use? Like I said, there are thousands, but we only care about one tiny part of the globe: Ann Arbor, Michigan. It turns out that there are local SRSs tailored to specific areas around the world. This makes sense: the closer you get to the surface of a sphere, the flatter that surface gets, and the easier it becomes to transform points on a sphere to points on a plane. If you try to flatten that orange peel, who cares if there’s a tear through Norway if you’re only concerned with coordinates in Ann Arbor, MI?

For this data, we’ll be using SRID 102121, which is a local coordinate system developed for use in Michigan. 102121 measures distances in feet; there’s also 102123 for distances in meters, but since the ordinance uses feet, we’ll stick with 102121. The Michigan DNR has a fantastic PDF that explains coordinate systems and map projection much better than I can. I actually didn’t notice until I started importing Shapefiles into PostGIS that 900916 is an ever-so-slightly more accurate SRS for where we are. All of my visualizations use SRID 102121, though, so we’ll stick with that, but keep in mind that I didn’t actually have to modify the SRID of the imported data.

To summarize: the city provides map data as points on an oblate spheroid, and we need to transform those into points on a plane so we can measure the distance between things in feet instead of degrees. This requires switching our SRS from a GCS to a PCS.

Loading Data Into PostGIS

I’m going to skip setting up Postgres/PostGIS. For those not familiar, Postgres is a powerful free and open source SQL database and PostGIS is a set of extensions to make working with GIS data easier. Postgres and PostGIS have a dazzling array of features that make it easy for us to do analysis like this.

If you’d like to follow along, you should install Postgres, then install and enable the PostGIS extensions. You shouldn’t need anything besides the defaults. I can cover getting started with Postgres/PostGIS in another article if there’s interest.

First we have to grab the actual Shapefiles. Shout out to Ann Arbor city government for providing this data. Open civic data is important and even if it doesn’t seem like it, a lot of us appreciate it because it enables us to do things like this. Plus map-making is fun!

Download: parcel Shapefile

Download: zoning Shapefile

Once they’re extracted, we can load them into PostGIS with ogr2ogr:

ogr2ogr -f "PostgreSQL" PG:"dbname=<dbname> host=127.0.0.1 user=<username> password=<password>" AA_Zoning_Districts.shp -nln zones -nlt PROMOTE_TO_MULTI -lco precision=NO -t s_srs epsg:102121

ogr2ogr -f "PostgreSQL" PG:"dbname=<dbname> host=127.0.0.1 user=<username> password=<password>" Parcels.shp -nln parcels -nlt PROMOTE_TO_MULTI -lco precision=NO -t s_srs epsg:102121

-nlt PROMOTE_TO_MULTI prevents an error about incorrect geometry types and -lco precision=NO prevents an error about numeric overflow. -t s_srs epsg:102121 transforms our geometry from the 1983 Michigan State Plane SRS into Michigan GeoRef.

We’ll also want to add a spatial index on each table, which will make computing things like intersections and distances much faster:

CREATE INDEX zones_gix ON zones USING gist (wkb_geometry)

CREATE INDEX parcels_giz ON parcels USING gist (wkb_geometry)

We now have a Postgres-queryable map of both Washtenaw County parcels and Ann Arbor zoning districts!

Part and Parcel

Now both maps are loaded into PostGIS and we need to figure out which parcels are inside which zones. What we’ll have to do is go through each parcel and check if it’s “inside” a zone. Before I go further, let me say this: geometry is tricky. Words like “within”, “contains”, “touches”, “intersects”, “overlaps”, etc. all have very specific meanings, and they’re not necessarily intuitive.

I initially tried doing this by just checking if a zone and parcel overlap or if a zone contains a parcel, but that was difficult because of the precision of the data. Geometric predicates are very specific. For example, if a piece of geometry “overlaps” another, it means that two pieces of geometry share space between them, but one is not completely contained inside the other. But for something like “is this geometry completely covered by another geometry?”, you have to use contains/within. This works if your geometry is perfect.

The data isn’t perfect, though. Some parcels have a tiny sliver of themselves that exist outside of a zone (or in some cases, a lot more than a sliver). There’s an easy heuristic, though. We can check for overlaps and contains to narrow down our choices, but as an additional condition, we can compute the intersection between the parcel and the zone, and if the intersection of the two is at least 80% of the area of the original parcel, then the parcel is probably inside that zone. I say “probably” because there are some degenerate cases where parcels are actually split by a zone, which shouldn’t happen, but I’ve checked with official zoning maps and some parcels do in fact seem to be split zoned. That’s for the Planning Commission to deal with, though. For now we can just ignore those parcels, of which there are only about 300.

So, all we should have to do is join our parcels table with our zones table, and for each geometry pair, check if the part of the parcel inside the zone is at least 80% of the area of the parcel itself. We do that with the following query:

CREATE TABLE parcels_with_zones AS
SELECT p.ogc_fid,
       p.wkb_geometry,
       p.pin,
       z.zoningclas,
       z.minorcivil
FROM parcels p
JOIN zones z ON ST_Overlaps(z.wkb_geometry, p.wkb_geometry)
OR ST_Within(p.wkb_geometry, z.wkb_geometry)
WHERE ST_Area(ST_Intersection(p.wkb_geometry, z.wkb_geometry)) / ST_Area(p.wkb_geometry) > 0.8;

We now have a parcel map of Ann Arbor where almost every parcel has its zone associated with it! Let’s test it out:

database=# select * from parcels_with_zones limit 1;
 ogc_fid | zoningclas | minorcivil |                                                                                                     wkb_geometry                                                                                                     |       pin        
---------+------------+------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------
    1313 | R1D        | 005        | 0106000020E98E010001000000010300000001000000050000003AD06949DA1C61414C5AB985164F6DC13258F038E11C61416145A2DC154F6DC1EDA7D6FFE11C61417C98E4531F4F6DC10E754D10DB1C61414EB7FBFC1F4F6DC13AD06949DA1C61414C5AB985164F6DC1 | 09-09-10-304-032

A Taco Truck On Every Corner… or Not?

We can now find all of the parcels that satisfy the following conditions:

  • The parcel is in one of the COMP zones.
  • The parcel is at least 200 feet away from the nearest residential parcel.

PostGIS has a handy function called ST_DWithin that takes two geometries and a distance and tells you whether or not they’re within that distance of each other. For multiploygons, which all of our geometry is, I’m almost positive this uses the nearest two points of each geometry, which is conveniently how the ordinance assumes distance as well.

This will give us the opposite result, though. This will tell us everywhere food trucks aren’t allowed, but we’re interested in knowing where they are allowed. I initially tried checking to see if residential parcels were not within 200 feet, but there will always be a parcel more than 200 feet away from your COMP parcel, so that wouldn’t work. What we’ll have to do is use set operations to subtract the set of parcels where food trucks aren’t allowed from the total set of all COMP parcels, which will leave us with the set of parcels where they are allowed.

The query is straightforward:

CREATE TABLE food_trucks AS
SELECT pin,
       wkb_geometry,
       zoningclas
FROM parcels_with_zones
WHERE zoningclas IN ('O', 'ORL', 'P', 'C1', 'C1A', 'C1A/R', 'C2A', 'C2A/R', 'C2B', 'C2B/R', 'C3', 'M1', 'M1A', 'M2')
EXCEPT
SELECT t1.pin,
       t1.wkb_geometry,
       t1.zoningclas
FROM parcels_with_zones t1
JOIN parcels_with_zones t2 ON t1.ogc_fid != t2.ogc_fid
AND t1.zoningclas IN ('O', 'ORL', 'P','C1', 'C1A', 'C1A/R', 'C2A', 'C2A/R', 'C2B', 'C2B/R', 'C3', 'M1', 'M1A', 'M2')
AND t2.zoningclas IN ('R1A', 'R1B', 'R1C', 'R1D', 'R2A', 'R2B', 'R3', 'R4A', 'R4B', 'R4C', 'R4D', 'R6')
AND ST_DWithin(t1.wkb_geometry, t2.wkb_geometry, 200);

We create a table that has all of the COMP zoned parcels, then do a set subtraction of all the COMP parcels that are within 200 feet of a residential parcel. For a set subtraction A - B, the operation will return all of the items in A that are not in B. For example: {1, 2, 3, 4} - {1, 2} will return {3, 4}. This query effectively removes the parcels where food trucks would be illegal from the set of all parcels, leaving parcels where they would be legal.

Visualization

We now have a PostGIS table with geometries for all parcels where food trucks are allowed. We can combine this with our parcels_with_zones table to visualize where food trucks are allowed. The lightest green are parcels that are unrelated to our consideration of the ordinance. The next lightest green are all residential parcels. The darker green are all COMP parcels, and the darkest green are COMP parcels that are at least 200 feet away from any residential parcels (so presumably, where food trucks would be allowed).

North Ann Arbor

North Ann Arbor

South Ann Arbor

South Ann Arbor

Downtown Ann Arbor

Downtown

Second Pass

This was a great first pass. I was initially okay with this solution because I managed to convince myself that if any part of the parcel was within 200 ft of a residential zone then the entire parcel wasn’t allowed to have food trucks. I’m pretty sure that’s not how buffer zones work, though. Luckily, if we change our approach slightly, we can easily figure out precisely where food trucks are allowed, even if the buffer ends halfway through a parcel. Here’s how we’ll do it:

  1. PostGIS provides a function, ST_Buffer, to generate buffer geometry around shapes. We give it a radius of 200 feet and it creates geometry that covers all points within 200 feet of a shape. In our case, we can use it to generate a 200-ft buffer zone around parcels.
  2. Once we have about 410,000 polygons, we (slowly) ST_Union all of them to create one multipolygon.
  3. We perform a CROSS JOIN between our parcels_with_zones geometry and the ultra-food-truck-buffer-zone geometry, but only for parcels that are COMP zoned. This should leave behind either empty GeometryCollections or polygons, which represent the COMP areas that food trucks are allowed to be.
  4. Finally, we clean up the data a bit so it’s ready to be visualized!

Creating a 200-ft Buffer Zone

The ordinance specifies a 200-ft buffer zone around all residential, so let’s create that (I couldn’t think of a better name for it):

CREATE TABLE buffer_zone AS
SELECT ST_Union(ST_Buffer(wkb_geometry, 200.0)) AS wkb_geometry
FROM parcels_with_zones
WHERE zoningclas IN ('R1A', 'R1B', 'R1C', 'R1D', 'R2A', 'R2B', 'R3', 'R4A', 'R4B', 'R4C', 'R4D', 'R6');

This will create rounded buffer geometry at each point that covers 200 feet in all directions for all residential parcels and then merge them all into one multipolygon. If we open it in QGIS, it should look something like this (the buffer shapes are in yellow):

200-ft buffer zone

This gives us a 200-ft zone around all residential parcels in Ann Arbor. Pretty cool, right?

Subtracting the Buffer

Now that we have the area where food trucks aren’t allowed, we can perform a subtraction (or difference, for geometry) to get the areas where food trucks are allowed. We’ll do this with the ST_Difference(geom a, geom b) function. Here’s a quick refresher on the various operations you can perform on geometry.

Geometric operations

Source

Here’s our query:

CREATE TABLE food_trucks AS
SELECT ST_Difference(t1.wkb_geometry, t2.wkb_geometry) AS wkb_geometry,
       t1.zoningclas
FROM parcels_with_zones t1
CROSS JOIN buffer_zone t2
WHERE zoningclas IN ('O', 'ORL', 'P', 'C1', 'C1A', 'C1A/R', 'C2A', 'C2A/R', 'C2B', 'C2B/R', 'C3', 'M1', 'M1A', 'M2');

t1 is our parcel table, and t2 is the buffer region. We want to remove the parcel geometry that shares space with the buffer zone geometry for only COMP-zoned parcels. This removes all of the parcel geometry where food trucks aren’t allowed (ignoring zones where they’re banned entirely, such as D1).

The CROSS JOIN lets us check each parcel against the buffer_zone object without specifying a join condition, which we don’t have because the buffer_zone table only contains one row.

If we open this in QGIS, we get something like this:

These are all of the areas where food trucks would be permitted. If we zoom in a bit, we can even see outlines of the buffer zone created by the residential parcels.

Bonus Visualization: Color!

Let’s add a little color! White is unrelated parcels, green is residential, purple is COMP parcels that are too close to residential to host food trucks, and blue is where food trucks are allowed.

North Ann Arbor

South Ann Arbor

Downtown

Conclusion

What does this data tell us? Assuming I chose the zones correctly, I think we can safely say that this wouldn’t be a de facto ban on food trucks. I have no idea if it’s too little or too much, but there appear to be several areas for vendors to set up shop. I’m not sure what will be economically viable for them, but that’s a separate issue.

Next Steps

It sounds like the Ordinance Revisions Committee meeting went really well. Lots of people showed up to offer suggestions and talk about why this was important to them and it sounds like the Committee was receptive.

Another good idea would be to clarify which zones are being considered as commercial, office, industrial, and parking (specifically, whether or not mixed-use commercial/residential zones count as commercial or residential for the sake of the ordinance).

I also plan to offer up some of this as interactive maps with OpenLayers, perhaps even replacing the images with explorable maps. This involves a little more effort to set up, though. I’ll write another post when I have some time to implement that.

Get In Touch

Finally, I hope this is the beginning of something great. There’s a lot we can do with public data like this, and this is just the beginning. If you have any ideas for things you’d like to see mapped, or questions you think we can answer with open data, or even if you just want to say “hi”, send me an e-mail at hello@a2civic.tech.

Thanks!