function initMap() { const flightsData = window.FUTURE_FLIGHTS_DATA || []; if (flightsData.length === 0) { document.getElementById('map').style.display = 'none'; document.getElementById('stats-toggle').style.display = 'none'; document.getElementById('empty-flights').style.display = 'flex'; return; } const map = L.map('map').setView([20, 0], 2); const lightTiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19 }); const darkTiles = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap contributors © CARTO', maxZoom: 19 }); function updateTiles() { const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; if (isDarkMode) { if (map.hasLayer(lightTiles)) { map.removeLayer(lightTiles); } if (!map.hasLayer(darkTiles)) { darkTiles.addTo(map); } } else { if (map.hasLayer(darkTiles)) { map.removeLayer(darkTiles); } if (!map.hasLayer(lightTiles)) { lightTiles.addTo(map); } } } updateTiles(); if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTiles); } const airportIcon = L.divIcon({ className: 'custom-marker', html: ` `, iconSize: [24, 24], iconAnchor: [12, 12], popupAnchor: [0, -12] }); let flightRoutes = []; let markers = []; function createCurvedPath(from, to) { const latlngs = []; const steps = 100; const lat1 = from[0]; const lng1 = from[1]; const lat2 = to[0]; const lng2 = to[1]; const midLat = (lat1 + lat2) / 2; const midLng = (lng1 + lng2) / 2; const distance = Math.sqrt(Math.pow(lat2 - lat1, 2) + Math.pow(lng2 - lng1, 2)); const offsetMagnitude = distance * 0.2; const dx = lng2 - lng1; const dy = lat2 - lat1; const offsetLat = -dx * offsetMagnitude / distance; const offsetLng = dy * offsetMagnitude / distance; const controlLat = midLat + offsetLat; const controlLng = midLng + offsetLng; for (let i = 0; i <= steps; i++) { const t = i / steps; const t1 = 1 - t; const lat = t1 * t1 * lat1 + 2 * t1 * t * controlLat + t * t * lat2; const lng = t1 * t1 * lng1 + 2 * t1 * t * controlLng + t * t * lng2; latlngs.push([lat, lng]); } return latlngs; } function getLineColor() { return '#03dac6'; } function createFlightRoutes(flights) { flightRoutes.forEach(route => map.removeLayer(route)); markers.forEach(marker => map.removeLayer(marker)); flightRoutes = []; markers = []; const airportMarkers = new Map(); flights.forEach(flight => { const pathCoords = createCurvedPath( [flight.from.lat, flight.from.lng], [flight.to.lat, flight.to.lng] ); const route = L.polyline(pathCoords, { color: getLineColor(), weight: 2, opacity: 0.6, smoothFactor: 1, dashArray: '5, 10' }).addTo(map); const decorator = L.polylineDecorator(route, { patterns: [ { offset: '100%', repeat: 0, symbol: L.Symbol.arrowHead({ pixelSize: 10, polygon: false, pathOptions: { color: getLineColor(), weight: 2, opacity: 0.6 } }) } ] }).addTo(map); const routePopup = ` `; route.bindPopup(routePopup); route.flightData = flight; flightRoutes.push(route); flightRoutes.push(decorator); if (!airportMarkers.has(flight.from.code)) { const fromMarker = L.marker([flight.from.lat, flight.from.lng], { icon: airportIcon }).addTo(map); fromMarker.bindPopup(` `); markers.push(fromMarker); airportMarkers.set(flight.from.code, fromMarker); } if (!airportMarkers.has(flight.to.code)) { const toMarker = L.marker([flight.to.lat, flight.to.lng], { icon: airportIcon }).addTo(map); toMarker.bindPopup(` `); markers.push(toMarker); airportMarkers.set(flight.to.code, toMarker); } }); if (flightRoutes.length > 0 && markers.length > 0) { const group = new L.featureGroup(markers); map.fitBounds(group.getBounds().pad(0.1)); } } function computeStats(flights) { const stats = { totalFlights: flights.length, airports: {}, aircraft: {}, airlines: {} }; const airportCounts = {}; const aircraftCounts = {}; const airlineCounts = {}; flights.forEach(flight => { airportCounts[flight.from.code] = (airportCounts[flight.from.code] || 0) + 1; airportCounts[flight.to.code] = (airportCounts[flight.to.code] || 0) + 1; if (flight.aircraft) { aircraftCounts[flight.aircraft] = (aircraftCounts[flight.aircraft] || 0) + 1; } if (flight.airline) { airlineCounts[flight.airline] = (airlineCounts[flight.airline] || 0) + 1; } }); stats.airports = airportCounts; stats.aircraft = aircraftCounts; stats.airlines = airlineCounts; return stats; } function renderStats(stats) { document.getElementById('stat-flights').textContent = stats.totalFlights; document.getElementById('stat-airports').textContent = Object.keys(stats.airports).length; document.getElementById('stat-airlines').textContent = Object.keys(stats.airlines).length; document.getElementById('stat-aircraft').textContent = Object.keys(stats.aircraft).length; const sortByCount = (obj) => Object.entries(obj).sort((a, b) => b[1] - a[1]); const topAirportsList = document.getElementById('top-airports'); topAirportsList.innerHTML = ''; sortByCount(stats.airports).slice(0, 5).forEach(([code, count]) => { const li = document.createElement('li'); li.innerHTML = `${code}${count}`; topAirportsList.appendChild(li); }); const topAircraftList = document.getElementById('top-aircraft'); topAircraftList.innerHTML = ''; sortByCount(stats.aircraft).slice(0, 5).forEach(([name, count]) => { const li = document.createElement('li'); const shortName = name.length > 20 ? name.substring(0, 18) + '...' : name; li.innerHTML = `${shortName}${count}`; topAircraftList.appendChild(li); }); const topAirlinesList = document.getElementById('top-airlines'); topAirlinesList.innerHTML = ''; sortByCount(stats.airlines).slice(0, 5).forEach(([code, count]) => { const li = document.createElement('li'); li.innerHTML = `${code}${count}`; topAirlinesList.appendChild(li); }); } const statsPanel = document.getElementById('stats-panel'); const statsToggle = document.getElementById('stats-toggle'); statsToggle.addEventListener('click', () => { statsPanel.classList.toggle('visible'); statsToggle.textContent = statsPanel.classList.contains('visible') ? 'hide stats' : 'stats'; }); const flightList = document.getElementById('flight-list'); const mapContainer = document.getElementById('map'); const viewToggle = document.getElementById('view-toggle'); let isListView = false; function formatDate(dateStr) { const date = new Date(dateStr); const day = date.getDate(); const month = date.toLocaleDateString('en-US', { month: 'short' }); const year = date.getFullYear(); return { day, month, year }; } function getDaysUntil(dateStr) { const today = new Date(); today.setHours(0, 0, 0, 0); const flightDate = new Date(dateStr); flightDate.setHours(0, 0, 0, 0); const diff = flightDate - today; return Math.ceil(diff / (1000 * 60 * 60 * 24)); } function renderFlightList(flights) { const sorted = [...flights].sort((a, b) => new Date(a.date) - new Date(b.date)); flightList.innerHTML = sorted.map(flight => { const { day, month, year } = formatDate(flight.date); const daysUntil = getDaysUntil(flight.date); const daysLabel = daysUntil === 1 ? 'day' : 'days'; return `
${day}
${month} ${year}
${flight.flightNumber}
${flight.from.city} to ${flight.to.city}
${flight.from.code} -> ${flight.to.code}
${daysUntil}
${daysLabel}
`; }).join(''); } function toggleView() { isListView = !isListView; if (isListView) { mapContainer.style.display = 'none'; flightList.classList.add('visible'); viewToggle.textContent = 'map'; viewToggle.classList.add('active'); } else { mapContainer.style.display = 'block'; flightList.classList.remove('visible'); viewToggle.textContent = 'list'; viewToggle.classList.remove('active'); map.invalidateSize(); } } viewToggle.addEventListener('click', toggleView); renderFlightList(flightsData); createFlightRoutes(flightsData); renderStats(computeStats(flightsData)); }