Tonight
This commit is contained in:
parent
28323f145e
commit
94f9c0d594
76 changed files with 3238 additions and 1230 deletions
|
|
@ -17,6 +17,7 @@ import {
|
|||
isAbortError,
|
||||
} from '../lib/api';
|
||||
import { getSchoolBackendFeatureName } from '../lib/school-filter';
|
||||
import { getSpecificCrimeFeatureName } from '../lib/crime-filter';
|
||||
import { POSTCODE_ZOOM_THRESHOLD } from '../lib/consts';
|
||||
import { COLOR_RANGE_LOW_PERCENTILE, COLOR_RANGE_HIGH_PERCENTILE } from '../lib/consts';
|
||||
import { type TravelTimeEntry } from './useTravelTime';
|
||||
|
|
@ -75,19 +76,26 @@ export function useMapData({
|
|||
const dragFeatureRef = useRef<string | null>(null);
|
||||
const dragAbortRef = useRef<AbortController | null>(null);
|
||||
const activeFeatureRef = useRef<string | null>(null);
|
||||
const latestDataRequestKeyRef = useRef<string>('');
|
||||
const latestDragRequestKeyRef = useRef<string>('');
|
||||
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const prevBoundsRef = useRef<string>('');
|
||||
|
||||
const usePostcodeView = zoom >= POSTCODE_ZOOM_THRESHOLD;
|
||||
const getBackendFeatureName = useCallback(
|
||||
(name: string) =>
|
||||
getSchoolBackendFeatureName(name) ?? getSpecificCrimeFeatureName(name) ?? name,
|
||||
[]
|
||||
);
|
||||
const dataViewFeature = useMemo(
|
||||
() => (viewFeature ? (getSchoolBackendFeatureName(viewFeature) ?? viewFeature) : null),
|
||||
[viewFeature]
|
||||
() => (viewFeature ? getBackendFeatureName(viewFeature) : null),
|
||||
[getBackendFeatureName, viewFeature]
|
||||
);
|
||||
const pinnedDataViewFeature = useMemo(
|
||||
() => (pinnedFeature ? (getSchoolBackendFeatureName(pinnedFeature) ?? pinnedFeature) : null),
|
||||
[pinnedFeature]
|
||||
() => (pinnedFeature ? getBackendFeatureName(pinnedFeature) : null),
|
||||
[getBackendFeatureName, pinnedFeature]
|
||||
);
|
||||
|
||||
// Determine if the current viewFeature is an enum (for enum_dist param)
|
||||
|
|
@ -166,6 +174,14 @@ export function useMapData({
|
|||
activeFeatureRef.current = activeFeature;
|
||||
}, [activeFeature]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeFeature) return;
|
||||
latestDragRequestKeyRef.current = '';
|
||||
dragFeatureRef.current = null;
|
||||
setDragHexData(null);
|
||||
setDragPostcodeData(null);
|
||||
}, [activeFeature]);
|
||||
|
||||
// Drag prefetch: when activeFeature starts, fetch data excluding that filter.
|
||||
// For regular filters: excludes the filter from the filter string.
|
||||
// For travel time: excludes the time range from that entry's travel param segment.
|
||||
|
|
@ -178,11 +194,23 @@ export function useMapData({
|
|||
const filtersStr = buildFilterString(filters, features, activeFeature);
|
||||
const boundsStr = `${bounds.south},${bounds.west},${bounds.north},${bounds.east}`;
|
||||
const isTravelTimeDrag = activeFeature.startsWith('tt_');
|
||||
const dataActiveFeature = getSchoolBackendFeatureName(activeFeature) ?? activeFeature;
|
||||
const dataActiveFeature = getBackendFeatureName(activeFeature);
|
||||
const dragTravelParam = isTravelTimeDrag ? buildTravelParam(activeFeature) : travelParam;
|
||||
// Travel time fields are computed from the travel param, not regular feature columns.
|
||||
// Sending a tt_* name as fields would cause a 400 (unknown field). Use empty string instead.
|
||||
const fieldsParam = isTravelTimeDrag ? '' : dataActiveFeature;
|
||||
const requestKey = [
|
||||
usePostcodeView ? 'postcodes' : 'hexagons',
|
||||
activeFeature,
|
||||
resolution,
|
||||
boundsStr,
|
||||
filtersStr,
|
||||
fieldsParam,
|
||||
dragTravelParam,
|
||||
viewFeatureIsEnum && dataViewFeature ? dataViewFeature : '',
|
||||
shareCode ?? '',
|
||||
].join('|');
|
||||
latestDragRequestKeyRef.current = requestKey;
|
||||
|
||||
if (usePostcodeView) {
|
||||
const params = new URLSearchParams({ bounds: boundsStr });
|
||||
|
|
@ -195,6 +223,7 @@ export function useMapData({
|
|||
fetch(apiUrl('postcodes', params), authHeaders({ signal: dragAbortRef.current.signal }))
|
||||
.then((res) => res.json())
|
||||
.then((json: { features: PostcodeFeature[] }) => {
|
||||
if (latestDragRequestKeyRef.current !== requestKey) return;
|
||||
setDragPostcodeData(json.features);
|
||||
setDragHexData(null);
|
||||
dragFeatureRef.current = activeFeature;
|
||||
|
|
@ -214,6 +243,7 @@ export function useMapData({
|
|||
fetch(apiUrl('hexagons', params), authHeaders({ signal: dragAbortRef.current.signal }))
|
||||
.then((res) => res.json())
|
||||
.then((json: ApiResponse) => {
|
||||
if (latestDragRequestKeyRef.current !== requestKey) return;
|
||||
setDragHexData(json.features);
|
||||
setDragPostcodeData(null);
|
||||
dragFeatureRef.current = activeFeature;
|
||||
|
|
@ -226,6 +256,9 @@ export function useMapData({
|
|||
dragAbortRef.current.abort();
|
||||
dragAbortRef.current = null;
|
||||
}
|
||||
if (latestDragRequestKeyRef.current === requestKey) {
|
||||
latestDragRequestKeyRef.current = '';
|
||||
}
|
||||
};
|
||||
}, [
|
||||
activeFeature,
|
||||
|
|
@ -237,13 +270,18 @@ export function useMapData({
|
|||
travelParam,
|
||||
buildTravelParam,
|
||||
dataViewFeature,
|
||||
getBackendFeatureName,
|
||||
viewFeatureIsEnum,
|
||||
shareCode,
|
||||
]);
|
||||
|
||||
// Fetch hexagons or postcodes when bounds/filters change
|
||||
useEffect(() => {
|
||||
if (!bounds) return;
|
||||
if (!bounds) {
|
||||
latestDataRequestKeyRef.current = '';
|
||||
return;
|
||||
}
|
||||
latestDataRequestKeyRef.current = dataRequestKey;
|
||||
|
||||
if (debounceRef.current) {
|
||||
clearTimeout(debounceRef.current);
|
||||
|
|
@ -255,10 +293,9 @@ export function useMapData({
|
|||
}
|
||||
abortControllerRef.current = new AbortController();
|
||||
|
||||
const requestKey = dataRequestKey;
|
||||
setLoading(true);
|
||||
try {
|
||||
const requestKey = dataRequestKey;
|
||||
|
||||
if (usePostcodeView) {
|
||||
const params = new URLSearchParams({ bounds: boundsParam });
|
||||
if (filtersParam) params.set('filters', filtersParam);
|
||||
|
|
@ -279,6 +316,7 @@ export function useMapData({
|
|||
);
|
||||
if (res.status === 403) {
|
||||
const errBody = await res.json();
|
||||
if (requestKey !== latestDataRequestKeyRef.current) return;
|
||||
if (errBody.error === 'license_required' && errBody.free_zone) {
|
||||
setLicenseRequired(true);
|
||||
setFreeZone(errBody.free_zone);
|
||||
|
|
@ -287,8 +325,10 @@ export function useMapData({
|
|||
}
|
||||
}
|
||||
assertOk(res, 'postcodes');
|
||||
if (requestKey !== latestDataRequestKeyRef.current) return;
|
||||
setLicenseRequired(false);
|
||||
const json: { features: PostcodeFeature[] } = await res.json();
|
||||
if (requestKey !== latestDataRequestKeyRef.current) return;
|
||||
setPostcodeData(json.features);
|
||||
setRawData([]);
|
||||
setLoadedDataKey(requestKey);
|
||||
|
|
@ -315,6 +355,7 @@ export function useMapData({
|
|||
);
|
||||
if (res.status === 403) {
|
||||
const errBody = await res.json();
|
||||
if (requestKey !== latestDataRequestKeyRef.current) return;
|
||||
if (errBody.error === 'license_required' && errBody.free_zone) {
|
||||
setLicenseRequired(true);
|
||||
setFreeZone(errBody.free_zone);
|
||||
|
|
@ -323,8 +364,10 @@ export function useMapData({
|
|||
}
|
||||
}
|
||||
assertOk(res, 'hexagons');
|
||||
if (requestKey !== latestDataRequestKeyRef.current) return;
|
||||
setLicenseRequired(false);
|
||||
const json: ApiResponse = await res.json();
|
||||
if (requestKey !== latestDataRequestKeyRef.current) return;
|
||||
setRawData(json.features);
|
||||
setPostcodeData([]);
|
||||
setLoadedDataKey(requestKey);
|
||||
|
|
@ -338,7 +381,7 @@ export function useMapData({
|
|||
}
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
if (!isAbortError(err)) {
|
||||
if (requestKey === latestDataRequestKeyRef.current && !isAbortError(err)) {
|
||||
logNonAbortError('Failed to fetch data', err);
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -349,6 +392,10 @@ export function useMapData({
|
|||
if (debounceRef.current) {
|
||||
clearTimeout(debounceRef.current);
|
||||
}
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
abortControllerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [
|
||||
resolution,
|
||||
|
|
@ -366,10 +413,12 @@ export function useMapData({
|
|||
|
||||
// Use drag data when it matches the current view feature, otherwise fall back to rawData
|
||||
const data =
|
||||
(viewFeature && dragFeatureRef.current === viewFeature ? dragHexData : null) ?? rawData;
|
||||
(activeFeature && viewFeature && dragFeatureRef.current === viewFeature ? dragHexData : null) ??
|
||||
rawData;
|
||||
const effectivePostcodeData =
|
||||
(viewFeature && dragFeatureRef.current === viewFeature ? dragPostcodeData : null) ??
|
||||
postcodeData;
|
||||
(activeFeature && viewFeature && dragFeatureRef.current === viewFeature
|
||||
? dragPostcodeData
|
||||
: null) ?? postcodeData;
|
||||
|
||||
// Compute p5/p95 from committed data for the viewed feature.
|
||||
// Always uses rawData/postcodeData (not drag preview data) so the color
|
||||
|
|
@ -548,6 +597,7 @@ export function useMapData({
|
|||
|
||||
return {
|
||||
data,
|
||||
committedHexagonData: rawData,
|
||||
postcodeData: effectivePostcodeData,
|
||||
resolution,
|
||||
bounds,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue