diff --git a/weather-service/src/airports/model.rs b/weather-service/src/airports/model.rs index 71054ea..6e29a71 100644 --- a/weather-service/src/airports/model.rs +++ b/weather-service/src/airports/model.rs @@ -40,8 +40,20 @@ pub struct Airports { pub longitude: f64, } +#[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 { - pub fn find_all(limit: i32, page: i32) -> Result, CustomError> { + pub fn find_all(bounds: Bounds, limit: i32, page: i32) -> Result, CustomError> { let conn = db::connection()?; let airports = airports::table .limit(limit as i64) diff --git a/weather-service/src/airports/routes.rs b/weather-service/src/airports/routes.rs index ef3124c..61c98d4 100644 --- a/weather-service/src/airports/routes.rs +++ b/weather-service/src/airports/routes.rs @@ -1,4 +1,4 @@ -use crate::airports::{Airport, Airports}; +use crate::airports::{Airport, Airports, Bounds, LatLng}; use actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest}; use log::error; use serde::{Serialize, Deserialize}; @@ -6,64 +6,74 @@ use serde_json::json; #[derive(Debug, Serialize, Deserialize)] struct FindAllParams { - limit: i32, - page: i32 + ne_lat: f32, + ne_lon: f32, + sw_lat: f32, + sw_lon: f32, + limit: i32, + page: i32 } + + #[get("/airports")] async fn find_all(req: HttpRequest) -> HttpResponse { - let params = web::Query::::from_query(req.query_string()).unwrap(); - match web::block(move || Airports::find_all(params.limit, params.page)).await.unwrap() { - Ok(a) => HttpResponse::Ok().json(a), - Err(err) => { - error!("{}", err); - HttpResponse::InternalServerError().finish() - } + let params = web::Query::::from_query(req.query_string()).unwrap(); + let bounds = Bounds { + north_east: LatLng { lat: params.ne_lat, lon: params.ne_lon }, + south_west: LatLng { lat: params.sw_lat, lon: params.sw_lon } + }; + match web::block(move || Airports::find_all(bounds, params.limit, params.page)).await.unwrap() { + Ok(a) => HttpResponse::Ok().json(a), + Err(err) => { + error!("{}", err); + HttpResponse::InternalServerError().finish() } + } } #[get("/airports/{id}")] async fn find(id: web::Path) -> HttpResponse { - match Airports::find(id.into_inner()) { - Ok(a) => HttpResponse::Ok().json(a), - Err(err) => { - error!("{}", err); - HttpResponse::InternalServerError().finish() - } + match Airports::find(id.into_inner()) { + Ok(a) => HttpResponse::Ok().json(a), + Err(err) => { + error!("{}", err); + HttpResponse::InternalServerError().finish() } + } } #[post("/airports")] async fn create(airport: web::Json) -> HttpResponse { - match Airports::create(airport.into_inner()) { - Ok(a) => HttpResponse::Ok().json(a), - Err(err) => { - error!("{}", err); - HttpResponse::InternalServerError().finish() - } + match Airports::create(airport.into_inner()) { + Ok(a) => HttpResponse::Ok().json(a), + Err(err) => { + error!("{}", err); + HttpResponse::InternalServerError().finish() } + } } #[put("/airports/{id}")] async fn update(id: web::Path, airport: web::Json) -> HttpResponse { - match Airports::update(id.into_inner(), airport.into_inner()) { - Ok(a) => HttpResponse::Ok().json(a), - Err(err) => { - error!("{}", err); - HttpResponse::InternalServerError().finish() - } + match Airports::update(id.into_inner(), airport.into_inner()) { + Ok(a) => HttpResponse::Ok().json(a), + Err(err) => { + error!("{}", err); + HttpResponse::InternalServerError().finish() } + } } #[delete("/airports/{id}")] async fn delete(id: web::Path) -> HttpResponse { - match Airports::delete(id.into_inner()) { - Ok(a) => HttpResponse::Ok().json(json!({ "deleted": a })), - Err(err) => { - error!("{}", err); - HttpResponse::InternalServerError().finish() - } + match Airports::delete(id.into_inner()) { + Ok(a) => HttpResponse::Ok().json(json!({ "deleted": a })), + Err(err) => { + error!("{}", err); + HttpResponse::InternalServerError().finish() } + } } pub fn init_routes(config: &mut web::ServiceConfig) { diff --git a/weather-ui/src/components/Metar.tsx b/weather-ui/src/components/Metar.tsx index cfa60e9..3ff5333 100644 --- a/weather-ui/src/components/Metar.tsx +++ b/weather-ui/src/components/Metar.tsx @@ -1,7 +1,4 @@ -import { Airport } from '@/js/api/airport.types'; -import { getAirports } from '@/js/api/airport'; import { Metar } from '@/js/api/metar.types'; -import { getMetars } from '@/js/api/metar'; import dynamic from 'next/dynamic'; export default async function Metar() { @@ -16,16 +13,5 @@ export default async function Metar() { ssr: false }); - let airports: Airport[] = []; - - async function update() { - airports = await getAirports({ limit: 10, page: 1 }); - const metars = await getMetars(airports); - for (let i = 0; i < metars.length; i++) { - airports[i].metar = metars[i]; - } - } - await update(); - - return ; + return ; } diff --git a/weather-ui/src/components/MetarMap.tsx b/weather-ui/src/components/MetarMap.tsx index c963a72..56f818f 100644 --- a/weather-ui/src/components/MetarMap.tsx +++ b/weather-ui/src/components/MetarMap.tsx @@ -1,17 +1,17 @@ 'use client'; +import { getAirports } from '@/js/api/airport'; import { Airport } from '@/js/api/airport.types'; +import { getMetars } from '@/js/api/metar'; import { Metar } from '@/js/api/metar.types'; import { faArrowsSpin, faLocationArrow, faLocationPin } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { DivIcon } from 'leaflet'; +import { DivIcon, LatLngBounds } from 'leaflet'; import Link from 'next/link'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import ReactDOMServer from 'react-dom/server'; -import { MapContainer, Marker, Popup, TileLayer, Tooltip, useMapEvents } from 'react-leaflet'; - -export default function Map({ airportString }: { airportString: string }) { - const [airports] = useState(JSON.parse(airportString)); +import { MapContainer, Marker, Popup, TileLayer, Tooltip, useMap, useMapEvents } from 'react-leaflet'; +export default function Map() { return ( - + ); } -function MapTiles({ airports }: { airports: Airport[] }) { +function MapTiles() { + const [airports, setAirports] = useState([]); const [zoomLevel, setZoomLevel] = useState(8); // const [dragging, setDragging] = useState(false); - // const [center, setCenter] = useState([50, 10.5]); + const map = useMap(); + const mapEvents = useMapEvents({ zoomend: () => { setZoomLevel(mapEvents.getZoom()); }, - moveend: () => { - console.log(mapEvents.getBounds()); + movestart: () => { + // setDragging(true); + }, + moveend: async () => { + // setDragging(false); + await updateAirports(mapEvents.getBounds()); } - // mouseup: () => { - // setCenter([mapEvents.getCenter().lat, mapEvents.getCenter().lng]); - // } }); + async function updateAirports(bounds: LatLngBounds) { + const ne = bounds.getNorthEast(); + const sw = bounds.getSouthWest(); + const _airports = await getAirports({ + ne_lat: ne.lat, + ne_lon: ne.lng, + sw_lat: sw.lat, + sw_lon: sw.lng, + limit: 10, + page: 1 + }); + const metars = await getMetars(_airports); + for (let i = 0; i < metars.length; i++) { + _airports[i].metar = metars[i]; + } + setAirports(_airports); + } + function metarBGColor(metar: Metar | undefined) { if (metar?.flight_category == 'VFR') { return 'bg-emerald-600'; @@ -109,6 +130,10 @@ function MapTiles({ airports }: { airports: Airport[] }) { }); } + useEffect(() => { + updateAirports(map.getBounds()); + }, []); + return ( <> {airports.map((airport) => ( - <> - - - {airport.icao} - - -
- -

