spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { HBars, StackedBars } from '@/components/charts/charts';4import { ChartBlock, Disclaimer, InlineRank, Note, StatGrid, TwoCol } from '@/components/stats/shared';5import { TypeBadge } from '@/components/ui/badges';6import { Container, PageHeader, Section, Stat } from '@/components/ui/section';7import { Unavailable } from '@/components/ui/unavailable';8import { api, safe } from '@/lib/api';9import { fmt2, fmtAgo, fmtDate, fmtInt, fmtKm, num } from '@/lib/format';10import { routes, SITE_NAME, SITE_URL } from '@/lib/site';1112export const revalidate = 900;1314const TITLE = 'Space debris — catalogued fragments, rocket bodies and their sources';15const DESC = 'Tracked space debris on orbit: totals, debris by country, altitude distribution, fragmentation events with the most fragments still on orbit, debris growth by launch year and the largest tracked objects. Catalogued object counts only.';1617export async function generateMetadata(): Promise<Metadata> {18 const url = `${SITE_URL}${routes.debris()}`;19 return {20 title: TITLE,21 description: DESC,22 alternates: { canonical: url },23 openGraph: { title: `${TITLE} | ${SITE_NAME}`, description: DESC, url, type: 'website', siteName: SITE_NAME },24 twitter: { card: 'summary_large_image', title: `${TITLE} | ${SITE_NAME}`, description: DESC },25 };26}2728export default async function DebrisPage() {29 const res = await safe(api.debris());30 const d = res?.data ?? null;3132 const byCountry = d ? [...d.by_country].sort((a, b) => (num(b.debris) ?? 0) - (num(a.debris) ?? 0)) : [];33 const debrisBars = byCountry.filter((c) => (num(c.debris) ?? 0) > 0).slice(0, 12).map((c) => ({ label: c.name, value: num(c.debris) ?? 0, color: 'var(--series-4)' }));34 const rbBars = [...byCountry].sort((a, b) => (num(b.rocket_bodies) ?? 0) - (num(a.rocket_bodies) ?? 0)).filter((c) => (num(c.rocket_bodies) ?? 0) > 0).slice(0, 12).map((c) => ({ label: c.name, value: num(c.rocket_bodies) ?? 0, color: 'var(--series-2)' }));3536 const byAlt = d ? [...d.by_altitude].sort((a, b) => (num(a.alt_km) ?? 0) - (num(b.alt_km) ?? 0)).map((r) => ({ x: String(num(r.alt_km) ?? 0), debris: num(r.debris) ?? 0, rocket_bodies: num(r.rocket_bodies) ?? 0 })) : [];37 const growth = d ? [...d.debris_growth].sort((a, b) => a.year - b.year).map((r) => {38 const cat = num(r.debris_catalogued) ?? 0;39 const still = num(r.debris_still_on_orbit) ?? 0;40 return { x: r.year, still_on_orbit: still, decayed: Math.max(0, cat - still) };41 }) : [];42 const decays = d ? [...d.decays_by_year].sort((a, b) => a.year - b.year).map((r) => ({ x: r.year, payloads: num(r.payloads) ?? 0, debris: num(r.debris) ?? 0, rocket_bodies: num(r.rocket_bodies) ?? 0 })) : [];43 const largest = d ? [...d.largest_objects].sort((a, b) => (num(b.rcs_m2) ?? 0) - (num(a.rcs_m2) ?? 0)) : [];4445 return (46 <Container>47 <PageHeader eyebrow="Debris" title="Space debris on orbit" lede="Catalogued fragments and spent rocket bodies currently tracked, where they come from and how the population has evolved since 1957. Counts come from the SATCAT as normalised by SatelliteIndex.">48 {res && <p className="mono mt-4 text-xs text-ink-3">Generated {fmtAgo(res.meta.generated_at)}</p>}49 </PageHeader>5051 {!d ? (52 <div className="py-6">53 <Unavailable what="Debris statistics" />54 </div>55 ) : (56 <>57 <div className="pb-8">58 <Disclaimer text={d.disclaimer} />59 </div>6061 <Section eyebrow="Totals" title="Non-operational objects on orbit" className="pt-0 md:pt-0">62 <StatGrid cols={4}>63 <Stat label="Debris" value={fmtInt(d.totals.debris)} accent hint="Catalogued fragments on orbit" />64 <Stat label="Rocket bodies" value={fmtInt(d.totals.rocket_bodies)} hint="Spent stages on orbit" />65 <Stat label="Unknown type" value={fmtInt(d.totals.unknown)} hint="Unclassified objects on orbit" />66 <Stat label="Inactive payloads" value={fmtInt(d.totals.inactive_payloads)} hint="Non-operational satellites on orbit" />67 </StatGrid>68 </Section>6970 <Section eyebrow="By country" title="Who the debris is attributed to" action={{ href: routes.rankings('countries-debris'), label: 'Full ranking' }}>71 <TwoCol>72 <ChartBlock title="Debris on orbit by country" hint="SATCAT owner code → country">73 {debrisBars.length ? <HBars data={debrisBars} /> : <Unavailable what="Debris by country" />}74 <ul className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs">75 {byCountry.slice(0, 12).map((c) => (76 <li key={c.code}>77 <Link href={routes.country(c.slug)} className="link">{c.name}</Link>78 </li>79 ))}80 </ul>81 </ChartBlock>82 <ChartBlock title="Rocket bodies on orbit by country">{rbBars.length ? <HBars data={rbBars} /> : <Unavailable what="Rocket bodies by country" />}</ChartBlock>83 </TwoCol>84 </Section>8586 <Section eyebrow="Altitude" title="Where debris and rocket bodies orbit">87 <ChartBlock title="Debris and rocket bodies by perigee altitude" hint="50 km bins" derived>88 <StackedBars data={byAlt} keys={['debris', 'rocket_bodies']} labels={{ debris: 'Debris', rocket_bodies: 'Rocket bodies' }} title="Debris and rocket bodies on orbit by perigee altitude, 50 km bins (km)" height={220} xTicks={10} />89 <Note className="mt-2">x-axis = lower edge of each 50 km perigee bin (km). Objects without a current element set are not placed. Informational density only.</Note>90 </ChartBlock>91 </Section>9293 <Section eyebrow="Sources" title="Launches with the most fragments still on orbit" action={{ href: routes.launches(), label: 'All launches' }}>94 {d.by_launch.length === 0 ? (95 <Unavailable what="Fragmentation sources" />96 ) : (97 <table className="data-table stack text-sm">98 <thead>99 <tr>100 <th>Launch</th>101 <th>Date</th>102 <th>Site</th>103 <th>Owners</th>104 <th className="num">Debris on orbit</th>105 </tr>106 </thead>107 <tbody>108 {d.by_launch.map((l, i) => (109 <tr key={l.cospar_launch_id}>110 <td className="primary">111 <InlineRank n={i + 1} />112 <Link href={routes.launch(l.cospar_launch_id)} className="link font-medium">{l.primary_name ?? l.cospar_launch_id}</Link>113 <span className="mono ml-2 text-xs text-ink-3">{l.cospar_launch_id}</span>114 </td>115 <td data-label="Date" className="mono text-xs">{fmtDate(l.launch_date)}</td>116 <td data-label="Site" className="text-ink-2">{l.site_name ?? '—'}</td>117 <td data-label="Owners" className="mono text-xs">{l.owner_codes?.join(', ') || '—'}</td>118 <td data-label="Debris on orbit" className="num tnum font-medium">{fmtInt(l.debris_on_orbit)}</td>119 </tr>120 ))}121 </tbody>122 </table>123 )}124 <Note className="mt-3">Fragments are attributed to the COSPAR launch of their parent object. Owner codes are SATCAT designators (e.g. PRC, CIS, US).</Note>125 </Section>126127 <Section eyebrow="History" title="Debris growth and decay">128 <TwoCol>129 <ChartBlock title="Debris catalogued by launch year" hint="Still on orbit vs decayed">130 <StackedBars data={growth} keys={['still_on_orbit', 'decayed']} labels={{ still_on_orbit: 'Still on orbit', decayed: 'Decayed' }} title="Debris catalogued by launch year of the parent object: still on orbit vs decayed" height={220} xTicks={7} />131 <Note className="mt-2">Decayed = catalogued − still on orbit (derived from the two published series). Year = launch year of the parent object, not the fragmentation date.</Note>132 </ChartBlock>133 <ChartBlock title="Decays per year by object type">134 <StackedBars data={decays} keys={['payloads', 'debris', 'rocket_bodies']} labels={{ payloads: 'Payloads', debris: 'Debris', rocket_bodies: 'Rocket bodies' }} title="Objects decayed per year by type" height={220} xTicks={7} />135 <Note className="mt-2">136 Published SATCAT decay dates. Recent reentries are listed on <Link href={routes.reentries()} className="text-accent hover:underline">/reentries</Link>.137 </Note>138 </ChartBlock>139 </TwoCol>140 </Section>141142 <Section eyebrow="Largest" title="Largest tracked debris and rocket bodies" action={{ href: routes.satellites('object_type=DEBRIS,ROCKET_BODY&on_orbit=true'), label: 'Browse all' }}>143 {largest.length === 0 ? (144 <Unavailable what="Largest objects" />145 ) : (146 <table className="data-table stack text-sm">147 <thead>148 <tr>149 <th>Object</th>150 <th>Type</th>151 <th className="num">RCS (m²)</th>152 <th className="num">Perigee</th>153 <th className="num">Apogee</th>154 <th>Launched</th>155 <th>Country</th>156 </tr>157 </thead>158 <tbody>159 {largest.map((o, i) => (160 <tr key={o.id}>161 <td className="primary">162 <InlineRank n={i + 1} />163 <Link href={routes.satellite(o.slug)} className="link font-medium">{o.name}</Link>164 {o.norad_id && <span className="mono ml-2 text-xs text-ink-3">#{o.norad_id}</span>}165 </td>166 <td data-label="Type"><TypeBadge type={o.object_type} /></td>167 <td data-label="RCS (m²)" className="num tnum">{fmt2(o.rcs_m2)}</td>168 <td data-label="Perigee" className="num tnum">{fmtKm(o.perigee_km)}</td>169 <td data-label="Apogee" className="num tnum">{fmtKm(o.apogee_km)}</td>170 <td data-label="Launched" className="mono text-xs">{fmtDate(o.launch_date)}</td>171 <td data-label="Country" className="mono text-xs">{o.country_code ?? '—'}</td>172 </tr>173 ))}174 </tbody>175 </table>176 )}177 <Note className="mt-3">RCS = radar cross-section as published in the SATCAT (m²); it is a size proxy, not mass. {d.disclaimer}</Note>178 </Section>179 </>180 )}181 </Container>182 );183}184