Skip to content

Commit

Permalink
Adds Route Map overlay feature
Browse files Browse the repository at this point in the history
Fixes #24 - Trip Details List and Map Overlay
Fixes #29 - The bus's route should also be projected onto the map
Fixes #30 - Hide unrelated stops on the map while a route line is being displayed

Also, checks to see if `window.google` exists, and only loads the Google Maps library if it does not. This resolves a warning in the console.

Co-authored-by: tarunsinghofficial <[email protected]>
  • Loading branch information
tarunsinghofficial authored and aaronbrethorst committed Aug 14, 2024
1 parent a51553d commit d32218b
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 25 deletions.
59 changes: 48 additions & 11 deletions src/components/map/GoogleMap.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@
PUBLIC_OBA_REGION_CENTER_LAT as initialLat,
PUBLIC_OBA_REGION_CENTER_LNG as initialLng
} from '$env/static/public';
import { pushState } from '$app/navigation';
import { debounce } from '$lib/utils';
import { pushState } from '$app/navigation';
import { createMap, loadGoogleMapsLibrary, nightModeStyles } from '$lib/googleMaps';
import LocationButton from '$lib/LocationButton/LocationButton.svelte';
import StopMarker from './StopMarker.svelte';
import RouteMap from './RouteMap.svelte';
import { debounce } from '$lib/utils';
export let selectedTrip;
export let selectedRoute = null;
export let showRoute = false;
const dispatch = createEventDispatcher();
let map = null;
let markers = [];
let allStops = [];
async function loadStopsForLocation(lat, lng) {
const response = await fetch(`/api/oba/stops-for-location?lat=${lat}&lng=${lng}`);
Expand Down Expand Up @@ -50,19 +55,41 @@
async function loadStopsAndAddMarkers(lat, lng) {
const json = await loadStopsForLocation(lat, lng);
const stops = json.data.list;
const newStops = json.data.list;
for (const s of stops) {
if (markerExists(s)) {
continue;
}
allStops = [...new Map([...allStops, ...newStops].map((stop) => [stop.id, stop])).values()];
clearAllMarkers();
addMarker(s);
if (showRoute && selectedRoute) {
const stopsToShow = allStops.filter((s) => s.routeIds.includes(selectedRoute.id));
stopsToShow.forEach((s) => addMarker(s));
} else {
newStops.forEach((s) => addMarker(s));
}
}
function markerExists(s) {
return markers.some((marker) => marker.s.id === s.id);
function clearAllMarkers() {
markers.forEach(({ marker, overlay, element }) => {
marker?.setMap(null);
if (overlay) {
overlay.setMap(null);
overlay.draw = () => {};
overlay.onRemove?.();
}
element?.parentNode?.removeChild(element);
});
markers = [];
}
$: if (selectedRoute && showRoute) {
clearAllMarkers();
const stopsToShow = allStops.filter((s) => s.routeIds.includes(selectedRoute.id));
stopsToShow.forEach((s) => addMarker(s));
} else if (!showRoute || !selectedRoute) {
clearAllMarkers();
allStops.forEach((s) => addMarker(s));
}
function addMarker(s) {
Expand Down Expand Up @@ -107,6 +134,9 @@
container.style.position = 'absolute';
this.getPanes().overlayMouseTarget.appendChild(container);
};
overlay.onRemove = function () {
container?.parentNode?.removeChild(container);
};
markers.push({ s, marker, overlay, element: container });
}
Expand Down Expand Up @@ -137,7 +167,9 @@
}
onMount(async () => {
loadGoogleMapsLibrary(apiKey);
if (!window.google) {
loadGoogleMapsLibrary(apiKey);
}
await initMap();
if (browser) {
const darkMode = document.documentElement.classList.contains('dark');
Expand All @@ -161,6 +193,11 @@
</script>
<div id="map"></div>
{#if selectedTrip}
<RouteMap {map} tripId={selectedTrip.tripId} />
{/if}
<LocationButton on:locationObtained={handleLocationObtained} />
<style>
Expand Down
85 changes: 85 additions & 0 deletions src/components/map/RouteMap.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script>
/* global google */
import { onMount, onDestroy } from 'svelte';
import { createPolyline, loadGoogleMapsLibrary } from '$lib/googleMaps';
export let map;
export let tripId;
let shapeId = null;
let polyline;
let stopMarkers = [];
let infoWindow;
let tripData = null;
let shapeData = null;
onMount(async () => {
await loadGoogleMapsLibrary();
await loadRouteData();
});
onDestroy(() => {
polyline?.setMap(null);
stopMarkers.forEach((marker) => marker.setMap(null));
infoWindow?.close();
});
async function loadRouteData() {
const tripResponse = await fetch(`/api/oba/trip-details/${tripId}`);
tripData = await tripResponse.json();
const tripReferences = tripData?.data?.references?.trips;
const moreTripData = tripReferences?.find((t) => t.id == tripId);
shapeId = moreTripData?.shapeId;
if (shapeId) {
const shapeResponse = await fetch(`/api/oba/shape/${shapeId}`);
shapeData = await shapeResponse.json();
const shape = shapeData?.data?.entry?.points;
if (shape) {
polyline = await createPolyline(shape);
polyline.setMap(map);
}
}
const stopTimes = tripData?.data?.entry?.schedule?.stopTimes ?? [];
const stops = tripData?.data?.references?.stops ?? [];
for (const stopTime of stopTimes) {
const stop = stops.find((s) => s.id === stopTime.stopId);
if (stop) {
addStopMarker(stopTime, stop);
}
}
}
function addStopMarker(stopTime, stop) {
const marker = new google.maps.Marker({
position: { lat: stop.lat, lng: stop.lon },
map: map,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 5,
fillColor: '#FFFFFF',
fillOpacity: 1,
strokeWeight: 1,
strokeColor: '#000000'
}
});
marker.addListener('click', () => {
infoWindow?.close();
infoWindow = new google.maps.InfoWindow({
content: `<div>
<h3>${stop.name}</h3>
<p>Arrival time: ${new Date(stopTime.arrivalTime * 1000).toLocaleTimeString()}</p>
</div>`
});
infoWindow.open(map, marker);
});
stopMarkers.push(marker);
}
</script>
4 changes: 4 additions & 0 deletions src/components/oba/StopPane.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import ArrivalDeparture from '../ArrivalDeparture.svelte';
import { onMount } from 'svelte';
import TripDetailsModal from '../navigation/TripDetailsModal.svelte';
import { createEventDispatcher } from 'svelte';
export let stop;
export let arrivalsAndDeparturesResponse = null;
Expand All @@ -13,6 +14,8 @@
let selectedTripDetails = null;
let interval;
const dispatch = createEventDispatcher();
async function loadData(stopID) {
loading = true;
const response = await fetch(`/api/oba/arrivals-and-departures-for-stop/${stopID}`);
Expand Down Expand Up @@ -62,6 +65,7 @@
scheduledArrivalTime: event.detail.scheduledArrivalTime
};
showTripDetails = true;
dispatch('tripSelected', selectedTripDetails);
}
function handleCloseTripDetailModal() {
showTripDetails = false;
Expand Down
12 changes: 0 additions & 12 deletions src/components/oba/TripDetailsPane.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
let stopInfo = {};
let error = null;
let interval;
let currentStopIndex = -1;
let busPosition = 0;
function formatTime(seconds) {
Expand Down Expand Up @@ -66,17 +65,6 @@
}, {});
}
if (tripDetails.status?.closestStop) {
currentStopIndex = tripDetails.schedule.stopTimes.findIndex(
(stop) => stop.stopId === tripDetails.status.closestStop
);
} else {
currentStopIndex = -1;
}
console.log('Current Stop Index:', currentStopIndex);
console.log('Closest Stop:', tripDetails.status?.closestStop);
calculateBusPosition();
} catch (err) {
console.error('Error fetching trip details:', err);
Expand Down
16 changes: 16 additions & 0 deletions src/lib/googleMaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,19 @@ export function nightModeStyles() {
}
];
}

