File size: 4,506 Bytes
9d2d895 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import type { MapContainer } from '@/components/MapContainer';
import type { MapLayers } from '@/types';
import { fetchEarthquakes } from '@/services/earthquakes';
import { fetchNaturalEvents } from '@/services/eonet';
import { fetchProtestEvents } from '@/services/unrest';
import { fetchWeatherAlerts } from '@/services/weather';
import { startSmartPollLoop, type SmartPollLoopHandle } from '@/services/smart-poll-loop';
import type { EmbedLayerId } from './embed-url';
import { ConflictServiceClient } from '@/services/generated-rpc-clients';
const REFRESH_MS = 10 * 60 * 1000;
const CONFLICT_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
const conflictClient = new ConflictServiceClient('', { fetch: (...args) => globalThis.fetch(...args) });
const STATIC_LAYER_READY_BY_EMBED_ID: Partial<Record<EmbedLayerId, keyof MapLayers>> = {
cables: 'cables',
pipelines: 'pipelines',
waterways: 'waterways',
tradeRoutes: 'tradeRoutes',
economic: 'economic',
stockExchanges: 'stockExchanges',
financialCenters: 'financialCenters',
centralBanks: 'centralBanks',
commodityHubs: 'commodityHubs',
gulfInvestments: 'gulfInvestments',
};
export class EmbedDataLoader {
private refreshLoop: SmartPollLoopHandle | null = null;
constructor(
private readonly map: MapContainer,
private readonly activeLayerIds: readonly EmbedLayerId[],
) {}
async start(): Promise<void> {
await this.loadOnce();
this.refreshLoop = startSmartPollLoop(() => this.loadOnce(), {
intervalMs: REFRESH_MS,
pauseWhenHidden: true,
refreshOnVisible: true,
runImmediately: false,
});
}
destroy(): void {
if (this.refreshLoop !== null) {
this.refreshLoop.stop();
this.refreshLoop = null;
}
}
async loadOnce(): Promise<void> {
await Promise.all(this.activeLayerIds.map((id) => this.loadLayer(id)));
}
private async loadLayer(id: EmbedLayerId): Promise<void> {
switch (id) {
case 'conflicts':
await this.loadConflicts();
return;
case 'earthquakes':
await this.loadEarthquakes();
return;
case 'protests':
await this.loadProtests();
return;
case 'weather':
await this.loadWeather();
return;
default:
this.markStaticLayerReady(id);
return;
}
}
private markStaticLayerReady(id: EmbedLayerId): void {
const layer = STATIC_LAYER_READY_BY_EMBED_ID[id];
if (layer) this.map.setLayerReady(layer, true);
}
private async loadConflicts(): Promise<void> {
if (!this.map.supportsLiveConflictEvents()) {
this.map.setLayerReady('conflicts', true);
return;
}
await this.withLayerState('conflicts', async () => {
const end = Date.now();
const start = end - CONFLICT_WINDOW_MS;
const data = await conflictClient.listAcledEvents({ country: '', start, end, pageSize: 0, cursor: '' });
this.map.setConflictEvents(data.events);
return data.events.length > 0;
});
}
private async loadEarthquakes(): Promise<void> {
await this.withLayerState('natural', async () => {
const [earthquakesResult, naturalEventsResult] = await Promise.allSettled([
fetchEarthquakes(),
fetchNaturalEvents(30),
]);
if (earthquakesResult.status === 'fulfilled') {
this.map.setEarthquakes(earthquakesResult.value);
}
if (naturalEventsResult.status === 'fulfilled') {
this.map.setNaturalEvents(naturalEventsResult.value);
}
return earthquakesResult.status === 'fulfilled' || naturalEventsResult.status === 'fulfilled';
});
}
private async loadProtests(): Promise<void> {
await this.withLayerState('protests', async () => {
const data = await fetchProtestEvents();
this.map.setProtests(data.events);
return true;
});
}
private async loadWeather(): Promise<void> {
await this.withLayerState('weather', async () => {
const alerts = await fetchWeatherAlerts();
this.map.setWeatherAlerts(alerts);
return true;
});
}
private async withLayerState(layer: keyof MapLayers, load: () => Promise<boolean>): Promise<void> {
this.map.setLayerLoading(layer, true);
try {
const hasData = await load();
this.map.setLayerReady(layer, hasData);
} catch (error) {
console.warn(`[embed] Failed to load ${layer}:`, error);
this.map.setLayerReady(layer, false);
} finally {
this.map.setLayerLoading(layer, false);
}
}
}
|