mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
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.
This commit is contained in:
1 parent
504f9342aa
commit
73e789489b
106 files changed
+180
-342
No files matched your search
@@ -18,7 +18,7 @@ export const CCTV_MAX_SOURCES_CEILING = 5000;
|
||||
export const AUSTIN_DOWNTOWN = { lat: 30.2672, lon: -97.7431 };
|
||||
/** Caltrans CCTV: one JSON feed per district, identical schema statewide. */
|
||||
/** TxDOT ITS: one keyless JSON catalog per district (25 districts statewide). */
|
||||
export const TXDOT_ORIGIN = 'https://its.txdot.gov';
|
||||
const TXDOT_ORIGIN = 'https://its.txdot.gov';
|
||||
export const TXDOT_CCTV_STATUS_URL = (district) =>
|
||||
`${TXDOT_ORIGIN}/its/DistrictIts/GetCctvStatusListByDistrict?districtCode=${encodeURIComponent(district)}`;
|
||||
/** Per-camera frame. Returns JSON `{snippet:<base64 jpeg>}`, not an image body;
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from 'node:path';
|
||||
import { poseHash } from '../../../src/data/cctvFootprint.js';
|
||||
|
||||
/** Sidecar written by scripts/precompute-cctv-heights.mjs. */
|
||||
export const DEFAULT_GROUND_HEIGHTS_FILE =
|
||||
const DEFAULT_GROUND_HEIGHTS_FILE =
|
||||
'src/data/local_data/cctv_ground_heights/cctv_ground_heights.json';
|
||||
|
||||
/** @type {{path:string, mtimeMs:number, cameras:object}|null} */
|
||||
|
||||
@@ -92,7 +92,7 @@ export function buildSyntheticCctvSvg({ cameraId, label, city, status }) {
|
||||
* @param {ReadableStream|NodeJS.ReadableStream|null} body
|
||||
* @returns {import('stream').Readable|null}
|
||||
*/
|
||||
export function toReadable(body) {
|
||||
function toReadable(body) {
|
||||
if (!body) return null;
|
||||
if (typeof body.pipe === 'function') return body;
|
||||
if (typeof body.getReader === 'function') {
|
||||
@@ -108,7 +108,7 @@ export function toReadable(body) {
|
||||
* @returns {{essence:string,params:Map<string,string>}|null} Null when the
|
||||
* value is missing or is not a `type/subtype` media type.
|
||||
*/
|
||||
export function parseMediaType(value) {
|
||||
function parseMediaType(value) {
|
||||
const [head, ...rest] = String(value ?? '').split(';');
|
||||
const essence = head.trim().toLowerCase();
|
||||
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(essence)) return null;
|
||||
|
||||
@@ -64,7 +64,7 @@ export function toFiniteNumber(value, fallback = NaN) {
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
export function normalizeKey(text) {
|
||||
function normalizeKey(text) {
|
||||
return String(text || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
@@ -80,7 +80,7 @@ export function normalizeKey(text) {
|
||||
* @param {string} value
|
||||
* @returns {{lat:number, lon:number}}
|
||||
*/
|
||||
export function parsePointString(value) {
|
||||
function parsePointString(value) {
|
||||
const match = String(value || '').match(
|
||||
/POINT\s*\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)/i,
|
||||
);
|
||||
@@ -100,7 +100,7 @@ export function parsePointString(value) {
|
||||
* @param {string|object|null} value
|
||||
* @returns {{lat:number, lon:number}}
|
||||
*/
|
||||
export function coerceLatLon(value) {
|
||||
function coerceLatLon(value) {
|
||||
if (!value) return { lat: NaN, lon: NaN };
|
||||
|
||||
if (typeof value === 'string') {
|
||||
|
||||
@@ -1868,7 +1868,7 @@ export function normalizeCataloniaImageUrl(rawUrl) {
|
||||
* @param {string} imageUrl - A URL normalizeCataloniaImageUrl accepted.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function cataloniaCameraId(imageUrl) {
|
||||
function cataloniaCameraId(imageUrl) {
|
||||
const parsed = new URL(imageUrl);
|
||||
const slug = (text) =>
|
||||
text
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function fetchHlsBytes(
|
||||
}
|
||||
}
|
||||
|
||||
export function sameOriginHlsUrl(value, base) {
|
||||
function sameOriginHlsUrl(value, base) {
|
||||
const url = new URL(value, base);
|
||||
if (
|
||||
!['https:', 'http:'].includes(url.protocol) ||
|
||||
|
||||
@@ -12,14 +12,14 @@ export const TAIWAN_FREEWAY_CCTV_URL =
|
||||
'https://tisvcloud.freeway.gov.tw/history/motc20/CCTV.xml';
|
||||
/** The whole network is ~1.2 MB. */
|
||||
export const TAIWAN_MAX_CATALOG_BYTES = 6 * 1024 * 1024;
|
||||
export const DEFAULT_TAIWAN_MAX_SOURCES = 600;
|
||||
const DEFAULT_TAIWAN_MAX_SOURCES = 600;
|
||||
/** After a failed download, wait this long before asking again. The catalog
|
||||
* host does not answer from some networks outside Taiwan, and every attempt
|
||||
* there holds the shared catalog refresh until the connect timeout. */
|
||||
export const TAIWAN_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1000;
|
||||
/** The table is republished daily; a good copy stands in for a failed
|
||||
* download for up to this long. */
|
||||
export const TAIWAN_LAST_GOOD_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
||||
const TAIWAN_LAST_GOOD_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
// The table publishes each camera's MJPEG stream. The same camera servers
|
||||
// answer a single JPEG for the same camera ID on a sibling path, which is what
|
||||
@@ -50,7 +50,7 @@ const ANCHORS = [
|
||||
|
||||
/** Taiwan main island with slack for the coast; the freeway network has no
|
||||
* offshore-island cameras. */
|
||||
export function isLikelyTaiwanCoordinate(lat, lon) {
|
||||
function isLikelyTaiwanCoordinate(lat, lon) {
|
||||
return (
|
||||
Number.isFinite(lat) &&
|
||||
Number.isFinite(lon) &&
|
||||
|
||||
@@ -27,13 +27,13 @@ import { normalizeDump1090Aircraft } from '../../src/sources/adsbRecords.js';
|
||||
*/
|
||||
|
||||
export const LOCAL_RECEIVERS_ROUTE = '/api/local-receivers/aircraft';
|
||||
export const LOCAL_RECEIVER_FEEDS_ENV = 'LOCAL_RECEIVER_FEEDS';
|
||||
export const LOCAL_RECEIVER_BANDS = Object.freeze(['1090', '978']);
|
||||
export const LOCAL_RECEIVER_TIMEOUT_MS = 2_000;
|
||||
export const LOCAL_RECEIVER_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
||||
export const LOCAL_RECEIVER_CACHE_MS = 1_000;
|
||||
const LOCAL_RECEIVER_FEEDS_ENV = 'LOCAL_RECEIVER_FEEDS';
|
||||
const LOCAL_RECEIVER_BANDS = Object.freeze(['1090', '978']);
|
||||
const LOCAL_RECEIVER_TIMEOUT_MS = 2_000;
|
||||
const LOCAL_RECEIVER_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
||||
const LOCAL_RECEIVER_CACHE_MS = 1_000;
|
||||
/** A feed whose own `now` is older than this is reported stale. */
|
||||
export const LOCAL_RECEIVER_STALE_MS = 10_000;
|
||||
const LOCAL_RECEIVER_STALE_MS = 10_000;
|
||||
const MAX_FEEDS = 8;
|
||||
const LOG_PREFIX = '[local-receivers]';
|
||||
|
||||
@@ -139,7 +139,7 @@ class FeedError extends Error {
|
||||
* @param {string} address Resolved IP address.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalReceiverAddress(address) {
|
||||
function isLocalReceiverAddress(address) {
|
||||
const value = String(address || '').toLowerCase();
|
||||
if (value === '::1') return true;
|
||||
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(value);
|
||||
@@ -152,7 +152,7 @@ export function isLocalReceiverAddress(address) {
|
||||
* @param {typeof dnsLookup} lookupImpl `dns.promises.lookup`-compatible.
|
||||
* @returns {Promise<Array<{address:string, family:number}>>}
|
||||
*/
|
||||
export async function resolveLocalReceiverAddresses(hostname, lookupImpl) {
|
||||
async function resolveLocalReceiverAddresses(hostname, lookupImpl) {
|
||||
let resolved;
|
||||
try {
|
||||
resolved = await lookupImpl(hostname, { all: true, verbatim: true });
|
||||
@@ -274,7 +274,7 @@ function fetchLocalReceiverFeed(url, options) {
|
||||
* @param {object} options
|
||||
* @returns {Promise<object>} Parsed JSON document.
|
||||
*/
|
||||
export async function fetchLocalReceiverDocument(
|
||||
async function fetchLocalReceiverDocument(
|
||||
url,
|
||||
{
|
||||
fetchImpl = fetchLocalReceiverFeed,
|
||||
|
||||
@@ -42,7 +42,7 @@ export class TileUpstreamError extends Error {
|
||||
}
|
||||
|
||||
/** Seconds a Retry-After header asks for (an integer or an HTTP date), else null. */
|
||||
export function retryAfterSeconds(header, now = Date.now()) {
|
||||
function retryAfterSeconds(header, now = Date.now()) {
|
||||
const text = String(header ?? '').trim();
|
||||
if (!text) return null;
|
||||
const seconds = /^\d+$/.test(text)
|
||||
|
||||
@@ -145,7 +145,7 @@ const CODEX_LOGIN_ENV =
|
||||
/^(?:PATH|PATHEXT|HOME|USER|USERNAME|LOGNAME|SHELL|LANG|LANGUAGE|LC_[A-Z_]+|TERM|TZ|TMPDIR|TEMP|TMP|XDG_[A-Z_]+|DISPLAY|WAYLAND_DISPLAY|DBUS_SESSION_BUS_ADDRESS|BROWSER|WSL_[A-Z_]+|WSLENV|(?:HTTPS?|ALL|NO)_PROXY|SSL_CERT_(?:FILE|DIR)|NODE_EXTRA_CA_CERTS|SYSTEMROOT|WINDIR|COMSPEC|USERPROFILE|APPDATA|LOCALAPPDATA|PROGRAMDATA|HOMEDRIVE|HOMEPATH|CODEX_[A-Z_]+)$/i;
|
||||
|
||||
/** The environment `codex login` runs with: the allowlist, then `CODEX_HOME`. */
|
||||
export function codexLoginEnvironment(environment, codexHome) {
|
||||
function codexLoginEnvironment(environment, codexHome) {
|
||||
const env = {};
|
||||
for (const [name, value] of Object.entries(environment || {}))
|
||||
if (CODEX_LOGIN_ENV.test(name) && value !== undefined) env[name] = value;
|
||||
|
||||
@@ -157,4 +157,4 @@ function createDebugLogHandler({
|
||||
};
|
||||
}
|
||||
|
||||
export { createDebugLogHandler, omitVoiceContent };
|
||||
export { createDebugLogHandler };
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
RADIO_UUID_RE,
|
||||
RADIO_FALLBACK_MIRRORS,
|
||||
} from './constants.js';
|
||||
export async function mapRadioConcurrent(values, concurrency, mapper) {
|
||||
async function mapRadioConcurrent(values, concurrency, mapper) {
|
||||
const results = new Array(values.length);
|
||||
let cursor = 0;
|
||||
const workers = Array.from(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export {
|
||||
createTransitHistory,
|
||||
TRANSIT_HISTORY_LIMITS,
|
||||
validTransitIdentifier,
|
||||
} from '../../src/sources/transitHistoryStore.js';
|
||||
@@ -3,7 +3,7 @@ import { decodeWindGribMessage } from './decode.js';
|
||||
import { resampleWeatherSnapshot, weatherScalarMetadata } from './grid.js';
|
||||
|
||||
/** Keyless ECMWF Open Data root for real-time IFS forecasts. */
|
||||
export const IFS_BASE = 'https://data.ecmwf.int/forecasts';
|
||||
const IFS_BASE = 'https://data.ecmwf.int/forecasts';
|
||||
|
||||
/**
|
||||
* Select the latest IFS cycle that should be available.
|
||||
|
||||
@@ -31,10 +31,10 @@ export const MIN_VERTICES = Object.freeze({ area: 3, line: 2, pin: 1 });
|
||||
*/
|
||||
export const MAX_VERTICES = 512;
|
||||
/** Two clicks closer than this are one vertex: a double-click to finish must not add a stray point. */
|
||||
export const MIN_VERTEX_SEPARATION_M = 0.5;
|
||||
const MIN_VERTEX_SEPARATION_M = 0.5;
|
||||
/** A line shorter than this, or an area thinner than this, is a mis-click rather than a shape. */
|
||||
export const MIN_PATH_LENGTH_M = 1;
|
||||
export const MIN_AREA_M2 = 1;
|
||||
const MIN_PATH_LENGTH_M = 1;
|
||||
const MIN_AREA_M2 = 1;
|
||||
|
||||
/** @param {string} shape @returns {'area'|'line'|'pin'} */
|
||||
export function normalizeShape(shape) {
|
||||
@@ -50,7 +50,7 @@ export function createDrawSession(shape = 'area') {
|
||||
}
|
||||
|
||||
/** Great-circle distance in metres between two {lon, lat} points. */
|
||||
export function greatCircleM(a, b) {
|
||||
function greatCircleM(a, b) {
|
||||
return greatCircleMeters(a, b);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
|
||||
import { msg, registerUnsavedWork, t, translateKnown } from '../i18n/index.js';
|
||||
/** The id this tool claims the pointer under. */
|
||||
export const DRAW_POINTER_OWNER = 'draw';
|
||||
const DRAW_POINTER_OWNER = 'draw';
|
||||
const PREVIEW_DATA_SOURCE_NAME = 'gev-draw-preview';
|
||||
|
||||
const COLORS = ['primary', 'amber', 'cyan', 'green', 'red'];
|
||||
|
||||
@@ -29,7 +29,7 @@ import { unavailablePlaceSearch } from '../search/placeSearch.js';
|
||||
import { isPickedWorldPosition } from '../data/scenePick.js';
|
||||
|
||||
/** Ms the analyst region lookup waits on geocode + admin boundary. */
|
||||
export const REGION_FALLBACK_BUDGET_MS = 3_000;
|
||||
const REGION_FALLBACK_BUDGET_MS = 3_000;
|
||||
|
||||
/** Entity facts that rule out a state/county reading of the ask. */
|
||||
const NON_ADMIN_ENTITY_KINDS = new Set([
|
||||
|
||||
+3
-3
@@ -15,9 +15,9 @@ import * as Cesium from 'cesium';
|
||||
import { announceNavigationAuthority } from '../navigationPolicy.js';
|
||||
import { annotationsFromParams, createView } from '../view/index.js';
|
||||
|
||||
export const EMBED_VIEW_MESSAGE = 'gev:view';
|
||||
export const EMBED_APPLIED_MESSAGE = 'gev:view-applied';
|
||||
export const EMBED_READY_MESSAGE = 'gev:ready';
|
||||
const EMBED_VIEW_MESSAGE = 'gev:view';
|
||||
const EMBED_APPLIED_MESSAGE = 'gev:view-applied';
|
||||
const EMBED_READY_MESSAGE = 'gev:ready';
|
||||
|
||||
const FOLLOW_LAYERS = {
|
||||
aircraft: 'flights',
|
||||
|
||||
@@ -53,7 +53,7 @@ export const CATALOG_SOURCE_CONTRACTS = Object.freeze({
|
||||
});
|
||||
|
||||
/** An omitted provider is unavailable, never an authoritative empty dataset. */
|
||||
export class SourceUnavailableError extends Error {
|
||||
class SourceUnavailableError extends Error {
|
||||
constructor(name) {
|
||||
super(`Data source not configured: ${name}`);
|
||||
this.name = 'SourceUnavailableError';
|
||||
|
||||
@@ -20,8 +20,8 @@ import { ownCameraArrival } from './data/cameraArrival.js';
|
||||
/** Minimum eye height above the rendered surface for a usable view. */
|
||||
export const MIN_EYE_CLEARANCE_M = 120;
|
||||
/** Retries while tiles stream in; sampling fails until the mesh exists. */
|
||||
export const GUARD_ATTEMPTS = 8;
|
||||
export const GUARD_INTERVAL_MS = 600;
|
||||
const GUARD_ATTEMPTS = 8;
|
||||
const GUARD_INTERVAL_MS = 600;
|
||||
/** Ignore sub-metre noise rather than nudging the camera forever. */
|
||||
const GUARD_EPSILON_M = 2;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export function runCctvLayerEnableFocus({
|
||||
* @param {Object} [after={}] Ownership immediately after the await.
|
||||
* @returns {{ trackedEntity: *, cockpitActive: boolean }} Conservative ownership.
|
||||
*/
|
||||
export function mergeCctvEnableOwnership(before = {}, after = {}) {
|
||||
function mergeCctvEnableOwnership(before = {}, after = {}) {
|
||||
return {
|
||||
trackedEntity: before.trackedEntity || after.trackedEntity || null,
|
||||
cockpitActive: !!(before.cockpitActive || after.cockpitActive),
|
||||
|
||||
@@ -174,7 +174,7 @@ export function cockpitWeatherEnabledFromStoredValue(value) {
|
||||
* Weather-backed volumetric cloud pass that exists only while cockpit mode is
|
||||
* active. It owns no Cesium fog/post-process stages and cannot affect map mode.
|
||||
*/
|
||||
export class CockpitCloudEffectsController {
|
||||
class CockpitCloudEffectsController {
|
||||
constructor(viewer, { weatherService = applicationServices.weather } = {}) {
|
||||
this.weatherService = weatherService;
|
||||
this.viewer = viewer;
|
||||
|
||||
@@ -123,7 +123,7 @@ export function decodeRing(encoded, decimals) {
|
||||
}
|
||||
|
||||
/** Even-odd point-in-ring test; ring is [[lon, lat], …], open or closed. */
|
||||
export function pointInRing(ring, lat, lon) {
|
||||
function pointInRing(ring, lat, lon) {
|
||||
if (!Array.isArray(ring) || ring.length < 3) return false;
|
||||
let inside = false;
|
||||
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
|
||||
|
||||
@@ -215,7 +215,7 @@ export const AIS_RECOGNIZED_MESSAGE_TYPES = Object.freeze(
|
||||
* @param {Object} body
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function aisEnvelopeMmsi(envelope, body = {}) {
|
||||
function aisEnvelopeMmsi(envelope, body = {}) {
|
||||
const metadata = envelope?.MetaData || envelope?.Metadata || {};
|
||||
const raw = metadata.MMSI ?? body.UserID ?? body.UserId ?? body.Mmsi;
|
||||
if (raw === null || raw === undefined) return null;
|
||||
|
||||
+1
-21
@@ -46,28 +46,8 @@ export const AIS_WATCHDOG_DEFAULTS = Object.freeze({
|
||||
authProbeMs: 3_600_000,
|
||||
});
|
||||
|
||||
/**
|
||||
* Statuses reported to the browser. 'missing-key' and 'unsupported' keep their
|
||||
* pre-watchdog meaning so a keyless install still reads as "feature off".
|
||||
*/
|
||||
export const AIS_WATCHDOG_STATUSES = Object.freeze([
|
||||
'idle',
|
||||
'missing-key',
|
||||
'unsupported',
|
||||
'connecting',
|
||||
'live',
|
||||
'stale',
|
||||
'reconnecting',
|
||||
'down',
|
||||
'auth-failed',
|
||||
]);
|
||||
|
||||
/** Failure classes. Only 'transport' is allowed to walk the fast ladder. */
|
||||
export const AIS_FAILURE_KINDS = Object.freeze([
|
||||
'transport',
|
||||
'auth',
|
||||
'rate-limit',
|
||||
]);
|
||||
const AIS_FAILURE_KINDS = Object.freeze(['transport', 'auth', 'rate-limit']);
|
||||
|
||||
/** Statuses that must not flip back to a hopeful 'connecting' on a retry. */
|
||||
const QUIET_TERMINAL = new Set(['down', 'auth-failed']);
|
||||
|
||||
@@ -62,7 +62,7 @@ const COMMON_FIELDS = Object.freeze(['id', 'lat', 'lon']);
|
||||
/** Derived per-record field for layers with a time field (hours before now). */
|
||||
const AGE_FIELD = 'ageHours';
|
||||
/** Spatial scope kinds `query()` accepts. */
|
||||
export const ANALYST_SCOPE_KINDS = Object.freeze([
|
||||
const ANALYST_SCOPE_KINDS = Object.freeze([
|
||||
'view',
|
||||
'region',
|
||||
'radius',
|
||||
@@ -74,7 +74,7 @@ export const ANALYST_SCOPE_KINDS = Object.freeze([
|
||||
* @param {string[]} layerKeys Queried layer ids.
|
||||
* @returns {string[]} Field names, common fields first.
|
||||
*/
|
||||
export function analystFieldsFor(layerKeys) {
|
||||
function analystFieldsFor(layerKeys) {
|
||||
const fields = new Set(COMMON_FIELDS);
|
||||
for (const key of layerKeys) {
|
||||
const layer = ANALYST_LAYERS[key];
|
||||
@@ -104,44 +104,6 @@ export function haversineKm(lat1, lon1, lat2, lon2) {
|
||||
return greatCircleKm({ lat: lat1, lon: lon1 }, { lat: lat2, lon: lon2 });
|
||||
}
|
||||
|
||||
/**
|
||||
* One filter: {field, op:'gt'|'lt'|'gte'|'lte'|'eq'|'neq'|'contains', value}.
|
||||
* With `time: true`, both sides compare as epoch ms (ISO strings accepted).
|
||||
*/
|
||||
export function applyFilter(records, filter, { time = false } = {}) {
|
||||
const { field, op } = filter || {};
|
||||
if (!field || !op) return records;
|
||||
const value = time ? toEpochMs(filter.value) : filter.value;
|
||||
return records.filter((r) => {
|
||||
const got = time ? toEpochMs(r[field]) : r[field];
|
||||
if (got === null || got === undefined || (time && !Number.isFinite(got)))
|
||||
return false;
|
||||
if (time && (op === 'eq' || op === 'neq'))
|
||||
return (got === value) === (op === 'eq');
|
||||
switch (op) {
|
||||
case 'gt':
|
||||
return Number(got) > Number(value);
|
||||
case 'gte':
|
||||
return Number(got) >= Number(value);
|
||||
case 'lt':
|
||||
return Number(got) < Number(value);
|
||||
case 'lte':
|
||||
return Number(got) <= Number(value);
|
||||
case 'eq': {
|
||||
if (typeof got === 'boolean' || typeof value === 'boolean')
|
||||
return Boolean(got) === Boolean(value);
|
||||
return String(got).toLowerCase() === String(value).toLowerCase();
|
||||
}
|
||||
case 'neq':
|
||||
return String(got).toLowerCase() !== String(value).toLowerCase();
|
||||
case 'contains':
|
||||
return String(got).toLowerCase().includes(String(value).toLowerCase());
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Scope records spatially. scope: {kind:'view'|'region'|'radius'|'anywhere', …}. */
|
||||
export function applyScope(records, scope, resolved) {
|
||||
if (!scope || scope.kind === 'anywhere') return records;
|
||||
@@ -191,7 +153,7 @@ const COMMON_UNITS = Object.freeze({
|
||||
* @param {string} field Field name.
|
||||
* @returns {'number'|'text'|'flag'|'time'|null} Field type.
|
||||
*/
|
||||
export function analystFieldType(layerKeys, field) {
|
||||
function analystFieldType(layerKeys, field) {
|
||||
if (!analystFieldsFor(layerKeys).includes(field)) return null;
|
||||
if (COMMON_TYPES[field]) return COMMON_TYPES[field];
|
||||
for (const key of layerKeys) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { BHOTE_KOSHI_FLOOD_PATH } from './bhoteKoshiFloodPath.js';
|
||||
import { INCIDENT_OVERVIEW_PLACES } from './bhoteKoshiIncidentPlaces.js';
|
||||
import { t, msg, getLocale } from '../i18n/index.js';
|
||||
|
||||
export const BHOTE_KOSHI_LOCATOR_LAYER_ID = 'bhote-koshi-locator';
|
||||
const BHOTE_KOSHI_LOCATOR_LAYER_ID = 'bhote-koshi-locator';
|
||||
export const BHOTE_KOSHI_LOCATOR_OVERLAY_SOURCE_ID =
|
||||
'bhote-koshi-locator-callout';
|
||||
export const NEPAL_CONTEXT_PRESENTATION = 'nepal-context';
|
||||
@@ -1247,6 +1247,3 @@ export function createBhoteKoshiLocatorLayer({
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const bhoteKoshiLocatorLayer = createBhoteKoshiLocatorLayer();
|
||||
export default bhoteKoshiLocatorLayer;
|
||||
@@ -23,4 +23,3 @@ export {
|
||||
BIKESHARE_SELECTED_OVERLAY_SOURCE_ID,
|
||||
BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS,
|
||||
} from '../layers/bikeshare/index.js';
|
||||
export default layer;
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
const EARTH_RADIUS_M = 6371000;
|
||||
export const PLANE_VERT_ASPECT = 16 / 9;
|
||||
const PLANE_VERT_ASPECT = 16 / 9;
|
||||
export const SUPPORT_KEYS = Object.freeze([
|
||||
'bl',
|
||||
'bm',
|
||||
@@ -128,7 +128,7 @@ export function planeSupportPoints(pose) {
|
||||
* @param {number} capAltM @param {-1|0|1} row @param {{upVert:number}} dims
|
||||
* @returns {number}
|
||||
*/
|
||||
export function supportAltitude(capAltM, row, dims) {
|
||||
function supportAltitude(capAltM, row, dims) {
|
||||
return capAltM + row * dims.upVert;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -94,7 +94,7 @@ export function incumbentRankKm(
|
||||
export const CCTV_CARD_CENTER_WEIGHT = 0.5;
|
||||
|
||||
/** Robust pool-distance percentile used to scale the screen-space term. */
|
||||
export const CCTV_CARD_SPREAD_PERCENTILE = 0.9;
|
||||
const CCTV_CARD_SPREAD_PERCENTILE = 0.9;
|
||||
|
||||
/** True only when both viewport dimensions are finite positive pixels. */
|
||||
export function hasFiniteCctvViewport(viewW, viewH) {
|
||||
@@ -172,8 +172,8 @@ export function blendCenterRankKm(
|
||||
// viewport is bucketed into this grid and every occupied cell gets its best
|
||||
// candidate before global rank fills the rest. Owner-tunable together with
|
||||
// the budgets above.
|
||||
export const CCTV_CARD_GRID_COLS = 5;
|
||||
export const CCTV_CARD_GRID_ROWS = 4;
|
||||
const CCTV_CARD_GRID_COLS = 5;
|
||||
const CCTV_CARD_GRID_ROWS = 4;
|
||||
|
||||
/**
|
||||
* Screen-space distribution pass (owner round 2, item C). Buckets the
|
||||
|
||||
@@ -25,11 +25,11 @@ import * as Cesium from 'cesium';
|
||||
* follows DATA_SOURCES.md (live sources, then bundled snapshots).
|
||||
* @type {{ key: string, html: string }[]}
|
||||
*/
|
||||
export const OSM_CREDIT = {
|
||||
const OSM_CREDIT = {
|
||||
key: 'openstreetmap',
|
||||
html: 'Map and place data <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">© OpenStreetMap contributors</a> (<a href="https://opendatacommons.org/licenses/odbl/1-0/" target="_blank" rel="noopener">ODbL</a>)',
|
||||
};
|
||||
export const OPENMAPTILES_CREDIT = {
|
||||
const OPENMAPTILES_CREDIT = {
|
||||
key: 'openfreemap',
|
||||
html: 'Vector tiles: <a href="https://openfreemap.org" target="_blank" rel="noopener">OpenFreeMap</a> © <a href="https://openmaptiles.org" target="_blank" rel="noopener">OpenMapTiles</a>',
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ export function stableIdentityHash(layerId, sourceId) {
|
||||
}
|
||||
|
||||
/** Negative means a is a better deterministic contender than b. */
|
||||
export function compareCohortContenders(a, b) {
|
||||
function compareCohortContenders(a, b) {
|
||||
const priorityDelta =
|
||||
(Number(b?._cohortPriority) || 0) - (Number(a?._cohortPriority) || 0);
|
||||
if (priorityDelta) return priorityDelta;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* module so density cannot drift into contradictory mode semantics again.
|
||||
*/
|
||||
|
||||
export const DENSITY_STOPS = Object.freeze([0, 25, 50, 75, 100]);
|
||||
export const ALLOCATION_ELASTIC = 'ELASTIC';
|
||||
export const ALLOCATION_WEIGHTED = 'WEIGHTED';
|
||||
export const ALLOCATION_STRATEGIES = Object.freeze([
|
||||
@@ -118,7 +117,7 @@ export function detectionHorizontalSector(screenX, viewportWidth) {
|
||||
return 'front';
|
||||
}
|
||||
|
||||
export const VIEW_SCALE_BUDGETS = Object.freeze({
|
||||
const VIEW_SCALE_BUDGETS = Object.freeze({
|
||||
street: Object.freeze({ 0: 9, 25: 23, 50: 45, 75: 68, 100: 90 }),
|
||||
city: Object.freeze({ 0: 7, 25: 18, 50: 35, 75: 53, 100: 70 }),
|
||||
metro: Object.freeze({ 0: 6, 25: 14, 50: 28, 75: 41, 100: 55 }),
|
||||
|
||||
@@ -24,7 +24,7 @@ import * as input from './inputOwnership.js';
|
||||
import { createDirectionsLayer } from '../layers/directions/index.js';
|
||||
|
||||
/** The shared scene owners every Directions instance runs on. */
|
||||
export const directionsServices = Object.freeze({
|
||||
const directionsServices = Object.freeze({
|
||||
credits,
|
||||
render,
|
||||
sprites,
|
||||
@@ -49,15 +49,11 @@ export {
|
||||
DIRECTIONS_MODES,
|
||||
DIRECTIONS_POINTER_OWNER,
|
||||
DIRECTIONS_ROUTE_COLOR,
|
||||
DIRECTIONS_STEP_OVERLAY_SOURCE_ID,
|
||||
DIRECTIONS_STEP_OVERLAY_SOURCE_OPTIONS,
|
||||
FLIGHT_PROGRESS_MS,
|
||||
POINTER_TOOL_EXITS,
|
||||
STEP_ANCHOR_DEADLINE_MS,
|
||||
STEP_ANCHOR_FAST_ATTEMPTS,
|
||||
STEP_ANCHOR_RETRY_MS,
|
||||
STEP_ANCHOR_SLOW_RETRY_MS,
|
||||
createDirectionsLayer,
|
||||
createDirectionsStepOverlayEntry,
|
||||
directionsRequestUrl,
|
||||
directionsRowControls,
|
||||
|
||||
@@ -8,4 +8,3 @@ export function createEarthquakesLayer({
|
||||
} = {}) {
|
||||
return createApplicationEarthquakes({ source, ...options });
|
||||
}
|
||||
export default createEarthquakesLayer();
|
||||
@@ -5,9 +5,9 @@ import { createImportedPins } from './geoImportPins.js';
|
||||
import { msg } from '../i18n/marker.js';
|
||||
|
||||
/** Largest file accepted, in bytes. */
|
||||
export const MAX_IMPORT_BYTES = 50 * 1024 * 1024;
|
||||
const MAX_IMPORT_BYTES = 50 * 1024 * 1024;
|
||||
/** Largest total a KMZ may inflate to — a zip bomb stops here. */
|
||||
export const MAX_KMZ_INFLATED_BYTES = 120 * 1024 * 1024;
|
||||
const MAX_KMZ_INFLATED_BYTES = 120 * 1024 * 1024;
|
||||
/** Most files on the globe at once. */
|
||||
export const MAX_IMPORTED_FILES = 12;
|
||||
/** Most entities one file may create before it is refused. */
|
||||
@@ -15,9 +15,9 @@ export const MAX_IMPORT_ENTITIES = 20_000;
|
||||
/** Most vertices (points of every line, ring and marker) in one file. */
|
||||
export const MAX_IMPORT_POSITIONS = 250_000;
|
||||
/** Most normalized style definitions and local image links in one KML/KMZ. */
|
||||
export const MAX_KML_LINKS = 2_000;
|
||||
const MAX_KML_LINKS = 2_000;
|
||||
/** Longest plain-text name kept from a file's features. */
|
||||
export const MAX_IMPORT_NAME_LENGTH = 120;
|
||||
const MAX_IMPORT_NAME_LENGTH = 120;
|
||||
/** What the file picker offers. */
|
||||
export const GEO_FILE_ACCEPT = '.geojson,.json,.kml,.kmz,.gpx';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Inspect encoded headers before allowing the browser to decode local images.
|
||||
export const MAX_IMPORT_IMAGE_PIXELS = 16 * 1024 * 1024;
|
||||
const MAX_IMPORT_IMAGE_PIXELS = 16 * 1024 * 1024;
|
||||
const MAX_SIDE = 8192;
|
||||
|
||||
/** Width and height from a supported raster header; no image is decoded. */
|
||||
|
||||
@@ -35,14 +35,14 @@
|
||||
import { PbfReader } from 'pbf';
|
||||
|
||||
/** VehiclePosition.VehicleStopStatus enum → label. */
|
||||
export const VEHICLE_STOP_STATUS = Object.freeze({
|
||||
const VEHICLE_STOP_STATUS = Object.freeze({
|
||||
0: 'INCOMING_AT',
|
||||
1: 'STOPPED_AT',
|
||||
2: 'IN_TRANSIT_TO',
|
||||
});
|
||||
|
||||
/** VehiclePosition.OccupancyStatus enum → label. */
|
||||
export const OCCUPANCY_STATUS = Object.freeze({
|
||||
const OCCUPANCY_STATUS = Object.freeze({
|
||||
0: 'EMPTY',
|
||||
1: 'MANY_SEATS_AVAILABLE',
|
||||
2: 'FEW_SEATS_AVAILABLE',
|
||||
@@ -64,7 +64,6 @@ export const GTFS_MAX_ENTITIES = 50_000;
|
||||
export const GTFS_MAX_STRING_CHARS = 256;
|
||||
/** FeedHeader.Incrementality: only FULL_DATASET carries a complete snapshot. */
|
||||
export const GTFS_INCREMENTALITY_FULL_DATASET = 0;
|
||||
export const GTFS_INCREMENTALITY_DIFFERENTIAL = 1;
|
||||
|
||||
/**
|
||||
* Read a length-delimited string, refusing one that is implausibly long.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
||||
export { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
||||
import {
|
||||
createLocalGeoJsonLayer as createLayer,
|
||||
createLocalInfrastructureOverlayPublisher as createPublisher,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { createRetryableLoader } from './retryableLoad.js';
|
||||
export const MILITARY_POINT_CAP = 512;
|
||||
export const MILITARY_LABEL_CAP = 24;
|
||||
|
||||
export const MILITARY_NAMES_TIMEOUT_MS = 10_000;
|
||||
const MILITARY_NAMES_TIMEOUT_MS = 10_000;
|
||||
|
||||
/** Share a bounded acquisition; failed assets become retryable after cooldown. */
|
||||
export function createMilitaryNamesLoader({
|
||||
|
||||
@@ -117,8 +117,3 @@ export function normalizeRadioCountryInput(value) {
|
||||
name: canonicalCountryName(code),
|
||||
});
|
||||
}
|
||||
|
||||
/** Return whether a value is a recognized ISO 3166-1 alpha-2 code. */
|
||||
export function isRadioCountryCode(value) {
|
||||
return typeof value === 'string' && ISO_ALPHA_2_SET.has(value.toUpperCase());
|
||||
}
|
||||
@@ -28,4 +28,3 @@ export {
|
||||
createCableReferenceSweepGate,
|
||||
updateCableReferenceStem,
|
||||
} from '../layers/submarineCables/index.js';
|
||||
export default createTeleGeographySubmarineCableLayer();
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createSourceSlot } from '../sources/sourceSlot.js';
|
||||
import { createWindLayer as createLayer } from '../layers/wind/index.js';
|
||||
import { createWindSource } from '../layers/wind/source.js';
|
||||
|
||||
const sourceSlot = createSourceSlot(
|
||||
@@ -8,10 +7,3 @@ const sourceSlot = createSourceSlot(
|
||||
'Wind source',
|
||||
);
|
||||
export const configureWindSource = sourceSlot.configure;
|
||||
|
||||
/** Create a wind layer using the configured application source by default. */
|
||||
export function createWindLayer(options = {}) {
|
||||
return createLayer({ ...options, feed: options.feed ?? sourceSlot.source });
|
||||
}
|
||||
|
||||
export default createWindLayer();
|
||||
+1
-1
@@ -210,7 +210,7 @@ export function formatRelativeTime(value, unit = 'second', options) {
|
||||
}
|
||||
|
||||
/** Collapse markup whitespace so a message reads the same in HTML and JS. */
|
||||
export function normalizeMarkupText(text) {
|
||||
function normalizeMarkupText(text) {
|
||||
return String(text ?? '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { QUERY_LIMIT, QUERY_SNAP_DEGREES } from './policy.js';
|
||||
|
||||
export function textTag(value) {
|
||||
function textTag(value) {
|
||||
const t = String(value ?? '').trim();
|
||||
return t || null;
|
||||
}
|
||||
|
||||
export function numTag(value) {
|
||||
function numTag(value) {
|
||||
// `Number('')` and `Number(' ')` are 0, not NaN — a blank or whitespace-only
|
||||
// tag value must read as missing, never as a due-north bearing.
|
||||
if (value == null) return null;
|
||||
@@ -102,7 +102,7 @@ export function normalizeAlprNode(el) {
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeDirection(value) {
|
||||
function normalizeDirection(value) {
|
||||
const degrees = numTag(value);
|
||||
return degrees != null && degrees >= 0 && degrees <= 360
|
||||
? degrees % 360
|
||||
|
||||
@@ -3,7 +3,7 @@ import { VectorTile } from '@mapbox/vector-tile';
|
||||
import { normalizeAlprNode } from './records.js';
|
||||
|
||||
/** Map extract attributes without treating an OSM edit date as a field verification. */
|
||||
export function normalizeAlprTileFeature(feature) {
|
||||
function normalizeAlprTileFeature(feature) {
|
||||
const p = feature?.properties || {};
|
||||
if (feature?.geometry?.type !== 'Point' || p.osmType !== 'node') return null;
|
||||
const [lon, lat] = feature.geometry.coordinates;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
export const AIRCRAFT_DEPENDENCIES = ['flights', 'military'];
|
||||
|
||||
export const DEFERRED_DEPENDENCIES = [
|
||||
'ais-live-vessels',
|
||||
'military-installations',
|
||||
];
|
||||
const DEFERRED_DEPENDENCIES = ['ais-live-vessels', 'military-installations'];
|
||||
|
||||
export const DEPENDENCIES = [
|
||||
...AIRCRAFT_DEPENDENCIES,
|
||||
|
||||
@@ -11,7 +11,7 @@ export const BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS = Object.freeze({
|
||||
// --- Activation / display thresholds ---
|
||||
/** Altitude (m) at which bikeshare layer becomes eligible for display. */
|
||||
|
||||
export const ACTIVATION_ALTITUDE_M = 50000;
|
||||
const ACTIVATION_ALTITUDE_M = 50000;
|
||||
|
||||
/** Hysteresis enter threshold — layer activates when camera drops below this. */
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export {
|
||||
FRAME_ENDPOINT,
|
||||
SOURCE_ENDPOINT,
|
||||
HEALTH_ENDPOINT,
|
||||
MEDIA_ENDPOINT,
|
||||
ACTIVE_FRAME_REFRESH_MS,
|
||||
} from './sourcePolicy.js';
|
||||
export { ACTIVE_FRAME_REFRESH_MS } from './sourcePolicy.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { CCTV_CARD_FETCH_BURST_SPACING_MS } from '../../data/cctvCards.js';
|
||||
import { CCTV_AMBIENT_CARD_MAX } from '../../data/cctvLod.js';
|
||||
@@ -90,9 +84,6 @@ export const CCTV_CALIBRATION_STORAGE_KEY_V2 =
|
||||
|
||||
export const PROJECTION_TEXTURE_SWAP_MS = 1000;
|
||||
|
||||
export const PROJECTION_VERT_ASPECT =
|
||||
PROJECTION_CANVAS_WIDTH / PROJECTION_CANVAS_HEIGHT;
|
||||
|
||||
// V2 frustum geometry (design §2a/§6): the far-cap center + corners never sink
|
||||
// below groundAlt + this clearance, so a fabricated pitch (-24°) cannot bury
|
||||
// the monitor plane in the 3D tiles. Exported for the unit suite.
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
export const FRAME_ENDPOINT = '/api/cctv/frame';
|
||||
|
||||
export const SOURCE_ENDPOINT = '/api/cctv/sources';
|
||||
|
||||
export const HEALTH_ENDPOINT = '/api/cctv/health';
|
||||
|
||||
export const MEDIA_ENDPOINT = '/api/cctv/media';
|
||||
|
||||
export const ACTIVE_FRAME_REFRESH_MS = 10000;
|
||||
@@ -64,7 +64,7 @@ const SHARED_CARD = Object.freeze({
|
||||
* @param {boolean} selected
|
||||
* @returns {object}
|
||||
*/
|
||||
export function cycloneStormEntry(storm, selected) {
|
||||
function cycloneStormEntry(storm, selected) {
|
||||
return {
|
||||
...SHARED_CARD,
|
||||
...markerClearance(
|
||||
@@ -92,7 +92,7 @@ export function cycloneStormEntry(storm, selected) {
|
||||
* @param {{tauHours:number,position:object}} point
|
||||
* @returns {object}
|
||||
*/
|
||||
export function cycloneLeadEntry(stormId, point) {
|
||||
function cycloneLeadEntry(stormId, point) {
|
||||
return {
|
||||
...SHARED_CARD,
|
||||
...markerClearance(CYCLONE_MARKER_PX.forecast),
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
export {
|
||||
GROUND_FLOOR_CLAMP_RADIUS_KM,
|
||||
GROUND_FLOOR_WARM_MAX_ALT_M,
|
||||
POSITION_HISTORY_LIMIT,
|
||||
LANDED_MISSING_POLL_LIMIT,
|
||||
MISSING_POLL_LIMIT,
|
||||
ERROR_BACKOFF_INTERVAL,
|
||||
} from './recordPolicy.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { EMERGENCY_TINT_CSS } from '../../data/aircraftEmergency.js';
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ export const LAYER_ID = 'military-installations';
|
||||
|
||||
export const REQUEST_DEBOUNCE_MS = 180;
|
||||
|
||||
export const MAX_VIEWPORT_DEGREES = 10;
|
||||
|
||||
export const MAX_RENDERED = 700;
|
||||
|
||||
export const GOOGLE_MILITARY_PLACE_TYPES = new Set(['military_base']);
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
|
||||
/** Same-origin route served by `server/providers/local-receivers.js`. */
|
||||
export const LOCAL_RECEIVERS_URL = '/api/local-receivers/aircraft';
|
||||
export const LOCAL_RECEIVER_POLL_MS = 1_000;
|
||||
const LOCAL_RECEIVER_POLL_MS = 1_000;
|
||||
/**
|
||||
* Browser-side deadline for one route request, body included. The server
|
||||
* bounds each upstream read at 2 s; this bounds the browser-to-server leg.
|
||||
*/
|
||||
export const LOCAL_RECEIVER_REQUEST_TIMEOUT_MS = 5_000;
|
||||
const LOCAL_RECEIVER_REQUEST_TIMEOUT_MS = 5_000;
|
||||
|
||||
function abortError() {
|
||||
const error = new Error('Local receiver request aborted');
|
||||
|
||||
@@ -36,18 +36,8 @@ import {
|
||||
} from './policy.js';
|
||||
import { localAdsbStatus } from './status.js';
|
||||
|
||||
export {
|
||||
LAYER_ID as LOCAL_ADSB_LAYER_ID,
|
||||
HEARD_BY_RECEIVER,
|
||||
} from './policy.js';
|
||||
export {
|
||||
localAdsbCardModel,
|
||||
localAdsbClassLine,
|
||||
localAdsbIsUat,
|
||||
localAdsbReceiverLine,
|
||||
localAdsbTitle,
|
||||
} from './card.js';
|
||||
export { localAdsbClass } from './enrichment.js';
|
||||
export { HEARD_BY_RECEIVER } from './policy.js';
|
||||
export { localAdsbCardModel, localAdsbReceiverLine } from './card.js';
|
||||
export { localAdsbStatus } from './status.js';
|
||||
|
||||
const FEET_TO_METERS = 0.3048;
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
export const LOCAL_ADSB_HISTORY_MS = 10 * 60_000;
|
||||
export const LOCAL_ADSB_HISTORY_POINTS = 600;
|
||||
/** A display correction larger than this snaps instead of sliding. */
|
||||
export const LOCAL_ADSB_SNAP_M = 3_000;
|
||||
const LOCAL_ADSB_SNAP_M = 3_000;
|
||||
/** Chord course uses the newest fix at least this much older than the anchor. */
|
||||
const CHORD_BASELINE_MS = 2_000;
|
||||
/** Turn rate is estimated over this window of heard fixes. */
|
||||
|
||||
@@ -24,7 +24,7 @@ function plural(count, one, many) {
|
||||
* @param {object[]} feeds Feed statuses from the route.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function describeFeedProblems(feeds) {
|
||||
function describeFeedProblems(feeds) {
|
||||
const byStatus = new Map();
|
||||
for (const feed of feeds) {
|
||||
if (feed?.status === 'live') continue;
|
||||
@@ -41,7 +41,7 @@ export function describeFeedProblems(feeds) {
|
||||
}
|
||||
|
||||
/** Whether the server reported at least one configured feed. */
|
||||
export function feedsConfigured(feedState) {
|
||||
function feedsConfigured(feedState) {
|
||||
return Boolean(
|
||||
feedState?.configured === true &&
|
||||
Array.isArray(feedState.feeds) &&
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
export {
|
||||
GROUND_FLOOR_WARM_MAX_ALT_M,
|
||||
POSITION_HISTORY_LIMIT,
|
||||
LANDED_MISSING_POLL_LIMIT,
|
||||
MISSING_POLL_LIMIT,
|
||||
ERROR_BACKOFF_INTERVAL,
|
||||
} from './recordPolicy.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { EMERGENCY_TINT_CSS } from '../../data/aircraftEmergency.js';
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export const MARKER_LIFT_M = 2.5;
|
||||
|
||||
export const SELECTED_LIFT_M = 5;
|
||||
|
||||
export const RADIO_PICK_TOLERANCE_PX = 8;
|
||||
const RADIO_PICK_TOLERANCE_PX = 8;
|
||||
|
||||
export const RADIO_TUNER_DIRECTORY_LIMIT = 750;
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ export function hhmm(iso) {
|
||||
}
|
||||
|
||||
/** Candidate key: `PRODUCT:YYYY-MM-DD`. */
|
||||
export function candidateKey(product, day) {
|
||||
function candidateKey(product, day) {
|
||||
return `${product}:${day}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,9 +94,9 @@ export const TRACK_VIEW_FROM_HIGH_SCALE = 4;
|
||||
* attribute, so classification costs nothing per frame.
|
||||
*/
|
||||
|
||||
export const POINT_OUTLINE = Cesium.Color.WHITE.withAlpha(0.3);
|
||||
const POINT_OUTLINE = Cesium.Color.WHITE.withAlpha(0.3);
|
||||
|
||||
export const _classColor = (group) =>
|
||||
const _classColor = (group) =>
|
||||
Cesium.Color.fromCssColorString(satelliteClassColor(group));
|
||||
|
||||
export const POINT_STYLES = {
|
||||
|
||||
@@ -17,18 +17,18 @@ const EXPANDED_SELECTORS = [
|
||||
];
|
||||
|
||||
/** A photo download (tens of kilobytes to a few megabytes). */
|
||||
export const IMAGE_TIMEOUT_MS = 15_000;
|
||||
const IMAGE_TIMEOUT_MS = 15_000;
|
||||
|
||||
/** A picture file or decoded side past these is refused (a photo is a few MB). */
|
||||
export const MAX_IMAGE_BYTES = 48 * 1024 * 1024;
|
||||
export const MAX_IMAGE_SIDE = 16_384;
|
||||
/** 64 MP: 256 MB once decoded to RGBA. A 16,384 px square is four times that. */
|
||||
export const MAX_IMAGE_PIXELS = 8_192 * 8_192;
|
||||
const MAX_IMAGE_PIXELS = 8_192 * 8_192;
|
||||
/** Where in a file to look for its dimensions (a JPEG's EXIF comes first). */
|
||||
const HEADER_BYTES = 256 * 1024;
|
||||
|
||||
/** Whether a picture this size is past what the viewer decodes. */
|
||||
export function tooManyPixels(width, height) {
|
||||
function tooManyPixels(width, height) {
|
||||
return (
|
||||
width > MAX_IMAGE_SIDE ||
|
||||
height > MAX_IMAGE_SIDE ||
|
||||
@@ -37,7 +37,7 @@ export function tooManyPixels(width, height) {
|
||||
}
|
||||
|
||||
/** Panorama texture widths: enough for a docked viewer, and for an expanded one. */
|
||||
export const PANO_MID_SIZE = Object.freeze([2048, 1024]);
|
||||
const PANO_MID_SIZE = Object.freeze([2048, 1024]);
|
||||
export const PANO_FULL_SIZE = Object.freeze([4096, 2048]);
|
||||
|
||||
/**
|
||||
@@ -155,7 +155,7 @@ async function loadBitmap(url, timeoutMs, resize, failure, signal) {
|
||||
* A picture refused for its size (`tooLarge`): the viewer will not load
|
||||
* that file another way, an <img> having no size limit at all.
|
||||
*/
|
||||
export function oversized(failure) {
|
||||
function oversized(failure) {
|
||||
return Object.assign(new Error(failure), { tooLarge: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export const KARTAVIEW_LABEL = 'KARTAVIEW';
|
||||
*/
|
||||
export const KARTAVIEW_API_HOST = 'https://api.kartaview.org';
|
||||
/** KartaView's image proxy: resizes a photo given its base64url address. */
|
||||
export const KARTAVIEW_CDN_HOST = 'https://cdn.kartaview.org';
|
||||
const KARTAVIEW_CDN_HOST = 'https://cdn.kartaview.org';
|
||||
|
||||
/** Stable id prefixes for picked primitives; every one starts with `kv:`. */
|
||||
export const PICK_PREFIX = Object.freeze({
|
||||
@@ -140,8 +140,6 @@ export const MASK_SLOW_MESSAGE = msg(
|
||||
export const FILTERED_OUT_MESSAGE = msg(
|
||||
'None of KartaView’s photos here pass the imagery filter',
|
||||
);
|
||||
/** Under the SINCE slider while KartaView is lit. */
|
||||
export const SINCE_NOTE = '(by upload date)';
|
||||
|
||||
/** The UTC calendar day of `ms`, as KartaView's startDate reads it. */
|
||||
function utcDay(ms) {
|
||||
|
||||
@@ -41,8 +41,6 @@ import {
|
||||
normalizeItem,
|
||||
} from './policy.js';
|
||||
|
||||
export { PANORAMAX_PROVIDER_ID } from './policy.js';
|
||||
|
||||
/**
|
||||
* A tile's bytes decoded by `decode`; a body that is not a vector tile
|
||||
* (an HTML error page, a cut-off answer) reads as such, not as a parser's
|
||||
|
||||
@@ -15,7 +15,7 @@ export const PANORAMAX_LABEL = 'PANORAMAX';
|
||||
*/
|
||||
export const PANORAMAX_API = 'https://api.panoramax.xyz/api';
|
||||
/** The meta-catalogue's web viewer, where the caption link goes. */
|
||||
export const PANORAMAX_VIEWER = 'https://api.panoramax.xyz/';
|
||||
const PANORAMAX_VIEWER = 'https://api.panoramax.xyz/';
|
||||
|
||||
/** Stable id prefixes for picked primitives; every one starts with `pnx:`. */
|
||||
export const PICK_PREFIX = Object.freeze({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { encodeIdSwitches } from '../../data/idSwitches.js';
|
||||
|
||||
export const IMAGERY_FILTERS = Object.freeze(['pano', 'since']);
|
||||
const IMAGERY_FILTERS = Object.freeze(['pano', 'since']);
|
||||
|
||||
export function filtersOf(provider) {
|
||||
return provider?.filters ?? IMAGERY_FILTERS;
|
||||
|
||||
@@ -5,8 +5,8 @@ import { createHorizonCull, metresBetween } from './view.js';
|
||||
import { msg } from '../../i18n/index.js';
|
||||
|
||||
/** Cones closer than this to the previous kept one are dropped. */
|
||||
export const IMAGE_CONE_MIN_SPACING_M = 3;
|
||||
export const IMAGE_CONE_SIZE_PX = 26;
|
||||
const IMAGE_CONE_MIN_SPACING_M = 3;
|
||||
const IMAGE_CONE_SIZE_PX = 26;
|
||||
|
||||
/**
|
||||
* @typedef {object} ConeImage One image of a sequence, as the cones draw it.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Decoded coverage tiles kept, and for how long. */
|
||||
export const TILE_CACHE_SIZE = 24;
|
||||
export const TILE_CACHE_TTL_MS = 10 * 60_000;
|
||||
const TILE_CACHE_SIZE = 24;
|
||||
const TILE_CACHE_TTL_MS = 10 * 60_000;
|
||||
|
||||
/** Least-recently-used map: reading an entry renews it. */
|
||||
export function createLru(limit) {
|
||||
|
||||
@@ -64,7 +64,7 @@ export function keyAlternatives(providers) {
|
||||
* The filter controls a switched-on provider honours (both when none is on).
|
||||
* @param {Array<ProviderSnapshot>} providers
|
||||
*/
|
||||
export function filterControls(providers) {
|
||||
function filterControls(providers) {
|
||||
const active = providers.filter((p) => p.on);
|
||||
const honoured = (key) =>
|
||||
active.length === 0 ||
|
||||
@@ -78,7 +78,7 @@ export function filterControls(providers) {
|
||||
* note needs no provider name.
|
||||
* @param {Array<ProviderSnapshot>} providers
|
||||
*/
|
||||
export function sinceNote(providers) {
|
||||
function sinceNote(providers) {
|
||||
return providers.some((p) => p.on && p.sinceBasis === 'upload')
|
||||
? t('(by upload date)')
|
||||
: null;
|
||||
|
||||
@@ -33,7 +33,7 @@ export function featureReference(feature) {
|
||||
};
|
||||
}
|
||||
|
||||
export function coordsFromProperty(value) {
|
||||
function coordsFromProperty(value) {
|
||||
if (!Array.isArray(value) || value.length < 2) return null;
|
||||
const lon = Number(value[0]);
|
||||
const lat = Number(value[1]);
|
||||
@@ -41,7 +41,7 @@ export function coordsFromProperty(value) {
|
||||
return [lon, lat];
|
||||
}
|
||||
|
||||
export function coordsFromPoint(value) {
|
||||
function coordsFromPoint(value) {
|
||||
if (!Array.isArray(value) || value.length < 2) return null;
|
||||
const lon = Number(value[0]);
|
||||
const lat = Number(value[1]);
|
||||
@@ -49,7 +49,7 @@ export function coordsFromPoint(value) {
|
||||
return [lon, lat];
|
||||
}
|
||||
|
||||
export function collectLonLat(value, out) {
|
||||
function collectLonLat(value, out) {
|
||||
if (!Array.isArray(value)) return;
|
||||
if (typeof value[0] === 'number' && typeof value[1] === 'number') {
|
||||
const lon = Number(value[0]);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { tilesForBounds, tileToBBox } from '../../data/tomtomTiles.js';
|
||||
|
||||
export const RETICLE_RANGE_KM = 10;
|
||||
export const RETICLE_TILE_CAP = 16;
|
||||
export const tileKey = (tile) => `${tile.z}/${tile.x}/${tile.y}`;
|
||||
const RETICLE_RANGE_KM = 10;
|
||||
const RETICLE_TILE_CAP = 16;
|
||||
const tileKey = (tile) => `${tile.z}/${tile.x}/${tile.y}`;
|
||||
const cross = (a, b, c) =>
|
||||
(b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
||||
|
||||
|
||||
@@ -175,13 +175,3 @@ export const STYLED_MIN_BASE_PX = 5;
|
||||
/** @const {number} Maximum tile cache entries before LRU eviction */
|
||||
|
||||
export const TILE_CACHE_MAX_ENTRIES = 64;
|
||||
|
||||
/**
|
||||
* Milliseconds the first dot paint will wait for flow data. Cached flow
|
||||
* settles within this window (decode cache, 120 s TTL) and renders fully
|
||||
* colored; a cold tile fetch loses the race, dots paint immediately in
|
||||
* white, and recolorDotsInPlace applies the colors when flow arrives —
|
||||
* field-test round 1's "takes forever to load" was the sequential wait.
|
||||
*/
|
||||
|
||||
export const FLOW_RENDER_RACE_MS = 250;
|
||||
@@ -99,7 +99,7 @@ export const VISIBILITY_REFRESH_MS = 250;
|
||||
* on screen. Generous on purpose: a vehicle that crosses the edge mid-glide
|
||||
* should already have been moving, not pop into motion at the border.
|
||||
*/
|
||||
export const VIEW_MARGIN_RATIO = 0.6;
|
||||
const VIEW_MARGIN_RATIO = 0.6;
|
||||
|
||||
/**
|
||||
* Whether a vehicle is close enough to the camera's look-at area to be worth
|
||||
@@ -516,10 +516,10 @@ export function createTransitSelectedOverlayEntry(key, position, copy, mode) {
|
||||
}
|
||||
|
||||
/** Modes that ordinarily run below the street, drawn on the surface above themselves. */
|
||||
export const UNDERGROUND_MODES = new Set(['subway']);
|
||||
const UNDERGROUND_MODES = new Set(['subway']);
|
||||
|
||||
/** The mode in plain words, for a card a person reads rather than scans. */
|
||||
export const TRANSIT_MODE_WORD = Object.freeze({
|
||||
const TRANSIT_MODE_WORD = Object.freeze({
|
||||
bus: msg('Bus'),
|
||||
tram: msg('Tram'),
|
||||
subway: msg('Subway'),
|
||||
@@ -529,7 +529,7 @@ export const TRANSIT_MODE_WORD = Object.freeze({
|
||||
});
|
||||
|
||||
/** Short, fixed-width mode word for the detection card's class field. */
|
||||
export const TRANSIT_MODE_ABBR = Object.freeze({
|
||||
const TRANSIT_MODE_ABBR = Object.freeze({
|
||||
bus: 'BUS',
|
||||
tram: 'TRAM',
|
||||
subway: 'METRO',
|
||||
@@ -588,7 +588,7 @@ export function transitDetectionId(record, feed = null) {
|
||||
}
|
||||
|
||||
/** Longest route text the single-line detection label can carry. */
|
||||
export const DETECTION_ID_MAX_CHARS = 14;
|
||||
const DETECTION_ID_MAX_CHARS = 14;
|
||||
|
||||
/**
|
||||
* Cut long route text with an ellipsis, so a reader can see it was cut.
|
||||
@@ -600,7 +600,7 @@ export const DETECTION_ID_MAX_CHARS = 14;
|
||||
* @param {string} text
|
||||
* @returns {string} Uppercase, at most DETECTION_ID_MAX_CHARS characters.
|
||||
*/
|
||||
export function elideRouteText(text) {
|
||||
function elideRouteText(text) {
|
||||
const value = String(text || '').toUpperCase();
|
||||
if (value.length <= DETECTION_ID_MAX_CHARS) return value;
|
||||
return `${value.slice(0, DETECTION_ID_MAX_CHARS - 1)}\u2026`;
|
||||
@@ -659,6 +659,6 @@ export function transitDetectionMetric(
|
||||
* @param {number} [nowMs]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function transitVehicleState(entry, nowMs = Date.now()) {
|
||||
function transitVehicleState(entry, nowMs = Date.now()) {
|
||||
return displayMotion(entry, nowMs).word;
|
||||
}
|
||||
@@ -1,8 +1,3 @@
|
||||
export {
|
||||
PARTIAL_RETENTION_MS,
|
||||
SELECTED_PIN_REFRESHES,
|
||||
AIS_FIRST_CONNECT_LABEL,
|
||||
} from './recordPolicy.js';
|
||||
import { VESSEL_LABEL_GRID_PX } from '../../data/vesselLabels.js';
|
||||
import { msg } from '../../i18n/index.js';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { readResponseJsonCapped } from '../../sources/httpBody.js';
|
||||
// the weather layer translates them where it shows them (t / translateKnown).
|
||||
const msg = (text) => text;
|
||||
|
||||
export const WEATHER_PRODUCTS = Object.freeze([
|
||||
const WEATHER_PRODUCTS = Object.freeze([
|
||||
'radar',
|
||||
'clouds',
|
||||
'clouds-regional',
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
WIND_NARROW_PATH_LIMIT,
|
||||
} from './streamlines.js';
|
||||
|
||||
export const WIND_FADE_LOW_METERS = 15000;
|
||||
export const WIND_FADE_HIGH_METERS = 60000;
|
||||
const WIND_FADE_LOW_METERS = 15000;
|
||||
const WIND_FADE_HIGH_METERS = 60000;
|
||||
|
||||
// Original material: a persistent fine curve plus a moving, tapered highlight.
|
||||
// The integer part of s identifies a path; its fractional part follows the wind.
|
||||
|
||||
@@ -64,5 +64,3 @@ export function locationMiniStatus({
|
||||
|
||||
return { ...EMPTY };
|
||||
}
|
||||
|
||||
export default locationMiniStatus;
|
||||
@@ -1,5 +1,5 @@
|
||||
/** Basemap that before/after imagery comparisons are read against. */
|
||||
export const IMAGERY_COMPARISON_STACK_ID = 'esri-imagery';
|
||||
const IMAGERY_COMPARISON_STACK_ID = 'esri-imagery';
|
||||
|
||||
const SWITCH_POLICIES = new Set(['preserve', 'esri', 'esri-if-photoreal']);
|
||||
const leases = new WeakMap();
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
*/
|
||||
|
||||
export const TERRAIN_RETRY_ATTEMPTS = 3;
|
||||
export const TERRAIN_RETRY_STATUSES = Object.freeze([429, 502, 503, 504]);
|
||||
const TERRAIN_RETRY_STATUSES = Object.freeze([429, 502, 503, 504]);
|
||||
export const TERRAIN_RETRY_BASE_MS = 750;
|
||||
export const TERRAIN_RETRY_MAX_MS = 15_000;
|
||||
export const TERRAIN_RETRY_SPREAD_MS = 1_500;
|
||||
|
||||
@@ -994,7 +994,7 @@ const PUBLIC_SCENE_RECIPES = [
|
||||
];
|
||||
|
||||
/** Build the recipe list without mutating stored user-authored projects. */
|
||||
export function createSceneRecipes({ localDemoRecipes = EVENT_RECIPES } = {}) {
|
||||
function createSceneRecipes({ localDemoRecipes = EVENT_RECIPES } = {}) {
|
||||
return [...localDemoRecipes, ...PUBLIC_SCENE_RECIPES];
|
||||
}
|
||||
|
||||
@@ -1004,7 +1004,7 @@ export const SCENE_APPEND_RECIPES = BHOTE_KOSHI_NEPAL_APPEND_RECIPE
|
||||
? [BHOTE_KOSHI_NEPAL_APPEND_RECIPE]
|
||||
: [];
|
||||
|
||||
export function getSceneRecipeById(id) {
|
||||
function getSceneRecipeById(id) {
|
||||
return SCENE_RECIPES.find((recipe) => recipe.id === id) || null;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@ const READ_PIPELINE_DEPTH = 4;
|
||||
const RATE_WINDOW_MS = 1_000;
|
||||
// A bulk read that has not completed in this long is treated as a stalled
|
||||
// USB transfer: the session is torn down instead of waiting forever.
|
||||
export const SDR_READ_STALL_MS = 5_000;
|
||||
const SDR_READ_STALL_MS = 5_000;
|
||||
// Decoder admission. Every block sent to the worker is numbered and must be
|
||||
// acknowledged; past these bounds the receiver stops with an overload error
|
||||
// rather than queueing raw IQ the decoder cannot keep up with. Eight 16 ms
|
||||
// blocks is 128 ms of input, about forty times the measured decode cost.
|
||||
export const SDR_WORKER_ADMISSION = Object.freeze({
|
||||
const SDR_WORKER_ADMISSION = Object.freeze({
|
||||
maxBlocks: 8,
|
||||
maxBytes: 8 * 65_536,
|
||||
maxAgeMs: 1_000,
|
||||
@@ -66,7 +66,7 @@ const DECODER_WINDOW_SAMPLES = 256;
|
||||
// raw USB device is closed directly, which aborts any pending transfer. The
|
||||
// raw close is bounded by the same deadline, so a teardown settles within
|
||||
// twice this even when `USBDevice.close()` itself never settles.
|
||||
export const SDR_CLOSE_TIMEOUT_MS = 2_000;
|
||||
const SDR_CLOSE_TIMEOUT_MS = 2_000;
|
||||
|
||||
/** Tuning presets per receiver mode; gain is a separate per-mode setting. */
|
||||
export const SDR_RECEIVER_PRESETS = Object.freeze({
|
||||
@@ -1912,9 +1912,3 @@ function formatMhz(hz) {
|
||||
function positiveModulo(value, divisor) {
|
||||
return ((value % divisor) + divisor) % divisor;
|
||||
}
|
||||
|
||||
export const SDR_FM_BAND = Object.freeze({
|
||||
minHz: FM_MIN_HZ,
|
||||
maxHz: FM_MAX_HZ,
|
||||
});
|
||||
export const SDR_ADSB_FREQUENCY_HZ = ADSB_FREQUENCY_HZ;
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
* about 1 msg/s and 28.0 dB about 9 msg/s on the same antenna.
|
||||
*/
|
||||
|
||||
export const SDR_GAIN_AUTO = 'auto';
|
||||
const SDR_GAIN_AUTO = 'auto';
|
||||
|
||||
/** R820T tuner gain steps in dB, as listed by librtlsdr. */
|
||||
export const R820T_GAIN_STEPS_DB = Object.freeze([
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ export const STAR_MAX_SIGMA_PX = 1.35;
|
||||
/** Peak opacity of the faintest star. */
|
||||
export const STAR_MIN_ALPHA = 0.22;
|
||||
/** B−V for stars the catalogue gives no colour (a solar-type white). */
|
||||
export const STAR_DEFAULT_BV = 0.6;
|
||||
const STAR_DEFAULT_BV = 0.6;
|
||||
/** Floats per star in the vertex buffer: xyz, σ, rgb, alpha. */
|
||||
export const STAR_VERTEX_FLOATS = 8;
|
||||
|
||||
@@ -76,7 +76,7 @@ export function bvToTemperature(bv) {
|
||||
}
|
||||
|
||||
/** Black-body colour of a temperature, sRGB 0..1 (Helland's fit). */
|
||||
export function temperatureToRgb(kelvin) {
|
||||
function temperatureToRgb(kelvin) {
|
||||
const t = clamp(kelvin, 1000, 40000) / 100;
|
||||
const red = t <= 66 ? 255 : 329.698727446 * (t - 60) ** -0.1332047592;
|
||||
const green =
|
||||
|
||||
@@ -41,12 +41,12 @@ const CATEGORY_PATTERN = /^[A-D][0-7]$/;
|
||||
/** A previous accepted fix older than this is no reference for a new one. */
|
||||
export const LOCAL_ADSB_REFERENCE_MAX_AGE_MS = 10 * 60_000;
|
||||
/** Speed limit (kt) for an aircraft that has not reported a ground speed. */
|
||||
export const LOCAL_ADSB_UNKNOWN_SPEED_LIMIT_KT = 1_000;
|
||||
const LOCAL_ADSB_UNKNOWN_SPEED_LIMIT_KT = 1_000;
|
||||
/** Tighter limit (kt) for light aircraft and rotorcraft without a speed. */
|
||||
export const LOCAL_ADSB_SLOW_CATEGORY_LIMIT_KT = 350;
|
||||
const LOCAL_ADSB_SLOW_CATEGORY_LIMIT_KT = 350;
|
||||
/** Reported ground speed is multiplied by this, plus the margin below. */
|
||||
export const LOCAL_ADSB_SPEED_FACTOR = 1.5;
|
||||
export const LOCAL_ADSB_SPEED_MARGIN_KT = 50;
|
||||
const LOCAL_ADSB_SPEED_FACTOR = 1.5;
|
||||
const LOCAL_ADSB_SPEED_MARGIN_KT = 50;
|
||||
/** Distance always allowed between two fixes (CPR and reception error). */
|
||||
export const LOCAL_ADSB_POSITION_MARGIN_M = 500;
|
||||
/**
|
||||
|
||||
@@ -77,7 +77,7 @@ export function simplifyRing(ring, tolM) {
|
||||
return out.length >= 4 ? out : ring;
|
||||
}
|
||||
|
||||
export function douglasPeucker(points, tol) {
|
||||
function douglasPeucker(points, tol) {
|
||||
const n = points.length;
|
||||
if (n < 3) return points.slice();
|
||||
// Iterative, index-based (no per-call array slicing / recursion), so a large
|
||||
@@ -113,7 +113,7 @@ export function douglasPeucker(points, tol) {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function perpDistance(p, a, b) {
|
||||
function perpDistance(p, a, b) {
|
||||
const dx = b[0] - a[0];
|
||||
const dy = b[1] - a[1];
|
||||
const len2 = dx * dx + dy * dy;
|
||||
@@ -149,7 +149,7 @@ export function stitchRing(ways) {
|
||||
return endpointGapM(largest) <= allow ? largest : [];
|
||||
}
|
||||
|
||||
export function largestByArea(components) {
|
||||
function largestByArea(components) {
|
||||
let best = components[0];
|
||||
let bestArea = approximateAreaM2(best);
|
||||
for (let i = 1; i < components.length; i += 1) {
|
||||
@@ -162,7 +162,7 @@ export function largestByArea(components) {
|
||||
return best;
|
||||
}
|
||||
|
||||
export function ringSpanM(chain) {
|
||||
function ringSpanM(chain) {
|
||||
let minLat = Infinity;
|
||||
let maxLat = -Infinity;
|
||||
let minLon = Infinity;
|
||||
@@ -176,7 +176,7 @@ export function ringSpanM(chain) {
|
||||
return approximateDistanceM(minLat, minLon, maxLat, maxLon);
|
||||
}
|
||||
|
||||
export function buildRingComponents(ways) {
|
||||
function buildRingComponents(ways) {
|
||||
const same = (a, b) =>
|
||||
Math.abs(a.lon - b.lon) < 1e-7 && Math.abs(a.lat - b.lat) < 1e-7;
|
||||
const remaining = ways.map((w) => w.slice());
|
||||
@@ -207,7 +207,7 @@ export function buildRingComponents(ways) {
|
||||
return components;
|
||||
}
|
||||
|
||||
export function endpointGapM(chain) {
|
||||
function endpointGapM(chain) {
|
||||
if (!chain || chain.length < 2) return Infinity;
|
||||
const a = chain[0];
|
||||
const b = chain[chain.length - 1];
|
||||
|
||||
@@ -24,7 +24,7 @@ const ROAD_TYPES = Object.freeze({
|
||||
* OpenMapTiles folds private access into `no`. Tunnels cannot be placed on
|
||||
* the surface above them; ramps/bridges remain valid only on a motor road.
|
||||
*/
|
||||
export function isDrivableOpenMapRoad(properties = {}) {
|
||||
function isDrivableOpenMapRoad(properties = {}) {
|
||||
return (
|
||||
Object.hasOwn(ROAD_TYPES, properties.class) &&
|
||||
!properties.subclass &&
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { validTransitIdentifier } from './transitHistory.js';
|
||||
export { validTransitIdentifier } from './transitHistory.js';
|
||||
/** Bounded, process-local operator position history. No upstream or scene access. */
|
||||
export const TRANSIT_HISTORY_LIMITS = Object.freeze({
|
||||
const TRANSIT_HISTORY_LIMITS = Object.freeze({
|
||||
retentionMs: 900000,
|
||||
fixes: 128,
|
||||
feedBytes: 16 * 1024 * 1024,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// DOM stand-ins for the KartaView viewer's tests (no browser in Node).
|
||||
|
||||
/** A DOM element with what the viewer touches: children, events, attributes. */
|
||||
export function fakeElement(tag) {
|
||||
function fakeElement(tag) {
|
||||
const listeners = new Map();
|
||||
const element = {
|
||||
tagName: tag.toUpperCase(),
|
||||
|
||||
@@ -55,7 +55,7 @@ const COMPRESS_MIN_BYTES = 1024;
|
||||
* rather than fail; past `PANEL_QUEUED_REQUESTS` waiting, they are refused.
|
||||
*/
|
||||
export const PANEL_CONCURRENT_REQUESTS = 6;
|
||||
export const PANEL_QUEUED_REQUESTS = 256;
|
||||
const PANEL_QUEUED_REQUESTS = 256;
|
||||
|
||||
/**
|
||||
* Each server's panel state: bodies too large for one call, kept for the
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AREA_SCHEMA, resolveArea } from '../area.js';
|
||||
import { cameraForArea, cameraLookingAt } from '../views.js';
|
||||
|
||||
/** Tool arguments that describe a view: an area to frame, or a camera. */
|
||||
export const VIEW_ARGUMENTS = Object.freeze({
|
||||
const VIEW_ARGUMENTS = Object.freeze({
|
||||
view: Object.freeze({
|
||||
type: 'object',
|
||||
description:
|
||||
|
||||
@@ -107,7 +107,7 @@ export function readCameraTargetFrame(viewer) {
|
||||
}
|
||||
|
||||
/** Apply an orbit frame, then return the camera to Cesium's fixed-world frame. */
|
||||
export function setCameraTargetFrame(viewer, frame) {
|
||||
function setCameraTargetFrame(viewer, frame) {
|
||||
const camera = viewer?.camera;
|
||||
if (!camera || !frame?.target || !Number.isFinite(frame.range)) return false;
|
||||
try {
|
||||
@@ -156,7 +156,7 @@ export function setCameraTargetFrame(viewer, frame) {
|
||||
*
|
||||
* @param {{pitch: number}|null} frame From {@link readCameraTargetFrame}.
|
||||
*/
|
||||
export function frameIsTilted(frame) {
|
||||
function frameIsTilted(frame) {
|
||||
const pitch = Number(frame?.pitch);
|
||||
return Number.isFinite(pitch) ? pitch > OBLIQUE_THRESHOLD : false;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ const SOURCE_LABELS = Object.freeze({
|
||||
});
|
||||
|
||||
/** "SNAPSHOT" / "СНИМОК"; unknown codes (HLS, MJPEG…) stay as uppercase codes. */
|
||||
export function cctvSourceKindLabel(kind) {
|
||||
function cctvSourceKindLabel(kind) {
|
||||
const key = String(kind || 'unknown').toLowerCase();
|
||||
return SOURCE_KIND_LABELS[key]
|
||||
? t(SOURCE_KIND_LABELS[key])
|
||||
|
||||
@@ -218,7 +218,7 @@ function statusPatternText(piece) {
|
||||
}
|
||||
|
||||
/** One piece of a status line in the UI language, or as it came. */
|
||||
export function translateStatusPiece(piece) {
|
||||
function translateStatusPiece(piece) {
|
||||
const known = translateKnown(piece);
|
||||
if (known !== piece) return known;
|
||||
return statusPatternText(piece) ?? piece;
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
|
||||
import { finiteOrNull } from './pointerContext.js';
|
||||
|
||||
export const POINTER_SENTINEL = 'pointer';
|
||||
const POINTER_SENTINEL = 'pointer';
|
||||
|
||||
const TRACKABLE_LAYERS = new Set([
|
||||
'flights',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { progressLine } from './speech.js';
|
||||
* Timing policy for filling silence while tools run. Tuned against the turn
|
||||
* spans' `max_silence_ms`.
|
||||
*/
|
||||
export const NARRATION_TIMING = Object.freeze({
|
||||
const NARRATION_TIMING = Object.freeze({
|
||||
/** Start the earcon when nothing is audible this long after end of speech. */
|
||||
earconAfterMs: 700,
|
||||
/** First spoken progress line when a tool is still running after this. */
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
*/
|
||||
|
||||
/** Movement below this many CSS pixels is still the same dwell. */
|
||||
export const POINTER_DWELL_PX = 12;
|
||||
const POINTER_DWELL_PX = 12;
|
||||
/** Stillness that makes a dwell point. */
|
||||
export const POINTER_DWELL_MS = 250;
|
||||
/** A resting pointer stays a referent this long after it last moved. */
|
||||
export const POINTER_FRESH_MS = 30000;
|
||||
/** Pixels from the canvas edge that count as "parked", never a referent. */
|
||||
export const POINTER_EDGE_PX = 8;
|
||||
const POINTER_EDGE_PX = 8;
|
||||
/** A dwell pick is reused by a snapshot only while this recent (contacts move). */
|
||||
export const POINTER_DWELL_REUSE_MS = 1000;
|
||||
const POINTER_DWELL_REUSE_MS = 1000;
|
||||
/**
|
||||
* Open mic has no gesture, so the pointer only counts when it moved or
|
||||
* settled on the map this recently: a cursor resting there for minutes is
|
||||
@@ -76,7 +76,7 @@ export function cameraPoseKey(viewer) {
|
||||
}
|
||||
|
||||
/** Bound and de-control a label before it goes to the model or the card. */
|
||||
export function pointerLabel(value, max = LABEL_MAX) {
|
||||
function pointerLabel(value, max = LABEL_MAX) {
|
||||
const clean = String(value ?? '')
|
||||
.replace(/[\p{Cc}\p{Cf}]+/gu, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cameraPoseKey } from './pointerContext.js';
|
||||
import * as defaultAnnotationResolver from '../annotations/annotationResolver.js';
|
||||
|
||||
/** "Around here" radius as a share of camera height, clamped to sane bounds. */
|
||||
export function pointerRadiusKm(cameraHeightM) {
|
||||
function pointerRadiusKm(cameraHeightM) {
|
||||
const heightKm = Number(cameraHeightM) / 1000;
|
||||
if (!Number.isFinite(heightKm) || heightKm <= 0) return 50;
|
||||
return Math.round(Math.min(400, Math.max(5, heightKm * 0.75)));
|
||||
|
||||
@@ -14,7 +14,7 @@ export const RETICLE_MAX_MS = 10000;
|
||||
export const RETICLE_LINGER_MS = 1500;
|
||||
|
||||
/** Tools that move the camera away from the pointed spot. */
|
||||
export const CAMERA_TOOLS = new Set([
|
||||
const CAMERA_TOOLS = new Set([
|
||||
'fly_to_location',
|
||||
'track_entity',
|
||||
'zoom_to_globe',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export const ERROR_LOG_LIMIT = 30;
|
||||
const ERROR_LOG_LIMIT = 30;
|
||||
|
||||
export const ERROR_STORAGE_KEY = 'gev-realtime-errors';
|
||||
const ERROR_STORAGE_KEY = 'gev-realtime-errors';
|
||||
|
||||
export const DEBUG_LOG_URL = '/api/realtime/debug-log';
|
||||
const DEBUG_LOG_URL = '/api/realtime/debug-log';
|
||||
|
||||
export function createDebugSessionId() {
|
||||
function createDebugSessionId() {
|
||||
const randomPart = Math.random().toString(36).slice(2, 10);
|
||||
return `gev-${Date.now().toString(36)}-${randomPart}`;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export function sanitizeDebugValue(value, depth = 0) {
|
||||
return output;
|
||||
}
|
||||
|
||||
export function sanitizeDebugString(value) {
|
||||
function sanitizeDebugString(value) {
|
||||
if (value.startsWith('data:image/')) {
|
||||
return `[Redacted image data URL, ${value.length} chars]`;
|
||||
}
|
||||
@@ -65,13 +65,13 @@ export function sanitizeDebugString(value) {
|
||||
: redacted;
|
||||
}
|
||||
|
||||
export function isSecretLikeKey(key) {
|
||||
function isSecretLikeKey(key) {
|
||||
return /(?:api[_-]?key|authorization|bearer|client[_-]?secret|token|secret|password)/i.test(
|
||||
key,
|
||||
);
|
||||
}
|
||||
|
||||
export function createErrorRecord(source, error, extra = {}) {
|
||||
function createErrorRecord(source, error, extra = {}) {
|
||||
const rtcError = error?.error || error;
|
||||
return {
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -90,7 +90,7 @@ export function createErrorRecord(source, error, extra = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
export function formatErrorForDisplay(record) {
|
||||
function formatErrorForDisplay(record) {
|
||||
const primary = [record.source, record.message].filter(Boolean).join(': ');
|
||||
const state = [
|
||||
record.errorDetail && `detail=${record.errorDetail}`,
|
||||
@@ -105,7 +105,7 @@ export function formatErrorForDisplay(record) {
|
||||
return state ? `${primary}\n${state}` : primary;
|
||||
}
|
||||
|
||||
export function removeEmptyValues(value) {
|
||||
function removeEmptyValues(value) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value || {}).filter(
|
||||
([, item]) => item !== null && item !== undefined && item !== '',
|
||||
@@ -113,14 +113,7 @@ export function removeEmptyValues(value) {
|
||||
);
|
||||
}
|
||||
|
||||
export function compactText(value, maxLength) {
|
||||
const text = String(value || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}…`;
|
||||
}
|
||||
|
||||
export function loadStoredErrors() {
|
||||
function loadStoredErrors() {
|
||||
try {
|
||||
const value = JSON.parse(localStorage.getItem(ERROR_STORAGE_KEY) || '[]');
|
||||
return Array.isArray(value) ? value.slice(0, ERROR_LOG_LIMIT) : [];
|
||||
@@ -129,7 +122,7 @@ export function loadStoredErrors() {
|
||||
}
|
||||
}
|
||||
|
||||
export function storeErrors(errors) {
|
||||
function storeErrors(errors) {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
ERROR_STORAGE_KEY,
|
||||
|
||||
@@ -8,12 +8,12 @@ import {
|
||||
|
||||
// Voice cost control (repo-wide `godsEyeView.<feature>.<field>` convention;
|
||||
// the neighbouring ERROR_STORAGE_KEY predates it).
|
||||
export const VOICE_TIER_STORAGE_KEY = 'godsEyeView.voiceCost.tier';
|
||||
const VOICE_TIER_STORAGE_KEY = 'godsEyeView.voiceCost.tier';
|
||||
|
||||
export const VOICE_LIMITS_STORAGE_KEY = 'godsEyeView.voiceCost.limits';
|
||||
const VOICE_LIMITS_STORAGE_KEY = 'godsEyeView.voiceCost.limits';
|
||||
|
||||
/** Best-effort localStorage handle; absent in tests and locked-down browsers. */
|
||||
export function voiceStorage(storage) {
|
||||
function voiceStorage(storage) {
|
||||
if (storage) return storage;
|
||||
try {
|
||||
return typeof localStorage === 'undefined' ? null : localStorage;
|
||||
|
||||
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user