// Konfiguracja API - sparametryzowana function getDefaultApiUrl() { // Auto-detect: użyj hostname z którego serwowany jest frontend const protocol = window.location.protocol; // http: lub https: const hostname = window.location.hostname; // np. 192.168.1.100 lub nazwa hosta // Jeśli otwarte jako file://, użyj localhost if (protocol === 'file:') { return 'http://localhost:5000/api'; } // W przeciwnym razie użyj tego samego hosta (przez reverse proxy) return `${window.location.origin}/api`; } function getApiUrl() { // Sprawdź localStorage const saved = localStorage.getItem('izochrona_api_url'); if (saved) { return saved; } // Użyj auto-detected return getDefaultApiUrl(); } function setApiUrl(url) { localStorage.setItem('izochrona_api_url', url); location.reload(); // Przeładuj stronę } let API_URL = getApiUrl(); // Stan aplikacji let map; let selectedStop = null; let isochroneLayers = []; let stopsLayer = null; // Kolory dla różnych przedziałów czasowych const ISOCHRONE_COLORS = { 30: '#0080ff', 60: '#00c864', 90: '#ffc800', 120: '#ff6400', 180: '#ff0064' }; // Funkcja do generowania koloru dla dowolnego czasu function getColorForTime(time) { // Jeśli jest w predefiniowanych, użyj go if (ISOCHRONE_COLORS[time]) { return ISOCHRONE_COLORS[time]; } // W przeciwnym razie generuj kolor na podstawie czasu // Im dłuższy czas, tym bardziej czerwony const hue = Math.max(0, 220 - (time * 1.2)); // Od niebieskiego (220) do czerwonego (0) return `hsl(${hue}, 80%, 50%)`; } // Inicjalizacja mapy function initMap() { map = L.map('map').setView([52.0, 19.0], 7); // Polska // Warstwa OSM L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 18 }).addTo(map); console.log('Mapa zainicjalizowana'); } // Wyszukiwanie stacji let searchTimeout; document.getElementById('station-search').addEventListener('input', (e) => { const query = e.target.value.trim(); clearTimeout(searchTimeout); if (query.length < 2) { document.getElementById('search-results').innerHTML = ''; return; } searchTimeout = setTimeout(() => { searchStations(query); }, 300); }); async function searchStations(query) { try { const response = await fetch(`${API_URL}/stops/search?q=${encodeURIComponent(query)}`); const data = await response.json(); displaySearchResults(data.stops); } catch (error) { console.error('Błąd wyszukiwania:', error); document.getElementById('search-results').innerHTML = '

Błąd połączenia z serwerem

'; } } function displaySearchResults(stops) { const resultsDiv = document.getElementById('search-results'); if (!stops || stops.length === 0) { resultsDiv.innerHTML = '

Brak wyników