- {airport.icao} {airport.name} -

- -
-

{airport.metar?.raw_text}

-
- - {airport.metar?.flight_category ? airport.metar?.flight_category : 'UNKN'} + + + {airport.icao} + + +
+ +

+ {airport.icao} {airport.name} +

+ +
+

{airport.metar?.raw_text}

+
+ + {airport.metar?.flight_category ? airport.metar?.flight_category : 'UNKN'} + +
+ + {airport.metar && airport.metar.wind_dir_degrees && Number(airport.metar.wind_dir_degrees) > 0 ? ( + + ) : ( + <> + )} + {airport.metar && airport.metar.wind_dir_degrees && airport.metar.wind_dir_degrees == 'VRB' ? ( + + ) : ( + <> + )} + {airport.metar?.wind_speed_kt != undefined && airport.metar?.wind_speed_kt > 0 + ? `${airport.metar?.wind_speed_kt} KT` + : 'CALM'} -
- - {airport.metar && airport.metar.wind_dir_degrees && Number(airport.metar.wind_dir_degrees) > 0 ? ( - - ) : ( - <> - )} - {airport.metar && airport.metar.wind_dir_degrees && airport.metar.wind_dir_degrees == 'VRB' ? ( - - ) : ( - <> - )} - {airport.metar?.wind_speed_kt != undefined && airport.metar?.wind_speed_kt > 0 - ? `${airport.metar?.wind_speed_kt} KT` - : 'CALM'} - -
- - - +
+
+
))} ); diff --git a/weather-ui/src/js/api/airport.ts b/weather-ui/src/js/api/airport.ts index 0b95cb0..07839d5 100644 --- a/weather-ui/src/js/api/airport.ts +++ b/weather-ui/src/js/api/airport.ts @@ -2,8 +2,12 @@ import axios from 'axios'; import { Airport } from './airport.types'; interface GetAirportsProps { - page: number; - limit: number; + ne_lat: number; + ne_lon: number; + sw_lat: number; + sw_lon: number; + page?: number; + limit?: number; } interface GetAirportProps { @@ -15,9 +19,16 @@ export async function getAirport({ icao }: GetAirportProps) { return response?.data; } -export async function getAirports({ limit = 10, page = 1 }: GetAirportsProps): Promise { +export async function getAirports({ + ne_lat, + ne_lon, + sw_lat, + sw_lon, + limit = 10, + page = 1 +}: GetAirportsProps): Promise { const response = await axios - .get(`http://localhost:5000/airports`, { params: { page: page, limit: limit } }) + .get(`http://localhost:5000/airports`, { params: { ne_lat, ne_lon, sw_lat, sw_lon, page, limit } }) .catch((error) => console.error(error)); return response?.data; }