Added live metar data updates

This commit is contained in:
2023-09-10 18:42:26 -04:00
parent 4c6bee686c
commit bcda1bbf3f
8 changed files with 76 additions and 63 deletions

View File

@@ -3,6 +3,7 @@ use crate::error_handler::CustomError;
use crate::schema::airports; use crate::schema::airports;
use diesel::prelude::*; use diesel::prelude::*;
use postgis_diesel::types::*; use postgis_diesel::types::*;
use postgis_diesel::functions::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, AsChangeset, Insertable)] #[derive(Serialize, Deserialize, AsChangeset, Insertable)]
@@ -39,24 +40,12 @@ pub struct Airports {
pub point: Point pub point: Point
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct Bounds {
pub north_east: LatLng,
pub south_west: LatLng,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LatLng {
pub lat: f32,
pub lon: f32
}
impl Airports { impl Airports {
pub fn find_all(bounds: Bounds, limit: i32, page: i32) -> Result<Vec<Self>, CustomError> { pub fn find_all(bounds: Polygon<Point>, limit: i32, page: i32) -> Result<Vec<Self>, CustomError> {
let mut conn = db::connection()?; let mut conn = db::connection()?;
let airports = airports::table let airports = airports::table
.limit(limit as i64) .limit(limit as i64)
.filter(airports::id.gt(page * limit)) .filter(airports::id.gt(page * limit).and(st_contains(bounds, airports::point)))
.load::<Airports>(&mut conn)?; .load::<Airports>(&mut conn)?;
Ok(airports) Ok(airports)
} }

View File

@@ -1,19 +1,26 @@
use crate::{airports::{Airport, Airports, Bounds, LatLng}, db}; use crate::{airports::{Airport, Airports}, db};
use actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest}; use actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest};
use log::error; use log::error;
use postgis_diesel::types::{Polygon, Point};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use serde_json::json; use serde_json::json;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct FindAllParams { struct FindAllParams {
ne_lat: f32, ne_lat: f64,
ne_lon: f32, ne_lon: f64,
sw_lat: f32, sw_lat: f64,
sw_lon: f32, sw_lon: f64,
limit: i32, limit: i32,
page: i32 page: i32
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
struct Coordinate {
lon: f64,
lat: f64
}
#[get("/setup")] #[get("/setup")]
async fn setup() -> HttpResponse { async fn setup() -> HttpResponse {
db::import_data(); db::import_data();
@@ -23,11 +30,13 @@ async fn setup() -> HttpResponse {
#[get("/airports")] #[get("/airports")]
async fn find_all(req: HttpRequest) -> HttpResponse { async fn find_all(req: HttpRequest) -> HttpResponse {
let params = web::Query::<FindAllParams>::from_query(req.query_string()).unwrap(); let params = web::Query::<FindAllParams>::from_query(req.query_string()).unwrap();
let bounds = Bounds { let mut polygon: Polygon<Point> = Polygon::new(Some(4326));
north_east: LatLng { lat: params.ne_lat, lon: params.ne_lon }, polygon.add_point(Point { x: params.sw_lon, y: params.sw_lat, srid: Some(4326) });
south_west: LatLng { lat: params.sw_lat, lon: params.sw_lon } polygon.add_point(Point { x: params.ne_lon, y: params.sw_lat, srid: Some(4326) });
}; polygon.add_point(Point { x: params.ne_lon, y: params.ne_lat, srid: Some(4326) });
match web::block(move || Airports::find_all(bounds, params.limit, params.page)).await.unwrap() { polygon.add_point(Point { x: params.sw_lon, y: params.ne_lat, srid: Some(4326) });
polygon.add_point(Point { x: params.sw_lon, y: params.sw_lat, srid: Some(4326) });
match web::block(move || Airports::find_all(polygon, params.limit, params.page)).await.unwrap() {
Ok(a) => HttpResponse::Ok().json(a), Ok(a) => HttpResponse::Ok().json(a),
Err(err) => { Err(err) => {
error!("{}", err); error!("{}", err);

View File

@@ -14,18 +14,17 @@ pub struct QualityControlFlags {
#[derive(Serialize, Deserialize, AsChangeset, Insertable)] #[derive(Serialize, Deserialize, AsChangeset, Insertable)]
#[diesel(table_name = metars)] #[diesel(table_name = metars)]
pub struct Metar { pub struct Metar {
pub icao: String,
pub raw_text: String, pub raw_text: String,
pub station_id: String, pub station_id: String,
pub observation_time: String, pub observation_time: String,
pub latitude: f64, pub latitude: f64,
pub longitude: f64, pub longitude: f64,
pub temp_c: f64, pub temp_c: Option<f64>,
pub dewpoint_c: f64, pub dewpoint_c: Option<f64>,
pub wind_dir_degrees: i32, pub wind_dir_degrees: Option<String>,
pub wind_speed_kt: i32, pub wind_speed_kt: Option<i32>,
pub visibility_statute_mi: String, pub visibility_statute_mi: Option<String>,
pub altim_in_hg: f64, pub altim_in_hg: Option<f64>,
pub sea_level_pressure_mb: Option<f64>, pub sea_level_pressure_mb: Option<f64>,
// pub quality_control_flags: Option<QualityControlFlags>, // pub quality_control_flags: Option<QualityControlFlags>,
pub wx_string: Option<String>, pub wx_string: Option<String>,
@@ -35,7 +34,7 @@ pub struct Metar {
pub metar_type: String, pub metar_type: String,
#[serde(rename = "maxT_c")] #[serde(rename = "maxT_c")]
pub max_t_c: Option<f64>, pub max_t_c: Option<f64>,
#[serde(rename = "minT_c")] #[serde(rename = " ")]
pub min_t_c: Option<f64>, pub min_t_c: Option<f64>,
pub precip_in: Option<f64>, pub precip_in: Option<f64>,
pub elevation_m: i32 pub elevation_m: i32
@@ -43,19 +42,17 @@ pub struct Metar {
#[derive(Serialize, Deserialize, Queryable)] #[derive(Serialize, Deserialize, Queryable)]
pub struct Metars { pub struct Metars {
// pub id: i32,
// pub icao: String,
pub raw_text: String, pub raw_text: String,
pub station_id: String, pub station_id: String,
pub observation_time: String, pub observation_time: String,
pub latitude: f64, pub latitude: f64,
pub longitude: f64, pub longitude: f64,
pub temp_c: f64, pub temp_c: Option<f64>,
pub dewpoint_c: f64, pub dewpoint_c: Option<f64>,
pub wind_dir_degrees: i32, pub wind_dir_degrees: Option<String>,
pub wind_speed_kt: i32, pub wind_speed_kt: Option<i32>,
pub visibility_statute_mi: String, pub visibility_statute_mi: Option<String>,
pub altim_in_hg: f64, pub altim_in_hg: Option<f64>,
pub sea_level_pressure_mb: Option<f64>, pub sea_level_pressure_mb: Option<f64>,
pub quality_control_flags: Option<QualityControlFlags>, pub quality_control_flags: Option<QualityControlFlags>,
pub wx_string: Option<String>, pub wx_string: Option<String>,
@@ -119,8 +116,10 @@ impl Metars {
let metar_bytes = Metars::read_to_end_into_buffer(&mut reader, &e, &mut junk_buf).unwrap(); let metar_bytes = Metars::read_to_end_into_buffer(&mut reader, &e, &mut junk_buf).unwrap();
let str = std::str::from_utf8(&metar_bytes).unwrap(); let str = std::str::from_utf8(&metar_bytes).unwrap();
let mut deserializer = Deserializer::from_str(str); let mut deserializer = Deserializer::from_str(str);
let metar = Metars::deserialize(&mut deserializer).unwrap(); match Metars::deserialize(&mut deserializer) {
metars.push(metar); Ok(m) => metars.push(m),
Err(err) => warn!("{}", err)
};
}, },
_ => () _ => ()
} }

View File

@@ -27,12 +27,12 @@ diesel::table! {
observation_time -> Text, observation_time -> Text,
latitude -> Double, latitude -> Double,
longitude -> Double, longitude -> Double,
temp_c -> Double, temp_c -> Nullable<Double>,
dewpoint_c -> Double, dewpoint_c -> Nullable<Double>,
wind_dir_degrees -> Integer, wind_dir_degrees -> Nullable<Text>,
wind_speed_kt -> Integer, wind_speed_kt -> Nullable<Integer>,
visibility_statute_mi -> Text, visibility_statute_mi -> Nullable<Text>,
altim_in_hg -> Double, altim_in_hg -> Nullable<Double>,
sea_level_pressure_mb -> Nullable<Double>, sea_level_pressure_mb -> Nullable<Double>,
wx_string -> Nullable<Text>, wx_string -> Nullable<Text>,
flight_category -> Text, flight_category -> Text,

View File

@@ -17,7 +17,7 @@ export default function Map() {
center={[38.7209, -77.5133]} center={[38.7209, -77.5133]}
zoom={8} zoom={8}
maxZoom={12} maxZoom={12}
minZoom={3} minZoom={1}
zoomControl={false} zoomControl={false}
style={{ height: '96.5vh' }} style={{ height: '96.5vh' }}
className='overflow-y-hidden overflow-x-hidden' className='overflow-y-hidden overflow-x-hidden'
@@ -55,13 +55,17 @@ function MapTiles() {
ne_lon: ne.lng, ne_lon: ne.lng,
sw_lat: sw.lat, sw_lat: sw.lat,
sw_lon: sw.lng, sw_lon: sw.lng,
limit: 10, limit: 100,
page: 1 page: 1
}); });
const metars = await getMetars(_airports); const metars = await getMetars(_airports);
for (let i = 0; i < metars.length; i++) { metars.forEach((metar) => {
_airports[i].metar = metars[i]; _airports.forEach((airport) => {
} if (metar.station_id == airport.icao) {
airport.metar = metar;
}
});
});
setAirports(_airports); setAirports(_airports);
} }
@@ -89,7 +93,7 @@ function MapTiles() {
} else if (metar?.flight_category == 'LIFR') { } else if (metar?.flight_category == 'LIFR') {
return 'text-red-700'; return 'text-red-700';
} else { } else {
return 'text-black'; return 'text-black/50';
} }
} }
@@ -141,15 +145,15 @@ function MapTiles() {
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/> />
{airports.map((airport) => ( {airports.map((airport) => (
<Marker key={airport.icao} position={[airport.latitude, airport.longitude]} icon={icon(airport)}> <Marker key={airport.icao} position={[airport.point.y, airport.point.x]} icon={icon(airport)}>
<Tooltip className='metar-tooltip' direction='top' offset={[5, -5]} opacity={1}> <Tooltip className='metar-tooltip' direction='top' offset={[5, -5]} opacity={1}>
{airport.icao} <b>{airport.icao}</b> - {airport.full_name}
</Tooltip> </Tooltip>
<Popup> <Popup>
<div className='min-w-0 flex-1 select-none'> <div className='min-w-0 flex-1 select-none'>
<Link href={`/airport/${airport.icao}`}> <Link href={`/airport/${airport.icao}`}>
<h1 className='text-base text-gray-900 pb-1'> <h1 className='text-base text-gray-900 pb-1'>
<span className='font-semibold'>{airport.icao}</span> {airport.name} <span className='font-semibold'>{airport.icao}</span> {airport.full_name}
</h1> </h1>
</Link> </Link>
<hr /> <hr />

View File

@@ -28,7 +28,7 @@ export async function getAirports({
page = 1 page = 1
}: GetAirportsProps): Promise<Airport[]> { }: GetAirportsProps): Promise<Airport[]> {
const response = await axios const response = await axios
.get(`http://localhost:5000/airports`, { params: { ne_lat, ne_lon, sw_lat, sw_lon, page, limit } }) .get(`http://localhost:5000/airports`, { params: { ne_lat, ne_lon, sw_lat, sw_lon, limit, page } })
.catch((error) => console.error(error)); .catch((error) => console.error(error));
return response?.data; return response?.data || [];
} }

View File

@@ -1,9 +1,21 @@
import { Metar } from './metar.types'; import { Metar } from './metar.types';
export interface Airport { export interface Airport {
name: string;
icao: string; icao: string;
latitude: number; category: string;
longitude: number; full_name: string;
elevation_ft: string;
continent: string;
iso_country: string;
iso_region: string;
municipality: string;
gps_code: string;
iata_code: string;
local_code: string;
point: {
x: number;
y: number;
srid: number;
};
metar?: Metar; metar?: Metar;
} }

View File

@@ -6,5 +6,5 @@ export async function getMetars(airports: Airport[]): Promise<Metar[]> {
const stationICAOs: string = airports.map((airport) => airport.icao).join(','); const stationICAOs: string = airports.map((airport) => airport.icao).join(',');
const url = `http://localhost:5000/metars/${stationICAOs}`; const url = `http://localhost:5000/metars/${stationICAOs}`;
const response = await axios.get(url).catch((error) => console.error(error)); const response = await axios.get(url).catch((error) => console.error(error));
return response?.data; return response?.data || [];
} }