Added live metar data updates
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
};
|
||||||
},
|
},
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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 || [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 || [];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user