Cleanup, refactored, various changes

This commit is contained in:
2023-12-01 16:47:44 -05:00
parent c3b9f0d649
commit 1feb713a47
13 changed files with 60 additions and 69 deletions

View File

@@ -9,3 +9,12 @@
2. Generate JWT RS256 (RSA Signature with SHA-256) Private/Public keys with `make generate`
3. Build the service and ui images with `make build`
4. Run the application with `make up`
## Decoding METARS
The following resources were used to help decode METARS.
- [Metar Decode Key PDF](https://www.weather.gov/media/wrh/mesowest/metar_decode_key.pdf)
- [Metar Decode (NPS EDU)](https://met.nps.edu/~bcreasey/mr3222/files/helpful/DecodeMETAR-TAF.html)
- [Weather Phenomena](http://www.moratech.com/aviation/metar-class/metar-pg9-ww.html)
## OpenMapTiles
[Generate Vector Tiles](https://openmaptiles.org/docs/generate/generate-openmaptiles/)

View File

@@ -34817,6 +34817,7 @@
},
{
"icao": "KIAH",
"full_name": "George Bush Intercontinental Airport",
"category": "large_airport",
"point":
{
@@ -36735,23 +36736,6 @@
"iata_code": "",
"local_code": "K19"
},
{
"icao": "KK20",
"category": "small_airport",
"full_name": "Wendell H Ford Airport",
"point":
{
"y": 37.384838,
"x": -83.259662
},
"elevation_ft": 1256,
"iso_country": "US",
"iso_region": "US-KY",
"municipality": "Chavies",
"gps_code": "",
"iata_code": "",
"local_code": "CPF"
},
{
"icao": "KK22",
"category": "small_airport",

View File

@@ -14,7 +14,6 @@ pub struct Airport {
pub icao: String,
pub category: String,
pub full_name: String,
pub point: Point,
pub elevation_ft: Option<i32>,
pub iso_country: String,
pub iso_region: String,
@@ -22,6 +21,7 @@ pub struct Airport {
pub gps_code: String,
pub iata_code: String,
pub local_code: String,
pub point: Point,
pub tower: Option<bool>,
}
@@ -31,13 +31,13 @@ impl Into<QueryAirport> for Airport {
icao: self.icao.clone(),
category: self.category.clone(),
full_name: self.full_name.clone(),
point: self.point.clone(),
iso_country: self.iso_country.clone(),
iso_region: self.iso_region.clone(),
municipality: self.municipality.clone(),
gps_code: self.gps_code.clone(),
iata_code: self.iata_code.clone(),
local_code: self.local_code.clone(),
point: self.point.clone(),
data: match serde_json::to_value(&self) {
Ok(d) => d,
Err(err) => {

View File

@@ -1,4 +1,4 @@
use crate::{error_handler::ServiceError, airports::QueryAirport};
use crate::{error_handler::ServiceError, airports::{QueryAirport, Airport}};
use diesel::{r2d2::ConnectionManager, PgConnection};
use redis::{Client as RedisClient, aio::Connection as RedisConnection};
use serde::{Deserialize, Serialize};
@@ -63,10 +63,10 @@ pub fn import_data() -> i32 {
let path = "airport-codes.json";
debug!("Importing data from {}", path);
let contents: String = std::fs::read_to_string(path).expect("Failed to read file");
let airports: Vec<QueryAirport> = serde_json::from_str(&contents).expect("JSON was not well formed.");
let airports: Vec<Airport> = serde_json::from_str(&contents).expect("JSON was not well formed.");
let mut count = 0;
for airport in airports {
match QueryAirport::insert(airport) {
match QueryAirport::insert(airport.into()) {
Ok(_) => count += 1,
Err(err) => error!("Error inserting airport; {}", err)
};

View File

@@ -176,7 +176,8 @@ impl Metar {
if metar_parts[0] == "AUTO" {
metar.quality_control_flags.auto = Some(true);
metar_parts.remove(0);
} else if metar_parts[0] == "COR" {
}
if metar_parts[0] == "COR" {
metar.quality_control_flags.corrected = Some(true);
metar_parts.remove(0);
}
@@ -270,7 +271,10 @@ impl Metar {
}
// Weather Phenomena
let wx_re = regex::Regex::new(r"^(?:[+-]|VC|MI|PR|BC|DR|BL|SH|TS|FZ)?(?:DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)$").unwrap();
let wx_intensity = "(?:[+-]|VC)?";
let wx_descriptor = "(?:MI|PR|BC|DR|BL|SH|TS|FZ)?";
let wx_precipitation = "(?:DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)?";
let wx_re = regex::Regex::new(&format!(r"^{}{}{}$", wx_intensity, wx_descriptor, wx_precipitation)).unwrap();
while wx_re.is_match(metar_parts[0]) {
metar.weather_phenomena.push(metar_parts[0].to_string());
metar_parts.remove(0);
@@ -396,7 +400,7 @@ impl Metar {
}
// Flight Category
if metar.visibility_statute_mi.is_none() || metar.sky_condition.is_empty() {
if metar.visibility_statute_mi.is_none() && metar.sky_condition.is_empty() {
metar.flight_category = FlightCategory::UNKN;
} else {
let visibility = match &metar.visibility_statute_mi {
@@ -407,7 +411,7 @@ impl Metar {
v.parse::<f64>().unwrap()
}
}
None => 0.0
None => 5.0 // Assume VFR if no visibility is present
};
let ceiling = match metar.sky_condition.first() {
Some(s) => {

View File

@@ -55,9 +55,10 @@ pub fn update_airports() {
}
}
debug!("METAR update complete");
// Sleep until the observation time is 1 hour old
// Sleep until the earliest observation time is 1 hour old
// Bounded by 1 and 3600 seconds
let now = chrono::Utc::now().timestamp();
let sleep_time = now - (observation_time + (3600));
let sleep_time = std::cmp::min(std::cmp::max(1, now - (observation_time + (3600))), 3600);
debug!("Next update in {} seconds", sleep_time);
sleep(Duration::from_secs(sleep_time as u64)).await;
}

View File

@@ -46,7 +46,6 @@ export interface Airport {
category: AirportCategory;
full_name: string;
elevation_ft: number;
continent: string;
iso_country: string;
iso_region: string;
municipality: string;
@@ -58,7 +57,7 @@ export interface Airport {
y: number;
srid: number;
};
metar?: Metar;
latest_metar?: Metar;
}
export interface GetAirportResponse {

View File

@@ -5,30 +5,39 @@ export interface SkyCondition {
export interface QualityControlFlags {
auto: boolean;
auto_station: boolean;
auto_station_without_precipitation: boolean;
auto_station_with_precipication: boolean;
maintenance_indicator_on: boolean;
corrected: boolean;
}
export interface RunwayVisualRange {
runway: string;
visibility_ft: string;
variable_visibility_high_ft: string;
variable_visibility_low_ft: string;
}
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: string;
wind_speed_kt: number;
wind_gust_kt: number;
variable_wind_dir_degrees: string;
visibility_statute_mi: string;
runway_visual_range: RunwayVisualRange[];
altim_in_hg: number;
sea_level_pressure_mb: number;
quality_control_flags: QualityControlFlags;
wx_string: string;
weather_phenomena: string[];
sky_condition: SkyCondition[];
flight_category: 'VFR' | 'MVFR' | 'LIFR' | 'IFR' | 'UNKN';
three_hr_pressure_tendency_mb: number;
metar_type: string;
maxT_c: number;
minT_c: number;
precip_in: number;
elevation_m: number;
}

View File

@@ -39,7 +39,6 @@ export default function AirportTablePanel({ setAirport }: { setAirport: (airport
<Table.Td>{airport.icao}</Table.Td>
<Table.Td>{airport.full_name}</Table.Td>
<Table.Td>{airportCategoryToText(airport.category)}</Table.Td>
<Table.Td>{airport.continent}</Table.Td>
<Table.Td>{airport.iso_country}</Table.Td>
<Table.Td>{airport.iso_region}</Table.Td>
<Table.Td>{airport.municipality}</Table.Td>
@@ -64,7 +63,6 @@ export default function AirportTablePanel({ setAirport }: { setAirport: (airport
<Table.Th>ICAO</Table.Th>
<Table.Th>Full Name</Table.Th>
<Table.Th>Category</Table.Th>
<Table.Th>Continent</Table.Th>
<Table.Th>ISO Country</Table.Th>
<Table.Th>ISO Region</Table.Th>
<Table.Th>Municipality</Table.Th>

View File

@@ -2,7 +2,6 @@ import { createAirport } from "@/api/airport";
import { Airport, AirportCategory } from "@/api/airport.types";
import { Card, TextInput, Select, Group, Flex, Space, Button } from "@mantine/core";
import { useForm } from "@mantine/form";
import { useEffect } from "react";
export default function CreateAirportPanel() {
const form = useForm<Airport>({
@@ -11,7 +10,6 @@ export default function CreateAirportPanel() {
category: AirportCategory.SMALL,
full_name: '',
elevation_ft: 0,
continent: '',
iso_country: '',
iso_region: '',
municipality: '',
@@ -64,12 +62,6 @@ export default function CreateAirportPanel() {
{...form.getInputProps('elevation_ft')}
/>
<Group>
<TextInput
required
label='Continent'
placeholder='NA'
{...form.getInputProps('continent')}
/>
<TextInput
required
label='ISO Country'
@@ -82,13 +74,13 @@ export default function CreateAirportPanel() {
placeholder='US-VA'
{...form.getInputProps('iso_region')}
/>
</Group>
<TextInput
required
label='Municipality'
placeholder='Manassas'
{...form.getInputProps('municipality')}
/>
</Group>
<Group>
<TextInput
required

View File

@@ -11,7 +11,6 @@ export default function UpdateAirportModal({ airport, setAirport }: { airport: A
category: airport?.category || AirportCategory.SMALL,
full_name: airport?.full_name || '',
elevation_ft: airport?.elevation_ft || 0,
continent: airport?.continent || '',
iso_country: airport?.iso_country || '',
iso_region: airport?.iso_region || '',
municipality: airport?.municipality || '',
@@ -73,12 +72,6 @@ export default function UpdateAirportModal({ airport, setAirport }: { airport: A
{...form.getInputProps('elevation_ft')}
/>
<Group>
<TextInput
required
label='Continent'
placeholder='NA'
{...form.getInputProps('continent')}
/>
<TextInput
required
label='ISO Country'

View File

@@ -62,7 +62,7 @@ export default function MapTiles() {
metars.forEach((metar) => {
airportData.forEach((airport) => {
if (metar.station_id == airport.icao) {
airport.metar = metar;
airport.latest_metar = metar;
}
});
});
@@ -82,16 +82,18 @@ export default function MapTiles() {
className: 'metar-marker-icon'
});
}
if (airport.metar?.flight_category == 'VFR') {
if (airport.latest_metar?.flight_category == 'VFR') {
return innerIcon({ tag: 'V', color: 'green' });
} else if (airport.metar?.flight_category == 'MVFR') {
} else if (airport.latest_metar?.flight_category == 'MVFR') {
return innerIcon({ tag: 'M', color: 'blue' });
} else if (airport.metar?.flight_category == 'IFR') {
} else if (airport.latest_metar?.flight_category == 'IFR') {
return innerIcon({ tag: 'I', color: 'red' });
} else if (airport.metar?.flight_category == 'LIFR') {
} else if (airport.latest_metar?.flight_category == 'LIFR') {
return innerIcon({ tag: 'L', color: 'purple' });
} else {
} else if (airport.latest_metar?.flight_category == 'UNKN') {
return innerIcon({ tag: 'U', color: 'black', size: 'xs' });
} else {
return innerIcon({tag: ' ', color: 'black', size: 'xs' });
}
}

View File

@@ -61,7 +61,7 @@ export default function MetarModal({ airport, isOpen, onClose }: MetarModalProps
</span>
<div className='min-w-0 flex-1'>
<Divider style={{ paddingTop: '0.1em' }} />
{airport.metar && <MetarInfo metar={airport.metar} />}
{airport.latest_metar && <MetarInfo metar={airport.latest_metar} />}
</div>
</Modal>
);
@@ -164,8 +164,8 @@ function MetarInfo({ metar }: { metar: Metar }) {
</Grid.Col>
<Grid.Col className='gutter-row' span={12}>
<Grid style={{ paddingTop: '1em', paddingBottom: '1em' }} gutter={48}>
{metar.wx_string &&
metar.wx_string.split(' ').map((wx) => (
{metar.weather_phenomena &&
metar.weather_phenomena.map((wx) => (
<Grid.Col span={1}>
<MetarIcon wx={wx} />
</Grid.Col>