Changes
This commit is contained in:
parent
3a3f899ea2
commit
128b3191e7
68 changed files with 28060 additions and 1152 deletions
|
|
@ -8,9 +8,10 @@ import type {
|
|||
ViewChangeParams,
|
||||
ApiResponse,
|
||||
} from '../types';
|
||||
import { buildFilterString, apiUrl, logNonAbortError, authHeaders } from '../lib/api';
|
||||
import { buildFilterString, apiUrl, assertOk, logNonAbortError, authHeaders } from '../lib/api';
|
||||
import { POSTCODE_ZOOM_THRESHOLD } from '../lib/consts';
|
||||
import { COLOR_RANGE_LOW_PERCENTILE, COLOR_RANGE_HIGH_PERCENTILE } from '../lib/consts';
|
||||
import { TRANSPORT_MODES, type TransportMode, type TravelTimeEntries } from './useTravelTime';
|
||||
|
||||
/** Return the p-th percentile (0–100) from a sorted array via linear interpolation. */
|
||||
function percentile(sorted: number[], p: number): number {
|
||||
|
|
@ -32,9 +33,7 @@ interface UseMapDataOptions {
|
|||
activeFeature: string | null;
|
||||
dragValue: [number, number] | null;
|
||||
dragData: HexagonData[] | null;
|
||||
travelTimeEnabled: boolean;
|
||||
travelTimeDestination: [number, number] | null;
|
||||
travelTimeMode: string;
|
||||
travelTimeEntries: TravelTimeEntries;
|
||||
}
|
||||
|
||||
export function useMapData({
|
||||
|
|
@ -44,9 +43,7 @@ export function useMapData({
|
|||
activeFeature,
|
||||
dragValue,
|
||||
dragData,
|
||||
travelTimeEnabled,
|
||||
travelTimeDestination,
|
||||
travelTimeMode,
|
||||
travelTimeEntries,
|
||||
}: UseMapDataOptions) {
|
||||
const [rawData, setRawData] = useState<HexagonData[]>([]);
|
||||
const [postcodeData, setPostcodeData] = useState<PostcodeFeature[]>([]);
|
||||
|
|
@ -71,6 +68,18 @@ export function useMapData({
|
|||
[filters, features]
|
||||
);
|
||||
|
||||
// Build the travel param string from entries with destinations
|
||||
const travelParam = useMemo((): string => {
|
||||
const segments: string[] = [];
|
||||
for (const mode of TRANSPORT_MODES) {
|
||||
const entry = travelTimeEntries[mode];
|
||||
if (entry?.destination) {
|
||||
segments.push(`${entry.destination[0]},${entry.destination[1]},${mode}`);
|
||||
}
|
||||
}
|
||||
return segments.join('|');
|
||||
}, [travelTimeEntries]);
|
||||
|
||||
// Fetch hexagons or postcodes when bounds/filters change
|
||||
useEffect(() => {
|
||||
if (!bounds) return;
|
||||
|
|
@ -100,6 +109,7 @@ export function useMapData({
|
|||
signal: abortControllerRef.current.signal,
|
||||
})
|
||||
);
|
||||
assertOk(res, 'postcodes');
|
||||
const json: { features: PostcodeFeature[] } = await res.json();
|
||||
setPostcodeData(json.features);
|
||||
setRawData([]);
|
||||
|
|
@ -110,9 +120,8 @@ export function useMapData({
|
|||
});
|
||||
if (filtersStr) params.set('filters', filtersStr);
|
||||
params.set('fields', viewFeature || '');
|
||||
if (travelTimeEnabled && travelTimeDestination) {
|
||||
params.set('destination', `${travelTimeDestination[0]},${travelTimeDestination[1]}`);
|
||||
params.set('mode', travelTimeMode);
|
||||
if (travelParam) {
|
||||
params.set('travel', travelParam);
|
||||
}
|
||||
const res = await fetch(
|
||||
apiUrl('hexagons', params),
|
||||
|
|
@ -120,6 +129,7 @@ export function useMapData({
|
|||
signal: abortControllerRef.current.signal,
|
||||
})
|
||||
);
|
||||
assertOk(res, 'hexagons');
|
||||
const json: ApiResponse = await res.json();
|
||||
setRawData(json.features);
|
||||
setPostcodeData([]);
|
||||
|
|
@ -136,7 +146,7 @@ export function useMapData({
|
|||
clearTimeout(debounceRef.current);
|
||||
}
|
||||
};
|
||||
}, [resolution, bounds, filters, buildFilterParam, viewFeature, usePostcodeView, travelTimeEnabled, travelTimeDestination, travelTimeMode]);
|
||||
}, [resolution, bounds, filters, buildFilterParam, viewFeature, usePostcodeView, travelParam]);
|
||||
|
||||
const data = dragData ?? rawData;
|
||||
|
||||
|
|
@ -159,7 +169,7 @@ export function useMapData({
|
|||
if (lat < bounds.south || lat > bounds.north || lng < bounds.west || lng > bounds.east)
|
||||
continue;
|
||||
}
|
||||
const val = feat.properties[`avg_${viewFeature}`] ?? feat.properties[`min_${viewFeature}`];
|
||||
const val = feat.properties[`avg_${viewFeature}`];
|
||||
if (typeof val === 'number' && !isNaN(val)) vals.push(val);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -170,7 +180,7 @@ export function useMapData({
|
|||
if (lat < bounds.south || lat > bounds.north || lon < bounds.west || lon > bounds.east)
|
||||
continue;
|
||||
}
|
||||
const val = item[`avg_${viewFeature}`] ?? item[`min_${viewFeature}`];
|
||||
const val = item[`avg_${viewFeature}`];
|
||||
if (typeof val === 'number' && !isNaN(val)) vals.push(val);
|
||||
}
|
||||
}
|
||||
|
|
@ -197,26 +207,32 @@ export function useMapData({
|
|||
return null;
|
||||
}, [viewFeature, features, dataRange, activeFeature, dragValue]);
|
||||
|
||||
// Color range for travel time (computed from response data)
|
||||
const travelTimeColorRange = useMemo((): [number, number] | null => {
|
||||
if (!travelTimeEnabled || !travelTimeDestination) return null;
|
||||
const vals: number[] = [];
|
||||
for (const item of data) {
|
||||
if (bounds) {
|
||||
const { lat, lon } = item;
|
||||
if (lat < bounds.south || lat > bounds.north || lon < bounds.west || lon > bounds.east)
|
||||
continue;
|
||||
// Color ranges for travel time per mode (computed from response data)
|
||||
const travelTimeColorRanges = useMemo((): Partial<Record<TransportMode, [number, number]>> => {
|
||||
const ranges: Partial<Record<TransportMode, [number, number]>> = {};
|
||||
for (const mode of TRANSPORT_MODES) {
|
||||
const entry = travelTimeEntries[mode];
|
||||
if (!entry?.destination) continue;
|
||||
const fieldName = `travel_time_${mode}`;
|
||||
const vals: number[] = [];
|
||||
for (const item of data) {
|
||||
if (bounds) {
|
||||
const { lat, lon } = item;
|
||||
if (lat < bounds.south || lat > bounds.north || lon < bounds.west || lon > bounds.east)
|
||||
continue;
|
||||
}
|
||||
const val = item[fieldName];
|
||||
if (typeof val === 'number' && !isNaN(val)) vals.push(val);
|
||||
}
|
||||
const val = item.travel_time;
|
||||
if (typeof val === 'number' && !isNaN(val)) vals.push(val);
|
||||
if (vals.length === 0) continue;
|
||||
vals.sort((a, b) => a - b);
|
||||
ranges[mode] = [
|
||||
percentile(vals, COLOR_RANGE_LOW_PERCENTILE),
|
||||
percentile(vals, COLOR_RANGE_HIGH_PERCENTILE),
|
||||
];
|
||||
}
|
||||
if (vals.length === 0) return null;
|
||||
vals.sort((a, b) => a - b);
|
||||
return [
|
||||
percentile(vals, COLOR_RANGE_LOW_PERCENTILE),
|
||||
percentile(vals, COLOR_RANGE_HIGH_PERCENTILE),
|
||||
];
|
||||
}, [travelTimeEnabled, travelTimeDestination, data, bounds]);
|
||||
return ranges;
|
||||
}, [travelTimeEntries, data, bounds]);
|
||||
|
||||
const handleViewChange = useCallback(
|
||||
({
|
||||
|
|
@ -257,7 +273,7 @@ export function useMapData({
|
|||
currentView,
|
||||
usePostcodeView,
|
||||
colorRange,
|
||||
travelTimeColorRange,
|
||||
travelTimeColorRanges,
|
||||
handleViewChange,
|
||||
setInitialView,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue