Find your spot in London β an interactive map to explore amenities, public transport, and neighbourhood context side by side. Pin postcodes, toggle layers, and see how areas compare at a glance.
- Search postcodes β jump to an area and keep a shortlist of places you care about.
- Point layers β tube and rail, groceries, coffee, libraries, pubs, parks, restaurants, GP surgeries, coworking, gyms, cinemas, bike parking, and more. Each layer can be switched on or off. POIs show as emoji markers on the map.
- Area layers β choropleths for crime, air quality (NOβ), estimated rent, IMD domains, population density, transport access (TfL PTAL / access index), green space score, and modelled noise (Lden), so you can read "how this patch of the city feels" next to the map.
- Scorecards β blended scores for each pinned postcode (proximity + area signals), with short explanations on what each metric means. Disable dimensions you don't care about.
- Filters β set thresholds on crime, air quality, rent, deprivation, population density, noise, minimum transport access index, and minimum green-space score to grey out areas that don't pass.
- Compare β pin multiple postcodes and see a side-by-side comparison across all dimensions.
- Walking rings β concentric distance rings around pinned postcodes (5/15/30/45 min walk), with each ring labelled on the map.
- Optional transit isochrones β public-transit reach from pinned postcodes when the app has a TfL API key configured.
- Shareable URLs β app state is encoded in the URL hash: postcodes, active POI layers, selected choropleth, choropleth opacity, walking rings, area filters, disabled scorecard dimensions, and optional transit isochrone visibility.
Data is bundled as static GeoJSON in the repo, so the map works offline once built β no database required.
npm install
npm run devThen open the URL Vite prints (usually http://localhost:5173).
npm run build
npm run preview # serve the dist/ folder locallyGeoJSON under public/data/ is generated by Node scripts in scrapers/. Run everything from the repository root after installing app dependencies (the IMD pipeline uses SheetJS xlsx, installed from the official CDN tarball β not the stale xlsx copy on the public npm registry).
npm install| File | When to delete it |
|---|---|
public/data/_lsoa-boundaries.geojson |
You want to re-download London LSOA polygons from the ONS ArcGIS services (e.g. after a boundary release). The next area scraper that needs boundaries will fetch again. |
public/data/_imd_scores.xlsx |
You want to re-download the government IMD 2019 scores spreadsheet from the URL in scrapers/imd.js (e.g. if the file on gov.uk is replaced). |
public/data/_population-density-ts006.xlsx |
You want to re-fetch Census 2021 TS006 LSOA population density from the URL in scrapers/population-density.js. |
public/data/_ptal-lsoa-2023.csv |
You want to re-fetch TfL LSOA PTAL / access stats from the URL in scrapers/ptal.js. |
These join or aggregate to LSOA boundaries (cached as above). crime.js is slow: it hits data.police.uk for many grid points with delays between batches β expect several minutes and be gentle on their service.
node scrapers/imd.js # IMD 2019 β imd.geojson (+ may cache _imd_scores.xlsx)
node scrapers/air-quality.js # NOβ interpolation β air-quality.geojson
node scrapers/rent.js # Modelled est. rent β rent.geojson (uses IMD cache + LSOA boundaries)
node scrapers/population-density.js # Census 2021 TS006 β population-density.geojson (may cache _population-density-ts006.xlsx)
node scrapers/ptal.js # TfL LSOA access index β ptal.geojson (may cache _ptal-lsoa-2023.csv)
node scrapers/noise.js # IDW from curated Lden points β noise.geojson
node scrapers/green-space.js # OSM greenspace β green-space.geojson (Overpass, multi-bbox; slow)
node scrapers/crime.js # Latest police month β crime.geojson (slow)Each script overwrites its GeoJSON. Respect Overpass usage (no hammering; try off-peak if a query times out). Several scrapers use scrapers/lib/overpass.js, which rotates public instances (e.g. kumi.systems, openstreetmap.fr, overpass-api.de) with retries. restaurants.js and green-space.js are the heaviest (many nodes / ways; sharded queries; expect long runs).
node scrapers/tube-rail.js
node scrapers/waitrose.js
node scrapers/coffee.js
node scrapers/libraries.js
node scrapers/pubs.js
node scrapers/parks.js
node scrapers/yoga.js
node scrapers/gyms.js
node scrapers/cinemas.js
node scrapers/bike-parking.js
node scrapers/betting.js
node scrapers/restaurants.js # sharded; slow
node scrapers/gp-surgeries.js
node scrapers/coworking.jsPOI scripts first, then area layers. Run crime.js last if you want lighter jobs first; run restaurants.js / green-space.js when you can wait (Overpass-heavy).
for f in tube-rail waitrose coffee libraries pubs parks yoga gyms cinemas bike-parking betting gp-surgeries coworking restaurants; do node scrapers/$f.js; done
node scrapers/imd.js
node scrapers/air-quality.js
node scrapers/rent.js
node scrapers/population-density.js
node scrapers/ptal.js
node scrapers/noise.js
node scrapers/green-space.js
node scrapers/crime.jsAfter updating files, commit the changed public/data/*.geojson (and optional caches if you intentionally refresh them), then rebuild or redeploy the site.
Bundled layers are static GeoJSON under public/data/, produced or refreshed by scripts in scrapers/. Interpretation here is for exploration only β not planning, legal, or financial advice.
| What | Source |
|---|---|
| Basemap | CARTO "Positron" style tiles; map data Β© OpenStreetMap contributors |
All of the following are queried from OpenStreetMap via the Overpass API (community-mapped data; completeness varies by area). This repo posts through scrapers/lib/overpass.js, which tries several public Overpass endpoints.
| Layer | OSM tags / notes | Output file |
|---|---|---|
| Tube & Rail | railway=station, station=subway, railway=halt, station=light_rail |
tube-rail.geojson |
| Waitrose | shop + brand / name matching Waitrose |
waitrose.geojson |
| Coffee shops | Cafes; chain filter in scraper | coffee.geojson |
| Libraries | amenity=library |
libraries.geojson |
| Pubs | amenity=pub |
pubs.geojson |
| Parks & green space | leisure=park, garden, nature_reserve (non-private where tagged) |
parks.geojson |
| Restaurants | amenity=restaurant (named only; London split into bbox shards in scraper) |
restaurants.geojson |
| GP surgeries | amenity=doctors, healthcare=doctor |
gp-surgeries.geojson |
| Coworking | amenity=coworking_space, office=coworking |
coworking.geojson |
| Yoga studios | leisure=fitness_centre + sport=yoga / name patterns, sport=yoga, etc. |
yoga.geojson |
| Gyms | leisure=fitness_centre |
gyms.geojson |
| Cinemas | amenity=cinema |
cinemas.geojson |
| Bike parking | amenity=bicycle_parking |
bike-parking.geojson |
| Betting shops | shop=bookmaker / betting, amenity=gambling |
betting.geojson |
| Layer | Source | Output file |
|---|---|---|
| LSOA boundaries | ONS Open Geography Portal β Lower Layer Super Output Areas (2021), filtered to London via LSOAβLAD lookup | Cached as _lsoa-boundaries.geojson (used by scrapers) |
| Crime (current) | data.police.uk street-level crime API (grid sampling + assignment to LSOA) | crime.geojson |
| Air quality (NOβ) | Published annual mean NOβ at monitoring sites (London Air / AURN-style figures in scrapers/air-quality.js), inverse-distance interpolated to LSOA centroids β modelled surface, not a regulatory map |
air-quality.geojson |
| Est. rent (Β£/mo) | Modelled in scrapers/rent.js: IMD 2019 income / housing-barriers signals + hand-tuned borough median anchors β indicative monthly Β£ per LSOA. Not official rents or listings β exploration only |
rent.geojson |
| Deprivation (IMD 2019) | UK government IMD 2019 scores (spreadsheet), joined to LSOA boundaries | imd.geojson |
| Population density | ONS Census 2021 TS006 β usual residents per kmΒ² at LSOA; table pulled in scrapers/population-density.js via the UK Data Service CKAN-hosted XLSX |
population-density.geojson |
| Transport access (PTAL) | TfL β LSOA aggregated PTAL stats 2023 (CSV on ArcGIS Hub). Choropleth uses mean access index (mean_AI); banded PTAL categories are in the source file |
ptal.geojson |
| Green space score | OpenStreetMap (Overpass): parks, gardens, nature reserves, recreation grounds, village greens β proximity-weighted count near LSOA centroids, then percentile score (exploratory proxy, not official greenspace %). See scrapers/green-space.js |
green-space.geojson |
| Noise (Lden) | Modelled in scrapers/noise.js: inverse-distance interpolation from hand-picked representative Lden (dB) points informed by Defra strategic noise mapping / London context β not a downloaded Defra raster |
noise.geojson |
| What | Source |
|---|---|
| Postcode search β coordinates | postcodes.io (Ordnance Survey open data) |
| Optional transit-time overlays | TfL Journey API (Journey/JourneyResults) when a client key is configured |
Map tiles: Β© OpenStreetMap contributors, Β© CARTO. ONS, police, and government statistics remain Β© their respective owners; use and republication are subject to their licences and terms.

