More
This commit is contained in:
parent
128b3191e7
commit
03445188ea
54 changed files with 596953 additions and 3577 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useRef, useState, useMemo } from 'react';
|
||||
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 type { PickingInfo } from '@deck.gl/core';
|
||||
|
|
@ -18,6 +18,7 @@ import {
|
|||
type TransportMode,
|
||||
type TravelTimeEntries,
|
||||
} from './useTravelTime';
|
||||
import { MarchingAntsExtension } from '../lib/MarchingAntsExtension';
|
||||
|
||||
/** Convert POI id (e.g. "n12345") to OpenStreetMap URL */
|
||||
function osmIdToUrl(id: string): string | null {
|
||||
|
|
@ -40,7 +41,7 @@ interface UseDeckLayersProps {
|
|||
features: FeatureMeta[];
|
||||
selectedHexagonId: string | null;
|
||||
hoveredHexagonId: string | null;
|
||||
onHexagonClick: (id: string, isPostcode?: boolean) => void;
|
||||
onHexagonClick: (id: string, isPostcode?: boolean, geometry?: PostcodeGeometry) => void;
|
||||
onHexagonHover: (h3: string | null, x?: number, y?: number) => void;
|
||||
theme: 'light' | 'dark';
|
||||
selectedPostcodeGeometry?: PostcodeGeometry | null;
|
||||
|
|
@ -89,9 +90,18 @@ export function useDeckLayers({
|
|||
}: UseDeckLayersProps) {
|
||||
const [popupInfo, setPopupInfo] = useState<PopupInfo | null>(null);
|
||||
const [hoverPosition, setHoverPosition] = useState<{ x: number; y: number } | null>(null);
|
||||
const [selectedPostcode, setSelectedPostcode] = useState<string | null>(null);
|
||||
const [hoveredPostcode, setHoveredPostcode] = useState<string | null>(null);
|
||||
|
||||
// Marching ants animation
|
||||
const [marchTime, setMarchTime] = useState(0);
|
||||
const hasPostcodeGeometry = selectedPostcodeGeometry != null;
|
||||
useEffect(() => {
|
||||
if (!hasPostcodeGeometry) return;
|
||||
setMarchTime(0);
|
||||
const id = setInterval(() => setMarchTime((t) => t + 0.3), 50);
|
||||
return () => clearInterval(id);
|
||||
}, [hasPostcodeGeometry]);
|
||||
|
||||
const isDark = theme === 'dark';
|
||||
const densityGradient = isDark ? DENSITY_GRADIENT_DARK : DENSITY_GRADIENT;
|
||||
|
||||
|
|
@ -110,8 +120,6 @@ export function useDeckLayers({
|
|||
selectedHexagonIdRef.current = selectedHexagonId;
|
||||
const hoveredHexagonIdRef = useRef(hoveredHexagonId);
|
||||
hoveredHexagonIdRef.current = hoveredHexagonId;
|
||||
const selectedPostcodeRef = useRef(selectedPostcode);
|
||||
selectedPostcodeRef.current = selectedPostcode;
|
||||
const hoveredPostcodeRef = useRef(hoveredPostcode);
|
||||
hoveredPostcodeRef.current = hoveredPostcode;
|
||||
|
||||
|
|
@ -233,8 +241,7 @@ export function useDeckLayers({
|
|||
const handlePostcodeClick = useCallback((info: PickingInfo<any>) => {
|
||||
const pc = info.object?.properties?.postcode;
|
||||
if (pc) {
|
||||
setSelectedPostcode((prev) => (prev === pc ? null : pc));
|
||||
onHexagonClickRef.current(pc, true);
|
||||
onHexagonClickRef.current(pc, true, info.object?.geometry);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -265,7 +272,7 @@ export function useDeckLayers({
|
|||
}, [travelTimeEntries, travelTimeColorRanges]);
|
||||
|
||||
const colorTrigger = `${viewFeature}|${colorRange?.[0]}|${colorRange?.[1]}|${filterRange?.[0]}|${filterRange?.[1]}|${countRange.min}|${countRange.max}|${selectedHexagonId}|${hoveredHexagonId}|${theme}|${ttTrigger}`;
|
||||
const postcodeColorTrigger = `${viewFeature}|${colorRange?.[0]}|${colorRange?.[1]}|${filterRange?.[0]}|${filterRange?.[1]}|${postcodeCountRange.min}|${postcodeCountRange.max}|${selectedPostcode}|${hoveredPostcode}|${theme}|${ttTrigger}`;
|
||||
const postcodeColorTrigger = `${viewFeature}|${colorRange?.[0]}|${colorRange?.[1]}|${filterRange?.[0]}|${filterRange?.[1]}|${postcodeCountRange.min}|${postcodeCountRange.max}|${hoveredPostcode}|${theme}|${ttTrigger}`;
|
||||
|
||||
// --- Layers ---
|
||||
const hexLayer = useMemo(
|
||||
|
|
@ -423,8 +430,6 @@ export function useDeckLayers({
|
|||
getLineColor: (f) => {
|
||||
const pc = f.properties.postcode;
|
||||
const dark = isDarkRef.current;
|
||||
if (pc === selectedPostcodeRef.current)
|
||||
return [255, 255, 255, 255] as [number, number, number, number];
|
||||
if (pc === hoveredPostcodeRef.current)
|
||||
return [29, 228, 195, 200] as [number, number, number, number];
|
||||
return (dark ? [180, 170, 160, 100] : [100, 100, 100, 150]) as [
|
||||
|
|
@ -436,7 +441,6 @@ export function useDeckLayers({
|
|||
},
|
||||
getLineWidth: (f) => {
|
||||
const pc = f.properties.postcode;
|
||||
if (pc === selectedPostcodeRef.current) return 3;
|
||||
if (pc === hoveredPostcodeRef.current) return 2;
|
||||
return 1;
|
||||
},
|
||||
|
|
@ -500,37 +504,28 @@ export function useDeckLayers({
|
|||
[pois, stablePoiHover]
|
||||
);
|
||||
|
||||
// Check if the selected postcode has data (passes current filters)
|
||||
const selectedPostcodeHasData = useMemo(() => {
|
||||
if (!selectedPostcodeGeometry || !selectedHexagonId) return false;
|
||||
return postcodeData.some((f) => f.properties.postcode === selectedHexagonId);
|
||||
}, [selectedPostcodeGeometry, selectedHexagonId, postcodeData]);
|
||||
|
||||
// Highlight layer for selected postcode (from search)
|
||||
const selectedPostcodeHighlightLayer = useMemo(() => {
|
||||
// Marching ants highlight layer for selected postcode (click or search)
|
||||
const marchingAntsLayer = useMemo(() => {
|
||||
if (!selectedPostcodeGeometry) return null;
|
||||
const hasData = selectedPostcodeHasData;
|
||||
const feature = {
|
||||
type: 'Feature' as const,
|
||||
geometry: selectedPostcodeGeometry,
|
||||
properties: {},
|
||||
};
|
||||
return new GeoJsonLayer({
|
||||
id: 'searched-postcode-highlight',
|
||||
data: [feature],
|
||||
getFillColor: hasData
|
||||
? [29, 228, 195, 40] // teal tint when has data
|
||||
: [255, 180, 0, 30], // orange tint when filtered out
|
||||
getLineColor: hasData
|
||||
? [29, 228, 195, 255] // solid teal when has data
|
||||
: [255, 180, 0, 200], // orange when filtered out (no matching properties)
|
||||
getLineWidth: hasData ? 4 : 3,
|
||||
lineWidthUnits: 'pixels',
|
||||
id: 'marching-ants',
|
||||
data: [
|
||||
{
|
||||
type: 'Feature' as const,
|
||||
geometry: selectedPostcodeGeometry,
|
||||
properties: {},
|
||||
},
|
||||
],
|
||||
filled: false,
|
||||
stroked: true,
|
||||
filled: true,
|
||||
getLineColor: [29, 228, 195, 255],
|
||||
getLineWidth: 3,
|
||||
lineWidthUnits: 'pixels' as const,
|
||||
pickable: false,
|
||||
marchTime,
|
||||
extensions: [new MarchingAntsExtension()],
|
||||
});
|
||||
}, [selectedPostcodeGeometry, selectedPostcodeHasData]);
|
||||
}, [selectedPostcodeGeometry, marchTime]);
|
||||
|
||||
// Destination markers: one red dot per mode with a destination
|
||||
const destinationMarkerData = useMemo(() => {
|
||||
|
|
@ -566,7 +561,7 @@ export function useDeckLayers({
|
|||
const baseLayers: any[] = usePostcodeView
|
||||
? [postcodeLayer, postcodeLabelsLayer, poiLayer]
|
||||
: [hexLayer, poiLayer];
|
||||
if (selectedPostcodeHighlightLayer) baseLayers.push(selectedPostcodeHighlightLayer);
|
||||
if (marchingAntsLayer) baseLayers.push(marchingAntsLayer);
|
||||
if (destinationMarkerLayer) baseLayers.push(destinationMarkerLayer);
|
||||
return baseLayers;
|
||||
}, [
|
||||
|
|
@ -575,7 +570,7 @@ export function useDeckLayers({
|
|||
postcodeLayer,
|
||||
postcodeLabelsLayer,
|
||||
poiLayer,
|
||||
selectedPostcodeHighlightLayer,
|
||||
marchingAntsLayer,
|
||||
destinationMarkerLayer,
|
||||
]);
|
||||
|
||||
|
|
@ -594,7 +589,6 @@ export function useDeckLayers({
|
|||
postcodeCountRange,
|
||||
colorFeatureMeta,
|
||||
handleMouseLeave,
|
||||
selectedPostcode,
|
||||
hoveredPostcode,
|
||||
primaryTravelMode,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue