Download frontend/src/lib/conjWorker.js from Harp404/Space: direct link, hf CLI and curl.
- Browser
- Download file 5.53 kB
-
https://huggingface.co/spaces/Harp404/Space/resolve/main/frontend/src/lib/conjWorker.js
- Command line
-
hf download hf://spaces/Harp404/Space/frontend/src/lib/conjWorker.js
-
curl -L -o conjWorker.js https://huggingface.co/spaces/Harp404/Space/resolve/main/frontend/src/lib/conjWorker.js
5.53 kB
| // Conjunction-screening Web Worker. | |
| // Pulls the full catalogue (3LE) from the gateway, then for a set of PRIMARY | |
| // objects screens against all others for close approaches over a time window: | |
| // apogee–perigee sieve → coarse SGP4 screen → fine TCA refine → Pc. | |
| // Runs off the main thread so the globe stays smooth. Posts progress + results. | |
| import * as satellite from 'satellite.js' | |
| const MU = 398600.4418 // km^3/s^2 | |
| const RE = 6378.137 // km | |
| function orbitParams(rec) { | |
| const nRadSec = rec.no / 60 | |
| const a = Math.cbrt(MU / (nRadSec * nRadSec)) | |
| const e = rec.ecco | |
| return { peri: a * (1 - e) - RE, apo: a * (1 + e) - RE } | |
| } | |
| // Screening-grade probability of collision (2-D circular Gaussian, Foster-style). | |
| // Honest: TLEs carry no covariance, so we assume a 1-sigma positional uncertainty | |
| // and a combined hard-body radius. This is a SCREENING estimate, not operational. | |
| function probCollision(missKm) { | |
| const sigma = 3.0 // km, combined 1-sigma — TLE error grows to a few km over a day | |
| const hbr = 0.03 // km, combined hard-body radius (~30 m) | |
| const pc = (hbr * hbr) / (2 * sigma * sigma) * Math.exp(-(missKm * missKm) / (2 * sigma * sigma)) | |
| return Math.min(pc, 1) | |
| } | |
| self.onmessage = async (ev) => { | |
| const { | |
| primaries, // array of NORAD ids to screen (the "watch list") | |
| startMs = Date.now(), | |
| hours = 24, | |
| coarseSec = 240, | |
| gateKm = 25, // apogee-perigee altitude gate | |
| candidateKm = 10, // keep approaches closer than this | |
| maxResults = 12, | |
| } = ev.data | |
| // 1) Fetch + parse the catalogue. | |
| let txt = '' | |
| try { | |
| const res = await fetch('/api/catalogue') | |
| txt = await res.text() | |
| } catch (e) { | |
| self.postMessage({ error: 'catalogue fetch failed: ' + e.message }); return | |
| } | |
| const lines = txt.split('\n').map((l) => l.trimEnd()) | |
| const objs = [] | |
| for (let i = 0; i + 2 < lines.length; i += 3) { | |
| let name = lines[i]; if (name.startsWith('0 ')) name = name.slice(2).trim() | |
| const l1 = lines[i + 1], l2 = lines[i + 2] | |
| if (!l1.startsWith('1 ') || !l2.startsWith('2 ')) continue | |
| const norad = parseInt(l2.substring(2, 7)); if (!norad) continue | |
| try { | |
| const rec = satellite.twoline2satrec(l1, l2) | |
| if (!rec || rec.error) continue | |
| objs.push({ norad, name, rec, ...orbitParams(rec) }) | |
| } catch { /* skip */ } | |
| } | |
| // 2) Candidate pairs via apogee-perigee sieve. | |
| const primarySet = new Set(primaries) | |
| const prim = objs.filter((o) => primarySet.has(o.norad)) | |
| const pairs = [] | |
| for (const A of prim) { | |
| for (const B of objs) { | |
| if (A.norad >= B.norad) continue // unordered, no self | |
| if (A.peri > B.apo + gateKm || B.peri > A.apo + gateKm) continue | |
| pairs.push([A, B]) | |
| } | |
| } | |
| // 3) Coarse screen — propagate only the involved objects once per step (cached). | |
| const involved = new Map() | |
| for (const [A, B] of pairs) { involved.set(A.norad, A); involved.set(B.norad, B) } | |
| const inv = [...involved.values()] | |
| const steps = Math.ceil((hours * 3600) / coarseSec) | |
| const best = new Map() | |
| for (let s = 0; s <= steps; s++) { | |
| const d = new Date(startMs + s * coarseSec * 1000) | |
| for (const o of inv) { const pv = satellite.propagate(o.rec, d); o._p = (pv && pv.position) ? pv.position : null } | |
| for (const [A, B] of pairs) { | |
| if (!A._p || !B._p) continue | |
| const dx = A._p.x - B._p.x, dy = A._p.y - B._p.y, dz = A._p.z - B._p.z | |
| const dist = Math.sqrt(dx * dx + dy * dy + dz * dz) | |
| const k = A.norad + '-' + B.norad | |
| const c = best.get(k) | |
| if (!c || dist < c.d) best.set(k, { d: dist, t: d.getTime(), A, B }) | |
| } | |
| if (s % 20 === 0) self.postMessage({ progress: s / steps, screened: pairs.length, objects: objs.length }) | |
| } | |
| // 4) Fine TCA refine for sub-candidate-threshold events, + Pc + relative speed. | |
| const results = [] | |
| for (const c of best.values()) { | |
| if (c.d > candidateKm) continue | |
| let bestD = c.d, bestT = c.t | |
| for (let tt = c.t - coarseSec * 1000; tt <= c.t + coarseSec * 1000; tt += 2000) { | |
| const d = new Date(tt) | |
| const pa = satellite.propagate(c.A.rec, d), pb = satellite.propagate(c.B.rec, d) | |
| if (!pa.position || !pb.position) continue | |
| const dx = pa.position.x - pb.position.x, dy = pa.position.y - pb.position.y, dz = pa.position.z - pb.position.z | |
| const dist = Math.sqrt(dx * dx + dy * dy + dz * dz) | |
| if (dist < bestD) { bestD = dist; bestT = tt } | |
| } | |
| // relative speed over 1 s at TCA | |
| const d1 = new Date(bestT), d2 = new Date(bestT + 1000) | |
| const a1 = satellite.propagate(c.A.rec, d1), b1 = satellite.propagate(c.B.rec, d1) | |
| const a2 = satellite.propagate(c.A.rec, d2), b2 = satellite.propagate(c.B.rec, d2) | |
| let relV = 0 | |
| if (a1.position && b1.position && a2.position && b2.position) { | |
| const r1x = a1.position.x - b1.position.x, r1y = a1.position.y - b1.position.y, r1z = a1.position.z - b1.position.z | |
| const r2x = a2.position.x - b2.position.x, r2y = a2.position.y - b2.position.y, r2z = a2.position.z - b2.position.z | |
| relV = Math.sqrt((r2x - r1x) ** 2 + (r2y - r1y) ** 2 + (r2z - r1z) ** 2) | |
| } | |
| results.push({ | |
| a: c.A.norad, aName: c.A.name, b: c.B.norad, bName: c.B.name, | |
| tcaMs: bestT, missKm: bestD, relVelKmS: relV, pc: probCollision(bestD), | |
| }) | |
| } | |
| results.sort((x, y) => x.missKm - y.missKm) | |
| self.postMessage({ conjunctions: results.slice(0, maxResults), screened: pairs.length, objects: objs.length, done: true }) | |
| } | |