How we built a real San Francisco map from OpenStreetMap in three.js
Zombie SF is a static site: an index.html, a folder of ES modules, a vendored copy of three.js and one JSON file of map data. No bundler and no server. Here is how a slice of San Francisco gets from OpenStreetMap into the browser.
1. Export the neighborhood
We pull a bounding box around the Tenderloin and Mid-Market from OpenStreetMap as JSON, for example with an Overpass query that returns ways and nodes. The raw export holds every building outline, every street centerline with its tags, and every point of interest, from laundromats to bike-share docks.
2. Project to local meters
A few kilometers of city is small enough that a flat projection is fine. A tiny Python script converts latitude and longitude to meters around a fixed origin:
LAT0, LON0 = 37.78390, -122.41325 # Eddy & Jones kx = 111320 * cos(radians(LAT0)); ky = 110540 x = (lon - LON0) * kx # east z = -(lat - LAT0) * ky # south (three.js -z is north)
Then everything is shifted so the exact node shared by Eddy Street and Jones Street sits at (0, 0). The player spawns there.
3. Keep only what the game needs
- Buildings: the footprint polygon, plus a height from the
heighttag, orbuilding:levels × 3.4 m + 1 m, or a plausible default when neither exists. The name is kept too, which matters later. - Roads: centerlines for drivable highway classes, with name, lane count and the
onewayflag. - Points of interest: shop and amenity nodes (837 of them) become storefront signs. Bay Wheels docks become rideable e-bike stations.
The output is a compact map.json: 1,400 buildings, about 500 road ways and the POIs, all in local meters with one decimal place.
4. Build the city in three.js
Buildings are extruded footprints with procedurally generated window textures, lit or unlit at random to suggest people living there. Repeated objects, like street lamps and their light pools, parked cars, trees, hydrants and other props, use InstancedMesh, so thousands of objects cost a handful of draw calls.
The OSM names drive a lot of the character for free:
- A building named "… Hotel" gets a vertical neon HOTEL sign on its most street-facing wall.
- A building named "… Theatre/Theater" gets a marquee with chaser bulbs and a vertical blade sign.
- Shop tags map to sign text:
laundry→ LAUNDROMAT,pawnbroker→ PAWN, and so on. - A few known buildings get rooftop signs, such as the X on Market Square.
Rendering uses a moonlight shadow map, an UnrealBloomPass for the neon, and exponential fog for Karl. On touch devices, a "lite" path drops shadows, lowers resolution and cheapens the bloom.
5. Traffic on the real street graph
Road centerlines are stitched into a graph at shared nodes. Vehicles pick edges, respect oneway, and use a look-ahead box: if the player or another car is inside it, they brake. Zombies are deliberately left out of that check, which produces the robotaxi roadkill. The cable car simply follows the nodes of the ways named "Powell Street", sorted from Market northward.
6. Zombie pathfinding: a flow field
Dozens of zombies each running A* every frame would be expensive. Instead, the walkable city is rasterized onto a 2 m grid once, and each tick a single breadth-first search runs outward from the player's cell. That produces a distance field, and every zombie just steps toward its lowest-distance neighbor. One search serves every zombie, and they naturally route around real blocks and funnel through real alleys.
7. Models and licensing
Vehicles, the zombie, the cable car, pigeons and props are CC BY 4.0 models from Sketchfab, simplified and re-encoded with WebP textures to keep downloads small. Map data is © OpenStreetMap contributors under the ODbL, credited on the title screen.
What we would do differently
- Pull
roof:shapeandbuilding:colourwhere they are mapped. Coverage is patchy but improving. - Tile the map and stream it, so the playable area could grow past one neighborhood.
- Store the map in a binary format instead of JSON. It is about 340 KB now, which is fine but could be a third of that.
If you map in San Francisco, thank you. Every building height you add makes the skyline in this game more accurate.