Catalonia liveability

mitma-sedona · geo-browser

Mobility flows →

Where could you live well in Catalonia — near a frequent train, a climbing gym or yoga nearby, a hospital in reach, green and coast close, clean air, and away from heavy industry and motorway noise? This map carries an H3 res-8 multi-criteria liveability index over 45,220 hexagons, built as an applied spatial-data-science pipeline on Apache Sedona. Every dimension is wired to real data — MITMA mobility, Renfe Rodalies + FGC GTFS, OpenStreetMap amenities, EEA tree cover, Natura 2000, XVPCA air quality, Landsat heat and VIIRS night-light — and every number is reproducible from the repository.

The liveability score is a relative index — a starting question, not a guarantee, not a recommendation, not an operational service. Distances are computed in metres (EPSG:25831). Two mobility windows live on this map, and they are kept separate on purpose: the score's mobility input is unchanged — the original 7-day March-2024 window — while the additive deep-Spark mobility & season display layers (rhythm, weekend hotspots, typology, geodemographics, the two month-window deltas) come from the full-scale 89-day 2025 run (Feb · May · Jun, a 390 M-row MITMA OD scan) and ride at weight 0 — browseable, but they do not move the score. Those season layers are a month-window comparison, not a seasonal/climate average — one cold month is not "winter climatology".

Layers

H3 res-8 cells coloured by the active field (viridis for the score). The weighted liveability output; recolour it with the preset or metric selectors below. Null cells render slate-grey — never the low end of the ramp.

All keyless. Swapping the basemap keeps the deck.gl hexes rendered on top and preserves your current view; the map credit updates to match. OSM falls back to CARTO Voyager if OpenFreeMap is unreachable.

Adjust how strongly the liveability hexes cover the basemap — drop it to read the imagery underneath. Live; no reload.

Recolour the hexes by one input dimension to read what drives the score. Diverging fields pivot at their natural midpoint; presence fields treat "no data" as a distinct grey state, not zero.

Flip the season-aware mobility metrics (weekend ratio, weekend hotspot, leisure/commute share, the 4 peak shares, peak-hour band) between three calendar month-windows — Feb vs Jun, same hexes, same crosswalk — to see where weekend pull switches ON as the coast warms. A month-window comparison, not a seasonal/climate average. Greyed when the active metric is pooled across all 89 days.

Raise each hex by the active field for a 2.5D read (tilts the camera). Elevation scale shrinks with zoom so towers don't occlude the base.

↻ Ctrl/Shift-drag + scroll for 3D navigation

Origin→destination flows from the MITMA v2 mobility matrix (cyan = origin, magenta = destination; width ∝ log trips/day). A 5,000-arc sample of the strongest corridors.

Climbing gyms (brown), yoga studios (violet) and hospitals (maroon) from OpenStreetMap — the amenity inputs to the proximity scores. Zoom in past z10.5 to see them. © OpenStreetMap contributors.

Legend

Loading…

Headline numbers

Loading…

