diff --git a/.env b/.env index 76667b2..e373858 100644 --- a/.env +++ b/.env @@ -16,4 +16,5 @@ CLIENT_PORT=8080 SERVICE_LOG_LEVEL=debug -OSM_PBF_PATH=europe-latest.osm.pbf +HOST_PBF_PATH=./oberbayern-251125.osm.pbf +HOST_CACHE_DIR=./cache diff --git a/.gitignore b/.gitignore index 0c87d91..4b64452 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ scylla_data/ pkg/ node_modules/ .DS_Store -*.pbf \ No newline at end of file +*.pbf +docker-compose-remote.yml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3f37ef5..5cdb152 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -576,6 +576,12 @@ dependencies = [ "libloading 0.8.9", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "earcutr" version = "0.4.3" @@ -1292,6 +1298,7 @@ name = "importer" version = "0.1.0" dependencies = [ "anyhow", + "dotenv", "memmap2 0.9.9", "osmpbf", "scylla", diff --git a/docker-compose.yml b/docker-compose.yml index 03d5682..b1a6531 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,10 +4,6 @@ services: scylla: image: scylladb/scylla:latest container_name: scylla - ports: - - "9042:9042" - - "9160:9160" - - "10000:10000" command: --smp 1 --memory 2G --overprovisioned 1 --api-address 0.0.0.0 --max-memory-for-unlimited-query-soft-limit 1073741824 --tombstone-warn-threshold 10000000 volumes: - scylla_data:/var/lib/scylla @@ -29,8 +25,8 @@ services: target: importer container_name: map-importer volumes: - - ./oberbayern-251125.osm.pbf:/app/data.osm.pbf - - importer_cache:/cache + - ${HOST_PBF_PATH:-./europe-latest.osm.pbf}:/app/data.osm.pbf + - ${HOST_CACHE_DIR:-./cache}:/cache environment: - SCYLLA_URI=scylla:9042 - OSM_PBF_PATH=/app/data.osm.pbf @@ -42,4 +38,3 @@ services: volumes: scylla_data: - importer_cache: diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 46d5743..24c9604 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -32,6 +32,7 @@ web-sys = { version = "0.3", features = [ "RequestInit", "RequestMode", "Response", + "HtmlInputElement", ] } wgpu = { version = "0.19", default-features = false, features = ["webgl", "wgsl"] } winit = { version = "0.29", default-features = false, features = ["rwh_06"] } diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index 3666223..c046f99 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -363,24 +363,70 @@ pub async fn run() { user_location: None, })); + // Zoom constants + const MIN_ZOOM: f32 = 20.0; + const MAX_ZOOM: f32 = 50000.0; + + // Helper to convert slider (0-100) to zoom (logarithmic) + let slider_to_zoom = |val: f64| -> f32 { + let t = val / 100.0; + let log_min = MIN_ZOOM.ln(); + let log_max = MAX_ZOOM.ln(); + let log_zoom = log_min + (log_max - log_min) * t as f32; + log_zoom.exp() + }; + + // Helper to convert zoom to slider (0-100) + let zoom_to_slider = |zoom: f32| -> f64 { + let log_min = MIN_ZOOM.ln(); + let log_max = MAX_ZOOM.ln(); + let log_zoom = zoom.ln(); + let t = (log_zoom - log_min) / (log_max - log_min); + (t * 100.0) as f64 + }; + let window_doc = web_sys::window().unwrap().document().unwrap(); + + // Slider + let camera_clone = camera.clone(); + let window_clone = window.clone(); + let slider = window_doc.get_element_by_id("zoom-slider") + .and_then(|e| e.dyn_into::().ok()); + + if let Some(slider) = slider { + let closure = wasm_bindgen::closure::Closure::::new(move |event: web_sys::Event| { + let target = event.target().unwrap(); + let input = target.dyn_into::().unwrap(); + let val = input.value().parse::().unwrap_or(50.0); + + let new_zoom = slider_to_zoom(val); + let mut cam = camera_clone.lock().unwrap(); + cam.zoom = new_zoom; + window_clone.request_redraw(); + }); + slider.add_event_listener_with_callback("input", closure.as_ref().unchecked_ref()).unwrap(); + closure.forget(); + } + + // Zoom In Button let camera_clone = camera.clone(); let window_clone = window.clone(); - let btn_zoom_in = window_doc.get_element_by_id("btn-zoom-in") .and_then(|e| e.dyn_into::().ok()); if let Some(btn) = btn_zoom_in { let closure = wasm_bindgen::closure::Closure::::new(move || { let mut cam = camera_clone.lock().unwrap(); - cam.zoom *= 1.5; - cam.zoom = cam.zoom.max(20.0).min(50000.0); + let current_slider = zoom_to_slider(cam.zoom); + let new_slider = (current_slider + 5.0).min(100.0); // +5% step + cam.zoom = slider_to_zoom(new_slider); window_clone.request_redraw(); }); btn.set_onclick(Some(closure.as_ref().unchecked_ref())); closure.forget(); } + // Zoom Out Button let camera_clone = camera.clone(); let window_clone = window.clone(); let btn_zoom_out = window_doc.get_element_by_id("btn-zoom-out") @@ -389,8 +435,9 @@ pub async fn run() { if let Some(btn) = btn_zoom_out { let closure = wasm_bindgen::closure::Closure::::new(move || { let mut cam = camera_clone.lock().unwrap(); - cam.zoom /= 1.5; - cam.zoom = cam.zoom.max(20.0).min(50000.0); + let current_slider = zoom_to_slider(cam.zoom); + let new_slider = (current_slider - 5.0).max(0.0); // -5% step + cam.zoom = slider_to_zoom(new_slider); window_clone.request_redraw(); }); btn.set_onclick(Some(closure.as_ref().unchecked_ref())); @@ -538,6 +585,23 @@ pub async fn run() { if let Some(el) = window_doc.get_element_by_id("debug-zoom") { el.set_inner_html(&format!("{:.1}", cam.zoom)); } + + // Sync slider + if let Some(slider) = window_doc.get_element_by_id("zoom-slider").and_then(|e| e.dyn_into::().ok()) { + let min_zoom: f32 = 20.0; + let max_zoom: f32 = 50000.0; + let log_min = min_zoom.ln(); + let log_max = max_zoom.ln(); + let log_zoom = cam.zoom.ln(); + let t = (log_zoom - log_min) / (log_max - log_min); + let val = (t * 100.0) as f64; + + // Only update if not dragging (optional, but good for UX? actually input event handles drag) + // But if we update while dragging it might fight. + // However, for scroll wheel we need this. + // Let's just update it. The browser handles the active state. + slider.set_value(&val.to_string()); + } if let Some(el) = window_doc.get_element_by_id("debug-pos") { el.set_inner_html(&format!("{:.4}, {:.4}", cam.y, cam.x)); // Lat, Lon approx } diff --git a/importer/Cargo.toml b/importer/Cargo.toml index a6f53f3..8ad5fdb 100644 --- a/importer/Cargo.toml +++ b/importer/Cargo.toml @@ -9,3 +9,4 @@ scylla = "0.12" tokio = { version = "1.0", features = ["full"] } anyhow = "1.0" memmap2 = "0.9" +dotenv = "0.15" diff --git a/importer/src/main.rs b/importer/src/main.rs index a82abf5..6dfe6e1 100644 --- a/importer/src/main.rs +++ b/importer/src/main.rs @@ -124,6 +124,9 @@ fn should_include(tags: &HashMap, zoom: u32) -> bool { #[tokio::main] async fn main() -> Result<()> { + // Load .env file if present + dotenv::dotenv().ok(); + // Connect to ScyllaDB let uri = std::env::var("SCYLLA_URI").unwrap_or_else(|_| "127.0.0.1:9042".to_string()); println!("Connecting to ScyllaDB at {}...", uri); @@ -160,7 +163,9 @@ async fn main() -> Result<()> { let insert_railways = session.prepare("INSERT INTO map_data.railways (zoom, tile_x, tile_y, id, tags, points) VALUES (?, ?, ?, ?, ?, ?)").await?; println!("Statements prepared."); - let path = std::env::var("OSM_PBF_PATH").unwrap_or_else(|_| "europe-latest.osm.pbf".to_string()); + let path = std::env::var("OSM_PBF_PATH") + .or_else(|_| std::env::var("HOST_PBF_PATH")) + .unwrap_or_else(|_| "europe-latest.osm.pbf".to_string()); println!("Reading {}...", path); let reader = ElementReader::from_path(path)?; diff --git a/run.sh b/run.sh index ed6737d..63fda07 100644 --- a/run.sh +++ b/run.sh @@ -1 +1,9 @@ -docker compose --profile import up --build importer \ No newline at end of file +#!/bin/bash +# Load .env variables +if [ -f .env ]; then + export $(cat .env | grep -v '#' | awk '/=/ {print $1}') +fi + +echo "Using PBF file: ${HOST_PBF_PATH:-./europe-latest.osm.pbf}" +docker compose --profile import up --build importer +docker compose build --no-cache \ No newline at end of file