Initial commit

This commit is contained in:
2023-08-22 10:33:15 -04:00
commit cb005a1b98
8 changed files with 1987 additions and 0 deletions

10
src/airport.rs Normal file
View 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
View 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
View 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
View 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;
}
}