mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-22 22:59:35 +02:00
update docs
This commit is contained in:
parent
a59a3d78dc
commit
1b0858a253
25 changed files with 115 additions and 2576 deletions
18
docs/api.md
18
docs/api.md
|
@ -1,17 +1,19 @@
|
||||||
# `hafas-client` API
|
# `db-vendo-client` API
|
||||||
|
|
||||||
|
Also see the [root readme](https://github.com/public-transport/db-vendo-client) for a shortlist of differences of db-vendo-client to hafas-client and of differences between the profiles.
|
||||||
|
|
||||||
- [`journeys(from, to, [opt])`](journeys.md) – get journeys between locations
|
- [`journeys(from, to, [opt])`](journeys.md) – get journeys between locations
|
||||||
- [`refreshJourney(refreshToken, [opt])`](refresh-journey.md) – fetch up-to-date/more details of a `journey`
|
- [`refreshJourney(refreshToken, [opt])`](refresh-journey.md) – fetch up-to-date/more details of a `journey`
|
||||||
- [`journeysFromTrip(tripId, previousStopover, to, [opt])`](journeys-from-trip.md) – get journeys from a trip to a location
|
- `journeysFromTrip(tripId, previousStopover, to, [opt])` – not supported
|
||||||
- [`trip(id, lineName, [opt])`](trip.md) – get details for a trip
|
- [`trip(id, lineName, [opt])`](trip.md) – get details for a trip
|
||||||
- [`tripsByName(lineNameOrFahrtNr, [opt])`](trips-by-name.md) – get all trips matching a name
|
- `tripsByName(lineNameOrFahrtNr, [opt])` – not supported
|
||||||
- [`departures(station, [opt])`](departures.md) – query the next departures at a station
|
- [`departures(station, [opt])`](departures.md) – query the next departures at a station
|
||||||
- [`arrivals(station, [opt])`](arrivals.md) – query the next arrivals at a station
|
- [`arrivals(station, [opt])`](arrivals.md) – query the next arrivals at a station
|
||||||
- [`locations(query, [opt])`](locations.md) – find stations, POIs and addresses
|
- [`locations(query, [opt])`](locations.md) – find stations, POIs and addresses
|
||||||
- [`stop(id, [opt])`](stop.md) – get details about a stop/station
|
- [`stop(id, [opt])`](stop.md) – get details about a stop/station
|
||||||
- [`nearby(location, [opt])`](nearby.md) – show stations & POIs around
|
- [`nearby(location, [opt])`](nearby.md) – show stations & POIs around
|
||||||
- [`radar(north, west, south, east, [opt])`](radar.md) – find all vehicles currently in a certain area
|
- `radar(north, west, south, east, [opt])` – not supporteda
|
||||||
- [`reachableFrom(address, [opt])`](reachable-from.md) – get all stations reachable from an address within `n` minutes
|
- `reachableFrom(address, [opt])` – not supported
|
||||||
- [`remarks([opt])`](remarks.md) – get all remarks
|
- `remarks([opt])` – not supported
|
||||||
- [`lines(query, [opt])`](lines.md) – get all lines matching a name
|
- `lines(query, [opt])` – not supported
|
||||||
- [`serverInfo([opt])`](server-info.md) – fetch meta information from HAFAS
|
- `serverInfo([opt])` – not supported
|
||||||
|
|
1075
docs/changelog.md
1075
docs/changelog.md
File diff suppressed because it is too large
Load diff
|
@ -24,58 +24,47 @@ With `opt`, you can override the default options, which look like this:
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
when: new Date(),
|
when: new Date(),
|
||||||
direction: null, // only show departures heading to this station
|
direction: null, // not supported
|
||||||
line: null, // filter by line ID
|
line: null, // not supported
|
||||||
duration: 10, // show departures for the next n minutes
|
duration: 10, // show departures for the next n minutes
|
||||||
results: null, // max. number of results; `null` means "whatever HAFAS wants"
|
results: null, // max. number of results; `null` means "whatever HAFAS wants"
|
||||||
subStops: true, // parse & expose sub-stops of stations?
|
subStops: true, // not supported
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // not supported
|
||||||
linesOfStops: false, // parse & expose lines at the stop/station?
|
linesOfStops: false, // not supported
|
||||||
remarks: true, // parse & expose hints & warnings?
|
remarks: true, // parse & expose hints & warnings?
|
||||||
stopovers: false, // fetch & parse previous/next stopovers?
|
stopovers: false, // fetch & parse previous/next stopovers?
|
||||||
// departures at related stations
|
// departures at related stations
|
||||||
// e.g. those that belong together on the metro map.
|
// e.g. those that belong together on the metro map.
|
||||||
includeRelatedStations: true,
|
includeRelatedStations: true, // only true supported
|
||||||
language: 'en' // language to get results in
|
language: 'en' // language to get results in
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
The maximum supported duration is 720 for `db` and 60 for `dbnav` profile.
|
||||||
If you pass an object `opt.products`, its fields will partially override the default products defined in the profile. An example with the [BVG profile](../p/bvg):
|
If you pass an object `opt.products`, its fields will partially override the default products defined in the profile.
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
|
||||||
const client = createClient(vbbProfile, userAgent)
|
|
||||||
|
|
||||||
// will query with these products: suburban, subway, bus, express, regional
|
|
||||||
await client.departures('900000024101', {products: {tram: false, ferry: false}})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Response
|
## Response
|
||||||
|
|
||||||
*Note:* As stated in the [*Friendly Public Transport Format* v2 draft spec](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md), the `when` field includes the current delay. The `delay` field, if present, expresses how much the former differs from the schedule.
|
*Note:* As stated in the [*Friendly Public Transport Format* v2 draft spec](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md), the `when` field includes the current delay. The `delay` field, if present, expresses how much the former differs from the schedule.
|
||||||
|
|
||||||
You may pass a departure's `tripId` into [`trip(id, lineName, [opt])`](trip.md) to get details on the whole trip.
|
You may pass a departure's `tripId` into [`trip(id, lineName, [opt])`](trip.md) to get details on the whole trip. For the `dbnav` profile HAFAS trip ids will be returned, for the `db` profile, RIS trip ids will be returned, then the `trip()` endpoint support both id types.
|
||||||
|
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
For `db` profile, cancelled trips will not be contained in the response!
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
import {profile as dbnavProfile} from 'db-vendo-client/p/dbnav/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
const client = createClient(vbbProfile, userAgent)
|
const client = createClient(dbnavProfile, userAgent)
|
||||||
|
|
||||||
// S Charlottenburg
|
// S Charlottenburg
|
||||||
const {
|
const {
|
||||||
departures,
|
departures,
|
||||||
realtimeDataUpdatedAt,
|
realtimeDataUpdatedAt,
|
||||||
} = await client.departures('900000024101', {duration: 3})
|
} = await client.departures('8089165', {duration: 3})
|
||||||
```
|
```
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some) of the response's realtime data have been updated.
|
`realtimeDataUpdatedAt` is currently not set in db-vendo-client, because the upstream APIs don't provide it.
|
||||||
|
|
||||||
`departures` may look like this:
|
`departures` may look like this:
|
||||||
|
|
||||||
|
@ -87,11 +76,11 @@ const {
|
||||||
// Depending on the HAFAS endpoint, the destination may be present:
|
// Depending on the HAFAS endpoint, the destination may be present:
|
||||||
destination: {
|
destination: {
|
||||||
type: 'stop',
|
type: 'stop',
|
||||||
id: '900000029101',
|
id: '8089165',
|
||||||
name: 'S Spandau',
|
name: 'S Spandau',
|
||||||
location: {
|
location: {
|
||||||
type: 'location',
|
type: 'location',
|
||||||
id: '900029101',
|
id: '8089165',
|
||||||
latitude: 52.534794,
|
latitude: 52.534794,
|
||||||
longitude: 13.197477
|
longitude: 13.197477
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,160 +0,0 @@
|
||||||
# HAFAS `mgate.exe` protocol
|
|
||||||
|
|
||||||
The protocol of `mgate.exe` HAFAS endpoints is not openly (and freely) documented. The following documentation is based on general observations and reverse-engineering.
|
|
||||||
|
|
||||||
*Note:* There are also `rest.exe` (a.k.a. "open API", a.k.a. "REST API") endpoints. This documentation is *not* about them.
|
|
||||||
|
|
||||||
## date & time format
|
|
||||||
|
|
||||||
Dates are encoded as `YYYYMMDD`, time strings as `HHMMSS`. These are in the timezone configured on the HAFAS/server side, *per endpoint*.
|
|
||||||
|
|
||||||
Whenever HAFAS returns a time string that exceeds the day the response describes, it will add a "day offset". As an example, when you query departures at `2019-12-12T23:50+01:00` for the next 30 minutes, it will encode the departure at `2019-12-13T00:13+01:00` as `20191212` & `01001300`.
|
|
||||||
|
|
||||||
For working code, check out [`parseDateTime()`](../parse/date-time.js).
|
|
||||||
|
|
||||||
## coordinate format
|
|
||||||
|
|
||||||
All endpoints I've seen so far use [WGS84](http://wiki.gis.com/wiki/index.php/WGS84). Values are multiplied by `10^6` though, so you would encode `{latitude: 1.23, longitude: -2.34}` as `{Y: 1230000: X: -2340000}`. There's an optional parameter `z` with the elevation.
|
|
||||||
|
|
||||||
For working code, check out [`formatAddress()`](../format/address.js).
|
|
||||||
|
|
||||||
## querying the API
|
|
||||||
|
|
||||||
In many aspects, the API looks and feels like [RPCs](https://en.wikipedia.org/wiki/Remote_procedure_call). You must send queries via HTTP `POST`, with the minimal JSON body looking like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"auth": {
|
|
||||||
"type": "AID",
|
|
||||||
"aid": "…" // endpoint-specific authentication token, e.g. `1Rxs112shyHLatUX4fofnmdxK`
|
|
||||||
},
|
|
||||||
"ver": "…", // endpoint-specific string, e.g. `1.15`
|
|
||||||
"ext": "…", // endpoint-specific string, e.g. `BVG.1`
|
|
||||||
"client": {
|
|
||||||
"type": "IPA", // might also be `IPH` for "iPhone" or `WEB` for "web client"
|
|
||||||
"id": "…", // endpoint-specific string, e.g. `BVG`
|
|
||||||
"name": "…", // endpoint-specific string, e.g. `FahrInfo`
|
|
||||||
"v": "…" // endpoint-specific string, e.g. `4070700`
|
|
||||||
},
|
|
||||||
"lang": "…", // language, sometimes 2-digit (e.g. `de`), sometimes 3-digit (e.g. `deu`)
|
|
||||||
"svcReqL": [
|
|
||||||
{
|
|
||||||
"meth": "…", // name of the API call, supported values depend on the endpoint
|
|
||||||
"req": {
|
|
||||||
// actual request parameters…
|
|
||||||
}
|
|
||||||
// some endpoints also require this:
|
|
||||||
"cfg": {
|
|
||||||
"cfgGrpL": [],
|
|
||||||
"cfgHash": "…" // endpoint-specific string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- The data in `client` must be correct, otherwise HAFAS will reject your request.
|
|
||||||
- HAFAS will return slightly different response formats (and slightly different levels of detail) for different `ver`, `ext` and `client.v` values.
|
|
||||||
- All endpoints known support JSON & UTF-8, so make sure to send `Accept: application/json` & `Accept-Charset: utf-8` headers.
|
|
||||||
- Most endpoints support at least GZIP compression, so make sure to send a `Accept-Encoding: gzip` header.
|
|
||||||
|
|
||||||
For working code, check out [`request()`](lib/request.js).
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
There are three known types of authentication used among `mgate.exe` endpoints.
|
|
||||||
|
|
||||||
For working code, check out [`hafas-client`'s `request()`](lib/request.js), [`public-transport-enabler`'s Java implementation](https://github.com/schildbach/public-transport-enabler/blob/69614c87af627e2feafc576882f2ccccdbf4b7e6/src/de/schildbach/pte/AbstractHafasClientInterfaceProvider.java#L845-L860), [`TripKit`'s Swift implementation](https://github.com/alexander-albers/tripkit/blob/724b6cd8c258c9c61e7443c81e914618b79393cb/TripKit/AbstractHafasClientInterfaceProvider.swift#L1473-L1495) or [`marudor.de`'s TypeScript implementation](https://github.com/marudor/BahnhofsAbfahrten/blob/cf64d53c6902981ec529d3952253b2c83bff9221/src/server/HAFAS/profiles.ts#L30-L54).
|
|
||||||
|
|
||||||
### unprotected endpoints
|
|
||||||
|
|
||||||
You can just query these, as long as you send a formally correct request.
|
|
||||||
|
|
||||||
### endpoints using the `checksum` query parameter
|
|
||||||
|
|
||||||
`checksum` is a [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code): You can compute it by [hashing](https://en.wikipedia.org/wiki/Hash_function) the request body and a secret *salt*.
|
|
||||||
|
|
||||||
This secret can be read from the config file inside the accompanying client app. There is no guide for this yet, so please [open an issue](https://github.com/public-transport/hafas-client/issues/new).
|
|
||||||
|
|
||||||
### endpoints using the `mic` & `mac` query parameters
|
|
||||||
|
|
||||||
`mic` is a [message integrity code](https://en.wikipedia.org/wiki/Message_authentication_code), the [hash](https://en.wikipedia.org/wiki/Hash_function) of the request body.
|
|
||||||
|
|
||||||
`mac` is a [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code), the hash of `mic` and a secret *salt*.
|
|
||||||
|
|
||||||
This secret can be read from the config file inside the accompanying client app. There is no guide for this yet, so please [open an issue](https://github.com/public-transport/hafas-client/issues/new).
|
|
||||||
|
|
||||||
## API responses
|
|
||||||
|
|
||||||
A minimal valid response looks like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"ver": "…", // endpoint-specific string, e.g. `1.15`
|
|
||||||
"lang": "…", // language
|
|
||||||
"ext": "…", // endpoint-specific string, e.g. `BVG.1`
|
|
||||||
"id": "…", // unique ID for each response?
|
|
||||||
"svcResL": [
|
|
||||||
{
|
|
||||||
"meth": "StationBoard",
|
|
||||||
"err": "OK",
|
|
||||||
"res": {
|
|
||||||
// result of the API call
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For working code, check out [`request()`](lib/request.js).
|
|
||||||
|
|
||||||
### parse error
|
|
||||||
|
|
||||||
todo: generic server error
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"ver": "…", // endpoint-specific string, e.g. `1.15`
|
|
||||||
"lang": "…", // language, sometimes 2-digit (e.g. `de`), sometimes 3-digit (e.g. `deu`)
|
|
||||||
"err": "PARSE", // error code
|
|
||||||
"errTxt": "…", // error message, not always present
|
|
||||||
"svcResL": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### authentication error
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"ver": "…", // endpoint-specific string, e.g. `1.15`
|
|
||||||
"lang": "…", // language
|
|
||||||
"ext": "…", // endpoint-specific string, e.g. `BVG.1`
|
|
||||||
"err": "AUTH", // error code
|
|
||||||
"errTxt": "…", // error message, not always present
|
|
||||||
"svcResL": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### API-call-specific error
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"ver": "…", // endpoint-specific string, e.g. `1.15`
|
|
||||||
"lang": "…", // language
|
|
||||||
"ext": "…", // endpoint-specific string, e.g. `BVG.1`
|
|
||||||
"svcResL": [
|
|
||||||
{
|
|
||||||
"meth": "StationBoard",
|
|
||||||
"err": "…", // error code, e.g. `H9300`
|
|
||||||
"errTxt": "…", // error message, e.g. `Unknown arrival station`
|
|
||||||
"res": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## more ressources
|
|
||||||
|
|
||||||
- [@Nakaner's `strecken.info` API docs](https://github.com/Nakaner/bahnstoerungen/tree/62a72b1e0f0255668500b438187ff65aef39242a/api-doc/db-strecken-info)
|
|
||||||
- [unfinished HAFAS glossary](https://gist.github.com/derhuerst/74b703e2a0fc64e4a0fa8fbb1f3a61b4)
|
|
||||||
- [various `mgate.exe` HTTP traffic recordings](https://gist.github.com/search?q=post+mgate.exe&ref=searchresults)
|
|
|
@ -1,51 +0,0 @@
|
||||||
# `journeysFromTrip(tripId, previousStopover, to, [opt])`
|
|
||||||
|
|
||||||
`to` must be an [*Friendly Public Transport Format* (FPTF) `stop`](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md#stop) or [`station`](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md#station). See [`journeys()`](journeys.md) for details.
|
|
||||||
|
|
||||||
With `opt`, you can override the default options, which look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
accessibility: 'none', // 'none', 'partial' or 'complete'
|
|
||||||
stopovers: false, // return stations on the way?
|
|
||||||
polylines: false, // return leg shapes?
|
|
||||||
transferTime: 0, // minimum time for a single transfer in minutes
|
|
||||||
tickets: false, // return tickets?
|
|
||||||
remarks: true // parse & expose hints & warnings?
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Response
|
|
||||||
|
|
||||||
*Note:* The returned `departure` and `arrival` times include the current delay. The `departureDelay`/`arrivalDelay` fields express how much they differ from `plannedDeparture`/`plannedArrival`, respectively.
|
|
||||||
|
|
||||||
As an example, we're going to use the [*Deutsche Bahn* profile](../p/db):
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
import {profile as dbProfile} from 'hafas-client/p/db/index.js'
|
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
|
||||||
const client = createClient(dbProfile, userAgent)
|
|
||||||
|
|
||||||
const berlinSüdkreuz = '8011113'
|
|
||||||
const münchenHbf = '8000261'
|
|
||||||
const kölnHbf = '8000207'
|
|
||||||
|
|
||||||
// find any journey from Berlin Südkreuz to München Hbf
|
|
||||||
const [journey] = await client.journeys(berlinSüdkreuz, münchenHbf, {results: 1, stopovers: true})
|
|
||||||
// find the ICE leg
|
|
||||||
const leg = journey.legs.find(l => l.line.product === 'nationalExpress')
|
|
||||||
// find the stopover at the stop you've just passed
|
|
||||||
const previousStopover = leg.stopovers.find(st => st.departure && new Date(st.departure) < Date.now())
|
|
||||||
|
|
||||||
// find journeys from the ICE train to Köln Hbf
|
|
||||||
const {
|
|
||||||
journeys,
|
|
||||||
realtimeDataUpdatedAt,
|
|
||||||
} = await client.journeysFromTrip(leg.id, previousStopover, kölnHbf)
|
|
||||||
```
|
|
||||||
|
|
||||||
`journeys` is an array of [FPTF `journey`s](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md#journey), as documented in [`journeys()`](journeys.md).
|
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated.
|
|
|
@ -53,11 +53,11 @@ With `opt`, you can override the default options, which look like this:
|
||||||
stopovers: false, // return stations on the way?
|
stopovers: false, // return stations on the way?
|
||||||
transfers: -1, // Maximum nr of transfers. Default: Let HAFAS decide.
|
transfers: -1, // Maximum nr of transfers. Default: Let HAFAS decide.
|
||||||
transferTime: 0, // minimum time for a single transfer in minutes
|
transferTime: 0, // minimum time for a single transfer in minutes
|
||||||
accessibility: 'none', // 'none', 'partial' or 'complete'
|
accessibility: 'none', // not supported
|
||||||
bike: false, // only bike-friendly journeys
|
bike: false, // only bike-friendly journeys
|
||||||
walkingSpeed: 'normal', // 'slow', 'normal', 'fast'
|
walkingSpeed: 'normal', // not supported
|
||||||
// Consider walking to nearby stations at the beginning of a journey?
|
// Consider walking to nearby stations at the beginning of a journey?
|
||||||
startWithWalking: true,
|
startWithWalking: true, // always true (?)
|
||||||
products: {
|
products: {
|
||||||
// these entries may vary from profile to profile
|
// these entries may vary from profile to profile
|
||||||
suburban: true,
|
suburban: true,
|
||||||
|
@ -65,15 +65,19 @@ With `opt`, you can override the default options, which look like this:
|
||||||
tram: true,
|
tram: true,
|
||||||
bus: true,
|
bus: true,
|
||||||
ferry: true,
|
ferry: true,
|
||||||
express: true,
|
nationalExpress: true,
|
||||||
|
national: true,
|
||||||
regional: true
|
regional: true
|
||||||
|
regionalExpress: true // this is actually FlixTrain and co.
|
||||||
},
|
},
|
||||||
tickets: false, // return tickets? only available with some profiles
|
tickets: false, // return tickets? only available for [refreshJourney](refresh-journey.md)
|
||||||
polylines: false, // return a shape for each leg?
|
polylines: false, // return a shape for each leg? only available for [refreshJourney](refresh-journey.md)
|
||||||
subStops: true, // parse & expose sub-stops of stations?
|
subStops: true, // not supported
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // not supported
|
||||||
remarks: true, // parse & expose hints & warnings?
|
remarks: true, // parse & expose hints & warnings?
|
||||||
scheduledDays: false, // parse which days each journey is valid on
|
scheduledDays: false, // not yet supported
|
||||||
|
firstClass: true // first or second class for tickets
|
||||||
|
loyaltyCard: '' // BahnCards etc., see below
|
||||||
language: 'en', // language to get results in
|
language: 'en', // language to get results in
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -82,17 +86,16 @@ With `opt`, you can override the default options, which look like this:
|
||||||
|
|
||||||
*Note:* As stated in the [*Friendly Public Transport Format* v2 draft spec](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md), the returned `departure` and `arrival` times include the current delay. The `departureDelay`/`arrivalDelay` fields express how much they differ from the schedule.
|
*Note:* As stated in the [*Friendly Public Transport Format* v2 draft spec](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md), the returned `departure` and `arrival` times include the current delay. The `departureDelay`/`arrivalDelay` fields express how much they differ from the schedule.
|
||||||
|
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} 'hafas-client'
|
import {createClient} 'db-vendo-client'
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
import {profile as vbbProfile} from 'db-vendo-client/p/vbb/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
const client = createClient(vbbProfile, userAgent)
|
const client = createClient(vbbProfile, userAgent)
|
||||||
|
|
||||||
// Hauptbahnhof to Heinrich-Heine-Str.
|
// Frankfurt to Stuttgart
|
||||||
await client.journeys('900000003201', '900000100008', {
|
await client.journeys('8000105', '8000096', {
|
||||||
results: 1,
|
results: 1,
|
||||||
stopovers: true
|
stopovers: true
|
||||||
})
|
})
|
||||||
|
@ -101,7 +104,7 @@ await client.journeys('900000003201', '900000100008', {
|
||||||
`journeys()` will resolve with an object with the following fields:
|
`journeys()` will resolve with an object with the following fields:
|
||||||
- `journeys`
|
- `journeys`
|
||||||
- `earlierRef`/`laterRef` – pass them as `opt.earlierThan`/`opt.laterThan` into another `journeys()` call to retrieve the next "page" of journeys
|
- `earlierRef`/`laterRef` – pass them as `opt.earlierThan`/`opt.laterThan` into another `journeys()` call to retrieve the next "page" of journeys
|
||||||
- `realtimeDataUpdatedAt` – a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated
|
- `realtimeDataUpdatedAt` – is currently not set in db-vendo-client, because the upstream APIs don't provide it.
|
||||||
|
|
||||||
This object might look like this:
|
This object might look like this:
|
||||||
|
|
||||||
|
@ -276,44 +279,6 @@ This object might look like this:
|
||||||
realtimeDataUpdatedAt: 1531259400, // 2018-07-10T23:50:00+02
|
realtimeDataUpdatedAt: 1531259400, // 2018-07-10T23:50:00+02
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Some [profiles](../p) are able to parse the ticket information, if returned by the API. For example, if you pass `tickets: true` with the [VBB profile](../p/vbb), each `journey` will have a tickets array that looks like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
[ {
|
|
||||||
name: 'Berlin Tarifgebiet A-B: Einzelfahrausweis – Regeltarif',
|
|
||||||
price: 2.8,
|
|
||||||
tariff: 'Berlin',
|
|
||||||
coverage: 'AB',
|
|
||||||
variant: 'adult',
|
|
||||||
amount: 1
|
|
||||||
}, {
|
|
||||||
name: 'Berlin Tarifgebiet A-B: Einzelfahrausweis – Ermäßigungstarif',
|
|
||||||
price: 1.7,
|
|
||||||
tariff: 'Berlin',
|
|
||||||
coverage: 'AB',
|
|
||||||
variant: 'reduced',
|
|
||||||
amount: 1,
|
|
||||||
reduced: true
|
|
||||||
}, /* … */ {
|
|
||||||
name: 'Berlin Tarifgebiet A-B: Tageskarte – Ermäßigungstarif',
|
|
||||||
price: 4.7,
|
|
||||||
tariff: 'Berlin',
|
|
||||||
coverage: 'AB',
|
|
||||||
variant: '1 day, reduced',
|
|
||||||
amount: 1,
|
|
||||||
reduced: true,
|
|
||||||
fullDay: true
|
|
||||||
}, /* … */ {
|
|
||||||
name: 'Berlin Tarifgebiet A-B: 4-Fahrten-Karte – Regeltarif',
|
|
||||||
price: 9,
|
|
||||||
tariff: 'Berlin',
|
|
||||||
coverage: 'AB',
|
|
||||||
variant: '4x adult',
|
|
||||||
amount: 4
|
|
||||||
} ]
|
|
||||||
```
|
|
||||||
|
|
||||||
If a journey leg has been cancelled, a `cancelled: true` will be added. Also, `departure`/`departureDelay`/`departurePlatform` and `arrival`/`arrivalDelay`/`arrivalPlatform` will be `null`.
|
If a journey leg has been cancelled, a `cancelled: true` will be added. Also, `departure`/`departureDelay`/`departurePlatform` and `arrival`/`arrivalDelay`/`arrivalPlatform` will be `null`.
|
||||||
|
|
||||||
To get more journeys earlier/later than the current set of results, pass `earlierRef`/`laterRef` into `opt.earlierThan`/`opt.laterThan`. For example, query *later* journeys as follows:
|
To get more journeys earlier/later than the current set of results, pass `earlierRef`/`laterRef` into `opt.earlierThan`/`opt.laterThan`. For example, query *later* journeys as follows:
|
||||||
|
@ -339,19 +304,16 @@ departure of last journey 2017-12-17T19:07:00+01:00
|
||||||
departure of first (later) journey 2017-12-17T19:19:00+01:00
|
departure of first (later) journey 2017-12-17T19:19:00+01:00
|
||||||
```
|
```
|
||||||
|
|
||||||
If you pass `polylines: true`, each journey leg will have a `polyline` field. Refer to [the section in the `trip()` docs](trip.md#polyline-option) for details.
|
## Using the `loyaltyCard` option
|
||||||
|
|
||||||
If you pass `scheduledDays: true`, each journey will have a `scheduledDays` object looking like this:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
import {data as loyaltyCards} from 'db-vendo-client/format/loyalty-cards.js' // see there for a list
|
||||||
'2018-01-01': true,
|
|
||||||
'2018-01-02': false,
|
hafas.journeys(from, to, {
|
||||||
// …
|
loyaltyCard: {type: data.BAHNCARD, discount: 25}
|
||||||
'2018-10-12': true,
|
})
|
||||||
'2018-10-13': true,
|
|
||||||
// …
|
|
||||||
'2019-01-02': false,
|
|
||||||
'2019-01-03': false
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## The `routingMode` option
|
||||||
|
|
||||||
|
The `routingMode` option is not supported by db-vendo-client. The behavior will be the same as the `HYBRID` mode of hafas-client, i.e. cancelled trains/infeasible journeys will also be contained for informational purpose.
|
|
@ -1,70 +0,0 @@
|
||||||
# `lines([opt])`
|
|
||||||
|
|
||||||
**Fetches all lines known to the HAFAS endpoint**, e.g. warnings about disruptions, planned construction work, and general notices about the operating situation.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
As an example, we're going to use the [SVV profile](../p/svv):
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
import {profile as svvProfile} from 'hafas-client/p/svv/index.js'
|
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
|
||||||
const client = createClient(svvProfile, userAgent)
|
|
||||||
|
|
||||||
const {
|
|
||||||
lines,
|
|
||||||
realtimeDataUpdatedAt,
|
|
||||||
} = await client.lines('S1')
|
|
||||||
```
|
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated.
|
|
||||||
|
|
||||||
`lines` may look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "obb-1-S1-V-j20-1",
|
|
||||||
"type": "line",
|
|
||||||
"name": "S1",
|
|
||||||
"public": true,
|
|
||||||
"mode": "train",
|
|
||||||
"product": "bahn-s-bahn",
|
|
||||||
"operator": {
|
|
||||||
"type": "operator",
|
|
||||||
"id": "montafonerbahn-ag",
|
|
||||||
"name": "Montafonerbahn AG"
|
|
||||||
},
|
|
||||||
"directions": [
|
|
||||||
"Bludenz Bahnhof",
|
|
||||||
"Bregenz Hafen Bahnhof",
|
|
||||||
"Lindau Hbf",
|
|
||||||
"Bregenz Bahnhof",
|
|
||||||
"Schruns Bahnhof",
|
|
||||||
"Lochau Bahnhof"
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// …
|
|
||||||
{
|
|
||||||
"id": "svv-42-50-j20-2",
|
|
||||||
"type": "line",
|
|
||||||
"name": "S1",
|
|
||||||
"public": true,
|
|
||||||
"mode": "train",
|
|
||||||
"product": "bahn-s-bahn",
|
|
||||||
"operator": {
|
|
||||||
"type": "operator",
|
|
||||||
"id": "salzburg-ag-salzburger-lokalbahn",
|
|
||||||
"name": "Salzburg AG - Salzburger Lokalbahn"
|
|
||||||
},
|
|
||||||
"directions": [
|
|
||||||
"Lamprechtshausen Bahnhof",
|
|
||||||
"Salzburg Hauptbahnhof",
|
|
||||||
"Acharting S-Bahn",
|
|
||||||
"Weitwörth-Nussdorf Bahnhof"
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
```
|
|
|
@ -6,14 +6,14 @@ With `opt`, you can override the default options, which look like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
fuzzy: true // find only exact matches?
|
fuzzy: true // not supported
|
||||||
, results: 5 // how many search results?
|
, results: 5 // how many search results?
|
||||||
, stops: true // return stops/stations?
|
, stops: true // return stops/stations?
|
||||||
, addresses: true
|
, addresses: true
|
||||||
, poi: true // points of interest
|
, poi: true // points of interest
|
||||||
, subStops: true // parse & expose sub-stops of stations?
|
, subStops: true // not supported
|
||||||
, entrances: true // parse & expose entrances of stops/stations?
|
, entrances: true // not supported
|
||||||
, linesOfStops: false // parse & expose lines at each stop/station?
|
, linesOfStops: false // not supported
|
||||||
, language: 'en' // language to get results in
|
, language: 'en' // language to get results in
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -23,8 +23,8 @@ With `opt`, you can override the default options, which look like this:
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
As an example, we're going to use the [VBB profile](../p/vbb):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
import {profile as vbbProfile} from 'db-vendo-client/p/vbb/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
const client = createClient(vbbProfile, userAgent)
|
const client = createClient(vbbProfile, userAgent)
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
# Migrating to `hafas-client@5`
|
|
||||||
|
|
||||||
## If you use Node `8` ("Carbon")…
|
|
||||||
|
|
||||||
…migrate to Node `10` ("Dubnium"), sorry. [Node `8` is out of maintenance now](https://nodejs.org/en/about/releases/). 83f43c6
|
|
||||||
|
|
||||||
## new fields for departure/arrival time & delays
|
|
||||||
|
|
||||||
An arrival/departure now looks like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
when: null, // realtime/prognosed
|
|
||||||
plannedWhen: '2019-10-10T10:10+10:00',
|
|
||||||
platform: '3', // realtime/prognosed
|
|
||||||
plannedPlatform: '4'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A stopover/journey leg now will look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
arrival: null, // realtime/prognosed
|
|
||||||
plannedArrival: '2019-10-10T10:10+10:00',
|
|
||||||
arrivalDelay: null,
|
|
||||||
arrivalPlatform: '3', // realtime/prognosed
|
|
||||||
plannedArrivalPlatform: '4',
|
|
||||||
|
|
||||||
departure: '2019-10-10T10:12+10:00', // realtime/prognosed
|
|
||||||
plannedDeparture: '2019-10-10T10:10+10:00',
|
|
||||||
departureDelay: 120, // seconds
|
|
||||||
departurePlatform: null, // realtime/prognosed
|
|
||||||
plannedDeparturePlatform: null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If the same stopover/journey leg is `cancelled: true`, it will look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
arrival: null,
|
|
||||||
prognosedArrival: null,
|
|
||||||
plannedArrival: '2019-10-10T10:10+10:00',
|
|
||||||
arrivalDelay: null,
|
|
||||||
arrivalPlatform: null,
|
|
||||||
prognosedArrivalPlatform: '3',
|
|
||||||
plannedArrivalPlatform: '4',
|
|
||||||
|
|
||||||
departure: null,
|
|
||||||
prognosedDeparture: '2019-10-10T10:12+10:00',
|
|
||||||
plannedDeparture: '2019-10-10T10:10+10:00',
|
|
||||||
departureDelay: 120, // seconds
|
|
||||||
departurePlatform: null,
|
|
||||||
prognosedDeparturePlatform: null,
|
|
||||||
plannedDeparturePlatform: null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## If you use `journeys()`…
|
|
||||||
|
|
||||||
…with the `walkingSpeed` option and a custom profile, add `journeysWalkingSpeed` to your profile. 937583e
|
|
||||||
…without the `results` option, but *expect* a certain number of results, you must pass `results` now. 0045587
|
|
||||||
|
|
||||||
## If you use `departures()`/`arrivals()` with the [BVG profile](../p/bvg)…
|
|
||||||
|
|
||||||
With the latest protocol version, the BVG endpoint doesn't support these options anymore:
|
|
||||||
|
|
||||||
- `stopovers` – Fetch & parse previous/next stopovers? Default: `false`
|
|
||||||
- `includeRelatedStations` – Fepartures at related stations, e.g. those that belong together on the metro map? Default: `true`
|
|
||||||
|
|
||||||
2d72391
|
|
||||||
|
|
||||||
## If you use a custom profile…
|
|
||||||
|
|
||||||
Let's assume you have parse function looking like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const createParseLine = (profile, opt, data) => (rawLine) => {
|
|
||||||
const operator = data.operators[rawLine.oprX] || null
|
|
||||||
if (operator && operator.name === 'foo') {
|
|
||||||
return {
|
|
||||||
type: 'line',
|
|
||||||
name: 'really special tram line',
|
|
||||||
mode: 'tram',
|
|
||||||
product: 'special-tram',
|
|
||||||
operator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultParseLine(rawLine)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Adapt your parse function like this:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
const createParseLine = (profile, opt, _) => (rawLine) => {
|
|
||||||
- const operator = data.operators[rawLine.oprX] || null
|
|
||||||
+ const operator = rawLine.operator || null
|
|
||||||
```
|
|
||||||
|
|
||||||
See also [`#127`](https://github.com/public-transport/hafas-client/pull/127).
|
|
||||||
|
|
||||||
If you use `icons` in `parseWarning`/`parseHint`, adapt the function(s) to take an object `data` as the first argument. You can access the list of *parsed* icons via `data.icons`, *parsed* warnings via `data.warnings`, etc. a229205 b36f0e3
|
|
||||||
|
|
||||||
## Other breaking changes
|
|
||||||
|
|
||||||
- `journey.price` will be `null` if there's no pricing data returned by the endpoint, instead of `{amount: null}`. 8fe277d
|
|
|
@ -1,93 +0,0 @@
|
||||||
# Migrating to `hafas-client@6`
|
|
||||||
|
|
||||||
## If you use Node.js <16 …
|
|
||||||
|
|
||||||
… migrate to Node `16` ("Gallium"), sorry. [Node `10`, `12` & `14` are out of (active) LTS now](https://nodejs.org/en/about/releases/).
|
|
||||||
|
|
||||||
## If you use `hafas-client` via [CommonJS](https://en.wikipedia.org/wiki/CommonJS) …
|
|
||||||
|
|
||||||
… you'll have to either
|
|
||||||
- migrate your code to ECMAScript Modules (ESM), or
|
|
||||||
- use [dynamic `import()`](https://nodejs.org/docs/latest-v16.x/api/esm.html#import-expressions), or
|
|
||||||
- use a (somewhat hacky) tool like [`esm`](https://www.npmjs.com/package/esm).
|
|
||||||
|
|
||||||
For more background information, check out [MDN's ESM explainer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) and [Node.js's ESM docs](https://nodejs.org/docs/latest-v16.x/api/esm.html).
|
|
||||||
|
|
||||||
## If you use `departures()` or `arrivals()` …
|
|
||||||
|
|
||||||
… adapt your code as follows:
|
|
||||||
- `departures()` now returns an object `{departures: […], realtimeDataUpdatedAt: …}`
|
|
||||||
- `arrivals()` now returns an object `{arrivals: […], realtimeDataUpdatedAt: …}`
|
|
||||||
|
|
||||||
### … with `opt.stopovers: true` …
|
|
||||||
|
|
||||||
… check if this still works. If `hafas-client` throws "`opt.stopovers` is not supported by this endpoint", you'll have to use `trip()` for each departure/arrival to get its trip's stopovers.
|
|
||||||
|
|
||||||
Most profiles had to be upgraded to a newer HAFAS protocol version to still work, and newer HAFAS protocol versions don't support this flag anymore.
|
|
||||||
|
|
||||||
## If you use `journeys()`, `refreshJourney()` or `journeysFromTrip()` …
|
|
||||||
|
|
||||||
… use `res.realtimeDataUpdatedAt` instead of `res.realtimeDataFrom`, it has been renamed.
|
|
||||||
|
|
||||||
## If you use `refreshJourney()` …
|
|
||||||
|
|
||||||
… adapt your code as follows: it now returns an object `{journey: …, realtimeDataUpdatedAt: …}`.
|
|
||||||
|
|
||||||
## If you use `trip()` …
|
|
||||||
|
|
||||||
… adapt your code as follows: it now returns an object `{trip: …, realtimeDataUpdatedAt: …}`.
|
|
||||||
|
|
||||||
… don't pass the `lineName` parameter anymore, it is not needed anymore and has been removed.
|
|
||||||
|
|
||||||
## If you use `tripsByName()` …
|
|
||||||
|
|
||||||
… adapt your code as follows: it now returns an object `{trips: […], realtimeDataUpdatedAt: …}`.
|
|
||||||
|
|
||||||
## If you use `radar()` …
|
|
||||||
|
|
||||||
… adapt your code as follows: it now returns an object `{movements: […], realtimeDataUpdatedAt: …}`.
|
|
||||||
|
|
||||||
## If you use `reachableFrom()` …
|
|
||||||
|
|
||||||
… and it sometimes fails with a server error (a.k.a. HAFAS is unable to process the request), wrap it in a retry logic ([open an Issue](https://github.com/public-transport/hafas-client/issues/new) to get help). Automatic retries have been removed.
|
|
||||||
|
|
||||||
## If you use `remarks()` …
|
|
||||||
|
|
||||||
… adapt your code as follows: it now returns an object `{remarks: […], realtimeDataUpdatedAt: …}`.
|
|
||||||
|
|
||||||
## If you use `lines()` …
|
|
||||||
|
|
||||||
… adapt your code as follows: it now returns an object `{lines: […], realtimeDataUpdatedAt: …}`.
|
|
||||||
|
|
||||||
## If you use the DB profile …
|
|
||||||
|
|
||||||
… be aware that the `regionalExp` product has been renamed to `regionalExpress`. Among other places, you will notice this in `line.product`.
|
|
||||||
|
|
||||||
## If you use the BVG or VBB profile …
|
|
||||||
|
|
||||||
### … and rely on `stop.weight` …
|
|
||||||
|
|
||||||
… use [`vbb-stations`](https://npmjs.com/package/vbb-stations) to get it instead. It has been removed from `hafas-client`.
|
|
||||||
|
|
||||||
### … and rely on 12-digit stop IDs …
|
|
||||||
|
|
||||||
… adapt your code to handle 9-digit (and sometimes 6-digit?) stop IDs. The translation logic has been removed from `hafas-client`.
|
|
||||||
|
|
||||||
## If you rely on `line.adminCode` …
|
|
||||||
|
|
||||||
… be aware that `hafas-client` now doesn't remove trailing `-` characters anymore (e.g. `DBS---` instead of `DBS`).
|
|
||||||
|
|
||||||
## If you use the VBB profile …
|
|
||||||
|
|
||||||
### … and rely on `line.{symbol,nr,metro,express,night}` …
|
|
||||||
|
|
||||||
… use [`vbb-parse-line`](https://npmjs.com/package/vbb-parse-line) with `line.name` by yourself. It has been removed from `hafas-client`.
|
|
||||||
|
|
||||||
### … and rely on `ticket.{amount,fullDay,group,tariff,coverage,variant}` …
|
|
||||||
|
|
||||||
… use [`vbb-parse-ticket`](https://npmjs.com/package/vbb-parse-ticket) to parse details from the ticket identifier ([open an Issue](https://github.com/public-transport/hafas-client/issues/new) to get help). It has been removed from `hafas-client`.
|
|
||||||
|
|
||||||
## Other breaking changes
|
|
||||||
|
|
||||||
- `warning.fromLoc`/`warning.toLoc` are now called `warning.fromLocation`/`warning.toLocation`
|
|
||||||
- `trip()`/`tripsByName()`: remove `trip.reachable` (it didn't make sense anyways)
|
|
|
@ -12,9 +12,9 @@ With `opt`, you can override the default options, which look like this:
|
||||||
distance: null, // maximum walking distance in meters
|
distance: null, // maximum walking distance in meters
|
||||||
poi: false, // return points of interest?
|
poi: false, // return points of interest?
|
||||||
stops: true, // return stops/stations?
|
stops: true, // return stops/stations?
|
||||||
subStops: true, // parse & expose sub-stops of stations?
|
subStops: true, // not supported
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // not supported
|
||||||
linesOfStops: false, // parse & expose lines at each stop/station?
|
linesOfStops: false, // not supported
|
||||||
language: 'en' // language to get results in
|
language: 'en' // language to get results in
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -24,8 +24,8 @@ With `opt`, you can override the default options, which look like this:
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
As an example, we're going to use the [VBB profile](../p/vbb):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
import {profile as vbbProfile} from 'db-vendo-client/p/vbb/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
const client = createClient(vbbProfile, userAgent)
|
const client = createClient(vbbProfile, userAgent)
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
// Refer to the the ./writing-a-profile.md guide.
|
|
||||||
|
|
||||||
const products = [
|
|
||||||
{
|
|
||||||
id: 'commuterTrain',
|
|
||||||
mode: 'train',
|
|
||||||
bitmasks: [16],
|
|
||||||
name: 'ACME Commuter Rail',
|
|
||||||
short: 'CR',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'metro',
|
|
||||||
mode: 'train',
|
|
||||||
bitmasks: [8],
|
|
||||||
name: 'Foo Bar Metro',
|
|
||||||
short: 'M',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const transformReqBody = (body) => {
|
|
||||||
// get these from the recorded app requests
|
|
||||||
// body.client = { … }
|
|
||||||
// body.ver = …
|
|
||||||
// body.auth = { … }
|
|
||||||
// body.lang = …
|
|
||||||
return body;
|
|
||||||
};
|
|
||||||
|
|
||||||
const insaProfile = {
|
|
||||||
// locale: …,
|
|
||||||
// timezone: …,
|
|
||||||
// endpoint: …,
|
|
||||||
transformReqBody,
|
|
||||||
|
|
||||||
products: products,
|
|
||||||
|
|
||||||
trip: false,
|
|
||||||
radar: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
insaProfile,
|
|
||||||
};
|
|
191
docs/radar.md
191
docs/radar.md
|
@ -1,191 +0,0 @@
|
||||||
# `radar({north, west, south, east}, [opt])`
|
|
||||||
|
|
||||||
Use this method to find all vehicles currently in an area. Note that it is not supported by every profile/endpoint.
|
|
||||||
|
|
||||||
`north`, `west`, `south` and `eath` must be numbers (e.g. `52.52411`). Together, they form a [bounding box](https://en.wikipedia.org/wiki/Minimum_bounding_box).
|
|
||||||
|
|
||||||
With `opt`, you can override the default options, which look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
results: 256, // maximum number of vehicles
|
|
||||||
duration: 30, // compute frames for the next n seconds
|
|
||||||
frames: 3, // nr of frames to compute
|
|
||||||
polylines: true, // return a track shape for each vehicle?
|
|
||||||
subStops: true, // parse & expose sub-stops of stations?
|
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
|
||||||
language: 'en' // language to get results in
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Response
|
|
||||||
|
|
||||||
*Note:* As stated in the [*Friendly Public Transport Format* v2 draft spec](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md), the returned `departure` and `arrival` times include the current delay. The `departureDelay`/`arrivalDelay` fields express how much they differ from the schedule.
|
|
||||||
|
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
|
||||||
const client = createClient(vbbProfile, userAgent)
|
|
||||||
|
|
||||||
const {
|
|
||||||
movements,
|
|
||||||
realtimeDataUpdatedAt,
|
|
||||||
} = await client.radar({
|
|
||||||
north: 52.52411,
|
|
||||||
west: 13.41002,
|
|
||||||
south: 52.51942,
|
|
||||||
east: 13.41709
|
|
||||||
}, {results: 5})
|
|
||||||
```
|
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated.
|
|
||||||
|
|
||||||
`movements` may look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
[ {
|
|
||||||
location: {
|
|
||||||
type: 'location',
|
|
||||||
latitude: 52.521508,
|
|
||||||
longitude: 13.411267
|
|
||||||
},
|
|
||||||
|
|
||||||
line: {
|
|
||||||
type: 'line',
|
|
||||||
id: 's9',
|
|
||||||
fahrtNr: '12345',
|
|
||||||
name: 'S9',
|
|
||||||
public: true,
|
|
||||||
mode: 'train',
|
|
||||||
product: 'suburban',
|
|
||||||
symbol: 'S',
|
|
||||||
nr: 9,
|
|
||||||
metro: false,
|
|
||||||
express: false,
|
|
||||||
night: false,
|
|
||||||
operator: {
|
|
||||||
type: 'operator',
|
|
||||||
id: 's-bahn-berlin-gmbh',
|
|
||||||
name: 'S-Bahn Berlin GmbH'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
direction: 'S Flughafen Berlin-Schönefeld',
|
|
||||||
trip: 31463, // todo: outdated, should be tripId!
|
|
||||||
|
|
||||||
nextStopovers: [ {
|
|
||||||
stop: {
|
|
||||||
type: 'stop',
|
|
||||||
id: '900000029101',
|
|
||||||
name: 'S Spandau',
|
|
||||||
location: {
|
|
||||||
type: 'location',
|
|
||||||
latitude: 52.534794,
|
|
||||||
longitude: 13.197477
|
|
||||||
},
|
|
||||||
products: {
|
|
||||||
suburban: true,
|
|
||||||
subway: false,
|
|
||||||
tram: false,
|
|
||||||
bus: true,
|
|
||||||
ferry: false,
|
|
||||||
express: true,
|
|
||||||
regional: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
arrival: null,
|
|
||||||
plannedArrival: null,
|
|
||||||
arrivalDelay: null,
|
|
||||||
arrivalPlatform: null,
|
|
||||||
plannedArrivalPlatform: null,
|
|
||||||
departure: null,
|
|
||||||
plannedDeparture: '2017-12-17T19:16:00+01:00',
|
|
||||||
departureDelay: null,
|
|
||||||
departurePlatform: null,
|
|
||||||
plannedDeparturePlatform: '1'
|
|
||||||
} /* … */ ],
|
|
||||||
frames: [ {
|
|
||||||
origin: {
|
|
||||||
type: 'stop',
|
|
||||||
id: '900000100003',
|
|
||||||
name: 'S+U Alexanderplatz',
|
|
||||||
location: { /* … */ },
|
|
||||||
products: { /* … */ }
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
type: 'stop',
|
|
||||||
id: '900000100004',
|
|
||||||
name: 'S+U Jannowitzbrücke',
|
|
||||||
location: { /* … */ },
|
|
||||||
products: { /* … */ }
|
|
||||||
},
|
|
||||||
t: 0
|
|
||||||
}, /* … */ {
|
|
||||||
origin: { /* Alexanderplatz */ },
|
|
||||||
destination: { /* Jannowitzbrücke */ },
|
|
||||||
t: 30000
|
|
||||||
} ]
|
|
||||||
}, {
|
|
||||||
location: {
|
|
||||||
type: 'location',
|
|
||||||
latitude: 52.523297,
|
|
||||||
longitude: 13.411151
|
|
||||||
},
|
|
||||||
line: {
|
|
||||||
type: 'line',
|
|
||||||
id: 'm2',
|
|
||||||
fahrtNr: '54321',
|
|
||||||
name: 'M2',
|
|
||||||
public: true,
|
|
||||||
mode: 'train',
|
|
||||||
product: 'tram',
|
|
||||||
symbol: 'M',
|
|
||||||
nr: 2,
|
|
||||||
metro: true,
|
|
||||||
express: false,
|
|
||||||
night: false,
|
|
||||||
operator: {
|
|
||||||
type: 'operator',
|
|
||||||
id: 'berliner-verkehrsbetriebe',
|
|
||||||
name: 'Berliner Verkehrsbetriebe'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
direction: 'Heinersdorf',
|
|
||||||
trip: 26321,
|
|
||||||
nextStopovers: [ {
|
|
||||||
stop: { /* S+U Alexanderplatz/Dircksenstr. */ },
|
|
||||||
arrival: null,
|
|
||||||
plannedArrival: null,
|
|
||||||
arrivalDelay: null,
|
|
||||||
departure: null,
|
|
||||||
plannedAeparture: '2017-12-17T19:52:00+01:00',
|
|
||||||
departureDelay: null
|
|
||||||
}, {
|
|
||||||
stop: { /* Memhardstr. */ },
|
|
||||||
arrival: null,
|
|
||||||
plannedArrival: '2017-12-17T19:54:00+01:00',
|
|
||||||
arrivalDelay: null,
|
|
||||||
arrivalPlatform: null,
|
|
||||||
plannedArrivalPlatform: null,
|
|
||||||
departure: null,
|
|
||||||
plannedDeparture: '2017-12-17T19:54:00+01:00',
|
|
||||||
departureDelay: null,
|
|
||||||
departurePlatform: null,
|
|
||||||
plannedDeparturePlatform: '1'
|
|
||||||
}, /* … */ ],
|
|
||||||
frames: [ {
|
|
||||||
origin: { /* S+U Alexanderplatz/Dircksenstr. */ },
|
|
||||||
destination: { /* Memhardstr. */ },
|
|
||||||
t: 0
|
|
||||||
}, /* … */ {
|
|
||||||
origin: { /* Memhardstr. */ },
|
|
||||||
destination: { /* Mollstr./Prenzlauer Allee */ },
|
|
||||||
t: 30000
|
|
||||||
} ]
|
|
||||||
}, /* … */ ]
|
|
||||||
```
|
|
||||||
|
|
||||||
If you pass `polylines: true`, each movement will have a `polyline` field, as documented in [the corresponding section in the `trip()` docs](trip.md#polyline-option), with the exception that station info is missing.
|
|
|
@ -1,98 +0,0 @@
|
||||||
# `reachableFrom(address, [opt])`
|
|
||||||
|
|
||||||
This method can be used to get stations reachable within a certain time from an address. This concept is called [isochrone diagram](https://en.wikipedia.org/wiki/Isochrone_map#Transportation_planning).
|
|
||||||
|
|
||||||
*Note*: It appears that HAFAS cannot generate actual isochrones, but only the list of reachable stations, which you can estimate the isochrone(s) from.
|
|
||||||
|
|
||||||
`address` must be an [*FPTF* `location` object](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md#location-objects).
|
|
||||||
|
|
||||||
With `opt`, you can override the default options, which look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
when: new Date(),
|
|
||||||
maxTransfers: 5, // maximum of 5 transfers
|
|
||||||
maxDuration: 20, // maximum travel duration in minutes, pass `null` for infinite
|
|
||||||
products: {
|
|
||||||
// These entries may vary from profile to profile!
|
|
||||||
suburban: true,
|
|
||||||
subway: true
|
|
||||||
// …
|
|
||||||
},
|
|
||||||
subStops: true, // parse & expose sub-stops of stations?
|
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Response
|
|
||||||
|
|
||||||
`reachableFrom(address, [opt])` returns an array, in which each item has a `duration` and an array of [*Friendly Public Transport Format* `station`s](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md#station).
|
|
||||||
|
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
|
||||||
const client = createClient(vbbProfile, userAgent)
|
|
||||||
|
|
||||||
const {
|
|
||||||
reachable,
|
|
||||||
realtimeDataUpdatedAt,
|
|
||||||
} = await client.reachableFrom({
|
|
||||||
type: 'location',
|
|
||||||
address: '13353 Berlin-Wedding, Torfstr. 17',
|
|
||||||
latitude: 52.541797,
|
|
||||||
longitude: 13.350042
|
|
||||||
}, {
|
|
||||||
maxDuration: 10 // minutes
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated.
|
|
||||||
|
|
||||||
`reachable` may look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
[
|
|
||||||
{
|
|
||||||
duration: 2,
|
|
||||||
stations: [
|
|
||||||
{
|
|
||||||
type: 'stop',
|
|
||||||
id: '900000009101',
|
|
||||||
name: 'U Amrumer Str.',
|
|
||||||
location: {type: 'location', latitude: 52.542201, longitude: 13.34953},
|
|
||||||
products: { /* … */ }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
duration: 3,
|
|
||||||
stations: [
|
|
||||||
{
|
|
||||||
type: 'stop',
|
|
||||||
id: '900000001201',
|
|
||||||
name: 'S+U Westhafen',
|
|
||||||
location: {type: 'location', latitude: 52.536179, longitude: 13.343839},
|
|
||||||
products: { /* … */ }
|
|
||||||
}
|
|
||||||
// …
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// …
|
|
||||||
{
|
|
||||||
duration: 10,
|
|
||||||
stations: [
|
|
||||||
{
|
|
||||||
type: 'stop',
|
|
||||||
id: '900000001203',
|
|
||||||
name: 'Döberitzer Str.',
|
|
||||||
location: {type: 'location', latitude: 52.530668, longitude: 13.36811},
|
|
||||||
products: { /* … */ }
|
|
||||||
}
|
|
||||||
// …
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
|
@ -1,19 +1,19 @@
|
||||||
# `hafas-client` documentation
|
# `db-vendo-client` documentation
|
||||||
|
|
||||||
**[API documentation](api.md)**
|
**[API documentation](api.md)**
|
||||||
|
|
||||||
## Migrating from an old `hafas-client` version
|
## Migrating from an old (v5) `hafas-client` version
|
||||||
|
|
||||||
- [`4` → `5` migration guide](migrating-to-5.md)
|
`db-vendo-client` tries to be as compatible as possible with `hafas-client` v6. If you were still on v5 or earlier, see the [`5` → `6` migration guide](https://github.com/public-transport/hafas-client/blob/main/docs/migrating-to-6.md) of `hafas-client`.
|
||||||
|
|
||||||
## Throttling requests
|
## Throttling requests
|
||||||
|
|
||||||
There's opt-in support for throttling requests to the endpoint.
|
There's opt-in support for throttling requests to the endpoint.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {withThrottling} from 'hafas-client/throttle.js'
|
import {withThrottling} from 'db-vendo-client/throttle.js'
|
||||||
import {profile as dbProfile} from 'hafas-client/p/db/index.js'
|
import {profile as dbProfile} from 'db-vendo-client/p/db/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
|
|
||||||
|
@ -37,9 +37,9 @@ const client = createClient(throttledDbProfile, userAgent)
|
||||||
There's opt-in support for retrying failed requests to the endpoint.
|
There's opt-in support for retrying failed requests to the endpoint.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {withRetrying} from 'hafas-client/retry.js'
|
import {withRetrying} from 'db-vendo-client/retry.js'
|
||||||
import {profile as dbProfile} from 'hafas-client/p/db/index.js'
|
import {profile as dbProfile} from 'db-vendo-client/p/db/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
|
|
||||||
|
@ -61,27 +61,12 @@ const client = createClient(retryingDbProfile, userAgent)
|
||||||
|
|
||||||
## User agent randomization
|
## User agent randomization
|
||||||
|
|
||||||
By default, `hafas-client` randomizes the client name that you pass into `createClient`, and sends it as [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) in a randomized form:
|
By default, `db-vendo-client` does not randomize the client name that you pass into `createClient`, and sends it as [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) as it is. At least DB Navigator always sends the same user agent as well (cf. `dbnav` profile). You can turn on randomization by setting `profile.randomizeUserAgent` to `false`:
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
// …
|
|
||||||
|
|
||||||
const userAgent = 'my-awesome-program'
|
|
||||||
const client = createClient(someProfile, userAgent)
|
|
||||||
|
|
||||||
await client.journeys(/* … */)
|
|
||||||
// User-Agent: my-awee70429some-pre70429ogram
|
|
||||||
await client.journeys(/* … */)
|
|
||||||
// User-Agent: my-awesom9bb8e2e-prog9bb8e2ram
|
|
||||||
```
|
|
||||||
|
|
||||||
You can turn this off by setting `profile.randomizeUserAgent` to `false`:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
...someProfile,
|
...someProfile,
|
||||||
randomizeUserAgent: false,
|
randomizeUserAgent: true,
|
||||||
}, userAgent)
|
}, userAgent)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -92,8 +77,8 @@ You can use `profile.logRequest` and `profile.logResponse` to process the raw [F
|
||||||
As an example, we can implement a custom logger:
|
As an example, we can implement a custom logger:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {profile as dbProfile} from 'hafas-client/p/db/index.js'
|
import {profile as dbProfile} from 'db-vendo-client/p/db/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
|
|
||||||
|
@ -140,15 +125,12 @@ The default `profile.logRequest` [`console.error`](https://nodejs.org/docs/lates
|
||||||
|
|
||||||
## Error handling
|
## Error handling
|
||||||
|
|
||||||
Unexpected errors – e.g. due to bugs in `hafas-client` itself – aside, its methods may reject with the following errors:
|
Unexpected errors – e.g. due to bugs in `db-vendo-client` itself – aside, its methods may reject with the following errors:
|
||||||
|
|
||||||
|
- `Error` – A generic error, e.g. if the DB backend returned a HTTP error.
|
||||||
- `HafasError` – A generic error to signal that something HAFAS-related went wrong, either in the client, or in the HAFAS endpoint.
|
- `HafasError` – A generic error to signal that something HAFAS-related went wrong, either in the client, or in the HAFAS endpoint.
|
||||||
- `HafasAccessDeniedError` – The HAFAS endpoint has rejected your request because you're not allowed to access it (or the specific API call). Subclass of `HafasError`.
|
|
||||||
- `HafasInvalidRequestError` – The HAFAS endpoint reports that an invalid request has been sent. Subclass of `HafasError`.
|
|
||||||
- `HafasNotFoundError` – The HAFAS endpoint does not know about such stop/trip/etc. Subclass of `HafasError`.
|
|
||||||
- `HafasServerError` – An error occured within the HAFAS endpoint, so that it can't fulfill the request; For example, this happens when HAFAS' internal backend is unavailable. Subclass of `HafasError`.
|
|
||||||
|
|
||||||
Each error has the following properties:
|
Each `HafasError` error has the following properties:
|
||||||
|
|
||||||
- `isHafasError` – Always `true`. Allows you to differente HAFAS-related errors from e.g. network errors.
|
- `isHafasError` – Always `true`. Allows you to differente HAFAS-related errors from e.g. network errors.
|
||||||
- `code` – A string representing the error type for all other error classes, e.g. `INVALID_REQUEST` for `HafasInvalidRequestError`. `null` for plain `HafasError`s.
|
- `code` – A string representing the error type for all other error classes, e.g. `INVALID_REQUEST` for `HafasInvalidRequestError`. `null` for plain `HafasError`s.
|
||||||
|
@ -158,27 +140,11 @@ Each error has the following properties:
|
||||||
- `url` – The URL of the request.
|
- `url` – The URL of the request.
|
||||||
- `response` – The [Fetch API `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
- `response` – The [Fetch API `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
||||||
|
|
||||||
To check **if an error from `hafas-client` is HAFAS-specific, use `error instanceof HafasError`**:
|
|
||||||
|
|
||||||
```js
|
## Using `db-vendo-client` from another language
|
||||||
import {HafasError} from 'hafas-client/lib/errors.js'
|
|
||||||
|
|
||||||
try {
|
If you want to use `db-vendo-client` to access DB APIs but work with non-Node.js environments, you can use it together with [hafas-rest-api](https://github.com/public-transport/hafas-rest-api) to create a REST API(see the [root readme](https://github.com/public-transport/db-vendo-client/tree/main#usage) and the Docker image).
|
||||||
await client.journeys(/* … */)
|
Or use [`hafas-client-rpc`](https://github.com/derhuerst/hafas-client-rpc) to create a [JSON-RPC](https://www.jsonrpc.org) interface that you can send commands to.
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof HafasError) {
|
|
||||||
// HAFAS-specific error
|
|
||||||
} else {
|
|
||||||
// different error, e.g. network (ETIMEDOUT, ENETDOWN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To determine **if you should automatically retry an error, use `!error.causedByServer`**.
|
|
||||||
|
|
||||||
## Using `hafas-client` from another language
|
|
||||||
|
|
||||||
If you want to use `hafas-client` to access HAFAS APIs but work with non-Node.js environments, you can use [`hafas-client-rpc`](https://github.com/derhuerst/hafas-client-rpc) to create a [JSON-RPC](https://www.jsonrpc.org) interface that you can send commands to.
|
|
||||||
|
|
||||||
## Writing a profile
|
## Writing a profile
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ With `opt`, you can override the default options, which look like this:
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
stopovers: false, // return stations on the way?
|
stopovers: false, // return stations on the way?
|
||||||
polylines: false, // return a shape for each leg?
|
polylines: false, // return a shape for each leg? mutually exclusive with tickets
|
||||||
tickets: false, // return tickets? only available with some profiles
|
tickets: false, // return tickets? mutually exclusive with polylines
|
||||||
subStops: true, // parse & expose sub-stops of stations?
|
subStops: true, // not supported
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // not supported
|
||||||
remarks: true, // parse & expose hints & warnings?
|
remarks: true, // parse & expose hints & warnings?
|
||||||
language: 'en' // language to get results in
|
language: 'en' // language to get results in
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,14 @@ With `opt`, you can override the default options, which look like this:
|
||||||
|
|
||||||
## Response
|
## Response
|
||||||
|
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
import {profile as vbbProfile} from 'db-vendo-client/p/vbb/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
const client = createClient(vbbProfile, userAgent)
|
const client = createClient(vbbProfile, userAgent)
|
||||||
|
|
||||||
// Hauptbahnhof to Heinrich-Heine-Str.
|
const {journeys} = await client.journeys('8000105', '8000096', {results: 1})
|
||||||
const {journeys} = await client.journeys('900000003201', '900000100008', {results: 1})
|
|
||||||
|
|
||||||
// later, fetch up-to-date info on the journey
|
// later, fetch up-to-date info on the journey
|
||||||
const {
|
const {
|
||||||
|
@ -39,4 +36,4 @@ const {
|
||||||
|
|
||||||
`journey` is a *single* [*Friendly Public Transport Format* v2 draft](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md) `journey`, in the same format as returned by [`journeys()`](journeys.md).
|
`journey` is a *single* [*Friendly Public Transport Format* v2 draft](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md) `journey`, in the same format as returned by [`journeys()`](journeys.md).
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated.
|
`realtimeDataUpdatedAt` is currently not set in db-vendo-client, because the upstream APIs don't provide it.
|
||||||
|
|
150
docs/remarks.md
150
docs/remarks.md
|
@ -1,150 +0,0 @@
|
||||||
# `remarks([opt])`
|
|
||||||
|
|
||||||
**Fetches all remarks known to the HAFAS endpoint**, e.g. warnings about disruptions, planned construction work, and general notices about the operating situation.
|
|
||||||
|
|
||||||
With `opt`, you can override the default options, which look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
results: 100, // maximum number of remarks
|
|
||||||
// filter by time
|
|
||||||
from: Date.now(),
|
|
||||||
to: null,
|
|
||||||
products: null, // filter by affected products
|
|
||||||
language: 'en', // depends on the profile
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
As an example, we're going to use the [SVV profile](../p/svv):
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
import {profile as svvProfile} from 'hafas-client/p/svv/index.js'
|
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
|
||||||
const client = createClient(svvProfile, userAgent)
|
|
||||||
|
|
||||||
const {
|
|
||||||
remarks,
|
|
||||||
realtimeDataUpdatedAt,
|
|
||||||
} = await client.remarks()
|
|
||||||
```
|
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated.
|
|
||||||
|
|
||||||
`remarks` may look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: 'HIM_FREETEXT_110342',
|
|
||||||
type: 'warning',
|
|
||||||
summary: 'Bus will be running at different times',
|
|
||||||
text: 'Due to operational changes, this bus will be running at different times. We apologise for any inconvenience this may cause.',
|
|
||||||
priority: 50,
|
|
||||||
company: 'KGÖVV',
|
|
||||||
validFrom: '2020-07-04T00:00:00+02:00',
|
|
||||||
validUntil: '2020-08-09T23:59:00+02:00',
|
|
||||||
modified: '2020-07-01T14:39:12+02:00',
|
|
||||||
products: {
|
|
||||||
'bahn-s-bahn': true,
|
|
||||||
'u-bahn': true,
|
|
||||||
strassenbahn: true,
|
|
||||||
fernbus: true,
|
|
||||||
regionalbus: true,
|
|
||||||
stadtbus: true,
|
|
||||||
'seilbahn-zahnradbahn': true,
|
|
||||||
schiff: true,
|
|
||||||
},
|
|
||||||
categories: [1],
|
|
||||||
icon: {type: 'HIM1', title: null},
|
|
||||||
},
|
|
||||||
// …
|
|
||||||
{
|
|
||||||
id: 'HIM_FREETEXT_110235',
|
|
||||||
type: 'warning',
|
|
||||||
summary: 'Linie 7 - Umleitungen',
|
|
||||||
text: 'Aufgrund einer Baustelle gibt es bei der Linie 7 umfangreiche Umleitungen.',
|
|
||||||
priority: 100,
|
|
||||||
company: 'VOR',
|
|
||||||
validFrom: '2020-07-13T00:00:00+02:00',
|
|
||||||
validUntil: '2020-08-31T23:59:00+02:00',
|
|
||||||
modified: '2020-06-30T12:37:38+02:00',
|
|
||||||
affectedLines: [{
|
|
||||||
type: 'line',
|
|
||||||
id: '7',
|
|
||||||
name: '7',
|
|
||||||
public: true,
|
|
||||||
}],
|
|
||||||
products: {
|
|
||||||
'bahn-s-bahn': true,
|
|
||||||
'u-bahn': true,
|
|
||||||
strassenbahn: true,
|
|
||||||
fernbus: true,
|
|
||||||
regionalbus: true,
|
|
||||||
stadtbus: true,
|
|
||||||
'seilbahn-zahnradbahn': false,
|
|
||||||
schiff: true,
|
|
||||||
},
|
|
||||||
categories: [1],
|
|
||||||
icon: {type: 'HIM1', title: null},
|
|
||||||
},
|
|
||||||
// …
|
|
||||||
{
|
|
||||||
id: 'HIM_FREETEXT_106619',
|
|
||||||
type: 'warning',
|
|
||||||
summary: 'Stop Bad Hall Bahnhofstraße can not be approached',
|
|
||||||
text: 'The stop at Bad Hall Bahnhofstraße can not be approached during 21.04.-24.07.2020. Please use alternatively the stop at Bad Hall Busterminal (Abzw Bahnhofstraße).',
|
|
||||||
priority: 100,
|
|
||||||
company: 'OÖVG',
|
|
||||||
validFrom: '2020-04-21T00:00:00+02:00',
|
|
||||||
validUntil: '2020-07-24T23:59:00+02:00',
|
|
||||||
modified: '2020-07-08T12:52:13+02:00',
|
|
||||||
affectedLines: [{
|
|
||||||
type: 'line',
|
|
||||||
id: '452',
|
|
||||||
name: '452',
|
|
||||||
public: true,
|
|
||||||
}],
|
|
||||||
products: {
|
|
||||||
'bahn-s-bahn': false,
|
|
||||||
'u-bahn': false,
|
|
||||||
strassenbahn: false,
|
|
||||||
fernbus: false,
|
|
||||||
regionalbus: true,
|
|
||||||
stadtbus: false,
|
|
||||||
'seilbahn-zahnradbahn': false,
|
|
||||||
schiff: false
|
|
||||||
},
|
|
||||||
categories: [1],
|
|
||||||
icon: {type: 'HIM1', title: null},
|
|
||||||
},
|
|
||||||
// …
|
|
||||||
{
|
|
||||||
id: 'HIM_FREETEXT_106671',
|
|
||||||
type: 'warning',
|
|
||||||
summary: 'Neue Haltestellennamen',
|
|
||||||
text: 'Im Zuge der Neuordnung der Regionalbusverkehre werden mit 6.7.2020 neue Fahrpläne und Liniennummern wirksam und dadurch können sich mitunter die Haltestellennamen verändern.',
|
|
||||||
priority: 100,
|
|
||||||
company: 'VOR',
|
|
||||||
validFrom: '2020-04-21T00:00:00+02:00',
|
|
||||||
validUntil: '2020-09-30T23:59:00+02:00',
|
|
||||||
modified: '2020-04-21T13:20:41+02:00',
|
|
||||||
products: {
|
|
||||||
'bahn-s-bahn': true,
|
|
||||||
'u-bahn': true,
|
|
||||||
strassenbahn: true,
|
|
||||||
fernbus: true,
|
|
||||||
regionalbus: true,
|
|
||||||
stadtbus: true,
|
|
||||||
'seilbahn-zahnradbahn': false,
|
|
||||||
schiff: true,
|
|
||||||
},
|
|
||||||
categories: [4],
|
|
||||||
icon: {type: 'HIM4', title: null},
|
|
||||||
},
|
|
||||||
// …
|
|
||||||
]
|
|
||||||
```
|
|
|
@ -1,38 +0,0 @@
|
||||||
# `serverInfo([opt])`
|
|
||||||
|
|
||||||
**Fetches meta information from the HAFAS endpoint.**
|
|
||||||
|
|
||||||
With `opt`, you can override the default options, which look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
versionInfo: true, // query HAFAS versions?
|
|
||||||
language: 'en', // depends on the profile
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
As an example, we're going to use the [SVV profile](../p/svv):
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
import {profile as svvProfile} from 'hafas-client/p/svv/index.js'
|
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
|
||||||
const client = createClient(svvProfile, userAgent)
|
|
||||||
|
|
||||||
await client.serverInfo()
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
// version of the HAFAS Connection Interface (HCI), the API that hafas-client uses
|
|
||||||
hciVersion: '1.23',
|
|
||||||
|
|
||||||
timetableStart: '20200517',
|
|
||||||
timetableEnd: '20201212',
|
|
||||||
serverTime: '2020-07-19T21:32:12+02:00',
|
|
||||||
realtimeDataUpdatedAt: 1595187102,
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,5 +1,7 @@
|
||||||
# `stop(id, [opt])`
|
# `stop(id, [opt])`
|
||||||
|
|
||||||
|
This endpoint is only available with `dbnav` profile.
|
||||||
|
|
||||||
`id` must be in one of these formats:
|
`id` must be in one of these formats:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@ -23,8 +25,8 @@ With `opt`, you can override the default options, which look like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
subStops: true, // parse & expose sub-stops of stations?
|
subStops: true, // not supported
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // not supported
|
||||||
linesOfStops: false, // parse & expose lines at the stop/station?
|
linesOfStops: false, // parse & expose lines at the stop/station?
|
||||||
language: 'en' // language to get results in
|
language: 'en' // language to get results in
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# automated tests in `hafas-client`
|
# automated tests in `db-vendo-client`
|
||||||
|
|
||||||
Because transit data is inherently dynamic (e.g. a different set of departures being returned for a stop now than in 10 minutes), and because it is of paramount importance that `hafas-client` actually works with HAFAS endpoints *as they currently work*, its testing setup is a bit unusual.
|
Because transit data is inherently dynamic (e.g. a different set of departures being returned for a stop now than in 10 minutes), and because it is of paramount importance that `db-vendo-client` actually works with HAFAS endpoints *as they currently work*, its testing setup is a bit unusual.
|
||||||
|
|
||||||
`hafas-client` has three kinds of automated tests:
|
`db-vendo-client` has three kinds of automated tests:
|
||||||
- unit tests, which test individual aspects of the case base in isolation (e.g. the parsing of HAFAS-formatted dates & times) – run via `npm run test-unit`
|
- unit tests, which test individual aspects of the case base in isolation (e.g. the parsing of HAFAS-formatted dates & times) – run via `npm run test-unit`
|
||||||
- end-to-end (E2E) tests, which run actual HTTP requests against their respective profile's HAFAS endpoint – run via `npm run test-e2e`
|
- end-to-end (E2E) tests, which run actual HTTP requests against their respective profile's HAFAS endpoint – run via `npm run test-e2e`
|
||||||
- integration tests, which are the E2E tests running against pre-recorded (and checked-in) HTTP request fixtures – run via `npm run test-integration`
|
- integration tests, which are the E2E tests running against pre-recorded (and checked-in) HTTP request fixtures – run via `npm run test-integration`
|
||||||
|
@ -16,7 +16,7 @@ Because the E2E & integration tests are based on the same code, when changing th
|
||||||
As an example, let's assume that we have added an entirely new test to [the *DB* profile's E2E tests](../test/e2e/db.js).
|
As an example, let's assume that we have added an entirely new test to [the *DB* profile's E2E tests](../test/e2e/db.js).
|
||||||
|
|
||||||
The behaviour of the HTTP request recording (into fixtures) and mocking (using the recorded fixtures) is controlled via an environment variable `$VCR_MODE`:
|
The behaviour of the HTTP request recording (into fixtures) and mocking (using the recorded fixtures) is controlled via an environment variable `$VCR_MODE`:
|
||||||
- By running the test(s) with `VCR_MODE=record`, we can record the HTTP requests being made. The tests will run just like without `$VCR_MODE`, except that they will query data for date+time specified in `T_MOCK` (e.g. [here](https://github.com/public-transport/hafas-client/blob/8ff945c07515155380de0acb33584e474d6d547c/test/e2e/db.js#L33)).
|
- By running the test(s) with `VCR_MODE=record`, we can record the HTTP requests being made. The tests will run just like without `$VCR_MODE`, except that they will query data for date+time specified in `T_MOCK` (e.g. [here](https://github.com/public-transport/db-vendo-client/blob/8ff945c07515155380de0acb33584e474d6d547c/test/e2e/db.js#L33)).
|
||||||
- Then, by running the test(s) with `VCR_MODE=playback`, because their HTTP requests match the pre-recorded fixtures, they work on the corresponding mocked HTTP responses.
|
- Then, by running the test(s) with `VCR_MODE=playback`, because their HTTP requests match the pre-recorded fixtures, they work on the corresponding mocked HTTP responses.
|
||||||
|
|
||||||
Usually, you would not want to update all *already existing* recorded HTTP request fixtures of the test suite you have made changes in, as they are unrelated to the test you have added. To only record your *added* test, temporarily change `tap.test(…)` to read `tap.only(…)`, and run with `TAP_ONLY=1 VCR_MODE=record`; This will skip all unrelated tests entirely.
|
Usually, you would not want to update all *already existing* recorded HTTP request fixtures of the test suite you have made changes in, as they are unrelated to the test you have added. To only record your *added* test, temporarily change `tap.test(…)` to read `tap.only(…)`, and run with `TAP_ONLY=1 VCR_MODE=record`; This will skip all unrelated tests entirely.
|
||||||
|
|
44
docs/trip.md
44
docs/trip.md
|
@ -2,19 +2,16 @@
|
||||||
|
|
||||||
This method can be used to refetch information about a trip – a vehicle stopping at a set of stops at specific times.
|
This method can be used to refetch information about a trip – a vehicle stopping at a set of stops at specific times.
|
||||||
|
|
||||||
*Note*: This method is not supported by every profile/endpoint.
|
|
||||||
|
|
||||||
Let's say you used [`journeys`](journeys.md) and now want to get more up-to-date data about the arrival/departure of a leg. You'd pass in the trip ID from `leg.tripId`, e.g. `'1|24983|22|86|18062017'`, and the name of the line from `leg.line.name` like this:
|
Let's say you used [`journeys`](journeys.md) and now want to get more up-to-date data about the arrival/departure of a leg. You'd pass in the trip ID from `leg.tripId`, e.g. `'1|24983|22|86|18062017'`, and the name of the line from `leg.line.name` like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
import {profile as vbbProfile} from 'db-vendo-client/p/dbnav/index.js'
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
||||||
const client = createClient(vbbProfile, userAgent)
|
const client = createClient(vbbProfile, userAgent)
|
||||||
|
|
||||||
// Hauptbahnhof to Heinrich-Heine-Str.
|
const {journeys} = client.journeys('8000096', '8000105', {results: 1})
|
||||||
const {journeys} = client.journeys('900000003201', '900000100008', {results: 1})
|
|
||||||
const leg = journeys[0].legs[0]
|
const leg = journeys[0].legs[0]
|
||||||
|
|
||||||
await client.trip(leg.tripId)
|
await client.trip(leg.tripId)
|
||||||
|
@ -25,9 +22,9 @@ With `opt`, you can override the default options, which look like this:
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
stopovers: true, // return stations on the way?
|
stopovers: true, // return stations on the way?
|
||||||
polyline: false, // return a shape for the trip?
|
polyline: false, // return a shape for the trip? only supported with HAFAS trip id (i.e. not with a trip id from a departure/arrival board of the `db` profile)
|
||||||
subStops: true, // parse & expose sub-stops of stations?
|
subStops: true, // not supported
|
||||||
entrances: true, // parse & expose entrances of stops/stations?
|
entrances: true, // not supported
|
||||||
remarks: true, // parse & expose hints & warnings?
|
remarks: true, // parse & expose hints & warnings?
|
||||||
language: 'en' // language to get results in
|
language: 'en' // language to get results in
|
||||||
}
|
}
|
||||||
|
@ -37,11 +34,10 @@ With `opt`, you can override the default options, which look like this:
|
||||||
|
|
||||||
*Note:* As stated in the [*Friendly Public Transport Format* v2 draft spec](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md), the returned `departure` and `arrival` times include the current delay. The `departureDelay`/`arrivalDelay` fields express how much they differ from the schedule.
|
*Note:* As stated in the [*Friendly Public Transport Format* v2 draft spec](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md), the returned `departure` and `arrival` times include the current delay. The `departureDelay`/`arrivalDelay` fields express how much they differ from the schedule.
|
||||||
|
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import {createClient} from 'hafas-client'
|
import {createClient} from 'db-vendo-client'
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
import {profile as vbbProfile} from 'db-vendo-client/p/dbnav/index.js'
|
||||||
|
|
||||||
const client = createClient(vbbProfile)
|
const client = createClient(vbbProfile)
|
||||||
|
|
||||||
|
@ -53,7 +49,7 @@ const {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated.
|
`realtimeDataUpdatedAt` is currently not set in db-vendo-client, because the upstream APIs don't provide it.
|
||||||
|
|
||||||
When running the code above, `trip` looked like this:
|
When running the code above, `trip` looked like this:
|
||||||
|
|
||||||
|
@ -142,9 +138,9 @@ When running the code above, `trip` looked like this:
|
||||||
|
|
||||||
### `polyline` option
|
### `polyline` option
|
||||||
|
|
||||||
If you pass `polyline: true`, the trip will have a `polyline` field, containing a [GeoJSON](http://geojson.org) [`FeatureCollection`](https://tools.ietf.org/html/rfc7946#section-3.3) of [`Point`s](https://tools.ietf.org/html/rfc7946#appendix-A.1). Every `Point` next to a station will have `properties` containing the station's metadata.
|
Only supported with HAFAS trip id (i.e. not with a trip id from a departure/arrival board of the `db` profile).
|
||||||
|
|
||||||
We'll look at an example for *U6* from *Alt-Mariendorf* to *Alt-Tegel*, taken from the [VBB profile](../p/vbb):
|
If you pass `polyline: true`, the trip will have a `polyline` field, containing a [GeoJSON](http://geojson.org) [`FeatureCollection`](https://tools.ietf.org/html/rfc7946#section-3.3) of [`Point`s](https://tools.ietf.org/html/rfc7946#appendix-A.1).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
|
@ -152,12 +148,6 @@ We'll look at an example for *U6* from *Alt-Mariendorf* to *Alt-Tegel*, taken fr
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: {
|
|
||||||
type: 'station',
|
|
||||||
id: '900000070301',
|
|
||||||
name: 'U Alt-Mariendorf',
|
|
||||||
/* … */
|
|
||||||
},
|
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
coordinates: [13.3875, 52.43993] // longitude, latitude
|
coordinates: [13.3875, 52.43993] // longitude, latitude
|
||||||
|
@ -166,12 +156,6 @@ We'll look at an example for *U6* from *Alt-Mariendorf* to *Alt-Tegel*, taken fr
|
||||||
/* … */
|
/* … */
|
||||||
{
|
{
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: {
|
|
||||||
type: 'station',
|
|
||||||
id: '900000017101',
|
|
||||||
name: 'U Mehringdamm',
|
|
||||||
/* … */
|
|
||||||
},
|
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
coordinates: [13.38892, 52.49448] // longitude, latitude
|
coordinates: [13.38892, 52.49448] // longitude, latitude
|
||||||
|
@ -189,12 +173,6 @@ We'll look at an example for *U6* from *Alt-Mariendorf* to *Alt-Tegel*, taken fr
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: {
|
|
||||||
type: 'station',
|
|
||||||
id: '900000089301',
|
|
||||||
name: 'U Alt-Tegel',
|
|
||||||
/* … */
|
|
||||||
},
|
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
coordinates: [13.28406, 52.58915] // longitude, latitude
|
coordinates: [13.28406, 52.58915] // longitude, latitude
|
||||||
|
|
|
@ -1,114 +0,0 @@
|
||||||
# `tripsByName([lineNameOrFahrtNr], [opt])`
|
|
||||||
|
|
||||||
Get all trips matching one or more criteria, e.g. a specific name.
|
|
||||||
|
|
||||||
## Response
|
|
||||||
|
|
||||||
As an example, we're going to use the [VBB profile](../p/vbb):
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {createClient} from 'hafas-client'
|
|
||||||
import {profile as vbbProfile} from 'hafas-client/p/vbb/index.js'
|
|
||||||
|
|
||||||
const userAgent = 'link-to-your-project-or-email' // adapt this to your project!
|
|
||||||
const client = createClient(vbbProfile, userAgent)
|
|
||||||
|
|
||||||
const {
|
|
||||||
trips,
|
|
||||||
realtimeDataUpdatedAt,
|
|
||||||
} = await client.tripsByName('S1')
|
|
||||||
```
|
|
||||||
|
|
||||||
With `opt`, you can override the default options, which look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
// use either this
|
|
||||||
when: null,
|
|
||||||
// or these
|
|
||||||
fromWhen: null, untilWhen: null,
|
|
||||||
|
|
||||||
onlyCurrentlyRunning: true,
|
|
||||||
products: {
|
|
||||||
// these entries may vary from profile to profile
|
|
||||||
suburban: true,
|
|
||||||
subway: true,
|
|
||||||
tram: true,
|
|
||||||
bus: true,
|
|
||||||
ferry: true,
|
|
||||||
express: true,
|
|
||||||
regional: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
currentlyStoppingAt: null, // only show trips currently stopping at a stop/station, string
|
|
||||||
lineName: null, // only show trips with this line name, string
|
|
||||||
operatorNames: null, // only show trips with these operator names, array of strings
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`realtimeDataUpdatedAt` is a UNIX timestamp reflecting the latest moment when (at least some of) the response's realtime data have been updated.
|
|
||||||
|
|
||||||
`trips` may look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: '1|1214|0|86|16092020'
|
|
||||||
direction: null,
|
|
||||||
line: {
|
|
||||||
type: 'line',
|
|
||||||
id: 's1',
|
|
||||||
fahrtNr: '325',
|
|
||||||
name: 'S1',
|
|
||||||
mode: 'train',
|
|
||||||
product: 'suburban',
|
|
||||||
// …
|
|
||||||
},
|
|
||||||
|
|
||||||
origin: {
|
|
||||||
type: 'stop',
|
|
||||||
id: '900000550239',
|
|
||||||
name: 'Warnemünde, Bhf',
|
|
||||||
location: { /* … */ },
|
|
||||||
products: { /* … */ },
|
|
||||||
},
|
|
||||||
departure: '2020-09-16T04:03:00+02:00',
|
|
||||||
plannedDeparture: '2020-09-16T04:03:00+02:00',
|
|
||||||
departureDelay: null,
|
|
||||||
departurePlatform: null,
|
|
||||||
plannedDeparturePlatform: null,
|
|
||||||
|
|
||||||
destination: {
|
|
||||||
type: 'stop',
|
|
||||||
id: '900000550002',
|
|
||||||
name: 'Rostock, Hbf',
|
|
||||||
location: { /* … */ },
|
|
||||||
products: { /* … */ },
|
|
||||||
},
|
|
||||||
arrival: '2020-09-16T04:24:00+02:00',
|
|
||||||
plannedArrival: '2020-09-16T04:24:00+02:00',
|
|
||||||
arrivalDelay: null,
|
|
||||||
arrivalPlatform: null,
|
|
||||||
plannedArrivalPlatform: null,
|
|
||||||
},
|
|
||||||
// …
|
|
||||||
{
|
|
||||||
id: '1|62554|0|86|16092020'
|
|
||||||
direction: null,
|
|
||||||
line: {
|
|
||||||
type: 'line',
|
|
||||||
id: 's1',
|
|
||||||
fahrtNr: '2001',
|
|
||||||
name: 'S1',
|
|
||||||
public: true,
|
|
||||||
mode: 'train',
|
|
||||||
product: 'suburban',
|
|
||||||
// …
|
|
||||||
},
|
|
||||||
|
|
||||||
origin: { /* … */ },
|
|
||||||
destination: { /* … */ },
|
|
||||||
// …
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
|
@ -1,163 +0,0 @@
|
||||||
# Writing a profile
|
|
||||||
|
|
||||||
**Per HAFAS endpoint, `hafas-client` has an endpoint-specific customisation called *profile*.** A profile may, for example, do the following:
|
|
||||||
|
|
||||||
- handle the additional requirements of the endpoint (e.g. authentication),
|
|
||||||
- extract additional information from the data provided by the endpoint,
|
|
||||||
- guard against triggering bugs of certain endpoints (e.g. time limits).
|
|
||||||
|
|
||||||
This guide is about writing such a profile. If you just want to use an already supported endpoint, refer to the [main readme](../readme.md) instead.
|
|
||||||
|
|
||||||
*Note*: **If you get stuck, ask for help by [creating an issue](https://github.com/public-transport/hafas-client/issues/new)**; We're happy to help you expand the scope of this library!
|
|
||||||
|
|
||||||
## 0. How do the profiles work?
|
|
||||||
|
|
||||||
A profile may consist of three things:
|
|
||||||
|
|
||||||
- **mandatory details about the HAFAS endpoint**
|
|
||||||
- `endpoint`: The protocol, host and path of the endpoint.
|
|
||||||
- `locale`: The [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag) [locale](https://en.wikipedia.org/wiki/Locale_(computer_software)) of your endpoint (or the area that your endpoint covers).
|
|
||||||
- `timezone`: An [IANA-time-zone](https://www.iana.org/time-zones)-compatible [timezone](https://en.wikipedia.org/wiki/Time_zone) of your endpoint.
|
|
||||||
- **flags indicating which features are supported by the endpoint** – e.g. `trip`
|
|
||||||
- **methods overriding the [default profile](../lib/default-profile.js)**
|
|
||||||
|
|
||||||
Let's use a fictional endpoint for [Austria](https://en.wikipedia.org/wiki/Austria) as an example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const myProfile = {
|
|
||||||
endpoint: 'https://example.org/bin/mgate.exe',
|
|
||||||
locale: 'de-AT',
|
|
||||||
timezone: 'Europe/Vienna'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Assuming their HAFAS endpoint returns all line names prefixed with `foo `, we can adapt our profile to clean them:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// get the default line parser
|
|
||||||
import {parseLine} from 'hafas-client/parse/line.js'
|
|
||||||
|
|
||||||
// wrapper function with additional logic
|
|
||||||
const parseLineWithoutFoo = (ctx, rawLine) => {
|
|
||||||
const line = parseLine(ctx, rawLine)
|
|
||||||
line.name = line.name.replace(/foo /g, '')
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
|
|
||||||
myProfile.parseLine = parseLineWithoutFoo
|
|
||||||
```
|
|
||||||
|
|
||||||
If you pass this profile into `hafas-client`, the `parseLine` method will override [the default one](../parse/line.js).
|
|
||||||
|
|
||||||
You can also use the `parseHook` helper to reduce boilerplate:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {parseHook} from 'hafas-client/lib/profile-hooks.js'
|
|
||||||
|
|
||||||
const removeFoo = (ctx, rawLine) => ({
|
|
||||||
...ctx.parsed,
|
|
||||||
name: line.name.replace(/foo /g, '')
|
|
||||||
})
|
|
||||||
|
|
||||||
myProfile.parseLine = parseHook(parseLine, removeFoo)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1. Setup
|
|
||||||
|
|
||||||
*Note*: There are many ways to find the required values. This way is rather easy and works with most endpoints by now.
|
|
||||||
|
|
||||||
1. **Find the journey planning webapp** corresponding to the API endpoint; Usually, you can find it on the public transport provider's website.
|
|
||||||
2. **Open your [browser's devtools](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools)**, switch to the "Network" tab, and **inspect the requests to the HAFAS API**.
|
|
||||||
|
|
||||||
If you can't find the webapp or your public transport provider doesn't have one, you can inspect their mobile app's traffic instead:
|
|
||||||
|
|
||||||
1. Get an iOS or Android device and **download the "official" app.**
|
|
||||||
2. **Configure a [man-in-the-middle HTTP proxy](https://docs.mitmproxy.org/stable/concepts-howmitmproxyworks/)** like [mitmproxy](https://mitmproxy.org).
|
|
||||||
- Configure your device to trust the self-signed SSL certificate, [as outlined in the mitmproxy docs](https://docs.mitmproxy.org/stable/concepts-certificates/).
|
|
||||||
- *Note*: This method does not work if the app uses [public key pinning](https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning). In this case (the app won't be able to query data), please [create an issue](https://github.com/public-transport/hafas-client/issues/new), so we can discuss other techniques.
|
|
||||||
3. **Record requests of the app.**
|
|
||||||
- [There's a video showing this step](https://stuff.jannisr.de/how-to-record-hafas-requests.mp4).
|
|
||||||
- Make sure to cover all relevant sections of the app, e.g. "journeys", "departures", "live map". Better record more than less!
|
|
||||||
- To help others in the future, post the requests (in their entirety!) on GitHub, e.g. in as format like [this](https://gist.github.com/derhuerst/5fa86ed5aec63645e5ae37e23e555886). This will also let us help you if you have any questions.
|
|
||||||
|
|
||||||
## 2. Basic profile
|
|
||||||
|
|
||||||
*Note:* You should have read the [general documentation on `mgate.exe` APIs](hafas-mgate-api.md) to make sense of the terminology used below.
|
|
||||||
|
|
||||||
You may want to start with the [profile boilerplate](profile-boilerplate.js).
|
|
||||||
|
|
||||||
- **Identify the `endpoint`.** The protocol, host and path of the endpoint, *but not* the query string.
|
|
||||||
- *Note*: **`hafas-client` for now only supports the interface providing JSON** (generated from XML), which is being used by the corresponding iOS/Android apps. It supports neither the JSONP, nor the XML, nor the HTML interface. If the endpoint does not end in `mgate.exe`, it mostly likely won't work.
|
|
||||||
- **Identify the `locale`.** Basically guess work; Use the date & time formats as an indicator.
|
|
||||||
- **Identify the `timezone`.** This may be tricky, a for example [Deutsche Bahn](https://en.wikipedia.org/wiki/Deutsche_Bahn) returns departures for Moscow as `+01:00` instead of `+03:00`.
|
|
||||||
- **Copy the authentication** and other meta fields, namely `ver`, `ext`, `client` and `lang`.
|
|
||||||
- You can find these fields in the root of each request JSON. Check [a VBB request](https://gist.github.com/derhuerst/ea5d6482b61aeb7384a2c788f43dc11d#file-0-serverinfo-http-L11-L33) and [the corresponding VBB profile](https://github.com/public-transport/hafas-client/blob/2baf2f6f0444ffc67317f8bafe0fe05f687e5fae/p/vbb/base.json#L2-L11) for an example.
|
|
||||||
- Add a function `transformReqBody(ctx, body)` to your profile, which adds the fields to `body`. todo: adapt this
|
|
||||||
- Some profiles have a `checksum` parameter (like [here](https://gist.github.com/derhuerst/2a735268bd82a0a6779633f15dceba33#file-journey-details-1-http-L1)) or two `mic` & `mac` parameters (like [here](https://gist.github.com/derhuerst/5fa86ed5aec63645e5ae37e23e555886#file-1-http-L1)). If you see one of them in your requests, jump to the [*Authentication* section of the `mgate.exe` docs](hafas-mgate-api.md#authentication). Unfortunately, this is necessary to get the profile working.
|
|
||||||
|
|
||||||
## 3. Products
|
|
||||||
|
|
||||||
In `hafas-client`, there's a distinction between the `mode` and the `product` fields:
|
|
||||||
|
|
||||||
- The `mode` field describes the mode of transport in general. [Standardised by the *Friendly Public Transport Format*](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md#modes), it is on purpose limited to a very small number of possible values, e.g. `train` or `bus`.
|
|
||||||
- The value for `product` relates to how a means of transport "works" *in local context*. Example: Even though [*S-Bahn*](https://en.wikipedia.org/wiki/Berlin_S-Bahn) and [*U-Bahn*](https://en.wikipedia.org/wiki/Berlin_U-Bahn) in Berlin are both `train`s, they have different operators, service patterns, stations and look different. Therefore, they are two distinct `product`s `subway` and `suburban`.
|
|
||||||
|
|
||||||
**Specify `product`s that appear in the app** you recorded requests of. For a fictional transit network, this may look like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const products = [
|
|
||||||
{
|
|
||||||
id: 'commuterTrain',
|
|
||||||
mode: 'train',
|
|
||||||
bitmasks: [16],
|
|
||||||
name: 'ACME Commuter Rail',
|
|
||||||
short: 'CR',
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'metro',
|
|
||||||
mode: 'train',
|
|
||||||
bitmasks: [8],
|
|
||||||
name: 'Foo Bar Metro',
|
|
||||||
short: 'M',
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's break this down:
|
|
||||||
|
|
||||||
- `id`: A sensible, [camelCased](https://en.wikipedia.org/wiki/Camel_case#Variations_and_synonyms), alphanumeric identifier. Use it for the key in the `products` array as well.
|
|
||||||
- `mode`: A [valid *Friendly Public Transport Format* mode](https://github.com/public-transport/friendly-public-transport-format/blob/3bd36faa721e85d9f5ca58fb0f38cdbedb87bbca/spec/readme.md#modes).
|
|
||||||
- `bitmasks`: HAFAS endpoints work with a [bitmask](https://en.wikipedia.org/wiki/Mask_(computing)#Arguments_to_functions) that toggles the individual products. It should be an array of values that toggle the appropriate bit(s) in the bitmask (see below).
|
|
||||||
- `name`: A short, but distinct name for the means of transport, *just precise enough in local context*, and in the local language. In Berlin, `S-Bahn-Schnellzug` would be too much, because everyone knows what `S-Bahn` means.
|
|
||||||
- `short`: The shortest possible symbol that identifies the product.
|
|
||||||
- `default`: Should the product be used for queries (e.g. journeys) by default?
|
|
||||||
|
|
||||||
If you want, you can now **verify that the profile works**; We've prepared [a script](https://runkit.com/derhuerst/hafas-client-profile-example/0.2.1) for that. Alternatively, [submit a Pull Request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) and we will help you out with testing and improvements.
|
|
||||||
|
|
||||||
### Finding the right values for the `bitmasks` field
|
|
||||||
|
|
||||||
As shown in [the video](https://stuff.jannisr.de/how-to-record-hafas-requests.mp4), search for a journey and toggle off one product at a time, recording the requests. After extracting the products bitmask ([example](https://gist.github.com/derhuerst/193ef489f8aa50c2343f8bf1f2a22069#file-via-http-L34)) you will end up with values looking like these:
|
|
||||||
|
|
||||||
```
|
|
||||||
toggles value binary subtraction bit(s)
|
|
||||||
all products 31 11111 31 - 0
|
|
||||||
all but ACME Commuter Rail 15 01111 31 - 2^4 2^4
|
|
||||||
all but Foo Bar Metro 23 10111 31 - 2^3 2^3
|
|
||||||
all but product E 25 11001 31 - 2^2 - 2^1 2^2, 2^1
|
|
||||||
all but product F 30 11110 31 - 2^0 2^0
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Additional info
|
|
||||||
|
|
||||||
We consider these improvements to be *optional*:
|
|
||||||
- **Check if the endpoint supports the `trip()` call.**
|
|
||||||
- In the app, check if you can re-fetch details for the status of a single journey leg. It should load realtime delays and the current progress.
|
|
||||||
- If this feature is supported, add `trip: true` to the profile.
|
|
||||||
- **Check if the endpoint supports the live map call.** Does the app have a "live map" showing all vehicles within an area? If so, add `radar: true` to the profile.
|
|
||||||
- **Consider transforming station & line names** into the formats that's most suitable for *local users*. This is just an optimal optimisation that makes it easier for users of the profile to use the data. Some examples:
|
|
||||||
- `M13 (Tram)` -> `M13`. With Berlin context, it is obvious that `M13` is a tram.
|
|
||||||
- `Berlin Jungfernheide Bhf` -> `Berlin Jungfernheide`. With local context, it's obvious that *Jungfernheide* is a train station.
|
|
||||||
- **Check if the endpoint has non-obvious limitations** and let use know about these. Examples:
|
|
||||||
- Some endpoints have a time limit, after which they won't return more departures, but silently discard them.
|
|
2
index.js
2
index.js
|
@ -76,7 +76,7 @@ const createClient = (profile, userAgent, opt = {}) => {
|
||||||
if (!profile.departuresGetPasslist && 'stopovers' in opt) {
|
if (!profile.departuresGetPasslist && 'stopovers' in opt) {
|
||||||
throw new Error('opt.stopovers is not supported by this endpoint');
|
throw new Error('opt.stopovers is not supported by this endpoint');
|
||||||
}
|
}
|
||||||
if (!profile.departuresStbFltrEquiv && 'includeRelatedStations' in opt) {
|
if (!profile.departuresStbFltrEquiv && 'includeRelatedStations' in opt) { // TODO artificially filter?
|
||||||
throw new Error('opt.includeRelatedStations is not supported by this endpoint');
|
throw new Error('opt.includeRelatedStations is not supported by this endpoint');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
This is a very early version. What works:
|
This is a very early version. What works:
|
||||||
|
|
||||||
* `journeys()`, `refreshJourney()` including tickets
|
* `journeys()`, `refreshJourney()` including tickets
|
||||||
* `locations()`, `nearby()`, `stop()`
|
* `locations()`, `nearby()`,
|
||||||
* `departures()`, `arrivals()` boards
|
* `departures()`, `arrivals()` boards
|
||||||
* `trip()`
|
* `trip()`
|
||||||
|
|
||||||
|
@ -38,8 +38,7 @@ Depending on the configured profile, db-vendo-client will use multiple different
|
||||||
|
|
||||||
Feel free to report anything that you stumble upon via Issues or create a PR :)
|
Feel free to report anything that you stumble upon via Issues or create a PR :)
|
||||||
|
|
||||||
Also consult the relevant **[documentation](https://github.com/public-transport/hafas-client/blob/main/docs/readme.md)** of [hafas-client](https://github.com/public-transport/hafas-client/) (but beware of the limited functionality of db-vendo-client).
|
Also consult the **[documentation](docs/readme.md)**.
|
||||||
|
|
||||||
|
|
||||||
## Background
|
## Background
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue