Fixing loading in docker environment, updated markers

This commit is contained in:
2025-04-11 13:36:59 -04:00
parent 98887d7fef
commit ecd01bd49c
14 changed files with 282 additions and 186 deletions

View File

@@ -23,7 +23,9 @@ L.Icon.Default.mergeOptions({
shadowUrl: markerShadow
});
const tileLayerUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
const openStreetMapUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
// const rainViewerUrl = 'https://tilecache.rainviewer.com/v2/radar/{time}/256/10/290/391/2/1_1.png'
// https://api.rainviewer.com/public/weather-maps.json
const defaultZoom = 6;
const defaultCenter: L.LatLngExpression = [38.944444, -77.455833];
@@ -42,14 +44,14 @@ function App() {
minZoom={3}
maxZoom={19}
maxBounds={[
[-85.06, -180],
[85.06, 180]
[-85.06, -181],
[85.06, 181]
]}
scrollWheelZoom={true}
zoomControl={false}
>
<ZoomControl position={'bottomright'} />
<TileLayer url={tileLayerUrl} />
<TileLayer url={openStreetMapUrl} />
<AirportLayer setAirport={setAirport} />
</MapContainer>
</div>

View File

@@ -31,8 +31,7 @@ export default function AirportLayer({ setAirport }: { setAirport: (airport: Air
getAirports({
bounds: boundsParam,
metars: true,
categories: [AirportCategory.SMALL, AirportCategory.MEDIUM, AirportCategory.LARGE],
limit: 200
categories: [AirportCategory.HELIPORT, AirportCategory.SMALL, AirportCategory.MEDIUM, AirportCategory.LARGE]
})
.then((response) => {
setAirports(response.data);
@@ -53,9 +52,39 @@ export default function AirportLayer({ setAirport }: { setAirport: (airport: Air
}
}, [map]);
const categoryOrder: { [key in AirportCategory]?: number } = {
[AirportCategory.LARGE]: 3,
[AirportCategory.MEDIUM]: 2,
[AirportCategory.SMALL]: 1,
[AirportCategory.HELIPORT]: 0
};
const sortedAirports = airports.slice().sort((a, b) => {
// Compare by airport category first.
const categoryA = categoryOrder[a.category] ?? 4;
const categoryB = categoryOrder[b.category] ?? 4;
if (categoryA !== categoryB) {
return categoryA - categoryB;
}
// Then compare by flight category if available.
// Assuming that latest_metar.flight_category is a string and "UNKN" needs to come last.
const fcA = a.latest_metar?.flight_category ?? 'UNKN';
const fcB = b.latest_metar?.flight_category ?? 'UNKN';
if (fcA === 'UNKN' && fcB !== 'UNKN') return 1;
if (fcB === 'UNKN' && fcA !== 'UNKN') return -1;
// If both flight categories are not "UNKN", do a simple alphabetical comparison.
// (You may wish to customize this logic based on the actual flight category values.)
if (fcA < fcB) return -1;
if (fcA > fcB) return 1;
return 0;
});
return (
<>
{airports.map((airport, index) => {
{sortedAirports.map((airport, index) => {
return <AirportMarker airport={airport} index={index} setAirport={setAirport} />;
})}
</>

View File

@@ -1,6 +1,7 @@
import { Airport } from '@lib/airport.types.ts';
import { Marker } from 'react-leaflet';
import { Airport, AirportCategory } from '@lib/airport.types.ts';
import { Marker, Popup } from 'react-leaflet';
import L from 'leaflet';
import { useRef } from 'react';
export default function AirportMarker({
index,
@@ -11,52 +12,96 @@ export default function AirportMarker({
airport: Airport;
setAirport: (airport: Airport) => void;
}) {
const markerColor = getMarkerColor(airport);
const icon = createCustomIcon(markerColor);
const icon = createCustomIcon(airport);
const markerRef = useRef<L.Marker>(null);
return (
<Marker
key={index}
ref={markerRef}
position={[airport.latitude, airport.longitude]}
icon={icon}
eventHandlers={{
click: () => setAirport(airport)
click: () => setAirport(airport),
mouseover: () => markerRef.current?.openPopup(),
mouseout: () => markerRef.current?.closePopup()
}}
/>
>
<Popup closeButton={false}>
{airport.icao} - {airport.name}
</Popup>
</Marker>
);
}
function getMarkerColor(airport: Airport): string {
if (airport.latest_metar) {
switch (airport.latest_metar.flight_category.toUpperCase()) {
case 'IFR':
return '#ff0100';
case 'LIFR':
return '#7f007f';
case 'MVFR':
return '#00f';
case 'VFR':
return '#018000';
case 'UNKNOWN':
return '#3e3e3e';
default:
return '#3e3e3e';
}
} else {
return '#696969';
function getMarkerColor(flightCategory: 'VFR' | 'MVFR' | 'LIFR' | 'IFR' | 'UNKN'): string {
switch (flightCategory) {
case 'IFR':
return '#ff0100';
case 'LIFR':
return '#7f007f';
case 'MVFR':
return '#00f';
case 'VFR':
return '#018000';
case 'UNKN':
return '#696969';
}
}
function createCustomIcon(color: string): L.DivIcon {
return L.divIcon({
html: `<div style="
background-color: ${color};
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid #fff;
"></div>`,
className: '',
iconSize: [20, 20],
iconAnchor: [10, 10]
});
function createCustomIcon(airport: Airport): L.DivIcon {
if (airport.category === AirportCategory.HELIPORT) {
return L.divIcon({
html: `
<div style="
width: 14px;
height: 14px;
border-radius: 50%;
border: 2px solid black;
background-color: white;
display: flex;
align-items: center;
justify-content: center;">
<span style="color: black; font-size: 8px; font-weight: bold;">H</span>
</div>
`,
className: '',
iconSize: [20, 20],
iconAnchor: [10, 10]
});
} else {
// Default to a filled circle.
const flightCategory = airport.latest_metar?.flight_category || 'UNKN';
const color = getMarkerColor(flightCategory);
if (flightCategory == 'UNKN') {
return L.divIcon({
html: `
<div style="
background-color: ${color};
width: 10px;
height: 10px;
border-radius: 50%;">
</div>
`,
className: '',
iconSize: [20, 20],
iconAnchor: [10, 10]
});
} else {
return L.divIcon({
html: `
<div style="
background-color: ${color};
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid #fff;">
</div>
`,
className: '',
iconSize: [20, 20],
iconAnchor: [10, 10]
});
}
}
}

View File

@@ -137,7 +137,7 @@ export function Header() {
<Group align='center' gap='xs'>
<Burger opened={opened} onClick={toggle} hiddenFrom='xs' size='sm' />
<Avatar src='/logo.svg' alt='logo' />
<Text>FlightLink</Text>
<Text>Aviation Data</Text>
</Group>
{/*<Group gap={5} visibleFrom='xs' className={classes.navGroup}>*/}
{/* {navItems}*/}

View File

@@ -1,7 +1,3 @@
// const protocol = process.env.HTTPD_PROTOCOL || 'http';
// const host = process.env.HTTPD_HOST || 'localhost';
// const port = process.env.HTTPD_PORT || 8080;
// const baseUrl = `${protocol}://${host}:${port}/api`;
const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080/api';
export async function getRequest(endpoint: string, params: Record<string, any> = {}): Promise<Response> {