HOW IT’S BUILT

Behind the map

Every question a resident has about their city already has an answer somewhere: on a city web page, in a PDF, or in an app that wants a postal code first. Almost none of them show the whole picture. Layers.City pulls the cities’ own open data into one map that keeps itself current.

The problem

A city is divided up a dozen ways at once: official neighbourhoods, council wards, provincial or state and federal districts, school board wards, business areas, the names locals actually use, and the transit lines that tie them together. Each lives in a different dataset, published by a different office, in a different format. Answering “who represents this address, what’s it like to live here, and how do I get downtown?” means opening five websites.

Layers.City puts them on one map you can switch on and off. Tap any spot, or search an address, and a single card answers it: every boundary the spot falls in, who represents it, what housing and commuting look like there, the transit, schools, libraries and parks nearby, road closures, what’s being built, and on election day, where to vote.

2cities live, on one engine
~30layers per city
235neighbourhood guides
25automated data jobs

Principles

Most of the work was deciding what the map should not do. These rules came first and shaped every feature after.

Built for two people
A resident learning things they didn’t know about their own city, and a newcomer getting their bearings. Every feature had to serve one of them.
Describe, don’t rank
The card describes a place someone picked. Demographic data never feeds rankings, filters or “best neighbourhood” lists, and there are no crime or school-score layers. Bus frequency is a description; a safety score would be a judgement.
Current or not at all
If the data isn’t current and accurate, the feature waits. A Hyderabad map was scoped and then shelved because its new ward boundaries aren’t published yet; Toronto school attendance areas were dropped because the board’s terms don’t allow reuse.
Honest about uncertainty
Road closures show permit dates, labelled as not a reopening forecast. Census figures carry their year. Informal neighbourhood names are marked approximate. Every source is credited.
Private by design
No accounts, ads or tracking cookies. “Use my location” and every lookup run in the browser; visit counts never include places, pins or searches.

Architecture

There is no server and no database. Scheduled jobs turn the cities’ open data into static files, and a CDN serves them. That keeps it fast, free to run and hard to break.

  1. 1 · SOURCESOpen dataCity portals, transit schedules (GTFS), election offices, the census, OpenStreetMap, Wikipedia.
  2. 2 · FETCHScheduled jobsGitHub Actions pull each source on its own rhythm: daily, weekly, monthly or hourly on election night.
  3. 3 · BUILDGeometry pipelinePython (Shapely) and mapshaper clean, join, clip and simplify everything into small GeoJSON files.
  4. 4 · PUBLISHStatic siteChanged files deploy to Cloudflare Pages automatically, about a minute after the data changes.
  5. 5 · USEWeb and iPhoneThe map runs in the browser with Leaflet; a native iOS app reads the same files.

One engine, a folder per city

The map is a single engine that knows how to draw kinds of layer: filled areas, boundaries, streets, transit lines, points of interest. Everything specific to a city (its layers, labels, colours, the rows on its card, its tour) lives in that city’s configuration file. Chicago went live without changing the engine; adding a city means adding its data and settings, not rewriting code.

A contract for the app

The iPhone app reads the website’s published files, so the files are treated as an API. A written data contract describes every file and field, changes are additive by default, and anything that would break the app bumps a schema version first.

Small on purpose

Each city’s data is a few megabytes, loaded in stages: what the first screen needs, then the rest after the map appears. The page has no framework and no build step beyond assembling one HTML file per city, and it follows the phone’s light or dark setting.

Keeping it current

A map that rebuilds itself needs to notice when a rebuild quietly stops. When one did (a scheduled job kept skipping because the scheduler started hours late), it became a feature:

  • A daily freshness check reads the date inside every live file and the last run of every scheduled job, and opens an alert, emailed to me, when anything is stale or failing. It closes the alert itself once things recover.
  • Freshness on the map. Live layers show “as of” dates, turning red when they’re older than they should be.
  • Report a problem. Every card ends with a link that writes an email for the visitor, with nothing sent unless they press Send.

How I built it

I built Layers.City with Claude, Anthropic’s AI model, as the engineering partner. Claude wrote most of the code and pipelines. My job was the one I do at work as an operations and business analyst: turning a fuzzy need into requirements, making the calls, and testing the result against reality.

  • Product ownership. I set the problem, the two audiences and the principles above, and prioritised a backlog from questions I had in my own years in Toronto and from friends and family in Chicago.
  • Scouting before building. Every data-heavy feature started with a written scouting report: what’s published, how current it is, its licence, its gaps. Several features were shelved at this stage, which is the cheapest place to stop.
  • Decisions on record. Trade-offs were written down with their reasons: half a mile instead of a mile for Chicago’s walking radius, why driving times are left out for city neighbourhoods, which hospitals and colleges count.
  • Acceptance testing. I checked the output against what I know of Toronto, tested on my own phone, and sent features back when they read oddly to a resident.
  • Licences and privacy. I reviewed each source’s terms before use, and set the rule that visit counts never record places, pins or searches.

Working this way, one person shipped a scope that would usually take more than one: two cities, a pipeline of two dozen automated data jobs, the data behind a native iPhone app, and a deployment that watches itself. The judgement about what to build, and what to leave out, stayed human.

Open Toronto’s map Open Chicago’s map Contact me on LinkedIn