Unlocking Housing · methodology v1

Three lenses, one set of buildings

Every empty building this project maps sits inside several boundaries at once — a municipality, an infrastructure asset's reach, a watershed. Which boundary you draw changes who has standing in a decision about it. This page is the full reasoning behind offering three lenses, and one real worked example per lens.

It also spells out the other number the map shows you: the 0–3 vacancy score on every building — every tag that produces it, and everything it does not claim.

← Back to /how unlocking-housing.org

Boundaries are never neutral — but some are less political than others

The map's default view already draws a boundary: the political one. A building is "in" Eisenerz, or Bruck an der Mur, because a municipal register says so. That boundary is useful and legible, but it is also an artefact of a specific history of administration — Gemeinde lines get redrawn (the 2015 Styrian municipal-structure reform folded several of this project's own pilot towns together), and they say nothing about who is actually affected by a given piece of infrastructure running through or near a building.

So a second lens: infrastructure. Instead of "which municipality is this building in", ask "which piece of infrastructure reaches this building, and how strongly". A rail line, a power line, a district-heating main — each has a real physical impact envelope, and buildings inside it have a stake in decisions about it whether or not they sit in the same Gemeinde as the operator's headquarters.

But infrastructure boundaries are themselves political artefacts — built by one country's decisions and not its neighbour's, financed and routed by treaties and state railway companies, not by physics alone. The Semmeringbahn exists because the Austrian Empire built it in 1854; the Koralmtunnel exists because Austria's and the EU's TEN-T corridor planning decided it should. So a third lens, deliberately the least political of the three: bioregional. Watersheds and the Alpine main divide are boundaries that predate every government that has ever drawn a line near them, and will outlast the next redrawing too. They don't replace the other two lenses — they offer a reading of the same territory that doesn't inherit either a state's or a rail company's boundary-drawing power.

🏛️ Political — administrative boundaries

This is the lens the app already uses everywhere by default: the region registry in @atlas/kernel's packages/kernel/src/regions.ts. Every pilot area on the map — the Mur-Mürz corridor, Reichenau an der Rax, Vienna's Innere Stadt, Villach, Udine, Trieste — is defined first as a political boundary (a Gemeinde, a Bezirk, a Stadtgemeinde, a comune), verified against Nominatim administrative-relation data, and buildings are attributed to a municipality by an addr:city/addr:place match rule.

Worked example — Bruck an der MurBezirk Bruck-Mürzzuschlag, Steiermark
Nominatim relation 3904390 post-2015-merger, incl. former Oberaich municipality bbox lat 47.3368–47.4430 / lng 15.1506–15.3604

One of the five towns in the pilot corridor's own regions.ts municipality list (/bruck/i → 'Bruck an der Mur'), and the corridor's own rail-junction town — the Mur and Mürz rivers, and the rail lines that follow them, meet here. Under the political lens, a building here has standing in decisions the Bruck municipal government makes; a building 200m away across the Kapfenberg town line does not, even if it is exposed to the exact same rail traffic.

🚆 Infrastructure — impact envelope of a specific asset

Specced in DEVLOG/modularization/spec-modular-map-platform.md §3.1 as the domain model for the (not-yet-built) Infrastructure Governance variant: an infrastructure object — a rail line, road, power line, district-heating main, and so on — carries an impactEnvelope (how far and in what way its effect reaches), and every building inside that envelope gets an affectedness record and a standing weight:

weight(house, infra) = clamp( floor + (1-floor) * f(maxExposureIntensity, permanence, irreversibility), floor, cap)

floor > 0 so any affected household has a voice; a cap so no single household dominates; burden and benefit are counted separately and never silently netted; permanence and irreversibility raise weight — a decision you cannot undo needs broader consent.

Worked example — the Wien–Eisenerz rail axishonorary axis, via Semmering + the historic Erzbergbahn
packages/kernel/src/corridor-axes.ts AXIS_NEAR_KM = 10 Wien → Wr. Neustadt → Gloggnitz → Semmering → Mürzzuschlag → Kindberg → Kapfenberg → Bruck → Leoben → Trofaiach → Vordernberg → Präbichl → Eisenerz

This is a real, already-coded polyline in this monorepo — not a hypothetical. It reuses the historic Südbahn/Semmeringbahn alignment out of Vienna, then continues via the Erzbergbahn branch to Eisenerz, and its waypoints are the same five towns the political-lens corridor region names by municipality (Eisenerz/Vordernberg/Leoben/Bruck/Kapfenberg) — under this lens, though, a building counts as "reached" by distance to the rail line itself (10km, using the same point-to-segment geometry — ptToSegDistM — that rail-distance.ts already uses at a much tighter 50m threshold for rail-adjacency on the live map), not by which Gemeinde it happens to sit in.

