// 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);
});