import Link from 'next/link'; import { HBars, Histogram } from '@/components/charts/charts'; import { Unavailable } from '@/components/ui/unavailable'; import { fmtInt, num } from '@/lib/format'; import { ORBIT_CLASS_COLORS, routes } from '@/lib/site'; import type { BucketStat, DensityPayload } from '@/lib/types'; import { GroupedHistogram } from './local-charts'; import { ChartBlock, Note, TwoCol } from './shared'; const BUCKET_ORDER = ['0-200', '200-300', '300-400', '400-500', '500-600', '600-700', '700-800', '800-900', '900-1000', '1000-2000', 'MEO', 'GEO', 'HEO', 'OTHER', 'UNKNOWN']; export function sortBuckets(buckets: T[]): T[] { const idx = (b: string) => { const i = BUCKET_ORDER.indexOf(b); return i === -1 ? BUCKET_ORDER.length : i; }; return [...buckets].sort((a, b) => idx(a.bucket) - idx(b.bucket)); } function bucketLabel(b: string): string { if (/^\d+-\d+$/.test(b)) return `LEO ${b} km`; return b === 'UNKNOWN' ? 'Unclassified' : b; } function bucketColor(b: string): string { if (/^\d+-\d+$/.test(b)) return ORBIT_CLASS_COLORS.LEO ?? 'var(--leo)'; return ORBIT_CLASS_COLORS[b] ?? 'var(--other)'; } /** Orbital density section (informational object counts). `density` null → Unavailable. */ export function StatsDensity({ density, fallbackBuckets }: { density: DensityPayload | null; fallbackBuckets?: BucketStat[] }) { const buckets = sortBuckets(density?.buckets ?? fallbackBuckets ?? []); const bucketBars = buckets.map((b) => ({ label: bucketLabel(b.bucket), value: num(b.objects) ?? 0, color: bucketColor(b.bucket) })); const leo = density ? [...density.leo_profile_25km].map((r) => ({ bin: num(r.alt_km) ?? 0, values: [num(r.active_payloads) ?? 0, num(r.debris) ?? 0] })).sort((a, b) => a.bin - b.bin) : []; const incl = density ? [...density.inclination_profile_5deg].map((r) => ({ bin: num(r.incl_deg) ?? 0, value: num(r.objects) ?? 0 })).sort((a, b) => a.bin - b.bin) : []; const inclActive = density ? [...density.inclination_profile_5deg].map((r) => ({ bin: num(r.incl_deg) ?? 0, values: [num(r.active_payloads) ?? 0, (num(r.objects) ?? 0) - (num(r.active_payloads) ?? 0)] })).sort((a, b) => a.bin - b.bin) : []; return (
{density?.methodology ?? 'Objects on orbit by perigee altitude. Informational density, not a collision-risk metric.'} SatelliteIndex publishes object counts only — never collision probabilities.{' '} Methodology {bucketBars.length ? : } {buckets.map((b) => ( ))}
Band Objects Active payloads Debris Rocket bodies
{bucketLabel(b.bucket)} {fmtInt(b.objects)} {fmtInt(b.active_payloads)} {fmtInt(b.debris)} {fmtInt(b.rocket_bodies)}
{leo.length ? : } {incl.length ? : } {inclActive.length > 0 && }
Debris growth by launch year, fragmentation sources and the largest tracked objects are on /debris. The most populated 50 km shells are listed under{' '} Rankings → LEO shells.
); }