refactor: unify map view logic to fix centering and data bugs

This commit is contained in:
2025-08-14 21:18:09 +00:00
parent 87e706c223
commit 435db835e9

View File

@@ -38,7 +38,7 @@ const parseSlider = (end, duration, slider) => {
//async function sha256(source) {
// const sourceBytes = new TextEncoder().encode(source);
// const digest = await crypto.subtle.digest('SHA-256', sourceBytes);
// const digest = await crypto.subtle.digest('SHA-26', sourceBytes);
// const resultBytes = [...new Uint8Array(digest)];
// return resultBytes.map(x => x.toString(16).padStart(2, '0')).join('');
//}
@@ -70,21 +70,17 @@ function useSensor(measurement, name, end, duration) {
return [data, loading];
};
function ChangeView({ center, zoom }) {
function MapViewManager({ coords, mapState, setMapState, loading }) {
const map = useMap();
map.setView(center, zoom);
return null;
}
function MapEvents({ onMapChange }) {
const map = useMapEvents({
// Effect 1: Handle map events (pan/zoom) from the user
useMapEvents({
moveend: () => {
const center = map.getCenter();
const newZoom = map.getZoom();
const newCenter = [center.lat, center.lng];
onMapChange(prevState => {
setMapState(prevState => {
// A small tolerance for floating point comparisons
const tolerance = 1e-5;
if (!prevState.center) {
@@ -104,49 +100,29 @@ function MapEvents({ onMapChange }) {
});
},
});
return null;
}
function FitBounds({ coords, mapState, end, duration, loading }) {
const map = useMap();
const prevEndRef = useRef();
const prevDurationRef = useRef();
const refitNeeded = useRef(false);
const isInitialLoad = useRef(mapState.center === null);
// Effect 1: Flag the need for a refit when end/duration changes.
// Effect 2: Handle programmatic view changes (refitting or setting from state)
useEffect(() => {
// Don't run on the very first render, wait until refs are populated.
if (prevEndRef.current && prevDurationRef.current) {
const endChanged = end.unix() !== prevEndRef.current.unix();
const durationChanged = duration.id !== prevDurationRef.current.id;
// Don't do anything while loading new data
if (loading) return;
if (endChanged || durationChanged) {
refitNeeded.current = true;
}
}
prevEndRef.current = end;
prevDurationRef.current = duration;
}, [end, duration]);
// Effect 2: Perform the fit when conditions are right.
useEffect(() => {
// Don't do anything while new data is being fetched.
if (loading) {
return;
}
// Perform fit on initial load OR if a refit has been flagged.
if ((isInitialLoad.current || refitNeeded.current) && coords.length > 0) {
// Case A: A refit is needed (signaled by null center)
if (mapState.center === null && coords.length > 0) {
const bounds = leaflet.latLngBounds(coords);
if (bounds.isValid()) {
map.fitBounds(bounds);
// After fitting, the 'moveend' event will fire and update the state naturally.
}
// Reset flags after fitting.
refitNeeded.current = false;
isInitialLoad.current = false;
}
}, [coords, loading, map]); // Dependencies: run when data is ready.
// Case B: A center is set in the state, ensure map is synced
else if (mapState.center) {
const currentCenter = map.getCenter();
const currentZoom = map.getZoom();
if (currentCenter.lat !== mapState.center[0] || currentCenter.lng !== mapState.center[1] || currentZoom !== mapState.zoom) {
map.setView(mapState.center, mapState.zoom);
}
}
}, [coords, mapState, loading, map, setMapState]);
return null;
}
@@ -179,7 +155,7 @@ function Map({end, duration, slider, mapState, setMapState}) {
continue;
}
// 5. Guard against invalid coordinates
// 5. Guard against invalid coordinates (null, undefined, NaN, non-number)
if (typeof lat !== 'number' || typeof lon !== 'number' || !isFinite(lat) || !isFinite(lon)) {
continue;
}
@@ -206,9 +182,7 @@ function Map({end, duration, slider, mapState, setMapState}) {
coords.length ?
(
<MapContainer center={mapState.center || [0, 0]} zoom={mapState.zoom} scrollWheelZoom={true} style={{ width: '100%', height: 'calc(100vh - 2.5rem)' }}>
{mapState.center && <ChangeView center={mapState.center} zoom={mapState.zoom} />}
<MapEvents onMapChange={setMapState} />
<FitBounds coords={coords} mapState={mapState} end={end} duration={duration} loading={loading} />
<MapViewManager coords={coords} mapState={mapState} setMapState={setMapState} loading={loading} />
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url='https://maptiles.p.rapidapi.com/en/map/v1/{z}/{x}/{y}.png?rapidapi-key=4375b0b1d8msh0c9e7fa3efb9adfp1769dfjsnd603a0387fea'
@@ -418,6 +392,16 @@ function App() {
zoom: initialZoom ? parseInt(initialZoom, 10) : 13,
});
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
// Reset map center to trigger a refit when new data arrives
setMapState(prev => ({ ...prev, center: null }));
}
}, [end, duration]);
useEffect(() => {
const handler = setTimeout(() => {
const params = new URLSearchParams();