Coverage (share of grid with data)

    Scope & caveats

    • Relative index, not a guarantee. The score orders hexes within Catalonia; it makes no absolute or "you should move here" claim.
    • Month-window scope, not climatology. These season-aware display layers (not the score, which keeps its 7-day March-2024 mobility input) pool 89 days across three 2025 calendar month-windows — Feb (28d), May (31d), Jun (30d). The Month-window dropdown splits the season-aware metrics into those three windows (winter · Feb / spring · May / summer-onset · Jun) so you can read where weekend pull switches on as the coast warms. This is a month-window comparison, not a seasonal/climate average: one cold month is not "winter climatology", Jun is summer-onset (the coastal weekend uplift is still building), and the contrast is a relative index — the starting question "where does weekend pull switch ON, Feb vs Jun?" — not a year-round verdict. Two summer−winter delta layers carry it (both Jun−Feb, both diverging, pivot 0): weekend_hotspot_summer_minus_winter (the leisure-weighted hotspot score differenced per hex — min −0.381 · median +0.036 · max +0.589, 75% of hexes positive) and weekend_ratio_summer_minus_winter (the raw weekend÷weekday ratio differenced — a tighter min −0.280 · median +0.005 · max +0.377, 57% positive, full grid coverage). Warm = a hex that tilts toward the weekend in summer-onset; ~0 = no shift; cool = relatively more weekend-busy in winter. The driver is coastal / second-home leisure specifically — sea-proximity is the signal (correlation with distance-to-coast ≈ −0.22, a monotone shoreline decay) — not leisure in general: high-leisure mountain hexes lean the OTHER way (winter-leaning). Real signal, not noise.
    • Sparse amenity layers. Some proximity layers cover only part of the grid — climbing ~49%, hospitals ~31%, yoga ~15%, coast ~6%. Cells with no amenity in the 10 km catchment simply earn no closeness reward there (and a recoloured presence metric renders them grey) — that absence is itself the finding, not a penalty.
    • Null is a distinct state. A missing value is rendered slate-grey, never as a ramp-low or zero value.

    How it's scored — dimensions & sources

    The liveability score sums six weighted dimensions over each H3 cell. Amenity closeness (climb, yoga, hospital, green) is a saturating positive reward — full bonus at the doorstep, decaying to zero at a 10 km catchment edge — so present-but-far always beats absent. Genuine harms (air, heat, light, industry, motorway) stay negative. All distances are computed in metres (EPSG:25831). Each source below is tagged live (wired to real data and feeding the gold table) or planned (a refinement not yet fed in). Default weights from configs/weights.yaml.

    DimensionSources
    Mobility & accessibilityw +1.4 / +1.0 Renfe Rodalies + FGC GTFS frequency (trains/day at nearest stop, ~91 distinct values) trains/day to BCN core (nearest stop) Valhalla bike isochrones from stations (refines the reach term)
    Lifestylew +5 / +4 OSM sport=climbing closeness reward (~49% of grid) OSM sport=yoga closeness reward (~15% of grid)
    Naturew +0.15…+6 OSM green closeness reward + sea-within-3km bonus EEA Tree-Cover-Density 2018 (zonal-mean %) Natura 2000 within 5 km (EEA terrestrial sites) GBIF / iNaturalist observation density
    Environmental healthw −0.05…−2 XVPCA NO₂ / PM₂.₅ annual means, penalised above WHO-2021 guidelines Landsat 8/9 LST — urban-heat-island Δ VIIRS DNB night-light radiance
    Penaltiesw −6 / −12 OSM landuse=industrial density E-PRTR facility proximity (nearest, inverse-distance) motorway within 500 m
    Health amenitiesw +5 / +1 OSM hospital closeness reward (~31% of grid) OSM pharmacy density (log)
    Mobility "vibe"w 0.0* MITMA daily OD inflow / outflow (v3: dasymetric, per-hex) through-flow ratio = log(out/in), pivot 0
    Mobility rhythm & typologyw 0.0† MITMA hourly OD cube → 24h rhythm + AM/PM/midday/night peak shares (Sedona Window + max_by) weekend ÷ weekday ratio + leisure share + weekend-hotspot score (min-rows gated) mobility typology (Spark MLlib KMeans: commuter-corridor / leisure-magnet / transit-corridor / self-contained / mixed-balanced) geodemographic inflow shares over the KNOWN subset (income / age / sex) + income×age Shannon-entropy diversity (bits) self-containment (intra-zone share) · OD-segment row count (density proxy)

    live = a populated gold column, wired to real data, that you can recolour the map by right now; planned = a refinement not yet fed in — in v2.3 only Valhalla bike isochrones remain (they would sharpen the train-reach term). *the "vibe" weight is 0 by default — set positive in quiet_strict to penalise busy through-flow. †the v3 rhythm/typology layers are browseable analytic dimensions, wired into configs/weights.yaml at weight 0 by default (so the published score is unchanged); a preset opts in to reward demographic-access diversity, leisure access or self-containment. Built by a real distributed Apache Sedona pipeline — see why_spark_sedona.md.

    Score range under the default preset: min 8 · median 59 · mean 57 · max 100 · std 14 across 45,220 hexes. The score's mobility input is the original 7-day March-2024 window (unchanged). The additive deep-Spark mobility & season display layers are the full-scale 89-day 2025 run (Feb + May + Jun) at weight 0 — flip the Month-window dropdown to compare them; a calendar-window comparison, not a climate average, and not a change to the published score. See the v2 improvement roadmap →

    Pipeline

    Rendering diagram…

    Bronze→Silver→Gold lakehouse on Apache Sedona; full method in architecture.md and scoring.md.

    Explore the project

    • github.com/lunasilvestre/mitma-sedona
      Full source, schemas, tests, Docker stack.
    • notebooks/
      Ingest → gold H3 build → liveability layer.
    • PLAN.md
      Canonical planning doc + milestone breakdown.
    • docs/sedona_sql_patterns.md
      8 advanced Sedona SQL patterns (H3 explode, dasymetric, ST_KNN, …).
    • docs/scoring.md
      The score: 6 dimensions, weights, four presets.
    • docs/data_sources.md
      Every upstream source, licence and data window.

    Display: Web Mercator over Esri World Imagery (keyless, attribution-bearing). H3 cells, OD arcs and POIs rendered with deck.gl.

    Data: MITMA v2 mobility · © OpenStreetMap contributors · EEA · Copernicus · Generalitat de Catalunya. Code: MIT.