Skip to content

Commit c0362b7

Browse files
authored
feat: Integrate routes data into UI map components (#1332)
1 parent be75b18 commit c0362b7

File tree

18 files changed

+633
-4639
lines changed

18 files changed

+633
-4639
lines changed

web-app/public/locales/en/common.json

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,49 @@
4848
"noWarnings": "No warnings",
4949
"warnings": "warnings",
5050
"infoNotices": "info notices"
51+
},
52+
"gtfsSpec": {
53+
"routeType": {
54+
"0": {
55+
"name": "Tram, Streetcar, Light rail",
56+
"description": "Any light rail or street level system within a metropolitan area."
57+
},
58+
"1": {
59+
"name": "Subway, Metro",
60+
"description": "Any underground rail system within a metropolitan area."
61+
},
62+
"2": {
63+
"name": "Rail",
64+
"description": "Used for intercity or long-distance travel."
65+
},
66+
"3": {
67+
"name": "Bus",
68+
"description": "Used for short- and long-distance bus routes."
69+
},
70+
"4": {
71+
"name": "Ferry",
72+
"description": "Used for short- and long-distance boat service."
73+
},
74+
"5": {
75+
"name": "Cable tram",
76+
"description": "Used for street-level rail cars where the cable runs beneath the vehicle (e.g., cable car in San Francisco)."
77+
},
78+
"6": {
79+
"name": "Aerial lift, suspended cable car (e.g., gondola lift, aerial tramway)",
80+
"description": "Cable transport where cabins, cars, gondolas or open chairs are suspended by means of one or more cables."
81+
},
82+
"7": {
83+
"name": "Funicular",
84+
"description": "Any rail system designed for steep inclines."
85+
},
86+
"11": {
87+
"name": "Trolleybus",
88+
"description": "Electric buses that draw power from overhead wires using poles."
89+
},
90+
"12": {
91+
"name": "Monorail",
92+
"description": "Railway in which the track consists of a single rail or a beam."
93+
}
5194
}
52-
}
95+
}
96+
}

web-app/src/app/components/GtfsVisualizationMap.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@ import type { FeatureCollection } from 'geojson';
1818
import { Box, useTheme } from '@mui/material';
1919
import { MapElement, MapRouteElement, MapStopElement } from './MapElement';
2020
import type { MapElementType } from './MapElement';
21-
import {
22-
reversedRouteTypesMapping,
23-
} from '../constants/RouteTypes';
2421
import { MapDataPopup } from './Map/MapDataPopup';
25-
import { useTheme as themeProvider } from '../context/ThemeProvider';
2622

