has issues

This commit is contained in:
Andras Schmelczer 2026-05-25 13:20:17 +01:00
parent 2e112d7398
commit c645b0f1d4
96 changed files with 2147083 additions and 5787 deletions

View file

@ -19,7 +19,7 @@ mod routes;
mod state;
pub mod utils;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
@ -167,6 +167,18 @@ struct Cli {
#[arg(long)]
tiles: PathBuf,
/// Optional PMTiles raster overlay for high-resolution strategic noise.
#[arg(long, env = "NOISE_OVERLAY_TILES")]
noise_overlay_tiles: Option<PathBuf>,
/// Optional PMTiles vector overlay for crime heatmap points.
#[arg(long, env = "CRIME_HOTSPOT_TILES")]
crime_hotspot_tiles: Option<PathBuf>,
/// Optional PMTiles vector overlay for Trees Outside Woodland polygons.
#[arg(long, env = "TREE_OVERLAY_TILES")]
tree_overlay_tiles: Option<PathBuf>,
/// Path to the frontend dist directory (optional; disables static serving and OG injection when omitted)
#[arg(long)]
dist: Option<PathBuf>,
@ -207,6 +219,10 @@ struct Cli {
#[arg(long, env = "ACTUAL_LISTINGS_PATH")]
actual_listings_path: Option<PathBuf>,
/// Optional path to the per-LSOA per-year crime parquet (display-only side table for the right pane).
#[arg(long, env = "CRIME_BY_YEAR_PATH")]
crime_by_year_path: Option<PathBuf>,
/// Google Maps API key for Street View metadata lookups
#[arg(long, env = "GOOGLE_MAPS_API_KEY")]
google_maps_api_key: String,
@ -280,6 +296,36 @@ async fn capture_server_error_responses(
response
}
async fn init_optional_tile_reader(
label: &'static str,
path: Option<&PathBuf>,
) -> anyhow::Result<Option<Arc<routes::TileReader>>> {
let Some(path) = path else {
info!("{label} overlay tiles not configured");
return Ok(None);
};
if !path.exists() {
bail!("{label} overlay PMTiles not found: {}", path.display());
}
info!("Loading {label} overlay PMTiles from {}", path.display());
Ok(Some(Arc::new(routes::init_tile_reader(path).await?)))
}
fn configured_or_default_overlay_path(
configured: &Option<PathBuf>,
tiles_path: &Path,
file_name: &str,
) -> Option<PathBuf> {
if let Some(path) = configured {
return Some(path.clone());
}
let default_path = tiles_path.parent()?.join(file_name);
default_path.exists().then_some(default_path)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
@ -424,6 +470,29 @@ async fn main() -> anyhow::Result<()> {
let tile_reader = Arc::new(routes::init_tile_reader(tiles_path).await?);
info!("PMTiles loaded successfully");
let noise_overlay_tiles = configured_or_default_overlay_path(
&cli.noise_overlay_tiles,
tiles_path,
"noise_lden_10m.pmtiles",
);
let crime_hotspot_tiles = configured_or_default_overlay_path(
&cli.crime_hotspot_tiles,
tiles_path,
"crime_hotspots.pmtiles",
);
let tree_overlay_tiles = configured_or_default_overlay_path(
&cli.tree_overlay_tiles,
tiles_path,
"trees_outside_woodlands.pmtiles",
);
let noise_overlay_reader =
init_optional_tile_reader("Noise", noise_overlay_tiles.as_ref()).await?;
let crime_hotspot_reader =
init_optional_tile_reader("Crime hotspots", crime_hotspot_tiles.as_ref()).await?;
let tree_overlay_reader =
init_optional_tile_reader("Trees outside woodland", tree_overlay_tiles.as_ref()).await?;
let feature_name_to_index: rustc_hash::FxHashMap<String, usize> = property_data
.feature_names
.iter()
@ -550,6 +619,18 @@ async fn main() -> anyhow::Result<()> {
None
};
let crime_by_year = if let Some(path) = cli.crime_by_year_path.as_ref() {
if !path.exists() {
bail!("Crime-by-year parquet not found: {}", path.display());
}
let data = data::CrimeByYearData::load(path)?;
trim_allocator("crime-by-year load");
Arc::new(data)
} else {
info!("CRIME_BY_YEAR_PATH not set; crime-over-time chart disabled");
Arc::new(data::CrimeByYearData::empty())
};
let app_state = AppState {
data: property_data,
grid,
@ -576,6 +657,7 @@ async fn main() -> anyhow::Result<()> {
gemini_model: cli.gemini_model,
travel_time_store,
actual_listings,
crime_by_year,
token_cache,
superuser_token_cache,
share_cache,
@ -610,6 +692,9 @@ async fn main() -> anyhow::Result<()> {
let reader_tile = tile_reader.clone();
let reader_style = tile_reader.clone();
let reader_noise_overlay = noise_overlay_reader.clone();
let reader_crime_hotspot = crime_hotspot_reader.clone();
let reader_tree_overlay = tree_overlay_reader.clone();
let public_url_tiles = initial_state.public_url.clone();
let api = Router::new()
@ -773,6 +858,42 @@ async fn main() -> anyhow::Result<()> {
})
.layer(ConcurrencyLimitLayer::new(20)),
)
.route(
"/api/overlays/noise/{z}/{x}/{y}",
get(move |path| {
routes::get_overlay_tile(
reader_noise_overlay.clone(),
routes::OverlayTileFormat::RasterPng,
"noise",
path,
)
})
.layer(ConcurrencyLimitLayer::new(30)),
)
.route(
"/api/overlays/crime-hotspots/{z}/{x}/{y}",
get(move |path| {
routes::get_overlay_tile(
reader_crime_hotspot.clone(),
routes::OverlayTileFormat::VectorMvtGzip,
"crime-hotspots",
path,
)
})
.layer(ConcurrencyLimitLayer::new(30)),
)
.route(
"/api/overlays/trees-outside-woodlands/{z}/{x}/{y}",
get(move |path| {
routes::get_overlay_tile(
reader_tree_overlay.clone(),
routes::OverlayTileFormat::VectorMvtGzip,
"trees-outside-woodlands",
path,
)
})
.layer(ConcurrencyLimitLayer::new(30)),
)
.route("/health", get(|| async { "ok" }))
.route(
"/metrics",