mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
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.
155 lines
5.6 KiB
JavaScript
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 };
|
|
},
|
|
};
|
|
}
|