From c3e77658521f37648ee499e4b731d804607bf498 Mon Sep 17 00:00:00 2001 From: Stephen D Date: Sun, 20 Oct 2024 21:16:41 -0300 Subject: [PATCH] allow custom world scales --- src/args.rs | 4 ++ src/osm_parser.rs | 97 ++++++++++++++++++++--------------------------- 2 files changed, 46 insertions(+), 55 deletions(-) diff --git a/src/args.rs b/src/args.rs index 2240f9b..e1c710e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -29,6 +29,10 @@ pub struct Args { #[arg(long, default_value = "requests")] pub downloader: String, + /// World scale to use, in blocks per meter + #[arg(long, default_value = "1.0")] + pub scale: f64, + /// Enable debug mode (optional) #[arg(long, default_value_t = false, action = clap::ArgAction::SetTrue)] pub debug: bool, diff --git a/src/osm_parser.rs b/src/osm_parser.rs index 7b92ae6..96d496c 100644 --- a/src/osm_parser.rs +++ b/src/osm_parser.rs @@ -116,36 +116,20 @@ fn lat_lon_to_minecraft_coords( lat: f64, lon: f64, bbox: (f64, f64, f64, f64), // (min_lon, min_lat, max_lon, max_lat) - scale_factor_x: f64, scale_factor_z: f64, + scale_factor_x: f64, ) -> (i32, i32) { let (min_lon, min_lat, max_lon, max_lat) = bbox; // Calculate the relative position within the bounding box - let rel_x: f64 = 1.0 - (lat - min_lat) / (max_lat - min_lat); - let rel_z: f64 = (lon - min_lon) / (max_lon - min_lon); + let rel_x: f64 = (lon - min_lon) / (max_lon - min_lon); + let rel_z: f64 = 1.0 - (lat - min_lat) / (max_lat - min_lat); // Apply scaling factors for each dimension and convert to Minecraft coordinates let x: i32 = (rel_x * scale_factor_x) as i32; let z: i32 = (rel_z * scale_factor_z) as i32; - (z, x) // Swap x and z coords to avoid a mirrored projection on the Minecraft map -} - -/// Function to determine the number of decimal places in a float as a string -fn count_decimal_places(value: f64) -> usize { - let s: String = value.to_string(); - if let Some(pos) = s.find('.') { - s.len() - pos - 1 // Number of digits after the decimal point - } else { - 0 - } -} - -/// Function to convert f64 to an integer based on the number of decimal places -fn convert_to_scaled_int(value: f64, max_decimal_places: usize) -> i64 { - let multiplier: i64 = 10_i64.pow(max_decimal_places as u32); // Compute multiplier - (value * multiplier as f64).round() as i64 // Scale and convert to integer + (x, z) } pub fn parse_osm_data( @@ -159,40 +143,10 @@ pub fn parse_osm_data( let data: OsmData = serde_json::from_value(json_data.clone()).expect("Failed to parse OSM data"); - // Calculate the maximum number of decimal places in bbox elements - let max_decimal_places: usize = [ - count_decimal_places(bbox.0), - count_decimal_places(bbox.1), - count_decimal_places(bbox.2), - count_decimal_places(bbox.3), - ] - .into_iter() - .max() - .unwrap(); - - // Convert each element to a scaled integer - let bbox_scaled: (i64, i64, i64, i64) = ( - convert_to_scaled_int(bbox.0, max_decimal_places), - convert_to_scaled_int(bbox.1, max_decimal_places), - convert_to_scaled_int(bbox.2, max_decimal_places), - convert_to_scaled_int(bbox.3, max_decimal_places), - ); - // Determine which dimension is larger and assign scale factors accordingly - let (scale_factor_x, scale_factor_z) = - if (bbox_scaled.2 - bbox_scaled.0) > (bbox_scaled.3 - bbox_scaled.1) { - // Longitude difference is greater than latitude difference - ( - ((bbox_scaled.3 - bbox_scaled.1) * 14 / 100) as f64, // Scale for width (x) is based on latitude difference - ((bbox_scaled.2 - bbox_scaled.0) * 10 / 100) as f64, // Scale for length (z) is based on longitude difference - ) - } else { - // Latitude difference is greater than or equal to longitude difference - ( - ((bbox_scaled.2 - bbox_scaled.0) * 14 / 100) as f64, // Scale for width (x) is based on longitude difference - ((bbox_scaled.3 - bbox_scaled.1) * 10 / 100) as f64, // Scale for length (z) is based on latitude difference - ) - }; + let (scale_factor_z, scale_factor_x) = geo_distance(bbox.1, bbox.3, bbox.0, bbox.2); + let scale_factor_z = scale_factor_z.floor() * args.scale; + let scale_factor_x = scale_factor_x.floor() * args.scale; if args.debug { println!("Scale factor X: {}", scale_factor_x); @@ -209,7 +163,7 @@ pub fn parse_osm_data( if element.r#type == "node" { if let (Some(lat), Some(lon)) = (element.lat, element.lon) { let (x, z) = - lat_lon_to_minecraft_coords(lat, lon, bbox, scale_factor_x, scale_factor_z); + lat_lon_to_minecraft_coords(lat, lon, bbox, scale_factor_z, scale_factor_x); let processed = ProcessedNode { id: element.id, @@ -309,7 +263,7 @@ pub fn parse_osm_data( })); } - (processed_elements, scale_factor_z, scale_factor_x) + (processed_elements, scale_factor_x, scale_factor_z) } const PRIORITY_ORDER: [&str; 6] = [ @@ -327,3 +281,36 @@ pub fn get_priority(element: &ProcessedElement) -> usize { // Return a default priority if none of the tags match PRIORITY_ORDER.len() } + +// (lat meters, lon meters) +fn geo_distance(lat1: f64, lat2: f64, lon1: f64, lon2: f64) -> (f64, f64) { + let z = lat_distance(lat1, lat2); + + // distance between two lons depends on their latitude. In this case we'll just average them + let x = lon_distance((lat1 + lat2) / 2.0, lon1, lon2); + + (z, x) +} + +// Haversine but optimized for a latitude delta of 0 +// returns meters +fn lon_distance(lat: f64, lon1: f64, lon2: f64) -> f64 { + const R: f64 = 6371_000.0; + let d_lon = (lon2 - lon1).to_radians(); + let a = + lat.to_radians().cos() * lat.to_radians().cos() * (d_lon / 2.0).sin() * (d_lon / 2.0).sin(); + let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt()); + + R * c +} + +// Haversine but optimized for a longitude delta of 0 +// returns meters +fn lat_distance(lat1: f64, lat2: f64) -> f64 { + const R: f64 = 6371_000.0; + let d_lat = (lat2 - lat1).to_radians(); + let a = (d_lat / 2.0).sin() * (d_lat / 2.0).sin(); + let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt()); + + R * c +}