Read-only mirror of https://github.com/StataBS/quartierfarben — Kanton Basel-Stadt. Issues & pull requests at the source.
  • JavaScript 93.1%
  • Svelte 4.5%
  • Python 2.3%
  • CSS 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Orhan Saeedi c40a5887d8 Add OGC API Features loading functionality in etl.py and update .gitignore
- Introduced a new function to load collections from OGC API Features (WFS3) as GeoJSON, supporting pagination and CRS transformation.
- Updated the return values in the main function to include the new OGC features URL.
- Added 'snapshots/' to .gitignore to exclude snapshot files from version control.
2026-08-23 16:15:19 +02:00
.cursor Add geocoding functionality with support with the help of MapBS-API 2026-04-10 16:40:59 +02:00
.github Remove workflows and add a not in the README on how to get to the tiles 2026-02-03 11:26:37 +01:00
scripts Add script to export map legend as SVG, PDF, JPEG, and TIFF formats 2026-06-11 10:50:31 +02:00
src Update package-lock.json and yarn.lock to include new dependencies and versions, enhancing project compatibility. Add @anthropic-ai/sdk, @babel/runtime, and @colors/colors, while updating @colordx/core and several @inquirer packages. Revise localization files for German and English to improve footer content and accessibility statements. 2026-07-02 11:39:29 +02:00
static Update QR code image in static assets to replace the expired one 2026-04-26 22:04:20 +02:00
.DS_Store add tracker 2023-06-05 15:28:25 +02:00
.gitignore Add OGC API Features loading functionality in etl.py and update .gitignore 2026-08-23 16:15:19 +02:00
etl.py Add OGC API Features loading functionality in etl.py and update .gitignore 2026-08-23 16:15:19 +02:00
LICENSE Initial commit 2023-03-01 09:12:12 +01:00
package-lock.json Update package-lock.json and yarn.lock to include new dependencies and versions, enhancing project compatibility. Add @anthropic-ai/sdk, @babel/runtime, and @colors/colors, while updating @colordx/core and several @inquirer packages. Revise localization files for German and English to improve footer content and accessibility statements. 2026-07-02 11:39:29 +02:00
package.json Add Fira Mono font support and update postcard back layout with new pie chart functionality. Remove unused checkCircleFits script, refactor PrintAndDownload component for improved export handling, and enhance Map and Postcard components with new styling and structure. Update package dependencies for D3 libraries. 2026-04-13 20:32:24 +02:00
postcss.config.cjs add files 2023-03-01 16:19:18 +01:00
README.md Update README.md with new url 2026-04-27 14:40:27 +02:00
svelte.config.js Add new control BASE_PATH 2026-02-04 16:26:44 +01:00
tailwind.config.cjs Refactor project structure by removing bun.lock, updating package.json and package-lock.json, and enhancing styling to cooperat design (using DDS from the canton) with Tailwind CSS. Introduce new components and scripts for improved functionality, while deleting unused scripts and components to streamline the codebase. 2026-04-13 09:43:47 +02:00
vite.config.js Refactor project structure by removing bun.lock, updating package.json and package-lock.json, and enhancing styling to cooperat design (using DDS from the canton) with Tailwind CSS. Introduce new components and scripts for improved functionality, while deleting unused scripts and components to streamline the codebase. 2026-04-13 09:43:47 +02:00
yarn.lock Update package-lock.json and yarn.lock to include new dependencies and versions, enhancing project compatibility. Add @anthropic-ai/sdk, @babel/runtime, and @colors/colors, while updating @colordx/core and several @inquirer packages. Revise localization files for German and English to improve footer content and accessibility statements. 2026-07-02 11:39:29 +02:00

Quartierfarben, aka Grätzlfarben, aka Kiezcolors

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!

Tech stack

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.

Developing

Install dependencies by running:

npm install

Start a development server by running:

npm run dev

or start the server and open the app in a new browser tab

npm run dev -- --open

Building

To create a production version of your app:

npm run build

