From a6427dfe08754f8deefa085d139d0cf9bf599e8e Mon Sep 17 00:00:00 2001 From: Marc Shade Date: Tue, 24 Feb 2026 13:51:58 -0500 Subject: [PATCH] feat: marker clustering, cable corridor click-through fix, keyboard shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Marker clustering (leaflet.markercluster) for air traffic, military, conflict, wildfires, and nav warnings — clusters expand on click - Cable corridor rectangles set to interactive:false so clicks pass through to markers underneath (fixes blocked clicks in Med/Middle East) - Batch addLayers() for air traffic performance with 9500+ markers - Keyboard shortcuts: Esc closes panels, D toggles drawer, R resets view - Custom dark-theme cluster styles with per-layer color coding Co-Authored-By: Claude Opus 4.6 --- src/world_intel_mcp/dashboard/index.html | 69 +++++++++++++++++------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/src/world_intel_mcp/dashboard/index.html b/src/world_intel_mcp/dashboard/index.html index c3f696a..9876f10 100644 --- a/src/world_intel_mcp/dashboard/index.html +++ b/src/world_intel_mcp/dashboard/index.html @@ -8,7 +8,9 @@ + + @@ -627,17 +643,29 @@ function initMap() { map = L.map('map', { zoomControl: false, attributionControl: false, worldCopyJump: true }).setView([20, 0], 2.5); L.control.zoom({ position: 'bottomleft' }).addTo(map); L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { maxZoom: 18 }).addTo(map); + var clusterOpts = function(cls, size) { + return { + maxClusterRadius: size || 40, + spiderfyOnMaxZoom: true, + showCoverageOnHover: false, + iconCreateFunction: function(cluster) { + var n = cluster.getChildCount(); + var sz = n < 50 ? 'small' : n < 200 ? 'medium' : 'large'; + return L.divIcon({ html: '
' + n + '
', className: 'marker-cluster marker-cluster-' + sz + (cls ? ' ' + cls : ''), iconSize: L.point(38, 38) }); + } + }; + }; mapLayers.quakes = L.layerGroup().addTo(map); - mapLayers.military = L.layerGroup().addTo(map); - mapLayers.conflict = L.layerGroup().addTo(map); - mapLayers.fires = L.layerGroup().addTo(map); + mapLayers.military = L.markerClusterGroup(clusterOpts('cluster-military', 50)).addTo(map); + mapLayers.conflict = L.markerClusterGroup(clusterOpts('cluster-conflict', 45)).addTo(map); + mapLayers.fires = L.markerClusterGroup(clusterOpts('cluster-fire', 45)).addTo(map); mapLayers.convergence = L.layerGroup().addTo(map); mapLayers.nuclear = L.layerGroup().addTo(map); mapLayers.infra = L.layerGroup().addTo(map); mapLayers.exposure = L.layerGroup().addTo(map); - mapLayers.airtraffic = L.layerGroup().addTo(map); + mapLayers.airtraffic = L.markerClusterGroup(clusterOpts(null, 60)).addTo(map); mapLayers.traffic = L.layerGroup().addTo(map); - mapLayers.navwarnings = L.layerGroup().addTo(map); + mapLayers.navwarnings = L.markerClusterGroup(clusterOpts(null, 40)).addTo(map); mapLayers.webcams = L.layerGroup().addTo(map); } @@ -1105,7 +1133,17 @@ document.addEventListener('click', function(e) { }); document.addEventListener('keydown', function(e) { - if (e.key === 'Escape') { closeDetail(); } + if (e.key === 'Escape') { + closeDetail(); + var dr = document.getElementById('drawer'); + if (dr) dr.classList.remove('open'); + } + if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; + if (e.key === 'd' || e.key === 'D') { + var dr = document.getElementById('drawer'); + if (dr) dr.classList.toggle('open'); + } + if (e.key === 'r' || e.key === 'R') { map.setView([20, 0], 2.5); } }); // ════════════ MAP MARKER MANAGEMENT ════════════ @@ -1334,23 +1372,16 @@ function updateMapInfra(data) { }); // Submarine cable corridors (shaded rectangles) var cables = (data.cable_corridors && data.cable_corridors.corridors) || []; - var cableStyle = { color: '#4da8ff', weight: 1, opacity: 0.2, fillColor: '#4da8ff', fillOpacity: 0.04, dashArray: '4 3', className: 'cable-corridor' }; + var cableStyle = { color: '#4da8ff', weight: 1, opacity: 0.2, fillColor: '#4da8ff', fillOpacity: 0.04, dashArray: '4 3', className: 'cable-corridor', interactive: false }; cables.forEach(function(c) { if (!c.lat_range || !c.lon_range) return; total++; - var tip = '' + esc(c.name.replace(/_/g, ' ')) + '
' + (c.cables || []).join(', ') + ''; // Handle antimeridian crossing (lon_start > lon_end means wrapping past 180) if (c.lon_range[0] > c.lon_range[1]) { - var r1 = L.rectangle([[c.lat_range[0], c.lon_range[0]], [c.lat_range[1], 180]], cableStyle); - var r2 = L.rectangle([[c.lat_range[0], -180], [c.lat_range[1], c.lon_range[1]]], cableStyle); - r1.bindTooltip(tip, { className: 'mk-tip', sticky: true }); - r2.bindTooltip(tip, { className: 'mk-tip', sticky: true }); - r1.addTo(mapLayers.infra); - r2.addTo(mapLayers.infra); + L.rectangle([[c.lat_range[0], c.lon_range[0]], [c.lat_range[1], 180]], cableStyle).addTo(mapLayers.infra); + L.rectangle([[c.lat_range[0], -180], [c.lat_range[1], c.lon_range[1]]], cableStyle).addTo(mapLayers.infra); } else { - var rect = L.rectangle([[c.lat_range[0], c.lon_range[0]], [c.lat_range[1], c.lon_range[1]]], cableStyle); - rect.bindTooltip(tip, { className: 'mk-tip', sticky: true }); - rect.addTo(mapLayers.infra); + L.rectangle([[c.lat_range[0], c.lon_range[0]], [c.lat_range[1], c.lon_range[1]]], cableStyle).addTo(mapLayers.infra); } }); $('#cntInfra').textContent = total; @@ -1393,6 +1424,7 @@ function updateMapAirTraffic(data) { if (!data || data.error) { $('#cntAirTraffic').textContent = '0'; return; } var positions = data.positions || []; $('#cntAirTraffic').textContent = data.total_aircraft || positions.length; + var markers = []; positions.forEach(function(p) { if (p.lat == null || p.lon == null) return; var mk = L.marker([p.lat, p.lon], { @@ -1402,8 +1434,9 @@ function updateMapAirTraffic(data) { if (p.alt) tip += ' @ ' + p.alt + 'm'; mk.bindTooltip(tip, { className: 'mk-tip', direction: 'top', offset: [0, -3] }); mk.on('click', function() { showDetail('airtraffic', p); }); - mk.addTo(mapLayers.airtraffic); + markers.push(mk); }); + mapLayers.airtraffic.addLayers(markers); } function updateMapTraffic(data) {