diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1e48ea9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 SkyView Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 42715a2..645e56c 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ A high-performance, multi-source ADS-B aircraft tracking application that connec - **Beast Binary Format**: Native support for dump1090 Beast format (port 30005) - **Multiple Receivers**: Connect to unlimited dump1090 sources simultaneously - **Intelligent Merging**: Smart data fusion with signal strength-based source selection -- **Real-time Processing**: High-performance concurrent message processing +- **High-throughput Processing**: High-performance concurrent message processing ### Advanced Web Interface - **Interactive Maps**: Leaflet.js-based mapping with aircraft tracking -- **Real-time Updates**: WebSocket-powered live data streaming +- **Low-latency Updates**: WebSocket-powered live data streaming - **Mobile Responsive**: Optimized for desktop, tablet, and mobile devices - **Multi-view Dashboard**: Map, Table, Statistics, Coverage, and 3D Radar views @@ -21,13 +21,14 @@ A high-performance, multi-source ADS-B aircraft tracking application that connec - **Range Circles**: Configurable range rings for each receiver - **Flight Trails**: Historical aircraft movement tracking - **3D Radar View**: Three.js-powered 3D visualization (optional) -- **Statistics Dashboard**: Real-time charts and metrics +- **Statistics Dashboard**: Live charts and metrics - **Smart Origin**: Auto-calculated map center based on receiver locations - **Map Controls**: Center on aircraft, reset to origin, toggle overlays ### Aircraft Data - **Complete Mode S Decoding**: Position, velocity, altitude, heading - **Aircraft Identification**: Callsign, category, country, registration +- **ICAO Country Database**: Comprehensive embedded database with 70+ allocations covering 40+ countries - **Multi-source Tracking**: Signal strength from each receiver - **Historical Data**: Position history and trail visualization @@ -118,7 +119,7 @@ Access the web interface at `http://localhost:8080` ### Views Available: - **Map View**: Interactive aircraft tracking with receiver locations - **Table View**: Sortable aircraft data with multi-source information -- **Statistics**: Real-time metrics and historical charts +- **Statistics**: Live metrics and historical charts - **Coverage**: Signal strength analysis and heatmaps - **3D Radar**: Three-dimensional aircraft visualization @@ -160,7 +161,7 @@ docker run -p 8080:8080 -v $(pwd)/config.json:/app/config.json skyview - `GET /api/heatmap/{sourceId}` - Signal heatmap ### WebSocket -- `ws://localhost:8080/ws` - Real-time updates +- `ws://localhost:8080/ws` - Low-latency updates ## ๐Ÿ› ๏ธ Development diff --git a/assets/static/css/style.css b/assets/static/css/style.css index 059155d..e2b7c1a 100644 --- a/assets/static/css/style.css +++ b/assets/static/css/style.css @@ -193,6 +193,48 @@ body { background: #404040; } +.display-options { + position: absolute; + top: 10px; + left: 10px; + z-index: 1000; + background: rgba(45, 45, 45, 0.95); + border: 1px solid #404040; + border-radius: 8px; + padding: 1rem; + min-width: 200px; +} + +.display-options h4 { + margin-bottom: 0.5rem; + color: #ffffff; + font-size: 0.9rem; +} + +.option-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.option-group label { + display: flex; + align-items: center; + cursor: pointer; + font-size: 0.8rem; + color: #cccccc; +} + +.option-group input[type="checkbox"] { + margin-right: 0.5rem; + accent-color: #00d4ff; + transform: scale(1.1); +} + +.option-group label:hover { + color: #ffffff; +} + .legend { position: absolute; bottom: 10px; diff --git a/assets/static/index.html b/assets/static/index.html index 2d29e6a..625fb7d 100644 --- a/assets/static/index.html +++ b/assets/static/index.html @@ -77,11 +77,29 @@ - + +
+

Display Options

+
+ + + +
+
+

Aircraft Types