'; return; } resultsDiv.innerHTML = stops.slice(0, 10).map(stop => `
${stop.stop_name}
`).join(''); // Dodaj event listenery resultsDiv.querySelectorAll('.search-result-item').forEach(item => { item.addEventListener('click', () => { const stopId = item.dataset.stopId; const stopName = item.querySelector('strong').textContent; const stop = stops.find(s => s.stop_id === stopId); selectStation(stop); }); }); } function selectStation(stop) { selectedStop = stop; // Ukryj wyniki wyszukiwania document.getElementById('search-results').innerHTML = ''; document.getElementById('station-search').value = ''; // Pokaż wybraną stację document.getElementById('selected-station').style.display = 'block'; document.getElementById('station-name').textContent = stop.stop_name; // Pokaż kontrolki document.getElementById('time-controls').style.display = 'block'; // Wycentruj mapę na stacji map.setView([stop.stop_lat, stop.stop_lon], 10); // Dodaj marker if (stopsLayer) { map.removeLayer(stopsLayer); } stopsLayer = L.marker([stop.stop_lat, stop.stop_lon], { icon: L.divIcon({ className: 'custom-marker', html: '📍', iconSize: [30, 30] }) }).addTo(map); stopsLayer.bindPopup(`${stop.stop_name}`).openPopup(); console.log('Wybrano stację:', stop); } // Wyczyść wybór document.getElementById('clear-selection').addEventListener('click', () => { selectedStop = null; document.getElementById('selected-station').style.display = 'none'; document.getElementById('time-controls').style.display = 'none'; document.getElementById('legend').style.display = 'none'; document.getElementById('stats').style.display = 'none'; clearIsochrones(); if (stopsLayer) { map.removeLayer(stopsLayer); stopsLayer = null; } }); // Oblicz izochrony document.getElementById('calculate-btn').addEventListener('click', async () => { if (!selectedStop) return; // Pobierz wartości z checkboxów const checkboxes = document.querySelectorAll('.checkbox-group input[type="checkbox"]:checked'); const timeIntervals = Array.from(checkboxes).map(cb => parseInt(cb.value)); // Pobierz własne wartości const customInput = document.getElementById('custom-times').value.trim(); if (customInput) { const customTimes = customInput .split(',') .map(t => parseInt(t.trim())) .filter(t => !isNaN(t) && t > 0); // Filtruj poprawne liczby timeIntervals.push(...customTimes); } // Usuń duplikaty i posortuj const uniqueIntervals = [...new Set(timeIntervals)].sort((a, b) => a - b); if (uniqueIntervals.length === 0) { alert('Wybierz przynajmniej jeden przedział czasowy lub wpisz własną wartość'); return; } await calculateIsochrones(selectedStop.stop_id, uniqueIntervals); }); async function calculateIsochrones(stopId, timeIntervals) { // Pokaż loading document.getElementById('loading').style.display = 'block'; document.getElementById('calculate-btn').disabled = true; try { const response = await fetch(`${API_URL}/isochrones`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ origin_stop_id: stopId, time_intervals: timeIntervals }) }); const data = await response.json(); if (response.ok) { displayIsochrones(data.isochrones, data.reachable_stops); } else { alert(`Błąd: ${data.error}`); } } catch (error) { console.error('Błąd:', error); alert('Błąd połączenia z serwerem'); } finally { document.getElementById('loading').style.display = 'none'; document.getElementById('calculate-btn').disabled = false; } } function displayIsochrones(isochrones, reachableStops) { // Usuń poprzednie warstwy clearIsochrones(); // Sortuj od największego do najmniejszego (żeby mniejsze były na wierzchu) isochrones.sort((a, b) => b.time - a.time); // Dodaj wielokąty izochron isochrones.forEach(iso => { if (!iso.geometry) return; const color = getColorForTime(iso.time); const layer = L.geoJSON(iso.geometry, { style: { color: color, weight: 2, opacity: 0.8, fillColor: color, fillOpacity: 0.2 } }).addTo(map); layer.bindPopup(`${iso.time} min
${iso.stop_count} stacji`); isochroneLayers.push(layer); }); // Dodaj markery dla osiągalnych stacji reachableStops.forEach(stop => { const marker = L.circleMarker([stop.lat, stop.lon], { radius: 4, fillColor: '#0080ff', color: 'white', weight: 1, opacity: 1, fillOpacity: 0.8 }).addTo(map); marker.bindPopup(` ${stop.name}
Czas: ${stop.time} min `); isochroneLayers.push(marker); }); // Aktualizuj legendę dynamicznie updateLegend(isochrones); // Pokaż statystyki const statsContent = ` Osiągalne stacje: ${reachableStops.length}
Izochrony: ${isochrones.length}
Najdalsza stacja: ${reachableStops[reachableStops.length - 1]?.name || 'brak'} (${reachableStops[reachableStops.length - 1]?.time || 0} min) `; document.getElementById('stats-content').innerHTML = statsContent; document.getElementById('stats').style.display = 'block'; console.log('Wyświetlono izochrony:', isochrones.length); } function clearIsochrones() { isochroneLayers.forEach(layer => map.removeLayer(layer)); isochroneLayers = []; } function updateLegend(isochrones) { const legend = document.getElementById('legend'); const legendContainer = legend.querySelector('.legend-items') || createLegendContainer(legend); // Wyczyść starą legendę legendContainer.innerHTML = ''; // Dodaj wpisy dla każdej izochrony isochrones.forEach(iso => { const color = getColorForTime(iso.time); const item = document.createElement('div'); item.className = 'legend-item'; item.innerHTML = ` ${iso.time} min `; legendContainer.appendChild(item); }); legend.style.display = 'block'; } function createLegendContainer(legend) { // Znajdź h3 i dodaj kontener po nim const h3 = legend.querySelector('h3'); const container = document.createElement('div'); container.className = 'legend-items'; // Usuń stare statyczne wpisy jeśli istnieją const oldItems = legend.querySelectorAll('.legend-item'); oldItems.forEach(item => item.remove()); legend.appendChild(container); return container; } // Konfiguracja API URL document.addEventListener('DOMContentLoaded', () => { const input = document.getElementById('api-url-input'); const saveBtn = document.getElementById('api-url-save'); const resetBtn = document.getElementById('api-url-reset'); const status = document.getElementById('api-url-status'); // Pokaż aktualny URL input.value = API_URL; status.textContent = `Aktualny: ${API_URL}`; // Zapisz nowy URL saveBtn.addEventListener('click', () => { const newUrl = input.value.trim(); if (!newUrl) { status.textContent = 'Błąd: Adres nie może być pusty'; status.style.color = 'red'; return; } setApiUrl(newUrl); }); // Reset do auto-detect resetBtn.addEventListener('click', () => { localStorage.removeItem('izochrona_api_url'); location.reload(); }); }); // Inicjalizacja przy starcie window.addEventListener('DOMContentLoaded', () => { initMap(); console.log('Aplikacja gotowa'); console.log('API URL:', API_URL); });