From 7ed780a45974ff77237f84b48c4732ea33ec245e Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Fri, 26 Sep 2025 15:40:58 -0700 Subject: [PATCH] Implement filtering of shuttles by proximity to the closest route polyline coordinate --- .../ApiBasedShuttleRepositoryLoader.ts | 54 +++++++++++++++++-- .../shuttle/ShuttleRepositoryLoader.ts | 2 +- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts b/src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts index e79dd45..fb7352a 100644 --- a/src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts +++ b/src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts @@ -1,7 +1,7 @@ import { ShuttleGetterSetterRepository } from "../../repositories/shuttle/ShuttleGetterSetterRepository"; import { IEta, IRoute, IShuttle, IStop } from "../../entities/ShuttleRepositoryEntities"; import { ShuttleRepositoryLoader } from "./ShuttleRepositoryLoader"; -import { IEntityWithId } from "../../entities/SharedEntities"; +import { ICoordinates, IEntityWithId } from "../../entities/SharedEntities"; import { ApiResponseError } from "../ApiResponseError"; /** @@ -10,12 +10,13 @@ import { ApiResponseError } from "../ApiResponseError"; * which inherit from `IEntityWithId`. */ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader { - baseUrl = "https://passiogo.com/mapGetData.php"; + readonly baseUrl = "https://passiogo.com/mapGetData.php"; constructor( public passioSystemId: string, public systemIdForConstructedData: string, public repository: ShuttleGetterSetterRepository, + readonly shuttleToRouteCoordinateMaximumDistanceMiles = 10, ) { } @@ -31,7 +32,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader public async updateAll() { await this.updateRouteDataForSystem(); await this.updateStopAndPolylineDataForRoutesInSystem(); - await this.updateShuttleDataForSystem(); + await this.updateShuttleDataForSystemBasedOnProximityToRoutes(); // Because ETA method doesn't support pruning yet, // add a call to the clear method here @@ -155,11 +156,12 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader return await response.json(); } - public async updateShuttleDataForSystem() { + public async updateShuttleDataForSystemBasedOnProximityToRoutes() { try { const json = await this.fetchShuttleDataJson(); - const shuttles = this.constructShuttlesFromJson(json); + let shuttles = this.constructShuttlesFromJson(json); if (shuttles !== null) { + shuttles = await this.filterShuttlesByDistanceFromCorrespondingRoute(shuttles); await this.updateShuttleDataInRepository(shuttles); } else { console.warn(`Shuttle update failed for the following JSON: ${JSON.stringify(json)}`) @@ -391,4 +393,46 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader } } + private filterShuttlesByDistanceFromCorrespondingRoute = async (shuttles: IShuttle[]) => { + let filteredShuttles: IShuttle[] = []; + + await Promise.all(shuttles.map(async (shuttle) => { + const route = await this.repository.getRouteById(shuttle.routeId); + if (route != null) { + let closestDistanceMiles = Number.MAX_VALUE; + + route.polylineCoordinates.forEach((coordinate) => { + const calculatedDistance = ApiBasedShuttleRepositoryLoader.convertDistanceBetweenCoordinatesToMiles(coordinate, shuttle.coordinates) + if (closestDistanceMiles > calculatedDistance) { + closestDistanceMiles = calculatedDistance; + } + }); + + if (closestDistanceMiles <= this.shuttleToRouteCoordinateMaximumDistanceMiles) { + filteredShuttles.push(shuttle); + } + } else { + console.warn(`No corresponding route found for ID ${shuttle.routeId} of shuttle ${shuttle.id}`) + } + })); + + return filteredShuttles; + }; + + public static convertDistanceBetweenCoordinatesToMiles = (fromCoordinate: ICoordinates, toCoordinate: ICoordinates): number => { + const earthRadiusMiles = 3959; + + const lat1Rad = fromCoordinate.latitude * Math.PI / 180; + const lat2Rad = toCoordinate.latitude * Math.PI / 180; + const deltaLatRad = (toCoordinate.latitude - fromCoordinate.latitude) * Math.PI / 180; + const deltaLonRad = (toCoordinate.longitude - fromCoordinate.longitude) * Math.PI / 180; + + const a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) + + Math.cos(lat1Rad) * Math.cos(lat2Rad) * + Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2); + + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return earthRadiusMiles * c; + }; } diff --git a/src/loaders/shuttle/ShuttleRepositoryLoader.ts b/src/loaders/shuttle/ShuttleRepositoryLoader.ts index 6c4eb3e..d556455 100644 --- a/src/loaders/shuttle/ShuttleRepositoryLoader.ts +++ b/src/loaders/shuttle/ShuttleRepositoryLoader.ts @@ -3,7 +3,7 @@ import { RepositoryLoader } from "../RepositoryLoader"; export interface ShuttleRepositoryLoader extends RepositoryLoader { updateRouteDataForSystem(): Promise; updateStopAndPolylineDataForRoutesInSystem(): Promise; - updateShuttleDataForSystem(): Promise; + updateShuttleDataForSystemBasedOnProximityToRoutes(): Promise; updateEtaDataForExistingStopsForSystem(): Promise; updateEtaDataForStopId(stopId: string): Promise; }