Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target/
|
||||
|
||||
.env
|
||||
1766
Cargo.lock
generated
Normal file
1766
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "aviation-weather"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.10.0"
|
||||
gtk = { version = "0.7.2", package = "gtk4", features = ["v4_12"] }
|
||||
log = "0.4.20"
|
||||
regex = "1.9.3"
|
||||
reqwest = "0.11.19"
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
10
src/airport.rs
Normal file
10
src/airport.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
pub struct Airport {
|
||||
pub name: String,
|
||||
pub icao: String
|
||||
}
|
||||
|
||||
impl Airport {
|
||||
pub fn new(name: String, icao: String) -> Airport {
|
||||
Airport { name, icao }
|
||||
}
|
||||
}
|
||||
37
src/main.rs
Normal file
37
src/main.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::error::Error;
|
||||
|
||||
use airport::Airport;
|
||||
use log::debug;
|
||||
use crate::weather::{Weather, metar::Metar};
|
||||
|
||||
mod airport;
|
||||
mod weather;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
env_logger::init();
|
||||
|
||||
let mut weather = Weather {
|
||||
base_url: "https://beta.aviationweather.gov/cgi-bin/data".to_string()
|
||||
};
|
||||
let airports: Vec<Airport> = vec![
|
||||
Airport::new("Leesburg Executive Airport".to_string(), "KJYO".to_string()),
|
||||
Airport::new("Manassas Regional Airpoirt".to_string(), "KHEF".to_string()),
|
||||
Airport::new("Dulles International Airport".to_string(), "KIAD".to_string()),
|
||||
Airport::new("Frederick Municipal Airport".to_string(), "KFDK".to_string()),
|
||||
Airport::new("Eastern West Virginia Regional Airport".to_string(), "KMRB".to_string()),
|
||||
Airport::new("Winchester Regional Airport".to_string(), "KOKV".to_string()),
|
||||
Airport::new("Front Royal-Warren County Airport".to_string(), "KFRR".to_string()),
|
||||
Airport::new("Luray Caverns Airport".to_string(), "KLUA".to_string()),
|
||||
Airport::new("Shenandoah Valley Airport".to_string(), "KSHD".to_string()),
|
||||
Airport::new("Charlottesville-Albemarle Airport".to_string(), "KCHO".to_string()),
|
||||
Airport::new("Culpeper Regional Airport".to_string(), "KCJR".to_string()),
|
||||
Airport::new("Warrenton-Fauquier Airport".to_string(), "KHWY".to_string()),
|
||||
Airport::new("Stafford Regional Airport".to_string(), "KRMN".to_string()),
|
||||
Airport::new("Shannon Airport".to_string(), "KEZF".to_string()),
|
||||
];
|
||||
|
||||
let metars: Vec<Metar> = weather.metar(airports).await;
|
||||
debug!("{:#?}", metars);
|
||||
Ok(())
|
||||
}
|
||||
102
src/weather/metar.rs
Normal file
102
src/weather/metar.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use regex::Regex;
|
||||
|
||||
use super::WeatherError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Metar {
|
||||
pub icao: String,
|
||||
pub date_time: String,
|
||||
pub report_modifier: String,
|
||||
pub wind_direction: String,
|
||||
pub wind_speed: String,
|
||||
pub wind_direction_variable: String,
|
||||
pub visibility: String,
|
||||
pub runway_visual_range: String,
|
||||
pub weather_phenomena: Vec<String>,
|
||||
pub sky_condition: Vec<String>,
|
||||
pub temperature: String,
|
||||
pub dew_point: String,
|
||||
pub altimeter: String,
|
||||
pub remarks: Vec<String>
|
||||
}
|
||||
|
||||
impl Metar {
|
||||
pub fn new(input: String) -> Result<Metar, WeatherError> {
|
||||
if input.is_empty() {
|
||||
return Err(WeatherError("Input is empty".to_string()))
|
||||
}
|
||||
let mut offset: usize = 0;
|
||||
let parts: Vec<&str> = input.split_whitespace().collect();
|
||||
if parts.len() < 5 {
|
||||
return Err(WeatherError("Unable to parse input".to_string()))
|
||||
}
|
||||
|
||||
let mut report_modifier = "";
|
||||
if parts[2] == "AUTO" {
|
||||
offset += 1;
|
||||
report_modifier = parts[2];
|
||||
}
|
||||
|
||||
let wind_re = Regex::new(r"^\d{3}V\d{3}$").unwrap();
|
||||
let wind_direction_variable = match wind_re.find(parts[3 + offset]) {
|
||||
Some(_) => {
|
||||
offset += 1;
|
||||
parts[3 + offset]
|
||||
},
|
||||
None => ""
|
||||
};
|
||||
|
||||
let mut runway_visual_range = "";
|
||||
if parts[4 + offset].ends_with("FT") {
|
||||
offset += 1;
|
||||
runway_visual_range = parts[4 + offset];
|
||||
}
|
||||
|
||||
let mut weather_phenomena: Vec<String> = vec![];
|
||||
let weather_re = Regex::new(r"^(-|\+)?[A-Z]{2}(?:[A-Z]{2})?$").unwrap();
|
||||
let mut sky_condition: Vec<String> = vec![];
|
||||
let sky_re = Regex::new(r"^CLR|(FEW|SCT|BKN|OVC)\d{3}$").unwrap();
|
||||
for n in (4 + offset)..parts.len() {
|
||||
match weather_re.find(parts[n]) {
|
||||
Some(_) => {
|
||||
offset += 1;
|
||||
weather_phenomena.push(parts[n].to_string());
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match sky_re.find(parts[n]) {
|
||||
Some(_) => {
|
||||
offset += 1;
|
||||
sky_condition.push(parts[n].to_string());
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
let temp_dew: Vec<&str> = parts[4 + offset].split("/").collect();
|
||||
let mut remarks: Vec<String> = vec![];
|
||||
if parts.len() > 6 + offset {
|
||||
// Skip the RMK string for remarks, starting at index + 1
|
||||
for n in (6 + offset + 1)..parts.len() {
|
||||
remarks.push(parts[n].to_string())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Metar {
|
||||
icao: parts[0].to_string(),
|
||||
date_time: parts[1].to_string(),
|
||||
report_modifier: report_modifier.to_string(),
|
||||
wind_direction: parts[2 + offset][..3].to_string(),
|
||||
wind_speed: parts[2 + offset][3..].to_string(),
|
||||
wind_direction_variable: wind_direction_variable.to_string(),
|
||||
visibility: parts[3 + offset].to_string(),
|
||||
runway_visual_range: runway_visual_range.to_string(),
|
||||
weather_phenomena,
|
||||
sky_condition,
|
||||
temperature: temp_dew[0].to_string(),
|
||||
dew_point: temp_dew[1].to_string(),
|
||||
altimeter: parts[5 + offset].to_string(),
|
||||
remarks
|
||||
})
|
||||
}
|
||||
}
|
||||
52
src/weather/mod.rs
Normal file
52
src/weather/mod.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use log::warn;
|
||||
|
||||
use crate::airport::Airport;
|
||||
use self::metar::Metar;
|
||||
|
||||
pub mod metar;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WeatherError(pub String);
|
||||
|
||||
impl fmt::Display for WeatherError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for WeatherError {}
|
||||
|
||||
pub struct Weather {
|
||||
pub base_url: String
|
||||
}
|
||||
|
||||
impl Weather {
|
||||
pub async fn metar(&mut self, airports: Vec<Airport>) -> Vec<Metar> {
|
||||
let mut station_icaos: Vec<&str> = vec![];
|
||||
for station in airports.iter() {
|
||||
station_icaos.push(&station.icao);
|
||||
}
|
||||
let station_string = station_icaos.join(",");
|
||||
let url = format!("{}/metar.php?ids={}", self.base_url, station_string);
|
||||
|
||||
let mut metars: Vec<Metar> = vec![];
|
||||
match reqwest::get(url).await {
|
||||
Ok(r) => match r.text().await {
|
||||
Ok(r) => {
|
||||
let lines: Vec<&str> = r.split("\n").collect();
|
||||
for line in lines.iter() {
|
||||
match Metar::new(line.to_string()) {
|
||||
Ok(m) => metars.push(m),
|
||||
Err(err) => warn!("{}", err)
|
||||
};
|
||||
}
|
||||
},
|
||||
Err(err) => warn!("Unable to parse METAR request: {}", err)
|
||||
},
|
||||
Err(err) => warn!("Unable to get METAR request: {}", err)
|
||||
}
|
||||
return metars;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user