2723
interface LatestDatasetLite {
2824
hosted_url?: string;
@@ -34,15 +30,15 @@ export interface GtfsVisualizationMapProps {
3430
polygon: LatLngExpression[];
3531
latestDataset?: LatestDatasetLite;
3632
filteredRoutes?: string[];
37-
filteredRouteTypes?: string[];
33+
filteredRouteTypeIds?: string[];
3834
hideStops?: boolean;
3935
}
4036

4137
export const GtfsVisualizationMap = ({
4238
polygon,
4339
latestDataset,
4440
filteredRoutes = [],
45-
filteredRouteTypes = [],
41+
filteredRouteTypeIds = [],
4642
hideStops = false,
4743
}: GtfsVisualizationMapProps): JSX.Element => {
4844

@@ -68,10 +64,6 @@ export const GtfsVisualizationMap = ({
6864
> | null>(null);
6965
const mapRef = useRef<MapRef>(null);
7066

71-
const filteredRouteTypesIds = filteredRouteTypes.map(
72-
(d) => reversedRouteTypesMapping[d],
73-
);
74-
7567
// Create a map to store routeId to routeColor mapping
7668
const routeIdToColorMap: Record<string, string> = {};
7769
mapElements.forEach((el) => {
@@ -106,8 +98,8 @@ export const GtfsVisualizationMap = ({
10698
}
10799

108100
const routeTypeFilter: ExpressionSpecification | boolean =
109-
filteredRouteTypes.length > 0
110-
? ['in', ['get', 'route_type'], ['literal', filteredRouteTypesIds]]
101+
filteredRouteTypeIds.length > 0
102+
? ['in', ['get', 'route_type'], ['literal', filteredRouteTypeIds]]
111103
: true; // if no filter applied, show all
112104

113105
const handleMouseClick = (event: maplibregl.MapLayerMouseEvent): void => {

web-app/src/app/components/RouteSelector.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import {
99
Box,
1010
ListItemButton,
1111
} from '@mui/material';
12+
import { type GtfsRoute } from '../types';
1213

1314
interface RouteSelectorProps {
14-
routes: Array<{ routeId: string; routeName: string; color?: string }>;
15+
routes: GtfsRoute[];
1516
selectedRouteIds?: string[];
1617
onSelectionChange?: (selectedRoutes: string[]) => void;
1718
}
@@ -29,16 +30,19 @@ export default function RouteSelector({
2930
const searchLower = search.toLowerCase();
3031
return routes.filter(
3132
(route) =>
32-
route.routeName.toLowerCase().includes(searchLower) ||
33-
route.routeId.includes(searchLower),
33+
(route.routeName ?? '').toLowerCase().includes(searchLower) ||
34+
(route.routeId ?? '').includes(searchLower),
3435
);
3536
}, [search, routes]);
3637

3738
useEffect(() => {
3839
setSelectedRoutes([...selectedRouteIds]);
3940
}, [selectedRouteIds]);
4041

41-
const handleToggle = (routeId: string): void => {
42+
const handleToggle = (routeId: string | undefined): void => {
43+
if (routeId == undefined) {
44+
return;
45+
}
4246
setSelectedRoutes((prev) => {
4347
const newSelection = prev.includes(routeId)
4448
? prev.filter((id) => id !== routeId)
@@ -76,7 +80,7 @@ export default function RouteSelector({
7680

7781
<List dense sx={{ maxHeight: 'none', overflow: 'auto', flex: 1 }}>
7882
{filteredRoutes
79-
.sort((a, b) => a.routeId.localeCompare(b.routeId))
83+
.sort((a, b) => (a.routeId ?? '').localeCompare(b.routeId ?? ''))
8084
.map((route) => (
8185
<ListItemButton
8286
key={route.routeId}
@@ -89,7 +93,7 @@ export default function RouteSelector({
8993
<ListItemIcon sx={{ minWidth: 34 }}>
9094
<Checkbox
9195
edge='start'
92-
checked={selectedRoutes.includes(route.routeId)}
96+
checked={selectedRoutes.includes(route.routeId ?? '')}
9397
tabIndex={-1}
9498
disableRipple
9599
/>

web-app/src/app/constants/RouteTypes.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ import MeetingRoomIcon from '@mui/icons-material/MeetingRoom';
1313
import PlaceIcon from '@mui/icons-material/Place';
1414
import DirectionsSubwayIcon from '@mui/icons-material/DirectionsSubway';
1515
import type { SvgIconComponent } from '@mui/icons-material';
16+
import type { TFunction } from 'i18next';
1617

1718
export interface RouteTypeMetadata {
1819
name: string;
1920
icon: SvgIconComponent;
21+
isDefault?: boolean;
2022
}
2123

22-
export type LocationTypeMetadata = RouteTypeMetadata;
24+
export const defaultRouteType: RouteTypeMetadata = {
25+
name: '',
26+
icon: PlaceIcon,
27+
isDefault: true,
28+
};
2329

2430
export const routeTypesMapping: Record<string, RouteTypeMetadata> = {
2531
'0': { name: 'Tram', icon: TramIcon },
@@ -36,7 +42,7 @@ export const routeTypesMapping: Record<string, RouteTypeMetadata> = {
3642
};
3743

3844
// TODO: find better icons for these
39-
export const locationTypesMapping: Record<string, LocationTypeMetadata> = {
45+
export const locationTypesMapping: Record<string, RouteTypeMetadata> = {
4046
'0': { name: 'Stop', icon: DirectionsBusIcon },
4147
'1': { name: 'Station', icon: TrainIcon },
4248
'2': { name: 'Entrance/Exit', icon: MeetingRoomIcon },
@@ -46,3 +52,22 @@ export const locationTypesMapping: Record<string, LocationTypeMetadata> = {
4652
export const reversedRouteTypesMapping = Object.fromEntries(
4753
Object.entries(routeTypesMapping).map(([k, v]) => [v.name, k]),
4854
);
55+
56+
export const getRouteByTypeOrDefault = (
57+
routeType: string | undefined | null,
58+
): RouteTypeMetadata => {
59+
if (routeType == null) {
60+
return defaultRouteType;
61+
}
62+
return routeTypesMapping[routeType] ?? defaultRouteType;
63+
};
64+
65+
export const getRouteTypeTranslatedName = (
66+
routeTypeId: string,
67+
t: TFunction,
68+
): string => {
69+
const routeType = getRouteByTypeOrDefault(routeTypeId);
70+
return !(routeType.isDefault ?? false)
71+
? t(`common:gtfsSpec.routeType.${routeTypeId}.name`)
72+
: '';
73+
};

web-app/src/app/screens/Feed/components/FeedSummary.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ import CommuteIcon from '@mui/icons-material/Commute';
4747
import { Link as RouterLink } from 'react-router-dom';
4848
import TravelExploreIcon from '@mui/icons-material/TravelExplore';
4949
import { useRemoteConfig } from '../../../context/RemoteConfigProvider';
50+
import { useSelector } from 'react-redux';
51+
import {
52+
selectGtfsDatasetRoutesTotal,
53+
selectGtfsDatasetRouteTypes,
54+
} from '../../../store/selectors';
55+
import { getRouteTypeTranslatedName } from '../../../constants/RouteTypes';
5056

5157
export interface FeedSummaryProps {
5258
feed: GTFSFeedType | GTFSRTFeedType | undefined;
@@ -69,6 +75,9 @@ export default function FeedSummary({
6975
? sortedProviders
7076
: sortedProviders.slice(0, 4);
7177
const { config } = useRemoteConfig();
78+
const totalRoutes = useSelector(selectGtfsDatasetRoutesTotal);
79+
const routeTypes = useSelector(selectGtfsDatasetRouteTypes);
80+
7281
return (
7382
<ContentBox
7483
width={width}
@@ -153,13 +162,14 @@ export default function FeedSummary({
153162
</Typography>
154163
</StyledTitleContainer>
155164
<Typography variant='body1'>
156-
130
165+
{totalRoutes ?? '---'}
157166
<Tooltip title='Find Routes On Map' placement='top' sx={{ ml: 1 }}>
158167
<IconButton
159168
size='small'
160169
component={RouterLink}
161170
to='./map'
162171
color={'primary'}
172+
disabled={totalRoutes == undefined}
163173
>
164174
<TravelExploreIcon></TravelExploreIcon>
165175
</IconButton>
@@ -177,7 +187,11 @@ export default function FeedSummary({
177187
</Typography>
178188
</StyledTitleContainer>
179189
<Typography variant='body1'>
180-
Bus, Ferry, Tram
190+
{routeTypes != null && routeTypes.length > 0
191+
? routeTypes
192+
.map((routeType) => getRouteTypeTranslatedName(routeType, t))
193+
.join(', ')
194+
: '---'}
181195
<Tooltip
182196
title='View Routes Types On Map'
183197
placement='top'
@@ -188,6 +202,7 @@ export default function FeedSummary({
188202
component={RouterLink}
189203
to='./map'
190204
color={'primary'}
205+
disabled={routeTypes == undefined}
191206
>
192207
<TravelExploreIcon></TravelExploreIcon>
193208
</IconButton>

0 commit comments

Comments
 (0)