Skip to content

Commit 1f4bad3

Browse files
add grid alerts layer on map
calculate centroid of polygon show alert icon in centroid for zones where there is an alert with "action" type create warning icon for the map
1 parent c52473f commit 1f4bad3

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

web/src/features/map/Map.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727

2828
import { useCo2ColorScale, useTheme } from '../../hooks/theme';
2929
import BackgroundLayer from './map-layers/BackgroundLayer';
30+
import GridAlertsLayer from './map-layers/GridAlertsLayer';
3031
import StatesLayer from './map-layers/StatesLayer';
3132
import ZonesLayer from './map-layers/ZonesLayer';
3233
import CustomLayer from './map-utils/CustomLayer';
@@ -90,6 +91,26 @@ export default function MapPage({ onMapLoad }: MapPageProps): ReactElement {
9091
const onMapReferenceChange = useCallback((reference: MapRef) => {
9192
setMapReference(reference);
9293
}, []);
94+
const WARNING_ICON_ID = 'lucide-warning';
95+
96+
useEffect(() => {
97+
if (!map) {
98+
return;
99+
}
100+
101+
if (!map.hasImage(WARNING_ICON_ID)) {
102+
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`;
103+
const svg = new Blob([svgString], { type: 'image/svg+xml' });
104+
const url = URL.createObjectURL(svg);
105+
106+
const img = new window.Image(40, 40);
107+
img.addEventListener('load', () => {
108+
map.addImage(WARNING_ICON_ID, img, { sdf: false });
109+
URL.revokeObjectURL(url);
110+
});
111+
img.src = url;
112+
}
113+
}, [map]);
93114

94115
useEffect(() => {
95116
let subscription: PluginListenerHandle | null = null;
@@ -457,6 +478,7 @@ export default function MapPage({ onMapLoad }: MapPageProps): ReactElement {
457478
<BackgroundLayer />
458479
<ZonesLayer />
459480
<StatesLayer />
481+
<GridAlertsLayer />
460482
<CustomLayer>
461483
<WindLayer />
462484
</CustomLayer>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import useGetState from 'api/getState';
2+
import { useMemo } from 'react';
3+
import { Layer, Source } from 'react-map-gl/maplibre';
4+
5+
import { useGetGeometries } from '../map-utils/getMapGrid';
6+
7+
// Helper function to calculate the centroid of a polygon
8+
const getPolygonCentroid = (coordinates: number[][][]) => {
9+
let x = 0,
10+
y = 0,
11+
area = 0;
12+
const ring = coordinates[0]; // Use outer ring
13+
14+
for (let index = 0; index < ring.length - 1; index++) {
15+
const xi = ring[index][0];
16+
const yi = ring[index][1];
17+
const xi1 = ring[index + 1][0];
18+
const yi1 = ring[index + 1][1];
19+
const a = xi * yi1 - xi1 * yi;
20+
area += a;
21+
x += (xi + xi1) * a;
22+
y += (yi + yi1) * a;
23+
}
24+
25+
area *= 0.5;
26+
x /= 6 * area;
27+
y /= 6 * area;
28+
29+
return [x, y];
30+
};
31+
32+
export default function GridAlertsLayer() {
33+
const { worldGeometries } = useGetGeometries();
34+
const dataState = useGetState();
35+
const dataState_ = dataState.data;
36+
console.log('State data:', dataState_);
37+
38+
const warningIconData = useMemo(() => {
39+
if (!worldGeometries?.features) {
40+
return null;
41+
}
42+
43+
// Create one point per unique zoneId that needs a warning
44+
const warningZones = dataState_.alerts; // Add more zone IDs as needed for example ['CA-ON', 'US-MIDA-PJM']
45+
const features = [];
46+
47+
for (const zoneId of warningZones) {
48+
const zoneFeature = worldGeometries.features.find(
49+
(feature) => feature.properties?.zoneId === zoneId
50+
);
51+
52+
if (zoneFeature?.geometry) {
53+
let centroid;
54+
55+
if (zoneFeature.geometry.type === 'Polygon') {
56+
centroid = getPolygonCentroid(zoneFeature.geometry.coordinates);
57+
} else if (zoneFeature.geometry.type === 'MultiPolygon') {
58+
// Use the centroid of the largest polygon
59+
let largest = zoneFeature.geometry.coordinates[0];
60+
for (let index = 1; index < zoneFeature.geometry.coordinates.length; index++) {
61+
if (zoneFeature.geometry.coordinates[index][0].length > largest[0].length) {
62+
largest = zoneFeature.geometry.coordinates[index];
63+
}
64+
}
65+
centroid = getPolygonCentroid(largest);
66+
}
67+
68+
if (centroid) {
69+
features.push({
70+
type: 'Feature',
71+
geometry: {
72+
type: 'Point',
73+
coordinates: centroid,
74+
},
75+
properties: {
76+
zoneId: zoneId,
77+
},
78+
});
79+
}
80+
}
81+
}
82+
83+
return {
84+
type: 'FeatureCollection',
85+
features,
86+
};
87+
}, [worldGeometries]);
88+
89+
return (
90+
<>
91+
{/* Separate source for warning icons */}
92+
{warningIconData && (
93+
<Source id="warning-icons-source" type="geojson" data={warningIconData}>
94+
<Layer
95+
id="zones-warning-icon-layer"
96+
type="symbol"
97+
layout={{
98+
'icon-image': 'lucide-warning',
99+
'icon-size': 1.2,
100+
'icon-allow-overlap': true,
101+
'symbol-placement': 'point',
102+
}}
103+
/>
104+
</Source>
105+
)}
106+
</>
107+
);
108+
}

0 commit comments

Comments
 (0)