lmao
This commit is contained in:
parent
03445188ea
commit
524580eb25
102 changed files with 36625 additions and 1295 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useRef, useState, useMemo, useEffect } from 'react';
|
||||
import { H3HexagonLayer } from '@deck.gl/geo-layers';
|
||||
import { GeoJsonLayer, IconLayer, TextLayer, ScatterplotLayer } from '@deck.gl/layers';
|
||||
import { GeoJsonLayer, IconLayer, TextLayer } from '@deck.gl/layers';
|
||||
import type { PickingInfo } from '@deck.gl/core';
|
||||
import type {
|
||||
HexagonData,
|
||||
|
|
@ -14,9 +14,8 @@ import type {
|
|||
import { DENSITY_GRADIENT, DENSITY_GRADIENT_DARK } from '../lib/consts';
|
||||
import { emojiToTwemojiUrl, getFeatureFillColor } from '../lib/map-utils';
|
||||
import {
|
||||
TRANSPORT_MODES,
|
||||
type TransportMode,
|
||||
type TravelTimeEntries,
|
||||
type TravelTimeEntry,
|
||||
travelFieldKey,
|
||||
} from './useTravelTime';
|
||||
import { MarchingAntsExtension } from '../lib/MarchingAntsExtension';
|
||||
|
||||
|
|
@ -46,8 +45,8 @@ interface UseDeckLayersProps {
|
|||
theme: 'light' | 'dark';
|
||||
selectedPostcodeGeometry?: PostcodeGeometry | null;
|
||||
bounds?: Bounds | null;
|
||||
travelTimeEntries?: TravelTimeEntries;
|
||||
travelTimeColorRanges?: Partial<Record<TransportMode, [number, number]>>;
|
||||
travelTimeEntries?: TravelTimeEntry[];
|
||||
travelTimeColorRanges?: Map<number, [number, number]>;
|
||||
}
|
||||
|
||||
export interface PopupInfo {
|
||||
|
|
@ -58,15 +57,15 @@ export interface PopupInfo {
|
|||
id: string;
|
||||
}
|
||||
|
||||
/** Find the primary travel mode: first mode (in canonical order) with a destination and color range. */
|
||||
function getPrimaryTravelMode(
|
||||
entries: TravelTimeEntries,
|
||||
colorRanges: Partial<Record<TransportMode, [number, number]>>
|
||||
): TransportMode | null {
|
||||
for (const mode of TRANSPORT_MODES) {
|
||||
if (entries[mode]?.destination && colorRanges[mode]) return mode;
|
||||
/** Find the primary travel time entry: first entry with a slug and color range. */
|
||||
function getPrimaryTravelIndex(
|
||||
entries: TravelTimeEntry[],
|
||||
colorRanges: Map<number, [number, number]>
|
||||
): number {
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
if (entries[i].slug && colorRanges.has(i)) return i;
|
||||
}
|
||||
return null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function useDeckLayers({
|
||||
|
|
@ -85,8 +84,8 @@ export function useDeckLayers({
|
|||
theme,
|
||||
selectedPostcodeGeometry,
|
||||
bounds: viewportBounds,
|
||||
travelTimeEntries = {},
|
||||
travelTimeColorRanges = {},
|
||||
travelTimeEntries = [],
|
||||
travelTimeColorRanges = new Map(),
|
||||
}: UseDeckLayersProps) {
|
||||
const [popupInfo, setPopupInfo] = useState<PopupInfo | null>(null);
|
||||
const [hoverPosition, setHoverPosition] = useState<{ x: number; y: number } | null>(null);
|
||||
|
|
@ -105,7 +104,7 @@ export function useDeckLayers({
|
|||
const isDark = theme === 'dark';
|
||||
const densityGradient = isDark ? DENSITY_GRADIENT_DARK : DENSITY_GRADIENT;
|
||||
|
||||
// --- Refs for deck.gl accessors (avoid re-creating layers on every change) ---
|
||||
// --- Refs for deck.gl accessors ---
|
||||
const viewFeatureRef = useRef(viewFeature);
|
||||
viewFeatureRef.current = viewFeature;
|
||||
const colorRangeRef = useRef(colorRange);
|
||||
|
|
@ -128,12 +127,12 @@ export function useDeckLayers({
|
|||
const travelTimeColorRangesRef = useRef(travelTimeColorRanges);
|
||||
travelTimeColorRangesRef.current = travelTimeColorRanges;
|
||||
|
||||
const primaryTravelMode = useMemo(
|
||||
() => getPrimaryTravelMode(travelTimeEntries, travelTimeColorRanges),
|
||||
const primaryTravelIndex = useMemo(
|
||||
() => getPrimaryTravelIndex(travelTimeEntries, travelTimeColorRanges),
|
||||
[travelTimeEntries, travelTimeColorRanges]
|
||||
);
|
||||
const primaryTravelModeRef = useRef(primaryTravelMode);
|
||||
primaryTravelModeRef.current = primaryTravelMode;
|
||||
const primaryTravelIndexRef = useRef(primaryTravelIndex);
|
||||
primaryTravelIndexRef.current = primaryTravelIndex;
|
||||
|
||||
const colorFeatureMeta = useMemo(
|
||||
() => (viewFeature ? features.find((f) => f.name === viewFeature) || null : null),
|
||||
|
|
@ -260,13 +259,12 @@ export function useDeckLayers({
|
|||
}, []);
|
||||
|
||||
// --- Color triggers ---
|
||||
// Build travel time trigger from all entries
|
||||
const ttTrigger = useMemo(() => {
|
||||
const parts: string[] = [];
|
||||
for (const mode of TRANSPORT_MODES) {
|
||||
const entry = travelTimeEntries[mode];
|
||||
const cr = travelTimeColorRanges[mode];
|
||||
parts.push(`${mode}:${entry?.destination?.[0]}|${entry?.destination?.[1]}|${cr?.[0]}|${cr?.[1]}|${entry?.timeRange?.[0]}|${entry?.timeRange?.[1]}`);
|
||||
for (let i = 0; i < travelTimeEntries.length; i++) {
|
||||
const entry = travelTimeEntries[i];
|
||||
const cr = travelTimeColorRanges.get(i);
|
||||
parts.push(`${i}:${entry.slug}|${cr?.[0]}|${cr?.[1]}|${entry.timeRange?.[0]}|${entry.timeRange?.[1]}`);
|
||||
}
|
||||
return parts.join(';');
|
||||
}, [travelTimeEntries, travelTimeColorRanges]);
|
||||
|
|
@ -283,23 +281,26 @@ export function useDeckLayers({
|
|||
getHexagon: (d) => d.h3,
|
||||
getFillColor: (d) => {
|
||||
const dark = isDarkRef.current;
|
||||
const pm = primaryTravelModeRef.current;
|
||||
const pti = primaryTravelIndexRef.current;
|
||||
const entries = travelTimeEntriesRef.current;
|
||||
const colorRanges = travelTimeColorRangesRef.current;
|
||||
|
||||
// Travel time coloring: primary mode colors, others dim-filter
|
||||
if (pm) {
|
||||
const ttVal = d[`travel_time_${pm}`];
|
||||
const ttClr = colorRanges[pm];
|
||||
// Travel time coloring: primary entry colors, others dim-filter
|
||||
if (pti >= 0) {
|
||||
const primaryEntry = entries[pti];
|
||||
const fieldKey = travelFieldKey(primaryEntry);
|
||||
const ttVal = d[`avg_${fieldKey}`];
|
||||
const ttClr = colorRanges.get(pti);
|
||||
if (ttVal == null) {
|
||||
return (dark ? [80, 70, 65, 80] : [128, 128, 128, 80]) as [number, number, number, number];
|
||||
}
|
||||
|
||||
// Check all modes with time ranges as filters (including primary)
|
||||
for (const mode of TRANSPORT_MODES) {
|
||||
const entry = entries[mode];
|
||||
if (!entry?.timeRange) continue;
|
||||
const modeVal = d[`travel_time_${mode}`];
|
||||
// Check all entries with time ranges as filters
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
if (!entry.timeRange || !entry.slug) continue;
|
||||
const fk = travelFieldKey(entry);
|
||||
const modeVal = d[`avg_${fk}`];
|
||||
if (modeVal == null || (modeVal as number) < entry.timeRange[0] || (modeVal as number) > entry.timeRange[1]) {
|
||||
return (dark ? [60, 55, 50, 60] : [180, 180, 180, 60]) as [number, number, number, number];
|
||||
}
|
||||
|
|
@ -504,7 +505,7 @@ export function useDeckLayers({
|
|||
[pois, stablePoiHover]
|
||||
);
|
||||
|
||||
// Marching ants highlight layer for selected postcode (click or search)
|
||||
// Marching ants highlight layer for selected postcode
|
||||
const marchingAntsLayer = useMemo(() => {
|
||||
if (!selectedPostcodeGeometry) return null;
|
||||
return new GeoJsonLayer({
|
||||
|
|
@ -527,42 +528,12 @@ export function useDeckLayers({
|
|||
});
|
||||
}, [selectedPostcodeGeometry, marchTime]);
|
||||
|
||||
// Destination markers: one red dot per mode with a destination
|
||||
const destinationMarkerData = useMemo(() => {
|
||||
const points: { position: [number, number] }[] = [];
|
||||
for (const mode of TRANSPORT_MODES) {
|
||||
const entry = travelTimeEntries[mode];
|
||||
if (entry?.destination) {
|
||||
points.push({ position: [entry.destination[1], entry.destination[0]] });
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}, [travelTimeEntries]);
|
||||
|
||||
const destinationMarkerLayer = useMemo(() => {
|
||||
if (destinationMarkerData.length === 0) return null;
|
||||
return new ScatterplotLayer({
|
||||
id: 'travel-time-destinations',
|
||||
data: destinationMarkerData,
|
||||
getPosition: (d: { position: [number, number] }) => d.position,
|
||||
getRadius: 8,
|
||||
getFillColor: [239, 68, 68, 220],
|
||||
getLineColor: [255, 255, 255, 255],
|
||||
getLineWidth: 2,
|
||||
lineWidthUnits: 'pixels' as const,
|
||||
radiusUnits: 'pixels' as const,
|
||||
stroked: true,
|
||||
pickable: false,
|
||||
});
|
||||
}, [destinationMarkerData]);
|
||||
|
||||
const layers = useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const baseLayers: any[] = usePostcodeView
|
||||
? [postcodeLayer, postcodeLabelsLayer, poiLayer]
|
||||
: [hexLayer, poiLayer];
|
||||
if (marchingAntsLayer) baseLayers.push(marchingAntsLayer);
|
||||
if (destinationMarkerLayer) baseLayers.push(destinationMarkerLayer);
|
||||
return baseLayers;
|
||||
}, [
|
||||
usePostcodeView,
|
||||
|
|
@ -571,7 +542,6 @@ export function useDeckLayers({
|
|||
postcodeLabelsLayer,
|
||||
poiLayer,
|
||||
marchingAntsLayer,
|
||||
destinationMarkerLayer,
|
||||
]);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
|
|
@ -590,6 +560,6 @@ export function useDeckLayers({
|
|||
colorFeatureMeta,
|
||||
handleMouseLeave,
|
||||
hoveredPostcode,
|
||||
primaryTravelMode,
|
||||
primaryTravelIndex,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue