Quartierfarben is a map based tool that creates a postcard showing the landuse distribution in your neighborhood in Basel-Stadt. It is based on the Kiezcolors tool of the Open Data Informationsstelle Berlin and the Grätzlfarben of the TU Wien. By zooming in and out you can pick a location and position it inside the circle. Quartierfarben then maps the individual areas onto a tree map diagram. You can print the resulting motive as a postcard and share it!
This website is a Svelte app. The map is rendered using MapLibre GL JS with vector tiles, and the analysis is done using Turf.js. The app is built as a fully static app and does not require any active server technology.
Install dependencies by running:
npm installStart a development server by running:
npm run devor start the server and open the app in a new browser tab
npm run dev -- --openTo create a production version of your app:
npm run buildYou can preview the production build with npm run preview.
To deploy your app, simply copy the build folder to your web server.
This repository does not include the generated tiles. Tiles for the date 03.02.2026 can be downloaded from an older repository, where tiles were generated daily using GitHub Actions. To generate tiles from the latest data, follow the steps below.
The data processing was done in Python and marimo.
All datasets are retrieved from the Canton Basel-Stadt WFS using robust requests with retries and backoff.
| Theme | WFS layer(s) / prefix | Key fields used |
|---|---|---|
| Bodenbedeckung (land cover) | ms:BS_Bodenbedeckungen* |
bs_art_txt, gml_id, geometry |
| Gebäudekategorien | DM_Gebaeudeinformationen_DatenmarktGebaeudekategorie |
gebaeudekategorieid, geometry |
| Öffentlicher Raum | OR_OeffentlicherRaum_Allmend, OR_OeffentlicherRaum_Noerg |
geometry |
| Kultur (POI) | BI_KulturUnterhaltung |
bi_subkategorie, geometry |
| Schulstandorte (Basel) | ms:SC* |
sc_schultyp, geometry |
| Schulstandorte (Riehen/Bettingen) | ms:SO* |
so_schultyp, geometry |
- Capabilities via OWSLib; features via
GetFeature(preferring GeoJSON, falling back to GML). - Jittered requests + retries to avoid rate-limits/outages.
- Working CRS for geometry ops: LV95 (EPSG:2056).
-
Bodenbedeckung base layer
- Load all
ms:BS_Bodenbedeckungen*. - Add a sequential
laufnr(1-based) to ensure row-unique IDs.
- Load all
-
Attach building categories (Gebäudekategorien → Gebäude)
- Filter buildings:
bs_art_txt == "Gebaeude.Gebaeude". sjoin(intersects)to attachgebaeudekategorieid.- If a building hits multiple categories, resolve by largest % area overlap (intersection area ÷ building area).
- Map
gebaeudekategorieidto German labels and constructnutzung = "Gebäude - <Label>".
- Filter buildings:
-
Classify public space for “übrige befestigte”
- Dissolve
OR_OeffentlicherRaum_*into a single geometry. - For
bs_art_txt == "befestigt.uebrige_befestigte.uebrige_befestigte", compute coverage % inside public space. - Threshold = 50 % (configurable).
- Add
oeffentlicher_raum_pctand labelöffentlicher Raumvskein öffentlicher Raum. - For these features (only), if no building category applies, set
nutzung = "befestigt - uebrige befestigte - <öffentlicher Raum|kein öffentlicher Raum>". - Otherwise (general fallback),
nutzung= cleanedbs_art_txt(dots →-, underscores → space).
- Dissolve
-
Kultur overrides (nearest point → building)
- From
BI_KulturUnterhaltungpoints: nearest-building join in LV95. - Max distance = 50 m.
- Where matched building exists, override
nutzung = "Gebäude - <bi_subkategorie>".
- From
-
Schulstandorte overrides (points inside buildings only)
- Prepare points:
- Basel (
ms:SC*): convert non-point geometries to centroids. - Riehen/Bettingen (
ms:SO*): use as-is. - Build unified
schultyp(sc_schultyporso_schultyp). - Concatenate to
gdf_schulstandorte.
- Basel (
- Nearest-building join with MAX_DISTANCE = 0 m (point must lie inside the building).
- Where matched, final override:
nutzung = "Gebäude - <schultyp>". - (School overrides take precedence over Kultur where both apply.)
- Prepare points:
-
Export
- Reproject to WGS84 (EPSG:4326). Necessary for
tippecanoe - Write
landuse.geojson.
- Reproject to WGS84 (EPSG:4326). Necessary for
Tiles are generated with tippecanoe.
This command can vary on the size of your city/region.
tippecanoe \
--output-to-directory ./static/tiles \
--layer landuse-data \
--force --no-tile-compression \
--minimum-zoom=12 --maximum-zoom=17 \
--full-detail=17 --low-detail=12 \
--no-tiny-polygon-reduction \
--detect-shared-borders \
--extend-zooms-if-still-dropping \
--no-feature-limit --no-tile-size-limit \
./{input-file}.geojson- Rebuilt daily at 05:00 UTC via GitHub Actions.
- Uses
uvto execute the marimo script (PEP-723 header drives Python & deps), then runstippecanoe, and commitsstatic/tiles/.
- WFS services may rate-limit or briefly refuse connections. The loader retries with backoff and polite jitter between layer requests.
- Ambiguous building category joins are resolved by largest percent area, not absolute area; adjust to taste.
- Distance thresholds: Kultur 50 m, Schulen 0 m (inside)—both configurable.
- All spatial analysis in EPSG:2056; export in EPSG:4326 for tippecanoe.
The landuse data Bodenbedeckung and all other data used in this project can be downloaded in the Dataportal or via WFS from the Geopotal of the Canton of Basel-Stadt and is licenced under CC BY 4.0 + OpenStreetMap.
The application is built to be easily implemented in other cities if suitable data is available.
| What to adapt | Where |
|---|---|
| App name, map bounds, URLs, etc. | src/lib/settings.js — at least: projectTitle, og_siteName, mapBounds, initialMapCenter, country, url |
| Categories and colors | src/lib/colors.json — categories, palettes, and landuseMapping (see Land-use data below) |
| Area modes | src/lib/cityConfig.js (see below) |
| Texts and labels | src/locales/ — add keys under inputs for new area modes |
| Map outline on postcard | src/lib/borders.js — a GeoJSON FeatureCollection (e.g. export default function () { return { type: "FeatureCollection", features: [...] }; }) |
| Tiles and static assets | static/ (tiles), plus any images |
Analysis modes are in src/lib/cityConfig.js. Circle mode (radius around a point) is always available; you can add polygon modes (e.g. districts, neighbourhoods) so users pick an area from a dropdown.
- Circle only: Set
areaModesto[{ id: "circle", labelKey: "useCircle", default: true }], setlocationLabelPolygonModeId = null, and remove or comment out theimport wahlkreiseData/import wohnviertelDatalines (and any polygon entries inareaModes) so the file doesn’t reference missing data. - Circle + polygon modes: Keep the default. Each polygon mode needs
id,labelKey,selectLabelKey,data(GeoJSON),idProperty, andnameProperty. Add matching keys insrc/locales/*.jsonunderinputsand a GeoJSON module per mode. Optionally setlocationLabelPolygonModeIdto the mode whose polygons supply the location name in circle mode (e.g. neighbourhood under the coordinates).
If you use your own land-use tiles (see Data & Processing for how Basel builds them): vector tiles must expose a property whose name is set in settings.js as landuseFieldname (Basel: "nutzung"). Values in that property are mapped to diagram categories via colors.json → landuseMapping; category labels and palette come from categories and palettes in the same file. The app expects the tile layer name used in your map style (e.g. landuse-data in the tippecanoe example).
For use in public settings, Quartierfarben can be run in "kiosk mode", which offers a single print button instead of download buttons for the postcard images. Only printing the postcard front (the treemap visualizaton) is supported in kiosk mode -- it is assumed that postcards pre-printed with the back side are provided on site.
To activate kiosk mode in the app, append the ?kiosk url parameter before the # sign in the url, for example:
opendatabs.github.io/quartierfarben?kiosk#13/48.20996/16.3704
You can start most browsers in kiosk mode, which causes the app to be displayed in full screen, disables any user interface elements, and supports printing without showing a dialog. E.g. for Firefox, the command to launch the app in kiosk mode would be:
"C:\Program Files\Mozilla Firefox\firefox.exe" -kiosk -private-window https://opendatabs.github.io/quartierfarben?kiosk
Before you create a pull request, write an issue so we can discuss your changes.
ODIS Berlin / CityLAB Berlin has made the biggest contribution by developing and coding the initial Kiezcolors tool. On part of the research unit cartography at TU Wien, Ester Scheck and Florian Ledermann mostly worked on the code and the documentation while Sacha Schlumpf and Andrea Binn supported with feedback and brainstorming ideas. In the Statistisches Amt Kanton Basel-Stadt Orhan Saeedi and Felix Michel adapted it for their use case.
If you have any questions, please contact opendata@bs.ch
Texts and content available as CC BY 4.0.