@drfrost/bods-js
Version:
JavaScript client for the UK's Bus Open Data Service (BODS) API
179 lines (167 loc) • 5.18 kB
text/typescript
import type { BoundingBox } from '../types/index.js';
/**
* Utility functions for working with BODS data
*/
/**
* Create a bounding box from a center point and radius
*
* @param centerLat - Center latitude
* @param centerLng - Center longitude
* @param radiusKm - Radius in kilometers
* @returns Bounding box coordinates [minLng, minLat, maxLng, maxLat]
*
* @example
* ```typescript
* import { createBoundingBox } from 'bods-js/utils';
*
* // Manchester city center, 10km radius
* const bbox = createBoundingBox(53.4808, -2.2426, 10);
* const vehicles = await client.avl.getByArea(bbox);
* ```
*/
export function 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
];
}
/**
* Check if a point is within a bounding box
*
* @param lat - Point latitude
* @param lng - Point longitude
* @param boundingBox - Bounding box to check against
* @returns True if point is within the bounding box
*
* @example
* ```typescript
* import { isPointInBoundingBox } from 'bods-js/utils';
*
* const manchester = createBoundingBox(53.4808, -2.2426, 10);
* const isInManchester = isPointInBoundingBox(53.4808, -2.2426, manchester); // true
* ```
*/
export function isPointInBoundingBox(lat: number, lng: number, boundingBox: BoundingBox): boolean {
const [minLng, minLat, maxLng, maxLat] = boundingBox;
return lng >= minLng && lng <= maxLng && lat >= minLat && lat <= maxLat;
}
/**
* Calculate distance between two points using Haversine formula
*
* @param lat1 - First point latitude
* @param lng1 - First point longitude
* @param lat2 - Second point latitude
* @param lng2 - Second point longitude
* @returns Distance in kilometers
*
* @example
* ```typescript
* import { calculateDistance } from 'bods-js/utils';
*
* const distance = calculateDistance(53.4808, -2.2426, 51.5074, -0.1278); // Manchester to London
* console.log(`Distance: ${distance.toFixed(2)}km`);
* ```
*/
export function calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
const R = 6371; // Earth's radius in kilometers
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLng = (lng2 - lng1) * Math.PI / 180;
const a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLng/2) * Math.sin(dLng/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
/**
* Format a date for BODS API queries
*
* @param date - Date to format
* @returns ISO string formatted for BODS API
*
* @example
* ```typescript
* import { formatDateForAPI } from 'bods-js/utils';
*
* const formatted = formatDateForAPI(new Date());
* const timetables = await client.timetables.search({
* modifiedDate: formatted
* });
* ```
*/
export function formatDateForAPI(date: Date): string {
return date.toISOString();
}
/**
* Parse an ISO date string to Date object
*
* @param dateString - ISO date string
* @returns Date object
*
* @example
* ```typescript
* import { parseDateFromAPI } from 'bods-js/utils';
*
* const date = parseDateFromAPI('2023-01-01T12:45:00+00:00');
* ```
*/
export function parseDateFromAPI(dateString: string): Date {
return new Date(dateString);
}
/**
* Validate National Operator Code format
*
* @param noc - National Operator Code to validate
* @returns True if NOC format appears valid
*
* @example
* ```typescript
* import { isValidNOC } from 'bods-js/utils';
*
* console.log(isValidNOC('SCMN')); // true
* console.log(isValidNOC('invalid')); // false
* ```
*/
export function isValidNOC(noc: string): boolean {
// NOCs are typically 4 characters, alphanumeric
return /^[A-Z0-9]{4}$/.test(noc);
}
/**
* Validate API key format
*
* @param apiKey - API key to validate
* @returns True if API key format appears valid
*
* @example
* ```typescript
* import { isValidAPIKey } from 'bods-js/utils';
*
* const isValid = isValidAPIKey('your-api-key');
* ```
*/
export function isValidAPIKey(apiKey: string): boolean {
// API keys are typically 40 character hex strings
return /^[a-f0-9]{40}$/.test(apiKey);
}
/**
* Get common UK city bounding boxes
*/
export const UK_CITIES = {
LONDON: createBoundingBox(51.5074, -0.1278, 30),
MANCHESTER: createBoundingBox(53.4808, -2.2426, 15),
BIRMINGHAM: createBoundingBox(52.4862, -1.8904, 15),
LEEDS: createBoundingBox(53.8008, -1.5491, 15),
LIVERPOOL: createBoundingBox(53.4084, -2.9916, 15),
BRISTOL: createBoundingBox(51.4545, -2.5879, 15),
SHEFFIELD: createBoundingBox(53.3811, -1.4701, 15),
LEICESTER: createBoundingBox(52.6369, -1.1398, 10),
COVENTRY: createBoundingBox(52.4068, -1.5197, 10),
BRADFORD: createBoundingBox(53.7959, -1.7594, 10)
} as const;