diff --git a/static/js/map.js b/static/js/map.js index 73ca7d1..91b39da 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -1,30 +1,60 @@ if (![].at) { - Array.prototype.at = function(pos) { return this.slice(pos, pos + 1)[0] } + Array.prototype.at = function(pos) { return this.slice(pos, pos + 1)[0]; }; } -function emoji_icon(emoji) { - var iconStyle = "
" + emoji + "
"; +var emojiByType = { + "station": "🚉", + "airport": "✈️", + "ferry_terminal": "🚢", + "accommodation": "🏨", + "conference": "🖥️", + "event": "🍷" +}; + +function getIconMetrics(zoom) { + var outerSize; + if (zoom <= 3) { + outerSize = 20; + } else if (zoom <= 5) { + outerSize = 26; + } else if (zoom <= 8) { + outerSize = 32; + } else { + outerSize = 38; + } + var innerSize = outerSize - 6; + var fontSize = Math.max(12, Math.round(innerSize * 0.65)); + + return { + outerSize: outerSize, + fontSize: fontSize, + anchor: [outerSize / 2, outerSize / 2] + }; +} + +function emojiIcon(emoji, zoom) { + var symbol = emoji || "📍"; + var metrics = getIconMetrics(zoom); + var iconStyle = [ + "
", + "
", + symbol, + "
" + ].join(""); return L.divIcon({ - className: 'custom-div-icon', + className: "custom-div-icon", html: iconStyle, - iconSize: [60, 60], - iconAnchor: [15, 15], + iconSize: [metrics.outerSize, metrics.outerSize], + iconAnchor: metrics.anchor }); } -var icons = { - "station": emoji_icon("🚉"), - "airport": emoji_icon("✈️"), - "ferry_terminal": emoji_icon("🚢"), - "accommodation": emoji_icon("🏨"), - "conference": emoji_icon("🖥️"), - "event": emoji_icon("🍷"), -} - function build_map(map_id, coordinates, routes) { - var map = L.map(map_id).fitBounds(coordinates.map(station => [station.latitude, station.longitude])); + var map = L.map(map_id).fitBounds(coordinates.map(function(station) { return [station.latitude, station.longitude]; })); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' @@ -33,39 +63,40 @@ function build_map(map_id, coordinates, routes) { var markers = []; var offset_lines = []; - function getIconBounds(latlng) { - let iconSize = 20; // Assuming the icon size as a square + function getIconBounds(latlng, zoom) { + var iconSize = getIconMetrics(zoom).outerSize; if (!latlng) return null; - let pixel = map.project(latlng, map.getZoom()); - let sw = map.unproject([pixel.x - iconSize / 2, pixel.y + iconSize / 2], map.getZoom()); - let ne = map.unproject([pixel.x + iconSize / 2, pixel.y - iconSize / 2], map.getZoom()); + var pixel = map.project(latlng, zoom); + var sw = map.unproject([pixel.x - iconSize / 2, pixel.y + iconSize / 2], zoom); + var ne = map.unproject([pixel.x + iconSize / 2, pixel.y - iconSize / 2], zoom); return L.latLngBounds(sw, ne); } function calculateCentroid(markers) { - let latSum = 0, lngSum = 0, count = 0; - markers.forEach(marker => { + var latSum = 0, lngSum = 0, count = 0; + markers.forEach(function(marker) { latSum += marker.getLatLng().lat; lngSum += marker.getLatLng().lng; - count++; + count += 1; }); return count > 0 ? L.latLng(latSum / count, lngSum / count) : null; } // Function to detect and group overlapping markers - function getOverlappingGroups() { - let groups = []; - let visited = new Set(); + function getOverlappingGroups(zoom) { + var groups = []; + var visited = new Set(); - markers.forEach((marker, index) => { + markers.forEach(function(marker) { if (visited.has(marker)) { return; } - let group = []; - let markerBounds = getIconBounds(marker.getLatLng()); + var group = []; + var markerBounds = getIconBounds(marker.getLatLng(), zoom); - markers.forEach((otherMarker) => { - if (marker !== otherMarker && markerBounds.intersects(getIconBounds(otherMarker.getLatLng()))) { + markers.forEach(function(otherMarker) { + var otherBounds = getIconBounds(otherMarker.getLatLng(), zoom); + if (marker !== otherMarker && markerBounds && otherBounds && markerBounds.intersects(otherBounds)) { group.push(otherMarker); visited.add(otherMarker); } @@ -82,60 +113,75 @@ function build_map(map_id, coordinates, routes) { } function displaceMarkers(group, zoom) { - const markerPixelSize = 30; // Width/height of the marker in pixels - let map = group[0]._map; // Assuming all markers are on the same map + var markerPixelSize = Math.max(18, getIconMetrics(zoom).outerSize); + var mapRef = group[0]._map; // Assuming all markers are on the same map - let centroid = calculateCentroid(group); - let centroidPoint = map.project(centroid, zoom); + var centroid = calculateCentroid(group); + if (!centroid) { + return; + } + var centroidPoint = mapRef.project(centroid, zoom); - const radius = markerPixelSize; // Set radius for even distribution - const angleIncrement = (2 * Math.PI) / group.length; // Evenly space markers + var radius = markerPixelSize * 1.1; + var angleIncrement = (2 * Math.PI) / group.length; - group.forEach((marker, index) => { - let angle = index * angleIncrement; - let newX = centroidPoint.x + radius * Math.cos(angle); - let newY = centroidPoint.y + radius * Math.sin(angle); - let newPoint = L.point(newX, newY); - let newLatLng = map.unproject(newPoint, zoom); + group.forEach(function(marker, index) { + var angle = index * angleIncrement; + var newX = centroidPoint.x + radius * Math.cos(angle); + var newY = centroidPoint.y + radius * Math.sin(angle); + var newPoint = L.point(newX, newY); + var newLatLng = mapRef.unproject(newPoint, zoom); - // Store original position for polyline - let originalPos = marker.getLatLng(); + var originalPos = marker.originalLatLng; marker.setLatLng(newLatLng); - marker.polyline = L.polyline([originalPos, newLatLng], {color: "gray", weight: 2}).addTo(map); + marker.polyline = L.polyline([originalPos, newLatLng], {color: "#909090", weight: 1, dashArray: "2,4"}).addTo(mapRef); offset_lines.push(marker.polyline); }); } - coordinates.forEach(function(item, index) { - let latlng = L.latLng(item.latitude, item.longitude); - let marker = L.marker(latlng, { icon: icons[item.type] }).addTo(map); + function updateMarkerIcons(zoom) { + markers.forEach(function(marker) { + marker.setIcon(emojiIcon(marker.emoji, zoom)); + }); + } + + coordinates.forEach(function(item) { + var latlng = L.latLng(item.latitude, item.longitude); + var marker = L.marker(latlng, { icon: emojiIcon(emojiByType[item.type], map.getZoom()) }).addTo(map); marker.bindPopup(item.name); + marker.originalLatLng = latlng; + marker.emoji = emojiByType[item.type]; markers.push(marker); }); - map.on('zoomend', function() { - markers.forEach((marker, index) => { - marker.setLatLng([coordinates[index].latitude, coordinates[index].longitude]); // Reset position on zoom + function resetMarkerPositions() { + markers.forEach(function(marker) { + marker.setLatLng(marker.originalLatLng); if (marker.polyline) { map.removeLayer(marker.polyline); + marker.polyline = null; } }); - - offset_lines.forEach(polyline => { + offset_lines.forEach(function(polyline) { map.removeLayer(polyline); }); + offset_lines = []; + } - let overlappingGroups = getOverlappingGroups(); - // console.log(overlappingGroups); // Process or display groups as needed + map.on('zoomend', function() { + resetMarkerPositions(); + updateMarkerIcons(map.getZoom()); - overlappingGroups.forEach(group => displaceMarkers(group, map.getZoom())); + var overlappingGroups = getOverlappingGroups(map.getZoom()); + + overlappingGroups.forEach(function(group) { return displaceMarkers(group, map.getZoom()); }); }); - let overlappingGroups = getOverlappingGroups(); - // console.log(overlappingGroups); // Process or display groups as needed + updateMarkerIcons(map.getZoom()); - overlappingGroups.forEach(group => displaceMarkers(group, map.getZoom())); + var initialGroups = getOverlappingGroups(map.getZoom()); + initialGroups.forEach(function(group) { return displaceMarkers(group, map.getZoom()); }); // Draw routes