UNPKG

@drfrost/bods-js

Version:

JavaScript client for the UK's Bus Open Data Service (BODS) API

222 lines (209 loc) 6.58 kB
import type { AVLSearchParams, GTFSRTSearchParams, SIRIVMData, GTFSRTData, BoundingBox } from '../types/index.js'; import { HttpClient } from './http-client.js'; /** * Client for interacting with the BODS Automatic Vehicle Location (AVL) API * * The AVL API provides real-time bus location data in two formats: * - SIRI-VM (XML format) * - GTFS-RT (Protocol Buffers format) * * Data is updated every 10 seconds. */ export class AVLClient { constructor(private httpClient: HttpClient) {} /** * Get real-time vehicle locations in SIRI-VM format (XML) * * @param params - Search parameters to filter vehicle data * @returns Promise resolving to SIRI-VM XML data * * @example * ```typescript * // Get all vehicles from specific operators * const vehicles = await client.avl.getSIRIVM({ * operatorRef: ['SCGH', 'SCLI'] * }); * * // Get vehicles in a specific area (Liverpool) * const areaVehicles = await client.avl.getSIRIVM({ * boundingBox: [-2.930, 53.374, -3.085, 53.453] * }); * * // Get vehicles on a specific line * const lineVehicles = await client.avl.getSIRIVM({ * lineRef: '85A' * }); * ``` */ async getSIRIVM(params: AVLSearchParams = {}): Promise<SIRIVMData> { const response = await this.httpClient.get<string>('/api/v1/datafeed', params); return { xmlData: response.data }; } /** * Get a specific SIRI-VM datafeed by ID * * @param datafeedId - The unique datafeed identifier * @returns Promise resolving to SIRI-VM XML data * * @example * ```typescript * const datafeed = await client.avl.getSIRIVMById(123); * ``` */ async getSIRIVMById(datafeedId: number): Promise<SIRIVMData> { const response = await this.httpClient.get<string>(`/api/v1/datafeed/${datafeedId}/`); return { xmlData: response.data }; } /** * Get real-time vehicle locations in GTFS-RT format (Protocol Buffers) * * @param params - Search parameters to filter vehicle data * @returns Promise resolving to GTFS-RT protobuf data * * @example * ```typescript * // Get all vehicles in GTFS-RT format * const vehicles = await client.avl.getGTFSRT({ * boundingBox: [-2.930, 53.374, -3.085, 53.453] * }); * * // Get vehicles for a specific route * const routeVehicles = await client.avl.getGTFSRT({ * routeId: 'route_123' * }); * ``` */ async getGTFSRT(params: GTFSRTSearchParams = {}): Promise<GTFSRTData> { const response = await this.httpClient.get<ArrayBuffer>('/api/v1/gtfsrtdatafeed/', params); return { protobufData: response.data }; } /** * Get vehicles by operator in SIRI-VM format * * @param operatorRef - Operator reference(s) (often National Operator Code) * @param additionalParams - Additional search parameters * @returns Promise resolving to SIRI-VM XML data * * @example * ```typescript * const stagecoachVehicles = await client.avl.getByOperator(['SCMN', 'SCGH']); * ``` */ async getByOperator( operatorRef: string | string[], additionalParams: Omit<AVLSearchParams, 'operatorRef'> = {} ): Promise<SIRIVMData> { return this.getSIRIVM({ ...additionalParams, operatorRef: Array.isArray(operatorRef) ? operatorRef : [operatorRef] }); } /** * Get vehicles by line reference in SIRI-VM format * * @param lineRef - Line reference (route number/identifier) * @param additionalParams - Additional search parameters * @returns Promise resolving to SIRI-VM XML data * * @example * ```typescript * const line85AVehicles = await client.avl.getByLine('85A'); * ``` */ async getByLine( lineRef: string, additionalParams: Omit<AVLSearchParams, 'lineRef'> = {} ): Promise<SIRIVMData> { return this.getSIRIVM({ ...additionalParams, lineRef }); } /** * Get vehicles by vehicle reference in SIRI-VM format * * @param vehicleRef - Vehicle reference/identifier * @param additionalParams - Additional search parameters * @returns Promise resolving to SIRI-VM XML data * * @example * ```typescript * const specificVehicle = await client.avl.getByVehicle('BUSC-001'); * ``` */ async getByVehicle( vehicleRef: string, additionalParams: Omit<AVLSearchParams, 'vehicleRef'> = {} ): Promise<SIRIVMData> { return this.getSIRIVM({ ...additionalParams, vehicleRef }); } /** * Get vehicles in a specific geographic area * * @param boundingBox - Geographic bounding box [minLng, minLat, maxLng, maxLat] * @param format - Data format ('siri-vm' or 'gtfs-rt') * @param additionalParams - Additional search parameters * @returns Promise resolving to location data in specified format * * @example * ```typescript * // Liverpool area in SIRI-VM format * const liverpoolVehicles = await client.avl.getByArea( * [-2.930, 53.374, -3.085, 53.453], * 'siri-vm' * ); * * // Manchester area in GTFS-RT format * const manchesterVehicles = await client.avl.getByArea( * [-2.3, 53.4, -2.2, 53.5], * 'gtfs-rt' * ); * ``` */ async getByArea( boundingBox: BoundingBox, format: 'siri-vm' | 'gtfs-rt' = 'siri-vm', additionalParams: Record<string, any> = {} ): Promise<SIRIVMData | GTFSRTData> { if (format === 'gtfs-rt') { return this.getGTFSRT({ ...additionalParams, boundingBox }); } return this.getSIRIVM({ ...additionalParams, boundingBox }); } /** * Helper method to create bounding box from center point and radius * * @param centerLat - Center latitude * @param centerLng - Center longitude * @param radiusKm - Radius in kilometers * @returns Bounding box coordinates * * @example * ```typescript * const bbox = client.avl.createBoundingBox(53.4808, -2.2426, 5); // Manchester, 5km radius * const nearbyVehicles = await client.avl.getByArea(bbox); * ``` */ createBoundingBox(centerLat: number, centerLng: number, radiusKm: number): BoundingBox { // Approximate degrees per kilometer const latDegPerKm = 1 / 111; const lngDegPerKm = 1 / (111 * Math.cos(centerLat * Math.PI / 180)); const latOffset = radiusKm * latDegPerKm; const lngOffset = radiusKm * lngDegPerKm; return [ centerLng - lngOffset, // minLng centerLat - latOffset, // minLat centerLng + lngOffset, // maxLng centerLat + latOffset // maxLat ]; } }