mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Traffic roads come from OpenFreeMap vector tiles, or TomTom flow tiles with a key (Hybrid by default). Military areas use OpenFreeMap polygons with a bundled worldwide name index; wide views show named points. ALPR reads an hourly OpenStreetMap extract. Cockpit place context and country, state and US county outlines use bundled Natural Earth and US Census data. Overpass is used only when an operator configures their own endpoint. Also: camera arrivals no longer land below ground, keyless terrain retries on 429, bounded relation geometry for configured Overpass, one shared OpenStreetMap credit, and separate tile and API transports for embedders. Co-authored-by: theGaryLarson <[email protected]> Co-authored-by: Milan Khanal <[email protected]>
This commit is contained in:
200 files changed
+26665
-1977
No files matched your search
Vendored
+172
-11
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
normalizeRoadMode,
|
||||
resolveRoadMode,
|
||||
TRAFFIC_ROAD_MODES,
|
||||
} from './roadModes.js';
|
||||
import {
|
||||
trafficBucketTier,
|
||||
trafficStyleProfile,
|
||||
@@ -5,6 +10,33 @@ import {
|
||||
import { TRAFFIC_TIMING_ENABLED } from './policy.js';
|
||||
|
||||
export function createControls({ state: layerState, services, parts, source }) {
|
||||
/** One status line: feed, drawn road source, then degraded-coverage notes. */
|
||||
function roadStatusLabel(feed) {
|
||||
const source = layerState._roadSource;
|
||||
if (layerState._roadError)
|
||||
return `UNAVAILABLE · ${source} · Roads unavailable`;
|
||||
const notes = [feed.loadingLabel];
|
||||
if (!layerState._liveMode || feed.error) notes.push(`Roads: ${source}`);
|
||||
// An explicit TomTom or Hybrid choice without a key draws OpenStreetMap.
|
||||
if (
|
||||
!layerState._liveMode &&
|
||||
['tomtom', 'hybrid'].includes(layerState._roadMode)
|
||||
)
|
||||
notes.push(
|
||||
layerState._flowStatusUnavailable
|
||||
? `${layerState._roadMode === 'tomtom' ? 'TomTom roads' : 'Hybrid'} unavailable while the traffic service is unreachable`
|
||||
: layerState._roadMode === 'tomtom'
|
||||
? 'TomTom roads need a TomTom key'
|
||||
: 'Hybrid needs a TomTom key',
|
||||
);
|
||||
if (layerState._surfacePending) notes.push('Local surface still loading');
|
||||
if (layerState._roadWarning) notes.push(layerState._roadWarning);
|
||||
else if (layerState._roadPartial) notes.push('Partial coverage');
|
||||
if (layerState._detailError) notes.push('Detailed roads unavailable');
|
||||
else if (layerState._detailLimited) notes.push('Reduced detail coverage');
|
||||
return notes.join(' · ');
|
||||
}
|
||||
|
||||
const { getFlowSessionStats } = source;
|
||||
|
||||
const methods = {
|
||||
@@ -14,19 +46,48 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
|
||||
icon: '🚗',
|
||||
|
||||
source: 'OpenStreetMap',
|
||||
source: 'OpenStreetMap / TomTom',
|
||||
|
||||
/** @type {number} Zero — layer is self-managed via camera listener + preRender */
|
||||
updateInterval: 0,
|
||||
|
||||
/**
|
||||
* Update user-adjustable parameters (density and speed scaling).
|
||||
* Update user-adjustable parameters (road source, density and speed scaling).
|
||||
*
|
||||
* @param {Object} [params]
|
||||
* @param {'tomtom'|'osm'|'hybrid'|null} [params.roadMode] - Road source; null = default.
|
||||
* @param {number} [params.densityScale] - Dot density multiplier (clamped 0.2–2.5).
|
||||
* @param {number} [params.speedScale] - Dot speed multiplier (clamped 0.3–3.0).
|
||||
* @param {{origin?:string}} [options] - Intent origin; restores never beat `?trafficRoads=`.
|
||||
*/
|
||||
setParams(params = {}) {
|
||||
setParams(params = {}, { origin } = {}) {
|
||||
const previousDensity = layerState._densityScale;
|
||||
const previousSpeed = layerState._speedScale;
|
||||
if (
|
||||
Object.hasOwn(params, 'roadMode') &&
|
||||
(params.roadMode === null || normalizeRoadMode(params.roadMode))
|
||||
) {
|
||||
const explicit = ['user', 'voice', 'tool'].includes(origin);
|
||||
const mode =
|
||||
!explicit && layerState._roadModeUrlOverride
|
||||
? layerState._roadModeUrlOverride
|
||||
: params.roadMode;
|
||||
if (explicit) layerState._roadModeUrlOverride = null;
|
||||
if (mode !== layerState._roadMode) {
|
||||
layerState._roadMode = mode;
|
||||
// Redraw from scratch: a different source means different roads.
|
||||
parts.ingestion.cancelActiveFetch();
|
||||
clearTimeout(layerState._retryTimer);
|
||||
layerState._retryTimer = null;
|
||||
layerState._loadGeneration++;
|
||||
layerState._fetching = false;
|
||||
layerState._lastBounds = null;
|
||||
layerState._lastViewCenter = null;
|
||||
parts.animation.clearDots();
|
||||
if (layerState._enabled)
|
||||
parts.viewport.onCameraChanged({ immediate: true });
|
||||
}
|
||||
}
|
||||
if (typeof params.densityScale === 'number') {
|
||||
layerState._densityScale = Math.max(
|
||||
0.2,
|
||||
@@ -39,6 +100,23 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
Math.min(3.0, params.speedScale),
|
||||
);
|
||||
}
|
||||
if (layerState._speedScale !== previousSpeed) {
|
||||
const ratio = layerState._speedScale / previousSpeed;
|
||||
for (const dot of layerState._dots) {
|
||||
dot.baseMps *= ratio;
|
||||
dot.mps *= ratio;
|
||||
}
|
||||
}
|
||||
if (
|
||||
layerState._densityScale !== previousDensity &&
|
||||
layerState._enabled &&
|
||||
layerState._roads.length
|
||||
)
|
||||
parts.rendering.renderRoadsForAltitude(
|
||||
layerState._roads,
|
||||
layerState._lastRenderAltitude,
|
||||
'Density',
|
||||
);
|
||||
// Live-mode treatment of roads TomTom has no flow data for:
|
||||
// 'sim' (default) keeps them as today's white ambient dots — colored =
|
||||
// real data, white = simulation; 'hide' spawns nothing on them (strict
|
||||
@@ -70,6 +148,7 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
*/
|
||||
getParams() {
|
||||
return {
|
||||
roadMode: layerState._roadMode,
|
||||
densityScale: layerState._densityScale,
|
||||
speedScale: layerState._speedScale,
|
||||
uncoveredRoads: layerState._uncoveredMode,
|
||||
@@ -78,6 +157,28 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
};
|
||||
},
|
||||
|
||||
/** Road-source chips on the Street Traffic row (TomTom / OSM / Hybrid). */
|
||||
getRowControls() {
|
||||
const selected =
|
||||
layerState._roadMode || resolveRoadMode(null, layerState._liveMode);
|
||||
const needsKey = layerState._liveMode ? '' : ' (needs a TomTom key)';
|
||||
const titles = {
|
||||
tomtom: `TomTom roads only, each with its live flow${needsKey}`,
|
||||
osm: 'OpenStreetMap roads, with TomTom flow matched when available',
|
||||
hybrid: `TomTom roads first; OpenStreetMap fills the rest, simulated${needsKey}`,
|
||||
};
|
||||
const labels = { tomtom: 'TomTom', osm: 'OSM', hybrid: 'Hybrid' };
|
||||
return {
|
||||
chips: TRAFFIC_ROAD_MODES.map((mode) => ({
|
||||
id: `roads-${mode}`,
|
||||
label: labels[mode],
|
||||
title: titles[mode],
|
||||
active: mode === selected,
|
||||
params: { roadMode: mode },
|
||||
})),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a sub-sampled list of active dot positions for detection overlays
|
||||
* (e.g. CCTV bounding-box rendering).
|
||||
@@ -106,7 +207,7 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
if (!pos) continue;
|
||||
const entry = {
|
||||
position: pos,
|
||||
id: `VEH-${String(i).padStart(4, '0')}`,
|
||||
id: `VEH-${String(layerState._dots[i].id).padStart(4, '0')}`,
|
||||
type: 'VEH',
|
||||
};
|
||||
// Live mode: the detection bracket carries the congestion signal —
|
||||
@@ -124,38 +225,94 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
return result;
|
||||
},
|
||||
|
||||
/** Visit stable dot identities and their owning geometry without allocating snapshots. */
|
||||
visitMotionDots(visitor) {
|
||||
for (let i = 0; i < layerState._dots.length; i++) {
|
||||
const dot = layerState._dots[i];
|
||||
visitor(dot.id, dot.road.key, dot.point, dot.road);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return current layer statistics for UI status chips.
|
||||
* `mode` is the CONFIGURED source — 'live' (a TomTom key is present) or
|
||||
* 'sim' (keyless simulation, which the manager renders as a FALLBACK chip);
|
||||
* `error` carries this instant's health, so a live-configured layer whose
|
||||
* flow feed went down reads DEGRADED with the reason instead of a stale
|
||||
* LIVE coverage number. `flowCoveragePct` is matched roads / roads with any
|
||||
* flow candidates (0–100 int); `tilesFetched` counts flow-tile requests
|
||||
* LIVE coverage number. `flowCoveragePct` is shown dots on matched roads /
|
||||
* all shown dots (0–100 int), excluding closures; `tilesFetched` counts flow-tile requests
|
||||
* issued to the proxy this session (decode-cache hits excluded).
|
||||
* @returns {{count:number, lastUpdate:number|null, loading:boolean,
|
||||
* mode:'live'|'sim', error:string|null, flowCoveragePct:number,
|
||||
* tilesFetched:number}}
|
||||
*/
|
||||
getLoadingDiagnostics() {
|
||||
let movingWaypoints = 0,
|
||||
maxResidualM = 0;
|
||||
for (const road of layerState._roads) {
|
||||
if (!road.heightMoving) continue;
|
||||
for (let i = 0; i < road.waypoints.length; i++) {
|
||||
const a = road.waypoints[i],
|
||||
b = road.targetWaypoints[i];
|
||||
const residual = Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
if (residual >= 0.01) movingWaypoints++;
|
||||
maxResidualM = Math.max(maxResidualM, residual);
|
||||
}
|
||||
}
|
||||
return {
|
||||
fetching: Boolean(layerState._fetching),
|
||||
flowPending: layerState._flowPending,
|
||||
surfaceRefining: Boolean(layerState._surfaceRefining),
|
||||
populationPending: Boolean(layerState._populationPending),
|
||||
heightPending: Boolean(layerState._heightPending),
|
||||
generation: layerState._loadGeneration,
|
||||
surfaceRevision: layerState._surfaceRevision,
|
||||
lastPaintStage: layerState._lastPaintStage || null,
|
||||
movingWaypoints,
|
||||
maxResidualM,
|
||||
};
|
||||
},
|
||||
|
||||
getStats() {
|
||||
// Outstanding flow work counts as loading: the paint race can leave a
|
||||
// TomTom request in flight after the roads have settled, and the shared
|
||||
// loading batch has to stay open long enough to announce its failure.
|
||||
const loading = layerState._fetching || layerState._flowPending > 0;
|
||||
const loading =
|
||||
layerState._fetching ||
|
||||
layerState._flowPending > 0 ||
|
||||
Boolean(layerState._surfaceRefining) ||
|
||||
Boolean(layerState._populationPending) ||
|
||||
Boolean(layerState._heightPending);
|
||||
const { free, slow, jam, sim } = layerState._bucketCounts;
|
||||
const matched = free + slow + jam;
|
||||
const flowCoveragePct =
|
||||
matched + sim > 0 ? Math.round((100 * matched) / (matched + sim)) : 0;
|
||||
const feed = parts.model.trafficFeedPresentation({
|
||||
liveMode: layerState._liveMode,
|
||||
fetching: loading,
|
||||
flowError: layerState._flowError,
|
||||
coveragePct: layerState._flowCoveragePct,
|
||||
coveragePct: flowCoveragePct,
|
||||
statusUnavailable: layerState._flowStatusUnavailable,
|
||||
roadSource: layerState._roadSource,
|
||||
});
|
||||
return {
|
||||
count: layerState._count,
|
||||
motion: { ...layerState._motion },
|
||||
surfaceBudgetOmitted: layerState._surfaceBudgetOmitted || 0,
|
||||
lastUpdate: layerState._lastUpdate,
|
||||
loading,
|
||||
mode: feed.mode,
|
||||
error: layerState._roadError || feed.error,
|
||||
flowCoveragePct: layerState._flowCoveragePct,
|
||||
error:
|
||||
layerState._roadError ||
|
||||
layerState._roadWarning ||
|
||||
layerState._detailError ||
|
||||
feed.error,
|
||||
roadBounds: layerState._lastBounds,
|
||||
surfacePending: layerState._surfacePending || 0,
|
||||
roadWarning: layerState._roadWarning || null,
|
||||
detailError: layerState._detailError || null,
|
||||
detailLimited: Boolean(layerState._detailLimited),
|
||||
flowCoveragePct,
|
||||
tilesFetched: getFlowSessionStats().tilesFetched,
|
||||
...(TRAFFIC_TIMING_ENABLED
|
||||
? { trafficTiming: parts.timing.getTrafficTimingDiagnostics() }
|
||||
@@ -180,7 +337,11 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
// in one had better be the honest one. This is also where LIVE vs
|
||||
// SIMULATED mode is surfaced, and it must never imply a live feed the
|
||||
// layer does not have.
|
||||
loadingLabel: feed.loadingLabel,
|
||||
roadMode: resolveRoadMode(layerState._roadMode, layerState._liveMode),
|
||||
roadModeRequested: layerState._roadMode,
|
||||
roadSource: layerState._roadSource,
|
||||
source: layerState._roadSource,
|
||||
loadingLabel: roadStatusLabel(feed),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user