# bods-js

[![npm version](https://badge.fury.io/js/@drfrost/bods-js.svg)](https://badge.fury.io/js/@drfrost/bods-js)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## BODS JavaScript Client

A comprehensive TypeScript/JavaScript client for the UK's **Bus Open Data Service (BODS) API**. This library provides a simple, type-safe interface to access bus timetables, fares, real-time vehicle locations, and service disruptions.

## 🚀 Features

- **🎯 Full TypeScript Support** - Complete type definitions for all API responses
- **📅 Timetables API** - Access to bus schedules and route information
- **🎟️ Fares API** - Bus fare information by operator and area
- **🚌 Real-time Vehicle Tracking** - Live bus locations in SIRI-VM and GTFS-RT formats
- **⚠️ Service Disruptions** - Current and planned service disruptions
- **🗺️ Geographic Queries** - Search by bounding box with built-in utilities
- **📄 Comprehensive Documentation** - JSDoc comments throughout
- **🛡️ Error Handling** - Robust error handling with detailed error types
- **⚡ Performance** - Built with modern fetch API and optimized for speed

## 📦 Installation

```bash
# Using npm
npm install @drfrost/bods-js

# Using yarn
yarn add @drfrost/bods-js

# Using pnpm
pnpm add @drfrost/bods-js

# Using bun
bun add @drfrost/bods-js
```

## 🔑 Getting Started

### 1. Get Your API Key

First, register for a free API key at [data.bus-data.dft.gov.uk](https://data.bus-data.dft.gov.uk/account/).

### 2. Basic Usage

```typescript
// Named import (recommended)
import { BODSClient } from '@drfrost/bods-js';

// Default import (also supported)
import BODSClient from '@drfrost/bods-js';

// Import specific clients
import { TimetablesClient, AVLClient } from '@drfrost/bods-js';

const client = new BODSClient({
  apiKey: 'your-api-key-here'
});

// Search for timetables
const timetables = await client.timetables.search({
  noc: ['SCMN'], // Stagecoach Manchester
  status: 'published'
});

console.log(`Found ${timetables.count} timetables`);
```

## 📚 API Reference

### 🏗️ Client Initialization

```typescript
const client = new BODSClient({
  apiKey: 'your-api-key',
  baseUrl: 'https://data.bus-data.dft.gov.uk', // optional
  timeout: 30000 // optional, in milliseconds
});
```

### 📅 Timetables API

Access bus schedules and route information. Data updated daily at 06:00 GMT.

```typescript
// Search timetables
const timetables = await client.timetables.search({
  noc: ['SCMN', 'SCGH'],     // Operator codes
  adminArea: ['060'],         // Area codes
  status: 'published',        // published | inactive
  dqRag: 'green',            // red | amber | green
  bodsCompliance: true,       // BODS compliant only
  limit: 50                   // Max results
});

// Get specific timetable
const timetable = await client.timetables.getById(123);

// Get by operator
const operatorTimetables = await client.timetables.getByOperator('SCMN');

// Get high-quality timetables
const quality = await client.timetables.getHighQuality();

// Get recently modified
const recent = await client.timetables.getRecentlyModified(
  new Date('2023-01-01')
);
```

### 🎟️ Fares API

Access bus fare information. Data updated daily at 06:00 GMT.

```typescript
// Search fares
const fares = await client.fares.search({
  noc: ['SCMN'],
  status: 'published',
  boundingBox: [-2.930, 53.374, -3.085, 53.453] // Liverpool area
});

// Get specific fares dataset
const fareData = await client.fares.getById(456);

// Get by operator
const operatorFares = await client.fares.getByOperator(['SCMN']);

// Get by geographic area
const areaFares = await client.fares.getByArea(
  [-2.930, 53.374, -3.085, 53.453]
);

// Get published fares only
const published = await client.fares.getPublished();
```

### 🚌 Real-time Vehicle Locations (AVL)

Access live bus locations. Data updated every 10 seconds.

```typescript
// Get vehicles in SIRI-VM format (XML)
const vehicles = await client.avl.getSIRIVM({
  operatorRef: ['SCMN'],
  boundingBox: [-2.930, 53.374, -3.085, 53.453],
  lineRef: '85A'
});

// Get vehicles in GTFS-RT format (Protocol Buffers)
const gtfsVehicles = await client.avl.getGTFSRT({
  boundingBox: [-2.930, 53.374, -3.085, 53.453]
});

// Get by operator
const operatorVehicles = await client.avl.getByOperator(['SCMN']);

// Get by line
const lineVehicles = await client.avl.getByLine('85A');

// Get by vehicle
const vehicle = await client.avl.getByVehicle('BUSC-001');

// Get by area in different formats
const siriVehicles = await client.avl.getByArea(boundingBox, 'siri-vm');
const gtfsVehicles2 = await client.avl.getByArea(boundingBox, 'gtfs-rt');
```

### ⚠️ Service Disruptions

Access current and planned service disruptions in SIRI-SX format.

```typescript
// Get all current disruptions (raw XML)
const disruptions = await client.disruptions.getCurrent();

// Get parsed disruptions
const parsed = await client.disruptions.getCurrentParsed();

parsed.forEach(disruption => {
  console.log(`${disruption.summary}: ${disruption.description}`);
  console.log(`Planned: ${disruption.planned}`);
  console.log(`Severity: ${disruption.severity}`);
});

// Filter disruptions
const unplanned = client.disruptions.filterDisruptions(parsed, {
  planned: false
});

const tfgmDisruptions = client.disruptions.filterDisruptions(parsed, {
  participantRef: 'TfGM'
});
```

## �️ Utilities

The library includes helpful utility functions:

```typescript
import { 
  createBoundingBox, 
  calculateDistance,
  isValidNOC,
  UK_CITIES 
} from '@drfrost/bods-js';

// Create bounding box from center point and radius
const bbox = createBoundingBox(53.4808, -2.2426, 10); // Manchester, 10km

// Use predefined city bounding boxes
const manchesterVehicles = await client.avl.getByArea(UK_CITIES.MANCHESTER);
const londonFares = await client.fares.getByArea(UK_CITIES.LONDON);

// Calculate distance between points
const distance = calculateDistance(53.4808, -2.2426, 51.5074, -0.1278);

// Validate NOC codes
const isValid = isValidNOC('SCMN'); // true
```

### Available City Bounding Boxes

```typescript
UK_CITIES.LONDON        // Greater London (30km radius)
UK_CITIES.MANCHESTER    // Greater Manchester (15km radius)
UK_CITIES.BIRMINGHAM    // Birmingham (15km radius)
UK_CITIES.LEEDS         // Leeds (15km radius)
UK_CITIES.LIVERPOOL     // Liverpool (15km radius)
UK_CITIES.BRISTOL       // Bristol (15km radius)
UK_CITIES.SHEFFIELD     // Sheffield (15km radius)
UK_CITIES.LEICESTER     // Leicester (10km radius)
UK_CITIES.COVENTRY      // Coventry (10km radius)
UK_CITIES.BRADFORD      // Bradford (10km radius)
```

## 🔧 Error Handling

The client provides detailed error handling:

```typescript
import { HttpClientError } from '@drfrost/bods-js';

try {
  const timetables = await client.timetables.search({ noc: ['INVALID'] });
} catch (error) {
  if (error instanceof HttpClientError) {
    switch (error.status) {
      case 401:
        console.error('Invalid API key');
        break;
      case 403:
        console.error('Access forbidden');
        break;
      case 429:
        console.error('Rate limit exceeded');
        break;
      default:
        console.error(`HTTP ${error.status}: ${error.message}`);
    }
  }
}
```

## 🌐 Environment Support

- **Node.js** 18+ (with fetch support)
- **Next.js** 13+ (App Router and Pages Router)
- **React** (Client and Server Components)
- **Browsers** (modern browsers with fetch support)
- **Deno** (with compatibility layer)
- **Bun** (native support)

### Using with Next.js

The library is fully compatible with Next.js applications:

```typescript
// app/page.tsx (App Router)
import { BODSClient } from '@drfrost/bods-js';

export default async function HomePage() {
  const client = new BODSClient({
    apiKey: process.env.BODS_API_KEY!
  });

  const timetables = await client.timetables.search({
    noc: ['SCMN'],
    limit: 10
  });

  return (
    <div>
      <h1>Bus Timetables</h1>
      {/* Render timetables */}
    </div>
  );
}
```

```typescript
// pages/api/buses.ts (API Routes)
import { BODSClient } from '@drfrost/bods-js';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const client = new BODSClient({
    apiKey: process.env.BODS_API_KEY!
  });

  try {
    const vehicles = await client.avl.getSIRIVM({
      operatorRef: ['SCMN']
    });
    res.status(200).json(vehicles);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch vehicles' });
  }
}
```

## 📖 Advanced Examples

### Monitoring Live Buses

```typescript
// Monitor buses in real-time
async function monitorBuses() {
  const interval = setInterval(async () => {
    try {
      const vehicles = await client.avl.getSIRIVM({
        operatorRef: ['SCMN'],
        boundingBox: UK_CITIES.MANCHESTER
      });
      
      // Parse and process vehicle locations
      console.log('Updated vehicle positions');
    } catch (error) {
      console.error('Failed to fetch vehicles:', error);
    }
  }, 10000); // Every 10 seconds

  // Clean up
  setTimeout(() => clearInterval(interval), 60000);
}
```

### Building a Route Planner

```typescript
async function getRouteInfo(operatorCode: string, routeNumber: string) {
  // Get timetable data
  const timetables = await client.timetables.search({
    noc: [operatorCode],
    search: routeNumber,
    status: 'published'
  });

  // Get fare information
  const fares = await client.fares.getByOperator(operatorCode);

  // Get live vehicle positions
  const vehicles = await client.avl.getSIRIVM({
    operatorRef: [operatorCode],
    lineRef: routeNumber
  });

  // Check for disruptions
  const disruptions = await client.disruptions.getCurrentParsed();
  
  return {
    timetables: timetables.results,
    fares: fares.results,
    liveVehicles: vehicles.xmlData,
    disruptions: disruptions.filter(d => 
      d.description?.includes(routeNumber)
    )
  };
}
```

## 🧪 Testing

```bash
bun test
```

## 📄 License

MIT License - see [LICENSE](LICENSE) for details.

## 🤝 Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests.

## 📞 Support

- **Documentation**: [GitHub Wiki](https://github.com/DRFR0ST/bods-js/wiki)
- **Issues**: [GitHub Issues](https://github.com/DRFR0ST/bods-js/issues)
- **BODS API Docs**: [data.bus-data.dft.gov.uk](https://data.bus-data.dft.gov.uk/guidance/)

---

## Bus Open Data Service (BODS) API Overview

The Bus Open Data Service provides comprehensive data about UK bus services through four main APIs:

### **1. Timetables Data API** 📅

Detailed information about bus schedules and routes, updated daily at 06:00 GMT.

### **2. Fares Data API** 🎟️

Bus fare information by operator and geographic area, updated daily at 06:00 GMT.

### **3. Automatic Vehicle Location (AVL) API** 🚌

Real-time bus location data in SIRI-VM (XML) and GTFS-RT (Protocol Buffers) formats, updated every 10 seconds.

### **4. Disruptions Data API** ⚠️

Current and planned service disruptions in SIRI-SX format, updated as information becomes available.

---

Made with ❤️ for the UK transport community