UI overhaul: kiosk-optimized landscape mode #23
@@ -709,77 +709,37 @@ class ConfigManager {
|
||||
if (e.target === mapModal) closeMap();
|
||||
});
|
||||
|
||||
// Load transit stops - search for common Stockholm areas
|
||||
const loadSitesOnMap = async () => {
|
||||
try {
|
||||
// Start with focused search to avoid too many markers
|
||||
const searchTerms = ['Ambassaderna'];
|
||||
const allSites = new Map();
|
||||
// Load nearby transit stops based on map center
|
||||
const markersLayer = L.layerGroup().addTo(map);
|
||||
const loadedSiteIds = new Set();
|
||||
|
||||
const loadNearbySites = async () => {
|
||||
const center = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
// Scale radius based on zoom: wider view = larger radius
|
||||
const radius = zoom >= 15 ? 500 : zoom >= 13 ? 1500 : zoom >= 11 ? 4000 : 8000;
|
||||
|
||||
for (const term of searchTerms) {
|
||||
try {
|
||||
const response = await fetch(`/api/sites/search?q=${encodeURIComponent(term)}`);
|
||||
const response = await fetch(`/api/sites/nearby?lat=${center.lat}&lon=${center.lng}&radius=${radius}`);
|
||||
const data = await response.json();
|
||||
console.log(`Search "${term}" returned:`, data);
|
||||
|
||||
if (data.sites) {
|
||||
data.sites.forEach(site => {
|
||||
// Only add sites with valid coordinates from API
|
||||
const lat = site.lat || site.latitude;
|
||||
const lon = site.lon || site.longitude;
|
||||
if (loadedSiteIds.has(site.id)) return;
|
||||
const lat = parseFloat(site.lat);
|
||||
const lon = parseFloat(site.lon);
|
||||
if (!lat || !lon || isNaN(lat) || isNaN(lon)) return;
|
||||
|
||||
if (lat && lon && !isNaN(parseFloat(lat)) && !isNaN(parseFloat(lon))) {
|
||||
if (!allSites.has(site.id)) {
|
||||
allSites.set(site.id, {
|
||||
id: site.id,
|
||||
name: site.name,
|
||||
lat: parseFloat(lat),
|
||||
lon: parseFloat(lon)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(`Site ${site.id} (${site.name}) missing coordinates, skipping`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error searching for ${term}:`, err);
|
||||
}
|
||||
}
|
||||
loadedSiteIds.add(site.id);
|
||||
|
||||
// Add known site with coordinates as fallback
|
||||
const knownSites = [
|
||||
{ id: '1411', name: 'Ambassaderna', lat: 59.3293, lon: 18.0686 }
|
||||
];
|
||||
|
||||
knownSites.forEach(site => {
|
||||
if (!allSites.has(site.id)) {
|
||||
allSites.set(site.id, site);
|
||||
}
|
||||
});
|
||||
|
||||
const sitesArray = Array.from(allSites.values());
|
||||
console.log(`Loading ${sitesArray.length} sites on map with coordinates:`, sitesArray);
|
||||
|
||||
if (sitesArray.length > 0) {
|
||||
const markers = [];
|
||||
sitesArray.forEach(site => {
|
||||
const lat = site.lat;
|
||||
const lon = site.lon;
|
||||
|
||||
if (!lat || !lon || isNaN(lat) || isNaN(lon)) {
|
||||
console.warn(`Invalid coordinates for site ${site.id}, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create custom icon
|
||||
const customIcon = L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `<div style="background-color: #0061a1; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 3px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.3);">🚌</div>`,
|
||||
html: '<div style="background-color: #0061a1; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 3px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.3);">🚌</div>',
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 15]
|
||||
});
|
||||
|
||||
const marker = L.marker([lat, lon], { icon: customIcon }).addTo(map);
|
||||
const marker = L.marker([lat, lon], { icon: customIcon });
|
||||
|
||||
const popupContent = `
|
||||
<div style="min-width: 200px;">
|
||||
@@ -793,36 +753,18 @@ class ConfigManager {
|
||||
`;
|
||||
|
||||
marker.bindPopup(popupContent);
|
||||
markers.push(marker);
|
||||
|
||||
// Make marker clickable to open popup
|
||||
marker.on('click', function() {
|
||||
this.openPopup();
|
||||
markersLayer.addLayer(marker);
|
||||
});
|
||||
});
|
||||
|
||||
// Fit map to show all markers, or center on first marker
|
||||
if (markers.length > 0) {
|
||||
if (markers.length === 1) {
|
||||
map.setView(markers[0].getLatLng(), 15);
|
||||
} else {
|
||||
const group = new L.featureGroup(markers);
|
||||
map.fitBounds(group.getBounds().pad(0.1));
|
||||
}
|
||||
} else {
|
||||
// If no markers, show message
|
||||
console.log('No sites with coordinates found');
|
||||
}
|
||||
console.log(`Loaded ${data.sites.length} nearby sites (${loadedSiteIds.size} total on map)`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading sites on map:', error);
|
||||
console.error('Error loading nearby sites:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Load sites after map is initialized
|
||||
setTimeout(() => {
|
||||
loadSitesOnMap();
|
||||
}, 500);
|
||||
// Load sites on init and when map is panned/zoomed
|
||||
setTimeout(() => loadNearbySites(), 300);
|
||||
map.on('moveend', loadNearbySites);
|
||||
|
||||
// Handle site selection from map popup - use event delegation on the modal
|
||||
mapModal.addEventListener('click', (e) => {
|
||||
@@ -874,73 +816,42 @@ class ConfigManager {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.sites && data.sites.length > 0) {
|
||||
// Clear existing markers
|
||||
map.eachLayer((layer) => {
|
||||
if (layer instanceof L.Marker) {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
// Clear existing markers and reset tracking
|
||||
markersLayer.clearLayers();
|
||||
loadedSiteIds.clear();
|
||||
|
||||
// Clear existing markers
|
||||
map.eachLayer((layer) => {
|
||||
if (layer instanceof L.Marker) {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// Add markers for search results
|
||||
const markers = [];
|
||||
const searchMarkers = [];
|
||||
data.sites.forEach(site => {
|
||||
let lat = site.lat || site.latitude;
|
||||
let lon = site.lon || site.longitude;
|
||||
const lat = parseFloat(site.lat);
|
||||
const lon = parseFloat(site.lon);
|
||||
if (!lat || !lon || isNaN(lat) || isNaN(lon)) return;
|
||||
|
||||
// If no coordinates, use approximate location based on map center
|
||||
if (!lat || !lon) {
|
||||
const center = map.getCenter();
|
||||
lat = center.lat + (Math.random() - 0.5) * 0.05;
|
||||
lon = center.lon + (Math.random() - 0.5) * 0.05;
|
||||
}
|
||||
loadedSiteIds.add(site.id);
|
||||
|
||||
// Create custom icon
|
||||
const customIcon = L.divIcon({
|
||||
className: 'custom-transit-marker',
|
||||
html: `<div style="background-color: #0061a1; color: white; border-radius: 50%; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 3px solid white; box-shadow: 0 2px 8px rgba(0,0,0,0.4); font-size: 18px;">🚌</div>`,
|
||||
className: 'custom-marker',
|
||||
html: '<div style="background-color: #28a745; color: white; border-radius: 50%; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 3px solid white; box-shadow: 0 2px 8px rgba(0,0,0,0.4); font-size: 18px;">🚌</div>',
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 16],
|
||||
popupAnchor: [0, -16]
|
||||
iconAnchor: [16, 16]
|
||||
});
|
||||
|
||||
const marker = L.marker([lat, lon], {
|
||||
icon: customIcon,
|
||||
title: site.name
|
||||
}).addTo(map);
|
||||
|
||||
const popupContent = document.createElement('div');
|
||||
popupContent.style.minWidth = '220px';
|
||||
popupContent.innerHTML = `
|
||||
<div style="margin-bottom: 8px;">
|
||||
<strong style="font-size: 1.1em; color: #0061a1; display: block; margin-bottom: 4px;">${site.name}</strong>
|
||||
<span style="color: #666; font-size: 0.9em;">ID: ${site.id}</span>
|
||||
</div>
|
||||
<button class="select-site-from-map-btn"
|
||||
data-site-id="${site.id}"
|
||||
data-site-name="${site.name}"
|
||||
style="margin-top: 8px; padding: 10px 15px; width: 100%; background-color: #0061a1; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 1em;">
|
||||
const marker = L.marker([lat, lon], { icon: customIcon });
|
||||
marker.bindPopup(`
|
||||
<div style="min-width: 200px;">
|
||||
<strong style="font-size: 1.1em; color: #0061a1;">${site.name}</strong><br>
|
||||
<span style="color: #666; font-size: 0.9em;">ID: ${site.id}</span><br>
|
||||
<button class="select-site-from-map" data-site-id="${site.id}" data-site-name="${site.name}"
|
||||
style="margin-top: 8px; padding: 8px 15px; width: 100%; background-color: #0061a1; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||||
Select This Stop
|
||||
</button>
|
||||
`;
|
||||
|
||||
marker.bindPopup(popupContent);
|
||||
markers.push(marker);
|
||||
|
||||
marker.on('click', function() {
|
||||
this.openPopup();
|
||||
});
|
||||
</div>
|
||||
`);
|
||||
markersLayer.addLayer(marker);
|
||||
searchMarkers.push(marker);
|
||||
});
|
||||
|
||||
// Fit map to show results
|
||||
if (markers.length > 0) {
|
||||
const group = new L.featureGroup(markers);
|
||||
if (searchMarkers.length > 0) {
|
||||
const group = new L.featureGroup(searchMarkers);
|
||||
map.fitBounds(group.getBounds().pad(0.1));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user