Skip to content

Commit

Permalink
feat: add Runway Visual Range (#5)
Browse files Browse the repository at this point in the history
* reoganising

* feat: add RVR

* rm benches
  • Loading branch information
lucianosrp authored Aug 22, 2024
1 parent b6832e6 commit 72f61e9
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 15 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ anyhow = "1.0.86"
nom = "7.1.3"

[dev-dependencies]
criterion = "0.5.1"
metar = "0.7.7"
reqwest = { version = "0.12.4", features = ["blocking"] }
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Following [this useful page](https://wiki.ivao.aero/en/home/training/documentati
- [x] Visibility (Meters only)
- [x] Visibility (SM)
- [x] Visibility (Custom directions)
- [ ] Runway Visual Range (RVR)
- [x] Runway Visual Range (RVR)
- [ ] Present Weather
- [ ] Cloud Layers
- [ ] Air temperature and dew point
Expand Down
9 changes: 4 additions & 5 deletions examples/parse_cycles.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::time::Instant;

use anyhow::{Ok, Result};
use metar_pars::METAR;

use metar_pars::Metar;

fn get_cycles() -> Result<String> {
let url = "https://tgftp.nws.noaa.gov/data/observations/metar/cycles/09Z.TXT";
let url = "https://tgftp.nws.noaa.gov/data/observations/metar/cycles/14Z.TXT";
let res = reqwest::blocking::get(url)?.text()?;
Ok(res)
}
Expand All @@ -15,8 +14,8 @@ fn main() -> Result<(), anyhow::Error> {
let s = Instant::now();
let lines: Vec<_> = res
.lines()
.filter(|x| !x.is_empty() && x.len() > 16 && x.contains("KTEB"))
.map(|x| METAR::parse(x))
.filter(|x| !x.is_empty() && x.len() > 16 && x.contains("EGLL"))
.map(|x| Metar::parse(x))
.collect();

dbg!(lines);
Expand Down
6 changes: 3 additions & 3 deletions examples/parse_str.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use metar_pars::METAR;
use metar_pars::Metar;
use std::time::Instant;

fn main() -> anyhow::Result<()> {
let s = Instant::now();
let sample = "METAR LICJ 141600Z 120120G50KT 090V150 CAVOK R04/P1500N R22/P1500U +SN BKN022 OVC050 M04/M07 Q1020 NOSIG 8849//91=";
let metar = METAR::parse(sample)?;
let sample = "Metar LICJ 141600Z 120120G50KT 090V150 CAVOK R04/P1500N R22/P1500U +SN BKN022 OVC050 M04/M07 Q1020 NOSIG 8849//91=";
let metar = Metar::parse(sample)?;
dbg!(metar);
println!("{:?}", s.elapsed());
Ok(())
Expand Down
14 changes: 9 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use nom::combinator::{map_res, opt};
use nom::multi::count;
use nom::sequence::tuple;
use nom::{bytes::complete::take, IResult};
use rvr::RunwayVisualRange;
use visibility::{parse_visibility, Visibility};
use wind::{parse_wind, Wind};
pub mod rvr;
pub mod visibility;
pub mod wind;

Expand Down Expand Up @@ -72,27 +74,29 @@ impl Time {
}

#[derive(Debug, PartialEq)]
pub struct METAR {
pub struct Metar {
report_type: ReportType,
station: String,
time: Time,
wind: Wind,
visibility: Visibility,
runway_visual_range: Vec<RunwayVisualRange>,
}

impl METAR {
pub fn parse(s: &str) -> Result<METAR, nom::Err<nom::error::Error<&str>>> {
impl Metar {
pub fn parse(s: &str) -> Result<Metar, nom::Err<nom::error::Error<&str>>> {
let (_, (station, (time, _), report_type, wind, visibility)) =
tuple((take4, time, report_type, parse_wind, parse_visibility))(
s.trim_start_matches("METAR").trim(),
s.trim_start_matches("Metar").trim(),
)?;

Ok(METAR {
Ok(Metar {
report_type,
station: station.to_owned(),
time,
wind,
visibility,
runway_visual_range: vec![],
})
}
}
Expand Down
176 changes: 176 additions & 0 deletions src/rvr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
use std::str::FromStr;

use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{i32 as nomi32, i8 as nomi8},
combinator::{map_opt, opt},
multi::separated_list0,
sequence::tuple,
IResult,
};

#[derive(Debug, PartialEq)]
pub enum RunwayPosition {
Left,
Center,
Right,
}

impl FromStr for RunwayPosition {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"L" => Ok(RunwayPosition::Left),
"R" => Ok(RunwayPosition::Right),
"C" => Ok(RunwayPosition::Center),
_ => Err(format!("Cannot parse {}", s)),
}
}
}

#[derive(Debug, PartialEq)]
pub enum VisibilityScale {
Plus,
Minus,
}

impl FromStr for VisibilityScale {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"P" => Ok(VisibilityScale::Plus),
"M" => Ok(VisibilityScale::Minus),
_ => Err(format!("Cannot parse {}", s)),
}
}
}
#[derive(Debug, PartialEq)]
pub enum VisibilityStatus {
Down,
Up,
No,
}
impl FromStr for VisibilityStatus {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"D" => Ok(VisibilityStatus::Down),
"U" => Ok(VisibilityStatus::Up),
"N" => Ok(VisibilityStatus::No),
_ => Err(format!("Cannot parse {}", s)),
}
}
}

#[derive(Debug, PartialEq)]
pub struct RunwayVisualRange {
pub number: i8,
pub position: Option<RunwayPosition>,
pub visibility_meters: i32,
pub visibility_scale: Option<VisibilityScale>,
pub visibility_status: Option<VisibilityStatus>,
}

pub fn parse_rvr(s: &str) -> IResult<&str, RunwayVisualRange> {
let meter_parser = nomi32;
let position_parser = map_opt(
opt(alt((tag("L"), tag("R"), tag("C")))),
|s: Option<&str>| s.and_then(|x| x.parse::<RunwayPosition>().ok()),
);

let vis_scale_parser = map_opt(opt(alt((tag("M"), tag("P")))), |s: Option<&str>| {
s.and_then(|x| x.parse::<VisibilityScale>().ok())
});

let vis_status_parser = map_opt(
opt(alt((tag("D"), tag("U"), tag("N")))),
|s: Option<&str>| s.and_then(|x| x.parse::<VisibilityStatus>().ok()),
);

let (other, (_, number, position, _, vis_scale, vis_meter, vis_status)) = tuple((
tag("R"),
nomi8,
opt(position_parser),
tag("/"),
opt(vis_scale_parser),
meter_parser,
opt(vis_status_parser),
))(s)?;
Ok((
other,
RunwayVisualRange {
number,
position,
visibility_meters: vis_meter,
visibility_scale: vis_scale,
visibility_status: vis_status,
},
))
}
pub fn parse_rvrs(s: &str) -> IResult<&str, Vec<RunwayVisualRange>> {
separated_list0(tag(" "), parse_rvr)(s)
}

#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_rvr_scale() -> anyhow::Result<()> {
let res = parse_rvr("R25/M0075U")?.1;
assert_eq!(
res,
RunwayVisualRange {
number: 25,
position: None,
visibility_meters: 75,
visibility_scale: Some(VisibilityScale::Minus),
visibility_status: Some(VisibilityStatus::Up)
}
);
Ok(())
}
#[test]
fn test_parse_rvr_position_scale_status() -> anyhow::Result<()> {
let res = parse_rvr("R25L/P1075N")?.1;
assert_eq!(
res,
RunwayVisualRange {
number: 25,
position: Some(RunwayPosition::Left),
visibility_meters: 1075,
visibility_scale: Some(VisibilityScale::Plus),
visibility_status: Some(VisibilityStatus::No)
}
);
Ok(())
}

#[test]
fn test_parse_rvrs() -> anyhow::Result<()> {
let res = parse_rvrs("R25L/M1075N R25C/P200U")?.1;
assert_eq!(
res,
vec![
RunwayVisualRange {
number: 25,
position: Some(RunwayPosition::Left),
visibility_meters: 1075,
visibility_scale: Some(VisibilityScale::Minus),
visibility_status: Some(VisibilityStatus::No)
},
RunwayVisualRange {
number: 25,
position: Some(RunwayPosition::Center),
visibility_meters: 200,
visibility_scale: Some(VisibilityScale::Plus),
visibility_status: Some(VisibilityStatus::Up)
}
]
);
Ok(())
}
}
18 changes: 17 additions & 1 deletion src/visibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ pub fn parse_visibility_full(s: &str) -> IResult<&str, Visibility> {
tag("S"),
tag("W"),
));
let s = s.trim_start();
let s = s.trim();
map_res(
tuple((
parse_visibility,
Expand Down Expand Up @@ -197,6 +197,22 @@ mod test {
VisibilityDirection::NorthWest
)
);
assert_eq!(
parse_visibility_full("3000 2000S")?.1,
Visibility::CustomDirection(
Box::new(Visibility::Meters(3000)),
Box::new(Visibility::Meters(2000)),
VisibilityDirection::South
)
);
assert_eq!(
parse_visibility_full("1 1/2SM 10SMS")?.1,
Visibility::CustomDirection(
Box::new(Visibility::StatuateMiles(1.5)),
Box::new(Visibility::StatuateMiles(10.0)),
VisibilityDirection::South
)
);
Ok(())
}
}

0 comments on commit 72f61e9

Please sign in to comment.