// Initialize map const map = L.map('map').setView([55.7558, 37.6173], 5); // Add OpenStreetMap tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 18 }).addTo(map); // Layers to store map features let routeLayers = L.layerGroup().addTo(map); let transferMarkers = L.layerGroup().addTo(map); // Transport colors const transportColors = { 'plane': '#ff9800', 'train': '#1976d2', 'bus': '#cddc39', 'other': '#9e9e9e' }; // Get transport color function getTransportColor(feature) { const transportType = feature.properties.transport_type || feature.properties.transport || 'other'; return transportColors[transportType] || transportColors.other; } // Get line style based on feature properties function getLineStyle(feature) { if (feature.properties && feature.properties.synthetic === 'true') { return { color: getTransportColor(feature), weight: 2, dashArray: '5, 5', opacity: 0.7 }; } return { color: getTransportColor(feature), weight: 3, opacity: 0.8 }; } // Get marker style based on feature properties function getMarkerStyle(feature) { const color = feature.properties.stroke_color || getTransportColor(feature); const width = feature.properties.stroke_width || 3; return { color: color, weight: width, fillColor: '#fff', fillOpacity: 1 }; } // Format duration from seconds to readable format function formatDuration(seconds) { if (!seconds || seconds === 0) return '0h'; const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); if (hours > 0) { return `${hours}h ${minutes}m`; } return `${minutes}m`; } // Format connection time function formatConnectionTime(connectionTime) { if (!connectionTime) return 'N/A'; return formatDuration(connectionTime); } // Render GeoJSON on map function renderGeoJSON(geojsonData) { // Clear existing layers routeLayers.clearLayers(); transferMarkers.clearLayers(); if (!geojsonData || !geojsonData.features) { return; } // Process features geojsonData.features.forEach(feature => { const kind = feature.properties?.kind || feature.type; if (kind === 'LineString' || feature.geometry?.type === 'LineString') { // LineString feature - route segment const style = getLineStyle(feature); L.geoJSON(feature, { style: function(feature) { return getLineStyle(feature); }, onEachFeature: function(feature, layer) { layer.addTo(routeLayers); } }).addTo(routeLayers); } else if (kind === 'Point' || feature.geometry?.type === 'Point') { // Point feature - transfer marker const markerType = feature.properties?.marker_type; if (markerType === 'transfer' || feature.properties?.is_transfer === 'true') { const style = getMarkerStyle(feature); const marker = L.circleMarker([feature.geometry.coordinates[1], feature.geometry.coordinates[0]], { color: style.color, weight: style.weight, fillColor: style.fillColor, fillOpacity: style.fillOpacity, radius: 6 }); // Create popup content let popupContent = `
Connection time: ${feature.properties.connection_time_formatted}
`; } else if (feature.properties?.connection_time) { popupContent += `Connection time: ${formatConnectionTime(feature.properties.connection_time)}
`; } if (feature.properties?.transfer_type) { popupContent += `Transfer type: ${feature.properties.transfer_type}
`; } marker.bindPopup(popupContent); marker.addTo(transferMarkers); } } }); // Fit map to bounds if there are features if (routeLayers.getLayers().length > 0 || transferMarkers.getLayers().length > 0) { const group = new L.featureGroup([...routeLayers.getLayers(), ...transferMarkers.getLayers()]); map.fitBounds(group.getBounds().pad(0.1)); } } // Show loading overlay function showLoading() { document.getElementById('loading-overlay').classList.remove('hidden'); } // Hide loading overlay function hideLoading() { document.getElementById('loading-overlay').classList.add('hidden'); } // Show error message function showError(message) { const errorEl = document.getElementById('error-message'); errorEl.textContent = message; errorEl.classList.remove('hidden'); setTimeout(() => { errorEl.classList.add('hidden'); }, 5000); } // Hide error message function hideError() { document.getElementById('error-message').classList.add('hidden'); } // Render routes list function renderRoutesList(routes) { const routesListEl = document.getElementById('routes-list'); if (!routes || routes.length === 0) { routesListEl.innerHTML = ''; return; } let html = ''; routes.forEach((route, index) => { const duration = formatDuration(route.duration_seconds || route.duration || 0); const transfers = route.transfers || route.transfer_count || 0; const cost = route.cost || 0; html += `