Updated metar checking

This commit is contained in:
2025-04-20 13:14:40 -04:00
parent 20d5bf26de
commit 4a200b3f94
9 changed files with 301 additions and 212 deletions

View File

@@ -36,8 +36,8 @@ export interface LayerInfo {
const layerMap: LayerInfo[] = [
{ url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', name: 'Open Street Map', markerOutline: 'black' },
{ url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', name: 'Carto Light', markerOutline: 'black' },
{ url: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', name: 'Carto Dark', markerOutline: 'white'},
]
{ url: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', name: 'Carto Dark', markerOutline: 'white' }
];
// const dark1Url = 'https://maps.rainviewer.com/data/v3/5/10/11.pbf';
// const dark2Url = 'https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer/tile/2/0/3.pbf';
const defaultZoom = 6;
@@ -137,7 +137,7 @@ function App() {
function BaseLayerChangeHandler() {
useMapEvents({
baselayerchange: (e) => {
const index = layerMap.findIndex(layer => layer.name === e.name);
const index = layerMap.findIndex((layer) => layer.name === e.name);
setSelectedLayerIndex(`${index}`);
Cookies.set('selectedLayer', `${index}`, { expires: 7 });
setSelectedLayer(layerMap[index]);

View File

@@ -1,7 +1,7 @@
import { Badge, Box, Divider, Drawer, Group, Tabs, TabsList, Text, Tooltip } from '@mantine/core';
import { Airport, AirportCategory } from '@lib/airport.types.ts';
import { getMarkerColor, Metar } from '@lib/metar.types.ts';
import { forwardRef, useEffect, useState } from 'react';
import { forwardRef, ReactNode, useEffect, useState } from 'react';
import { getMetars } from '@lib/metar.ts';
import { useMediaQuery } from '@mantine/hooks';
@@ -56,7 +56,9 @@ export default function AirportDrawer({
>
<Drawer.Content>
<Drawer.Header>
<Drawer.Title><Text size={'xl'}>{airport.name}</Text></Drawer.Title>
<Drawer.Title>
<Text size={'xl'}>{airport.name}</Text>
</Drawer.Title>
<Drawer.CloseButton />
</Drawer.Header>
<Drawer.Body>
@@ -72,7 +74,7 @@ export default function AirportDrawer({
padding: '10px'
}}
>
<Badge size="lg" color={metarColor}>
<Badge size='lg' color={metarColor}>
{metar.flight_category}
</Badge>
{/*<Text style={{ color: metarColor }}>{metar.flight_category}</Text>*/}
@@ -86,10 +88,12 @@ export default function AirportDrawer({
<Tabs.Tab value={'info'}>Info</Tabs.Tab>
<Tabs.Tab value={'weather'}>Weather</Tabs.Tab>
</TabsList>
<Tabs.Panel value={'info'}><AirportInfo airport={airport}/></Tabs.Panel>
{airport.latest_metar && (
<Tabs.Panel value={'weather'}><WeatherInfo metar={airport.latest_metar} /></Tabs.Panel>
)}
<Tabs.Panel value={'info'}>
<AirportInfo airport={airport} />
</Tabs.Panel>
<Tabs.Panel value={'weather'}>
<WeatherInfo metar={airport.latest_metar} />
</Tabs.Panel>
</Tabs>
</Box>
</Drawer.Body>
@@ -98,53 +102,62 @@ export default function AirportDrawer({
);
}
function AirportInfo({ airport }: { airport: Airport }) {
return (<div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
padding: 'var(--mantine-spacing-sm) var(--mantine-spacing-lg)',
borderTop: '1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5))'
}}>
<div>
<Text size="xs" color="dimmed">
ICAO
</Text>
<Text fw={500} size="sm">
{airport.icao}
</Text>
</div>
<div>
<Text size="xs" color="dimmed">
IATA
</Text>
<Text fw={500} size="sm">
{airport.iata}
</Text>
</div>
<div>
<Text size="xs" color="dimmed">
Local
</Text>
<Text fw={500} size="sm">
{airport.local}
</Text>
</div>
<div>
<Text size="xs" color="dimmed">
Category
</Text>
<Text fw={500} size="sm">
{airportCategoryToText(airport.category)}
</Text>
</div>
function AirportInfoSlot({ title, value, units }: { title: string; value: string | number; units?: string }) {
return (
<div>
<Text size='xs' color='dimmed'>
{title}
</Text>
<Text fw={500} size='sm'>
{value}
{units}
</Text>
</div>
<Divider />
</div>);
);
}
function WeatherInfo({ metar }: { metar: Metar }) {
return <>{metar.raw_text}</>
function AirportInfoRow({ children }: { children: ReactNode }) {
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignContent: 'center',
padding: 'var(--mantine-spacing-sm) var(--mantine-spacing-lg)',
borderTop: '1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5))'
}}
>
{children}
</div>
);
}
function AirportInfo({ airport }: { airport: Airport }) {
return (
<div>
<AirportInfoRow>
<AirportInfoSlot title={'ICAO'} value={airport.icao} />
<AirportInfoSlot title={'IATA'} value={airport.iata} />
<AirportInfoSlot title={'LOCAL'} value={airport.local} />
<AirportInfoSlot title={'Category'} value={airportCategoryToText(airport.category)} />
</AirportInfoRow>
<AirportInfoRow>
<AirportInfoSlot title={'Latitude'} value={airport.latitude} units={'°'} />
<AirportInfoSlot title={'Longitude'} value={airport.longitude} units={'°'} />
<AirportInfoSlot title={'Elevation'} value={airport.elevation_ft} units={' ft'} />
Zoom To
</AirportInfoRow>
<Divider />
</div>
);
}
function WeatherInfo({ metar }: { metar?: Metar }) {
if (metar) {
return <>{metar.raw_text}</>;
} else {
return <>No METAR observation available</>;
}
}
function airportCategoryToText(category: AirportCategory): string {
@@ -168,20 +181,26 @@ function airportCategoryToText(category: AirportCategory): string {
}
}
const TimeSince = forwardRef<HTMLParagraphElement, { date: string }>(
({ date }, ref) => {
const inputDate = new Date(date);
// @ts-expect-error doing arithmetic with dates
const seconds = Math.floor((new Date() - inputDate) / 1000);
const TimeSince = forwardRef<HTMLParagraphElement, { date: string }>(({ date }, ref) => {
const inputDate = new Date(date);
// @ts-expect-error doing arithmetic with dates
const seconds = Math.floor((new Date() - inputDate) / 1000);
if (seconds < 60) {
const content = seconds + (seconds === 1 ? " second ago" : " seconds ago");
return <Text ref={ref} style={{ userSelect: 'none' }} >{content}</Text>;
} else {
const minutes = Math.floor(seconds / 60);
const content = minutes + (minutes === 1 ? " minute ago" : " minutes ago");
// If more than 60 minutes have passed, set the text color to yellow
return <Text ref={ref} style={{ color: minutes >= 60 ? '#fca903' : undefined, userSelect: 'none' }}>{content}</Text>;
}
if (seconds < 60) {
const content = seconds + (seconds === 1 ? ' second ago' : ' seconds ago');
return (
<Text ref={ref} style={{ userSelect: 'none' }}>
{content}
</Text>
);
} else {
const minutes = Math.floor(seconds / 60);
const content = minutes + (minutes === 1 ? ' minute ago' : ' minutes ago');
// If more than 60 minutes have passed, set the text color to yellow
return (
<Text ref={ref} style={{ color: minutes >= 60 ? '#fca903' : undefined, userSelect: 'none' }}>
{content}
</Text>
);
}
);
});

View File

@@ -9,7 +9,7 @@ export default function AirportMarker({
index,
airport,
setAirport,
selectedLayer,
selectedLayer
}: {
index: number;
airport: Airport;
@@ -28,7 +28,7 @@ export default function AirportMarker({
eventHandlers={{
click: () => setAirport(airport),
mouseover: () => markerRef.current?.openPopup(),
mouseout: () => markerRef.current?.closePopup(),
mouseout: () => markerRef.current?.closePopup()
}}
>
<Popup closeButton={false} autoPan={false} interactive={false}>

View File

@@ -155,9 +155,7 @@ export function Header() {
)}
</Group>
)}
{isMobile && (
<Burger opened={opened} onClick={toggle} size='sm' />
)}
{isMobile && <Burger opened={opened} onClick={toggle} size='sm' />}
</Group>
</header>
</Box>