diff --git a/Dockerfile b/Dockerfile
index 7197f0b..bea6458 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -22,9 +22,10 @@ COPY --from=frontend /app/frontend/dist ./frontend/dist/
COPY property-data/wide.parquet ./data/
COPY property-data/filtered_uk_pois.parquet ./data/
+COPY property-data/places.parquet ./data/
COPY property-data/uk.pmtiles ./data/
COPY manual-data/postcode_boundaries ./data/postcode_boundaries/
EXPOSE 8001
ENTRYPOINT ["./property-map-server"]
-CMD ["--data", "/app/data/wide.parquet", "--pois", "/app/data/filtered_uk_pois.parquet", "--tiles", "/app/data/uk.pmtiles", "--postcodes", "/app/data/postcode_boundaries"]
+CMD ["--data", "/app/data/wide.parquet", "--pois", "/app/data/filtered_uk_pois.parquet", "--places", "/app/data/places.parquet", "--tiles", "/app/data/uk.pmtiles", "--postcodes", "/app/data/postcode_boundaries"]
diff --git a/Makefile.data b/Makefile.data
index 0e12cd4..395b184 100644
--- a/Makefile.data
+++ b/Makefile.data
@@ -36,6 +36,7 @@ OFSTED := $(DATA_DIR)/ofsted.parquet
NAPTAN := $(DATA_DIR)/naptan.parquet
BROADBAND := $(DATA_DIR)/broadband.parquet
SCHOOL_PROX := $(DATA_DIR)/school_proximity.parquet
+RENTAL := $(DATA_DIR)/rental_prices.parquet
GEOSURE_DIR := $(DATA_DIR)/geosure
GEOSURE := $(DATA_DIR)/geosure.parquet
INSPIRE_DIR := $(DATA_DIR)/inspire
@@ -44,6 +45,7 @@ UPRN_LOOKUP := $(DATA_DIR)/uprn_lookup.parquet
PC_BOUNDARIES := $(MANUAL_DATA)/postcode_boundaries
TRANSIT_DIR := $(DATA_DIR)/transit
TRANSIT_STAMP := $(TRANSIT_DIR)/.done
+GREENSPACE := $(DATA_DIR)/greenspace_water.parquet
# Sentinel files for directory targets (Make doesn't track directories well)
GEOSURE_STAMP := $(GEOSURE_DIR)/.done
@@ -55,9 +57,9 @@ PMTILES_VERSION := 1.22.3
.PHONY: prepare wide tiles \
download-arcgis download-price-paid download-deprivation download-ethnicity \
- download-naptan download-pois download-ofsted download-broadband \
+ download-naptan download-pois download-ofsted download-broadband download-rental-prices \
download-postcodes download-geosure download-noise download-inspire \
- download-oa-boundaries download-uprn-lookup download-transit-network \
+ download-oa-boundaries download-uprn-lookup download-transit-network download-greenspace \
transform-pois transform-epc-pp transform-crime transform-poi-proximity \
transform-school-proximity transform-geosure transform-postcode-boundaries \
generate-postcode-boundaries \
@@ -76,11 +78,13 @@ download-ofsted: $(OFSTED)
download-broadband: $(BROADBAND)
download-postcodes: $(POSTCODES)
download-geosure: $(GEOSURE_STAMP)
+download-rental-prices: $(RENTAL)
download-noise: $(NOISE)
download-inspire: $(INSPIRE_STAMP)
download-oa-boundaries: $(OA_BOUNDARIES)
download-uprn-lookup: $(UPRN_LOOKUP)
download-transit-network: $(TRANSIT_STAMP)
+download-greenspace: $(GREENSPACE)
transform-pois: $(POIS_FILTERED)
transform-epc-pp: $(EPC_PP)
transform-crime: $(CRIME)
@@ -159,6 +163,12 @@ $(TRANSIT_STAMP):
uv run python -m pipeline.download.transit_network --output $(TRANSIT_DIR)
@touch $@
+$(RENTAL):
+ uv run python -m pipeline.download.rental_prices --output $@
+
+$(GREENSPACE):
+ uv run python -m pipeline.download.greenspace_water --output $@
+
# ── Journey times (requires TFL_API_KEY) ──────────────────────────────────────
$(JT_BANK):
@@ -231,7 +241,7 @@ $(PC_BOUNDARIES):
# ── Final merge ───────────────────────────────────────────────────────────────
$(WIDE): $(EPC_PP) $(ARCGIS) $(IOD) $(POI_PROXIMITY) $(JT_BANK) $(JT_FITZROVIA) \
- $(ETHNICITY) $(CRIME) $(NOISE) $(SCHOOL_PROX) $(BROADBAND) $(GEOSURE)
+ $(ETHNICITY) $(CRIME) $(NOISE) $(SCHOOL_PROX) $(BROADBAND) $(GEOSURE) $(RENTAL)
uv run python -m pipeline.transform.merge \
--epc-pp $(EPC_PP) \
--arcgis $(ARCGIS) \
@@ -245,6 +255,7 @@ $(WIDE): $(EPC_PP) $(ARCGIS) $(IOD) $(POI_PROXIMITY) $(JT_BANK) $(JT_FITZROVIA)
--school-proximity $(SCHOOL_PROX) \
--broadband $(BROADBAND) \
--geosure $(GEOSURE) \
+ --rental-prices $(RENTAL) \
--output $@
# ── Price estimation (post-merge) ────────────────────────────────────────────
diff --git a/Taskfile.yml b/Taskfile.yml
index b02d9ba..1c3df20 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -16,10 +16,10 @@ tasks:
cmds:
- uv run python -m pipeline.download.map_assets --output frontend/public/assets
- download:greenspace:
- desc: Extract park/water polygons from OSM PBF for postcode boundary trimming
+ download:places:
+ desc: Extract place names from OSM PBF
cmds:
- - uv run python -m pipeline.download.greenspace_water --output data/greenspace_water.parquet {{.CLI_ARGS}}
+ - uv run python -m pipeline.download.places --output ./property_data/places.parquet {{.CLI_ARGS}}
test:
desc: Run all tests (Python and Rust)
diff --git a/docker-compose.yml b/docker-compose.yml
index 1c85931..7e415e4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,11 +1,12 @@
services:
server:
image: rust:1.84
+ init: true
working_dir: /app/server-rs
command: >
bash -c "
cargo install cargo-watch &&
- cargo watch -x 'run -- --data /app/data/wide.parquet --pois /app/data/filtered_uk_pois.parquet --tiles /app/data/uk.pmtiles --postcodes /app/data/postcode_boundaries'
+ cargo watch -x 'run -- --data /app/data/wide.parquet --pois /app/data/filtered_uk_pois.parquet --places /app/data/places.parquet --tiles /app/data/uk.pmtiles --postcodes /app/data/postcode_boundaries'
"
ports:
- "8001:8001"
@@ -18,7 +19,6 @@ services:
- cargo-registry:/usr/local/cargo/registry
- cargo-target:/app/server-rs/target
- ./property-data:/app/data:ro
-
environment:
POCKETBASE_URL: http://pocketbase:8090
POCKETBASE_ADMIN_EMAIL: ${POCKETBASE_ADMIN_EMAIL:-}
@@ -31,6 +31,7 @@ services:
condition: service_healthy
screenshot:
+ init: true
build: /volumes/syncthing/Projects/property-map/screenshot
environment:
APP_URL: http://frontend:3001
@@ -54,6 +55,7 @@ services:
count: 1
frontend:
+ init: true
image: node:22-slim
working_dir: /app/frontend
command: >
@@ -73,6 +75,7 @@ services:
PB_PROXY_TARGET: http://pocketbase:8090
pocketbase:
+ init: true
image: ghcr.io/muchobien/pocketbase:latest
ports:
- "8090:8090"
@@ -88,6 +91,7 @@ services:
start_period: 5s
r5:
+ init: true
build: ./r5-java
ports:
- "8004:8003"
@@ -96,8 +100,10 @@ services:
volumes:
- r5-network:/data/network
- ./property-data/transit:/data/transit:ro
+ - ./property-data/transit/raw:/data/transit-raw:ro
environment:
DATA_DIR: /data/transit
+ OSM_DIR: /data/transit-raw
NETWORK_CACHE_DIR: /data/network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8003/health"]
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b7c3ade..21a0680 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -191,7 +191,7 @@ export default function App() {
initialFilters={urlState.filters || {}}
initialViewState={initialViewState}
initialPOICategories={urlState.poiCategories || new Set()}
- initialTab={urlState.tab || 'pois'}
+ initialTab={urlState.tab || 'area'}
initialLoading={initialLoading}
theme={theme}
pendingInfoFeature={null}
@@ -249,7 +249,7 @@ export default function App() {
initialFilters={urlState.filters || {}}
initialViewState={initialViewState}
initialPOICategories={urlState.poiCategories || new Set()}
- initialTab={urlState.tab || 'pois'}
+ initialTab={urlState.tab || 'area'}
initialLoading={initialLoading}
theme={theme}
pendingInfoFeature={pendingInfoFeature}
diff --git a/frontend/src/components/data-sources/DataSources.tsx b/frontend/src/components/data-sources/DataSources.tsx
deleted file mode 100644
index 80cd48d..0000000
--- a/frontend/src/components/data-sources/DataSources.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-export default function DataSources({ onNavigate }: { onNavigate: () => void }) {
- return (
-
- );
-}
diff --git a/frontend/src/components/data-sources/DataSourcesPage.tsx b/frontend/src/components/data-sources/DataSourcesPage.tsx
deleted file mode 100644
index 650656c..0000000
--- a/frontend/src/components/data-sources/DataSourcesPage.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import { useEffect, useState, useRef } from 'react';
-
-const DATA_SOURCES = [
- {
- id: 'price-paid',
- name: 'Price Paid Data',
- origin: 'HM Land Registry',
- use: 'Complete historical property sale prices for England and Wales. Used for the last known sale price of each property.',
- url: 'https://www.gov.uk/government/statistical-data-sets/price-paid-data-downloads',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'epc',
- name: 'Energy Performance Certificates (EPC)',
- origin: 'Ministry of Housing, Communities & Local Government',
- use: 'Domestic Energy Performance Certificates providing floor area, number of rooms, construction age, energy ratings, property type, and built form. Fuzzy-joined with Price Paid records by address within postcode buckets. Property owners can opt out of public disclosure.',
- optOutUrl: 'https://www.gov.uk/guidance/energy-performance-certificates-opt-out-of-public-disclosure',
- url: 'https://epc.opendatacommunities.org/downloads/domestic',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'nspl',
- name: 'National Statistics Postcode Lookup (NSPL)',
- origin: 'ONS / ArcGIS',
- use: 'Maps postcodes to latitude/longitude, LSOA, and Output Area codes for geolocation and joining area-level datasets.',
- url: 'https://www.arcgis.com/sharing/rest/content/items/077631e063eb4e1ab43575d01381ec33/data',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'iod',
- name: 'English Indices of Deprivation 2025',
- origin: 'Ministry of Housing, Communities & Local Government',
- use: 'Relative deprivation scores for 33,755 LSOAs across domains: Income, Employment, Education, Health, Crime, Living Environment, and sub-domains. Joined to properties via LSOA code.',
- url: 'https://www.gov.uk/government/statistics/english-indices-of-deprivation-2025',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'ethnicity',
- name: 'Population by Ethnicity (2021 Census)',
- origin: 'ONS',
- use: 'Population percentages by ethnic group (Asian, Black, Mixed, White, Other) per Local Authority. Joined via Local Authority District code.',
- url: 'https://www.ethnicity-facts-figures.service.gov.uk/uk-population-by-ethnicity/national-and-regional-populations/regional-ethnic-diversity/latest/#download-the-data',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'crime',
- name: 'Street-level Crime Data',
- origin: 'data.police.uk',
- use: 'Street-level crime data from 2023 to 2025, aggregated into yearly averages by LSOA and crime type (violence, burglary, anti-social behaviour, drugs, vehicle crime, etc.).',
- url: 'https://data.police.uk/data/',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'tfl-journey-times',
- name: 'TfL Journey Times',
- origin: 'Transport for London',
- use: "Journey time calculations from postcodes to central London destinations (Bank, Waterloo, King's Cross, etc.) via public transport and cycling.",
- url: 'https://api-portal.tfl.gov.uk/',
- license: 'Powered by TfL Open Data',
- },
- {
- id: 'osm-pois',
- name: 'OpenStreetMap POIs',
- origin: 'OpenStreetMap contributors / Geofabrik',
- use: 'Points of interest extracted from the Great Britain PBF extract. Covers amenities, shops, healthcare, leisure, tourism, and more. Filtered and remapped to friendly category names.',
- url: 'https://download.geofabrik.de/europe/great-britain-latest.osm.pbf',
- license: 'Open Data Commons Open Database License (ODbL)',
- },
- {
- id: 'naptan',
- name: 'NaPTAN (Public Transport Stops)',
- origin: 'Department for Transport',
- use: 'National Public Transport Access Nodes providing station and stop locations (rail, bus, metro/tram, ferry, airport), merged into the POI dataset.',
- url: 'https://naptan.dft.gov.uk/naptan/schema/2.4/doc/NaPTANSchemaGuide-2.4-v0.57.pdf',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'noise',
- name: 'Defra Noise Mapping',
- origin: 'Defra / Environment Agency',
- use: 'Strategic noise mapping Round 4 (2022) for road, rail, and airport sources. Lden (day-evening-night 24h weighted average) at 10m grid resolution, modelled at 4m above ground. Sampled at postcode centroids via WCS GeoTIFF tiles.',
- url: 'https://environment.data.gov.uk/spatialdata/road-noise-all-metrics-england-round-4/wcs',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'ofsted',
- name: 'Ofsted School Inspections',
- origin: 'Ofsted',
- use: 'Latest inspection outcomes for state-funded schools (as at April 2025). Averaged per postcode to give a local school quality score (1=Outstanding to 4=Inadequate).',
- url: 'https://www.gov.uk/government/statistical-data-sets/monthly-management-information-ofsteds-school-inspections-outcomes',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'broadband',
- name: 'Ofcom Broadband Performance',
- origin: 'Ofcom',
- use: 'Fixed broadband coverage and speeds by Output Area from Connected Nations 2025. Includes max download/upload speeds across different speed tiers.',
- url: 'https://www.ofcom.org.uk/phones-and-broadband/coverage-and-speeds/connected-nations-20252/data-downloads-2025',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'geosure',
- name: 'GeoSure Ground Stability',
- origin: 'Ordnance Survey',
- use: 'Ground stability hazard ratings on a 5km hex grid covering Great Britain. Six risk categories (collapsible deposits, compressible ground, landslides, running sand, shrink-swell, and soluble rocks) rated Low, Moderate, or Significant. Spatial-joined to postcodes via centroid intersection.',
- url: 'https://osdatahub.os.uk/downloads/open/GeoSure',
- license: 'Open Government Licence v3.0',
- },
- {
- id: 'council-tax',
- name: 'Council Tax Levels 2025-26',
- origin: 'Ministry of Housing, Communities & Local Government',
- use: 'Annual council tax rates for Bands A-H for all 296 billing authorities in England, for a dwelling occupied by two adults. Joined to properties via local authority district code from the NSPL postcode lookup.',
- url: 'https://www.gov.uk/government/statistics/council-tax-levels-set-by-local-authorities-in-england-2025-to-2026',
- license: 'Open Government Licence v3.0',
- },
-];
-
-export default function DataSourcesPage() {
- const [highlightedId, setHighlightedId] = useState(null);
- const cardRefs = useRef>({});
-
- useEffect(() => {
- function handleHash() {
- const hash = window.location.hash.replace('#', '');
- if (hash && DATA_SOURCES.some((s) => s.id === hash)) {
- setHighlightedId(hash);
- // Scroll after a brief delay to allow render
- setTimeout(() => {
- cardRefs.current[hash]?.scrollIntoView({ behavior: 'smooth', block: 'center' });
- }, 100);
- } else {
- setHighlightedId(null);
- }
- }
- handleHash();
- window.addEventListener('hashchange', handleHash);
- return () => window.removeEventListener('hashchange', handleHash);
- }, []);
-
- return (
-
-
-
-
Data Sources
-
- This application combines {DATA_SOURCES.length} open datasets covering property prices,
- energy performance, transport, demographics, crime, environment, and more.
-