Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 88 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ A TypeScript library for loading GTFS (General Transit Feed Specification) data
- Email: contact@sys-dev-run.fr
- Website: https://www.sys-dev-run.fr/

## Documentation & Demo

📚 **[View Documentation and Interactive Demo](https://sysdevrun.github.io/gtfs-sqljs/)**

Try the live demo to explore GTFS data, view routes with colors, and see trip schedules in action!

## Features

- ✅ Load GTFS data from ZIP files (URL or local path)
Expand Down Expand Up @@ -125,6 +131,71 @@ const gtfs = await GtfsSqlJs.fromDatabase(dbBuffer);

### Querying Data

The library provides two ways to query GTFS data:
1. **Flexible filter-based methods** (recommended) - Pass an object with optional filters
2. **Convenience methods** - Direct methods for common use cases

#### Flexible Filter-Based Queries (Recommended)

The new flexible API allows you to pass multiple optional filters in a single method call:

```typescript
// Get stops - combine any filters
const stops = gtfs.getStops({
name: 'Station', // Search by name
limit: 10 // Limit results
});

// Get routes - with or without filters
const allRoutes = gtfs.getRoutes();
const agencyRoutes = gtfs.getRoutes({ agencyId: 'AGENCY_1' });

// Get trips - combine multiple filters
const trips = gtfs.getTrips({
routeId: 'ROUTE_1', // Filter by route
date: '20240115', // Filter by date (gets active services)
directionId: 0, // Filter by direction
limit: 50 // Limit results
});

// Get stop times - flexible filtering
const stopTimes = gtfs.getStopTimes({
stopId: 'STOP_123', // At a specific stop
routeId: 'ROUTE_1', // For a specific route
date: '20240115', // On a specific date
directionId: 0 // In a specific direction
});
```

**Available Filter Options:**

- `getStops(filters?)`:
- `stopId`: string - Filter by stop ID
- `stopCode`: string - Filter by stop code
- `name`: string - Search by stop name (partial match)
- `tripId`: string - Get stops for a trip
- `limit`: number - Limit results

- `getRoutes(filters?)`:
- `routeId`: string - Filter by route ID
- `agencyId`: string - Filter by agency
- `limit`: number - Limit results

- `getTrips(filters?)`:
- `tripId`: string - Filter by trip ID
- `routeId`: string - Filter by route
- `date`: string - Filter by date (YYYYMMDD format)
- `directionId`: number - Filter by direction
- `limit`: number - Limit results

- `getStopTimes(filters?)`:
- `tripId`: string - Filter by trip
- `stopId`: string - Filter by stop
- `routeId`: string - Filter by route
- `date`: string - Filter by date (YYYYMMDD format)
- `directionId`: number - Filter by direction
- `limit`: number - Limit results

#### Get Stop Information

```typescript
Expand Down Expand Up @@ -313,11 +384,18 @@ example();

### Instance Methods

#### Flexible Filter-Based Methods (Recommended)
- `getStops(filters?)` - Get stops with optional filters (stopId, stopCode, name, tripId, limit)
- `getRoutes(filters?)` - Get routes with optional filters (routeId, agencyId, limit)
- `getTrips(filters?)` - Get trips with optional filters (tripId, routeId, date, directionId, limit)
- `getStopTimes(filters?)` - Get stop times with optional filters (tripId, stopId, routeId, date, directionId, limit)

#### Stop Methods
- `getStopById(stopId)` - Get stop by stop_id
- `getStopByCode(stopCode)` - Get stop by stop_code
- `searchStopsByName(name, limit?)` - Search stops by name
- `getAllStops(limit?)` - Get all stops
- `getStopsByTrip(tripId)` - Get stops for a trip (ordered by sequence)

#### Route Methods
- `getRouteById(routeId)` - Get route by route_id
Expand All @@ -335,6 +413,7 @@ example();
- `getTripsByRoute(routeId)` - Get trips for a route
- `getTripsByRouteAndDate(routeId, date)` - Get trips for route and date
- `getTripsByRouteAndDateAndDirection(routeId, date, directionId)` - Get trips with direction filter
- `getTripsByDate(date)` - Get all trips for a date

#### Stop Time Methods
- `getStopTimesByTrip(tripId)` - Get stop times for a trip
Expand All @@ -348,12 +427,19 @@ example();

## TypeScript Support

This library is written in TypeScript and provides full type definitions for all GTFS entities:
This library is written in TypeScript and provides full type definitions for all GTFS entities and filter options:

```typescript
import type { Stop, Route, Trip, StopTime } from 'gtfs-sqljs';
import type { Stop, Route, Trip, StopTime, TripFilters, StopTimeFilters } from 'gtfs-sqljs';

const stop: Stop = gtfs.getStopById('STOP_123')!;

// Use filter types for better type safety
const filters: TripFilters = {
routeId: 'ROUTE_1',
directionId: 0
};
const trips = gtfs.getTrips(filters);
```

## GTFS Specification
Expand Down
98 changes: 96 additions & 2 deletions src/gtfs-sqljs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { loadGTFSZip } from './loaders/zip-loader';
import { loadGTFSData } from './loaders/data-loader';

// Query methods
import { getStopById, getStopByCode, searchStopsByName, getAllStops, getStopsByTrip } from './queries/stops';
import { getRouteById, getAllRoutes, getRoutesByAgency } from './queries/routes';
import { getStopById, getStopByCode, searchStopsByName, getAllStops, getStopsByTrip, getStops, type StopFilters } from './queries/stops';
import { getRouteById, getAllRoutes, getRoutesByAgency, getRoutes, type RouteFilters } from './queries/routes';
import {
getActiveServiceIds,
getCalendarByServiceId,
Expand All @@ -22,16 +22,23 @@ import {
getTripsByRouteAndService,
getTripsByRouteServiceAndDirection,
getTripsByService,
getTrips,
type TripFilters,
} from './queries/trips';
import {
getStopTimesByTrip,
getStopTimesByStop,
getStopTimesForStopRouteDirection,
getStopTimes,
type StopTimeFilters,
} from './queries/stop-times';

// Types
import type { Stop, Route, Trip, StopTime, Calendar, CalendarDate } from './types/gtfs';

// Export filter types for users
export type { StopFilters, RouteFilters, TripFilters, StopTimeFilters };

export interface GtfsSqlJsOptions {
/**
* Path or URL to GTFS ZIP file
Expand Down Expand Up @@ -191,6 +198,14 @@ export class GtfsSqlJs {
return getAllStops(this.db, limit);
}

/**
* Get stops with optional filters
*/
getStops(filters?: StopFilters): Stop[] {
if (!this.db) throw new Error('Database not initialized');
return getStops(this.db, filters);
}

// ==================== Route Methods ====================

/**
Expand All @@ -217,6 +232,14 @@ export class GtfsSqlJs {
return getRoutesByAgency(this.db, agencyId);
}

/**
* Get routes with optional filters
*/
getRoutes(filters?: RouteFilters): Route[] {
if (!this.db) throw new Error('Database not initialized');
return getRoutes(this.db, filters);
}

// ==================== Calendar Methods ====================

/**
Expand Down Expand Up @@ -304,6 +327,39 @@ export class GtfsSqlJs {
return getStopsByTrip(this.db, tripId);
}

/**
* Get trips with optional filters
*
* @param filters - Optional filters
* @param filters.tripId - Filter by trip ID
* @param filters.routeId - Filter by route ID
* @param filters.date - Filter by date (YYYYMMDD format) - will get active services for that date
* @param filters.directionId - Filter by direction ID
* @param filters.limit - Limit number of results
*
* @example
* // Get all trips for a route on a specific date
* const trips = gtfs.getTrips({ routeId: 'ROUTE_1', date: '20240115' });
*
* @example
* // Get all trips for a route going in one direction
* const trips = gtfs.getTrips({ routeId: 'ROUTE_1', directionId: 0 });
*/
getTrips(filters?: TripFilters & { date?: string }): Trip[] {
if (!this.db) throw new Error('Database not initialized');

// Handle date parameter by converting it to serviceIds
const { date, ...restFilters } = filters || {};
const finalFilters = { ...restFilters };

if (date) {
const serviceIds = getActiveServiceIds(this.db, date);
finalFilters.serviceIds = serviceIds;
}

return getTrips(this.db, finalFilters);
}

// ==================== Stop Time Methods ====================

/**
Expand All @@ -330,4 +386,42 @@ export class GtfsSqlJs {
const serviceIds = getActiveServiceIds(this.db, date);
return getStopTimesForStopRouteDirection(this.db, stopId, routeId, serviceIds, directionId);
}

/**
* Get stop times with optional filters
*
* @param filters - Optional filters
* @param filters.tripId - Filter by trip ID
* @param filters.stopId - Filter by stop ID
* @param filters.routeId - Filter by route ID
* @param filters.date - Filter by date (YYYYMMDD format) - will get active services for that date
* @param filters.directionId - Filter by direction ID
* @param filters.limit - Limit number of results
*
* @example
* // Get stop times for a specific trip
* const stopTimes = gtfs.getStopTimes({ tripId: 'TRIP_123' });
*
* @example
* // Get stop times at a stop for a specific route on a date
* const stopTimes = gtfs.getStopTimes({
* stopId: 'STOP_123',
* routeId: 'ROUTE_1',
* date: '20240115'
* });
*/
getStopTimes(filters?: StopTimeFilters & { date?: string }): StopTime[] {
if (!this.db) throw new Error('Database not initialized');

// Handle date parameter by converting it to serviceIds
const { date, ...restFilters } = filters || {};
const finalFilters = { ...restFilters };

if (date) {
const serviceIds = getActiveServiceIds(this.db, date);
finalFilters.serviceIds = serviceIds;
}

return getStopTimes(this.db, finalFilters);
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @license MIT
*/

export { GtfsSqlJs, type GtfsSqlJsOptions } from './gtfs-sqljs';
export { GtfsSqlJs, type GtfsSqlJsOptions, type StopFilters, type RouteFilters, type TripFilters, type StopTimeFilters } from './gtfs-sqljs';

// Export types
export type {
Expand Down
30 changes: 23 additions & 7 deletions src/loaders/zip-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,30 @@ export async function loadGTFSZip(source: string | ArrayBuffer | Uint8Array): Pr
* Fetch ZIP file from URL or file path
*/
async function fetchZip(source: string): Promise<ArrayBuffer> {
// Check if source is a URL or file path
// Check if source is a URL
const isUrl = source.startsWith('http://') || source.startsWith('https://');

// If it's a file path, try to use fs first
if (!isUrl) {
// For URLs, always use fetch
if (isUrl) {
if (typeof fetch !== 'undefined') {
const response = await fetch(source);
if (!response.ok) {
throw new Error(`Failed to fetch GTFS ZIP: ${response.status} ${response.statusText}`);
}
return await response.arrayBuffer();
}
throw new Error('fetch is not available to load URL');
}

// For non-URLs, try Node.js fs first, fall back to fetch for browser
// Check if we're in Node.js environment
const isNode = typeof process !== 'undefined' &&
process.versions != null &&
process.versions.node != null;

if (isNode) {
// In Node.js, treat as file path
try {
// Dynamic import for Node.js fs module
const fs = await import('fs');
const buffer = await fs.promises.readFile(source);
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
Expand All @@ -65,17 +82,16 @@ async function fetchZip(source: string): Promise<ArrayBuffer> {
}
}

// For URLs, use fetch
// In browser, treat as relative URL and use fetch
if (typeof fetch !== 'undefined') {
// Browser or Node.js with fetch support
const response = await fetch(source);
if (!response.ok) {
throw new Error(`Failed to fetch GTFS ZIP: ${response.status} ${response.statusText}`);
}
return await response.arrayBuffer();
}

throw new Error('No method available to fetch ZIP file from URL');
throw new Error('No method available to load ZIP file');
}

/**
Expand Down
Loading