# Municipal Transit Planner

A route-planning service for municipal transit networks. It combines schedule data, service calendars, walking connections, and real-time vehicle updates to help riders compare practical trips across buses, streetcars, subways, and municipal rail.

## Features

- Plan trips by origin, destination, departure time, or arrival time
- Compare fastest, fewest-transfer, and accessible routes
- Import routes, stops, trips, calendars, and fares from GTFS feeds
- Apply GTFS Realtime delays, cancellations, and vehicle positions
- Include walking transfers between nearby stops
- Respect wheelchair accessibility and maximum walking preferences
- Support multiple agencies, service areas, and time zones
- Expose JSON APIs suitable for web, mobile, and kiosk clients
- Provide health checks and structured application logs

## Requirements

- Node.js 20 or later
- PostgreSQL 15 or later with PostGIS
- Redis 7 or later
- A valid GTFS static feed
- Optional GTFS Realtime endpoints

## Installation

Clone the repository and install dependencies:

```bash
git clone https://github.com/example/municipal-transit-planner.git
cd municipal-transit-planner
npm install
```

Create a local environment file:

```bash
cp .env.example .env
```

Create the database and run migrations:

```bash
createdb transit_planner
npm run db:migrate
```

Import the transit agency's GTFS feed:

```bash
npm run gtfs:import -- ./data/agency-gtfs.zip
```

Start the development server:

```bash
npm run dev
```

The API is available at `http://localhost:3000`.

## Usage

Plan a trip by sending a request to the journey-planning endpoint:

```bash
curl "http://localhost:3000/api/v1/journeys?from=43.6532,-79.3832&to=43.6426,-79.3871&departAt=2026-08-01T08:30:00-04:00"
```

Example response:

```json
{
  "journeys": [
    {
      "durationMinutes": 24,
      "transfers": 1,
      "walkDistanceMeters": 410,
      "accessible": true,
      "legs": [
        {
          "mode": "walk",
          "durationMinutes": 5,
          "toStop": "City Hall"
        },
        {
          "mode": "bus",
          "route": "12",
          "headsign": "Central Station",
          "departure": "2026-08-01T08:36:00-04:00",
          "arrival": "2026-08-01T08:49:00-04:00"
        },
        {
          "mode": "walk",
          "durationMinutes": 6,
          "to": "Destination"
        }
      ]
    }
  ]
}
```

Common query parameters:

| Parameter | Description |
| --- | --- |
| `from` | Origin as `latitude,longitude` or a stop ID |
| `to` | Destination as `latitude,longitude` or a stop ID |
| `departAt` | Desired departure time in ISO 8601 format |
| `arriveBy` | Desired arrival time in ISO 8601 format |
| `wheelchair` | Set to `true` to require accessible journeys |
| `maxWalkMeters` | Maximum walking distance |
| `preference` | `fastest`, `fewest-transfers`, or `least-walking` |

Use either `departAt` or `arriveBy`, but not both.

Other useful commands:

```bash
npm test
npm run lint
npm run gtfs:validate -- ./data/agency-gtfs.zip
npm run gtfs:refresh
npm run build
npm start
```

## Configuration

Configuration is loaded from environment variables in development and from the deployment environment in production.

```dotenv
NODE_ENV=development
PORT=3000
LOG_LEVEL=info

DATABASE_URL=postgresql://localhost:5432/transit_planner
REDIS_URL=redis://localhost:6379

AGENCY_TIMEZONE=America/Toronto
GTFS_STATIC_PATH=./data/agency-gtfs.zip
GTFS_REFRESH_CRON=0 3 * * *

GTFS_RT_TRIP_UPDATES_URL=
GTFS_RT_VEHICLE_POSITIONS_URL=
GTFS_RT_ALERTS_URL=
GTFS_RT_REFRESH_SECONDS=30

DEFAULT_MAX_WALK_METERS=1200
DEFAULT_TRANSFER_BUFFER_MINUTES=3
MAX_JOURNEY_RESULTS=5
WHEELCHAIR_ROUTING_ENABLED=true
```

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `DATABASE_URL` | Yes | — | PostgreSQL connection string |
| `REDIS_URL` | Yes | — | Redis connection string for caching |
| `AGENCY_TIMEZONE` | Yes | — | IANA time zone used by the transit agency |
| `GTFS_STATIC_PATH` | Yes | — | Local path or mounted path to the GTFS archive |
| `GTFS_REFRESH_CRON` | No | `0 3 * * *` | Schedule for refreshing static data |
| `GTFS_RT_TRIP_UPDATES_URL` | No | — | GTFS Realtime trip updates endpoint |
| `GTFS_RT_VEHICLE_POSITIONS_URL` | No | — | GTFS Realtime vehicle positions endpoint |
| `GTFS_RT_ALERTS_URL` | No | — | GTFS Realtime service alerts endpoint |
| `GTFS_RT_REFRESH_SECONDS` | No | `30` | Realtime polling interval |
| `DEFAULT_MAX_WALK_METERS` | No | `1200` | Default maximum walking distance |
| `DEFAULT_TRANSFER_BUFFER_MINUTES` | No | `3` | Minimum connection time between trips |
| `MAX_JOURNEY_RESULTS` | No | `5` | Maximum alternatives returned per request |
| `WHEELCHAIR_ROUTING_ENABLED` | No | `true` | Enables accessibility-aware routing |

Never commit production credentials or private feed URLs. Use a secrets manager in deployed environments.