mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
refactor: export infrastructure layers with explicit application callbacks
This commit is contained in:
1 parent
e465197cee
commit
142bf583f8
10 files changed
+1338
-1115
No files matched your search
@@ -0,0 +1,38 @@
|
||||
import { createLocalGeoJsonLayer } from './localGeojsonCore.js';
|
||||
|
||||
// Resolved by Vite in builds and relative to this module in other consumers.
|
||||
const datacentersUrl = new URL('./local_data/datacenters/datacenters.geojsonl', import.meta.url).href;
|
||||
const damsUrl = new URL('./local_data/dams/dams.geojsonl', import.meta.url).href;
|
||||
|
||||
/**
|
||||
* Create fresh datacenter and dam layers without starting or loading them.
|
||||
* @param {object} services Caller-owned context, overlay and render operations.
|
||||
* @returns {object[]} Datacenters then dams, with stable standalone identities.
|
||||
*/
|
||||
export function createInfrastructureLayers(services) {
|
||||
const datacenters = createLocalGeoJsonLayer({
|
||||
id: 'local-datacenters',
|
||||
url: datacentersUrl,
|
||||
name: 'Datacenters',
|
||||
color: '#00ffff', // Cyan
|
||||
icon: '▣',
|
||||
source: 'Local',
|
||||
labels: true,
|
||||
labelMax: 700,
|
||||
labelGridPx: 138,
|
||||
}, services);
|
||||
|
||||
const dams = createLocalGeoJsonLayer({
|
||||
id: 'local-dams',
|
||||
url: damsUrl,
|
||||
name: 'Dams',
|
||||
color: '#0088ff', // Blue
|
||||
icon: '▰',
|
||||
source: 'USACE',
|
||||
labels: true,
|
||||
labelMax: 900,
|
||||
labelGridPx: 132,
|
||||
}, services);
|
||||
|
||||
return [datacenters, dams];
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import * as Cesium from 'cesium';
|
||||
import { createInfrastructureLayers } from 'gods-eye-view/infrastructure';
|
||||
import { createLocalGeoJsonLayer } from 'gods-eye-view/infrastructure/geojson';
|
||||
|
||||
function services() {
|
||||
const records = new Map();
|
||||
let selection;
|
||||
return {
|
||||
records,
|
||||
overlayHost: { setEntries() {}, setVisible() {}, clearSource() {} },
|
||||
registerEntityContext(entity, metadata) { records.set(metadata.id, { entity, ...metadata }); },
|
||||
selectEntityContext(entity) { selection = entity; },
|
||||
clearSelectedEntityContextForLayer() { selection = undefined; },
|
||||
removeEntityContextsForLayer(id) {
|
||||
for (const [key, record] of records) if (record.layerId === id) records.delete(key);
|
||||
},
|
||||
governorRequestRender() {},
|
||||
selection: () => selection,
|
||||
};
|
||||
}
|
||||
|
||||
test('package exports import without an application, DOM, fetch, or timers', () => {
|
||||
const result = spawnSync(process.execPath, ['--input-type=module', '-e', `
|
||||
globalThis.fetch = () => { throw new Error('unexpected fetch'); };
|
||||
globalThis.setTimeout = () => { throw new Error('unexpected timer'); };
|
||||
for (const key of ['window', 'document']) {
|
||||
delete globalThis[key];
|
||||
}
|
||||
await import('gods-eye-view/infrastructure');
|
||||
await import('gods-eye-view/infrastructure/geojson');
|
||||
await import('gods-eye-view/infrastructure/lod');
|
||||
`], { cwd: new URL('../..', import.meta.url), encoding: 'utf8' });
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
});
|
||||
|
||||
test('infrastructure factory preserves identity and creates independent state without loading', t => {
|
||||
t.mock.method(globalThis, 'fetch', () => { throw new Error('factory must not fetch'); });
|
||||
const first = createInfrastructureLayers(services());
|
||||
const second = createInfrastructureLayers(services());
|
||||
assert.deepEqual(first.map(({ id, name, source }) => ({ id, name, source })), [
|
||||
{ id: 'local-datacenters', name: 'Datacenters', source: 'Local' },
|
||||
{ id: 'local-dams', name: 'Dams', source: 'USACE' },
|
||||
]);
|
||||
first.forEach((layer, index) => {
|
||||
assert.notEqual(layer, second[index]);
|
||||
layer.destroy();
|
||||
assert.deepEqual(second[index].getStats(), { count: 0, lastUpdate: null, error: null });
|
||||
second[index].destroy();
|
||||
});
|
||||
});
|
||||
|
||||
test('dataset URLs still name the complete bundled sources', () => {
|
||||
for (const [file, count] of [['datacenters', 4351], ['dams', 704]]) {
|
||||
const lines = readFileSync(new URL(`./local_data/${file}/${file}.geojsonl`, import.meta.url), 'utf8')
|
||||
.split('\n').filter(line => line.trim());
|
||||
assert.equal(lines.length, count);
|
||||
}
|
||||
});
|
||||
|
||||
test('two viewers use their supplied contexts and dispose independently', async t => {
|
||||
t.mock.method(globalThis, 'fetch', async () => ({ ok: true, text: async () => JSON.stringify({
|
||||
type: 'Feature', id: 'same-id', properties: { name: 'Dam' },
|
||||
geometry: { type: 'Polygon', coordinates: [[[0, 0], [0.01, 0], [0, 0.01], [0, 0]]] },
|
||||
}) }));
|
||||
const hosts = [services(), services()];
|
||||
const instances = hosts.map(host => {
|
||||
const sources = new Cesium.DataSourceCollection();
|
||||
let click;
|
||||
const layer = createLocalGeoJsonLayer({
|
||||
id: 'local-dams', name: 'Dams', color: '#0088ff', url: '/dams.geojsonl',
|
||||
screenSpaceEventHandlerFactory: () => ({ setInputAction(fn) { click = fn; }, destroy() {} }),
|
||||
}, host);
|
||||
const viewer = {
|
||||
dataSources: sources,
|
||||
scene: {
|
||||
canvas: {}, preRender: new Cesium.Event(), requestRender() {},
|
||||
screenSpaceCameraController: { enableInputs: true },
|
||||
pick: () => ({ id: sources.get(0).entities.values[0] }),
|
||||
},
|
||||
camera: { moveEnd: new Cesium.Event(), flyTo() {} },
|
||||
};
|
||||
return { layer, viewer, click: () => click({ position: {} }) };
|
||||
});
|
||||
t.after(() => instances.forEach(({ layer, viewer }) => layer.destroy(viewer)));
|
||||
await Promise.all(instances.map(({ layer, viewer }) => layer.enable(viewer)));
|
||||
assert.equal(hosts[0].records.size, 1);
|
||||
assert.equal(hosts[1].records.size, 1);
|
||||
instances[0].click();
|
||||
assert.ok(hosts[0].selection());
|
||||
assert.equal(hosts[1].selection(), undefined);
|
||||
instances[0].layer.destroy(instances[0].viewer);
|
||||
assert.equal(hosts[0].records.size, 0);
|
||||
assert.equal(hosts[1].records.size, 1);
|
||||
assert.equal(instances[1].viewer.dataSources.length, 1);
|
||||
});
|
||||
+24
-1081
File diff suppressed because it is too large.
Load diff
@@ -489,7 +489,7 @@ for (const [heightM, minStemM, maxStemM] of [[500, 45, 90], [10000, 1100, 1400]]
|
||||
}
|
||||
|
||||
test('local infrastructure creates no native labels or per-frame geometry callbacks', () => {
|
||||
const source = readFileSync(new URL('./localGeojson.js', import.meta.url), 'utf8');
|
||||
const source = readFileSync(new URL('./localGeojsonCore.js', import.meta.url), 'utf8');
|
||||
assert.doesNotMatch(source, /new Cesium\.LabelGraphics/);
|
||||
assert.doesNotMatch(source, /new Cesium\.CallbackProperty/);
|
||||
assert.match(source, /feature\.position = tip/);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+3
-32
@@ -1,38 +1,9 @@
|
||||
import { createLocalGeoJsonLayer } from './localGeojson.js';
|
||||
import { localGeoJsonServices } from './localGeojson.js';
|
||||
import { createInfrastructureLayers } from './infrastructure.js';
|
||||
import { createFirmsHeatmapLayer } from './firmsHeatmap.js';
|
||||
import submarineCablesLayer from './telegeographySubmarineCables.js';
|
||||
|
||||
// Use Vite's ?url import to properly resolve these assets in dev and build
|
||||
import datacentersUrl from './local_data/datacenters/datacenters.geojsonl?url';
|
||||
import damsUrl from './local_data/dams/dams.geojsonl?url';
|
||||
|
||||
/**
|
||||
* Registry of local GeoJSON datasets.
|
||||
* These are lazily loaded natively into Cesium when enabled.
|
||||
*/
|
||||
const datacenters = createLocalGeoJsonLayer({
|
||||
id: 'local-datacenters',
|
||||
url: datacentersUrl,
|
||||
name: 'Datacenters',
|
||||
color: '#00ffff', // Cyan
|
||||
icon: '▣',
|
||||
source: 'Local',
|
||||
labels: true,
|
||||
labelMax: 700,
|
||||
labelGridPx: 138,
|
||||
});
|
||||
|
||||
const dams = createLocalGeoJsonLayer({
|
||||
id: 'local-dams',
|
||||
url: damsUrl,
|
||||
name: 'Dams',
|
||||
color: '#0088ff', // Blue
|
||||
icon: '▰',
|
||||
source: 'USACE',
|
||||
labels: true,
|
||||
labelMax: 900,
|
||||
labelGridPx: 132,
|
||||
});
|
||||
const [datacenters, dams] = createInfrastructureLayers(localGeoJsonServices);
|
||||
|
||||
// Live NASA FIRMS fires (VIIRS ×3 NRT via the /api/firms proxy). The id keeps
|
||||
// the historical `local-` prefix for persistence + voice-tool-enum compat,
|
||||
|
||||
Reference in new issue
Block a user