export async function createPolyline(shape) {
await window.google.maps.importLibrary('geometry');
const decodedPath = google.maps.geometry.encoding.decodePath(shape);
const path = decodedPath.map((point) => ({ lat: point.lat(), lng: point.lng() }));

const polyline = new window.google.maps.Polyline({
path,
geodesic: true,
strokeColor: '#00FF00',
strokeOpacity: 1.0,
strokeWeight: 4
});

return polyline;
}
19 changes: 17 additions & 2 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,35 @@
import StopPane from '../components/oba/StopPane.svelte';
let stop;
let selectedTrip = null;
let showRoute = false;
let selectedRoute = null;
function stopSelected(event) {
stop = event.detail.stop;
}
function closePane() {
stop = null;
selectedTrip = null;
selectedRoute = null;
showRoute = false;
}
function tripSelected(event) {
selectedTrip = event.detail;
showRoute = true;
selectedRoute = {
id: event.detail.routeId,
shortName: event.detail.routeShortName
};
}
</script>

{#if stop}
<ModalPane on:close={closePane}>
<StopPane {stop} />
<StopPane {stop} on:tripSelected={tripSelected} />
</ModalPane>
{/if}

<GoogleMap on:stopSelected={stopSelected} />
<GoogleMap {selectedTrip} {selectedRoute} on:stopSelected={stopSelected} {showRoute} />
24 changes: 24 additions & 0 deletions src/routes/api/oba/shape/[shapeId]/+server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { error, json } from '@sveltejs/kit';
import { PUBLIC_OBA_SERVER_URL as baseURL } from '$env/static/public';
import { PRIVATE_OBA_API_KEY as apiKey } from '$env/static/private';

export async function GET({ params }) {
const { shapeId } = params;

let apiURL = `${baseURL}/api/where/shape/${shapeId}.json?key=${apiKey}`;

try {
const response = await fetch(apiURL);
console.log('response:', response);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
return json(data);
} catch (err) {
console.error('Error fetching shape data:', err);
throw error(500, 'Unable to fetch shape data.');
}
}

0 comments on commit d32218b

Please sign in to comment.