GODSIGH/js/layers/infra.js
jing 6c7b945848 phases 2-5: satellite, aircraft, ship, infra & event layers
- satellites: Celestrak TLE -> SGP4 -> time-dynamic entities w/ glowing orbit
  paths; per-source cap + reordering so COSMOS/ISS aren't starved by the 72-sat
  Gaofen fleet (now ~97 sats, diverse); chunked propagation, orbital InfoBox
- aircraft: global OpenSky ADS-B -> BillboardCollection primitive (6444 planes
  verified), altitude-banded, quota/scrub/visibility-gated polling, click overlay
- ships: [DEMO] fleet through Hormuz incl. toll-route carrier, dark-vessel AIS
  gap, Fujairah idle cluster; optional aisstream.io live path
- infra: choke-point rings, Petroline + Habshan-Fujairah pipelines, 9 facilities
- events: [DEMO] time-anchored pulsing markers that appear as the slider crosses
- authored + adversarially hardened via parallel agent workflow

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:25:51 +10:00

162 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Infrastructure overlay (SPEC §6.5): static vector context for the theater —
// maritime choke points, major oil pipelines, and key facilities. No time
// dynamics. All coordinates are approximate and every description says so.
export default function create(ctx) {
const { viewer, CONFIG, lib, ui, Cesium } = ctx;
const C = CONFIG.colors;
const infraDS = new Cesium.CustomDataSource('infra');
viewer.dataSources.add(infraDS);
const ents = infraDS.entities;
const APPROX = 'Location approximate — illustrative reference geometry, not survey-grade.';
// Shared label defaults: readable at range, fades in as you zoom.
const labelBase = {
font: '12px "Segoe UI", system-ui, sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.fromCssColorString('#0a0f14'),
outlineWidth: 3,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
translucencyByDistance: new Cesium.NearFarScalar(2.0e6, 1.0, 1.2e7, 0.0),
};
// ---- choke points: red ring + label ----
const chokepoints = [
{ name: 'Strait of Hormuz', lon: 56.5, lat: 26.6 },
{ name: 'Bab el-Mandeb', lon: 43.33, lat: 12.58 },
{ name: 'Suez Canal', lon: 32.35, lat: 30.45 },
{ name: 'Strait of Malacca', lon: 101.0, lat: 2.5 },
];
for (const cp of chokepoints) {
ents.add({
name: `Choke point — ${cp.name}`,
position: Cesium.Cartesian3.fromDegrees(cp.lon, cp.lat),
ellipse: {
semiMajorAxis: 80000,
semiMinorAxis: 80000,
material: Cesium.Color.fromCssColorString('rgba(255,0,60,1)').withAlpha(0.06),
outline: true,
outlineColor: lib.cz(C.chokepoint),
height: 0,
},
label: {
...labelBase,
text: cp.name,
fillColor: lib.cz(C.chokepoint),
pixelOffset: new Cesium.Cartesian2(0, -6),
},
description:
`<p><b>Maritime choke point.</b></p>` +
`<p>${cp.name} — a strategic strait for global energy and trade flows.</p>` +
`<p><i>${APPROX} The ring is a fixed ~80 km radius marker, not the true channel.</i></p>`,
});
}
// ---- pipelines: amber dashed polylines clamped to ground + midpoint label ----
const pipelines = [
{
name: 'EastWest (Petroline) crude pipeline',
note: 'Abqaiq → Yanbu',
coords: [49.68, 25.94, 46.7, 24.6, 44.5, 24.3, 41.5, 24.2, 38.06, 24.09],
},
{
name: 'HabshanFujairah crude pipeline',
note: 'bypasses the Strait of Hormuz',
coords: [53.61, 23.75, 54.6, 24.2, 55.5, 24.8, 56.33, 25.12],
},
];
for (const pl of pipelines) {
ents.add({
name: `Pipeline — ${pl.name}`,
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray(pl.coords),
width: 2,
clampToGround: true,
material: new Cesium.PolylineDashMaterialProperty({ color: lib.cz(C.pipeline) }),
},
description:
`<p><b>Oil pipeline.</b> ${pl.note}.</p>` +
`<p>${pl.name}.</p>` +
`<p><i>${APPROX} The route is a simplified schematic between endpoints.</i></p>`,
});
// Midpoint label at the middle vertex of the schematic route.
const mid = pipelineMidpoint(pl.coords);
ents.add({
name: `${pl.name} (label)`,
position: Cesium.Cartesian3.fromDegrees(mid[0], mid[1]),
label: { ...labelBase, text: pl.name, fillColor: lib.cz(C.pipeline) },
});
}
// ---- facilities: small square markers + type-annotated labels ----
const facilities = [
{ name: 'Ras Tanura', type: 'oil terminal', lon: 50.16, lat: 26.64 },
{ name: 'Abqaiq', type: 'oil processing plant', lon: 49.68, lat: 25.94 },
{ name: 'Yanbu', type: 'port / oil terminal', lon: 38.06, lat: 24.09 },
{ name: 'Kharg Island', type: 'oil export terminal', lon: 50.32, lat: 29.23 },
{ name: 'Fujairah', type: 'oil terminal', lon: 56.37, lat: 25.18 },
{ name: 'Jebel Ali', type: 'desalination plant', lon: 55.02, lat: 25.01 },
{ name: 'Ras Al-Khair', type: 'desalination plant', lon: 49.19, lat: 27.55 },
{ name: 'Al Udeid', type: 'air base', lon: 51.32, lat: 25.12 },
{ name: 'Haifa', type: 'refinery', lon: 35.05, lat: 32.79 },
];
const squareGlyph = facilitySquare(Cesium, C.facility);
for (const f of facilities) {
ents.add({
name: `Facility — ${f.name} ${f.type}`,
position: Cesium.Cartesian3.fromDegrees(f.lon, f.lat),
billboard: {
image: squareGlyph,
width: 9,
height: 9,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
label: {
...labelBase,
text: f.name,
fillColor: lib.cz(C.facility),
pixelOffset: new Cesium.Cartesian2(0, -8),
},
description:
`<p><b>${f.name}</b> — ${f.type}.</p>` +
`<p><i>${APPROX}</i></p>`,
});
}
// ---- HUD row + status ----
ui.addLayer('infra', 'Infrastructure', true, (on) => { infraDS.show = on; });
ui.setStatus(
'infra',
`${chokepoints.length} choke points · ${pipelines.length} pipelines · ${facilities.length} facilities`,
'ok',
);
return { id: 'infra' };
}
// Geographic midpoint = the middle vertex of the route's [lon,lat,...] array.
function pipelineMidpoint(coords) {
const n = coords.length / 2;
const i = Math.floor(n / 2) * 2;
return [coords[i], coords[i + 1]];
}
// Tiny filled square canvas for facility markers (billboard image).
function facilitySquare(Cesium, hex) {
const s = 16;
const c = document.createElement('canvas');
c.width = s;
c.height = s;
const g = c.getContext('2d');
g.fillStyle = hex;
g.strokeStyle = '#0a0f14';
g.lineWidth = 2;
g.fillRect(2, 2, s - 4, s - 4);
g.strokeRect(2, 2, s - 4, s - 4);
return c;
}