Added airport data to map

This commit is contained in:
2025-04-10 18:08:06 -04:00
parent 0f8edc192b
commit 05c49dee4c
20 changed files with 653 additions and 78 deletions

40
ui/src/lib/airport.ts Normal file
View File

@@ -0,0 +1,40 @@
import { Airport, AirportCategory, Bounds, GetAirportsResponse } from '@lib/airport.types.ts';
import { getRequest } from '@lib/index.ts';
export async function getAirport({ icao }: { icao: string }): Promise<Airport> {
const response = await getRequest(`airports/${icao}`);
return response?.json() || {};
}
interface GetAirportsParameters {
icaos?: string[];
name?: string;
categories?: AirportCategory[];
bounds?: Bounds;
metars?: boolean;
page?: number;
limit?: number;
}
export async function getAirports({
icaos,
name,
categories,
bounds,
metars = false,
limit = 1000,
page = 1
}: GetAirportsParameters): Promise<GetAirportsResponse> {
const response = await getRequest('airports', {
bounds: bounds
? `${bounds?.northEast.lat},${bounds?.northEast.lon},${bounds?.southWest.lat},${bounds?.southWest.lon}`
: undefined,
categories: categories ?? undefined,
icaos: icaos ?? undefined,
name: name ?? undefined,
metars: metars ?? undefined,
limit,
page
});
return response?.json() || { data: [] };
}

View File

@@ -0,0 +1,93 @@
import { Metar } from './metar.types';
export enum AirportCategory {
SMALL = 'small_airport',
MEDIUM = 'medium_airport',
LARGE = 'large_airport',
HELIPORT = 'heliport',
BALLOONPORT = 'balloon_port',
CLOSED = 'closed',
SEAPLANE = 'seaplane_base',
UNKNOWN = 'unknown'
}
export function airportCategoryToText(category: AirportCategory): string {
switch (category) {
case AirportCategory.SMALL:
return 'Small';
case AirportCategory.MEDIUM:
return 'Medium';
case AirportCategory.LARGE:
return 'Large';
case AirportCategory.HELIPORT:
return 'Helipad';
case AirportCategory.CLOSED:
return 'Closed';
case AirportCategory.SEAPLANE:
return 'Seaplane Base';
case AirportCategory.BALLOONPORT:
return 'Balloon Port';
default:
return 'Unknown';
}
}
export enum AirportOrderField {
ICAO = 'icao',
NAME = 'name',
CATEGORY = 'category',
CONTINENT = 'continent',
ISO_COUNTRY = 'iso_country',
ISO_REGION = 'iso_region',
MUNICIPALITY = 'municipality'
}
export interface Bounds {
northEast: Coordinate;
southWest: Coordinate;
}
export interface Coordinate {
lat: number;
lon: number;
}
export interface Airport {
icao: string;
iata: string;
local: string;
name: string;
category: AirportCategory;
iso_country: string;
iso_region: string;
municipality: string;
elevation_ft: number;
latitude: number;
longitude: number;
has_tower: boolean;
has_beacon: boolean;
has_metar: boolean;
public: boolean;
runways: Runway[];
frequencies: Frequency[];
latest_metar?: Metar;
}
export interface Runway {
id: string;
length_ft: number;
width_ft: number;
surface: string;
}
export interface Frequency {
id: string;
frequency_mhz: number;
}
export interface GetAirportsResponse {
data: Airport[];
limit: number;
page: number;
total: number;
}

72
ui/src/lib/index.ts Normal file
View File

@@ -0,0 +1,72 @@
// const serviceHost = process.env.SERVICE_HOST || 'http://localhost';
// const servicePort = process.env.SERVICE_PORT || 5000;'
// const baseURL = `${serviceHost}:${servicePort}`;
const baseUrl = 'http://localhost:5000';
export async function getRequest(endpoint: string, params: Record<string, any> = {}): Promise<Response> {
Object.keys(params).forEach((key) => params[key] === undefined && delete params[key]);
const urlParams = new URLSearchParams(params);
const url = urlParams && urlParams.size > 0 ? `${baseUrl}/${endpoint}?${urlParams}` : `${baseUrl}/${endpoint}`;
return await fetch(url, {
method: 'GET',
credentials: 'include'
});
}
interface PostOptions {
headers?: Record<string, any>;
type?: 'json' | 'form';
}
export async function postRequest(endpoint: string, body?: any, options?: PostOptions): Promise<Response> {
const url = `${baseUrl}/${endpoint}`;
let response;
if (body && (!options?.type || options.type === 'json')) {
response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(body)
});
} else {
response = await fetch(url, {
method: 'POST',
credentials: 'include',
body
});
}
return response;
}
export async function putRequest(endpoint: string, body?: any, options?: PostOptions): Promise<Response> {
const url = `${baseUrl}/${endpoint}`;
let response;
if (body && (!options?.type || options.type === 'json')) {
response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(body)
});
} else {
response = await fetch(url, {
method: 'PUT',
credentials: 'include',
body
});
}
return response;
}
export async function deleteRequest(endpoint: string): Promise<Response> {
const url = `${baseUrl}/${endpoint}`;
const response = await fetch(url, {
method: 'DELETE',
credentials: 'include'
});
return response;
}

43
ui/src/lib/metar.types.ts Normal file
View File

@@ -0,0 +1,43 @@
export interface SkyCondition {
sky_cover: string;
cloud_base_ft_agl: number;
}
export interface QualityControlFlags {
auto: 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;
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;
weather_phenomena: string[];
sky_condition: SkyCondition[];
flight_category: 'VFR' | 'MVFR' | 'LIFR' | 'IFR' | 'UNKN';
three_hr_pressure_tendency_mb: number;
max_t_c: number;
min_t_c: number;
precip_in: number;
}