Blog · October 20, 2026 · 8 min read

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

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:

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

If you map in San Francisco, thank you. Every building height you add makes the skyline in this game more accurate.

PLAY ZOMBIE SF FREE