refactor: unify map view logic to fix centering and data bugs
This commit is contained in:
@@ -38,7 +38,7 @@ const parseSlider = (end, duration, slider) => {
|
|||||||
|
|
||||||
//async function sha256(source) {
|
//async function sha256(source) {
|
||||||
// const sourceBytes = new TextEncoder().encode(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)];
|
// const resultBytes = [...new Uint8Array(digest)];
|
||||||
// return resultBytes.map(x => x.toString(16).padStart(2, '0')).join('');
|
// return resultBytes.map(x => x.toString(16).padStart(2, '0')).join('');
|
||||||
//}
|
//}
|
||||||
@@ -70,21 +70,17 @@ function useSensor(measurement, name, end, duration) {
|
|||||||
return [data, loading];
|
return [data, loading];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function MapViewManager({ coords, mapState, setMapState, loading }) {
|
||||||
function ChangeView({ center, zoom }) {
|
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
map.setView(center, zoom);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MapEvents({ onMapChange }) {
|
// Effect 1: Handle map events (pan/zoom) from the user
|
||||||
const map = useMapEvents({
|
useMapEvents({
|
||||||
moveend: () => {
|
moveend: () => {
|
||||||
const center = map.getCenter();
|
const center = map.getCenter();
|
||||||
const newZoom = map.getZoom();
|
const newZoom = map.getZoom();
|
||||||
const newCenter = [center.lat, center.lng];
|
const newCenter = [center.lat, center.lng];
|
||||||
|
|
||||||
onMapChange(prevState => {
|
setMapState(prevState => {
|
||||||
// A small tolerance for floating point comparisons
|
// A small tolerance for floating point comparisons
|
||||||
const tolerance = 1e-5;
|
const tolerance = 1e-5;
|
||||||
if (!prevState.center) {
|
if (!prevState.center) {
|
||||||
@@ -104,49 +100,29 @@ function MapEvents({ onMapChange }) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FitBounds({ coords, mapState, end, duration, loading }) {
|
// Effect 2: Handle programmatic view changes (refitting or setting from state)
|
||||||
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.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Don't run on the very first render, wait until refs are populated.
|
// Don't do anything while loading new data
|
||||||
if (prevEndRef.current && prevDurationRef.current) {
|
if (loading) return;
|
||||||
const endChanged = end.unix() !== prevEndRef.current.unix();
|
|
||||||
const durationChanged = duration.id !== prevDurationRef.current.id;
|
|
||||||
|
|
||||||
if (endChanged || durationChanged) {
|
// Case A: A refit is needed (signaled by null center)
|
||||||
refitNeeded.current = true;
|
if (mapState.center === null && coords.length > 0) {
|
||||||
}
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
const bounds = leaflet.latLngBounds(coords);
|
const bounds = leaflet.latLngBounds(coords);
|
||||||
if (bounds.isValid()) {
|
if (bounds.isValid()) {
|
||||||
map.fitBounds(bounds);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -179,7 +155,7 @@ function Map({end, duration, slider, mapState, setMapState}) {
|
|||||||
continue;
|
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)) {
|
if (typeof lat !== 'number' || typeof lon !== 'number' || !isFinite(lat) || !isFinite(lon)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -206,9 +182,7 @@ function Map({end, duration, slider, mapState, setMapState}) {
|
|||||||
coords.length ?
|
coords.length ?
|
||||||
(
|
(
|
||||||
<MapContainer center={mapState.center || [0, 0]} zoom={mapState.zoom} scrollWheelZoom={true} style={{ width: '100%', height: 'calc(100vh - 2.5rem)' }}>
|
<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} />}
|
<MapViewManager coords={coords} mapState={mapState} setMapState={setMapState} loading={loading} />
|
||||||
<MapEvents onMapChange={setMapState} />
|
|
||||||
<FitBounds coords={coords} mapState={mapState} end={end} duration={duration} loading={loading} />
|
|
||||||
<TileLayer
|
<TileLayer
|
||||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution='© <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'
|
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,
|
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(() => {
|
useEffect(() => {
|
||||||
const handler = setTimeout(() => {
|
const handler = setTimeout(() => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
Reference in New Issue
Block a user