Fixing loading in docker environment, updated markers
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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} />;
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -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]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}*/}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user