Working on passing bounds to database airports query

This commit is contained in:
2023-09-09 22:46:20 -04:00
parent c9699c16c3
commit 612d168c05
5 changed files with 155 additions and 113 deletions

View File

@@ -40,8 +40,20 @@ pub struct Airports {
pub longitude: f64, 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 { impl Airports {
pub fn find_all(limit: i32, page: i32) -> Result<Vec<Self>, CustomError> { pub fn find_all(bounds: Bounds, limit: i32, page: i32) -> Result<Vec<Self>, CustomError> {
let conn = db::connection()?; let conn = db::connection()?;
let airports = airports::table let airports = airports::table
.limit(limit as i64) .limit(limit as i64)

View File

@@ -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 actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest};
use log::error; use log::error;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
@@ -6,64 +6,74 @@ use serde_json::json;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct FindAllParams { struct FindAllParams {
limit: i32, ne_lat: f32,
page: i32 ne_lon: f32,
sw_lat: f32,
sw_lon: f32,
limit: i32,
page: i32
} }
#[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();
match web::block(move || Airports::find_all(params.limit, params.page)).await.unwrap() { let bounds = Bounds {
Ok(a) => HttpResponse::Ok().json(a), north_east: LatLng { lat: params.ne_lat, lon: params.ne_lon },
Err(err) => { south_west: LatLng { lat: params.sw_lat, lon: params.sw_lon }
error!("{}", err); };
HttpResponse::InternalServerError().finish() 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}")] #[get("/airports/{id}")]
async fn find(id: web::Path<i32>) -> HttpResponse { async fn find(id: web::Path<i32>) -> HttpResponse {
match Airports::find(id.into_inner()) { match Airports::find(id.into_inner()) {
Ok(a) => HttpResponse::Ok().json(a), Ok(a) => HttpResponse::Ok().json(a),
Err(err) => { Err(err) => {
error!("{}", err); error!("{}", err);
HttpResponse::InternalServerError().finish() HttpResponse::InternalServerError().finish()
}
} }
}
} }
#[post("/airports")] #[post("/airports")]
async fn create(airport: web::Json<Airport>) -> HttpResponse { async fn create(airport: web::Json<Airport>) -> HttpResponse {
match Airports::create(airport.into_inner()) { match Airports::create(airport.into_inner()) {
Ok(a) => HttpResponse::Ok().json(a), Ok(a) => HttpResponse::Ok().json(a),
Err(err) => { Err(err) => {
error!("{}", err); error!("{}", err);
HttpResponse::InternalServerError().finish() HttpResponse::InternalServerError().finish()
}
} }
}
} }
#[put("/airports/{id}")] #[put("/airports/{id}")]
async fn update(id: web::Path<i32>, airport: web::Json<Airport>) -> HttpResponse { async fn update(id: web::Path<i32>, airport: web::Json<Airport>) -> HttpResponse {
match Airports::update(id.into_inner(), airport.into_inner()) { match Airports::update(id.into_inner(), airport.into_inner()) {
Ok(a) => HttpResponse::Ok().json(a), Ok(a) => HttpResponse::Ok().json(a),
Err(err) => { Err(err) => {
error!("{}", err); error!("{}", err);
HttpResponse::InternalServerError().finish() HttpResponse::InternalServerError().finish()
}
} }
}
} }
#[delete("/airports/{id}")] #[delete("/airports/{id}")]
async fn delete(id: web::Path<i32>) -> HttpResponse { async fn delete(id: web::Path<i32>) -> HttpResponse {
match Airports::delete(id.into_inner()) { match Airports::delete(id.into_inner()) {
Ok(a) => HttpResponse::Ok().json(json!({ "deleted": a })), Ok(a) => HttpResponse::Ok().json(json!({ "deleted": a })),
Err(err) => { Err(err) => {
error!("{}", err); error!("{}", err);
HttpResponse::InternalServerError().finish() HttpResponse::InternalServerError().finish()
}
} }
}
} }
pub fn init_routes(config: &mut web::ServiceConfig) { pub fn init_routes(config: &mut web::ServiceConfig) {

View File

@@ -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 { Metar } from '@/js/api/metar.types';
import { getMetars } from '@/js/api/metar';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
export default async function Metar() { export default async function Metar() {
@@ -16,16 +13,5 @@ export default async function Metar() {
ssr: false ssr: false
}); });
let airports: Airport[] = []; return <Map />;
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 <Map airportString={JSON.stringify(airports)} />;
} }

View File

@@ -1,17 +1,17 @@
'use client'; 'use client';
import { getAirports } from '@/js/api/airport';
import { Airport } from '@/js/api/airport.types'; import { Airport } from '@/js/api/airport.types';
import { getMetars } from '@/js/api/metar';
import { Metar } from '@/js/api/metar.types'; import { Metar } from '@/js/api/metar.types';
import { faArrowsSpin, faLocationArrow, faLocationPin } from '@fortawesome/free-solid-svg-icons'; import { faArrowsSpin, faLocationArrow, faLocationPin } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { DivIcon } from 'leaflet'; import { DivIcon, LatLngBounds } from 'leaflet';
import Link from 'next/link'; import Link from 'next/link';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import ReactDOMServer from 'react-dom/server'; import ReactDOMServer from 'react-dom/server';
import { MapContainer, Marker, Popup, TileLayer, Tooltip, useMapEvents } from 'react-leaflet'; import { MapContainer, Marker, Popup, TileLayer, Tooltip, useMap, useMapEvents } from 'react-leaflet';
export default function Map({ airportString }: { airportString: string }) {
const [airports] = useState<Airport[]>(JSON.parse(airportString));
export default function Map() {
return ( return (
<MapContainer <MapContainer
center={[38.7209, -77.5133]} center={[38.7209, -77.5133]}
@@ -23,27 +23,48 @@ export default function Map({ airportString }: { airportString: string }) {
className='overflow-y-hidden overflow-x-hidden' className='overflow-y-hidden overflow-x-hidden'
attributionControl={false} attributionControl={false}
> >
<MapTiles airports={airports} /> <MapTiles />
</MapContainer> </MapContainer>
); );
} }
function MapTiles({ airports }: { airports: Airport[] }) { function MapTiles() {
const [airports, setAirports] = useState<Airport[]>([]);
const [zoomLevel, setZoomLevel] = useState(8); const [zoomLevel, setZoomLevel] = useState(8);
// const [dragging, setDragging] = useState(false); // const [dragging, setDragging] = useState(false);
// const [center, setCenter] = useState([50, 10.5]); const map = useMap();
const mapEvents = useMapEvents({ const mapEvents = useMapEvents({
zoomend: () => { zoomend: () => {
setZoomLevel(mapEvents.getZoom()); setZoomLevel(mapEvents.getZoom());
}, },
moveend: () => { movestart: () => {
console.log(mapEvents.getBounds()); // 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) { function metarBGColor(metar: Metar | undefined) {
if (metar?.flight_category == 'VFR') { if (metar?.flight_category == 'VFR') {
return 'bg-emerald-600'; return 'bg-emerald-600';
@@ -109,6 +130,10 @@ function MapTiles({ airports }: { airports: Airport[] }) {
}); });
} }
useEffect(() => {
updateAirports(map.getBounds());
}, []);
return ( return (
<> <>
<TileLayer <TileLayer
@@ -116,54 +141,52 @@ function MapTiles({ airports }: { airports: Airport[] }) {
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.latitude, airport.longitude]} 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}
{airport.icao} </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.name} </h1>
</h1> </Link>
</Link> <hr />
<hr /> <p className='text-sm font-medium text-gray-500'>{airport.metar?.raw_text}</p>
<p className='text-sm font-medium text-gray-500'>{airport.metar?.raw_text}</p> <div className='mt-2 flex'>
<div className='mt-2 flex'> <span
<span className={`flex inline-block text-sm text-white ${metarBGColor(
className={`flex inline-block text-sm text-white ${metarBGColor( airport.metar
airport.metar )} py-2 px-4 rounded-full`}
)} py-2 px-4 rounded-full`} >
> {airport.metar?.flight_category ? airport.metar?.flight_category : 'UNKN'}
{airport.metar?.flight_category ? airport.metar?.flight_category : 'UNKN'} </span>
<div className='flex inline-block px-2'>
<span className={`text-sm text-black ${windColor(airport.metar)} py-2 px-2 rounded-full`}>
{airport.metar && airport.metar.wind_dir_degrees && Number(airport.metar.wind_dir_degrees) > 0 ? (
<FontAwesomeIcon
className='pr-1'
icon={faLocationArrow}
style={{ rotate: `${-45 + 180 + Number(airport.metar.wind_dir_degrees)}deg` }}
/>
) : (
<></>
)}
{airport.metar && airport.metar.wind_dir_degrees && airport.metar.wind_dir_degrees == 'VRB' ? (
<FontAwesomeIcon className='pr-1' icon={faArrowsSpin} />
) : (
<></>
)}
{airport.metar?.wind_speed_kt != undefined && airport.metar?.wind_speed_kt > 0
? `${airport.metar?.wind_speed_kt} KT`
: 'CALM'}
</span> </span>
<div className='flex inline-block px-2'>
<span className={`text-sm text-black ${windColor(airport.metar)} py-2 px-2 rounded-full`}>
{airport.metar && airport.metar.wind_dir_degrees && Number(airport.metar.wind_dir_degrees) > 0 ? (
<FontAwesomeIcon
className='pr-1'
icon={faLocationArrow}
style={{ rotate: `${-45 + 180 + Number(airport.metar.wind_dir_degrees)}deg` }}
/>
) : (
<></>
)}
{airport.metar && airport.metar.wind_dir_degrees && airport.metar.wind_dir_degrees == 'VRB' ? (
<FontAwesomeIcon className='pr-1' icon={faArrowsSpin} />
) : (
<></>
)}
{airport.metar?.wind_speed_kt != undefined && airport.metar?.wind_speed_kt > 0
? `${airport.metar?.wind_speed_kt} KT`
: 'CALM'}
</span>
</div>
</div> </div>
</div> </div>
</Popup> </div>
</Marker> </Popup>
</> </Marker>
))} ))}
</> </>
); );

View File

@@ -2,8 +2,12 @@ import axios from 'axios';
import { Airport } from './airport.types'; import { Airport } from './airport.types';
interface GetAirportsProps { interface GetAirportsProps {
page: number; ne_lat: number;
limit: number; ne_lon: number;
sw_lat: number;
sw_lon: number;
page?: number;
limit?: number;
} }
interface GetAirportProps { interface GetAirportProps {
@@ -15,9 +19,16 @@ export async function getAirport({ icao }: GetAirportProps) {
return response?.data; return response?.data;
} }
export async function getAirports({ limit = 10, page = 1 }: GetAirportsProps): Promise<Airport[]> { export async function getAirports({
ne_lat,
ne_lon,
sw_lat,
sw_lon,
limit = 10,
page = 1
}: GetAirportsProps): Promise<Airport[]> {
const response = await axios 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)); .catch((error) => console.error(error));
return response?.data; return response?.data;
} }