You can preview the production build with npm run preview.

To deploy your app, simply copy the build folder to your web server.

Data Processing Pipeline & Tile-Creation

The data processing was done in Python and marimo.

Check it out on molab: Open in molab

Sources (WFS)

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

Retrieval

  • 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).

Pipeline (high-level)

  1. Bodenbedeckung base layer

    • Load all ms:BS_Bodenbedeckungen*.
    • Add a sequential laufnr (1-based) to ensure row-unique IDs.
  2. Attach building categories (Gebäudekategorien → Gebäude)

    • Filter buildings: bs_art_txt == "Gebaeude.Gebaeude".
    • sjoin(intersects) to attach gebaeudekategorieid.
    • If a building hits multiple categories, resolve by largest % area overlap (intersection area ÷ building area).
    • Map gebaeudekategorieid to German labels and construct nutzung = "Gebäude - <Label>".
  3. 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_pct and label öffentlicher Raum vs kein ö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 = cleaned bs_art_txt (dots → -, underscores → space).
  4. Kultur overrides (nearest point → building)

    • From BI_KulturUnterhaltung points: nearest-building join in LV95.
    • Max distance = 50 m.
    • Where matched building exists, override
      nutzung = "Gebäude - <bi_subkategorie>".
  5. 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_schultyp or so_schultyp).
      • Concatenate to gdf_schulstandorte.
    • 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.)
  6. Export

    • Reproject to WGS84 (EPSG:4326). Necessary for tippecanoe
    • Write landuse.geojson.

Tiles (Vector MBTiles/PMTiles directory)

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 uv to execute the marimo script (PEP-723 header drives Python & deps), then runs tippecanoe, and commits static/tiles/.

Notes & Limitations

  • 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.

Data Licence

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.

Adapting to your city

The application is built to be easily implemented in other cities if suitable data is available.

Reference implementation (opendatabs/quartierfarben)

The opendatabs/quartierfarben repository on GitHub is the baseline fork-oriented app: it still ships optional Nominatim geocoding (for regions without a canton search API like MapBS) and polygon analysis modes (e.g. choosing an area such as Wahlkreis or Wohnviertel from a dropdown). This codebase removes those features on purpose—smaller bundle, simpler UX, and less runtime work—while keeping circle analysis and optional neighbourhood labels for the postcard. If you need Nominatim or full polygon modes, compare against or port from that repo.

What to adapt Where
App name, map bounds, URLs, etc. src/lib/settings.js — at least: projectTitle, og_siteName, mapBounds, initialMapCenter, url
Address search (geocoding) src/lib/geo/geocodeSearch.js (searchAddresses) → src/lib/geo/mapBsGeocode.js. UI entry: src/components/search/requestAddress.js (used by the sidebar search).
Categories and colors src/lib/colors.jsoncategories, palettes, and landuseMapping (see Land-use data below)
Analysis area & neighbourhood label src/lib/cityConfig.js — see Analysis (circle) & neighbourhood name below
Texts and labels src/locales/
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

This deployment (Kanton Basel-Stadt) uses the cantons Search / Suchdienst API (documentation).

Analysis (circle) & neighbourhood name

Land-use analysis uses a circle around the map focal point only. Configuration is in src/lib/cityConfig.js:

  • areaModes — must include the circle entry (only analysis mode in this deployment).
  • circleLocationNeighbourhood — optional GeoJSON + nameProperty used for the postcard / map label (e.g. Wohnviertel name via point-in-polygon at the centre). Set data to null to disable neighbourhood names, or point data at your own boundary GeoJSON and set nameProperty to the label attribute.

Land-use data

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.jsonlanduseMapping; 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).

Kiosk mode

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:

statistik.bs.ch/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://statistik.bs.ch/quartierfarben?kiosk

Contributing

Before you create a pull request, write an issue so we can discuss your changes.

Contributors

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.

Contact

If you have any questions, please contact opendata@bs.ch

Content Licensing

Texts and content available as CC BY 4.0.