-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFunctionalAreaVisualization.tsx
More file actions
125 lines (103 loc) · 3.27 KB
/
Copy pathFunctionalAreaVisualization.tsx
File metadata and controls
125 lines (103 loc) · 3.27 KB
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
125
import noop from 'lodash/noop';
import { CanvasSource } from 'maplibre-gl';
import { FC, useEffect } from 'react';
import { MapInstance, useMap } from 'react-map-gl/maplibre';
import { useAppSelector } from '../../../../hooks';
import { selectSelectedStopAreaId } from '../../../../redux';
import { isMapStyleReady } from '../../../../utils/map';
import { MapStop } from '../../types';
import { CanvasRenderer } from './CanvasRenderer';
import { pixelSize } from './constants';
import { useVisualizationData } from './useVisualizationData';
const sourceId = 'FunctionalAreaVisualizationSource';
const layerId = 'FunctionalAreaVisualizationLayer';
function assertIsCanvasSource(source: unknown): asserts source is CanvasSource {
if (!(source instanceof CanvasSource)) {
throw new TypeError(
`Expected source to be a CanvasSource but type is: ${typeof source} | ${source?.constructor?.name}`,
);
}
}
// react-map-gl/maplibre does not expose the CanvasSource interface
// so we need to register the source and layer manually.
function useMapSourceAndLayer(map: MapInstance | null) {
useEffect(() => {
if (!map || !isMapStyleReady(map)) {
return noop();
}
const canvas = document.createElement('canvas');
canvas.width = pixelSize;
canvas.height = pixelSize;
// Initial placeholder coordinates
const [west, north, east, south] = [0, 0, 1, 1];
map.addSource(sourceId, {
type: 'canvas',
animate: false,
canvas,
coordinates: [
[west, north],
[east, north],
[east, south],
[west, south],
],
});
map.addLayer({
id: layerId,
type: 'raster',
source: sourceId,
minzoom: 14,
layout: { visibility: 'none' },
});
return () => {
// If the map has not yet de-loaded.
if (map?.getStyle()) {
map.removeLayer(layerId);
map.removeSource(sourceId);
}
};
}, [map]);
const source = map?.getSource(sourceId);
const layer = map?.getLayer(layerId);
if (!source || !layer) {
return { source: null, layer: null };
}
assertIsCanvasSource(source);
return { source, layer };
}
type FunctionalAreaVisualizationProps = {
readonly stops: ReadonlyArray<MapStop>;
};
export const FunctionalAreaVisualization: FC<
FunctionalAreaVisualizationProps
> = ({ stops }) => {
const selectedStopAreaId = useAppSelector(selectSelectedStopAreaId);
const { boundingBox, circles, converter } = useVisualizationData(
stops,
selectedStopAreaId,
);
const map = useMap().current?.getMap() ?? null;
const { source, layer } = useMapSourceAndLayer(map);
// Update the source with new data
useEffect(() => {
if (!source || !layer) {
return noop;
}
layer.setLayoutProperty('visibility', boundingBox ? 'visible' : 'none');
if (boundingBox) {
const [west, north, east, south] = boundingBox;
source.setCoordinates([
[west, north],
[east, north],
[east, south],
[west, south],
]);
}
const renderer = new CanvasRenderer(source.getCanvas());
renderer.render(converter, circles);
// Update\reread the texture from the canvas
source.play();
source.pause();
return noop;
}, [source, layer, boundingBox, circles, converter]);
return null;
};