mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
feat: add shared observer location with opt-out
This commit is contained in:
27
app.py
27
app.py
@@ -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 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.process import cleanup_stale_processes
|
||||
from utils.sdr import SDRFactory
|
||||
@@ -271,15 +271,22 @@ def login():
|
||||
return render_template('login.html', version=VERSION)
|
||||
|
||||
@app.route('/')
|
||||
def index() -> str:
|
||||
tools = {
|
||||
'rtl_fm': check_tool('rtl_fm'),
|
||||
'multimon': check_tool('multimon-ng'),
|
||||
'rtl_433': check_tool('rtl_433'),
|
||||
'rtlamr': check_tool('rtlamr')
|
||||
}
|
||||
devices = [d.to_dict() for d in SDRFactory.detect_devices()]
|
||||
return render_template('index.html', tools=tools, devices=devices, version=VERSION, changelog=CHANGELOG)
|
||||
def index() -> str:
|
||||
tools = {
|
||||
'rtl_fm': check_tool('rtl_fm'),
|
||||
'multimon': check_tool('multimon-ng'),
|
||||
'rtl_433': check_tool('rtl_433'),
|
||||
'rtlamr': check_tool('rtlamr')
|
||||
}
|
||||
devices = [d.to_dict() for d in SDRFactory.detect_devices()]
|
||||
return render_template(
|
||||
'index.html',
|
||||
tools=tools,
|
||||
devices=devices,
|
||||
version=VERSION,
|
||||
changelog=CHANGELOG,
|
||||
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
|
||||
)
|
||||
|
||||
|
||||
@app.route('/favicon.svg')
|
||||
|
||||
27
config.py
27
config.py
@@ -136,23 +136,26 @@ AIRODUMP_HEADER_LINES = _get_env_int('AIRODUMP_HEADER_LINES', 2)
|
||||
BT_SCAN_TIMEOUT = _get_env_int('BT_SCAN_TIMEOUT', 10)
|
||||
BT_UPDATE_INTERVAL = _get_env_float('BT_UPDATE_INTERVAL', 2.0)
|
||||
|
||||
# ADS-B settings
|
||||
ADSB_SBS_PORT = _get_env_int('ADSB_SBS_PORT', 30003)
|
||||
ADSB_UPDATE_INTERVAL = _get_env_float('ADSB_UPDATE_INTERVAL', 1.0)
|
||||
ADSB_HISTORY_ENABLED = _get_env_bool('ADSB_HISTORY_ENABLED', False)
|
||||
# ADS-B settings
|
||||
ADSB_SBS_PORT = _get_env_int('ADSB_SBS_PORT', 30003)
|
||||
ADSB_UPDATE_INTERVAL = _get_env_float('ADSB_UPDATE_INTERVAL', 1.0)
|
||||
ADSB_HISTORY_ENABLED = _get_env_bool('ADSB_HISTORY_ENABLED', False)
|
||||
ADSB_DB_HOST = _get_env('ADSB_DB_HOST', 'localhost')
|
||||
ADSB_DB_PORT = _get_env_int('ADSB_DB_PORT', 5432)
|
||||
ADSB_DB_NAME = _get_env('ADSB_DB_NAME', 'intercept_adsb')
|
||||
ADSB_DB_USER = _get_env('ADSB_DB_USER', 'intercept')
|
||||
ADSB_DB_PASSWORD = _get_env('ADSB_DB_PASSWORD', 'intercept')
|
||||
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_QUEUE_SIZE = _get_env_int('ADSB_HISTORY_QUEUE_SIZE', 50000)
|
||||
|
||||
# Satellite settings
|
||||
SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30)
|
||||
SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30)
|
||||
SATELLITE_ORBIT_MINUTES = _get_env_int('SATELLITE_ORBIT_MINUTES', 45)
|
||||
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_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_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30)
|
||||
SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30)
|
||||
SATELLITE_ORBIT_MINUTES = _get_env_int('SATELLITE_ORBIT_MINUTES', 45)
|
||||
|
||||
# Update checking
|
||||
GITHUB_REPO = _get_env('GITHUB_REPO', 'smittix/intercept')
|
||||
|
||||
@@ -36,6 +36,8 @@ services:
|
||||
# - INTERCEPT_ADSB_DB_NAME=intercept_adsb
|
||||
# - INTERCEPT_ADSB_DB_USER=intercept
|
||||
# - INTERCEPT_ADSB_DB_PASSWORD=intercept
|
||||
# Shared observer location across modules
|
||||
- INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true}
|
||||
# Network mode for WiFi scanning (requires host network)
|
||||
# network_mode: host
|
||||
restart: unless-stopped
|
||||
@@ -68,6 +70,8 @@ services:
|
||||
- INTERCEPT_ADSB_DB_NAME=intercept_adsb
|
||||
- INTERCEPT_ADSB_DB_USER=intercept
|
||||
- INTERCEPT_ADSB_DB_PASSWORD=intercept
|
||||
# Shared observer location across modules
|
||||
- INTERCEPT_SHARED_OBSERVER_LOCATION=${INTERCEPT_SHARED_OBSERVER_LOCATION:-true}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:5050/health"]
|
||||
|
||||
@@ -61,11 +61,13 @@ INTERCEPT automatically detects known trackers:
|
||||
|
||||
1. **Select Hardware** - Choose your SDR type (RTL-SDR uses dump1090, others use readsb)
|
||||
2. **Check Tools** - Ensure dump1090 or readsb is installed
|
||||
3. **Set Location** - Choose location source:
|
||||
- **Manual Entry** - Type coordinates directly
|
||||
- **Browser GPS** - Use browser's built-in geolocation (requires HTTPS)
|
||||
- **USB GPS Dongle** - Connect a USB GPS receiver for continuous updates
|
||||
4. **Start Tracking** - Click "Start Tracking" to begin ADS-B reception
|
||||
3. **Set Location** - Choose location source:
|
||||
- **Manual Entry** - Type coordinates directly
|
||||
- **Browser GPS** - Use browser's built-in geolocation (requires HTTPS)
|
||||
- **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
|
||||
5. **View Map** - Aircraft appear on the interactive Leaflet map
|
||||
6. **Click Aircraft** - Click markers for detailed information
|
||||
7. **Display Options** - Toggle callsigns, altitude, trails, range rings, clustering
|
||||
|
||||
@@ -34,6 +34,7 @@ from config import (
|
||||
ADSB_DB_PORT,
|
||||
ADSB_DB_USER,
|
||||
ADSB_HISTORY_ENABLED,
|
||||
SHARED_OBSERVER_LOCATION_ENABLED,
|
||||
)
|
||||
from utils.logging import adsb_logger as logger
|
||||
from utils.validation import (
|
||||
@@ -812,7 +813,10 @@ def stream_adsb():
|
||||
@adsb_bp.route('/dashboard')
|
||||
def adsb_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_bp.route('/history')
|
||||
|
||||
@@ -15,6 +15,7 @@ from typing import Generator
|
||||
from flask import Blueprint, jsonify, request, Response, render_template
|
||||
|
||||
import app as app_module
|
||||
from config import SHARED_OBSERVER_LOCATION_ENABLED
|
||||
from utils.logging import get_logger
|
||||
from utils.validation import validate_device_index, validate_gain
|
||||
from utils.sse import format_sse
|
||||
@@ -480,4 +481,7 @@ def stream_ais():
|
||||
@ais_bp.route('/dashboard')
|
||||
def ais_dashboard():
|
||||
"""Popout AIS dashboard."""
|
||||
return render_template('ais_dashboard.html')
|
||||
return render_template(
|
||||
'ais_dashboard.html',
|
||||
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
|
||||
)
|
||||
|
||||
@@ -11,7 +11,9 @@ from urllib.parse import urlparse
|
||||
|
||||
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 utils.logging import satellite_logger as logger
|
||||
@@ -118,9 +120,12 @@ def _fetch_iss_realtime(observer_lat: Optional[float] = None, observer_lon: Opti
|
||||
|
||||
|
||||
@satellite_bp.route('/dashboard')
|
||||
def satellite_dashboard():
|
||||
"""Popout satellite tracking dashboard."""
|
||||
return render_template('satellite_dashboard.html')
|
||||
def satellite_dashboard():
|
||||
"""Popout satellite tracking dashboard."""
|
||||
return render_template(
|
||||
'satellite_dashboard.html',
|
||||
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
|
||||
)
|
||||
|
||||
|
||||
@satellite_bp.route('/predict', methods=['POST'])
|
||||
|
||||
@@ -31,16 +31,19 @@ let autoScroll = localStorage.getItem('autoScroll') !== 'false';
|
||||
let muted = localStorage.getItem('audioMuted') === 'true';
|
||||
|
||||
// Observer location (load from localStorage or default to London)
|
||||
let observerLocation = (function() {
|
||||
const saved = localStorage.getItem('observerLocation');
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.lat && parsed.lon) return parsed;
|
||||
} catch (e) {}
|
||||
}
|
||||
return { lat: 51.5074, lon: -0.1278 };
|
||||
})();
|
||||
let observerLocation = (function() {
|
||||
if (window.ObserverLocation && ObserverLocation.getForModule) {
|
||||
return ObserverLocation.getForModule('observerLocation');
|
||||
}
|
||||
const saved = localStorage.getItem('observerLocation');
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.lat && parsed.lon) return parsed;
|
||||
} catch (e) {}
|
||||
}
|
||||
return { lat: 51.5074, lon: -0.1278 };
|
||||
})();
|
||||
|
||||
// Message storage for export
|
||||
let allMessages = [];
|
||||
|
||||
103
static/js/core/observer-location.js
Normal file
103
static/js/core/observer-location.js
Normal 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 }
|
||||
};
|
||||
})();
|
||||
@@ -547,9 +547,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
/**
|
||||
* Load and display current observer location
|
||||
*/
|
||||
function loadObserverLocation() {
|
||||
const lat = localStorage.getItem('observerLat');
|
||||
const lon = localStorage.getItem('observerLon');
|
||||
function loadObserverLocation() {
|
||||
let lat = localStorage.getItem('observerLat');
|
||||
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 lonInput = document.getElementById('observerLonInput');
|
||||
@@ -622,9 +627,9 @@ function detectLocationGPS(btn) {
|
||||
/**
|
||||
* Save observer location to localStorage
|
||||
*/
|
||||
function saveObserverLocation() {
|
||||
const latInput = document.getElementById('observerLatInput');
|
||||
const lonInput = document.getElementById('observerLonInput');
|
||||
function saveObserverLocation() {
|
||||
const latInput = document.getElementById('observerLatInput');
|
||||
const lonInput = document.getElementById('observerLonInput');
|
||||
|
||||
const lat = parseFloat(latInput?.value);
|
||||
const lon = parseFloat(lonInput?.value);
|
||||
@@ -647,18 +652,27 @@ function saveObserverLocation() {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem('observerLat', lat.toString());
|
||||
localStorage.setItem('observerLon', lon.toString());
|
||||
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
|
||||
ObserverLocation.setShared({ lat, lon });
|
||||
} else {
|
||||
localStorage.setItem('observerLat', lat.toString());
|
||||
localStorage.setItem('observerLon', lon.toString());
|
||||
}
|
||||
|
||||
// Update display
|
||||
const currentLatDisplay = document.getElementById('currentLatDisplay');
|
||||
const currentLonDisplay = document.getElementById('currentLonDisplay');
|
||||
if (currentLatDisplay) currentLatDisplay.textContent = lat.toFixed(4) + '°';
|
||||
if (currentLonDisplay) currentLonDisplay.textContent = lon.toFixed(4) + '°';
|
||||
|
||||
if (typeof showNotification === 'function') {
|
||||
showNotification('Location', 'Observer location saved');
|
||||
}
|
||||
if (currentLatDisplay) currentLatDisplay.textContent = lat.toFixed(4) + '°';
|
||||
if (currentLonDisplay) currentLonDisplay.textContent = lon.toFixed(4) + '°';
|
||||
|
||||
if (typeof showNotification === 'function') {
|
||||
showNotification('Location', 'Observer location saved');
|
||||
}
|
||||
|
||||
if (window.observerLocation) {
|
||||
window.observerLocation.lat = lat;
|
||||
window.observerLocation.lon = lon;
|
||||
}
|
||||
|
||||
// Refresh SSTV ISS schedule if available
|
||||
if (typeof SSTV !== 'undefined' && typeof SSTV.loadIssSchedule === 'function') {
|
||||
|
||||
@@ -37,15 +37,20 @@ const SSTV = (function() {
|
||||
/**
|
||||
* Load location into input fields
|
||||
*/
|
||||
function loadLocationInputs() {
|
||||
const latInput = document.getElementById('sstvObsLat');
|
||||
const lonInput = document.getElementById('sstvObsLon');
|
||||
|
||||
const storedLat = localStorage.getItem('observerLat');
|
||||
const storedLon = localStorage.getItem('observerLon');
|
||||
|
||||
if (latInput && storedLat) latInput.value = storedLat;
|
||||
if (lonInput && storedLon) lonInput.value = storedLon;
|
||||
function loadLocationInputs() {
|
||||
const latInput = document.getElementById('sstvObsLat');
|
||||
const lonInput = document.getElementById('sstvObsLon');
|
||||
|
||||
let storedLat = localStorage.getItem('observerLat');
|
||||
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 (lonInput && storedLon) lonInput.value = storedLon;
|
||||
|
||||
// Add change handlers to save and refresh
|
||||
if (latInput) latInput.addEventListener('change', saveLocationFromInputs);
|
||||
@@ -55,19 +60,23 @@ const SSTV = (function() {
|
||||
/**
|
||||
* Save location from input fields
|
||||
*/
|
||||
function saveLocationFromInputs() {
|
||||
const latInput = document.getElementById('sstvObsLat');
|
||||
const lonInput = document.getElementById('sstvObsLon');
|
||||
function saveLocationFromInputs() {
|
||||
const latInput = document.getElementById('sstvObsLat');
|
||||
const lonInput = document.getElementById('sstvObsLon');
|
||||
|
||||
const lat = parseFloat(latInput?.value);
|
||||
const lon = parseFloat(lonInput?.value);
|
||||
|
||||
if (!isNaN(lat) && lat >= -90 && lat <= 90 &&
|
||||
!isNaN(lon) && lon >= -180 && lon <= 180) {
|
||||
localStorage.setItem('observerLat', lat.toString());
|
||||
localStorage.setItem('observerLon', lon.toString());
|
||||
loadIssSchedule(); // Refresh pass predictions
|
||||
}
|
||||
if (!isNaN(lat) && lat >= -90 && lat <= 90 &&
|
||||
!isNaN(lon) && lon >= -180 && lon <= 180) {
|
||||
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
|
||||
ObserverLocation.setShared({ lat, lon });
|
||||
} else {
|
||||
localStorage.setItem('observerLat', lat.toString());
|
||||
localStorage.setItem('observerLon', lon.toString());
|
||||
}
|
||||
loadIssSchedule(); // Refresh pass predictions
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,8 +103,12 @@ const SSTV = (function() {
|
||||
if (latInput) latInput.value = lat;
|
||||
if (lonInput) lonInput.value = lon;
|
||||
|
||||
localStorage.setItem('observerLat', lat);
|
||||
localStorage.setItem('observerLon', lon);
|
||||
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
|
||||
ObserverLocation.setShared({ lat: parseFloat(lat), lon: parseFloat(lon) });
|
||||
} else {
|
||||
localStorage.setItem('observerLat', lat);
|
||||
localStorage.setItem('observerLon', lon);
|
||||
}
|
||||
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/adsb_dashboard.css') }}">
|
||||
</head>
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/adsb_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>
|
||||
<body>
|
||||
<div class="radar-bg"></div>
|
||||
<div class="scanline"></div>
|
||||
@@ -517,16 +521,19 @@
|
||||
}
|
||||
|
||||
// Observer location and range rings (load from localStorage or default to London)
|
||||
let observerLocation = (function() {
|
||||
const saved = localStorage.getItem('observerLocation');
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.lat && parsed.lon) return parsed;
|
||||
} catch (e) {}
|
||||
}
|
||||
return { lat: 51.5074, lon: -0.1278 };
|
||||
})();
|
||||
let observerLocation = (function() {
|
||||
if (window.ObserverLocation && ObserverLocation.getForModule) {
|
||||
return ObserverLocation.getForModule('observerLocation');
|
||||
}
|
||||
const saved = localStorage.getItem('observerLocation');
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.lat && parsed.lon) return parsed;
|
||||
} catch (e) {}
|
||||
}
|
||||
return { lat: 51.5074, lon: -0.1278 };
|
||||
})();
|
||||
let rangeRingsLayer = null;
|
||||
let observerMarker = null;
|
||||
|
||||
@@ -1802,8 +1809,12 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
||||
observerLocation.lat = lat;
|
||||
observerLocation.lon = lon;
|
||||
|
||||
// Save to localStorage for persistence
|
||||
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
|
||||
// Save to localStorage for persistence
|
||||
if (window.ObserverLocation) {
|
||||
ObserverLocation.setForModule('observerLocation', observerLocation);
|
||||
} else {
|
||||
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
|
||||
}
|
||||
|
||||
if (radarMap) {
|
||||
radarMap.setView([lat, lon], radarMap.getZoom());
|
||||
@@ -1830,8 +1841,12 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
||||
observerLocation.lat = position.coords.latitude;
|
||||
observerLocation.lon = position.coords.longitude;
|
||||
|
||||
// Save to localStorage for persistence
|
||||
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
|
||||
// Save to localStorage for persistence
|
||||
if (window.ObserverLocation) {
|
||||
ObserverLocation.setForModule('observerLocation', observerLocation);
|
||||
} else {
|
||||
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
|
||||
}
|
||||
|
||||
document.getElementById('obsLat').value = observerLocation.lat.toFixed(4);
|
||||
document.getElementById('obsLon').value = observerLocation.lon.toFixed(4);
|
||||
@@ -1920,14 +1935,17 @@ ACARS: ${r.statistics.acarsMessages} messages`;
|
||||
}
|
||||
});
|
||||
|
||||
function updateLocationFromGps(position) {
|
||||
observerLocation.lat = position.latitude;
|
||||
observerLocation.lon = position.longitude;
|
||||
document.getElementById('obsLat').value = position.latitude.toFixed(4);
|
||||
document.getElementById('obsLon').value = position.longitude.toFixed(4);
|
||||
|
||||
// Center map on GPS location (on first fix)
|
||||
if (radarMap && !radarMap._gpsInitialized) {
|
||||
function updateLocationFromGps(position) {
|
||||
observerLocation.lat = position.latitude;
|
||||
observerLocation.lon = position.longitude;
|
||||
document.getElementById('obsLat').value = position.latitude.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)
|
||||
if (radarMap && !radarMap._gpsInitialized) {
|
||||
radarMap.setView([position.latitude, position.longitude], radarMap.getZoom());
|
||||
radarMap._gpsInitialized = true;
|
||||
// Draw range rings immediately after centering
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/ais_dashboard.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>
|
||||
<body>
|
||||
<!-- Radar background effects -->
|
||||
@@ -219,7 +223,12 @@
|
||||
const MAX_TRAIL_POINTS = 50;
|
||||
|
||||
// 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 observerMarker = null;
|
||||
|
||||
@@ -376,17 +385,9 @@
|
||||
|
||||
// Initialize map
|
||||
function initMap() {
|
||||
// Load saved observer location
|
||||
const saved = localStorage.getItem('ais_observerLocation');
|
||||
if (saved) {
|
||||
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) {}
|
||||
if (observerLocation) {
|
||||
document.getElementById('obsLat').value = observerLocation.lat;
|
||||
document.getElementById('obsLon').value = observerLocation.lon;
|
||||
}
|
||||
|
||||
vesselMap = L.map('vesselMap', {
|
||||
@@ -470,7 +471,11 @@
|
||||
const lon = parseFloat(document.getElementById('obsLon').value);
|
||||
if (!isNaN(lat) && !isNaN(lon) && lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180) {
|
||||
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) {
|
||||
observerMarker.setLatLng([lat, lon]);
|
||||
}
|
||||
@@ -1058,7 +1063,11 @@
|
||||
drawRangeRings();
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
window._showDisclaimerOnLoad = true;
|
||||
}
|
||||
</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 -->
|
||||
{% if offline_settings.fonts_source == 'local' %}
|
||||
<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)
|
||||
let observerLocation = (function () {
|
||||
if (window.ObserverLocation && ObserverLocation.getForModule) {
|
||||
return ObserverLocation.getForModule('observerLocation');
|
||||
}
|
||||
const saved = localStorage.getItem('observerLocation');
|
||||
if (saved) {
|
||||
try {
|
||||
@@ -8364,8 +8371,15 @@
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
position => {
|
||||
document.getElementById('obsLat').value = position.coords.latitude.toFixed(4);
|
||||
document.getElementById('obsLon').value = position.coords.longitude.toFixed(4);
|
||||
const lat = position.coords.latitude;
|
||||
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!');
|
||||
},
|
||||
error => {
|
||||
@@ -8465,6 +8479,9 @@
|
||||
// Update observerLocation
|
||||
observerLocation.lat = position.latitude;
|
||||
observerLocation.lon = position.longitude;
|
||||
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
|
||||
ObserverLocation.setShared({ lat: position.latitude, lon: position.longitude });
|
||||
}
|
||||
|
||||
// Update APRS user location
|
||||
updateAprsUserLocation(position);
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/satellite_dashboard.css') }}">
|
||||
</head>
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/responsive.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>
|
||||
<body>
|
||||
<div class="grid-bg"></div>
|
||||
<div class="scanline"></div>
|
||||
@@ -313,16 +317,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setupEmbeddedMode();
|
||||
initGroundMap();
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
setInterval(updateCountdown, 1000);
|
||||
setInterval(updateRealTimePositions, 5000);
|
||||
loadAgents();
|
||||
getLocation();
|
||||
});
|
||||
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', () => {
|
||||
setupEmbeddedMode();
|
||||
const usedShared = applySharedObserverLocation();
|
||||
initGroundMap();
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
setInterval(updateCountdown, 1000);
|
||||
setInterval(updateRealTimePositions, 5000);
|
||||
loadAgents();
|
||||
if (!usedShared) {
|
||||
getLocation();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadAgents() {
|
||||
try {
|
||||
@@ -376,10 +397,13 @@
|
||||
|
||||
if (data.status === 'success' && data.result) {
|
||||
const agentStatus = data.result;
|
||||
if (agentStatus.gps_position) {
|
||||
const gps = agentStatus.gps_position;
|
||||
document.getElementById('obsLat').value = gps.lat.toFixed(4);
|
||||
document.getElementById('obsLon').value = gps.lon.toFixed(4);
|
||||
if (agentStatus.gps_position) {
|
||||
const gps = agentStatus.gps_position;
|
||||
document.getElementById('obsLat').value = gps.lat.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
|
||||
const agent = agents.find(a => a.id == agentId);
|
||||
@@ -430,16 +454,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getLocation() {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(pos => {
|
||||
document.getElementById('obsLat').value = pos.coords.latitude.toFixed(4);
|
||||
document.getElementById('obsLon').value = pos.coords.longitude.toFixed(4);
|
||||
calculatePasses();
|
||||
}, () => {
|
||||
calculatePasses();
|
||||
});
|
||||
} else {
|
||||
function getLocation() {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(pos => {
|
||||
const lat = pos.coords.latitude;
|
||||
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();
|
||||
});
|
||||
} else {
|
||||
calculatePasses();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user