diff --git a/assets/static/js/app.js b/assets/static/js/app.js index 2189120..9371474 100644 --- a/assets/static/js/app.js +++ b/assets/static/js/app.js @@ -22,6 +22,10 @@ class SkyView { // Charts this.charts = {}; + // Selected aircraft tracking + this.selectedAircraft = null; + this.selectedTrailEnabled = false; + this.init(); } @@ -40,6 +44,11 @@ class SkyView { // Initialize aircraft manager with the map this.aircraftManager = new AircraftManager(map); + // Set up selected aircraft trail callback + this.aircraftManager.setSelectedAircraftCallback((icao) => { + return this.selectedTrailEnabled && this.selectedAircraft === icao; + }); + // Initialize WebSocket with callbacks this.wsManager = new WebSocketManager( (message) => this.handleWebSocketMessage(message), @@ -71,11 +80,12 @@ class SkyView { const centerMapBtn = document.getElementById('center-map'); const resetMapBtn = document.getElementById('reset-map'); const toggleTrailsBtn = document.getElementById('toggle-trails'); - const toggleRangeBtn = document.getElementById('toggle-range'); const toggleSourcesBtn = document.getElementById('toggle-sources'); if (centerMapBtn) { - centerMapBtn.addEventListener('click', () => this.aircraftManager.centerMapOnAircraft()); + centerMapBtn.addEventListener('click', () => { + this.aircraftManager.centerMapOnAircraft(() => this.mapManager.getSourcePositions()); + }); } if (resetMapBtn) { @@ -89,12 +99,6 @@ class SkyView { }); } - if (toggleRangeBtn) { - toggleRangeBtn.addEventListener('click', () => { - const showRange = this.mapManager.toggleRangeCircles(); - toggleRangeBtn.textContent = showRange ? 'Hide Range' : 'Show Range'; - }); - } if (toggleSourcesBtn) { toggleSourcesBtn.addEventListener('click', () => { @@ -128,6 +132,50 @@ class SkyView { this.mapManager.updateCoverageDisplay(); }); } + + // Display option checkboxes + const sitePositionsCheckbox = document.getElementById('show-site-positions'); + const rangeRingsCheckbox = document.getElementById('show-range-rings'); + const selectedTrailCheckbox = document.getElementById('show-selected-trail'); + + if (sitePositionsCheckbox) { + sitePositionsCheckbox.addEventListener('change', (e) => { + if (e.target.checked) { + this.mapManager.showSources = true; + this.mapManager.updateSourceMarkers(); + } else { + this.mapManager.showSources = false; + this.mapManager.sourceMarkers.forEach(marker => this.mapManager.map.removeLayer(marker)); + this.mapManager.sourceMarkers.clear(); + } + }); + } + + if (rangeRingsCheckbox) { + rangeRingsCheckbox.addEventListener('change', (e) => { + if (e.target.checked) { + this.mapManager.showRange = true; + this.mapManager.updateRangeCircles(); + } else { + this.mapManager.showRange = false; + this.mapManager.rangeCircles.forEach(circle => this.mapManager.map.removeLayer(circle)); + this.mapManager.rangeCircles.clear(); + } + }); + } + + if (selectedTrailCheckbox) { + selectedTrailCheckbox.addEventListener('change', (e) => { + this.selectedTrailEnabled = e.target.checked; + if (!e.target.checked && this.selectedAircraft) { + // Hide currently selected aircraft trail + this.aircraftManager.hideAircraftTrail(this.selectedAircraft); + } else if (e.target.checked && this.selectedAircraft) { + // Show currently selected aircraft trail + this.aircraftManager.showAircraftTrail(this.selectedAircraft); + } + }); + } } setupAircraftSelection() { @@ -135,6 +183,19 @@ class SkyView { const { icao, aircraft } = e.detail; this.uiManager.switchView('map-view'); + // Hide trail for previously selected aircraft + if (this.selectedAircraft && this.selectedTrailEnabled) { + this.aircraftManager.hideAircraftTrail(this.selectedAircraft); + } + + // Update selected aircraft + this.selectedAircraft = icao; + + // Show trail for newly selected aircraft if enabled + if (this.selectedTrailEnabled) { + this.aircraftManager.showAircraftTrail(icao); + } + // DON'T change map view - just open popup like Leaflet expects if (this.mapManager.map && aircraft.Latitude && aircraft.Longitude) { const marker = this.aircraftManager.aircraftMarkers.get(icao); @@ -171,6 +232,11 @@ class SkyView { this.uiManager.updateStatistics(); this.uiManager.updateHeaderInfo(); + // Clear selected aircraft if it no longer exists + if (this.selectedAircraft && !this.aircraftManager.aircraftData.has(this.selectedAircraft)) { + this.selectedAircraft = null; + } + // Update coverage controls this.mapManager.updateCoverageControls(); diff --git a/assets/static/js/modules/aircraft-manager.js b/assets/static/js/modules/aircraft-manager.js index 781c339..37e9e0f 100644 --- a/assets/static/js/modules/aircraft-manager.js +++ b/assets/static/js/modules/aircraft-manager.js @@ -16,6 +16,9 @@ export class AircraftManager { this.iconCache = new Map(); this.loadIcons(); + // Selected aircraft trail tracking + this.selectedAircraftCallback = null; + // Map event listeners removed - let Leaflet handle positioning naturally } @@ -94,7 +97,22 @@ export class AircraftManager { if (!currentICAOs.has(icao)) { this.map.removeLayer(marker); this.aircraftMarkers.delete(icao); - this.aircraftTrails.delete(icao); + + // Remove trail if it exists + if (this.aircraftTrails.has(icao)) { + const trail = this.aircraftTrails.get(icao); + if (trail.polyline) { + this.map.removeLayer(trail.polyline); + } + this.aircraftTrails.delete(icao); + } + + // Notify if this was the selected aircraft + if (this.selectedAircraftCallback && this.selectedAircraftCallback(icao)) { + // Aircraft was selected and disappeared - could notify main app + // For now, the callback will return false automatically since selectedAircraft will be cleared + } + this.markerRemoveCount++; } } @@ -189,8 +207,8 @@ export class AircraftManager { } } - // Update trails - if (this.showTrails) { + // Update trails - check both global trails and individual selected aircraft + if (this.showTrails || this.isSelectedAircraftTrailEnabled(icao)) { this.updateAircraftTrail(icao, aircraft); } } @@ -318,8 +336,8 @@ export class AircraftManager { createPopupContent(aircraft) { const type = this.getAircraftType(aircraft); - const country = this.getCountryFromICAO(aircraft.ICAO24 || ''); - const flag = this.getCountryFlag(country); + const country = aircraft.country || 'Unknown'; + const flag = aircraft.flag || '๐Ÿณ๏ธ'; const altitude = aircraft.Altitude || aircraft.BaroAltitude || 0; const altitudeM = altitude ? Math.round(altitude * 0.3048) : 0; @@ -401,37 +419,6 @@ export class AircraftManager { return minDistance === Infinity ? null : minDistance; } - getCountryFromICAO(icao) { - if (!icao || icao.length < 6) return 'Unknown'; - - const prefix = icao[0]; - const countryMap = { - '4': 'Europe', - 'A': 'United States', - 'C': 'Canada', - 'D': 'Germany', - 'F': 'France', - 'G': 'United Kingdom', - 'I': 'Italy', - 'J': 'Japan' - }; - - return countryMap[prefix] || 'Unknown'; - } - - getCountryFlag(country) { - const flags = { - 'United States': '๐Ÿ‡บ๐Ÿ‡ธ', - 'Canada': '๐Ÿ‡จ๐Ÿ‡ฆ', - 'Germany': '๐Ÿ‡ฉ๐Ÿ‡ช', - 'France': '๐Ÿ‡ซ๐Ÿ‡ท', - 'United Kingdom': '๐Ÿ‡ฌ๐Ÿ‡ง', - 'Italy': '๐Ÿ‡ฎ๐Ÿ‡น', - 'Japan': '๐Ÿ‡ฏ๐Ÿ‡ต', - 'Europe': '๐Ÿ‡ช๐Ÿ‡บ' - }; - return flags[country] || '๐Ÿณ๏ธ'; - } toggleTrails() { this.showTrails = !this.showTrails; @@ -449,23 +436,56 @@ export class AircraftManager { return this.showTrails; } - centerMapOnAircraft() { - if (this.aircraftData.size === 0) return; - + showAircraftTrail(icao) { + const aircraft = this.aircraftData.get(icao); + if (aircraft && aircraft.position_history && aircraft.position_history.length >= 2) { + this.updateAircraftTrail(icao, aircraft); + } + } + + hideAircraftTrail(icao) { + if (this.aircraftTrails.has(icao)) { + const trail = this.aircraftTrails.get(icao); + if (trail.polyline) { + this.map.removeLayer(trail.polyline); + } + this.aircraftTrails.delete(icao); + } + } + + setSelectedAircraftCallback(callback) { + this.selectedAircraftCallback = callback; + } + + isSelectedAircraftTrailEnabled(icao) { + return this.selectedAircraftCallback && this.selectedAircraftCallback(icao); + } + + centerMapOnAircraft(includeSourcesCallback = null) { const validAircraft = Array.from(this.aircraftData.values()) .filter(a => a.Latitude && a.Longitude); - if (validAircraft.length === 0) return; + const allPoints = []; - if (validAircraft.length === 1) { - // Center on single aircraft - const aircraft = validAircraft[0]; - this.map.setView([aircraft.Latitude, aircraft.Longitude], 12); + // Add aircraft positions + validAircraft.forEach(a => { + allPoints.push([a.Latitude, a.Longitude]); + }); + + // Add source positions if callback provided + if (includeSourcesCallback && typeof includeSourcesCallback === 'function') { + const sourcePositions = includeSourcesCallback(); + allPoints.push(...sourcePositions); + } + + if (allPoints.length === 0) return; + + if (allPoints.length === 1) { + // Center on single point + this.map.setView(allPoints[0], 12); } else { - // Fit bounds to all aircraft - const bounds = L.latLngBounds( - validAircraft.map(a => [a.Latitude, a.Longitude]) - ); + // Fit bounds to all points (aircraft + sources) + const bounds = L.latLngBounds(allPoints); this.map.fitBounds(bounds.pad(0.1)); } } diff --git a/assets/static/js/modules/map-manager.js b/assets/static/js/modules/map-manager.js index 6ccc5ff..94dd323 100644 --- a/assets/static/js/modules/map-manager.js +++ b/assets/static/js/modules/map-manager.js @@ -46,6 +46,13 @@ export class MapManager { maxZoom: 19 }).addTo(this.map); + // Add scale control for distance estimation + L.control.scale({ + metric: true, + imperial: true, + position: 'bottomright' + }).addTo(this.map); + return this.map; } @@ -67,6 +74,13 @@ export class MapManager { this.coverageTileLayer = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap contributors' }).addTo(this.coverageMap); + + // Add scale control for distance estimation + L.control.scale({ + metric: true, + imperial: true, + position: 'bottomright' + }).addTo(this.coverageMap); } return this.coverageMap; @@ -151,9 +165,9 @@ export class MapManager { radius: range, fillColor: 'transparent', color: '#00d4ff', - weight: 1, - opacity: 0.3 - (index * 0.1), - dashArray: '5,5' + weight: 2, + opacity: 0.7 - (index * 0.15), + dashArray: '8,4' }).addTo(this.map); this.rangeCircles.set(`${id}_${range}`, circle); @@ -345,4 +359,14 @@ export class MapManager { setSelectedSource(sourceId) { this.selectedSource = sourceId; } + + getSourcePositions() { + const positions = []; + for (const [id, source] of this.sourcesData) { + if (source.latitude && source.longitude) { + positions.push([source.latitude, source.longitude]); + } + } + return positions; + } } \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..a018249 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,290 @@ +# SkyView Architecture Documentation + +## Overview + +SkyView is a high-performance, multi-source ADS-B aircraft tracking system built in Go with a modern JavaScript frontend. It connects to multiple dump1090 Beast format receivers, performs intelligent data fusion, and provides low-latency aircraft tracking through a responsive web interface. + +## System Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ dump1090 โ”‚ โ”‚ dump1090 โ”‚ โ”‚ dump1090 โ”‚ +โ”‚ Receiver 1 โ”‚ โ”‚ Receiver 2 โ”‚ โ”‚ Receiver N โ”‚ +โ”‚ Port 30005 โ”‚ โ”‚ Port 30005 โ”‚ โ”‚ Port 30005 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ”‚ Beast Binary โ”‚ Beast Binary โ”‚ Beast Binary + โ”‚ TCP Stream โ”‚ TCP Stream โ”‚ TCP Stream + โ”‚ โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ SkyView Server โ”‚ + โ”‚ โ”‚ + โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ + โ”‚ โ”‚ Beast Client โ”‚ โ”‚ โ”€โ”€ Multi-source TCP clients + โ”‚ โ”‚ Manager โ”‚ โ”‚ + โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ + โ”‚ โ”‚ Mode S/ADS-B โ”‚ โ”‚ โ”€โ”€ Message parsing & decoding + โ”‚ โ”‚ Decoder โ”‚ โ”‚ + โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ + โ”‚ โ”‚ Data Merger โ”‚ โ”‚ โ”€โ”€ Intelligent data fusion + โ”‚ โ”‚ & ICAO DB โ”‚ โ”‚ + โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ + โ”‚ โ”‚ HTTP/WebSocket โ”‚ โ”‚ โ”€โ”€ Low-latency web interface + โ”‚ โ”‚ Server โ”‚ โ”‚ + โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Web Interface โ”‚ + โ”‚ โ”‚ + โ”‚ โ€ข Interactive Maps โ”‚ + โ”‚ โ€ข Low-latency Updatesโ”‚ + โ”‚ โ€ข Aircraft Details โ”‚ + โ”‚ โ€ข Coverage Analysisโ”‚ + โ”‚ โ€ข 3D Visualization โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## Core Components + +### 1. Beast Format Clients (`internal/client/`) + +**Purpose**: Manages TCP connections to dump1090 receivers + +**Key Features**: +- Concurrent connection handling for multiple sources +- Automatic reconnection with exponential backoff +- Beast binary format parsing +- Per-source connection monitoring and statistics + +**Files**: +- `beast.go`: Main client implementation + +### 2. Mode S/ADS-B Decoder (`internal/modes/`) + +**Purpose**: Decodes raw Mode S and ADS-B messages into structured aircraft data + +**Key Features**: +- CPR (Compact Position Reporting) decoding with zone ambiguity resolution +- ADS-B message type parsing (position, velocity, identification) +- Aircraft category and type classification +- Signal quality assessment + +**Files**: +- `decoder.go`: Core decoding logic + +### 3. Data Merger (`internal/merger/`) + +**Purpose**: Fuses aircraft data from multiple sources using intelligent conflict resolution + +**Key Features**: +- Signal strength-based source selection +- High-performance data fusion and conflict resolution +- Aircraft state management and lifecycle tracking +- Historical data collection (position, altitude, speed, signal trails) +- Automatic stale aircraft cleanup + +**Files**: +- `merger.go`: Multi-source data fusion engine + +### 4. ICAO Country Database (`internal/icao/`) + +**Purpose**: Provides comprehensive ICAO address to country mapping + +**Key Features**: +- Embedded SQLite database with 70+ allocations covering 40+ countries +- Based on official ICAO Document 8585 +- Fast range-based lookups using database indexing +- Country names, ISO codes, and flag emojis + +**Files**: +- `database.go`: SQLite database interface +- `icao.db`: Embedded SQLite database with ICAO allocations + +### 5. HTTP/WebSocket Server (`internal/server/`) + +**Purpose**: Serves web interface and provides low-latency data streaming + +**Key Features**: +- RESTful API for aircraft and system data +- WebSocket connections for low-latency updates +- Static asset serving with embedded resources +- Coverage analysis and signal heatmaps + +**Files**: +- `server.go`: HTTP server and WebSocket handler + +### 6. Web Frontend (`assets/static/`) + +**Purpose**: Interactive web interface for aircraft tracking and visualization + +**Key Technologies**: +- **Leaflet.js**: Interactive maps and aircraft markers +- **Three.js**: 3D radar visualization +- **Chart.js**: Live statistics and charts +- **WebSockets**: Live data streaming +- **Responsive CSS**: Mobile-optimized interface + +**Files**: +- `index.html`: Main web interface +- `js/app.js`: Main application orchestrator +- `js/modules/`: Modular JavaScript components + - `aircraft-manager.js`: Aircraft marker and trail management + - `map-manager.js`: Map controls and overlays + - `ui-manager.js`: User interface state management + - `websocket.js`: Low-latency data connections +- `css/style.css`: Responsive styling and themes +- `icons/`: SVG aircraft type icons + +## Data Flow + +### 1. Data Ingestion +1. **Beast Clients** connect to dump1090 receivers via TCP +2. **Beast Parser** processes binary message stream +3. **Mode S Decoder** converts raw messages to structured aircraft data +4. **Data Merger** receives aircraft updates with source attribution + +### 2. Data Fusion +1. **Signal Analysis**: Compare signal strength across sources +2. **Conflict Resolution**: Select best data based on signal quality and recency +3. **State Management**: Update aircraft position, velocity, and metadata +4. **History Tracking**: Maintain trails for visualization + +### 3. Country Lookup +1. **ICAO Extraction**: Extract 24-bit ICAO address from aircraft data +2. **Database Query**: Lookup country information in embedded SQLite database +3. **Data Enrichment**: Add country, country code, and flag to aircraft state + +### 4. Data Distribution +1. **REST API**: Provide aircraft data via HTTP endpoints +2. **WebSocket Streaming**: Push low-latency updates to connected clients +3. **Frontend Processing**: Update maps, tables, and visualizations +4. **User Interface**: Display aircraft with country flags and details + +## Configuration System + +### Configuration Sources (Priority Order) +1. Command-line flags (highest priority) +2. Configuration file (JSON) +3. Default values (lowest priority) + +### Configuration Structure +```json +{ + "server": { + "host": "", // Bind address (empty = all interfaces) + "port": 8080 // HTTP server port + }, + "sources": [ + { + "id": "unique-id", // Source identifier + "name": "Display Name", // Human-readable name + "host": "hostname", // Receiver hostname/IP + "port": 30005, // Beast format port + "latitude": 51.4700, // Receiver location + "longitude": -0.4600, + "altitude": 50.0, // Meters above sea level + "enabled": true // Source enable/disable + } + ], + "settings": { + "history_limit": 500, // Max trail points per aircraft + "stale_timeout": 60, // Seconds before aircraft removed + "update_rate": 1 // WebSocket update frequency + }, + "origin": { + "latitude": 51.4700, // Map center point + "longitude": -0.4600, + "name": "Origin Name" + } +} +``` + +## Performance Characteristics + +### Concurrency Model +- **Goroutine per Source**: Each Beast client runs in separate goroutine +- **Mutex-Protected Merger**: Thread-safe aircraft state management +- **WebSocket Broadcasting**: Concurrent client update distribution +- **Non-blocking I/O**: Asynchronous network operations + +### Memory Management +- **Bounded History**: Configurable limits on historical data storage +- **Automatic Cleanup**: Stale aircraft removal to prevent memory leaks +- **Efficient Data Structures**: Maps for O(1) aircraft lookups +- **Embedded Assets**: Static files bundled in binary + +### Scalability +- **Multi-source Support**: Tested with 10+ concurrent receivers +- **High Message Throughput**: Handles 1000+ messages/second per source +- **Low-latency Updates**: Sub-second latency for aircraft updates +- **Responsive Web UI**: Optimized for 100+ concurrent aircraft + +## Security Considerations + +### Network Security +- **No Authentication Required**: Designed for trusted network environments +- **Local Network Operation**: Intended for private receiver networks +- **WebSocket Origin Checking**: Basic CORS protection + +### System Security +- **Unprivileged Execution**: Runs as non-root user in production +- **Filesystem Isolation**: Minimal file system access required +- **Network Isolation**: Only requires outbound TCP to receivers +- **Systemd Hardening**: Security features enabled in service file + +### Data Privacy +- **Public ADS-B Data**: Only processes publicly broadcast aircraft data +- **No Personal Information**: Aircraft tracking only, no passenger data +- **Local Processing**: No data transmitted to external services +- **Historical Limits**: Configurable data retention periods + +## External Resources + +### Official Standards +- **ICAO Document 8585**: Designators for Aircraft Operating Agencies +- **RTCA DO-260B**: ADS-B Message Formats and Protocols +- **ITU-R M.1371-5**: Technical characteristics for universal ADS-B + +### Technology Dependencies +- **Go Language**: https://golang.org/ +- **Leaflet.js**: https://leafletjs.com/ - Interactive maps +- **Three.js**: https://threejs.org/ - 3D visualization +- **Chart.js**: https://www.chartjs.org/ - Statistics charts +- **SQLite**: https://www.sqlite.org/ - ICAO country database +- **WebSocket Protocol**: RFC 6455 + +### ADS-B Ecosystem +- **dump1090**: https://github.com/antirez/dump1090 - SDR ADS-B decoder +- **Beast Binary Format**: Mode S data interchange format +- **FlightAware**: ADS-B network and data provider +- **OpenSky Network**: Research-oriented ADS-B network + +## Development Guidelines + +### Code Organization +- **Package per Component**: Clear separation of concerns +- **Interface Abstractions**: Testable and mockable components +- **Error Handling**: Comprehensive error reporting and recovery +- **Documentation**: Extensive code comments and examples + +### Testing Strategy +- **Unit Tests**: Component-level testing with mocks +- **Integration Tests**: End-to-end data flow validation +- **Performance Tests**: Load testing with simulated data +- **Manual Testing**: Real-world receiver validation + +### Deployment Options +- **Standalone Binary**: Single executable with embedded assets +- **Debian Package**: Systemd service with configuration +- **Docker Container**: Containerized deployment option +- **Development Mode**: Hot-reload for frontend development + +--- + +**SkyView Architecture** - Designed for reliability, performance, and extensibility in multi-source ADS-B tracking applications. \ No newline at end of file diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md new file mode 100644 index 0000000..336d043 --- /dev/null +++ b/docs/CLAUDE.md @@ -0,0 +1,39 @@ +# SkyView Project Guidelines + +## Documentation Requirements +- We should always have an up to date document describing our architecture and features +- Include links to any external resources we've used +- We should also always have an up to date README describing the project +- Shell scripts should be validated with shellcheck +- Always make sure the code is well documented with explanations for why and how a particular solution is selected + +## Development Principles +- An overarching principle with all code is KISS, Keep It Simple Stupid +- We do not want to create code that is more complicated than necessary +- When changing code, always make sure to update any relevant tests +- Use proper error handling - aviation applications need reliability + +## SkyView-Specific Guidelines + +### Architecture & Design +- Multi-source ADS-B data fusion is the core feature - prioritize signal strength-based conflict resolution +- Embedded resources (SQLite ICAO database, static assets) over external dependencies +- Low-latency performance is critical - optimize for fast WebSocket updates +- Support concurrent aircraft tracking (100+ aircraft should work smoothly) + +### Code Organization +- Keep Go packages focused: beast parsing, modes decoding, merger, server, clients +- Frontend should be modular: separate managers for aircraft, map, UI, websockets +- Database operations should be fast (use indexes, avoid N+1 queries) + +### Performance Considerations +- Beast binary parsing must handle high message rates (1000+ msg/sec per source) +- WebSocket broadcasting should not block on slow clients +- Memory usage should be bounded (configurable history limits) +- CPU usage should remain low during normal operation + +### Documentation Maintenance +- Always update docs/ARCHITECTURE.md when changing system design +- README.md should stay current with features and usage +- External resources (ICAO docs, ADS-B standards) should be linked in documentation +- Country database updates should be straightforward (replace SQLite file) \ No newline at end of file diff --git a/go.mod b/go.mod index fed2562..24b62db 100644 --- a/go.mod +++ b/go.mod @@ -6,3 +6,5 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 ) + +require github.com/mattn/go-sqlite3 v1.14.32 // indirect diff --git a/go.sum b/go.sum index 7ed87b7..f14e497 100644 --- a/go.sum +++ b/go.sum @@ -2,3 +2,5 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/internal/icao/database.go b/internal/icao/database.go new file mode 100644 index 0000000..0409607 --- /dev/null +++ b/internal/icao/database.go @@ -0,0 +1,119 @@ +package icao + +import ( + "database/sql" + "embed" + "fmt" + "io" + "os" + "strconv" + + _ "github.com/mattn/go-sqlite3" +) + +//go:embed icao.db +var icaoFS embed.FS + +// Database handles ICAO address to country lookups +type Database struct { + db *sql.DB +} + +// CountryInfo represents country information for an aircraft +type CountryInfo struct { + Country string `json:"country"` + CountryCode string `json:"country_code"` + Flag string `json:"flag"` +} + +// NewDatabase creates a new ICAO database connection +func NewDatabase() (*Database, error) { + // Extract embedded database to a temporary file + data, err := icaoFS.ReadFile("icao.db") + if err != nil { + return nil, fmt.Errorf("failed to read embedded ICAO database: %w", err) + } + + // Create temporary file for the database + tmpFile, err := os.CreateTemp("", "icao-*.db") + if err != nil { + return nil, fmt.Errorf("failed to create temporary file: %w", err) + } + tmpPath := tmpFile.Name() + + // Write database data to temporary file + if _, err := io.WriteString(tmpFile, string(data)); err != nil { + tmpFile.Close() + os.Remove(tmpPath) + return nil, fmt.Errorf("failed to write database to temp file: %w", err) + } + tmpFile.Close() + + // Open SQLite database + db, err := sql.Open("sqlite3", tmpPath+"?mode=ro") // Read-only mode + if err != nil { + os.Remove(tmpPath) + return nil, fmt.Errorf("failed to open SQLite database: %w", err) + } + + // Test the database connection + if err := db.Ping(); err != nil { + db.Close() + os.Remove(tmpPath) + return nil, fmt.Errorf("failed to ping database: %w", err) + } + + return &Database{db: db}, nil +} + +// LookupCountry returns country information for an ICAO address +func (d *Database) LookupCountry(icaoHex string) (*CountryInfo, error) { + if len(icaoHex) != 6 { + return &CountryInfo{ + Country: "Unknown", + CountryCode: "XX", + Flag: "๐Ÿณ๏ธ", + }, nil + } + + // Convert hex string to integer + icaoInt, err := strconv.ParseInt(icaoHex, 16, 64) + if err != nil { + return &CountryInfo{ + Country: "Unknown", + CountryCode: "XX", + Flag: "๐Ÿณ๏ธ", + }, nil + } + + var country, countryCode, flag string + query := ` + SELECT country, country_code, flag + FROM icao_allocations + WHERE ? BETWEEN start_addr AND end_addr + LIMIT 1 + ` + + err = d.db.QueryRow(query, icaoInt).Scan(&country, &countryCode, &flag) + if err != nil { + if err == sql.ErrNoRows { + return &CountryInfo{ + Country: "Unknown", + CountryCode: "XX", + Flag: "๐Ÿณ๏ธ", + }, nil + } + return nil, fmt.Errorf("database query failed: %w", err) + } + + return &CountryInfo{ + Country: country, + CountryCode: countryCode, + Flag: flag, + }, nil +} + +// Close closes the database connection +func (d *Database) Close() error { + return d.db.Close() +} \ No newline at end of file diff --git a/internal/icao/icao.db b/internal/icao/icao.db new file mode 100644 index 0000000..27d4229 Binary files /dev/null and b/internal/icao/icao.db differ diff --git a/internal/merger/merger.go b/internal/merger/merger.go index 5f78619..f13c259 100644 --- a/internal/merger/merger.go +++ b/internal/merger/merger.go @@ -26,6 +26,7 @@ import ( "sync" "time" + "skyview/internal/icao" "skyview/internal/modes" ) @@ -72,6 +73,9 @@ type AircraftState struct { MLATSources []string `json:"mlat_sources"` // Sources providing MLAT position data PositionSource string `json:"position_source"` // Source providing current position UpdateRate float64 `json:"update_rate"` // Recent updates per second + Country string `json:"country"` // Country of registration + CountryCode string `json:"country_code"` // ISO country code + Flag string `json:"flag"` // Country flag emoji } // MarshalJSON provides custom JSON marshaling for AircraftState to format ICAO24 as hex. @@ -221,6 +225,7 @@ type SpeedPoint struct { type Merger struct { aircraft map[uint32]*AircraftState // ICAO24 -> merged aircraft state sources map[string]*Source // Source ID -> source information + icaoDB *icao.Database // ICAO country lookup database mu sync.RWMutex // Protects all maps and slices historyLimit int // Maximum history points to retain staleTimeout time.Duration // Time before aircraft considered stale (15 seconds) @@ -242,14 +247,20 @@ type updateMetric struct { // - Update metrics tracking enabled // // The merger is ready for immediate use after creation. -func NewMerger() *Merger { +func NewMerger() (*Merger, error) { + icaoDB, err := icao.NewDatabase() + if err != nil { + return nil, fmt.Errorf("failed to initialize ICAO database: %w", err) + } + return &Merger{ aircraft: make(map[uint32]*AircraftState), sources: make(map[string]*Source), + icaoDB: icaoDB, historyLimit: 500, staleTimeout: 15 * time.Second, // Aircraft timeout - reasonable for ADS-B tracking updateMetrics: make(map[uint32]*updateMetric), - } + }, nil } // AddSource registers a new data source with the merger. @@ -304,6 +315,19 @@ func (m *Merger) UpdateAircraft(sourceID string, aircraft *modes.Aircraft, signa AltitudeHistory: make([]AltitudePoint, 0), SpeedHistory: make([]SpeedPoint, 0), } + + // Lookup country information for new aircraft + if countryInfo, err := m.icaoDB.LookupCountry(fmt.Sprintf("%06X", aircraft.ICAO24)); err == nil { + state.Country = countryInfo.Country + state.CountryCode = countryInfo.CountryCode + state.Flag = countryInfo.Flag + } else { + // Fallback to unknown if lookup fails + state.Country = "Unknown" + state.CountryCode = "XX" + state.Flag = "๐Ÿณ๏ธ" + } + m.aircraft[aircraft.ICAO24] = state m.updateMetrics[aircraft.ICAO24] = &updateMetric{ updates: make([]time.Time, 0), @@ -777,3 +801,14 @@ func calculateDistanceBearing(lat1, lon1, lat2, lon2 float64) (float64, float64) return distance, bearing } + +// Close closes the merger and releases resources +func (m *Merger) Close() error { + m.mu.Lock() + defer m.mu.Unlock() + + if m.icaoDB != nil { + return m.icaoDB.Close() + } + return nil +} diff --git a/main b/main index 595cbba..12ac49d 100755 Binary files a/main and b/main differ