mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04: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 };
|
export const AUSTIN_DOWNTOWN = { lat: 30.2672, lon: -97.7431 };
|
||||||
/** Caltrans CCTV: one JSON feed per district, identical schema statewide. */
|
/** Caltrans CCTV: one JSON feed per district, identical schema statewide. */
|
||||||
/** TxDOT ITS: one keyless JSON catalog per district (25 districts 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) =>
|
export const TXDOT_CCTV_STATUS_URL = (district) =>
|
||||||
`${TXDOT_ORIGIN}/its/DistrictIts/GetCctvStatusListByDistrict?districtCode=${encodeURIComponent(district)}`;
|
`${TXDOT_ORIGIN}/its/DistrictIts/GetCctvStatusListByDistrict?districtCode=${encodeURIComponent(district)}`;
|
||||||
/** Per-camera frame. Returns JSON `{snippet:<base64 jpeg>}`, not an image body;
|
/** 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';
|
import { poseHash } from '../../../src/data/cctvFootprint.js';
|
||||||
|
|
||||||
/** Sidecar written by scripts/precompute-cctv-heights.mjs. */
|
/** 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';
|
'src/data/local_data/cctv_ground_heights/cctv_ground_heights.json';
|
||||||
|
|
||||||
/** @type {{path:string, mtimeMs:number, cameras:object}|null} */
|
/** @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
|
* @param {ReadableStream|NodeJS.ReadableStream|null} body
|
||||||
* @returns {import('stream').Readable|null}
|
* @returns {import('stream').Readable|null}
|
||||||
*/
|
*/
|
||||||
export function toReadable(body) {
|
function toReadable(body) {
|
||||||
if (!body) return null;
|
if (!body) return null;
|
||||||
if (typeof body.pipe === 'function') return body;
|
if (typeof body.pipe === 'function') return body;
|
||||||
if (typeof body.getReader === 'function') {
|
if (typeof body.getReader === 'function') {
|
||||||
@@ -108,7 +108,7 @@ export function toReadable(body) {
|
|||||||
* @returns {{essence:string,params:Map<string,string>}|null} Null when the
|
* @returns {{essence:string,params:Map<string,string>}|null} Null when the
|
||||||
* value is missing or is not a `type/subtype` media type.
|
* 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 [head, ...rest] = String(value ?? '').split(';');
|
||||||
const essence = head.trim().toLowerCase();
|
const essence = head.trim().toLowerCase();
|
||||||
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(essence)) return null;
|
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(essence)) return null;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function toFiniteNumber(value, fallback = NaN) {
|
|||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function normalizeKey(text) {
|
function normalizeKey(text) {
|
||||||
return String(text || '')
|
return String(text || '')
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -80,7 +80,7 @@ export function normalizeKey(text) {
|
|||||||
* @param {string} value
|
* @param {string} value
|
||||||
* @returns {{lat:number, lon:number}}
|
* @returns {{lat:number, lon:number}}
|
||||||
*/
|
*/
|
||||||
export function parsePointString(value) {
|
function parsePointString(value) {
|
||||||
const match = String(value || '').match(
|
const match = String(value || '').match(
|
||||||
/POINT\s*\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)/i,
|
/POINT\s*\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)/i,
|
||||||
);
|
);
|
||||||
@@ -100,7 +100,7 @@ export function parsePointString(value) {
|
|||||||
* @param {string|object|null} value
|
* @param {string|object|null} value
|
||||||
* @returns {{lat:number, lon:number}}
|
* @returns {{lat:number, lon:number}}
|
||||||
*/
|
*/
|
||||||
export function coerceLatLon(value) {
|
function coerceLatLon(value) {
|
||||||
if (!value) return { lat: NaN, lon: NaN };
|
if (!value) return { lat: NaN, lon: NaN };
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
|
|||||||
@@ -1868,7 +1868,7 @@ export function normalizeCataloniaImageUrl(rawUrl) {
|
|||||||
* @param {string} imageUrl - A URL normalizeCataloniaImageUrl accepted.
|
* @param {string} imageUrl - A URL normalizeCataloniaImageUrl accepted.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function cataloniaCameraId(imageUrl) {
|
function cataloniaCameraId(imageUrl) {
|
||||||
const parsed = new URL(imageUrl);
|
const parsed = new URL(imageUrl);
|
||||||
const slug = (text) =>
|
const slug = (text) =>
|
||||||
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);
|
const url = new URL(value, base);
|
||||||
if (
|
if (
|
||||||
!['https:', 'http:'].includes(url.protocol) ||
|
!['https:', 'http:'].includes(url.protocol) ||
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ export const TAIWAN_FREEWAY_CCTV_URL =
|
|||||||
'https://tisvcloud.freeway.gov.tw/history/motc20/CCTV.xml';
|
'https://tisvcloud.freeway.gov.tw/history/motc20/CCTV.xml';
|
||||||
/** The whole network is ~1.2 MB. */
|
/** The whole network is ~1.2 MB. */
|
||||||
export const TAIWAN_MAX_CATALOG_BYTES = 6 * 1024 * 1024;
|
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
|
/** After a failed download, wait this long before asking again. The catalog
|
||||||
* host does not answer from some networks outside Taiwan, and every attempt
|
* host does not answer from some networks outside Taiwan, and every attempt
|
||||||
* there holds the shared catalog refresh until the connect timeout. */
|
* there holds the shared catalog refresh until the connect timeout. */
|
||||||
export const TAIWAN_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1000;
|
export const TAIWAN_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1000;
|
||||||
/** The table is republished daily; a good copy stands in for a failed
|
/** The table is republished daily; a good copy stands in for a failed
|
||||||
* download for up to this long. */
|
* 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
|
// 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
|
// 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
|
/** Taiwan main island with slack for the coast; the freeway network has no
|
||||||
* offshore-island cameras. */
|
* offshore-island cameras. */
|
||||||
export function isLikelyTaiwanCoordinate(lat, lon) {
|
function isLikelyTaiwanCoordinate(lat, lon) {
|
||||||
return (
|
return (
|
||||||
Number.isFinite(lat) &&
|
Number.isFinite(lat) &&
|
||||||
Number.isFinite(lon) &&
|
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_RECEIVERS_ROUTE = '/api/local-receivers/aircraft';
|
||||||
export const LOCAL_RECEIVER_FEEDS_ENV = 'LOCAL_RECEIVER_FEEDS';
|
const LOCAL_RECEIVER_FEEDS_ENV = 'LOCAL_RECEIVER_FEEDS';
|
||||||
export const LOCAL_RECEIVER_BANDS = Object.freeze(['1090', '978']);
|
const LOCAL_RECEIVER_BANDS = Object.freeze(['1090', '978']);
|
||||||
export const LOCAL_RECEIVER_TIMEOUT_MS = 2_000;
|
const LOCAL_RECEIVER_TIMEOUT_MS = 2_000;
|
||||||
export const LOCAL_RECEIVER_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
const LOCAL_RECEIVER_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
||||||
export const LOCAL_RECEIVER_CACHE_MS = 1_000;
|
const LOCAL_RECEIVER_CACHE_MS = 1_000;
|
||||||
/** A feed whose own `now` is older than this is reported stale. */
|
/** 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 MAX_FEEDS = 8;
|
||||||
const LOG_PREFIX = '[local-receivers]';
|
const LOG_PREFIX = '[local-receivers]';
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ class FeedError extends Error {
|
|||||||
* @param {string} address Resolved IP address.
|
* @param {string} address Resolved IP address.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isLocalReceiverAddress(address) {
|
function isLocalReceiverAddress(address) {
|
||||||
const value = String(address || '').toLowerCase();
|
const value = String(address || '').toLowerCase();
|
||||||
if (value === '::1') return true;
|
if (value === '::1') return true;
|
||||||
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(value);
|
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.
|
* @param {typeof dnsLookup} lookupImpl `dns.promises.lookup`-compatible.
|
||||||
* @returns {Promise<Array<{address:string, family:number}>>}
|
* @returns {Promise<Array<{address:string, family:number}>>}
|
||||||
*/
|
*/
|
||||||
export async function resolveLocalReceiverAddresses(hostname, lookupImpl) {
|
async function resolveLocalReceiverAddresses(hostname, lookupImpl) {
|
||||||
let resolved;
|
let resolved;
|
||||||
try {
|
try {
|
||||||
resolved = await lookupImpl(hostname, { all: true, verbatim: true });
|
resolved = await lookupImpl(hostname, { all: true, verbatim: true });
|
||||||
@@ -274,7 +274,7 @@ function fetchLocalReceiverFeed(url, options) {
|
|||||||
* @param {object} options
|
* @param {object} options
|
||||||
* @returns {Promise<object>} Parsed JSON document.
|
* @returns {Promise<object>} Parsed JSON document.
|
||||||
*/
|
*/
|
||||||
export async function fetchLocalReceiverDocument(
|
async function fetchLocalReceiverDocument(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
fetchImpl = fetchLocalReceiverFeed,
|
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. */
|
/** 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();
|
const text = String(header ?? '').trim();
|
||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
const seconds = /^\d+$/.test(text)
|
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;
|
/^(?: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`. */
|
/** The environment `codex login` runs with: the allowlist, then `CODEX_HOME`. */
|
||||||
export function codexLoginEnvironment(environment, codexHome) {
|
function codexLoginEnvironment(environment, codexHome) {
|
||||||
const env = {};
|
const env = {};
|
||||||
for (const [name, value] of Object.entries(environment || {}))
|
for (const [name, value] of Object.entries(environment || {}))
|
||||||
if (CODEX_LOGIN_ENV.test(name) && value !== undefined) env[name] = value;
|
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_UUID_RE,
|
||||||
RADIO_FALLBACK_MIRRORS,
|
RADIO_FALLBACK_MIRRORS,
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
export async function mapRadioConcurrent(values, concurrency, mapper) {
|
async function mapRadioConcurrent(values, concurrency, mapper) {
|
||||||
const results = new Array(values.length);
|
const results = new Array(values.length);
|
||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
const workers = Array.from(
|
const workers = Array.from(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export {
|
export {
|
||||||
createTransitHistory,
|
createTransitHistory,
|
||||||
TRANSIT_HISTORY_LIMITS,
|
|
||||||
validTransitIdentifier,
|
validTransitIdentifier,
|
||||||
} from '../../src/sources/transitHistoryStore.js';
|
} from '../../src/sources/transitHistoryStore.js';
|
||||||
@@ -3,7 +3,7 @@ import { decodeWindGribMessage } from './decode.js';
|
|||||||
import { resampleWeatherSnapshot, weatherScalarMetadata } from './grid.js';
|
import { resampleWeatherSnapshot, weatherScalarMetadata } from './grid.js';
|
||||||
|
|
||||||
/** Keyless ECMWF Open Data root for real-time IFS forecasts. */
|
/** 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.
|
* 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;
|
export const MAX_VERTICES = 512;
|
||||||
/** Two clicks closer than this are one vertex: a double-click to finish must not add a stray point. */
|
/** 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. */
|
/** 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;
|
const MIN_PATH_LENGTH_M = 1;
|
||||||
export const MIN_AREA_M2 = 1;
|
const MIN_AREA_M2 = 1;
|
||||||
|
|
||||||
/** @param {string} shape @returns {'area'|'line'|'pin'} */
|
/** @param {string} shape @returns {'area'|'line'|'pin'} */
|
||||||
export function normalizeShape(shape) {
|
export function normalizeShape(shape) {
|
||||||
@@ -50,7 +50,7 @@ export function createDrawSession(shape = 'area') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Great-circle distance in metres between two {lon, lat} points. */
|
/** Great-circle distance in metres between two {lon, lat} points. */
|
||||||
export function greatCircleM(a, b) {
|
function greatCircleM(a, b) {
|
||||||
return greatCircleMeters(a, b);
|
return greatCircleMeters(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import {
|
|||||||
|
|
||||||
import { msg, registerUnsavedWork, t, translateKnown } from '../i18n/index.js';
|
import { msg, registerUnsavedWork, t, translateKnown } from '../i18n/index.js';
|
||||||
/** The id this tool claims the pointer under. */
|
/** 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 PREVIEW_DATA_SOURCE_NAME = 'gev-draw-preview';
|
||||||
|
|
||||||
const COLORS = ['primary', 'amber', 'cyan', 'green', 'red'];
|
const COLORS = ['primary', 'amber', 'cyan', 'green', 'red'];
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { unavailablePlaceSearch } from '../search/placeSearch.js';
|
|||||||
import { isPickedWorldPosition } from '../data/scenePick.js';
|
import { isPickedWorldPosition } from '../data/scenePick.js';
|
||||||
|
|
||||||
/** Ms the analyst region lookup waits on geocode + admin boundary. */
|
/** 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. */
|
/** Entity facts that rule out a state/county reading of the ask. */
|
||||||
const NON_ADMIN_ENTITY_KINDS = new Set([
|
const NON_ADMIN_ENTITY_KINDS = new Set([
|
||||||
|
|||||||
+3
-3
@@ -15,9 +15,9 @@ import * as Cesium from 'cesium';
|
|||||||
import { announceNavigationAuthority } from '../navigationPolicy.js';
|
import { announceNavigationAuthority } from '../navigationPolicy.js';
|
||||||
import { annotationsFromParams, createView } from '../view/index.js';
|
import { annotationsFromParams, createView } from '../view/index.js';
|
||||||
|
|
||||||
export const EMBED_VIEW_MESSAGE = 'gev:view';
|
const EMBED_VIEW_MESSAGE = 'gev:view';
|
||||||
export const EMBED_APPLIED_MESSAGE = 'gev:view-applied';
|
const EMBED_APPLIED_MESSAGE = 'gev:view-applied';
|
||||||
export const EMBED_READY_MESSAGE = 'gev:ready';
|
const EMBED_READY_MESSAGE = 'gev:ready';
|
||||||
|
|
||||||
const FOLLOW_LAYERS = {
|
const FOLLOW_LAYERS = {
|
||||||
aircraft: 'flights',
|
aircraft: 'flights',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const CATALOG_SOURCE_CONTRACTS = Object.freeze({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** An omitted provider is unavailable, never an authoritative empty dataset. */
|
/** An omitted provider is unavailable, never an authoritative empty dataset. */
|
||||||
export class SourceUnavailableError extends Error {
|
class SourceUnavailableError extends Error {
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
super(`Data source not configured: ${name}`);
|
super(`Data source not configured: ${name}`);
|
||||||
this.name = 'SourceUnavailableError';
|
this.name = 'SourceUnavailableError';
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import { ownCameraArrival } from './data/cameraArrival.js';
|
|||||||
/** Minimum eye height above the rendered surface for a usable view. */
|
/** Minimum eye height above the rendered surface for a usable view. */
|
||||||
export const MIN_EYE_CLEARANCE_M = 120;
|
export const MIN_EYE_CLEARANCE_M = 120;
|
||||||
/** Retries while tiles stream in; sampling fails until the mesh exists. */
|
/** Retries while tiles stream in; sampling fails until the mesh exists. */
|
||||||
export const GUARD_ATTEMPTS = 8;
|
const GUARD_ATTEMPTS = 8;
|
||||||
export const GUARD_INTERVAL_MS = 600;
|
const GUARD_INTERVAL_MS = 600;
|
||||||
/** Ignore sub-metre noise rather than nudging the camera forever. */
|
/** Ignore sub-metre noise rather than nudging the camera forever. */
|
||||||
const GUARD_EPSILON_M = 2;
|
const GUARD_EPSILON_M = 2;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function runCctvLayerEnableFocus({
|
|||||||
* @param {Object} [after={}] Ownership immediately after the await.
|
* @param {Object} [after={}] Ownership immediately after the await.
|
||||||
* @returns {{ trackedEntity: *, cockpitActive: boolean }} Conservative ownership.
|
* @returns {{ trackedEntity: *, cockpitActive: boolean }} Conservative ownership.
|
||||||
*/
|
*/
|
||||||
export function mergeCctvEnableOwnership(before = {}, after = {}) {
|
function mergeCctvEnableOwnership(before = {}, after = {}) {
|
||||||
return {
|
return {
|
||||||
trackedEntity: before.trackedEntity || after.trackedEntity || null,
|
trackedEntity: before.trackedEntity || after.trackedEntity || null,
|
||||||
cockpitActive: !!(before.cockpitActive || after.cockpitActive),
|
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
|
* 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.
|
* active. It owns no Cesium fog/post-process stages and cannot affect map mode.
|
||||||
*/
|
*/
|
||||||
export class CockpitCloudEffectsController {
|
class CockpitCloudEffectsController {
|
||||||
constructor(viewer, { weatherService = applicationServices.weather } = {}) {
|
constructor(viewer, { weatherService = applicationServices.weather } = {}) {
|
||||||
this.weatherService = weatherService;
|
this.weatherService = weatherService;
|
||||||
this.viewer = viewer;
|
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. */
|
/** 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;
|
if (!Array.isArray(ring) || ring.length < 3) return false;
|
||||||
let inside = false;
|
let inside = false;
|
||||||
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
|
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
|
* @param {Object} body
|
||||||
* @returns {string|null}
|
* @returns {string|null}
|
||||||
*/
|
*/
|
||||||
export function aisEnvelopeMmsi(envelope, body = {}) {
|
function aisEnvelopeMmsi(envelope, body = {}) {
|
||||||
const metadata = envelope?.MetaData || envelope?.Metadata || {};
|
const metadata = envelope?.MetaData || envelope?.Metadata || {};
|
||||||
const raw = metadata.MMSI ?? body.UserID ?? body.UserId ?? body.Mmsi;
|
const raw = metadata.MMSI ?? body.UserID ?? body.UserId ?? body.Mmsi;
|
||||||
if (raw === null || raw === undefined) return null;
|
if (raw === null || raw === undefined) return null;
|
||||||
|
|||||||
+1
-21
@@ -46,28 +46,8 @@ export const AIS_WATCHDOG_DEFAULTS = Object.freeze({
|
|||||||
authProbeMs: 3_600_000,
|
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. */
|
/** Failure classes. Only 'transport' is allowed to walk the fast ladder. */
|
||||||
export const AIS_FAILURE_KINDS = Object.freeze([
|
const AIS_FAILURE_KINDS = Object.freeze(['transport', 'auth', 'rate-limit']);
|
||||||
'transport',
|
|
||||||
'auth',
|
|
||||||
'rate-limit',
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** Statuses that must not flip back to a hopeful 'connecting' on a retry. */
|
/** Statuses that must not flip back to a hopeful 'connecting' on a retry. */
|
||||||
const QUIET_TERMINAL = new Set(['down', 'auth-failed']);
|
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). */
|
/** Derived per-record field for layers with a time field (hours before now). */
|
||||||
const AGE_FIELD = 'ageHours';
|
const AGE_FIELD = 'ageHours';
|
||||||
/** Spatial scope kinds `query()` accepts. */
|
/** Spatial scope kinds `query()` accepts. */
|
||||||
export const ANALYST_SCOPE_KINDS = Object.freeze([
|
const ANALYST_SCOPE_KINDS = Object.freeze([
|
||||||
'view',
|
'view',
|
||||||
'region',
|
'region',
|
||||||
'radius',
|
'radius',
|
||||||
@@ -74,7 +74,7 @@ export const ANALYST_SCOPE_KINDS = Object.freeze([
|
|||||||
* @param {string[]} layerKeys Queried layer ids.
|
* @param {string[]} layerKeys Queried layer ids.
|
||||||
* @returns {string[]} Field names, common fields first.
|
* @returns {string[]} Field names, common fields first.
|
||||||
*/
|
*/
|
||||||
export function analystFieldsFor(layerKeys) {
|
function analystFieldsFor(layerKeys) {
|
||||||
const fields = new Set(COMMON_FIELDS);
|
const fields = new Set(COMMON_FIELDS);
|
||||||
for (const key of layerKeys) {
|
for (const key of layerKeys) {
|
||||||
const layer = ANALYST_LAYERS[key];
|
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 });
|
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', …}. */
|
/** Scope records spatially. scope: {kind:'view'|'region'|'radius'|'anywhere', …}. */
|
||||||
export function applyScope(records, scope, resolved) {
|
export function applyScope(records, scope, resolved) {
|
||||||
if (!scope || scope.kind === 'anywhere') return records;
|
if (!scope || scope.kind === 'anywhere') return records;
|
||||||
@@ -191,7 +153,7 @@ const COMMON_UNITS = Object.freeze({
|
|||||||
* @param {string} field Field name.
|
* @param {string} field Field name.
|
||||||
* @returns {'number'|'text'|'flag'|'time'|null} Field type.
|
* @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 (!analystFieldsFor(layerKeys).includes(field)) return null;
|
||||||
if (COMMON_TYPES[field]) return COMMON_TYPES[field];
|
if (COMMON_TYPES[field]) return COMMON_TYPES[field];
|
||||||
for (const key of layerKeys) {
|
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 { INCIDENT_OVERVIEW_PLACES } from './bhoteKoshiIncidentPlaces.js';
|
||||||
import { t, msg, getLocale } from '../i18n/index.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 =
|
export const BHOTE_KOSHI_LOCATOR_OVERLAY_SOURCE_ID =
|
||||||
'bhote-koshi-locator-callout';
|
'bhote-koshi-locator-callout';
|
||||||
export const NEPAL_CONTEXT_PRESENTATION = 'nepal-context';
|
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_ID,
|
||||||
BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS,
|
BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS,
|
||||||
} from '../layers/bikeshare/index.js';
|
} from '../layers/bikeshare/index.js';
|
||||||
export default layer;
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const EARTH_RADIUS_M = 6371000;
|
const EARTH_RADIUS_M = 6371000;
|
||||||
export const PLANE_VERT_ASPECT = 16 / 9;
|
const PLANE_VERT_ASPECT = 16 / 9;
|
||||||
export const SUPPORT_KEYS = Object.freeze([
|
export const SUPPORT_KEYS = Object.freeze([
|
||||||
'bl',
|
'bl',
|
||||||
'bm',
|
'bm',
|
||||||
@@ -128,7 +128,7 @@ export function planeSupportPoints(pose) {
|
|||||||
* @param {number} capAltM @param {-1|0|1} row @param {{upVert:number}} dims
|
* @param {number} capAltM @param {-1|0|1} row @param {{upVert:number}} dims
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function supportAltitude(capAltM, row, dims) {
|
function supportAltitude(capAltM, row, dims) {
|
||||||
return capAltM + row * dims.upVert;
|
return capAltM + row * dims.upVert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -94,7 +94,7 @@ export function incumbentRankKm(
|
|||||||
export const CCTV_CARD_CENTER_WEIGHT = 0.5;
|
export const CCTV_CARD_CENTER_WEIGHT = 0.5;
|
||||||
|
|
||||||
/** Robust pool-distance percentile used to scale the screen-space term. */
|
/** 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. */
|
/** True only when both viewport dimensions are finite positive pixels. */
|
||||||
export function hasFiniteCctvViewport(viewW, viewH) {
|
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
|
// viewport is bucketed into this grid and every occupied cell gets its best
|
||||||
// candidate before global rank fills the rest. Owner-tunable together with
|
// candidate before global rank fills the rest. Owner-tunable together with
|
||||||
// the budgets above.
|
// the budgets above.
|
||||||
export const CCTV_CARD_GRID_COLS = 5;
|
const CCTV_CARD_GRID_COLS = 5;
|
||||||
export const CCTV_CARD_GRID_ROWS = 4;
|
const CCTV_CARD_GRID_ROWS = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Screen-space distribution pass (owner round 2, item C). Buckets the
|
* 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).
|
* follows DATA_SOURCES.md (live sources, then bundled snapshots).
|
||||||
* @type {{ key: string, html: string }[]}
|
* @type {{ key: string, html: string }[]}
|
||||||
*/
|
*/
|
||||||
export const OSM_CREDIT = {
|
const OSM_CREDIT = {
|
||||||
key: 'openstreetmap',
|
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>)',
|
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',
|
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>',
|
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. */
|
/** Negative means a is a better deterministic contender than b. */
|
||||||
export function compareCohortContenders(a, b) {
|
function compareCohortContenders(a, b) {
|
||||||
const priorityDelta =
|
const priorityDelta =
|
||||||
(Number(b?._cohortPriority) || 0) - (Number(a?._cohortPriority) || 0);
|
(Number(b?._cohortPriority) || 0) - (Number(a?._cohortPriority) || 0);
|
||||||
if (priorityDelta) return priorityDelta;
|
if (priorityDelta) return priorityDelta;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
* module so density cannot drift into contradictory mode semantics again.
|
* 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_ELASTIC = 'ELASTIC';
|
||||||
export const ALLOCATION_WEIGHTED = 'WEIGHTED';
|
export const ALLOCATION_WEIGHTED = 'WEIGHTED';
|
||||||
export const ALLOCATION_STRATEGIES = Object.freeze([
|
export const ALLOCATION_STRATEGIES = Object.freeze([
|
||||||
@@ -118,7 +117,7 @@ export function detectionHorizontalSector(screenX, viewportWidth) {
|
|||||||
return 'front';
|
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 }),
|
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 }),
|
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 }),
|
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';
|
import { createDirectionsLayer } from '../layers/directions/index.js';
|
||||||
|
|
||||||
/** The shared scene owners every Directions instance runs on. */
|
/** The shared scene owners every Directions instance runs on. */
|
||||||
export const directionsServices = Object.freeze({
|
const directionsServices = Object.freeze({
|
||||||
credits,
|
credits,
|
||||||
render,
|
render,
|
||||||
sprites,
|
sprites,
|
||||||
@@ -49,15 +49,11 @@ export {
|
|||||||
DIRECTIONS_MODES,
|
DIRECTIONS_MODES,
|
||||||
DIRECTIONS_POINTER_OWNER,
|
DIRECTIONS_POINTER_OWNER,
|
||||||
DIRECTIONS_ROUTE_COLOR,
|
DIRECTIONS_ROUTE_COLOR,
|
||||||
DIRECTIONS_STEP_OVERLAY_SOURCE_ID,
|
|
||||||
DIRECTIONS_STEP_OVERLAY_SOURCE_OPTIONS,
|
|
||||||
FLIGHT_PROGRESS_MS,
|
|
||||||
POINTER_TOOL_EXITS,
|
POINTER_TOOL_EXITS,
|
||||||
STEP_ANCHOR_DEADLINE_MS,
|
STEP_ANCHOR_DEADLINE_MS,
|
||||||
STEP_ANCHOR_FAST_ATTEMPTS,
|
STEP_ANCHOR_FAST_ATTEMPTS,
|
||||||
STEP_ANCHOR_RETRY_MS,
|
STEP_ANCHOR_RETRY_MS,
|
||||||
STEP_ANCHOR_SLOW_RETRY_MS,
|
STEP_ANCHOR_SLOW_RETRY_MS,
|
||||||
createDirectionsLayer,
|
|
||||||
createDirectionsStepOverlayEntry,
|
createDirectionsStepOverlayEntry,
|
||||||
directionsRequestUrl,
|
directionsRequestUrl,
|
||||||
directionsRowControls,
|
directionsRowControls,
|
||||||
|
|||||||
@@ -8,4 +8,3 @@ export function createEarthquakesLayer({
|
|||||||
} = {}) {
|
} = {}) {
|
||||||
return createApplicationEarthquakes({ source, ...options });
|
return createApplicationEarthquakes({ source, ...options });
|
||||||
}
|
}
|
||||||
export default createEarthquakesLayer();
|
|
||||||
@@ -5,9 +5,9 @@ import { createImportedPins } from './geoImportPins.js';
|
|||||||
import { msg } from '../i18n/marker.js';
|
import { msg } from '../i18n/marker.js';
|
||||||
|
|
||||||
/** Largest file accepted, in bytes. */
|
/** 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. */
|
/** 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. */
|
/** Most files on the globe at once. */
|
||||||
export const MAX_IMPORTED_FILES = 12;
|
export const MAX_IMPORTED_FILES = 12;
|
||||||
/** Most entities one file may create before it is refused. */
|
/** 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. */
|
/** Most vertices (points of every line, ring and marker) in one file. */
|
||||||
export const MAX_IMPORT_POSITIONS = 250_000;
|
export const MAX_IMPORT_POSITIONS = 250_000;
|
||||||
/** Most normalized style definitions and local image links in one KML/KMZ. */
|
/** 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. */
|
/** 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. */
|
/** What the file picker offers. */
|
||||||
export const GEO_FILE_ACCEPT = '.geojson,.json,.kml,.kmz,.gpx';
|
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.
|
// 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;
|
const MAX_SIDE = 8192;
|
||||||
|
|
||||||
/** Width and height from a supported raster header; no image is decoded. */
|
/** Width and height from a supported raster header; no image is decoded. */
|
||||||
|
|||||||
@@ -35,14 +35,14 @@
|
|||||||
import { PbfReader } from 'pbf';
|
import { PbfReader } from 'pbf';
|
||||||
|
|
||||||
/** VehiclePosition.VehicleStopStatus enum → label. */
|
/** VehiclePosition.VehicleStopStatus enum → label. */
|
||||||
export const VEHICLE_STOP_STATUS = Object.freeze({
|
const VEHICLE_STOP_STATUS = Object.freeze({
|
||||||
0: 'INCOMING_AT',
|
0: 'INCOMING_AT',
|
||||||
1: 'STOPPED_AT',
|
1: 'STOPPED_AT',
|
||||||
2: 'IN_TRANSIT_TO',
|
2: 'IN_TRANSIT_TO',
|
||||||
});
|
});
|
||||||
|
|
||||||
/** VehiclePosition.OccupancyStatus enum → label. */
|
/** VehiclePosition.OccupancyStatus enum → label. */
|
||||||
export const OCCUPANCY_STATUS = Object.freeze({
|
const OCCUPANCY_STATUS = Object.freeze({
|
||||||
0: 'EMPTY',
|
0: 'EMPTY',
|
||||||
1: 'MANY_SEATS_AVAILABLE',
|
1: 'MANY_SEATS_AVAILABLE',
|
||||||
2: 'FEW_SEATS_AVAILABLE',
|
2: 'FEW_SEATS_AVAILABLE',
|
||||||
@@ -64,7 +64,6 @@ export const GTFS_MAX_ENTITIES = 50_000;
|
|||||||
export const GTFS_MAX_STRING_CHARS = 256;
|
export const GTFS_MAX_STRING_CHARS = 256;
|
||||||
/** FeedHeader.Incrementality: only FULL_DATASET carries a complete snapshot. */
|
/** FeedHeader.Incrementality: only FULL_DATASET carries a complete snapshot. */
|
||||||
export const GTFS_INCREMENTALITY_FULL_DATASET = 0;
|
export const GTFS_INCREMENTALITY_FULL_DATASET = 0;
|
||||||
export const GTFS_INCREMENTALITY_DIFFERENTIAL = 1;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a length-delimited string, refusing one that is implausibly long.
|
* Read a length-delimited string, refusing one that is implausibly long.
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
import { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
||||||
export { localGeoJsonServices } from '../app/localGeojsonServices.js';
|
|
||||||
import {
|
import {
|
||||||
createLocalGeoJsonLayer as createLayer,
|
createLocalGeoJsonLayer as createLayer,
|
||||||
createLocalInfrastructureOverlayPublisher as createPublisher,
|
createLocalInfrastructureOverlayPublisher as createPublisher,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { createRetryableLoader } from './retryableLoad.js';
|
|||||||
export const MILITARY_POINT_CAP = 512;
|
export const MILITARY_POINT_CAP = 512;
|
||||||
export const MILITARY_LABEL_CAP = 24;
|
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. */
|
/** Share a bounded acquisition; failed assets become retryable after cooldown. */
|
||||||
export function createMilitaryNamesLoader({
|
export function createMilitaryNamesLoader({
|
||||||
|
|||||||
@@ -117,8 +117,3 @@ export function normalizeRadioCountryInput(value) {
|
|||||||
name: canonicalCountryName(code),
|
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,
|
createCableReferenceSweepGate,
|
||||||
updateCableReferenceStem,
|
updateCableReferenceStem,
|
||||||
} from '../layers/submarineCables/index.js';
|
} from '../layers/submarineCables/index.js';
|
||||||
export default createTeleGeographySubmarineCableLayer();
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { createSourceSlot } from '../sources/sourceSlot.js';
|
import { createSourceSlot } from '../sources/sourceSlot.js';
|
||||||
import { createWindLayer as createLayer } from '../layers/wind/index.js';
|
|
||||||
import { createWindSource } from '../layers/wind/source.js';
|
import { createWindSource } from '../layers/wind/source.js';
|
||||||
|
|
||||||
const sourceSlot = createSourceSlot(
|
const sourceSlot = createSourceSlot(
|
||||||
@@ -8,10 +7,3 @@ const sourceSlot = createSourceSlot(
|
|||||||
'Wind source',
|
'Wind source',
|
||||||
);
|
);
|
||||||
export const configureWindSource = sourceSlot.configure;
|
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. */
|
/** Collapse markup whitespace so a message reads the same in HTML and JS. */
|
||||||
export function normalizeMarkupText(text) {
|
function normalizeMarkupText(text) {
|
||||||
return String(text ?? '')
|
return String(text ?? '')
|
||||||
.replace(/\s+/g, ' ')
|
.replace(/\s+/g, ' ')
|
||||||
.trim();
|
.trim();
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { QUERY_LIMIT, QUERY_SNAP_DEGREES } from './policy.js';
|
import { QUERY_LIMIT, QUERY_SNAP_DEGREES } from './policy.js';
|
||||||
|
|
||||||
export function textTag(value) {
|
function textTag(value) {
|
||||||
const t = String(value ?? '').trim();
|
const t = String(value ?? '').trim();
|
||||||
return t || null;
|
return t || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function numTag(value) {
|
function numTag(value) {
|
||||||
// `Number('')` and `Number(' ')` are 0, not NaN — a blank or whitespace-only
|
// `Number('')` and `Number(' ')` are 0, not NaN — a blank or whitespace-only
|
||||||
// tag value must read as missing, never as a due-north bearing.
|
// tag value must read as missing, never as a due-north bearing.
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
@@ -102,7 +102,7 @@ export function normalizeAlprNode(el) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeDirection(value) {
|
function normalizeDirection(value) {
|
||||||
const degrees = numTag(value);
|
const degrees = numTag(value);
|
||||||
return degrees != null && degrees >= 0 && degrees <= 360
|
return degrees != null && degrees >= 0 && degrees <= 360
|
||||||
? degrees % 360
|
? degrees % 360
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { VectorTile } from '@mapbox/vector-tile';
|
|||||||
import { normalizeAlprNode } from './records.js';
|
import { normalizeAlprNode } from './records.js';
|
||||||
|
|
||||||
/** Map extract attributes without treating an OSM edit date as a field verification. */
|
/** Map extract attributes without treating an OSM edit date as a field verification. */
|
||||||
export function normalizeAlprTileFeature(feature) {
|
function normalizeAlprTileFeature(feature) {
|
||||||
const p = feature?.properties || {};
|
const p = feature?.properties || {};
|
||||||
if (feature?.geometry?.type !== 'Point' || p.osmType !== 'node') return null;
|
if (feature?.geometry?.type !== 'Point' || p.osmType !== 'node') return null;
|
||||||
const [lon, lat] = feature.geometry.coordinates;
|
const [lon, lat] = feature.geometry.coordinates;
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
export const AIRCRAFT_DEPENDENCIES = ['flights', 'military'];
|
export const AIRCRAFT_DEPENDENCIES = ['flights', 'military'];
|
||||||
|
|
||||||
export const DEFERRED_DEPENDENCIES = [
|
const DEFERRED_DEPENDENCIES = ['ais-live-vessels', 'military-installations'];
|
||||||
'ais-live-vessels',
|
|
||||||
'military-installations',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const DEPENDENCIES = [
|
export const DEPENDENCIES = [
|
||||||
...AIRCRAFT_DEPENDENCIES,
|
...AIRCRAFT_DEPENDENCIES,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS = Object.freeze({
|
|||||||
// --- Activation / display thresholds ---
|
// --- Activation / display thresholds ---
|
||||||
/** Altitude (m) at which bikeshare layer becomes eligible for display. */
|
/** 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. */
|
/** Hysteresis enter threshold — layer activates when camera drops below this. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
export {
|
export { ACTIVE_FRAME_REFRESH_MS } from './sourcePolicy.js';
|
||||||
FRAME_ENDPOINT,
|
|
||||||
SOURCE_ENDPOINT,
|
|
||||||
HEALTH_ENDPOINT,
|
|
||||||
MEDIA_ENDPOINT,
|
|
||||||
ACTIVE_FRAME_REFRESH_MS,
|
|
||||||
} from './sourcePolicy.js';
|
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { CCTV_CARD_FETCH_BURST_SPACING_MS } from '../../data/cctvCards.js';
|
import { CCTV_CARD_FETCH_BURST_SPACING_MS } from '../../data/cctvCards.js';
|
||||||
import { CCTV_AMBIENT_CARD_MAX } from '../../data/cctvLod.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_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
|
// V2 frustum geometry (design §2a/§6): the far-cap center + corners never sink
|
||||||
// below groundAlt + this clearance, so a fabricated pitch (-24°) cannot bury
|
// below groundAlt + this clearance, so a fabricated pitch (-24°) cannot bury
|
||||||
// the monitor plane in the 3D tiles. Exported for the unit suite.
|
// 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 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 MEDIA_ENDPOINT = '/api/cctv/media';
|
||||||
|
|
||||||
export const ACTIVE_FRAME_REFRESH_MS = 10000;
|
export const ACTIVE_FRAME_REFRESH_MS = 10000;
|
||||||
@@ -64,7 +64,7 @@ const SHARED_CARD = Object.freeze({
|
|||||||
* @param {boolean} selected
|
* @param {boolean} selected
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
export function cycloneStormEntry(storm, selected) {
|
function cycloneStormEntry(storm, selected) {
|
||||||
return {
|
return {
|
||||||
...SHARED_CARD,
|
...SHARED_CARD,
|
||||||
...markerClearance(
|
...markerClearance(
|
||||||
@@ -92,7 +92,7 @@ export function cycloneStormEntry(storm, selected) {
|
|||||||
* @param {{tauHours:number,position:object}} point
|
* @param {{tauHours:number,position:object}} point
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
export function cycloneLeadEntry(stormId, point) {
|
function cycloneLeadEntry(stormId, point) {
|
||||||
return {
|
return {
|
||||||
...SHARED_CARD,
|
...SHARED_CARD,
|
||||||
...markerClearance(CYCLONE_MARKER_PX.forecast),
|
...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 * as Cesium from 'cesium';
|
||||||
import { EMERGENCY_TINT_CSS } from '../../data/aircraftEmergency.js';
|
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 REQUEST_DEBOUNCE_MS = 180;
|
||||||
|
|
||||||
export const MAX_VIEWPORT_DEGREES = 10;
|
|
||||||
|
|
||||||
export const MAX_RENDERED = 700;
|
export const MAX_RENDERED = 700;
|
||||||
|
|
||||||
export const GOOGLE_MILITARY_PLACE_TYPES = new Set(['military_base']);
|
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`. */
|
/** Same-origin route served by `server/providers/local-receivers.js`. */
|
||||||
export const LOCAL_RECEIVERS_URL = '/api/local-receivers/aircraft';
|
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
|
* 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.
|
* 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() {
|
function abortError() {
|
||||||
const error = new Error('Local receiver request aborted');
|
const error = new Error('Local receiver request aborted');
|
||||||
|
|||||||
@@ -36,18 +36,8 @@ import {
|
|||||||
} from './policy.js';
|
} from './policy.js';
|
||||||
import { localAdsbStatus } from './status.js';
|
import { localAdsbStatus } from './status.js';
|
||||||
|
|
||||||
export {
|
export { HEARD_BY_RECEIVER } from './policy.js';
|
||||||
LAYER_ID as LOCAL_ADSB_LAYER_ID,
|
export { localAdsbCardModel, localAdsbReceiverLine } from './card.js';
|
||||||
HEARD_BY_RECEIVER,
|
|
||||||
} from './policy.js';
|
|
||||||
export {
|
|
||||||
localAdsbCardModel,
|
|
||||||
localAdsbClassLine,
|
|
||||||
localAdsbIsUat,
|
|
||||||
localAdsbReceiverLine,
|
|
||||||
localAdsbTitle,
|
|
||||||
} from './card.js';
|
|
||||||
export { localAdsbClass } from './enrichment.js';
|
|
||||||
export { localAdsbStatus } from './status.js';
|
export { localAdsbStatus } from './status.js';
|
||||||
|
|
||||||
const FEET_TO_METERS = 0.3048;
|
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_MS = 10 * 60_000;
|
||||||
export const LOCAL_ADSB_HISTORY_POINTS = 600;
|
export const LOCAL_ADSB_HISTORY_POINTS = 600;
|
||||||
/** A display correction larger than this snaps instead of sliding. */
|
/** 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. */
|
/** Chord course uses the newest fix at least this much older than the anchor. */
|
||||||
const CHORD_BASELINE_MS = 2_000;
|
const CHORD_BASELINE_MS = 2_000;
|
||||||
/** Turn rate is estimated over this window of heard fixes. */
|
/** 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.
|
* @param {object[]} feeds Feed statuses from the route.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function describeFeedProblems(feeds) {
|
function describeFeedProblems(feeds) {
|
||||||
const byStatus = new Map();
|
const byStatus = new Map();
|
||||||
for (const feed of feeds) {
|
for (const feed of feeds) {
|
||||||
if (feed?.status === 'live') continue;
|
if (feed?.status === 'live') continue;
|
||||||
@@ -41,7 +41,7 @@ export function describeFeedProblems(feeds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Whether the server reported at least one configured feed. */
|
/** Whether the server reported at least one configured feed. */
|
||||||
export function feedsConfigured(feedState) {
|
function feedsConfigured(feedState) {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
feedState?.configured === true &&
|
feedState?.configured === true &&
|
||||||
Array.isArray(feedState.feeds) &&
|
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 * as Cesium from 'cesium';
|
||||||
import { EMERGENCY_TINT_CSS } from '../../data/aircraftEmergency.js';
|
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 SELECTED_LIFT_M = 5;
|
||||||
|
|
||||||
export const RADIO_PICK_TOLERANCE_PX = 8;
|
const RADIO_PICK_TOLERANCE_PX = 8;
|
||||||
|
|
||||||
export const RADIO_TUNER_DIRECTORY_LIMIT = 750;
|
export const RADIO_TUNER_DIRECTORY_LIMIT = 750;
|
||||||
|
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ export function hhmm(iso) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Candidate key: `PRODUCT:YYYY-MM-DD`. */
|
/** Candidate key: `PRODUCT:YYYY-MM-DD`. */
|
||||||
export function candidateKey(product, day) {
|
function candidateKey(product, day) {
|
||||||
return `${product}:${day}`;
|
return `${product}:${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ export const TRACK_VIEW_FROM_HIGH_SCALE = 4;
|
|||||||
* attribute, so classification costs nothing per frame.
|
* 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));
|
Cesium.Color.fromCssColorString(satelliteClassColor(group));
|
||||||
|
|
||||||
export const POINT_STYLES = {
|
export const POINT_STYLES = {
|
||||||
|
|||||||
@@ -17,18 +17,18 @@ const EXPANDED_SELECTORS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/** A photo download (tens of kilobytes to a few megabytes). */
|
/** 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). */
|
/** 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_BYTES = 48 * 1024 * 1024;
|
||||||
export const MAX_IMAGE_SIDE = 16_384;
|
export const MAX_IMAGE_SIDE = 16_384;
|
||||||
/** 64 MP: 256 MB once decoded to RGBA. A 16,384 px square is four times that. */
|
/** 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). */
|
/** Where in a file to look for its dimensions (a JPEG's EXIF comes first). */
|
||||||
const HEADER_BYTES = 256 * 1024;
|
const HEADER_BYTES = 256 * 1024;
|
||||||
|
|
||||||
/** Whether a picture this size is past what the viewer decodes. */
|
/** Whether a picture this size is past what the viewer decodes. */
|
||||||
export function tooManyPixels(width, height) {
|
function tooManyPixels(width, height) {
|
||||||
return (
|
return (
|
||||||
width > MAX_IMAGE_SIDE ||
|
width > MAX_IMAGE_SIDE ||
|
||||||
height > 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. */
|
/** 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]);
|
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
|
* A picture refused for its size (`tooLarge`): the viewer will not load
|
||||||
* that file another way, an <img> having no size limit at all.
|
* 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 });
|
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';
|
export const KARTAVIEW_API_HOST = 'https://api.kartaview.org';
|
||||||
/** KartaView's image proxy: resizes a photo given its base64url address. */
|
/** 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:`. */
|
/** Stable id prefixes for picked primitives; every one starts with `kv:`. */
|
||||||
export const PICK_PREFIX = Object.freeze({
|
export const PICK_PREFIX = Object.freeze({
|
||||||
@@ -140,8 +140,6 @@ export const MASK_SLOW_MESSAGE = msg(
|
|||||||
export const FILTERED_OUT_MESSAGE = msg(
|
export const FILTERED_OUT_MESSAGE = msg(
|
||||||
'None of KartaView’s photos here pass the imagery filter',
|
'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. */
|
/** The UTC calendar day of `ms`, as KartaView's startDate reads it. */
|
||||||
function utcDay(ms) {
|
function utcDay(ms) {
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ import {
|
|||||||
normalizeItem,
|
normalizeItem,
|
||||||
} from './policy.js';
|
} 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
|
* 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
|
* (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';
|
export const PANORAMAX_API = 'https://api.panoramax.xyz/api';
|
||||||
/** The meta-catalogue's web viewer, where the caption link goes. */
|
/** 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:`. */
|
/** Stable id prefixes for picked primitives; every one starts with `pnx:`. */
|
||||||
export const PICK_PREFIX = Object.freeze({
|
export const PICK_PREFIX = Object.freeze({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { encodeIdSwitches } from '../../data/idSwitches.js';
|
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) {
|
export function filtersOf(provider) {
|
||||||
return provider?.filters ?? IMAGERY_FILTERS;
|
return provider?.filters ?? IMAGERY_FILTERS;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { createHorizonCull, metresBetween } from './view.js';
|
|||||||
import { msg } from '../../i18n/index.js';
|
import { msg } from '../../i18n/index.js';
|
||||||
|
|
||||||
/** Cones closer than this to the previous kept one are dropped. */
|
/** Cones closer than this to the previous kept one are dropped. */
|
||||||
export const IMAGE_CONE_MIN_SPACING_M = 3;
|
const IMAGE_CONE_MIN_SPACING_M = 3;
|
||||||
export const IMAGE_CONE_SIZE_PX = 26;
|
const IMAGE_CONE_SIZE_PX = 26;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} ConeImage One image of a sequence, as the cones draw it.
|
* @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. */
|
/** Decoded coverage tiles kept, and for how long. */
|
||||||
export const TILE_CACHE_SIZE = 24;
|
const TILE_CACHE_SIZE = 24;
|
||||||
export const TILE_CACHE_TTL_MS = 10 * 60_000;
|
const TILE_CACHE_TTL_MS = 10 * 60_000;
|
||||||
|
|
||||||
/** Least-recently-used map: reading an entry renews it. */
|
/** Least-recently-used map: reading an entry renews it. */
|
||||||
export function createLru(limit) {
|
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).
|
* The filter controls a switched-on provider honours (both when none is on).
|
||||||
* @param {Array<ProviderSnapshot>} providers
|
* @param {Array<ProviderSnapshot>} providers
|
||||||
*/
|
*/
|
||||||
export function filterControls(providers) {
|
function filterControls(providers) {
|
||||||
const active = providers.filter((p) => p.on);
|
const active = providers.filter((p) => p.on);
|
||||||
const honoured = (key) =>
|
const honoured = (key) =>
|
||||||
active.length === 0 ||
|
active.length === 0 ||
|
||||||
@@ -78,7 +78,7 @@ export function filterControls(providers) {
|
|||||||
* note needs no provider name.
|
* note needs no provider name.
|
||||||
* @param {Array<ProviderSnapshot>} providers
|
* @param {Array<ProviderSnapshot>} providers
|
||||||
*/
|
*/
|
||||||
export function sinceNote(providers) {
|
function sinceNote(providers) {
|
||||||
return providers.some((p) => p.on && p.sinceBasis === 'upload')
|
return providers.some((p) => p.on && p.sinceBasis === 'upload')
|
||||||
? t('(by upload date)')
|
? t('(by upload date)')
|
||||||
: null;
|
: 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;
|
if (!Array.isArray(value) || value.length < 2) return null;
|
||||||
const lon = Number(value[0]);
|
const lon = Number(value[0]);
|
||||||
const lat = Number(value[1]);
|
const lat = Number(value[1]);
|
||||||
@@ -41,7 +41,7 @@ export function coordsFromProperty(value) {
|
|||||||
return [lon, lat];
|
return [lon, lat];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function coordsFromPoint(value) {
|
function coordsFromPoint(value) {
|
||||||
if (!Array.isArray(value) || value.length < 2) return null;
|
if (!Array.isArray(value) || value.length < 2) return null;
|
||||||
const lon = Number(value[0]);
|
const lon = Number(value[0]);
|
||||||
const lat = Number(value[1]);
|
const lat = Number(value[1]);
|
||||||
@@ -49,7 +49,7 @@ export function coordsFromPoint(value) {
|
|||||||
return [lon, lat];
|
return [lon, lat];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function collectLonLat(value, out) {
|
function collectLonLat(value, out) {
|
||||||
if (!Array.isArray(value)) return;
|
if (!Array.isArray(value)) return;
|
||||||
if (typeof value[0] === 'number' && typeof value[1] === 'number') {
|
if (typeof value[0] === 'number' && typeof value[1] === 'number') {
|
||||||
const lon = Number(value[0]);
|
const lon = Number(value[0]);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { tilesForBounds, tileToBBox } from '../../data/tomtomTiles.js';
|
import { tilesForBounds, tileToBBox } from '../../data/tomtomTiles.js';
|
||||||
|
|
||||||
export const RETICLE_RANGE_KM = 10;
|
const RETICLE_RANGE_KM = 10;
|
||||||
export const RETICLE_TILE_CAP = 16;
|
const RETICLE_TILE_CAP = 16;
|
||||||
export const tileKey = (tile) => `${tile.z}/${tile.x}/${tile.y}`;
|
const tileKey = (tile) => `${tile.z}/${tile.x}/${tile.y}`;
|
||||||
const cross = (a, b, c) =>
|
const cross = (a, b, c) =>
|
||||||
(b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
(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 */
|
/** @const {number} Maximum tile cache entries before LRU eviction */
|
||||||
|
|
||||||
export const TILE_CACHE_MAX_ENTRIES = 64;
|
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
|
* 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.
|
* 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
|
* 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. */
|
/** 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. */
|
/** 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'),
|
bus: msg('Bus'),
|
||||||
tram: msg('Tram'),
|
tram: msg('Tram'),
|
||||||
subway: msg('Subway'),
|
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. */
|
/** 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',
|
bus: 'BUS',
|
||||||
tram: 'TRAM',
|
tram: 'TRAM',
|
||||||
subway: 'METRO',
|
subway: 'METRO',
|
||||||
@@ -588,7 +588,7 @@ export function transitDetectionId(record, feed = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Longest route text the single-line detection label can carry. */
|
/** 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.
|
* 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
|
* @param {string} text
|
||||||
* @returns {string} Uppercase, at most DETECTION_ID_MAX_CHARS characters.
|
* @returns {string} Uppercase, at most DETECTION_ID_MAX_CHARS characters.
|
||||||
*/
|
*/
|
||||||
export function elideRouteText(text) {
|
function elideRouteText(text) {
|
||||||
const value = String(text || '').toUpperCase();
|
const value = String(text || '').toUpperCase();
|
||||||
if (value.length <= DETECTION_ID_MAX_CHARS) return value;
|
if (value.length <= DETECTION_ID_MAX_CHARS) return value;
|
||||||
return `${value.slice(0, DETECTION_ID_MAX_CHARS - 1)}\u2026`;
|
return `${value.slice(0, DETECTION_ID_MAX_CHARS - 1)}\u2026`;
|
||||||
@@ -659,6 +659,6 @@ export function transitDetectionMetric(
|
|||||||
* @param {number} [nowMs]
|
* @param {number} [nowMs]
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function transitVehicleState(entry, nowMs = Date.now()) {
|
function transitVehicleState(entry, nowMs = Date.now()) {
|
||||||
return displayMotion(entry, nowMs).word;
|
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 { VESSEL_LABEL_GRID_PX } from '../../data/vesselLabels.js';
|
||||||
import { msg } from '../../i18n/index.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).
|
// the weather layer translates them where it shows them (t / translateKnown).
|
||||||
const msg = (text) => text;
|
const msg = (text) => text;
|
||||||
|
|
||||||
export const WEATHER_PRODUCTS = Object.freeze([
|
const WEATHER_PRODUCTS = Object.freeze([
|
||||||
'radar',
|
'radar',
|
||||||
'clouds',
|
'clouds',
|
||||||
'clouds-regional',
|
'clouds-regional',
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
WIND_NARROW_PATH_LIMIT,
|
WIND_NARROW_PATH_LIMIT,
|
||||||
} from './streamlines.js';
|
} from './streamlines.js';
|
||||||
|
|
||||||
export const WIND_FADE_LOW_METERS = 15000;
|
const WIND_FADE_LOW_METERS = 15000;
|
||||||
export const WIND_FADE_HIGH_METERS = 60000;
|
const WIND_FADE_HIGH_METERS = 60000;
|
||||||
|
|
||||||
// Original material: a persistent fine curve plus a moving, tapered highlight.
|
// 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.
|
// The integer part of s identifies a path; its fractional part follows the wind.
|
||||||
|
|||||||
@@ -64,5 +64,3 @@ export function locationMiniStatus({
|
|||||||
|
|
||||||
return { ...EMPTY };
|
return { ...EMPTY };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default locationMiniStatus;
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/** Basemap that before/after imagery comparisons are read against. */
|
/** 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 SWITCH_POLICIES = new Set(['preserve', 'esri', 'esri-if-photoreal']);
|
||||||
const leases = new WeakMap();
|
const leases = new WeakMap();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const TERRAIN_RETRY_ATTEMPTS = 3;
|
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_BASE_MS = 750;
|
||||||
export const TERRAIN_RETRY_MAX_MS = 15_000;
|
export const TERRAIN_RETRY_MAX_MS = 15_000;
|
||||||
export const TERRAIN_RETRY_SPREAD_MS = 1_500;
|
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. */
|
/** 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];
|
return [...localDemoRecipes, ...PUBLIC_SCENE_RECIPES];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1004,7 +1004,7 @@ export const SCENE_APPEND_RECIPES = BHOTE_KOSHI_NEPAL_APPEND_RECIPE
|
|||||||
? [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;
|
return SCENE_RECIPES.find((recipe) => recipe.id === id) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ const READ_PIPELINE_DEPTH = 4;
|
|||||||
const RATE_WINDOW_MS = 1_000;
|
const RATE_WINDOW_MS = 1_000;
|
||||||
// A bulk read that has not completed in this long is treated as a stalled
|
// 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.
|
// 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
|
// Decoder admission. Every block sent to the worker is numbered and must be
|
||||||
// acknowledged; past these bounds the receiver stops with an overload error
|
// 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
|
// 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.
|
// 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,
|
maxBlocks: 8,
|
||||||
maxBytes: 8 * 65_536,
|
maxBytes: 8 * 65_536,
|
||||||
maxAgeMs: 1_000,
|
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 USB device is closed directly, which aborts any pending transfer. The
|
||||||
// raw close is bounded by the same deadline, so a teardown settles within
|
// raw close is bounded by the same deadline, so a teardown settles within
|
||||||
// twice this even when `USBDevice.close()` itself never settles.
|
// 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. */
|
/** Tuning presets per receiver mode; gain is a separate per-mode setting. */
|
||||||
export const SDR_RECEIVER_PRESETS = Object.freeze({
|
export const SDR_RECEIVER_PRESETS = Object.freeze({
|
||||||
@@ -1912,9 +1912,3 @@ function formatMhz(hz) {
|
|||||||
function positiveModulo(value, divisor) {
|
function positiveModulo(value, divisor) {
|
||||||
return ((value % divisor) + divisor) % 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.
|
* 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. */
|
/** R820T tuner gain steps in dB, as listed by librtlsdr. */
|
||||||
export const R820T_GAIN_STEPS_DB = Object.freeze([
|
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. */
|
/** Peak opacity of the faintest star. */
|
||||||
export const STAR_MIN_ALPHA = 0.22;
|
export const STAR_MIN_ALPHA = 0.22;
|
||||||
/** B−V for stars the catalogue gives no colour (a solar-type white). */
|
/** 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. */
|
/** Floats per star in the vertex buffer: xyz, σ, rgb, alpha. */
|
||||||
export const STAR_VERTEX_FLOATS = 8;
|
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). */
|
/** 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 t = clamp(kelvin, 1000, 40000) / 100;
|
||||||
const red = t <= 66 ? 255 : 329.698727446 * (t - 60) ** -0.1332047592;
|
const red = t <= 66 ? 255 : 329.698727446 * (t - 60) ** -0.1332047592;
|
||||||
const green =
|
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. */
|
/** 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;
|
export const LOCAL_ADSB_REFERENCE_MAX_AGE_MS = 10 * 60_000;
|
||||||
/** Speed limit (kt) for an aircraft that has not reported a ground speed. */
|
/** 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. */
|
/** 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. */
|
/** Reported ground speed is multiplied by this, plus the margin below. */
|
||||||
export const LOCAL_ADSB_SPEED_FACTOR = 1.5;
|
const LOCAL_ADSB_SPEED_FACTOR = 1.5;
|
||||||
export const LOCAL_ADSB_SPEED_MARGIN_KT = 50;
|
const LOCAL_ADSB_SPEED_MARGIN_KT = 50;
|
||||||
/** Distance always allowed between two fixes (CPR and reception error). */
|
/** Distance always allowed between two fixes (CPR and reception error). */
|
||||||
export const LOCAL_ADSB_POSITION_MARGIN_M = 500;
|
export const LOCAL_ADSB_POSITION_MARGIN_M = 500;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function simplifyRing(ring, tolM) {
|
|||||||
return out.length >= 4 ? out : ring;
|
return out.length >= 4 ? out : ring;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function douglasPeucker(points, tol) {
|
function douglasPeucker(points, tol) {
|
||||||
const n = points.length;
|
const n = points.length;
|
||||||
if (n < 3) return points.slice();
|
if (n < 3) return points.slice();
|
||||||
// Iterative, index-based (no per-call array slicing / recursion), so a large
|
// Iterative, index-based (no per-call array slicing / recursion), so a large
|
||||||
@@ -113,7 +113,7 @@ export function douglasPeucker(points, tol) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function perpDistance(p, a, b) {
|
function perpDistance(p, a, b) {
|
||||||
const dx = b[0] - a[0];
|
const dx = b[0] - a[0];
|
||||||
const dy = b[1] - a[1];
|
const dy = b[1] - a[1];
|
||||||
const len2 = dx * dx + dy * dy;
|
const len2 = dx * dx + dy * dy;
|
||||||
@@ -149,7 +149,7 @@ export function stitchRing(ways) {
|
|||||||
return endpointGapM(largest) <= allow ? largest : [];
|
return endpointGapM(largest) <= allow ? largest : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function largestByArea(components) {
|
function largestByArea(components) {
|
||||||
let best = components[0];
|
let best = components[0];
|
||||||
let bestArea = approximateAreaM2(best);
|
let bestArea = approximateAreaM2(best);
|
||||||
for (let i = 1; i < components.length; i += 1) {
|
for (let i = 1; i < components.length; i += 1) {
|
||||||
@@ -162,7 +162,7 @@ export function largestByArea(components) {
|
|||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ringSpanM(chain) {
|
function ringSpanM(chain) {
|
||||||
let minLat = Infinity;
|
let minLat = Infinity;
|
||||||
let maxLat = -Infinity;
|
let maxLat = -Infinity;
|
||||||
let minLon = Infinity;
|
let minLon = Infinity;
|
||||||
@@ -176,7 +176,7 @@ export function ringSpanM(chain) {
|
|||||||
return approximateDistanceM(minLat, minLon, maxLat, maxLon);
|
return approximateDistanceM(minLat, minLon, maxLat, maxLon);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildRingComponents(ways) {
|
function buildRingComponents(ways) {
|
||||||
const same = (a, b) =>
|
const same = (a, b) =>
|
||||||
Math.abs(a.lon - b.lon) < 1e-7 && Math.abs(a.lat - b.lat) < 1e-7;
|
Math.abs(a.lon - b.lon) < 1e-7 && Math.abs(a.lat - b.lat) < 1e-7;
|
||||||
const remaining = ways.map((w) => w.slice());
|
const remaining = ways.map((w) => w.slice());
|
||||||
@@ -207,7 +207,7 @@ export function buildRingComponents(ways) {
|
|||||||
return components;
|
return components;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function endpointGapM(chain) {
|
function endpointGapM(chain) {
|
||||||
if (!chain || chain.length < 2) return Infinity;
|
if (!chain || chain.length < 2) return Infinity;
|
||||||
const a = chain[0];
|
const a = chain[0];
|
||||||
const b = chain[chain.length - 1];
|
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
|
* OpenMapTiles folds private access into `no`. Tunnels cannot be placed on
|
||||||
* the surface above them; ramps/bridges remain valid only on a motor road.
|
* the surface above them; ramps/bridges remain valid only on a motor road.
|
||||||
*/
|
*/
|
||||||
export function isDrivableOpenMapRoad(properties = {}) {
|
function isDrivableOpenMapRoad(properties = {}) {
|
||||||
return (
|
return (
|
||||||
Object.hasOwn(ROAD_TYPES, properties.class) &&
|
Object.hasOwn(ROAD_TYPES, properties.class) &&
|
||||||
!properties.subclass &&
|
!properties.subclass &&
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { validTransitIdentifier } from './transitHistory.js';
|
import { validTransitIdentifier } from './transitHistory.js';
|
||||||
export { validTransitIdentifier } from './transitHistory.js';
|
export { validTransitIdentifier } from './transitHistory.js';
|
||||||
/** Bounded, process-local operator position history. No upstream or scene access. */
|
/** 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,
|
retentionMs: 900000,
|
||||||
fixes: 128,
|
fixes: 128,
|
||||||
feedBytes: 16 * 1024 * 1024,
|
feedBytes: 16 * 1024 * 1024,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// DOM stand-ins for the KartaView viewer's tests (no browser in Node).
|
// DOM stand-ins for the KartaView viewer's tests (no browser in Node).
|
||||||
|
|
||||||
/** A DOM element with what the viewer touches: children, events, attributes. */
|
/** A DOM element with what the viewer touches: children, events, attributes. */
|
||||||
export function fakeElement(tag) {
|
function fakeElement(tag) {
|
||||||
const listeners = new Map();
|
const listeners = new Map();
|
||||||
const element = {
|
const element = {
|
||||||
tagName: tag.toUpperCase(),
|
tagName: tag.toUpperCase(),
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const COMPRESS_MIN_BYTES = 1024;
|
|||||||
* rather than fail; past `PANEL_QUEUED_REQUESTS` waiting, they are refused.
|
* rather than fail; past `PANEL_QUEUED_REQUESTS` waiting, they are refused.
|
||||||
*/
|
*/
|
||||||
export const PANEL_CONCURRENT_REQUESTS = 6;
|
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
|
* 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';
|
import { cameraForArea, cameraLookingAt } from '../views.js';
|
||||||
|
|
||||||
/** Tool arguments that describe a view: an area to frame, or a camera. */
|
/** 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({
|
view: Object.freeze({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export function readCameraTargetFrame(viewer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Apply an orbit frame, then return the camera to Cesium's fixed-world frame. */
|
/** 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;
|
const camera = viewer?.camera;
|
||||||
if (!camera || !frame?.target || !Number.isFinite(frame.range)) return false;
|
if (!camera || !frame?.target || !Number.isFinite(frame.range)) return false;
|
||||||
try {
|
try {
|
||||||
@@ -156,7 +156,7 @@ export function setCameraTargetFrame(viewer, frame) {
|
|||||||
*
|
*
|
||||||
* @param {{pitch: number}|null} frame From {@link readCameraTargetFrame}.
|
* @param {{pitch: number}|null} frame From {@link readCameraTargetFrame}.
|
||||||
*/
|
*/
|
||||||
export function frameIsTilted(frame) {
|
function frameIsTilted(frame) {
|
||||||
const pitch = Number(frame?.pitch);
|
const pitch = Number(frame?.pitch);
|
||||||
return Number.isFinite(pitch) ? pitch > OBLIQUE_THRESHOLD : false;
|
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. */
|
/** "SNAPSHOT" / "СНИМОК"; unknown codes (HLS, MJPEG…) stay as uppercase codes. */
|
||||||
export function cctvSourceKindLabel(kind) {
|
function cctvSourceKindLabel(kind) {
|
||||||
const key = String(kind || 'unknown').toLowerCase();
|
const key = String(kind || 'unknown').toLowerCase();
|
||||||
return SOURCE_KIND_LABELS[key]
|
return SOURCE_KIND_LABELS[key]
|
||||||
? t(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. */
|
/** 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);
|
const known = translateKnown(piece);
|
||||||
if (known !== piece) return known;
|
if (known !== piece) return known;
|
||||||
return statusPatternText(piece) ?? piece;
|
return statusPatternText(piece) ?? piece;
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
import { finiteOrNull } from './pointerContext.js';
|
import { finiteOrNull } from './pointerContext.js';
|
||||||
|
|
||||||
export const POINTER_SENTINEL = 'pointer';
|
const POINTER_SENTINEL = 'pointer';
|
||||||
|
|
||||||
const TRACKABLE_LAYERS = new Set([
|
const TRACKABLE_LAYERS = new Set([
|
||||||
'flights',
|
'flights',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { progressLine } from './speech.js';
|
|||||||
* Timing policy for filling silence while tools run. Tuned against the turn
|
* Timing policy for filling silence while tools run. Tuned against the turn
|
||||||
* spans' `max_silence_ms`.
|
* 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. */
|
/** Start the earcon when nothing is audible this long after end of speech. */
|
||||||
earconAfterMs: 700,
|
earconAfterMs: 700,
|
||||||
/** First spoken progress line when a tool is still running after this. */
|
/** 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. */
|
/** 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. */
|
/** Stillness that makes a dwell point. */
|
||||||
export const POINTER_DWELL_MS = 250;
|
export const POINTER_DWELL_MS = 250;
|
||||||
/** A resting pointer stays a referent this long after it last moved. */
|
/** A resting pointer stays a referent this long after it last moved. */
|
||||||
export const POINTER_FRESH_MS = 30000;
|
export const POINTER_FRESH_MS = 30000;
|
||||||
/** Pixels from the canvas edge that count as "parked", never a referent. */
|
/** 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). */
|
/** 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
|
* 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
|
* 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. */
|
/** 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 ?? '')
|
const clean = String(value ?? '')
|
||||||
.replace(/[\p{Cc}\p{Cf}]+/gu, ' ')
|
.replace(/[\p{Cc}\p{Cf}]+/gu, ' ')
|
||||||
.replace(/\s+/g, ' ')
|
.replace(/\s+/g, ' ')
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { cameraPoseKey } from './pointerContext.js';
|
|||||||
import * as defaultAnnotationResolver from '../annotations/annotationResolver.js';
|
import * as defaultAnnotationResolver from '../annotations/annotationResolver.js';
|
||||||
|
|
||||||
/** "Around here" radius as a share of camera height, clamped to sane bounds. */
|
/** "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;
|
const heightKm = Number(cameraHeightM) / 1000;
|
||||||
if (!Number.isFinite(heightKm) || heightKm <= 0) return 50;
|
if (!Number.isFinite(heightKm) || heightKm <= 0) return 50;
|
||||||
return Math.round(Math.min(400, Math.max(5, heightKm * 0.75)));
|
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;
|
export const RETICLE_LINGER_MS = 1500;
|
||||||
|
|
||||||
/** Tools that move the camera away from the pointed spot. */
|
/** Tools that move the camera away from the pointed spot. */
|
||||||
export const CAMERA_TOOLS = new Set([
|
const CAMERA_TOOLS = new Set([
|
||||||
'fly_to_location',
|
'fly_to_location',
|
||||||
'track_entity',
|
'track_entity',
|
||||||
'zoom_to_globe',
|
'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);
|
const randomPart = Math.random().toString(36).slice(2, 10);
|
||||||
return `gev-${Date.now().toString(36)}-${randomPart}`;
|
return `gev-${Date.now().toString(36)}-${randomPart}`;
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ export function sanitizeDebugValue(value, depth = 0) {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeDebugString(value) {
|
function sanitizeDebugString(value) {
|
||||||
if (value.startsWith('data:image/')) {
|
if (value.startsWith('data:image/')) {
|
||||||
return `[Redacted image data URL, ${value.length} chars]`;
|
return `[Redacted image data URL, ${value.length} chars]`;
|
||||||
}
|
}
|
||||||
@@ -65,13 +65,13 @@ export function sanitizeDebugString(value) {
|
|||||||
: redacted;
|
: redacted;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSecretLikeKey(key) {
|
function isSecretLikeKey(key) {
|
||||||
return /(?:api[_-]?key|authorization|bearer|client[_-]?secret|token|secret|password)/i.test(
|
return /(?:api[_-]?key|authorization|bearer|client[_-]?secret|token|secret|password)/i.test(
|
||||||
key,
|
key,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createErrorRecord(source, error, extra = {}) {
|
function createErrorRecord(source, error, extra = {}) {
|
||||||
const rtcError = error?.error || error;
|
const rtcError = error?.error || error;
|
||||||
return {
|
return {
|
||||||
timestamp: new Date().toISOString(),
|
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 primary = [record.source, record.message].filter(Boolean).join(': ');
|
||||||
const state = [
|
const state = [
|
||||||
record.errorDetail && `detail=${record.errorDetail}`,
|
record.errorDetail && `detail=${record.errorDetail}`,
|
||||||
@@ -105,7 +105,7 @@ export function formatErrorForDisplay(record) {
|
|||||||
return state ? `${primary}\n${state}` : primary;
|
return state ? `${primary}\n${state}` : primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeEmptyValues(value) {
|
function removeEmptyValues(value) {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(value || {}).filter(
|
Object.entries(value || {}).filter(
|
||||||
([, item]) => item !== null && item !== undefined && item !== '',
|
([, item]) => item !== null && item !== undefined && item !== '',
|
||||||
@@ -113,14 +113,7 @@ export function removeEmptyValues(value) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compactText(value, maxLength) {
|
function loadStoredErrors() {
|
||||||
const text = String(value || '')
|
|
||||||
.replace(/\s+/g, ' ')
|
|
||||||
.trim();
|
|
||||||
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}…`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadStoredErrors() {
|
|
||||||
try {
|
try {
|
||||||
const value = JSON.parse(localStorage.getItem(ERROR_STORAGE_KEY) || '[]');
|
const value = JSON.parse(localStorage.getItem(ERROR_STORAGE_KEY) || '[]');
|
||||||
return Array.isArray(value) ? value.slice(0, ERROR_LOG_LIMIT) : [];
|
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 {
|
try {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
ERROR_STORAGE_KEY,
|
ERROR_STORAGE_KEY,
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import {
|
|||||||
|
|
||||||
// Voice cost control (repo-wide `godsEyeView.<feature>.<field>` convention;
|
// Voice cost control (repo-wide `godsEyeView.<feature>.<field>` convention;
|
||||||
// the neighbouring ERROR_STORAGE_KEY predates it).
|
// 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. */
|
/** Best-effort localStorage handle; absent in tests and locked-down browsers. */
|
||||||
export function voiceStorage(storage) {
|
function voiceStorage(storage) {
|
||||||
if (storage) return storage;
|
if (storage) return storage;
|
||||||
try {
|
try {
|
||||||
return typeof localStorage === 'undefined' ? null : localStorage;
|
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