// Import Three.js modules import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // Import our modular components import { WebSocketManager } from './modules/websocket.js?v=2'; import { AircraftManager } from './modules/aircraft-manager.js?v=2'; import { MapManager } from './modules/map-manager.js?v=2'; import { UIManager } from './modules/ui-manager.js?v=2'; import { CallsignManager } from './modules/callsign-manager.js'; class SkyView { constructor() { // Initialize managers this.wsManager = null; this.aircraftManager = null; this.mapManager = null; this.uiManager = null; this.callsignManager = null; // 3D Radar this.radar3d = null; // Charts this.charts = {}; // Selected aircraft tracking this.selectedAircraft = null; this.selectedTrailEnabled = false; // Debug/verbose logging control // Enable verbose logging with: ?verbose in URL or localStorage.setItem('skyview-verbose', 'true') const urlParams = new URLSearchParams(window.location.search); this.verbose = urlParams.has('verbose') || localStorage.getItem('skyview-verbose') === 'true'; // Set global verbose flag for other modules window.skyviewVerbose = this.verbose; this.init(); } async init() { try { // Initialize UI manager first this.uiManager = new UIManager(); this.uiManager.initializeViews(); this.uiManager.initializeEventListeners(); // Initialize callsign manager for enriched callsign display this.callsignManager = new CallsignManager(); // Initialize map manager and get the main map this.mapManager = new MapManager(); const map = await this.mapManager.initializeMap(); // Initialize aircraft manager with the map and callsign manager this.aircraftManager = new AircraftManager(map, this.callsignManager); // 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), (status) => this.uiManager.updateConnectionStatus(status) ); await this.wsManager.connect(); // Initialize other components this.initializeCharts(); this.uiManager.updateClocks(); // Flag to track 3D radar initialization this.radar3dInitialized = false; // Add view change listener for 3D radar initialization this.setupViewChangeListener(); // Set up map controls this.setupMapControls(); // Set up aircraft selection listener this.setupAircraftSelection(); this.startPeriodicTasks(); } catch (error) { console.error('Initialization failed:', error); this.uiManager.showError('Failed to initialize application'); } } setupMapControls() { const centerMapBtn = document.getElementById('center-map'); const resetMapBtn = document.getElementById('reset-map'); const toggleTrailsBtn = document.getElementById('toggle-trails'); const toggleSourcesBtn = document.getElementById('toggle-sources'); if (centerMapBtn) { centerMapBtn.addEventListener('click', () => { this.aircraftManager.centerMapOnAircraft(() => this.mapManager.getSourcePositions()); }); } if (resetMapBtn) { resetMapBtn.addEventListener('click', () => this.mapManager.resetMap()); } if (toggleTrailsBtn) { toggleTrailsBtn.addEventListener('click', () => { const showTrails = this.aircraftManager.toggleTrails(); toggleTrailsBtn.textContent = showTrails ? 'Hide Trails' : 'Show Trails'; }); } if (toggleSourcesBtn) { toggleSourcesBtn.addEventListener('click', () => { const showSources = this.mapManager.toggleSources(); toggleSourcesBtn.textContent = showSources ? 'Hide Sources' : 'Show Sources'; }); } // Setup collapsible sections this.setupCollapsibleSections(); const toggleDarkModeBtn = document.getElementById('toggle-dark-mode'); if (toggleDarkModeBtn) { toggleDarkModeBtn.addEventListener('click', () => { const isDarkMode = this.mapManager.toggleDarkMode(); toggleDarkModeBtn.innerHTML = isDarkMode ? '☀️ Light Mode' : '🌙 Night Mode'; }); } // Coverage controls const toggleHeatmapBtn = document.getElementById('toggle-heatmap'); const coverageSourceSelect = document.getElementById('coverage-source'); if (toggleHeatmapBtn) { toggleHeatmapBtn.addEventListener('click', async () => { const isActive = await this.mapManager.toggleHeatmap(); toggleHeatmapBtn.textContent = isActive ? 'Hide Heatmap' : 'Show Heatmap'; }); } if (coverageSourceSelect) { coverageSourceSelect.addEventListener('change', (e) => { this.mapManager.setSelectedSource(e.target.value); 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() { document.addEventListener('aircraftSelected', (e) => { 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; // Automatically enable selected aircraft trail when an aircraft is selected if (!this.selectedTrailEnabled) { this.selectedTrailEnabled = true; const selectedTrailCheckbox = document.getElementById('show-selected-trail'); if (selectedTrailCheckbox) { selectedTrailCheckbox.checked = true; } } // Show trail for newly selected aircraft 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); if (marker) { marker.openPopup(); } } }); } handleWebSocketMessage(message) { const aircraftCount = Object.keys(message.data.aircraft || {}).length; // Only log WebSocket messages in verbose mode if (this.verbose) { console.debug(`WebSocket message: ${message.type}, ${aircraftCount} aircraft, timestamp: ${message.timestamp}`); } switch (message.type) { case 'initial_data': console.log(`Received initial data with ${aircraftCount} aircraft`); this.updateData(message.data); // Setup source markers only on initial data load this.mapManager.updateSourceMarkers(); break; case 'aircraft_update': this.updateData(message.data); break; default: console.warn(`Unknown WebSocket message type: ${message.type}`); } } updateData(data) { // Update all managers with new data - ORDER MATTERS // Only log data updates in verbose mode if (this.verbose) { console.debug(`Updating data: ${Object.keys(data.aircraft || {}).length} aircraft`); } this.uiManager.updateData(data); this.aircraftManager.updateAircraftData(data); this.mapManager.updateSourcesData(data); // Update UI components - CRITICAL: updateMarkers must be called for track propagation this.aircraftManager.updateMarkers(); // Update map components that depend on aircraft data this.mapManager.updateSourceMarkers(); // Update UI tables and statistics this.uiManager.updateAircraftTable(); this.uiManager.updateStatistics(); this.uiManager.updateHeaderInfo(); // Clear selected aircraft if it no longer exists if (this.selectedAircraft && !this.aircraftManager.aircraftData.has(this.selectedAircraft)) { console.debug(`Selected aircraft ${this.selectedAircraft} no longer exists, clearing selection`); this.selectedAircraft = null; } // Update coverage controls this.mapManager.updateCoverageControls(); if (this.uiManager.currentView === 'radar3d-view' && this.radar3dInitialized) { this.update3DRadar(); } // Only log completion messages in verbose mode if (this.verbose) { console.debug(`Data update complete: ${this.aircraftManager.aircraftMarkers.size} markers displayed`); } } // View switching async switchView(viewId) { const actualViewId = this.uiManager.switchView(viewId); // Handle view-specific initialization const baseName = actualViewId.replace('-view', ''); switch (baseName) { case 'coverage': await this.mapManager.initializeCoverageMap(); break; case 'radar3d': this.update3DRadar(); break; } } // Charts initializeCharts() { const aircraftChartCanvas = document.getElementById('aircraft-chart'); if (!aircraftChartCanvas) { console.warn('Aircraft chart canvas not found'); return; } try { this.charts.aircraft = new Chart(aircraftChartCanvas, { type: 'line', data: { labels: [], datasets: [{ label: 'Aircraft Count', data: [], borderColor: '#00d4ff', backgroundColor: 'rgba(0, 212, 255, 0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { beginAtZero: true, ticks: { color: '#888' } } } } }); } catch (error) { console.warn('Chart.js not available, skipping charts initialization'); } } updateCharts() { if (!this.charts.aircraft) return; const now = new Date(); const timeLabel = now.toLocaleTimeString(); // Update aircraft count chart const chart = this.charts.aircraft; chart.data.labels.push(timeLabel); chart.data.datasets[0].data.push(this.aircraftManager.aircraftData.size); if (chart.data.labels.length > 20) { chart.data.labels.shift(); chart.data.datasets[0].data.shift(); } chart.update('none'); } setupViewChangeListener() { // Override the ui manager's switchView to handle 3D radar initialization const originalSwitchView = this.uiManager.switchView.bind(this.uiManager); this.uiManager.switchView = (viewId) => { const result = originalSwitchView(viewId); // Initialize 3D radar when switching to 3D radar view if (viewId === 'radar3d-view' && !this.radar3dInitialized) { setTimeout(() => { this.initialize3DRadar(); this.radar3dInitialized = true; }, 100); // Small delay to ensure the view is visible } return result; }; } // 3D Radar (basic implementation) initialize3DRadar() { if (this.verbose) { console.log('🚀 Starting 3D radar initialization'); } try { const container = document.getElementById('radar3d-container'); if (!container) { console.error('❌ Container radar3d-container not found'); return; } if (this.verbose) { console.log('✅ Container found:', container, 'Size:', container.clientWidth, 'x', container.clientHeight); // Check if container is visible const containerStyles = window.getComputedStyle(container); console.log('📋 Container styles:', { display: containerStyles.display, visibility: containerStyles.visibility, width: containerStyles.width, height: containerStyles.height }); } // Check if Three.js is available if (typeof THREE === 'undefined') { console.error('❌ Three.js is not available'); return; } if (this.verbose) { console.log('✅ Three.js available, version:', THREE.REVISION); } // Quick WebGL test const testCanvas = document.createElement('canvas'); const gl = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl'); if (!gl) { console.error('❌ WebGL is not supported in this browser'); container.innerHTML = '