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 `
${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));
}