Interactive choropleth map of property sale prices and advertised rents per commune in Luxembourg.
Purimo visualises Luxembourg’s residential property market at the commune level. It renders median sale prices (EUR/m²) and advertised rents (EUR/m²/month) as a choropleth overlay on an interactive Leaflet map, allowing users to compare municipalities against each other and against the national median. The tool is intended for exploratory use — it surfaces publicly available indicators, not official valuations.
The name Purimo is shorthand for Prix Immobiliers. The project was built to make commune-level housing data more accessible than browsing raw CSV tables on the national open data portal.
Purimo is a single HTML file with no build step. All logic runs in the browser: data is fetched as static JSON files from the assets/data/ directory, parsed, and rendered onto a Leaflet map. There is no server-side processing.
Commune boundaries are loaded from a GeoJSON file containing 100 Luxembourg municipalities. Each polygon is styled with a fill colour derived from its median value. The colour scale uses five graduated earth-tone steps from light (#d4c5a9) to dark (#332a18), mapped linearly between the minimum and maximum values across all communes.
var COLORS = ['#d4c5a9', '#b5a07a', '#8a7450', '#5e4d30', '#332a18'];
function getColor(value, min, max) {
if (max === min) return COLORS[2];
var ratio = (value - min) / (max - min);
return COLORS[Math.min(Math.floor(ratio * 5), 4)];
}
Users toggle between sale prices and advertised rents via radio buttons. On change, the choropleth is re-rendered with the selected dataset. The detail panel is closed on switch to avoid showing stale per-commune data from the previous indicator.
Clicking a commune opens a detail panel showing: median value, sample size (number of transactions or advertised listings), percentage deviation from the national median, and a badge classification (above market, below market, or near average). The national median is computed as the true median of all commune-level values at initialisation time.
function computeNationalMedian(data, key) {
var vals = data.map(function(r) { return r[key]; })
.filter(function(v) { return v > 0; })
.sort(function(a, b) { return a - b; });
var mid = Math.floor(vals.length / 2);
return vals.length % 2 ? vals[mid] : (vals[mid - 1] + vals[mid]) / 2;
}
All three datasets (commune boundaries, sale prices, rents) are fetched in parallel using Promise.all on page load. If any fetch fails, the map displays an error message. Data is pre-processed into compact JSON files to avoid client-side CSV parsing and to keep page load fast.
| Dataset | Source | Format | License | Scope |
|---|---|---|---|---|
| Sale prices | Observatoire de l’Habitat | JSON (pre-processed) | CC0 (data.public.lu) | Median EUR/m² per commune (apartments) |
| Advertised rents | Observatoire de l’Habitat | JSON (pre-processed) | CC0 (data.public.lu) | Median EUR/m²/month per commune |
| Commune boundaries | Limites communales | GeoJSON | CC0 (data.public.lu) | 100 commune polygons |
Purimo processes no personal data. All data displayed is aggregated at the commune level — no individual transactions, addresses, or property owners are identifiable. The tool makes no API calls to external services beyond loading OpenStreetMap tiles for the base map. No cookies are set, no analytics are collected, and no data is sent from the browser to any server.
The source datasets on data.public.lu are themselves anonymised and aggregated before publication by the Observatoire de l’Habitat.
Promise, fetch, arrow functions). No fallback is provided for older browsers.assets/data/