Make airport markers minimal and de-cluttered for global airport density (#5)
* Initial plan * chore: outline plan for global airport dataset update Co-authored-by: kewonit <108450560+kewonit@users.noreply.github.com> * feat: expand airport dataset to 9k+ global airports Co-authored-by: kewonit <108450560+kewonit@users.noreply.github.com> * feat: make airport dots subtle and resilient Co-authored-by: kewonit <108450560+kewonit@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kewonit <108450560+kewonit@users.noreply.github.com>
This commit is contained in:
@ -15,18 +15,34 @@ type AirportLayerProps = {
|
|||||||
isDark: boolean;
|
isDark: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isValidCoordinates(
|
||||||
|
coordinates: readonly [number, number],
|
||||||
|
): coordinates is [number, number] {
|
||||||
|
const [lng, lat] = coordinates;
|
||||||
|
return (
|
||||||
|
Number.isFinite(lng) &&
|
||||||
|
Number.isFinite(lat) &&
|
||||||
|
lng >= -180 &&
|
||||||
|
lng <= 180 &&
|
||||||
|
lat >= -90 &&
|
||||||
|
lat <= 90
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const airportGeoJson: GeoJSON.FeatureCollection = {
|
const airportGeoJson: GeoJSON.FeatureCollection = {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: AIRPORTS.map((a) => ({
|
features: AIRPORTS.filter((a) => isValidCoordinates([a.lng, a.lat])).map(
|
||||||
type: "Feature" as const,
|
(a) => ({
|
||||||
geometry: { type: "Point" as const, coordinates: [a.lng, a.lat] },
|
type: "Feature" as const,
|
||||||
properties: {
|
geometry: { type: "Point" as const, coordinates: [a.lng, a.lat] },
|
||||||
iata: a.iata,
|
properties: {
|
||||||
name: a.name,
|
iata: a.iata,
|
||||||
city: a.city,
|
name: a.name,
|
||||||
country: a.country,
|
city: a.city,
|
||||||
},
|
country: a.country,
|
||||||
})),
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const LAYER_CSS = `
|
const LAYER_CSS = `
|
||||||
@ -75,8 +91,9 @@ export function AirportLayer({
|
|||||||
injectCSS();
|
injectCSS();
|
||||||
const m = map;
|
const m = map;
|
||||||
|
|
||||||
const dotColor = isDark ? "rgba(74,222,128,0.6)" : "rgba(22,163,74,0.55)";
|
const dotColor = isDark
|
||||||
const strokeColor = isDark ? "rgba(74,222,128,0.8)" : "rgba(22,163,74,0.7)";
|
? "rgba(167,243,208,0.28)"
|
||||||
|
: "rgba(15,118,110,0.22)";
|
||||||
|
|
||||||
function addSourceAndLayers() {
|
function addSourceAndLayers() {
|
||||||
if (m.getSource(SOURCE_ID)) return;
|
if (m.getSource(SOURCE_ID)) return;
|
||||||
@ -89,21 +106,29 @@ export function AirportLayer({
|
|||||||
source: SOURCE_ID,
|
source: SOURCE_ID,
|
||||||
paint: {
|
paint: {
|
||||||
"circle-radius": [
|
"circle-radius": [
|
||||||
|
"step",
|
||||||
|
["zoom"],
|
||||||
|
0.55,
|
||||||
|
6,
|
||||||
|
0.8,
|
||||||
|
10,
|
||||||
|
1.05,
|
||||||
|
14,
|
||||||
|
1.35,
|
||||||
|
],
|
||||||
|
"circle-color": dotColor,
|
||||||
|
"circle-opacity": [
|
||||||
"interpolate",
|
"interpolate",
|
||||||
["linear"],
|
["linear"],
|
||||||
["zoom"],
|
["zoom"],
|
||||||
3,
|
|
||||||
2,
|
2,
|
||||||
6,
|
0.14,
|
||||||
3,
|
8,
|
||||||
10,
|
0.22,
|
||||||
4.5,
|
|
||||||
14,
|
14,
|
||||||
7,
|
0.34,
|
||||||
],
|
],
|
||||||
"circle-color": dotColor,
|
"circle-stroke-width": 0,
|
||||||
"circle-stroke-width": 1,
|
|
||||||
"circle-stroke-color": strokeColor,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -127,11 +152,12 @@ export function AirportLayer({
|
|||||||
m.getCanvas().style.cursor = "pointer";
|
m.getCanvas().style.cursor = "pointer";
|
||||||
const f = e.features?.[0];
|
const f = e.features?.[0];
|
||||||
if (f?.properties) {
|
if (f?.properties) {
|
||||||
|
const iata = String(f.properties.iata ?? "").toUpperCase();
|
||||||
|
const city = String(f.properties.city ?? "");
|
||||||
|
if (!iata) return;
|
||||||
popup
|
popup
|
||||||
.setLngLat(e.lngLat)
|
.setLngLat(e.lngLat)
|
||||||
.setHTML(
|
.setText(city ? `${iata} · ${city}` : iata)
|
||||||
`<strong>${f.properties.iata}</strong> · ${f.properties.city}`,
|
|
||||||
)
|
|
||||||
.addTo(m);
|
.addTo(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,8 +173,9 @@ export function AirportLayer({
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const f = e.features?.[0];
|
const f = e.features?.[0];
|
||||||
if (f?.properties?.iata) {
|
const iata = String(f?.properties?.iata ?? "");
|
||||||
const city = resolveCity(f.properties.iata as string);
|
if (iata) {
|
||||||
|
const city = resolveCity(iata);
|
||||||
callbackRef.current(city);
|
callbackRef.current(city);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,6 +210,7 @@ export function AirportLayer({
|
|||||||
'<div class="airport-beacon-ring"></div>' +
|
'<div class="airport-beacon-ring"></div>' +
|
||||||
'<div class="airport-beacon-ring"></div>' +
|
'<div class="airport-beacon-ring"></div>' +
|
||||||
'<div class="airport-beacon-core"></div>';
|
'<div class="airport-beacon-core"></div>';
|
||||||
|
if (!isValidCoordinates(activeCity.coordinates)) return;
|
||||||
|
|
||||||
const marker = new maplibregl.Marker({ element: el })
|
const marker = new maplibregl.Marker({ element: el })
|
||||||
.setLngLat(activeCity.coordinates)
|
.setLngLat(activeCity.coordinates)
|
||||||
|
|||||||
@ -484,7 +484,7 @@ function SearchContent({
|
|||||||
|
|
||||||
{!query && (
|
{!query && (
|
||||||
<p className="px-3 pt-3 pb-1 text-center text-[10px] font-medium text-white/10">
|
<p className="px-3 pt-3 pb-1 text-center text-[10px] font-medium text-white/10">
|
||||||
Search 400+ airports worldwide
|
Search 9,000+ airports worldwide
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
71789
src/lib/airports.ts
71789
src/lib/airports.ts
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user