Files
gods-eye-view/src/layers/weather/source.js
T
Sameh Khamis 73e789489b chore: drop exports nothing imports
Knip, configured with the application, package exports, scripts, QA
harnesses, workers, tools and tests as entry points, reports exports
that no module imports. Each was checked by hand against string-based
and namespace imports.

- Symbols that are still used inside their own module lose only the
  `export` keyword (176 constants and helpers).
- Unused re-exports are removed from barrel modules (flight, military,
  vessel, CCTV, directions, transit history, local ADS-B and street-level
  policy modules).
- Declarations that nothing uses at all are deleted, among them
  AIS_WATCHDOG_STATUSES, applyFilter, isRadioCountryCode, compactText,
  DENSITY_STOPS, the unused CCTV endpoint constants, and the SDR band
  constants.
- Default exports that nothing imports are removed. The ones in
  src/data/earthquakes.js, src/data/telegeographySubmarineCables.js,
  src/data/wind.js and src/data/bhoteKoshiLocator.js each constructed a
  layer instance at import time that nothing used; those instances are
  no longer created.

Every export of a package.json entry stays, as does TOMTOM_CREDIT,
which the traffic layer reads from the credits namespace it is given.
2026-10-22 19:00:40 -07:00

155 lines
5.6 KiB
JavaScript

import { readResponseJsonCapped } from '../../sources/httpBody.js';
// Shared with the AI tools, so no i18n import: failures stay English here and
// the weather layer translates them where it shows them (t / translateKnown).
const msg = (text) => text;
const WEATHER_PRODUCTS = Object.freeze([
'radar',
'clouds',
'clouds-regional',
'lightning',
]);
/** Only bounded, explicit observations may become imagery requests. */
export function validateWeatherSnapshot(value, product) {
if (!value || value.schemaVersion !== 1 || value.product !== product)
throw new Error(msg('Malformed weather manifest'));
if (value.unavailable) return value;
const { bounds, times } = value;
if (
!bounds ||
!['west', 'south', 'east', 'north'].every((key) =>
Number.isFinite(bounds[key]),
) ||
bounds.west < -180 ||
bounds.east > 180 ||
bounds.south < -90 ||
bounds.north > 90 ||
bounds.west >= bounds.east ||
bounds.south >= bounds.north ||
!Array.isArray(times) ||
times.length < 1 ||
times.length > 13 ||
times.some(
(time, i) =>
typeof time !== 'string' ||
!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(time) ||
!Number.isFinite(Date.parse(time)) ||
new Date(time).toISOString() !== time ||
(i > 0 && time <= times[i - 1]),
) ||
value.latest !== times.at(-1) ||
value.tileSize !== 256 ||
value.maxLevel !== 6 ||
value.tilingScheme !== 'geographic'
)
throw new Error(msg('Malformed weather manifest'));
return value;
}
/** Largest whole-extent image per product; also the proxy default size. */
export const WEATHER_IMAGE_SIZES = Object.freeze({
radar: Object.freeze({ width: 4096, height: 2048 }),
'clouds-regional': Object.freeze({ width: 4096, height: 2048 }),
clouds: Object.freeze({ width: 2048, height: 1024 }),
lightning: Object.freeze({ width: 4096, height: 2048 }),
});
/** Largest detail-window image for every product; also the proxy default. */
export const WEATHER_DETAIL_SIZE = Object.freeze({ width: 4096, height: 2048 });
/** A whole-extent image, or with `bbox` ({ west, south, east, north } degrees)
* a detail window of the same product. */
export function weatherImageUrl(
product,
time,
{ width, height } = {},
bbox = null,
) {
if (!WEATHER_PRODUCTS.includes(product) || !Number.isFinite(Date.parse(time)))
throw new Error('Invalid weather frame');
let box = '';
if (bbox !== null) {
const edges = [bbox.west, bbox.south, bbox.east, bbox.north];
if (
!edges.every(Number.isFinite) ||
edges[0] >= edges[2] ||
edges[1] >= edges[3]
)
throw new Error('Invalid weather window');
box = `&bbox=${edges.join(',')}`;
}
const largest =
bbox === null ? WEATHER_IMAGE_SIZES[product] : WEATHER_DETAIL_SIZE;
let size = '';
if (width !== undefined || height !== undefined) {
if (
![1024, 2048, 4096].includes(width) ||
height !== width / 2 ||
width > largest.width
)
throw new Error('Invalid weather image size');
// The largest size is the proxy default: one frame has one URL.
if (width !== largest.width) size = `&size=${width}x${height}`;
}
return `/api/weather/image?product=${product}&time=${encodeURIComponent(time)}${box}${size}`;
}
export function weatherTileUrl(product, time, { size } = {}) {
if (!WEATHER_PRODUCTS.includes(product) || !Number.isFinite(Date.parse(time)))
throw new Error('Invalid weather frame');
if (size !== undefined && ![256, 512, 1024].includes(size))
throw new Error('Invalid weather tile size');
// Construct locally; never accept a manifest-provided host or template.
return `/api/weather/tile?product=${product}&time=${encodeURIComponent(time)}&z={z}&x={x}&y={y}${size === undefined ? '' : `&size=${size}`}`;
}
/** Acquisition is lazy and shares the application's existing source contract. */
export function createWeatherSource({
fetchImpl = (...args) => globalThis.fetch(...args),
timeoutMs = 15_000,
} = {}) {
return {
async getSnapshot({ product = 'radar', signal } = {}) {
if (!WEATHER_PRODUCTS.includes(product))
throw new Error('Unknown weather product');
const controller = new AbortController();
const abort = () => controller.abort(signal.reason);
signal?.addEventListener('abort', abort, { once: true });
const timer = setTimeout(
() => controller.abort(new Error(msg('Weather request timed out'))),
timeoutMs,
);
try {
signal?.throwIfAborted();
const response = await fetchImpl(
`/api/weather/manifest?product=${product}`,
{ signal: controller.signal, cache: 'no-store', redirect: 'error' },
);
if (!response.ok) throw new Error(`Weather HTTP ${response.status}`);
return validateWeatherSnapshot(
await readResponseJsonCapped(response, 16_384, controller.signal),
product,
);
} finally {
clearTimeout(timer);
signal?.removeEventListener('abort', abort);
}
},
/** Read one image frame through the bounded image route. */
async getImage({ product, time, size, bbox = null, signal } = {}) {
signal?.throwIfAborted();
const response = await fetchImpl(
weatherImageUrl(product, time, size, bbox),
{ signal, redirect: 'error' },
);
if (!response.ok)
throw new Error(`Weather image HTTP ${response.status}`);
const bytes = new Uint8Array(await response.arrayBuffer());
signal?.throwIfAborted();
const type = response.headers.get('content-type') || '';
return { contentType: type.split(';')[0].trim().toLowerCase(), bytes };
},
};
}