2025-08-24 00:19:00 +02:00
|
|
|
// Import Three.js modules
|
|
|
|
|
import * as THREE from 'three';
|
|
|
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// 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';
|
|
|
|
|
|
2025-08-24 00:19:00 +02:00
|
|
|
class SkyView {
|
|
|
|
|
constructor() {
|
2025-08-24 14:55:54 +02:00
|
|
|
// Initialize managers
|
|
|
|
|
this.wsManager = null;
|
|
|
|
|
this.aircraftManager = null;
|
|
|
|
|
this.mapManager = null;
|
|
|
|
|
this.uiManager = null;
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// 3D Radar
|
|
|
|
|
this.radar3d = null;
|
2025-08-24 00:19:00 +02:00
|
|
|
|
|
|
|
|
// Charts
|
|
|
|
|
this.charts = {};
|
|
|
|
|
|
2025-08-24 16:24:46 +02:00
|
|
|
// Selected aircraft tracking
|
|
|
|
|
this.selectedAircraft = null;
|
|
|
|
|
this.selectedTrailEnabled = false;
|
|
|
|
|
|
2025-08-24 00:19:00 +02:00
|
|
|
this.init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async init() {
|
|
|
|
|
try {
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Initialize UI manager first
|
|
|
|
|
this.uiManager = new UIManager();
|
|
|
|
|
this.uiManager.initializeViews();
|
|
|
|
|
this.uiManager.initializeEventListeners();
|
2025-08-24 00:24:45 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Initialize map manager and get the main map
|
|
|
|
|
this.mapManager = new MapManager();
|
|
|
|
|
const map = await this.mapManager.initializeMap();
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Initialize aircraft manager with the map
|
|
|
|
|
this.aircraftManager = new AircraftManager(map);
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 16:24:46 +02:00
|
|
|
// Set up selected aircraft trail callback
|
|
|
|
|
this.aircraftManager.setSelectedAircraftCallback((icao) => {
|
|
|
|
|
return this.selectedTrailEnabled && this.selectedAircraft === icao;
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Initialize WebSocket with callbacks
|
|
|
|
|
this.wsManager = new WebSocketManager(
|
|
|
|
|
(message) => this.handleWebSocketMessage(message),
|
|
|
|
|
(status) => this.uiManager.updateConnectionStatus(status)
|
|
|
|
|
);
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
await this.wsManager.connect();
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Initialize other components
|
|
|
|
|
this.initializeCharts();
|
|
|
|
|
this.uiManager.updateClocks();
|
|
|
|
|
this.initialize3DRadar();
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Set up map controls
|
|
|
|
|
this.setupMapControls();
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Set up aircraft selection listener
|
|
|
|
|
this.setupAircraftSelection();
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
this.startPeriodicTasks();
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Initialization failed:', error);
|
|
|
|
|
this.uiManager.showError('Failed to initialize application');
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
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');
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
if (centerMapBtn) {
|
2025-08-24 16:24:46 +02:00
|
|
|
centerMapBtn.addEventListener('click', () => {
|
|
|
|
|
this.aircraftManager.centerMapOnAircraft(() => this.mapManager.getSourcePositions());
|
|
|
|
|
});
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
if (resetMapBtn) {
|
|
|
|
|
resetMapBtn.addEventListener('click', () => this.mapManager.resetMap());
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
if (toggleTrailsBtn) {
|
|
|
|
|
toggleTrailsBtn.addEventListener('click', () => {
|
|
|
|
|
const showTrails = this.aircraftManager.toggleTrails();
|
|
|
|
|
toggleTrailsBtn.textContent = showTrails ? 'Hide Trails' : 'Show Trails';
|
|
|
|
|
});
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
if (toggleSourcesBtn) {
|
|
|
|
|
toggleSourcesBtn.addEventListener('click', () => {
|
|
|
|
|
const showSources = this.mapManager.toggleSources();
|
|
|
|
|
toggleSourcesBtn.textContent = showSources ? 'Hide Sources' : 'Show Sources';
|
|
|
|
|
});
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 18:24:08 +02:00
|
|
|
// Setup collapsible sections
|
|
|
|
|
this.setupCollapsibleSections();
|
|
|
|
|
|
2025-08-24 15:09:54 +02:00
|
|
|
const toggleDarkModeBtn = document.getElementById('toggle-dark-mode');
|
|
|
|
|
if (toggleDarkModeBtn) {
|
|
|
|
|
toggleDarkModeBtn.addEventListener('click', () => {
|
|
|
|
|
const isDarkMode = this.mapManager.toggleDarkMode();
|
|
|
|
|
toggleDarkModeBtn.innerHTML = isDarkMode ? '☀️ Light Mode' : '🌙 Night Mode';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Coverage controls
|
|
|
|
|
const toggleHeatmapBtn = document.getElementById('toggle-heatmap');
|
|
|
|
|
const coverageSourceSelect = document.getElementById('coverage-source');
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
if (toggleHeatmapBtn) {
|
|
|
|
|
toggleHeatmapBtn.addEventListener('click', async () => {
|
|
|
|
|
const isActive = await this.mapManager.toggleHeatmap();
|
|
|
|
|
toggleHeatmapBtn.textContent = isActive ? 'Hide Heatmap' : 'Show Heatmap';
|
|
|
|
|
});
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
if (coverageSourceSelect) {
|
|
|
|
|
coverageSourceSelect.addEventListener('change', (e) => {
|
|
|
|
|
this.mapManager.setSelectedSource(e.target.value);
|
|
|
|
|
this.mapManager.updateCoverageDisplay();
|
|
|
|
|
});
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
2025-08-24 16:24:46 +02:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
setupAircraftSelection() {
|
|
|
|
|
document.addEventListener('aircraftSelected', (e) => {
|
|
|
|
|
const { icao, aircraft } = e.detail;
|
|
|
|
|
this.uiManager.switchView('map-view');
|
|
|
|
|
|
2025-08-24 16:24:46 +02:00
|
|
|
// Hide trail for previously selected aircraft
|
|
|
|
|
if (this.selectedAircraft && this.selectedTrailEnabled) {
|
|
|
|
|
this.aircraftManager.hideAircraftTrail(this.selectedAircraft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update selected aircraft
|
|
|
|
|
this.selectedAircraft = icao;
|
|
|
|
|
|
2025-08-24 17:54:17 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
2025-08-24 16:24:46 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 17:54:17 +02:00
|
|
|
// Show trail for newly selected aircraft
|
|
|
|
|
this.aircraftManager.showAircraftTrail(icao);
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// 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);
|
2025-08-24 00:19:00 +02:00
|
|
|
if (marker) {
|
|
|
|
|
marker.openPopup();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
handleWebSocketMessage(message) {
|
|
|
|
|
switch (message.type) {
|
|
|
|
|
case 'initial_data':
|
|
|
|
|
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:
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
updateData(data) {
|
|
|
|
|
// Update all managers with new data
|
|
|
|
|
this.uiManager.updateData(data);
|
|
|
|
|
this.aircraftManager.updateAircraftData(data);
|
|
|
|
|
this.mapManager.updateSourcesData(data);
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Update UI components
|
|
|
|
|
this.aircraftManager.updateMarkers();
|
|
|
|
|
this.uiManager.updateAircraftTable();
|
|
|
|
|
this.uiManager.updateStatistics();
|
|
|
|
|
this.uiManager.updateHeaderInfo();
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 16:24:46 +02:00
|
|
|
// Clear selected aircraft if it no longer exists
|
|
|
|
|
if (this.selectedAircraft && !this.aircraftManager.aircraftData.has(this.selectedAircraft)) {
|
|
|
|
|
this.selectedAircraft = null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Update coverage controls
|
|
|
|
|
this.mapManager.updateCoverageControls();
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
if (this.uiManager.currentView === 'radar3d-view') {
|
|
|
|
|
this.update3DRadar();
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// View switching
|
|
|
|
|
async switchView(viewId) {
|
|
|
|
|
const actualViewId = this.uiManager.switchView(viewId);
|
2025-08-24 00:19:00 +02:00
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Handle view-specific initialization
|
|
|
|
|
const baseName = actualViewId.replace('-view', '');
|
|
|
|
|
switch (baseName) {
|
|
|
|
|
case 'coverage':
|
|
|
|
|
await this.mapManager.initializeCoverageMap();
|
|
|
|
|
break;
|
|
|
|
|
case 'radar3d':
|
|
|
|
|
this.update3DRadar();
|
|
|
|
|
break;
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 14:55:54 +02:00
|
|
|
// Charts
|
|
|
|
|
initializeCharts() {
|
|
|
|
|
const aircraftChartCanvas = document.getElementById('aircraft-chart');
|
|
|
|
|
if (!aircraftChartCanvas) {
|
|
|
|
|
console.warn('Aircraft chart canvas not found');
|
|
|
|
|
return;
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-24 14:55:54 +02:00
|
|
|
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' }
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
2025-08-24 14:55:54 +02:00
|
|
|
console.warn('Chart.js not available, skipping charts initialization');
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateCharts() {
|
2025-08-24 14:55:54 +02:00
|
|
|
if (!this.charts.aircraft) return;
|
|
|
|
|
|
2025-08-24 00:19:00 +02:00
|
|
|
const now = new Date();
|
|
|
|
|
const timeLabel = now.toLocaleTimeString();
|
|
|
|
|
|
|
|
|
|
// Update aircraft count chart
|
2025-08-24 14:55:54 +02:00
|
|
|
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();
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
2025-08-24 14:55:54 +02:00
|
|
|
|
|
|
|
|
chart.update('none');
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3D Radar (basic implementation)
|
|
|
|
|
initialize3DRadar() {
|
|
|
|
|
try {
|
|
|
|
|
const container = document.getElementById('radar3d-container');
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
// Create scene
|
|
|
|
|
this.radar3d = {
|
|
|
|
|
scene: new THREE.Scene(),
|
|
|
|
|
camera: new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000),
|
|
|
|
|
renderer: new THREE.WebGLRenderer({ alpha: true, antialias: true }),
|
|
|
|
|
controls: null,
|
|
|
|
|
aircraftMeshes: new Map()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set up renderer
|
|
|
|
|
this.radar3d.renderer.setSize(container.clientWidth, container.clientHeight);
|
|
|
|
|
this.radar3d.renderer.setClearColor(0x0a0a0a, 0.9);
|
|
|
|
|
container.appendChild(this.radar3d.renderer.domElement);
|
|
|
|
|
|
|
|
|
|
// Add lighting
|
|
|
|
|
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
|
|
|
|
|
this.radar3d.scene.add(ambientLight);
|
|
|
|
|
|
|
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
|
|
|
directionalLight.position.set(10, 10, 5);
|
|
|
|
|
this.radar3d.scene.add(directionalLight);
|
|
|
|
|
|
|
|
|
|
// Set up camera
|
|
|
|
|
this.radar3d.camera.position.set(0, 50, 50);
|
|
|
|
|
this.radar3d.camera.lookAt(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
// Add controls
|
|
|
|
|
this.radar3d.controls = new OrbitControls(this.radar3d.camera, this.radar3d.renderer.domElement);
|
|
|
|
|
this.radar3d.controls.enableDamping = true;
|
|
|
|
|
this.radar3d.controls.dampingFactor = 0.05;
|
|
|
|
|
|
|
|
|
|
// Add ground plane
|
|
|
|
|
const groundGeometry = new THREE.PlaneGeometry(200, 200);
|
|
|
|
|
const groundMaterial = new THREE.MeshLambertMaterial({
|
|
|
|
|
color: 0x2a4d3a,
|
|
|
|
|
transparent: true,
|
|
|
|
|
opacity: 0.5
|
|
|
|
|
});
|
|
|
|
|
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
|
|
|
|
ground.rotation.x = -Math.PI / 2;
|
|
|
|
|
this.radar3d.scene.add(ground);
|
|
|
|
|
|
|
|
|
|
// Add grid
|
|
|
|
|
const gridHelper = new THREE.GridHelper(200, 20, 0x44aa44, 0x44aa44);
|
|
|
|
|
this.radar3d.scene.add(gridHelper);
|
|
|
|
|
|
|
|
|
|
// Start render loop
|
|
|
|
|
this.render3DRadar();
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to initialize 3D radar:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update3DRadar() {
|
2025-08-24 14:55:54 +02:00
|
|
|
if (!this.radar3d || !this.radar3d.scene || !this.aircraftManager) return;
|
2025-08-24 00:19:00 +02:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Update aircraft positions in 3D space
|
2025-08-24 14:55:54 +02:00
|
|
|
this.aircraftManager.aircraftData.forEach((aircraft, icao) => {
|
2025-08-24 00:19:00 +02:00
|
|
|
if (aircraft.Latitude && aircraft.Longitude) {
|
|
|
|
|
const key = icao.toString();
|
|
|
|
|
|
|
|
|
|
if (!this.radar3d.aircraftMeshes.has(key)) {
|
|
|
|
|
// Create new aircraft mesh
|
|
|
|
|
const geometry = new THREE.ConeGeometry(0.5, 2, 6);
|
|
|
|
|
const material = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
|
|
|
|
|
const mesh = new THREE.Mesh(geometry, material);
|
|
|
|
|
this.radar3d.aircraftMeshes.set(key, mesh);
|
|
|
|
|
this.radar3d.scene.add(mesh);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mesh = this.radar3d.aircraftMeshes.get(key);
|
|
|
|
|
|
|
|
|
|
// Convert lat/lon to local coordinates (simplified)
|
|
|
|
|
const x = (aircraft.Longitude - (-0.4600)) * 111320 * Math.cos(aircraft.Latitude * Math.PI / 180) / 1000;
|
|
|
|
|
const z = -(aircraft.Latitude - 51.4700) * 111320 / 1000;
|
|
|
|
|
const y = (aircraft.Altitude || 0) / 1000; // Convert feet to km for display
|
|
|
|
|
|
|
|
|
|
mesh.position.set(x, y, z);
|
|
|
|
|
|
|
|
|
|
// Orient mesh based on track
|
|
|
|
|
if (aircraft.Track !== undefined) {
|
|
|
|
|
mesh.rotation.y = -aircraft.Track * Math.PI / 180;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Remove old aircraft
|
|
|
|
|
this.radar3d.aircraftMeshes.forEach((mesh, key) => {
|
2025-08-24 14:55:54 +02:00
|
|
|
if (!this.aircraftManager.aircraftData.has(key)) {
|
2025-08-24 00:19:00 +02:00
|
|
|
this.radar3d.scene.remove(mesh);
|
|
|
|
|
this.radar3d.aircraftMeshes.delete(key);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to update 3D radar:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render3DRadar() {
|
|
|
|
|
if (!this.radar3d) return;
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(() => this.render3DRadar());
|
|
|
|
|
|
|
|
|
|
if (this.radar3d.controls) {
|
|
|
|
|
this.radar3d.controls.update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.radar3d.renderer.render(this.radar3d.scene, this.radar3d.camera);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startPeriodicTasks() {
|
|
|
|
|
// Update clocks every second
|
2025-08-24 14:55:54 +02:00
|
|
|
setInterval(() => this.uiManager.updateClocks(), 1000);
|
|
|
|
|
|
|
|
|
|
// Update charts every 10 seconds
|
|
|
|
|
setInterval(() => this.updateCharts(), 10000);
|
2025-08-24 00:19:00 +02:00
|
|
|
|
|
|
|
|
// Periodic cleanup
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
// Clean up old trail data, etc.
|
|
|
|
|
}, 30000);
|
|
|
|
|
}
|
2025-08-24 18:24:08 +02:00
|
|
|
|
|
|
|
|
setupCollapsibleSections() {
|
|
|
|
|
// Setup Display Options collapsible
|
|
|
|
|
const displayHeader = document.getElementById('display-options-header');
|
|
|
|
|
const displayContent = document.getElementById('display-options-content');
|
|
|
|
|
|
|
|
|
|
if (displayHeader && displayContent) {
|
|
|
|
|
displayHeader.addEventListener('click', () => {
|
|
|
|
|
const isCollapsed = displayContent.classList.contains('collapsed');
|
|
|
|
|
|
|
|
|
|
if (isCollapsed) {
|
|
|
|
|
// Expand
|
|
|
|
|
displayContent.classList.remove('collapsed');
|
|
|
|
|
displayHeader.classList.remove('collapsed');
|
|
|
|
|
} else {
|
|
|
|
|
// Collapse
|
|
|
|
|
displayContent.classList.add('collapsed');
|
|
|
|
|
displayHeader.classList.add('collapsed');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save state to localStorage
|
|
|
|
|
localStorage.setItem('displayOptionsCollapsed', !isCollapsed);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Restore saved state (default to collapsed)
|
|
|
|
|
const savedState = localStorage.getItem('displayOptionsCollapsed');
|
|
|
|
|
const shouldCollapse = savedState === null ? true : savedState === 'true';
|
|
|
|
|
|
|
|
|
|
if (shouldCollapse) {
|
|
|
|
|
displayContent.classList.add('collapsed');
|
|
|
|
|
displayHeader.classList.add('collapsed');
|
|
|
|
|
} else {
|
|
|
|
|
displayContent.classList.remove('collapsed');
|
|
|
|
|
displayHeader.classList.remove('collapsed');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-24 00:19:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize application when DOM is ready
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
window.skyview = new SkyView();
|
|
|
|
|
});
|