mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-09 21:02:19 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eb53463a8 | ||
|
|
bfe2e964fb | ||
|
|
a93f2bb4d7 | ||
|
|
fff5d5a479 | ||
|
|
1a06c3e4cf | ||
|
|
8921d87b53 | ||
|
|
9d3d541b66 | ||
|
|
36f99cabac |
No files matched your search
@@ -0,0 +1,79 @@
|
||||
# RPO replay: Cosmos 2612 → ICEYE-X36 (Sep 30 – Oct 21, 2026)
|
||||
|
||||
A 45-second, physics-grounded reconstruction of the Russian military satellite
|
||||
Cosmos 2612 (NORAD 68762) maneuvering to within a few hundred meters of the
|
||||
U.S.-registered ICEYE-X36 SAR satellite (NORAD 59103), rendered in CesiumJS
|
||||
over Esri World Imagery and exported as 16:9 and 9:16 video.
|
||||
|
||||
```sh
|
||||
python3 tools/rpo-replay/model.py # orbit reconstruction -> data.json
|
||||
python3 tools/rpo-replay/build_models.py # procedural GLBs
|
||||
node tools/rpo-replay/render.mjs --format 16x9 --stills 2,24,32 # spot checks
|
||||
node tools/rpo-replay/render.mjs --format 16x9 # all frames (resumable)
|
||||
node tools/rpo-replay/render.mjs --format 16x9 --encode # -> MP4
|
||||
```
|
||||
|
||||
Formats: `16x9` (1920×1080), `9x16` (1080×1920), and `preview` / `preview-v`
|
||||
at half size. Output goes to `output/rpo-replay/<format>/`. Requires
|
||||
`pip install sgp4 numpy scipy pillow`, Playwright's Chromium (SwiftShader, no
|
||||
GPU), and ffmpeg.
|
||||
|
||||
## What is measured and what is modeled
|
||||
|
||||
**ICEYE-X36** is propagated with SGP4 from its public CelesTrak element set
|
||||
(`tles.txt`). X36 did not maneuver in this window.
|
||||
|
||||
**Cosmos 2612** has no public element-set history without an account, so its
|
||||
path is a relative-orbit reconstruction about X36:
|
||||
|
||||
- Quasi-nonsingular relative orbital elements (δa, δλ, δe, δi), advanced
|
||||
with Keplerian along-track drift and the differential J2 node drift. The
|
||||
47 km "high park" is what swings Cosmos 2612's orbit plane into X36's.
|
||||
The model needs a 0.27° initial plane offset; published analysis gives
|
||||
~0.22°.
|
||||
- Burns are impulses through Gauss's variational equations. SMA changes are
|
||||
two half-orbit-apart tangential impulses. Each burn is timed inside the
|
||||
element-set window that KeepTrack, LeoLabs, and Marco Langbroek published.
|
||||
- Solved checkpoints:
|
||||
- Oct 12 overhead pass, then 480 km behind.
|
||||
- ~400 km on Oct 15.
|
||||
- 3 km behind after the Oct 16 braking burn.
|
||||
- 2.0 / 0.99 / 0.72 / 0.60 km on Oct 17.
|
||||
- 5.1 km behind and 140 m higher at 07:38 Oct 18.
|
||||
- 293 km on Oct 19.
|
||||
- Matched SMA, 477–479 km behind, from Oct 21. This matches today's Cosmos
|
||||
2612 element set.
|
||||
- The model gives ~60 m/s total Δv (KeepTrack estimates ~67 m/s). The
|
||||
closest approach comes out at **~325 m, 01:28 UTC Oct 18**, in Earth's
|
||||
shadow. Published estimates range 150–650 m. Langbroek gives ~01:35 UTC.
|
||||
|
||||
The 26 m and 25 m figures some outlets quoted come from element sets taken
|
||||
after the departure burn, and KeepTrack rejects them. This replay does not
|
||||
use them.
|
||||
|
||||
## Rendering conventions
|
||||
|
||||
- Camera and Earth ride with X36 on a slower "backdrop" clock during
|
||||
time-lapse to avoid strobing. Relative motion runs at the on-screen rate.
|
||||
Around closest approach the two clocks are identical, so lighting, shadow,
|
||||
and the following orbital sunrise are true to time.
|
||||
- Up close, the spacecraft are drawn enlarged (the factor is on screen, capped
|
||||
at 150×). At closest approach they are at true scale.
|
||||
- In Earth's shadow the models get a dim blue fill so their shapes stay
|
||||
readable.
|
||||
- ICEYE-X36 is modeled to ICEYE's published Gen-3 dimensions: 3.2 × 0.4 m
|
||||
antenna, ~3.25 m span. No public imagery of Cosmos 2612's design exists, so
|
||||
its model is illustrative.
|
||||
|
||||
## Sources
|
||||
|
||||
- CelesTrak GP element sets for 59103 and 68762.
|
||||
- KeepTrack, "Cosmos 2612 / ICEYE-X36 rendezvous" deep dive: burn windows,
|
||||
separations, SMA offsets.
|
||||
- LeoLabs event report via Via Satellite, Oct 19, 2026.
|
||||
- Marco Langbroek (SatTrackCam Leiden), quoted by Space.com, Aerospace Global
|
||||
News, and TWZ.
|
||||
- Vantor image of the pair, Oct 17 (~1.02 km), published by Axios Oct 22.
|
||||
- Imagery: Esri World Imagery; NASA GIBS VIIRS Black Marble (night side).
|
||||
- Sky: Yale Bright Star Catalogue and ESO/S. Brunier Milky Way (see
|
||||
`src/data/local_data/sky/README.md`).
|
||||
@@ -0,0 +1,399 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Procedural GLB models for the RPO replay.
|
||||
|
||||
ICEYE-X36: ICEYE Gen-3 class SAR microsatellite. Dimensions follow ICEYE's
|
||||
fleet documentation (3.2 m x 0.4 m phased-array antenna, ~3.25 m span,
|
||||
two deployable solar arrays, ~0.7 x 0.5 m bus).
|
||||
|
||||
Cosmos 2612: no public imagery of the design exists. The model is an
|
||||
illustrative small military bus (box body, two three-panel wings, a forward
|
||||
optical sensor and a rear propulsion module) sized like its launch cohort.
|
||||
|
||||
Axes (glTF, Y-up): +X along-track (velocity), +Y zenith, +Z = -orbit normal.
|
||||
"""
|
||||
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import struct
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
|
||||
HERE = __file__.rsplit('/', 1)[0]
|
||||
RNG = np.random.default_rng(7)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Textures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def png_bytes(img):
|
||||
b = io.BytesIO()
|
||||
img.save(b, 'PNG', optimize=True)
|
||||
return b.getvalue()
|
||||
|
||||
|
||||
def solar_texture(cols=8, rows=6, size=(512, 384)):
|
||||
w, h = size
|
||||
img = Image.new('RGB', size, (18, 22, 40))
|
||||
d = ImageDraw.Draw(img)
|
||||
cw, ch = w / cols, h / rows
|
||||
for i in range(cols):
|
||||
for j in range(rows):
|
||||
x0, y0 = i * cw + 3, j * ch + 3
|
||||
x1, y1 = (i + 1) * cw - 3, (j + 1) * ch - 3
|
||||
shade = int(RNG.integers(-6, 6))
|
||||
d.rectangle([x0, y0, x1, y1], fill=(28 + shade, 40 + shade, 92 + shade))
|
||||
# clipped corners like triple-junction cells
|
||||
c = 6
|
||||
for cx, cy in [(x0, y0), (x1, y0), (x0, y1), (x1, y1)]:
|
||||
d.polygon([(cx, cy), (cx + (c if cx == x0 else -c), cy), (cx, cy + (c if cy == y0 else -c))],
|
||||
fill=(18, 22, 40))
|
||||
for k in range(1, 6):
|
||||
yy = y0 + (y1 - y0) * k / 6
|
||||
d.line([x0 + 2, yy, x1 - 2, yy], fill=(70, 82, 130), width=1)
|
||||
d.line([x0, (y0 + y1) / 2, x1, (y0 + y1) / 2], fill=(150, 155, 170), width=2)
|
||||
return img
|
||||
|
||||
|
||||
def mli_texture(base, size=256):
|
||||
n = RNG.normal(0, 1, (size // 8, size // 8))
|
||||
n = np.kron(n, np.ones((8, 8)))
|
||||
img = Image.fromarray(np.uint8(np.clip(128 + n * 30, 0, 255))).filter(ImageFilter.GaussianBlur(3))
|
||||
a = np.asarray(img, dtype=float) / 255
|
||||
rgb = np.stack([np.clip(base[c] * (0.78 + 0.45 * a), 0, 255) for c in range(3)], -1)
|
||||
# seams / tape
|
||||
out = Image.fromarray(np.uint8(rgb))
|
||||
d = ImageDraw.Draw(out)
|
||||
for x in (0, size // 2):
|
||||
d.line([x, 0, x, size], fill=tuple(int(c * 0.65) for c in base), width=2)
|
||||
return out
|
||||
|
||||
|
||||
def mli_normal(size=256):
|
||||
n = RNG.normal(0, 1, (size // 6 + 1, size // 6 + 1))
|
||||
n = np.kron(n, np.ones((6, 6)))[:size, :size]
|
||||
h = np.asarray(Image.fromarray(np.uint8(np.clip(128 + n * 40, 0, 255))).filter(ImageFilter.GaussianBlur(2)),
|
||||
dtype=float) / 255
|
||||
gx = np.roll(h, -1, 1) - np.roll(h, 1, 1)
|
||||
gy = np.roll(h, -1, 0) - np.roll(h, 1, 0)
|
||||
s = 3.0
|
||||
nx, ny, nz = -gx * s, -gy * s, np.ones_like(h)
|
||||
l = np.sqrt(nx ** 2 + ny ** 2 + nz ** 2)
|
||||
rgb = np.stack([(nx / l + 1) / 2, (ny / l + 1) / 2, (nz / l + 1) / 2], -1)
|
||||
return Image.fromarray(np.uint8(rgb * 255))
|
||||
|
||||
|
||||
def sar_texture(size=(1024, 128)):
|
||||
w, h = size
|
||||
img = Image.new('RGB', size, (34, 36, 40))
|
||||
d = ImageDraw.Draw(img)
|
||||
nx, ny = 64, 8
|
||||
for i in range(nx):
|
||||
for j in range(ny):
|
||||
cx, cy = (i + 0.5) * w / nx, (j + 0.5) * h / ny
|
||||
r = 4.5
|
||||
d.rectangle([cx - r, cy - r, cx + r, cy + r], fill=(150, 112, 70))
|
||||
d.rectangle([cx - r + 2, cy - r + 2, cx + r - 2, cy + r - 2], fill=(118, 88, 56))
|
||||
for k in range(1, 5):
|
||||
x = w * k / 5
|
||||
d.line([x, 0, x, h], fill=(10, 10, 12), width=3)
|
||||
return img
|
||||
|
||||
|
||||
def panel_texture(color, size=128, lines=4):
|
||||
img = Image.new('RGB', (size, size), color)
|
||||
d = ImageDraw.Draw(img)
|
||||
for k in range(1, lines):
|
||||
p = size * k / lines
|
||||
d.line([p, 0, p, size], fill=tuple(int(c * 0.8) for c in color), width=1)
|
||||
return img
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Minimal glTF builder
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class Gltf:
|
||||
def __init__(self):
|
||||
self.bin = bytearray()
|
||||
self.j = dict(asset=dict(version='2.0', generator='gods-eye-view rpo-replay'),
|
||||
scenes=[dict(nodes=[0])], scene=0, nodes=[dict(name='root', children=[])],
|
||||
meshes=[], materials=[], accessors=[], bufferViews=[], buffers=[],
|
||||
images=[], textures=[], samplers=[dict(magFilter=9729, minFilter=9987, wrapS=10497, wrapT=10497)])
|
||||
self.prims = []
|
||||
|
||||
def _view(self, data, target=None):
|
||||
while len(self.bin) % 4:
|
||||
self.bin.append(0)
|
||||
off = len(self.bin)
|
||||
self.bin += data
|
||||
v = dict(buffer=0, byteOffset=off, byteLength=len(data))
|
||||
if target:
|
||||
v['target'] = target
|
||||
self.j['bufferViews'].append(v)
|
||||
return len(self.j['bufferViews']) - 1
|
||||
|
||||
def _acc(self, arr, typ, target):
|
||||
arr = np.ascontiguousarray(arr)
|
||||
comp = 5126 if arr.dtype == np.float32 else 5125
|
||||
view = self._view(arr.tobytes(), target)
|
||||
a = dict(bufferView=view, componentType=comp, count=int(arr.shape[0]), type=typ)
|
||||
if typ == 'VEC3' and comp == 5126:
|
||||
a['min'] = arr.min(0).tolist()
|
||||
a['max'] = arr.max(0).tolist()
|
||||
self.j['accessors'].append(a)
|
||||
return len(self.j['accessors']) - 1
|
||||
|
||||
def texture(self, img):
|
||||
view = self._view(png_bytes(img))
|
||||
self.j['images'].append(dict(bufferView=view, mimeType='image/png'))
|
||||
self.j['textures'].append(dict(sampler=0, source=len(self.j['images']) - 1))
|
||||
return len(self.j['textures']) - 1
|
||||
|
||||
def material(self, name, color=(1, 1, 1), metal=0.0, rough=0.6, tex=None, normal=None, emissive=None,
|
||||
double=False):
|
||||
m = dict(name=name, pbrMetallicRoughness=dict(baseColorFactor=[*color, 1.0], metallicFactor=metal,
|
||||
roughnessFactor=rough))
|
||||
if tex is not None:
|
||||
m['pbrMetallicRoughness']['baseColorTexture'] = dict(index=tex)
|
||||
if normal is not None:
|
||||
m['normalTexture'] = dict(index=normal, scale=0.6)
|
||||
if emissive:
|
||||
m['emissiveFactor'] = list(emissive)
|
||||
if double:
|
||||
m['doubleSided'] = True
|
||||
self.j['materials'].append(m)
|
||||
return len(self.j['materials']) - 1
|
||||
|
||||
def add(self, name, pos, nrm, uv, idx, mat, translation=(0, 0, 0), rotation=None):
|
||||
p = dict(attributes=dict(POSITION=self._acc(np.float32(pos), 'VEC3', 34962),
|
||||
NORMAL=self._acc(np.float32(nrm), 'VEC3', 34962),
|
||||
TEXCOORD_0=self._acc(np.float32(uv), 'VEC2', 34962)),
|
||||
indices=self._acc(np.uint32(idx).ravel(), 'SCALAR', 34963), material=mat)
|
||||
self.j['meshes'].append(dict(name=name, primitives=[p]))
|
||||
node = dict(name=name, mesh=len(self.j['meshes']) - 1, translation=list(map(float, translation)))
|
||||
if rotation is not None:
|
||||
node['rotation'] = list(map(float, rotation))
|
||||
self.j['nodes'].append(node)
|
||||
self.j['nodes'][0]['children'].append(len(self.j['nodes']) - 1)
|
||||
|
||||
def save(self, path):
|
||||
while len(self.bin) % 4:
|
||||
self.bin.append(0)
|
||||
self.j['buffers'] = [dict(byteLength=len(self.bin))]
|
||||
js = json.dumps(self.j, separators=(',', ':')).encode()
|
||||
while len(js) % 4:
|
||||
js += b' '
|
||||
out = struct.pack('<III', 0x46546C67, 2, 12 + 8 + len(js) + 8 + len(self.bin))
|
||||
out += struct.pack('<II', len(js), 0x4E4F534A) + js
|
||||
out += struct.pack('<II', len(self.bin), 0x004E4942) + bytes(self.bin)
|
||||
open(path, 'wb').write(out)
|
||||
print(path, f'{len(out)/1024:.0f} KB')
|
||||
|
||||
|
||||
def box(sx, sy, sz, uv_scale=(1, 1)):
|
||||
"""Axis-aligned box centred at origin with per-face UVs."""
|
||||
hx, hy, hz = sx / 2, sy / 2, sz / 2
|
||||
faces = [
|
||||
((1, 0, 0), [(hx, -hy, hz), (hx, -hy, -hz), (hx, hy, -hz), (hx, hy, hz)]),
|
||||
((-1, 0, 0), [(-hx, -hy, -hz), (-hx, -hy, hz), (-hx, hy, hz), (-hx, hy, -hz)]),
|
||||
((0, 1, 0), [(-hx, hy, hz), (hx, hy, hz), (hx, hy, -hz), (-hx, hy, -hz)]),
|
||||
((0, -1, 0), [(-hx, -hy, -hz), (hx, -hy, -hz), (hx, -hy, hz), (-hx, -hy, hz)]),
|
||||
((0, 0, 1), [(-hx, -hy, hz), (hx, -hy, hz), (hx, hy, hz), (-hx, hy, hz)]),
|
||||
((0, 0, -1), [(hx, -hy, -hz), (-hx, -hy, -hz), (-hx, hy, -hz), (hx, hy, -hz)]),
|
||||
]
|
||||
P, N, U, I = [], [], [], []
|
||||
for n, quad in faces:
|
||||
b = len(P)
|
||||
P += quad
|
||||
N += [n] * 4
|
||||
U += [(0, uv_scale[1]), (uv_scale[0], uv_scale[1]), (uv_scale[0], 0), (0, 0)]
|
||||
I += [(b, b + 1, b + 2), (b, b + 2, b + 3)]
|
||||
return P, N, U, I
|
||||
|
||||
|
||||
def cylinder(r0, r1, length, seg=32, axis='x', caps=True):
|
||||
P, N, U, I = [], [], [], []
|
||||
for k in range(seg + 1):
|
||||
a = 2 * math.pi * k / seg
|
||||
c, s = math.cos(a), math.sin(a)
|
||||
slope = (r0 - r1) / length
|
||||
nl = math.sqrt(1 + slope ** 2)
|
||||
P += [(0, r0 * c, r0 * s), (length, r1 * c, r1 * s)]
|
||||
N += [(slope / nl, c / nl, s / nl)] * 2
|
||||
U += [(k / seg, 0), (k / seg, 1)]
|
||||
for k in range(seg):
|
||||
a, b = 2 * k, 2 * k + 2
|
||||
I += [(a, b, a + 1), (a + 1, b, b + 1)]
|
||||
if caps:
|
||||
for x, r, nx in ((0, r0, -1), (length, r1, 1)):
|
||||
if r <= 0:
|
||||
continue
|
||||
c0 = len(P)
|
||||
P.append((x, 0, 0)); N.append((nx, 0, 0)); U.append((0.5, 0.5))
|
||||
for k in range(seg + 1):
|
||||
a = 2 * math.pi * k / seg
|
||||
P.append((x, r * math.cos(a), r * math.sin(a))); N.append((nx, 0, 0))
|
||||
U.append((0.5 + 0.5 * math.cos(a), 0.5 + 0.5 * math.sin(a)))
|
||||
for k in range(seg):
|
||||
tri = (c0, c0 + 1 + k, c0 + 2 + k)
|
||||
I.append(tri if nx > 0 else tri[::-1])
|
||||
P = np.array(P, float)
|
||||
N = np.array(N, float)
|
||||
if axis == 'y':
|
||||
P, N = P[:, [1, 0, 2]] * [1, 1, 1], N[:, [1, 0, 2]]
|
||||
I = [t[::-1] for t in I]
|
||||
elif axis == 'z':
|
||||
P, N = P[:, [1, 2, 0]], N[:, [1, 2, 0]]
|
||||
return P.tolist(), N.tolist(), U, I
|
||||
|
||||
|
||||
def dish(r, depth, seg=32, rings=6):
|
||||
"""Paraboloid dish opening toward +X."""
|
||||
P, N, U, I = [], [], [], []
|
||||
for i in range(rings + 1):
|
||||
rr = r * i / rings
|
||||
x = depth * (rr / r) ** 2
|
||||
for k in range(seg + 1):
|
||||
a = 2 * math.pi * k / seg
|
||||
P.append((x, rr * math.cos(a), rr * math.sin(a)))
|
||||
n = np.array([-1.0, 2 * depth * rr / r ** 2 * math.cos(a), 2 * depth * rr / r ** 2 * math.sin(a)])
|
||||
n = -n / np.linalg.norm(n)
|
||||
N.append(tuple(n))
|
||||
U.append((k / seg, i / rings))
|
||||
for i in range(rings):
|
||||
for k in range(seg):
|
||||
a = i * (seg + 1) + k
|
||||
b = a + seg + 1
|
||||
I += [(a, a + 1, b), (a + 1, b + 1, b)]
|
||||
return P, N, U, I
|
||||
|
||||
|
||||
def quat(axis, deg):
|
||||
axis = np.array(axis, float) / np.linalg.norm(axis)
|
||||
h = math.radians(deg) / 2
|
||||
return [*(axis * math.sin(h)), math.cos(h)]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ICEYE-X36
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_iceye():
|
||||
g = Gltf()
|
||||
t_sar = g.texture(sar_texture())
|
||||
t_sol = g.texture(solar_texture(cols=6, rows=4, size=(384, 256)))
|
||||
t_gold = g.texture(mli_texture((214, 168, 72)))
|
||||
t_n = g.texture(mli_normal())
|
||||
t_rad = g.texture(panel_texture((226, 228, 232)))
|
||||
|
||||
m_sar = g.material('sar-face', tex=t_sar, metal=0.55, rough=0.42)
|
||||
m_back = g.material('antenna-back', color=(0.82, 0.83, 0.86), metal=0.2, rough=0.55, tex=t_rad)
|
||||
m_gold = g.material('mli-gold', tex=t_gold, normal=t_n, metal=0.85, rough=0.32)
|
||||
m_sol = g.material('solar', tex=t_sol, metal=0.4, rough=0.25)
|
||||
m_frame = g.material('frame', color=(0.55, 0.56, 0.6), metal=0.9, rough=0.35)
|
||||
m_black = g.material('black', color=(0.05, 0.05, 0.06), metal=0.1, rough=0.6)
|
||||
m_white = g.material('white', color=(0.9, 0.9, 0.92), metal=0.0, rough=0.5)
|
||||
|
||||
# SAR antenna: five hinged segments, 3.25 m along-track x 0.40 m, face tilted
|
||||
# 25 deg off nadir toward -Z (right-looking).
|
||||
seg_len, gap = 0.638, 0.015
|
||||
tilt = quat((1, 0, 0), -25)
|
||||
for k in range(5):
|
||||
x = (k - 2) * (seg_len + gap)
|
||||
P, N, U, I = box(seg_len, 0.035, 0.40)
|
||||
# face (-Y) UVs map into 1/5 of the antenna texture
|
||||
U = [(u * 0.2 + 0.2 * k, v) for (u, v) in U]
|
||||
P = np.array(P) + [x, 0, 0]
|
||||
g.add(f'sar-seg-{k}', P, N, U, I, m_sar if True else m_back, rotation=tilt)
|
||||
Pb, Nb, Ub, Ib = box(seg_len - 0.04, 0.012, 0.36)
|
||||
g.add(f'sar-back-{k}', np.array(Pb) + [x, 0.022, 0], Nb, Ub, Ib, m_back, rotation=tilt)
|
||||
# Bus behind the antenna
|
||||
P, N, U, I = box(0.62, 0.42, 0.46)
|
||||
g.add('bus', P, N, U, I, m_gold, translation=(0, 0.25, 0.06))
|
||||
P, N, U, I = box(0.6, 0.02, 0.44)
|
||||
g.add('bus-top-radiator', P, N, U, I, m_white, translation=(0, 0.47, 0.06))
|
||||
# Solar wings off the bus, canted toward the sun-side
|
||||
for side in (1, -1):
|
||||
P, N, U, I = box(0.58, 0.02, 0.78)
|
||||
g.add(f'solar-{side}', P, N, U, I, m_sol, translation=(0, 0.42, 0.06 + side * 0.66),
|
||||
rotation=quat((1, 0, 0), side * 14))
|
||||
P, N, U, I = cylinder(0.012, 0.012, 0.16, seg=8, axis='z')
|
||||
g.add(f'hinge-{side}', P, N, U, I, m_frame, translation=(0, 0.42, 0.06 + side * 0.20 - (0.16 if side < 0 else 0)))
|
||||
# Star trackers, X-band downlink horn, GNSS patch
|
||||
for k, z in enumerate((-0.12, 0.12)):
|
||||
P, N, U, I = cylinder(0.035, 0.05, 0.08, seg=16, axis='y')
|
||||
g.add(f'star-tracker-{k}', P, N, U, I, m_black, translation=(-0.2, 0.48, 0.06 + z))
|
||||
P, N, U, I = cylinder(0.05, 0.02, 0.09, seg=16, axis='y')
|
||||
g.add('x-band', P, N, U, I, m_frame, translation=(0.2, 0.04, 0.2), rotation=quat((1, 0, 0), 180))
|
||||
P, N, U, I = box(0.08, 0.012, 0.08)
|
||||
g.add('gnss', P, N, U, I, m_white, translation=(0.18, 0.485, 0.06))
|
||||
g.save(f'{HERE}/iceye-x36.glb')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cosmos 2612 (illustrative)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_cosmos():
|
||||
g = Gltf()
|
||||
t_sol = g.texture(solar_texture(cols=8, rows=6))
|
||||
t_mli = g.texture(mli_texture((178, 182, 186)))
|
||||
t_dark = g.texture(mli_texture((64, 62, 58)))
|
||||
t_n = g.texture(mli_normal())
|
||||
t_rad = g.texture(panel_texture((210, 212, 205)))
|
||||
|
||||
m_mli = g.material('mli-silver', tex=t_mli, normal=t_n, metal=0.9, rough=0.28)
|
||||
m_dark = g.material('mli-dark', tex=t_dark, normal=t_n, metal=0.6, rough=0.4)
|
||||
m_sol = g.material('solar', tex=t_sol, metal=0.4, rough=0.25)
|
||||
m_rad = g.material('radiator', tex=t_rad, metal=0.0, rough=0.55)
|
||||
m_frame = g.material('frame', color=(0.6, 0.6, 0.62), metal=0.9, rough=0.3)
|
||||
m_black = g.material('black', color=(0.03, 0.03, 0.035), metal=0.2, rough=0.4)
|
||||
m_lens = g.material('lens', color=(0.05, 0.08, 0.12), metal=1.0, rough=0.05, emissive=(0.02, 0.05, 0.08))
|
||||
m_nozzle = g.material('nozzle', color=(0.35, 0.3, 0.28), metal=0.95, rough=0.4)
|
||||
|
||||
P, N, U, I = box(0.95, 0.72, 0.72)
|
||||
g.add('bus', P, N, U, I, m_mli)
|
||||
P, N, U, I = box(0.5, 0.74, 0.74)
|
||||
g.add('bus-band', P, N, U, I, m_dark, translation=(-0.12, 0, 0))
|
||||
P, N, U, I = box(0.8, 0.02, 0.6)
|
||||
g.add('radiator-top', P, N, U, I, m_rad, translation=(0.02, 0.375, 0))
|
||||
# Three-panel wings along +/-Z
|
||||
for side in (1, -1):
|
||||
P, N, U, I = cylinder(0.018, 0.018, 0.32, seg=10, axis='z')
|
||||
g.add(f'yoke-{side}', P, N, U, I, m_frame, translation=(0, 0, side * 0.36 - (0.32 if side < 0 else 0)))
|
||||
for k in range(3):
|
||||
P, N, U, I = box(0.62, 0.02, 0.5)
|
||||
z = side * (0.36 + 0.32 + 0.26 + k * 0.52)
|
||||
g.add(f'wing-{side}-{k}', P, N, U, I, m_sol, translation=(0, 0, z), rotation=quat((0, 0, 1), 10))
|
||||
# Forward optical sensor (faces +X: toward a target ahead along-track)
|
||||
P, N, U, I = cylinder(0.13, 0.13, 0.24, seg=32, axis='x')
|
||||
g.add('sensor-barrel', P, N, U, I, m_black, translation=(0.475, 0.12, 0.0))
|
||||
P, N, U, I = cylinder(0.145, 0.16, 0.06, seg=32, axis='x', caps=False)
|
||||
g.add('sensor-hood', P, N, U, I, m_frame, translation=(0.70, 0.12, 0.0))
|
||||
P, N, U, I = cylinder(0.1, 0.1, 0.01, seg=32, axis='x')
|
||||
g.add('sensor-lens', P, N, U, I, m_lens, translation=(0.715, 0.12, 0.0))
|
||||
# Proximity radar / laser rangefinder block
|
||||
P, N, U, I = box(0.12, 0.12, 0.16)
|
||||
g.add('ranging', P, N, U, I, m_dark, translation=(0.53, -0.18, 0.18))
|
||||
# Rear propulsion
|
||||
P, N, U, I = cylinder(0.16, 0.16, 0.12, seg=24, axis='x')
|
||||
g.add('prop-module', P, N, U, I, m_dark, translation=(-0.595, 0, 0))
|
||||
P, N, U, I = cylinder(0.035, 0.09, 0.16, seg=24, axis='x', caps=False)
|
||||
g.add('nozzle', P, N, U, I, m_nozzle, translation=(-0.76, 0, 0), rotation=quat((0, 0, 1), 180))
|
||||
for k, (y, z) in enumerate(((0.3, 0.3), (0.3, -0.3), (-0.3, 0.3), (-0.3, -0.3))):
|
||||
P, N, U, I = cylinder(0.012, 0.025, 0.05, seg=10, axis='x', caps=False)
|
||||
g.add(f'rcs-{k}', P, N, U, I, m_nozzle, translation=(-0.48, y, z), rotation=quat((0, 0, 1), 180))
|
||||
# S-band dish on a short mast (zenith)
|
||||
P, N, U, I = cylinder(0.012, 0.012, 0.22, seg=8, axis='y')
|
||||
g.add('mast', P, N, U, I, m_frame, translation=(-0.25, 0.37, 0.18))
|
||||
P, N, U, I = dish(0.14, 0.05)
|
||||
g.add('dish', P, N, U, I, m_rad, translation=(-0.25, 0.6, 0.18), rotation=quat((0, 0, 1), 90))
|
||||
g.save(f'{HERE}/cosmos-2612.glb')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
build_iceye()
|
||||
build_cosmos()
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"t0":"2026-09-29T12:00:00Z","dt":60,"a_km":6921.372691085778,"a_ref_km":6920.0,"x36":[-3636.5139,-5325.6638,2523.2654,-3776.5096,-5412.4061,2098.9707,-3900.1841,-5475.7572,1665.5797,-4007.0029,-5515.4434,1224.9706,-4096.5051,-5531.2942,779.0531,-4168.3055,-5523.2431,329.7599,-4222.0959,-5491.3278,-120.9621,-4257.647,-5435.6901,-571.1601,-4274.809,-5356.5752,-1018.8846,-4273.5121,-5254.3303,-1462.1974,-4253.7672,-5129.4034,-1899.1809,-4215.6653,-4982.3406,-2327.946,-4159.3769,-4813.7838,-2746.6409,-4085.1516,-4624.4681,-3153.4587,-3993.3163,-4415.2174,-3546.6454,-3884.2738,-4186.9416,-3924.5078,-3758.5011,-3940.6318,-4285.4201,-3616.5466,-3677.3561,-4627.8314,-3459.0281,-3398.2545,-4950.2719,-3286.6295,-3104.5344,-5251.3587,-3100.0982,-2797.4644,-5529.8021,-2900.2412,-2478.3695,-5784.4104,-2687.922,-2148.6252,-6014.0949,-2464.0566,-1809.6512,-6217.8741,-2229.6096,-1462.9058,-6394.8777,-1985.5901,-1109.8796,-6544.3502,-1733.0473,-752.0892,-6665.6532,-1473.0664,-391.0708,-6758.2687,-1206.7638,-28.374,-6821.8005,-935.2824,334.4444,-6855.9762,-659.7873,695.8277,-6860.6479,-381.4604,1054.2244,-6835.7929,-101.4961,1408.0959,-6781.5141,178.9039,1755.9218,-6698.0393,458.5355,2096.2069,-6585.7205,736.1974,2427.4872,-6445.0327,1010.6955,2748.3357,-6276.5723,1280.8486,3057.369,-6081.0542,1545.4928,3353.2529,-5859.3101,1803.4868,3634.7078,-5612.2842,2053.7166,3900.515,-5341.0304,2295.1003,4149.5213,-5046.7077,2526.5929,4380.6447,-4730.5756,2747.1908,4592.8788,-4393.9893,2955.9365,4785.2977,-4038.3938,3151.9223,4957.06,-3665.3179,3334.2952,5107.4132,-3276.3684,3502.2602,5235.6966,-2873.2223,3655.0842,5341.345,-2457.6207,3792.0994,5423.8912,-2031.3603,3912.7064,5482.9686,-1596.2862,4016.377,5518.3126,-1154.2836,4102.6567,5529.7625,-707.2693,4171.1671,5517.2618,-257.1833,4221.6071,5480.8593,194.0199,4253.7552,5420.7085,644.38,4267.4696,5337.0671,1091.94,4262.6897,5230.2959,1534.7546,4239.4358,5100.8572,1970.8995,4197.8093,4949.3128,2398.4794,4137.992,4776.321,2815.6372,4060.2455,4582.6341,3220.5616,3964.9095,4369.0948,3611.4956,3852.4006,4136.6318,3986.7443,3723.21,3886.2564,4344.682,3577.9013,3619.0572,4683.7593,3417.1081,3336.1953,5002.5102,3241.5307,3038.8993,5299.5578,3051.9331,2728.4595,5573.6206,2849.1395,2406.2223,5823.5174,2634.0305,2073.5843,6048.1727,2407.5394,1731.9861,6246.6206,2170.648,1382.9058,6418.0091,1924.3821,1027.8532,6561.6033,1669.8075,668.3627,6676.7885,1408.0247,305.9871,6763.0725,1140.1652,-57.7091,6820.0873,867.3856,-421.1563,6847.5911,590.8636,-782.7863,6845.4685,311.7924,-1141.0393,6813.7315,31.3762,-1494.3698,6752.519,-249.1755,-1841.2539,6662.0964,-528.6522,-2180.1949,6542.8545,-805.8484,-2509.7303,6395.3079,-1079.5677,-2828.4376,6220.0927,-1348.6289,-3134.9406,6017.9643,-1611.8702,-3427.9149,5789.7941,-1868.1546,-3706.0943,5536.5659,-2116.3747,-3968.2755,5259.372,-2355.4576,-4213.324,4959.4084,-2584.3693,-4440.1788,4637.9703,-2802.1194,-4647.857,4296.4462,-3007.7657,-4835.4587,3936.3122,-3200.4179,-5002.1703,3559.1258,-3379.2419,-5147.2689,3166.519,-3543.4636,-5270.1251,2760.1914,-3692.3721,-5370.2064,2341.9027,-3825.3231,-5447.079,1913.465,-3941.7417,-5500.4104,1476.735,-4041.1253,-5529.9706,1033.6053,-4123.0454,-5535.6333,585.9966,-4187.1497,-5517.3764,135.8488,-4233.1638,-5475.2822,-314.8875,-4260.8921,-5409.5368,-764.26,-4270.2191,-5320.4293,-1210.3229,-4261.1091,-5208.3505,-1651.1459,-4233.607,-5073.7909,-2084.8225,-4187.8373,-4917.3385,-2509.4788,-4124.004,-4739.6759,-2923.281,-4042.3891,-4541.577,-3324.4444,-3943.3515,-4323.9038,-3711.24,-3827.325,-4087.6022,-4082.0028,-3694.8164,-3833.6975,-4435.1385,-3546.403,-3563.2902,-4769.1302,-3382.7301,-3277.5506,-5082.5447,-3204.5078,-2977.714,-5374.0386,-3012.508,-2665.0749,-5642.3639,-2807.561,-2340.9818,-5886.3724,-2590.5517,-2006.8309,-6105.0213,-2362.4157,-1664.0604,-6297.3763,-2124.1357,-1314.1445,-6462.6162,-1876.7367,-958.5869,-6600.0355,-1621.2823,-598.9148,-6709.0474,-1358.8697,-236.6723,-6789.1862,-1090.6255,126.586,-6840.1086,-817.701,489.301,-6861.5959,-541.2671,849.9161,-6853.5539,-262.5101,1206.8831,-6816.0141,17.3739,1558.6692,-6749.1331,297.1832,1903.7625,-6653.1924,575.716,2240.6791,-6528.5971,851.7752,2567.9687,-6375.8748,1124.1735,2884.221,-6195.6731,1391.7379,3188.0716,-5988.7576,1653.3154,3478.2079,-5756.0088,1907.7771,3753.3743,-5498.4187,2154.0239,4012.3785,-5217.087,2390.9903,4254.0961,-4913.2165,2617.65,4477.4757,-4588.1085,2833.0198,4681.544,-4243.1576,3036.1641,4865.4097,-3879.8457,3226.1992,5028.2684,-3499.736,3402.2976,5169.4055,-3104.4664,3563.6911,5288.2002,-2695.7423,3709.675,5384.1285,-2275.3296,3839.6111,5456.7654,-1845.0471,3952.9306,5505.7873,-1406.7579,4049.1372,5530.9736,-962.362,4127.8089,5532.2079,-513.7871,4188.6005,5509.4787,-62.9806,4231.2448,5462.8794,388.0994,4255.5543,5392.6084,837.493,4261.4217,5298.9679,1283.2473,4248.8209,5182.3623,1723.4247,4217.8063,5043.2974,2156.1126,4168.5135,4882.3768,2579.431,4101.1579,4700.3001,2991.5414,4016.0339,4497.8592,3390.6552,3913.5135,4275.9347,3775.0411,3794Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"t0":"2026-09-29T12:00:00Z","dt":60,"a_km":6920.86656753336,"x36":[-3625.5189,-5318.3928,2553.1804,-3766.6429,-5406.7449,2129.5637,-3891.4855,-5471.7261,1696.7165,-3999.5064,-5513.0548,1256.5148,-4090.2389,-5530.5527,810.8666,-4163.2919,-5524.1455,361.704,-4218.3513,-5493.863,-89.0263,-4255.1815,-5439.8394,-539.3709,-4273.6267,-5362.3124,-987.3787,-4273.6112,-5261.6219,-1431.1097,-4255.1399,-5138.2088,-1868.6435,-4218.2979,-4992.6125,-2298.0876,-4163.2501,-4825.4685,-2717.5863,-4090.2405,-4637.5056,-3125.3281,-3999.5906,-4429.5422,-3519.5541,-3891.6983,-4202.4827,-3898.5655,-3767.0355,-3957.3135,-4260.7306,-3626.1461,-3695.098,-4604.4922,-3469.6435,-3416.9724,-4928.3736,-3298.2077,-3124.1403,-5230.9851,-3112.5823,-2817.8673,-5511.0297,-2913.5708,-2499.4756,-5767.3082,-2702.0336,-2170.3385,-5998.7243,-2478.884,-1831.874,-6204.2888,-2245.084,-1485.5388,-6383.1236,-2001.6406,-1132.8228,-6534.465,-1749.6012,-775.2418,-6657.667,-1490.0494,-414.3321,-6752.2034,-1224.1004,-51.6435,-6817.6704,-952.8961,311.2668,-6853.7876,-677.601,672.8408,-6860.3993,-399.3967,1031.526,-6837.4753,-119.4774,1385.782,-6785.1109,160.9549,1734.0865,-6703.5265,440.6958,2074.9421,-6593.067,718.5431,2406.882,-6454.2004,993.3019,2728.4768,-6287.5159,1263.7898,3038.3397,-6093.7221,1528.8414,3335.1331,-5873.6438,1787.3137,3617.5742,-5628.2193,2038.0909,3884.4401,-5358.4963,2280.089,4134.5737,-5065.628,2512.2606,4366.8886,-4750.8684,2733.5996,4580.374,-4415.5673,2943.1456,4774.0993,-4061.1646,3139.9881,4947.2182,-3689.1846,3323.2708,5098.9729,-3301.2294,3492.1953,5228.6976,-2898.9722,3646.0251,5335.8216,-2484.1499,3784.0885,5419.8722,-2058.5561,3905.7822,5480.4768,-1624.0326,4010.5739,5517.3649,-1182.4621,4098.0049,5530.3699,-735.7589,4167.6921,5519.4291,-285.8613,4219.3301,5484.5851,165.2776,4252.6923,5425.985,615.6986,4267.6325,5343.8803,1063.4451,4264.0848,5238.6252,1506.572,4242.0646,5110.6759,1943.1542,4201.6682,4960.5876,2371.2956,4143.0722,4789.0125,2789.1375,4066.533,4596.6966,3194.8666,3972.3851,4384.4763,3586.7233,3861.0399,4153.2748,3963.0095,3732.9835,3904.0971,4322.0959,3588.7745,3638.0265,4662.4289,3429.0413,3356.2186,4982.5378,3254.4793,3059.8969,5281.0405,3065.8479,2750.347,5556.6496,2863.9667,2428.9108,5808.1779,2649.7119,2096.9807,6034.543,2424.0126,1755.9934,6234.7722,2187.8466,1407.424,6408.0057,1942.2361,1052.779,6553.5008,1688.2435,693.5905,6670.6345,1426.9663,331.4093,6758.906,1159.5332,-32.2016,6817.9385,887.0984,-395.6736,6847.4811,610.8377,-757.4391,6847.4092,331.9425,-1115.938,6817.7252,51.6159,-1469.6244,6758.5586,-228.9334,-1816.9732,6670.1653,-508.4954,-2156.4859,6552.9263,-785.8643,-2486.6979,6407.3469,-1059.8435,-2806.1839,6234.0537,-1329.2506,-3113.5641,6033.7929,-1592.9226,-3407.5106,5807.4269,-1849.7206,-3686.7526,5555.9306,-2098.535,-3950.0821,5280.3881,-2338.2901,-4196.3592,4981.9873,-2567.9488,-4424.5171,4662.0157,-2786.5173,-4633.5668,4321.8548,-2993.0494,-4822.6018,3962.9741,-3186.6507,-4990.8017,3586.925,-3366.4824,-5137.4364,3195.3341,-3531.7656,-5261.8692,2789.8963,-3681.7841,-5363.5598,2372.3671,-3815.8884,-5442.0668,1944.5552,-3933.498,-5497.0496,1508.3147,-4034.1043,-5528.2705,1065.5362,-4117.273,-5535.5952,618.1391,-4182.6458,-5518.9936,168.063,-4229.9422,-5478.5403,-282.7416,-4258.9606,-5414.4137,-732.321,-4269.5793,-5326.8954,-1178.7281,-4261.7566,-5216.369,-1620.03,-4235.5314,-5083.318,-2054.3173,-4191.0225,-4928.3236,-2479.7125,-4128.4281,-4752.0621,-2894.3776,-4048.0249,-4555.3015,-3296.5229,-3950.1663,-4338.8981,-3684.4144,-3835.2812,-4103.7925,-4056.3812,-3703.8716,-3851.0055,-4410.8229,-3556.5102,-3581.633,-4746.216,-3393.8381,-3296.8416,-5061.1204,-3216.5615,-2997.8631,-5354.1857,-3025.4486,-2685.989,-5624.1563,-2821.3264,-2362.5652,-5869.8766,-2605.0768,-2028.9858,-6090.2958,-2377.6327,-1686.6876,-6284.472,-2139.9745,-1337.1435,-6451.5758,-1893.1251,-981.8563,-6590.8939,-1638.1463,-622.3528,-6701.8315,-1376.134,-260.1771,-6783.9148,-1108.2137,103.1156,-6836.7927,-835.5356,465.9655,-6860.2385,-559.2705,826.815,-6854.1503,-280.6041,1184.1145,-6818.552,-0.7327,1536.3291,-6753.5926,279.1418,1881.9451,-6659.5462,557.817,2219.4761,-6536.8107,834.095,2547.4691,-6385.9067,1106.7874,2864.5109,-6207.4749,1374.7199,3169.2339,-6002.2742,1636.738,3460.3218,-5771.1787,1891.7111,3736.5156,-5515.1741,2138.5378,3996.6187,-5235.3541,2376.1508,4239.5027,-4932.9158,2603.521,4464.1117,-4609.155,2819.6626,4669.4677,-4265.461,3023.6373,4854.6748,-3903.3108,3214.5582,5018.9232,-3524.2629,3391.5945,5161.4933,-3129.9508,3553.9745,5281.7589,-2722.076,3700.9899,5379.1903,-2302.4007,3831.9985,5453.3567,-1872.7403,3946.4277,5503.9286,-1434.9551,4043.7766,5530.6795,-990.9423,4123.6191,5533.4868,-542.6278,4185.6053,5512.3327,-91.9572,4229.4634,5467.3044,359.1127,4255.0011,5398.5938,808.6228,4262.1061,5306.4965,1254.6202,4250.7472,5191.4108,1695.1675,4220.9739,5053.8358,2128.3511,4172.9164,4894.3688,2552.2897,4106.785,4713.7032,2965.1433,4022.8689,4512.6245,3365.1207,3921.5348,4292.0072,3750.4879,3803.2253,4052.811,4Line truncated
|
||||
@@ -0,0 +1,408 @@
|
||||
// HUD for the RPO replay (v4), sized for phone playback: a 16:9 video in a
|
||||
// feed plays ~390 px wide (≈0.2×), so body copy is ≥34 px at 1080p and nothing
|
||||
// that must be read is under ~24 px. Animation is a function of video time `s`,
|
||||
// except label placement, which carries state between consecutive frames so
|
||||
// labels hold their side (hysteresis) and ease instead of jumping.
|
||||
|
||||
import { createInset } from './inset.js';
|
||||
|
||||
export function createHud(ctx) {
|
||||
const { D, W, H, U, PORTRAIT, sec, rate, sOfTau, CA_T, VANTOR_T, BURNS, CUTS, SHOTS, shotAt, DUR, project, relAt } = ctx;
|
||||
const $ = (q) => document.querySelector(q);
|
||||
// Element sets can't resolve tens of metres: quote the pass to the nearest 50 m.
|
||||
const CA_M = Math.round(D.ca.m / 50) * 50;
|
||||
const END = SHOTS.find((x) => x.id === 'end').s0;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Story
|
||||
// -------------------------------------------------------------------------
|
||||
const span = (id) => {
|
||||
const x = SHOTS.find((sh) => sh.id === id);
|
||||
return [x.s0, x.s1];
|
||||
};
|
||||
const CHAPTERS = [
|
||||
{ key: 'climb', label: 'Climb', s0: span('climb')[0], s1: span('lap')[1] },
|
||||
{ key: 'drop', label: 'Drop in', s0: span('drop')[0], s1: span('close')[1] },
|
||||
{ key: 'dance', label: 'Close in', s0: span('dance')[0], s1: span('approach')[1] },
|
||||
{ key: 'closest', label: 'Closest', s0: span('ca')[0], s1: span('sunrise')[1] },
|
||||
{ key: 'leave', label: 'Leave', s0: span('leave')[0], s1: span('leave')[1] },
|
||||
];
|
||||
const cap = (id, title, body, extra = {}) => ({ s0: span(id)[0], s1: span(id)[1], title, body, ...extra });
|
||||
const CAPTIONS = [
|
||||
cap('hook', `A Russian military satellite came within <span class="c-cos nw">~${CA_M} m</span> of a U.S. one`,
|
||||
'Rebuilt from public tracking data.', { hook: true, s0: -1 }),
|
||||
cap('climb', 'Climb higher to fall behind',
|
||||
'Two burns lift <b class="c-cos">Cosmos 2612</b> <b>47 km</b>. A higher orbit is a longer lap, so it falls behind.'),
|
||||
cap('lap', 'Lapped, twice',
|
||||
'Losing <b>~6,700 km a day</b>, it gets lapped by <b class="c-x36">X36</b> on Oct 5 and Oct 12. Earth\'s bulge, plus a 4 m/s nudge, swing the orbits into line.'),
|
||||
cap('drop', 'Drop into the same orbit',
|
||||
'Oct 12: from <b>54 km</b> directly overhead it dives into X36\'s orbit, landing <b>~500 km behind</b>.'),
|
||||
cap('close', 'Speed up, then brake',
|
||||
'A 2 km dip makes it faster, closing <b>~350 km a day</b>. Then it brakes <b>~10 km</b> behind.'),
|
||||
cap('dance', 'The dance',
|
||||
'Every orbit is a day and a night. Each one, Cosmos traces a <b>~1.5 km</b> loop as it edges in from 8 to 3 km behind.'),
|
||||
cap('dance-ff', 'Flatten the loops',
|
||||
'A tiny burn puts Cosmos almost exactly in X36\'s path. It creeps in <b>~3 km a day</b>.'),
|
||||
cap('approach', 'Closing in, slowly',
|
||||
'Creeping in at <b>a few centimetres a second</b>, while both fly at <b>27,000 km/h</b>.'),
|
||||
cap('ca', `Closest pass: <span class="c-cos nw">~${CA_M} m</span>`,
|
||||
'In Earth\'s shadow, drifting slower than a tortoise. LeoLabs radar: <b>~500 m</b>. Estimates: <b>150–650 m</b>.'),
|
||||
cap('sunrise', 'Then the sun comes up',
|
||||
'Twenty minutes later both leave Earth\'s shadow. Within the hour, Cosmos backs away.'),
|
||||
cap('leave', 'Back off, and wait',
|
||||
'It drifts back <b>~350 km a day</b>, then matches orbits again. By Oct 25 it sat <b>~480 km behind</b>.'),
|
||||
];
|
||||
const BURN_NOTES = {
|
||||
'Phasing raise': 'two burns raise its orbit 46 km',
|
||||
'Plane trim': 'tilts its orbit into line',
|
||||
'Descent to X36 orbit': 'drops 47 km into X36\'s orbit',
|
||||
'Closing burn': 'dips 2.3 km to speed up',
|
||||
'Braking burn': 'rises 2.5 km to stop',
|
||||
'Back-off': 'rises 140 m and starts to drift back',
|
||||
'Ellipse trim': 'flattens its loops',
|
||||
'Departure burn': 'rises 2.4 km to drift away',
|
||||
'Re-match orbit': 'drops 2.6 km to hold position',
|
||||
};
|
||||
const EVENTS = [
|
||||
...BURNS.map((b) => ({
|
||||
t: b.t, kind: 'burn', title: 'Engine burn',
|
||||
sub: `${BURN_NOTES[b.label] || b.label} · Δv ${b.dv < 1 ? b.dv.toFixed(1) : Math.round(b.dv)} m/s`,
|
||||
})),
|
||||
{ t: sec('2026-10-05T12:28:00Z'), kind: 'note', title: 'Passes overhead', sub: '55 km above its target' },
|
||||
{ t: sec('2026-10-12T00:30:00Z'), kind: 'note', title: 'Directly overhead', sub: '54 km above its target' },
|
||||
{ t: VANTOR_T, kind: 'vantor', title: 'Vantor photographs the pair', sub: `from its own satellite · ~${D.vantor.km.toFixed(0)} km apart · Oct 17` },
|
||||
{ t: CA_T, kind: 'ca', title: 'Closest approach', sub: '~01:17 UTC, Oct 18 · in Earth\'s shadow' },
|
||||
].map((e) => ({ ...e, s: sOfTau(e.t) })).filter((e) => e.s !== null).sort((a, b) => a.s - b.s);
|
||||
const caS = EVENTS.find((e) => e.kind === 'ca')?.s ?? 1e9;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// DOM
|
||||
// -------------------------------------------------------------------------
|
||||
const fade = (s, s0, s1, ramp = 0.4) => Math.min(1, Math.max(0, (s - s0) / ramp), Math.max(0, (s1 - s) / ramp));
|
||||
const capEl = $('#caption');
|
||||
const tagsEl = $('#tags');
|
||||
const leaders = $('#leaders');
|
||||
const evEl = $('#events');
|
||||
leaders.setAttribute('viewBox', `0 0 ${W} ${H}`);
|
||||
$('#chapters').innerHTML = CHAPTERS.map((c) => `<div class="ch" data-k="${c.key}"><i><b></b></i><span>${c.label}</span></div>`).join('');
|
||||
|
||||
// Linear 22-day timeline with event markers (replaces the log chart).
|
||||
const TL = { t0: sec('2026-09-30T00:00:00Z'), t1: sec('2026-10-21T23:59:00Z') };
|
||||
const tlBox = $('#timeline');
|
||||
const timeline = (() => {
|
||||
const cw = tlBox.clientWidth;
|
||||
const ch = tlBox.clientHeight;
|
||||
const y = ch * 0.42;
|
||||
const x = (t) => ((t - TL.t0) / (TL.t1 - TL.t0)) * cw;
|
||||
const fs = (PORTRAIT ? 24 : 26) * U;
|
||||
let ticks = '';
|
||||
for (let d = 0; d <= 21; d++) {
|
||||
const t = sec(`2026-${d === 0 ? '09-30' : `10-${String(d).padStart(2, '0')}`}T00:00:00Z`);
|
||||
ticks += `<line x1="${x(t)}" x2="${x(t)}" y1="${y - 6 * U}" y2="${y + 6 * U}" stroke="rgba(244,247,250,0.3)" stroke-width="${1.5 * U}"/>`;
|
||||
}
|
||||
const labs = [['2026-09-30', 'SEP 30', 'start'], ['2026-10-12', 'OCT 12', 'middle'], ['2026-10-18', 'OCT 18', 'middle'], ['2026-10-21T23:59:00Z', 'OCT 21', 'end']]
|
||||
.map(([d, l, a]) => `<text x="${x(sec(d.length > 10 ? d : `${d}T00:00:00Z`))}" y="${ch - 2 * U}" text-anchor="${a}" font-size="${fs}" fill="rgba(244,247,250,0.66)">${l}</text>`).join('');
|
||||
const burns = BURNS.map((b) => `<circle class="burn" data-t="${b.t}" cx="${x(b.t)}" cy="${y}" r="${7 * U}" fill="#ffb547"/>`).join('');
|
||||
const ca = `<g class="camk" data-t="${CA_T}"><circle cx="${x(CA_T)}" cy="${y}" r="${10 * U}" fill="none" stroke="#fff" stroke-width="${3 * U}"/></g>`;
|
||||
tlBox.innerHTML = `<svg viewBox="0 0 ${cw} ${ch}">
|
||||
<line x1="0" x2="${cw}" y1="${y}" y2="${y}" stroke="rgba(244,247,250,0.22)" stroke-width="${4 * U}" stroke-linecap="round"/>
|
||||
<line id="tlDone" x1="0" x2="0" y1="${y}" y2="${y}" stroke="#ff5a3c" stroke-width="${4 * U}" stroke-linecap="round"/>
|
||||
${ticks}${burns}${ca}${labs}
|
||||
<circle id="tlHead" cx="0" cy="${y}" r="${11 * U}" fill="#fff"/>
|
||||
</svg>`;
|
||||
return { x };
|
||||
})();
|
||||
|
||||
function tag(id, name, cls) {
|
||||
let el = document.getElementById(id);
|
||||
if (!el) {
|
||||
el = document.createElement('div');
|
||||
el.id = id;
|
||||
el.className = 'tag';
|
||||
el.innerHTML = `<div class="name ${cls}">${name}</div><div class="meta"></div>`;
|
||||
tagsEl.appendChild(el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
const fmtNum = (km) => (km >= 100 ? Math.round(km).toLocaleString('en-US') : km >= 10 ? km.toFixed(0) : km.toFixed(1));
|
||||
function fmtSep(m) {
|
||||
if (m >= 10e3) return `${fmtNum(m / 1000)}<small>km</small>`;
|
||||
if (m >= 1000) return `${(m / 1000).toFixed(1)}<small>km</small>`;
|
||||
return `${Math.round(m / 10) * 10}<small>m</small>`;
|
||||
}
|
||||
function perSecond(r) {
|
||||
if (r < 90) return `${Math.round(r)} sec`;
|
||||
if (r < 5400) return `${Math.round(r / 60)} min`;
|
||||
if (r < 129600) return `${(r / 3600).toFixed(r < 36000 ? 1 : 0)} hours`.replace(/^1\.0 hours$/, '1 hour');
|
||||
return `${(r / 86400).toFixed(1)} days`.replace('.0 days', ' days');
|
||||
}
|
||||
|
||||
// "Seen from X36" inset (relative frame).
|
||||
const insetEl = $('#inset');
|
||||
insetEl.width = Math.round(insetEl.clientWidth * 2);
|
||||
insetEl.height = Math.round(insetEl.clientHeight * 2);
|
||||
const inset = createInset({ D, U, relAt, canvas: insetEl });
|
||||
if (PORTRAIT) $('#readout').prepend($('#clock .light'));
|
||||
|
||||
// Label placement memory (hysteresis).
|
||||
const memo = { tagX: null, tagC: null, lastS: -1 };
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Per-frame update
|
||||
// -------------------------------------------------------------------------
|
||||
function update(s, t, b, st) {
|
||||
const sh = shotAt(s);
|
||||
const endA = fade(s, END - 0.15, 99, 0.25);
|
||||
const live = 1 - endA;
|
||||
if (Math.abs(s - memo.lastS) > 0.2 || CUTS.some((c) => memo.lastS < c && s >= c)) {
|
||||
memo.tagX = null;
|
||||
memo.tagC = null;
|
||||
}
|
||||
memo.lastS = s;
|
||||
|
||||
// Chapters
|
||||
for (const c of CHAPTERS) {
|
||||
const el = document.querySelector(`.ch[data-k="${c.key}"]`);
|
||||
const p = Math.min(1, Math.max(0, (s - c.s0) / (c.s1 - c.s0)));
|
||||
el.classList.toggle('on', s >= c.s0 && s < c.s1);
|
||||
el.querySelector('b').style.width = `${p * 100}%`;
|
||||
}
|
||||
$('#chapters').style.opacity = fade(s, CHAPTERS[0].s0 - 0.2, END, 0.4);
|
||||
|
||||
// Caption
|
||||
let cur = null;
|
||||
let capA = 0;
|
||||
for (const c of CAPTIONS) {
|
||||
const a = fade(s, c.s0, c.s1, 0.3);
|
||||
if (a > capA) {
|
||||
cur = c;
|
||||
capA = a;
|
||||
}
|
||||
}
|
||||
if (cur) {
|
||||
capEl.classList.toggle('hook', !!cur.hook);
|
||||
capEl.querySelector('h1').innerHTML = cur.title;
|
||||
capEl.querySelector('p').innerHTML = cur.body;
|
||||
}
|
||||
capEl.style.opacity = capA * live;
|
||||
capEl.style.transform = `translateY(${(1 - capA) * 14 * U}px)`;
|
||||
|
||||
// "22 days earlier" interstitial after the cold open
|
||||
const climbS = span('climb')[0];
|
||||
const inter = $('#interstitial');
|
||||
inter.style.opacity = fade(s, climbS - 0.05, climbS + 1.3, 0.25);
|
||||
|
||||
// Clock, speed, mode, light
|
||||
const d = new Date(Date.parse(D.t0) + t * 1000);
|
||||
const mon = d.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' }).toUpperCase();
|
||||
$('#clock .date').textContent = `${mon} ${d.getUTCDate()}`;
|
||||
$('#clock .time').textContent = `${d.toISOString().slice(11, 16)} UTC`;
|
||||
$('#clock .rate').innerHTML = `1 sec = ${perSecond(rate(s))}`;
|
||||
$('#clock .ff').style.display = sh.mode === 'ff' ? 'inline-block' : 'none';
|
||||
const lit = st.lx > 0.5;
|
||||
const light = $('.light');
|
||||
light.className = `light ${lit ? 'day' : 'night'}`;
|
||||
const orbitN = sh.id === 'dance' ? Math.floor((t - sh.tauF(sh.s0)) / 5730) + 1 : 0;
|
||||
light.innerHTML = (lit ? '<i></i>Day' : '<i></i>Night · Earth\'s shadow') + (orbitN ? ` · orbit ${orbitN}` : '');
|
||||
light.style.display = sh.mode === 'true' && sh.id !== 'climb' && sh.id !== 'end' ? 'inline-flex' : 'none';
|
||||
$('#clock').style.opacity = live;
|
||||
|
||||
// Distance readout
|
||||
const ro = $('#readout');
|
||||
ro.style.opacity = fade(s, climbS + 0.6, END, 0.4);
|
||||
const lapMode = sh.id === 'climb' || sh.id === 'lap';
|
||||
ro.querySelector('.label').textContent = lapMode ? 'GAP ALONG THE ORBIT' : 'DISTANCE APART';
|
||||
ro.querySelector('.val').innerHTML = lapMode
|
||||
? `${(-st.rel[1] / (2 * Math.PI * D.a_ref_km * 1000)).toFixed(2)}<small>laps</small>`
|
||||
: fmtSep(st.sep);
|
||||
const [r, tt0] = st.rel;
|
||||
const circ = 2 * Math.PI * D.a_ref_km * 1000;
|
||||
const tt = ((tt0 % circ) + circ * 1.5) % circ - circ / 2;
|
||||
let ctxLine;
|
||||
if (st.sep < 1500) ctxLine = `≈ ${Math.max(1, Math.round(st.sep / 100))} football fields`;
|
||||
else if (st.sep < 2e6) ctxLine = `${Math.abs(tt) >= 1e4 ? fmtNum(Math.abs(tt) / 1000) + ' km' : (Math.abs(tt) / 1000).toFixed(1) + ' km'} ${tt < 0 ? 'behind' : 'ahead'}${Math.abs(r) > 20e3 ? ` · ${fmtNum(Math.abs(r) / 1000)} km ${r > 0 ? 'higher' : 'lower'}` : ''}`;
|
||||
else ctxLine = `${Math.abs(r) > 20e3 ? `${fmtNum(Math.abs(r) / 1000)} km higher · ` : ''}${fmtNum(st.sep / 1000)} km apart in a straight line`;
|
||||
if (s > caS && s < span('leave')[0] + 0.5) ctxLine += ` · closest ${CA_M} m`;
|
||||
ro.querySelector('.ctx').textContent = lapMode ? `${st.da > 20e3 ? `${Math.round(st.da / 1000)} km higher on average · ` : ''}behind X36` : ctxLine;
|
||||
const relLine = ro.querySelector('.rel');
|
||||
relLine.textContent = st.sep < 2e4 && sh.mode === 'true' ? (st.relSpeed < 0.2 ? `relative speed ~${Math.max(1, Math.round(st.relSpeed * 100))} cm/s (modelled)` : `relative speed ~${st.relSpeed.toFixed(1)} m/s (modelled)`) : '';
|
||||
|
||||
// Scale chip
|
||||
const magEl = $('#mag');
|
||||
const enlarged = st.mag > 1.5;
|
||||
const trueSize = !enlarged && st.camDistX < 200;
|
||||
magEl.style.opacity = (enlarged || trueSize) ? fade(s, climbS, END, 0.3) : 0;
|
||||
magEl.classList.toggle('enlarged', enlarged);
|
||||
magEl.classList.toggle('low', !sh.inset);
|
||||
magEl.textContent = enlarged ? `Satellites enlarged ${Math.round(st.mag)}×` : sh.id === 'ca' ? 'True size · tracking error ±1.5 km > the gap' : 'True size';
|
||||
const relChip = $('#relchip');
|
||||
relChip.style.opacity = 0;
|
||||
|
||||
// Inset
|
||||
const insA = sh.inset && s < END - 0.1 ? fade(s, Math.max(sh.s0, climbS + 0.8), END, 0.3) : 0;
|
||||
insetEl.style.opacity = insA * live > 0.01 ? 1 : 0;
|
||||
inset.draw(s, t, (st.sepG || st.sep) / 1000, { alpha: insA * live, trailHours: sh.inset || 1, cut: CUTS.some((c) => Math.abs(s - c) < 0.05), behindNote: sh.id === 'climb' || sh.id === 'lap' });
|
||||
|
||||
// Timeline
|
||||
const px = timeline.x(Math.min(Math.max(t, TL.t0), TL.t1));
|
||||
document.getElementById('tlHead').setAttribute('cx', px);
|
||||
document.getElementById('tlDone').setAttribute('x2', px);
|
||||
for (const g of tlBox.querySelectorAll('.burn')) g.style.opacity = Number(g.dataset.t) <= t ? 1 : 0.35;
|
||||
tlBox.querySelector('.camk').style.opacity = t >= CA_T - 60 ? 1 : 0.4;
|
||||
$('#tlwrap').style.opacity = fade(s, climbS + 0.6, END, 0.4);
|
||||
|
||||
// Event toasts (max two, newest first)
|
||||
let html = '';
|
||||
const quiet = sh.id === 'ca';
|
||||
const active = EVENTS.map((e) => ({ e, a: fade(s, e.s, e.s + (e.kind === 'ca' ? 3.0 : e.kind === 'vantor' ? 2.2 : 1.9), 0.2) }))
|
||||
.filter((x) => x.a > 0).slice(-2).reverse();
|
||||
for (const { e, a } of quiet ? [] : active) {
|
||||
html += `<div class="event ${e.kind}" style="opacity:${a * live}"><i></i><div><div class="t">${e.title}</div><div class="sub">${e.sub}</div></div></div>`;
|
||||
}
|
||||
evEl.innerHTML = html;
|
||||
|
||||
// Satellite tags with speeds, dimension line, pulse
|
||||
const pX = project(st.x36W);
|
||||
const pC = project(st.cosW);
|
||||
const tX = tag('tagX', 'ICEYE-X36', 'c-x36');
|
||||
const tC = tag('tagC', 'COSMOS 2612', 'c-cos');
|
||||
const tagA = fade(s, 0.3, END, 0.3);
|
||||
const onScreen = (p) => p && p.x > -40 && p.y > -40 && p.x < W + 40 && p.y < H + 40;
|
||||
const speedX = `${(st.vX / 1000).toFixed(2)} km/s`;
|
||||
const speedC = `${(st.vC / 1000).toFixed(2)} km/s`;
|
||||
const speedShot = ['climb', 'lap', 'drop'].includes(sh.id);
|
||||
tX.querySelector('.meta').textContent = speedShot ? `U.S. radar satellite · ${speedX}` : sh.id === 'hook' ? 'U.S. radar satellite' : '';
|
||||
tC.querySelector('.meta').textContent = speedShot ? `Russian military · ${speedC}` : sh.id === 'hook' ? 'Russian military' : '';
|
||||
tX.querySelector('.meta').style.display = tX.querySelector('.meta').textContent ? '' : 'none';
|
||||
tC.querySelector('.meta').style.display = tC.querySelector('.meta').textContent ? '' : 'none';
|
||||
let svg = '';
|
||||
const M = 24 * U;
|
||||
const obstacles = [];
|
||||
for (const q of ['#logo', '#clock', '#caption', '#readout', '#tlwrap', '#chapters', '#mag', '#inset', '#relchip']) {
|
||||
const el = $(q);
|
||||
if (Number(el.style.opacity || 1) > 0.05) obstacles.push(el.getBoundingClientRect());
|
||||
}
|
||||
for (const el of evEl.children) obstacles.push(el.getBoundingClientRect());
|
||||
const overlap = (r) => obstacles.reduce((acc, o) => acc + Math.max(0, Math.min(r.r, o.right + 8 * U) - Math.max(r.l, o.left - 8 * U)) * Math.max(0, Math.min(r.b, o.bottom + 8 * U) - Math.max(r.t, o.top - 8 * U)), 0);
|
||||
|
||||
// Dimension line + pill
|
||||
const dim = $('#dim');
|
||||
const gap = onScreen(pX) && onScreen(pC) ? Math.hypot(pC.x - pX.x, pC.y - pX.y) : 0;
|
||||
const dimA = st.sep < 60e3 && gap > 160 * U ? tagA : 0;
|
||||
if (dimA > 0) {
|
||||
svg += `<line x1="${pX.x}" y1="${pX.y}" x2="${pC.x}" y2="${pC.y}" stroke="#fff" stroke-opacity="${0.75 * dimA}" stroke-width="${2.5 * U}" stroke-dasharray="${10 * U} ${8 * U}"/>`;
|
||||
const nx = -(pC.y - pX.y) / gap;
|
||||
const ny = (pC.x - pX.x) / gap;
|
||||
const side = ny > 0 ? -1 : 1;
|
||||
dim.innerHTML = fmtSep(st.sep).replace(/<\/?small>/g, ' ').trim();
|
||||
const mx = (pX.x + pC.x) / 2 + side * nx * 40 * U;
|
||||
const my = (pX.y + pC.y) / 2 + side * ny * 40 * U;
|
||||
dim.style.left = `${mx}px`;
|
||||
dim.style.top = `${my}px`;
|
||||
const w = dim.offsetWidth;
|
||||
const h = dim.offsetHeight;
|
||||
obstacles.push({ left: mx - w / 2, right: mx + w / 2, top: my - h / 2, bottom: my + h / 2 });
|
||||
}
|
||||
dim.style.opacity = dimA;
|
||||
const k = (s - caS) / 1.2;
|
||||
if (k > 0 && k < 1) {
|
||||
for (const p of [pX, pC]) {
|
||||
if (!onScreen(p)) continue;
|
||||
svg += `<circle cx="${p.x}" cy="${p.y}" r="${(18 + 90 * k) * U}" fill="none" stroke="#fff" stroke-opacity="${(1 - k) * 0.9}" stroke-width="${4 * U}"/>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Tags: keep the previous quadrant unless it has been blocked for a while;
|
||||
// ease the label toward its target so it never jumps.
|
||||
const place = (key, el, p, prefer, color) => {
|
||||
if (!onScreen(p) || p.hidden) {
|
||||
el.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
el.style.opacity = tagA;
|
||||
const w = el.offsetWidth;
|
||||
const h = el.offsetHeight;
|
||||
const rectFor = ([sx, sy]) => {
|
||||
const lx = p.x + sx * 66 * U;
|
||||
const ly = p.y + sy * 56 * U;
|
||||
const l = sx > 0 ? lx + 10 * U : lx - 10 * U - w;
|
||||
const r = { l, r: l + w, t: ly - h / 2, b: ly + h / 2 };
|
||||
const inside = r.l > M && r.r < W - M && r.t > M && r.b < H - M;
|
||||
return { sx, sy, lx, ly, r, cost: (inside ? 0 : 1e9) + overlap(r) };
|
||||
};
|
||||
const m = memo[key];
|
||||
let q = m ? [m.sx, m.sy] : prefer;
|
||||
let cand = rectFor(q);
|
||||
if (m) {
|
||||
m.blocked = cand.cost > 4000 * U * U ? m.blocked + 1 : 0;
|
||||
}
|
||||
if (!m || m.blocked > 20) {
|
||||
const quads = [q, prefer, [prefer[0], -prefer[1]], [-prefer[0], prefer[1]], [-prefer[0], -prefer[1]]];
|
||||
for (const qq of quads) {
|
||||
const c = rectFor(qq);
|
||||
if (c.cost < cand.cost - 1) cand = c;
|
||||
}
|
||||
q = [cand.sx, cand.sy];
|
||||
}
|
||||
// Ease: dx/dy offsets glide (state), anchor follows the satellite exactly.
|
||||
const target = { dx: cand.r.l - p.x, dy: cand.r.t - p.y };
|
||||
const prev = m && m.dx !== undefined ? m : null;
|
||||
const e = prev ? 0.3 : 1;
|
||||
const dx = prev ? prev.dx + (target.dx - prev.dx) * e : target.dx;
|
||||
const dy = prev ? prev.dy + (target.dy - prev.dy) * e : target.dy;
|
||||
memo[key] = { sx: q[0], sy: q[1], blocked: m && m.sx === q[0] && m.sy === q[1] ? m.blocked : 0, dx, dy };
|
||||
const left = Math.min(Math.max(p.x + dx, M), W - M - w);
|
||||
const top = Math.min(Math.max(p.y + dy, M), H - M - h);
|
||||
el.style.left = `${left}px`;
|
||||
el.style.top = `${top}px`;
|
||||
el.style.transform = 'none';
|
||||
if (overlap({ l: left, r: left + w, t: top, b: top + h }) > 0.35 * w * h) {
|
||||
el.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
el.style.textAlign = q[0] > 0 ? 'left' : 'right';
|
||||
obstacles.push({ left, right: left + w, top, bottom: top + h });
|
||||
const ax = q[0] > 0 ? left - 6 * U : left + w + 6 * U;
|
||||
const ay = top + h * 0.32;
|
||||
svg += `<line x1="${p.x + Math.sign(ax - p.x) * 14 * U}" y1="${p.y + Math.sign(ay - p.y) * 10 * U}" x2="${ax}" y2="${ay}" stroke="${color}" stroke-opacity="${tagA * 0.85}" stroke-width="${2.5 * U}"/>`;
|
||||
svg += `<circle cx="${p.x}" cy="${p.y}" r="${15 * U}" fill="none" stroke="${color}" stroke-opacity="${tagA * 0.9}" stroke-width="${2.5 * U}"/>`;
|
||||
};
|
||||
let pX0 = [1, -1];
|
||||
let pC0 = [-1, 1];
|
||||
if (onScreen(pX) && onScreen(pC)) {
|
||||
const sx = pC.x - pX.x >= 0 ? -1 : 1;
|
||||
const sy = pC.y - pX.y >= 0 ? -1 : 1;
|
||||
pX0 = [sx, sy];
|
||||
pC0 = [-sx, -sy];
|
||||
}
|
||||
place('tagX', tX, pX, pX0, '#5ce1e6');
|
||||
place('tagC', tC, pC, pC0, '#ff5a3c');
|
||||
leaders.innerHTML = svg;
|
||||
|
||||
// Dip at cuts
|
||||
let dip = 0;
|
||||
|
||||
$('#dip').style.opacity = Math.max(0, dip);
|
||||
|
||||
// End card
|
||||
const ec = $('#endcard');
|
||||
ec.style.opacity = endA;
|
||||
ec.style.background = `rgba(2,4,8,${0.8 * endA})`;
|
||||
if (!ec.dataset.built) {
|
||||
ec.dataset.built = '1';
|
||||
ec.innerHTML = `<div class="panel">
|
||||
<div class="kicker">Closest pass <span class="c-cos">~${CA_M} m</span> · Oct 18 · in Earth's shadow</div>
|
||||
<div class="quote">“Consistent with on-orbit weapons operations.”</div>
|
||||
<div class="who">— Thomas DiNanno, U.S. Under Secretary of State</div>
|
||||
<div class="still">By Oct 25, Cosmos 2612 was still parked ~480 km behind, in the same orbit.</div>
|
||||
<div class="statline">18 days from first burn · ${BURNS.length} manoeuvres · ~${Math.round(D.total_dv)} m/s Δv · LeoLabs ~500 m · estimates 150–650 m</div>
|
||||
</div>`;
|
||||
}
|
||||
$('#endlogo').style.opacity = endA;
|
||||
$('#logo').style.opacity = live;
|
||||
void DUR;
|
||||
}
|
||||
|
||||
return { update };
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,205 @@
|
||||
// "Seen from ICEYE-X36": a 2D inset in X36's rotating frame. X36 stays fixed
|
||||
// at the top of its orbit ring; Cosmos 2612's path is drawn around it.
|
||||
// Zoomed out it is the whole orbit (heights exaggerated, so "higher = slower,
|
||||
// falls behind, laps" is visible); zoomed in it becomes a true-scale
|
||||
// radial / along-track plot of the loops and the approach.
|
||||
|
||||
export function createInset({ D, U, relAt, canvas }) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
const A = D.a_km; // km
|
||||
const AREF = D.a_ref_km;
|
||||
const RE = 6371;
|
||||
const state = { hw: null, lastS: -1 };
|
||||
const smooth = (x) => {
|
||||
const t = Math.min(1, Math.max(0, x));
|
||||
return t * t * (3 - 2 * t);
|
||||
};
|
||||
|
||||
function niceScale(km) {
|
||||
const p = 10 ** Math.floor(Math.log10(km));
|
||||
for (const m of [1, 2, 5, 10]) if (m * p >= km) return m * p;
|
||||
return 10 * p;
|
||||
}
|
||||
const fmt = (km) => (km >= 1 ? `${km.toLocaleString('en-US')} km` : `${Math.round(km * 1000)} m`);
|
||||
|
||||
function draw(s, t, sepGuideKm, opts) {
|
||||
const W = canvas.width;
|
||||
const H = canvas.height;
|
||||
const dpr = W / canvas.clientWidth || 1;
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
if (opts.alpha <= 0.01) return;
|
||||
ctx.globalAlpha = opts.alpha;
|
||||
|
||||
// Zoom: half-width (km) follows the guiding separation; eased frame to frame.
|
||||
const target = Math.min(9200, Math.max(1.0, 1.25 * sepGuideKm));
|
||||
if (state.hw === null || Math.abs(s - state.lastS) > 0.2 || opts.cut) state.hw = target;
|
||||
else state.hw = Math.exp(Math.log(state.hw) + (Math.log(target) - Math.log(state.hw)) * 0.12);
|
||||
state.lastS = s;
|
||||
const hw = state.hw;
|
||||
const wOut = smooth((hw - 150) / 2500); // 0 = close-up, 1 = whole-orbit view
|
||||
const exag = 1 + 19 * wOut; // radial exaggeration when zoomed out
|
||||
const wCenter = smooth((hw - 2500) / 5500);
|
||||
|
||||
// Panel
|
||||
const pad = 18 * U * dpr;
|
||||
const r = 14 * U * dpr;
|
||||
ctx.fillStyle = 'rgba(5,9,16,0.78)';
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.14)';
|
||||
ctx.lineWidth = 1.5 * dpr;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(0, 0, W, H, r);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
const head = 52 * U * dpr;
|
||||
ctx.fillStyle = 'rgba(244,247,250,0.92)';
|
||||
ctx.font = `800 ${26 * U * dpr}px Inter, sans-serif`;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('SEEN FROM X36', pad, head / 2 + 4 * dpr);
|
||||
const tw = ctx.measureText('SEEN FROM X36 ').width;
|
||||
ctx.fillStyle = '#ff5a3c';
|
||||
ctx.fillText('· red = Cosmos', pad + tw, head / 2 + 4 * dpr);
|
||||
ctx.fillStyle = 'rgba(244,247,250,0.6)';
|
||||
ctx.font = `700 ${24 * U * dpr}px Inter, sans-serif`;
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText('flight →', W - pad, head / 2 + 4 * dpr);
|
||||
ctx.textAlign = 'left';
|
||||
|
||||
// Plot area
|
||||
const px0 = pad;
|
||||
const py0 = head;
|
||||
const pw = W - 2 * pad;
|
||||
const ph = H - head - pad;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(px0, py0, pw, ph);
|
||||
ctx.clip();
|
||||
const scale = pw / 2 / hw; // px per km
|
||||
// Centre: X36 when close; drifts toward Earth's centre when zoomed out.
|
||||
const cx = 0;
|
||||
const cy = A * (1 - wCenter) + 0 * wCenter;
|
||||
const ox = px0 + pw / 2;
|
||||
const oy = py0 + ph * (0.5 + 0.12 * (1 - wCenter));
|
||||
const P = (x, y) => [ox + (x - cx) * scale, oy - (y - cy) * scale];
|
||||
const ring = (rad) => {
|
||||
// Polyline of the visible part of a circle about Earth's centre.
|
||||
const pts = [];
|
||||
const span = Math.min(Math.PI, (hw * 1.6) / rad + 0.05);
|
||||
const n = 180;
|
||||
for (let k = 0; k <= n; k++) {
|
||||
const a = -span + (2 * span * k) / n;
|
||||
pts.push(P(rad * Math.sin(a), rad * Math.cos(a)));
|
||||
}
|
||||
return pts;
|
||||
};
|
||||
const path = (pts) => {
|
||||
ctx.beginPath();
|
||||
pts.forEach(([x, y], i) => (i ? ctx.lineTo(x, y) : ctx.moveTo(x, y)));
|
||||
};
|
||||
|
||||
// Earth (exaggeration shrinks it so the altitude gap reads)
|
||||
const earthR = Math.max(A * 0.5, A - (A - RE) * exag); // schematic when heights are exaggerated
|
||||
if (earthR > 0) {
|
||||
const [ex, ey] = P(0, 0);
|
||||
const g = ctx.createRadialGradient(ex, ey - earthR * scale * 0.3, earthR * scale * 0.2, ex, ey, earthR * scale);
|
||||
g.addColorStop(0, '#1d3a5c');
|
||||
g.addColorStop(1, '#0b1a2c');
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath();
|
||||
ctx.arc(ex, ey, Math.max(0, earthR * scale), 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'rgba(120,190,255,0.5)';
|
||||
ctx.lineWidth = 2 * dpr;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// X36's orbit
|
||||
ctx.setLineDash([8 * dpr, 7 * dpr]);
|
||||
ctx.strokeStyle = 'rgba(92,225,230,0.55)';
|
||||
ctx.lineWidth = 2 * dpr;
|
||||
path(ring(A));
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Cosmos path (relative), with a fading tail
|
||||
const toXY = (rel) => {
|
||||
const phi = rel[1] / 1000 / AREF;
|
||||
const rad = A + (rel[0] / 1000) * exag;
|
||||
return [rad * Math.sin(phi), rad * Math.cos(phi)];
|
||||
};
|
||||
const spanH = opts.trailHours;
|
||||
const n = 260;
|
||||
let prev = null;
|
||||
for (let k = 0; k <= n; k++) {
|
||||
const tt = t - spanH * 3600 + (k / n) * spanH * 3600;
|
||||
if (tt < 0) continue;
|
||||
const [x, y] = P(...toXY(relAt(tt)));
|
||||
if (prev) {
|
||||
ctx.strokeStyle = `rgba(255,90,60,${0.08 + 0.85 * (k / n) ** 1.5})`;
|
||||
ctx.lineWidth = (2.5 + 4 * (k / n)) * U * 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(prev[0], prev[1]);
|
||||
ctx.lineTo(x, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
prev = [x, y];
|
||||
}
|
||||
const rel = relAt(t);
|
||||
const [cxp, cyp] = P(...toXY(rel));
|
||||
const [xxp, xyp] = P(0, A);
|
||||
// Gap line
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.5)';
|
||||
ctx.setLineDash([5 * dpr, 6 * dpr]);
|
||||
ctx.lineWidth = 1.5 * dpr;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xxp, xyp);
|
||||
ctx.lineTo(cxp, cyp);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
const dot = (x, y, col, rad) => {
|
||||
ctx.fillStyle = col;
|
||||
ctx.shadowColor = col;
|
||||
ctx.shadowBlur = 14 * dpr;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, rad * dpr, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.shadowBlur = 0;
|
||||
};
|
||||
dot(xxp, xyp, '#5ce1e6', 11 * U);
|
||||
dot(cxp, cyp, '#ff5a3c', 11 * U);
|
||||
ctx.font = `800 ${26 * U * dpr}px Inter, sans-serif`;
|
||||
ctx.fillStyle = '#5ce1e6';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('X36', xxp, xyp - 24 * U * dpr);
|
||||
ctx.fillStyle = '#ff5a3c';
|
||||
const below = cyp > xyp + 10 * dpr || Math.abs(cxp - xxp) < 40 * U * dpr;
|
||||
ctx.fillText(opts.behindNote ? '← COSMOS falling behind' : 'COSMOS', Math.min(Math.max(cxp, px0 + 150 * U * dpr), px0 + pw - 150 * U * dpr), cyp + (below ? 32 : -24) * U * dpr);
|
||||
ctx.textAlign = 'left';
|
||||
ctx.restore();
|
||||
|
||||
// Scale bar + exaggeration note
|
||||
const sb = niceScale(hw * 0.4);
|
||||
const sbpx = sb * scale;
|
||||
const by = H - pad * 0.55;
|
||||
ctx.strokeStyle = 'rgba(244,247,250,0.8)';
|
||||
ctx.lineWidth = 2.5 * dpr;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pad, by);
|
||||
ctx.lineTo(pad + sbpx, by);
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = 'rgba(244,247,250,0.85)';
|
||||
ctx.font = `700 ${24 * U * dpr}px 'JetBrains Mono', monospace`;
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText(fmt(sb), pad, by - 6 * dpr);
|
||||
if (exag > 1.5) {
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillStyle = 'rgba(255,181,71,0.95)';
|
||||
ctx.font = `700 ${24 * U * dpr}px Inter, sans-serif`;
|
||||
ctx.fillText(`heights ×${Math.round(exag)}`, W - pad, by - 6 * dpr);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
return { draw };
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="180 120 775 520" role="img" aria-labelledby="logo-title">
|
||||
<title id="logo-title">God's Eye View</title>
|
||||
<defs>
|
||||
<style>
|
||||
.cls-4 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.cls-2, .cls-4 {
|
||||
stroke: #000;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.cls-1 {
|
||||
fill: #4454727a;
|
||||
stroke: #48b;
|
||||
stroke-width: 10px;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2, .cls-3, .cls-4 {
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #00f6ff;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
stroke: #00f6ff;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 6.99px;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
opacity: 0.63;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path id="outer_eye" class="cls-1" d="M567.5,623c-163.183,0-303.026-99.084-361.823-239.922C257.508,238.76,401.037,135,570,135c166.451,0,308.222,100.7,361.954,241.667C874.771,520.905,733.152,623,567.5,623ZM567,156c124.816,0,226,101.184,226,226S691.816,608,567,608,341,506.816,341,382,442.184,156,567,156ZM816,256.213A272.836,272.836,0,0,1,846,381M793.574,488.677a249.545,249.545,0,0,0,3.239-208.3A303.384,303.384,0,0,0,816,256.213a274.468,274.468,0,0,1-1.516,252.475q-0.927,1.758-1.88,3.5-1.632-2.238-3.306-4.446Q801.826,497.884,793.574,488.677ZM345.5,490.85q-8.055,9.164-15.37,18.966a274.353,274.353,0,0,1-3.748-250.343q7.347,10.012,15.469,19.393A249.512,249.512,0,0,0,345.5,490.85Z"/>
|
||||
<path id="inner_eye" class="cls-2" d="M568,179.985c107.678,0,194.968,88.655,194.968,198.015S675.678,576.015,568,576.015,373.032,487.36,373.032,378,460.322,179.985,568,179.985Z"/>
|
||||
<path id="globe" class="cls-3" d="M569.5,499.831A120.331,120.331,0,1,1,689.831,379.5,120.33,120.33,0,0,1,569.5,499.831Zm65.1-50.585c1.225-.4,3.268-2.858,4.492-3.677s3.268-4.083,3.675-5.717,1.227-4.495,1.634-6.126a36.015,36.015,0,0,0,.818-6.135,14.639,14.639,0,0,0-.407-2.855l-3.266,6.127s-4.9,2.04-5.315,3.674-0.817,4.088-.817,4.088a61.248,61.248,0,0,0-2.45,6.133C632.554,446.388,633.372,449.66,634.6,449.246ZM461.011,377.072c0.72-1.105,1.928-6.276,1.6-7.894-0.411-2.037,1.225-6.94,1.225-9.392s4.083-7.358,4.9-9.4,4.491-7.349,6.535-10.623-0.4-2.038-2.449-.809,1.225-3.274,2.449-4.086,8.172-5.72,8.172-5.72,2.449-2.454-.409-7.762c-2.211-4.1,0-4.5,0-6.128a2.43,2.43,0,0,0-1.28-.223c2.129-2.952,4.408-5.788,6.811-8.512A108.871,108.871,0,0,0,494.612,458.6a0.454,0.454,0,0,1,.683-0.363c2.043,0.819,2.449-.4,4.085-0.4s4.086-2.043,4.086-2.043l-1.641-4.9,1.641-6.126,2.041-5.717s0-4.095-2.041-4.5-5.722-4.081-6.946-4.081a10.018,10.018,0,0,1-4.491-1.636c-1.227-.813-3.677-1.634-4.5-2.857a31.144,31.144,0,0,1-2.858-5.314c-0.816-2.039-.409-2.039-3.681-5.715s-2.452-2.863-4.9-4.093-3.265-2.443-5.308-3.263a13.378,13.378,0,0,1-4.5-2.857,4.884,4.884,0,0,0-2.711-1.519,110.041,110.041,0,0,1-2.595-23.808C460.98,378.618,461,377.84,461.011,377.072Zm35.743-78.7a13.8,13.8,0,0,0-1.051,2.589c-0.408,1.638,2.864.416,2.864,0.416L502.239,303l4.5-1.631a13.741,13.741,0,0,1,4.083-4.5c2.857-2.043,2.454-2.452,3.677-4.084s0-.006,5.314-5.732,8.167-5.718,8.167-5.718,1.915-2.1,3.244-3.895A108.986,108.986,0,0,0,496.754,298.368Zm41.024,56.1c-1.224.82-5.715,3.272-6.944,5.315a22.681,22.681,0,0,0-2.859,6.535,31.042,31.042,0,0,0-.408,6.538c0,2.854-.41,4.5,0,6.949s4.5,8.568,6.128,10.623c1.63,2.038,3.674,4.483,5.719,5.713a47.235,47.235,0,0,1,4.9,3.674,4.03,4.03,0,0,0,4.9,1.223c3.272-1.223,6.538-1.223,9.4-2.853s7.353-3.685,7.353-1.231,1.632,4.084,4.082,3.272,6.539-2.858,6.13,0S577,406.764,577,408.4s-0.4,3.265.818,4.495,4.5,3.265,4.5,4.49,0.409,5.311,1.229,6.946,1.639,4.08.823,5.724-2.434,6.123-2.434,6.123-0.779,3.676-.37,4.9a12.683,12.683,0,0,0,3.332,4.906c2.043,2.042,1.771,4.492,2.994,6.124s2.993,6.539,2.993,6.539l1.236,4.088v3.677s7.966-1.231,9.591-1.64a30.673,30.673,0,0,0,8.618-4.9c1.636-1.632,1.311-2.856,3.349-4.488s5.762-4.5,5.762-4.5l-0.387-6.126,4.914-6.544,3.684-3.677s3.267-4.484,2.865-6.128c-0.413-1.628-1.231-9.8-1.231-9.8a37.972,37.972,0,0,1,2.863-5.313c1.224-1.634,1.227-3.678,3.679-5.719a33.109,33.109,0,0,0,5.306-6.126c1.229-1.634,3.681-4.9,4.5-6.127a21.871,21.871,0,0,0,1.635-4.894l-7.355,1.216c-2.45-4.082-9.392-13.073-10.621-15.112a37.04,37.04,0,0,1-2.861-7.76c-0.4-1.634-2.445-6.131-2.856-7.356s-3.27-4.908-4.088-6.128-3.675-2.043-5.309-3.268-11.843-3.27-11.843-3.27-3.129-1.991-4.088-1.226c-4.083,3.269-13.48,1.226-15.523.415s-3.269-2.041-3.269-3.272-1.835-7.143-1.835-7.143-3.985.705-5.213,0.705a20.333,20.333,0,0,1-4.442-1.276s-4.055.379-5.693,1.2a61.205,61.205,0,0,1-6.115,2.438,36.488,36.488,0,0,1-4.488.814c-3.671-1.636-6.529,2.45-7.352,4.079s-2.445,2.454-2.445,4.084S539,353.659,537.778,354.471Zm18.191-54.739-3.128-4.5-3.727,2.045V304.5l-3.839-1.5-1.715,3.674,1.685,2.452L549.114,305v4.129l8.478-1.636,3.164-2.449Zm71.15-12.676c-1.669.869-4.808,3.094-6,1.894-1.227-1.218-4.086-3.423-4.086-3.423,2.448-1.636-4.9-5.212-4.9-5.212l-8.576-1.022h-6.125c-1.227,0-3.681.825-5.723,0.825s-6.943,3.583-8.577,3.994-5.314,2.5-6.944,2.5-4.088,3.086-4.088,3.086-2.856,5.218-.4,4.4,5.717-.858,6.122.367,1.227,3.242,1.636,4.471,6.131,1.618,6.131,1.618a18.551,18.551,0,0,1,2.038-3.683c1.227-1.634,2.452-3.675,2.452-3.675s2.044,4.082,2.452,5.3-0.818,4.908-.818,4.908-4.491,2.852-5.716,2.852-8.175-6.126-8.175-6.126a25.4,25.4,0,0,0-4.489,2.043c-2.856,2.861-5.717,5.31-5.717,5.31a17.057,17.057,0,0,0-4.081,2.046c-1.629,1.222-5.138,1.487-6.117,2.854-2.039,2.863.84,4.9,0.84,4.9l-2.408,3.271-1.548,1.225-5.553,1.221-2.751.616c0.4,1.227,1.258,5.105,1.258,5.105s-1.87,6.943,0,6.943c0,0,4.276,1.64,5.5,1.64s2.625,0.4,4.265-1.227a59.861,59.861,0,0,0,5.156-6.538,8.047,8.047,0,0,1,4.49-3.267c2.454-.82,3-2.047,4.231-2.452s6.47-1.229,6.47-1.229,3.709,2.043,4.932,2.861c0.573,0.386-1.393,1.658,6.086,2.907V331.6l3.7-.411-4.207-3.264-0.44-.1-4.778-4.8-0.419-3.672s4.861,2.862,6.493,3.672a43.671,43.671,0,0,1,4.81,3.272l1.783,5.719,2.4,4.5s-1.87-5.722,0-5.722c0,0,5.158.405,6.381,0a36.253,36.253,0,0,0,5.237-1.632c1.636-.815-0.648-3.68.986-5.721a10.385,10.385,0,0,1,4.981-3.27c1.23-.405,5.35-2.447,5.35-2.447l4.522,0.82c-2.041,1.627-4.486,4.077-2.85,3.676s5.313,1.63,6.949,2.855,0.413,4.087.413,4.087-4.5-.411-7.355-0.815a29.263,29.263,0,0,0-7.351.4l-3.677,2.043s-2.454.816-2.045,2.861,2.045,4.5,3.679,6.535,2.858,4.5,4.085,3.675,5.309-3.265,5.309-3.265a12.784,12.784,0,0,0-.409,4.9,30.6,30.6,0,0,1,.409,4.9s-4.082,0-2.861.822,2.967,3.905,3.681,4.9c4.083,5.717,12.664,18.383,13.482,20.015s0.44,4.126,1.631,5.31c2.454,2.45,7.352,0,8.578-.817s5.311-2.863,6.536-4.082,0.818-.822,3.272-2.454,4.079-2.856,2.859-4.085-3.68-5.725-3.68-5.725c-3.676.825-7.351-1.222-7.351-2.445s-3.269-6.124-3.269-6.124,0.407-.82,3.269.809,5.306,4.908Line truncated
|
||||
<path id="globe_cage" class="cls-4" d="M569.5,576.491c-106.968,0-193.988-89.044-193.988-198.491S462.532,179.509,569.5,179.509,763.488,268.553,763.488,378,676.468,576.491,569.5,576.491ZM578.4,368.9h76.237a314.2,314.2,0,0,0-11.945-78.026H578.4V368.9ZM654.634,387.1H578.4V464.94h64.345A314.251,314.251,0,0,0,654.634,387.1Zm90.813,0H671.861a333.4,333.4,0,0,1-11.206,77.836h63.112A182.117,182.117,0,0,0,745.447,387.1Zm-32.528,95.444H655.363a288.421,288.421,0,0,1-32.3,67.191A177.214,177.214,0,0,0,712.919,482.548Zm-115.67,73.48a263.416,263.416,0,0,0,39.828-73.48H578.4v75.476A173.846,173.846,0,0,0,597.249,556.028ZM560.6,558.036V482.548H497.674a263.837,263.837,0,0,0,39.135,72.575A174.279,174.279,0,0,0,560.6,558.036ZM510.434,547.83a289.038,289.038,0,0,1-31.078-65.282H426.1A177.5,177.5,0,0,0,510.434,547.83Zm-95.208-82.89H474.1A333.353,333.353,0,0,1,462.878,387.1H393.555A182.127,182.127,0,0,0,415.226,464.94ZM393.555,368.9h69.323a333.4,333.4,0,0,1,11.268-78.026H415.332A182.13,182.13,0,0,0,393.555,368.9Zm32.676-95.633h53.185a289.064,289.064,0,0,1,31-65.087A177.487,177.487,0,0,0,426.231,273.263Zm134.372-75.3a174.068,174.068,0,0,0-23.8,2.914,263.8,263.8,0,0,0-39.06,72.384H560.6v-75.3Zm0,92.905H492.051A314.088,314.088,0,0,0,480.106,368.9h80.5V290.87Zm-80.5,96.234A314.1,314.1,0,0,0,492,464.94h68.6V387.1h-80.5ZM578.4,197.977v75.286h58.612a263.412,263.412,0,0,0-39.751-73.289A174.011,174.011,0,0,0,578.4,197.977Zm44.672,8.286a288.6,288.6,0,0,1,32.231,67h57.491A177.213,177.213,0,0,0,623.069,206.263Zm100.6,84.607H660.6A333.3,333.3,0,0,1,671.861,368.9h73.586A182.129,182.129,0,0,0,723.667,290.87Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,445 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reconstruct the Cosmos 2612 / ICEYE-X36 rendezvous (Sep 30 - Oct 22, 2026).
|
||||
|
||||
ICEYE-X36 is propagated with SGP4 from its public CelesTrak element set.
|
||||
Cosmos 2612 is carried as quasi-nonsingular relative orbital elements (ROE)
|
||||
about X36 and advanced with linearised Keplerian drift plus the differential
|
||||
J2 node drift. Manoeuvres are tangential / normal impulses applied through
|
||||
Gauss's variational equations, timed inside the element-set windows that
|
||||
KeepTrack, LeoLabs and Marco Langbroek published. The final state is pinned
|
||||
to Cosmos 2612's own current element set (479 km behind, matched SMA).
|
||||
|
||||
Output: data.json (60 s samples) consumed by scene.html.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import numpy as np
|
||||
from scipy.optimize import least_squares
|
||||
from sgp4.api import Satrec, jday
|
||||
|
||||
HERE = __file__.rsplit('/', 1)[0]
|
||||
MU = 398600.4418
|
||||
RE = 6378.137
|
||||
J2 = 1.08262668e-3
|
||||
|
||||
lines = open(f'{HERE}/tles.txt').read().split('\n')
|
||||
cosmos_tle = Satrec.twoline2rv(lines[1], lines[2])
|
||||
x36 = Satrec.twoline2rv(lines[4], lines[5])
|
||||
|
||||
A = (MU / (x36.no_kozai / 60) ** 2) ** (1 / 3) # km
|
||||
N_RAD_S = math.sqrt(MU / A ** 3)
|
||||
N_DAY = N_RAD_S * 86400
|
||||
INC = x36.inclo
|
||||
V = N_RAD_S * A * 1000 # m/s orbital speed
|
||||
# Differential node rate per unit da/a: dOmegadot = -3.5 * Omegadot * da/a
|
||||
OMEGA_DOT = -1.5 * N_RAD_S * J2 * (RE / A) ** 2 * math.cos(INC) # rad/s
|
||||
|
||||
T0 = datetime(2026, 9, 29, 12, 0, tzinfo=timezone.utc)
|
||||
T1 = datetime(2026, 10, 22, 12, 0, tzinfo=timezone.utc)
|
||||
DT = 60
|
||||
|
||||
|
||||
def utc(s):
|
||||
return datetime.fromisoformat(s.replace('Z', '+00:00'))
|
||||
|
||||
|
||||
def secs(d):
|
||||
return (d - T0).total_seconds()
|
||||
|
||||
|
||||
def x36_state(t):
|
||||
d = T0 + timedelta(seconds=t)
|
||||
jd, fr = jday(d.year, d.month, d.day, d.hour, d.minute, d.second + d.microsecond / 1e6)
|
||||
e, r, v = x36.sgp4(jd, fr)
|
||||
assert e == 0
|
||||
return np.array(r), np.array(v)
|
||||
|
||||
|
||||
def frame(r, v):
|
||||
R = r / np.linalg.norm(r)
|
||||
Nn = np.cross(r, v)
|
||||
Nn /= np.linalg.norm(Nn)
|
||||
return R, np.cross(Nn, R), Nn
|
||||
|
||||
|
||||
def arg_lat(r, v):
|
||||
R, T, Nn = frame(r, v)
|
||||
node = np.cross([0, 0, 1], Nn)
|
||||
node /= np.linalg.norm(node)
|
||||
return math.atan2(np.dot(np.cross(node, R), Nn), np.dot(node, R))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ROE dynamics. roe = [da, dl, dex, dey, dix, diy] (dimensionless, / a)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def drift(roe, dt):
|
||||
out = roe.copy()
|
||||
out[1] += -1.5 * N_RAD_S * roe[0] * dt
|
||||
out[5] += -3.5 * OMEGA_DOT * roe[0] * math.sin(INC) * dt
|
||||
return out
|
||||
|
||||
|
||||
def impulse(roe, u, dvr=0.0, dvt=0.0, dvn=0.0):
|
||||
k = 1 / (N_RAD_S * A * 1000)
|
||||
out = roe.copy()
|
||||
out[0] += 2 * k * dvt
|
||||
out[1] += -2 * k * dvr
|
||||
out[2] += k * (math.sin(u) * dvr + 2 * math.cos(u) * dvt)
|
||||
out[3] += k * (-math.cos(u) * dvr + 2 * math.sin(u) * dvt)
|
||||
out[4] += k * math.cos(u) * dvn
|
||||
out[5] += k * math.sin(u) * dvn
|
||||
return out
|
||||
|
||||
|
||||
def rtn(roe, u):
|
||||
da, dl, dex, dey, dix, diy = roe
|
||||
return A * 1000 * np.array([
|
||||
da - dex * math.cos(u) - dey * math.sin(u),
|
||||
dl + 2 * dex * math.sin(u) - 2 * dey * math.cos(u),
|
||||
dix * math.sin(u) - diy * math.cos(u),
|
||||
])
|
||||
|
||||
|
||||
def dv_for_da(da_km):
|
||||
return V * da_km / A / 2 # m/s total, split into two half-orbit impulses
|
||||
|
||||
|
||||
PERIOD = 2 * math.pi / N_RAD_S
|
||||
U_CACHE = {}
|
||||
|
||||
|
||||
def u_at(t):
|
||||
key = round(t)
|
||||
if key not in U_CACHE:
|
||||
U_CACHE[key] = arg_lat(*x36_state(t))
|
||||
return U_CACHE[key]
|
||||
|
||||
|
||||
def build(params):
|
||||
"""Return (burns, simulate) from a parameter dict."""
|
||||
p = params
|
||||
burns = []
|
||||
|
||||
def hohmann(t, da_km, label, de_vec=None):
|
||||
"""Two tangential impulses half an orbit apart -> pure SMA change.
|
||||
|
||||
de_vec optionally re-targets the relative eccentricity vector by
|
||||
splitting the impulses unevenly (a small radial-tangential trim)."""
|
||||
dv = dv_for_da(da_km)
|
||||
burns.append(dict(t=t, dvt=dv / 2, label=label))
|
||||
burns.append(dict(t=t + PERIOD / 2, dvt=dv / 2, label=label, second=True, de=de_vec))
|
||||
|
||||
hohmann(p['t_sep30'], p['da_sep30'], 'Phasing raise')
|
||||
burns.append(dict(t=p['t_oct7'], dvn=p['dvn_oct7'], label='Plane trim'))
|
||||
hohmann(p['t_oct12'], p['da_oct12'], 'Descent to X36 orbit')
|
||||
hohmann(p['t_oct15'], p['da_oct15'], 'Closing burn')
|
||||
hohmann(p['t_oct16'], p['da_oct16'], 'Braking burn', de_vec=p['de_prox'])
|
||||
burns.append(dict(t=p['t_oct18a'], dvt=dv_for_da(p['da_oct18a']), label='Back-off'))
|
||||
hohmann(p['t_oct18'], p['da_oct18'], 'Departure burn')
|
||||
hohmann(p['t_oct19'], p['da_oct19'], 'Re-match orbit')
|
||||
burns.sort(key=lambda b: b['t'])
|
||||
return burns
|
||||
|
||||
|
||||
def simulate(params, times):
|
||||
burns = build(params)
|
||||
roe = np.array(params['roe0'], dtype=float)
|
||||
t = 0.0
|
||||
bi = 0
|
||||
out = []
|
||||
roe_out = []
|
||||
for tt in times:
|
||||
while bi < len(burns) and burns[bi]['t'] <= tt:
|
||||
b = burns[bi]
|
||||
roe = drift(roe, b['t'] - t)
|
||||
t = b['t']
|
||||
u = u_at(t)
|
||||
roe = impulse(roe, u, dvt=b.get('dvt', 0), dvn=b.get('dvn', 0))
|
||||
if b.get('de') is not None:
|
||||
roe[2], roe[3] = b['de'][0] / (A * 1000), b['de'][1] / (A * 1000)
|
||||
bi += 1
|
||||
roe = drift(roe, tt - t)
|
||||
t = tt
|
||||
out.append(rtn(roe, u_at(tt)))
|
||||
roe_out.append(roe.copy())
|
||||
return np.array(out), np.array(roe_out), burns
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Timeline (UTC). Windows are the published element-set gaps.
|
||||
# ---------------------------------------------------------------------------
|
||||
OCT12_OVERHEAD = secs(utc('2026-10-12T00:10:00Z'))
|
||||
LAP_DA = 47.0 # km, KeepTrack: "raised orbit by 47 km in total"
|
||||
lap_rate = 1.5 * N_RAD_S * LAP_DA / A # rad/s
|
||||
t_sep30 = secs(utc('2026-09-30T11:00:00Z'))
|
||||
|
||||
params = dict(
|
||||
t_sep30=t_sep30, da_sep30=LAP_DA - 1.0,
|
||||
t_oct7=secs(utc('2026-10-07T10:30:00Z')), dvn_oct7=4.0,
|
||||
t_oct12=secs(utc('2026-10-12T01:30:00Z')), da_oct12=-(LAP_DA + 0.2),
|
||||
t_oct15=secs(utc('2026-10-15T03:00:00Z')), da_oct15=-2.3,
|
||||
t_oct16=secs(utc('2026-10-16T05:30:00Z')), da_oct16=2.48,
|
||||
de_prox=(0.0, 800.0),
|
||||
t_oct18a=secs(utc('2026-10-18T02:00:00Z')), da_oct18a=0.13,
|
||||
t_oct18=secs(utc('2026-10-18T12:26:00Z')), da_oct18=2.40,
|
||||
t_oct19=secs(utc('2026-10-19T20:00:00Z')), da_oct19=-2.45,
|
||||
roe0=[1.0 / A, 0.0, 0.0, 0.0, -0.032 * math.pi / 180, 0.0],
|
||||
)
|
||||
|
||||
|
||||
def sep_at(p, iso_list):
|
||||
ts = [secs(utc(s)) for s in iso_list]
|
||||
r, roe, _ = simulate(p, ts)
|
||||
return np.linalg.norm(r, axis=1) / 1000, r, roe
|
||||
|
||||
|
||||
def dl_at(p, iso):
|
||||
_, _, roe = sep_at(p, [iso])
|
||||
return roe[0][1]
|
||||
|
||||
|
||||
# 1) Initial along-track phase so the Oct 12 overhead pass (dl = -2 pi) is hit.
|
||||
def f_phase(x):
|
||||
p = dict(params, roe0=[params['roe0'][0], x[0]] + params['roe0'][2:])
|
||||
return [dl_at(p, '2026-10-12T00:10:00Z') + 2 * math.pi]
|
||||
|
||||
|
||||
params['roe0'][1] = least_squares(f_phase, [-0.5]).x[0]
|
||||
# Undo the lap so dl is near zero afterwards (wrap): handled by subtracting 2 pi.
|
||||
params['roe0'][1] += 2 * math.pi
|
||||
# Wrap: overhead at Oct 12 is now dl=0.
|
||||
|
||||
# 1b) Initial relative inclination vector: the 47 km "high park" lets
|
||||
# differential J2 node drift close the plane; the Oct 7 normal burn
|
||||
# trims the rest. Fit so the plane angle is ~0.002 deg by Oct 16.
|
||||
def f_plane(x):
|
||||
p = dict(params, roe0=params['roe0'][:4] + [x[0], x[1]])
|
||||
_, _, roe = sep_at(p, ['2026-10-16T12:00:00Z'])
|
||||
target = 0.002 * math.pi / 180 / math.sqrt(2)
|
||||
return [(roe[0][4] - target) * 1e5, (roe[0][5] - target) * 1e5]
|
||||
|
||||
|
||||
params['roe0'][4:6] = list(least_squares(f_plane, [0.0, -0.002]).x)
|
||||
plane0 = math.degrees(math.hypot(*params['roe0'][4:6]))
|
||||
|
||||
# 2) Oct 12 descent timing -> ~480 km behind once the drift stops.
|
||||
def f_desc(x):
|
||||
p = dict(params, t_oct12=x[0])
|
||||
return [dl_at(p, '2026-10-12T06:00:00Z') * A + 480.0]
|
||||
|
||||
|
||||
params['t_oct12'] = least_squares(f_desc, [params['t_oct12']]).x[0]
|
||||
|
||||
# 3) Oct 15 closing burn timing so drift is arrested ~3 km behind by the Oct 16 braking burn.
|
||||
def f_close(x):
|
||||
p = dict(params, t_oct15=x[0])
|
||||
t16 = params['t_oct16'] + PERIOD
|
||||
d = (T0 + timedelta(seconds=t16)).isoformat()
|
||||
return [dl_at(p, d) * A + 3.0]
|
||||
|
||||
|
||||
params['t_oct15'] = least_squares(f_close, [params['t_oct15']]).x[0]
|
||||
|
||||
# 4) Proximity phase: fit the residual SMA offset, relative e / i vectors to
|
||||
# published separations (Oct 17) and a sub-km closest approach early Oct 18.
|
||||
CHECK = [
|
||||
('2026-10-17T07:45:00Z', 2.1),
|
||||
('2026-10-17T17:18:00Z', 0.85),
|
||||
('2026-10-17T20:29:00Z', 0.64),
|
||||
('2026-10-17T23:40:00Z', 0.64),
|
||||
]
|
||||
|
||||
|
||||
DE_PROX_M = 800.0 # KeepTrack: relative loops ~1.6 km tall after the braking burn
|
||||
|
||||
|
||||
def prox_params(x):
|
||||
t16, da16, phi = x
|
||||
return dict(params, t_oct16=t16, da_oct16=da16,
|
||||
de_prox=(DE_PROX_M * math.cos(phi), DE_PROX_M * math.sin(phi)))
|
||||
|
||||
|
||||
def window(start, end, step_min):
|
||||
t, out = utc(start), []
|
||||
while t < utc(end):
|
||||
out.append(t.isoformat())
|
||||
t += timedelta(minutes=step_min)
|
||||
return out
|
||||
|
||||
|
||||
ca_window = window('2026-10-17T23:30:00Z', '2026-10-18T02:00:00Z', 1)
|
||||
pre_window = window('2026-10-16T08:00:00Z', '2026-10-17T23:30:00Z', 4)
|
||||
T_CA_TARGET = 75 # minutes after 00:00 -> 01:15 UTC (Langbroek ~01:35, KeepTrack ~01:10)
|
||||
|
||||
|
||||
def f_prox(x):
|
||||
p = prox_params(x)
|
||||
d, _, roe = sep_at(p, [c[0] for c in CHECK] + ['2026-10-16T12:00:00Z'])
|
||||
res = [(d[i] - c[1]) / 0.5 for i, c in enumerate(CHECK)]
|
||||
res.append((roe[-1][1] * A + 3.0) / 0.3)
|
||||
dw, _, _ = sep_at(p, ca_window)
|
||||
k = int(np.argmin(dw))
|
||||
res.append((dw[k] - 0.20) / 0.02)
|
||||
res.append((k - 30 - T_CA_TARGET) / 15)
|
||||
dp, _, _ = sep_at(p, pre_window)
|
||||
res.append(max(0.0, 0.55 - dp.min()) / 0.02)
|
||||
return res
|
||||
|
||||
|
||||
best = None
|
||||
for phi0 in np.linspace(0, 2 * math.pi, 8, endpoint=False):
|
||||
f = least_squares(f_prox, [params['t_oct16'] - 1800, 2.49, phi0], x_scale=[600, 0.01, 0.3],
|
||||
diff_step=[1e-4, 1e-4, 1e-3])
|
||||
if best is None or f.cost < best.cost:
|
||||
best = f
|
||||
fit = best
|
||||
params.update(prox_params(fit.x))
|
||||
print('prox fit residuals', np.round(fit.fun, 2), 'x', fit.x)
|
||||
|
||||
# 5) Post-CA back-off so Oct 18 07:38 is ~5.1 km behind.
|
||||
def f_back(x):
|
||||
p = dict(params, da_oct18a=x[0])
|
||||
d, r, _ = sep_at(p, ['2026-10-18T07:38:00Z'])
|
||||
return [d[0] - 5.1]
|
||||
|
||||
|
||||
params['da_oct18a'] = least_squares(f_back, [0.13]).x[0]
|
||||
|
||||
# 6) Re-match burn so the state lands on the current element sets
|
||||
# (Oct 24: 479 km behind, SMA offset +0.017 km).
|
||||
def pair_phase(iso):
|
||||
d = utc(iso)
|
||||
jd, fr = jday(d.year, d.month, d.day, d.hour, d.minute, d.second)
|
||||
_, rc, _ = cosmos_tle.sgp4(jd, fr)
|
||||
r, v = x36_state(secs(d))
|
||||
R, T, Nn = frame(r, v)
|
||||
return math.atan2(np.dot(np.cross(r, rc), Nn), np.dot(r, rc))
|
||||
|
||||
|
||||
A_COSMOS = (MU / (cosmos_tle.no_kozai / 60) ** 2) ** (1 / 3)
|
||||
END_DA = A_COSMOS - A
|
||||
target_dl = pair_phase('2026-10-21T12:00:00Z')
|
||||
|
||||
|
||||
def f_rematch(x):
|
||||
p = dict(params, t_oct19=x[0], da_oct19=END_DA - (params['da_oct18a'] + params['da_oct18'] + params['roe0'][0] * 0 + 0))
|
||||
return [(dl_at(p, '2026-10-21T12:00:00Z') - target_dl) * A]
|
||||
|
||||
|
||||
# SMA bookkeeping: total offset after Oct 18 burns.
|
||||
def sma_after(p, iso):
|
||||
_, _, roe = sep_at(p, [iso])
|
||||
return roe[0][0] * A
|
||||
|
||||
|
||||
da_before_rematch = sma_after(params, '2026-10-19T12:00:00Z')
|
||||
params['da_oct19'] = END_DA - da_before_rematch
|
||||
params['t_oct19'] = least_squares(
|
||||
lambda x: [(dl_at(dict(params, t_oct19=x[0]), '2026-10-21T12:00:00Z') - target_dl) * A],
|
||||
[params['t_oct19']],
|
||||
).x[0]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Sample and export
|
||||
# ---------------------------------------------------------------------------
|
||||
times = np.arange(0, secs(T1) + 1, DT)
|
||||
rel, roe, burns = simulate(params, list(times))
|
||||
x36_pos = np.array([x36_state(t)[0] for t in times])
|
||||
|
||||
dist_km = np.linalg.norm(rel, axis=1) / 1000
|
||||
# Chord distance for large phases: the ROE map is linear, so use the
|
||||
# curvilinear placement for the true separation.
|
||||
def true_sep(i):
|
||||
r, v = x36_state(times[i])
|
||||
R, T, Nn = frame(r, v)
|
||||
th = rel[i][1] / 1000 / np.linalg.norm(r)
|
||||
Rp = R * math.cos(th) + T * math.sin(th)
|
||||
pc = (np.linalg.norm(r) + rel[i][0] / 1000) * Rp + rel[i][2] / 1000 * Nn
|
||||
return np.linalg.norm(pc - r)
|
||||
|
||||
|
||||
sep = np.array([true_sep(i) for i in range(len(times))])
|
||||
ica = int(np.argmin(np.where(times > secs(utc('2026-10-17T12:00:00Z')), sep, 1e9)))
|
||||
|
||||
events = [
|
||||
dict(t='2026-09-30T11:00:00Z', key='raise'),
|
||||
dict(t='2026-10-12T00:10:00Z', key='overhead'),
|
||||
]
|
||||
|
||||
burn_list = []
|
||||
for b in burns:
|
||||
if b.get('second'):
|
||||
continue
|
||||
dv = abs(b.get('dvt', 0)) * (1 if b['label'] == 'Back-off' else 2) + abs(b.get('dvn', 0))
|
||||
burn_list.append(dict(t=(T0 + timedelta(seconds=b['t'])).isoformat().replace('+00:00', 'Z'),
|
||||
label=b['label'], dv=round(dv, 2)))
|
||||
|
||||
total_dv = sum(b['dv'] for b in burn_list)
|
||||
ca_t = (T0 + timedelta(seconds=float(times[ica]))).isoformat().replace('+00:00', 'Z')
|
||||
# Refine CA with 2 s sampling
|
||||
fine = list(np.arange(times[ica] - 120, times[ica] + 120, 2.0))
|
||||
relf, _, _ = simulate(params, fine)
|
||||
dfine = np.linalg.norm(relf, axis=1)
|
||||
j = int(np.argmin(dfine))
|
||||
ca_t = (T0 + timedelta(seconds=float(fine[j]))).isoformat().replace('+00:00', 'Z')
|
||||
ca_m = float(dfine[j])
|
||||
ca_rtn = relf[j].tolist()
|
||||
|
||||
# Vantor image: Oct 17, ~1.02 km. Pick the Oct 17 sample nearest 1.02 km in the afternoon.
|
||||
# Vantor imaged optically, so the pair must be sunlit at capture.
|
||||
def sun_dir(t):
|
||||
n = (T0 + timedelta(seconds=float(t)) - datetime(2000, 1, 1, 12, tzinfo=timezone.utc)).total_seconds() / 86400
|
||||
L = math.radians((280.46 + 0.9856474 * n) % 360)
|
||||
g = math.radians((357.528 + 0.9856003 * n) % 360)
|
||||
lam = L + math.radians(1.915) * math.sin(g) + math.radians(0.02) * math.sin(2 * g)
|
||||
eps = math.radians(23.439)
|
||||
return np.array([math.cos(lam), math.cos(eps) * math.sin(lam), math.sin(eps) * math.sin(lam)])
|
||||
|
||||
|
||||
def sunlit(t):
|
||||
r = x36_state(t)[0]
|
||||
s = sun_dir(t)
|
||||
p = np.dot(r, s)
|
||||
return p > 0 or np.linalg.norm(r - p * s) > RE
|
||||
|
||||
|
||||
lit = np.array([sunlit(t) for t in times])
|
||||
mask = (times > secs(utc('2026-10-17T06:00:00Z'))) & (times < secs(utc('2026-10-17T23:59:00Z'))) & lit
|
||||
iv = int(np.argmin(np.where(mask, np.abs(sep - 1.02), 1e9)))
|
||||
|
||||
out = dict(
|
||||
t0=T0.isoformat().replace('+00:00', 'Z'),
|
||||
dt=DT,
|
||||
a_km=A,
|
||||
x36=np.round(x36_pos, 4).ravel().tolist(),
|
||||
rel=np.round(rel, 2).ravel().tolist(),
|
||||
sep_km=np.round(sep, 4).tolist(),
|
||||
burns=burn_list,
|
||||
total_dv=round(total_dv, 1),
|
||||
ca=dict(t=ca_t, m=round(ca_m, 1), rtn=[round(v, 1) for v in ca_rtn]),
|
||||
vantor=dict(t=(T0 + timedelta(seconds=float(times[iv]))).isoformat().replace('+00:00', 'Z'),
|
||||
km=round(float(sep[iv]), 2)),
|
||||
lit=''.join('1' if v else '0' for v in lit),
|
||||
sma_offset_km=np.round(roe[:, 0] * A, 3).tolist()[::10],
|
||||
)
|
||||
json.dump(out, open(f'{HERE}/data.json', 'w'), separators=(',', ':'))
|
||||
|
||||
print(f'a = {A:.3f} km, period = {PERIOD/60:.2f} min')
|
||||
print('burns:')
|
||||
for b in burn_list:
|
||||
print(' ', b)
|
||||
print('total dv', total_dv)
|
||||
print('CA', ca_t, f'{ca_m:.0f} m', 'RTN', np.round(ca_rtn, 0))
|
||||
print('Vantor', out['vantor'])
|
||||
for iso in ['2026-10-05T12:00:00Z', '2026-10-12T00:10:00Z', '2026-10-12T06:00:00Z', '2026-10-15T00:00:00Z',
|
||||
'2026-10-15T12:00:00Z', '2026-10-16T06:00:00Z', '2026-10-16T12:00:00Z', '2026-10-17T07:45:00Z',
|
||||
'2026-10-17T17:18:00Z', '2026-10-17T20:29:00Z', '2026-10-17T23:40:00Z', '2026-10-18T07:38:00Z',
|
||||
'2026-10-19T07:32:00Z', '2026-10-21T12:00:00Z', '2026-10-22T12:00:00Z']:
|
||||
i = int(secs(utc(iso)) // DT)
|
||||
print(f' {iso} sep {sep[i]:9.2f} km rtn(km) {np.round(rel[i]/1000, 2)} dSMA {roe[i][0]*A:7.3f}')
|
||||
print('plane angle at T0 deg', plane0)
|
||||
print('target dl (Oct 21 from TLEs) km', target_dl * A, 'END_DA', END_DA)
|
||||
@@ -0,0 +1,305 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Data-driven reconstruction of the Cosmos 2612 / ICEYE-X36 encounter (v4).
|
||||
|
||||
Inputs: the public element-set history in tle/ (Space-Track catalog via the
|
||||
OMPub/RSO daily archive and jazzpi/ssa; see tle/SOURCES.md).
|
||||
|
||||
Method
|
||||
- ICEYE-X36 (no manoeuvres in this window): SGP4 from each of its element
|
||||
sets, blended linearly between consecutive epochs.
|
||||
- Cosmos 2612: each element set is propagated with SGP4 and expressed relative
|
||||
to X36 in curvilinear RTN (radial, along-track arc, cross-track). Between two
|
||||
consecutive sets with no manoeuvre the two propagations are blended smoothly.
|
||||
Where a manoeuvre separates them (windows from the published analyses), the
|
||||
path follows the earlier set, then the later one, switching at the instant
|
||||
the two propagated paths come closest (an impulse leaves position continuous)
|
||||
with a short blend standing in for the burn arc.
|
||||
- The closest approach is read off the stitched path. Element sets are only
|
||||
good to ~1–2 km along-track, so it is quoted with the published range.
|
||||
|
||||
Output: data.json (same schema as model.py) consumed by scene.js.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import numpy as np
|
||||
from sgp4.api import Satrec, jday
|
||||
|
||||
HERE = __file__.rsplit('/', 1)[0]
|
||||
MU = 398600.4418
|
||||
RE = 6378.137
|
||||
UTC = timezone.utc
|
||||
T0 = datetime(2026, 9, 29, 12, 0, tzinfo=UTC)
|
||||
T1 = datetime(2026, 10, 22, 12, 0, tzinfo=UTC)
|
||||
DT = 60
|
||||
|
||||
|
||||
def utc(s):
|
||||
return datetime.fromisoformat(s.replace('Z', '+00:00'))
|
||||
|
||||
|
||||
def load(path):
|
||||
L = [l.rstrip('\n') for l in open(path) if l.strip()]
|
||||
out = []
|
||||
for i in range(len(L) - 1):
|
||||
if L[i].startswith('1 ') and L[i + 1].startswith('2 '):
|
||||
s = Satrec.twoline2rv(L[i], L[i + 1])
|
||||
ep = datetime(2000, 1, 1, 12, tzinfo=UTC) + timedelta(days=s.jdsatepoch + s.jdsatepochF - 2451545.0)
|
||||
out.append((ep, s))
|
||||
out.sort(key=lambda x: x[0])
|
||||
# Re-issued fits of the same epoch: keep the last one.
|
||||
ded = []
|
||||
for ep, s in out:
|
||||
if ded and abs((ep - ded[-1][0]).total_seconds()) < 60:
|
||||
ded[-1] = (ep, s)
|
||||
else:
|
||||
ded.append((ep, s))
|
||||
return ded
|
||||
|
||||
|
||||
def sgp(s, d):
|
||||
jd, fr = jday(d.year, d.month, d.day, d.hour, d.minute, d.second + d.microsecond / 1e6)
|
||||
e, r, v = s.sgp4(jd, fr)
|
||||
return np.array(r), np.array(v)
|
||||
|
||||
|
||||
def sma(s):
|
||||
return (MU / (s.no_kozai / 60) ** 2) ** (1 / 3)
|
||||
|
||||
|
||||
X = load(f'{HERE}/tle/59103.tle')
|
||||
C = load(f'{HERE}/tle/68762.tle')
|
||||
|
||||
|
||||
def x36(d):
|
||||
"""X36 TEME state, blended between the bracketing element sets."""
|
||||
i = max(0, min(len(X) - 2, max(k for k in range(len(X)) if X[k][0] <= d) if d >= X[0][0] else 0))
|
||||
(ea, sa), (eb, sb) = X[i], X[i + 1]
|
||||
w = min(1, max(0, (d - ea).total_seconds() / (eb - ea).total_seconds()))
|
||||
ra, va = sgp(sa, d)
|
||||
rb, vb = sgp(sb, d)
|
||||
return ra + (rb - ra) * w, va + (vb - va) * w
|
||||
|
||||
|
||||
def frame(r, v):
|
||||
R = r / np.linalg.norm(r)
|
||||
N = np.cross(r, v)
|
||||
N /= np.linalg.norm(N)
|
||||
return R, np.cross(N, R), N
|
||||
|
||||
|
||||
A_REF = 6920.0 # km; along-track is stored as phase angle x A_REF so laps don't pick up radius wobble
|
||||
|
||||
|
||||
def rel_of(rx, vx, rc):
|
||||
"""Curvilinear RTN (m): radius difference, along-track (phase x A_REF), cross-track."""
|
||||
R, T, N = frame(rx, vx)
|
||||
th = math.atan2(np.dot(np.cross(rx, rc), N), np.dot(rx, rc))
|
||||
return np.array([np.linalg.norm(rc) - np.linalg.norm(rx), th * A_REF, np.dot(rc, N)]) * 1000
|
||||
|
||||
|
||||
def rel_from(k, d):
|
||||
rx, vx = x36(d)
|
||||
rc, _ = sgp(C[k][1], d)
|
||||
return rel_of(rx, vx, rc), rx
|
||||
|
||||
|
||||
# Manoeuvre windows between element sets (UTC). Burn time is solved inside each.
|
||||
BURNS = [
|
||||
('Phasing raise', '2026-09-30T06:00:00Z', '2026-09-30T16:00:00Z', 48),
|
||||
('Descent to X36 orbit', '2026-10-12T00:30:00Z', '2026-10-12T05:00:00Z', 48),
|
||||
('Closing burn', '2026-10-15T01:37:00Z', '2026-10-15T15:56:00Z', 20),
|
||||
('Braking burn', '2026-10-16T04:40:00Z', '2026-10-16T14:14:00Z', 20),
|
||||
('Ellipse trim', '2026-10-16T19:00:00Z', '2026-10-17T07:45:00Z', 10),
|
||||
('Back-off', '2026-10-17T22:00:00Z', '2026-10-18T05:00:00Z', 6),
|
||||
('Departure burn', '2026-10-18T12:25:00Z', '2026-10-18T23:34:00Z', 20),
|
||||
('Re-match orbit', '2026-10-19T07:32:00Z', '2026-10-20T07:26:00Z', 20),
|
||||
]
|
||||
|
||||
|
||||
def seg_index(d):
|
||||
return max(0, min(len(C) - 2, max(k for k in range(len(C)) if C[k][0] <= d) if d >= C[0][0] else 0))
|
||||
|
||||
|
||||
def unwrap_to(ref, val, circ):
|
||||
while val - ref > circ / 2:
|
||||
val -= circ
|
||||
while val - ref < -circ / 2:
|
||||
val += circ
|
||||
return val
|
||||
|
||||
|
||||
# Solve each burn's switch time: where the two neighbouring propagations meet.
|
||||
solved = []
|
||||
for name, a, b, blend_min in BURNS:
|
||||
ta, tb = utc(a), utc(b)
|
||||
k = seg_index(ta + (tb - ta) / 2)
|
||||
best = None
|
||||
t = ta
|
||||
while t <= tb:
|
||||
r1, rx = rel_from(k, t)
|
||||
r2, _ = rel_from(k + 1, t)
|
||||
circ = 2 * math.pi * A_REF * 1000
|
||||
r2[1] = unwrap_to(r1[1], r2[1], circ)
|
||||
dd = np.linalg.norm(r1 - r2)
|
||||
if best is None or dd < best[0]:
|
||||
best = (dd, t)
|
||||
t += timedelta(minutes=2)
|
||||
xs = min(X, key=lambda x: abs((x[0] - best[1]).total_seconds()))[1] if best else X[0][1]
|
||||
da0 = sma(C[k][1]) - sma(xs)
|
||||
da1 = sma(C[k + 1][1]) - sma(xs)
|
||||
di = math.degrees(C[k + 1][1].inclo - C[k][1].inclo)
|
||||
xs = min(X, key=lambda x: abs((x[0] - best[1]).total_seconds()))[1]
|
||||
da0 = sma(C[k][1]) - sma(xs)
|
||||
da1 = sma(C[k + 1][1]) - sma(xs)
|
||||
solved.append(dict(name=name, t=best[1], k=k, mismatch_m=best[0], blend=blend_min * 60, da0=da0, da1=da1, di=di))
|
||||
print(f'{name:22} switch {best[1].isoformat()[:16]} path mismatch {best[0]:8.0f} m '
|
||||
f'dSMA {da0:+8.3f} -> {da1:+8.3f} km di {di:+.3f} deg (sets {C[k][0].isoformat()[:16]} / {C[k+1][0].isoformat()[:16]})')
|
||||
|
||||
switch = {s['k']: s for s in solved}
|
||||
|
||||
|
||||
def rel_at(d):
|
||||
k = seg_index(d)
|
||||
r1, rx = rel_from(k, d)
|
||||
if k + 1 >= len(C):
|
||||
return r1, rx
|
||||
r2, _ = rel_from(k + 1, d)
|
||||
circ = 2 * math.pi * A_REF * 1000
|
||||
r2[1] = unwrap_to(r1[1], r2[1], circ)
|
||||
if k in switch:
|
||||
s = switch[k]
|
||||
x = (d - s['t']).total_seconds() / s['blend'] + 0.5
|
||||
else:
|
||||
x = (d - C[k][0]).total_seconds() / (C[k + 1][0] - C[k][0]).total_seconds()
|
||||
x = min(1, max(0, x))
|
||||
w = x * x * (3 - 2 * x)
|
||||
return r1 + (r2 - r1) * w, rx
|
||||
|
||||
|
||||
times = [T0 + timedelta(seconds=i * DT) for i in range(int((T1 - T0).total_seconds() // DT) + 1)]
|
||||
rel = []
|
||||
x36p = []
|
||||
prevT = None
|
||||
for d in times:
|
||||
r, rx = rel_at(d)
|
||||
if prevT is not None:
|
||||
r[1] = unwrap_to(prevT, r[1], 2 * math.pi * A_REF * 1000)
|
||||
prevT = r[1]
|
||||
rel.append(r)
|
||||
x36p.append(rx)
|
||||
rel = np.array(rel)
|
||||
x36p = np.array(x36p)
|
||||
A = float(np.mean([sma(s) for _, s in X]))
|
||||
|
||||
|
||||
def true_sep(i):
|
||||
rx = x36p[i]
|
||||
d = times[i]
|
||||
_, vx = x36(d)
|
||||
R, T, N = frame(rx, vx)
|
||||
th = rel[i][1] / 1000 / A_REF
|
||||
Rp = R * math.cos(th) + T * math.sin(th)
|
||||
pc = (np.linalg.norm(rx) + rel[i][0] / 1000) * Rp + rel[i][2] / 1000 * N
|
||||
return np.linalg.norm(pc - rx)
|
||||
|
||||
|
||||
sep = np.array([true_sep(i) for i in range(len(times))])
|
||||
|
||||
# Closest approach on the stitched path, refined at 5 s.
|
||||
i0 = int(np.argmin(np.where([utc('2026-10-17T12:00:00Z') < d < utc('2026-10-18T12:00:00Z') for d in times], sep, 1e9)))
|
||||
best = None
|
||||
d = times[i0] - timedelta(minutes=20)
|
||||
while d < times[i0] + timedelta(minutes=20):
|
||||
r, _ = rel_at(d)
|
||||
m = np.linalg.norm(r)
|
||||
if best is None or m < best[0]:
|
||||
best = (m, d, r)
|
||||
d += timedelta(seconds=5)
|
||||
ca_m, ca_t, ca_r = best
|
||||
r1, _ = rel_at(ca_t + timedelta(seconds=30))
|
||||
r0, _ = rel_at(ca_t - timedelta(seconds=30))
|
||||
ca_v = float(np.linalg.norm(r1 - r0) / 60)
|
||||
ecl = [times[i] for i in range(1, len(times)) if lit[i] != lit[i - 1] and abs((times[i] - ca_t).total_seconds()) < 3600] if 'lit' in dir() else []
|
||||
print(f'CA {ca_t.isoformat()[:19]}Z {ca_m:.0f} m RTN {np.round(ca_r, 0)} rel speed {ca_v:.2f} m/s')
|
||||
# Every pass within 1 km on Oct 17-18 (Cosmos may cross X36 more than once).
|
||||
passes = []
|
||||
win = [i for i, d in enumerate(times) if utc('2026-10-17T12:00:00Z') < d < utc('2026-10-18T12:00:00Z')]
|
||||
for j in win[1:-1]:
|
||||
if sep[j] < sep[j - 1] and sep[j] <= sep[j + 1] and sep[j] < 1.0:
|
||||
passes.append((times[j].isoformat()[:16], round(float(sep[j]) * 1000)))
|
||||
print('local minima < 1 km:', passes)
|
||||
|
||||
|
||||
# Sunlit flag for X36 (cylindrical shadow).
|
||||
def sun_dir(d):
|
||||
n = (d - datetime(2000, 1, 1, 12, tzinfo=UTC)).total_seconds() / 86400
|
||||
L = math.radians((280.46 + 0.9856474 * n) % 360)
|
||||
g = math.radians((357.528 + 0.9856003 * n) % 360)
|
||||
lam = L + math.radians(1.915) * math.sin(g) + math.radians(0.02) * math.sin(2 * g)
|
||||
eps = math.radians(23.439)
|
||||
return np.array([math.cos(lam), math.cos(eps) * math.sin(lam), math.sin(eps) * math.sin(lam)])
|
||||
|
||||
|
||||
def sunlit(r, d):
|
||||
s = sun_dir(d)
|
||||
p = np.dot(r, s)
|
||||
return p > 0 or np.linalg.norm(r - p * s) > RE
|
||||
|
||||
|
||||
lit = np.array([sunlit(x36p[i], times[i]) for i in range(len(times))])
|
||||
mask = np.array([utc('2026-10-17T06:00:00Z') < d < utc('2026-10-17T23:59:00Z') for d in times]) & lit
|
||||
iv = int(np.argmin(np.where(mask, np.abs(sep - 1.02), 1e9)))
|
||||
|
||||
# Burns for the HUD: time, label, Δv estimate from the element-set changes.
|
||||
V = math.sqrt(MU / A) * 1000
|
||||
burn_list = []
|
||||
for s in solved:
|
||||
dvt = V * abs(s['da1'] - s['da0']) / A / 2
|
||||
dvn = V * math.radians(abs(s['di']))
|
||||
dv = math.hypot(dvt, dvn)
|
||||
if s['name'] == 'Ellipse trim':
|
||||
dv = max(dv, 0.4)
|
||||
burn_list.append(dict(t=s['t'].isoformat().replace('+00:00', 'Z'), label=s['name'], dv=round(dv, 2)))
|
||||
# The Oct 7 out-of-plane trim (ISR: ~4 m/s, inclination +0.031 deg).
|
||||
burn_list.append(dict(t='2026-10-07T10:30:00Z', label='Plane trim', dv=4.0))
|
||||
burn_list.sort(key=lambda b: b['t'])
|
||||
total_dv = sum(b['dv'] for b in burn_list)
|
||||
|
||||
# SMA offset (km), stepping at the solved switch times.
|
||||
def da_at(d):
|
||||
k = seg_index(d)
|
||||
xs = min(X, key=lambda x: abs((x[0] - d).total_seconds()))[1]
|
||||
if k in switch and d >= switch[k]['t']:
|
||||
k += 1
|
||||
return sma(C[min(k, len(C) - 1)][1]) - sma(xs)
|
||||
|
||||
|
||||
out = dict(
|
||||
t0=T0.isoformat().replace('+00:00', 'Z'),
|
||||
dt=DT,
|
||||
a_km=A,
|
||||
a_ref_km=A_REF,
|
||||
x36=np.round(x36p, 4).ravel().tolist(),
|
||||
rel=np.round(rel, 2).ravel().tolist(),
|
||||
sep_km=np.round(sep, 4).tolist(),
|
||||
burns=burn_list,
|
||||
total_dv=round(total_dv, 1),
|
||||
ca=dict(t=ca_t.isoformat().replace('+00:00', 'Z'), m=round(ca_m, 1), rtn=[round(v, 1) for v in ca_r], v=round(ca_v, 2),
|
||||
shadow=[t.isoformat().replace('+00:00', 'Z') for t in [times[i] for i in range(1, len(times)) if lit[i] != lit[i - 1] and abs((times[i] - ca_t).total_seconds()) < 3600]]),
|
||||
passes=passes,
|
||||
vantor=dict(t=times[iv].isoformat().replace('+00:00', 'Z'), km=round(float(sep[iv]), 2)),
|
||||
lit=''.join('1' if v else '0' for v in lit),
|
||||
sma_offset_km=[round(da_at(times[i]), 3) for i in range(0, len(times), 10)],
|
||||
source='public element sets (Space-Track via OMPub/RSO, jazzpi/ssa), stitched',
|
||||
)
|
||||
json.dump(out, open(f'{HERE}/data.json', 'w'), separators=(',', ':'))
|
||||
print('burns:', [(b['t'][5:16], b['label'], b['dv']) for b in burn_list], 'total', round(total_dv, 1))
|
||||
print('vantor', out['vantor'])
|
||||
for iso in ['2026-09-30T08:00:00Z', '2026-10-05T12:00:00Z', '2026-10-12T00:10:00Z', '2026-10-13T00:00:00Z', '2026-10-15T12:00:00Z',
|
||||
'2026-10-16T12:00:00Z', '2026-10-17T07:45:00Z', '2026-10-17T17:18:00Z', '2026-10-17T23:40:00Z', '2026-10-18T01:00:00Z',
|
||||
'2026-10-18T03:00:00Z', '2026-10-18T07:38:00Z', '2026-10-19T07:32:00Z', '2026-10-21T12:00:00Z']:
|
||||
i = int((utc(iso) - T0).total_seconds() // DT)
|
||||
print(f' {iso} sep {sep[i]:10.3f} km rtn(km) {np.round(rel[i] / 1000, 3)}')
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* render.mjs — deterministic frame capture for the Cosmos 2612 / ICEYE-X36 replay.
|
||||
*
|
||||
* node tools/rpo-replay/render.mjs --format 16x9 --stills 2,9,24,32
|
||||
* node tools/rpo-replay/render.mjs --format 9x16 --from 0 --to 675
|
||||
* node tools/rpo-replay/render.mjs --format 16x9 --encode
|
||||
*
|
||||
* Frames are written to output/rpo-replay/<format>/frames/NNNNN.jpg; existing
|
||||
* frames are skipped, so interrupted renders resume. --encode muxes them to MP4.
|
||||
* Uses Playwright's Chromium with SwiftShader (no GPU needed).
|
||||
*/
|
||||
import { createServer } from 'node:http';
|
||||
import { readFile, mkdir, access } from 'node:fs/promises';
|
||||
import { extname, join, normalize } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
const HERE = fileURLToPath(new URL('.', import.meta.url));
|
||||
const ROOT = process.env.GEV_PROJECT_ROOT || join(HERE, '../..');
|
||||
const CESIUM = join(ROOT, 'node_modules/cesium/Build/Cesium');
|
||||
const SKY = join(ROOT, 'src/data/local_data/sky');
|
||||
|
||||
const args = Object.fromEntries(
|
||||
process.argv.slice(2).reduce((acc, a, i, all) => {
|
||||
if (a.startsWith('--')) acc.push([a.slice(2), all[i + 1] && !all[i + 1].startsWith('--') ? all[i + 1] : true]);
|
||||
return acc;
|
||||
}, []),
|
||||
);
|
||||
const FORMATS = { '16x9': [1920, 1080], '9x16': [1080, 1920], preview: [960, 540], 'preview-v': [540, 960] };
|
||||
const format = args.format || '16x9';
|
||||
const [W, H] = FORMATS[format];
|
||||
const FPS = Number(args.fps || 30);
|
||||
const TAG = args.tag ? String(args.tag) : '';
|
||||
const RS = Number(args.rs || 1);
|
||||
const outDir = join(ROOT, 'output/rpo-replay', TAG, format);
|
||||
const frameDir = join(outDir, 'frames');
|
||||
|
||||
const MIME = {
|
||||
'.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.json': 'application/json',
|
||||
'.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.wasm': 'application/wasm',
|
||||
'.glb': 'model/gltf-binary', '.xml': 'application/xml', '.ktx2': 'image/ktx2', '.webp': 'image/webp',
|
||||
};
|
||||
|
||||
function serve() {
|
||||
const server = createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://localhost');
|
||||
const p = decodeURIComponent(url.pathname);
|
||||
const file = p.startsWith('/cesium/')
|
||||
? join(CESIUM, normalize(p.slice('/cesium/'.length)))
|
||||
: p.startsWith('/sky/')
|
||||
? join(SKY, normalize(p.slice('/sky/'.length)))
|
||||
: join(HERE, normalize(p === '/' ? 'scene.html' : p.slice(1)));
|
||||
if (!file.startsWith(CESIUM) && !file.startsWith(SKY) && !file.startsWith(HERE)) {
|
||||
res.writeHead(403).end();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const body = await readFile(file);
|
||||
res.writeHead(200, { 'Content-Type': MIME[extname(file)] || 'application/octet-stream' });
|
||||
res.end(body);
|
||||
} catch {
|
||||
res.writeHead(404).end();
|
||||
}
|
||||
});
|
||||
return new Promise((r) => server.listen(0, '127.0.0.1', () => r(server)));
|
||||
}
|
||||
|
||||
async function exists(p) {
|
||||
try {
|
||||
await access(p);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function encode() {
|
||||
const out = join(outDir, `cosmos2612-iceye-x36-${TAG ? TAG + '-' : ''}${format}.mp4`);
|
||||
const r = spawnSync('ffmpeg', [
|
||||
'-y', '-framerate', String(FPS), '-i', join(frameDir, '%05d.jpg'),
|
||||
'-c:v', 'libx264', '-preset', 'slow', '-crf', '16', '-pix_fmt', 'yuv420p', '-movflags', '+faststart',
|
||||
'-vf', 'format=yuv420p', out,
|
||||
], { stdio: 'inherit' });
|
||||
if (r.status !== 0) process.exit(r.status || 1);
|
||||
console.log(out);
|
||||
}
|
||||
|
||||
if (args.encode) {
|
||||
encode();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const server = await serve();
|
||||
const port = server.address().port;
|
||||
const browser = await chromium.launch({
|
||||
args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader', '--ignore-gpu-blocklist',
|
||||
'--disable-gpu-sandbox', '--enable-webgl'],
|
||||
});
|
||||
const page = await browser.newPage({ viewport: { width: W, height: H }, deviceScaleFactor: 1 });
|
||||
page.on('console', (m) => {
|
||||
if (m.type() === 'error' || m.type() === 'warning') console.log(`[page ${m.type()}]`, m.text());
|
||||
});
|
||||
page.on('pageerror', (e) => console.log('[pageerror]', e.message));
|
||||
await page.goto(`http://127.0.0.1:${port}/scene.html?w=${W}&h=${H}&rs=${RS}&settle=${args.stills ? 60 : Number(args.settle || 4)}&fps=${FPS}`);
|
||||
await page.waitForFunction(() => window.rpo?.ready, null, { timeout: 180000 });
|
||||
const total = Math.round((await page.evaluate(() => window.rpo.duration)) * FPS);
|
||||
|
||||
if (args.stills) {
|
||||
const dir = join(outDir, 'stills');
|
||||
await mkdir(dir, { recursive: true });
|
||||
for (const s of String(args.stills).split(',').map(Number)) {
|
||||
const t0 = Date.now();
|
||||
const info = await page.evaluate((x) => window.rpo.frame(x), s);
|
||||
await page.screenshot({ path: join(dir, `s${s.toFixed(2).padStart(5, '0')}.jpg`), type: 'jpeg', quality: 92 });
|
||||
console.log(`still ${s}s sep=${(info.sep / 1000).toFixed(3)} km mag=${info.mag.toFixed(1)} ${Date.now() - t0} ms`);
|
||||
}
|
||||
} else {
|
||||
await mkdir(frameDir, { recursive: true });
|
||||
const from = Number(args.from || 0);
|
||||
const to = Math.min(Number(args.to || total), total);
|
||||
const t0 = Date.now();
|
||||
let done = 0;
|
||||
for (let f = from; f < to; f++) {
|
||||
const path = join(frameDir, `${String(f).padStart(5, '0')}.jpg`);
|
||||
if (await exists(path)) continue;
|
||||
const tf = Date.now();
|
||||
await page.evaluate((x) => window.rpo.frame(x), f / FPS);
|
||||
await page.screenshot({ path, type: 'jpeg', quality: 94 });
|
||||
done++;
|
||||
if (args.profile) console.log(`frame ${f} ${(Date.now() - tf) / 1000}s`);
|
||||
if (done % 25 === 0) {
|
||||
const per = (Date.now() - t0) / done;
|
||||
console.log(`${format} frame ${f}/${to} ${(per / 1000).toFixed(2)} s/frame eta ${(((to - f) * per) / 60000).toFixed(1)} min`);
|
||||
}
|
||||
}
|
||||
}
|
||||
await browser.close();
|
||||
server.close();
|
||||
@@ -0,0 +1,226 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cosmos 2612 / ICEYE-X36 replay</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;600;700&display=block" rel="stylesheet">
|
||||
<link href="/cesium/Widgets/widgets.css" rel="stylesheet">
|
||||
<style>
|
||||
/* Sizes are in --u: 1 px at 1920x1080 (landscape) or 1080x1920 (portrait).
|
||||
Phone playback is ~0.2x (16:9 in a feed) to ~0.36x (9:16 full screen);
|
||||
nothing that must be read is smaller than 24u. */
|
||||
:root {
|
||||
--x36: #5ce1e6;
|
||||
--cosmos: #ff5a3c;
|
||||
--burn: #ffb547;
|
||||
--ink: #f4f7fa;
|
||||
--ink-2: rgba(244, 247, 250, 0.78);
|
||||
--ink-3: rgba(244, 247, 250, 0.42);
|
||||
--u: 1px;
|
||||
--pad: calc(56 * var(--u));
|
||||
}
|
||||
html, body { margin: 0; height: 100%; background: #000; overflow: hidden; }
|
||||
body { font-family: Inter, system-ui, sans-serif; color: var(--ink); -webkit-font-smoothing: antialiased; }
|
||||
#globe { position: absolute; inset: 0; }
|
||||
.cesium-viewer-bottom, .cesium-widget-credits { display: none !important; }
|
||||
#hud { position: absolute; inset: 0; pointer-events: none; }
|
||||
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; font-feature-settings: 'tnum' 1; }
|
||||
.c-x36 { color: var(--x36) !important; }
|
||||
.c-cos { color: var(--cosmos) !important; }
|
||||
.shadow-text { text-shadow: 0 calc(2 * var(--u)) calc(14 * var(--u)) rgba(0,0,0,0.85), 0 0 calc(3 * var(--u)) rgba(0,0,0,0.6); }
|
||||
|
||||
#topshade { position: absolute; left: 0; right: 0; top: 0; height: calc(520 * var(--u));
|
||||
background: linear-gradient(rgba(0,0,0,0.78), rgba(0,0,0,0.35) 55%, transparent); }
|
||||
#botshade { position: absolute; left: 0; right: 0; bottom: 0; height: calc(420 * var(--u));
|
||||
background: linear-gradient(transparent, rgba(0,0,0,0.8)); }
|
||||
|
||||
/* Brand */
|
||||
#logo, #endlogo { position: absolute; display: flex; align-items: center; gap: calc(14 * var(--u)); }
|
||||
#logo { left: var(--pad); top: calc(44 * var(--u)); }
|
||||
#logo img { height: calc(58 * var(--u)); }
|
||||
#logo span, #endlogo span { white-space: nowrap; font: 800 calc(26 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.16em; }
|
||||
|
||||
/* Chapters */
|
||||
#chapters { position: absolute; left: calc(520 * var(--u)); right: calc(440 * var(--u)); top: calc(58 * var(--u));
|
||||
display: grid; grid-template-columns: repeat(5, 1fr); gap: calc(12 * var(--u)); }
|
||||
.ch i { display: block; height: calc(6 * var(--u)); background: rgba(255,255,255,0.22); border-radius: calc(3 * var(--u)); overflow: hidden; }
|
||||
.ch b { display: block; height: 100%; width: 0; background: #fff; }
|
||||
.ch span { display: block; margin-top: calc(10 * var(--u)); white-space: nowrap; font: 800 calc(26 * var(--u))/1 Inter, sans-serif;
|
||||
letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); }
|
||||
.ch.on span { color: var(--ink); }
|
||||
|
||||
/* Caption */
|
||||
#caption { position: absolute; left: var(--pad); top: calc(150 * var(--u)); width: calc(1000 * var(--u)); }
|
||||
#caption h1 { margin: 0 0 calc(14 * var(--u)); font: 800 calc(70 * var(--u))/1.04 Inter, sans-serif; letter-spacing: -0.025em; }
|
||||
#caption p { margin: 0; font: 500 calc(40 * var(--u))/1.26 Inter, sans-serif; color: var(--ink-2); max-width: calc(900 * var(--u)); }
|
||||
#caption p b { color: var(--ink); font-weight: 700; }
|
||||
#caption.hook { top: calc(170 * var(--u)); width: calc(1240 * var(--u)); }
|
||||
#caption.hook h1 { font-size: calc(84 * var(--u)); }
|
||||
#caption.hook p { font-size: calc(42 * var(--u)); max-width: calc(1100 * var(--u)); }
|
||||
|
||||
/* Events */
|
||||
#events { position: absolute; left: var(--pad); top: calc(440 * var(--u)); width: calc(900 * var(--u)); }
|
||||
.event { display: flex; gap: calc(16 * var(--u)); align-items: flex-start; margin-bottom: calc(18 * var(--u)); }
|
||||
.event i { flex: none; width: calc(18 * var(--u)); height: calc(18 * var(--u)); border-radius: 50%; margin-top: calc(8 * var(--u)); background: var(--burn);
|
||||
box-shadow: 0 0 calc(16 * var(--u)) var(--burn); }
|
||||
.event .t { font: 800 calc(34 * var(--u))/1.1 Inter, sans-serif; letter-spacing: 0.02em; text-transform: uppercase; color: var(--burn); }
|
||||
.event .sub { font: 500 calc(30 * var(--u))/1.25 Inter, sans-serif; color: var(--ink-2); margin-top: calc(4 * var(--u)); }
|
||||
.event.note i { background: #fff; box-shadow: 0 0 calc(12 * var(--u)) #fff; }
|
||||
.event.note .t { color: #fff; }
|
||||
.event.vantor i { background: #c9b8ff; box-shadow: 0 0 calc(12 * var(--u)) #c9b8ff; }
|
||||
.event.vantor .t { color: #c9b8ff; }
|
||||
.event.ca i { background: #fff; }
|
||||
.event.ca .t { color: #fff; }
|
||||
.event.shadow i { background: #9fb4ff; box-shadow: 0 0 calc(12 * var(--u)) #9fb4ff; }
|
||||
.event.shadow .t { color: #b9c8ff; }
|
||||
|
||||
/* Clock */
|
||||
#clock { position: absolute; right: var(--pad); top: calc(40 * var(--u)); text-align: right; }
|
||||
#clock .date { font: 700 calc(48 * var(--u))/1 'JetBrains Mono', monospace; letter-spacing: -0.02em; }
|
||||
#clock .time { font: 500 calc(28 * var(--u))/1 'JetBrains Mono', monospace; color: var(--ink-2); margin-top: calc(10 * var(--u)); }
|
||||
#clock .rate { display: inline-block; margin-top: calc(16 * var(--u)); padding: calc(10 * var(--u)) calc(16 * var(--u));
|
||||
font: 800 calc(34 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.02em; color: #000; background: #fff; border-radius: calc(8 * var(--u)); }
|
||||
#clock .shadow { display: none; margin-top: calc(14 * var(--u)); font: 700 calc(26 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.04em; color: #b9c8ff; }
|
||||
|
||||
/* Readout */
|
||||
#readout { position: absolute; right: var(--pad); bottom: calc(190 * var(--u)); text-align: right; }
|
||||
#readout .label { font: 800 calc(26 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.14em; color: var(--ink-2); }
|
||||
#readout .val { font: 700 calc(132 * var(--u))/0.95 'JetBrains Mono', monospace; letter-spacing: -0.05em; margin-top: calc(8 * var(--u)); }
|
||||
#readout .val small { font-size: 0.42em; color: var(--ink-2); margin-left: 0.12em; letter-spacing: 0; }
|
||||
#readout .ctx { margin-top: calc(10 * var(--u)); font: 600 calc(32 * var(--u))/1.2 Inter, sans-serif; color: var(--ink); }
|
||||
|
||||
#mag { position: absolute; left: var(--pad); bottom: calc(590 * var(--u)); padding: calc(7 * var(--u)) calc(14 * var(--u)); border-radius: calc(6 * var(--u));
|
||||
font: 800 calc(28 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.02em; color: #000; background: #fff; }
|
||||
#mag.enlarged { background: var(--burn); }
|
||||
#mag.low { bottom: calc(196 * var(--u)); }
|
||||
body.portrait #mag.low { bottom: calc(440 * var(--u)); }
|
||||
|
||||
.nw { white-space: nowrap; }
|
||||
#clock .pills { display: flex; gap: calc(10 * var(--u)); justify-content: flex-end; margin-top: calc(16 * var(--u)); }
|
||||
#clock .pills .rate { margin-top: 0; }
|
||||
#clock .ff { display: none; padding: calc(10 * var(--u)) calc(14 * var(--u)); font: 800 calc(26 * var(--u))/1.15 Inter, sans-serif;
|
||||
letter-spacing: 0.06em; text-transform: uppercase; color: #000; background: var(--burn); border-radius: calc(8 * var(--u)); }
|
||||
#clock .light { display: inline-flex; align-items: center; gap: calc(10 * var(--u)); margin-top: calc(14 * var(--u));
|
||||
font: 800 calc(26 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.04em; text-transform: uppercase; }
|
||||
#clock .light i { width: calc(18 * var(--u)); height: calc(18 * var(--u)); border-radius: 50%; }
|
||||
#clock .light.day { color: #ffe08a; } #clock .light.day i { background: #ffd34d; box-shadow: 0 0 calc(14 * var(--u)) #ffd34d; }
|
||||
#clock .light.night { color: #b9c8ff; } #clock .light.night i { background: #20263a; border: calc(3 * var(--u)) solid #b9c8ff; }
|
||||
#readout .rel { margin-top: calc(6 * var(--u)); font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); }
|
||||
/* Inset + chips */
|
||||
#inset { position: absolute; left: var(--pad); bottom: calc(196 * var(--u)); width: calc(640 * var(--u)); height: calc(380 * var(--u)); }
|
||||
#relchip { position: absolute; left: calc(56 * var(--u) + 536 * var(--u)); bottom: calc(196 * var(--u)); padding: calc(7 * var(--u)) calc(14 * var(--u));
|
||||
border-radius: calc(6 * var(--u)); font: 800 calc(26 * var(--u))/1 Inter, sans-serif; color: #fff; background: rgba(255,90,60,0.85); opacity: 0; }
|
||||
.event { background: rgba(4,8,14,0.62); padding: calc(10 * var(--u)) calc(16 * var(--u)) calc(10 * var(--u)) calc(14 * var(--u)); border-radius: calc(8 * var(--u)); width: fit-content; }
|
||||
/* Timeline */
|
||||
#tlwrap { position: absolute; left: var(--pad); right: var(--pad); bottom: calc(52 * var(--u)); }
|
||||
#tlwrap .lab { font: 700 calc(24 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.1em; color: var(--ink-3); margin-bottom: calc(8 * var(--u)); }
|
||||
#tlwrap .lab span { color: var(--burn); } #tlwrap .lab b { color: var(--ink-2); font-weight: 700; }
|
||||
#timeline { height: calc(70 * var(--u)); }
|
||||
#timeline svg { width: 100%; height: 100%; overflow: visible; font-family: 'JetBrains Mono', monospace; font-weight: 600; }
|
||||
#interstitial { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; opacity: 0;
|
||||
background: radial-gradient(ellipse at center, rgba(0,0,0,0.55), rgba(0,0,0,0.15) 70%); }
|
||||
#interstitial .k { font: 800 calc(30 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.3em; text-transform: uppercase; color: var(--ink-2); }
|
||||
#interstitial .d { margin-top: calc(18 * var(--u)); font: 700 calc(110 * var(--u))/1 'JetBrains Mono', monospace; letter-spacing: -0.03em; }
|
||||
/* Chart */
|
||||
#chartwrap { position: absolute; left: var(--pad); right: var(--pad); bottom: calc(58 * var(--u)); }
|
||||
#chartwrap .lab { font: 700 calc(24 * var(--u))/1 Inter, sans-serif; letter-spacing: 0.12em; color: var(--ink-3); margin-bottom: calc(10 * var(--u)); }
|
||||
#chart { height: calc(170 * var(--u)); }
|
||||
#chart svg { width: 100%; height: 100%; overflow: visible; font-family: 'JetBrains Mono', monospace; font-weight: 600; }
|
||||
|
||||
/* Tags + dimension */
|
||||
#leaders { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.tag { position: absolute; white-space: nowrap; }
|
||||
.tag { background: rgba(3,6,12,0.55); padding: calc(6 * var(--u)) calc(12 * var(--u)); border-radius: calc(8 * var(--u)); }
|
||||
.tag .name { font: 800 calc(40 * var(--u))/1.05 Inter, sans-serif; letter-spacing: 0.01em; }
|
||||
.tag .meta { font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); margin-top: calc(4 * var(--u)); }
|
||||
#dim { position: absolute; transform: translate(-50%, -50%); padding: calc(6 * var(--u)) calc(14 * var(--u)); white-space: nowrap;
|
||||
font: 700 calc(34 * var(--u))/1 'JetBrains Mono', monospace; color: #000; background: #fff; border-radius: calc(6 * var(--u)); }
|
||||
|
||||
/* Attribution: present, deliberately quiet */
|
||||
#credits { position: absolute; left: var(--pad); right: var(--pad); bottom: calc(16 * var(--u));
|
||||
font: 400 calc(17 * var(--u))/1.3 Inter, sans-serif; color: rgba(244,247,250,0.45); white-space: nowrap; overflow: hidden; }
|
||||
|
||||
/* End card */
|
||||
#endcard { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
|
||||
#endcard .panel { width: calc(1300 * var(--u)); text-align: center; }
|
||||
#endcard .big { font: 800 calc(200 * var(--u))/0.9 Inter, sans-serif; letter-spacing: -0.04em; color: var(--cosmos); }
|
||||
#endcard .range { margin-top: calc(18 * var(--u)); font: 600 calc(32 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); }
|
||||
#endcard .lead { margin: calc(26 * var(--u)) auto 0; max-width: calc(1100 * var(--u)); font: 700 calc(44 * var(--u))/1.2 Inter, sans-serif; letter-spacing: -0.01em; }
|
||||
#endcard .stats { display: flex; justify-content: center; gap: calc(80 * var(--u)); margin: calc(44 * var(--u)) 0; }
|
||||
#endcard .stats b { display: block; font: 700 calc(64 * var(--u))/1 'JetBrains Mono', monospace; letter-spacing: -0.03em; }
|
||||
#endcard .stats span { display: block; margin-top: calc(10 * var(--u)); font: 600 calc(28 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); }
|
||||
#endcard .why { max-width: calc(1100 * var(--u)); margin: 0 auto; font: 500 calc(32 * var(--u))/1.35 Inter, sans-serif; color: var(--ink-2); }
|
||||
#endcard .kicker { font: 800 calc(30 * var(--u))/1.2 Inter, sans-serif; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-2); }
|
||||
#endcard .quote { margin-top: calc(26 * var(--u)); font: 800 calc(76 * var(--u))/1.08 Inter, sans-serif; letter-spacing: -0.02em; }
|
||||
#endcard .who { margin-top: calc(18 * var(--u)); font: 600 calc(32 * var(--u))/1.2 Inter, sans-serif; color: var(--ink-2); }
|
||||
#endcard .still { margin-top: calc(34 * var(--u)); font: 700 calc(38 * var(--u))/1.25 Inter, sans-serif; color: var(--ink); }
|
||||
#endcard .statline { margin-top: calc(30 * var(--u)); font: 500 calc(22 * var(--u))/1.4 'JetBrains Mono', monospace; color: var(--ink-3); }
|
||||
body.portrait #endcard .quote { font-size: calc(72 * var(--u)); }
|
||||
body.portrait #endcard .still { font-size: calc(40 * var(--u)); }
|
||||
body.portrait #endcard .statline { font-size: calc(24 * var(--u)); }
|
||||
#endlogo { left: 50%; top: calc(70 * var(--u)); transform: translateX(-50%); }
|
||||
#endlogo img { height: calc(96 * var(--u)); }
|
||||
#endlogo span { font-size: calc(40 * var(--u)); }
|
||||
|
||||
/* ---------------- Portrait (Reels/Shorts/TikTok safe zones: top ~170, bottom ~400) ---------------- */
|
||||
body.portrait { --pad: calc(52 * var(--u)); }
|
||||
body.portrait #logo { top: calc(168 * var(--u)); }
|
||||
body.portrait #clock { top: calc(160 * var(--u)); }
|
||||
body.portrait #clock .date { font-size: calc(44 * var(--u)); }
|
||||
body.portrait #chapters { left: var(--pad); right: var(--pad); top: calc(318 * var(--u)); }
|
||||
body.portrait .ch span { font-size: calc(22 * var(--u)); letter-spacing: 0.02em; }
|
||||
body.portrait #caption, body.portrait #caption.hook { top: calc(392 * var(--u)); width: auto; right: var(--pad); }
|
||||
body.portrait #caption h1 { font-size: calc(68 * var(--u)); }
|
||||
body.portrait #caption.hook h1 { font-size: calc(76 * var(--u)); }
|
||||
body.portrait #caption p, body.portrait #caption.hook p { font-size: calc(38 * var(--u)); max-width: none; }
|
||||
body.portrait #events { top: calc(700 * var(--u)); width: auto; right: var(--pad); }
|
||||
body.portrait #readout { right: auto; left: var(--pad); bottom: calc(420 * var(--u)); text-align: left; }
|
||||
body.portrait #readout .val { font-size: calc(104 * var(--u)); }
|
||||
body.portrait #mag { left: auto; right: var(--pad); bottom: calc(934 * var(--u)); }
|
||||
body.portrait #tlwrap { display: none; }
|
||||
body.portrait #inset { left: auto; right: var(--pad); bottom: calc(420 * var(--u)); width: calc(620 * var(--u)); height: calc(500 * var(--u)); }
|
||||
body.portrait #relchip { left: auto; right: var(--pad); bottom: calc(866 * var(--u)); }
|
||||
body.portrait #readout { max-width: calc(340 * var(--u)); }
|
||||
body.portrait #readout .light { margin: 0 0 calc(18 * var(--u)); display: inline-flex; }
|
||||
body.portrait .ch span { font-size: calc(24 * var(--u)); }
|
||||
body.portrait #interstitial .d { font-size: calc(120 * var(--u)); }
|
||||
body.portrait #topshade { height: calc(1000 * var(--u)); }
|
||||
body.portrait #botshade { height: calc(1200 * var(--u)); background: linear-gradient(transparent, rgba(0,0,0,0.78) 45%, rgba(0,0,0,0.85)); }
|
||||
body.portrait #credits { white-space: normal; bottom: calc(330 * var(--u)); font-size: calc(17 * var(--u)); }
|
||||
body.portrait #endcard .panel { width: calc(960 * var(--u)); }
|
||||
body.portrait #endcard .big { font-size: calc(210 * var(--u)); }
|
||||
body.portrait #endcard .lead { font-size: calc(46 * var(--u)); }
|
||||
body.portrait #endcard .stats { gap: calc(40 * var(--u)); }
|
||||
body.portrait #endcard .stats b { font-size: calc(58 * var(--u)); }
|
||||
body.portrait #endlogo { top: calc(260 * var(--u)); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="globe"></div>
|
||||
<div id="hud">
|
||||
<div id="topshade"></div>
|
||||
<div id="botshade"></div>
|
||||
<svg id="leaders"></svg>
|
||||
<div id="tags" class="shadow-text"></div>
|
||||
<div id="dim"></div>
|
||||
<div id="logo" class="shadow-text"><img src="logo.svg" alt=""><span>GOD'S EYE VIEW</span></div>
|
||||
<div id="chapters" class="shadow-text"></div>
|
||||
<div id="caption" class="shadow-text"><h1></h1><p></p></div>
|
||||
<div id="events" class="shadow-text"></div>
|
||||
<div id="clock" class="shadow-text"><div class="date"></div><div class="time"></div><div class="pills"><div class="ff">Fast-forward · Earth slowed</div><div class="rate"></div></div><div class="light"></div></div>
|
||||
<div id="readout" class="shadow-text"><div class="label">DISTANCE APART</div><div class="val"></div><div class="ctx"></div><div class="rel"></div></div>
|
||||
<div id="mag" class="shadow-text"></div>
|
||||
<div id="tlwrap"><div id="timeline"></div></div>
|
||||
<canvas id="inset"></canvas>
|
||||
<div id="relchip">Red path: Cosmos as seen from X36</div>
|
||||
<div id="interstitial"><div class="k">18 days earlier</div><div class="d">SEP 30, 2026</div></div>
|
||||
<div id="endcard"></div>
|
||||
<div id="endlogo"><img src="logo.svg" alt=""><span>GOD'S EYE VIEW</span></div>
|
||||
<div id="credits">Reconstruction from public Space-Track element sets (41 Cosmos 2612, 54 ICEYE-X36), not tracking data · Analyses: LeoLabs, KeepTrack, M. Langbroek, COMSPOC, Integrity ISR · Imagery: Esri, Maxar, Earthstar Geographics, NASA · Sky: ESO/S. Brunier (CC BY 4.0), Yale BSC</div>
|
||||
<div id="dip" style="position:absolute;inset:0;background:#000;opacity:0"></div>
|
||||
</div>
|
||||
<script src="/cesium/Cesium.js"></script>
|
||||
<script type="module" src="scene.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,687 @@
|
||||
/* global Cesium */
|
||||
// Deterministic frame renderer for the Cosmos 2612 / ICEYE-X36 replay.
|
||||
// window.rpo.frame(seconds) sets every piece of state for one video instant,
|
||||
// renders, and resolves once imagery and models have settled.
|
||||
|
||||
import { createHud } from './hud.js';
|
||||
import { buildStory } from './story.js';
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const W = Number(params.get('w') || 1920);
|
||||
const H = Number(params.get('h') || 1080);
|
||||
const PORTRAIT = H > W;
|
||||
const U = PORTRAIT ? W / 1080 : W / 1920;
|
||||
document.documentElement.style.setProperty('--u', `${U}px`);
|
||||
if (PORTRAIT) document.body.classList.add('portrait');
|
||||
|
||||
const D = await (await fetch('data.json')).json();
|
||||
const T0 = Date.parse(D.t0);
|
||||
const DT = D.dt;
|
||||
const NS = D.sep_km.length;
|
||||
const sec = (iso) => (Date.parse(iso) - T0) / 1000;
|
||||
const FPS = Number(params.get('fps') || 30);
|
||||
const SHUTTER = 0.5; // 180° shutter for motion blur
|
||||
const SETTLE = Number(params.get('settle') || 4);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interpolation
|
||||
// ---------------------------------------------------------------------------
|
||||
function cr(arr, t, out, scale = 1) {
|
||||
const x = Math.min(Math.max(t / DT, 1), NS - 3);
|
||||
const i = Math.floor(x);
|
||||
const u = x - i;
|
||||
const u2 = u * u;
|
||||
const u3 = u2 * u;
|
||||
for (let k = 0; k < 3; k++) {
|
||||
const p0 = arr[(i - 1) * 3 + k];
|
||||
const p1 = arr[i * 3 + k];
|
||||
const p2 = arr[(i + 1) * 3 + k];
|
||||
const p3 = arr[(i + 2) * 3 + k];
|
||||
out[k] =
|
||||
0.5 * scale *
|
||||
(2 * p1 + (-p0 + p2) * u + (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 + (-p0 + 3 * p1 - 3 * p2 + p3) * u3);
|
||||
if (out.length > 3) {
|
||||
out[3 + k] = (0.5 * scale * ((-p0 + p2) + 2 * (2 * p0 - 5 * p1 + 4 * p2 - p3) * u + 3 * (-p0 + 3 * p1 - 3 * p2 + p3) * u2)) / DT;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const v3 = Cesium.Cartesian3;
|
||||
const tmp6 = new Float64Array(6);
|
||||
function x36Teme(t) {
|
||||
cr(D.x36, t, tmp6, 1000);
|
||||
return { r: new v3(tmp6[0], tmp6[1], tmp6[2]), v: new v3(tmp6[3], tmp6[4], tmp6[5]) };
|
||||
}
|
||||
const tmp3 = new Float64Array(3);
|
||||
function relAt(t) {
|
||||
cr(D.rel, t, tmp3, 1);
|
||||
return [tmp3[0], tmp3[1], tmp3[2]];
|
||||
}
|
||||
function sepAt(t) {
|
||||
const x = Math.min(Math.max(t / DT, 0), NS - 2);
|
||||
const i = Math.floor(x);
|
||||
return D.sep_km[i] + (D.sep_km[i + 1] - D.sep_km[i]) * (x - i);
|
||||
}
|
||||
function litAt(t) {
|
||||
return D.lit[Math.round(Math.min(Math.max(t / DT, 0), NS - 1))] === '1';
|
||||
}
|
||||
function basis(r, v) {
|
||||
const R = v3.normalize(r, new v3());
|
||||
const N = v3.normalize(v3.cross(r, v, new v3()), new v3());
|
||||
const T = v3.cross(N, R, new v3());
|
||||
return { R, T, N };
|
||||
}
|
||||
// Curvilinear placement: along-track offset is an arc on the anchor orbit.
|
||||
function place(anchor, B, rel) {
|
||||
const rm = v3.magnitude(anchor);
|
||||
const th = rel[1] / (D.a_ref_km * 1000);
|
||||
const c = Math.cos(th);
|
||||
const s = Math.sin(th);
|
||||
const Rp = new v3(B.R.x * c + B.T.x * s, B.R.y * c + B.T.y * s, B.R.z * c + B.T.z * s);
|
||||
const out = v3.multiplyByScalar(Rp, rm + rel[0], new v3());
|
||||
return v3.add(out, v3.multiplyByScalar(B.N, rel[2], new v3()), out);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Time maps. tau(s): simulation clock. b(s): backdrop/anchor clock (Earth and
|
||||
// lighting), equal to tau around closest approach.
|
||||
// ---------------------------------------------------------------------------
|
||||
const CA_T = sec(D.ca.t);
|
||||
const VANTOR_T = sec(D.vantor.t);
|
||||
|
||||
function monotone(keys) {
|
||||
const n = keys.length;
|
||||
const xs = keys.map((k) => k[0]);
|
||||
const ys = keys.map((k) => k[1]);
|
||||
const d = [];
|
||||
for (let i = 0; i < n - 1; i++) d.push((ys[i + 1] - ys[i]) / (xs[i + 1] - xs[i]));
|
||||
// Geometric-mean slopes keep huge rate swings smooth in log-space.
|
||||
const m = [d[0]];
|
||||
for (let i = 1; i < n - 1; i++) m.push(Math.sqrt(d[i - 1] * d[i]));
|
||||
m.push(d[n - 2]);
|
||||
for (let i = 0; i < n - 1; i++) {
|
||||
const a = m[i] / d[i];
|
||||
const b = m[i + 1] / d[i];
|
||||
const h = a * a + b * b;
|
||||
if (h > 9) {
|
||||
const t = 3 / Math.sqrt(h);
|
||||
m[i] = t * a * d[i];
|
||||
m[i + 1] = t * b * d[i];
|
||||
}
|
||||
}
|
||||
return (s) => {
|
||||
let i = 0;
|
||||
while (i < n - 2 && s > xs[i + 1]) i++;
|
||||
const hh = xs[i + 1] - xs[i];
|
||||
const t = Math.min(Math.max((s - xs[i]) / hh, 0), 1);
|
||||
const t2 = t * t;
|
||||
const t3 = t2 * t;
|
||||
return (2 * t3 - 3 * t2 + 1) * ys[i] + (t3 - 2 * t2 + t) * hh * m[i] + (-2 * t3 + 3 * t2) * ys[i + 1] + (t3 - t2) * hh * m[i + 1];
|
||||
};
|
||||
}
|
||||
|
||||
// Shots own their clocks (see story.js). tau = simulation time; bclock = the
|
||||
// clock the Earth, sun and X36's place on its orbit follow (== tau in `true` shots).
|
||||
const STORY = buildStory({ sec, D });
|
||||
const SHOTS = STORY.shots.map((sh) => ({ ...sh, tauF: monotone(sh.tau) }));
|
||||
const DUR = STORY.duration;
|
||||
const CUTS = SHOTS.slice(1).map((sh) => sh.s0);
|
||||
function shotAt(s) {
|
||||
let k = 0;
|
||||
for (let i = 0; i < SHOTS.length; i++) if (s >= SHOTS[i].s0) k = i;
|
||||
return SHOTS[k];
|
||||
}
|
||||
const tau = (s) => shotAt(s).tauF(s);
|
||||
function rate(s) {
|
||||
const sh = shotAt(s);
|
||||
const a = Math.max(sh.s0, s - 0.02);
|
||||
const b = Math.min(sh.s1 - 1e-4, s + 0.02);
|
||||
return (sh.tauF(b) - sh.tauF(a)) / (b - a);
|
||||
}
|
||||
function bclock(s) {
|
||||
const sh = shotAt(s);
|
||||
return sh.mode === 'true' ? sh.tauF(s) : sh.tauF(sh.s0) + (s - sh.s0) * (sh.bgRate || 140);
|
||||
}
|
||||
// Video time at which simulation time t is shown (skipping the cold open).
|
||||
function sOfTau(t) {
|
||||
for (let i = SHOTS.length - 1; i >= 1; i--) {
|
||||
const sh = SHOTS[i];
|
||||
const a = sh.tauF(sh.s0);
|
||||
const b = sh.tauF(sh.s1);
|
||||
if (t >= a && t <= b) {
|
||||
let lo = sh.s0;
|
||||
let hi = sh.s1;
|
||||
for (let k = 0; k < 40; k++) {
|
||||
const mid = (lo + hi) / 2;
|
||||
if (sh.tauF(mid) < t) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
return lo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Story: captions, events, camera
|
||||
// ---------------------------------------------------------------------------
|
||||
const BURNS = D.burns.map((b) => ({ ...b, t: sec(b.t) }));
|
||||
|
||||
const smooth = (x) => x * x * x * (x * (x * 6 - 15) + 10);
|
||||
function camAt(s) {
|
||||
const keys = shotAt(s).cam;
|
||||
let i = 0;
|
||||
while (i < keys.length - 2 && s > keys[i + 1].s) i++;
|
||||
const a = keys[i];
|
||||
const b = keys[Math.min(i + 1, keys.length - 1)];
|
||||
const u = b === a ? 0 : smooth(Math.min(Math.max((s - a.s) / (b.s - a.s), 0), 1));
|
||||
const lerp = (x, y) => x + (y - x) * u;
|
||||
const lg = (x, y) => Math.exp(lerp(Math.log(x), Math.log(y)));
|
||||
const ar = a.aimRel || [0, 0, 0];
|
||||
const br = b.aimRel || [0, 0, 0];
|
||||
return {
|
||||
frame: a.frame || 'rtn',
|
||||
dist: lg(a.dist, b.dist),
|
||||
f: lerp(a.f || 0, b.f || 0),
|
||||
az: lerp(a.az, b.az),
|
||||
el: lerp(a.el, b.el),
|
||||
look: lerp(a.look || 0, b.look || 0),
|
||||
earth: lerp(a.earth || 0, b.earth || 0),
|
||||
fov: lerp(a.fov, b.fov),
|
||||
mag: lerp(a.mag || 0, b.mag || 0),
|
||||
mx: lg(a.mx || 1, b.mx || 1),
|
||||
rtrail: lerp(a.rtrail || 0, b.rtrail || 0),
|
||||
atrail: lerp(a.atrail || 0, b.atrail || 0),
|
||||
sun: lerp(a.sun || 0, b.sun || 0),
|
||||
vup: lerp(a.vup || 0, b.vup || 0),
|
||||
comp: lerp(a.comp ?? 1, b.comp ?? 1),
|
||||
compY: lerp(a.compY ?? 0, b.compY ?? 0) + (PORTRAIT ? 0 : lerp(a.compYL ?? 0, b.compYL ?? 0)),
|
||||
pairD: lerp(a.pairD || 0, b.pairD || 0),
|
||||
pairTh: lerp(a.pairTh || 0, b.pairTh || 0),
|
||||
aimRel: [0, 1, 2].map((k) => lerp(ar[k], br[k])),
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cesium scene
|
||||
// ---------------------------------------------------------------------------
|
||||
const esri = await Cesium.ArcGisMapServerImageryProvider.fromUrl(
|
||||
'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer',
|
||||
{ enablePickFeatures: false, maximumLevel: 9 },
|
||||
);
|
||||
const viewer = new Cesium.Viewer('globe', {
|
||||
baseLayer: new Cesium.ImageryLayer(esri, { brightness: 0.92, contrast: 1.08, gamma: 1.05 }),
|
||||
animation: false, timeline: false, baseLayerPicker: false, geocoder: false, homeButton: false,
|
||||
sceneModePicker: false, navigationHelpButton: false, fullscreenButton: false, infoBox: false,
|
||||
selectionIndicator: false, useDefaultRenderLoop: false, shouldAnimate: false,
|
||||
contextOptions: { webgl: { preserveDrawingBuffer: true, alpha: false } },
|
||||
msaaSamples: 1,
|
||||
});
|
||||
const scene = viewer.scene;
|
||||
// Motion-blur accumulator: sub-frames are averaged here, above the WebGL canvas.
|
||||
const accum = document.createElement('canvas');
|
||||
accum.width = W;
|
||||
accum.height = H;
|
||||
accum.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;visibility:hidden';
|
||||
document.getElementById('globe').appendChild(accum);
|
||||
const actx = accum.getContext('2d');
|
||||
// Night side: NASA Black Marble city lights (keyless GIBS tiles).
|
||||
const nightLayer = viewer.imageryLayers.addImageryProvider(new Cesium.UrlTemplateImageryProvider({
|
||||
url: 'https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/VIIRS_Black_Marble/default/2016-01-01/GoogleMapsCompatible_Level8/{z}/{y}/{x}.png',
|
||||
maximumLevel: 8, credit: 'NASA Black Marble',
|
||||
}));
|
||||
nightLayer.dayAlpha = 0.0;
|
||||
nightLayer.nightAlpha = 1.0;
|
||||
nightLayer.brightness = 1.6;
|
||||
viewer.imageryLayers.get(0).nightAlpha = 0.0;
|
||||
// Sky: ESO Milky Way cube (stars removed) + Yale Bright Star Catalogue points.
|
||||
scene.skyBox = new Cesium.SkyBox({ sources: Object.fromEntries(
|
||||
[['positiveX', 'px'], ['negativeX', 'nx'], ['positiveY', 'py'], ['negativeY', 'ny'], ['positiveZ', 'pz'], ['negativeZ', 'nz']]
|
||||
.map(([k, f]) => [k, `/sky/milkyway_${f}.webp`])) });
|
||||
const starData = await (await fetch('/sky/bright_stars.json')).json();
|
||||
const starArr = Array.isArray(starData) ? starData : starData.stars || starData.data;
|
||||
const stars = scene.primitives.add(new Cesium.PointPrimitiveCollection({ blendOption: Cesium.BlendOption.TRANSLUCENT }));
|
||||
const starDirs = [];
|
||||
for (let i = 0; i + 3 < starArr.length; i += 4) {
|
||||
const [ra, dec, vm, bv] = [starArr[i], starArr[i + 1], starArr[i + 2], starArr[i + 3]];
|
||||
const r = Cesium.Math.toRadians(ra);
|
||||
const d = Cesium.Math.toRadians(dec);
|
||||
const dir = new Cesium.Cartesian3(Math.cos(d) * Math.cos(r), Math.cos(d) * Math.sin(r), Math.sin(d));
|
||||
const b = Math.min(1, Math.sqrt(10 ** (-0.4 * ((Number.isFinite(vm) ? vm : 6.5) - 6.5))) / 9);
|
||||
const t = Math.min(Math.max(((Number.isFinite(bv) ? bv : 0.6) + 0.3) / 2.0, 0), 1);
|
||||
const col = new Cesium.Color(0.78 + 0.22 * t, 0.84 + 0.06 * (1 - Math.abs(t - 0.4)), 1.0 - 0.3 * t, 0.25 + 0.75 * b);
|
||||
starDirs.push(dir);
|
||||
stars.add({ position: dir, color: col, pixelSize: 0.9 + 2.6 * b });
|
||||
}
|
||||
|
||||
const globe = scene.globe;
|
||||
globe.enableLighting = true;
|
||||
globe.dynamicAtmosphereLighting = true;
|
||||
globe.dynamicAtmosphereLightingFromSun = true;
|
||||
globe.showGroundAtmosphere = true;
|
||||
globe.atmosphereLightIntensity = 9;
|
||||
globe.lightingFadeOutDistance = 1;
|
||||
globe.lightingFadeInDistance = 2;
|
||||
globe.nightFadeOutDistance = 1e10;
|
||||
globe.nightFadeInDistance = 1e10;
|
||||
globe.maximumScreenSpaceError = 2.2;
|
||||
globe.tileCacheSize = 1500;
|
||||
globe.preloadSiblings = false;
|
||||
globe.baseColor = Cesium.Color.fromCssColorString('#04070d');
|
||||
scene.fog.enabled = false;
|
||||
scene.skyAtmosphere.show = true;
|
||||
scene.skyAtmosphere.atmosphereLightIntensity = 10;
|
||||
scene.highDynamicRange = false;
|
||||
scene.sun.glowFactor = 1.6;
|
||||
scene.moon.show = true;
|
||||
scene.postProcessStages.fxaa.enabled = true;
|
||||
scene.backgroundColor = Cesium.Color.BLACK;
|
||||
scene.screenSpaceCameraController.enableInputs = false;
|
||||
scene.camera.frustum.near = 0.5;
|
||||
scene.camera.frustum.far = 1e9;
|
||||
viewer.resolutionScale = Number(params.get('rs') || 1);
|
||||
const bloom = scene.postProcessStages.bloom;
|
||||
bloom.enabled = false;
|
||||
bloom.uniforms.contrast = 119;
|
||||
bloom.uniforms.brightness = -0.42;
|
||||
bloom.uniforms.glowOnly = false;
|
||||
bloom.uniforms.delta = 1.0;
|
||||
bloom.uniforms.sigma = 3.2;
|
||||
bloom.uniforms.stepSize = 1.0;
|
||||
|
||||
const COL_X36 = Cesium.Color.fromCssColorString('#5ce1e6');
|
||||
const COL_COS = Cesium.Color.fromCssColorString('#ff5a3c');
|
||||
const COL_BURN = Cesium.Color.fromCssColorString('#ffb547');
|
||||
|
||||
const modelOpts = { upAxis: Cesium.Axis.Z, forwardAxis: Cesium.Axis.X, shadows: Cesium.ShadowMode.DISABLED };
|
||||
const mX36 = await Cesium.Model.fromGltfAsync({ url: 'iceye-x36.glb', ...modelOpts });
|
||||
const mCos = await Cesium.Model.fromGltfAsync({ url: 'cosmos-2612.glb', ...modelOpts });
|
||||
scene.primitives.add(mX36);
|
||||
scene.primitives.add(mCos);
|
||||
for (const m of [mX36, mCos]) {
|
||||
m.environmentMapOptions = { enabled: false };
|
||||
m.imageBasedLighting.sphericalHarmonicCoefficients = undefined;
|
||||
}
|
||||
|
||||
function glowCanvas(color, core = 0.18) {
|
||||
const c = document.createElement('canvas');
|
||||
c.width = c.height = 128;
|
||||
const g = c.getContext('2d');
|
||||
const grd = g.createRadialGradient(64, 64, 0, 64, 64, 64);
|
||||
grd.addColorStop(0, 'rgba(255,255,255,1)');
|
||||
grd.addColorStop(core, color.withAlpha(1).toCssColorString());
|
||||
grd.addColorStop(0.45, color.withAlpha(0.28).toCssColorString());
|
||||
grd.addColorStop(1, color.withAlpha(0).toCssColorString());
|
||||
g.fillStyle = grd;
|
||||
g.fillRect(0, 0, 128, 128);
|
||||
return c;
|
||||
}
|
||||
const billboards = scene.primitives.add(new Cesium.BillboardCollection({ scene }));
|
||||
const bX36 = billboards.add({ image: glowCanvas(COL_X36), scale: 0.3 * U, disableDepthTestDistance: 0 });
|
||||
const bCos = billboards.add({ image: glowCanvas(COL_COS), scale: 0.3 * U, disableDepthTestDistance: 0 });
|
||||
const bBurn = billboards.add({ image: glowCanvas(COL_BURN, 0.08), scale: 0, disableDepthTestDistance: 0 });
|
||||
|
||||
let dynamic = [];
|
||||
function clearDynamic() {
|
||||
for (const p of dynamic) scene.primitives.remove(p);
|
||||
dynamic = [];
|
||||
}
|
||||
function polyline(positions, colors, width) {
|
||||
if (positions.length < 2) return;
|
||||
const p = new Cesium.Primitive({
|
||||
geometryInstances: new Cesium.GeometryInstance({
|
||||
geometry: new Cesium.PolylineGeometry({
|
||||
positions, colors, colorsPerVertex: true, width: width * U, arcType: Cesium.ArcType.NONE,
|
||||
}),
|
||||
}),
|
||||
appearance: new Cesium.PolylineColorAppearance({ translucent: true }),
|
||||
asynchronous: false,
|
||||
});
|
||||
scene.primitives.add(p);
|
||||
dynamic.push(p);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HUD
|
||||
// ---------------------------------------------------------------------------
|
||||
function project(world) {
|
||||
const p = Cesium.SceneTransforms.worldToWindowCoordinates(scene, world);
|
||||
if (!p) return null;
|
||||
// Behind-camera / occluded test
|
||||
const cam = scene.camera;
|
||||
const d = v3.subtract(world, cam.positionWC, new v3());
|
||||
if (v3.dot(d, cam.directionWC) <= 0) return null;
|
||||
const ray = v3.normalize(d, new v3());
|
||||
const hit = Cesium.IntersectionTests.rayEllipsoid(new Cesium.Ray(cam.positionWC, ray), Cesium.Ellipsoid.WGS84);
|
||||
if (hit && hit.start * 1.0005 < v3.magnitude(d)) return { ...p, hidden: true };
|
||||
return p;
|
||||
}
|
||||
const hud = createHud({
|
||||
D, W, H, U, PORTRAIT, sec, rate, sOfTau, sepAt, CA_T, VANTOR_T, BURNS, CUTS, SHOTS, shotAt, DUR, project, relAt,
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frame
|
||||
// ---------------------------------------------------------------------------
|
||||
const M = new Cesium.Matrix3();
|
||||
function toWorld(teme, jd) {
|
||||
return Cesium.Matrix3.multiplyByVector(M, teme, new v3());
|
||||
}
|
||||
|
||||
function modelMatrix(pos, X, Y, scale) {
|
||||
const Z = v3.cross(X, Y, new v3());
|
||||
const m = new Cesium.Matrix4(
|
||||
X.x * scale, Y.x * scale, Z.x * scale, pos.x,
|
||||
X.y * scale, Y.y * scale, Z.y * scale, pos.y,
|
||||
X.z * scale, Y.z * scale, Z.z * scale, pos.z,
|
||||
0, 0, 0, 1,
|
||||
);
|
||||
return m;
|
||||
}
|
||||
|
||||
function eclipseLight(worldPos, sunW) {
|
||||
// Cylindrical shadow with a soft edge.
|
||||
const p = v3.dot(worldPos, sunW);
|
||||
if (p > 0) return 1;
|
||||
const perp = v3.magnitude(v3.subtract(worldPos, v3.multiplyByScalar(sunW, p, new v3()), new v3()));
|
||||
const e = (perp - 6.371e6) / 6e4;
|
||||
return Math.min(1, Math.max(0, 0.5 + e));
|
||||
}
|
||||
|
||||
// Orbital speed from vis-viva, using each satellite's own SMA (m/s).
|
||||
const MU = 3.986004418e14;
|
||||
function smaOffsetAt(t) {
|
||||
const arr = D.sma_offset_km;
|
||||
const x = Math.min(Math.max(t / (DT * 10), 0), arr.length - 1);
|
||||
return arr[Math.round(x)] * 1000;
|
||||
}
|
||||
|
||||
// Set every scene element for video time s. Returns state for the HUD.
|
||||
function setState(s) {
|
||||
const sh = shotAt(s);
|
||||
const t = tau(s);
|
||||
const b = bclock(s);
|
||||
const jd = Cesium.JulianDate.addSeconds(Cesium.JulianDate.fromIso8601(D.t0), b, new Cesium.JulianDate());
|
||||
Cesium.Transforms.computeTemeToPseudoFixedMatrix(jd, M);
|
||||
const cam = camAt(s);
|
||||
|
||||
// Anchor (X36 on the shot's display clock) and relative state at simulation time.
|
||||
const A = x36Teme(b);
|
||||
const B = basis(A.r, A.v);
|
||||
const rel = relAt(t);
|
||||
const x36W = toWorld(A.r);
|
||||
const cosW = toWorld(place(A.r, B, rel));
|
||||
const RW = toWorld(B.R);
|
||||
const TW = toWorld(B.T);
|
||||
const NW = toWorld(B.N);
|
||||
const sep = v3.distance(x36W, cosW);
|
||||
// Inertial camera basis: X36's frame frozen at the shot start.
|
||||
let cR = RW;
|
||||
let cT = TW;
|
||||
let cN = NW;
|
||||
if (cam.frame === 'inertial') {
|
||||
const A0 = x36Teme(bclock(sh.s0));
|
||||
const B0 = basis(A0.r, A0.v);
|
||||
cR = toWorld(B0.R);
|
||||
cT = toWorld(B0.T);
|
||||
cN = toWorld(B0.N);
|
||||
}
|
||||
|
||||
const sunW = v3.normalize(Cesium.Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(jd, new v3()), new v3());
|
||||
Cesium.Matrix3.multiplyByVector(Cesium.Transforms.computeIcrfToFixedMatrix(jd) || M, sunW, sunW);
|
||||
v3.normalize(sunW, sunW);
|
||||
|
||||
// Frame on Cosmos's guiding centre (its loop averaged over one orbit) and the
|
||||
// loop's size: both change slowly, so the shot doesn't pump every orbit.
|
||||
const P = 5730;
|
||||
const gc = [0, 0, 0];
|
||||
const relS = [];
|
||||
for (let k = 0; k < 16; k++) {
|
||||
const rk = relAt(t - P / 2 + (k + 0.5) * (P / 16));
|
||||
relS.push(rk);
|
||||
gc[0] += rk[0] / 16;
|
||||
gc[1] += rk[1] / 16;
|
||||
gc[2] += rk[2] / 16;
|
||||
}
|
||||
let loop = 0;
|
||||
for (const rk of relS) loop = Math.max(loop, Math.hypot(rk[0] - gc[0], rk[1] - gc[1], rk[2] - gc[2]));
|
||||
const gcW = toWorld(place(A.r, B, gc));
|
||||
const sepG = v3.distance(x36W, gcW) + loop;
|
||||
const pivot = v3.lerp(x36W, gcW, cam.look, new v3());
|
||||
v3.lerp(pivot, v3.ZERO, cam.earth, pivot);
|
||||
const aim = v3.clone(pivot);
|
||||
v3.add(aim, v3.multiplyByScalar(RW, cam.aimRel[0], new v3()), aim);
|
||||
v3.add(aim, v3.multiplyByScalar(TW, cam.aimRel[1], new v3()), aim);
|
||||
v3.add(aim, v3.multiplyByScalar(NW, cam.aimRel[2], new v3()), aim);
|
||||
const dist = Math.max(cam.dist, cam.f * sepG) * (PORTRAIT && cam.earth > 0.5 ? 0.74 : 1) * (PORTRAIT && sh.id === 'hook' ? 0.6 : 1);
|
||||
const az = Cesium.Math.toRadians(cam.az);
|
||||
const el = Cesium.Math.toRadians(cam.el);
|
||||
const off = new v3();
|
||||
v3.add(off, v3.multiplyByScalar(cT, -Math.cos(el) * Math.cos(az), new v3()), off);
|
||||
v3.add(off, v3.multiplyByScalar(cN, Math.cos(el) * Math.sin(az), new v3()), off);
|
||||
v3.add(off, v3.multiplyByScalar(cR, Math.sin(el), new v3()), off);
|
||||
const sunW8 = Math.min(0.8, cam.sun * (PORTRAIT ? 2 : 1));
|
||||
if (sunW8 > 0) {
|
||||
v3.normalize(v3.add(v3.multiplyByScalar(off, 1 - sunW8, off), v3.multiplyByScalar(sunW, sunW8, new v3()), off), off);
|
||||
}
|
||||
const camPos = v3.add(pivot, v3.multiplyByScalar(off, dist, new v3()), new v3());
|
||||
if (cam.pairD > 0) {
|
||||
// Pair-locked: camera just off X36, angled pairTh from the X36->Cosmos line
|
||||
// (sideways, in the orbit's horizontal), aimed at the bisector of the two.
|
||||
const u = v3.normalize(v3.subtract(cosW, x36W, new v3()), new v3());
|
||||
const perp = v3.normalize(v3.subtract(NW, v3.multiplyByScalar(u, v3.dot(NW, u), new v3()), new v3()), new v3());
|
||||
const th = Cesium.Math.toRadians(cam.pairTh * (PORTRAIT ? 0.7 : 1));
|
||||
v3.add(x36W, v3.multiplyByScalar(u, -cam.pairD * Math.cos(th), new v3()), camPos);
|
||||
v3.add(camPos, v3.multiplyByScalar(perp, cam.pairD * Math.sin(th), new v3()), camPos);
|
||||
const a1 = v3.normalize(v3.subtract(x36W, camPos, new v3()), new v3());
|
||||
const a2 = v3.normalize(v3.subtract(cosW, camPos, new v3()), new v3());
|
||||
v3.add(camPos, v3.add(a1, a2, new v3()), aim);
|
||||
}
|
||||
const minR = 6.371e6 + 1.2e5;
|
||||
if (v3.magnitude(camPos) < minR) v3.multiplyByScalar(v3.normalize(camPos, camPos), minR, camPos);
|
||||
const dir = v3.normalize(v3.subtract(aim, camPos, new v3()), new v3());
|
||||
// Up: local zenith; in portrait chase shots, the direction of flight (so the
|
||||
// pair travels up the tall frame and Earth's limb runs down one side).
|
||||
const vup = PORTRAIT ? cam.vup : 0;
|
||||
let upHint = v3.normalize(v3.lerp(RW, TW, vup, new v3()), new v3());
|
||||
if (PORTRAIT && cam.pairD > 0) {
|
||||
// Portrait true-size shot: run the pair up the tall frame.
|
||||
upHint = v3.normalize(v3.subtract(cosW, x36W, new v3()), new v3());
|
||||
}
|
||||
let right = v3.normalize(v3.cross(dir, upHint, new v3()), new v3());
|
||||
let up = v3.cross(right, dir, new v3());
|
||||
{
|
||||
// Composition: put the aim point right of centre in 16:9 (caption column on the left).
|
||||
const fovR = Cesium.Math.toRadians(PORTRAIT ? cam.fov * 1.55 : cam.fov);
|
||||
const aspect = W / H;
|
||||
const tanH = PORTRAIT ? Math.tan(fovR / 2) * aspect : Math.tan(fovR / 2);
|
||||
const tanV = PORTRAIT ? Math.tan(fovR / 2) : Math.tan(fovR / 2) / aspect;
|
||||
const ndcX = (PORTRAIT ? 0 : 0.24) * cam.comp;
|
||||
const ndcY = (PORTRAIT ? 0.14 : 0.06) * cam.comp + cam.compY;
|
||||
const ndcYp = ndcY + (PORTRAIT && cam.pairD > 0 ? 0.13 : 0);
|
||||
v3.normalize(v3.add(v3.add(dir, v3.multiplyByScalar(right, -ndcX * tanH, new v3()), new v3()),
|
||||
v3.multiplyByScalar(up, -ndcYp * tanV, new v3()), new v3()), dir);
|
||||
right = v3.normalize(v3.cross(dir, upHint, new v3()), new v3());
|
||||
up = v3.cross(right, dir, new v3());
|
||||
}
|
||||
scene.camera.setView({ destination: camPos, orientation: { direction: dir, up } });
|
||||
scene.skyBox.show = !sh.noSky;
|
||||
scene.camera.frustum.fov = Cesium.Math.toRadians(PORTRAIT ? cam.fov * 1.55 : cam.fov);
|
||||
{
|
||||
const icrf = Cesium.Transforms.computeIcrfToFixedMatrix(jd) || M;
|
||||
const tmp = new v3();
|
||||
for (let i = 0; i < starDirs.length; i++) {
|
||||
Cesium.Matrix3.multiplyByVector(icrf, starDirs[i], tmp);
|
||||
stars.get(i).position = v3.add(camPos, v3.multiplyByScalar(tmp, 3e8, tmp), new v3());
|
||||
}
|
||||
}
|
||||
scene.camera.frustum.near = Math.max(0.2, Math.min(dist * 0.02, 1000));
|
||||
|
||||
// Models: attitude, fixed per-shot enlargement, lighting
|
||||
const camDistX = v3.distance(camPos, x36W);
|
||||
const camDistC = v3.distance(camPos, cosW);
|
||||
const pxPerRad = W / (2 * Math.tan(scene.camera.frustum.fov / 2));
|
||||
const mag = cam.mag > 0.5 ? cam.mx : 1;
|
||||
mX36.show = cam.mag > 0.01 || camDistX < 3000;
|
||||
mCos.show = cam.mag > 0.01 || camDistC < 6000;
|
||||
mX36.modelMatrix = modelMatrix(x36W, TW, RW, mag);
|
||||
const toX = v3.normalize(v3.subtract(x36W, cosW, new v3()), new v3());
|
||||
const w = Math.min(1, Math.max(0, (60e3 - sep) / 40e3));
|
||||
const cx = v3.normalize(v3.lerp(TW, toX, w, new v3()), new v3());
|
||||
const cy = v3.normalize(v3.subtract(RW, v3.multiplyByScalar(cx, v3.dot(RW, cx), new v3()), new v3()), new v3());
|
||||
mCos.modelMatrix = modelMatrix(cosW, cx, cy, mag);
|
||||
const lx = eclipseLight(x36W, sunW);
|
||||
const lc = eclipseLight(cosW, sunW);
|
||||
const lightFor = (l) => new v3(0.6 + (2.3 - 0.6) * l, 0.66 + (2.3 - 0.66) * l, 0.92 + (2.3 - 0.92) * l);
|
||||
mX36.lightColor = lightFor(lx);
|
||||
mCos.lightColor = lightFor(lc);
|
||||
|
||||
// Glows: always on as "lights"; they shrink when the model is big enough to read.
|
||||
const pxX = (3.25 * mag * pxPerRad) / camDistX;
|
||||
const pxC = (3.6 * mag * pxPerRad) / camDistC;
|
||||
bX36.position = x36W;
|
||||
bCos.position = cosW;
|
||||
bX36.scale = (pxX > 24 ? 0.16 : 0.5) * U;
|
||||
bCos.scale = (pxC > 24 ? 0.16 : 0.5) * U;
|
||||
bX36.color = Cesium.Color.WHITE.withAlpha(pxX > 60 ? 0.6 : 1);
|
||||
bCos.color = Cesium.Color.WHITE.withAlpha(pxC > 60 ? 0.6 : 1);
|
||||
|
||||
// Burn flare
|
||||
let flare = 0;
|
||||
for (const bn of BURNS) {
|
||||
const sb = sOfTauCached(bn.t);
|
||||
if (sb === null) continue;
|
||||
const k = (s - sb) / 0.9;
|
||||
if (k > -0.08 && k < 1) flare = Math.max(flare, k < 0 ? 1 + k / 0.08 : (1 - k) ** 1.6);
|
||||
}
|
||||
bBurn.position = cosW;
|
||||
bBurn.scale = flare * 1.1 * U;
|
||||
bBurn.color = Cesium.Color.WHITE.withAlpha(flare);
|
||||
|
||||
// Trails
|
||||
clearDynamic();
|
||||
// Orbit trails (inertial): where each satellite has actually flown in the last
|
||||
// `atrail` minutes. Only meaningful when the shot runs on one clock.
|
||||
if (cam.atrail > 0 && sh.mode === 'true') {
|
||||
const span = cam.atrail * 60;
|
||||
const n = Math.min(400, Math.max(40, Math.round(span / 15)));
|
||||
for (const which of ['x36', 'cos']) {
|
||||
const pts = [];
|
||||
const cols = [];
|
||||
const col = which === 'x36' ? COL_X36 : COL_COS;
|
||||
for (let k = 0; k <= n; k++) {
|
||||
const tt = t - span + (k / n) * span;
|
||||
if (tt < 0) continue;
|
||||
const Ak = x36Teme(tt);
|
||||
const p = which === 'x36' ? Ak.r : place(Ak.r, basis(Ak.r, Ak.v), relAt(tt));
|
||||
pts.push(toWorld(p));
|
||||
cols.push(col.withAlpha(0.02 + 0.8 * (k / n) ** 2.2));
|
||||
}
|
||||
pts.push(which === 'x36' ? x36W : cosW);
|
||||
cols.push(col.withAlpha(0.85));
|
||||
polyline(pts, cols, which === 'x36' ? 3 : 3);
|
||||
}
|
||||
}
|
||||
// Cosmos relative trail (its path as seen from X36), in the current anchor frame
|
||||
if (cam.rtrail > 0) {
|
||||
const span = cam.rtrail * 3600;
|
||||
const n = Math.min(1200, Math.max(60, Math.round(span / 40)));
|
||||
const pts = [];
|
||||
const cols = [];
|
||||
for (let k = 0; k <= n; k++) {
|
||||
const tt = t - span + (k / n) * span;
|
||||
if (tt < 0) continue;
|
||||
pts.push(toWorld(place(A.r, B, relAt(tt))));
|
||||
cols.push(COL_COS.withAlpha(0.05 + 0.9 * (k / n) ** 1.6));
|
||||
}
|
||||
pts.push(cosW);
|
||||
cols.push(COL_COS.withAlpha(1));
|
||||
polyline(pts, cols, 2.6);
|
||||
}
|
||||
// X36 orbit ring for wide shots (inertial, at the anchor epoch)
|
||||
const ringA = Math.min(1, Math.max(0, (camDistX - 1.5e4) / 6e4)) * (cam.atrail > 0 && sh.mode === 'true' ? 0.35 : 1);
|
||||
if (ringA > 0.01) {
|
||||
const P = 5730;
|
||||
const pts = [];
|
||||
const cols = [];
|
||||
for (let k = 0; k <= 240; k++) {
|
||||
pts.push(toWorld(x36Teme(b - P + (k / 240) * P).r));
|
||||
cols.push(COL_X36.withAlpha((0.05 + 0.3 * (k / 240) ** 2) * ringA));
|
||||
}
|
||||
polyline(pts, cols, 1.4);
|
||||
}
|
||||
// Element-set uncertainty at closest approach: ±1.5 km along-track.
|
||||
if (sh.id === 'ca') {
|
||||
const a0 = v3.add(cosW, v3.multiplyByScalar(TW, -1500, new v3()), new v3());
|
||||
const a1 = v3.add(cosW, v3.multiplyByScalar(TW, 1500, new v3()), new v3());
|
||||
polyline([a0, cosW, a1], [COL_COS.withAlpha(0.25), COL_COS.withAlpha(0.6), COL_COS.withAlpha(0.25)], 22);
|
||||
}
|
||||
// RIC reference rings (orbital plane) during proximity
|
||||
const gridA = Math.min(1, Math.max(0, (8e3 - Math.max(sep, camDistX)) / 3e3)) * (cam.mag > 0.4 ? 1 : 0.35);
|
||||
if (gridA > 0.01) {
|
||||
for (const r of [500, 1000, 2000]) {
|
||||
const pts = [];
|
||||
const cols = [];
|
||||
for (let k = 0; k <= 120; k++) {
|
||||
const a = (k / 120) * Math.PI * 2;
|
||||
const p = v3.add(x36W, v3.multiplyByScalar(RW, r * Math.sin(a), new v3()), new v3());
|
||||
v3.add(p, v3.multiplyByScalar(TW, r * Math.cos(a), new v3()), p);
|
||||
pts.push(p);
|
||||
cols.push(Cesium.Color.WHITE.withAlpha(0.14 * gridA));
|
||||
}
|
||||
polyline(pts, cols, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Speeds: X36 from its state vector; Cosmos by vis-viva with its own SMA.
|
||||
const rC = v3.magnitude(place(A.r, B, rel));
|
||||
const aC = D.a_km * 1000 + smaOffsetAt(t);
|
||||
const vC = Math.sqrt(MU * (2 / rC - 1 / aC));
|
||||
const vXv = Math.sqrt(MU * (2 / v3.magnitude(A.r) - 1 / (D.a_km * 1000)));
|
||||
const r1 = relAt(t + 30);
|
||||
const r0 = relAt(t - 30);
|
||||
const relSpeed = Math.hypot(r1[0] - r0[0], r1[1] - r0[1], r1[2] - r0[2]) / 60;
|
||||
|
||||
return { sh, t, b, jd, x36W, cosW, RW, TW, NW, rel, sep, sepG, da: smaOffsetAt(t), mag, gridA, lx, camDistX, vX: vXv, vC, relSpeed };
|
||||
}
|
||||
|
||||
async function frame(s) {
|
||||
const sh = shotAt(s);
|
||||
const K = Math.max(1, (sh.blur | 0) % 2 ? sh.blur | 0 : (sh.blur | 0) + 1);
|
||||
globe.maximumScreenSpaceError = K > 1 ? 4 : 2.2;
|
||||
const mid = Math.floor(K / 2);
|
||||
let hudState = null;
|
||||
accum.style.visibility = K > 1 ? 'visible' : 'hidden';
|
||||
for (let k = 0; k < K; k++) {
|
||||
// Sub-frames spread across the shutter, clamped inside the shot.
|
||||
const sk = K === 1 ? s : Math.min(sh.s1 - 1e-4, Math.max(sh.s0, s + ((k + 0.5) / K - 0.5) * (SHUTTER / FPS)));
|
||||
const st = setState(k === mid ? s : sk);
|
||||
scene.render(st.jd);
|
||||
if (k === 0 || K === 1) {
|
||||
for (let j = 0; j < SETTLE && !(globe.tilesLoaded && mX36.ready && mCos.ready); j++) {
|
||||
await new Promise((r) => setTimeout(r, 60));
|
||||
scene.render(st.jd);
|
||||
}
|
||||
}
|
||||
if (k === mid) {
|
||||
hud.update(s, st.t, st.b, st);
|
||||
hudState = st;
|
||||
}
|
||||
if (K > 1) {
|
||||
actx.globalAlpha = 1 / (k + 1);
|
||||
actx.drawImage(viewer.canvas, 0, 0, W, H);
|
||||
}
|
||||
}
|
||||
const gl = scene.context._gl;
|
||||
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
|
||||
return { s, t: hudState.t, sep: hudState.sep, mag: hudState.mag, shot: sh.id, K };
|
||||
}
|
||||
|
||||
const sOfTauMemo = new Map();
|
||||
function sOfTauCached(t) {
|
||||
if (!sOfTauMemo.has(t)) sOfTauMemo.set(t, sOfTau(t));
|
||||
return sOfTauMemo.get(t);
|
||||
}
|
||||
|
||||
await document.fonts.ready;
|
||||
window.rpo = { frame, tau, bclock, duration: DUR, ready: true, SHOTS };
|
||||
@@ -0,0 +1,125 @@
|
||||
// Storyboard (v5): a list of shots separated by hard cuts. Each shot owns its
|
||||
// clock. `true` shots run Earth, sun, both satellites and the camera on one
|
||||
// physical clock (a real time-lapse: day and night cycle, the ground rushes
|
||||
// by). `ff` (fast-forward) shots are too fast for that to read, so the Earth
|
||||
// and X36's place on its orbit advance on a slow display clock while the
|
||||
// relative motion runs at the stated rate; they carry a FAST-FORWARD badge.
|
||||
//
|
||||
// Camera keys use X36's local frame (R up, T velocity, N orbit normal):
|
||||
// dist (m) or f × guiding separation; az 0 = behind X36 looking forward;
|
||||
// el up; look 0 = X36 … 1 = Cosmos's guiding centre; earth pulls the aim
|
||||
// toward Earth's centre; frame 'inertial' freezes the basis at the shot
|
||||
// start (wide shots); vup (portrait) rolls the camera so the direction of
|
||||
// flight points up; rtrail = Cosmos relative-path trail (hours);
|
||||
// atrail = orbit trails (min); mag/mx = fixed model enlargement;
|
||||
// pairD/pairTh = pair-locked true-size camera; inset = hours of path shown
|
||||
// in the "seen from X36" inset.
|
||||
|
||||
export function buildStory({ sec, D }) {
|
||||
const CA = sec(D.ca.t);
|
||||
const M = 60;
|
||||
const iso = (s) => sec(s);
|
||||
const WIDE = { frame: 'inertial', look: 0, earth: 0.86, f: 0, fov: 40, mag: 0, sun: 0.35 };
|
||||
|
||||
const shots = [
|
||||
{
|
||||
id: 'hook', mode: 'true', s0: 0, s1: 3.6, blur: 1, inset: 1,
|
||||
tau: [[0, CA - 13 * M], [3.6, CA - 6 * M]],
|
||||
cam: [
|
||||
{ s: 0, dist: 1300, f: 2.4, az: 120, el: 20, look: 0.5, fov: 34, mag: 1, mx: 25, rtrail: 1.0, atrail: 2, vup: 1, compY: -0.16 },
|
||||
{ s: 3.6, dist: 1100, f: 2.2, az: 108, el: 16, look: 0.5, fov: 34, mag: 1, mx: 25, rtrail: 1.0, atrail: 2, vup: 1, compY: -0.16 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'climb', mode: 'true', s0: 3.6, s1: 9.6, blur: 1, inset: 6,
|
||||
tau: [[3.6, iso('2026-09-30T07:30:00Z')], [9.6, iso('2026-09-30T12:30:00Z')]],
|
||||
cam: [
|
||||
{ s: 3.6, ...WIDE, dist: 3.3e7, az: 92, el: 28, atrail: 80, rtrail: 0 },
|
||||
{ s: 9.6, ...WIDE, dist: 2.9e7, az: 100, el: 24, atrail: 80, rtrail: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'lap', mode: 'ff', s0: 9.6, s1: 12.6, blur: 1, bgRate: 260, inset: 30,
|
||||
tau: [[9.6, iso('2026-09-30T13:00:00Z')], [12.6, iso('2026-10-11T23:40:00Z')]],
|
||||
cam: [
|
||||
{ s: 9.6, ...WIDE, dist: 2.9e7, az: 100, el: 30, atrail: 0, rtrail: 30 },
|
||||
{ s: 12.6, ...WIDE, dist: 2.5e7, az: 116, el: 28, atrail: 0, rtrail: 30 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'drop', mode: 'true', s0: 12.6, s1: 17.6, blur: 3, inset: 4,
|
||||
tau: [[12.6, iso('2026-10-12T00:00:00Z')], [17.6, iso('2026-10-12T03:30:00Z')]],
|
||||
cam: [
|
||||
{ s: 12.6, dist: 2.4e5, f: 2.6, az: 62, el: 28, look: 0.5, fov: 36, mag: 0, rtrail: 3, atrail: 20, vup: 1 },
|
||||
{ s: 15.0, dist: 3.0e5, f: 2.6, az: 48, el: 30, look: 0.5, fov: 36, mag: 0, rtrail: 4, atrail: 20, vup: 1 },
|
||||
{ s: 17.6, dist: 4.0e5, f: 2.6, az: 34, el: 32, look: 0.5, fov: 36, mag: 0, rtrail: 4, atrail: 20, vup: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'close', mode: 'ff', s0: 17.6, s1: 21.0, blur: 1, bgRate: 140, inset: 30,
|
||||
tau: [[17.6, iso('2026-10-12T03:40:00Z')], [19.3, iso('2026-10-15T06:00:00Z')], [21.0, iso('2026-10-16T07:00:00Z')]],
|
||||
cam: [
|
||||
{ s: 17.6, dist: 1.0e6, f: 2.8, az: 30, el: 34, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 },
|
||||
{ s: 21.0, dist: 2.0e4, f: 2.8, az: 46, el: 34, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dance', mode: 'true', s0: 21.0, s1: 27.0, blur: 5, inset: 3.2,
|
||||
tau: [[21.0, iso('2026-10-16T07:10:00Z')], [27.0, iso('2026-10-16T12:10:00Z')]],
|
||||
cam: [
|
||||
{ s: 21.0, dist: 5.5e3, f: 1.7, az: 62, el: 30, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 3, atrail: 1.2, vup: 1 },
|
||||
{ s: 27.0, dist: 5.0e3, f: 1.7, az: 80, el: 26, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 3, atrail: 1.2, vup: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dance-ff', mode: 'ff', s0: 27.0, s1: 29.0, blur: 1, bgRate: 140, inset: 10,
|
||||
tau: [[27.0, iso('2026-10-16T12:20:00Z')], [29.0, iso('2026-10-17T22:20:00Z')]],
|
||||
cam: [
|
||||
{ s: 27.0, dist: 4.5e3, f: 1.7, az: 72, el: 38, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 12, atrail: 0, vup: 1 },
|
||||
{ s: 29.0, dist: 3.4e3, f: 1.7, az: 90, el: 32, look: 0.5, fov: 38, mag: 1, mx: 40, rtrail: 12, atrail: 0, vup: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'approach', mode: 'true', s0: 29.0, s1: 32.6, blur: 3, inset: 3,
|
||||
tau: [[29.0, iso('2026-10-17T22:30:00Z')], [31.0, CA - 40 * M], [32.6, CA - 5 * M]],
|
||||
cam: [
|
||||
{ s: 29.0, dist: 3.0e3, f: 1.7, az: 96, el: 26, look: 0.5, fov: 38, mag: 1, mx: 30, rtrail: 3, atrail: 1.5, vup: 1 },
|
||||
{ s: 32.6, dist: 1.4e3, f: 1.7, az: 112, el: 18, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 1.6, atrail: 1.5, vup: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ca', mode: 'true', s0: 32.6, s1: 35.6, blur: 1, inset: 0, noSky: true,
|
||||
tau: [[32.6, CA - 4 * M], [35.6, CA + 2 * M]],
|
||||
cam: [
|
||||
{ s: 32.6, dist: 26, az: 160, el: 0, look: 0, fov: 34, mag: 0, rtrail: 0.4, atrail: 0.6, comp: 0, compYL: 0.07, pairD: 26, pairTh: 24 },
|
||||
{ s: 35.6, dist: 20, az: 163, el: 0, look: 0, fov: 34, mag: 0, rtrail: 0.4, atrail: 0.6, comp: 0, compYL: 0.07, pairD: 20, pairTh: 14 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'sunrise', mode: 'true', s0: 35.6, s1: 38.0, blur: 1, inset: 1.2,
|
||||
tau: [[35.6, CA + 6 * M], [38.0, CA + 27 * M]],
|
||||
cam: [
|
||||
{ s: 35.6, dist: 1.3e3, f: 2.2, az: 96, el: 16, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 1.6, atrail: 1.5, vup: 1 },
|
||||
{ s: 38.0, dist: 2.2e3, f: 2.4, az: 104, el: 22, look: 0.5, fov: 36, mag: 1, mx: 25, rtrail: 2, atrail: 1.5, vup: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'leave', mode: 'ff', s0: 38.0, s1: 40.6, blur: 1, bgRate: 140, inset: 40,
|
||||
tau: [[38.0, CA + 30 * M], [39.3, iso('2026-10-19T08:00:00Z')], [40.6, iso('2026-10-21T12:00:00Z')]],
|
||||
cam: [
|
||||
{ s: 38.0, dist: 1.2e4, f: 2.8, az: 110, el: 30, look: 0.4, fov: 36, mag: 1, mx: 40, rtrail: 8, atrail: 0, vup: 1 },
|
||||
{ s: 39.3, dist: 4.0e5, f: 2.8, az: 70, el: 30, look: 0.5, fov: 34, mag: 0, rtrail: 30, atrail: 0, vup: 1 },
|
||||
{ s: 40.6, dist: 1.6e6, f: 2.8, az: 44, el: 30, look: 0.5, fov: 34, mag: 0, rtrail: 60, atrail: 0, vup: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'end', mode: 'true', s0: 40.6, s1: 43.6, blur: 1, inset: 0,
|
||||
tau: [[40.6, iso('2026-10-21T12:00:00Z')], [43.6, iso('2026-10-21T13:30:00Z')]],
|
||||
cam: [
|
||||
{ s: 40.6, ...WIDE, dist: 3.4e7, az: 92, el: 28, atrail: 80, rtrail: 0 },
|
||||
{ s: 43.6, ...WIDE, dist: 3.1e7, az: 100, el: 26, atrail: 80, rtrail: 0 },
|
||||
],
|
||||
},
|
||||
];
|
||||
return { shots, duration: 43.6 };
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26086.28993682 .00004707 00000+0 32592-3 0 9991
|
||||
2 59103 97.8231 223.0188 0005114 225.4450 134.6360 15.06153609112819
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26158.68825802 .00004079 00000+0 27826-3 0 9999
|
||||
2 59103 97.8355 296.5937 0005746 7.3897 352.7413 15.06793192123716
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26159.35233280 .00003366 00000+0 23040-3 0 9999
|
||||
2 59103 97.8353 297.2694 0005687 4.3753 355.7522 15.06796494123817
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26166.92262582 .00003618 00000+0 24691-3 0 9990
|
||||
2 59103 97.8373 304.9710 0005108 336.3826 23.7165 15.06858602124952
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26255.14828070 .00003049 00000-0 20468-3 0 9990
|
||||
2 59103 97.8486 34.8692 0006466 36.6781 323.4888 15.07597002138247
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26260.59073212 .00003209 00000-0 21497-3 0 9996
|
||||
2 59103 97.8501 40.4232 0006153 21.4151 338.7333 15.07633492139063
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26261.71902949 .00002764 00000-0 18576-3 0 9998
|
||||
2 59103 97.8500 41.5746 0006060 18.1231 342.0211 15.07639150139232
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26262.78095287 .00002216 00000-0 14977-3 0 9997
|
||||
2 59103 97.8499 42.6583 0005964 14.9606 345.1797 15.07643239139395
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26263.57739316 .00002113 00000-0 14303-3 0 9992
|
||||
2 59103 97.8498 43.4711 0005895 12.3871 347.7500 15.07646792139516
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26264.30746210 .00001920 00000-0 13036-3 0 9992
|
||||
2 59103 97.8497 44.2161 0005836 9.7865 350.3475 15.07649397139628
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26265.03752949 .00001861 00000-0 12644-3 0 9997
|
||||
2 59103 97.8497 44.9611 0005772 7.0979 353.0329 15.07652335139737
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26266.36492184 .00002136 00000-0 14447-3 0 9995
|
||||
2 59103 97.8497 46.3158 0005664 1.7767 358.3479 15.07658162139935
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26266.82950772 .00002606 00000-0 17530-3 0 9996
|
||||
2 59103 97.8498 46.7899 0005624 359.8800 0.2424 15.07661498140002
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26268.55510567 .00003708 00000-0 24744-3 0 9991
|
||||
2 59103 97.8503 48.5513 0005525 352.8204 7.2943 15.07672947140265
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26269.55063681 .00004055 00000-0 27015-3 0 9992
|
||||
2 59103 97.8507 49.5673 0005452 348.1373 11.9725 15.07679449140412
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26270.28068919 .00004003 00000-0 26673-3 0 9995
|
||||
2 59103 97.8511 50.3126 0005443 345.0274 15.0791 15.07685221140521
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26271.60805178 .00003113 00000-0 20835-3 0 9993
|
||||
2 59103 97.8515 51.6677 0005238 341.1568 18.9464 15.07692503140727
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26272.80267229 .00002578 00000-0 17326-3 0 9990
|
||||
2 59103 97.8520 52.8874 0005148 337.4380 22.6620 15.07698623140900
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26273.59908483 .00002526 00000-0 16987-3 0 9996
|
||||
2 59103 97.8517 53.7006 0005124 334.7779 25.3197 15.07701220141020
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26274.59459599 .00002356 00000-0 15872-3 0 9999
|
||||
2 59103 97.8519 54.7168 0005042 330.9518 29.1428 15.07706167141170
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26275.85557286 .00002149 00000-0 14515-3 0 9994
|
||||
2 59103 97.8520 56.0042 0004958 326.1509 33.9401 15.07711702141366
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26276.58561018 .00002146 00000-0 14495-3 0 9997
|
||||
2 59103 97.8521 56.7496 0004921 323.3047 36.7843 15.07715077141475
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26277.58111317 .00002116 00000-0 14295-3 0 9996
|
||||
2 59103 97.8521 57.7660 0004867 319.4078 40.6785 15.07719422141622
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26278.64297961 .00002417 00000-0 16265-3 0 9997
|
||||
2 59103 97.8519 58.8502 0004685 315.9000 44.1853 15.07725514141781
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26279.57210904 .00002277 00000-0 15345-3 0 9994
|
||||
2 59103 97.8518 59.7988 0004670 311.6646 48.4181 15.07729980141929
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26280.56760193 .00002036 00000-0 13765-3 0 9992
|
||||
2 59103 97.8518 60.8152 0004668 307.1200 52.9600 15.07734422142078
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26281.56309204 .00002593 00000-0 17409-3 0 9992
|
||||
2 59103 97.8520 61.8317 0004677 302.4752 57.6023 15.07740758142222
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26281.82855571 .00002832 00000-0 18970-3 0 9994
|
||||
2 59103 97.8521 62.1027 0004687 301.2023 58.8744 15.07742590142267
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26282.16038467 .00002473 00000-0 16620-3 0 9990
|
||||
2 59103 97.8522 62.4416 0004691 299.7095 60.3665 15.07742989142316
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26283.62042731 .00003762 00000-0 25050-3 0 9990
|
||||
2 59103 97.8527 63.9326 0004705 293.2618 66.8114 15.07754202142538
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26284.61590186 .00005963 00000-0 39419-3 0 9991
|
||||
2 59103 97.8530 64.9492 0004745 288.8173 71.2539 15.07767500142689
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26285.27954860 .00004849 00000-0 32139-3 0 9991
|
||||
2 59103 97.8533 65.6269 0004726 286.1660 73.9047 15.07771501142783
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26286.60683222 .00004622 00000-0 30645-3 0 9999
|
||||
2 59103 97.8539 66.9826 0004656 280.5005 79.5697 15.07785006142985
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26287.27047001 .00004244 00000-0 28171-3 0 9993
|
||||
2 59103 97.8541 67.6605 0004622 277.7934 82.2768 15.07790448143081
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26288.66410193 .00003889 00000-0 25844-3 0 9991
|
||||
2 59103 97.8545 69.0840 0004529 271.8666 88.2043 15.07801823143290
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26288.92955421 .00004098 00000+0 27211-3 0 9990
|
||||
2 59103 97.8545 69.3551 0004502 270.6218 89.4494 15.07804721143331
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26289.85863499 .00003871 00000-0 25722-3 0 9990
|
||||
2 59103 97.8545 70.3041 0004450 266.4128 93.6590 15.07811918143477
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26289.85863500 .00003814 00000-0 25347-3 0 9993
|
||||
2 59103 97.8545 70.3041 0004447 266.3883 93.6835 15.07811854143471
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26290.05772307 .00003989 00000-0 26490-3 0 9994
|
||||
2 59103 97.8544 70.5074 0004441 265.4495 94.6225 15.07813723143506
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26290.19044830 .00004188 00000-0 27790-3 0 9998
|
||||
2 59103 97.8544 70.6430 0004435 264.8939 95.1782 15.07815295143524
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26290.32317349 .00004105 00000-0 27248-3 0 9998
|
||||
2 59103 97.8544 70.7786 0004431 264.3247 95.7475 15.07816338143548
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26290.38953603 .00004145 00000-0 27503-3 0 9991
|
||||
2 59103 97.8544 70.8464 0004430 263.9776 96.0946 15.07817008143556
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26290.52226120 .00003839 00000-0 25506-3 0 9994
|
||||
2 59103 97.8544 70.9820 0004425 263.2954 96.7769 15.07817240143578
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26290.85407330 .00004039 00000-0 26811-3 0 9997
|
||||
2 59103 97.8543 71.3209 0004420 261.9119 98.1606 15.07820589143628
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26290.98679776 .00004571 00000-0 30280-3 0 9992
|
||||
2 59103 97.8543 71.4564 0004423 261.4686 98.6040 15.07822695143649
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26291.18588521 .00003960 00000-0 26292-3 0 9991
|
||||
2 59103 97.8542 71.6597 0004427 260.5670 99.5056 15.07822602143671
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26291.31860976 .00003914 00000-0 25995-3 0 9991
|
||||
2 59103 97.8542 71.7953 0004429 260.0318 100.0410 15.07823629143698
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26291.51769637 .00003838 00000-0 25493-3 0 9993
|
||||
2 59103 97.8542 71.9987 0004428 259.1266 100.9463 15.07825171143722
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26291.65042070 .00003903 00000-0 25923-3 0 9994
|
||||
2 59103 97.8541 72.1342 0004427 258.5532 101.5199 15.07826318143746
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26291.84950712 .00003783 00000-0 25134-3 0 9996
|
||||
2 59103 97.8541 72.3375 0004428 257.6442 102.4290 15.07827581143773
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26291.91586913 .00003881 00000-0 25774-3 0 9991
|
||||
2 59103 97.8541 72.4053 0004426 257.2678 102.8055 15.07828340143780
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26292.18131736 .00003704 00000-0 24618-3 0 9990
|
||||
2 59103 97.8540 72.6765 0004435 256.1254 103.9480 15.07829570143824
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26292.31404130 .00003675 00000-0 24428-3 0 9992
|
||||
2 59103 97.8540 72.8120 0004445 255.4996 104.5739 15.07830592143849
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26292.51312700 .00003657 00000-0 24311-3 0 9996
|
||||
2 59103 97.8540 73.0154 0004451 254.5213 105.5523 15.07832036143878
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26292.84493628 .00003709 00000-0 24646-3 0 9990
|
||||
2 59103 97.8539 73.3543 0004476 253.0298 107.0439 15.07834455143921
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26293.44219192 .00003376 00000-0 22475-3 0 9997
|
||||
2 59103 97.8538 73.9643 0004546 250.9081 109.1654 15.07837575144010
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26294.83578209 .00003518 00000-0 23393-3 0 9996
|
||||
2 59103 97.8534 75.3877 0004672 244.9804 115.0939 15.07846787144227
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26295.83119797 .00003685 00000-0 24476-3 0 9998
|
||||
2 59103 97.8534 76.4045 0004779 241.2210 118.8537 15.07854273144375
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26296.82660950 .00003555 00000-0 23623-3 0 9999
|
||||
2 59103 97.8536 77.4214 0004866 237.7481 122.3275 15.07861103144529
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26297.22477300 .00003484 00000+0 23160-3 0 9995
|
||||
2 59103 97.8537 77.8281 0004894 236.2883 123.7878 15.07863566144584
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26297.22477300 .00003484 00000-0 23160-3 0 9996
|
||||
2 59103 97.8537 77.8281 0004895 236.2883 123.7878 15.07863566144585
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26298.68469998 .00003088 00000-0 20571-3 0 9994
|
||||
2 59103 97.8545 79.3198 0004979 230.3517 129.7271 15.07872062144809
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26299.21558029 .00002859 00000-0 19077-3 0 9992
|
||||
2 59103 97.8547 79.8622 0005010 228.4636 131.6162 15.07874757144887
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26311.22646569 .00005238 00000-0 34511-3 0 09999
|
||||
2 59103 97.8549 92.1350 0005747 184.5121 175.6056 15.07948998146693
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26311.89003246 .00006200 00000-0 40753-3 0 9996
|
||||
2 59103 97.8551 92.8132 0005791 182.5432 177.5767 15.07957163146798
|
||||
@@ -0,0 +1,138 @@
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26152.70872904 .00002576 00000+0 17505-3 0 9992
|
||||
2 68754 98.1201 300.7511 0012336 120.4488 239.7959 15.07304170 6897
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26159.74542199 .00002602 00000+0 17651-3 0 9995
|
||||
2 68754 98.1217 308.1752 0012593 99.0372 261.2280 15.07345927 7951
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26167.71129863 .00001705 00000+0 11720-3 0 9994
|
||||
2 68754 98.1221 316.5801 0012449 74.9313 285.3289 15.07374465 9150
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26171.42865760 .00001950 00000+0 13332-3 0 9993
|
||||
2 68754 98.1227 320.5028 0012296 63.7881 296.4607 15.07388042 9713
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26260.83772171 .00001889 00000-0 13485-3 0 9991
|
||||
2 68754 98.1354 54.7766 0012651 78.2307 282.0337 15.05804681 23175
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26261.63513074 .00001689 00000-0 12108-3 0 9994
|
||||
2 68754 98.1356 55.6175 0012659 75.8927 284.3705 15.05806771 23299
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26262.76479120 .00001398 00000-0 10109-3 0 9998
|
||||
2 68754 98.1357 56.8087 0012671 72.5451 287.7159 15.05809555 23467
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26263.69509759 .00001263 00000-0 91788-4 0 9997
|
||||
2 68754 98.1358 57.7898 0012670 69.7721 290.4866 15.05812144 23605
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26264.62540278 .00001092 00000-0 79956-4 0 9995
|
||||
2 68754 98.1359 58.7708 0012661 66.9675 293.2884 15.05814180 23742
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26265.68860691 .00001069 00000-0 78418-4 0 9992
|
||||
2 68754 98.1360 59.8920 0012647 63.7205 296.5318 15.05817107 23908
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26266.75180937 .00001035 00000-0 76042-4 0 9993
|
||||
2 68754 98.1360 61.0133 0012617 60.4631 299.7851 15.05819637 24068
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26267.74855964 .00001064 00000-0 78038-4 0 9991
|
||||
2 68754 98.1361 62.0644 0012576 57.3610 302.8827 15.05822334 24217
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26268.67885843 .00001135 00000-0 82934-4 0 9992
|
||||
2 68754 98.1361 63.0456 0012530 54.4397 305.7994 15.05825007 24351
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26269.67560572 .00001132 00000-0 82722-4 0 9994
|
||||
2 68754 98.1362 64.0968 0012465 51.3289 308.9049 15.05827331 24509
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26270.73880166 .00001022 00000-0 75166-4 0 9999
|
||||
2 68754 98.1363 65.2180 0012381 47.9832 312.2445 15.05829058 24664
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26271.60264739 .00001004 00000-0 73915-4 0 9998
|
||||
2 68754 98.1364 66.1291 0012309 45.2217 315.0007 15.05830737 24791
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26273.72903350 .00001055 00000-0 77449-4 0 9994
|
||||
2 68754 98.1366 68.3717 0012124 38.3566 321.8519 15.05834760 25115
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26274.65932542 .00001208 00000-0 87949-4 0 9998
|
||||
2 68754 98.1368 69.3530 0012053 35.3565 324.8457 15.05837208 25257
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26275.58961617 .00001303 00000-0 94453-4 0 9990
|
||||
2 68754 98.1369 70.3342 0011978 32.3930 327.8028 15.05839335 25394
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26276.58635420 .00001462 00000-0 10541-3 0 9992
|
||||
2 68754 98.1370 71.3855 0011901 29.2495 330.9395 15.05842269 25542
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26277.58308985 .00001570 00000-0 11285-3 0 9992
|
||||
2 68754 98.1372 72.4369 0011829 26.1294 334.0526 15.05845393 25696
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26278.71272005 .00001621 00000-0 11631-3 0 9993
|
||||
2 68754 98.1374 73.6285 0011751 22.6263 337.5478 15.05849284 25865
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26279.70945022 .00001533 00000-0 11028-3 0 9991
|
||||
2 68754 98.1376 74.6800 0011680 19.5487 340.6184 15.05852289 26011
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26280.77262624 .00001379 00000-0 99701-4 0 9993
|
||||
2 68754 98.1379 75.8015 0011605 16.2593 343.9002 15.05855405 26179
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26281.63645474 .00001500 00000-0 10796-3 0 9999
|
||||
2 68754 98.1380 76.7128 0011555 13.5395 346.6138 15.05859310 26304
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26281.96869591 .00001572 00000-0 11296-3 0 9998
|
||||
2 68754 98.1381 77.0633 0011536 12.5007 347.6502 15.05860963 26355
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26282.30093732 .00001417 00000-0 10225-3 0 9992
|
||||
2 68754 98.1381 77.4138 0011513 11.4362 348.7123 15.05861209 26407
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26282.69962578 .00001523 00000-0 10953-3 0 9993
|
||||
2 68754 98.1382 77.8344 0011482 10.1907 349.9548 15.05863355 26461
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26283.69634675 .00001558 00000-0 11195-3 0 9995
|
||||
2 68754 98.1384 78.8860 0011420 7.0157 353.1226 15.05866972 26616
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26284.69306260 .00001868 00000-0 13327-3 0 9995
|
||||
2 68754 98.1384 79.9376 0011359 3.7945 356.3365 15.05872893 26763
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26285.29109375 .00001759 00000-0 12573-3 0 9991
|
||||
2 68754 98.1385 80.5685 0011291 2.5721 357.5560 15.05873974 26850
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26286.62004434 .00001614 00000-0 11575-3 0 9990
|
||||
2 68754 98.1386 81.9707 0011170 358.0409 2.0770 15.05878976 27054
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26287.61675478 .00001297 00000-0 94015-4 0 9990
|
||||
2 68754 98.1386 83.0224 0011085 354.4382 5.6718 15.05881642 27208
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26288.67991186 .00001324 00000-0 95812-4 0 9991
|
||||
2 68754 98.1385 84.1440 0011078 350.1976 9.9031 15.05883714 27367
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26288.94570044 .00001361 00000+0 98344-4 0 9990
|
||||
2 68754 98.1385 84.4244 0011058 349.2550 10.8438 15.05884825 27408
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26289.61017217 .00001341 00000-0 96980-4 0 9996
|
||||
2 68754 98.1384 85.1255 0011012 346.8712 13.2225 15.05886718 27505
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26290.27464265 .00001428 00000-0 10299-3 0 9993
|
||||
2 68754 98.1384 85.8265 0010972 344.4623 15.6264 15.05889247 27602
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26291.60358214 .00001675 00000-0 11991-3 0 9991
|
||||
2 68754 98.1384 87.2287 0010887 339.6211 20.4578 15.05894006 27809
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26292.26805095 .00001832 00000-0 13073-3 0 9996
|
||||
2 68754 98.1384 87.9298 0010847 337.1519 22.9222 15.05896458 27905
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26293.26475213 .00001872 00000-0 13349-3 0 9992
|
||||
2 68754 98.1385 88.9814 0010780 333.4795 26.5877 15.05899349 28057
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26294.59368299 .00002052 00000-0 14583-3 0 9996
|
||||
2 68754 98.1387 90.3836 0010699 328.6668 31.3918 15.05904380 28258
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26295.59037760 .00002154 00000-0 15280-3 0 9991
|
||||
2 68754 98.1389 91.4353 0010628 325.0265 35.0261 15.05908509 28408
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26296.25483896 .00002152 00000-0 15268-3 0 9994
|
||||
2 68754 98.1390 92.1365 0010579 322.6332 37.4157 15.05911139 28508
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26297.25152876 .00002089 00000-0 14829-3 0 9997
|
||||
2 68754 98.1392 93.1882 0010503 319.0562 40.9874 15.05914963 28656
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26298.71333574 .00001706 00000-0 12200-3 0 9991
|
||||
2 68754 98.1395 94.7308 0010387 313.8286 46.2080 15.05919229 28876
|
||||
COSMOS 2609
|
||||
1 68754U 26083B 26299.24490067 .00001512 00000-0 10868-3 0 9994
|
||||
2 68754 98.1396 95.2918 0010346 311.9169 48.1173 15.05920507 28958
|
||||
@@ -0,0 +1,129 @@
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26152.72862595 .00003478 00000+0 23983-3 0 9992
|
||||
2 68758 97.8035 290.4876 0011902 105.5191 254.7350 15.06445751 6874
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26159.70293477 .00002294 00000+0 15951-3 0 9995
|
||||
2 68758 97.8044 297.5512 0012260 84.6938 275.5687 15.06485307 7925
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26260.84984109 .00001716 00000-0 11933-3 0 9997
|
||||
2 68758 97.8189 40.1047 0011975 99.1459 261.1123 15.06852447 23153
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26261.64670071 .00001479 00000-0 10343-3 0 9996
|
||||
2 68758 97.8190 40.9137 0011993 96.7788 263.4804 15.06854153 23271
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26262.77558333 .00001205 00000-0 85066-4 0 9999
|
||||
2 68758 97.8190 42.0599 0012000 93.3343 266.9256 15.06856478 23446
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26263.70524966 .00001075 00000-0 76368-4 0 9998
|
||||
2 68758 97.8191 43.0038 0011997 90.5207 269.7395 15.06858624 23583
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26264.63491513 .00000996 00000-0 71110-4 0 9993
|
||||
2 68758 97.8192 43.9477 0011985 87.7216 272.5383 15.06860574 23727
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26265.63098391 .00001128 00000-0 79941-4 0 9999
|
||||
2 68758 97.8193 44.9591 0011963 84.7349 275.5243 15.06863556 23873
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26266.75986014 .00001324 00000-0 93025-4 0 9998
|
||||
2 68758 97.8195 46.1054 0011928 81.3433 278.9145 15.06866971 24043
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26267.75592490 .00001597 00000-0 11132-3 0 9990
|
||||
2 68758 97.8198 47.1168 0011891 78.3528 281.9033 15.06870739 24196
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26268.68558311 .00001890 00000-0 13090-3 0 9998
|
||||
2 68758 97.8200 48.0609 0011856 75.5517 284.7024 15.06874737 24337
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26269.68164297 .00002066 00000-0 14264-3 0 9995
|
||||
2 68758 97.8202 49.0723 0011815 72.6147 287.6371 15.06878638 24486
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26270.81050765 .00002052 00000-0 14170-3 0 9997
|
||||
2 68758 97.8205 50.2188 0011769 69.2625 290.9862 15.06882383 24652
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26271.60735111 .00002028 00000-0 14009-3 0 9994
|
||||
2 68758 97.8206 51.0280 0011738 66.9087 293.3376 15.06885228 24778
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26273.73225854 .00001660 00000-0 11546-3 0 9992
|
||||
2 68758 97.8211 53.1861 0011669 60.6152 299.6238 15.06891907 25092
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26274.66190204 .00001547 00000-0 10786-3 0 9994
|
||||
2 68758 97.8212 54.1303 0011639 57.8469 302.3885 15.06894976 25236
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26275.59154395 .00001361 00000-0 95456-4 0 9990
|
||||
2 68758 97.8214 55.0745 0011609 55.0681 305.1634 15.06897345 25372
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26276.65398968 .00001285 00000-0 90334-4 0 9999
|
||||
2 68758 97.8215 56.1537 0011569 51.8609 308.3658 15.06900445 25538
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26277.58362798 .00001188 00000-0 83870-4 0 9993
|
||||
2 68758 97.8216 57.0980 0011527 49.0105 311.2117 15.06902796 25674
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26278.71247184 .00001130 00000-0 79996-4 0 9991
|
||||
2 68758 97.8217 58.2447 0011471 45.5151 314.7011 15.06905918 25845
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26279.70850916 .00001044 00000-0 74195-4 0 9994
|
||||
2 68758 97.8218 59.2565 0011417 42.3824 317.8283 15.06908206 25994
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26280.77094721 .00000937 00000-0 67062-4 0 9992
|
||||
2 68758 97.8219 60.3357 0011347 39.0190 321.1854 15.06910458 26151
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26281.63417664 .00001217 00000-0 85762-4 0 9992
|
||||
2 68758 97.8220 61.2126 0011295 36.2545 323.9445 15.06914174 26281
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26281.96618754 .00001333 00000-0 93516-4 0 9996
|
||||
2 68758 97.8221 61.5499 0011271 35.1938 325.0031 15.06915688 26337
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26282.69661095 .00001407 00000-0 98466-4 0 9994
|
||||
2 68758 97.8222 62.2919 0011214 32.8393 327.3529 15.06917967 26440
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26283.69264145 .00001680 00000-0 11674-3 0 9998
|
||||
2 68758 97.8224 63.3038 0011131 29.5994 330.5861 15.06921909 26596
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26284.75506972 .00002767 00000-0 18933-3 0 9995
|
||||
2 68758 97.8223 64.3829 0010961 25.8941 334.2832 15.06927652 26753
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26285.28628350 .00002499 00000-0 17141-3 0 9991
|
||||
2 68758 97.8224 64.9226 0010917 24.0469 336.1265 15.06928628 26832
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26286.61431176 .00002865 00000-0 19583-3 0 9996
|
||||
2 68758 97.8229 66.2719 0010811 19.6031 340.5609 15.06936248 27038
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26287.61032854 .00002778 00000-0 19000-3 0 9994
|
||||
2 68758 97.8232 67.2840 0010743 16.3763 343.7809 15.06941500 27188
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26288.73914347 .00002629 00000-0 18004-3 0 9992
|
||||
2 68758 97.8235 68.4310 0010668 12.8500 347.2996 15.06947198 27356
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26288.93834545 .00002660 00000+0 18210-3 0 9998
|
||||
2 68758 97.8235 68.6334 0010653 12.2371 347.9112 15.06948519 27386
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26289.66875211 .00002554 00000-0 17501-3 0 9999
|
||||
2 68758 97.8236 69.3755 0010598 10.0169 350.1267 15.06952212 27495
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26290.33275620 .00002595 00000-0 17776-3 0 9992
|
||||
2 68758 97.8237 70.0503 0010549 8.0135 352.1259 15.06956191 27596
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26291.66076058 .00002485 00000-0 17035-3 0 9999
|
||||
2 68758 97.8238 71.3997 0010441 4.0387 356.0922 15.06962777 27793
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26292.32476102 .00002353 00000-0 16153-3 0 9991
|
||||
2 68758 97.8237 72.0744 0010386 1.9824 358.1443 15.06965458 27891
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26293.32075912 .00002062 00000-0 14213-3 0 9998
|
||||
2 68758 97.8237 73.0865 0010292 358.8694 1.2507 15.06969043 28040
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26294.64875251 .00001922 00000-0 13274-3 0 9998
|
||||
2 68758 97.8237 74.4359 0010162 354.4951 5.6162 15.06974085 28246
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26295.64474431 .00001805 00000-0 12495-3 0 9998
|
||||
2 68758 97.8238 75.4481 0010070 351.0065 9.0980 15.06977834 28398
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26296.30873745 .00001677 00000-0 11638-3 0 9998
|
||||
2 68758 97.8238 76.1229 0010019 348.5573 11.5425 15.06980145 28496
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26297.30472565 .00001334 00000-0 93484-4 0 9994
|
||||
2 68758 97.8238 77.1351 0009943 344.8023 15.2904 15.06982792 28646
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26298.69910817 .00001187 00000-0 83681-4 0 9998
|
||||
2 68758 97.8245 78.5524 0009902 339.3585 20.7240 15.06984851 28853
|
||||
COSMOS 2610
|
||||
1 68758U 26083F 26299.29669974 .00001076 00000-0 76253-4 0 9994
|
||||
2 68758 97.8245 79.1598 0009861 337.1406 22.9380 15.06985925 28945
|
||||
@@ -0,0 +1,129 @@
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26159.71053314 .00002533 00000+0 17718-3 0 9990
|
||||
2 68759 97.8106 297.0050 0013014 90.2940 269.9778 15.06157628 7910
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26260.87971400 .00001968 00000-0 13737-3 0 9994
|
||||
2 68759 97.8246 39.6071 0012602 105.1572 255.1050 15.06513645 23145
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26261.61033398 .00001958 00000-0 13670-3 0 9997
|
||||
2 68759 97.8247 40.3490 0012611 103.0092 257.2544 15.06515762 23259
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26262.80589069 .00001906 00000-0 13321-3 0 9996
|
||||
2 68759 97.8249 41.5631 0012629 99.4504 260.8151 15.06519529 23435
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26263.73576495 .00001882 00000-0 13156-3 0 9997
|
||||
2 68759 97.8251 42.5075 0012650 96.6842 263.5825 15.06523006 23577
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26264.59921772 .00001799 00000-0 12594-3 0 9996
|
||||
2 68759 97.8253 43.3844 0012671 94.1102 266.1573 15.06525950 23701
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26265.72834500 .00001825 00000-0 12768-3 0 9997
|
||||
2 68759 97.8256 44.5312 0012699 90.7079 269.5602 15.06530662 23876
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26266.72463106 .00001778 00000-0 12448-3 0 9990
|
||||
2 68759 97.8259 45.5431 0012718 87.6902 272.5780 15.06534452 24024
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26267.78733273 .00001783 00000-0 12480-3 0 9994
|
||||
2 68759 97.8262 46.6225 0012735 84.4681 275.7998 15.06538841 24188
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26268.71719412 .00001824 00000-0 12754-3 0 9997
|
||||
2 68759 97.8264 47.5670 0012743 81.6582 278.6088 15.06542834 24327
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26269.71347191 .00001697 00000-0 11901-3 0 9990
|
||||
2 68759 97.8265 48.5790 0012741 78.6577 281.6080 15.06546084 24473
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26270.77616603 .00001346 00000-0 95279-4 0 9997
|
||||
2 68759 97.8270 49.6584 0012866 75.7764 284.4890 15.06549610 24632
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26271.63960356 .00001251 00000-0 88874-4 0 9998
|
||||
2 68759 97.8267 50.5356 0012710 72.8000 287.4617 15.06550538 24760
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26273.69856644 .00000861 00000-0 62580-4 0 9992
|
||||
2 68759 97.8268 52.6272 0012614 66.4409 293.8141 15.06554086 25076
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26274.62841921 .00000889 00000-0 64436-4 0 9990
|
||||
2 68759 97.8269 53.5718 0012558 63.5744 296.6770 15.06556131 25217
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26275.62468937 .00000946 00000-0 68264-4 0 9990
|
||||
2 68759 97.8270 54.5840 0012493 60.4847 299.7623 15.06558013 25362
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26276.62095821 .00001162 00000-0 82847-4 0 9993
|
||||
2 68759 97.8271 55.5961 0012424 57.4149 302.8276 15.06560609 25514
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26277.55080763 .00001356 00000-0 95950-4 0 9999
|
||||
2 68759 97.8272 56.5408 0012356 54.5445 305.6934 15.06563165 25650
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26278.74632481 .00001624 00000-0 11399-3 0 9993
|
||||
2 68759 97.8275 57.7554 0012276 50.8670 309.3645 15.06567283 25833
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26279.74258669 .00001728 00000-0 12100-3 0 9992
|
||||
2 68759 97.8277 58.7677 0012209 47.8019 312.4242 15.06570547 25982
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26280.80526284 .00001729 00000-0 12105-3 0 9998
|
||||
2 68759 97.8279 59.8474 0012141 44.5619 315.6581 15.06574097 26140
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26281.66868447 .00002002 00000-0 13946-3 0 9992
|
||||
2 68759 97.8282 60.7247 0012095 41.8892 318.3258 15.06578914 26271
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26281.93435211 .00002097 00000-0 14589-3 0 9999
|
||||
2 68759 97.8283 60.9947 0012081 41.0732 319.1402 15.06580592 26311
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26282.33285373 .00001930 00000-0 13462-3 0 9990
|
||||
2 68759 97.8283 61.3996 0012056 39.8456 320.3654 15.06581253 26374
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26282.73135394 .00002069 00000-0 14397-3 0 9993
|
||||
2 68759 97.8285 61.8045 0012035 38.6414 321.5671 15.06583877 26437
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26283.72760367 .00002165 00000-0 15042-3 0 9992
|
||||
2 68759 97.8288 62.8169 0011984 35.5717 324.6307 15.06588728 26588
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26284.72384823 .00002805 00000-0 19357-3 0 9996
|
||||
2 68759 97.8292 63.8291 0011976 32.8791 327.3178 15.06596383 26732
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26285.32159440 .00002299 00000-0 15943-3 0 9992
|
||||
2 68759 97.8294 64.4367 0011947 31.0909 329.1022 15.06597603 26829
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26286.64991358 .00002188 00000-0 15192-3 0 9993
|
||||
2 68759 97.8297 65.7868 0011870 27.1212 333.0632 15.06604131 27029
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26287.64614964 .00001865 00000-0 13017-3 0 9994
|
||||
2 68759 97.8297 66.7994 0011788 24.0114 336.1660 15.06607829 27173
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26288.70879903 .00001627 00000-0 11409-3 0 9997
|
||||
2 68759 97.8297 67.8794 0011690 20.5283 339.6411 15.06611269 27336
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26288.97446075 .00001646 00000+0 11536-3 0 9997
|
||||
2 68759 97.8296 68.1494 0011662 19.6184 340.5489 15.06612455 27372
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26289.63861492 .00001556 00000-0 10932-3 0 9995
|
||||
2 68759 97.8295 68.8244 0011598 17.3523 342.8097 15.06614540 27479
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26290.30276783 .00001611 00000-0 11301-3 0 9997
|
||||
2 68759 97.8293 69.4994 0011533 14.9763 345.1803 15.06617154 27570
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26291.63107155 .00001671 00000-0 11709-3 0 9998
|
||||
2 68759 97.8290 70.8493 0011396 10.1904 349.9551 15.06621464 27772
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26292.29522244 .00001722 00000-0 12048-3 0 9995
|
||||
2 68759 97.8289 71.5242 0011332 7.7228 352.4171 15.06623372 27879
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26293.29144718 .00001854 00000-0 12938-3 0 9996
|
||||
2 68759 97.8295 72.5368 0011307 4.2435 355.8886 15.06627614 28021
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26294.61974244 .00002055 00000-0 14287-3 0 9991
|
||||
2 68759 97.8297 73.8869 0011181 359.5921 0.5295 15.06632873 28222
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26295.61596169 .00002529 00000-0 17483-3 0 9995
|
||||
2 68759 97.8291 74.8991 0011060 355.6003 4.5125 15.06636043 28372
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26296.28010552 .00002596 00000-0 17930-3 0 9997
|
||||
2 68759 97.8293 75.5742 0011018 353.2762 6.8315 15.06639287 28475
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26297.27631845 .00002482 00000-0 17164-3 0 9994
|
||||
2 68759 97.8297 76.5869 0010941 349.8959 10.2046 15.06643963 28626
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26298.73742535 .00001887 00000-0 13154-3 0 9998
|
||||
2 68759 97.8306 78.0726 0010793 345.2900 14.8011 15.06648313 28847
|
||||
COSMOS 2611
|
||||
1 68759U 26083G 26299.26873591 .00001733 00000-0 12119-3 0 9994
|
||||
2 68759 97.8307 78.6128 0010749 343.4743 16.6132 15.06649756 28921
|
||||
@@ -0,0 +1,153 @@
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26155.90287272 .00002690 00000+0 18390-3 0 9994
|
||||
2 68762 97.8530 293.7591 0014565 92.1716 268.1179 15.06956289 7356
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26159.75407367 .00002135 00000+0 14684-3 0 9996
|
||||
2 68762 97.8522 297.6867 0014348 80.4063 279.8784 15.06972914 7938
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26171.44020111 .00001567 00000+0 10885-3 0 9991
|
||||
2 68762 97.8544 309.6054 0013827 44.2942 315.9388 15.07023515 9691
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26260.86758294 .00002233 00000-0 15183-3 0 9994
|
||||
2 68762 97.8674 40.9197 0014205 95.7247 264.5600 15.07354460 23168
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26261.59779413 .00002048 00000-0 13963-3 0 9999
|
||||
2 68762 97.8675 41.6662 0014214 93.5134 266.7719 15.07356643 23277
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26262.79268196 .00001736 00000-0 11898-3 0 9991
|
||||
2 68762 97.8677 42.8878 0014229 89.8825 270.4032 15.07360212 23459
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26263.72203639 .00001508 00000-0 10392-3 0 9991
|
||||
2 68762 97.8678 43.8380 0014235 87.0399 273.2457 15.07363086 23591
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26264.65138936 .00001311 00000-0 90972-4 0 9996
|
||||
2 68762 97.8679 44.7882 0014238 84.1896 276.0954 15.07365540 23738
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26265.71350497 .00001300 00000-0 90218-4 0 9993
|
||||
2 68762 97.8680 45.8742 0014234 80.8835 279.4002 15.07369086 23896
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26266.77561874 .00001291 00000-0 89592-4 0 9995
|
||||
2 68762 97.8682 46.9602 0014220 77.5856 282.6961 15.07372236 24056
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26267.77134797 .00001419 00000-0 98072-4 0 9991
|
||||
2 68762 97.8684 47.9783 0014198 74.4763 285.8030 15.07375851 24201
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26268.70069343 .00001593 00000-0 10953-3 0 9993
|
||||
2 68762 97.8686 48.9286 0014169 71.5962 288.6804 15.07379466 24340
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26269.69641887 .00001644 00000-0 11291-3 0 9994
|
||||
2 68762 97.8685 49.9465 0014102 68.7395 291.5337 15.07381940 24494
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26270.75852367 .00001612 00000-0 11077-3 0 9997
|
||||
2 68762 97.8688 51.0327 0014008 65.3516 294.9168 15.07385028 24659
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26271.62148238 .00001673 00000-0 11480-3 0 9994
|
||||
2 68762 97.8691 51.9154 0014029 62.6118 297.6534 15.07388326 24786
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26273.68247567 .00000811 00000-0 84943-4 0 9995
|
||||
2 68762 97.8688 54.0158 0009837 239.4926 120.5322 14.92325354 25091
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26274.62118805 .00000863 00000-0 90072-4 0 9999
|
||||
2 68762 97.8691 54.9532 0009851 235.9877 124.0406 14.92325532 25238
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26275.62695029 .00000615 00000-0 65754-4 0 9992
|
||||
2 68762 97.8692 55.9580 0009897 232.2465 127.7858 14.92326645 25389
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26276.63271178 .00000534 00000-0 57914-4 0 9998
|
||||
2 68762 97.8694 56.9628 0009946 228.5318 131.5047 14.92328027 25531
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26277.63847267 .00000482 00000-0 52779-4 0 9993
|
||||
2 68762 97.8695 57.9676 0009999 224.7981 135.2431 14.92329266 25688
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26278.71128329 .00000522 00000-0 56735-4 0 9995
|
||||
2 68762 97.8697 59.0395 0010063 220.8688 139.1777 14.92330931 25841
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26279.71704291 .00000574 00000-0 61753-4 0 9997
|
||||
2 68762 97.8699 60.0444 0010133 217.2289 142.8228 14.92332289 25991
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26280.78985167 .00000785 00000-0 82448-4 0 9997
|
||||
2 68762 97.9003 61.1183 0010228 213.4540 146.6034 14.92334849 26156
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26281.66150731 .00000839 00000-0 87750-4 0 9997
|
||||
2 68762 97.9005 61.9925 0010285 210.4383 149.6240 14.92337159 26288
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26281.92970876 .00000969 00000-0 10044-3 0 9991
|
||||
2 68762 97.9005 62.2615 0010305 209.4903 150.5735 14.92338079 26324
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26282.73431255 .00001102 00000-0 11340-3 0 9994
|
||||
2 68762 97.9007 63.0685 0010362 206.7000 153.3686 14.92339854 26444
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26283.74006574 .00001271 00000-0 12990-3 0 9997
|
||||
2 68762 97.9009 64.0773 0010433 203.2304 156.8444 14.92342594 26594
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26284.74581481 .00001555 00000-0 15767-3 0 9993
|
||||
2 68762 97.9010 65.0862 0010502 199.7743 160.3069 14.92347040 26740
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26284.94696506 .00001431 00000-0 14552-3 0 9994
|
||||
2 68762 97.9011 65.2879 0010518 199.0879 160.9947 14.92346808 26776
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26286.60754176 .00003113 00000-0 20748-3 0 9990
|
||||
2 68762 97.8531 66.9813 0004629 289.3941 70.6786 15.07850417 27027
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26287.66931602 .00002747 00000-0 18360-3 0 9997
|
||||
2 68762 97.8534 68.0658 0004555 285.0033 75.0689 15.07856291 27187
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26288.73075855 .00002568 00000-0 16852-3 0 9990
|
||||
2 68762 97.8543 69.1543 0007699 273.5225 86.5122 15.08583912 27344
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26288.99607294 .00005758 00000+0 37233-3 0 9995
|
||||
2 68762 97.8548 69.4259 0007586 272.3400 87.6959 15.08588279 27388
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26289.59318872 .00001558 00000-0 10604-3 0 9991
|
||||
2 68762 97.8549 70.0366 0004576 255.0538 105.0183 15.07839734 27470
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26289.79227700 .00002282 00000-0 15345-3 0 9994
|
||||
2 68762 97.8552 70.2402 0004640 252.3357 107.7364 15.07815618 27507
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26290.32317661 .00002533 00000-0 16976-3 0 9991
|
||||
2 68762 97.8543 70.7797 0004438 264.6717 95.4004 15.07822656 27582
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26290.72134983 .00002215 00000-0 14901-3 0 9992
|
||||
2 68762 97.8545 71.1849 0004479 262.1722 97.8997 15.07823289 27648
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26291.31861747 .00002270 00000-0 15278-3 0 9992
|
||||
2 68762 97.8541 71.7953 0004465 259.9972 100.0752 15.07776700 27733
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26291.51771032 .00002106 00000-0 14210-3 0 9994
|
||||
2 68762 97.8541 71.9986 0004460 258.9279 101.1447 15.07777458 27762
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26291.98249573 .00001307 00000-0 91664-4 0 9996
|
||||
2 68762 97.8542 72.4721 0004032 300.1157 59.9670 15.07011839 27836
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26292.31448618 .00002307 00000-0 15841-3 0 9998
|
||||
2 68762 97.8540 72.8115 0003973 302.2888 57.7953 15.07013899 27885
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26293.31019816 .00001134 00000-0 78377-4 0 9994
|
||||
2 68762 97.8542 73.8278 0006285 264.0700 95.9811 15.07841994 28035
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26294.63742370 .00001869 00000-0 12632-3 0 9994
|
||||
2 68762 97.8537 75.1835 0006258 257.9047 102.1479 15.07847584 28232
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26295.56647921 .00002005 00000-0 13516-3 0 9998
|
||||
2 68762 97.8537 76.1326 0006327 254.2189 105.8341 15.07851426 28375
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26296.29644999 .00002091 00000-0 14081-3 0 9994
|
||||
2 68762 97.8538 76.8783 0006385 251.4327 108.6207 15.07854402 28488
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26297.29186266 .00002093 00000-0 14092-3 0 9997
|
||||
2 68762 97.8545 77.8974 0006462 247.4186 112.6358 15.07858138 28632
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26298.68543625 .00001992 00000-0 13427-3 0 9995
|
||||
2 68762 97.8551 79.3213 0006547 241.9903 118.0662 15.07863046 28847
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26299.28268042 .00001802 00000-0 12187-3 0 9991
|
||||
2 68762 97.8556 79.9319 0006489 239.3908 120.6680 15.07864828 28934
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26311.29374648 .00003029 00000-0 20161-3 0 09991
|
||||
2 68762 97.8560 92.2054 0007271 194.2330 165.8694 15.07910707030747
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26311.95733085 .00003625 00000+0 24040-3 0 9999
|
||||
2 68762 97.8562 92.8836 0007328 192.0782 168.0271 15.07915641 30842
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26311.95733085 .00003625 00000-0 24040-3 0 9990
|
||||
2 68762 97.8562 92.8836 0007329 192.0782 168.0271 15.07915641 30843
|
||||
@@ -0,0 +1,138 @@
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26154.89498458 .00002226 00000+0 15111-3 0 9995
|
||||
2 68763 97.7787 292.7811 0011357 98.5189 261.7326 15.07430304 7207
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26158.74498038 .00002982 00000+0 20082-3 0 9999
|
||||
2 68763 97.7785 296.6735 0011128 87.1639 273.0862 15.07449590 7784
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26159.74066109 .00003023 00000+0 20351-3 0 9996
|
||||
2 68763 97.7785 297.6801 0011082 84.1892 276.0598 15.07454508 7937
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26167.70597507 .00001729 00000+0 11813-3 0 9996
|
||||
2 68763 97.7805 305.7336 0011083 61.0051 299.2286 15.07493333 9130
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26171.42306266 .00001432 00000+0 98590-4 0 9999
|
||||
2 68763 97.7804 309.4927 0010780 49.0648 311.1511 15.07503759 9697
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26260.78621025 .00001682 00000-0 12029-3 0 9990
|
||||
2 68763 97.7944 39.7319 0008396 48.6841 311.5106 15.05837905 23141
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26261.65005557 .00001473 00000-0 10589-3 0 9990
|
||||
2 68763 97.7944 40.6048 0008362 46.2290 313.9627 15.05839874 23271
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26262.77969735 .00001215 00000-0 88112-4 0 9993
|
||||
2 68763 97.7945 41.7464 0008308 42.9774 317.2100 15.05842274 23446
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26263.70998881 .00001063 00000-0 77649-4 0 9995
|
||||
2 68763 97.7945 42.6865 0008252 40.1937 319.9898 15.05844326 23588
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26264.64027928 .00000913 00000-0 67280-4 0 9997
|
||||
2 68763 97.7945 43.6266 0008192 37.3519 322.8275 15.05846041 23725
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26265.70346696 .00000944 00000-0 69472-4 0 9990
|
||||
2 68763 97.7946 44.7011 0008120 33.9908 326.1837 15.05848778 23888
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26266.76665336 .00001006 00000-0 73707-4 0 9998
|
||||
2 68763 97.7947 45.7756 0008037 30.5254 329.6439 15.05851340 24044
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26267.76338870 .00001156 00000-0 84041-4 0 9998
|
||||
2 68763 97.7949 46.7829 0007949 27.2142 332.9500 15.05854292 24199
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26268.69367350 .00001376 00000-0 99156-4 0 9993
|
||||
2 68763 97.7950 47.7231 0007862 24.1106 336.0487 15.05857491 24338
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26269.69040564 .00001507 00000-0 10820-3 0 9995
|
||||
2 68763 97.7952 48.7305 0007763 20.7288 339.4252 15.05860441 24488
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26270.75358484 .00001539 00000-0 11037-3 0 9993
|
||||
2 68763 97.7954 49.8050 0007658 17.0861 343.0621 15.05863087 24646
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26271.61741635 .00001596 00000-0 11433-3 0 9996
|
||||
2 68763 97.7956 50.6782 0007578 14.1178 346.0259 15.05865562 24774
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26273.74376433 .00001611 00000-0 11535-3 0 9993
|
||||
2 68763 97.7960 52.8275 0007381 6.8716 353.2610 15.05871560 25090
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26274.60759023 .00001673 00000-0 11960-3 0 9999
|
||||
2 68763 97.7962 53.7006 0007295 4.0484 356.0800 15.05874545 25223
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26275.60431045 .00001646 00000-0 11769-3 0 9998
|
||||
2 68763 97.7964 54.7082 0007198 0.8184 359.3053 15.05877560 25375
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26276.60102819 .00001684 00000-0 12035-3 0 9992
|
||||
2 68763 97.7966 55.7159 0007103 357.6369 2.4822 15.05881074 25529
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26277.59774344 .00001644 00000-0 11753-3 0 9991
|
||||
2 68763 97.7969 56.7235 0007008 354.4822 5.6326 15.05884399 25674
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26278.72735052 .00001585 00000-0 11346-3 0 9992
|
||||
2 68763 97.7971 57.8656 0006904 350.8713 9.2387 15.05888446 25842
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26279.72406058 .00001463 00000-0 10510-3 0 9991
|
||||
2 68763 97.7974 58.8733 0006817 347.6155 12.4903 15.05891475 25991
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26280.78721545 .00001278 00000-0 92369-4 0 9997
|
||||
2 68763 97.7976 59.9482 0006721 344.0516 16.0498 15.05894380 26158
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26281.65102690 .00001458 00000-0 10472-3 0 9992
|
||||
2 68763 97.7977 60.8216 0006657 341.1204 18.9774 15.05898417 26282
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26281.98326154 .00001526 00000-0 10939-3 0 9995
|
||||
2 68763 97.7978 61.1576 0006632 339.9776 20.1189 15.05900003 26334
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26282.71417713 .00001485 00000-0 10655-3 0 9998
|
||||
2 68763 97.7979 61.8966 0006576 337.3911 22.7025 15.05902288 26441
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26283.71087862 .00001565 00000-0 11209-3 0 9992
|
||||
2 68763 97.7980 62.9044 0006505 333.8302 26.2595 15.05905994 26593
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26284.70757623 .00002249 00000-0 15909-3 0 9992
|
||||
2 68763 97.7982 63.9122 0006329 330.8765 29.2107 15.05912328 26745
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26285.30559424 .00001761 00000-0 12555-3 0 9990
|
||||
2 68763 97.7982 64.5170 0006303 328.3577 31.7270 15.05913005 26832
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26286.63451875 .00001661 00000-0 11860-3 0 9995
|
||||
2 68763 97.7983 65.8609 0006251 322.6607 37.4184 15.05918109 27037
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26287.63120986 .00001442 00000-0 10358-3 0 9990
|
||||
2 68763 97.7982 66.8689 0006234 318.2015 41.8734 15.05920928 27188
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26288.76079212 .00001672 00000-0 11936-3 0 9998
|
||||
2 68763 97.7982 68.0109 0006261 313.8003 46.2705 15.05924734 27352
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26288.96012904 .00001558 00000+0 11157-3 0 9992
|
||||
2 68763 97.7981 68.2127 0006235 312.3181 47.7517 15.05924946 27380
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26289.62458747 .00001730 00000-0 12333-3 0 9995
|
||||
2 68763 97.7980 68.8846 0006236 309.3955 50.6718 15.05927201 27483
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26290.28904463 .00002059 00000-0 14592-3 0 9994
|
||||
2 68763 97.7980 69.5564 0006247 306.5734 53.4917 15.05930287 27585
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26291.61795593 .00002576 00000-0 18146-3 0 9997
|
||||
2 68763 97.7980 70.9002 0006242 300.9724 59.0889 15.05936335 27782
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26292.28240978 .00002673 00000-0 18807-3 0 9995
|
||||
2 68763 97.7980 71.5721 0006237 298.2822 61.7774 15.05939213 27881
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26293.27908662 .00002483 00000-0 17505-3 0 9999
|
||||
2 68763 97.7987 72.5803 0006148 294.7514 65.3073 15.05944828 28038
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26294.60798485 .00002580 00000-0 18166-3 0 9999
|
||||
2 68763 97.7991 73.9243 0006097 289.2700 70.7867 15.05951235 28238
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26295.60465466 .00002751 00000-0 19339-3 0 9993
|
||||
2 68763 97.7989 74.9319 0006088 285.0002 75.0551 15.05956451 28388
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26296.26909865 .00002594 00000-0 18256-3 0 9994
|
||||
2 68763 97.7991 75.6040 0006038 282.3400 77.7150 15.05959872 28482
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26297.33220613 .00002225 00000-0 15721-3 0 9995
|
||||
2 68763 97.7996 76.6794 0005955 278.0182 82.0369 15.05964701 28646
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26298.72753094 .00001747 00000-0 12440-3 0 9996
|
||||
2 68763 97.8002 78.0913 0005908 272.0033 88.0517 15.05967749 28850
|
||||
COSMOS 2613
|
||||
1 68763U 26083J 26299.32552597 .00001488 00000-0 10661-3 0 9993
|
||||
2 68763 97.8002 78.6962 0005892 269.4581 90.5970 15.05969133 28940
|
||||
@@ -0,0 +1,126 @@
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26159.75792086 .00001829 00000+0 13070-3 0 9990
|
||||
2 68764 97.7766 296.9035 0007598 55.5546 304.6397 15.05748131 7934
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26260.88806664 .00001417 00000-0 10135-3 0 9998
|
||||
2 68764 97.7911 38.9656 0007662 68.3607 291.8435 15.06103110 23156
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26261.61888566 .00001431 00000-0 10228-3 0 9990
|
||||
2 68764 97.7911 39.7041 0007609 66.2476 293.9548 15.06104642 23266
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26262.74833115 .00001492 00000-0 10648-3 0 9998
|
||||
2 68764 97.7913 40.8455 0007534 63.0211 297.1784 15.06107474 23432
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26263.74489829 .00001596 00000-0 11352-3 0 9996
|
||||
2 68764 97.7914 41.8526 0007470 60.1746 300.0222 15.06110555 23584
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26264.60858818 .00001665 00000-0 11827-3 0 9994
|
||||
2 68764 97.7916 42.7255 0007418 57.7445 302.4499 15.06113219 23712
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26265.73802573 .00001876 00000-0 13270-3 0 9994
|
||||
2 68764 97.7919 43.8669 0007359 54.5898 305.6015 15.06117829 23885
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26266.73458536 .00002006 00000-0 14157-3 0 9992
|
||||
2 68764 97.7922 44.8741 0007313 51.8326 308.3558 15.06121963 24031
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26267.79757833 .00002127 00000-0 14980-3 0 9999
|
||||
2 68764 97.7926 45.9485 0007272 48.8657 311.3196 15.06126942 24190
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26268.72769419 .00002203 00000-0 15495-3 0 9993
|
||||
2 68764 97.7929 46.8887 0007235 46.3317 313.8508 15.06131441 24334
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26269.72424397 .00002092 00000-0 14736-3 0 9990
|
||||
2 68764 97.7932 47.8960 0007195 43.6529 316.5266 15.06135427 24486
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26270.78722771 .00001798 00000-0 12726-3 0 9994
|
||||
2 68764 97.7935 48.9706 0007148 40.7283 319.4477 15.06138578 24646
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26271.65090005 .00001596 00000-0 11346-3 0 9992
|
||||
2 68764 97.7936 49.8437 0007106 38.3118 321.8612 15.06141147 24770
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26273.71042126 .00001025 00000-0 74421-4 0 9993
|
||||
2 68764 97.7938 51.9257 0006991 32.2542 327.9111 15.06145492 25084
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26274.64052572 .00000903 00000-0 66112-4 0 9994
|
||||
2 68764 97.7938 52.8661 0006929 29.4007 330.7608 15.06147459 25222
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26275.63706546 .00000779 00000-0 57605-4 0 9999
|
||||
2 68764 97.7938 53.8735 0006855 26.2631 333.8942 15.06149002 25378
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26276.63360420 .00000793 00000-0 58570-4 0 9995
|
||||
2 68764 97.7938 54.8811 0006772 23.0480 337.1049 15.06150949 25523
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26277.56370613 .00000838 00000-0 61683-4 0 9992
|
||||
2 68764 97.7939 55.8214 0006691 19.9562 340.1925 15.06152766 25668
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26278.75954941 .00000975 00000-0 71042-4 0 9998
|
||||
2 68764 97.7940 57.0304 0006578 15.9086 344.2346 15.06155647 25848
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26279.68964875 .00001088 00000-0 78732-4 0 9997
|
||||
2 68764 97.7941 57.9708 0006491 12.6802 347.4586 15.06157838 25980
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26280.75261772 .00001184 00000-0 85281-4 0 9997
|
||||
2 68764 97.7943 59.0456 0006394 8.9266 351.2073 15.06160383 26141
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26281.61627792 .00001614 00000-0 11463-3 0 9999
|
||||
2 68764 97.7945 59.9189 0006321 5.8811 354.2489 15.06164646 26271
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26281.94845454 .00001727 00000-0 12238-3 0 9998
|
||||
2 68764 97.7946 60.2547 0006288 4.6926 355.4359 15.06166166 26325
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26282.74567713 .00001920 00000-0 13553-3 0 9992
|
||||
2 68764 97.7948 61.0608 0006212 1.8797 358.2452 15.06169342 26443
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26283.74220313 .00002220 00000-0 15601-3 0 9995
|
||||
2 68764 97.7950 62.0685 0006126 358.3927 1.7279 15.06174058 26594
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26284.73872464 .00003394 00000-0 23608-3 0 9994
|
||||
2 68764 97.7953 63.0758 0006038 354.2947 5.8210 15.06181438 26745
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26285.33663588 .00002938 00000-0 20497-3 0 9992
|
||||
2 68764 97.7955 63.6805 0005996 352.3742 7.7392 15.06183389 26839
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26286.66532086 .00002813 00000-0 19638-3 0 9994
|
||||
2 68764 97.7961 65.0244 0005880 348.3702 11.7388 15.06191524 27035
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26287.66183042 .00002453 00000-0 17181-3 0 9990
|
||||
2 68764 97.7964 66.0323 0005781 345.4457 14.6603 15.06196416 27185
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26288.72477090 .00002195 00000-0 15420-3 0 9994
|
||||
2 68764 97.7966 67.1075 0005667 342.3215 17.7813 15.06201050 27340
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26288.92407169 .00002230 00000+0 15655-3 0 9995
|
||||
2 68764 97.7966 67.3090 0005644 341.7157 18.3866 15.06202253 27376
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26289.65484094 .00002125 00000-0 14940-3 0 9998
|
||||
2 68764 97.7966 68.0482 0005563 339.4496 20.6506 15.06205312 27487
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26290.31917500 .00002186 00000-0 15354-3 0 9993
|
||||
2 68764 97.7966 68.7201 0005497 337.3105 22.7878 15.06208668 27586
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26291.64783982 .00002164 00000-0 15203-3 0 9994
|
||||
2 68764 97.7965 70.0640 0005355 332.8273 27.2673 15.06214376 27785
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26292.31217072 .00002061 00000-0 14497-3 0 9991
|
||||
2 68764 97.7963 70.7359 0005299 330.3334 29.7591 15.06216680 27882
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26293.30866468 .00001850 00000-0 13056-3 0 9995
|
||||
2 68764 97.7964 71.7438 0005338 325.3729 34.7149 15.06219571 28033
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26294.63732004 .00001699 00000-0 12027-3 0 9998
|
||||
2 68764 97.7965 73.0877 0005253 320.1245 39.9595 15.06223911 28235
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26295.63380879 .00001522 00000-0 10820-3 0 9992
|
||||
2 68764 97.7961 74.0956 0005110 316.0960 43.9860 15.06227146 28380
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26296.23170105 .00001454 00000-0 10359-3 0 9999
|
||||
2 68764 97.7961 74.7003 0005105 313.2849 46.7951 15.06228985 28473
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26297.29461942 .00001283 00000-0 91891-4 0 9999
|
||||
2 68764 97.7961 75.7755 0005105 308.3285 51.7482 15.06231680 28636
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26298.68969847 .00001285 00000-0 92040-4 0 9999
|
||||
2 68764 97.7970 77.1870 0005106 302.6161 57.4572 15.06234669 28847
|
||||
COSMOS 2614
|
||||
1 68764U 26083K 26299.28758840 .00001215 00000-0 87210-4 0 9991
|
||||
2 68764 97.7970 77.7918 0005096 299.9236 60.1484 15.06235866 28938
|
||||
@@ -0,0 +1,28 @@
|
||||
# TLE/OMM sources (collected 2026-11-08)
|
||||
|
||||
Files: `<norad>.tle` (3-line TLEs, sorted by epoch, deduplicated on numeric element content, checksums verified),
|
||||
`<norad>.omm.json` (full Space-Track OMM records from OMPub/RSO), `index.csv` (epoch, lines, and every source that carried each element set).
|
||||
All content is untrusted downloaded data; nothing from it was executed.
|
||||
|
||||
## Worked
|
||||
1. **OMPub/RSO "Orbital Witness" daily archive** (main source; one element set per object per day, the latest Space-Track GP_HISTORY row each day).
|
||||
Ledger: https://raw.githubusercontent.com/OMPub/RSO/node/ledger.json
|
||||
Bundles: https://github.com/OMPub/RSO/releases/download/rso-archive-YYYY-MM-DD/rso-archive-YYYY-MM-DD.tar.gz (catalog.json.gz inside)
|
||||
Downloaded 2026-09-18 through 2026-10-27. Source tag in index.csv: `OMPub_RSO:<archive date>`. Found through KeepTrack's public OpenAPI (`/v4/rso/{date}` proxies these releases).
|
||||
2. **jazzpi/ssa**, `data/cosmos-2612-iceye-x36.csv` (Space-Track OMM CSV with TLE lines; epochs 2026-10-16 to 2026-10-19, several per day, including revised re-issues of the same epoch).
|
||||
https://raw.githubusercontent.com/jazzpi/ssa/HEAD/data/cosmos-2612-iceye-x36.csv
|
||||
3. Single snapshots found in other GitHub repos (searched across full git history):
|
||||
- code-lab-org/tatc `docs/validation/socrates_2026-10-25.tle` (59103, epoch 2026-10-24)
|
||||
- Atharvchaskar008/EDITH `deploy/showcase/runs/20261009T2252Z/catalog.json`, `addons/a_history/out/catalog_enriched.json` (epochs 2026-10-08/09)
|
||||
- shishird-03/Orbitguard `data/catalog.tle` (epochs 2026-10-15)
|
||||
- Out of window (earlier in 2026): Proxy939/AstraShield `data/cache/celestrak_active.tle`, ColaBear101/SattrackSlop `data/catalogue.txt`, Sky-OuO/Pc_TCN `data/collected_tle_datas_2026*.json`, kris70lesgo/helix `coral/data/helix_core/satellites.jsonl`
|
||||
4. Current element sets only (Nov 7-8 2026, outside the window): n2yo.com satellite pages, orbitalradar.com/satellite/<id>, celestrak gp.php?CATNR=68762, tle.ivanstanojevic.me, db.satnogs.org API.
|
||||
|
||||
## Failed / not useful
|
||||
- web.archive.org: every connection reset through the agent proxy (CDX and snapshots). archive.org/wayback/available reported no snapshots of celestrak gp.php?CATNR=59103/68762 for Oct 2026.
|
||||
- CelesTrak: serves current data only. Connections were reset some of the time (gp.php?CATNR=59103, last-30-days, supplemental).
|
||||
- SatNOGS DB `/api/tle/?norad_cat_id=`: latest TLE only, no history. For 68762 it returned an empty list.
|
||||
- KeepTrack: /satellite/<id> pages are behind a Cloudflare challenge (403). The v4 API needs a key (401), and its OpenAPI has no per-object history endpoint. The v2 API was shut down (410).
|
||||
- tle.ivanstanojevic.me, n2yo, orbitalradar: latest set only.
|
||||
- Marco Langbroek blog (sattrackcam.blogspot.com), keeptrack.space deep-dive, integrityisr.com, diplomacyandlaw.com: analysis only, no TLE text.
|
||||
- zendir-dev/space-range-scripts and DigitalArsenal/space-data-network-modules: derived or illustrative data only (May 2026), or a local archive that is not in the repo.
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare the reconstruction with the public element-set history (tle/*.tle)."""
|
||||
import json, math, sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import numpy as np
|
||||
from sgp4.api import Satrec, jday
|
||||
|
||||
HERE = __file__.rsplit('/', 1)[0]
|
||||
MU = 398600.4418
|
||||
|
||||
|
||||
def load(path):
|
||||
L = [l.rstrip('\n') for l in open(path) if l.strip()]
|
||||
out = []
|
||||
for i in range(len(L)):
|
||||
if L[i].startswith('1 ') and i + 1 < len(L) and L[i + 1].startswith('2 '):
|
||||
s = Satrec.twoline2rv(L[i], L[i + 1])
|
||||
ep = datetime(2000, 1, 1, tzinfo=timezone.utc) + timedelta(days=s.jdsatepoch + s.jdsatepochF - 2451545.0 + 0.5)
|
||||
out.append((ep, s, L[i], L[i + 1]))
|
||||
out.sort(key=lambda x: x[0])
|
||||
return out
|
||||
|
||||
|
||||
def st(s, d):
|
||||
jd, fr = jday(d.year, d.month, d.day, d.hour, d.minute, d.second + d.microsecond / 1e6)
|
||||
e, r, v = s.sgp4(jd, fr)
|
||||
return np.array(r), np.array(v)
|
||||
|
||||
|
||||
def rel_rtn(rx, vx, rc):
|
||||
R = rx / np.linalg.norm(rx)
|
||||
N = np.cross(rx, vx); N /= np.linalg.norm(N)
|
||||
T = np.cross(N, R)
|
||||
th = math.atan2(np.dot(np.cross(rx, rc), N), np.dot(rx, rc))
|
||||
return np.array([np.linalg.norm(rc) - np.linalg.norm(rx), th * np.linalg.norm(rx), np.dot(rc, N)]) * 1000
|
||||
|
||||
|
||||
def sma(s):
|
||||
n = s.no_kozai / 60
|
||||
return (MU / n ** 2) ** (1 / 3)
|
||||
|
||||
|
||||
X = load(f'{HERE}/tle/59103.tle')
|
||||
C = load(f'{HERE}/tle/68762.tle')
|
||||
D = json.load(open(f'{HERE}/data.json'))
|
||||
T0 = datetime.fromisoformat(D['t0'].replace('Z', '+00:00'))
|
||||
rel = np.array(D['rel']).reshape(-1, 3)
|
||||
|
||||
|
||||
def model_rel(d):
|
||||
i = (d - T0).total_seconds() / D['dt']
|
||||
if i < 0 or i >= len(rel) - 1:
|
||||
return None
|
||||
k = int(i)
|
||||
return rel[k] + (rel[k + 1] - rel[k]) * (i - k)
|
||||
|
||||
|
||||
def nearest_x(d):
|
||||
return min(X, key=lambda x: abs((x[0] - d).total_seconds()))
|
||||
|
||||
|
||||
rows = []
|
||||
for ep, s, l1, l2 in C:
|
||||
if ep < datetime(2026, 9, 25, tzinfo=timezone.utc) or ep > datetime(2026, 10, 26, tzinfo=timezone.utc):
|
||||
continue
|
||||
xe, xs, _, _ = nearest_x(ep)
|
||||
rx, vx = st(xs, ep)
|
||||
rc, vc = st(s, ep)
|
||||
r = rel_rtn(rx, vx, rc)
|
||||
m = model_rel(ep)
|
||||
dA = sma(s) - sma(xs)
|
||||
di = math.degrees(s.inclo - xs.inclo)
|
||||
dO = math.degrees(s.nodeo - xs.nodeo)
|
||||
ec = (s.ecco * math.cos(s.argpo), s.ecco * math.sin(s.argpo))
|
||||
ex = (xs.ecco * math.cos(xs.argpo), xs.ecco * math.sin(xs.argpo))
|
||||
rows.append(dict(ep=ep.isoformat()[:16], xage_h=round((ep - xe).total_seconds() / 3600, 1), R=r[0], T=r[1], N=r[2], dA_km=dA, di=di, dO=dO,
|
||||
dex=(ec[0] - ex[0]) * sma(xs) * 1000, dey=(ec[1] - ex[1]) * sma(xs) * 1000,
|
||||
mT=None if m is None else m[1], mR=None if m is None else m[0]))
|
||||
print(f"{'epoch':17} {'xAge':>5} {'TLE T km':>10} {'model T':>10} {'TLE R km':>9} {'mod R':>7} {'N km':>7} {'dA km':>8} {'di deg':>7} {'dRAAN':>7} {'|de| m':>7}")
|
||||
for r in rows:
|
||||
print(f"{r['ep']:17} {r['xage_h']:5} {r['T']/1000:10.2f} {('' if r['mT'] is None else f'{r[chr(109)+chr(84)]/1000:10.2f}'):>10} {r['R']/1000:9.2f} {('' if r['mR'] is None else f'{r[chr(109)+chr(82)]/1000:7.2f}'):>7} {r['N']/1000:7.2f} {r['dA_km']:8.3f} {r['di']:7.3f} {r['dO']:7.3f} {math.hypot(r['dex'], r['dey']):7.0f}")
|
||||
json.dump(rows, open(f'{HERE}/tle_rows.json', 'w'), default=str, indent=0)
|
||||
@@ -0,0 +1,506 @@
|
||||
[
|
||||
{
|
||||
"ep": "2026-09-25T16:48",
|
||||
"xage_h": 3.5,
|
||||
"R": 8301.670539523002,
|
||||
"T": -8431513.722806757,
|
||||
"N": -27791.39877508352,
|
||||
"dA_km": 0.8983573483865257,
|
||||
"di": 0.018299999999994283,
|
||||
"dO": 0.3773000000000063,
|
||||
"dex": -697.9292820779607,
|
||||
"dey": 9783.360065963709,
|
||||
"mT": null,
|
||||
"mR": null
|
||||
},
|
||||
{
|
||||
"ep": "2026-09-26T16:42",
|
||||
"xage_h": 3.5,
|
||||
"R": 7570.832860136761,
|
||||
"T": -8559145.016498953,
|
||||
"N": -27973.72346945707,
|
||||
"dA_km": 0.9106827027590043,
|
||||
"di": 0.017800000000001946,
|
||||
"dO": 0.3791999999999937,
|
||||
"dex": -153.69173268221,
|
||||
"dey": 9872.028733885176,
|
||||
"mT": null,
|
||||
"mR": null
|
||||
},
|
||||
{
|
||||
"ep": "2026-09-27T18:12",
|
||||
"xage_h": 11.5,
|
||||
"R": 6815.196592958273,
|
||||
"T": -8698111.802384106,
|
||||
"N": -28185.292493290643,
|
||||
"dA_km": 0.9188939955174646,
|
||||
"di": 0.01769999999998821,
|
||||
"dO": 0.7201000000000007,
|
||||
"dex": 404.0796528142847,
|
||||
"dey": 9785.414288801614,
|
||||
"mT": null,
|
||||
"mR": null
|
||||
},
|
||||
{
|
||||
"ep": "2026-09-28T14:54",
|
||||
"xage_h": 0.3,
|
||||
"R": 6251.370799312099,
|
||||
"T": -8811622.590756347,
|
||||
"N": -28360.676199371275,
|
||||
"dA_km": 0.9310836171125629,
|
||||
"di": 0.017600000000012644,
|
||||
"dO": 0.24769999999999298,
|
||||
"dex": 1035.6495628865,
|
||||
"dey": 9792.560732639002,
|
||||
"mT": null,
|
||||
"mR": null
|
||||
},
|
||||
{
|
||||
"ep": "2026-09-30T16:22",
|
||||
"xage_h": 2.0,
|
||||
"R": 59781.09715962455,
|
||||
"T": -11167760.90800771,
|
||||
"N": -27690.064016225777,
|
||||
"dA_km": 47.460623747911995,
|
||||
"di": 0.017099999999994866,
|
||||
"dO": 0.31519999999999515,
|
||||
"dex": -6664.747931481048,
|
||||
"dey": -4354.727977192556,
|
||||
"mT": 75639512.43065125,
|
||||
"mR": 47012.709780189165
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-01T14:54",
|
||||
"xage_h": 0.6,
|
||||
"R": 53198.61601430057,
|
||||
"T": -17454723.663578935,
|
||||
"N": -24621.218235828022,
|
||||
"dA_km": 47.475209599662776,
|
||||
"di": 0.017200000000008597,
|
||||
"dO": 0.2364000000000059,
|
||||
"dex": -6864.688025004593,
|
||||
"dey": -3957.3136394122175,
|
||||
"mT": 69369559.69491303,
|
||||
"mR": 46997.502157282
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-02T15:02",
|
||||
"xage_h": -5.5,
|
||||
"R": 47456.624127597934,
|
||||
"T": 19341422.85974959,
|
||||
"N": -22997.83211902794,
|
||||
"dA_km": 47.488684068408475,
|
||||
"di": 0.017200000000008597,
|
||||
"dO": -0.04620000000000298,
|
||||
"dex": -7044.010744269706,
|
||||
"dey": -3504.5754305638343,
|
||||
"mT": 62651790.55734729,
|
||||
"mR": 46983.64728609734
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-03T15:11",
|
||||
"xage_h": 1.1,
|
||||
"R": 49113.90241477966,
|
||||
"T": 12623642.1421986,
|
||||
"N": -20230.508617234882,
|
||||
"dA_km": 47.494710533775105,
|
||||
"di": 0.01730000000000961,
|
||||
"dO": 0.21320000000000022,
|
||||
"dex": -7289.578703191674,
|
||||
"dey": -3123.0249822608152,
|
||||
"mT": 55934040.62735785,
|
||||
"mR": 46984.004027369
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-04T15:19",
|
||||
"xage_h": 1.4,
|
||||
"R": 51956.892104823964,
|
||||
"T": 5903878.433825442,
|
||||
"N": -16608.864333671896,
|
||||
"dA_km": 47.504150727611886,
|
||||
"di": 0.017400000000010622,
|
||||
"dO": 0.201599999999991,
|
||||
"dex": -7468.8080473937525,
|
||||
"dey": -2684.489511437165,
|
||||
"mT": 49216279.297819875,
|
||||
"mR": 46998.27676768883
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-05T17:04",
|
||||
"xage_h": 1.6,
|
||||
"R": 56127.7648462119,
|
||||
"T": -1272584.9636815456,
|
||||
"N": -14658.070253380942,
|
||||
"dA_km": 47.517611120165384,
|
||||
"di": 0.017799999999989224,
|
||||
"dO": 0.18929999999999383,
|
||||
"dex": -7595.534591930674,
|
||||
"dey": -2300.7489457738766,
|
||||
"mT": 42050639.05895918,
|
||||
"mR": 47014.7910767715
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-06T17:12",
|
||||
"xage_h": 3.5,
|
||||
"R": 61162.61451995888,
|
||||
"T": -8001362.706066734,
|
||||
"N": -12259.211912978088,
|
||||
"dA_km": 47.527051009651586,
|
||||
"di": 0.01810000000000498,
|
||||
"dO": 0.24560000000000357,
|
||||
"dex": -7732.876100480503,
|
||||
"dey": -1828.4307699860303,
|
||||
"mT": 35332833.7000085,
|
||||
"mR": 47017.20328388
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-07T18:57",
|
||||
"xage_h": 5.3,
|
||||
"R": 58306.33261745879,
|
||||
"T": -15185307.898810113,
|
||||
"N": -8576.099649933036,
|
||||
"dA_km": 47.53267538061027,
|
||||
"di": 0.04850000000000716,
|
||||
"dO": 0.3031,
|
||||
"dex": -7856.039095884964,
|
||||
"dey": -1326.2684424572599,
|
||||
"mT": 28167197.222816538,
|
||||
"mR": 47003.41404208034
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-08T15:52",
|
||||
"xage_h": 2.4,
|
||||
"R": 52514.75479914916,
|
||||
"T": -21033724.175330356,
|
||||
"N": -7142.919170412297,
|
||||
"dA_km": 47.54487423568935,
|
||||
"di": 0.048499999999994436,
|
||||
"dO": 0.16079999999999814,
|
||||
"dex": -7875.484256176535,
|
||||
"dey": -875.4380866070594,
|
||||
"mT": 22345153.534087613,
|
||||
"mR": 46988.85800007833
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-08T22:18",
|
||||
"xage_h": 2.4,
|
||||
"R": 51497.189675082154,
|
||||
"T": 20689322.520410504,
|
||||
"N": -6897.771094663387,
|
||||
"dA_km": 47.54761663741556,
|
||||
"di": 0.048400000000006146,
|
||||
"dO": 0.15880000000000336,
|
||||
"dex": -7888.8449969662925,
|
||||
"dey": -736.3583175980484,
|
||||
"mT": 20553761.926370453,
|
||||
"mR": 46985.514538949996
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-09T17:37",
|
||||
"xage_h": 13.8,
|
||||
"R": 51446.268358212365,
|
||||
"T": 15308183.123688515,
|
||||
"N": -5564.4826493221235,
|
||||
"dA_km": 47.54331188363631,
|
||||
"di": 0.048499999999994436,
|
||||
"dO": 0.6269000000000058,
|
||||
"dex": -8016.165683777107,
|
||||
"dey": -402.45493298203405,
|
||||
"mT": 15179597.556924822,
|
||||
"mR": 46981.85561155733
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-10T17:45",
|
||||
"xage_h": 2.9,
|
||||
"R": 53263.35654009927,
|
||||
"T": 8580389.659196287,
|
||||
"N": -2318.666088893505,
|
||||
"dA_km": 47.56909692627778,
|
||||
"di": 0.04820000000000413,
|
||||
"dO": 0.14469999999998795,
|
||||
"dex": -7921.534256572002,
|
||||
"dey": 143.57808612758384,
|
||||
"mT": 8461897.028362235,
|
||||
"mR": 46991.616345465336
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-11T17:53",
|
||||
"xage_h": 3.1,
|
||||
"R": 54550.0623332573,
|
||||
"T": 1846837.5125594211,
|
||||
"N": -193.04127280518975,
|
||||
"dA_km": 47.595951177626375,
|
||||
"di": 0.048000000000002103,
|
||||
"dO": 0.13699999999999912,
|
||||
"dex": -7899.280713350153,
|
||||
"dey": 649.4787396541252,
|
||||
"mT": 1744195.6615911566,
|
||||
"mR": 47008.70145931934
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-11T22:43",
|
||||
"xage_h": 7.9,
|
||||
"R": 55217.78072005691,
|
||||
"T": 498276.48568582215,
|
||||
"N": 14.719970954402454,
|
||||
"dA_km": 47.596673418797764,
|
||||
"di": 0.04810000000000311,
|
||||
"dO": 0.3386999999999784,
|
||||
"dex": -7938.713847787283,
|
||||
"dey": 727.9745830575645,
|
||||
"mT": 400648.5541642541,
|
||||
"mR": 47011.6456086155
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-13T14:34",
|
||||
"xage_h": 0.0,
|
||||
"R": -379.3799917648357,
|
||||
"T": -465249.99398902856,
|
||||
"N": 155.68420920650658,
|
||||
"dA_km": -0.20016123426285048,
|
||||
"di": -0.0007999999999953703,
|
||||
"dO": -0.001300000000000428,
|
||||
"dex": 476.58445577466443,
|
||||
"dey": 146.51391943944606,
|
||||
"mT": -441405.656846914,
|
||||
"mR": -137.67734491649946
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-14T16:03",
|
||||
"xage_h": 9.6,
|
||||
"R": -370.6623293664961,
|
||||
"T": -435616.048035603,
|
||||
"N": 161.9253376635085,
|
||||
"dA_km": -0.20148191778662294,
|
||||
"di": -0.0007000000000070809,
|
||||
"dO": 0.40530000000000327,
|
||||
"dex": 382.3378309829219,
|
||||
"dey": 124.29206463422811,
|
||||
"mT": -411226.797806984,
|
||||
"mR": -137.69767118799967
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-15T17:32",
|
||||
"xage_h": 1.6,
|
||||
"R": -2503.896276883097,
|
||||
"T": -192460.3169489883,
|
||||
"N": -301.68892792708317,
|
||||
"dA_km": -2.392243099448933,
|
||||
"di": -0.0002000000000020231,
|
||||
"dO": 0.07029999999999868,
|
||||
"dex": 225.28681157558015,
|
||||
"dey": -2185.570947233477,
|
||||
"mT": -186092.5266262922,
|
||||
"mR": -2435.684305768334
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-15T23:54",
|
||||
"xage_h": 1.6,
|
||||
"R": -2520.7756466024875,
|
||||
"T": -102137.59140317388,
|
||||
"N": -361.49078247572623,
|
||||
"dA_km": -2.3967268360192975,
|
||||
"di": 0.00030000000000303466,
|
||||
"dO": 0.07080000000000373,
|
||||
"dex": 180.5525690268643,
|
||||
"dey": -2130.256216241443,
|
||||
"mT": -91829.42227460886,
|
||||
"mR": -2435.787483158333
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-16T14:14",
|
||||
"xage_h": -6.4,
|
||||
"R": 583.0853823463258,
|
||||
"T": -3270.557846359225,
|
||||
"N": -433.39312076937335,
|
||||
"dA_km": -0.08511771647772548,
|
||||
"di": 0.0004000000000040462,
|
||||
"dO": -0.2674999999999961,
|
||||
"dex": -624.1237466424434,
|
||||
"dey": 13.90747080039349,
|
||||
"mT": -1951.5043704634477,
|
||||
"mR": -566.8429822244725
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-16T19:00",
|
||||
"xage_h": -1.6,
|
||||
"R": 783.0003643284726,
|
||||
"T": -3185.0422923992455,
|
||||
"N": -464.5619337225683,
|
||||
"dA_km": -0.011322251530145877,
|
||||
"di": 0.0006999999999943587,
|
||||
"dO": -0.06390000000001027,
|
||||
"dex": -781.7527486226974,
|
||||
"dey": 13.879317152998238,
|
||||
"mT": -1793.30056536121,
|
||||
"mR": -566.9045184706075
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-17T07:45",
|
||||
"xage_h": 0.0,
|
||||
"R": -36.06833963021927,
|
||||
"T": -2062.7231150018165,
|
||||
"N": -131.69286971465834,
|
||||
"dA_km": -0.019333387072038022,
|
||||
"di": -0.00010000000000101155,
|
||||
"dO": 0.0011000000000111272,
|
||||
"dex": 18.03735131556586,
|
||||
"dey": -6.604478707308161,
|
||||
"mT": -1372.2502944852936,
|
||||
"mR": -566.865747285313
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-17T17:18",
|
||||
"xage_h": -3.2,
|
||||
"R": 4.599611484081834,
|
||||
"T": -844.7988259747882,
|
||||
"N": 52.9706873529662,
|
||||
"dA_km": -0.008262108925919165,
|
||||
"di": 0.0002000000000020231,
|
||||
"dO": -0.13600000000000173,
|
||||
"dex": 8.20241659340391,
|
||||
"dey": -42.37747499780447,
|
||||
"mT": -1056.1695658493434,
|
||||
"mR": -567.0426313209194
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-18T07:38",
|
||||
"xage_h": 0.0,
|
||||
"R": 152.94882451416925,
|
||||
"T": -5054.667467974843,
|
||||
"N": -0.0015860543004973268,
|
||||
"dA_km": 0.14360808967558114,
|
||||
"di": -0.00010000000001373377,
|
||||
"dO": 0.0,
|
||||
"dex": -6.1507886417584405,
|
||||
"dey": -24.215847918516292,
|
||||
"mT": -5048.3488581106485,
|
||||
"mR": -309.2383605079865
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-18T12:25",
|
||||
"xage_h": 0.0,
|
||||
"R": 166.4897103273688,
|
||||
"T": -9141.165631446744,
|
||||
"N": 11.970493629610738,
|
||||
"dA_km": 0.1460070334496777,
|
||||
"di": -0.00010000000001373377,
|
||||
"dO": -0.00010000000000101155,
|
||||
"dex": -14.68690078801386,
|
||||
"dey": -19.71195752297889,
|
||||
"mT": -8607.886910045327,
|
||||
"mR": -308.84969100266164
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-18T23:34",
|
||||
"xage_h": 1.6,
|
||||
"R": 524.4227639013843,
|
||||
"T": -173453.73135471187,
|
||||
"N": 117.0080639040351,
|
||||
"dA_km": 2.499636333592207,
|
||||
"di": 0.00010000000001373377,
|
||||
"dO": 0.06680000000000144,
|
||||
"dex": 2075.262986735747,
|
||||
"dey": 574.0477987188896,
|
||||
"mT": -169649.0269195732,
|
||||
"mR": 2103.8094298719734
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-19T07:32",
|
||||
"xage_h": 0.0,
|
||||
"R": 450.93551363061124,
|
||||
"T": -291684.47012140456,
|
||||
"N": 59.83498641613323,
|
||||
"dA_km": 2.5002181625650337,
|
||||
"di": 0.0,
|
||||
"dO": -0.0004999999999923356,
|
||||
"dex": 2239.136832296919,
|
||||
"dey": 653.8710657306938,
|
||||
"mT": -288802.92716569477,
|
||||
"mR": 2113.1023277569593
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-20T07:26",
|
||||
"xage_h": -3.2,
|
||||
"R": -248.29427269560256,
|
||||
"T": -478274.52944777923,
|
||||
"N": 113.74612285833368,
|
||||
"dA_km": -0.01352205154580588,
|
||||
"di": 0.0004000000000040462,
|
||||
"dO": -0.13649999999999404,
|
||||
"dex": 579.6990452869835,
|
||||
"dey": -1353.3356906949068,
|
||||
"mT": -471798.60920187604,
|
||||
"mR": -384.0763980043663
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-21T15:17",
|
||||
"xage_h": -4.8,
|
||||
"R": -106.56981143984012,
|
||||
"T": -475547.58343992,
|
||||
"N": 105.74999393116944,
|
||||
"dA_km": -0.00243878409582976,
|
||||
"di": 0.0003000000000157569,
|
||||
"dO": -0.2041999999999919,
|
||||
"dex": 459.9848709505605,
|
||||
"dey": -1304.92577357932,
|
||||
"mT": -474931.43770828,
|
||||
"mR": -384.41977726933646
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-22T13:35",
|
||||
"xage_h": -6.4,
|
||||
"R": -25.612278194785176,
|
||||
"T": -475275.7919380849,
|
||||
"N": 96.85685953749777,
|
||||
"dA_km": 0.00871163720421464,
|
||||
"di": 0.0003000000000157569,
|
||||
"dO": -0.2719000000000024,
|
||||
"dex": 401.45129456279716,
|
||||
"dey": -1314.8371707017855,
|
||||
"mT": null,
|
||||
"mR": null
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-23T07:06",
|
||||
"xage_h": 11.2,
|
||||
"R": 79.06994805762224,
|
||||
"T": -476056.157720825,
|
||||
"N": 73.51348049365886,
|
||||
"dA_km": -0.00039473106971854577,
|
||||
"di": 0.00040000000001676845,
|
||||
"dO": 0.4737999999999965,
|
||||
"dex": 185.24785242772384,
|
||||
"dey": -1290.0198369590855,
|
||||
"mT": null,
|
||||
"mR": null
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-24T07:00",
|
||||
"xage_h": 1.6,
|
||||
"R": 182.63009205929848,
|
||||
"T": -478012.9950510453,
|
||||
"N": -182.2959878778244,
|
||||
"dA_km": 0.01660918457491789,
|
||||
"di": 0.0007999999999953703,
|
||||
"dO": 0.06929999999998857,
|
||||
"dex": 162.54377588451672,
|
||||
"dey": -1311.8927366875912,
|
||||
"mT": null,
|
||||
"mR": null
|
||||
},
|
||||
{
|
||||
"ep": "2026-10-25T16:27",
|
||||
"xage_h": 0.0,
|
||||
"R": 310.18787481571053,
|
||||
"T": -482647.12777369755,
|
||||
"N": -179.45165744213804,
|
||||
"dA_km": 0.027587932723690756,
|
||||
"di": 0.0005999999999933472,
|
||||
"dO": 0.001499999999989729,
|
||||
"dex": 70.84036878505839,
|
||||
"dey": -1347.0923526858505,
|
||||
"mT": null,
|
||||
"mR": null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
COSMOS 2612
|
||||
1 68762U 26083H 26297.29186266 .00002093 00000+0 14092-3 0 9996
|
||||
2 68762 97.8545 77.8974 0006461 247.4186 112.6358 15.07858138 28631
|
||||
ICEYE-X36
|
||||
1 59103U 24043F 26297.22477300 .00003484 00000+0 23160-3 0 9995
|
||||
2 59103 97.8537 77.8281 0004894 236.2883 123.7878 15.07863566144584
|
||||
Reference in new issue
Block a user