simple website with default airports
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
pub struct Airport {
|
||||
pub name: String,
|
||||
pub icao: String
|
||||
}
|
||||
|
||||
impl Airport {
|
||||
pub fn new(name: String, icao: String) -> Airport {
|
||||
Airport { name, icao }
|
||||
}
|
||||
}
|
||||
0
src/app/components/.gitkeep
Normal file
0
src/app/components/.gitkeep
Normal file
49
src/app/components/MetarCard.tsx
Normal file
49
src/app/components/MetarCard.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Airport } from "@/js/airport";
|
||||
import { Metar, getMetars } from "@/js/weather"
|
||||
import Link from "next/link"
|
||||
|
||||
export default async function MetarCard({airports}: {airports: Airport[]}) {
|
||||
// await getMetars(defaultAirports).then((result) => {
|
||||
// setMetars(result);
|
||||
// });
|
||||
const metars = await getMetars(airports);
|
||||
for (let i = 0; i < airports.length; i++) {
|
||||
airports[i].metar = metars[i];
|
||||
}
|
||||
|
||||
function metarBGColor(metar: Metar | undefined) {
|
||||
if (metar?.flight_category == 'VFR') {
|
||||
return 'bg-emerald-600'
|
||||
} else if (metar?.flight_category == 'MVFR') {
|
||||
return 'bg-blue-600'
|
||||
} else if (metar?.flight_category == 'IFR') {
|
||||
return 'bg-red-600'
|
||||
} else if (metar?.flight_category == 'LIFR') {
|
||||
return 'bg-purple-600'
|
||||
} else {
|
||||
return 'bg-black'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
{airports.map((airport) => (
|
||||
<div
|
||||
key={airport.metar?.station_id}
|
||||
className={`relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-4 py-2 shadow-sm focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2 hover:border-gray-400`}
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<Link href={'#'}>
|
||||
<span className="absolute inset-0" aria-hidden="true" />
|
||||
<p className="text-gray-900 pb-1">{airport.metar?.station_id} - <span>{airport.name}</span></p>
|
||||
<p className='text-sm font-medium text-gray-500'>{airport.metar?.raw_text}</p>
|
||||
<div className='mt-2'>
|
||||
<span className={`truncate text-sm text-white ${metarBGColor(airport.metar)} inline-block py-2 px-4 rounded-full`}>{airport.metar?.flight_category}</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
14
src/app/layout.tsx
Normal file
14
src/app/layout.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import RecoilRootWrapper from '@app/recoil-root-wrapper';
|
||||
|
||||
import 'styles/globals.css';
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className='bg-gray-200'>
|
||||
<RecoilRootWrapper>{children}</RecoilRootWrapper>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
55
src/app/page.tsx
Normal file
55
src/app/page.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
// 'use client';
|
||||
|
||||
import { Airport } from '@/js/airport';
|
||||
import React from 'react';
|
||||
import MetarCard from './components/MetarCard';
|
||||
|
||||
export default function Page() {
|
||||
// const [airports, setAirports] = useRecoilState(airportsState);
|
||||
|
||||
// useEffect(() => {
|
||||
// const defaultAirports = [
|
||||
// new Airport('Leesburg Executive Airport', 'KJYO'),
|
||||
// new Airport('Manassas Regional Airpoirt', 'KHEF'),
|
||||
// new Airport('Dulles International Airport', 'KIAD'),
|
||||
// new Airport('Frederick Municipal Airport', 'KFDK'),
|
||||
// new Airport('Eastern West Virginia Regional Airport', 'KMRB'),
|
||||
// new Airport('Winchester Regional Airport', 'KOKV'),
|
||||
// new Airport('Front Royal-Warren County Airport', 'KFRR'),
|
||||
// new Airport('Luray Caverns Airport', 'KLUA'),
|
||||
// new Airport('Shenandoah Valley Airport', 'KSHD'),
|
||||
// new Airport('Charlottesville-Albemarle Airport', 'KCHO'),
|
||||
// new Airport('Culpeper Regional Airport', 'KCJR'),
|
||||
// new Airport('Warrenton-Fauquier Airport', 'KHWY'),
|
||||
// new Airport('Stafford Regional Airport', 'KRMN'),
|
||||
// new Airport('Shannon Airport', 'KEZF'),
|
||||
// ];
|
||||
// setAirports(defaultAirports);
|
||||
// }, []);
|
||||
const defaultAirports = [
|
||||
new Airport('Leesburg Executive Airport', 'KJYO'),
|
||||
new Airport('Manassas Regional Airpoirt', 'KHEF'),
|
||||
new Airport('Dulles International Airport', 'KIAD'),
|
||||
new Airport('Frederick Municipal Airport', 'KFDK'),
|
||||
new Airport('Eastern West Virginia Regional Airport', 'KMRB'),
|
||||
new Airport('Winchester Regional Airport', 'KOKV'),
|
||||
new Airport('Front Royal-Warren County Airport', 'KFRR'),
|
||||
new Airport('Luray Caverns Airport', 'KLUA'),
|
||||
new Airport('Shenandoah Valley Airport', 'KSHD'),
|
||||
new Airport('Charlottesville-Albemarle Airport', 'KCHO'),
|
||||
new Airport('Culpeper Regional Airport', 'KCJR'),
|
||||
new Airport('Warrenton-Fauquier Airport', 'KHWY'),
|
||||
new Airport('Stafford Regional Airport', 'KRMN'),
|
||||
new Airport('Shannon Airport', 'KEZF'),
|
||||
];
|
||||
|
||||
|
||||
return <>
|
||||
<div className="border-b border-gray-200 bg-gray-400 px-4 py-5 sm:px-6">
|
||||
<h3 className="text-base font-semibold leading-6 text-gray-900">Airports</h3>
|
||||
</div>
|
||||
<div className='p-4'>
|
||||
<MetarCard airports={defaultAirports}/>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
10
src/app/recoil-root-wrapper.tsx
Normal file
10
src/app/recoil-root-wrapper.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
const RecoilRootWrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
);
|
||||
|
||||
export default RecoilRootWrapper;
|
||||
13
src/js/airport.ts
Normal file
13
src/js/airport.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Metar } from "./weather";
|
||||
|
||||
export class Airport {
|
||||
name: string;
|
||||
icao: string;
|
||||
metar: Metar | undefined;
|
||||
|
||||
constructor(name: string, icao: string) {
|
||||
this.name = name;
|
||||
this.icao = icao;
|
||||
this.metar = undefined;
|
||||
}
|
||||
}
|
||||
7
src/js/state.ts
Normal file
7
src/js/state.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { atom } from "recoil";
|
||||
import { Airport } from "./airport";
|
||||
|
||||
export const airportsState = atom({
|
||||
key: 'airportsState',
|
||||
default: [] as Airport[]
|
||||
});
|
||||
104
src/js/weather.ts
Normal file
104
src/js/weather.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import axios from 'axios';
|
||||
import { xml2json } from 'xml-js';
|
||||
import { Airport } from './airport';
|
||||
|
||||
const base_url = 'https://beta.aviationweather.gov/cgi-bin/data';
|
||||
|
||||
export async function getMetars(airports: Airport[]): Promise<Metar[]> {
|
||||
const stationICAOs: string = airports
|
||||
.map((airport) => airport.icao)
|
||||
.join(',');
|
||||
const url = `${base_url}/metar.php?ids=${stationICAOs}&format=xml`;
|
||||
const response = await axios
|
||||
.get(`${url}`)
|
||||
.catch((error) => console.error(`${error}`));
|
||||
// const metars = new Map<string, Metar>();
|
||||
const metars: Metar[] = [];
|
||||
if (response != null && response != undefined) {
|
||||
const json = xml2json(response.data, { compact: true });
|
||||
const jsonObject = JSON.parse(json);
|
||||
const metarData = jsonObject?.response?.data?.METAR;
|
||||
for (const data of metarData) {
|
||||
const sky_condition: {
|
||||
sky_cover: string;
|
||||
cloud_base_ft_agl: number;
|
||||
}[] = [];
|
||||
if (Array.isArray(data.sky_condition)) {
|
||||
for (const sc of data.sky_condition) {
|
||||
sky_condition.push({
|
||||
sky_cover: sc.sky_cover,
|
||||
cloud_base_ft_agl: Number(sc.cloud_base_ft_agl)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
sky_condition.push({
|
||||
sky_cover: data.sky_condition?.sky_cover,
|
||||
cloud_base_ft_agl: Number(data.sky_condition?.cloud_base_ft_agl)
|
||||
})
|
||||
}
|
||||
const metar: Metar = {
|
||||
raw_text: data.raw_text._text,
|
||||
station_id: data.station_id._text,
|
||||
observation_time: data.observation_time._text,
|
||||
latitude: Number(data.latitude._text),
|
||||
longitude: Number(data.longitude._text),
|
||||
temp_c: Number(data.temp_c._text),
|
||||
dewpoint_c: Number(data.dewpoint_c._text),
|
||||
wind_dir_degrees: Number(data.wind_dir_degrees._text),
|
||||
wind_speed_kt: Number(data.wind_speed_kt._text),
|
||||
visibility_statute_mi: data.visibility_statute_mi._text,
|
||||
altim_in_hg: Number(data.altim_in_hg._text),
|
||||
sea_level_pressure_mb: data.sea_level_pressure_mb?._text,
|
||||
quality_control_flags: {
|
||||
auto: data.quality_control_flags?.auto?._text == 'TRUE',
|
||||
auto_station: data.quality_control_flags?.auto_station?._text == 'TRUE',
|
||||
},
|
||||
wx_string: data.wx_string?._text,
|
||||
sky_condition: sky_condition,
|
||||
flight_category: data.flight_category._text,
|
||||
three_hr_pressure_tendency_mb: data.three_hr_pressure_tendency_mb?._text,
|
||||
metar_type: data.metar_type._text,
|
||||
maxT_c: Number(data.maxT_c?._text),
|
||||
minT_c: Number(data.minT_c?._text),
|
||||
precip_in: Number(data.precip_in?._text),
|
||||
elevation_m: Number(data.elevation_m._text),
|
||||
};
|
||||
metars.push(metar);
|
||||
}
|
||||
}
|
||||
return metars;
|
||||
}
|
||||
|
||||
export interface Metar {
|
||||
raw_text: string;
|
||||
station_id: string;
|
||||
observation_time: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
temp_c: number;
|
||||
dewpoint_c: number;
|
||||
wind_dir_degrees: number;
|
||||
wind_speed_kt: number;
|
||||
visibility_statute_mi: string;
|
||||
altim_in_hg: number;
|
||||
sea_level_pressure_mb: number;
|
||||
quality_control_flags: {
|
||||
auto: boolean;
|
||||
auto_station: boolean;
|
||||
};
|
||||
wx_string: string;
|
||||
sky_condition: {
|
||||
sky_cover: string;
|
||||
cloud_base_ft_agl: number;
|
||||
}[];
|
||||
flight_category: string;
|
||||
three_hr_pressure_tendency_mb: number;
|
||||
metar_type: string;
|
||||
maxT_c: number;
|
||||
minT_c: number;
|
||||
precip_in: number;
|
||||
elevation_m: number;
|
||||
}
|
||||
162
src/lib.rs
Normal file
162
src/lib.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use log::warn;
|
||||
use std::io::BufRead;
|
||||
use quick_xml::{Reader, events::{Event, BytesStart}, Writer, de::Deserializer};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub struct Airport {
|
||||
pub name: String,
|
||||
pub icao: String
|
||||
}
|
||||
|
||||
impl Airport {
|
||||
pub fn new(name: String, icao: String) -> Airport {
|
||||
Airport { name, icao }
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Metar {
|
||||
pub raw_text: String,
|
||||
pub station_id: String,
|
||||
pub observation_time: String,
|
||||
pub latitude: f32,
|
||||
pub longitude: f32,
|
||||
pub temp_c: f32,
|
||||
pub dewpoint_c: f32,
|
||||
pub wind_dir_degrees: i32,
|
||||
pub wind_speed_kt: i32,
|
||||
pub visibility_statute_mi: String,
|
||||
pub altim_in_hg: f32,
|
||||
pub sea_level_pressure_mb: Option<f32>,
|
||||
pub quality_control_flags: Option<QualityControlFlags>,
|
||||
pub wx_string: Option<String>,
|
||||
// pub sky_con dition: Option<Vec<String>>, // TODO work on attributes
|
||||
pub flight_category: String,
|
||||
pub three_hr_pressure_tendency_mb: Option<f32>,
|
||||
pub metar_type: String,
|
||||
#[serde(rename = "maxT_c")]
|
||||
pub max_t_c: Option<f32>,
|
||||
#[serde(rename = "minT_c")]
|
||||
pub min_t_c: Option<f32>,
|
||||
pub precip_in: Option<f32>,
|
||||
pub elevation_m: i32
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct QualityControlFlags {
|
||||
pub auto: Option<bool>,
|
||||
pub auto_station: Option<bool>
|
||||
}
|
||||
|
||||
impl Metar {
|
||||
pub fn parse(input: String) -> Result<Vec<Metar>, WeatherError> {
|
||||
if input.is_empty() {
|
||||
return Err(WeatherError("Input is empty".to_string()))
|
||||
}
|
||||
|
||||
let mut reader = Reader::from_str(&input);
|
||||
let mut buf = Vec::new();
|
||||
let mut junk_buf: Vec<u8> = Vec::new();
|
||||
|
||||
loop {
|
||||
match reader.read_event_into(&mut buf) {
|
||||
Err(e) => panic!("Error at position: {}: {:?}", reader.buffer_position(), e),
|
||||
Ok(Event::Eof) => break,
|
||||
Ok(Event::Start(e)) => {
|
||||
match e.name().as_ref() {
|
||||
b"METAR" => {
|
||||
let metar_bytes = Metar::read_to_end_into_buffer(&mut reader, &e, &mut junk_buf).unwrap();
|
||||
let str = std::str::from_utf8(&metar_bytes).unwrap();
|
||||
let mut deserializer = Deserializer::from_str(str);
|
||||
let metar = Metar::deserialize(&mut deserializer).unwrap();
|
||||
println!("{:#?}", metar);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(vec![])
|
||||
}
|
||||
|
||||
// https://capnfabs.net/posts/parsing-huge-xml-quickxml-rust-serde/
|
||||
pub fn read_to_end_into_buffer<R: BufRead>(reader: &mut Reader<R>, start_tag: &BytesStart, junk_buf: &mut Vec<u8>) -> Result<Vec<u8>, quick_xml::Error> {
|
||||
let mut depth = 0;
|
||||
let mut output_buf: Vec<u8> = Vec::new();
|
||||
let mut w = Writer::new(&mut output_buf);
|
||||
let tag_name = start_tag.name();
|
||||
w.write_event(Event::Start(start_tag.clone()))?;
|
||||
loop {
|
||||
junk_buf.clear();
|
||||
let event = reader.read_event_into(junk_buf)?;
|
||||
w.write_event(&event)?;
|
||||
|
||||
match event {
|
||||
Event::Start(e) if e.name() == tag_name => depth += 1,
|
||||
Event::End(e) if e.name() == tag_name => {
|
||||
if depth == 0 {
|
||||
return Ok(output_buf);
|
||||
}
|
||||
depth -= 1;
|
||||
}
|
||||
Event::Eof => {
|
||||
panic!("EOF")
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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={}&format=xml", self.base_url, station_string);
|
||||
|
||||
let metars: Vec<Metar> = match reqwest::get(url).await {
|
||||
Ok(r) => match r.text().await {
|
||||
Ok(r) => {
|
||||
match Metar::parse(r) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("Unable to parse METAR request: {}", err);
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("Unable to get METAR request: {}", err);
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
return metars;
|
||||
}
|
||||
}
|
||||
37
src/main.rs
37
src/main.rs
@@ -1,37 +0,0 @@
|
||||
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(())
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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