feat(sdr): local receivers — browser RTL-SDR, Local ADS-B layer and decoder feeds (#732)

Adds a Local ADS-B layer fed by your own receivers: a browser WebUSB
RTL-SDR (with FM listening) and local 1090/978 MHz decoder feeds
(LOCAL_RECEIVER_FEEDS, local and private addresses only). Local aircraft
draw in magenta with class, silhouettes, 3D models, trails and a position
sanity filter; the click card names the band and input that heard them.

Builds on Sameh Khamis's SDR prototype and the initial GEV port by
Mazeyar Moeini Feizabadi (#134); the decoder-feed design follows the
notes on #57. Closes #57.

Co-authored-by: Sameh Khamis <[email protected]>
Co-authored-by: Mazeyar Moeini Feizabadi <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-09-23 16:00:53 -05:00
1 parent 143cffd7ca
commit 1fc795577a
78 files changed
+12267 -108

No files matched your search

+28 -2
View File
@@ -16,6 +16,7 @@ import { CockpitCoordinator } from './cockpitCoordinator.js';
import { ContextControls } from './context.js';
import { CctvControls } from './cctv.js';
import { RadioControls } from './radio.js';
import { LocalSdrControls } from './localSdrControls.js';
import { LocationNavigation } from './locationNavigation.js';
import { bindClearLayersControl } from './layers.js';
import { bindCameraOrientationControls } from './cameraOrientationControls.js';
@@ -77,6 +78,7 @@ export class StyleManager extends ShellFacade {
transitLayer,
aisLiveVesselsLayer,
militaryAwarenessLayer,
localAdsbLayer,
} = services;
this.services = services;
this._lifetime = new UiLifetime();
@@ -449,12 +451,13 @@ export class StyleManager extends ShellFacade {
trafficLayer,
flightsLayer,
militaryFlightsLayer,
localAdsbLayer,
satellitesLayer,
cctvLayer,
bikeshareLayer,
transitLayer,
aisLiveVesselsLayer,
],
].filter(Boolean),
(modeLabel) => {
this._updateDetectionButton(modeLabel);
},
@@ -860,11 +863,33 @@ export class StyleManager extends ShellFacade {
isCockpitActive: () => this.cockpitView?.active,
signalUserCollapsed: () => this.cockpitView?.signalUserCollapsed,
layoutCockpit: () => this.cockpitView?.scheduleContextLayout(),
// An open local receiver keeps the shared Radio panel expanded.
preservePanelStateDuringClear: () =>
this._preservePanelStateDuringLayerClear,
this._preservePanelStateDuringLayerClear ||
Boolean(this._localSdrControls?.isActive()),
scheduleLayout: () => this._scheduleRightPanelLayout(),
},
});
this._localSdrControls?.destroy();
this._localSdrControls = null;
const receiver = this.services.localAdsbLayer?.receiver;
if (receiver) {
this._localSdrControls = new LocalSdrControls({
document,
receiver,
feeds: this.services.localAdsbLayer?.feeds || null,
radio: radioLayer,
actions: {
isLocalAdsbEnabled: () =>
Boolean(this._dataManager?.isEnabled('local-adsb')),
setLocalAdsbEnabled: (enabled) =>
this._dataManager?.setEnabled('local-adsb', enabled, {
origin: 'user',
}),
scheduleLayout: () => this._scheduleRightPanelLayout(),
},
});
}
}
/**
@@ -1479,6 +1504,7 @@ export class StyleManager extends ShellFacade {
this._clearLayersControl?.destroy();
this._cctvControls?.destroy();
this._radioControls?.destroy();
this._localSdrControls?.destroy();
this._cockpitCoordinator.stop();
this._visualSettings.stop();
this.shareLinkManager?.destroy();