Spaces:
Paused
Paused
File size: 4,099 Bytes
729628a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | import { useMemo, useState } from 'react';
import DeckGL from '@deck.gl/react';
import { TileLayer } from '@deck.gl/geo-layers';
import { BitmapLayer, ScatterplotLayer } from '@deck.gl/layers';
import CircularProgress from '@mui/material/CircularProgress';
import { useFilters } from '../../context/FilterContext';
import { GeoJSONFeature, SEVERITY_COLORS, SEVERITY_RGB } from '../../types';
const INITIAL_VIEW_STATE = {
longitude: -119.4,
latitude: 36.6,
zoom: 5.3,
pitch: 0,
bearing: 0,
};
interface HoverInfo {
x: number;
y: number;
feature: GeoJSONFeature;
}
interface PickInfo {
x: number;
y: number;
object?: GeoJSONFeature;
}
export default function MapCanvas() {
const { data, loading, selected, setSelected } = useFilters();
const [hover, setHover] = useState<HoverInfo | null>(null);
const features = data?.geojson.features ?? [];
const layers = useMemo(() => {
const basemap = new TileLayer({
id: 'carto-dark-basemap',
data: 'https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
minZoom: 0,
maxZoom: 19,
tileSize: 256,
renderSubLayers: (props: {
tile: { bbox: { west: number; south: number; east: number; north: number } };
data: unknown;
}) => {
const { west, south, east, north } = props.tile.bbox;
return new BitmapLayer(props, {
data: undefined,
image: props.data,
bounds: [west, south, east, north],
});
},
});
const points = new ScatterplotLayer({
id: 'ssam-conflicts',
data: features,
pickable: true,
stroked: true,
radiusUnits: 'pixels',
lineWidthUnits: 'pixels',
getPosition: (f: GeoJSONFeature) => f.geometry.coordinates,
getRadius: (f: GeoJSONFeature) => 4 + f.properties.severity_index * 10,
getFillColor: (f: GeoJSONFeature) => {
const [r, g, b] = SEVERITY_RGB[f.properties.severity_label] ?? [128, 128, 128];
return [r, g, b, 200];
},
getLineColor: (f: GeoJSONFeature) =>
selected && f.properties.id === selected.id ? [255, 255, 255, 255] : [0, 0, 0, 120],
getLineWidth: (f: GeoJSONFeature) => (selected && f.properties.id === selected.id ? 3 : 1),
onHover: (info: PickInfo) =>
setHover(info.object ? { x: info.x, y: info.y, feature: info.object } : null),
onClick: (info: PickInfo) => {
const feature = info.object;
if (!feature) return;
const [lng, lat] = feature.geometry.coordinates;
setSelected({ ...feature.properties, lng, lat });
},
updateTriggers: {
getLineColor: [selected?.id],
getLineWidth: [selected?.id],
},
});
return [basemap, points];
}, [features, selected, setSelected]);
return (
<section className="map-canvas">
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
layers={layers}
getCursor={({ isHovering, isDragging }: { isHovering: boolean; isDragging: boolean }) =>
isDragging ? 'grabbing' : isHovering ? 'pointer' : 'grab'
}
/>
{hover && (
<div className="map-tooltip" style={{ left: hover.x + 12, top: hover.y + 12 }}>
<strong>{hover.feature.properties.street_name}</strong>
<span>{hover.feature.properties.county} County</span>
<span style={{ color: SEVERITY_COLORS[hover.feature.properties.severity_label] }}>
{hover.feature.properties.severity_label} · index {hover.feature.properties.severity_index.toFixed(2)}
</span>
<span>
TTC {hover.feature.properties.min_ttc.toFixed(1)}s · PET {hover.feature.properties.min_pet.toFixed(1)}s
</span>
</div>
)}
{loading && (
<div className="map-loading">
<CircularProgress size={28} />
</div>
)}
<div className="map-attribution">
© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> · © <a href="https://carto.com/attributions">CARTO</a>
</div>
</section>
);
}
|