gtfs-utils
Version:
Utilities to process GTFS data sets.
859 lines (675 loc) • 30.1 kB
Markdown
# gtfs-utils API
- [store API](#store-api)
- [`readCsv(path)`](#readcsv)
- [`readStops(readFile, filter)`](#readstops)
- [`readTrips(readFile, filter)`](#readtrips)
- [`parseDate(dateStr, timezone)`](#parsedate)
- [`formatDate(t, timezone)`](#formatdate)
- [`parseTime(timeStr)`](#parsetime)
- [`routeTypes`](#routetypes)
- [`readServicesAndExceptions(readFile, timezone, filters)`](#readservicesandexceptions)
- [`computeConnections(readFile, timezone, filter)`](#computeconnections)
- [`computeSchedules(readFile, filters, [computeSignature])`](#computeschedules)
- [`computeSortedConnections(readFile, filters, timezone)`](#computesortedconnections)
- [`findAlternativeTrips(trips, services, schedules) => (fromId, tDep, toId, tArr)`](#findalternativetrips)
- [`computeServiceBreaks(sortedConnections)`](#computeservicebreakssortedconnections)
- [`computeStopovers(readFile, timezone, filters)`](#computestopovers)
- [`readPathways(readFile, filters)`](#readpathways)
- [`readShapes(readFile, filters)`](#readshapes)
- [`computeTrajectories(readFile, filters)`](#computetrajectories)
- [`optimiseServicesAndExceptions(readFile, timezone, filters)`](#optimiseservicesandexceptions)
## `readCsv`
```js
const readCsv = require('gtfs-utils/read-csv')
readCsv('path-to-file.txt')
.on('error', console.error)
.on('data', row => console.log(row))
// or
for await (const row of await readCsv('path-to-file.txt')) {
console.log(row)
}
```
`readCsv(path)` is an async function that returns a [readable stream](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_readable_streams) in [`objectMode`](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_object_mode).
`path` can also be a [readable stream](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_readable_streams) like [`process.stdin`](https://nodejs.org/api/process.html#process_process_stdin).
## `readStops`
```js
const readCsv = require('gtfs-utils/read-csv')
const readStops = require('gtfs-utils/read-stops')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const stops = await readStops(readFile, {
stop: s => s.stop_id[0] === 'a',
})
for await (const stop of stops.values()) {
console.log(stop)
break
}
```
```js
{
stop_id: 'airport',
stop_name: 'International Airport (ABC)',
stop_lat: '52',
stop_lon: '14',
stop_code: '🛫',
stop_desc: 'train station at the Internationl Airport (ABC)',
stop_url: 'https://fta.example.org/stations/airport.html',
location_type: '1',
stop_timezone: 'Europe/Berlin',
wheelchair_boarding: '1',
parent_station: '',
level_id: '',
platform_code: '',
stops: ['airport-1', 'airport-2'],
entrances: ['airport-entrance'],
boardingAreas: [],
}
```
`readStops(readFile, filters = {}, opt = {})`
1. reads `stops.txt` into a [store](#store-api) `stop_id => stop`,
2. adds stop IDs of a station as `station.stop`,
3. adds entrance IDs of a stop as `stop.entrances`,
4. adds boarding area IDs of a stop as `stop.boardingAreas`.
## `readTrips`
```js
const readCsv = require('gtfs-utils/read-csv')
const readTrips = require('gtfs-utils/read-trips')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const trips = await readTrips(readFile, {
trip: t => t.route_id === 'A',
})
for await (const trip of trips.values()) {
console.log(trip)
break
}
```
```js
{
trip_id: 'a-downtown-all-day',
route_id: 'A',
service_id: 'all-day'
}
```
`readTrips(readFile, filters = {}, opt = {})` reads `trips.txt` and reduces it into a map `tripId => trip`. Returns a [store](#store-api).
## `parseDate`
```js
const parseDate = require('gtfs-utils/parse-date')
parseDate('20190303', 'Europe/Berlin')
// 1551567600
```
`parseDate(dateStr, timezone)` parses a GTFS Date value.
- `dateStr` must be in the `YYYYMMDD` format, as specific in [GTFS](https://developers.google.com/transit/gtfs/).
- `timezone` must a timezone name from the [tz database](https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones).
## `formatDate(t, timezone)`
```js
const formatDate = require('gtfs-utils/format-date')
formatDate(1551567600, 'Europe/Berlin')
// '20190303'
```
`formatDate(t, timezone)` formats a timestamp as a GTFS Date value.
- `t` must be a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time).
- `timezone` must a timezone name from the [tz database](https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones).
## `parseTime`
```js
const parseTime = require('gtfs-utils/parse-date')
parseTime('21:30')
// {hours: 21, minutes: 30, seconds: null}
parseTime('21:30:45')
// {hours: 21, minutes: 30, seconds: 45}
```
`parseTime(timeStr)` parses a GTFS Time value.
## `routeTypes`
```js
const routeTypes = require('gtfs-utils/route-types')
console.log(routeTypes.basic.find(type => type.gtfs === 3))
// { gtfs: 3, fptf: 'bus' }
```
`fptf` contains the [*Friendly Public Transport Format (FPTF)* mode](https://github.com/public-transport/friendly-public-transport-format/tree/1.2.1/spec#modes).
## `pickupTypes`
```js
const pickupTypes = require('gtfs-utils/pickup-types')
console.log(pickupTypes.MUST_PHONE_AGENCY) // 2
```
Contains all possible values of the `pickup_type` field in [`stop_times.txt`](https://gtfs.org/reference/static/#stop_timestxt).
## `dropOffTypes`
```js
const dropOffTypes = require('gtfs-utils/drop-off-types')
console.log(dropOffTypes.MUST_COORDINATE_WITH_DRIVER) // 3
```
Contains all possible values of the `drop_off_type` field in [`stop_times.txt`](https://gtfs.org/reference/static/#stop_timestxt).
## `bookingTypes`
```js
const bookingTypes = require('gtfs-utils/booking-types')
console.log(bookingTypes.SAME_DAY) // 1
```
Contains all possible values of the `booking_type` field in [`booking_rules.txt` of the GTFS-BookingRules extension (from GTFS-Flex)](https://github.com/MobilityData/gtfs-flex/blob/e1832cfea5ddb9df29bd2fc50e80b0a4987695c1/spec/reference.md#booking_rulestxt-file-added).
## `readServicesAndExceptions`
```js
const readCsv = require('gtfs-utils/read-csv')
const readServices = require('gtfs-utils/read-services-and-exceptions')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const filters = {
service: s => s.monday === '1',
serviceException: e => e.exception_type === '2'
}
const services = readServices(readFile, 'Europe/Berlin', filters)
for await (const [id, days, svc] of services) console.log(id, days, svc)
```
`readServicesAndExceptions(readFile, timezone, filters = {})` reads `calendar.txt` and `calendar_dates.txt` and condenses each service into the a list of days it is valid for. Returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) of `[serviceId, daysOfOperation]` entries.
The code above will print the following:
```js
service-1 [
1551394800,
1551481200,
1551567600,
1551654000,
// …
] {
service_id: 'service-1',
start_date: '20190301',
end_date: '20190531',
monday: '1',
tuesday: '1',
wednesday: '0',
thursday: '0',
friday: '1',
saturday: '1',
sunday: '1',
}
service-2 [
1551567600,
1552690800,
1555797600
] {
service_id: 'service-1',
start_date: '20190227',
end_date: '20190510',
monday: '0',
// …
}
// …
```
*Note:* Be careful when filtering services by day of the week! Time values in `stop_times.txt` can be >24h, so the day in `calendar.txt` does not necessarily indicate the day of every stopover.
## `computeConnections`
```js
const readCsv = require('gtfs-utils/read-csv')
const computeConnections = require('gtfs-utils/compute-connections')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const connectionsByTrip = computeConnections(readFile, {
stopTime: s => s.stop_id === 'some-stop-id',
})
for await (const connectionsOfTrip of connectionsByTrip) {
for (const connection of connectionsOfTrip) console.log(connection)
break
}
```
```js
{
tripId: 'a-downtown-all-day',
fromStop: 'airport',
// .departure and .arrival are *not* "wall clock times", but seconds since
// noon minus 12 hours!
// see https://gist.github.com/derhuerst/574edc94981a21ef0ce90713f1cff7f6)
departure: 55440, // 15h, 24m
toStop: 'museum',
arrival: 55800, // 15h, 30m
}
{
tripId: 'a-downtown-all-day',
fromStop: 'museum',
departure: 55860, // 15h, 31m
toStop: 'center',
arrival: 56100, // 15h, 35m
}
```
A "connection" is a pair of `stop_time`s. `computeConnections(readFile, filters = {})` iterates over `stop_times.txt` and `frequencies.txt`, and emits all connections in the whole dataset. It returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator).
## `computeSchedules`
This utility computes what we call "schedules", temporal patterns by which vehicles visit stops. An example schedule:
```js
{
id: '248tGP', // signature, just a hash of the schedule's data
trips: [
// The trip `a downtown-all-day-1` follows this schedule and starts
// 55380 seconds after `noon - 12h` on each day it runs.
{tripId: 'a-downtown-all-day-1', start: 55380}
],
// Arrives at `airport` after 0s, departs 30s later.
// Arrives at `museum` after 420s, departs 60s later.
// Arrives at `center` after 720s, departs 90s later.
stops: ['airport', 'museum', 'center'],
arrivals: [0, 420, 720],
departures: [30, 480, 810],
}
```
In schedule-based public transport systems, schedules reduce the implicit complexity of GTFS data sets a lot, because one schedule summarizes many trips with a certain "pattern". Paired with [`readServicesAndExceptions`](#readservicesandexceptions), you can easily answer questions like *Which vehicles run from X to Y at T?* and *Which other vehicles run as well?*.
```js
const readCsv = require('gtfs-utils/read-csv')
const computeSchedules = require('gtfs-utils/compute-schedules')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const schedules = await computeSchedules(readFile)
for await (const schedule of schedules.values()) {
console.log(schedule)
}
```
`computeSchedules(readFile, filters = {}, opt = {})` reads `trips.txt` and `stop_times.txt` and computes schedules from it. Returns a [store](#store-api).
- `readFile` must be a function that, when called with a file name, returns a [readable stream](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_readable_streams) in [`objectMode`](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_object_mode).
- `filters` must be an object; It may have the fields `trip`, `stopTime` & `frequencies`, each with a filter function.
- `opt` must be an object; It may optionally have a custom `computeSig` function that, given a schedule, computes a signature of it.
*Note:* In order to work, it must load (a reduced form of) `trips.txt`, `stop_times.txt` and `frequencies.txt` into memory. See [*store API*](#store-api) for more details.
## `computeStopovers`
```js
const readCsv = require('gtfs-utils/read-csv')
const computeStopovers = require('gtfs-utils/compute-stopovers')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const stopovers = computeStopovers(readFile, 'Europe/Berlin', {
trip: t => t.route_id === 'A',
stopTime: s => s.stop_id === 'some-stop-id',
})
for await (const stopover of stopovers) {
console.log(stopover)
}
```
`computeStopovers(readFile, timezone, filters = {})` reads *per-day* stop times from `trips.txt`, `stop_times.txt` and `frequencies.txt`, and applies them to the days of operation returned by [`readServicesAndExceptions(readFile, timezone, filters)`](#readservicesandexceptions), in order to compute *absolute* stop times.
- `readFile` must be a function that, when called with a file name, returns a [readable stream](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_readable_streams) in [`objectMode`](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_object_mode).
- `timezone` must a timezone name from the [tz database](https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones).
- `filters` must be an object; It may have the fields `trip`, `service`, `serviceException`, `stopTime`, `frequencies`, each with a filter function.
Returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) of `{stop_id, trip_id, service_id, route_id, start_of_trip, arrival, departure}` entries.
The code above will print the following:
```js
{
stop_id: 'airport',
trip_id: 'a-downtown-all-day',
service_id: 'all-day',
route_id: 'A',
shape_id: 'a-downtown-all-day-s0',
start_of_trip: 1551394800, // 2019-03-01T00:00:00+01:00
arrival: 1551450180, // 2019-03-01T15:23:00+01:00
departure: 1551450240, // 2019-03-01T15:24:00+01:00
// Items have this additional entry if they're based on an exact_times=0
// entry in frequencies.txt. See https://gtfs.org/reference/static/#frequenciestxt
// headwayBased: true,
}
{
stop_id: 'museum',
trip_id: 'a-downtown-all-day',
service_id: 'all-day',
route_id: 'A',
shape_id: 'a-downtown-all-day-s0',
start_of_trip: 1551394800, // 2019-03-01T00:00:00+01:00
arrival: 1551450600, // 2019-03-01T15:30:00+01:00
departure: 1551450660, // 2019-03-01T15:31:00+01:00
}
// …
```
*Note:* In order to work, it must load reduced forms of `trips.txt`, `calendar.txt` and `calendar_dates.txt` into memory. See [*store API*](#store-api) for more details.
## `computeSortedConnections`
```js
const readCsv = require('gtfs-utils/read-csv')
const computeSortedConnections = require('gtfs-utils/compute-sorted-connections')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const sortedConnections = await computeSortedConnections(readFile, 'Europe/Berlin')
const from = 1552324800 // UNIX timestamp
const to = 1552393800 // UNIX timestamp
const fromI = sortedConnections.findIndex(c => c.departure >= from)
const endI = sortedConnections.findIndex(c => c.departure > to)
for (let i = 0; i < endI; i++) {
console.log(sortedConnections[i])
}
```
```js
{
tripId: 'b-outbound-on-working-days',
fromStop: 'lake',
departure: 1552324920, // 2019-03-11T18:22:00+01:00
toStop: 'airport',
arrival: 1552325400, // 2019-03-11T18:30:00+01:00
routeId: 'B',
serviceId: 'on-working-days',
}
{
tripId: 'b-downtown-on-working-days',
fromStop: 'airport',
departure: 1552392840, // 2019-03-12T13:14:00+01:00
toStop: 'lake',
arrival: 1552393200, // 2019-03-12T13:20:00+01:00
routeId: 'B',
serviceId: 'on-working-days',
}
```
`computeSortedConnections(readFile, timezone, filters = {})` reads all [connections](#computeConnections) and applies each to the respective [service](#readservicesandexceptions), to compute all *absolute-time* connections in the dataset. Returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/promise) that will resolve with an array of connections.
- `readFile` must be a function that, when called with a file name, returns a [readable stream](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_readable_streams) in [`objectMode`](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_object_mode).
- `timezone` must a timezone name from the [tz database](https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones).
- `filters` must be an object; It may have the fields `service`, `trip` & `stopover`, each with a filter function.
*Note:* `computeSortedConnections` must load (reduced forms of) `trips.txt`, `stop_times.txt` and `frequencies.txt` into memory. See [*store API*](#store-api) for more details.
## `findAlternativeTrips`
```
fromId --time window--> toId
departure at tDep arrival at tArr
```
For a time window `(tDep, tArr)` to get from stop `fromId` to stop `toId`, `findAlternativeTrips` will return a list of all trips that run from `fromId` to `toId` equally fast or faster.
```js
// signature:
async findAlternativeTrips(readFile, timezone, services, schedules) => async function* findAltTrips(fromId, tDep, toId, tArr) {}
```
`services` must be a [store](docs/api#store-api) with a `serviceId => daysOfOperation` mapping. `schedules` must be in the format returned by `computeSchedules`.
*Note:* The purpose of this function is to identify *direct* alternative trips to a given trip; **It *is not* a replacement for a proper routing engine.** (There might be a faster way from `fromId` to `toId` via transfer, and `findAlternativeTrips` won't return it.)
As an example, we're gonna use [`sample-gtfs-feed`](https://npmjs.com/package/sample-gtfs-feed):
```js
const readCsv = require('gtfs-utils/read-csv')
const inMemoryStore = require('gtfs-utils/lib/in-memory-store')
const readServices = require('gtfs-utils/read-services-and-exceptions')
const computeSchedules = require('gtfs-utils/compute-schedules')
const findAlternativeTrips = require('gtfs-utils/find-alternative-trips')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const timezone = 'Europe/Berlin'
// read services into in-memory store
const services = inMemoryStore()
for await (const [id, svc] of readServices(readFile, timezone)) {
await services.set(id, svc)
}
// read schedules
const schedules = await computeSchedules(readFile)
// travel times of a downtown trip of the A line
const fromId = 'airport'
const departure = 1551795840 // 2019-03-05T15:24:00+01:00
const toId = 'center'
const arrival = 1551796500 // 2019-03-05T15:35:00+01:00
// find an alternative trip of the C line
const findAltTrips = await findAlternativeTrips(
readFile,
timezone,
services,
schedules,
)
const altTrips = findAltTrips(fromId, departure, toId, arrival)
for await (const alt of altTrips) console.log(alt)
```
```js
{ // This is the trip we were using as query.
tripId: 'a-downtown-all-day',
routeId: 'A',
serviceId: 'all-day',
departure: 1551795840, // 2019-03-05T15:24:00+01:00
arrival: 1551796500, // 2019-03-05T15:35:00+01:00
}
{ // This is an alternative trip.
tripId: 'c-downtown-all-day',
routeId: 'C',
serviceId: 'all-day',
departure: 1551796080, // 2019-03-05T15:28:00+01:00
arrival: 1551796380, // 2019-03-05T15:33:00+01:00
}
```
## `computeServiceBreaks`
Most public transport networks don't run 24/7, but instead have regular scheduled "service breaks", e.g. at night or on Sundays.
Given [sorted connections](#computesortedconnections), `computeServiceBreaks(sortedConnections, opt = {})` finds periods of time without service between two stations.
It depends on the specific network what period of time can be considered a "break": In a large city, it could be no bus/train running from 2am to 3am; In a small town there might only be a bus/train every hour, with a break of 8 hours at night. You can pass a custom `opt.minLength` value in seconds.
```js
const readCsv = require('gtfs-utils/read-csv')
const computeSortedConnections = require('gtfs-utils/compute-sorted-connections')
const computeServiceBreaks = require('gtfs-utils/compute-service-breaks')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
let connections = await computeSortedConnections(readFile, 'Europe/Berlin')
// select time frame
const start = 1557313200 // 2019-05-08T12:00:00+01:00
const startI = connections.findIndex(c => c.departure >= start)
const end = 1557496800 // 2019-05-10T15:00:00+01:00
const endI = connections.findIndex(c => c.departure > end)
connections = connections.slice(startI, endI)
const serviceBreaks = computeServiceBreaks(connections, {
minLength: 30 * 60, // 30 minutes
})
for await (const serviceBreak of serviceBreaks) {
console.log(serviceBreak)
}
```
## store API
Some of the tools above need to read data into a [map](https://en.wikipedia.org/wiki/Associative_array) in order to work with it. In memory-constrained environments (such as [FaaS](https://en.wikipedia.org/wiki/Function_as_a_service)es), the amount of data to be read might be bigger than the available memory; This is why **`gtfs-utils` allows you to pass in your own store implementation**.
In this case you could pass in a [Redis](https://redis.io)-backed store instead:
```js
const createRedisStore = require('gtfs-utils/lib/redis-store')
computeSchedules(readFile, filters, {
// let computeSchedules use a Redis-backed store
createStore: createRedisStore,
})
```
These stores are available:
- `gtfs-utils/lib/in-memory-store` – stores data in memory; used by default
- `gtfs-utils/lib/redis-store` – stores data in [Redis](https://redis.io)
## `readPathways`
```js
const readCsv = require('gtfs-utils/read-csv')
const readPathways = require('gtfs-utils/read-pathways')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const pathways = readPathways(readFile)
for await (const [stationId, node, allNodes] of pathways) {
console.log(stationId, node)
}
```
`readPathways(readFile, filters = {}, opt = {})` reads all pathways into memory, reads all pathways of a station into a [directed graph](https://en.wikipedia.org/wiki/Directed_graph). Each node of this graph has two fields `id` and `connectedTo`:
```js
{ // node `stop-123`
id: 'stop-123',
connectedTo: {
'stop-321': [
{ // pathway connecting `stop-123` to `stop-321`
pathway_id: 'pw-1234',
from_stop_id: 'stop-123',
to_stop_id: 'stop-321',
pathway_mode: '4',
},
{ // node `stop-321`
id: 'stop-321',
connectedTo: {
// …
},
},
// …
],
},
}
```
`readPathways` returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) of `[stationId, node, allNodesById]` triple.
- For each station, it will only emit *one* triple, with `node` being the first pathway (that is connected to this station) that it came across.
- `allNodesById` as an object, with all nodes of the graph stored by their IDs.
## `readShapes`
```js
const readCsv = require('gtfs-utils/read-csv')
const readShapes = require('gtfs-utils/read-shapes')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const shapes = readShapes(readFile)
for await (const [shapeId, points] of shapes) {
console.log(shapeId, points)
}
```
`readShapes(readFile, filters = {})` reads all shapes from `shapes.txt`. It returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) of `[stationId, points]` pairs, with `points` looking like this:
```js
[
{
shape_pt_lat: 48.59430,
shape_pt_lon: 8.86477,
shape_pt_sequence: 1,
shape_dist_traveled: 0,
}, {
shape_pt_lat: 48.59394,
shape_pt_lon: 8.86377,
shape_pt_sequence: 3,
shape_dist_traveled: 83.98,
},
// …
{
shape_pt_lat: 48.59431,
shape_pt_lon: 8.86476,
shape_pt_sequence: 82,
shape_dist_traveled: 3112.71,
}
]
```
## `computeTrajectories`
```js
const readCsv = require('gtfs-utils/read-csv')
const computeTrajectories = require('gtfs-utils/compute-trajectories')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
for await (const trajectory of computeTrajectories(readFile)) {
console.log(trajectory)
}
```
`computeTrajectories(readFile, filters = {})` reads *per-day* stop times from `trips.txt`, `stop_times.txt` and `frequencies.txt`, and applies them to the days of operation returned by [`readServicesAndExceptions(readFile, timezone, filters)`](#readservicesandexceptions), in order to compute *absolute* stop times.
- `readFile` must be a function that, when called with a file name, returns a [readable stream](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_readable_streams) in [`objectMode`](https://nodejs.org/docs/latest-v10.x/api/stream.html#stream_object_mode).
- `filters` must be an object; It may have the fields `trip`, `service`, `serviceException`, `stopTime`, `frequencies`, each with a filter function.
Returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) of trajectories; Each trajectory is a [GeoJSON](https://geojson.org) [`LineString`](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.4), with additional items in each coordinate:
1. longitude
2. latitude
3. altitude
4. arrival time
5. departure time
As an example, we're gonna use [`sample-gtfs-feed`](https://npmjs.com/package/sample-gtfs-feed):
```js
const readCsv = require('gtfs-utils/read-csv')
const computeTrajectories = require('gtfs-utils/compute-trajectories')
const readFile = async (name) => {
const path = require.resolve('sample-gtfs-feed/gtfs/' + name + '.txt')
return await readCsv(path)
}
const filters = {
trip: t => t.route_id === 'A',
}
for await (const trajectory of computeTrajectories(readFile, filters)) {
console.log(trajectory)
}
```
```js
{
type: 'Feature',
properties: {
id: 'ZGB8W9-a-downtown-all-day-s0',
scheduleId: 'Z2gvHvF',
shapeId: 'a-downtown-all-day-s0',
tripId: 'a-downtown-all-day',
serviceId: 'all-day',
},
geometry: {
type: 'LineString',
coordinates: [
[13.510294914, 52.364833832, null, 61, 61],
[13.510567665, 52.364398956, null, 63, 63],
[13.510860443, 52.363952637, null, 64, 64],
// …
[13.452836037, 52.44562149, null, 387, 387],
[13.451435089, 52.445671082, null, 390, 390],
[13.449950218, 52.445732117, null, 392, 392],
// …
[13.495876312, 52.500293732, null, 713, 713],
[13.496304512, 52.500156403, null, 714, 714],
[13.497889519, 52.499641418, null, 717, 717],
],
},
}
{
type: 'Feature',
properties: {
id: 'R8lSc-a-outbound-all-day-s0',
scheduleId: 'Z1bgqY0',
shapeId: 'a-outbound-all-day-s0',
tripId: 'a-outbound-all-day',
serviceId: 'all-day',
},
geometry: {
type: 'LineString',
coordinates: [
[13.497889519, 52.499641418, null, 65, 65],
[13.496304512, 52.500156403, null, 69, 69],
[13.495876312, 52.500293732, null, 70, 70],
[13.495686531, 52.500354767, null, 71, 71],
[13.495450974, 52.500431061, null, 71, 71],
// …
[13.465647697, 52.49892807, null, 153, 153],
[13.465513229, 52.498714447, null, 154, 154],
// …
[13.509624481, 52.366386414, null, 720, 720],
[13.509587288, 52.366352081, null, 720, 720],
[13.509503365, 52.366222382, null, 720, 720],
[13.509493828, 52.366146088, null, 721, 721],
[13.509539604, 52.366039276, null, 721, 721],
[13.510294914, 52.364833832, null, 725, 725],
],
},
}
```
*Note:* In order to work, `computeTrajectories` must load reduced forms of `trips.txt`, `stop_times.txt`, `frequencies.txt` and `shapes.txt` into memory. See [*store API*](#store-api) for more details.
## `optimiseServicesAndExceptions`
A GTFS feed may have a set of `calendar.txt` and/or `calendar_dates.txt` rows that express service days in an overly verbose way. Some examples:
- feeds without `calendar.txt`, where every service day is expressed as a `exception_type=1` (added) exception – In many of such cases, we can reduce the number of exceptions by adding a row in `calendar.txt` with the respective day(s) turned on (e.g. `tuesday=1`).
- feeds with `calendar.txt`, where some services have more `exception_type=2` (removed) exceptions than "regular" day-of-the-week-based service dates (e.g. `thursday=1`) – In this case, we can turn off the "regular" service dates (`thursday=0`) and use `exception_type=1` (added) exceptions.
For each service, **`optimiseServicesAndExceptions` computes the optimal combination of day of the week flags (e.g. `monday=1`) and exceptions, minimalising the number of exceptions necessary to express the set of service dates**.
```js
const readCsv = require('gtfs-utils/read-csv')
const optimiseServices = require('gtfs-utils/optimise-services-and-exceptions')
const readFile = name => readCsv('path/to/gtfs/' + name + '.txt')
const services = readServices(readFile, 'Europe/Berlin')
for await (const [id, changed, service, exceptions] of services) {
if (changed) {
console.log(id, 'changed!')
console.log('service:', service)
console.log('exceptions:', exceptions)
} else {
console.log(id, 'unchanged!', id)
}
}
```
`optimiseServicesAndExceptions(readFile, timezone, filters = {})` reads `calendar.txt` and `calendar_dates.txt`. It returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) of `[serviceId, changed, service, exceptions]` entries.
- If `changed` is `true`,
- the service's `calendar.txt` row or `calendar_dates.txt` rows (or both) have been optimised,
- `service` contains the optimised service,
- `exceptions` contains all `calendar_dates.txt` rows applying to the *optimised* service.
- If `changed` is `false`,
- the service cannot be optimised,
- `service` contains the `calendar.txt` as it was before, or a mock service if there was none before,
- `exceptions` contains the `calendar_dates.txt` rows as they were before.
The [test fixture](../test/fixtures/optimise-services-and-exceptions) contains three services (`more-exceptions-than-regular`, `more-regular-than-exceptions`, should-stay-unchanged), of which the first two can be optimised. With its files as input, the code above will print the following:
```
more-exceptions-than-regular changed!
service: {
service_id: 'more-exceptions-than-regular',
start_date: '20220301',
end_date: '20220410',
monday: '0',
tuesday: '0',
wednesday: '0',
thursday: '0',
friday: '0',
saturday: '0',
sunday: '0',
}
exceptions: [{
service_id: 'more-exceptions-than-regular',
date: '20220302',
exception_type: '1',
}, {
service_id: 'more-exceptions-than-regular',
date: '20220324',
exception_type: '1',
}, {
service_id: 'more-exceptions-than-regular',
date: '20220330',
exception_type: '1',
}, {
service_id: 'more-exceptions-than-regular',
date: '20220331',
exception_type: '1',
}]
more-regular-than-exceptions changed!
service: {
service_id: 'more-regular-than-exceptions',
monday: '1',
tuesday: '0',
wednesday: '0',
thursday: '0',
friday: '1',
saturday: '0',
sunday: '0',
start_date: '20220301',
end_date: '20220410',
}
exceptions: []
should-stay-unchanged unchanged! should-stay-unchanged
```