u/Kingleyend

Image 1 — Multi-city OSM extraction pipeline: 332k features across 4 cities, manifest-driven architecture, Overpass-only — Cities Skylines 2 mapping use case but the GIS bits are generic [OC]
Image 2 — Multi-city OSM extraction pipeline: 332k features across 4 cities, manifest-driven architecture, Overpass-only — Cities Skylines 2 mapping use case but the GIS bits are generic [OC]

Multi-city OSM extraction pipeline: 332k features across 4 cities, manifest-driven architecture, Overpass-only — Cities Skylines 2 mapping use case but the GIS bits are generic [OC]

Sharing v3.3 of an open-source project that pulls landuse/building/service polygons from OpenStreetMap via Overpass and classifies them into 11 zoning categories. The use case is reference data for a city-building game (Cities: Skylines 2), but the pipeline is generic for urban analysis at scale.

Just refactored from single-city (Minneapolis) to multi-city. 4 cities now in the same pipeline. Sharing some of the architecture decisions in case they're useful for others doing similar OSM-at-scale work.

**Architecture changes from single-city → multi-city:**

  1. **City registry as single source of truth.** A root `cities.json` declares: slug, name, bbox, country, what modules are supported. Pipeline reads this; no more bbox constants scattered across extract scripts. Adding a city = adding 1 entry + running 3 extract scripts.

  2. **Per-city manifest with content hashes.** Each city has `visualizer/cities/<slug>/manifest.json` listing every prebuilt module + sha256 of the JS file. The web viewer fetches the manifest, only injects `<script>` tags for modules that exist, and uses the sha256 as a cache-busting query string. Means a zoning-only city like Amsterdam doesn't try to load a roads layer.

  3. **Killed the live-Overpass fallback.** v3.2 had ~300 lines of code that would re-query Overpass at view time if the prebuilt JSON was missing. Sounded great, broke half the time — Overpass community endpoints return 200 with empty `{"elements": []}` under load instead of 504, so the fallback's retry logic was useless. v3.3 is prebuilt-only. More reliable, ~30% less code.

**Overpass-specific gotchas worth flagging:**

- **Spatial joins with named sets are non-obvious.** For mixed-use detection (commercial POIs inside apartment building polygons), the naive `(around:5)` returns 0. The fix is explicit named sets:

```overpass

(

node["shop"](bbox);

node["amenity"~"^(restaurant|cafe|bar|pub|fast_food)$"](bbox);

)->.comm;

(

way["building"="apartments"](around.comm:5);

way["building"="residential"](around.comm:5);

);

out body geom;

```

This returns 123 polygons in Minneapolis. Naive query returns 3.

- **Multi-endpoint rotation matters at scale.** I rotate across `overpass-api.de`, `kumi.systems`, `openstreetmap.ru`, `maps.mail.ru` with 3s/6s/12s backoff. For 332k features across 4 bboxes, hitting just one endpoint = guaranteed throttle.

- **Cross-country tagging variance.** Amsterdam's Dutch OSM contributors tag at parcel resolution (89k features for a small bbox — densest per km² of the four cities). US cities are much sparser — Charleston was 14.5k for a similar-sized historic district. The classifier intentionally degrades gracefully — anything unclassifiable gets dropped, not mis-classified — but raw count varies by contributor density, not just urban form.

- **Streaming sha256 for prebuilt manifest.** With 100 MB of prebuilts across 4 cities, computing hashes by reading the whole file into memory was wasteful. Switched to chunked streaming (8 KB chunks), zero impact on RAM.

**Stats per city (zoning only — Mpls also has roads + services):**

- Minneapolis: 192k features (zoning + 108k roads + 2.3k services)

- Amsterdam: 89k features (densest per km², fine-grained OSM tagging at parcel resolution)

- Madison: 37k features

- Charleston: 14.5k features (added via community-request workflow, 30 min from issue to deployed — validates the per-request bbox→prebuilt pipeline at human scale)

**Stack:**

- Python 3.11+, Overpass API, no PostGIS, no QGIS, no geopandas

- Leaflet.js + Canvas renderer for the visualizer

- 171 pytest tests passing

- MIT license

**Repo:** https://github.com/Osyanne/CitiesSkylines2-osm-toolkit

**Hosted viewer:** https://osyanne.github.io/CitiesSkylines2-osm-toolkit/

**Methodology doc** (in repo): walks through every classification decision + the spatial-join gotcha + the multi-endpoint retry logic

---

## Contributing / requesting cities

Adding a new city is a single GitHub Issue: bbox + slug + name. Pipeline runs on my end in ~30 min, no Python or Overpass knowledge required on the requester side. Useful for folks who want to see the classifier output for a specific area without setting up the toolchain locally.

