as-app/src/scenes/AlertCurMap/useFeatures.js
2026-03-07 07:53:08 +01:00

228 lines
6.1 KiB
JavaScript

import { useState } from "react";
import { getDistance } from "geolib";
import Supercluster from "supercluster";
import useShallowMemo from "~/hooks/useShallowMemo";
import useShallowEffect from "~/hooks/useShallowEffect";
import { deepEqual } from "fast-equals";
import { useDefibsState } from "~/stores";
import { getDefibAvailability } from "~/utils/dae/getDefibAvailability";
export default function useFeatures({
clusterFeature,
alertingList,
userCoords,
routeCoords,
route,
alertCoords,
}) {
const { showDefibsOnAlertMap, corridorDefibs } = useDefibsState([
"showDefibsOnAlertMap",
"corridorDefibs",
]);
// Check if we have valid coordinates
const hasUserCoords =
userCoords && userCoords.longitude !== null && userCoords.latitude !== null;
const list = useShallowMemo(() => {
const computedList = alertingList.map((row) => {
const { oneAlert } = row;
const { coordinates: alertCoords } = oneAlert.location;
const [longitude, latitude] = alertCoords;
let distance;
if (longitude && latitude && hasUserCoords) {
distance = getDistance(
{ longitude, latitude },
{
longitude: userCoords.longitude,
latitude: userCoords.latitude,
},
);
}
return { ...row, alert: { ...oneAlert, distance } };
});
return computedList;
}, [alertingList, userCoords, hasUserCoords]);
const featureCollection = useShallowMemo(() => {
const features = list.map((row) => {
const { alert } = row;
const { level, state } = alert;
const [longitude, latitude] = alert.location.coordinates;
const coordinates = [longitude, latitude];
const id = `alert:${alert.id}`;
const icon = state === "open" ? level : `${level}Disabled`;
return {
type: "Feature",
id,
properties: {
id,
level,
icon,
alert,
coordinates,
},
geometry: {
type: "Point",
coordinates,
},
};
});
// Add initial location marker if locations are different
list.forEach((row) => {
const { alert } = row;
if (
alert.initialLocation &&
alert.location &&
!deepEqual(alert.initialLocation, alert.location)
) {
const [longitude, latitude] = alert.initialLocation.coordinates;
const coordinates = [longitude, latitude];
const id = `alert:${alert.id}:initial`;
features.push({
type: "Feature",
id,
properties: {
id,
icon: "origin",
level: alert.level,
alert,
coordinates,
isInitialLocation: true,
},
geometry: {
type: "Point",
coordinates,
},
});
}
});
// Add defibs (DAE) as separate, non-clustered features
if (showDefibsOnAlertMap && Array.isArray(corridorDefibs)) {
corridorDefibs.forEach((defib) => {
const lon = defib.longitude;
const lat = defib.latitude;
if (
lon === null ||
lat === null ||
lon === undefined ||
lat === undefined
) {
return;
}
const { status } = getDefibAvailability(
defib.horaires_std,
defib.disponible_24h,
);
const icon =
status === "open" ? "green" : status === "closed" ? "red" : "grey";
const id = `defib:${defib.id}`;
features.push({
type: "Feature",
id,
properties: {
id,
icon,
defib,
isDefib: true,
},
geometry: {
type: "Point",
coordinates: [lon, lat],
},
});
});
}
return {
type: "FeatureCollection",
features,
};
}, [list, showDefibsOnAlertMap, corridorDefibs]);
const superCluster = useShallowMemo(() => {
const cluster = new Supercluster({ radius: 40, maxZoom: 16 });
// Do not cluster defibs in v1
const clusterable = featureCollection.features.filter(
(f) => !f?.properties?.isDefib,
);
cluster.load(clusterable);
return cluster;
}, [featureCollection.features]);
// console.log({ superCluster: JSON.stringify(superCluster) });
const [shape, setShape] = useState({
type: "FeatureCollection",
features: clusterFeature,
});
useShallowEffect(() => {
// Early return if no user coordinates
if (!hasUserCoords) {
setShape({ type: "FeatureCollection", features: clusterFeature });
return;
}
const userCoordinates = [userCoords.longitude, userCoords.latitude];
const features = [...clusterFeature];
// Ensure defibs are always present even if they are not part of the clustered set
if (showDefibsOnAlertMap && Array.isArray(featureCollection.features)) {
featureCollection.features.forEach((f) => {
if (f?.properties?.isDefib) {
features.push(f);
}
});
}
// Only add route line if we have valid route data
const isRouteEnding = route?.distance !== 0 || routeCoords?.length === 0;
const hasValidAlertCoords =
Array.isArray(alertCoords) && alertCoords.length === 2;
if (isRouteEnding) {
const lineCoordinates = [userCoordinates];
// Add route coordinates if available
if (Array.isArray(routeCoords) && routeCoords.length > 0) {
lineCoordinates.push(...routeCoords);
}
// Add alert coordinates if valid
if (hasValidAlertCoords) {
lineCoordinates.push(alertCoords);
}
const lineString = {
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates: lineCoordinates,
},
};
features.push(lineString);
}
// console.log("features", JSON.stringify(features));
setShape({ type: "FeatureCollection", features });
}, [
setShape,
clusterFeature,
featureCollection.features,
showDefibsOnAlertMap,
userCoords,
hasUserCoords,
routeCoords,
alertCoords,
route?.distance,
]);
return { superCluster, shape };
}