Skip to content

Commit 49c7e9a

Browse files
Merge pull request #273 from OneBusAway/refactor/map-state-simplification
Refactor/map-state-simplification
2 parents b0a407d + 3591ad8 commit 49c7e9a

File tree

9 files changed

+268
-72
lines changed

9 files changed

+268
-72
lines changed

src/components/MapContainer.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import { MapSource } from './../config/mapSource.js';
1111
1212
let apiKey = env.PUBLIC_OBA_GOOGLE_MAPS_API_KEY;
13-
let { handleStopMarkerSelect, mapProvider = $bindable(), ...restProps } = $props();
13+
let { handleStopMarkerSelect, mapProvider = $bindable(), stop, ...restProps } = $props();
1414
1515
onMount(() => {
1616
if (PUBLIC_OBA_MAP_PROVIDER === MapSource.Google) {
@@ -24,7 +24,7 @@
2424
</script>
2525

2626
{#if mapProvider}
27-
<MapView {handleStopMarkerSelect} {mapProvider} {...restProps} />
27+
<MapView {handleStopMarkerSelect} {mapProvider} {stop} {...restProps} />
2828
{:else}
2929
<FullPageLoadingSpinner />
3030
{/if}

src/components/map/MapView.svelte

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import LocationButton from '$lib/LocationButton/LocationButton.svelte';
1111
import RouteMap from './RouteMap.svelte';
1212
13-
import { faBus } from '@fortawesome/free-solid-svg-icons';
14-
import { RouteType, routePriorities, prioritizedRouteTypeForDisplay } from '$config/routeConfig';
1513
import { isMapLoaded } from '$src/stores/mapStore';
1614
import { userLocation } from '$src/stores/userLocationStore';
1715
/**
@@ -21,16 +19,18 @@
2119
* @property {boolean} [showRoute]
2220
* @property {boolean} [showRouteMap]
2321
* @property {any} [mapProvider]
22+
* @property {any} [stop] - Currently selected stop to preserve visual context
2423
*/
2524
2625
/** @type {Props} */
2726
let {
2827
handleStopMarkerSelect,
2928
selectedTrip = null,
3029
selectedRoute = null,
31-
showRoute = false,
30+
isRouteSelected = false,
3231
showRouteMap = false,
33-
mapProvider = null
32+
mapProvider = null,
33+
stop = null
3434
} = $props();
3535
3636
let isTripPlanModeActive = $state(false);
@@ -41,6 +41,45 @@
4141
let allStopsMap = new Map();
4242
let stopsCache = new Map();
4343
44+
const Modes = {
45+
NORMAL: 'normal',
46+
TRIP_PLAN: 'tripPlan',
47+
ROUTE: 'route'
48+
};
49+
50+
let mapMode = $state(Modes.NORMAL);
51+
let modeChangeTimeout = null;
52+
53+
$effect(() => {
54+
let newMode;
55+
if (isTripPlanModeActive) {
56+
newMode = Modes.TRIP_PLAN;
57+
} else if (selectedRoute || isRouteSelected || showRouteMap || selectedTrip) {
58+
newMode = Modes.ROUTE;
59+
} else {
60+
newMode = Modes.NORMAL;
61+
}
62+
if (modeChangeTimeout) {
63+
clearTimeout(modeChangeTimeout);
64+
}
65+
if (mapMode === Modes.ROUTE && newMode === Modes.NORMAL) {
66+
modeChangeTimeout = setTimeout(() => {
67+
mapMode = newMode;
68+
}, 100);
69+
} else if (mapMode !== newMode) {
70+
mapMode = newMode;
71+
}
72+
});
73+
74+
$effect(() => {
75+
if (!mapInstance) return;
76+
if (mapMode === Modes.NORMAL) {
77+
batchAddMarkers(allStops);
78+
} else {
79+
clearAllMarkers();
80+
}
81+
});
82+
4483
function cacheKey(zoomLevel, boundingBox) {
4584
const multiplier = 100; // 2 decimal places
4685
const north = Math.round(boundingBox.north * multiplier);
@@ -103,13 +142,12 @@
103142
await loadStopsAndAddMarkers(initialLat, initialLng, true);
104143
105144
const debouncedLoadMarkers = debounce(async () => {
106-
const center = mapInstance.getCenter();
107-
const zoomLevel = mapInstance.map.getZoom();
108-
109-
// Prevent fetching stops in the background when a route is selected or trip plan mode is active, we only fetch stops when we are see other stops
110-
if (selectedRoute || showRoute || isTripPlanModeActive) {
145+
if (mapMode !== Modes.NORMAL) {
111146
return;
112147
}
148+
149+
const center = mapInstance.getCenter();
150+
const zoomLevel = mapInstance.map.getZoom();
113151
await loadStopsAndAddMarkers(center.lat, center.lng, false, zoomLevel);
114152
}, 300);
115153
@@ -148,17 +186,13 @@
148186
}
149187
}
150188
151-
function updateMarkers() {
152-
if (!selectedRoute && !isTripPlanModeActive) {
153-
batchAddMarkers(allStops);
154-
}
155-
}
156-
157189
// Batch operation to add multiple markers efficiently
158190
function batchAddMarkers(stops) {
159191
const stopsToAdd = stops.filter((s) => !mapInstance.hasMarker(s.id));
160192
161-
if (stopsToAdd.length === 0) return;
193+
if (stopsToAdd.length === 0) {
194+
return;
195+
}
162196
163197
// Group DOM operations to minimize reflows/repaints
164198
requestAnimationFrame(() => {
@@ -172,32 +206,17 @@
172206
return;
173207
}
174208
175-
// Delegate marker existence check to provider
176209
if (mapInstance.hasMarker(s.id)) {
177210
return;
178211
}
179212
180-
let icon = faBus;
181-
182-
if (s.routes && s.routes.length > 0) {
183-
const routeTypes = s.routes.map((r) => r.type);
184-
let prioritizedType = RouteType.UNKNOWN;
185-
186-
// Optimized priority lookup - check highest priority first
187-
for (const priority of routePriorities) {
188-
if (routeTypes.includes(priority)) {
189-
prioritizedType = priority;
190-
break;
191-
}
192-
}
193-
194-
icon = prioritizedRouteTypeForDisplay(prioritizedType);
195-
}
213+
// Check if this marker should be highlighted (if it's the currently selected stop)
214+
const shouldHighlight = stop && s.id === stop.id;
196215
197216
const markerObj = mapInstance.addMarker({
198217
position: { lat: s.lat, lng: s.lon },
199-
icon: icon,
200218
stop: s,
219+
isHighlighted: shouldHighlight,
201220
onClick: () => {
202221
handleStopMarkerSelect(s);
203222
}
@@ -250,31 +269,22 @@
250269
if (tabSwitchHandler) window.removeEventListener('tabSwitched', tabSwitchHandler);
251270
}
252271
272+
if (modeChangeTimeout) {
273+
clearTimeout(modeChangeTimeout);
274+
}
275+
253276
clearAllMarkers();
254277
255278
allStopsMap.clear();
256279
stopsCache.clear();
257280
});
258-
$effect(() => {
259-
if (selectedRoute) {
260-
clearAllMarkers();
261-
updateMarkers();
262-
} else if (!isTripPlanModeActive) {
263-
batchAddMarkers(allStops);
264-
}
265-
});
266-
$effect(() => {
267-
if (isTripPlanModeActive) {
268-
clearAllMarkers();
269-
}
270-
});
271281
</script>
272282
273283
<div class="map-container">
274284
<div id="map" bind:this={mapElement}></div>
275285
276286
{#if selectedTrip && showRouteMap}
277-
<RouteMap mapProvider={mapInstance} tripId={selectedTrip.tripId} />
287+
<RouteMap mapProvider={mapInstance} tripId={selectedTrip.tripId} currentSelectedStop={stop} />
278288
{/if}
279289
</div>
280290

src/components/map/RouteMap.svelte

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
import { calculateMidpoint } from '$lib/mathUtils';
33
import { clearVehicleMarkersMap, fetchAndUpdateVehicles } from '$lib/vehicleUtils';
44
import { onMount, onDestroy } from 'svelte';
5-
let { mapProvider, tripId } = $props();
5+
let { mapProvider, tripId, currentSelectedStop = null } = $props();
66
let shapeId = null;
7-
let polyline;
87
let tripData = null;
98
let shapeData = null;
109
let isMounted = true;
@@ -23,17 +22,25 @@
2322
if (loadRouteDataPromise) {
2423
await loadRouteDataPromise;
2524
}
25+
2626
await Promise.all([
27-
mapProvider.removePolyline(await polyline),
27+
mapProvider.clearAllPolylines(),
2828
mapProvider.removeStopMarkers(),
2929
mapProvider.cleanupInfoWindow(),
30+
mapProvider.clearVehicleMarkers(),
3031
clearInterval(currentIntervalId),
31-
clearVehicleMarkersMap(mapProvider),
32-
mapProvider.clearVehicleMarkers()
32+
clearVehicleMarkersMap(mapProvider)
3333
]);
34+
35+
if (currentSelectedStop) {
36+
mapProvider.flyTo(currentSelectedStop.lat, currentSelectedStop.lon, 18);
37+
}
3438
});
3539
3640
async function loadRouteData() {
41+
mapProvider.clearAllPolylines();
42+
mapProvider.removeStopMarkers();
43+
3744
const tripResponse = await fetch(`/api/oba/trip-details/${tripId}`);
3845
tripData = await tripResponse.json();
3946
@@ -48,7 +55,7 @@
4855
shapeData = await shapeResponse.json();
4956
const shapePoints = shapeData?.data?.entry?.points;
5057
if (shapePoints && isMounted) {
51-
polyline = await mapProvider.createPolyline(shapePoints);
58+
await mapProvider.createPolyline(shapePoints);
5259
}
5360
}
5461

src/components/oba/TripDetailsPane.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
8181
onDestroy(() => {
8282
clearInterval(interval);
83+
interval = null;
8384
});
8485
</script>
8586

src/components/search/SearchPane.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import { removeAgencyPrefix } from '$lib/utils';
1616
1717
let {
18-
clearPolylines,
1918
handleRouteSelected,
2019
handleViewAllRoutes,
2120
handleStopMarkerSelect,
@@ -60,6 +59,10 @@
6059
}
6160
6261
async function handleRouteClick(route) {
62+
mapProvider.clearAllPolylines();
63+
mapProvider.removeStopMarkers();
64+
mapProvider.clearVehicleMarkers();
65+
clearVehicleMarkersMap(mapProvider);
6366
clearResults();
6467
try {
6568
const response = await fetch(`/api/oba/stops-for-route/${route.id}`);
@@ -108,7 +111,7 @@
108111
109112
function clearResults() {
110113
if (polylines) {
111-
clearPolylines();
114+
mapProvider.clearAllPolylines();
112115
}
113116
routes = null;
114117
stops = null;

src/components/search/__tests__/SearchPane.test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ describe('SearchPane', () => {
7575
flyTo: vi.fn(),
7676
createPolyline: vi.fn().mockReturnValue('polyline-mock'),
7777
addStopRouteMarker: vi.fn(),
78-
clearVehicleMarkers: vi.fn()
78+
clearVehicleMarkers: vi.fn(),
79+
clearAllPolylines: vi.fn(),
80+
removeStopMarkers: vi.fn(),
81+
addMarker: vi.fn()
7982
};
8083

8184
// Mock props

0 commit comments

Comments
 (0)