import { createPerimetersCesiumRenderer } from '../../layers/perimeters/render/cesium.js'; import { createGnssCesiumRenderer } from '../../layers/gnss/render/cesium.js'; import { createAirQualityCesiumRenderer } from '../../layers/airQuality/render/cesium.js'; import { createFirmsCesiumRenderer } from '../../layers/firms/render/cesium.js'; import { createEarthquakesCesiumRenderer } from '../../layers/earthquakes/render/cesium.js'; import { createLaunchesCesiumRenderer } from '../../layers/launches/render/cesium.js'; // Which engine draws which layer: the layer renderer registry (plan, "layer // renderer interface") and the layer availability API built on it. // // A renderer factory `(engine, options) => renderer` draws one layer on one // engine kind (`render/cesium.js`, `render/navara.js` of a layer). Cesium // layers still draw themselves until their render/cesium.js is extracted // (the *a tasks): they are registered as CESIUM_BUILTIN. Cesium entries are // registered here, statically (main chunk); Navara factories are registered // by the Navara mode after its import() (src/app/navaraMode.js). // // ## Layer availability (stable API, used by phase 4's layer list) // // import { createLayerAvailability } from './app/layers/renderers.js'; // const availability = createLayerAvailability(engine.kind); // availability.engine // 'cesium' | 'navara' // availability.isAvailable(id) // can this engine show layer `id`? // availability.available() // [{ id, name }] in catalog order // availability.unavailable() // [{ id, name }] the catalog leaves out // // The object is frozen; available() and unavailable() return fresh arrays. // It reads the registry when called, so a Navara factory registered later // (after the Navara mode loads) shows up. `name` is the layer's display name // (src/app/layers/directory.js). Phase 4 injects it as its `availability` // provider (`{ engine, unavailable() }` is the part it reads). import { APPLICATION_LAYER_DIRECTORY } from './directory.js'; import { createFlightsCesiumRenderer } from '../../layers/flights/render/cesium.js'; import { createMilitaryCesiumRenderer } from '../../layers/military/render/cesium.js'; import { createLocalAdsbCesiumRenderer } from '../../layers/localAdsb/render/cesium.js'; import { createSatellitesCesiumRenderer } from '../../layers/satellites/render/cesium.js'; import { createVesselsCesiumRenderer } from '../../layers/vessels/render/cesium.js'; const ENGINE_KINDS = new Set(['cesium', 'navara']); /** The layer draws itself on Cesium (no separate renderer yet). */ export const CESIUM_BUILTIN = Object.freeze({ builtin: 'cesium' }); const isFactory = (value) => typeof value === 'function' || value === CESIUM_BUILTIN; /** A registry of layer renderers per engine kind. */ export function createRendererRegistry() { const byEngine = new Map(); const entries = (engineKind) => byEngine.get(engineKind) ?? new Map(); return { /** Adds `{ [layerId]: factory }` for an engine kind; one per layer. */ register(engineKind, factories) { if (typeof engineKind !== 'string' || !engineKind) throw new TypeError('registerLayerRenderers needs an engine kind'); let map = byEngine.get(engineKind); if (!map) byEngine.set(engineKind, (map = new Map())); for (const [layerId, factory] of Object.entries(factories ?? {})) { if (!isFactory(factory)) throw new TypeError( `Layer renderer for ${layerId} on ${engineKind} is not a factory`, ); if (map.has(layerId)) throw new Error( `Layer ${layerId} already has a renderer on ${engineKind}`, ); map.set(layerId, factory); } }, /** * The renderer of a layer on `engine` (factory(engine, options)), or null * when the engine has none or the layer still draws itself (Cesium). */ rendererFor(layerId, engine, options = {}) { const factory = entries(engine?.kind).get(layerId); return typeof factory === 'function' ? factory(engine, options) : null; }, layerAvailableOn: (layerId, engineKind) => entries(engineKind).has(layerId), availableIds: (engineKind) => [...entries(engineKind).keys()], }; } const defaultRegistry = createRendererRegistry(); /** Registers layer renderers on the application's registry. */ export const registerLayerRenderers = (engineKind, factories) => defaultRegistry.register(engineKind, factories); /** The application registry's renderer of a layer on an engine, or null. */ export const rendererFor = (layerId, engine, options) => defaultRegistry.rendererFor(layerId, engine, options); /** Whether the application can show a layer on an engine kind. */ export const layerAvailableOn = (layerId, engineKind) => defaultRegistry.layerAvailableOn(layerId, engineKind); /** Cesium renderers extracted from their layers (render/cesium.js). */ export const CESIUM_LAYER_RENDERERS = Object.freeze({ earthquakes: createEarthquakesCesiumRenderer, 'air-quality': (_engine, options) => createAirQualityCesiumRenderer(options), 'gnss-integrity': (_engine, options) => createGnssCesiumRenderer(options), 'fire-perimeters': (_engine, options) => createPerimetersCesiumRenderer(options), 'local-firms': createFirmsCesiumRenderer, 'rocket-launches': createLaunchesCesiumRenderer, satellites: createSatellitesCesiumRenderer, flights: createFlightsCesiumRenderer, military: createMilitaryCesiumRenderer, 'local-adsb': createLocalAdsbCesiumRenderer, 'ais-live-vessels': createVesselsCesiumRenderer, }); // The other application layers still draw themselves on Cesium. registerLayerRenderers( 'cesium', Object.fromEntries( APPLICATION_LAYER_DIRECTORY.map(({ id }) => [ id, CESIUM_LAYER_RENDERERS[id] ?? CESIUM_BUILTIN, ]), ), ); /** * @typedef {object} LayerAvailability * @property {'cesium' | 'navara'} engine * @property {(layerId: string) => boolean} isAvailable * @property {() => { id: string, name: string }[]} available * @property {() => { id: string, name: string }[]} unavailable */ /** * The layers an engine kind can show (stable API; see the module comment). * @param {'cesium' | 'navara'} engineKind * @param {{ registry?: object, directory?: { id: string, name: string }[] }} [options] * Test seams: the registry and the layer directory. * @returns {LayerAvailability} */ export function createLayerAvailability( engineKind, { registry = defaultRegistry, directory = APPLICATION_LAYER_DIRECTORY } = {}, ) { if (!ENGINE_KINDS.has(engineKind)) throw new TypeError(`Unknown engine kind for layers: ${engineKind}`); const isAvailable = (layerId) => registry.layerAvailableOn(layerId, engineKind); const rows = (wanted) => directory .filter(({ id }) => isAvailable(id) === wanted) .map(({ id, name }) => ({ id, name })); return Object.freeze({ engine: engineKind, isAvailable, available: () => rows(true), unavailable: () => rows(false), }); }