spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import { WorldMap, type MapMarker } from '@/components/map/world-map';2import { num } from '@/lib/format';3import { routes } from '@/lib/site';4import type { LaunchSiteRow } from '@/lib/types';56/** All launch sites as markers sized by launch count (sqrt scale). Sites without coordinates are listed, not plotted. */7export function SitesMap({ sites, highlight, labelTop = 8 }: { sites: LaunchSiteRow[]; highlight?: string; labelTop?: number }) {8 const plotted = sites.filter((s) => s.latitude !== null && s.longitude !== null);9 const max = Math.max(1, ...plotted.map((s) => num(s.launches) ?? 0));10 const ranked = [...plotted].sort((a, b) => (num(b.launches) ?? 0) - (num(a.launches) ?? 0));11 const labelled = new Set(ranked.slice(0, labelTop).map((s) => s.slug));12 const markers: MapMarker[] = ranked13 .reverse() // draw big ones first so small markers stay clickable on top14 .map((s) => {15 const n = num(s.launches) ?? 0;16 const active = s.slug === highlight;17 return {18 lat: s.latitude!,19 lon: s.longitude!,20 size: Math.max(2.5, 2.5 + Math.sqrt(n / max) * 11),21 color: active ? 'var(--accent)' : n >= max * 0.25 ? 'var(--series-4)' : 'var(--series-2)',22 href: routes.launchSite(s.slug),23 label: active || labelled.has(s.slug) ? s.name.replace(/\s*\(.*\)$/, '') : undefined,24 pulse: active,25 };26 });27 const missing = sites.length - plotted.length;28 return (29 <div>30 <div className="overflow-hidden rounded-lg border border-rule">31 <WorldMap markers={markers} title="Launch sites of the world, sized by number of launches" />32 </div>33 <p className="mt-2 text-2xs text-ink-3">34 Marker area ∝ launches. {plotted.length} sites plotted{missing > 0 ? `; ${missing} site${missing > 1 ? 's' : ''} without coordinates in SATCAT (listed below)` : ''}.35 </p>36 </div>37 );38}39