mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
chore: remove four modules nothing in the app uses (#1403)
* chore: remove four modules nothing in the app uses src/app/sourceSlot.js re-exported src/sources/sourceSlot.js, which every caller imports directly. src/data/flowTiles.js and src/data/issPass.js were reached only by their own tests. src/data/localGeojson.js wrapped localGeojsonCore.js with the app's services, which the catalog already passes itself. The local GeoJSON layer tests now build layers the way the catalog does, so they keep covering localGeojsonCore.js; only the test of the wrapper's own default host goes. Boundary lists, format scope and docs drop the removed paths. * docs: changelog for #1403
This commit is contained in:
1 parent
a43ff8619e
commit
2006c035d9
21 files changed
+46
-355
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
- Remove four modules nothing in the app imports: `src/app/sourceSlot.js`,
|
||||||
|
`src/data/flowTiles.js`, `src/data/issPass.js` and `src/data/localGeojson.js`.
|
||||||
|
The local GeoJSON layer tests now build layers the way the catalog does, so
|
||||||
|
they keep covering `localGeojsonCore.js`. Package exports are unchanged
|
||||||
|
(samehkhamis, #1403).
|
||||||
@@ -81,8 +81,6 @@ moves it. Do not label all ingestion modules platform-independent by filename.
|
|||||||
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `src/data/manager.js` | Lifecycle/panel compatibility and existing tests; normal assembly constructs lifecycle and presentation separately |
|
| `src/data/manager.js` | Lifecycle/panel compatibility and existing tests; normal assembly constructs lifecycle and presentation separately |
|
||||||
| `src/data/<layer>.js` | Legacy default instances; new assembly calls layer factories with supplied sources |
|
| `src/data/<layer>.js` | Legacy default instances; new assembly calls layer factories with supplied sources |
|
||||||
| `src/data/localGeojson.js` | Infrastructure compatibility factories; catalog imports services from `src/app/localGeojsonServices.js` directly |
|
|
||||||
| `src/app/sourceSlot.js` | Re-export for direct callers/tests; portable slot ownership is `src/sources/sourceSlot.js` |
|
|
||||||
| `src/app/sources.js`, `src/services/application.js` | Existing default/override API; current application construction supplies its own instances |
|
| `src/app/sources.js`, `src/services/application.js` | Existing default/override API; current application construction supplies its own instances |
|
||||||
| `server/providers/local.js` | Existing Node composition barrel and provider helper re-exports |
|
| `server/providers/local.js` | Existing Node composition barrel and provider helper re-exports |
|
||||||
|
|
||||||
|
|||||||
@@ -4662,7 +4662,7 @@ current route throughout enabling, enabled, disabling, and uncertain lifecycle
|
|||||||
renders. Both routes preserve Radio power, playback, station, filter, and volume
|
renders. Both routes preserve Radio power, playback, station, filter, and volume
|
||||||
state.
|
state.
|
||||||
|
|
||||||
Bundled datasets live in `src/data/local_data/` with per-folder provenance READMEs; they are lazily loaded via `src/data/localGeojson.js` (Vite `?url` assets) when toggled on.
|
Bundled datasets live in `src/data/local_data/` with per-folder provenance READMEs; they are lazily loaded via `src/data/localGeojsonCore.js` (Vite `?url` assets) when toggled on.
|
||||||
|
|
||||||
A bundled dataset that fails to load is a broken install, not a blip, so it is
|
A bundled dataset that fails to load is a broken install, not a blip, so it is
|
||||||
never swallowed: `localGeojson.js` guards `response.ok`, reports `error` +
|
never swallowed: `localGeojson.js` guards `response.ok`, reports `error` +
|
||||||
|
|||||||
@@ -53,9 +53,8 @@ Cesium installation shared with its viewer; it must not bundle separate copies.
|
|||||||
`gods-eye-view/infrastructure/geojson` exports the lower-level
|
`gods-eye-view/infrastructure/geojson` exports the lower-level
|
||||||
`createLocalGeoJsonLayer(options, services)` and existing infrastructure overlay
|
`createLocalGeoJsonLayer(options, services)` and existing infrastructure overlay
|
||||||
helpers. `gods-eye-view/infrastructure/lod` exports the existing LOD policy helpers.
|
helpers. `gods-eye-view/infrastructure/lod` exports the existing LOD policy helpers.
|
||||||
The standalone app's `src/data/localGeojson.js` keeps its original single-argument
|
Consumers use the package exports, which do not import standalone application
|
||||||
factory and supplies its existing functions. Other consumers should use the package
|
globals.
|
||||||
exports, which do not import standalone application globals.
|
|
||||||
|
|
||||||
Dataset files and source/license notices remain under `src/data/local_data/`.
|
Dataset files and source/license notices remain under `src/data/local_data/`.
|
||||||
Their input counts are 4,351 datacenter features and 704 dam features. Cesium may
|
Their input counts are 4,351 datacenter features and 704 dam features. Cesium may
|
||||||
|
|||||||
@@ -153,14 +153,6 @@ export function checkImportDirections(root) {
|
|||||||
file,
|
file,
|
||||||
'Actions must consume feed state without the manager facade',
|
'Actions must consume feed state without the manager facade',
|
||||||
);
|
);
|
||||||
if (
|
|
||||||
file === 'src/app/constructCatalog.js' &&
|
|
||||||
to === 'src/data/localGeojson.js'
|
|
||||||
)
|
|
||||||
report(
|
|
||||||
file,
|
|
||||||
'Catalog must use the services owner without compatibility layer construction',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -518,7 +518,6 @@
|
|||||||
"src/layers/bikeshare/selection.js",
|
"src/layers/bikeshare/selection.js",
|
||||||
"src/data/traffic.js",
|
"src/data/traffic.js",
|
||||||
"src/data/bikeshare.js",
|
"src/data/bikeshare.js",
|
||||||
"src/data/flowTiles.js",
|
|
||||||
"src/layers/traffic/source.test.mjs",
|
"src/layers/traffic/source.test.mjs",
|
||||||
"src/layers/bikeshare/source.test.mjs",
|
"src/layers/bikeshare/source.test.mjs",
|
||||||
"src/layers/bikeshare/proxyRoute.test.mjs",
|
"src/layers/bikeshare/proxyRoute.test.mjs",
|
||||||
@@ -651,7 +650,6 @@
|
|||||||
"src/agent/worldPacket.test.mjs",
|
"src/agent/worldPacket.test.mjs",
|
||||||
"src/voice/layerSummary.js",
|
"src/voice/layerSummary.js",
|
||||||
"build/application-html.js",
|
"build/application-html.js",
|
||||||
"src/app/sourceSlot.js",
|
|
||||||
"src/app/sources.js",
|
"src/app/sources.js",
|
||||||
"src/ui/composition.js",
|
"src/ui/composition.js",
|
||||||
"src/services/application.js",
|
"src/services/application.js",
|
||||||
|
|||||||
@@ -1163,7 +1163,6 @@
|
|||||||
"satellites-layer": {
|
"satellites-layer": {
|
||||||
"exports": ["./layers/satellites"],
|
"exports": ["./layers/satellites"],
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/data/issPass.js",
|
|
||||||
"src/data/satelliteClass.js",
|
"src/data/satelliteClass.js",
|
||||||
"src/data/satellitePass.js",
|
"src/data/satellitePass.js",
|
||||||
"src/sources/tle.js",
|
"src/sources/tle.js",
|
||||||
@@ -1853,14 +1852,12 @@
|
|||||||
"src/data/iconOrientation.js",
|
"src/data/iconOrientation.js",
|
||||||
"src/data/infrastructure.js",
|
"src/data/infrastructure.js",
|
||||||
"src/data/installationFeedback.js",
|
"src/data/installationFeedback.js",
|
||||||
"src/data/issPass.js",
|
|
||||||
"src/data/labelArbiter.js",
|
"src/data/labelArbiter.js",
|
||||||
"src/data/idSwitches.js",
|
"src/data/idSwitches.js",
|
||||||
"src/data/layerState.js",
|
"src/data/layerState.js",
|
||||||
"src/data/layerStateCoordinator.js",
|
"src/data/layerStateCoordinator.js",
|
||||||
"src/data/layerStateTokenReservations.json",
|
"src/data/layerStateTokenReservations.json",
|
||||||
"src/data/lifecycle.js",
|
"src/data/lifecycle.js",
|
||||||
"src/data/localGeojson.js",
|
|
||||||
"src/data/localGeojsonCore.js",
|
"src/data/localGeojsonCore.js",
|
||||||
"src/data/localGeojsonLod.js",
|
"src/data/localGeojsonLod.js",
|
||||||
"src/data/local_data/natural_earth/marine.json",
|
"src/data/local_data/natural_earth/marine.json",
|
||||||
@@ -2672,13 +2669,11 @@
|
|||||||
"src/data/iconOrientation.js",
|
"src/data/iconOrientation.js",
|
||||||
"src/data/infrastructure.js",
|
"src/data/infrastructure.js",
|
||||||
"src/data/installationFeedback.js",
|
"src/data/installationFeedback.js",
|
||||||
"src/data/issPass.js",
|
|
||||||
"src/data/satellitePass.js",
|
"src/data/satellitePass.js",
|
||||||
"src/data/labelArbiter.js",
|
"src/data/labelArbiter.js",
|
||||||
"src/data/idSwitches.js",
|
"src/data/idSwitches.js",
|
||||||
"src/data/layerState.js",
|
"src/data/layerState.js",
|
||||||
"src/data/layerStateTokenReservations.json",
|
"src/data/layerStateTokenReservations.json",
|
||||||
"src/data/localGeojson.js",
|
|
||||||
"src/data/localGeojsonCore.js",
|
"src/data/localGeojsonCore.js",
|
||||||
"src/data/localGeojsonLod.js",
|
"src/data/localGeojsonLod.js",
|
||||||
"src/data/local_data/natural_earth/marine.json",
|
"src/data/local_data/natural_earth/marine.json",
|
||||||
@@ -3663,7 +3658,6 @@
|
|||||||
"exports": ["./layers/transit/standalone"],
|
"exports": ["./layers/transit/standalone"],
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/cyberSonar.js",
|
"src/cyberSonar.js",
|
||||||
"src/app/sourceSlot.js",
|
|
||||||
"src/celestialRing.js",
|
"src/celestialRing.js",
|
||||||
"src/data/approxDistance.js",
|
"src/data/approxDistance.js",
|
||||||
"src/data/cameraSensitivity.js",
|
"src/data/cameraSensitivity.js",
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
// Compatibility entry; source contracts are owned by sources/.
|
|
||||||
export { createSourceSlot } from '../sources/sourceSlot.js';
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import { createSourceSlot } from './sourceSlot.js';
|
|
||||||
|
|
||||||
test('replacing a source rejects its late result and old cleanup cannot clear the new source', async () => {
|
|
||||||
let finish;
|
|
||||||
const slot = createSourceSlot({ read: async () => 'default' }, ['read']);
|
|
||||||
const old = slot.configure({ read: () => new Promise(resolve => { finish = resolve; }) });
|
|
||||||
const pending = slot.source.read();
|
|
||||||
const stop = slot.configure({ read: async () => 'replacement', attribution: { name: 'Replacement' } });
|
|
||||||
old();
|
|
||||||
finish('stale');
|
|
||||||
await assert.rejects(pending, { name: 'AbortError' });
|
|
||||||
assert.equal(await slot.source.read(), 'replacement');
|
|
||||||
assert.equal(slot.source.attribution.name, 'Replacement');
|
|
||||||
stop();
|
|
||||||
assert.throws(() => slot.source.read(), /not configured/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('an invalid replacement leaves the working source intact', async () => {
|
|
||||||
const slot = createSourceSlot({ read: async () => 42 }, ['read']);
|
|
||||||
assert.throws(() => slot.configure({}), TypeError);
|
|
||||||
assert.equal(await slot.source.read(), 42);
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFlowTileSource } from '../layers/traffic/flowSource.js';
|
|
||||||
export { tilesForBounds } from './tomtomTiles.js';
|
|
||||||
export { decodeFlowTile } from '../layers/traffic/flowDecode.js';
|
|
||||||
const source = createFlowTileSource();
|
|
||||||
export const { fetchFlowForBounds, getFlowSessionStats, resetFlowTileCache } =
|
|
||||||
source;
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
// src/data/flowTiles.test.mjs
|
|
||||||
import { test } from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import {
|
|
||||||
decodeFlowTile,
|
|
||||||
fetchFlowForBounds,
|
|
||||||
tilesForBounds,
|
|
||||||
getFlowSessionStats,
|
|
||||||
resetFlowTileCache,
|
|
||||||
} from './flowTiles.js';
|
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
||||||
// Real TomTom flow tile, downtown Austin z12 x935 y1686 (probed live 2026-07-16).
|
|
||||||
const FIXTURE = path.join(__dirname, 'fixtures', 'tomtom-flow-austin-12-935-1686.pbf');
|
|
||||||
const FIXTURE_TILE = { z: 12, x: 935, y: 1686 };
|
|
||||||
|
|
||||||
function loadFixture() {
|
|
||||||
return fs.readFileSync(FIXTURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── decodeFlowTile against the real fixture ─────────────────
|
|
||||||
|
|
||||||
test('fixture decode: more than 50 flow segments', () => {
|
|
||||||
const segments = decodeFlowTile(loadFixture(), FIXTURE_TILE.z, FIXTURE_TILE.x, FIXTURE_TILE.y);
|
|
||||||
assert.ok(segments.length > 50, `got ${segments.length}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fixture decode: every trafficLevel is within [0, 1]', () => {
|
|
||||||
const segments = decodeFlowTile(loadFixture(), FIXTURE_TILE.z, FIXTURE_TILE.x, FIXTURE_TILE.y);
|
|
||||||
for (const s of segments) {
|
|
||||||
assert.ok(Number.isFinite(s.trafficLevel), `non-finite level: ${s.trafficLevel}`);
|
|
||||||
assert.ok(s.trafficLevel >= 0 && s.trafficLevel <= 1, `level out of range: ${s.trafficLevel}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fixture decode: all coordinates land in downtown Austin', () => {
|
|
||||||
const segments = decodeFlowTile(loadFixture(), FIXTURE_TILE.z, FIXTURE_TILE.x, FIXTURE_TILE.y);
|
|
||||||
for (const s of segments) {
|
|
||||||
assert.ok(Array.isArray(s.coords) && s.coords.length >= 2, 'polyline too short');
|
|
||||||
for (const [lon, lat] of s.coords) {
|
|
||||||
assert.ok(lon >= -98.0 && lon <= -97.5, `lon out of Austin range: ${lon}`);
|
|
||||||
assert.ok(lat >= 30.0 && lat <= 30.5, `lat out of Austin range: ${lat}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fixture decode: congestion exists (at least one trafficLevel < 1)', () => {
|
|
||||||
const segments = decodeFlowTile(loadFixture(), FIXTURE_TILE.z, FIXTURE_TILE.x, FIXTURE_TILE.y);
|
|
||||||
assert.ok(segments.some((s) => s.trafficLevel < 1), 'no congested segment found');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fixture decode: segment shape is {coords, trafficLevel, roadType, closure}', () => {
|
|
||||||
const segments = decodeFlowTile(loadFixture(), FIXTURE_TILE.z, FIXTURE_TILE.x, FIXTURE_TILE.y);
|
|
||||||
for (const s of segments) {
|
|
||||||
assert.equal(typeof s.roadType, 'string');
|
|
||||||
assert.equal(typeof s.closure, 'boolean');
|
|
||||||
}
|
|
||||||
// The fixture carries real closures — closure decoding is exercised, not vacuous.
|
|
||||||
assert.ok(segments.some((s) => s.closure === true), 'expected at least one closure in fixture');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('decode of a non-MVT buffer returns [] (defensive)', () => {
|
|
||||||
assert.deepEqual(decodeFlowTile(Buffer.from('not a protobuf tile'), 12, 935, 1686), []);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── tilesForBounds (re-exported slippy math) ────────────────
|
|
||||||
|
|
||||||
test('tilesForBounds: 30.2672,-97.7431 @ z12 -> covers x935 y1686', () => {
|
|
||||||
const tiles = tilesForBounds({
|
|
||||||
south: 30.2672 - 0.001, north: 30.2672 + 0.001,
|
|
||||||
west: -97.7431 - 0.001, east: -97.7431 + 0.001,
|
|
||||||
}, 12);
|
|
||||||
assert.ok(
|
|
||||||
tiles.some((t) => t.z === 12 && t.x === 935 && t.y === 1686),
|
|
||||||
`fixture tile missing: ${JSON.stringify(tiles)}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── fetchFlowForBounds (stubbed fetch: cache + abort) ───────
|
|
||||||
|
|
||||||
/** Bounds fully inside the fixture tile. */
|
|
||||||
const FIXTURE_BOUNDS = { south: 30.24, north: 30.26, west: -97.76, east: -97.74 };
|
|
||||||
|
|
||||||
function stubFetch(impl) {
|
|
||||||
const original = globalThis.fetch;
|
|
||||||
globalThis.fetch = impl;
|
|
||||||
return () => { globalThis.fetch = original; };
|
|
||||||
}
|
|
||||||
|
|
||||||
test('fetchFlowForBounds: fetches covering tiles via /api/tomtom and decodes', async () => {
|
|
||||||
resetFlowTileCache();
|
|
||||||
const calls = [];
|
|
||||||
const restore = stubFetch(async (url) => {
|
|
||||||
calls.push(String(url));
|
|
||||||
return new Response(loadFixture(), {
|
|
||||||
status: 200,
|
|
||||||
headers: { 'Content-Type': 'application/x-protobuf' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const segments = await fetchFlowForBounds(FIXTURE_BOUNDS);
|
|
||||||
assert.equal(calls.length, 1);
|
|
||||||
assert.match(calls[0], /^\/api\/tomtom\/flow\/12\/935\/1686\.pbf$/);
|
|
||||||
assert.ok(segments.length > 50);
|
|
||||||
assert.ok(getFlowSessionStats().tilesFetched >= 1);
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchFlowForBounds: decode cache serves repeat calls within TTL (no refetch)', async () => {
|
|
||||||
resetFlowTileCache();
|
|
||||||
let calls = 0;
|
|
||||||
const restore = stubFetch(async () => {
|
|
||||||
calls += 1;
|
|
||||||
return new Response(loadFixture(), { status: 200 });
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const first = await fetchFlowForBounds(FIXTURE_BOUNDS);
|
|
||||||
const second = await fetchFlowForBounds(FIXTURE_BOUNDS);
|
|
||||||
assert.equal(calls, 1, 'second call must be served from the decode cache');
|
|
||||||
assert.equal(second.length, first.length);
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchFlowForBounds: aborted signal rejects (AbortSignal-aware)', async () => {
|
|
||||||
resetFlowTileCache();
|
|
||||||
const restore = stubFetch(async (url, opts) => {
|
|
||||||
// Mimic real fetch abort semantics.
|
|
||||||
if (opts?.signal?.aborted) {
|
|
||||||
const err = new Error('aborted');
|
|
||||||
err.name = 'AbortError';
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
return new Response(loadFixture(), { status: 200 });
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const controller = new AbortController();
|
|
||||||
controller.abort();
|
|
||||||
await assert.rejects(
|
|
||||||
fetchFlowForBounds(FIXTURE_BOUNDS, { signal: controller.signal }),
|
|
||||||
(err) => err.name === 'AbortError'
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchFlowForBounds: non-OK tile responses reject when nothing succeeds', async () => {
|
|
||||||
resetFlowTileCache();
|
|
||||||
const restore = stubFetch(async () => new Response(JSON.stringify({ error: 'no_key' }), { status: 503 }));
|
|
||||||
try {
|
|
||||||
await assert.rejects(fetchFlowForBounds(FIXTURE_BOUNDS));
|
|
||||||
} finally {
|
|
||||||
restore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// src/data/issPass.js
|
|
||||||
/**
|
|
||||||
* Next-ISS-pass prediction. Kept as a thin wrapper over satellitePass.js so the
|
|
||||||
* original findNextIssPass signature and defaults still work.
|
|
||||||
*/
|
|
||||||
import { findNextSatellitePass, lookAnglesAt } from './satellitePass.js';
|
|
||||||
|
|
||||||
export { lookAnglesAt };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Predict the next ISS pass over an observer location.
|
|
||||||
* @param {Object} options
|
|
||||||
* @param {Object} options.satrec SGP4 satellite record
|
|
||||||
* @param {number} options.latDeg Observer latitude in degrees [-90, 90]
|
|
||||||
* @param {number} options.lonDeg Observer longitude in degrees [-180, 180]
|
|
||||||
* @param {number} options.fromMs UTC start time in milliseconds
|
|
||||||
* @param {number} [options.minElevDeg=10] Elevation that defines rise and set, in degrees
|
|
||||||
* @param {number} [options.horizonHours=24] Maximum search window in hours
|
|
||||||
* @param {number} [options.coarseStepSec=30] Coarse search step in seconds
|
|
||||||
* @param {number} [options.fineStepSec=5] Fine transit step in seconds for peak tracking and visibility
|
|
||||||
* @param {boolean} [options.requireVisible=false] Require a naked-eye-visible pass
|
|
||||||
* @returns {{ riseMs: number, setMs: number, maxElevDeg: number, maxElevMs: number, riseAzDeg: number, visible: boolean, sunlit: boolean, observerDark: boolean } | null}
|
|
||||||
*/
|
|
||||||
export function findNextIssPass({
|
|
||||||
satrec,
|
|
||||||
latDeg,
|
|
||||||
lonDeg,
|
|
||||||
fromMs,
|
|
||||||
minElevDeg = 10,
|
|
||||||
horizonHours = 24,
|
|
||||||
coarseStepSec = 30,
|
|
||||||
fineStepSec = 5,
|
|
||||||
requireVisible = false,
|
|
||||||
}) {
|
|
||||||
return findNextSatellitePass({
|
|
||||||
satrec,
|
|
||||||
latDeg,
|
|
||||||
lonDeg,
|
|
||||||
fromMs,
|
|
||||||
minElevDeg,
|
|
||||||
horizonHours,
|
|
||||||
coarseStepSec,
|
|
||||||
fineStepSec,
|
|
||||||
requireVisible,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// src/data/issPass.test.mjs
|
|
||||||
import { test } from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import { twoline2satrec } from 'satellite.js';
|
|
||||||
import { findNextIssPass, lookAnglesAt } from './issPass.js';
|
|
||||||
import { findNextSatellitePass } from './satellitePass.js';
|
|
||||||
|
|
||||||
// Canonical archived ISS TLE (valid checksums; epoch 2008-09-20 ~12:25 UTC).
|
|
||||||
const L1 = '1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927';
|
|
||||||
const L2 = '2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537';
|
|
||||||
const AUSTIN = { latDeg: 30.2672, lonDeg: -97.7431 };
|
|
||||||
const FROM_MS = Date.UTC(2008, 8, 20, 12, 30, 0); // just after the TLE epoch
|
|
||||||
|
|
||||||
test('finds a structurally-consistent ISS pass within 24h of the TLE epoch', () => {
|
|
||||||
const satrec = twoline2satrec(L1, L2);
|
|
||||||
const pass = findNextIssPass({ satrec, ...AUSTIN, fromMs: FROM_MS, minElevDeg: 10 });
|
|
||||||
assert.ok(pass, 'expected a pass within 24h at 30°N for a 51.6° inclination orbit');
|
|
||||||
assert.ok(pass.riseMs > FROM_MS);
|
|
||||||
assert.ok(pass.riseMs < pass.maxElevMs && pass.maxElevMs < pass.setMs);
|
|
||||||
assert.ok(pass.maxElevDeg >= 10);
|
|
||||||
assert.ok(pass.riseAzDeg >= 0 && pass.riseAzDeg < 360);
|
|
||||||
|
|
||||||
// Independent re-check: elevation at the reported peak really is the max-ish
|
|
||||||
// and ≥ threshold; one minute before rise it is below threshold.
|
|
||||||
const peak = lookAnglesAt(satrec, pass.maxElevMs, AUSTIN.latDeg, AUSTIN.lonDeg);
|
|
||||||
assert.ok(peak && Math.abs(peak.elevDeg - pass.maxElevDeg) < 1.5);
|
|
||||||
const before = lookAnglesAt(satrec, pass.riseMs - 60_000, AUSTIN.latDeg, AUSTIN.lonDeg);
|
|
||||||
assert.ok(before && before.elevDeg < 10);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('forwards its original 30 s coarse step and fineStepSec to the pass engine', () => {
|
|
||||||
const satrec = twoline2satrec(L1, L2);
|
|
||||||
const opts = { satrec, ...AUSTIN, fromMs: FROM_MS, minElevDeg: 10, fineStepSec: 30 };
|
|
||||||
assert.deepEqual(findNextIssPass(opts), findNextSatellitePass({ ...opts, coarseStepSec: 30 }));
|
|
||||||
assert.notDeepEqual(findNextIssPass(opts), findNextIssPass({ ...opts, fineStepSec: 5 }));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns null when no pass clears an absurd threshold', () => {
|
|
||||||
const satrec = twoline2satrec(L1, L2);
|
|
||||||
const pass = findNextIssPass({ satrec, ...AUSTIN, fromMs: FROM_MS, minElevDeg: 89.9, horizonHours: 2 });
|
|
||||||
assert.equal(pass, null);
|
|
||||||
});
|
|
||||||
@@ -2,7 +2,18 @@
|
|||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { createLocalGeoJsonLayer, mapAnalystRecord } from './localGeojson.js';
|
import { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
||||||
|
import {
|
||||||
|
createLocalGeoJsonLayer as createLayer,
|
||||||
|
mapAnalystRecord,
|
||||||
|
} from './localGeojsonCore.js';
|
||||||
|
|
||||||
|
/** The catalog's services, with the overlay host each test supplies. */
|
||||||
|
const createLocalGeoJsonLayer = (options) =>
|
||||||
|
createLayer(options, {
|
||||||
|
...localGeoJsonServices,
|
||||||
|
overlayHost: options.overlayHost,
|
||||||
|
});
|
||||||
|
|
||||||
const DC_RAW = {
|
const DC_RAW = {
|
||||||
id: '1176042553',
|
id: '1176042553',
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
|
||||||
import {
|
|
||||||
createLocalGeoJsonLayer as createLayer,
|
|
||||||
createLocalInfrastructureOverlayPublisher as createPublisher,
|
|
||||||
} from './localGeojsonCore.js';
|
|
||||||
|
|
||||||
export * from './localGeojsonCore.js';
|
|
||||||
|
|
||||||
/** Create a layer using the standalone app's operations; retain overlay overrides. */
|
|
||||||
export function createLocalGeoJsonLayer(options) {
|
|
||||||
return createLayer(options, {
|
|
||||||
...localGeoJsonServices,
|
|
||||||
overlayHost:
|
|
||||||
options.overlayHost === undefined
|
|
||||||
? localGeoJsonServices.overlayHost
|
|
||||||
: options.overlayHost,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create an overlay publisher using the standalone app's host by default. */
|
|
||||||
export function createLocalInfrastructureOverlayPublisher(options) {
|
|
||||||
return createPublisher({
|
|
||||||
...options,
|
|
||||||
host:
|
|
||||||
options.host === undefined
|
|
||||||
? localGeoJsonServices.overlayHost
|
|
||||||
: options.host,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -6,13 +6,14 @@ import {
|
|||||||
GROUND_SAMPLE_MAX_ARMED_RETRIES,
|
GROUND_SAMPLE_MAX_ARMED_RETRIES,
|
||||||
LOCAL_OVERLAY_COHORT_LIMIT,
|
LOCAL_OVERLAY_COHORT_LIMIT,
|
||||||
LOCAL_STEM_TIP_EPSILON_M,
|
LOCAL_STEM_TIP_EPSILON_M,
|
||||||
createLocalGeoJsonLayer,
|
createLocalGeoJsonLayer as createLayer,
|
||||||
createLocalInfrastructureOverlayEntry,
|
createLocalInfrastructureOverlayEntry,
|
||||||
createLocalInfrastructureOverlayPublisher,
|
createLocalInfrastructureOverlayPublisher,
|
||||||
localDatasetError,
|
localDatasetError,
|
||||||
localInfrastructureOverlayCopy,
|
localInfrastructureOverlayCopy,
|
||||||
selectLocalInfrastructureOverlayCohort,
|
selectLocalInfrastructureOverlayCohort,
|
||||||
} from './localGeojson.js';
|
} from './localGeojsonCore.js';
|
||||||
|
import { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
||||||
import { layerFeedState } from './manager.js';
|
import { layerFeedState } from './manager.js';
|
||||||
import { INFRA_LOD_ACTIVE_MIN, INFRA_LOD_ACTIVE_MAX } from './localGeojsonLod.js';
|
import { INFRA_LOD_ACTIVE_MIN, INFRA_LOD_ACTIVE_MAX } from './localGeojsonLod.js';
|
||||||
import {
|
import {
|
||||||
@@ -21,6 +22,13 @@ import {
|
|||||||
_resetRenderGovernorForTest,
|
_resetRenderGovernorForTest,
|
||||||
} from '../renderGovernor.js';
|
} from '../renderGovernor.js';
|
||||||
|
|
||||||
|
/** The catalog's services, with the overlay host each test supplies. */
|
||||||
|
const createLocalGeoJsonLayer = (options) =>
|
||||||
|
createLayer(options, {
|
||||||
|
...localGeoJsonServices,
|
||||||
|
overlayHost: options.overlayHost ?? localGeoJsonServices.overlayHost,
|
||||||
|
});
|
||||||
|
|
||||||
class MockLayerEvent {
|
class MockLayerEvent {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.listeners = new Set();
|
this.listeners = new Set();
|
||||||
@@ -1337,14 +1345,3 @@ test('globe-LOD re-selects during continuous motion, without ever seeing a moveE
|
|||||||
assert.deepEqual(env.layer.getLodDiagnostics(), parked, 'a parked camera never re-selects');
|
assert.deepEqual(env.layer.getLodDiagnostics(), parked, 'a parked camera never re-selects');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('standalone publisher retains its default host for an undefined override', () => {
|
|
||||||
const publisher = createLocalInfrastructureOverlayPublisher({
|
|
||||||
sourceId: 'local-default-host-test', host: undefined,
|
|
||||||
});
|
|
||||||
assert.doesNotThrow(() => {
|
|
||||||
publisher.show();
|
|
||||||
publisher.hide();
|
|
||||||
publisher.destroy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { createLocalGeoJsonLayer } from './localGeojson.js';
|
import { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
||||||
|
import { createLocalGeoJsonLayer as createLayer } from './localGeojsonCore.js';
|
||||||
import { getContextStore } from './contextStore.js';
|
import { getContextStore } from './contextStore.js';
|
||||||
|
|
||||||
|
/** The catalog's services, with the overlay host each test supplies. */
|
||||||
|
const createLocalGeoJsonLayer = (options) =>
|
||||||
|
createLayer(options, {
|
||||||
|
...localGeoJsonServices,
|
||||||
|
overlayHost: options.overlayHost,
|
||||||
|
});
|
||||||
|
|
||||||
const dataset = JSON.stringify({
|
const dataset = JSON.stringify({
|
||||||
type: 'Feature', id: 'dam', properties: { name: 'Test dam' },
|
type: 'Feature', id: 'dam', properties: { name: 'Test dam' },
|
||||||
geometry: { type: 'Polygon', coordinates: [[[0, 0], [0.01, 0], [0, 0.01], [0, 0]]] },
|
geometry: { type: 'Polygon', coordinates: [[[0, 0], [0.01, 0], [0, 0.01], [0, 0]]] },
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Pure selection policy for the bundled local-infrastructure layers
|
* Pure selection policy for the bundled local-infrastructure layers
|
||||||
* (`local-datacenters`, `local-dams`, and any future `createLocalGeoJsonLayer`
|
* (`local-datacenters`, `local-dams`, and any future `createLocalGeoJsonLayer`
|
||||||
* dataset). Cesium-specific projection, occlusion, and stem geometry stay in
|
* dataset). Cesium-specific projection, occlusion, and stem geometry stay in
|
||||||
* `localGeojson.js`; this module turns the resulting in-view records into a
|
* `localGeojsonCore.js`; this module turns the resulting in-view records into a
|
||||||
* bounded "active" set — the only records that layer should spend per-frame
|
* bounded "active" set — the only records that layer should spend per-frame
|
||||||
* stem-geometry and ground-sample work on.
|
* stem-geometry and ground-sample work on.
|
||||||
*
|
*
|
||||||
@@ -80,7 +80,7 @@ export const INFRA_LOD_INCUMBENT_BONUS = 250;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Distance at or beyond which the proximity penalty is fully applied (m).
|
* Distance at or beyond which the proximity penalty is fully applied (m).
|
||||||
* Matches LOCAL_OVERLAY_MAX_DISTANCE_M's order of magnitude in localGeojson.js.
|
* Matches LOCAL_OVERLAY_MAX_DISTANCE_M's order of magnitude in infrastructureOverlayEntry.js.
|
||||||
*/
|
*/
|
||||||
export const INFRA_LOD_FAR_M = 12_000_000;
|
export const INFRA_LOD_FAR_M = 12_000_000;
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export const INFRA_LOD_MAX_DISTANCE_PENALTY = 200;
|
|||||||
* (callers only compare it), but every input is normalized so the output is
|
* (callers only compare it), but every input is normalized so the output is
|
||||||
* always a finite number.
|
* always a finite number.
|
||||||
*
|
*
|
||||||
* @param {number} priority Existing label-priority score (localGeojson.js).
|
* @param {number} priority Existing label-priority score (localGeojsonCore.js).
|
||||||
* @param {number} distanceM Camera→feature distance in metres.
|
* @param {number} distanceM Camera→feature distance in metres.
|
||||||
* @param {boolean} isIncumbent Record currently holds an active stem.
|
* @param {boolean} isIncumbent Record currently holds an active stem.
|
||||||
* @param {object} [options]
|
* @param {object} [options]
|
||||||
@@ -297,7 +297,7 @@ export function applyInfraEvictionGrace({
|
|||||||
* ------------------------------------------------------------------ */
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Probe window for the motion fallback. `localGeojson.js` marks its selection
|
* Probe window for the motion fallback. `localGeojsonCore.js` marks its selection
|
||||||
* dirty on `moveEnd`, which covers drags, one-shot `setView`, and voice
|
* dirty on `moveEnd`, which covers drags, one-shot `setView`, and voice
|
||||||
* `flyTo` — but a tracked-entity follow camera, a Cockpit view, a route
|
* `flyTo` — but a tracked-entity follow camera, a Cockpit view, a route
|
||||||
* flight, or a continuous orbit never emits one. Without a fallback the
|
* flight, or a continuous orbit never emits one. Without a fallback the
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @file Pure tile math + budget accounting for the TomTom traffic-flow proxy.
|
* @file Pure tile math + budget accounting for the TomTom traffic-flow proxy.
|
||||||
*
|
*
|
||||||
* Shared by the `/api/tomtom` vite plugin (server-side: coordinate validation,
|
* Shared by the `/api/tomtom` vite plugin (server-side: coordinate validation,
|
||||||
* daily budget governor) and `src/data/flowTiles.js` (client-side: which tiles
|
* daily budget governor) and `src/layers/traffic/flowSource.js` (client-side: which tiles
|
||||||
* cover the current traffic fetch bounds). Zero dependencies, Cesium-free, so
|
* cover the current traffic fetch bounds). Zero dependencies, Cesium-free, so
|
||||||
* both sides can unit-test against it with node:test.
|
* both sides can unit-test against it with node:test.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { VectorTile } from '@mapbox/vector-tile';
|
|||||||
* tree-shakeable; decoding happens client-side so the proxy stays a dumb
|
* tree-shakeable; decoding happens client-side so the proxy stays a dumb
|
||||||
* binary cache.
|
* binary cache.
|
||||||
*
|
*
|
||||||
* @module data/flowTiles
|
* @module layers/traffic/flowDecode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const FLOW_LAYER_NAME = 'Traffic flow';
|
const FLOW_LAYER_NAME = 'Traffic flow';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import test from 'node:test';
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { LOCAL_OVERLAY_COHORT_LIMIT } from '../data/localGeojson.js';
|
import { LOCAL_OVERLAY_COHORT_LIMIT } from '../data/localGeojsonCore.js';
|
||||||
import { FIRMS_AMBIENT_COHORT_LIMIT } from '../data/firmsLabels.js';
|
import { FIRMS_AMBIENT_COHORT_LIMIT } from '../data/firmsLabels.js';
|
||||||
import { vesselOverlayCohortLimit } from '../data/vesselLabels.js';
|
import { vesselOverlayCohortLimit } from '../data/vesselLabels.js';
|
||||||
import { CCTV_AMBIENT_CARD_MAX } from '../data/cctvLod.js';
|
import { CCTV_AMBIENT_CARD_MAX } from '../data/cctvLod.js';
|
||||||
|
|||||||
Reference in new issue
Block a user