From e0d653f05588038cb65db2b11bcde02f6e793716 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Sat, 9 Nov 2024 21:59:09 -0800 Subject: [PATCH 01/43] refactor: trip planning API to accept dynamic parameters for fromPlace and toPlace --- src/routes/api/otp/plan/+server.js | 54 ++++++++++++++++-------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/routes/api/otp/plan/+server.js b/src/routes/api/otp/plan/+server.js index 1f76039..33386fe 100644 --- a/src/routes/api/otp/plan/+server.js +++ b/src/routes/api/otp/plan/+server.js @@ -1,31 +1,35 @@ import { error, json } from '@sveltejs/kit'; -export async function GET() { - try { - const response = await fetch( - 'https://otp.prod.sound.obaweb.org/otp/routers/default/plan?fromPlace=47.5423055%2C-122.38677&toPlace=47.639376%2C-122.128238', - { - headers: { - 'Accept': 'application/json' - } - } - ); +export async function GET({ url }) { + const fromPlace = url.searchParams.get('fromPlace'); + const toPlace = url.searchParams.get('toPlace'); - if (!response.ok) { - throw error(response.status, `OpenTripPlanner API returned status ${response.status}`); - } + if (!fromPlace || !toPlace) { + throw error(400, 'Missing required parameters: fromPlace and toPlace'); + } - const data = await response.json(); - return json(data); + try { + const response = await fetch( + `https://otp.prod.sound.obaweb.org/otp/routers/default/plan?fromPlace=${encodeURIComponent(fromPlace)}&toPlace=${encodeURIComponent(toPlace)}`, + { + headers: { + Accept: 'application/json' + } + } + ); - } catch (err) { - // If it's already a SvelteKit error, rethrow it - if (err.status) throw err; + if (!response.ok) { + throw error(response.status, `OpenTripPlanner API returned status ${response.status}`); + } - // Otherwise wrap it in a 500 error - throw error(500, { - message: 'Failed to fetch trip planning data', - error: err.message - }); - } -} \ No newline at end of file + const data = await response.json(); + return json(data); + } catch (err) { + if (err.status) throw err; + + throw error(500, { + message: 'Failed to fetch trip planning data', + error: err.message + }); + } +} From db94b7188af4ebe0afb7a99cef9f5d1c454015ec Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 22:07:22 +0200 Subject: [PATCH 02/43] docs: update README to clarify Geocoder API key requirements --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index e36ce80..a9543ff 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,9 @@ See `.env.example` for an example of the required keys and values. ### Geocoding -- `PRIVATE_OBA_GEOCODER_API_KEY` - string: Your Geocoder service's API key. Leave this blank if you don't have one. +- `PRIVATE_OBA_GEOCODER_API_KEY` - string: Your Geocoder service's API key. Ensure that the Geocoder and Places API permissions are enabled. Leave this blank if you don't have one. - `PRIVATE_OBA_GEOCODER_PROVIDER` - string: Your Geocoder service. We currently only support the Google Places SDK (value: "google"). - ### Trip Planner - `PUBLIC_OTP_SERVER_URL` - string: Your OpenTripPlanner 1.x-compatible trip planner server URL. Leave this blank if you don't have one. From c71f2d66e3cdebb006c52bcf5c86134b6345599b Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 22:53:00 +0200 Subject: [PATCH 03/43] feat: add Google Places autocomplete API integration --- .../oba/google-place-autocomplete/+server.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/routes/api/oba/google-place-autocomplete/+server.js diff --git a/src/routes/api/oba/google-place-autocomplete/+server.js b/src/routes/api/oba/google-place-autocomplete/+server.js new file mode 100644 index 0000000..8400bea --- /dev/null +++ b/src/routes/api/oba/google-place-autocomplete/+server.js @@ -0,0 +1,30 @@ +import { googlePlacesAutocomplete } from '$lib/geocoder'; + +import { + PRIVATE_OBA_GEOCODER_API_KEY as geocoderApiKey, + PRIVATE_OBA_GEOCODER_PROVIDER as geocoderProvider +} from '$env/static/private'; + +async function autoCompletePlacesSearch(input) { + if (geocoderProvider === 'google') { + return googlePlacesAutocomplete({ apiKey: geocoderApiKey, input }); + } else { + return []; + } +} + +export async function GET({ url }) { + const searchInput = url.searchParams.get('query')?.trim(); + + const suggestions = await autoCompletePlacesSearch(searchInput); + return new Response( + JSON.stringify({ + suggestions + }), + { + headers: { + 'Content-Type': 'application/json' + } + } + ); +} From 6d95b767aa92acaeffe6d2ee792e02ba6b1ebd59 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 22:54:07 +0200 Subject: [PATCH 04/43] feat: implement Google Geocode location search API endpoint --- .../oba/google-geocode-location/+server.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/routes/api/oba/google-geocode-location/+server.js diff --git a/src/routes/api/oba/google-geocode-location/+server.js b/src/routes/api/oba/google-geocode-location/+server.js new file mode 100644 index 0000000..c43d41e --- /dev/null +++ b/src/routes/api/oba/google-geocode-location/+server.js @@ -0,0 +1,31 @@ +import { googleGeocode, googleGeocodeByPlaceId } from '$lib/geocoder'; + +import { + PRIVATE_OBA_GEOCODER_API_KEY as geocoderApiKey, + PRIVATE_OBA_GEOCODER_PROVIDER as geocoderProvider +} from '$env/static/private'; + +async function locationSearch(query) { + if (geocoderProvider === 'google') { + return googleGeocode({ apiKey: geocoderApiKey, query }); + } else { + return []; + } +} + +export async function GET({ url }) { + const searchInput = url.searchParams.get('query')?.trim(); + + const locationResponse = await locationSearch(searchInput); + + return new Response( + JSON.stringify({ + location: locationResponse + }), + { + headers: { + 'Content-Type': 'application/json' + } + } + ); +} From 5956f558d168101d7d38f509f7e85d522939e761 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 22:54:40 +0200 Subject: [PATCH 05/43] feat: add TripPlanModal integration for trip planning functionality --- src/routes/+page.svelte | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 18ce417..53a7bcf 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -8,6 +8,7 @@ import AlertsModal from '$components/navigation/AlertsModal.svelte'; import { onMount } from 'svelte'; import StopModal from '$components/stops/StopModal.svelte'; + import TripPlanModal from '$components/trip-planner/TripPlanModal.svelte'; let stop; let selectedTrip = null; @@ -16,6 +17,7 @@ let showRouteMap = false; let showAllStops = false; let showAllRoutesModal = false; + let showTripPlanModal = false; let showRouteModal; let mapProvider = null; let currentIntervalId = null; @@ -24,6 +26,11 @@ let polylines = []; let stops = []; + let tripItineraries = []; + let loadingItineraries = false; + let fromMarker = null; + let toMarker = null; + $: { if (showRouteModal && showAllRoutesModal) { showAllRoutesModal = false; @@ -76,6 +83,7 @@ showAllRoutesModal = false; mapProvider.unHighlightMarker(currentHighlightedStopId); currentHighlightedStopId = null; + showTripPlanModal = false; } function tripSelected(event) { @@ -138,6 +146,17 @@ } } + function handleTripPlan(event) { + const tripData = event.detail.data; + fromMarker = event.detail.fromMarker; + toMarker = event.detail.toMarker; + tripItineraries = tripData.plan?.itineraries; + if (!tripItineraries) { + console.error('No itineraries found', 404); + } + showTripPlanModal = true; + } + onMount(() => { loadAlerts(); }); @@ -158,6 +177,7 @@ on:routeSelected={handleRouteSelected} on:clearResults={clearPolylines} on:viewAllRoutes={handleShowAllRoutes} + on:tripPlanned={handleTripPlan} />
@@ -182,6 +202,17 @@ on:routeSelected={handleRouteSelectedFromModal} /> {/if} + + {#if showTripPlanModal} + + {/if}
From 000ded670673b309cded1c027909f58fc3b7f8b6 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:22:55 +0200 Subject: [PATCH 06/43] feat: add pin marker functionality and enhance polyline creation options in OpenStreetMapProvider --- src/lib/Provider/OpenStreetMapProvider.js | 43 ++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/lib/Provider/OpenStreetMapProvider.js b/src/lib/Provider/OpenStreetMapProvider.js index a7a9ea6..f6de311 100644 --- a/src/lib/Provider/OpenStreetMapProvider.js +++ b/src/lib/Provider/OpenStreetMapProvider.js @@ -7,6 +7,7 @@ import { COLORS } from '$lib/colors'; import PopupContent from '$components/map/PopupContent.svelte'; import { createVehicleIconSvg } from '$lib/MapHelpers/generateVehicleIcon'; import VehiclePopupContent from '$components/map/VehiclePopupContent.svelte'; +import TripPlanPinMarker from '$components/trip-planner/tripPlanPinMarker.svelte'; export default class OpenStreetMapProvider { constructor() { @@ -89,6 +90,38 @@ export default class OpenStreetMapProvider { return marker; } + addPinMarker(position, text) { + if (!this.map) return null; + + const container = document.createElement('div'); + + new TripPlanPinMarker({ + target: container, + props: { + text: text + } + }); + + const customIcon = this.L.divIcon({ + html: container, + className: '', + iconSize: [32, 50], + iconAnchor: [16, 50] + }); + + const marker = this.L.marker([position.lat, position.lng], { icon: customIcon }).addTo( + this.map + ); + + return marker; + } + + removePinMarker(marker) { + if (marker) { + marker.remove(); + } + } + highlightMarker(stopId) { const marker = this.markersMap.get(stopId); if (!marker) return; @@ -312,7 +345,7 @@ export default class OpenStreetMapProvider { }).addTo(this.map); } - createPolyline(points) { + createPolyline(points, options = { withArrow: true }) { if (!browser || !this.map) return null; const decodedPolyline = PolylineUtil.decode(points); @@ -322,11 +355,13 @@ export default class OpenStreetMapProvider { } const polyline = new this.L.Polyline(decodedPolyline, { - color: COLORS.POLYLINE, - weight: 4, - opacity: 1.0 + color: options.color || COLORS.POLYLINE, + weight: options.weight || 4, + opacity: options.opacity || 1 }).addTo(this.map); + if (!options.withArrow) return polyline; + const arrowDecorator = this.L.polylineDecorator(polyline, { patterns: [ { From 4416880adf3b42700235fd030fe9e1ed105f7724 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:23:10 +0200 Subject: [PATCH 07/43] feat: Google Places autocomplete API function --- src/lib/geocoder.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/geocoder.js b/src/lib/geocoder.js index ae3f072..8493da9 100644 --- a/src/lib/geocoder.js +++ b/src/lib/geocoder.js @@ -10,3 +10,17 @@ export async function googleGeocode({ apiKey, query }) { return null; } } + +export async function googlePlacesAutocomplete({ apiKey, input }) { + const response = await fetch(`https://places.googleapis.com/v1/places:autocomplete`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Goog-Api-Key': apiKey + }, + body: JSON.stringify({ input }) + }); + const data = await response.json(); + + return data.suggestions; +} From e9155b83cff27c9aed792c505cccb11af7b1dd57 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:23:17 +0200 Subject: [PATCH 08/43] feat: add formatTime function for localized time formatting --- src/lib/formatters.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/formatters.js b/src/lib/formatters.js index c7e57bb..f11cfde 100644 --- a/src/lib/formatters.js +++ b/src/lib/formatters.js @@ -34,3 +34,11 @@ export function formatLastUpdated(timestamp, translations) { } return `${seconds} ${translations.sec} ${translations.ago}`; } + +export function formatTime(dateString) { + return new Date(dateString).toLocaleTimeString([], { + hour: 'numeric', + minute: '2-digit', + hour12: true + }); +} From d701991450a9bd24829e2ad6ee841fb64a922c1e Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:52:15 +0200 Subject: [PATCH 09/43] feat: remove unused googleGeocodeByPlaceId import from geocoder --- src/routes/api/oba/google-geocode-location/+server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/api/oba/google-geocode-location/+server.js b/src/routes/api/oba/google-geocode-location/+server.js index c43d41e..27cc05c 100644 --- a/src/routes/api/oba/google-geocode-location/+server.js +++ b/src/routes/api/oba/google-geocode-location/+server.js @@ -1,4 +1,4 @@ -import { googleGeocode, googleGeocodeByPlaceId } from '$lib/geocoder'; +import { googleGeocode } from '$lib/geocoder'; import { PRIVATE_OBA_GEOCODER_API_KEY as geocoderApiKey, From ede785efe3a71e8ea9781d564b98d780618d7bc4 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:52:27 +0200 Subject: [PATCH 10/43] feat: close TripPlanModal when the tab is switched --- src/routes/+page.svelte | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 53a7bcf..e340e5d 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -9,6 +9,7 @@ import { onMount } from 'svelte'; import StopModal from '$components/stops/StopModal.svelte'; import TripPlanModal from '$components/trip-planner/TripPlanModal.svelte'; + import { browser } from '$app/environment'; let stop; let selectedTrip = null; @@ -159,6 +160,13 @@ onMount(() => { loadAlerts(); + + // close the trip plan modal when the tab is switched + if (browser) { + window.addEventListener('tabSwitched', () => { + showTripPlanModal = false; + }); + } }); From e9a8d7c9f2b9591f1c963f6b42f2c6f045244d0a Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:53:03 +0200 Subject: [PATCH 11/43] feat: add TripPlanSearchField component --- .../trip-planner/TripPlanSearchField.svelte | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/components/trip-planner/TripPlanSearchField.svelte diff --git a/src/components/trip-planner/TripPlanSearchField.svelte b/src/components/trip-planner/TripPlanSearchField.svelte new file mode 100644 index 0000000..a1d155a --- /dev/null +++ b/src/components/trip-planner/TripPlanSearchField.svelte @@ -0,0 +1,79 @@ + + +
+ +
+ + {#if place} + + {/if} +
+ {#if isLoading} +

+ Loading... +

+ {:else if results.length > 0} +
    + {#each results as result} + + {/each} +
+ {/if} +
From b3ee7b8e880fba9d1687d6e14b097dca55db6e2d Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:53:15 +0200 Subject: [PATCH 12/43] feat: add TripPlanPinMarker component --- .../trip-planner/tripPlanPinMarker.svelte | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/components/trip-planner/tripPlanPinMarker.svelte diff --git a/src/components/trip-planner/tripPlanPinMarker.svelte b/src/components/trip-planner/tripPlanPinMarker.svelte new file mode 100644 index 0000000..d3d0706 --- /dev/null +++ b/src/components/trip-planner/tripPlanPinMarker.svelte @@ -0,0 +1,31 @@ + + +
+
+ {text} +
+ + + +
From 57c9de38544cbaa75830d54d5767a3a4163f5394 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:57:31 +0200 Subject: [PATCH 13/43] feat: add TripPlanModal component --- .../trip-planner/TripPlanModal.svelte | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/components/trip-planner/TripPlanModal.svelte diff --git a/src/components/trip-planner/TripPlanModal.svelte b/src/components/trip-planner/TripPlanModal.svelte new file mode 100644 index 0000000..cd6028e --- /dev/null +++ b/src/components/trip-planner/TripPlanModal.svelte @@ -0,0 +1,88 @@ + + + + {#if loading} + + {/if} + + {#if itineraries.length > 0} +
+ + {#each itineraries as _, index} + + {/each} +
+ +
+ {#if itineraries[activeTab]} + + {/if} +
+ {:else} +
+ No itineraries found +
+ {/if} +
From b21eed09a0d8e71d48a8a00da9c213ed07032226 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:57:55 +0200 Subject: [PATCH 14/43] feat: implement trip planner mood toggle in MapView component --- src/components/map/MapView.svelte | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/map/MapView.svelte b/src/components/map/MapView.svelte index 37a698b..437371d 100644 --- a/src/components/map/MapView.svelte +++ b/src/components/map/MapView.svelte @@ -20,6 +20,8 @@ export let showAllStops = true; export let stop = null; export let mapProvider = null; + let isTripPlanMoodActive = false; + let selectedStopID = null; const dispatch = createEventDispatcher(); @@ -163,6 +165,17 @@ allStops.forEach((s) => addMarker(s)); } + // TODO: prevent fetch stops-for-location if the trip planner mood is on - we should do this after merge. + $: { + if (isTripPlanMoodActive) { + clearAllMarkers(); + } else { + if (!selectedRoute || !showRoute) { + allStops.forEach((s) => addMarker(s)); + } + } + } + function addMarker(s, routeReference) { if (!mapInstance) { console.error('Map not initialized yet'); @@ -218,6 +231,12 @@ await initMap(); if (browser) { const darkMode = document.documentElement.classList.contains('dark'); + window.addEventListener('planTripTabClicked', () => { + isTripPlanMoodActive = true; + }); + window.addEventListener('tabSwitched', () => { + isTripPlanMoodActive = false; + }); const event = new CustomEvent('themeChange', { detail: { darkMode } }); window.dispatchEvent(event); } From e85cc13157b6df0a200f510a1eb8fe2d801b24b4 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:58:28 +0200 Subject: [PATCH 15/43] feat: integrate TripPlan component --- src/components/search/SearchPane.svelte | 39 +++++++++++++++---------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/components/search/SearchPane.svelte b/src/components/search/SearchPane.svelte index 7371314..679fb5a 100644 --- a/src/components/search/SearchPane.svelte +++ b/src/components/search/SearchPane.svelte @@ -9,10 +9,13 @@ import { clearVehicleMarkersMap, fetchAndUpdateVehicles } from '$lib/vehicleUtils'; import { calculateMidpoint } from '$lib/mathUtils'; import { Tabs, TabItem } from 'flowbite-svelte'; + import { PUBLIC_OTP_SERVER_URL } from '$env/static/public'; + import TripPlan from '$components/trip-planner/TripPlan.svelte'; const dispatch = createEventDispatcher(); export let cssClasses = ''; + export let mapProvider = null; let routes = null; let stops = null; @@ -21,32 +24,24 @@ let polylines = []; let currentIntervalId = null; - export let mapProvider = null; - function handleLocationClick(location) { clearResults(); - const lat = location.geometry.location.lat; const lng = location.geometry.location.lng; - mapProvider.panTo(lat, lng); mapProvider.setZoom(20); - dispatch('locationSelected', { location }); } function handleStopClick(stop) { clearResults(); - mapProvider.panTo(stop.lat, stop.lon); mapProvider.setZoom(20); - dispatch('stopSelected', { stop }); } async function handleRouteClick(route) { clearResults(); - const response = await fetch(`/api/oba/stops-for-route/${route.id}`); const stopsForRoute = await response.json(); const stops = stopsForRoute.data.references.stops; @@ -55,7 +50,6 @@ for (const polylineData of polylinesData) { const shape = polylineData.points; let polyline; - polyline = mapProvider.createPolyline(shape); polylines.push(polyline); } @@ -63,10 +57,8 @@ await showStopsOnRoute(stops); currentIntervalId = await fetchAndUpdateVehicles(route.id, mapProvider); const midpoint = calculateMidpoint(stopsForRoute.data.references.stops); - mapProvider.panTo(midpoint.lat, midpoint.lng); mapProvider.setZoom(12); - dispatch('routeSelected', { route, stopsForRoute, stops, polylines, currentIntervalId }); } @@ -101,6 +93,20 @@ clearInterval(currentIntervalId); } + function handleTripPlan(event) { + dispatch('tripPlanned', event.detail); + } + + function handlePlanTripTabClick() { + const event = new CustomEvent('planTripTabClicked'); + window.dispatchEvent(event); + } + + function handleTabSwitch() { + const event = new CustomEvent('tabSwitched'); + window.dispatchEvent(event); + } + onMount(() => { window.addEventListener('routeSelectedFromModal', (event) => { handleRouteClick(event.detail.route); @@ -110,7 +116,7 @@
- + {#if query} @@ -168,8 +174,11 @@ >
- - plan a trip UI goes here! - + + {#if PUBLIC_OTP_SERVER_URL} + + + + {/if} From e496638ad7c43a0c14d0d5682ccae4fc078fc033 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:59:06 +0200 Subject: [PATCH 16/43] feat: add TripPlan component for trip planning functionality --- src/components/trip-planner/TripPlan.svelte | 197 ++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/components/trip-planner/TripPlan.svelte diff --git a/src/components/trip-planner/TripPlan.svelte b/src/components/trip-planner/TripPlan.svelte new file mode 100644 index 0000000..7ac0a95 --- /dev/null +++ b/src/components/trip-planner/TripPlan.svelte @@ -0,0 +1,197 @@ + + +
+ handleSearchInput(query, true)} + onClear={() => clearInput(true)} + onSelect={(location) => selectLocation(location, true)} + /> + + handleSearchInput(query, false)} + onClear={() => clearInput(false)} + onSelect={(location) => selectLocation(location, false)} + /> + + +
From 1eeaa73fc6b74739b9fd291ae8f23066a18f8f07 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:59:30 +0200 Subject: [PATCH 17/43] feat: add LegDetails component for displaying trip leg information --- src/components/trip-planner/LegDetails.svelte | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/components/trip-planner/LegDetails.svelte diff --git a/src/components/trip-planner/LegDetails.svelte b/src/components/trip-planner/LegDetails.svelte new file mode 100644 index 0000000..e2379c8 --- /dev/null +++ b/src/components/trip-planner/LegDetails.svelte @@ -0,0 +1,98 @@ + + +
+
+ {#if icon} + + {/if} +
+ +
+

{modeText}

+

From: {leg.from.name}

+

To: {leg.to.name}

+

Distance: {Math.round(leg.distance)} meters

+

Duration: {Math.round(leg.duration / 60)} minutes

+

Start Time: {formatTime(leg.startTime)}

+

End Time: {formatTime(leg.endTime)}

+ + {#if leg.mode === 'WALK'} + + + {#if expandedSteps[index]} +
+ {#each leg.steps as step} +
+

+ {step.relativeDirection} on {step.streetName} +

+

Distance: {Math.round(step.distance)} meters

+

Direction: {step.absoluteDirection}

+
+ {/each} +
+ {/if} + {/if} +
+
From 5ce4b63db6fd6b674679fe57acc0de3444a7d459 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:59:43 +0200 Subject: [PATCH 18/43] feat: add ItineraryTab component for managing itinerary navigation --- src/components/trip-planner/ItineraryTab.svelte | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/components/trip-planner/ItineraryTab.svelte diff --git a/src/components/trip-planner/ItineraryTab.svelte b/src/components/trip-planner/ItineraryTab.svelte new file mode 100644 index 0000000..30272af --- /dev/null +++ b/src/components/trip-planner/ItineraryTab.svelte @@ -0,0 +1,14 @@ + + + From c2559eb8cfc654e39126e34d90929c3529098f90 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Mon, 11 Nov 2024 23:59:52 +0200 Subject: [PATCH 19/43] feat: add ItineraryDetails component for displaying itinerary information --- .../trip-planner/ItineraryDetails.svelte | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/components/trip-planner/ItineraryDetails.svelte diff --git a/src/components/trip-planner/ItineraryDetails.svelte b/src/components/trip-planner/ItineraryDetails.svelte new file mode 100644 index 0000000..ba57017 --- /dev/null +++ b/src/components/trip-planner/ItineraryDetails.svelte @@ -0,0 +1,19 @@ + + +
+

Duration: {Math.round(itinerary.duration / 60)} minutes

+

Start Time: {formatTime(itinerary.startTime)}

+

End Time: {formatTime(itinerary.endTime)}

+ +
+ {#each itinerary.legs as leg, index} + + {/each} +
+
From cb86a5328b9fdbda658953ca9bc5ee7a3d3c7727 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Tue, 12 Nov 2024 00:00:52 +0200 Subject: [PATCH 20/43] fix: update button color --- src/components/trip-planner/TripPlan.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/trip-planner/TripPlan.svelte b/src/components/trip-planner/TripPlan.svelte index 7ac0a95..a00a4ab 100644 --- a/src/components/trip-planner/TripPlan.svelte +++ b/src/components/trip-planner/TripPlan.svelte @@ -175,7 +175,7 @@ {/each} From b4ee0013868589f04585b56d667b680dc966facc Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Tue, 12 Nov 2024 19:38:09 +0200 Subject: [PATCH 28/43] style: enhance layout and styling --- .../trip-planner/ItineraryDetails.svelte | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/components/trip-planner/ItineraryDetails.svelte b/src/components/trip-planner/ItineraryDetails.svelte index ba57017..8c15685 100644 --- a/src/components/trip-planner/ItineraryDetails.svelte +++ b/src/components/trip-planner/ItineraryDetails.svelte @@ -6,14 +6,22 @@ export let toggleSteps; -
-

Duration: {Math.round(itinerary.duration / 60)} minutes

-

Start Time: {formatTime(itinerary.startTime)}

-

End Time: {formatTime(itinerary.endTime)}

- -
- {#each itinerary.legs as leg, index} - - {/each} +
+
+

Duration

+

{Math.round(itinerary.duration / 60)} min

+
+
+

Start Time

+

{formatTime(itinerary.startTime)}

+
+

End Time

+

{formatTime(itinerary.endTime)}

+
+
+
+ {#each itinerary.legs as leg, index} + + {/each}
From f891328b22d9ba629132eb852be79f6cca2154a8 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Tue, 12 Nov 2024 20:52:10 +0200 Subject: [PATCH 29/43] feat: enhance LegDetails component with additional icons and improved layout --- src/components/trip-planner/LegDetails.svelte | 95 +++++++++++++------ 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/src/components/trip-planner/LegDetails.svelte b/src/components/trip-planner/LegDetails.svelte index e2379c8..4b55117 100644 --- a/src/components/trip-planner/LegDetails.svelte +++ b/src/components/trip-planner/LegDetails.svelte @@ -7,7 +7,12 @@ faChevronDown, faChevronUp, faFerry, - faTrainSubway + faTrainSubway, + faMapMarkerAlt, + faRulerCombined, + faClock, + faArrowRight, + faArrowAltCircleRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/svelte-fontawesome'; @@ -15,15 +20,12 @@ export let index; export let expandedSteps; export let toggleSteps; - - // TODO: ADD ICONS FOR OTHER MODES - let icon; - let modeText; - let iconColor; + let icon, modeText, iconColor; + let isWalking = leg.mode === 'WALK'; switch (leg.mode) { case 'WALK': icon = faWalking; - modeText = 'Walking'; + modeText = 'Walk'; iconColor = 'text-blue-600'; break; case 'BUS': @@ -32,11 +34,6 @@ iconColor = 'text-green-600'; break; case 'TRAIN': - icon = faTrain; - modeText = `Train - ${leg.route}`; - iconColor = 'text-red-600'; - break; - case 'RAIL': icon = faTrain; modeText = `Train - ${leg.route}`; @@ -58,37 +55,73 @@ } -
-
+
+
+ +
{#if icon} - + {/if}
-
-

{modeText}

-

From: {leg.from.name}

-

To: {leg.to.name}

-

Distance: {Math.round(leg.distance)} meters

-

Duration: {Math.round(leg.duration / 60)} minutes

-

Start Time: {formatTime(leg.startTime)}

-

End Time: {formatTime(leg.endTime)}

+
+
+
{leg.from.name}
+
+ +
+
+ + Start: + {formatTime(leg.startTime)} +
+
+ + End: {formatTime(leg.endTime)} +
+
+ +
+
+ + {leg.to.name} +
+
+ + Distance: {Math.round(leg.distance)} meters +
+
+ + Duration: {Math.round(leg.duration / 60)} minutes +
+
- {#if leg.mode === 'WALK'} - {#if expandedSteps[index]} -
+
{#each leg.steps as step}
-

- {step.relativeDirection} on {step.streetName} -

-

Distance: {Math.round(step.distance)} meters

-

Direction: {step.absoluteDirection}

+
{step.relativeDirection} on {step.streetName}
+
+ + Distance: {Math.round(step.distance)} meters +
+
+ + {step.absoluteDirection} +
{/each}
From b2a8e7140bd56752f04453381da801f74f2418f1 Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Tue, 12 Nov 2024 23:59:38 +0200 Subject: [PATCH 30/43] feat: add pin marker functionality to GoogleMapProvider --- src/lib/Provider/GoogleMapProvider.js | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/lib/Provider/GoogleMapProvider.js b/src/lib/Provider/GoogleMapProvider.js index 90ccfb2..e4feffd 100644 --- a/src/lib/Provider/GoogleMapProvider.js +++ b/src/lib/Provider/GoogleMapProvider.js @@ -5,6 +5,7 @@ import { COLORS } from '$lib/colors'; import PopupContent from '$components/map/PopupContent.svelte'; import VehiclePopupContent from '$components/map/VehiclePopupContent.svelte'; import { createVehicleIconSvg } from '$lib/MapHelpers/generateVehicleIcon'; +import TripPlanPinMarker from '$components/trip-planner/tripPlanPinMarker.svelte'; export default class GoogleMapProvider { constructor(apiKey) { this.apiKey = apiKey; @@ -154,6 +155,10 @@ export default class GoogleMapProvider { unHighlightMarker(stopId) { const marker = this.markersMap.get(stopId); + + if (!marker) { + return; + } marker.$set({ isHighlighted: false }); } @@ -164,6 +169,57 @@ export default class GoogleMapProvider { this.stopMarkers = []; } + addPinMarker(position, text) { + const container = document.createElement('div'); + document.body.appendChild(container); + + new TripPlanPinMarker({ + target: container, + props: { + text: text + } + }); + + const overlay = new google.maps.OverlayView(); + + overlay.onAdd = function () { + this.getPanes().overlayMouseTarget.appendChild(container); + }; + + overlay.draw = function () { + const projection = this.getProjection(); + const pos = projection.fromLatLngToDivPixel( + new google.maps.LatLng(position.lat, position.lng) + ); + container.style.left = `${pos.x - 16}px`; + container.style.top = `${pos.y - 50}px`; + container.style.position = 'absolute'; + container.style.zIndex = '1000'; + }; + + overlay.onRemove = function () { + container.parentNode.removeChild(container); + }; + + overlay.setMap(this.map); + + return { overlay, element: container }; + } + + removePinMarker(marker) { + if (!marker) { + return; + } + + if (marker.overlay) { + marker.overlay.setMap(null); + } + + if (marker.element && marker.element.parentNode) { + marker.element.parentNode.removeChild(marker.element); + } + } + addVehicleMarker(vehicle, activeTrip) { if (!this.map) return null; From 452e65756fa271e1f85206cf1c41ba78beaefb8e Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Tue, 12 Nov 2024 23:59:47 +0200 Subject: [PATCH 31/43] fix: ensure polyline removal handles asynchronous operations in TripPlanModal --- src/components/trip-planner/TripPlanModal.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/trip-planner/TripPlanModal.svelte b/src/components/trip-planner/TripPlanModal.svelte index cd6028e..47d4d0c 100644 --- a/src/components/trip-planner/TripPlanModal.svelte +++ b/src/components/trip-planner/TripPlanModal.svelte @@ -55,8 +55,8 @@ mapProvider.removePinMarker(toMarker); if (currPolylines.length > 0) { - currPolylines.forEach((polyline) => { - mapProvider.removePolyline(polyline); + currPolylines.forEach(async (polyline) => { + mapProvider.removePolyline(await polyline); }); } }); From dc0bd6cf108d5308234e99671db91c4846ec17ce Mon Sep 17 00:00:00 2001 From: Ahmedhossamdev Date: Wed, 13 Nov 2024 00:00:03 +0200 Subject: [PATCH 32/43] style: improve button styling --- src/components/trip-planner/TripPlan.svelte | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/trip-planner/TripPlan.svelte b/src/components/trip-planner/TripPlan.svelte index 191304d..ddf75af 100644 --- a/src/components/trip-planner/TripPlan.svelte +++ b/src/components/trip-planner/TripPlan.svelte @@ -175,12 +175,20 @@