Skip to content

Commit

Permalink
compute-trajectories πŸ“βœ…
Browse files Browse the repository at this point in the history
  • Loading branch information
derhuerst committed Jun 9, 2021
1 parent 2013db5 commit 1449e34
Show file tree
Hide file tree
Showing 6 changed files with 1,435 additions and 0 deletions.
105 changes: 105 additions & 0 deletions compute-trajectories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
'use strict'

const debug = require('debug')('gtfs-utils:compute-trajectories')
const {unique: shorthash} = require('shorthash')

const readTrips = require('./read-trips')
const inMemoryStore = require('./lib/in-memory-store')
const {STOP, STATION} = require('./lib/location-types')
const readShapes = require('./read-shapes')
const computeSchedules = require('./compute-schedules')
const buildTrajectory = require('./lib/build-trajectory')

const computeTrajectories = async function* (readFile, filters = {}, opt = {}) {
if (typeof readFile !== 'function') {
throw new TypeError('readFile must be a function')
}
const {
stop: stopFilter,
} = {
stop: () => true,
...filters,
}
if (typeof stopFilter !== 'function') {
throw new TypeError('filters.stop must be a function')
}

const {
createStore,
} = {
createStore: inMemoryStore,
...opt,
}

debug('reading trips')
const trips = await readTrips(readFile, filters, {
...opt,
formatTrip: t => [t.shape_id, t.service_id],
})

debug('reading stop locations')
const stopLocsById = createStore() // stop ID -> stop location
for await (const s of await readFile('stops')) {
if (
s.location_type !== undefined && s.location_type !== ''
&& s.location_type !== STOP && s.location_type !== STATION
) continue
if (!stopFilter(s)) continue
const loc = [parseFloat(s.stop_lon), parseFloat(s.stop_lat)]
await stopLocsById.set(s.stop_id, loc)
}

debug('reading shapes')
const shapesById = createStore() // shape ID -> shape
// todo: only read shapes that belong to non-filtered trips
for await (const [shapeId, points] of readShapes(readFile, filters)) {
await shapesById.set(shapeId, points)
}

debug('computing schedules')
const schedules = await computeSchedules(readFile, filters, opt)

debug('computing trajectories')
let i = 0
for await (const schedule of schedules.values()) {
if (++i % 100 === 0) debug(i)

// Taking only the list of stops alon into account wouldn't suffice,
// as there might be two trips that visit the same stops with vastly
// different timing. Same with using the list of arrivals/departures only.
const temporalSig = shorthash(JSON.stringify({
stops: schedule.stops,
arrivals: schedule.arrivals,
departures: schedule.departures,
}))

for (const _ of schedule.trips) {
const {tripId, start: tripStartTime} = _
const [shapeId, serviceId] = await trips.get(tripId)
if (!shapeId) {
// todo: is this a bug?
debug('missing shape ID for trip ID', tripId)
continue
}
if (!serviceId) {
debug('missing service ID for trip ID', tripId)
continue
}
const shape = await shapesById.get(shapeId)
if (!shapeId) {
// todo: is this a bug?
debug('missing shape for shape ID', shapeId)
continue
}

// todo: support headway-based (frequencies.txt) trips
const tr = await buildTrajectory(shapeId, shape, schedule, stopLocsById, tripStartTime)
tr.properties.tripId = tripId
tr.properties.serviceId = serviceId
tr.properties.id = temporalSig + '-' + shapeId
yield tr
}
}
}

module.exports = computeTrajectories
109 changes: 109 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [`computeStopovers(readFile, timezone, filters)`](#computestopovers)
- [`readPathways(readFile, filters)`](#readpathways)
- [`readShapes(readFile, filters)`](#readshapes)
- [`computeTrajectories(readFile, filters)`](#computetrajectories)


## `readCsv`
Expand Down Expand Up @@ -638,3 +639,111 @@ for await (const [shapeId, points] of shapes) {
}
]
```


## `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.
19 changes: 19 additions & 0 deletions examples/compute-trajectories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict'

const readCsv = require('../read-csv')
const computeTrajectories = require('../compute-trajectories')

const readFile = async (file) => {
return await readCsv(require.resolve('sample-gtfs-feed/gtfs/' + file + '.txt'))
}

;(async () => {
const trajectories = await computeTrajectories(readFile)
for await (const trajectory of trajectories) {
console.log(trajectory)
}
})()
.catch((err) => {
console.error(err)
process.exit(1)
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"compute-schedules.js",
"read-pathways.js",
"read-shapes.js",
"compute-trajectories.js",
"lib",
"docs",
"examples",
Expand Down
Loading

0 comments on commit 1449e34

Please sign in to comment.