Merge branch 'upstream-shared-observer-location'

This commit is contained in:
Smittix
2026-01-30 22:52:17 +00:00
16 changed files with 581 additions and 250 deletions
+41
View File
@@ -74,6 +74,47 @@ The ADS-B history feature persists aircraft messages to Postgres for long-term a
docker compose --profile history up -d docker compose --profile history up -d
``` ```
Set the following environment variables (for example in a `.env` file):
```bash
INTERCEPT_ADSB_HISTORY_ENABLED=true
INTERCEPT_ADSB_DB_HOST=adsb_db
INTERCEPT_ADSB_DB_PORT=5432
INTERCEPT_ADSB_DB_NAME=intercept_adsb
INTERCEPT_ADSB_DB_USER=intercept
INTERCEPT_ADSB_DB_PASSWORD=intercept
```
### Other ADS-B Settings
Set these as environment variables for either local installs or Docker:
| Variable | Default | Description |
|----------|---------|-------------|
| `INTERCEPT_ADSB_AUTO_START` | `false` | Auto-start ADS-B tracking when the dashboard loads |
| `INTERCEPT_SHARED_OBSERVER_LOCATION` | `true` | Share observer location across ADS-B/AIS/SSTV/Satellite modules |
**Local install example**
```bash
INTERCEPT_ADSB_AUTO_START=true \
INTERCEPT_SHARED_OBSERVER_LOCATION=false \
python app.py
```
**Docker example (.env)**
```bash
INTERCEPT_ADSB_AUTO_START=true
INTERCEPT_SHARED_OBSERVER_LOCATION=false
```
To store Postgres data on external storage, set `PGDATA_PATH` (defaults to `./pgdata`):
```bash
PGDATA_PATH=/mnt/usbpi1/intercept/pgdata
```
Then open **/adsb/history** for the reporting dashboard. Then open **/adsb/history** for the reporting dashboard.
### Open the Interface ### Open the Interface
+9 -2
View File
@@ -27,7 +27,7 @@ from typing import Any
from flask import Flask, render_template, jsonify, send_file, Response, request,redirect, url_for, flash, session from flask import Flask, render_template, jsonify, send_file, Response, request,redirect, url_for, flash, session
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from config import VERSION, CHANGELOG from config import VERSION, CHANGELOG, SHARED_OBSERVER_LOCATION_ENABLED
from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES
from utils.process import cleanup_stale_processes from utils.process import cleanup_stale_processes
from utils.sdr import SDRFactory from utils.sdr import SDRFactory
@@ -279,7 +279,14 @@ def index() -> str:
'rtlamr': check_tool('rtlamr') 'rtlamr': check_tool('rtlamr')
} }
devices = [d.to_dict() for d in SDRFactory.detect_devices()] devices = [d.to_dict() for d in SDRFactory.detect_devices()]
return render_template('index.html', tools=tools, devices=devices, version=VERSION, changelog=CHANGELOG) return render_template(
'index.html',
tools=tools,
devices=devices,
version=VERSION,
changelog=CHANGELOG,
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
)
@app.route('/favicon.svg') @app.route('/favicon.svg')
+4
View File
@@ -139,6 +139,7 @@ BT_UPDATE_INTERVAL = _get_env_float('BT_UPDATE_INTERVAL', 2.0)
# ADS-B settings # ADS-B settings
ADSB_SBS_PORT = _get_env_int('ADSB_SBS_PORT', 30003) ADSB_SBS_PORT = _get_env_int('ADSB_SBS_PORT', 30003)
ADSB_UPDATE_INTERVAL = _get_env_float('ADSB_UPDATE_INTERVAL', 1.0) ADSB_UPDATE_INTERVAL = _get_env_float('ADSB_UPDATE_INTERVAL', 1.0)
ADSB_AUTO_START = _get_env_bool('ADSB_AUTO_START', False)
ADSB_HISTORY_ENABLED = _get_env_bool('ADSB_HISTORY_ENABLED', False) ADSB_HISTORY_ENABLED = _get_env_bool('ADSB_HISTORY_ENABLED', False)
ADSB_DB_HOST = _get_env('ADSB_DB_HOST', 'localhost') ADSB_DB_HOST = _get_env('ADSB_DB_HOST', 'localhost')
ADSB_DB_PORT = _get_env_int('ADSB_DB_PORT', 5432) ADSB_DB_PORT = _get_env_int('ADSB_DB_PORT', 5432)
@@ -149,6 +150,9 @@ ADSB_HISTORY_BATCH_SIZE = _get_env_int('ADSB_HISTORY_BATCH_SIZE', 500)
ADSB_HISTORY_FLUSH_INTERVAL = _get_env_float('ADSB_HISTORY_FLUSH_INTERVAL', 1.0) ADSB_HISTORY_FLUSH_INTERVAL = _get_env_float('ADSB_HISTORY_FLUSH_INTERVAL', 1.0)
ADSB_HISTORY_QUEUE_SIZE = _get_env_int('ADSB_HISTORY_QUEUE_SIZE', 50000) ADSB_HISTORY_QUEUE_SIZE = _get_env_int('ADSB_HISTORY_QUEUE_SIZE', 50000)
# Observer location settings
SHARED_OBSERVER_LOCATION_ENABLED = _get_env_bool('SHARED_OBSERVER_LOCATION', True)
# Satellite settings # Satellite settings
SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30) SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30)
SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30) SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30)
+8
View File
@@ -36,6 +36,10 @@ services:
# - INTERCEPT_ADSB_DB_NAME=intercept_adsb # - INTERCEPT_ADSB_DB_NAME=intercept_adsb
# - INTERCEPT_ADSB_DB_USER=intercept # - INTERCEPT_ADSB_DB_USER=intercept
# - INTERCEPT_ADSB_DB_PASSWORD=intercept # - INTERCEPT_ADSB_DB_PASSWORD=intercept
# ADS-B auto-start on dashboard load (default false)
- INTERCEPT_ADSB_AUTO_START=${INTERCEPT_ADSB_AUTO_START:-false}
# Shared observer location across modules
- INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true}
# Network mode for WiFi scanning (requires host network) # Network mode for WiFi scanning (requires host network)
# network_mode: host # network_mode: host
restart: unless-stopped restart: unless-stopped
@@ -68,6 +72,10 @@ services:
- INTERCEPT_ADSB_DB_NAME=intercept_adsb - INTERCEPT_ADSB_DB_NAME=intercept_adsb
- INTERCEPT_ADSB_DB_USER=intercept - INTERCEPT_ADSB_DB_USER=intercept
- INTERCEPT_ADSB_DB_PASSWORD=intercept - INTERCEPT_ADSB_DB_PASSWORD=intercept
# ADS-B auto-start on dashboard load (default false)
- INTERCEPT_ADSB_AUTO_START=${INTERCEPT_ADSB_AUTO_START:-false}
# Shared observer location across modules
- INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true}
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:5050/health"] test: ["CMD", "curl", "-sf", "http://localhost:5050/health"]
+34 -1
View File
@@ -65,6 +65,8 @@ INTERCEPT automatically detects known trackers:
- **Manual Entry** - Type coordinates directly - **Manual Entry** - Type coordinates directly
- **Browser GPS** - Use browser's built-in geolocation (requires HTTPS) - **Browser GPS** - Use browser's built-in geolocation (requires HTTPS)
- **USB GPS Dongle** - Connect a USB GPS receiver for continuous updates - **USB GPS Dongle** - Connect a USB GPS receiver for continuous updates
- **Shared Location** - By default, the observer location is shared across modules
(disable with `INTERCEPT_SHARED_OBSERVER_LOCATION=false`)
4. **Start Tracking** - Click "Start Tracking" to begin ADS-B reception 4. **Start Tracking** - Click "Start Tracking" to begin ADS-B reception
5. **View Map** - Aircraft appear on the interactive Leaflet map 5. **View Map** - Aircraft appear on the interactive Leaflet map
6. **Click Aircraft** - Click markers for detailed information 6. **Click Aircraft** - Click markers for detailed information
@@ -72,6 +74,9 @@ INTERCEPT automatically detects known trackers:
8. **Filter Aircraft** - Use dropdown to show all, military, civil, or emergency only 8. **Filter Aircraft** - Use dropdown to show all, military, civil, or emergency only
9. **Full Dashboard** - Click "Full Screen Dashboard" for dedicated radar view 9. **Full Dashboard** - Click "Full Screen Dashboard" for dedicated radar view
> Note: ADS-B auto-start is disabled by default. To enable auto-start on dashboard load,
> set `INTERCEPT_ADSB_AUTO_START=true`.
### Emergency Squawks ### Emergency Squawks
The system highlights aircraft transmitting emergency squawks: The system highlights aircraft transmitting emergency squawks:
@@ -96,12 +101,40 @@ Set the following environment variables (Docker recommended):
| `INTERCEPT_ADSB_DB_USER` | `intercept` | Database user | | `INTERCEPT_ADSB_DB_USER` | `intercept` | Database user |
| `INTERCEPT_ADSB_DB_PASSWORD` | `intercept` | Database password | | `INTERCEPT_ADSB_DB_PASSWORD` | `intercept` | Database password |
### Other ADS-B Settings
| Variable | Default | Description |
|----------|---------|-------------|
| `INTERCEPT_ADSB_AUTO_START` | `false` | Auto-start ADS-B tracking when the dashboard loads |
| `INTERCEPT_SHARED_OBSERVER_LOCATION` | `true` | Share observer location across ADS-B/AIS/SSTV/Satellite modules |
**Local install example**
```bash
INTERCEPT_ADSB_AUTO_START=true \
INTERCEPT_SHARED_OBSERVER_LOCATION=false \
python app.py
```
**Docker example (.env)**
```bash
INTERCEPT_ADSB_AUTO_START=true
INTERCEPT_SHARED_OBSERVER_LOCATION=false
```
### Docker Setup ### Docker Setup
`docker-compose.yml` includes an `adsb_db` service and a persistent volume for history storage: `docker-compose.yml` includes an `adsb_db` service and a persistent volume for history storage:
```bash ```bash
docker compose up -d docker compose --profile history up -d
```
To store Postgres data on external storage, set `PGDATA_PATH` (defaults to `./pgdata`):
```bash
PGDATA_PATH=/mnt/usbpi1/intercept/pgdata
``` ```
### Using the History Dashboard ### Using the History Dashboard
+7 -1
View File
@@ -33,7 +33,9 @@ from config import (
ADSB_DB_PASSWORD, ADSB_DB_PASSWORD,
ADSB_DB_PORT, ADSB_DB_PORT,
ADSB_DB_USER, ADSB_DB_USER,
ADSB_AUTO_START,
ADSB_HISTORY_ENABLED, ADSB_HISTORY_ENABLED,
SHARED_OBSERVER_LOCATION_ENABLED,
) )
from utils.logging import adsb_logger as logger from utils.logging import adsb_logger as logger
from utils.validation import ( from utils.validation import (
@@ -812,7 +814,11 @@ def stream_adsb():
@adsb_bp.route('/dashboard') @adsb_bp.route('/dashboard')
def adsb_dashboard(): def adsb_dashboard():
"""Popout ADS-B dashboard.""" """Popout ADS-B dashboard."""
return render_template('adsb_dashboard.html') return render_template(
'adsb_dashboard.html',
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
adsb_auto_start=ADSB_AUTO_START,
)
@adsb_bp.route('/history') @adsb_bp.route('/history')
+5 -1
View File
@@ -15,6 +15,7 @@ from typing import Generator
from flask import Blueprint, jsonify, request, Response, render_template from flask import Blueprint, jsonify, request, Response, render_template
import app as app_module import app as app_module
from config import SHARED_OBSERVER_LOCATION_ENABLED
from utils.logging import get_logger from utils.logging import get_logger
from utils.validation import validate_device_index, validate_gain from utils.validation import validate_device_index, validate_gain
from utils.sse import format_sse from utils.sse import format_sse
@@ -480,4 +481,7 @@ def stream_ais():
@ais_bp.route('/dashboard') @ais_bp.route('/dashboard')
def ais_dashboard(): def ais_dashboard():
"""Popout AIS dashboard.""" """Popout AIS dashboard."""
return render_template('ais_dashboard.html') return render_template(
'ais_dashboard.html',
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
)
+6 -1
View File
@@ -13,6 +13,8 @@ import requests
from flask import Blueprint, jsonify, request, render_template, Response from flask import Blueprint, jsonify, request, render_template, Response
from config import SHARED_OBSERVER_LOCATION_ENABLED
from data.satellites import TLE_SATELLITES from data.satellites import TLE_SATELLITES
from utils.logging import satellite_logger as logger from utils.logging import satellite_logger as logger
from utils.validation import validate_latitude, validate_longitude, validate_hours, validate_elevation from utils.validation import validate_latitude, validate_longitude, validate_hours, validate_elevation
@@ -120,7 +122,10 @@ def _fetch_iss_realtime(observer_lat: Optional[float] = None, observer_lon: Opti
@satellite_bp.route('/dashboard') @satellite_bp.route('/dashboard')
def satellite_dashboard(): def satellite_dashboard():
"""Popout satellite tracking dashboard.""" """Popout satellite tracking dashboard."""
return render_template('satellite_dashboard.html') return render_template(
'satellite_dashboard.html',
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
)
@satellite_bp.route('/predict', methods=['POST']) @satellite_bp.route('/predict', methods=['POST'])
+3
View File
@@ -32,6 +32,9 @@ let muted = localStorage.getItem('audioMuted') === 'true';
// Observer location (load from localStorage or default to London) // Observer location (load from localStorage or default to London)
let observerLocation = (function() { let observerLocation = (function() {
if (window.ObserverLocation && ObserverLocation.getForModule) {
return ObserverLocation.getForModule('observerLocation');
}
const saved = localStorage.getItem('observerLocation'); const saved = localStorage.getItem('observerLocation');
if (saved) { if (saved) {
try { try {
+103
View File
@@ -0,0 +1,103 @@
// Shared observer location helper for map-based modules.
// Default: shared location enabled unless explicitly disabled via config.
window.ObserverLocation = (function() {
const DEFAULT_LOCATION = { lat: 51.5074, lon: -0.1278 };
const SHARED_KEY = 'observerLocation';
const AIS_KEY = 'ais_observerLocation';
const LEGACY_LAT_KEY = 'observerLat';
const LEGACY_LON_KEY = 'observerLon';
function isSharedEnabled() {
return window.INTERCEPT_SHARED_OBSERVER_LOCATION !== false;
}
function normalize(lat, lon) {
const latNum = parseFloat(lat);
const lonNum = parseFloat(lon);
if (Number.isNaN(latNum) || Number.isNaN(lonNum)) return null;
if (latNum < -90 || latNum > 90 || lonNum < -180 || lonNum > 180) return null;
return { lat: latNum, lon: lonNum };
}
function parseLocation(raw) {
if (!raw) return null;
try {
const parsed = JSON.parse(raw);
if (parsed && parsed.lat !== undefined && parsed.lon !== undefined) {
return normalize(parsed.lat, parsed.lon);
}
} catch (e) {}
return null;
}
function readKey(key) {
return parseLocation(localStorage.getItem(key));
}
function readLegacyLatLon() {
const lat = localStorage.getItem(LEGACY_LAT_KEY);
const lon = localStorage.getItem(LEGACY_LON_KEY);
if (!lat || !lon) return null;
return normalize(lat, lon);
}
function getShared() {
const current = readKey(SHARED_KEY);
if (current) return current;
const legacy = readKey(AIS_KEY) || readLegacyLatLon();
if (legacy) {
setShared(legacy);
return legacy;
}
return { ...DEFAULT_LOCATION };
}
function setShared(location, options = {}) {
if (!location) return;
localStorage.setItem(SHARED_KEY, JSON.stringify(location));
if (options.updateLegacy !== false) {
localStorage.setItem(LEGACY_LAT_KEY, location.lat.toString());
localStorage.setItem(LEGACY_LON_KEY, location.lon.toString());
}
}
function getForModule(moduleKey, options = {}) {
if (isSharedEnabled()) {
return getShared();
}
if (moduleKey) {
const moduleLocation = readKey(moduleKey);
if (moduleLocation) return moduleLocation;
}
if (options.fallbackToLatLon) {
const legacy = readLegacyLatLon();
if (legacy) return legacy;
}
return { ...DEFAULT_LOCATION };
}
function setForModule(moduleKey, location, options = {}) {
if (!location) return;
if (isSharedEnabled()) {
setShared(location, options);
return;
}
if (moduleKey) {
localStorage.setItem(moduleKey, JSON.stringify(location));
} else if (options.fallbackToLatLon) {
localStorage.setItem(LEGACY_LAT_KEY, location.lat.toString());
localStorage.setItem(LEGACY_LON_KEY, location.lon.toString());
}
}
return {
isSharedEnabled,
getShared,
setShared,
getForModule,
setForModule,
normalize,
DEFAULT_LOCATION: { ...DEFAULT_LOCATION }
};
})();
+18 -4
View File
@@ -548,8 +548,13 @@ document.addEventListener('DOMContentLoaded', () => {
* Load and display current observer location * Load and display current observer location
*/ */
function loadObserverLocation() { function loadObserverLocation() {
const lat = localStorage.getItem('observerLat'); let lat = localStorage.getItem('observerLat');
const lon = localStorage.getItem('observerLon'); let lon = localStorage.getItem('observerLon');
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
const shared = ObserverLocation.getShared();
lat = shared.lat.toString();
lon = shared.lon.toString();
}
const latInput = document.getElementById('observerLatInput'); const latInput = document.getElementById('observerLatInput');
const lonInput = document.getElementById('observerLonInput'); const lonInput = document.getElementById('observerLonInput');
@@ -658,8 +663,12 @@ function saveObserverLocation() {
return; return;
} }
localStorage.setItem('observerLat', lat.toString()); if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
localStorage.setItem('observerLon', lon.toString()); ObserverLocation.setShared({ lat, lon });
} else {
localStorage.setItem('observerLat', lat.toString());
localStorage.setItem('observerLon', lon.toString());
}
// Also update dashboard-specific location keys for ADS-B and AIS // Also update dashboard-specific location keys for ADS-B and AIS
const locationObj = JSON.stringify({ lat: lat, lon: lon }); const locationObj = JSON.stringify({ lat: lat, lon: lon });
@@ -676,6 +685,11 @@ function saveObserverLocation() {
showNotification('Location', 'Observer location saved'); showNotification('Location', 'Observer location saved');
} }
if (window.observerLocation) {
window.observerLocation.lat = lat;
window.observerLocation.lon = lon;
}
// Refresh SSTV ISS schedule if available // Refresh SSTV ISS schedule if available
if (typeof SSTV !== 'undefined' && typeof SSTV.loadIssSchedule === 'function') { if (typeof SSTV !== 'undefined' && typeof SSTV.loadIssSchedule === 'function') {
SSTV.loadIssSchedule(); SSTV.loadIssSchedule();
+19 -6
View File
@@ -41,8 +41,13 @@ const SSTV = (function() {
const latInput = document.getElementById('sstvObsLat'); const latInput = document.getElementById('sstvObsLat');
const lonInput = document.getElementById('sstvObsLon'); const lonInput = document.getElementById('sstvObsLon');
const storedLat = localStorage.getItem('observerLat'); let storedLat = localStorage.getItem('observerLat');
const storedLon = localStorage.getItem('observerLon'); let storedLon = localStorage.getItem('observerLon');
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
const shared = ObserverLocation.getShared();
storedLat = shared.lat.toString();
storedLon = shared.lon.toString();
}
if (latInput && storedLat) latInput.value = storedLat; if (latInput && storedLat) latInput.value = storedLat;
if (lonInput && storedLon) lonInput.value = storedLon; if (lonInput && storedLon) lonInput.value = storedLon;
@@ -64,8 +69,12 @@ const SSTV = (function() {
if (!isNaN(lat) && lat >= -90 && lat <= 90 && if (!isNaN(lat) && lat >= -90 && lat <= 90 &&
!isNaN(lon) && lon >= -180 && lon <= 180) { !isNaN(lon) && lon >= -180 && lon <= 180) {
localStorage.setItem('observerLat', lat.toString()); if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
localStorage.setItem('observerLon', lon.toString()); ObserverLocation.setShared({ lat, lon });
} else {
localStorage.setItem('observerLat', lat.toString());
localStorage.setItem('observerLon', lon.toString());
}
loadIssSchedule(); // Refresh pass predictions loadIssSchedule(); // Refresh pass predictions
} }
} }
@@ -94,8 +103,12 @@ const SSTV = (function() {
if (latInput) latInput.value = lat; if (latInput) latInput.value = lat;
if (lonInput) lonInput.value = lon; if (lonInput) lonInput.value = lon;
localStorage.setItem('observerLat', lat); if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
localStorage.setItem('observerLon', lon); ObserverLocation.setShared({ lat: parseFloat(lat), lon: parseFloat(lon) });
} else {
localStorage.setItem('observerLat', lat);
localStorage.setItem('observerLon', lon);
}
btn.innerHTML = originalText; btn.innerHTML = originalText;
btn.disabled = false; btn.disabled = false;
+39 -10
View File
@@ -20,6 +20,11 @@
{% endif %} {% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/adsb_dashboard.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/adsb_dashboard.css') }}">
<script>
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
window.INTERCEPT_ADSB_AUTO_START = {{ adsb_auto_start | tojson }};
</script>
<script src="{{ url_for('static', filename='js/core/observer-location.js') }}"></script>
</head> </head>
<body> <body>
<div class="radar-bg"></div> <div class="radar-bg"></div>
@@ -518,6 +523,9 @@
// Observer location and range rings (load from localStorage or default to London) // Observer location and range rings (load from localStorage or default to London)
let observerLocation = (function() { let observerLocation = (function() {
if (window.ObserverLocation && ObserverLocation.getForModule) {
return ObserverLocation.getForModule('observerLocation');
}
const saved = localStorage.getItem('observerLocation'); const saved = localStorage.getItem('observerLocation');
if (saved) { if (saved) {
try { try {
@@ -1803,7 +1811,11 @@ ACARS: ${r.statistics.acarsMessages} messages`;
observerLocation.lon = lon; observerLocation.lon = lon;
// Save to localStorage for persistence // Save to localStorage for persistence
localStorage.setItem('observerLocation', JSON.stringify(observerLocation)); if (window.ObserverLocation) {
ObserverLocation.setForModule('observerLocation', observerLocation);
} else {
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
}
if (radarMap) { if (radarMap) {
radarMap.setView([lat, lon], radarMap.getZoom()); radarMap.setView([lat, lon], radarMap.getZoom());
@@ -1831,7 +1843,11 @@ ACARS: ${r.statistics.acarsMessages} messages`;
observerLocation.lon = position.coords.longitude; observerLocation.lon = position.coords.longitude;
// Save to localStorage for persistence // Save to localStorage for persistence
localStorage.setItem('observerLocation', JSON.stringify(observerLocation)); if (window.ObserverLocation) {
ObserverLocation.setForModule('observerLocation', observerLocation);
} else {
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
}
document.getElementById('obsLat').value = observerLocation.lat.toFixed(4); document.getElementById('obsLat').value = observerLocation.lat.toFixed(4);
document.getElementById('obsLon').value = observerLocation.lon.toFixed(4); document.getElementById('obsLon').value = observerLocation.lon.toFixed(4);
@@ -1925,6 +1941,9 @@ ACARS: ${r.statistics.acarsMessages} messages`;
observerLocation.lon = position.longitude; observerLocation.lon = position.longitude;
document.getElementById('obsLat').value = position.latitude.toFixed(4); document.getElementById('obsLat').value = position.latitude.toFixed(4);
document.getElementById('obsLon').value = position.longitude.toFixed(4); document.getElementById('obsLon').value = position.longitude.toFixed(4);
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
ObserverLocation.setShared({ lat: position.latitude, lon: position.longitude });
}
// Center map on GPS location (on first fix) // Center map on GPS location (on first fix)
if (radarMap && !radarMap._gpsInitialized) { if (radarMap && !radarMap._gpsInitialized) {
@@ -2537,9 +2556,13 @@ sudo make install</code>
try { try {
const response = await fetch('/adsb/session'); const response = await fetch('/adsb/session');
if (!response.ok) { if (!response.ok) {
// No session info - try to auto-start if SDR available // No session info - only auto-start if enabled
console.log('[ADS-B] No session found, attempting auto-start...'); if (window.INTERCEPT_ADSB_AUTO_START) {
await tryAutoStartLocal(); console.log('[ADS-B] No session found, attempting auto-start...');
await tryAutoStartLocal();
} else {
console.log('[ADS-B] No session found; auto-start disabled');
}
return; return;
} }
const data = await response.json(); const data = await response.json();
@@ -2581,15 +2604,21 @@ sudo make install</code>
const statusEl = document.getElementById('trackingStatus'); const statusEl = document.getElementById('trackingStatus');
statusEl.textContent = 'TRACKING'; statusEl.textContent = 'TRACKING';
} else { } else {
// Session not active - try to auto-start // Session not active - only auto-start if enabled
console.log('[ADS-B] No active session, attempting auto-start...'); if (window.INTERCEPT_ADSB_AUTO_START) {
await tryAutoStartLocal(); console.log('[ADS-B] No active session, attempting auto-start...');
await tryAutoStartLocal();
} else {
console.log('[ADS-B] No active session; auto-start disabled');
}
} }
} catch (err) { } catch (err) {
console.warn('[ADS-B] Failed to sync tracking status:', err); console.warn('[ADS-B] Failed to sync tracking status:', err);
// Try auto-start anyway // Try auto-start only if enabled
await tryAutoStartLocal(); if (window.INTERCEPT_ADSB_AUTO_START) {
await tryAutoStartLocal();
}
} }
} }
+23 -14
View File
@@ -20,6 +20,10 @@
{% endif %} {% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/ais_dashboard.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/ais_dashboard.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
<script>
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
</script>
<script src="{{ url_for('static', filename='js/core/observer-location.js') }}"></script>
</head> </head>
<body> <body>
<!-- Radar background effects --> <!-- Radar background effects -->
@@ -219,7 +223,12 @@
const MAX_TRAIL_POINTS = 50; const MAX_TRAIL_POINTS = 50;
// Observer location // Observer location
let observerLocation = { lat: 51.5074, lon: -0.1278 }; let observerLocation = (function() {
if (window.ObserverLocation && ObserverLocation.getForModule) {
return ObserverLocation.getForModule('ais_observerLocation');
}
return { lat: 51.5074, lon: -0.1278 };
})();
let rangeRingsLayer = null; let rangeRingsLayer = null;
let observerMarker = null; let observerMarker = null;
@@ -376,17 +385,9 @@
// Initialize map // Initialize map
function initMap() { function initMap() {
// Load saved observer location if (observerLocation) {
const saved = localStorage.getItem('ais_observerLocation'); document.getElementById('obsLat').value = observerLocation.lat;
if (saved) { document.getElementById('obsLon').value = observerLocation.lon;
try {
const parsed = JSON.parse(saved);
if (parsed.lat && parsed.lon) {
observerLocation = parsed;
document.getElementById('obsLat').value = parsed.lat;
document.getElementById('obsLon').value = parsed.lon;
}
} catch (e) {}
} }
vesselMap = L.map('vesselMap', { vesselMap = L.map('vesselMap', {
@@ -470,7 +471,11 @@
const lon = parseFloat(document.getElementById('obsLon').value); const lon = parseFloat(document.getElementById('obsLon').value);
if (!isNaN(lat) && !isNaN(lon) && lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180) { if (!isNaN(lat) && !isNaN(lon) && lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180) {
observerLocation = { lat, lon }; observerLocation = { lat, lon };
localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation)); if (window.ObserverLocation) {
ObserverLocation.setForModule('ais_observerLocation', observerLocation);
} else {
localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation));
}
if (observerMarker) { if (observerMarker) {
observerMarker.setLatLng([lat, lon]); observerMarker.setLatLng([lat, lon]);
} }
@@ -1058,7 +1063,11 @@
drawRangeRings(); drawRangeRings();
// Save to localStorage // Save to localStorage
localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation)); if (window.ObserverLocation) {
ObserverLocation.setForModule('ais_observerLocation', observerLocation);
} else {
localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation));
}
} }
function showGpsIndicator(show) { function showGpsIndicator(show) {
+19 -2
View File
@@ -14,6 +14,10 @@
window._showDisclaimerOnLoad = true; window._showDisclaimerOnLoad = true;
} }
</script> </script>
<script>
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
</script>
<script src="{{ url_for('static', filename='js/core/observer-location.js') }}"></script>
<!-- Fonts - Conditional CDN/Local loading --> <!-- Fonts - Conditional CDN/Local loading -->
{% if offline_settings.fonts_source == 'local' %} {% if offline_settings.fonts_source == 'local' %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/fonts-local.css') }}">
@@ -2252,6 +2256,9 @@
// Observer location for distance calculations (load from localStorage or default to London) // Observer location for distance calculations (load from localStorage or default to London)
let observerLocation = (function () { let observerLocation = (function () {
if (window.ObserverLocation && ObserverLocation.getForModule) {
return ObserverLocation.getForModule('observerLocation');
}
const saved = localStorage.getItem('observerLocation'); const saved = localStorage.getItem('observerLocation');
if (saved) { if (saved) {
try { try {
@@ -8375,8 +8382,15 @@
if (navigator.geolocation) { if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
position => { position => {
document.getElementById('obsLat').value = position.coords.latitude.toFixed(4); const lat = position.coords.latitude;
document.getElementById('obsLon').value = position.coords.longitude.toFixed(4); const lon = position.coords.longitude;
document.getElementById('obsLat').value = lat.toFixed(4);
document.getElementById('obsLon').value = lon.toFixed(4);
observerLocation.lat = lat;
observerLocation.lon = lon;
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
ObserverLocation.setShared({ lat, lon });
}
showInfo('Location updated!'); showInfo('Location updated!');
}, },
error => { error => {
@@ -8476,6 +8490,9 @@
// Update observerLocation // Update observerLocation
observerLocation.lat = position.latitude; observerLocation.lat = position.latitude;
observerLocation.lon = position.longitude; observerLocation.lon = position.longitude;
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
ObserverLocation.setShared({ lat: position.latitude, lon: position.longitude });
}
// Update APRS user location // Update APRS user location
updateAprsUserLocation(position); updateAprsUserLocation(position);
+38 -3
View File
@@ -20,6 +20,10 @@
{% endif %} {% endif %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/satellite_dashboard.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/satellite_dashboard.css') }}">
<script>
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
</script>
<script src="{{ url_for('static', filename='js/core/observer-location.js') }}"></script>
</head> </head>
<body> <body>
<div class="grid-bg"></div> <div class="grid-bg"></div>
@@ -313,15 +317,32 @@
} }
} }
function applySharedObserverLocation() {
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
const shared = ObserverLocation.getShared();
if (shared) {
const latInput = document.getElementById('obsLat');
const lonInput = document.getElementById('obsLon');
if (latInput) latInput.value = shared.lat.toFixed(4);
if (lonInput) lonInput.value = shared.lon.toFixed(4);
return true;
}
}
return false;
}
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
setupEmbeddedMode(); setupEmbeddedMode();
const usedShared = applySharedObserverLocation();
initGroundMap(); initGroundMap();
updateClock(); updateClock();
setInterval(updateClock, 1000); setInterval(updateClock, 1000);
setInterval(updateCountdown, 1000); setInterval(updateCountdown, 1000);
setInterval(updateRealTimePositions, 5000); setInterval(updateRealTimePositions, 5000);
loadAgents(); loadAgents();
getLocation(); if (!usedShared) {
getLocation();
}
}); });
async function loadAgents() { async function loadAgents() {
@@ -380,6 +401,9 @@
const gps = agentStatus.gps_position; const gps = agentStatus.gps_position;
document.getElementById('obsLat').value = gps.lat.toFixed(4); document.getElementById('obsLat').value = gps.lat.toFixed(4);
document.getElementById('obsLon').value = gps.lon.toFixed(4); document.getElementById('obsLon').value = gps.lon.toFixed(4);
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
ObserverLocation.setShared({ lat: gps.lat, lon: gps.lon });
}
// Update observer marker label // Update observer marker label
const agent = agents.find(a => a.id == agentId); const agent = agents.find(a => a.id == agentId);
@@ -428,13 +452,24 @@
subdomains: 'abcd' subdomains: 'abcd'
}).addTo(groundMap); }).addTo(groundMap);
} }
const lat = parseFloat(document.getElementById('obsLat')?.value);
const lon = parseFloat(document.getElementById('obsLon')?.value);
if (!Number.isNaN(lat) && !Number.isNaN(lon)) {
groundMap.setView([lat, lon], 3);
}
} }
function getLocation() { function getLocation() {
if (navigator.geolocation) { if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(pos => { navigator.geolocation.getCurrentPosition(pos => {
document.getElementById('obsLat').value = pos.coords.latitude.toFixed(4); const lat = pos.coords.latitude;
document.getElementById('obsLon').value = pos.coords.longitude.toFixed(4); const lon = pos.coords.longitude;
document.getElementById('obsLat').value = lat.toFixed(4);
document.getElementById('obsLon').value = lon.toFixed(4);
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
ObserverLocation.setShared({ lat, lon });
}
calculatePasses(); calculatePasses();
}, () => { }, () => {
calculatePasses(); calculatePasses();