diff --git a/src/components/DeckGLMap.ts b/src/components/DeckGLMap.ts index dd5fd285e..982b4bb5e 100644 --- a/src/components/DeckGLMap.ts +++ b/src/components/DeckGLMap.ts @@ -49,6 +49,7 @@ import { SPACEPORTS, APT_GROUPS, CRITICAL_MINERALS, + COUNTRY_LABELS, } from '@/config'; import { MapPopup, type PopupType } from './MapPopup'; import { @@ -152,7 +153,10 @@ export class DeckGLMap { private earthquakes: Earthquake[] = []; private weatherAlerts: WeatherAlert[] = []; private outages: InternetOutage[] = []; + private aisDisruptions: AisDisruptionEvent[] = []; private aisDensity: AisDensityZone[] = []; + private cableAdvisories: CableAdvisory[] = []; + private repairShips: RepairShip[] = []; private protests: SocialUnrestEvent[] = []; private militaryFlights: MilitaryFlight[] = []; private militaryVessels: MilitaryVessel[] = []; @@ -634,11 +638,31 @@ export class DeckGLMap { layers.push(this.createAisDensityLayer()); } + // AIS disruptions layer (spoofing/jamming) + if (mapLayers.ais && this.aisDisruptions.length > 0) { + layers.push(this.createAisDisruptionsLayer()); + } + // Strategic ports layer (shown with AIS) if (mapLayers.ais) { layers.push(this.createPortsLayer()); } + // Cable advisories layer (shown with cables) + if (mapLayers.cables && this.cableAdvisories.length > 0) { + layers.push(this.createCableAdvisoriesLayer()); + } + + // Repair ships layer (shown with cables) + if (mapLayers.cables && this.repairShips.length > 0) { + layers.push(this.createRepairShipsLayer()); + } + + // Country labels layer + if (mapLayers.countries) { + layers.push(this.createCountryLabelsLayer()); + } + // Flight delays layer if (mapLayers.flights && this.flightDelays.length > 0) { layers.push(this.createFlightDelaysLayer()); @@ -1027,6 +1051,82 @@ export class DeckGLMap { }); } + private createAisDisruptionsLayer(): ScatterplotLayer { + // AIS spoofing/jamming events + return new ScatterplotLayer({ + id: 'ais-disruptions-layer', + data: this.aisDisruptions, + getPosition: (d) => [d.lon, d.lat], + getRadius: 12000, + getFillColor: (d) => { + // Color by severity/type + if (d.severity === 'high' || d.type === 'spoofing') { + return [255, 50, 50, 220] as [number, number, number, number]; // Red + } + if (d.severity === 'medium') { + return [255, 150, 0, 200] as [number, number, number, number]; // Orange + } + return [255, 200, 100, 180] as [number, number, number, number]; // Yellow + }, + radiusMinPixels: 6, + radiusMaxPixels: 14, + pickable: true, + stroked: true, + getLineColor: [255, 255, 255, 150] as [number, number, number, number], + lineWidthMinPixels: 1, + }); + } + + private createCableAdvisoriesLayer(): ScatterplotLayer { + // Cable fault/maintenance advisories + return new ScatterplotLayer({ + id: 'cable-advisories-layer', + data: this.cableAdvisories, + getPosition: (d) => [d.lon, d.lat], + getRadius: 10000, + getFillColor: (d) => { + if (d.severity === 'fault') { + return [255, 50, 50, 220] as [number, number, number, number]; // Red for faults + } + return [255, 200, 0, 200] as [number, number, number, number]; // Yellow for maintenance + }, + radiusMinPixels: 5, + radiusMaxPixels: 12, + pickable: true, + stroked: true, + getLineColor: [0, 200, 255, 200] as [number, number, number, number], // Cyan outline (cable color) + lineWidthMinPixels: 2, + }); + } + + private createRepairShipsLayer(): ScatterplotLayer { + // Cable repair ships + return new ScatterplotLayer({ + id: 'repair-ships-layer', + data: this.repairShips, + getPosition: (d) => [d.lon, d.lat], + getRadius: 8000, + getFillColor: [0, 255, 200, 200] as [number, number, number, number], // Teal + radiusMinPixels: 4, + radiusMaxPixels: 10, + pickable: true, + }); + } + + private createCountryLabelsLayer(): ScatterplotLayer { + // Country labels as small markers (text would require TextLayer) + return new ScatterplotLayer({ + id: 'country-labels-layer', + data: COUNTRY_LABELS, + getPosition: (d) => [d.lon, d.lat], + getRadius: 3000, + getFillColor: [200, 200, 200, 100] as [number, number, number, number], + radiusMinPixels: 2, + radiusMaxPixels: 4, + pickable: true, + }); + } + // Note: Protests layer now rendered via HTML overlays in renderProtestClusters() private createMilitaryVesselsLayer(): ScatterplotLayer { @@ -1287,6 +1387,23 @@ export class DeckGLMap { return { html: `