This commit is contained in:
Dongho Kim
2025-12-03 04:01:36 +09:00
parent afdcf23222
commit 003aae2b6b
10 changed files with 915 additions and 348 deletions

View File

@@ -10,3 +10,4 @@ tokio = { version = "1.0", features = ["full"] }
anyhow = "1.0"
memmap2 = "0.9"
dotenv = "0.15"
earcutr = "0.4"

View File

@@ -1,4 +1,5 @@
use anyhow::Result;
use earcutr::earcut;
use osmpbf::{Element, ElementReader};
use scylla::SessionBuilder;
use std::collections::HashMap;
@@ -8,7 +9,7 @@ use std::io::{BufWriter, Write, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use memmap2::Mmap;
const ZOOM_LEVELS: [u32; 5] = [2, 6, 9, 12, 14];
const ZOOM_LEVELS: [u32; 6] = [2, 4, 6, 9, 12, 14];
struct NodeStore {
writer: Option<BufWriter<File>>,
@@ -134,6 +135,23 @@ fn simplify_points(points: &[(f64, f64)], epsilon: f64) -> Vec<(f64, f64)> {
}
}
fn triangulate_polygon(points: &[(f64, f64)]) -> Vec<(f64, f64)> {
let mut flat_points = Vec::with_capacity(points.len() * 2);
for (lat, lon) in points {
flat_points.push(*lat);
flat_points.push(*lon);
}
// We assume simple polygons (no holes) for now as we are just processing ways
let indices = earcut(&flat_points, &[], 2).unwrap_or_default();
let mut triangles = Vec::with_capacity(indices.len());
for i in indices {
triangles.push(points[i]);
}
triangles
}
fn should_include(tags: &HashMap<String, String>, zoom: u32) -> bool {
if zoom >= 14 { return true; }
@@ -144,19 +162,34 @@ fn should_include(tags: &HashMap<String, String>, zoom: u32) -> bool {
let waterway = tags.get("waterway").map(|s| s.as_str());
match zoom {
match zoom {
2 => {
// Space View: Continents and Countries
matches!(place, Some("continent" | "country")) ||
matches!(natural, Some("water")) // Major water bodies
matches!(natural, Some("water")) || // Major water bodies
matches!(highway, Some("motorway")) || // Added motorway
matches!(tags.get("landuse").map(|s| s.as_str()), Some("forest" | "grass" | "meadow" | "farmland" | "residential")) || // Added more green + farmland/residential
matches!(tags.get("leisure").map(|s| s.as_str()), Some("park" | "nature_reserve")) || // Added parks
matches!(natural, Some("wood" | "scrub")) // Added wood/scrub
},
4 => {
// Regional View (NEW)
matches!(highway, Some("motorway" | "trunk")) ||
matches!(place, Some("city" | "town")) ||
matches!(natural, Some("water" | "wood" | "scrub" | "heath" | "wetland")) ||
matches!(tags.get("landuse").map(|s| s.as_str()), Some("forest" | "grass" | "meadow" | "farmland" | "residential")) ||
matches!(tags.get("leisure").map(|s| s.as_str()), Some("park" | "nature_reserve")) ||
matches!(waterway, Some("river"))
},
6 => {
// Enterprise Grade: ONLY Motorways and Trunk roads. No primary/secondary.
// ONLY Cities. No nature/landuse.
matches!(highway, Some("motorway" | "trunk")) ||
matches!(highway, Some("motorway" | "trunk" | "primary")) || // Added primary
matches!(place, Some("city")) ||
matches!(natural, Some("water")) || // Only major water bodies
matches!(waterway, Some("river")) // Only major rivers
matches!(natural, Some("water" | "wood" | "scrub" | "heath" | "wetland")) ||
matches!(tags.get("landuse").map(|s| s.as_str()), Some("forest" | "grass" | "meadow" | "farmland" | "residential")) ||
matches!(tags.get("leisure").map(|s| s.as_str()), Some("park" | "nature_reserve")) ||
matches!(waterway, Some("river"))
},
9 => {
// Enterprise Grade: Add Primary roads.
@@ -166,12 +199,15 @@ fn should_include(tags: &HashMap<String, String>, zoom: u32) -> bool {
matches!(place, Some("city" | "town")) ||
matches!(railway, Some("rail")) ||
matches!(natural, Some("water" | "wood")) ||
matches!(tags.get("landuse").map(|s| s.as_str()), Some("forest")) ||
matches!(tags.get("leisure").map(|s| s.as_str()), Some("park")) ||
matches!(waterway, Some("river" | "riverbank"))
},
12 => {
matches!(highway, Some("motorway" | "trunk" | "primary" | "secondary" | "tertiary")) ||
matches!(place, Some("city" | "town" | "village")) ||
matches!(railway, Some("rail")) ||
tags.contains_key("building") ||
tags.contains_key("landuse") ||
tags.contains_key("leisure") ||
matches!(natural, Some("water" | "wood" | "scrub" | "wetland" | "heath")) ||
@@ -396,11 +432,19 @@ async fn main() -> Result<()> {
if !should_include(&tags, zoom) { continue; }
// Apply simplification based on zoom level
let epsilon = match zoom {
2 => 0.1, // Very high simplification
6 => 0.005, // High simplification
9 => 0.001, // Medium simplification
_ => 0.0, // No simplification
let base_epsilon = match zoom {
2 => 0.0001,
4 => 0.00005,
6 => 0.00002,
9 => 0.00001,
12 => 0.000005,
_ => 0.0,
};
let epsilon = if is_water || is_landuse || is_highway {
base_epsilon * 0.5 // Preserve more detail for natural features AND roads
} else {
base_epsilon
};
let simplified_points = if epsilon > 0.0 {
@@ -409,17 +453,28 @@ async fn main() -> Result<()> {
points.clone()
};
if simplified_points.len() < 2 { continue; }
// Serialize points
let mut final_points = simplified_points;
// Triangulate if it's a polygon type
if is_building || is_water || is_landuse {
// Close the loop if not closed
if final_points.first() != final_points.last() {
final_points.push(final_points[0]);
}
final_points = triangulate_polygon(&final_points);
}
let (first_lat, first_lon) = simplified_points[0];
if final_points.len() < 2 { continue; }
let (first_lat, first_lon) = final_points[0];
let (x, y) = lat_lon_to_tile(first_lat, first_lon, zoom);
let zoom_i32 = zoom as i32;
// Serialize simplified points
let mut blob = Vec::with_capacity(simplified_points.len() * 16);
for (lat, lon) in simplified_points {
blob.extend_from_slice(&lat.to_be_bytes());
blob.extend_from_slice(&lon.to_be_bytes());
let mut blob = Vec::with_capacity(final_points.len() * 8); // 4 bytes lat + 4 bytes lon
for (lat, lon) in final_points {
blob.extend_from_slice(&(lat as f32).to_le_bytes());
blob.extend_from_slice(&(lon as f32).to_le_bytes());
}
if is_highway {