Skip to content

Commit 8e8eed1

Browse files
feat: tariff zones in map (#190)
* feat: tariff zones in map * refactor: use constant Co-authored-by: Adrian Santana Berg <[email protected]> --------- Co-authored-by: Adrian Santana Berg <[email protected]>
1 parent f7ae304 commit 8e8eed1

File tree

6 files changed

+1448
-2
lines changed

6 files changed

+1448
-2
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@mapbox/polyline": "^1.2.1",
3030
"@react-aria/focus": "^3.15.0",
3131
"@react-hook/resize-observer": "^1.2.6",
32+
"@turf/turf": "5.1.6",
3233
"bunyan": "^1.8.15",
3334
"compare-versions": "^6.1.0",
3435
"cookies-next": "^4.1.0",

src/components/map/map.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useMapPin } from './use-map-pin';
1414
import { useMapLegs } from './use-map-legs';
1515
import { and } from '@atb/utils/css';
1616
import { MapLegType, Position } from './types';
17+
import { useMapTariffZones } from './use-map-tariff-zones';
1718

1819
export type MapProps = {
1920
position?: Position;
@@ -59,6 +60,7 @@ export function Map({
5960
);
6061
useMapPin(map, position, layer);
6162
useMapLegs(map, mapLegs);
63+
useMapTariffZones(map);
6264

6365
useEffect(() => {
6466
if (map.current) {
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { useCallback, useEffect, useState } from 'react';
2+
import { Language, useTranslation } from '@atb/translations';
3+
import mapboxgl from 'mapbox-gl';
4+
import { getReferenceDataName } from '@atb/utils/reference-data';
5+
import { centroid } from '@turf/turf';
6+
import { type TariffZone, getTariffZones } from '@atb/modules/firebase';
7+
import useSWR from 'swr';
8+
9+
const TARIFF_ZONE_SOURCE_ID = 'tariff-zones';
10+
const ZONE_BOUNDARY_LAYER_ID = 'zone-boundary-layer';
11+
const ZONE_NAMES_LAYER_ID = 'zone-names-layer';
12+
13+
export const useMapTariffZones = async (
14+
mapRef: React.MutableRefObject<mapboxgl.Map | undefined>,
15+
) => {
16+
const { language } = useTranslation();
17+
const [isZonesVisible, setIsZonesVisible] = useState(false);
18+
const [isStyleLoaded, setIsStyleLoaded] = useState(false);
19+
const { data } = useSWR('tariffZones', getTariffZones, {
20+
revalidateOnFocus: false,
21+
revalidateOnReconnect: false,
22+
revalidateIfStale: false,
23+
});
24+
25+
const addTariffZonesToMap = useCallback(
26+
(map: mapboxgl.Map) => {
27+
if (!data || !isStyleLoaded) return;
28+
if (!map.getSource(TARIFF_ZONE_SOURCE_ID)) {
29+
map.addSource(
30+
TARIFF_ZONE_SOURCE_ID,
31+
createTariffZonesFeatureCollection(data, language),
32+
);
33+
}
34+
35+
if (!map.getLayer(ZONE_BOUNDARY_LAYER_ID)) {
36+
map.addLayer({
37+
id: ZONE_BOUNDARY_LAYER_ID,
38+
type: 'line',
39+
source: TARIFF_ZONE_SOURCE_ID,
40+
paint: {
41+
'line-width': 1,
42+
'line-color': '#666666',
43+
},
44+
});
45+
}
46+
47+
if (!map.getLayer(ZONE_NAMES_LAYER_ID)) {
48+
map.addLayer({
49+
id: ZONE_NAMES_LAYER_ID,
50+
type: 'symbol',
51+
source: TARIFF_ZONE_SOURCE_ID,
52+
layout: {
53+
'text-field': ['get', 'name'],
54+
'text-justify': 'auto',
55+
'text-offset': [0, 0],
56+
},
57+
paint: {
58+
'text-color': '#000000',
59+
'text-halo-color': '#FFFFFF',
60+
'text-halo-width': 2,
61+
},
62+
filter: ['==', '$type', 'Point'],
63+
});
64+
}
65+
66+
setIsZonesVisible(true);
67+
},
68+
[language, data, isStyleLoaded],
69+
);
70+
71+
useEffect(() => {
72+
if (!mapRef.current) return;
73+
74+
mapRef.current.on('style.load', () => setIsStyleLoaded(true));
75+
76+
// If the map is already loaded, call the handler manually
77+
if (mapRef.current.isStyleLoaded()) setIsStyleLoaded(true);
78+
79+
return () => {
80+
setIsStyleLoaded(false);
81+
};
82+
}, [setIsStyleLoaded, mapRef]);
83+
84+
useEffect(() => {
85+
if (isZonesVisible || !data || !mapRef.current || !isStyleLoaded) {
86+
return;
87+
}
88+
addTariffZonesToMap(mapRef.current);
89+
}, [addTariffZonesToMap, data, isZonesVisible, mapRef, isStyleLoaded]);
90+
};
91+
92+
const createTariffZonesFeatureCollection = (
93+
tariffZones: TariffZone[],
94+
language: Language,
95+
) => ({
96+
type: 'geojson' as const,
97+
data: {
98+
type: 'FeatureCollection' as const,
99+
features: tariffZones
100+
.map((tariffZone) => [
101+
{
102+
type: 'Feature' as const,
103+
properties: {},
104+
geometry: {
105+
type: 'Polygon' as const,
106+
coordinates: tariffZone.geometry.coordinates,
107+
},
108+
},
109+
{
110+
type: 'Feature' as const,
111+
properties: {
112+
name: getReferenceDataName(tariffZone, language),
113+
middlePoint: centroid(tariffZone.geometry),
114+
},
115+
geometry: {
116+
type: 'Point' as const,
117+
coordinates:
118+
centroid(tariffZone.geometry)?.geometry?.coordinates ?? [],
119+
},
120+
},
121+
])
122+
.flat(),
123+
},
124+
});

src/modules/firebase/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export { init, logSpecificEvent } from './analytics';
2+
export { getTariffZones } from './tariff-zones';
3+
export * from './types';

src/utils/reference-data.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Language, getTextForLanguage } from '@atb/translations';
2+
import { LanguageAndTextType } from '@atb/translations/types';
3+
4+
/**
5+
* Wrapper for getting the name of a NeTeX entity in the given language.
6+
*
7+
* The name should always be present, however we fall back to "Unknown" so we
8+
* don't get any unexpected errors in the code. If we actually end up getting
9+
* "Unknown" somewhere in the app it should be fixed by updating the reference
10+
* data source.
11+
*/
12+
export const getReferenceDataName = <
13+
T extends {
14+
name: LanguageAndTextType;
15+
alternativeNames?: LanguageAndTextType[];
16+
},
17+
>(
18+
{ name, alternativeNames }: T,
19+
language: Language,
20+
): string =>
21+
getTextForLanguage([name, ...(alternativeNames || [])], language) ||
22+
'Unknown';

0 commit comments

Comments
 (0)