Fixed metar visibility and sky condition bugs
This commit is contained in:
16
api/Cargo.lock
generated
16
api/Cargo.lock
generated
@@ -362,7 +362,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "api"
|
name = "api"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-cors",
|
"actix-cors",
|
||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
@@ -3763,7 +3763,8 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "utoipa"
|
name = "utoipa"
|
||||||
version = "5.3.1"
|
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 = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3774,7 +3775,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "utoipa-actix-web"
|
name = "utoipa-actix-web"
|
||||||
version = "0.1.2"
|
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 = [
|
dependencies = [
|
||||||
"actix-service",
|
"actix-service",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
@@ -3784,7 +3786,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "utoipa-gen"
|
name = "utoipa-gen"
|
||||||
version = "5.3.1"
|
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 = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3795,8 +3798,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utoipa-swagger-ui"
|
name = "utoipa-swagger-ui"
|
||||||
version = "9.0.1"
|
version = "9.0.2"
|
||||||
source = "git+https://github.com/juhaku/utoipa.git?rev=cecda0531bf7d90800af66b186055932ee730526#cecda0531bf7d90800af66b186055932ee730526"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "api"
|
name = "api"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["Ben Sherriff <ben@bensherriff.com>"]
|
authors = ["Ben Sherriff <ben@bensherriff.com>"]
|
||||||
repository = "https://gitea.bensherriff.com/bsherriff/aviation"
|
repository = "https://gitea.bensherriff.com/bsherriff/aviation"
|
||||||
@@ -30,13 +30,9 @@ rust-s3 = "0.35.1"
|
|||||||
rand = "0.9.1"
|
rand = "0.9.1"
|
||||||
rand_chacha = "0.9.0"
|
rand_chacha = "0.9.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
#utoipa = { version = "5.3.1", features = ["chrono", "uuid", "actix_extras"] }
|
utoipa = { version = "5.3.1", features = ["chrono", "uuid", "actix_extras"] }
|
||||||
#utoipa-swagger-ui = { version = "9.0.1", features = ["actix-web"] }
|
utoipa-swagger-ui = { version = "9.0.2", features = ["actix-web"] }
|
||||||
#utoipa-actix-web = "0.1.2"
|
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" }
|
|
||||||
webpki-roots = "1.0.0"
|
webpki-roots = "1.0.0"
|
||||||
lettre = { version = "0.11.16", features = ["builder", "smtp-transport", "tokio1-native-tls"] }
|
lettre = { version = "0.11.16", features = ["builder", "smtp-transport", "tokio1-native-tls"] }
|
||||||
handlebars = "6.3.2"
|
handlebars = "6.3.2"
|
||||||
|
|||||||
@@ -146,8 +146,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_environment() -> std::io::Result<()> {
|
fn initialize_environment() -> std::io::Result<()> {
|
||||||
|
fn init_dir(directory: &str) -> std::io::Result<()> {
|
||||||
// Iterate over files in the current directory
|
// Iterate over files in the current directory
|
||||||
for entry in std::fs::read_dir(".")? {
|
for entry in std::fs::read_dir(directory)? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
@@ -163,6 +164,11 @@ fn initialize_environment() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
init_dir("..")?;
|
||||||
|
init_dir(".")?;
|
||||||
|
|
||||||
env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "warn,api=info"));
|
env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "warn,api=info"));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -475,8 +475,23 @@ impl Metar {
|
|||||||
let visibility_parts: Vec<&str> = metar_parts[0].split("/").collect();
|
let visibility_parts: Vec<&str> = metar_parts[0].split("/").collect();
|
||||||
metar_parts.remove(0);
|
metar_parts.remove(0);
|
||||||
let visibility_left = visibility_parts[0];
|
let visibility_left = visibility_parts[0];
|
||||||
let visibility_right =
|
// Parse the right-hand of visibility, with or without an SM suffix
|
||||||
visibility_parts[1][0..visibility_parts[1].len() - 2].parse::<f64>()?;
|
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::<f64>()?;
|
||||||
let visibility = if visibility_left.starts_with("M") {
|
let visibility = if visibility_left.starts_with("M") {
|
||||||
format!(
|
format!(
|
||||||
"M{}",
|
"M{}",
|
||||||
@@ -562,11 +577,16 @@ impl Metar {
|
|||||||
metar_parts.remove(0);
|
metar_parts.remove(0);
|
||||||
}
|
}
|
||||||
let sky_condition_re =
|
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();
|
.unwrap();
|
||||||
while !metar_parts.is_empty() && sky_condition_re.is_match(metar_parts[0]) {
|
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);
|
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 sky_condition = SkyCondition::default();
|
||||||
let mut vv_offset = 0;
|
let mut vv_offset = 0;
|
||||||
if &sky_condition_string[0..2] == "VV" {
|
if &sky_condition_string[0..2] == "VV" {
|
||||||
|
|||||||
@@ -204,11 +204,89 @@ function AirportInfo({ map, airport }: { map: LeafletMap; airport: Airport }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function WeatherInfo({ metar }: { metar?: Metar }) {
|
function WeatherInfo({ metar }: { metar?: Metar }) {
|
||||||
if (metar) {
|
if (!metar) {
|
||||||
return <>{metar.raw_text}</>;
|
return <>No METAR observation available/</>
|
||||||
} else {
|
|
||||||
return <>No METAR observation available</>;
|
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Text size={'xs'} color={'dimmed'} mt={'xs'}>
|
||||||
|
Raw METAR
|
||||||
|
</Text>
|
||||||
|
<Text size={'sm'} mb={'md'}>
|
||||||
|
{metar.raw_text}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Group mb={'xs'}>
|
||||||
|
{metar.report_modifier && <Badge>{metar.report_modifier}</Badge>}
|
||||||
|
{metar.becoming_change && <Badge>TEMPO/BCMG</Badge>}
|
||||||
|
{metar.temporary_change && <Badge>TEMPO</Badge>}
|
||||||
|
{metar.no_significant_change && <Badge>No Change</Badge>}
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Text size={'xs'} color={'dimmed'}>
|
||||||
|
Observation Time
|
||||||
|
</Text>
|
||||||
|
<Text size={'sm'} mb={'md'}>
|
||||||
|
{new Date(metar.observation_time).toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{metar.wind_dir_degrees && metar.wind_speed_kt != null && (
|
||||||
|
<Text mb="sm">
|
||||||
|
<strong>Wind:</strong> {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})`}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{metar.visibility_statute_mi && (
|
||||||
|
<Text mb="sm">
|
||||||
|
<strong>Visibility:</strong> {metar.visibility_statute_mi} statute miles
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(metar.temp_c != null || metar.dew_point_c != null) && (
|
||||||
|
<Text mb="sm">
|
||||||
|
<strong>Temp / Dew Point:</strong> {metar.temp_c}°C / {metar.dew_point_c}°C
|
||||||
|
{metar.estimated_humidity != null && ` (${metar.estimated_humidity}% RH)`}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(metar.altimeter_in_hg != null || metar.sea_level_pressure_mb != null) && (
|
||||||
|
<Text mb="sm">
|
||||||
|
<strong>Pressure:</strong>
|
||||||
|
{metar.altimeter_in_hg != null && ` Alt ${metar.altimeter_in_hg} inHg`}
|
||||||
|
{metar.sea_level_pressure_mb != null && `, SLP ${metar.sea_level_pressure_mb} mb`}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{metar.weather_phenomena.length > 0 && (
|
||||||
|
<Text mb="sm">
|
||||||
|
<strong>Weather:</strong> {metar.weather_phenomena.join(', ')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{metar.sky_condition.length > 0 && (
|
||||||
|
<Text mb="sm">
|
||||||
|
<strong>Sky:</strong>{' '}
|
||||||
|
{metar.sky_condition
|
||||||
|
.map((s) => `${s.sky_cover}${s.cloud_base_ft_agl ? ` at ${s.cloud_base_ft_agl} ft` : ''}`)
|
||||||
|
.join(', ')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(metar.max_temp_c != null && metar.min_temp_c != null) && (
|
||||||
|
<Text mb="sm">
|
||||||
|
<strong>Max / Min:</strong> {metar.max_temp_c}°C / {metar.min_temp_c}°C
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{metar.density_altutude != null && (
|
||||||
|
<Text mb="sm">
|
||||||
|
<strong>Density Altitude:</strong> {metar.density_altutude} ft
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function airportCategoryToText(category: AirportCategory): string {
|
function airportCategoryToText(category: AirportCategory): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user