diff --git a/static/js/map.js b/static/js/map.js
index 8403f41..8aaea69 100644
--- a/static/js/map.js
+++ b/static/js/map.js
@@ -22,39 +22,136 @@ var icons = {
"event": emoji_icon("🍷"),
}
+
function build_map(map_id, coordinates, routes) {
- // Initialize the map
- var map = L.map(map_id).fitBounds(coordinates.map(function(station) {
- return [station.latitude, station.longitude];
- }));
+ var map = L.map(map_id).fitBounds(coordinates.map(station => [station.latitude, station.longitude]));
- // Set up the tile layer
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
- attribution: '© OpenStreetMap contributors'
- }).addTo(map);
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '© OpenStreetMap contributors'
+ }).addTo(map);
- // Add markers with appropriate icons to the map
- coordinates.forEach(function(item) {
- var marker = L.marker([item.latitude, item.longitude], { icon: icons[item.type] }).addTo(map);
- marker.bindPopup(item.name);
- });
+ var markers = [];
+ var offset_lines = [];
- // Draw routes
- routes.forEach(function(route) {
- var color = {"train": "blue", "flight": "red", "unbooked_flight": "orange"}[route.type];
- var style = { weight: 3, opacity: 0.5, color: color };
- if (route.geojson) {
- // If route is defined as GeoJSON
- L.geoJSON(JSON.parse(route.geojson), {
- style: function(feature) { return style; }
- }).addTo(map);
- } else if (route.type === "flight" || route.type === "unbooked_flight") {
- var flightPath = new L.Geodesic([[route.from, route.to]], style).addTo(map);
- } else {
- // If route is defined by 'from' and 'to' coordinates
- L.polyline([route.from, route.to], style).addTo(map);
+ function getIconBounds(latlng) {
+ let iconSize = 30; // Assuming the icon size as a square of 30x30 pixels
+ 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());
+ return L.latLngBounds(sw, ne);
}
- });
- return map;
+ function calculateCentroid(markers) {
+ let latSum = 0, lngSum = 0, count = 0;
+ markers.forEach(marker => {
+ latSum += marker.getLatLng().lat;
+ lngSum += marker.getLatLng().lng;
+ count++;
+ });
+ 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();
+
+ markers.forEach((marker, index) => {
+ if (visited.has(marker)) {
+ return;
+ }
+ let group = [];
+ let markerBounds = getIconBounds(marker.getLatLng());
+
+ markers.forEach((otherMarker) => {
+ if (marker !== otherMarker && markerBounds.intersects(getIconBounds(otherMarker.getLatLng()))) {
+ group.push(otherMarker);
+ visited.add(otherMarker);
+ }
+ });
+
+ if (group.length > 0) {
+ group.push(marker); // Add the original marker to the group
+ groups.push(group);
+ visited.add(marker);
+ }
+ });
+
+ return groups;
+ }
+
+ 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
+
+ let centroid = calculateCentroid(group);
+ let centroidPoint = map.project(centroid, zoom);
+
+ const radius = markerPixelSize; // Set radius for even distribution
+ const angleIncrement = (2 * Math.PI) / group.length; // Evenly space markers
+
+ 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);
+
+ // Store original position for polyline
+ let originalPos = marker.getLatLng();
+ marker.setLatLng(newLatLng);
+
+ marker.polyline = L.polyline([originalPos, newLatLng], {color: "gray", weight: 2}).addTo(map);
+ 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);
+ marker.bindPopup(item.name);
+ markers.push(marker);
+ });
+
+ map.on('zoomend', function() {
+ markers.forEach((marker, index) => {
+ marker.setLatLng([coordinates[index].latitude, coordinates[index].longitude]); // Reset position on zoom
+ if (marker.polyline) {
+ map.removeLayer(marker.polyline);
+ }
+ });
+
+ offset_lines.forEach(polyline => {
+ map.removeLayer(polyline);
+ });
+
+ let overlappingGroups = getOverlappingGroups();
+ // console.log(overlappingGroups); // Process or display groups as needed
+
+ overlappingGroups.forEach(group => displaceMarkers(group, map.getZoom()));
+ });
+
+ let overlappingGroups = getOverlappingGroups();
+ // console.log(overlappingGroups); // Process or display groups as needed
+
+ overlappingGroups.forEach(group => displaceMarkers(group, map.getZoom()));
+
+
+ // Draw routes
+ routes.forEach(function(route) {
+ var color = {"train": "blue", "flight": "red", "unbooked_flight": "orange"}[route.type];
+ var style = { weight: 3, opacity: 0.5, color: color };
+ if (route.geojson) {
+ L.geoJSON(JSON.parse(route.geojson), {
+ style: function(feature) { return style; }
+ }).addTo(map);
+ } else if (route.type === "flight" || route.type === "unbooked_flight") {
+ var flightPath = new L.Geodesic([[route.from, route.to]], style).addTo(map);
+ } else {
+ L.polyline([route.from, route.to], style).addTo(map);
+ }
+ });
+
+ return map;
}