**Template:** [city-request issue](https://github.com/Osyanne/CitiesSkylines2-osm-toolkit/issues/new?template=city-request.yml)

---

Feedback welcome. The contributor-density variance between Amsterdam (89k features tagged at parcel resolution) and US cities (sparser, gappier) was the most interesting bit — if you've worked on normalizing OSM extraction across regions with different contributor cultures, I'd love to compare notes.

u/Kingleyend — 3 days ago

[Tool Update] Real-world zoning maps for CS2 — now 6 cities included, hosted in your browser, free, and you can request your own city

Hey r/CitiesSkylines2 !

A few days ago I posted my open-source Minneapolis 1:1 tool (extracts real-world zoning, roads, services from OpenStreetMap as a CS2-style visualizer). The feedback was great, and one comment kept coming up:

&gt; "Cool, but I want MY city."

So that's what v3.3 does. Multi-city. Hosted. Request yours in one click.

**What's new in v3.3 — Featured Cities Pack:**

🌎 **4 cities now live** — all loaded in the same hosted viewer:

| City | Modules | Features |

|---|---|---|

| **Minneapolis, MN** 🇺🇸 | Zoning + Roads + Services (hero) | 192k |

| **Amsterdam** 🇳🇱 | Zoning | 89k |

| **Madison, WI** 🇺🇸 | Zoning | 37k |

| **Charleston, SC** 🇺🇸 | Zoning (added via community request, 30 min after issue) | 14.5k |

**~332,000 real-world features rendered**, all classified into the 11 official CS2 zone categories.

🌐 **Hosted viewer — zero install:**

**→ https://osyanne.github.io/CitiesSkylines2-osm-toolkit/

Just open the URL. Pick a city card. Pan/zoom on a dark CS2-style map. Everything is prebuilt and cached, loads in 1-2 seconds even with 192k polygons.

----

## 🏙️ Want YOUR city next?

**One-click GitHub Issue template.** Submit bbox + name + slug → I generate the prebuilt zoning data and publish it (~30–60 min when actively maintaining). No fork, no Python, no Overpass knowledge required.

**→ [Request your city here](https://github.com/Osyanne/CitiesSkylines2-osm-toolkit/issues/new?template=city-request.yml)\*\*

&gt; Real social proof: **Charleston, SC** was filed as an issue today and went live ~30 min later. The workflow works — if you'd actually use this for your CS2 build, file a request.

---

  1. **Killing the live-Overpass fallback was the right call.** v3.2 had ~300 lines of code that would re-query Overpass if the prebuilt was missing. Sounded nice in theory, in practice it broke half the time (silent empty responses, timeouts on big bboxes). v3.3 is prebuilt-only, much more reliable.

  2. **OSM coverage varies wildly between cities.** Amsterdam was the densest at 89k features for the same-ish bbox size as Madison's 37k — Dutch OSM contributors tag at parcel resolution. Charleston was the sparsest at 14.5k features (smaller historic district + less granular tagging in the US South). Method had to be country-agnostic.

**Current state:**

- **171 pytest tests passing** (127 historic + 44 new for multi-city)

- **~100 MB prebuilts in repo** (zero external downloads)

- **4 city thumbnails** auto-generated via Playwright

- **MIT license, zero API keys, zero PostGIS/QGIS dependency**

**What's next:**

I'm gating the next step on real demand. If this v3.3 release lands well (>20 city requests or >50 stars in 7 days), I move to **Phase 3: heightmap pipeline** — terrain data via SRTM/OpenTopography, exported as CS2-ready 16-bit PNG. If demand is moderate, I do the Infrastructure module (electricity, water, waste) for Mpls instead. If demand is low, I move to a different open-source project.

So if you'd actually use this for your city or wanted a heightmap export, leaving a star or filing a city request is the signal that keeps it going.

**Repo:** https://github.com/Osyanne/CitiesSkylines2-osm-toolkit

Happy to answer questions. Amsterdam's canal-grid zoning is honestly worth a look — fine-grained tagging at parcel resolution that reveals genuine mixed-use intent block by block.

u/Kingleyend — 3 days ago
▲ 454 r/openstreetmap+2 crossposts

[OC] I built a free tool that maps real Minneapolis zoning into the 11 official CS2 zone types — open source, works for any city

>Hey !

After spending way too many hours trying to build a 1:1 Minneapolis in CS2, I got frustrated guessing where Mixed Housing should go and which corridors are actually high-density commercial vs just stripes of Low Density Business. So I built a tool that pulls real-world zoning data from OpenStreetMap and classifies every block into the exact 11 zones CS2 has in the painter.

**What it gives you:**

A scrollable dark-mode map of any city, color-coded by the zones you'd actually use in-game:

🟢 **Residential (6):** Low Density Housing, Medium Density Row Housing, Medium Density Housing, Mixed Housing, Low Rent Housing, High Density Housing
🔵 **Commercial (2):** Low Density Business, High Density Business
🟣 **Offices (2):** Low Density Offices, High Density Offices
🟡 **Industrial Manufacturing**

For Minneapolis specifically that's 81,732 polygons. You can pan/zoom around and check "ok this corridor is Mixed Housing in real life, so I should zone it the same way in my build."

**Things I learned the hard way:**

  1. CS2's "Mixed Housing" doesn't exist as a clean tag in OpenStreetMap. People tag the apartment building as one polygon and the shop/restaurant inside it as a separate node. My first version found 3 Mixed Housing polygons in all of Minneapolis (lol). After switching to an Overpass spatial join (`around.comm:5`), it found 123. Big difference.

  2. I tried to fill OSM coverage gaps using Microsoft's satellite-derived building footprints (5M+ buildings in MN alone). It mostly worked but classified suburban houses near a Target store as "commercial" because my logic was naive. Script is still in the repo with a known-bug caveat if anyone wants to fix it.

  3. Leaflet's default SVG renderer chokes at 80k+ polygons. Switching to Canvas (`preferCanvas: true`) was the difference between unusable and fluid.

**How to use it:**

Clone, run `uv run extract_zoning.py`, open the visualizer. Whole thing takes 5 minutes if you have Python. If you want a different city, just change the bounding box — no code changes needed.

**New in v3.1**: Now also includes a **Road Network module** — 108,825 OSM roads classified into 6 CS2 categories (Highway / Major / Minor / Local / Pedestrian / Bike). Toggle modules on/off with the new pill UI to compare zones-only vs roads-only vs overlay views.

Happy to answer questions. Genuinely curious if anyone uses this for their own 1:1 build.

u/Kingleyend — 5 days ago