Honesty note: what exists in code today is the geometry — distance from a building to this named axis. The standing-weight formula above (floor/cap/permanence) is the spec-modular-map-platform.md §3.1 specification for the not-yet-built Infrastructure Governance variant, not something this axis currently computes. The axis is also marked honorary: true in corridor-axes.ts — it is excluded from the main off-corridor scoring boost gate and only annotates today, a smaller role than the on-axis worked example above implies for a fully built infrastructure-governance lens.

🌱 Bioregional — the least political boundary

packages/bioregions is a standalone package in this monorepo (own SvelteKit app, own map, not yet wired into the main Unlocking Housing map's lens switcher) carrying real watershed and Alpine-divide data: HydroSHEDS/HydroBASINS sub-basins, a hand-traced Alpine main-divide (Alpenhauptkamm) ridge line verified against those basin boundaries, and EEA biogeographical-region polygons.

Worked example — the Mur Basin, and where it stopsHydroBASINS Level-6, PFAF_ID 2078
packages/bioregions/static/hydrobasins-l6-alps-seed.geojson 13,800 km² Mur → Drava → Danube

The corridor pilot's five towns — Eisenerz, Vordernberg, Leoben, Bruck an der Mur, Kapfenberg — drain south into the Mur river, which the package's own seed data labels "Mur river basin — drains Styria southward through Slovenia into the Drava/Danube". That is the corridor's real bioregion, and it cuts across the same political boundaries and the same Wien–Eisenerz rail axis described above without matching either.

The contrast that makes the lens worth having: Reichenau an der Rax — another Unlocking Housing pilot region, ~50km north of the corridor, still Alpine terrain, still Austria — sits at the Alpine main divide's own eastern terminus. The divide line in alps-divide.ts is hand-traced to end at the Rax massif (47.72°N/15.73°E), described there as "the northeast terminus of the main Alpine chain" and "the divide between Styria, Oberösterreich, and Niederösterreich". Reichenau sits on the north-draining side of that divide — its water reaches the Danube directly via the Schwarza river, not via the Mur. Two pilot regions, both squarely Austrian, both Alpine, but in different bioregions — a distinction neither the political lens nor the rail-axis lens above draws at all.

Honesty note: the package's EEA biogeographical-region seed file (eea-bioregions-europe-seed.geojson, an offline fallback for when the live EEA data portal is unreachable) is a coarse hand-authored approximation, not the official EEA polygon service — checked pointwise, it currently classifies both the corridor and Reichenau as "Continental" rather than "Alpine", which does not match the terrain. That is a seed-data simplification we are naming rather than quietly relying on. The watershed and Alps-divide worked example above is used instead because those layers are hand-traced and verified against real HydroSHEDS basin boundaries — the more precisely grounded data this package has today.

🎯 What “Score: 2 / 3” actually means

The score measures how strong the open-data signal is that a building might be empty. It never measures that it is empty. Nothing in it observes vacancy directly — the registers that would (the GWR dwelling register, the ZMR residents register) are closed to non-authorities.

Every OpenStreetMap-derived building on the map carries a whole number from 0 to 3 and a fixed plain-language label, computed by the app's own scoring pass (loadVacancyBuildings in packages/vacancy/src/lib/vacancy-api.ts) and clamped to that range. Three paths lead to a score, checked in order — and the first one that matches wins outright.

ScoreLabel the app showsExample paths to this score
3 / 3 High vacancy signal Any explicit disuse or lifecycle tag (the two blocks below) — or, where zoning data exists at all, a residential building inside a tagged residential area that also sits off the investor corridors
2 / 3 Moderate vacancy signal A residential building inside a tagged residential area — or any building that picked up one point for its type or surroundings and a second for sitting off the investor corridors
1 / 3 Weak vacancy signal A residential building where no zoning data was ever loaded — or a non-residential building that happens to sit inside a tagged residential area
0 / 3 No vacancy signals Everything else: no disuse tag, not a residential building type, no zoning data. This is the great majority of footprints on the map.

These two are checked first and never combined with anything else — either one takes the building straight to 3, and no further points are added or removed. They are kept apart from each other in the code and in the map popup's wording, because their provenance genuinely differs: one is a record that the former use is gone, the other that the building is standing but idle.

Community-tagged lifecycle recorda mapper recorded that the former use has ended
demolished:* was:* removed:* razed:*

Any OSM key carrying one of these four lifecycle prefixes. Where a was:* tag is present, the former use is carried through to the popup as written — e.g. was:amenity=restaurant reads back as "former use: amenity=restaurant".

historic:* is excluded on purpose. OSM's own wiki calls it misleading for lifecycle tagging, and in practice it is overwhelmingly a heritage or period-of-construction tag (historic:period, historic:railway) — a listed, fully occupied townhouse carries it happily. It is not a disuse signal and is never read as one here.

Standing, but tagged idledisused, abandoned, or a ruin
disused=yes · disused=building abandoned=yes ruins=yes · building=ruins any disused: / abandoned: / ruins: prefixed key

The prefixed form matters as much as the bare one. Once a feature is re-tagged abandoned:building=industrial, the plain building key is frequently dropped from the object altogether — a check that only read bare tags would not merely misscore such a building, it would never see it at all. Whichever tag actually fired is the one named in the popup, never a generic stand-in.

A building with no disuse and no lifecycle tag takes the third path, and only here are points genuinely additive: one building-type-and-zoning component, plus at most a single corridor point.

ComponentConditionPoints
Building type & zoning A residential building type (apartments, residential, house, detached, semidetached_house) whose centroid falls inside an OSM-tagged residential area +2
Building type & zoning A residential building type outside any tagged residential area — or, far more often, in a region where no zoning data was ever loaded +1
Building type & zoning A non-residential building type that happens to sit inside a tagged residential area +1
Building type & zoning A non-residential building type with no tagged residential area around it +0
Investor corridor Off all major investor corridors (Vienna–Graz, Westbahn, Trans-Alpine) — structurally below developer attention — and the building has already scored above 0 on the component above +1
Investor corridor On a high-value corridor; or the building scored 0 so far; or the region does not use corridor scoring at all +0

So this third path tops out at 3, and only as 2 + 1: a residential building inside tagged residential land that is also off-corridor. The corridor point is deliberately gated on the building already having scored something — an isolated shed in the middle of nowhere does not become a vacancy candidate merely by being remote.

What the score does not mean. The app itself puts it this way, and this page says nothing stronger: the vacancy score combines open-data signals, score 3 is the strongest combination available, and it does not confirm vacancy — field verification is always needed. Concretely:

  • Nobody has been there. No site visit, no ownership check, no register lookup, no contact with a municipality. A 3/3 building may be someone's fully occupied home whose OSM tag is simply stale.
  • A ruin and a merely idle building both score 3. The score cannot tell them apart, because building=ruins and disused=yes land in the same branch. That distinction lives in a separate field — a move-in readiness tier running from "disused but usable" down to "ruin, outside this project's scope" — which is shown alongside the score and never folded into it.
  • The zoning tier is not official zoning. The residential-area polygons are derived from OSM land-use tags for a single pilot town, not from any municipal Flächenwidmungsplan, and they are loaded for that one region only. Everywhere else the check returns "not in a residential area" simply because no zoning data was ever fetched — that must be read as never checked, never as checked and negative. In practice the +2 tier is therefore out of reach outside that one pilot, so an untagged standing building caps at 2/3 where corridor scoring applies, and at 1/3 where it does not.
  • Most buildings score 0. Outside the pilot with zoning data, a 3/3 is reachable essentially only through an explicit disuse or lifecycle tag. The map is mostly grey for a reason.
  • Absence of a tag is not evidence of occupancy. OSM coverage is community-contributed and uneven: an empty building nobody has re-tagged looks exactly like an occupied one. The score can only ever be a false negative in that direction, and it says nothing at all about the buildings it scores 0.
  • Some popup reasons carry no points. The storey count, a large footprint, and the note that a building sits on a high-value corridor are context shown to help you judge — they do not move the number. Reading the reason list as an itemised scorecard will not reproduce the score.
  • Everything above describes the default score. Switching corridor axes on or off in the app recomputes an effective score live from the same pre-corridor baseline, so the displayed number can differ from the one described here while that lens is active.

One more score, and a different one. The sortable Table view ranks buildings by a second, unrelated number: the renovation-opportunity score, a composite of five weighted components summing to 100 — space, a renovation-price proxy, quietness, nature access, and access — plus additive bonuses for nearby amenities and for move-in readiness that push its ceiling above 100 (125 at the app's default weights, and the app labels each score against the actual maximum rather than a fixed 100). It answers "how attractive is this as a project", not "is it empty", and the vacancy score above is deliberately excluded from every one of its components so the same signals are never counted twice. Its price-proxy component is exactly what its name says: it infers "likely cheaper to acquire" from disuse and building-typology tags — Kaserne, Kloster, Gasthaus, Fabrik and ten more — and uses no real price data whatsoever. It is not a valuation.

Same provenance rule as the rest of this site: every boundary shown carries its source and its confidence, and a lens that is speculative or seed-data-only says so, rather than presenting itself as measured fact.

All three lenses answer the same underlying question differently: "who has a stake in this building, and by what right." None of the three is neutral. Political boundaries encode administrative history; infrastructure envelopes encode which asset and whose money built it; even bioregional boundaries encode a choice of dataset resolution and a divide line someone still had to trace. The bioregional lens is offered as less political, not apolitical — it is simply the frame whose boundary-drawing power belongs to neither a state nor a company.

Current status: political lens live and default; infrastructure lens's geometry (corridor axes, rail-adjacency) live, its standing-weight model spec-only; bioregional lens data real but package-standalone, not yet merged into the main map.