first commit

This commit is contained in:
2025-11-25 16:58:24 +01:00
commit f5f5f10338
17 changed files with 5389 additions and 0 deletions

1
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

15
backend/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "backend"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
scylla = "0.12" # Check for latest version, using a recent stable one
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "fs"] }
tracing = "0.1"
tracing-subscriber = "0.3"

83
backend/src/db.rs Normal file
View File

@@ -0,0 +1,83 @@
use scylla::{Session, SessionBuilder};
use std::sync::Arc;
pub async fn initialize_schema(session: &Session) -> Result<(), Box<dyn std::error::Error>> {
// Create keyspace
session
.query(
"CREATE KEYSPACE IF NOT EXISTS map_data WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }",
&[],
)
.await?;
// Create table for OSM nodes (points)
// Partition by tile coordinates (zoom, x, y) for efficient retrieval
// This is a simplified schema. Real OSM data is more complex.
session
.query(
"CREATE TABLE IF NOT EXISTS map_data.nodes (
zoom int,
tile_x int,
tile_y int,
id bigint,
lat double,
lon double,
tags map<text, text>,
PRIMARY KEY ((zoom, tile_x, tile_y), id)
)",
&[],
)
.await?;
session
.query(
"CREATE TABLE IF NOT EXISTS map_data.ways (
zoom int,
tile_x int,
tile_y int,
id bigint,
tags map<text, text>,
points blob,
PRIMARY KEY ((zoom, tile_x, tile_y), id)
)",
&[],
)
.await?;
session
.query(
"CREATE TABLE IF NOT EXISTS map_data.buildings (
zoom int,
tile_x int,
tile_y int,
id bigint,
tags map<text, text>,
points blob,
PRIMARY KEY ((zoom, tile_x, tile_y), id)
)",
&[],
)
.await?;
println!("Schema initialized.");
Ok(())
}
pub async fn seed_data(session: &Session) -> Result<(), Box<dyn std::error::Error>> {
// Insert some dummy data for Munich (approx lat/lon)
// Munich is roughly at lat 48.1351, lon 11.5820
// At zoom 10, this falls into a specific tile.
// For simplicity, we'll just use a fixed tile coordinate for testing: 10/500/500 (not accurate, just for ID)
let insert_stmt = "INSERT INTO map_data.nodes (zoom, tile_x, tile_y, id, lat, lon, tags) VALUES (?, ?, ?, ?, ?, ?, ?)";
let prepared = session.prepare(insert_stmt).await?;
// Point 1: Marienplatz
session.execute(&prepared, (10, 500, 500, 1_i64, 48.137, 11.575, std::collections::HashMap::from([("name".to_string(), "Marienplatz".to_string())]))).await?;
// Point 2: English Garden
session.execute(&prepared, (10, 500, 500, 2_i64, 48.150, 11.590, std::collections::HashMap::from([("name".to_string(), "English Garden".to_string())]))).await?;
println!("Test data seeded.");
Ok(())
}

146
backend/src/main.rs Normal file
View File

@@ -0,0 +1,146 @@
mod db;
use axum::{
routing::get,
Router,
extract::{State, Path},
Json,
};
use scylla::{Session, SessionBuilder};
use std::sync::Arc;
use tower_http::services::ServeDir;
use tower_http::cors::CorsLayer;
use serde::Serialize;
struct AppState {
scylla_session: Arc<Session>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize tracing
tracing_subscriber::fmt::init();
println!("Connecting to ScyllaDB...");
let uri = std::env::var("SCYLLA_URI").unwrap_or_else(|_| "127.0.0.1:9042".to_string());
let session = SessionBuilder::new()
.known_node(uri)
.build()
.await?;
// Initialize schema and seed data
db::initialize_schema(&session).await?;
db::seed_data(&session).await?;
let session = Arc::new(session);
println!("Connected to ScyllaDB!");
let state = Arc::new(AppState {
scylla_session: session,
});
let app = Router::new()
.route("/health", get(health_check))
.route("/api/tiles/:z/:x/:y", get(get_tile))
.route("/api/tiles/:z/:x/:y/ways", get(get_tile_ways))
.route("/api/tiles/:z/:x/:y/buildings", get(get_tile_buildings))
.nest_service("/", ServeDir::new("static"))
.layer(CorsLayer::permissive())
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
println!("Server listening on {}", listener.local_addr()?);
axum::serve(listener, app).await?;
Ok(())
}
async fn health_check() -> &'static str {
"OK"
}
#[derive(Serialize)]
struct MapNode {
id: i64,
lat: f64,
lon: f64,
tags: std::collections::HashMap<String, String>,
}
async fn get_tile(
Path((z, x, y)): Path<(i32, i32, i32)>,
State(state): State<Arc<AppState>>,
) -> Json<Vec<MapNode>> {
let query = "SELECT id, lat, lon, tags FROM map_data.nodes WHERE zoom = ? AND tile_x = ? AND tile_y = ?";
let rows = state.scylla_session.query(query, (z, x, y)).await.unwrap().rows.unwrap_or_default();
let mut nodes = Vec::new();
for row in rows {
let (id, lat, lon, tags) = row.into_typed::<(i64, f64, f64, std::collections::HashMap<String, String>)>().unwrap();
nodes.push(MapNode { id, lat, lon, tags });
}
Json(nodes)
}
#[derive(Serialize)]
struct MapWay {
id: i64,
tags: std::collections::HashMap<String, String>,
points: Vec<Vec<f64>>, // List of [lat, lon]
}
async fn get_tile_ways(
Path((z, x, y)): Path<(i32, i32, i32)>,
State(state): State<Arc<AppState>>,
) -> Json<Vec<MapWay>> {
let query = "SELECT id, tags, points FROM map_data.ways WHERE zoom = ? AND tile_x = ? AND tile_y = ?";
let rows = state.scylla_session.query(query, (z, x, y)).await.unwrap().rows.unwrap_or_default();
let mut ways = Vec::new();
for row in rows {
let (id, tags, points_blob) = row.into_typed::<(i64, std::collections::HashMap<String, String>, Vec<u8>)>().unwrap();
// Deserialize points blob
let mut points = Vec::new();
for chunk in points_blob.chunks(16) {
if chunk.len() == 16 {
let lat = f64::from_be_bytes(chunk[0..8].try_into().unwrap());
let lon = f64::from_be_bytes(chunk[8..16].try_into().unwrap());
points.push(vec![lat, lon]);
}
}
ways.push(MapWay { id, tags, points });
}
Json(ways)
}
async fn get_tile_buildings(
Path((z, x, y)): Path<(i32, i32, i32)>,
State(state): State<Arc<AppState>>,
) -> Json<Vec<MapWay>> {
let query = "SELECT id, tags, points FROM map_data.buildings WHERE zoom = ? AND tile_x = ? AND tile_y = ?";
let rows = state.scylla_session.query(query, (z, x, y)).await.unwrap().rows.unwrap_or_default();
let mut buildings = Vec::new();
for row in rows {
let (id, tags, points_blob) = row.into_typed::<(i64, std::collections::HashMap<String, String>, Vec<u8>)>().unwrap();
// Deserialize points blob
let mut points = Vec::new();
for chunk in points_blob.chunks(16) {
if chunk.len() == 16 {
let lat = f64::from_be_bytes(chunk[0..8].try_into().unwrap());
let lon = f64::from_be_bytes(chunk[8..16].try_into().unwrap());
points.push(vec![lat, lon]);
}
}
buildings.push(MapWay { id, tags, points });
}
Json(buildings)
}