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

wip: created osm and googlemap providers classes, added env support #50

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"type": "module",
"dependencies": {
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@googlemaps/js-api-loader": "^1.16.8",
"leaflet": "^1.9.4",
"onebusaway-sdk": "^1.0.1"
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
<script>
/* global google */
import { browser } from '$app/environment';
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
import {
PUBLIC_OBA_GOOGLE_MAPS_API_KEY as apiKey,
PUBLIC_OBA_REGION_CENTER_LAT as initialLat,
PUBLIC_OBA_REGION_CENTER_LNG as initialLng
PUBLIC_OBA_REGION_CENTER_LNG as initialLng,
PUBLIC_OBA_MAP_PROVIDER as mapProvider
} from '$env/static/public';

import { debounce } from '$lib/utils';
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 MapTypeButton from '$lib/MapTypeButton/MapTypeButton.svelte';
import { faBus } from '@fortawesome/free-solid-svg-icons';
import {
RouteType,
routePriorities,
prioritizedRouteTypeForDisplay
} from '../../config/routeConfig';
import { RouteType, routePriorities, prioritizedRouteTypeForDisplay } from '$config/routeConfig';

import GoogleMapProvider from '$lib/Provider/GoogleMapProvider';
import OpenStreetMapProvider from '$lib/Provider/OpenStreetMapProvider';

export let selectedTrip = null;
export let selectedRoute = null;
Expand All @@ -33,13 +31,25 @@

const dispatch = createEventDispatcher();

let map = null;
let mapInstance = null;
let mapTypeId = 'roadmap';
let mapElement;

let markers = [];
let allStops = [];
let routeReference = [];

const createMapProvider = () => {
switch (mapProvider) {
case 'google':
return new GoogleMapProvider(apiKey);
case 'osm':
return new OpenStreetMapProvider();
default:
throw new Error(`Unsupported map provider: ${mapProvider}`);
}
};

async function loadStopsForLocation(lat, lng) {
const response = await fetch(`/api/oba/stops-for-location?lat=${lat}&lng=${lng}`);
if (!response.ok) {
Expand All @@ -49,21 +59,29 @@
}

async function initMap() {
const element = document.getElementById('map');
map = await createMap({ element, lat: initialLat, lng: initialLng });
mapInstance = createMapProvider();

await loadStopsAndAddMarkers(initialLat, initialLng);
try {
await mapInstance.initMap(mapElement, {
lat: Number(initialLat),
lng: Number(initialLng)
});

const debouncedLoadMarkers = debounce(async () => {
const center = map.getCenter();
await loadStopsAndAddMarkers(center.lat(), center.lng());
}, 300);
await loadStopsAndAddMarkers(initialLat, initialLng);

map.addListener('dragend', debouncedLoadMarkers);
map.addListener('zoom_changed', debouncedLoadMarkers);
const debouncedLoadMarkers = debounce(async () => {
const center = mapInstance.getCenter();
await loadStopsAndAddMarkers(center.lat, center.lng);
}, 300);

if (browser) {
window.addEventListener('themeChange', handleThemeChange);
mapInstance.addListener('dragend', debouncedLoadMarkers);
mapInstance.addListener('zoom_changed', debouncedLoadMarkers);

if (browser) {
window.addEventListener('themeChange', handleThemeChange);
}
} catch (error) {
console.error('Error initializing map:', error);
}
}

Expand All @@ -86,7 +104,7 @@

function clearAllMarkers() {
markers.forEach(({ marker, overlay, element }) => {
marker?.setMap(null);
mapInstance.removeMarker(marker);

if (overlay) {
overlay.setMap(null);
Expand All @@ -98,12 +116,12 @@
markers = [];
}

$: if (stop && map) {
$: if (stop && mapInstance) {
// TODO: make sure that these markers are deduped. i.e. we shouldn't
// show the same stop twice on the map
if (stop.id != selectedStopID) {
addMarker(stop);
map.setCenter({ lat: stop.lat, lng: stop.lon });
mapInstance.setCenter({ lat: stop.lat, lng: stop.lon });
}
}

Expand Down Expand Up @@ -137,6 +155,7 @@
icon = prioritizedRouteTypeForDisplay(prioritizedType);
}

// TODO: move this into GoogleMapProvider
new StopMarker({
target: container,
props: {
Expand All @@ -149,75 +168,32 @@
}
});

const marker = new window.google.maps.Marker({
map: map,
const marker = mapInstance.addMarker({
position: { lat: s.lat, lng: s.lon },
icon: {
url:
'data:image/svg+xml;charset=UTF-8,' +
encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>'),
anchor: new google.maps.Point(0, 0),
scaledSize: new google.maps.Size(1, 1)
},
label: {
text: ' ',
fontSize: '0px'
},
optimized: false
icon: icon,
element: container
});
const overlay = new google.maps.OverlayView();
overlay.setMap(map);
overlay.draw = function () {
const projection = this.getProjection();
const position = projection.fromLatLngToDivPixel(marker.getPosition());
container.style.left = position.x - 20 + 'px';
container.style.top = position.y - 20 + 'px';
container.style.position = 'absolute';
this.getPanes().overlayMouseTarget.appendChild(container);
};
overlay.onRemove = function () {
container?.parentNode?.removeChild(container);
};
markers.push({ s, marker, overlay, element: container });

markers.push({ s, marker, element: container });
}

function handleThemeChange(event) {
const { darkMode } = event.detail;
const styles = darkMode ? nightModeStyles() : null;
map.setOptions({ styles });
mapInstance.setTheme(darkMode ? 'dark' : 'light');
}

function handleLocationObtained(event) {
const { latitude, longitude } = event.detail;
const userLocation = new google.maps.LatLng(latitude, longitude);
map.setCenter(userLocation);

new google.maps.Marker({
map: map,
position: userLocation,
title: 'Your Location',
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 8,
fillColor: '#007BFF',
fillOpacity: 1,
strokeWeight: 2,
strokeColor: '#FFFFFF'
}
});
mapInstance.setCenter({ lat: latitude, lng: longitude });
mapInstance.addUserLocationMarker({ lat: latitude, lng: longitude });
}

function handleMapTypeChange(event) {
mapTypeId = event.detail;
if (map) {
map.setMapTypeId(mapTypeId);
}
mapInstance.setMapType(mapTypeId);
}

onMount(async () => {
if (!window.google) {
loadGoogleMapsLibrary(apiKey);
}
await initMap();
if (browser) {
const darkMode = document.documentElement.classList.contains('dark');
Expand All @@ -230,26 +206,33 @@
if (browser) {
window.removeEventListener('themeChange', handleThemeChange);
}
markers.forEach(({ marker, overlay, element }) => {
marker.setMap(null);
overlay.setMap(null);
markers.forEach(({ marker, element }) => {
mapInstance.removeMarker(marker);
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
});
});
</script>

<div id="map"></div>
<div class="map-container">
<div id="map" bind:this={mapElement}></div>

{#if selectedTrip && showRouteMap}
<RouteMap {map} tripId={selectedTrip.tripId} />
{/if}
{#if selectedTrip && showRouteMap}
<RouteMap map={mapInstance} tripId={selectedTrip.tripId} />
{/if}

<LocationButton on:locationObtained={handleLocationObtained} />
<MapTypeButton {mapTypeId} on:mapTypeChanged={handleMapTypeChange} />
<LocationButton on:locationObtained={handleLocationObtained} />
<MapTypeButton {mapTypeId} on:mapTypeChanged={handleMapTypeChange} />
</div>

<style>
.map-container {
position: relative;
height: 100%;
width: 100%;
z-index: 1;
}
#map {
height: 100vh;
width: 100%;
Expand Down
Loading
Loading