spacemap 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -5
- package/dist/spacemap.iife.js +58 -58
- package/dist/spacemap.js +58 -58
- package/index.d.ts +193 -3
- package/index.js +1799 -99
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -24,6 +24,7 @@ var DEFAULT_HOST = {
|
|
|
24
24
|
messages: {
|
|
25
25
|
body_note_no_model: () => "no model available",
|
|
26
26
|
body_note_no_radius: () => "no size data available",
|
|
27
|
+
body_note_crash_site: () => "crash site, no wreckage model",
|
|
27
28
|
carried_by_scene_label: ({ carrier }) => `Carried by ${carrier}`,
|
|
28
29
|
scene_canvas_label: () => "Interactive 3D map of the Solar System",
|
|
29
30
|
attribution_orbits: () => "Orbits",
|
|
@@ -36,7 +37,17 @@ var DEFAULT_HOST = {
|
|
|
36
37
|
layer_nomenclature: () => "Named features",
|
|
37
38
|
cooperative_wheel: () => "Use ctrl + scroll to zoom the map",
|
|
38
39
|
cooperative_wheel_mac: () => "Use ⌘ + scroll to zoom the map",
|
|
39
|
-
cooperative_touch: () => "Use two fingers to move the map"
|
|
40
|
+
cooperative_touch: () => "Use two fingers to move the map",
|
|
41
|
+
planetary_system_primary: () => "primary",
|
|
42
|
+
planetary_system_retrograde: () => "retrograde",
|
|
43
|
+
planetary_system_axis_unit: () => "primary radii · log",
|
|
44
|
+
tab_rings: () => "Rings",
|
|
45
|
+
system_map_solar_system: () => "Solar System",
|
|
46
|
+
system_map_moons: ({ count }) => count === 1 ? "1 moon" : `${count} moons`,
|
|
47
|
+
system_map_zone_inner: () => "Inner asteroids & comets",
|
|
48
|
+
system_map_zone_outer: () => "Outer asteroids & comets",
|
|
49
|
+
unit_symbol_astronomical_unit: () => "AU",
|
|
50
|
+
system_map_axis_au: () => "AU · log"
|
|
40
51
|
},
|
|
41
52
|
bodyHref: () => "",
|
|
42
53
|
textures: "open"
|
|
@@ -6212,6 +6223,9 @@ var BodyIndex = class {
|
|
|
6212
6223
|
set orientationVersion(value) {
|
|
6213
6224
|
set(this.#orientationVersion, value, true);
|
|
6214
6225
|
}
|
|
6226
|
+
orientationRead = /* @__PURE__ */ new Set();
|
|
6227
|
+
hostRead = /* @__PURE__ */ new Map();
|
|
6228
|
+
hostReadVersion = 0;
|
|
6215
6229
|
childrenByParent = /* @__PURE__ */ new Map();
|
|
6216
6230
|
moonMaxAByParent = /* @__PURE__ */ new Map();
|
|
6217
6231
|
addListeners = /* @__PURE__ */ new Set();
|
|
@@ -6434,8 +6448,8 @@ var loadProgress = new LoadProgress();
|
|
|
6434
6448
|
//#endregion
|
|
6435
6449
|
//#region src/lib/fetch/fetch-timeout.ts
|
|
6436
6450
|
var BOOT_FETCH_TIMEOUT_MS = 3e4;
|
|
6437
|
-
function countBootBytes(res) {
|
|
6438
|
-
if (!loadProgress.active || !res.body) return res;
|
|
6451
|
+
function countBootBytes(res, background) {
|
|
6452
|
+
if (background || !loadProgress.active || !res.body) return res;
|
|
6439
6453
|
const total = Number(res.headers.get("content-length"));
|
|
6440
6454
|
if (!Number.isFinite(total) || total <= 0) return res;
|
|
6441
6455
|
loadProgress.announce(total);
|
|
@@ -6458,7 +6472,7 @@ async function fetchWithTimeout(input, init = {}, timeoutMs = BOOT_FETCH_TIMEOUT
|
|
|
6458
6472
|
return countBootBytes(await fetch(input, {
|
|
6459
6473
|
...init,
|
|
6460
6474
|
signal: controller.signal
|
|
6461
|
-
}));
|
|
6475
|
+
}), init.priority === "low");
|
|
6462
6476
|
} catch (e) {
|
|
6463
6477
|
if (controller.signal.aborted && !init.signal?.aborted) throw new Error(`Request timed out after ${timeoutMs} ms: ${input}`, { cause: e });
|
|
6464
6478
|
throw e;
|
|
@@ -7307,6 +7321,7 @@ function parseKeplerPayload(buffer, offset, method, float64) {
|
|
|
7307
7321
|
var LANDED_LATLNG_SCALE = 1e-7;
|
|
7308
7322
|
var LANDED_ALT_MM_SCALE = .001;
|
|
7309
7323
|
var LANDED_FLAG_STATIC = 1;
|
|
7324
|
+
var LANDED_FLAG_DESTROYED = 2;
|
|
7310
7325
|
var LANDED_SAMPLE_SIZE = 16;
|
|
7311
7326
|
var LANDED_HEADER_SIZE = 32;
|
|
7312
7327
|
function parseLandedPayload(buffer, offset, chunkStartEt) {
|
|
@@ -7336,6 +7351,7 @@ function parseLandedPayload(buffer, offset, chunkStartEt) {
|
|
|
7336
7351
|
return {
|
|
7337
7352
|
bodyNaifId,
|
|
7338
7353
|
isStatic: (flagsByte & LANDED_FLAG_STATIC) !== 0,
|
|
7354
|
+
isDestroyed: (flagsByte & LANDED_FLAG_DESTROYED) !== 0,
|
|
7339
7355
|
startEt: chunkStartEt + startOffsetS,
|
|
7340
7356
|
endEt: chunkStartEt + endOffsetS,
|
|
7341
7357
|
latRefDeg,
|
|
@@ -9549,7 +9565,7 @@ function memoizedGzJson(url, opts = {}) {
|
|
|
9549
9565
|
//#region src/lib/fetch/bundle-cache.ts
|
|
9550
9566
|
var MAX_BUNDLES = 24;
|
|
9551
9567
|
var cache$2 = /* @__PURE__ */ new Map();
|
|
9552
|
-
function fetchGzipBundle(url) {
|
|
9568
|
+
function fetchGzipBundle(url, init) {
|
|
9553
9569
|
let p = cache$2.get(url);
|
|
9554
9570
|
if (p) {
|
|
9555
9571
|
cache$2.delete(url);
|
|
@@ -9557,7 +9573,7 @@ function fetchGzipBundle(url) {
|
|
|
9557
9573
|
}
|
|
9558
9574
|
if (!p) {
|
|
9559
9575
|
p = (async () => {
|
|
9560
|
-
const res = await fetchWithTimeout(url);
|
|
9576
|
+
const res = await fetchWithTimeout(url, init);
|
|
9561
9577
|
if (!res.ok) {
|
|
9562
9578
|
if (res.status === 404) return {};
|
|
9563
9579
|
throw new Error(`fetchGzipBundle: ${url} returned ${res.status} ${res.statusText}`);
|
|
@@ -10308,20 +10324,35 @@ async function loadTierBitmaps(id, tier, priority) {
|
|
|
10308
10324
|
}));
|
|
10309
10325
|
}
|
|
10310
10326
|
var tierPrefetch = /* @__PURE__ */ new Map();
|
|
10327
|
+
var tierClaims = /* @__PURE__ */ new Map();
|
|
10311
10328
|
function tierKey(id, tier) {
|
|
10312
10329
|
return `${id}:${tier}`;
|
|
10313
10330
|
}
|
|
10314
|
-
function tierBitmaps(id, tier, priority) {
|
|
10331
|
+
function tierBitmaps(id, tier, priority, claim = true) {
|
|
10315
10332
|
const key = tierKey(id, tier);
|
|
10316
10333
|
let p = tierPrefetch.get(key);
|
|
10317
10334
|
if (!p) {
|
|
10318
|
-
|
|
10335
|
+
const loading = loadTierBitmaps(id, tier, priority);
|
|
10336
|
+
p = loading;
|
|
10319
10337
|
tierPrefetch.set(key, p);
|
|
10338
|
+
loading.catch(() => {
|
|
10339
|
+
if (tierPrefetch.get(key) !== loading) return;
|
|
10340
|
+
tierPrefetch.delete(key);
|
|
10341
|
+
tierClaims.delete(key);
|
|
10342
|
+
});
|
|
10320
10343
|
}
|
|
10344
|
+
if (claim) tierClaims.set(key, (tierClaims.get(key) ?? 0) + 1);
|
|
10321
10345
|
return p;
|
|
10322
10346
|
}
|
|
10323
10347
|
function releaseTierBitmaps(id, tier, bitmaps) {
|
|
10324
|
-
|
|
10348
|
+
const key = tierKey(id, tier);
|
|
10349
|
+
const left = (tierClaims.get(key) ?? 1) - 1;
|
|
10350
|
+
if (left > 0) {
|
|
10351
|
+
tierClaims.set(key, left);
|
|
10352
|
+
return;
|
|
10353
|
+
}
|
|
10354
|
+
tierClaims.delete(key);
|
|
10355
|
+
tierPrefetch.delete(key);
|
|
10325
10356
|
for (const b of bitmaps) b.close();
|
|
10326
10357
|
}
|
|
10327
10358
|
function makeCube(bitmaps) {
|
|
@@ -10336,10 +10367,10 @@ function pickLowTier(meta) {
|
|
|
10336
10367
|
}
|
|
10337
10368
|
function prefetchSkyboxTiers(meta) {
|
|
10338
10369
|
const low = pickLowTier(meta);
|
|
10339
|
-
if (low) tierBitmaps(meta.id, low).catch(() =>
|
|
10370
|
+
if (low) tierBitmaps(meta.id, low, void 0, false).catch(() => {});
|
|
10340
10371
|
}
|
|
10341
10372
|
var FULL_TIER_GATE_TIMEOUT_MS = 12e3;
|
|
10342
|
-
async function loadFromMeta(scene, renderer, meta) {
|
|
10373
|
+
async function loadFromMeta(scene, renderer, meta, disposed) {
|
|
10343
10374
|
const tier = pickTier$1(meta, Math.min(renderer.capabilities.maxTextureSize, currentRenderTier().maxSkyboxFace));
|
|
10344
10375
|
if (!tier) {
|
|
10345
10376
|
console.warn(`Skybox ${meta.id}: no tiers declared`);
|
|
@@ -10352,7 +10383,7 @@ async function loadFromMeta(scene, renderer, meta) {
|
|
|
10352
10383
|
};
|
|
10353
10384
|
let fullInstalled = false;
|
|
10354
10385
|
if (lowTier && lowTier !== tier) tierBitmaps(meta.id, lowTier).then((bitmaps) => {
|
|
10355
|
-
if (fullInstalled) {
|
|
10386
|
+
if (fullInstalled || disposed()) {
|
|
10356
10387
|
releaseTierBitmaps(meta.id, lowTier, bitmaps);
|
|
10357
10388
|
return;
|
|
10358
10389
|
}
|
|
@@ -10360,11 +10391,38 @@ async function loadFromMeta(scene, renderer, meta) {
|
|
|
10360
10391
|
lowRef.bitmaps = bitmaps;
|
|
10361
10392
|
scene.background = lowRef.cube;
|
|
10362
10393
|
performance.mark("sm-skybox-low");
|
|
10394
|
+
if (fullFailed) keepLow();
|
|
10363
10395
|
}).catch(() => {});
|
|
10396
|
+
let fullFailed = false;
|
|
10397
|
+
const keepLow = () => {
|
|
10398
|
+
if (!lowTier || !lowRef.cube || !lowRef.bitmaps) return;
|
|
10399
|
+
renderer.initTexture(lowRef.cube);
|
|
10400
|
+
releaseTierBitmaps(meta.id, lowTier, lowRef.bitmaps);
|
|
10401
|
+
lowRef.bitmaps = null;
|
|
10402
|
+
};
|
|
10364
10403
|
if (tier !== lowTier) await Promise.race([eagerMinorsDone, new Promise((resolve) => setTimeout(resolve, FULL_TIER_GATE_TIMEOUT_MS))]);
|
|
10365
|
-
|
|
10366
|
-
|
|
10404
|
+
let fullBitmaps;
|
|
10405
|
+
try {
|
|
10406
|
+
fullBitmaps = await tierBitmaps(meta.id, tier, tier === lowTier ? void 0 : "low");
|
|
10407
|
+
} catch (err) {
|
|
10408
|
+
fullFailed = true;
|
|
10409
|
+
if (!disposed()) keepLow();
|
|
10410
|
+
else if (lowTier && lowRef.bitmaps) {
|
|
10411
|
+
lowRef.cube?.dispose();
|
|
10412
|
+
releaseTierBitmaps(meta.id, lowTier, lowRef.bitmaps);
|
|
10413
|
+
lowRef.bitmaps = null;
|
|
10414
|
+
}
|
|
10415
|
+
throw err;
|
|
10416
|
+
}
|
|
10367
10417
|
await new Promise((resolve) => "requestIdleCallback" in window ? requestIdleCallback(() => resolve(), { timeout: 2e3 }) : setTimeout(resolve, 500));
|
|
10418
|
+
if (disposed()) {
|
|
10419
|
+
fullInstalled = true;
|
|
10420
|
+
lowRef.cube?.dispose();
|
|
10421
|
+
releaseTierBitmaps(meta.id, tier, fullBitmaps);
|
|
10422
|
+
if (lowTier && lowTier !== tier && lowRef.bitmaps) releaseTierBitmaps(meta.id, lowTier, lowRef.bitmaps);
|
|
10423
|
+
return;
|
|
10424
|
+
}
|
|
10425
|
+
const full = makeCube(fullBitmaps);
|
|
10368
10426
|
renderer.initTexture(full);
|
|
10369
10427
|
fullInstalled = true;
|
|
10370
10428
|
const prevBackground = scene.background;
|
|
@@ -10375,7 +10433,7 @@ async function loadFromMeta(scene, renderer, meta) {
|
|
|
10375
10433
|
releaseTierBitmaps(meta.id, tier, fullBitmaps);
|
|
10376
10434
|
if (lowTier && lowTier !== tier && lowRef.bitmaps) releaseTierBitmaps(meta.id, lowTier, lowRef.bitmaps);
|
|
10377
10435
|
}
|
|
10378
|
-
async function loadSkybox(scene, renderer, ctx) {
|
|
10436
|
+
async function loadSkybox(scene, renderer, ctx, disposed = () => false) {
|
|
10379
10437
|
if (ctx?.layers.skipped.has("stars")) return;
|
|
10380
10438
|
try {
|
|
10381
10439
|
const meta = await fetchMetadata();
|
|
@@ -10387,7 +10445,7 @@ async function loadSkybox(scene, renderer, ctx) {
|
|
|
10387
10445
|
attribution: meta.skybox.attribution,
|
|
10388
10446
|
description: meta.skybox.description
|
|
10389
10447
|
};
|
|
10390
|
-
await loadFromMeta(scene, renderer, meta.skybox);
|
|
10448
|
+
await loadFromMeta(scene, renderer, meta.skybox, disposed);
|
|
10391
10449
|
} catch (err) {
|
|
10392
10450
|
console.warn("Failed to load skybox:", err);
|
|
10393
10451
|
}
|
|
@@ -12390,6 +12448,7 @@ function bootThree(canvas, labelContainer, ctx) {
|
|
|
12390
12448
|
//#region src/lib/scene/minor-body-position.ts
|
|
12391
12449
|
function refreshMinorBodyPosition(body, jd, ctx) {
|
|
12392
12450
|
const d = body.data;
|
|
12451
|
+
body.placedJd = jd;
|
|
12393
12452
|
const isParabolic = d.q != null;
|
|
12394
12453
|
let parentPos;
|
|
12395
12454
|
if (d.parentId === "naif-0") parentPos = [
|
|
@@ -12428,6 +12487,47 @@ function refreshMinorBodyPosition(body, jd, ctx) {
|
|
|
12428
12487
|
}
|
|
12429
12488
|
body.positionUnknown = false;
|
|
12430
12489
|
}
|
|
12490
|
+
function refreshMoonPosition(body, jd, ctx) {
|
|
12491
|
+
const d = body.data;
|
|
12492
|
+
body.placedJd = jd;
|
|
12493
|
+
body.positionUnknown = true;
|
|
12494
|
+
if (d.unplaceable) return;
|
|
12495
|
+
if (d.visibleFromDays !== void 0 && jd - 2451545 < d.visibleFromDays) return;
|
|
12496
|
+
const parent = ctx.getBody(d.parentId);
|
|
12497
|
+
if (!parent || !placeForRead(parent, jd, ctx)) return;
|
|
12498
|
+
const parentPos = parent.position;
|
|
12499
|
+
const offset = ctx.chebStore?.has(d.id) ? ctx.chebStore.positionScene(d.id, jd) : d.a === 0 ? [
|
|
12500
|
+
0,
|
|
12501
|
+
0,
|
|
12502
|
+
0
|
|
12503
|
+
] : orbitalElementsToPositionJD(d, jd);
|
|
12504
|
+
if (!offset) return;
|
|
12505
|
+
body.position[0] = parentPos[0] + offset[0];
|
|
12506
|
+
body.position[1] = parentPos[1] + offset[1];
|
|
12507
|
+
body.position[2] = parentPos[2] + offset[2];
|
|
12508
|
+
if (body.orbitCenter) {
|
|
12509
|
+
body.orbitCenter[0] = parentPos[0];
|
|
12510
|
+
body.orbitCenter[1] = parentPos[1];
|
|
12511
|
+
body.orbitCenter[2] = parentPos[2];
|
|
12512
|
+
}
|
|
12513
|
+
body.positionUnknown = false;
|
|
12514
|
+
}
|
|
12515
|
+
var HOST_READ_MS = 2e3;
|
|
12516
|
+
function placeForRead(body, jd, ctx) {
|
|
12517
|
+
const d = body.data;
|
|
12518
|
+
const stale = body.placedJd !== jd;
|
|
12519
|
+
if (d.orbitalSource === OrbitalSource.SPICE_PROBE) {
|
|
12520
|
+
const { hostRead } = ctx.bodies;
|
|
12521
|
+
if (!stale && !hostRead.has(d.id)) return !body.positionUnknown;
|
|
12522
|
+
if (!hostRead.has(d.id)) ctx.bodies.hostReadVersion++;
|
|
12523
|
+
hostRead.set(d.id, performance.now());
|
|
12524
|
+
if (stale) return false;
|
|
12525
|
+
} else if (stale) {
|
|
12526
|
+
if (d.objectType === ObjectType.MOON) refreshMoonPosition(body, jd, ctx);
|
|
12527
|
+
else if (!ctx.bodies.bodiesById.has(d.id) && !isSurfaceFeature(body)) refreshMinorBodyPosition(body, jd, ctx);
|
|
12528
|
+
}
|
|
12529
|
+
return !body.positionUnknown;
|
|
12530
|
+
}
|
|
12431
12531
|
|
|
12432
12532
|
//#endregion
|
|
12433
12533
|
//#region src/lib/fetch/attitude/parse.ts
|
|
@@ -13101,6 +13201,7 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
|
|
|
13101
13201
|
fitToUnitRadius(root, anchor && baseFrame ? anchor.applyQuaternion(baseFrame) : anchor);
|
|
13102
13202
|
enableShadows(root);
|
|
13103
13203
|
modelScene.add(root);
|
|
13204
|
+
root.visible = !bo.isCrashed;
|
|
13104
13205
|
bo.model = root;
|
|
13105
13206
|
bo.modelName = slug;
|
|
13106
13207
|
setHaloLoading(bo, false);
|
|
@@ -14121,11 +14222,16 @@ function refreshVisibleBodyLabelRects(bodyObjects, screenWidth, screenHeight, ca
|
|
|
14121
14222
|
//#region src/lib/scene/label/factory.ts
|
|
14122
14223
|
var SPACECRAFT_ICON_D = "M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z";
|
|
14123
14224
|
var LANDED_PROBE_ICON_D = "M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z";
|
|
14124
|
-
|
|
14225
|
+
var SPACECRAFT_GLYPH_D = {
|
|
14226
|
+
flying: SPACECRAFT_ICON_D,
|
|
14227
|
+
landed: LANDED_PROBE_ICON_D,
|
|
14228
|
+
crashed: LANDED_PROBE_ICON_D + "M15 9l-6 6M9 9l6 6"
|
|
14229
|
+
};
|
|
14230
|
+
function setSpacecraftGlyph(halo, glyph) {
|
|
14125
14231
|
if (!halo) return;
|
|
14126
14232
|
const path = halo.querySelector("path");
|
|
14127
14233
|
if (!path) return;
|
|
14128
|
-
path.setAttribute("d",
|
|
14234
|
+
path.setAttribute("d", SPACECRAFT_GLYPH_D[glyph]);
|
|
14129
14235
|
}
|
|
14130
14236
|
function addLabelNameSpan(el, name, variant, isLarge) {
|
|
14131
14237
|
const span = document.createElement("span");
|
|
@@ -16334,6 +16440,8 @@ function attachRingBundles(bodyId, systemId, rings, radii, bo, scene, maxTexture
|
|
|
16334
16440
|
return;
|
|
16335
16441
|
}
|
|
16336
16442
|
node.mesh.userData.ringBundle = ringMeta.bundle;
|
|
16443
|
+
const host = bo.mesh ?? bo.model;
|
|
16444
|
+
if (host) node.mesh.quaternion.copy(host.quaternion);
|
|
16337
16445
|
bo.rings.push(node);
|
|
16338
16446
|
scene.add(node.mesh);
|
|
16339
16447
|
bo.extraObjects.push(node.mesh);
|
|
@@ -16755,6 +16863,7 @@ function applyRadiiToMesh(bo, radii) {
|
|
|
16755
16863
|
async function loadBodyTexture(bo, textureLoader, currentJd, scene, maxTextureSize, ctx) {
|
|
16756
16864
|
if (bo.textureTier || bo.textureLoading) return;
|
|
16757
16865
|
const detail = await fetchObjectDetail(bo.body.data.id, false);
|
|
16866
|
+
ctx?.bodies.orientationRead.add(bo.body.data.id);
|
|
16758
16867
|
if (!detail.global) return;
|
|
16759
16868
|
if (detail.global.orientation && !bo.body.orientation) applyBodyOrientation(bo, detail.global.orientation, ctx);
|
|
16760
16869
|
if (detail.global.radii && bo.radiusScene > 0 && !bo.radiiApplied && !detail.global.displacement?.absolute_radius) applyRadiiToMesh(bo, detail.global.radii);
|
|
@@ -17923,6 +18032,7 @@ async function loadSystemData(barycenterId, bodyObjects, scene, textureLoader, c
|
|
|
17923
18032
|
if (surface) ctx?.credits.registerImagery("surface", bodyId, barycenterId, surface);
|
|
17924
18033
|
const bo = bodyObjects.get(bodyId);
|
|
17925
18034
|
if (!bo?.mesh) continue;
|
|
18035
|
+
ctx?.bodies.orientationRead.add(bodyId);
|
|
17926
18036
|
if (bodyMeta.orientation) {
|
|
17927
18037
|
applyBodyOrientation(bo, bodyMeta.orientation, ctx, barycenterId);
|
|
17928
18038
|
if (bodyMeta.nut_prec) {
|
|
@@ -19087,6 +19197,7 @@ var SMALL_BODY_MAX_OPACITY = EMPHASIS_MAX_OPACITY;
|
|
|
19087
19197
|
var PointCloudSystem = class {
|
|
19088
19198
|
orbitPool = new OrbitWorkerPool();
|
|
19089
19199
|
pickRegistry = new PickRegistry();
|
|
19200
|
+
disposed = false;
|
|
19090
19201
|
workerPoints = {
|
|
19091
19202
|
asteroid: /* @__PURE__ */ new Map(),
|
|
19092
19203
|
spacecraft: /* @__PURE__ */ new Map()
|
|
@@ -19272,7 +19383,7 @@ var PointCloudSystem = class {
|
|
|
19272
19383
|
return true;
|
|
19273
19384
|
}
|
|
19274
19385
|
async rebuildMinorPass() {
|
|
19275
|
-
if (this.ctx.bodies.dirtyAsteroidZones.size === 0 && this.ctx.bodies.dirtySpacecraftGroups.size === 0) return;
|
|
19386
|
+
if (this.disposed || this.ctx.bodies.dirtyAsteroidZones.size === 0 && this.ctx.bodies.dirtySpacecraftGroups.size === 0) return;
|
|
19276
19387
|
const seedBasis = [
|
|
19277
19388
|
this.basisPos[0],
|
|
19278
19389
|
this.basisPos[1],
|
|
@@ -19292,6 +19403,7 @@ var PointCloudSystem = class {
|
|
|
19292
19403
|
groups: [],
|
|
19293
19404
|
baseWorker: 0
|
|
19294
19405
|
};
|
|
19406
|
+
if (this.disposed) return;
|
|
19295
19407
|
const cloudColor = bucket && groups.length > 0 ? bucket.cloudColor() : "#888888";
|
|
19296
19408
|
for (let i = 0; i < k; i++) {
|
|
19297
19409
|
const key = `${zone}#${i}`;
|
|
@@ -19319,6 +19431,7 @@ var PointCloudSystem = class {
|
|
|
19319
19431
|
this.ctx.bodies.dirtySpacecraftGroups.delete(gid);
|
|
19320
19432
|
drainedGroups.push(gid);
|
|
19321
19433
|
const { buckets, baseWorker } = await partitionForWorkersSliced(gid, drawnSpacecraft(this.ctx.layers, gid, bucket ? Array.from(bucket.values()) : []), k);
|
|
19434
|
+
if (this.disposed) return;
|
|
19322
19435
|
const skip = new Set(this.bodyObjects.keys());
|
|
19323
19436
|
for (let i = 0; i < k; i++) {
|
|
19324
19437
|
const key = `${gid}#${i}`;
|
|
@@ -19640,6 +19753,7 @@ var PointCloudSystem = class {
|
|
|
19640
19753
|
return this.moonPoints;
|
|
19641
19754
|
}
|
|
19642
19755
|
dispose() {
|
|
19756
|
+
this.disposed = true;
|
|
19643
19757
|
this.orbitPool.destroy();
|
|
19644
19758
|
this.pickRegistry.clear();
|
|
19645
19759
|
for (const kind of WORKER_CLOUD_KINDS) this.disposeClouds(this.workerPoints[kind]);
|
|
@@ -19910,9 +20024,15 @@ function updateBodyVisibility(bodyObjects, camera, ctx, focusTruePos, focusedBod
|
|
|
19910
20024
|
const cameraInverse = camera.matrixWorldInverse;
|
|
19911
20025
|
for (const bo of bodyObjects.values()) {
|
|
19912
20026
|
bo.cachedDist = f64dist(camTrue, bo.body.position);
|
|
19913
|
-
if (bo.noPhysical) {
|
|
20027
|
+
if (bo.isCrashed || bo.noPhysical) {
|
|
19914
20028
|
const isModel = bo.noPhysical === "model";
|
|
19915
|
-
|
|
20029
|
+
const near = sceneToKm(bo.cachedDist) < (bo.isCrashed || isModel ? .1 : 1);
|
|
20030
|
+
let text = null;
|
|
20031
|
+
if (near) {
|
|
20032
|
+
const m = host().messages;
|
|
20033
|
+
text = bo.isCrashed ? m.body_note_crash_site() : isModel ? m.body_note_no_model() : m.body_note_no_radius();
|
|
20034
|
+
}
|
|
20035
|
+
setLabelAnnotation(bo, "missing", text);
|
|
19916
20036
|
}
|
|
19917
20037
|
const label = bo.label;
|
|
19918
20038
|
if (!label) continue;
|
|
@@ -21254,18 +21374,32 @@ function updatePositions(params) {
|
|
|
21254
21374
|
for (const body of ctx.bodies.bodiesById.values()) positionMap.set(body.data.id, body.position);
|
|
21255
21375
|
for (const bo of bodyObjects.values()) if (!ctx.bodies.bodiesById.has(bo.body.data.id)) positionMap.set(bo.body.data.id, bo.body.position);
|
|
21256
21376
|
const computed = /* @__PURE__ */ new Set();
|
|
21377
|
+
const placeFirst = (id) => {
|
|
21378
|
+
if (computed.has(id)) return;
|
|
21379
|
+
const body = ctx.bodies.bodiesById.get(id) ?? bodyObjects.get(id)?.body;
|
|
21380
|
+
if (body) computePosition(body);
|
|
21381
|
+
};
|
|
21382
|
+
const now = performance.now();
|
|
21383
|
+
const hostReads = (id) => {
|
|
21384
|
+
const at = ctx.bodies.hostRead.get(id);
|
|
21385
|
+
if (at === void 0) return false;
|
|
21386
|
+
if (now - at < 2e3) return true;
|
|
21387
|
+
ctx.bodies.hostRead.delete(id);
|
|
21388
|
+
return false;
|
|
21389
|
+
};
|
|
21257
21390
|
const computePosition = (body) => {
|
|
21258
21391
|
if (computed.has(body.data.id)) return;
|
|
21259
21392
|
computed.add(body.data.id);
|
|
21260
21393
|
const d = body.data;
|
|
21261
21394
|
const bo = bodyObjects.get(d.id);
|
|
21262
|
-
if (bo && frame !== void 0 && d.id !== focusedId && !bo.group.visible && !bo.trail?.visible && (d.orbitalSource === OrbitalSource.SPICE_PROBE || !isMajorBody(d.objectType))) {
|
|
21395
|
+
if (bo && frame !== void 0 && d.id !== focusedId && !hostReads(d.id) && !bo.group.visible && !bo.trail?.visible && (d.orbitalSource === OrbitalSource.SPICE_PROBE || !isMajorBody(d.objectType))) {
|
|
21263
21396
|
bo.staggerSlot ??= nextStaggerSlot++ % HIDDEN_STRIDE;
|
|
21264
21397
|
if (frame % HIDDEN_STRIDE !== bo.staggerSlot) return;
|
|
21265
21398
|
}
|
|
21266
21399
|
const hide = (notForNotice = false) => {
|
|
21267
21400
|
if (bo) bo.outOfRange = true;
|
|
21268
21401
|
body.positionUnknown = true;
|
|
21402
|
+
body.placedJd = jd;
|
|
21269
21403
|
if (!notForNotice && d.id === focusedId) oorState.focusedOutOfRange = true;
|
|
21270
21404
|
};
|
|
21271
21405
|
if (d.unplaceable) {
|
|
@@ -21346,6 +21480,7 @@ function updatePositions(params) {
|
|
|
21346
21480
|
const probeLanded = located.probe.landed;
|
|
21347
21481
|
if (probeLanded && isLandedAt(located.probe, jd)) {
|
|
21348
21482
|
const flyingFrameId = d.parentId;
|
|
21483
|
+
placeFirst(`naif-${probeLanded.bodyNaifId}`);
|
|
21349
21484
|
const landedRender = renderLandedProbe(d, located.probe, probeLanded, jd, positionMap, ctx, bodyObjects);
|
|
21350
21485
|
if (!landedRender) {
|
|
21351
21486
|
hide();
|
|
@@ -21353,13 +21488,17 @@ function updatePositions(params) {
|
|
|
21353
21488
|
}
|
|
21354
21489
|
diagnostics.clear("probe-unavailable", d.id);
|
|
21355
21490
|
body.positionUnknown = false;
|
|
21491
|
+
body.placedJd = jd;
|
|
21356
21492
|
if (flyingFrameId !== d.parentId) body.trailBuffer?.clear();
|
|
21357
21493
|
if (bo) {
|
|
21358
21494
|
bo.outOfRange = false;
|
|
21359
|
-
|
|
21360
|
-
|
|
21495
|
+
const crashed = probeLanded.isDestroyed;
|
|
21496
|
+
if (!bo.isLanded || bo.isCrashed !== crashed) {
|
|
21497
|
+
setSpacecraftGlyph(bo.labelHalo, crashed ? "crashed" : "landed");
|
|
21361
21498
|
bo.isLanded = true;
|
|
21499
|
+
bo.isCrashed = crashed;
|
|
21362
21500
|
}
|
|
21501
|
+
if (bo.model) bo.model.visible = !crashed;
|
|
21363
21502
|
}
|
|
21364
21503
|
body.position[0] = landedRender.x;
|
|
21365
21504
|
body.position[1] = landedRender.y;
|
|
@@ -21398,8 +21537,13 @@ function updatePositions(params) {
|
|
|
21398
21537
|
}
|
|
21399
21538
|
diagnostics.clear("probe-unavailable", d.id);
|
|
21400
21539
|
if (bo?.isLanded) {
|
|
21401
|
-
|
|
21540
|
+
setSpacecraftGlyph(bo.labelHalo, "flying");
|
|
21402
21541
|
bo.isLanded = false;
|
|
21542
|
+
if (bo.isCrashed) {
|
|
21543
|
+
bo.isCrashed = false;
|
|
21544
|
+
if (bo.model) bo.model.visible = true;
|
|
21545
|
+
if (!bo.noPhysical) setLabelAnnotation(bo, "missing", null);
|
|
21546
|
+
}
|
|
21403
21547
|
}
|
|
21404
21548
|
const earthPos = positionMap.get(EARTH_ID);
|
|
21405
21549
|
const sunPos = positionMap.get(SUN_ID);
|
|
@@ -21424,10 +21568,7 @@ function updatePositions(params) {
|
|
|
21424
21568
|
populateProbeTrailBuffer(buf, ctx.probeStore, ctx.chebStore ?? null, d.id, probeParentKey, jd);
|
|
21425
21569
|
}
|
|
21426
21570
|
if (probeParentChanged) d.parentId = probeParentKey;
|
|
21427
|
-
|
|
21428
|
-
const parentBody = ctx.bodies.bodiesById.get(probeParentKey) ?? bodyObjects.get(probeParentKey)?.body;
|
|
21429
|
-
if (parentBody) computePosition(parentBody);
|
|
21430
|
-
}
|
|
21571
|
+
placeFirst(probeParentKey);
|
|
21431
21572
|
const probeParentPos = positionMap.get(probeParentKey);
|
|
21432
21573
|
if (!probeParentPos) {
|
|
21433
21574
|
hide();
|
|
@@ -21482,6 +21623,7 @@ function updatePositions(params) {
|
|
|
21482
21623
|
}
|
|
21483
21624
|
if (bo) bo.outOfRange = false;
|
|
21484
21625
|
body.positionUnknown = false;
|
|
21626
|
+
body.placedJd = jd;
|
|
21485
21627
|
body.position[0] = x;
|
|
21486
21628
|
body.position[1] = y;
|
|
21487
21629
|
body.position[2] = z;
|
|
@@ -23155,7 +23297,7 @@ function upgradeTargets(focus, ctx) {
|
|
|
23155
23297
|
//#endregion
|
|
23156
23298
|
//#region src/lib/scene/probe-coverage-watch.ts
|
|
23157
23299
|
var STOP_INSET_JD = 1e-6;
|
|
23158
|
-
function displayName(body) {
|
|
23300
|
+
function displayName$1(body) {
|
|
23159
23301
|
return body.data.name ?? body.data.id;
|
|
23160
23302
|
}
|
|
23161
23303
|
var ProbeCoverageWatch = class {
|
|
@@ -23198,7 +23340,7 @@ var ProbeCoverageWatch = class {
|
|
|
23198
23340
|
onHit(body, hitJd, forwardJd) {
|
|
23199
23341
|
this.notices.notify({
|
|
23200
23342
|
topic: "coverage-pause",
|
|
23201
|
-
name: displayName(body),
|
|
23343
|
+
name: displayName$1(body),
|
|
23202
23344
|
direction: hitJd >= forwardJd ? "forward" : "backward",
|
|
23203
23345
|
jd: hitJd
|
|
23204
23346
|
});
|
|
@@ -23274,10 +23416,14 @@ function rotateByQuaternion(q, x, y, z, out, at) {
|
|
|
23274
23416
|
out[at + 1] = y + q.w * ty + (q.z * tx - q.x * tz);
|
|
23275
23417
|
out[at + 2] = z + q.w * tz + (q.x * ty - q.y * tx);
|
|
23276
23418
|
}
|
|
23419
|
+
function bodyCentre(body, ctx, jd) {
|
|
23420
|
+
return placeForRead(body, jd, ctx) ? body.position : null;
|
|
23421
|
+
}
|
|
23277
23422
|
function resolveAnchor(anchor, ctx, jd) {
|
|
23278
23423
|
const body = ctx.getBody(anchor.body);
|
|
23279
|
-
|
|
23280
|
-
|
|
23424
|
+
const centre = body && bodyCentre(body, ctx, jd);
|
|
23425
|
+
if (!centre) return null;
|
|
23426
|
+
const [cx, cy, cz] = centre;
|
|
23281
23427
|
if (isSurface(anchor)) {
|
|
23282
23428
|
const distance = kmToScene(effectiveRadiusKm(body.data) + (anchor.altitudeKm ?? 0));
|
|
23283
23429
|
const [dx, dy, dz] = surfaceDirection(anchor.latitude, anchor.longitude);
|
|
@@ -23313,12 +23459,13 @@ function resolveAnchor(anchor, ctx, jd) {
|
|
|
23313
23459
|
cz + oz
|
|
23314
23460
|
];
|
|
23315
23461
|
}
|
|
23316
|
-
function anchorNormal(anchor, world, ctx) {
|
|
23462
|
+
function anchorNormal(anchor, world, ctx, jd) {
|
|
23317
23463
|
const body = ctx.getBody(anchor.body);
|
|
23318
|
-
|
|
23319
|
-
|
|
23320
|
-
const
|
|
23321
|
-
const
|
|
23464
|
+
const centre = body && bodyCentre(body, ctx, jd);
|
|
23465
|
+
if (!centre) return null;
|
|
23466
|
+
const dx = world[0] - centre[0];
|
|
23467
|
+
const dy = world[1] - centre[1];
|
|
23468
|
+
const dz = world[2] - centre[2];
|
|
23322
23469
|
const length = Math.hypot(dx, dy, dz);
|
|
23323
23470
|
if (length === 0) return null;
|
|
23324
23471
|
return [
|
|
@@ -23362,6 +23509,9 @@ function resolvePose(pose, ctx, jd) {
|
|
|
23362
23509
|
//#region src/lib/scene/camera/motion-scale.ts
|
|
23363
23510
|
var MIN_TRANSLATE_SCALE = .02;
|
|
23364
23511
|
var MIN_ROTATE_SCALE = 1e-4;
|
|
23512
|
+
function orbitDamping(reducedMotion) {
|
|
23513
|
+
return reducedMotion ? .4 : .05;
|
|
23514
|
+
}
|
|
23365
23515
|
function cameraMotionScale(camera, focusTruePos, focused, parent, seated) {
|
|
23366
23516
|
const orbitRadius = camera.position.length();
|
|
23367
23517
|
if (!(orbitRadius > 0)) return {
|
|
@@ -23437,8 +23587,6 @@ function updateUserLocationOcclusion(marker, bodyObjects, camera) {
|
|
|
23437
23587
|
//#endregion
|
|
23438
23588
|
//#region src/lib/scene/renderer.ts
|
|
23439
23589
|
var NO_TOUCH_GESTURE = -1;
|
|
23440
|
-
var DEFAULT_DAMPING = .05;
|
|
23441
|
-
var REDUCED_MOTION_DAMPING = .4;
|
|
23442
23590
|
var FAR_MARGIN = 1.2;
|
|
23443
23591
|
var FAR_MIN = kmToScene(1e6);
|
|
23444
23592
|
var FAR_UPDATE_EPS = .02;
|
|
@@ -23542,9 +23690,14 @@ var SceneRenderer = class SceneRenderer {
|
|
|
23542
23690
|
focusDurationMs: FOCUS_DURATION_MS
|
|
23543
23691
|
};
|
|
23544
23692
|
lastUpdatedJd = NaN;
|
|
23693
|
+
get placedJd() {
|
|
23694
|
+
return this.lastUpdatedJd;
|
|
23695
|
+
}
|
|
23545
23696
|
lastUpdatedSystemId = null;
|
|
23546
23697
|
lastSeatConfigKey = null;
|
|
23547
23698
|
lastProbeVersion = 0;
|
|
23699
|
+
disposed = false;
|
|
23700
|
+
lastHostReadVersion = 0;
|
|
23548
23701
|
focusWasOutOfRange = false;
|
|
23549
23702
|
_positionMapScratch = /* @__PURE__ */ new Map();
|
|
23550
23703
|
frameIndex = 0;
|
|
@@ -23647,7 +23800,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
23647
23800
|
this.orbitPreview = new OrbitPreviewOverlay(this.scene);
|
|
23648
23801
|
this.orbitPreview.setLayer(SceneRenderer.MAP_LAYER);
|
|
23649
23802
|
this.skyboxAdjuster.set(0, 0, 0);
|
|
23650
|
-
loadSkybox(this.scene, this.renderer, ctx);
|
|
23803
|
+
loadSkybox(this.scene, this.renderer, ctx, () => this.disposed);
|
|
23651
23804
|
const sunBody = ctx.bodies.majorBodies.find((b) => b.data.id === SUN_ID);
|
|
23652
23805
|
const resident = ctx.getBody(initialView.id);
|
|
23653
23806
|
const matchedBody = resident && !resident.positionUnknown ? resident : void 0;
|
|
@@ -23667,7 +23820,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
23667
23820
|
this.pointClouds.setPointBudget(this.tier.pointBudget);
|
|
23668
23821
|
this.controls = new OrbitControls(this.camera, canvas);
|
|
23669
23822
|
this.controls.enableDamping = true;
|
|
23670
|
-
this.controls.dampingFactor = sceneSettings().resolvedReducedMotion
|
|
23823
|
+
this.controls.dampingFactor = orbitDamping(sceneSettings().resolvedReducedMotion);
|
|
23671
23824
|
this.controls.minDistance = focusBody ? minCameraDistance(focusBody) : kmToScene(.01);
|
|
23672
23825
|
this.controls.maxDistance = 31620.5 * 10;
|
|
23673
23826
|
this.controls.target.set(0, 0, 0);
|
|
@@ -24249,7 +24402,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24249
24402
|
if (this.modelShadowPlane) {
|
|
24250
24403
|
this._tmpUp.set(0, 1, 0).applyQuaternion(bo.model.quaternion);
|
|
24251
24404
|
const daytime = this._tmpSun.dot(this._tmpUp) > .03;
|
|
24252
|
-
this.modelShadowPlane.visible = Boolean(bo.isLanded) && daytime;
|
|
24405
|
+
this.modelShadowPlane.visible = Boolean(bo.isLanded) && !bo.isCrashed && daytime;
|
|
24253
24406
|
if (this.modelShadowPlane.visible) this.modelShadowPlane.quaternion.setFromUnitVectors(this._planeNormal, this._tmpUp);
|
|
24254
24407
|
}
|
|
24255
24408
|
this._tmpV3.set(0, 0, 0);
|
|
@@ -24516,7 +24669,8 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24516
24669
|
const systemId = this.ctx.visibility.focusedSystemId;
|
|
24517
24670
|
const seatKey = this.focusedSeatConfigKey();
|
|
24518
24671
|
const probeVersion = this.ctx.probeStore?.version ?? 0;
|
|
24519
|
-
|
|
24672
|
+
const hostReadVersion = this.ctx.bodies.hostReadVersion;
|
|
24673
|
+
if (this.clock.jd === this.lastUpdatedJd && systemId === this.lastUpdatedSystemId && seatKey === this.lastSeatConfigKey && probeVersion === this.lastProbeVersion && hostReadVersion === this.lastHostReadVersion) {
|
|
24520
24674
|
this.clock.seeked = false;
|
|
24521
24675
|
return;
|
|
24522
24676
|
}
|
|
@@ -24526,6 +24680,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24526
24680
|
this.lastUpdatedSystemId = systemId;
|
|
24527
24681
|
this.lastSeatConfigKey = seatKey;
|
|
24528
24682
|
this.lastProbeVersion = probeVersion;
|
|
24683
|
+
this.lastHostReadVersion = hostReadVersion;
|
|
24529
24684
|
this.ctx.refreshTick(jdToDate(this.clock.jd));
|
|
24530
24685
|
this.focusController.promotion.onSimTimeChanged();
|
|
24531
24686
|
const result = updatePositions({
|
|
@@ -24555,7 +24710,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24555
24710
|
}
|
|
24556
24711
|
}
|
|
24557
24712
|
setReducedMotion(on) {
|
|
24558
|
-
this.controls.dampingFactor = on
|
|
24713
|
+
this.controls.dampingFactor = orbitDamping(on);
|
|
24559
24714
|
}
|
|
24560
24715
|
focusOnBody(id, zoom, latitude, longitude) {
|
|
24561
24716
|
this.disarmLimits();
|
|
@@ -24861,7 +25016,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24861
25016
|
this.renderer.setPixelRatio(renderPixelRatio());
|
|
24862
25017
|
this.composer.setPixelRatio(renderPixelRatio());
|
|
24863
25018
|
this.resize(this.canvas.clientWidth, this.canvas.clientHeight);
|
|
24864
|
-
loadSkybox(this.scene, this.renderer, this.ctx);
|
|
25019
|
+
loadSkybox(this.scene, this.renderer, this.ctx, () => this.disposed);
|
|
24865
25020
|
this.resume();
|
|
24866
25021
|
}
|
|
24867
25022
|
recoverWorkersIfDead(timeoutMs) {
|
|
@@ -24892,6 +25047,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24892
25047
|
setTrailResolution(width, height);
|
|
24893
25048
|
}
|
|
24894
25049
|
dispose() {
|
|
25050
|
+
this.disposed = true;
|
|
24895
25051
|
cancelAnimationFrame(this.rafId);
|
|
24896
25052
|
this.pointerInteraction.detach();
|
|
24897
25053
|
this.gpuPick.dispose();
|
|
@@ -25313,8 +25469,8 @@ var MarkerExtension = class {
|
|
|
25313
25469
|
this.label.visible = this.occlude ? this.facesCamera(world, frame) : true;
|
|
25314
25470
|
this.object.updateMatrixWorld(true);
|
|
25315
25471
|
}
|
|
25316
|
-
facesCamera(world, { camera, ctx }) {
|
|
25317
|
-
const normal = anchorNormal(this.anchor, world, ctx);
|
|
25472
|
+
facesCamera(world, { camera, ctx, jd }) {
|
|
25473
|
+
const normal = anchorNormal(this.anchor, world, ctx, jd);
|
|
25318
25474
|
if (!normal) return true;
|
|
25319
25475
|
const { position } = this.label;
|
|
25320
25476
|
const dx = camera.position.x - position.x;
|
|
@@ -26676,16 +26832,17 @@ var SurfaceShapeExtension = class {
|
|
|
26676
26832
|
}
|
|
26677
26833
|
update({ jd, basis, camera, ctx }) {
|
|
26678
26834
|
const body = this.wanted && this.count >= 2 ? ctx.getBody(this.body) : void 0;
|
|
26679
|
-
|
|
26680
|
-
if (this.
|
|
26681
|
-
if (
|
|
26835
|
+
const centre = body && bodyCentre(body, ctx, jd);
|
|
26836
|
+
if (this.line) this.line.visible = !!centre;
|
|
26837
|
+
if (this.fill) this.fill.visible = !!centre;
|
|
26838
|
+
if (!body || !centre) return;
|
|
26682
26839
|
const radiusKm = effectiveRadiusKm(body.data);
|
|
26683
26840
|
const scale = kmToScene(radiusKm + (this.altitudeKm ?? radiusKm * DEFAULT_LIFT));
|
|
26684
26841
|
const q = body.orientation ? bodyQuaternion(body.orientation, jd, body.nutPrec) : null;
|
|
26685
26842
|
const origin = [
|
|
26686
|
-
|
|
26687
|
-
|
|
26688
|
-
|
|
26843
|
+
centre[0] - basis[0],
|
|
26844
|
+
centre[1] - basis[1],
|
|
26845
|
+
centre[2] - basis[2]
|
|
26689
26846
|
];
|
|
26690
26847
|
if (this.line) {
|
|
26691
26848
|
this.place(this.dirs, this.count, q, scale, origin, this.scratch);
|
|
@@ -26915,6 +27072,50 @@ var MapObjectCollection = class {
|
|
|
26915
27072
|
}
|
|
26916
27073
|
};
|
|
26917
27074
|
|
|
27075
|
+
//#endregion
|
|
27076
|
+
//#region src/lib/scene/extensions/measure.ts
|
|
27077
|
+
function anchorOffsetKm(a, b, ctx, jd) {
|
|
27078
|
+
const from = resolveAnchor(a, ctx, jd);
|
|
27079
|
+
const to = resolveAnchor(b, ctx, jd);
|
|
27080
|
+
if (!from || !to) return null;
|
|
27081
|
+
return sceneToEcliptic([
|
|
27082
|
+
to[0] - from[0],
|
|
27083
|
+
to[1] - from[1],
|
|
27084
|
+
to[2] - from[2]
|
|
27085
|
+
]);
|
|
27086
|
+
}
|
|
27087
|
+
function anchorDistanceKm(a, b, ctx, jd) {
|
|
27088
|
+
const offset = anchorOffsetKm(a, b, ctx, jd);
|
|
27089
|
+
return offset && Math.hypot(...offset);
|
|
27090
|
+
}
|
|
27091
|
+
function subsolarPoint(id, ctx, jd) {
|
|
27092
|
+
const body = ctx.getBody(id);
|
|
27093
|
+
const sun = ctx.getBody(SUN_ID);
|
|
27094
|
+
if (!body || !sun || body === sun) return null;
|
|
27095
|
+
if (!body.orientation && !ctx.bodies.orientationRead.has(id)) return null;
|
|
27096
|
+
const centre = bodyCentre(body, ctx, jd);
|
|
27097
|
+
if (!centre) return null;
|
|
27098
|
+
const turn = body.orientation ? bodyQuaternion(body.orientation, jd, body.nutPrec).toArray() : [
|
|
27099
|
+
0,
|
|
27100
|
+
0,
|
|
27101
|
+
0,
|
|
27102
|
+
1
|
|
27103
|
+
];
|
|
27104
|
+
const { latitude, longitude, distance } = cartesianToSpherical([
|
|
27105
|
+
sun.position[0],
|
|
27106
|
+
sun.position[1],
|
|
27107
|
+
sun.position[2]
|
|
27108
|
+
], [
|
|
27109
|
+
centre[0],
|
|
27110
|
+
centre[1],
|
|
27111
|
+
centre[2]
|
|
27112
|
+
], turn);
|
|
27113
|
+
return distance > 0 ? {
|
|
27114
|
+
lon: longitude,
|
|
27115
|
+
lat: latitude
|
|
27116
|
+
} : null;
|
|
27117
|
+
}
|
|
27118
|
+
|
|
26918
27119
|
//#endregion
|
|
26919
27120
|
//#region src/lib/interaction/gesture.ts
|
|
26920
27121
|
var GestureHandler = class {
|
|
@@ -27095,7 +27296,8 @@ var KEY_ROTATE_RAD = .05;
|
|
|
27095
27296
|
var KEY_ZOOM_FACTOR = 1.15;
|
|
27096
27297
|
var CONTEXT_LOST_PANEL_DELAY_MS = 2e3;
|
|
27097
27298
|
var RESTORE_PING_TIMEOUT_MS = 4e3;
|
|
27098
|
-
var
|
|
27299
|
+
var mapsIn = /* @__PURE__ */ new WeakMap();
|
|
27300
|
+
var onceTarget$2 = /* @__PURE__ */ new WeakMap();
|
|
27099
27301
|
var SpaceMap = class {
|
|
27100
27302
|
ctx = new ContextManager();
|
|
27101
27303
|
clock;
|
|
@@ -27196,14 +27398,14 @@ var SpaceMap = class {
|
|
|
27196
27398
|
off();
|
|
27197
27399
|
listener(...args);
|
|
27198
27400
|
};
|
|
27199
|
-
onceTarget$
|
|
27401
|
+
onceTarget$2.set(wrapped, listener);
|
|
27200
27402
|
const off = this.on(event, wrapped);
|
|
27201
27403
|
return off;
|
|
27202
27404
|
}
|
|
27203
27405
|
off(event, listener) {
|
|
27204
27406
|
const set = this.listeners[event];
|
|
27205
27407
|
set.delete(listener);
|
|
27206
|
-
for (const held of set) if (onceTarget$
|
|
27408
|
+
for (const held of set) if (onceTarget$2.get(held) === listener) set.delete(held);
|
|
27207
27409
|
}
|
|
27208
27410
|
emit(event, ...args) {
|
|
27209
27411
|
for (const listener of this.listeners[event]) listener(...args);
|
|
@@ -27221,6 +27423,19 @@ var SpaceMap = class {
|
|
|
27221
27423
|
getChildren(id) {
|
|
27222
27424
|
return [...this.ctx.bodies.getChildren(id) ?? []];
|
|
27223
27425
|
}
|
|
27426
|
+
get placedJd() {
|
|
27427
|
+
const jd = this.renderer?.placedJd;
|
|
27428
|
+
return jd === void 0 || Number.isNaN(jd) ? this.clock.jd : jd;
|
|
27429
|
+
}
|
|
27430
|
+
distanceKm(from, to) {
|
|
27431
|
+
return anchorDistanceKm(from, to, this.ctx, this.placedJd);
|
|
27432
|
+
}
|
|
27433
|
+
offsetKm(from, to) {
|
|
27434
|
+
return anchorOffsetKm(from, to, this.ctx, this.placedJd);
|
|
27435
|
+
}
|
|
27436
|
+
getSubsolarPoint(id) {
|
|
27437
|
+
return subsolarPoint(id, this.ctx, this.placedJd);
|
|
27438
|
+
}
|
|
27224
27439
|
load(targetId = this.initialView.id) {
|
|
27225
27440
|
return this.ctx.load(jdToDate(this.clock.jd), targetId);
|
|
27226
27441
|
}
|
|
@@ -27252,6 +27467,7 @@ var SpaceMap = class {
|
|
|
27252
27467
|
if (this.container) throw new Error("SpaceMap is already mounted");
|
|
27253
27468
|
this.container = container;
|
|
27254
27469
|
container.classList.add("sm-map");
|
|
27470
|
+
mapsIn.set(container, (mapsIn.get(container) ?? 0) + 1);
|
|
27255
27471
|
this.controls = new ControlHost(this, container);
|
|
27256
27472
|
const canvas = document.createElement("canvas");
|
|
27257
27473
|
canvas.className = "sm-map__canvas";
|
|
@@ -27331,7 +27547,9 @@ var SpaceMap = class {
|
|
|
27331
27547
|
this.renderer = null;
|
|
27332
27548
|
canvas.remove();
|
|
27333
27549
|
this.labelLayer?.remove();
|
|
27334
|
-
|
|
27550
|
+
const left = (mapsIn.get(container) ?? 1) - 1;
|
|
27551
|
+
mapsIn.set(container, left);
|
|
27552
|
+
if (left === 0) container.classList.remove("sm-map");
|
|
27335
27553
|
this.container = this.canvas = this.labelLayer = null;
|
|
27336
27554
|
}
|
|
27337
27555
|
watch(renderer) {
|
|
@@ -27752,7 +27970,7 @@ var SpaceMap = class {
|
|
|
27752
27970
|
|
|
27753
27971
|
//#endregion
|
|
27754
27972
|
//#region src/lib/flatmap/layers.ts
|
|
27755
|
-
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
27973
|
+
var SVG_NS$1 = "http://www.w3.org/2000/svg";
|
|
27756
27974
|
function layerInfo(layer) {
|
|
27757
27975
|
return {
|
|
27758
27976
|
id: layer.id,
|
|
@@ -27770,7 +27988,7 @@ function creditOf(bundle) {
|
|
|
27770
27988
|
};
|
|
27771
27989
|
}
|
|
27772
27990
|
function pathElement(d, className) {
|
|
27773
|
-
const path = document.createElementNS(SVG_NS, "path");
|
|
27991
|
+
const path = document.createElementNS(SVG_NS$1, "path");
|
|
27774
27992
|
path.setAttribute("d", d);
|
|
27775
27993
|
path.setAttribute("class", className);
|
|
27776
27994
|
return path;
|
|
@@ -27789,7 +28007,7 @@ function graticuleLayer(label, stepDeg = 30) {
|
|
|
27789
28007
|
if (viewport.projection.azimuthal) {
|
|
27790
28008
|
const centre = viewport.toScreen(0, 0);
|
|
27791
28009
|
const edge = viewport.toScreen(viewport.projection.extent.maxX, 0);
|
|
27792
|
-
const circle = document.createElementNS(SVG_NS, "circle");
|
|
28010
|
+
const circle = document.createElementNS(SVG_NS$1, "circle");
|
|
27793
28011
|
circle.setAttribute("cx", String(centre[0]));
|
|
27794
28012
|
circle.setAttribute("cy", String(centre[1]));
|
|
27795
28013
|
circle.setAttribute("r", String(Math.abs(edge[0] - centre[0])));
|
|
@@ -27826,12 +28044,12 @@ function nomenclatureLayer(bodyId, label) {
|
|
|
27826
28044
|
for (const shift of shifts) {
|
|
27827
28045
|
const x = at[0] + shift;
|
|
27828
28046
|
if (x < 0 || x > viewport.width) continue;
|
|
27829
|
-
const dot = document.createElementNS(SVG_NS, "circle");
|
|
28047
|
+
const dot = document.createElementNS(SVG_NS$1, "circle");
|
|
27830
28048
|
dot.setAttribute("cx", x.toFixed(1));
|
|
27831
28049
|
dot.setAttribute("cy", at[1].toFixed(1));
|
|
27832
28050
|
dot.setAttribute("r", "1.6");
|
|
27833
28051
|
dot.setAttribute("class", "sm-flat__feature-dot");
|
|
27834
|
-
const text = document.createElementNS(SVG_NS, "text");
|
|
28052
|
+
const text = document.createElementNS(SVG_NS$1, "text");
|
|
27835
28053
|
text.setAttribute("x", (x + 4).toFixed(1));
|
|
27836
28054
|
text.setAttribute("y", (at[1] + 3).toFixed(1));
|
|
27837
28055
|
text.setAttribute("class", "sm-flat__feature-label");
|
|
@@ -27878,12 +28096,12 @@ var PathDrawing = class {
|
|
|
27878
28096
|
this.onRemove = onRemove;
|
|
27879
28097
|
this.onChange = onChange;
|
|
27880
28098
|
this.points = points;
|
|
27881
|
-
this.node = document.createElementNS(SVG_NS, "g");
|
|
28099
|
+
this.node = document.createElementNS(SVG_NS$1, "g");
|
|
27882
28100
|
applyStyle(this.node, style);
|
|
27883
28101
|
bindEvents(this.node, style);
|
|
27884
|
-
this.area = closed ? document.createElementNS(SVG_NS, "path") : null;
|
|
28102
|
+
this.area = closed ? document.createElementNS(SVG_NS$1, "path") : null;
|
|
27885
28103
|
this.area?.setAttribute("stroke", "none");
|
|
27886
|
-
this.edge = document.createElementNS(SVG_NS, "path");
|
|
28104
|
+
this.edge = document.createElementNS(SVG_NS$1, "path");
|
|
27887
28105
|
this.edge.setAttribute("fill", "none");
|
|
27888
28106
|
if (this.area) this.node.append(this.area);
|
|
27889
28107
|
this.node.append(this.edge);
|
|
@@ -28204,27 +28422,33 @@ async function fetchJson(url) {
|
|
|
28204
28422
|
return null;
|
|
28205
28423
|
}
|
|
28206
28424
|
}
|
|
28207
|
-
async function
|
|
28208
|
-
const
|
|
28209
|
-
|
|
28210
|
-
|
|
28211
|
-
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
|
|
28215
|
-
].includes(k));
|
|
28216
|
-
if (tiers.length === 0) return {};
|
|
28217
|
-
return { surface: {
|
|
28425
|
+
async function tiersOf(bundleId) {
|
|
28426
|
+
const found = await Promise.all(TIER_ORDER.map((tier) => fetch(versionedUrl(`/v1/textures/${bundleId}/${tier}.webp`, "textures"), { method: "HEAD" }).then((response) => response.ok).catch(() => false)));
|
|
28427
|
+
return TIER_ORDER.filter((_, i) => found[i]);
|
|
28428
|
+
}
|
|
28429
|
+
async function fromRecord(bodyId, record, tiers = tiersOf) {
|
|
28430
|
+
if (!record?.map_texture_available || !record.texture) return {};
|
|
28431
|
+
const surface = pickTexture([{
|
|
28432
|
+
...record.texture,
|
|
28218
28433
|
id: bodyId,
|
|
28219
|
-
tiers
|
|
28220
|
-
|
|
28221
|
-
|
|
28222
|
-
|
|
28223
|
-
|
|
28224
|
-
|
|
28225
|
-
|
|
28226
|
-
|
|
28227
|
-
|
|
28434
|
+
tiers: void 0
|
|
28435
|
+
}, ...record.alternates ?? []]);
|
|
28436
|
+
if (!surface) return {};
|
|
28437
|
+
const written = surface.tiers ?? await tiers(surface.id);
|
|
28438
|
+
if (written.length === 0) return {};
|
|
28439
|
+
const { frames, ...bundle } = surface;
|
|
28440
|
+
const radii = record.radii;
|
|
28441
|
+
return {
|
|
28442
|
+
surface: {
|
|
28443
|
+
...bundle,
|
|
28444
|
+
tiers: written,
|
|
28445
|
+
monthlyFrames: frames
|
|
28446
|
+
},
|
|
28447
|
+
radiusKm: radii ? (radii.a + radii.b + radii.c) / 3 : void 0
|
|
28448
|
+
};
|
|
28449
|
+
}
|
|
28450
|
+
async function fromBundle(bodyId) {
|
|
28451
|
+
return fromRecord(bodyId, (await fetchObjectDetail(bodyId, false).catch(() => null))?.global);
|
|
28228
28452
|
}
|
|
28229
28453
|
async function loadBodySources(bodyId) {
|
|
28230
28454
|
const systemId = systemIdFor(bodyId);
|
|
@@ -28525,14 +28749,14 @@ var RASTER_LAYERS = [
|
|
|
28525
28749
|
function isRaster(layer) {
|
|
28526
28750
|
return layer.kind === "raster";
|
|
28527
28751
|
}
|
|
28528
|
-
var onceTarget = /* @__PURE__ */ new WeakMap();
|
|
28752
|
+
var onceTarget$1 = /* @__PURE__ */ new WeakMap();
|
|
28529
28753
|
var FlatMap = class {
|
|
28530
28754
|
container = null;
|
|
28531
28755
|
root = document.createElement("div");
|
|
28532
28756
|
canvas = document.createElement("canvas");
|
|
28533
|
-
svg = document.createElementNS(SVG_NS, "svg");
|
|
28534
|
-
layerGroup = document.createElementNS(SVG_NS, "g");
|
|
28535
|
-
shapeGroup = document.createElementNS(SVG_NS, "g");
|
|
28757
|
+
svg = document.createElementNS(SVG_NS$1, "svg");
|
|
28758
|
+
layerGroup = document.createElementNS(SVG_NS$1, "g");
|
|
28759
|
+
shapeGroup = document.createElementNS(SVG_NS$1, "g");
|
|
28536
28760
|
markerLayer = document.createElement("div");
|
|
28537
28761
|
ctx;
|
|
28538
28762
|
overlay;
|
|
@@ -28903,7 +29127,7 @@ var FlatMap = class {
|
|
|
28903
29127
|
off();
|
|
28904
29128
|
listener(...args);
|
|
28905
29129
|
});
|
|
28906
|
-
onceTarget.set(wrapped, listener);
|
|
29130
|
+
onceTarget$1.set(wrapped, listener);
|
|
28907
29131
|
const off = this.on(event, wrapped);
|
|
28908
29132
|
return off;
|
|
28909
29133
|
}
|
|
@@ -28911,7 +29135,7 @@ var FlatMap = class {
|
|
|
28911
29135
|
const set = this.listeners.get(event);
|
|
28912
29136
|
if (!set) return;
|
|
28913
29137
|
set.delete(listener);
|
|
28914
|
-
for (const held of set) if (onceTarget.get(held) === listener) set.delete(held);
|
|
29138
|
+
for (const held of set) if (onceTarget$1.get(held) === listener) set.delete(held);
|
|
28915
29139
|
}
|
|
28916
29140
|
emit(event, ...args) {
|
|
28917
29141
|
const set = this.listeners.get(event);
|
|
@@ -29115,7 +29339,7 @@ var FlatMap = class {
|
|
|
29115
29339
|
this.layerGroup.replaceChildren();
|
|
29116
29340
|
for (const layer of this.layerList) {
|
|
29117
29341
|
if (isRaster(layer) || !layer.visible) continue;
|
|
29118
|
-
const group = document.createElementNS(SVG_NS, "g");
|
|
29342
|
+
const group = document.createElementNS(SVG_NS$1, "g");
|
|
29119
29343
|
group.setAttribute("data-layer", layer.id);
|
|
29120
29344
|
layer.render(viewport, group);
|
|
29121
29345
|
this.layerGroup.append(group);
|
|
@@ -30048,6 +30272,1455 @@ var PanoramaView = class {
|
|
|
30048
30272
|
};
|
|
30049
30273
|
};
|
|
30050
30274
|
|
|
30275
|
+
//#endregion
|
|
30276
|
+
//#region src/lib/fetch/groups/planetary-systems-map.ts
|
|
30277
|
+
var fetchPlanetarySystemsMap = memoizedGzJson(() => `${dataBase()}/v1/groups/__planetary_systems_map__.json.gz`, { error: (res) => `Failed to fetch planetary systems map: ${res.status}` });
|
|
30278
|
+
|
|
30279
|
+
//#endregion
|
|
30280
|
+
//#region src/lib/fetch/groups/solar-system-map.ts
|
|
30281
|
+
var fetchSolarSystemMap = memoizedGzJson(() => `${dataBase()}/v1/groups/__solar_system_map__.json.gz`, { error: (res) => `Failed to fetch solar system map: ${res.status}` });
|
|
30282
|
+
|
|
30283
|
+
//#endregion
|
|
30284
|
+
//#region src/lib/charts/scrub.ts
|
|
30285
|
+
var DRAG_SLOP = 8;
|
|
30286
|
+
function createScrub({ onScrub, onEnd }) {
|
|
30287
|
+
let downX = null;
|
|
30288
|
+
let downY = 0;
|
|
30289
|
+
let scrubbing = false;
|
|
30290
|
+
function end() {
|
|
30291
|
+
if (scrubbing) onEnd();
|
|
30292
|
+
downX = null;
|
|
30293
|
+
scrubbing = false;
|
|
30294
|
+
}
|
|
30295
|
+
return {
|
|
30296
|
+
onpointerdown(e) {
|
|
30297
|
+
if (e.pointerType === "mouse") return;
|
|
30298
|
+
downX = e.clientX;
|
|
30299
|
+
downY = e.clientY;
|
|
30300
|
+
scrubbing = false;
|
|
30301
|
+
},
|
|
30302
|
+
onpointermove(e) {
|
|
30303
|
+
if (e.pointerType === "mouse" || downX === null) return;
|
|
30304
|
+
if (!scrubbing && Math.hypot(e.clientX - downX, e.clientY - downY) < 8) return;
|
|
30305
|
+
scrubbing = true;
|
|
30306
|
+
onScrub(e.clientX, e.clientY);
|
|
30307
|
+
},
|
|
30308
|
+
onpointerup: end,
|
|
30309
|
+
onpointercancel: end,
|
|
30310
|
+
onpointerleave: end
|
|
30311
|
+
};
|
|
30312
|
+
}
|
|
30313
|
+
|
|
30314
|
+
//#endregion
|
|
30315
|
+
//#region src/lib/systemmap/layout.ts
|
|
30316
|
+
var VIEW_H = 240;
|
|
30317
|
+
var X_LEFT = 44;
|
|
30318
|
+
var X_RIGHT = 700;
|
|
30319
|
+
var CY = 120;
|
|
30320
|
+
var MIN_R = 2.2;
|
|
30321
|
+
var MAX_OFFSET = 84;
|
|
30322
|
+
var PRIMARY_LIMB_X = 44 - 4;
|
|
30323
|
+
var MOON_GAP = 4;
|
|
30324
|
+
var MOON_SPACING = 3;
|
|
30325
|
+
var HIT_MARGIN = 16;
|
|
30326
|
+
var INSIDE = 1e4;
|
|
30327
|
+
var MOON_ZONE_W = 34;
|
|
30328
|
+
var MOON_ZONE_PAD = 7;
|
|
30329
|
+
var RING_FORESHORTEN = .42;
|
|
30330
|
+
var BAND_Y = 26;
|
|
30331
|
+
var BAND_H = 240 - 60;
|
|
30332
|
+
var BAND_MIN_HIT = 30;
|
|
30333
|
+
function minDistance(cx, cy, placed) {
|
|
30334
|
+
let min = Infinity;
|
|
30335
|
+
for (const p of placed) min = Math.min(min, Math.hypot(p.cx - cx, p.cy - cy));
|
|
30336
|
+
return min;
|
|
30337
|
+
}
|
|
30338
|
+
function foldTilt(tiltDeg) {
|
|
30339
|
+
return tiltDeg > 90 ? 180 - tiltDeg : tiltDeg;
|
|
30340
|
+
}
|
|
30341
|
+
function layout(model) {
|
|
30342
|
+
const logLo = Math.log10(model.domain[0]);
|
|
30343
|
+
const logSpan = Math.log10(model.domain[1]) - logLo;
|
|
30344
|
+
const xOf = (km) => {
|
|
30345
|
+
return 44 + (Math.log10(Math.max(km / model.unitKm, 1e-9)) - logLo) / logSpan * (700 - 44);
|
|
30346
|
+
};
|
|
30347
|
+
const bodies = [];
|
|
30348
|
+
for (const b of [...model.bodies].sort((a, b) => a.aKm - b.aKm)) {
|
|
30349
|
+
const cx = xOf(b.aKm);
|
|
30350
|
+
const r = Math.max(MIN_R, b.radiusKm * model.pxPerKm);
|
|
30351
|
+
let cy = 120;
|
|
30352
|
+
if (b.tiltDeg > 0) {
|
|
30353
|
+
const offset = Math.min(foldTilt(b.tiltDeg) * model.pxPerDeg, MAX_OFFSET);
|
|
30354
|
+
const up = 120 - offset;
|
|
30355
|
+
const down = 120 + offset;
|
|
30356
|
+
cy = minDistance(cx, up, bodies) >= minDistance(cx, down, bodies) ? up : down;
|
|
30357
|
+
}
|
|
30358
|
+
bodies.push({
|
|
30359
|
+
...b,
|
|
30360
|
+
cx,
|
|
30361
|
+
cy,
|
|
30362
|
+
r
|
|
30363
|
+
});
|
|
30364
|
+
}
|
|
30365
|
+
const moonZones = [];
|
|
30366
|
+
for (const parent of bodies) {
|
|
30367
|
+
if (!parent.satellites?.length) continue;
|
|
30368
|
+
const ringTop = parent.rings ? parent.r * parent.rings.outer * RING_FORESHORTEN : 0;
|
|
30369
|
+
let y = parent.cy - Math.max(parent.r, ringTop) - MOON_GAP;
|
|
30370
|
+
const moons = [];
|
|
30371
|
+
for (const s of [...parent.satellites].sort((a, b) => b.radiusKm - a.radiusKm)) {
|
|
30372
|
+
const r = Math.max(MIN_R, s.radiusKm * model.pxPerKm);
|
|
30373
|
+
y -= r;
|
|
30374
|
+
moons.push({
|
|
30375
|
+
...s,
|
|
30376
|
+
cx: parent.cx,
|
|
30377
|
+
cy: y,
|
|
30378
|
+
r,
|
|
30379
|
+
hitY: 0,
|
|
30380
|
+
hitHeight: 0
|
|
30381
|
+
});
|
|
30382
|
+
y -= r + MOON_SPACING;
|
|
30383
|
+
}
|
|
30384
|
+
const top = Math.min(...moons.map((mn) => mn.cy - mn.r)) - MOON_ZONE_PAD;
|
|
30385
|
+
const bottom = Math.max(...moons.map((mn) => mn.cy + mn.r)) + MOON_ZONE_PAD;
|
|
30386
|
+
moons.forEach((mn, idx) => {
|
|
30387
|
+
const below = idx === 0 ? bottom : (moons[idx - 1].cy + mn.cy) / 2;
|
|
30388
|
+
const above = idx === moons.length - 1 ? top : (moons[idx + 1].cy + mn.cy) / 2;
|
|
30389
|
+
mn.hitY = above;
|
|
30390
|
+
mn.hitHeight = below - above;
|
|
30391
|
+
});
|
|
30392
|
+
moonZones.push({
|
|
30393
|
+
parent,
|
|
30394
|
+
moons,
|
|
30395
|
+
x: parent.cx - MOON_ZONE_W / 2,
|
|
30396
|
+
y: top,
|
|
30397
|
+
width: MOON_ZONE_W,
|
|
30398
|
+
height: bottom - top,
|
|
30399
|
+
perMoon: !parent.grouped && !parent.satellitesLink && moons.some((mn) => mn.link)
|
|
30400
|
+
});
|
|
30401
|
+
}
|
|
30402
|
+
const bands = model.bands.map((b) => {
|
|
30403
|
+
const x = xOf(b.innerKm);
|
|
30404
|
+
const width = xOf(b.outerKm) - x;
|
|
30405
|
+
const hitWidth = Math.max(width, BAND_MIN_HIT);
|
|
30406
|
+
return {
|
|
30407
|
+
...b,
|
|
30408
|
+
x,
|
|
30409
|
+
width,
|
|
30410
|
+
hitX: x + (width - hitWidth) / 2,
|
|
30411
|
+
hitWidth
|
|
30412
|
+
};
|
|
30413
|
+
}).sort((a, b) => b.width - a.width);
|
|
30414
|
+
let cloudPath = "";
|
|
30415
|
+
model.cloud?.points.forEach((p, idx) => {
|
|
30416
|
+
const aUnits = p.aKm / model.unitKm;
|
|
30417
|
+
if (aUnits < model.domain[0] || aUnits > model.domain[1]) return;
|
|
30418
|
+
const offset = Math.min(Math.max(foldTilt(p.tiltDeg), 0) * model.pxPerDeg, MAX_OFFSET);
|
|
30419
|
+
const cy = 120 + (idx % 2 === 0 ? -offset : offset);
|
|
30420
|
+
cloudPath += `M${xOf(p.aKm).toFixed(1)} ${cy.toFixed(1)}h0.01`;
|
|
30421
|
+
});
|
|
30422
|
+
const primaryR = model.primary.radiusKm * model.pxPerKm;
|
|
30423
|
+
return {
|
|
30424
|
+
bodies,
|
|
30425
|
+
moonZones,
|
|
30426
|
+
bands,
|
|
30427
|
+
cloudPath,
|
|
30428
|
+
primaryR,
|
|
30429
|
+
primaryCx: PRIMARY_LIMB_X - primaryR,
|
|
30430
|
+
xOf
|
|
30431
|
+
};
|
|
30432
|
+
}
|
|
30433
|
+
function inside(z, vx, vy) {
|
|
30434
|
+
return vx >= z.x && vx <= z.x + z.width && vy >= z.y && vy <= z.y + z.height;
|
|
30435
|
+
}
|
|
30436
|
+
function nearestBody(l, vx, vy, reach) {
|
|
30437
|
+
for (const pickable of [true, false]) {
|
|
30438
|
+
let nearest = null;
|
|
30439
|
+
let gap = reach;
|
|
30440
|
+
for (const b of l.bodies) {
|
|
30441
|
+
if (!!b.link !== pickable) continue;
|
|
30442
|
+
const centre = Math.hypot(vx - b.cx, vy - b.cy);
|
|
30443
|
+
const edge = centre <= b.r ? centre - INSIDE : centre - b.r;
|
|
30444
|
+
if (edge > gap) continue;
|
|
30445
|
+
nearest = b;
|
|
30446
|
+
gap = edge;
|
|
30447
|
+
}
|
|
30448
|
+
if (nearest) return nearest;
|
|
30449
|
+
}
|
|
30450
|
+
return null;
|
|
30451
|
+
}
|
|
30452
|
+
function hitTest(l, vx, vy) {
|
|
30453
|
+
const on = nearestBody(l, vx, vy, 0);
|
|
30454
|
+
if (on) return {
|
|
30455
|
+
kind: "body",
|
|
30456
|
+
id: on.id
|
|
30457
|
+
};
|
|
30458
|
+
for (const z of l.moonZones) {
|
|
30459
|
+
if (!inside(z, vx, vy)) continue;
|
|
30460
|
+
if (z.parent.grouped) return {
|
|
30461
|
+
kind: "body",
|
|
30462
|
+
id: z.parent.id
|
|
30463
|
+
};
|
|
30464
|
+
if (!z.perMoon) return {
|
|
30465
|
+
kind: "stack",
|
|
30466
|
+
id: z.parent.id
|
|
30467
|
+
};
|
|
30468
|
+
const moon = z.moons.find((mn) => vy >= mn.hitY && vy <= mn.hitY + mn.hitHeight);
|
|
30469
|
+
if (moon) return {
|
|
30470
|
+
kind: "moon",
|
|
30471
|
+
parent: z.parent.id,
|
|
30472
|
+
id: moon.id
|
|
30473
|
+
};
|
|
30474
|
+
}
|
|
30475
|
+
const near = nearestBody(l, vx, vy, 16);
|
|
30476
|
+
if (near) return {
|
|
30477
|
+
kind: "body",
|
|
30478
|
+
id: near.id
|
|
30479
|
+
};
|
|
30480
|
+
for (let i = l.bands.length - 1; i >= 0; i--) {
|
|
30481
|
+
const band = l.bands[i];
|
|
30482
|
+
if (vx >= band.hitX && vx <= band.hitX + band.hitWidth && vy >= 26 && vy <= 26 + 180) return {
|
|
30483
|
+
kind: "band",
|
|
30484
|
+
key: band.key
|
|
30485
|
+
};
|
|
30486
|
+
}
|
|
30487
|
+
if (Math.hypot(vx - l.primaryCx, vy - 120) <= l.primaryR) return { kind: "primary" };
|
|
30488
|
+
return null;
|
|
30489
|
+
}
|
|
30490
|
+
function tipFor(model, l, hover) {
|
|
30491
|
+
if (!hover) return null;
|
|
30492
|
+
const { text } = model;
|
|
30493
|
+
switch (hover.kind) {
|
|
30494
|
+
case "primary": return {
|
|
30495
|
+
cx: Math.max(44, l.primaryCx),
|
|
30496
|
+
cy: 120 - Math.min(l.primaryR, 120 - 40),
|
|
30497
|
+
title: model.primary.name,
|
|
30498
|
+
sub: text.primary
|
|
30499
|
+
};
|
|
30500
|
+
case "body": {
|
|
30501
|
+
const b = l.bodies.find((p) => p.id === hover.id);
|
|
30502
|
+
if (!b) return null;
|
|
30503
|
+
const parts = [text.distance(b.aKm)];
|
|
30504
|
+
if (b.tiltDeg > 90) parts.push(text.retrograde);
|
|
30505
|
+
if (b.grouped && b.satellites?.length) parts.push(text.moons(b.satelliteCount ?? b.satellites.length));
|
|
30506
|
+
return {
|
|
30507
|
+
cx: b.cx,
|
|
30508
|
+
cy: b.cy - b.r - 4,
|
|
30509
|
+
title: b.name,
|
|
30510
|
+
sub: parts.join(" · ")
|
|
30511
|
+
};
|
|
30512
|
+
}
|
|
30513
|
+
case "stack": {
|
|
30514
|
+
const zone = l.moonZones.find((z) => z.parent.id === hover.id);
|
|
30515
|
+
if (!zone) return null;
|
|
30516
|
+
const cx = zone.x + zone.width / 2;
|
|
30517
|
+
return zone.parent.satellitesTab ? {
|
|
30518
|
+
cx,
|
|
30519
|
+
cy: zone.y,
|
|
30520
|
+
title: zone.parent.name,
|
|
30521
|
+
sub: text.moons(zone.parent.satelliteCount ?? zone.moons.length)
|
|
30522
|
+
} : {
|
|
30523
|
+
cx,
|
|
30524
|
+
cy: zone.y,
|
|
30525
|
+
title: zone.moons[0].name,
|
|
30526
|
+
sub: zone.parent.name
|
|
30527
|
+
};
|
|
30528
|
+
}
|
|
30529
|
+
case "moon": {
|
|
30530
|
+
const zone = l.moonZones.find((z) => z.parent.id === hover.parent);
|
|
30531
|
+
const moon = zone?.moons.find((mn) => mn.id === hover.id);
|
|
30532
|
+
if (!zone || !moon) return null;
|
|
30533
|
+
return {
|
|
30534
|
+
cx: moon.cx,
|
|
30535
|
+
cy: moon.cy - moon.r - 4,
|
|
30536
|
+
title: moon.name,
|
|
30537
|
+
sub: zone.parent.name
|
|
30538
|
+
};
|
|
30539
|
+
}
|
|
30540
|
+
case "band": {
|
|
30541
|
+
const band = l.bands.find((bd) => bd.key === hover.key);
|
|
30542
|
+
if (!band) return null;
|
|
30543
|
+
return {
|
|
30544
|
+
cx: band.x + band.width / 2,
|
|
30545
|
+
cy: 44,
|
|
30546
|
+
title: band.name ?? band.label,
|
|
30547
|
+
sub: band.sub ?? `${text.distance(band.innerKm)}–${text.distance(band.outerKm)}`
|
|
30548
|
+
};
|
|
30549
|
+
}
|
|
30550
|
+
}
|
|
30551
|
+
}
|
|
30552
|
+
|
|
30553
|
+
//#endregion
|
|
30554
|
+
//#region src/lib/systemmap/chart.ts
|
|
30555
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
30556
|
+
var PRIMARY_RIM = 10;
|
|
30557
|
+
var RING_TILT = -18;
|
|
30558
|
+
var CLOUD_R = 1.2;
|
|
30559
|
+
var TIP_H = 28;
|
|
30560
|
+
var TIP_MARGIN = 6;
|
|
30561
|
+
var LABEL_MARGIN = 4;
|
|
30562
|
+
var LABEL_SIZE = 18;
|
|
30563
|
+
var LABEL_MIN_SIZE = 12;
|
|
30564
|
+
var LABEL_MAX_W = 190;
|
|
30565
|
+
function svg(tag, attrs = {}, ...children) {
|
|
30566
|
+
const el = document.createElementNS(SVG_NS, tag);
|
|
30567
|
+
for (const [name, value] of Object.entries(attrs)) if (value !== void 0) el.setAttribute(name, String(value));
|
|
30568
|
+
for (const child of children) if (child) el.append(child);
|
|
30569
|
+
return el;
|
|
30570
|
+
}
|
|
30571
|
+
function sameHover(a, b) {
|
|
30572
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
30573
|
+
}
|
|
30574
|
+
function linksOf(model, l) {
|
|
30575
|
+
const links = /* @__PURE__ */ new Map();
|
|
30576
|
+
const add = (hover, link) => {
|
|
30577
|
+
if (link?.href || link?.onclick) links.set(JSON.stringify(hover), link);
|
|
30578
|
+
};
|
|
30579
|
+
for (const band of l.bands) add({
|
|
30580
|
+
kind: "band",
|
|
30581
|
+
key: band.key
|
|
30582
|
+
}, band);
|
|
30583
|
+
add({ kind: "primary" }, model.primary.link);
|
|
30584
|
+
for (const b of l.bodies) add({
|
|
30585
|
+
kind: "body",
|
|
30586
|
+
id: b.id
|
|
30587
|
+
}, b.link);
|
|
30588
|
+
for (const z of l.moonZones) {
|
|
30589
|
+
if (z.parent.grouped) continue;
|
|
30590
|
+
if (!z.perMoon) add({
|
|
30591
|
+
kind: "stack",
|
|
30592
|
+
id: z.parent.id
|
|
30593
|
+
}, z.parent.satellitesLink);
|
|
30594
|
+
else for (const mn of z.moons) add({
|
|
30595
|
+
kind: "moon",
|
|
30596
|
+
parent: z.parent.id,
|
|
30597
|
+
id: mn.id
|
|
30598
|
+
}, mn.link);
|
|
30599
|
+
}
|
|
30600
|
+
return links;
|
|
30601
|
+
}
|
|
30602
|
+
var nextUid = 0;
|
|
30603
|
+
var SystemMapChart = class {
|
|
30604
|
+
root = document.createElement("div");
|
|
30605
|
+
svg = svg("svg");
|
|
30606
|
+
tip = document.createElement("div");
|
|
30607
|
+
tipTitle = document.createElement("span");
|
|
30608
|
+
tipSub = document.createElement("span");
|
|
30609
|
+
uid = `sm-system-map-${nextUid++}`;
|
|
30610
|
+
resize = new ResizeObserver(() => {
|
|
30611
|
+
this.fitLabels();
|
|
30612
|
+
this.placeTip();
|
|
30613
|
+
});
|
|
30614
|
+
model = null;
|
|
30615
|
+
placed = null;
|
|
30616
|
+
background = false;
|
|
30617
|
+
hover = null;
|
|
30618
|
+
highlights = /* @__PURE__ */ new Map();
|
|
30619
|
+
targets = /* @__PURE__ */ new Map();
|
|
30620
|
+
links = /* @__PURE__ */ new Map();
|
|
30621
|
+
shape = "";
|
|
30622
|
+
labels = [];
|
|
30623
|
+
constructor() {
|
|
30624
|
+
this.tip.className = "sm-system-map__tip";
|
|
30625
|
+
this.tipTitle.className = "sm-system-map__tip-title";
|
|
30626
|
+
this.tipSub.className = "sm-system-map__tip-sub";
|
|
30627
|
+
this.tip.append(this.tipTitle, this.tipSub);
|
|
30628
|
+
this.tip.hidden = true;
|
|
30629
|
+
this.root.append(this.svg, this.tip);
|
|
30630
|
+
this.resize.observe(this.root);
|
|
30631
|
+
const scrub = createScrub({
|
|
30632
|
+
onScrub: (x, y) => this.setHover(this.hitAt(x, y)),
|
|
30633
|
+
onEnd: () => this.setHover(null)
|
|
30634
|
+
});
|
|
30635
|
+
this.svg.addEventListener("pointerdown", scrub.onpointerdown);
|
|
30636
|
+
this.svg.addEventListener("pointermove", scrub.onpointermove);
|
|
30637
|
+
this.svg.addEventListener("pointerup", scrub.onpointerup);
|
|
30638
|
+
this.svg.addEventListener("pointercancel", scrub.onpointercancel);
|
|
30639
|
+
this.svg.addEventListener("pointerleave", scrub.onpointerleave);
|
|
30640
|
+
let at = [0, 0];
|
|
30641
|
+
const track = (e) => {
|
|
30642
|
+
at = [e.clientX, e.clientY];
|
|
30643
|
+
};
|
|
30644
|
+
this.svg.addEventListener("pointerdown", track);
|
|
30645
|
+
this.svg.addEventListener("pointerup", track);
|
|
30646
|
+
this.svg.addEventListener("pointermove", (e) => {
|
|
30647
|
+
track(e);
|
|
30648
|
+
if (e.pointerType === "mouse") this.setHover(this.hitAt(e.clientX, e.clientY));
|
|
30649
|
+
});
|
|
30650
|
+
this.svg.addEventListener("pointerleave", (e) => {
|
|
30651
|
+
if (e.pointerType === "mouse") this.setHover(null);
|
|
30652
|
+
});
|
|
30653
|
+
this.svg.addEventListener("click", (e) => {
|
|
30654
|
+
if (e.detail === 0) return;
|
|
30655
|
+
const hit = Math.abs(at[0] - e.clientX) < 1 && Math.abs(at[1] - e.clientY) < 1 ? this.hitAt(...at) : this.hitAt(e.clientX, e.clientY);
|
|
30656
|
+
if (!hit) return;
|
|
30657
|
+
const meant = this.targets.get(JSON.stringify(hit));
|
|
30658
|
+
if (meant?.contains(e.target)) return;
|
|
30659
|
+
e.stopPropagation();
|
|
30660
|
+
e.preventDefault();
|
|
30661
|
+
meant?.dispatchEvent(new MouseEvent("click", {
|
|
30662
|
+
bubbles: true,
|
|
30663
|
+
cancelable: true,
|
|
30664
|
+
ctrlKey: e.ctrlKey,
|
|
30665
|
+
metaKey: e.metaKey,
|
|
30666
|
+
shiftKey: e.shiftKey,
|
|
30667
|
+
altKey: e.altKey,
|
|
30668
|
+
button: e.button
|
|
30669
|
+
}));
|
|
30670
|
+
}, true);
|
|
30671
|
+
}
|
|
30672
|
+
destroy() {
|
|
30673
|
+
this.resize.disconnect();
|
|
30674
|
+
this.root.remove();
|
|
30675
|
+
}
|
|
30676
|
+
update(model, options) {
|
|
30677
|
+
this.model = model;
|
|
30678
|
+
this.placed = layout(model);
|
|
30679
|
+
const links = linksOf(model, this.placed);
|
|
30680
|
+
const shape = JSON.stringify([
|
|
30681
|
+
JSON.stringify(model, (key, value) => key === "href" ? void 0 : value),
|
|
30682
|
+
options,
|
|
30683
|
+
[...links].map(([key, link]) => [
|
|
30684
|
+
key,
|
|
30685
|
+
!!link.href,
|
|
30686
|
+
!!link.onclick
|
|
30687
|
+
])
|
|
30688
|
+
]);
|
|
30689
|
+
this.links = links;
|
|
30690
|
+
if (shape === this.shape) {
|
|
30691
|
+
for (const [key, el] of this.targets) {
|
|
30692
|
+
const href = links.get(key)?.href;
|
|
30693
|
+
if (href) el.setAttribute("href", href);
|
|
30694
|
+
}
|
|
30695
|
+
return;
|
|
30696
|
+
}
|
|
30697
|
+
this.shape = shape;
|
|
30698
|
+
this.background = options.variant === "background";
|
|
30699
|
+
this.hover = null;
|
|
30700
|
+
this.root.className = this.background ? "sm-system-map sm-system-map--background" : "sm-system-map";
|
|
30701
|
+
this.draw(model, this.placed, options.ariaLabel);
|
|
30702
|
+
this.fitLabels();
|
|
30703
|
+
this.placeTip();
|
|
30704
|
+
}
|
|
30705
|
+
draw(model, l, ariaLabel) {
|
|
30706
|
+
const bg = this.background;
|
|
30707
|
+
this.highlights = /* @__PURE__ */ new Map();
|
|
30708
|
+
this.targets = /* @__PURE__ */ new Map();
|
|
30709
|
+
this.labels = [];
|
|
30710
|
+
const root = this.svg;
|
|
30711
|
+
root.replaceChildren();
|
|
30712
|
+
root.setAttribute("viewBox", bg && model.backgroundView || `0 0 ${720} ${240}`);
|
|
30713
|
+
root.setAttribute("preserveAspectRatio", bg ? model.backgroundFit === "fit" ? "xMidYMid meet" : model.backgroundView ? "xMinYMid slice" : "xMidYMid slice" : "xMidYMid meet");
|
|
30714
|
+
root.setAttribute("class", "sm-system-map__svg");
|
|
30715
|
+
root.setAttribute("role", "group");
|
|
30716
|
+
root.setAttribute("aria-label", ariaLabel);
|
|
30717
|
+
root.setAttribute("data-vaul-no-drag", "");
|
|
30718
|
+
const stop = (offset, opacity) => svg("stop", {
|
|
30719
|
+
offset,
|
|
30720
|
+
"stop-color": model.primary.color,
|
|
30721
|
+
"stop-opacity": opacity
|
|
30722
|
+
});
|
|
30723
|
+
root.append(svg("defs", {}, svg("radialGradient", {
|
|
30724
|
+
id: `${this.uid}-primary`,
|
|
30725
|
+
gradientUnits: "userSpaceOnUse",
|
|
30726
|
+
cx: l.primaryCx,
|
|
30727
|
+
cy: 120,
|
|
30728
|
+
r: l.primaryR
|
|
30729
|
+
}, stop("0%", 1), stop(Math.max(0, 1 - PRIMARY_RIM / l.primaryR), .85), stop("100%", .35)), svg("pattern", {
|
|
30730
|
+
id: `${this.uid}-band`,
|
|
30731
|
+
width: 9,
|
|
30732
|
+
height: 9,
|
|
30733
|
+
patternUnits: "userSpaceOnUse"
|
|
30734
|
+
}, svg("circle", {
|
|
30735
|
+
cx: 2,
|
|
30736
|
+
cy: 2,
|
|
30737
|
+
r: .9,
|
|
30738
|
+
fill: "currentColor",
|
|
30739
|
+
opacity: .55
|
|
30740
|
+
}), svg("circle", {
|
|
30741
|
+
cx: 6.5,
|
|
30742
|
+
cy: 6,
|
|
30743
|
+
r: .9,
|
|
30744
|
+
fill: "currentColor",
|
|
30745
|
+
opacity: .55
|
|
30746
|
+
}))));
|
|
30747
|
+
if (!bg) this.drawAxis(model, l);
|
|
30748
|
+
if (l.cloudPath) root.append(svg("path", {
|
|
30749
|
+
d: l.cloudPath,
|
|
30750
|
+
stroke: model.cloud?.color,
|
|
30751
|
+
"stroke-width": CLOUD_R * 2,
|
|
30752
|
+
"stroke-linecap": "round",
|
|
30753
|
+
fill: "none",
|
|
30754
|
+
opacity: .45,
|
|
30755
|
+
"pointer-events": "none"
|
|
30756
|
+
}));
|
|
30757
|
+
for (const band of l.bands) {
|
|
30758
|
+
const fill = svg("rect", {
|
|
30759
|
+
x: band.x,
|
|
30760
|
+
y: 26,
|
|
30761
|
+
width: band.width,
|
|
30762
|
+
height: 180,
|
|
30763
|
+
fill: `url(#${this.uid}-band)`,
|
|
30764
|
+
class: `sm-system-map__band sm-system-map__band--${band.tone}`
|
|
30765
|
+
});
|
|
30766
|
+
if (bg) {
|
|
30767
|
+
root.append(fill);
|
|
30768
|
+
continue;
|
|
30769
|
+
}
|
|
30770
|
+
const label = svg("text", {
|
|
30771
|
+
x: band.x + band.width / 2,
|
|
30772
|
+
y: 240 - 8,
|
|
30773
|
+
"text-anchor": "middle",
|
|
30774
|
+
"font-size": LABEL_SIZE,
|
|
30775
|
+
class: `sm-system-map__band-label sm-system-map__band-label--${band.tone}`
|
|
30776
|
+
});
|
|
30777
|
+
label.textContent = band.label;
|
|
30778
|
+
this.labels.push(label);
|
|
30779
|
+
root.append(this.target(band, band.name ?? band.label, {
|
|
30780
|
+
kind: "band",
|
|
30781
|
+
key: band.key
|
|
30782
|
+
}, fill, svg("rect", {
|
|
30783
|
+
x: band.hitX,
|
|
30784
|
+
y: 26,
|
|
30785
|
+
width: band.hitWidth,
|
|
30786
|
+
height: 180,
|
|
30787
|
+
fill: "transparent"
|
|
30788
|
+
}), label));
|
|
30789
|
+
}
|
|
30790
|
+
const disc = svg("circle", {
|
|
30791
|
+
cx: l.primaryCx,
|
|
30792
|
+
cy: 120,
|
|
30793
|
+
r: l.primaryR,
|
|
30794
|
+
fill: `url(#${this.uid}-primary)`
|
|
30795
|
+
});
|
|
30796
|
+
root.append(bg ? disc : this.target(model.primary.link, model.primary.name, { kind: "primary" }, disc));
|
|
30797
|
+
for (const b of l.bodies) {
|
|
30798
|
+
if (!b.rings) continue;
|
|
30799
|
+
const mid = b.r * (b.rings.inner + b.rings.outer) / 2;
|
|
30800
|
+
root.append(svg("ellipse", {
|
|
30801
|
+
cx: b.cx,
|
|
30802
|
+
cy: b.cy,
|
|
30803
|
+
rx: mid,
|
|
30804
|
+
ry: mid * RING_FORESHORTEN,
|
|
30805
|
+
transform: `rotate(${RING_TILT} ${b.cx} ${b.cy})`,
|
|
30806
|
+
fill: "none",
|
|
30807
|
+
stroke: "#d9c89f",
|
|
30808
|
+
"stroke-width": b.r * (b.rings.outer - b.rings.inner),
|
|
30809
|
+
opacity: .45
|
|
30810
|
+
}));
|
|
30811
|
+
}
|
|
30812
|
+
const zoneOf = new Map(l.moonZones.map((z) => [z.parent.id, z]));
|
|
30813
|
+
for (const b of l.bodies) root.append(this.body(b, zoneOf.get(b.id)));
|
|
30814
|
+
for (const z of l.moonZones) if (!z.parent.grouped) this.stack(z);
|
|
30815
|
+
}
|
|
30816
|
+
drawAxis(model, l) {
|
|
30817
|
+
for (const t of model.ticks) {
|
|
30818
|
+
const x = l.xOf(t * model.unitKm);
|
|
30819
|
+
const tick = svg("text", {
|
|
30820
|
+
x,
|
|
30821
|
+
y: 16,
|
|
30822
|
+
"text-anchor": "middle",
|
|
30823
|
+
"font-size": 16,
|
|
30824
|
+
fill: "currentColor",
|
|
30825
|
+
opacity: .85
|
|
30826
|
+
});
|
|
30827
|
+
tick.textContent = String(t);
|
|
30828
|
+
this.svg.append(svg("line", {
|
|
30829
|
+
x1: x,
|
|
30830
|
+
x2: x,
|
|
30831
|
+
y1: 20,
|
|
30832
|
+
y2: 240 - 28,
|
|
30833
|
+
stroke: "currentColor",
|
|
30834
|
+
"stroke-width": .5,
|
|
30835
|
+
opacity: .12
|
|
30836
|
+
}), tick);
|
|
30837
|
+
}
|
|
30838
|
+
const unit = svg("text", {
|
|
30839
|
+
x: 700,
|
|
30840
|
+
y: 16,
|
|
30841
|
+
"text-anchor": "end",
|
|
30842
|
+
"font-size": 14,
|
|
30843
|
+
fill: "currentColor",
|
|
30844
|
+
opacity: .65
|
|
30845
|
+
});
|
|
30846
|
+
unit.textContent = model.axisLabel;
|
|
30847
|
+
this.svg.append(unit);
|
|
30848
|
+
}
|
|
30849
|
+
body(b, zone) {
|
|
30850
|
+
const dot = b.reference ? svg("circle", {
|
|
30851
|
+
cx: b.cx,
|
|
30852
|
+
cy: b.cy,
|
|
30853
|
+
r: b.r + 1,
|
|
30854
|
+
fill: "none",
|
|
30855
|
+
stroke: b.color,
|
|
30856
|
+
"stroke-width": 1.2,
|
|
30857
|
+
class: "sm-system-map__reference"
|
|
30858
|
+
}) : svg("circle", {
|
|
30859
|
+
cx: b.cx,
|
|
30860
|
+
cy: b.cy,
|
|
30861
|
+
r: b.r,
|
|
30862
|
+
fill: b.color
|
|
30863
|
+
});
|
|
30864
|
+
if (this.background) {
|
|
30865
|
+
if (!zone || !b.grouped) return dot;
|
|
30866
|
+
return svg("g", {}, dot, ...this.moonDots(zone));
|
|
30867
|
+
}
|
|
30868
|
+
const hover = {
|
|
30869
|
+
kind: "body",
|
|
30870
|
+
id: b.id
|
|
30871
|
+
};
|
|
30872
|
+
const ring = this.highlight(hover, svg("circle", {
|
|
30873
|
+
cx: b.cx,
|
|
30874
|
+
cy: b.cy,
|
|
30875
|
+
r: b.r + 3.5,
|
|
30876
|
+
fill: "none",
|
|
30877
|
+
stroke: "currentColor",
|
|
30878
|
+
"stroke-width": 1,
|
|
30879
|
+
opacity: .8
|
|
30880
|
+
}));
|
|
30881
|
+
const hit = svg("circle", {
|
|
30882
|
+
cx: b.cx,
|
|
30883
|
+
cy: b.cy,
|
|
30884
|
+
r: b.r + 16,
|
|
30885
|
+
fill: "transparent"
|
|
30886
|
+
});
|
|
30887
|
+
if (!zone || !b.grouped) return this.target(b.link, b.name, hover, ring, dot, hit);
|
|
30888
|
+
return this.target(b.link, b.name, hover, this.highlight(hover, this.zoneWash(zone)), ring, dot, hit, ...this.moonDots(zone), this.zoneHit(zone));
|
|
30889
|
+
}
|
|
30890
|
+
stack(z) {
|
|
30891
|
+
if (this.background) {
|
|
30892
|
+
this.svg.append(...this.moonDots(z));
|
|
30893
|
+
return;
|
|
30894
|
+
}
|
|
30895
|
+
const p = z.parent;
|
|
30896
|
+
if (z.perMoon) {
|
|
30897
|
+
this.svg.append(...this.moonDots(z));
|
|
30898
|
+
for (const mn of z.moons) {
|
|
30899
|
+
const hover = {
|
|
30900
|
+
kind: "moon",
|
|
30901
|
+
parent: p.id,
|
|
30902
|
+
id: mn.id
|
|
30903
|
+
};
|
|
30904
|
+
this.svg.append(this.target(mn.link, mn.name, hover, this.highlight(hover, svg("circle", {
|
|
30905
|
+
cx: mn.cx,
|
|
30906
|
+
cy: mn.cy,
|
|
30907
|
+
r: mn.r + 2.5,
|
|
30908
|
+
fill: "none",
|
|
30909
|
+
stroke: "currentColor",
|
|
30910
|
+
"stroke-width": 1,
|
|
30911
|
+
opacity: .8
|
|
30912
|
+
})), svg("rect", {
|
|
30913
|
+
x: z.x,
|
|
30914
|
+
y: mn.hitY,
|
|
30915
|
+
width: z.width,
|
|
30916
|
+
height: mn.hitHeight,
|
|
30917
|
+
fill: "transparent"
|
|
30918
|
+
})));
|
|
30919
|
+
}
|
|
30920
|
+
return;
|
|
30921
|
+
}
|
|
30922
|
+
const hover = {
|
|
30923
|
+
kind: "stack",
|
|
30924
|
+
id: p.id
|
|
30925
|
+
};
|
|
30926
|
+
const first = z.moons[0];
|
|
30927
|
+
this.svg.append(this.highlight(hover, this.zoneWash(z)), ...this.moonDots(z), this.target(p.satellitesLink, p.satellitesTab ? `${p.name} · ${this.model.text.moons(p.satelliteCount ?? z.moons.length)}` : first.name, hover, this.zoneHit(z)));
|
|
30928
|
+
}
|
|
30929
|
+
moonDots(z) {
|
|
30930
|
+
return z.moons.map((mn) => svg("circle", {
|
|
30931
|
+
cx: mn.cx,
|
|
30932
|
+
cy: mn.cy,
|
|
30933
|
+
r: mn.r,
|
|
30934
|
+
fill: mn.color
|
|
30935
|
+
}));
|
|
30936
|
+
}
|
|
30937
|
+
zoneWash(z) {
|
|
30938
|
+
return svg("rect", {
|
|
30939
|
+
x: z.x,
|
|
30940
|
+
y: z.y,
|
|
30941
|
+
width: z.width,
|
|
30942
|
+
height: z.height,
|
|
30943
|
+
rx: 4,
|
|
30944
|
+
fill: "currentColor",
|
|
30945
|
+
opacity: .1,
|
|
30946
|
+
"pointer-events": "none"
|
|
30947
|
+
});
|
|
30948
|
+
}
|
|
30949
|
+
zoneHit(z) {
|
|
30950
|
+
return svg("rect", {
|
|
30951
|
+
x: z.x,
|
|
30952
|
+
y: z.y,
|
|
30953
|
+
width: z.width,
|
|
30954
|
+
height: z.height,
|
|
30955
|
+
fill: "transparent"
|
|
30956
|
+
});
|
|
30957
|
+
}
|
|
30958
|
+
highlight(hover, el) {
|
|
30959
|
+
const key = JSON.stringify(hover);
|
|
30960
|
+
const list = this.highlights.get(key);
|
|
30961
|
+
if (list) list.push(el);
|
|
30962
|
+
else this.highlights.set(key, [el]);
|
|
30963
|
+
el.style.display = "none";
|
|
30964
|
+
return el;
|
|
30965
|
+
}
|
|
30966
|
+
target(link, label, hover, ...children) {
|
|
30967
|
+
const linked = !!(link?.href || link?.onclick);
|
|
30968
|
+
const el = link?.href ? svg("a", {
|
|
30969
|
+
href: link.href,
|
|
30970
|
+
"aria-label": label
|
|
30971
|
+
}) : linked ? svg("g", {
|
|
30972
|
+
role: "button",
|
|
30973
|
+
tabindex: 0,
|
|
30974
|
+
"aria-label": label,
|
|
30975
|
+
class: "sm-system-map__button"
|
|
30976
|
+
}) : svg("g");
|
|
30977
|
+
el.append(...children);
|
|
30978
|
+
if (!linked) return el;
|
|
30979
|
+
const key = JSON.stringify(hover);
|
|
30980
|
+
this.targets.set(key, el);
|
|
30981
|
+
el.addEventListener("focus", () => this.setHover(hover));
|
|
30982
|
+
el.addEventListener("blur", () => {
|
|
30983
|
+
if (sameHover(this.hover, hover)) this.setHover(null);
|
|
30984
|
+
});
|
|
30985
|
+
el.addEventListener("click", (e) => this.links.get(key)?.onclick?.(e));
|
|
30986
|
+
if (!link?.href) el.addEventListener("keydown", (e) => {
|
|
30987
|
+
const key = e.key;
|
|
30988
|
+
if (key !== "Enter" && key !== " ") return;
|
|
30989
|
+
e.preventDefault();
|
|
30990
|
+
el.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
30991
|
+
});
|
|
30992
|
+
return el;
|
|
30993
|
+
}
|
|
30994
|
+
setHover(hover) {
|
|
30995
|
+
if (sameHover(this.hover, hover)) return;
|
|
30996
|
+
for (const el of this.highlights.get(JSON.stringify(this.hover)) ?? []) el.style.display = "none";
|
|
30997
|
+
this.targets.get(JSON.stringify(this.hover))?.removeAttribute("data-near");
|
|
30998
|
+
this.hover = hover;
|
|
30999
|
+
for (const el of this.highlights.get(JSON.stringify(hover)) ?? []) el.style.display = "";
|
|
31000
|
+
const near = this.targets.get(JSON.stringify(hover));
|
|
31001
|
+
near?.setAttribute("data-near", "");
|
|
31002
|
+
this.svg.classList.toggle("sm-system-map__svg--near", !!near);
|
|
31003
|
+
this.placeTip();
|
|
31004
|
+
}
|
|
31005
|
+
hitAt(clientX, clientY) {
|
|
31006
|
+
if (!this.placed || this.background) return null;
|
|
31007
|
+
const rect = this.svg.getBoundingClientRect();
|
|
31008
|
+
if (!rect.width || !rect.height) return null;
|
|
31009
|
+
const vx = (clientX - rect.left) / rect.width * 720;
|
|
31010
|
+
const vy = (clientY - rect.top) / rect.height * 240;
|
|
31011
|
+
return hitTest(this.placed, vx, vy);
|
|
31012
|
+
}
|
|
31013
|
+
fitLabels() {
|
|
31014
|
+
for (const label of this.labels) {
|
|
31015
|
+
let width = 0;
|
|
31016
|
+
label.setAttribute("font-size", String(LABEL_SIZE));
|
|
31017
|
+
try {
|
|
31018
|
+
width = label.getComputedTextLength();
|
|
31019
|
+
} catch {}
|
|
31020
|
+
if (!width) continue;
|
|
31021
|
+
if (width > LABEL_MAX_W) {
|
|
31022
|
+
const size = Math.max(LABEL_MIN_SIZE, LABEL_SIZE * LABEL_MAX_W / width);
|
|
31023
|
+
label.setAttribute("font-size", String(size));
|
|
31024
|
+
width *= size / LABEL_SIZE;
|
|
31025
|
+
}
|
|
31026
|
+
const centre = Number(label.dataset.centre ?? label.getAttribute("x"));
|
|
31027
|
+
label.dataset.centre = String(centre);
|
|
31028
|
+
const half = width / 2;
|
|
31029
|
+
const x = Math.min(Math.max(centre, half + LABEL_MARGIN), Math.max(half, 720 - half - LABEL_MARGIN));
|
|
31030
|
+
label.setAttribute("x", String(x));
|
|
31031
|
+
}
|
|
31032
|
+
}
|
|
31033
|
+
placeTip() {
|
|
31034
|
+
const tip = this.model && this.placed && !this.background ? tipFor(this.model, this.placed, this.hover) : null;
|
|
31035
|
+
this.tip.hidden = !tip;
|
|
31036
|
+
if (!tip) return;
|
|
31037
|
+
this.tipTitle.textContent = tip.title;
|
|
31038
|
+
this.tipSub.textContent = tip.sub ? ` · ${tip.sub}` : "";
|
|
31039
|
+
const containerW = this.root.clientWidth;
|
|
31040
|
+
const scale = containerW / 720;
|
|
31041
|
+
this.tip.style.maxWidth = `${Math.max(0, containerW - 2 * TIP_MARGIN)}px`;
|
|
31042
|
+
const half = this.tip.offsetWidth / 2;
|
|
31043
|
+
const left = Math.min(Math.max(tip.cx * scale, half + TIP_MARGIN), Math.max(half, containerW - half - TIP_MARGIN));
|
|
31044
|
+
this.tip.style.left = `${left}px`;
|
|
31045
|
+
this.tip.style.top = `${Math.max(4, tip.cy * scale - TIP_H)}px`;
|
|
31046
|
+
}
|
|
31047
|
+
};
|
|
31048
|
+
|
|
31049
|
+
//#endregion
|
|
31050
|
+
//#region src/lib/systemmap/planetary.ts
|
|
31051
|
+
function systemFromMapEntry(entry, name) {
|
|
31052
|
+
return {
|
|
31053
|
+
planetId: entry.primary.id,
|
|
31054
|
+
planetName: name,
|
|
31055
|
+
planetRadiusKm: entry.primary.radius_km,
|
|
31056
|
+
planetColor: BODY_COLORS[entry.primary.id] ?? "#cccccc",
|
|
31057
|
+
moons: entry.moons.map((mn) => ({
|
|
31058
|
+
id: mn.id,
|
|
31059
|
+
name: mn.id,
|
|
31060
|
+
aRp: mn.a_rp,
|
|
31061
|
+
tiltDeg: mn.tilt_deg,
|
|
31062
|
+
radiusKm: mn.radius_km,
|
|
31063
|
+
color: BODY_COLORS[mn.id] ?? mn.color ?? "#cccccc"
|
|
31064
|
+
})),
|
|
31065
|
+
rings: entry.rings ? {
|
|
31066
|
+
innerRp: entry.rings.inner_rp,
|
|
31067
|
+
outerRp: entry.rings.outer_rp
|
|
31068
|
+
} : null,
|
|
31069
|
+
moonCount: entry.moon_count
|
|
31070
|
+
};
|
|
31071
|
+
}
|
|
31072
|
+
var TOP_MOON_R = 14;
|
|
31073
|
+
var AT_SURFACE = 1.001;
|
|
31074
|
+
var PX_PER_DEG = .62;
|
|
31075
|
+
var TICK_LADDER = [
|
|
31076
|
+
1,
|
|
31077
|
+
2,
|
|
31078
|
+
3,
|
|
31079
|
+
5,
|
|
31080
|
+
10,
|
|
31081
|
+
20,
|
|
31082
|
+
30,
|
|
31083
|
+
50,
|
|
31084
|
+
100,
|
|
31085
|
+
200,
|
|
31086
|
+
300,
|
|
31087
|
+
500,
|
|
31088
|
+
1e3,
|
|
31089
|
+
2e3,
|
|
31090
|
+
5e3
|
|
31091
|
+
];
|
|
31092
|
+
var BG_VIEW = "0 96 320 144";
|
|
31093
|
+
var BG_VIEW_WIDE = "0 96 720 144";
|
|
31094
|
+
var EMPTY_DOMAIN = [1, 100];
|
|
31095
|
+
function domainOf(system, bands) {
|
|
31096
|
+
let lo = Math.min(...system.moons.map((mn) => mn.aRp));
|
|
31097
|
+
let hi = Math.max(...system.moons.map((mn) => mn.aRp));
|
|
31098
|
+
for (const b of bands) {
|
|
31099
|
+
lo = Math.min(lo, b.innerKm / system.planetRadiusKm);
|
|
31100
|
+
hi = Math.max(hi, b.outerKm / system.planetRadiusKm);
|
|
31101
|
+
}
|
|
31102
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi)) return EMPTY_DOMAIN;
|
|
31103
|
+
if (hi <= lo) {
|
|
31104
|
+
lo /= 2;
|
|
31105
|
+
hi *= 2;
|
|
31106
|
+
}
|
|
31107
|
+
lo = lo <= AT_SURFACE ? 1 : lo;
|
|
31108
|
+
return [lo > 1 ? 10 ** (Math.log10(lo) - .12) : 1, 10 ** (Math.log10(hi) + .12)];
|
|
31109
|
+
}
|
|
31110
|
+
function planetarySystemModel(system, parts) {
|
|
31111
|
+
const rKm = system.planetRadiusKm;
|
|
31112
|
+
const domain = domainOf(system, parts.bands);
|
|
31113
|
+
return {
|
|
31114
|
+
primary: {
|
|
31115
|
+
id: system.planetId,
|
|
31116
|
+
name: system.planetName,
|
|
31117
|
+
radiusKm: rKm,
|
|
31118
|
+
color: system.planetColor,
|
|
31119
|
+
link: parts.primaryLink
|
|
31120
|
+
},
|
|
31121
|
+
bodies: system.moons.map((mn) => ({
|
|
31122
|
+
id: mn.id,
|
|
31123
|
+
name: mn.name,
|
|
31124
|
+
aKm: mn.aRp * rKm,
|
|
31125
|
+
tiltDeg: mn.tiltDeg,
|
|
31126
|
+
radiusKm: mn.radiusKm,
|
|
31127
|
+
color: mn.color,
|
|
31128
|
+
link: parts.moonLink?.(mn)
|
|
31129
|
+
})),
|
|
31130
|
+
bands: parts.bands,
|
|
31131
|
+
cloud: parts.cloud,
|
|
31132
|
+
unitKm: rKm,
|
|
31133
|
+
domain,
|
|
31134
|
+
ticks: TICK_LADDER.filter((t) => t >= domain[0] && t <= domain[1]),
|
|
31135
|
+
axisLabel: parts.axisLabel,
|
|
31136
|
+
pxPerKm: TOP_MOON_R / Math.max(...system.moons.map((mn) => mn.radiusKm).filter((r) => r > 0), 1),
|
|
31137
|
+
pxPerDeg: PX_PER_DEG,
|
|
31138
|
+
text: parts.text,
|
|
31139
|
+
backgroundView: parts.wide ? BG_VIEW_WIDE : BG_VIEW,
|
|
31140
|
+
backgroundFit: parts.wide ? "fit" : "slice"
|
|
31141
|
+
};
|
|
31142
|
+
}
|
|
31143
|
+
|
|
31144
|
+
//#endregion
|
|
31145
|
+
//#region src/lib/systemmap/solar.ts
|
|
31146
|
+
var SOLAR_PX_PER_KM = 224e-6;
|
|
31147
|
+
var SOLAR_PX_PER_DEG = 1.8;
|
|
31148
|
+
var DOMAIN = [.3, 50];
|
|
31149
|
+
var TICKS = [
|
|
31150
|
+
.3,
|
|
31151
|
+
1,
|
|
31152
|
+
3,
|
|
31153
|
+
10,
|
|
31154
|
+
30
|
|
31155
|
+
];
|
|
31156
|
+
var SUN_COLOR = "#ffdd44";
|
|
31157
|
+
function displayName(name) {
|
|
31158
|
+
const stripped = name.replace(/^\d+\s+/, "");
|
|
31159
|
+
if (stripped === stripped.toUpperCase() && /[A-Z]/.test(stripped)) return stripped.charAt(0) + stripped.slice(1).toLowerCase();
|
|
31160
|
+
return stripped;
|
|
31161
|
+
}
|
|
31162
|
+
function solarColor(o) {
|
|
31163
|
+
return o.color || BODY_COLORS[o.id] || "#cccccc";
|
|
31164
|
+
}
|
|
31165
|
+
function fileStacks(file, name) {
|
|
31166
|
+
const stacks = /* @__PURE__ */ new Map();
|
|
31167
|
+
const planets = new Map(file.objects.map((o) => [o.id, o]));
|
|
31168
|
+
for (const o of file.objects) {
|
|
31169
|
+
if (o.kind !== "moon" || !o.parent) continue;
|
|
31170
|
+
const stack = stacks.get(o.parent) ?? {
|
|
31171
|
+
satellites: [],
|
|
31172
|
+
count: planets.get(o.parent)?.moon_count,
|
|
31173
|
+
tab: false
|
|
31174
|
+
};
|
|
31175
|
+
stack.satellites.push({
|
|
31176
|
+
id: o.id,
|
|
31177
|
+
name: name(o),
|
|
31178
|
+
radiusKm: o.diameter_km / 2,
|
|
31179
|
+
color: solarColor(o)
|
|
31180
|
+
});
|
|
31181
|
+
stack.tab ||= !!o.link_parent;
|
|
31182
|
+
stacks.set(o.parent, stack);
|
|
31183
|
+
}
|
|
31184
|
+
return stacks;
|
|
31185
|
+
}
|
|
31186
|
+
function solarSystemModel(file, parts) {
|
|
31187
|
+
const sun = file.objects.find((o) => o.kind === "star");
|
|
31188
|
+
if (!sun) return null;
|
|
31189
|
+
const listed = fileStacks(file, parts.name);
|
|
31190
|
+
const stackOf = parts.stack ?? ((o) => listed.get(o.id));
|
|
31191
|
+
const bodies = file.objects.filter((o) => o.kind !== "moon" && o.kind !== "star" && (parts.keep?.(o) ?? true)).map((o) => {
|
|
31192
|
+
const stack = stackOf(o);
|
|
31193
|
+
return {
|
|
31194
|
+
id: o.id,
|
|
31195
|
+
name: parts.name(o),
|
|
31196
|
+
aKm: o.a * AU_KM,
|
|
31197
|
+
tiltDeg: o.i,
|
|
31198
|
+
radiusKm: o.diameter_km / 2,
|
|
31199
|
+
color: solarColor(o),
|
|
31200
|
+
rings: o.rings,
|
|
31201
|
+
satellites: stack?.satellites,
|
|
31202
|
+
satelliteCount: stack?.count,
|
|
31203
|
+
satellitesTab: stack?.tab ?? false,
|
|
31204
|
+
...parts.links(o, stack)
|
|
31205
|
+
};
|
|
31206
|
+
});
|
|
31207
|
+
return {
|
|
31208
|
+
primary: {
|
|
31209
|
+
id: sun.id,
|
|
31210
|
+
name: parts.name(sun),
|
|
31211
|
+
radiusKm: sun.diameter_km / 2,
|
|
31212
|
+
color: SUN_COLOR,
|
|
31213
|
+
link: parts.primaryLink
|
|
31214
|
+
},
|
|
31215
|
+
bodies,
|
|
31216
|
+
bands: file.belts.map((b) => ({
|
|
31217
|
+
key: b.slug,
|
|
31218
|
+
innerKm: b.inner_au * AU_KM,
|
|
31219
|
+
outerKm: b.outer_au * AU_KM,
|
|
31220
|
+
tone: b.kind === "kuiper_belt" ? "sky" : "muted",
|
|
31221
|
+
...parts.band(b)
|
|
31222
|
+
})),
|
|
31223
|
+
unitKm: AU_KM,
|
|
31224
|
+
domain: DOMAIN,
|
|
31225
|
+
ticks: TICKS,
|
|
31226
|
+
axisLabel: parts.text.axisAu,
|
|
31227
|
+
pxPerKm: SOLAR_PX_PER_KM,
|
|
31228
|
+
pxPerDeg: SOLAR_PX_PER_DEG,
|
|
31229
|
+
text: parts.text
|
|
31230
|
+
};
|
|
31231
|
+
}
|
|
31232
|
+
|
|
31233
|
+
//#endregion
|
|
31234
|
+
//#region src/lib/systemmap/views.ts
|
|
31235
|
+
function extraFromPlace(place) {
|
|
31236
|
+
return {
|
|
31237
|
+
id: place.id,
|
|
31238
|
+
name: place.name ?? place.id,
|
|
31239
|
+
moon: place.moon ?? false,
|
|
31240
|
+
aAu: place.aAu ?? 0,
|
|
31241
|
+
tiltDeg: place.tiltDeg ?? 0,
|
|
31242
|
+
radiusKm: place.radiusKm ?? 0,
|
|
31243
|
+
color: place.color ?? BODY_COLORS[place.id] ?? "#cccccc",
|
|
31244
|
+
parent: place.parent
|
|
31245
|
+
};
|
|
31246
|
+
}
|
|
31247
|
+
var MAX_STACK = 4;
|
|
31248
|
+
var ZONE_TOP_R = 10;
|
|
31249
|
+
var ZONE_DOMAIN = {
|
|
31250
|
+
inner: [.6, 5.5],
|
|
31251
|
+
outer: [4.5, 55]
|
|
31252
|
+
};
|
|
31253
|
+
var ZONE_TICKS = [
|
|
31254
|
+
.1,
|
|
31255
|
+
.2,
|
|
31256
|
+
.3,
|
|
31257
|
+
.5,
|
|
31258
|
+
1,
|
|
31259
|
+
2,
|
|
31260
|
+
3,
|
|
31261
|
+
5,
|
|
31262
|
+
10,
|
|
31263
|
+
20,
|
|
31264
|
+
30,
|
|
31265
|
+
50,
|
|
31266
|
+
100,
|
|
31267
|
+
200,
|
|
31268
|
+
500,
|
|
31269
|
+
1e3
|
|
31270
|
+
];
|
|
31271
|
+
var LAST_AU_TICK_AT = .9;
|
|
31272
|
+
var LAST_RADII_TICK_AT = .76;
|
|
31273
|
+
function tickAt(tick, domain) {
|
|
31274
|
+
return Math.log(tick / domain[0]) / Math.log(domain[1] / domain[0]);
|
|
31275
|
+
}
|
|
31276
|
+
var JUPITER_ID = "naif-599";
|
|
31277
|
+
function unplacedIds(solar, systems, bodies, placed = []) {
|
|
31278
|
+
if (!bodies) return [];
|
|
31279
|
+
const known = new Set([...solar.objects.map((o) => o.id), ...placed]);
|
|
31280
|
+
for (const entry of Object.values(systems)) {
|
|
31281
|
+
known.add(entry.primary.id);
|
|
31282
|
+
for (const moon of entry.moons) known.add(moon.id);
|
|
31283
|
+
}
|
|
31284
|
+
return [...bodies].filter((id) => !known.has(id));
|
|
31285
|
+
}
|
|
31286
|
+
function extraFromBundle(global) {
|
|
31287
|
+
const moon = global.type === "moon";
|
|
31288
|
+
const parent = global.orbit?.parent_id;
|
|
31289
|
+
const a = parent === void 0 || parent === "naif-10" || parent === "naif-0" ? global.orbit?.a ?? global.orbit?.q : void 0;
|
|
31290
|
+
if (a === void 0 && !moon) return null;
|
|
31291
|
+
const radii = global.radii;
|
|
31292
|
+
return {
|
|
31293
|
+
id: global.id,
|
|
31294
|
+
name: displayName(global.name ?? global.id),
|
|
31295
|
+
moon,
|
|
31296
|
+
aAu: a ?? 0,
|
|
31297
|
+
tiltDeg: global.orbit?.i ?? 0,
|
|
31298
|
+
radiusKm: radii ? (radii.a + radii.b + radii.c) / 3 : (global.sbdb?.diameter ?? 0) / 2,
|
|
31299
|
+
color: BODY_COLORS[global.id] ?? global.sbdb?.color ?? global.color ?? "#cccccc",
|
|
31300
|
+
parent: global.orbit?.parent_id
|
|
31301
|
+
};
|
|
31302
|
+
}
|
|
31303
|
+
var ZONE_CUT = .92;
|
|
31304
|
+
function zoneOf(aAu, jupiterAu) {
|
|
31305
|
+
return aAu < jupiterAu * ZONE_CUT ? "inner" : "outer";
|
|
31306
|
+
}
|
|
31307
|
+
function systemEntry(systems, primaryId) {
|
|
31308
|
+
return Object.values(systems).find((entry) => entry.primary.id === primaryId);
|
|
31309
|
+
}
|
|
31310
|
+
function targetKey(target) {
|
|
31311
|
+
return target.kind === "zone" ? `zone:${target.zone}` : `${target.kind}:${target.id}`;
|
|
31312
|
+
}
|
|
31313
|
+
var Scope = class {
|
|
31314
|
+
targets = /* @__PURE__ */ new Map();
|
|
31315
|
+
constructor(data, options, pick) {
|
|
31316
|
+
this.data = data;
|
|
31317
|
+
this.options = options;
|
|
31318
|
+
this.pick = pick;
|
|
31319
|
+
}
|
|
31320
|
+
has(id) {
|
|
31321
|
+
return !this.options.bodies || this.options.bodies.has(id);
|
|
31322
|
+
}
|
|
31323
|
+
name(id, exported) {
|
|
31324
|
+
return this.options.names[id] ?? (exported ? displayName(exported) : id);
|
|
31325
|
+
}
|
|
31326
|
+
named(id, name) {
|
|
31327
|
+
return this.options.names[id] ?? name;
|
|
31328
|
+
}
|
|
31329
|
+
link(target) {
|
|
31330
|
+
this.targets.set(targetKey(target), target);
|
|
31331
|
+
return { onclick: () => this.pick(target) };
|
|
31332
|
+
}
|
|
31333
|
+
body(id, name) {
|
|
31334
|
+
return this.has(id) ? this.link({
|
|
31335
|
+
kind: "body",
|
|
31336
|
+
id,
|
|
31337
|
+
name
|
|
31338
|
+
}) : void 0;
|
|
31339
|
+
}
|
|
31340
|
+
stack(primaryId, listed) {
|
|
31341
|
+
if (listed && !this.options.bodies) return listed;
|
|
31342
|
+
const moons = (systemEntry(this.data.systems, primaryId)?.moons ?? []).filter((moon) => this.has(moon.id)).map((moon) => ({
|
|
31343
|
+
id: moon.id,
|
|
31344
|
+
name: this.name(moon.id),
|
|
31345
|
+
radiusKm: moon.radius_km,
|
|
31346
|
+
color: BODY_COLORS[moon.id] ?? moon.color ?? "#cccccc"
|
|
31347
|
+
}));
|
|
31348
|
+
if (!moons.length) return void 0;
|
|
31349
|
+
moons.sort((a, b) => b.radiusKm - a.radiusKm);
|
|
31350
|
+
return {
|
|
31351
|
+
satellites: moons.slice(0, MAX_STACK),
|
|
31352
|
+
count: moons.length,
|
|
31353
|
+
tab: true
|
|
31354
|
+
};
|
|
31355
|
+
}
|
|
31356
|
+
links(id, name, stack) {
|
|
31357
|
+
if (!stack) return { link: this.body(id, name) };
|
|
31358
|
+
const system = () => this.link({
|
|
31359
|
+
kind: "system",
|
|
31360
|
+
id,
|
|
31361
|
+
name
|
|
31362
|
+
});
|
|
31363
|
+
if (this.options.grouping === "systems") return {
|
|
31364
|
+
link: system(),
|
|
31365
|
+
grouped: true
|
|
31366
|
+
};
|
|
31367
|
+
const largest = stack.satellites[0];
|
|
31368
|
+
return {
|
|
31369
|
+
link: this.body(id, name),
|
|
31370
|
+
satellitesLink: stack.tab ? system() : this.body(largest.id, largest.name)
|
|
31371
|
+
};
|
|
31372
|
+
}
|
|
31373
|
+
built(model, label) {
|
|
31374
|
+
return {
|
|
31375
|
+
model,
|
|
31376
|
+
targets: [...this.targets.values()],
|
|
31377
|
+
label
|
|
31378
|
+
};
|
|
31379
|
+
}
|
|
31380
|
+
};
|
|
31381
|
+
function buildSolarView(data, options, text, pick) {
|
|
31382
|
+
const scope = new Scope(data, options, pick);
|
|
31383
|
+
const name = (o) => scope.name(o.id, o.name);
|
|
31384
|
+
const listed = fileStacks(data.solar, name);
|
|
31385
|
+
const stacks = new Map(data.solar.objects.map((o) => [o.id, scope.stack(o.id, listed.get(o.id))]));
|
|
31386
|
+
const sun = data.solar.objects.find((o) => o.kind === "star");
|
|
31387
|
+
const zoneBand = (zone) => {
|
|
31388
|
+
const label = zone === "inner" ? text.zoneInner : text.zoneOuter;
|
|
31389
|
+
return {
|
|
31390
|
+
label,
|
|
31391
|
+
sub: "",
|
|
31392
|
+
...scope.link({
|
|
31393
|
+
kind: "zone",
|
|
31394
|
+
zone,
|
|
31395
|
+
name: label
|
|
31396
|
+
})
|
|
31397
|
+
};
|
|
31398
|
+
};
|
|
31399
|
+
const model = solarSystemModel(data.solar, {
|
|
31400
|
+
text,
|
|
31401
|
+
name,
|
|
31402
|
+
primaryLink: sun && scope.body(sun.id, name(sun)),
|
|
31403
|
+
keep: (o) => scope.has(o.id) || !!stacks.get(o.id),
|
|
31404
|
+
stack: (o) => stacks.get(o.id),
|
|
31405
|
+
links: (o, stack) => scope.links(o.id, name(o), stack),
|
|
31406
|
+
band: (belt) => options.zones === "belts" ? { label: belt.label } : zoneBand(belt.kind === "kuiper_belt" ? "outer" : "inner")
|
|
31407
|
+
});
|
|
31408
|
+
if (!model) throw new Error("spacemap: the Solar System map names no Sun");
|
|
31409
|
+
return scope.built(model, text.solarSystem);
|
|
31410
|
+
}
|
|
31411
|
+
function buildSystemView(data, primaryId, exported, options, text, pick) {
|
|
31412
|
+
const entry = systemEntry(data.systems, primaryId);
|
|
31413
|
+
if (!entry) throw new Error(`spacemap: no planetary system around ${primaryId}`);
|
|
31414
|
+
const scope = new Scope(data, options, pick);
|
|
31415
|
+
const system = systemFromMapEntry(entry, scope.name(primaryId, exported[primaryId]));
|
|
31416
|
+
system.moons = system.moons.filter((moon) => scope.has(moon.id)).map((moon) => ({
|
|
31417
|
+
...moon,
|
|
31418
|
+
name: scope.name(moon.id, exported[moon.id])
|
|
31419
|
+
}));
|
|
31420
|
+
const rKm = system.planetRadiusKm;
|
|
31421
|
+
const bands = system.rings ? [{
|
|
31422
|
+
key: "rings",
|
|
31423
|
+
label: text.rings,
|
|
31424
|
+
innerKm: system.rings.innerRp * rKm,
|
|
31425
|
+
outerKm: system.rings.outerRp * rKm,
|
|
31426
|
+
tone: "amber"
|
|
31427
|
+
}] : [];
|
|
31428
|
+
const model = planetarySystemModel(system, {
|
|
31429
|
+
text,
|
|
31430
|
+
axisLabel: text.axisRadii,
|
|
31431
|
+
bands,
|
|
31432
|
+
primaryLink: scope.body(primaryId, system.planetName),
|
|
31433
|
+
moonLink: (moon) => scope.body(moon.id, moon.name)
|
|
31434
|
+
});
|
|
31435
|
+
model.ticks = model.ticks.filter((t) => tickAt(t, model.domain) < LAST_RADII_TICK_AT);
|
|
31436
|
+
return scope.built(model, system.planetName);
|
|
31437
|
+
}
|
|
31438
|
+
function buildZoneView(data, zone, options, text, pick) {
|
|
31439
|
+
const scope = new Scope(data, options, pick);
|
|
31440
|
+
const { solar, extras } = data;
|
|
31441
|
+
const sun = solar.objects.find((o) => o.kind === "star");
|
|
31442
|
+
const jupiterAu = solar.objects.find((o) => o.id === JUPITER_ID)?.a;
|
|
31443
|
+
if (!sun || jupiterAu === void 0) throw new Error("spacemap: the Solar System map names no Sun or no Jupiter");
|
|
31444
|
+
const small = solar.objects.filter((o) => o.kind === "dwarf" || o.kind === "asteroid").map((o) => ({
|
|
31445
|
+
id: o.id,
|
|
31446
|
+
name: scope.name(o.id, o.name),
|
|
31447
|
+
aAu: o.a,
|
|
31448
|
+
tiltDeg: o.i,
|
|
31449
|
+
radiusKm: o.diameter_km / 2,
|
|
31450
|
+
color: solarColor(o)
|
|
31451
|
+
}));
|
|
31452
|
+
for (const extra of extras) {
|
|
31453
|
+
if (extra.moon || small.some((s) => s.id === extra.id)) continue;
|
|
31454
|
+
if (!(extra.aAu > 0)) continue;
|
|
31455
|
+
small.push({
|
|
31456
|
+
...extra,
|
|
31457
|
+
name: scope.named(extra.id, extra.name)
|
|
31458
|
+
});
|
|
31459
|
+
}
|
|
31460
|
+
const bodies = [];
|
|
31461
|
+
for (const s of small) {
|
|
31462
|
+
if (zoneOf(s.aAu, jupiterAu) !== zone) continue;
|
|
31463
|
+
const base = {
|
|
31464
|
+
...s,
|
|
31465
|
+
aKm: s.aAu * AU_KM
|
|
31466
|
+
};
|
|
31467
|
+
const stack = scope.stack(s.id);
|
|
31468
|
+
if (stack) {
|
|
31469
|
+
bodies.push({
|
|
31470
|
+
...base,
|
|
31471
|
+
satellites: stack.satellites,
|
|
31472
|
+
satelliteCount: stack.count,
|
|
31473
|
+
satellitesTab: true,
|
|
31474
|
+
...scope.links(s.id, s.name, stack)
|
|
31475
|
+
});
|
|
31476
|
+
continue;
|
|
31477
|
+
}
|
|
31478
|
+
const moons = extras.filter((extra) => extra.moon && extra.parent === s.id && scope.has(extra.id)).map((extra) => {
|
|
31479
|
+
const name = scope.named(extra.id, extra.name);
|
|
31480
|
+
return {
|
|
31481
|
+
...extra,
|
|
31482
|
+
name,
|
|
31483
|
+
link: scope.body(extra.id, name)
|
|
31484
|
+
};
|
|
31485
|
+
});
|
|
31486
|
+
if (!scope.has(s.id) && !moons.length) continue;
|
|
31487
|
+
bodies.push({
|
|
31488
|
+
...base,
|
|
31489
|
+
link: scope.body(s.id, s.name),
|
|
31490
|
+
satellites: moons.length ? moons : void 0
|
|
31491
|
+
});
|
|
31492
|
+
}
|
|
31493
|
+
const [lo, hi] = ZONE_DOMAIN[zone];
|
|
31494
|
+
const domain = [Math.min(lo, ...bodies.map((b) => b.aKm / AU_KM * .85)), Math.max(hi, ...bodies.map((b) => b.aKm / AU_KM * 1.15))];
|
|
31495
|
+
const within = (au) => au >= domain[0] && au <= domain[1];
|
|
31496
|
+
const pxPerKm = ZONE_TOP_R / Math.max(...bodies.map((b) => b.radiusKm), 1);
|
|
31497
|
+
for (const o of solar.objects) {
|
|
31498
|
+
if (o.kind !== "planet" || !within(o.a)) continue;
|
|
31499
|
+
bodies.push({
|
|
31500
|
+
id: o.id,
|
|
31501
|
+
name: scope.name(o.id, o.name),
|
|
31502
|
+
aKm: o.a * AU_KM,
|
|
31503
|
+
tiltDeg: 0,
|
|
31504
|
+
radiusKm: 0,
|
|
31505
|
+
color: solarColor(o),
|
|
31506
|
+
reference: true
|
|
31507
|
+
});
|
|
31508
|
+
}
|
|
31509
|
+
const label = zone === "inner" ? text.zoneInner : text.zoneOuter;
|
|
31510
|
+
return scope.built({
|
|
31511
|
+
primary: {
|
|
31512
|
+
id: sun.id,
|
|
31513
|
+
name: scope.name(sun.id, sun.name),
|
|
31514
|
+
radiusKm: sun.diameter_km / 2 * SOLAR_PX_PER_KM / pxPerKm,
|
|
31515
|
+
color: SUN_COLOR
|
|
31516
|
+
},
|
|
31517
|
+
bodies,
|
|
31518
|
+
bands: solar.belts.filter((belt) => within(belt.inner_au) && within(belt.outer_au)).map((belt) => ({
|
|
31519
|
+
key: belt.slug,
|
|
31520
|
+
label: belt.label,
|
|
31521
|
+
innerKm: belt.inner_au * AU_KM,
|
|
31522
|
+
outerKm: belt.outer_au * AU_KM,
|
|
31523
|
+
tone: belt.kind === "kuiper_belt" ? "sky" : "muted"
|
|
31524
|
+
})),
|
|
31525
|
+
unitKm: AU_KM,
|
|
31526
|
+
domain,
|
|
31527
|
+
ticks: ZONE_TICKS.filter((t) => within(t) && tickAt(t, domain) < LAST_AU_TICK_AT),
|
|
31528
|
+
axisLabel: text.axisAu,
|
|
31529
|
+
pxPerKm,
|
|
31530
|
+
pxPerDeg: SOLAR_PX_PER_DEG,
|
|
31531
|
+
text
|
|
31532
|
+
}, label);
|
|
31533
|
+
}
|
|
31534
|
+
|
|
31535
|
+
//#endregion
|
|
31536
|
+
//#region src/lib/systemmap/system-map.ts
|
|
31537
|
+
var onceTarget = /* @__PURE__ */ new WeakMap();
|
|
31538
|
+
var AU_TO_KM_THRESHOLD = .01;
|
|
31539
|
+
function viewText() {
|
|
31540
|
+
const { messages, locale } = host();
|
|
31541
|
+
const number = new Intl.NumberFormat(locale(), { maximumSignificantDigits: 3 });
|
|
31542
|
+
const kilometres = new Intl.NumberFormat(locale(), {
|
|
31543
|
+
style: "unit",
|
|
31544
|
+
unit: "kilometer",
|
|
31545
|
+
maximumSignificantDigits: 3
|
|
31546
|
+
});
|
|
31547
|
+
const au = messages.unit_symbol_astronomical_unit();
|
|
31548
|
+
return {
|
|
31549
|
+
primary: messages.planetary_system_primary(),
|
|
31550
|
+
retrograde: messages.planetary_system_retrograde(),
|
|
31551
|
+
moons: (count) => messages.system_map_moons({ count }),
|
|
31552
|
+
distance: (km) => {
|
|
31553
|
+
const distance = km / AU_KM;
|
|
31554
|
+
return distance > 0 && distance < AU_TO_KM_THRESHOLD ? kilometres.format(km) : `${number.format(distance)} ${au}`;
|
|
31555
|
+
},
|
|
31556
|
+
axisAu: messages.system_map_axis_au(),
|
|
31557
|
+
solarSystem: messages.system_map_solar_system(),
|
|
31558
|
+
rings: messages.tab_rings(),
|
|
31559
|
+
axisRadii: messages.planetary_system_axis_unit(),
|
|
31560
|
+
zoneInner: messages.system_map_zone_inner(),
|
|
31561
|
+
zoneOuter: messages.system_map_zone_outer()
|
|
31562
|
+
};
|
|
31563
|
+
}
|
|
31564
|
+
var SystemMap = class {
|
|
31565
|
+
chart = new SystemMapChart();
|
|
31566
|
+
listeners = /* @__PURE__ */ new Map();
|
|
31567
|
+
view;
|
|
31568
|
+
bodies;
|
|
31569
|
+
names;
|
|
31570
|
+
grouping;
|
|
31571
|
+
zones;
|
|
31572
|
+
places;
|
|
31573
|
+
targets = [];
|
|
31574
|
+
extras = /* @__PURE__ */ new Map();
|
|
31575
|
+
queue = Promise.resolve();
|
|
31576
|
+
disposed = false;
|
|
31577
|
+
constructor(options = {}) {
|
|
31578
|
+
this.view = options.view ?? { kind: "solar-system" };
|
|
31579
|
+
this.bodies = options.bodies ? new Set(options.bodies) : null;
|
|
31580
|
+
this.names = options.names ?? {};
|
|
31581
|
+
this.grouping = options.grouping ?? "bodies";
|
|
31582
|
+
this.zones = options.zones ?? "belts";
|
|
31583
|
+
this.places = (options.places ?? []).map(extraFromPlace);
|
|
31584
|
+
}
|
|
31585
|
+
mount(container) {
|
|
31586
|
+
container.append(this.chart.root);
|
|
31587
|
+
}
|
|
31588
|
+
remove() {
|
|
31589
|
+
this.disposed = true;
|
|
31590
|
+
this.chart.destroy();
|
|
31591
|
+
}
|
|
31592
|
+
load() {
|
|
31593
|
+
return this.draw();
|
|
31594
|
+
}
|
|
31595
|
+
async setView(view) {
|
|
31596
|
+
await this.draw(view);
|
|
31597
|
+
}
|
|
31598
|
+
getView() {
|
|
31599
|
+
return this.view;
|
|
31600
|
+
}
|
|
31601
|
+
setBodies(ids) {
|
|
31602
|
+
const bodies = ids ? new Set(ids) : null;
|
|
31603
|
+
return this.draw(void 0, bodies).catch(() => {});
|
|
31604
|
+
}
|
|
31605
|
+
getTargets() {
|
|
31606
|
+
return [...this.targets];
|
|
31607
|
+
}
|
|
31608
|
+
draw(view, bodies) {
|
|
31609
|
+
const turn = this.queue.then(() => {
|
|
31610
|
+
if (bodies !== void 0) this.bodies = bodies;
|
|
31611
|
+
return this.drawNow(view ?? this.view);
|
|
31612
|
+
});
|
|
31613
|
+
this.queue = turn.catch(() => {});
|
|
31614
|
+
return turn;
|
|
31615
|
+
}
|
|
31616
|
+
async drawNow(view) {
|
|
31617
|
+
let built;
|
|
31618
|
+
try {
|
|
31619
|
+
built = await this.build(view);
|
|
31620
|
+
} catch (cause) {
|
|
31621
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
31622
|
+
if (!this.disposed) this.emit("error", error);
|
|
31623
|
+
throw error;
|
|
31624
|
+
}
|
|
31625
|
+
if (this.disposed) return;
|
|
31626
|
+
const changed = JSON.stringify(view) !== JSON.stringify(this.view);
|
|
31627
|
+
this.view = view;
|
|
31628
|
+
this.targets = built.targets;
|
|
31629
|
+
this.chart.update(built.model, { ariaLabel: built.label });
|
|
31630
|
+
if (changed) this.emit("viewchange", view);
|
|
31631
|
+
}
|
|
31632
|
+
async build(view) {
|
|
31633
|
+
const [solar, systems] = await Promise.all([fetchSolarSystemMap(), fetchPlanetarySystemsMap()]);
|
|
31634
|
+
const options = {
|
|
31635
|
+
bodies: this.bodies,
|
|
31636
|
+
names: this.names,
|
|
31637
|
+
grouping: this.grouping,
|
|
31638
|
+
zones: this.zones
|
|
31639
|
+
};
|
|
31640
|
+
const text = viewText();
|
|
31641
|
+
const pick = (target) => this.emit("select", target);
|
|
31642
|
+
const data = {
|
|
31643
|
+
solar,
|
|
31644
|
+
systems,
|
|
31645
|
+
extras: []
|
|
31646
|
+
};
|
|
31647
|
+
switch (view.kind) {
|
|
31648
|
+
case "solar-system": return buildSolarView(data, options, text, pick);
|
|
31649
|
+
case "system": return buildSystemView(data, view.id, await this.exportedNames(view.id, systems), options, text, pick);
|
|
31650
|
+
case "zone": {
|
|
31651
|
+
const ids = unplacedIds(solar, systems, this.bodies, this.places.map((place) => place.id));
|
|
31652
|
+
const read = (await Promise.all(ids.map((id) => this.extra(id)))).filter((extra) => extra !== null);
|
|
31653
|
+
const placed = [...this.places, ...read];
|
|
31654
|
+
const parents = unplacedIds(solar, systems, new Set(placed.flatMap((extra) => extra.moon && extra.parent ? [extra.parent] : [])), placed.map((extra) => extra.id));
|
|
31655
|
+
const hosts = await Promise.all(parents.map((id) => this.extra(id)));
|
|
31656
|
+
const extras = [...placed, ...hosts.filter((extra) => extra !== null)];
|
|
31657
|
+
return buildZoneView({
|
|
31658
|
+
...data,
|
|
31659
|
+
extras
|
|
31660
|
+
}, view.zone, options, text, pick);
|
|
31661
|
+
}
|
|
31662
|
+
}
|
|
31663
|
+
}
|
|
31664
|
+
extra(id) {
|
|
31665
|
+
let pending = this.extras.get(id);
|
|
31666
|
+
if (!pending) {
|
|
31667
|
+
pending = fetchObjectDetail(id, false).then((detail) => {
|
|
31668
|
+
if (!detail.global) throw new Error(`spacemap: no such body as ${id}`);
|
|
31669
|
+
return extraFromBundle(detail.global);
|
|
31670
|
+
}).catch((cause) => {
|
|
31671
|
+
this.extras.delete(id);
|
|
31672
|
+
this.emit("error", cause instanceof Error ? cause : new Error(String(cause)));
|
|
31673
|
+
return null;
|
|
31674
|
+
});
|
|
31675
|
+
this.extras.set(id, pending);
|
|
31676
|
+
}
|
|
31677
|
+
return pending;
|
|
31678
|
+
}
|
|
31679
|
+
async exportedNames(primaryId, systems) {
|
|
31680
|
+
if (![primaryId, ...(systemEntry(systems, primaryId)?.moons ?? []).map((m) => m.id)].some((id) => !(id in this.names) && (id === primaryId || !this.bodies || this.bodies.has(id)))) return {};
|
|
31681
|
+
try {
|
|
31682
|
+
const { global, localized } = await fetchObjectDetail(primaryId, true, host().locale());
|
|
31683
|
+
const names = {};
|
|
31684
|
+
for (const moon of global?.notable_moons ?? []) if (moon.id) names[moon.id] = moon.name;
|
|
31685
|
+
Object.assign(names, localized?.notable_moon_names);
|
|
31686
|
+
const name = localized?.name ?? global?.name;
|
|
31687
|
+
if (name) names[primaryId] = name;
|
|
31688
|
+
return names;
|
|
31689
|
+
} catch {
|
|
31690
|
+
return {};
|
|
31691
|
+
}
|
|
31692
|
+
}
|
|
31693
|
+
on(event, listener) {
|
|
31694
|
+
let set = this.listeners.get(event);
|
|
31695
|
+
if (!set) {
|
|
31696
|
+
set = /* @__PURE__ */ new Set();
|
|
31697
|
+
this.listeners.set(event, set);
|
|
31698
|
+
}
|
|
31699
|
+
set.add(listener);
|
|
31700
|
+
return () => set.delete(listener);
|
|
31701
|
+
}
|
|
31702
|
+
once(event, listener) {
|
|
31703
|
+
const wrapped = ((...args) => {
|
|
31704
|
+
off();
|
|
31705
|
+
listener(...args);
|
|
31706
|
+
});
|
|
31707
|
+
onceTarget.set(wrapped, listener);
|
|
31708
|
+
const off = this.on(event, wrapped);
|
|
31709
|
+
return off;
|
|
31710
|
+
}
|
|
31711
|
+
off(event, listener) {
|
|
31712
|
+
const set = this.listeners.get(event);
|
|
31713
|
+
if (!set) return;
|
|
31714
|
+
set.delete(listener);
|
|
31715
|
+
for (const held of set) if (onceTarget.get(held) === listener) set.delete(held);
|
|
31716
|
+
}
|
|
31717
|
+
emit(event, ...args) {
|
|
31718
|
+
const set = this.listeners.get(event);
|
|
31719
|
+
if (!set) return;
|
|
31720
|
+
for (const listener of [...set]) listener(...args);
|
|
31721
|
+
}
|
|
31722
|
+
};
|
|
31723
|
+
|
|
30051
31724
|
//#endregion
|
|
30052
31725
|
//#region src/lib/scene/state/attribution.ts
|
|
30053
31726
|
var DEFAULT_ORBIT_LABELS = Object.fromEntries(ORBIT_SOURCE_ORDER.map((s) => [s, ORBIT_SOURCES[s].label]));
|
|
@@ -30308,10 +31981,16 @@ function kmToAu(km) {
|
|
|
30308
31981
|
|
|
30309
31982
|
//#endregion
|
|
30310
31983
|
//#region src/lib/fetch/panoramas.ts
|
|
30311
|
-
|
|
30312
|
-
|
|
30313
|
-
if (
|
|
30314
|
-
|
|
31984
|
+
var index = null;
|
|
31985
|
+
function fetchPanoramaIndex(fetcher = fetchWithTimeout) {
|
|
31986
|
+
if (index) return index;
|
|
31987
|
+
index = (async () => {
|
|
31988
|
+
const res = await fetcher(`${dataBase()}/v1/panoramas.json`);
|
|
31989
|
+
if (!res.ok) throw new Error(`Failed to load panoramas.json: ${res.status}`);
|
|
31990
|
+
return (await res.json()).bodies ?? [];
|
|
31991
|
+
})();
|
|
31992
|
+
index.catch(() => index = null);
|
|
31993
|
+
return index;
|
|
30315
31994
|
}
|
|
30316
31995
|
async function fetchPanoramas(bodyId) {
|
|
30317
31996
|
return (await fetchObjectDetail(bodyId, false)).global?.panoramas ?? [];
|
|
@@ -30382,6 +32061,27 @@ async function createPanorama(options) {
|
|
|
30382
32061
|
await view.load();
|
|
30383
32062
|
return view;
|
|
30384
32063
|
}
|
|
32064
|
+
async function createSystemMap(options) {
|
|
32065
|
+
const { container, dataUrl, imagesUrl, locale, messages, includeNonCommercial, events, ...rest } = options;
|
|
32066
|
+
const element = resolveContainer(container);
|
|
32067
|
+
applyHost({
|
|
32068
|
+
dataUrl,
|
|
32069
|
+
imagesUrl,
|
|
32070
|
+
locale,
|
|
32071
|
+
messages,
|
|
32072
|
+
includeNonCommercial
|
|
32073
|
+
});
|
|
32074
|
+
const map = new SystemMap(rest);
|
|
32075
|
+
map.mount(element);
|
|
32076
|
+
for (const [event, listener] of Object.entries(events ?? {})) map.on(event, listener);
|
|
32077
|
+
try {
|
|
32078
|
+
await map.load();
|
|
32079
|
+
} catch (error) {
|
|
32080
|
+
map.remove();
|
|
32081
|
+
throw error;
|
|
32082
|
+
}
|
|
32083
|
+
return map;
|
|
32084
|
+
}
|
|
30385
32085
|
function resolveContainer(container) {
|
|
30386
32086
|
if (typeof container !== "string") return container;
|
|
30387
32087
|
const element = document.querySelector(container);
|
|
@@ -30399,5 +32099,5 @@ function applyHost(options) {
|
|
|
30399
32099
|
}
|
|
30400
32100
|
|
|
30401
32101
|
//#endregion
|
|
30402
|
-
export { AttributionControl, FlatAttributionControl, FlatMap, GestureHandler, MAP_LAYERS, PROJECTION_IDS, PanoramaAttributionControl, PanoramaView, SpaceMap, Viewport, bearingDeg, boxRing, circlePoints, configureHost, createFlatMap, createMap, createPanorama, createProjection, dateToJD, defaultSceneSettings, elements, fetchPanoramaIndex, fetchPanoramas, findPanorama, fixed, graticule, groundDistanceM, jdToDate, kmToAu, neighboursOf, panoramaAt, pathFor, planeBasis, projectionAspect, samples, smallCircle, tle };
|
|
30403
|
-
(function(){if(typeof document==='undefined')return;var s=document.createElement('style');s.textContent=".scene-label{pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;color:inherit;touch-action:none;z-index:1;width:32px;height:32px;text-decoration:none;display:block;position:relative;left:0}.scene-label__halo{border-radius:50%;width:100%;height:100%;transition:transform .1s}.scene-label__halo--spacecraft,.scene-label__halo--debris{overflow:visible}.scene-label__loader{box-sizing:border-box;pointer-events:none;border:2px solid #fff;border-color:#fff #fff #fff3 #fff3;border-radius:50%;width:32px;height:32px;margin:auto;animation:.9s linear infinite scene-label-loader-spin;position:fixed;inset:0}@keyframes scene-label-loader-spin{to{transform:rotate(360deg)}}.reduce-motion .scene-label__loader{animation-duration:.9s!important;animation-iteration-count:infinite!important}.scene-label__name{white-space:nowrap;pointer-events:auto;cursor:pointer;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;padding-left:8px;font-size:14px;font-weight:700;text-decoration:none;position:absolute;top:50%;left:32px;transform:translateY(-50%)}.scene-label__name--large{font-size:16px}.scene-label__annotations{top:28px;pointer-events:none;flex-direction:column;padding-inline-start:8px;display:flex;position:absolute;inset-inline-start:32px}.scene-label__annotation{opacity:0;white-space:nowrap;color:#fff9;text-shadow:0 0 3px #000,0 0 3px #000;transition:opacity .15s, display .15s allow-discrete;font-size:11px;line-height:1.3;display:none}.scene-label__annotation--visible{opacity:1;display:block}@starting-style{.scene-label__annotation--visible{opacity:0}}.scene-feature-label{color:#ffffffb3;white-space:nowrap;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;text-shadow:0 0 2px #000,0 0 2px #000;z-index:0;font-size:12px;transition:color .1s;left:0}.scene-feature-label:hover{color:#fff;text-shadow:0 0 3px #000,0 0 3px #000}.scene-feature-label--active{color:#fff;font-size:14px;font-weight:600}.scene-path-label{color:#fff;font:inherit;white-space:nowrap;text-align:start;text-shadow:0 0 4px #000,0 0 4px #000;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:none;z-index:2;background:0 0;border:0;align-items:center;gap:8px;padding:0;line-height:1.15;display:flex;left:0}.scene-path-label--static{pointer-events:none;cursor:default}.scene-path-label__halo{border-radius:50%;flex-shrink:0;width:32px;height:32px;margin-inline-start:-16px;transition:transform .1s}.scene-path-label:hover .scene-path-label__halo{transform:scale(1.15)}.scene-path-label__text{flex-direction:column;gap:1px;display:flex}.scene-path-label__name{font-size:14px;font-weight:700}.scene-path-label__when{opacity:.8;font-variant-numeric:tabular-nums;font-size:12px}.scene-path-label--dim .scene-path-label__text{display:none}.scene-path-label--dim .scene-path-label__halo{transform:scale(.55)}.scene-path-label--dim:hover .scene-path-label__halo{transform:scale(.75)}.scene-path-label--step .scene-path-label__halo{background:0 0;border:0}.scene-path-label--step:hover .scene-path-label__halo{background:#ffffff1f}.scene-path-label--step .scene-path-label__name{font-size:12px;font-weight:600}.scene-path-label--step .scene-path-label__when{font-size:11px}.scene-path-label__hazard{font-size:11px;font-weight:700}.scene-path-label--lit{color:#fff}.scene-path-label--lit .scene-path-label__halo{transform:scale(1.15)}.scene-path-label--lit.scene-path-label--dim .scene-path-label__halo{transform:scale(.75)}.scene-path-label--faint .scene-path-label__text{display:none}.scene-path-label--faint .scene-path-label__halo{opacity:.45;transform:scale(.5)}.scene-path-label--faint:hover .scene-path-label__halo{opacity:1;transform:scale(.75)}.scene-overlay--no-names .scene-label__name,.scene-overlay--no-names .scene-label__annotations{display:none}.scene-hazard-label{white-space:nowrap;pointer-events:none;-webkit-user-select:none;user-select:none;text-shadow:0 0 4px #000,0 0 4px #000;margin-inline-start:16px;font-size:12px;font-weight:700;position:relative;left:0}.user-location-marker{pointer-events:none;background:#1a73e8;border:2px solid #fff;border-radius:50%;width:16px;height:16px;box-shadow:0 0 6px #0006}.sm-drawing-label{white-space:nowrap;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;-webkit-user-select:none;user-select:none;pointer-events:none;font:700 14px/1.2 system-ui,sans-serif}.sm-drawing-icon{-webkit-user-select:none;user-select:none;pointer-events:none;display:block}.sm-drawing--interactive{pointer-events:auto;cursor:pointer;touch-action:none}.sm-cooperative-hint{z-index:4;color:#fff;text-align:center;pointer-events:none;opacity:0;background:#0000008c;place-content:center;padding:16px;font:15px/1.4 system-ui,sans-serif;transition:opacity .2s;display:grid;position:absolute;inset:0}.sm-cooperative-hint--shown{opacity:1}@media (prefers-reduced-motion:reduce){.sm-cooperative-hint{transition:none}}.sm-ctrl{z-index:2;pointer-events:none;flex-direction:column;gap:6px;max-width:100%;padding:6px;display:flex;position:absolute}.sm-ctrl>*{pointer-events:auto}.sm-ctrl--top-left{align-items:flex-start;top:0;left:0}.sm-ctrl--top-right{align-items:flex-end;top:0;right:0}.sm-ctrl--bottom-left{align-items:flex-start;bottom:0;left:0}.sm-ctrl--bottom-right{align-items:flex-end;bottom:0;right:0}.sm-map{-webkit-user-select:none;user-select:none
|
|
32102
|
+
export { AttributionControl, FlatAttributionControl, FlatMap, GestureHandler, MAP_LAYERS, PROJECTION_IDS, PanoramaAttributionControl, PanoramaView, SpaceMap, SystemMap, Viewport, bearingDeg, boxRing, circlePoints, configureHost, createFlatMap, createMap, createPanorama, createProjection, createSystemMap, dateToJD, defaultSceneSettings, elements, fetchPanoramaIndex, fetchPanoramas, findPanorama, fixed, graticule, groundDistanceM, isViewable, jdToDate, kmToAu, neighboursOf, panoramaAt, pathFor, planeBasis, projectionAspect, samples, smallCircle, tle };
|
|
32103
|
+
(function(){if(typeof document==='undefined')return;var s=document.createElement('style');s.textContent=".scene-label{pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;color:inherit;touch-action:none;z-index:1;width:32px;height:32px;text-decoration:none;display:block;position:relative;left:0}.scene-label__halo{border-radius:50%;width:100%;height:100%;transition:transform .1s}.scene-label__halo--spacecraft,.scene-label__halo--debris{overflow:visible}.scene-label__loader{box-sizing:border-box;pointer-events:none;border:2px solid #fff;border-color:#fff #fff #fff3 #fff3;border-radius:50%;width:32px;height:32px;margin:auto;animation:.9s linear infinite scene-label-loader-spin;position:fixed;inset:0}@keyframes scene-label-loader-spin{to{transform:rotate(360deg)}}.reduce-motion .scene-label__loader{animation-duration:.9s!important;animation-iteration-count:infinite!important}.scene-label__name{white-space:nowrap;pointer-events:auto;cursor:pointer;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;padding-left:8px;font-size:14px;font-weight:700;text-decoration:none;position:absolute;top:50%;left:32px;transform:translateY(-50%)}.scene-label__name--large{font-size:16px}.scene-label__annotations{top:28px;pointer-events:none;flex-direction:column;padding-inline-start:8px;display:flex;position:absolute;inset-inline-start:32px}.scene-label__annotation{opacity:0;white-space:nowrap;color:#fff9;text-shadow:0 0 3px #000,0 0 3px #000;transition:opacity .15s, display .15s allow-discrete;font-size:11px;line-height:1.3;display:none}.scene-label__annotation--visible{opacity:1;display:block}@starting-style{.scene-label__annotation--visible{opacity:0}}.scene-feature-label{color:#ffffffb3;white-space:nowrap;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;text-shadow:0 0 2px #000,0 0 2px #000;z-index:0;font-size:12px;transition:color .1s;left:0}.scene-feature-label:hover{color:#fff;text-shadow:0 0 3px #000,0 0 3px #000}.scene-feature-label--active{color:#fff;font-size:14px;font-weight:600}.scene-path-label{color:#fff;font:inherit;white-space:nowrap;text-align:start;text-shadow:0 0 4px #000,0 0 4px #000;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:none;z-index:2;background:0 0;border:0;align-items:center;gap:8px;padding:0;line-height:1.15;display:flex;left:0}.scene-path-label--static{pointer-events:none;cursor:default}.scene-path-label__halo{border-radius:50%;flex-shrink:0;width:32px;height:32px;margin-inline-start:-16px;transition:transform .1s}.scene-path-label:hover .scene-path-label__halo{transform:scale(1.15)}.scene-path-label__text{flex-direction:column;gap:1px;display:flex}.scene-path-label__name{font-size:14px;font-weight:700}.scene-path-label__when{opacity:.8;font-variant-numeric:tabular-nums;font-size:12px}.scene-path-label--dim .scene-path-label__text{display:none}.scene-path-label--dim .scene-path-label__halo{transform:scale(.55)}.scene-path-label--dim:hover .scene-path-label__halo{transform:scale(.75)}.scene-path-label--step .scene-path-label__halo{background:0 0;border:0}.scene-path-label--step:hover .scene-path-label__halo{background:#ffffff1f}.scene-path-label--step .scene-path-label__name{font-size:12px;font-weight:600}.scene-path-label--step .scene-path-label__when{font-size:11px}.scene-path-label__hazard{font-size:11px;font-weight:700}.scene-path-label--lit{color:#fff}.scene-path-label--lit .scene-path-label__halo{transform:scale(1.15)}.scene-path-label--lit.scene-path-label--dim .scene-path-label__halo{transform:scale(.75)}.scene-path-label--faint .scene-path-label__text{display:none}.scene-path-label--faint .scene-path-label__halo{opacity:.45;transform:scale(.5)}.scene-path-label--faint:hover .scene-path-label__halo{opacity:1;transform:scale(.75)}.scene-overlay--no-names .scene-label__name,.scene-overlay--no-names .scene-label__annotations{display:none}.scene-hazard-label{white-space:nowrap;pointer-events:none;-webkit-user-select:none;user-select:none;text-shadow:0 0 4px #000,0 0 4px #000;margin-inline-start:16px;font-size:12px;font-weight:700;position:relative;left:0}.user-location-marker{pointer-events:none;background:#1a73e8;border:2px solid #fff;border-radius:50%;width:16px;height:16px;box-shadow:0 0 6px #0006}.sm-drawing-label{white-space:nowrap;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;-webkit-user-select:none;user-select:none;pointer-events:none;font:700 14px/1.2 system-ui,sans-serif}.sm-drawing-icon{-webkit-user-select:none;user-select:none;pointer-events:none;display:block}.sm-drawing--interactive{pointer-events:auto;cursor:pointer;touch-action:none}.sm-cooperative-hint{z-index:4;color:#fff;text-align:center;pointer-events:none;opacity:0;background:#0000008c;place-content:center;padding:16px;font:15px/1.4 system-ui,sans-serif;transition:opacity .2s;display:grid;position:absolute;inset:0}.sm-cooperative-hint--shown{opacity:1}@media (prefers-reduced-motion:reduce){.sm-cooperative-hint{transition:none}}.sm-ctrl{z-index:2;pointer-events:none;flex-direction:column;gap:6px;max-width:100%;padding:6px;display:flex;position:absolute}.sm-ctrl>*{pointer-events:auto}.sm-ctrl--top-left{align-items:flex-start;top:0;left:0}.sm-ctrl--top-right{align-items:flex-end;top:0;right:0}.sm-ctrl--bottom-left{align-items:flex-start;bottom:0;left:0}.sm-ctrl--bottom-right{align-items:flex-end;bottom:0;right:0}.sm-map{-webkit-user-select:none;user-select:none}:where(.sm-map){width:100%;height:100%;position:relative}.sm-map__canvas{pointer-events:auto;touch-action:none;width:100%;height:100%;display:block}.sm-map__canvas:focus-visible{outline:2px solid var(--ring,Highlight)}.sm-map .scene-overlay{pointer-events:none;z-index:0;position:absolute;inset:0}.sm-flat{touch-action:none;background:#05070e;width:100%;height:100%;position:relative;overflow:hidden}.sm-flat__raster,.sm-flat__vector,.sm-flat__markers{width:100%;height:100%;position:absolute;inset:0}.sm-flat__raster{display:block}.sm-flat__vector,.sm-flat__markers{pointer-events:none}.sm-flat--interactive{cursor:grab}.sm-flat--cooperative{touch-action:pan-x pan-y}.sm-flat--dragging{cursor:grabbing}.sm-flat__graticule{fill:none;stroke:#ffffff47;stroke-width:1px}.sm-flat__frame{fill:none;stroke:#ffffff73;stroke-width:1px}.sm-flat__feature-dot{fill:#fffc}.sm-flat__feature-label{fill:#ffffffd9;paint-order:stroke;stroke:#000000a6;stroke-width:2.5px;font:10px/1 system-ui,sans-serif}.sm-flat__marker-dot{background:#4ea3ff;border:2px solid #fff;border-radius:50%;width:10px;height:10px;box-shadow:0 0 0 1px #00000073}.sm-system-map{border-radius:var(--sm-system-map-radius,6px);background:var(--sm-system-map-background,#ffffff0d);width:100%;color:var(--sm-system-map-ink,#ffffff9e);position:relative;overflow:hidden}.sm-system-map--background{pointer-events:none;background:0 0;border-radius:0;height:100%}.sm-system-map__svg{touch-action:pan-y;width:100%;height:auto;display:block}.sm-system-map--background .sm-system-map__svg{height:100%}.sm-system-map__svg--near :is(a,.sm-system-map__button):not([data-near]){pointer-events:none}.sm-system-map__button{cursor:pointer}.sm-system-map__button:focus{outline:none}.sm-system-map__reference{opacity:.6}.sm-system-map__band--muted{color:var(--sm-system-map-ink,#ffffff9e)}.sm-system-map__band--sky{color:var(--sm-system-map-sky,oklch(74.6% .16 232.661/.7))}.sm-system-map__band--amber{color:var(--sm-system-map-amber,oklch(92.4% .12 95.746/.8))}.sm-system-map__band-label{font-weight:600}.sm-system-map__band-label--muted{fill:var(--sm-system-map-ink,#ffffff9e)}.sm-system-map__band-label--sky{fill:var(--sm-system-map-sky-label,oklch(74.6% .16 232.661))}.sm-system-map__band-label--amber{fill:var(--sm-system-map-amber-label,oklch(92.4% .12 95.746/.9))}.sm-system-map__tip{z-index:10;background:var(--sm-system-map-tip-background,#0a0c14e6);width:max-content;color:var(--sm-system-map-tip-ink,#fffffff2);white-space:nowrap;pointer-events:none;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-radius:.25rem;padding:.25rem .5rem;font-size:.75rem;line-height:1.33333;position:absolute;transform:translate(-50%);box-shadow:0 1px 3px #0000001a,0 1px 2px -1px #0000001a}.sm-system-map__tip[hidden]{display:none}.sm-system-map__tip-title{font-weight:500}.sm-system-map__tip-sub{color:var(--sm-system-map-ink,#ffffff9e)}.sm-ctrl:has(>.sm-attribution:only-child){padding:0}.sm-ctrl--top-left:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 0 var(--sm-attribution-radius) 0}.sm-ctrl--top-right:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 0 0 var(--sm-attribution-radius)}.sm-ctrl--bottom-left:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 var(--sm-attribution-radius) 0 0}.sm-ctrl--bottom-right:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:var(--sm-attribution-radius) 0 0 0}.sm-attribution{--sm-attribution-radius:3px;border-radius:var(--sm-attribution-radius);color:#ffffffbf;white-space:nowrap;background:#0006;align-items:center;gap:.5em;max-width:100%;padding:1px 6px;font:11px/1.45 system-ui,sans-serif;display:flex}.sm-attribution a{color:inherit;text-decoration:none}.sm-attribution a:hover{color:#fff;text-decoration:underline}.sm-attribution__home{font-weight:600}.sm-attribution__group{text-overflow:ellipsis;overflow:hidden}.sm-attribution__label{color:#ffffff80}\n/*$vite$:1*/";document.head.appendChild(s);})();
|