@@ -67,7 +67,9 @@ import {
6767 FINANCIAL_CENTERS ,
6868 CENTRAL_BANKS ,
6969 COMMODITY_HUBS ,
70+ GULF_INVESTMENTS ,
7071} from '@/config' ;
72+ import type { GulfInvestment } from '@/types' ;
7173import { MapPopup , type PopupType } from './MapPopup' ;
7274import {
7375 updateHotspotEscalation ,
@@ -156,6 +158,7 @@ const LAYER_ZOOM_THRESHOLDS: Partial<Record<keyof MapLayers, { minZoom: number;
156158 datacenters : { minZoom : 5 } ,
157159 irradiators : { minZoom : 4 } ,
158160 spaceports : { minZoom : 3 } ,
161+ gulfInvestments : { minZoom : 2 , showLabels : 5 } ,
159162} ;
160163// Export for external use
161164export { LAYER_ZOOM_THRESHOLDS } ;
@@ -210,6 +213,8 @@ function getOverlayColors() {
210213 commodityHub : isLight
211214 ? [ 190 , 95 , 40 , 220 ] as [ number , number , number , number ]
212215 : [ 255 , 150 , 80 , 200 ] as [ number , number , number , number ] ,
216+ gulfInvestmentSA : [ 0 , 168 , 107 , 220 ] as [ number , number , number , number ] ,
217+ gulfInvestmentUAE : [ 255 , 0 , 100 , 220 ] as [ number , number , number , number ] ,
213218 ucdpStateBased : [ 255 , 50 , 50 , 200 ] as [ number , number , number , number ] ,
214219 ucdpNonState : [ 255 , 165 , 0 , 200 ] as [ number , number , number , number ] ,
215220 ucdpOneSided : [ 255 , 255 , 0 , 200 ] as [ number , number , number , number ] ,
@@ -1144,6 +1149,11 @@ export class DeckGLMap {
11441149 }
11451150 }
11461151
1152+ // Gulf FDI investments layer
1153+ if ( mapLayers . gulfInvestments ) {
1154+ layers . push ( this . createGulfInvestmentsLayer ( ) ) ;
1155+ }
1156+
11471157 // News geo-locations (always shown if data exists)
11481158 if ( this . newsLocations . length > 0 ) {
11491159 layers . push ( ...this . createNewsLocationsLayer ( ) ) ;
@@ -2143,6 +2153,28 @@ export class DeckGLMap {
21432153 return layers ;
21442154 }
21452155
2156+ private createGulfInvestmentsLayer ( ) : ScatterplotLayer {
2157+ return new ScatterplotLayer < GulfInvestment > ( {
2158+ id : 'gulf-investments-layer' ,
2159+ data : GULF_INVESTMENTS ,
2160+ getPosition : ( d : GulfInvestment ) => [ d . lon , d . lat ] ,
2161+ getRadius : ( d : GulfInvestment ) => {
2162+ if ( ! d . investmentUSD ) return 20000 ;
2163+ if ( d . investmentUSD >= 50000 ) return 70000 ;
2164+ if ( d . investmentUSD >= 10000 ) return 55000 ;
2165+ if ( d . investmentUSD >= 1000 ) return 40000 ;
2166+ return 25000 ;
2167+ } ,
2168+ getFillColor : ( d : GulfInvestment ) =>
2169+ d . investingCountry === 'SA' ? COLORS . gulfInvestmentSA : COLORS . gulfInvestmentUAE ,
2170+ getLineColor : [ 255 , 255 , 255 , 80 ] as [ number , number , number , number ] ,
2171+ lineWidthMinPixels : 1 ,
2172+ radiusMinPixels : 5 ,
2173+ radiusMaxPixels : 28 ,
2174+ pickable : true ,
2175+ } ) ;
2176+ }
2177+
21462178 private pulseTime = 0 ;
21472179
21482180 private canPulse ( now = Date . now ( ) ) : boolean {
@@ -2401,6 +2433,23 @@ export class DeckGLMap {
24012433 return { html : `<div class="deckgl-tooltip"><strong>Cyber Threat</strong><br/>${ text ( obj . severity || 'medium' ) } · ${ text ( obj . country || 'Unknown' ) } </div>` } ;
24022434 case 'news-locations-layer' :
24032435 return { html : `<div class="deckgl-tooltip"><strong>📰 News</strong><br/>${ text ( obj . title ?. slice ( 0 , 80 ) || '' ) } </div>` } ;
2436+ case 'gulf-investments-layer' : {
2437+ const inv = obj as GulfInvestment ;
2438+ const flag = inv . investingCountry === 'SA' ? '🇸🇦' : '🇦🇪' ;
2439+ const usd = inv . investmentUSD != null
2440+ ? ( inv . investmentUSD >= 1000 ? `$${ ( inv . investmentUSD / 1000 ) . toFixed ( 1 ) } B` : `$${ inv . investmentUSD } M` )
2441+ : 'Undisclosed' ;
2442+ const stake = inv . stakePercent != null ? `<br/>${ text ( String ( inv . stakePercent ) ) } % stake` : '' ;
2443+ return {
2444+ html : `<div class="deckgl-tooltip">
2445+ <strong>${ flag } ${ text ( inv . assetName ) } </strong><br/>
2446+ <em>${ text ( inv . investingEntity ) } </em><br/>
2447+ ${ text ( inv . targetCountry ) } · ${ text ( inv . sector ) } <br/>
2448+ <strong>${ usd } </strong>${ stake } <br/>
2449+ <span style="text-transform:capitalize">${ text ( inv . status ) } </span>
2450+ </div>` ,
2451+ } ;
2452+ }
24042453 default :
24052454 return null ;
24062455 }
0 commit comments