Cleanup, refactored, various changes
This commit is contained in:
@@ -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/)
|
||||
@@ -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",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')}
|
||||
/>
|
||||
<TextInput
|
||||
required
|
||||
label='Municipality'
|
||||
placeholder='Manassas'
|
||||
{...form.getInputProps('municipality')}
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
required
|
||||
label='Municipality'
|
||||
placeholder='Manassas'
|
||||
{...form.getInputProps('municipality')}
|
||||
/>
|
||||
<Group>
|
||||
<TextInput
|
||||
required
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user