Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented the Bus Route, Draw polyline feat, and hidden unrelated stops #31

Merged
merged 1 commit into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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.');
}
}
Loading