diff --git a/static/index.html b/static/index.html index cd2635d..8c7579d 100644 --- a/static/index.html +++ b/static/index.html @@ -1393,6 +1393,9 @@ clearTimeout(moveTimeout); moveTimeout = setTimeout(renderLiveVessels, 100); + // Re-render infrastructure for viewport culling (debounced) + debouncedRenderInfrastructure(); + // Auto-update bounding box silently (500ms debounce, with padding) if (autoUpdateBounds) { clearTimeout(boundingBoxTimeout); @@ -3396,6 +3399,8 @@ if (!showInfrastructure || !balticInfrastructure.length) return; const layers = []; + const zoom = map.getZoom(); + const bounds = map.getBounds(); // Infrastructure type colors const typeColors = { @@ -3406,15 +3411,22 @@ 'fiber_optic': '#9b59b6' // Purple }; + // Performance: only render assets in viewport (with padding) + const paddedBounds = bounds.pad(0.2); + balticInfrastructure.forEach(asset => { const color = typeColors[asset.type] || '#95a5a6'; const waypoints = asset.waypoints || []; if (waypoints.length > 1) { + // Check if any waypoint is in viewport + const inView = waypoints.some(wp => paddedBounds.contains([wp[0], wp[1]])); + if (!inView) return; // Skip assets outside viewport + // Draw cable/pipeline route as a line const line = L.polyline(waypoints, { color: color, - weight: 3, + weight: zoom >= 6 ? 3 : 2, opacity: 0.7, dashArray: asset.type.includes('cable') ? null : '10, 5' }); @@ -3432,43 +3444,51 @@

${asset.name}

`); layers.push(line); - // Draw protection zone circles at key points - const midIdx = Math.floor(waypoints.length / 2); - const midpoint = waypoints[midIdx]; - const circle = L.circle([midpoint[0], midpoint[1]], { - radius: asset.protection_radius_nm * 1852, // nm to meters - color: color, - fillColor: color, - fillOpacity: 0.1, - weight: 1, - dashArray: '5, 5' - }); - layers.push(circle); - - // Add text label for the infrastructure line - const labelIcon = L.divIcon({ - className: 'infra-label', - html: `
${asset.name}
`, - iconSize: [100, 20], - iconAnchor: [50, 10] - }); - const label = L.marker([midpoint[0], midpoint[1]], { - icon: labelIcon, - interactive: false - }); - layers.push(label); + // Only show protection zones and labels at higher zoom (performance) + if (zoom >= 7) { + const midIdx = Math.floor(waypoints.length / 2); + const midpoint = waypoints[midIdx]; + + // Protection zone circle + const circle = L.circle([midpoint[0], midpoint[1]], { + radius: asset.protection_radius_nm * 1852, + color: color, + fillColor: color, + fillOpacity: 0.1, + weight: 1, + dashArray: '5, 5' + }); + layers.push(circle); + + // Text label only at zoom 8+ + if (zoom >= 8) { + const labelIcon = L.divIcon({ + className: 'infra-label', + html: `
${asset.name}
`, + iconSize: [100, 20], + iconAnchor: [50, 10] + }); + const label = L.marker([midpoint[0], midpoint[1]], { + icon: labelIcon, + interactive: false + }); + layers.push(label); + } + } } else if (asset.latitude && asset.longitude) { - // Point infrastructure + // Point infrastructure - check if in view + if (!paddedBounds.contains([asset.latitude, asset.longitude])) return; + const marker = L.circleMarker([asset.latitude, asset.longitude], { radius: 8, color: color, @@ -3485,6 +3505,13 @@

${asset.name}

} } + // Debounced render for map movement + let infraRenderTimeout = null; + function debouncedRenderInfrastructure() { + if (infraRenderTimeout) clearTimeout(infraRenderTimeout); + infraRenderTimeout = setTimeout(renderInfrastructure, 150); + } + function toggleInfrastructure() { showInfrastructure = !showInfrastructure; const btn = document.getElementById('toggle-infrastructure');