From 28dc464ec5a99ca633aefb3323129cdb6b59b483 Mon Sep 17 00:00:00 2001 From: Ben Sherriff Date: Wed, 28 May 2025 19:14:10 -0400 Subject: [PATCH] Fixed metar visibility and sky condition bugs --- api/Cargo.lock | 16 +++-- api/Cargo.toml | 12 ++-- api/src/main.rs | 30 ++++---- api/src/metars/model.rs | 28 ++++++-- ui/src/components/AirportDrawer/index.tsx | 86 +++++++++++++++++++++-- 5 files changed, 138 insertions(+), 34 deletions(-) diff --git a/api/Cargo.lock b/api/Cargo.lock index f143356..8e1cec1 100644 --- a/api/Cargo.lock +++ b/api/Cargo.lock @@ -362,7 +362,7 @@ dependencies = [ [[package]] name = "api" -version = "0.1.1" +version = "0.1.2" dependencies = [ "actix-cors", "actix-multipart", @@ -3763,7 +3763,8 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" version = "5.3.1" -source = "git+https://github.com/juhaku/utoipa.git?rev=cecda0531bf7d90800af66b186055932ee730526#cecda0531bf7d90800af66b186055932ee730526" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" dependencies = [ "indexmap", "serde", @@ -3774,7 +3775,8 @@ dependencies = [ [[package]] name = "utoipa-actix-web" version = "0.1.2" -source = "git+https://github.com/juhaku/utoipa.git?rev=cecda0531bf7d90800af66b186055932ee730526#cecda0531bf7d90800af66b186055932ee730526" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7eda9c23c05af0fb812f6a177514047331dac4851a2c8e9c4b895d6d826967f" dependencies = [ "actix-service", "actix-web", @@ -3784,7 +3786,8 @@ dependencies = [ [[package]] name = "utoipa-gen" version = "5.3.1" -source = "git+https://github.com/juhaku/utoipa.git?rev=cecda0531bf7d90800af66b186055932ee730526#cecda0531bf7d90800af66b186055932ee730526" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" dependencies = [ "proc-macro2", "quote", @@ -3795,8 +3798,9 @@ dependencies = [ [[package]] name = "utoipa-swagger-ui" -version = "9.0.1" -source = "git+https://github.com/juhaku/utoipa.git?rev=cecda0531bf7d90800af66b186055932ee730526#cecda0531bf7d90800af66b186055932ee730526" +version = "9.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" dependencies = [ "actix-web", "base64", diff --git a/api/Cargo.toml b/api/Cargo.toml index c876077..b53ff03 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "api" -version = "0.1.1" +version = "0.1.2" edition = "2024" authors = ["Ben Sherriff "] repository = "https://gitea.bensherriff.com/bsherriff/aviation" @@ -30,13 +30,9 @@ rust-s3 = "0.35.1" rand = "0.9.1" rand_chacha = "0.9.0" futures = "0.3.31" -#utoipa = { version = "5.3.1", features = ["chrono", "uuid", "actix_extras"] } -#utoipa-swagger-ui = { version = "9.0.1", features = ["actix-web"] } -#utoipa-actix-web = "0.1.2" -# Temporary fix until crate is updated to fix zip yank -utoipa = { git = "https://github.com/juhaku/utoipa.git", rev = "cecda0531bf7d90800af66b186055932ee730526", features = ["chrono", "uuid", "actix_extras"] } -utoipa-swagger-ui = { git = "https://github.com/juhaku/utoipa.git", rev = "cecda0531bf7d90800af66b186055932ee730526", features = ["actix-web"] } -utoipa-actix-web = { git = "https://github.com/juhaku/utoipa.git", rev = "cecda0531bf7d90800af66b186055932ee730526" } +utoipa = { version = "5.3.1", features = ["chrono", "uuid", "actix_extras"] } +utoipa-swagger-ui = { version = "9.0.2", features = ["actix-web"] } +utoipa-actix-web = "0.1.2" webpki-roots = "1.0.0" lettre = { version = "0.11.16", features = ["builder", "smtp-transport", "tokio1-native-tls"] } handlebars = "6.3.2" diff --git a/api/src/main.rs b/api/src/main.rs index 5e48ce2..b03a86a 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -146,24 +146,30 @@ async fn main() -> Result<(), Box> { } fn initialize_environment() -> std::io::Result<()> { - // Iterate over files in the current directory - for entry in std::fs::read_dir(".")? { - let entry = entry?; - let path = entry.path(); + fn init_dir(directory: &str) -> std::io::Result<()> { + // Iterate over files in the current directory + for entry in std::fs::read_dir(directory)? { + let entry = entry?; + let path = entry.path(); - // Check if the file name starts with ".env" and is a file - if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { - if file_name.starts_with(".env") && path.is_file() { - // Try to load the file - if let Err(err) = from_filename(&file_name) { - eprintln!("Failed to load {}: {}", file_name, err); - } else { - println!("Loaded: {}", file_name); + // Check if the file name starts with ".env" and is a file + if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { + if file_name.starts_with(".env") && path.is_file() { + // Try to load the file + if let Err(err) = from_filename(&file_name) { + eprintln!("Failed to load {}: {}", file_name, err); + } else { + println!("Loaded: {}", file_name); + } } } } + Ok(()) } + init_dir("..")?; + init_dir(".")?; + env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "warn,api=info")); Ok(()) } diff --git a/api/src/metars/model.rs b/api/src/metars/model.rs index e0b612d..691ad55 100644 --- a/api/src/metars/model.rs +++ b/api/src/metars/model.rs @@ -475,8 +475,23 @@ impl Metar { let visibility_parts: Vec<&str> = metar_parts[0].split("/").collect(); metar_parts.remove(0); let visibility_left = visibility_parts[0]; - let visibility_right = - visibility_parts[1][0..visibility_parts[1].len() - 2].parse::()?; + // Parse the right-hand of visibility, with or without an SM suffix + let visibility_right_string = match visibility_parts[1].strip_suffix("SM") { + Some(s) => s, + None => { + if visibility_parts[1].chars().all(|c| c.is_numeric() || c == '.') { + visibility_parts[1] + } else { + log::warn!( + "Skipping invalid visibility field '{}' ({})", + metar_parts[0], + metar_string + ); + continue; + } + } + }; + let visibility_right = visibility_right_string.parse::()?; let visibility = if visibility_left.starts_with("M") { format!( "M{}", @@ -562,11 +577,16 @@ impl Metar { metar_parts.remove(0); } let sky_condition_re = - regex::Regex::new(r"^(?:CLR|SKC|NSC|NCD|(?:FEW|SCT|BKN|OVC|VV)([0-9/]{3})?(?:CB|TCU)?)$") + regex::Regex::new(r"^(?:CLR|SKC|NSC|NCD|(?:FEW|SCT|BKN|OVC|VV)([0-9/]{3})?(?:CB|TCU)?)(?:///)?$") .unwrap(); while !metar_parts.is_empty() && sky_condition_re.is_match(metar_parts[0]) { - let sky_condition_string = metar_parts[0]; + let mut sky_condition_string = metar_parts[0]; metar_parts.remove(0); + + if sky_condition_string.ends_with("///") { + sky_condition_string = &sky_condition_string[..sky_condition_string.len() - 3]; + } + let mut sky_condition = SkyCondition::default(); let mut vv_offset = 0; if &sky_condition_string[0..2] == "VV" { diff --git a/ui/src/components/AirportDrawer/index.tsx b/ui/src/components/AirportDrawer/index.tsx index 48e21b7..bcf9abf 100644 --- a/ui/src/components/AirportDrawer/index.tsx +++ b/ui/src/components/AirportDrawer/index.tsx @@ -204,11 +204,89 @@ function AirportInfo({ map, airport }: { map: LeafletMap; airport: Airport }) { } function WeatherInfo({ metar }: { metar?: Metar }) { - if (metar) { - return <>{metar.raw_text}; - } else { - return <>No METAR observation available; + if (!metar) { + return <>No METAR observation available/ } + return ( + + + Raw METAR + + + {metar.raw_text} + + + + {metar.report_modifier && {metar.report_modifier}} + {metar.becoming_change && TEMPO/BCMG} + {metar.temporary_change && TEMPO} + {metar.no_significant_change && No Change} + + + + Observation Time + + + {new Date(metar.observation_time).toLocaleString()} + + + {metar.wind_dir_degrees && metar.wind_speed_kt != null && ( + + Wind: {metar.wind_dir_degrees}° at {metar.wind_speed_kt} kt + {metar.wind_gust_kt && `, gusts ${metar.wind_gust_kt} kt`} + {metar.variable_wind_dir_degrees && ` (variable ${metar.variable_wind_dir_degrees})`} + + )} + + {metar.visibility_statute_mi && ( + + Visibility: {metar.visibility_statute_mi} statute miles + + )} + + {(metar.temp_c != null || metar.dew_point_c != null) && ( + + Temp / Dew Point: {metar.temp_c}°C / {metar.dew_point_c}°C + {metar.estimated_humidity != null && ` (${metar.estimated_humidity}% RH)`} + + )} + + {(metar.altimeter_in_hg != null || metar.sea_level_pressure_mb != null) && ( + + Pressure: + {metar.altimeter_in_hg != null && ` Alt ${metar.altimeter_in_hg} inHg`} + {metar.sea_level_pressure_mb != null && `, SLP ${metar.sea_level_pressure_mb} mb`} + + )} + + {metar.weather_phenomena.length > 0 && ( + + Weather: {metar.weather_phenomena.join(', ')} + + )} + + {metar.sky_condition.length > 0 && ( + + Sky:{' '} + {metar.sky_condition + .map((s) => `${s.sky_cover}${s.cloud_base_ft_agl ? ` at ${s.cloud_base_ft_agl} ft` : ''}`) + .join(', ')} + + )} + + {(metar.max_temp_c != null && metar.min_temp_c != null) && ( + + Max / Min: {metar.max_temp_c}°C / {metar.min_temp_c}°C + + )} + + {metar.density_altutude != null && ( + + Density Altitude: {metar.density_altutude} ft + + )} + + ) } function airportCategoryToText(category: AirportCategory): string {