mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-04-20 23:23:56 +03:00
Merge branch 'next' into format-location-err-msg
This commit is contained in:
commit
bebb975584
23 changed files with 274 additions and 60 deletions
|
@ -1,6 +1,45 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## `2.7.0`
|
||||||
|
|
||||||
|
- `journeys()`: `polylines` option
|
||||||
|
- `journeyLeg()`: `polyline` option
|
||||||
|
- `radar()`: `polylines` option
|
||||||
|
|
||||||
|
## `2.6.0`
|
||||||
|
|
||||||
|
- 5d10d76 journey legs: parse cycle
|
||||||
|
|
||||||
|
## `2.5.3`
|
||||||
|
|
||||||
|
- d676b84 fix parsing for journey leg alternatives 🐛
|
||||||
|
|
||||||
|
## `2.5.2`
|
||||||
|
|
||||||
|
- 16e6dd6 departure docs: fix method 📝
|
||||||
|
- c60213a DB: tram mode should be `train` 🐛
|
||||||
|
|
||||||
|
## `2.5.1`
|
||||||
|
|
||||||
|
- afc0124 fix stopover parsing 🐛
|
||||||
|
|
||||||
## `2.5.0`
|
## `2.5.0`
|
||||||
|
|
||||||
- new [Schleswig-Holstein (NAH.SH)](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) [profile](../p/nahsh)
|
- new [Schleswig-Holstein (NAH.SH)](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) [profile](../p/nahsh)
|
||||||
- new [*writing a profile* guide](./writing-a-profile.md)
|
- new [*writing a profile* guide](./writing-a-profile.md)
|
||||||
|
|
||||||
|
## `2.4.2`
|
||||||
|
|
||||||
|
- `parseStopover`: expose canceled arrivals & departures 🐛
|
||||||
|
|
||||||
|
## `2.4.1`
|
||||||
|
|
||||||
|
- new [*writing a profile* guide](./writing-a-profile.md)
|
||||||
|
- `parseMovement`: use `parseStopover` 🐛
|
||||||
|
- `parseStopover`: use `parseStationName` 🐛
|
||||||
|
|
||||||
|
## `2.4.0`
|
||||||
|
|
||||||
|
- new [Nahverkehr Sachsen-Anhalt (NASA)](https://de.wikipedia.org/wiki/Nahverkehrsservice_Sachsen-Anhalt)/[INSA](https://insa.de) profile
|
||||||
|
- new `earlierRef`/`laterRef` feature to query earlier/later journeys (pagination)
|
||||||
|
- former scheduled date & time for canceled departures & journeys
|
||||||
|
|
|
@ -44,7 +44,7 @@ const vbbProfile = require('hafas-client/p/vbb')
|
||||||
const client = createClient(vbbProfile)
|
const client = createClient(vbbProfile)
|
||||||
|
|
||||||
// S Charlottenburg
|
// S Charlottenburg
|
||||||
client.journeys('900000024101', {duration: 3})
|
client.departures('900000024101', {duration: 3})
|
||||||
.then(console.log)
|
.then(console.log)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
```
|
```
|
||||||
|
|
|
@ -25,7 +25,8 @@ With `opt`, you can override the default options, which look like this:
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
when: new Date(),
|
when: new Date(),
|
||||||
passedStations: true // return stations on the way?
|
passedStations: true, // return stations on the way?
|
||||||
|
polyline: false // return a shape for the leg?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -116,3 +117,67 @@ The response looked like this:
|
||||||
passed: [ /* … */ ]
|
passed: [ /* … */ ]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `polyline` option
|
||||||
|
|
||||||
|
If you pass `polyline: true`, the leg 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.
|
||||||
|
|
||||||
|
We'll look at an example for *U6* from *Alt-Mariendorf* to *Alt-Tegel*, taken from the [VBB profile](../p/vbb):
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
type: 'station',
|
||||||
|
id: '900000070301',
|
||||||
|
name: 'U Alt-Mariendorf',
|
||||||
|
/* … */
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [13.3875, 52.43993] // longitude, latitude
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* … */
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
type: 'station',
|
||||||
|
id: '900000017101',
|
||||||
|
name: 'U Mehringdamm',
|
||||||
|
/* … */
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [13.38892, 52.49448] // longitude, latitude
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* … */
|
||||||
|
{
|
||||||
|
// intermediate point, without associated station
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [13.28599, 52.58742] // longitude, latitude
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
type: 'station',
|
||||||
|
id: '900000089301',
|
||||||
|
name: 'U Alt-Tegel',
|
||||||
|
/* … */
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [13.28406, 52.58915] // longitude, latitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -60,7 +60,8 @@ With `opt`, you can override the default options, which look like this:
|
||||||
express: true,
|
express: true,
|
||||||
regional: true
|
regional: true
|
||||||
},
|
},
|
||||||
tickets: false // return tickets? only available with some profiles
|
tickets: false, // return tickets? only available with some profiles
|
||||||
|
polylines: false // return a shape for each leg?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -260,3 +261,5 @@ client.journeys(hbf, heinrichHeineStr)
|
||||||
departure of last journey 2017-12-17T19:07:00.000+01:00
|
departure of last journey 2017-12-17T19:07:00.000+01:00
|
||||||
departure of first (later) journey 2017-12-17T19:19:00.000+01:00
|
departure of first (later) journey 2017-12-17T19:19:00.000+01:00
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you pass `polylines: true`, each journey leg will have a `polyline` field. Refer to [the section in the `journeyLeg()` docs](journey-leg.md#polyline-option) for details.
|
||||||
|
|
|
@ -11,6 +11,7 @@ With `opt`, you can override the default options, which look like this:
|
||||||
results: 256, // maximum number of vehicles
|
results: 256, // maximum number of vehicles
|
||||||
duration: 30, // compute frames for the next n seconds
|
duration: 30, // compute frames for the next n seconds
|
||||||
frames: 3, // nr of frames to compute
|
frames: 3, // nr of frames to compute
|
||||||
|
polylines: false // return a track shape for each vehicle?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -161,3 +162,5 @@ The response may look like this:
|
||||||
} ]
|
} ]
|
||||||
}, /* … */ ]
|
}, /* … */ ]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you pass `polylines: true`, each journey leg will have a `polyline` field, as documented in [the corresponding section in the `journeyLeg()` docs](journey-leg.md#polyline-option), with the exception that station info is missing.
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
This guide is about writing such a profile. If you just want to use an already supported endpoint, refer to the [API documentation](readme.md) instead.
|
This guide is about writing such a profile. If you just want to use an already supported endpoint, refer to the [API documentation](readme.md) instead.
|
||||||
|
|
||||||
*Note*: **If you get stuck, ask for help by [creating an issue](https://github.com/derhuerst/hafas-client/issues/new)!** We want to help people expand the scope of this library.
|
*Note*: **If you get stuck, ask for help by [creating an issue](https://github.com/public-transport/hafas-client/issues/new)!** We want to help people expand the scope of this library.
|
||||||
|
|
||||||
## 0. How do the profiles work?
|
## 0. How do the profiles work?
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ If you pass this profile into `hafas-client`, the `parseLine` method will overri
|
||||||
1. **Get an iOS or Android device and download the "official" app** for the public transport provider that you want to build a profile for.
|
1. **Get an iOS or Android device and download the "official" app** for the public transport provider that you want to build a profile for.
|
||||||
2. **Configure a [man-in-the-middle HTTP proxy](https://docs.mitmproxy.org/stable/concepts-howmitmproxyworks/)** like [mitmproxy](https://mitmproxy.org).
|
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/).
|
- 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/derhuerst/hafas-client/issues/new), so we can discuss other techniques.
|
- *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.**
|
3. **Record requests of the app.**
|
||||||
- [There's a video showing this step](https://stuff.jannisr.de/how-to-record-hafas-requests.mp4).
|
- [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; You will regret not having enough information later on.
|
- Make sure to cover all relevant sections of the app, e.g. "journeys", "departures", "live map". Better record more than less; You will regret not having enough information later on.
|
||||||
|
@ -74,7 +74,7 @@ If you pass this profile into `hafas-client`, the `parseLine` method will overri
|
||||||
- **Identify the `locale`.** Basically guess work; Use the date & time formats as an indicator.
|
- **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`.
|
- **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`.
|
- **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/5fa86ed5aec63645e5ae37e23e555886#file-1-http-L13-L22) and [the corresponding VBB profile](https://github.com/derhuerst/hafas-client/blob/6e61097687a37b60d53e767f2711466b80c5142c/p/vbb/index.js#L22-L29) for an example.
|
- You can find these fields in the root of each request JSON. Check [a VBB request](https://gist.github.com/derhuerst/5fa86ed5aec63645e5ae37e23e555886#file-1-http-L13-L22) and [the corresponding VBB profile](https://github.com/public-transport/hafas-client/blob/6e61097687a37b60d53e767f2711466b80c5142c/p/vbb/index.js#L22-L29) for an example.
|
||||||
- Add a function `transformReqBody(body)` to your profile, which assigns them to `body`.
|
- Add a function `transformReqBody(body)` to your profile, which assigns them to `body`.
|
||||||
- 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 [*Appendix A: checksum, mic, mac*](#appendix-a-checksum-mic-mac). Unfortunately, this is necessary to get the profile working.
|
- 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 [*Appendix A: checksum, mic, mac*](#appendix-a-checksum-mic-mac). Unfortunately, this is necessary to get the profile working.
|
||||||
|
|
||||||
|
@ -160,10 +160,10 @@ You can just query these, as long as you send a formally correct request.
|
||||||
|
|
||||||
### endpoints using the `checksum` query parameter
|
### endpoints using the `checksum` query parameter
|
||||||
|
|
||||||
`checksum` is a [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code): `hafas-client` will 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 app bundle.** There is no guide for this yet, so please [open an issue](https://github.com/derhuerst/hafas-client/issues/new) instead.
|
`checksum` is a [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code): `hafas-client` will 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 app bundle.** There is no guide for this yet, so please [open an issue](https://github.com/public-transport/hafas-client/issues/new) instead.
|
||||||
|
|
||||||
### endpoints using the `mic` & `mac` query parameters
|
### 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.
|
`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 app bundle.** There is no guide for this yet, so please [open an issue](https://github.com/derhuerst/hafas-client/issues/new) instead.
|
`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 app bundle.** There is no guide for this yet, so please [open an issue](https://github.com/public-transport/hafas-client/issues/new) instead.
|
||||||
|
|
40
index.js
40
index.js
|
@ -31,7 +31,8 @@ const createClient = (profile, request = _request) => {
|
||||||
direction: null, // only show departures heading to this station
|
direction: null, // only show departures heading to this station
|
||||||
duration: 10 // show departures for the next n minutes
|
duration: 10 // show departures for the next n minutes
|
||||||
}, opt)
|
}, opt)
|
||||||
opt.when = opt.when || new Date()
|
opt.when = new Date(opt.when || Date.now())
|
||||||
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
||||||
const products = profile.formatProductsFilter(opt.products || {})
|
const products = profile.formatProductsFilter(opt.products || {})
|
||||||
|
|
||||||
const dir = opt.direction ? profile.formatStation(opt.direction) : null
|
const dir = opt.direction ? profile.formatStation(opt.direction) : null
|
||||||
|
@ -93,9 +94,11 @@ const createClient = (profile, request = _request) => {
|
||||||
accessibility: 'none', // 'none', 'partial' or 'complete'
|
accessibility: 'none', // 'none', 'partial' or 'complete'
|
||||||
bike: false, // only bike-friendly journeys
|
bike: false, // only bike-friendly journeys
|
||||||
tickets: false, // return tickets?
|
tickets: false, // return tickets?
|
||||||
|
polylines: false // return leg shapes?
|
||||||
}, opt)
|
}, opt)
|
||||||
if (opt.via) opt.via = profile.formatLocation(profile, opt.via, 'opt.via')
|
if (opt.via) opt.via = profile.formatLocation(profile, opt.via, 'opt.via')
|
||||||
opt.when = opt.when || new Date()
|
opt.when = new Date(opt.when || Date.now())
|
||||||
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
||||||
|
|
||||||
const filters = [
|
const filters = [
|
||||||
profile.formatProductsFilter(opt.products || {})
|
profile.formatProductsFilter(opt.products || {})
|
||||||
|
@ -113,7 +116,7 @@ const createClient = (profile, request = _request) => {
|
||||||
// `CGI_READ_FAILED` if you pass `numF`, the parameter for the number
|
// `CGI_READ_FAILED` if you pass `numF`, the parameter for the number
|
||||||
// of results. To circumvent this, we loop here, collecting journeys
|
// of results. To circumvent this, we loop here, collecting journeys
|
||||||
// until we have enough.
|
// until we have enough.
|
||||||
// see https://github.com/derhuerst/hafas-client/pull/23#issuecomment-370246163
|
// see https://github.com/public-transport/hafas-client/pull/23#issuecomment-370246163
|
||||||
// todo: check if `numF` is supported again, revert this change
|
// todo: check if `numF` is supported again, revert this change
|
||||||
const journeys = []
|
const journeys = []
|
||||||
const more = (when, journeysRef) => {
|
const more = (when, journeysRef) => {
|
||||||
|
@ -134,7 +137,7 @@ const createClient = (profile, request = _request) => {
|
||||||
getPT: true, // todo: what is this?
|
getPT: true, // todo: what is this?
|
||||||
outFrwd: true, // todo: what is this?
|
outFrwd: true, // todo: what is this?
|
||||||
getIV: false, // todo: walk & bike as alternatives?
|
getIV: false, // todo: walk & bike as alternatives?
|
||||||
getPolyline: false // todo: shape for displaying on a map?
|
getPolyline: !!opt.polylines
|
||||||
}
|
}
|
||||||
if (profile.journeysNumF) query.numF = opt.results
|
if (profile.journeysNumF) query.numF = opt.results
|
||||||
|
|
||||||
|
@ -145,7 +148,10 @@ const createClient = (profile, request = _request) => {
|
||||||
})
|
})
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
if (!Array.isArray(d.outConL)) return []
|
if (!Array.isArray(d.outConL)) return []
|
||||||
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks)
|
|
||||||
|
const polylines = opt.polyline && d.common.polyL || []
|
||||||
|
const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks, polylines)
|
||||||
|
|
||||||
if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB
|
if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB
|
||||||
|
|
||||||
let latestDep = -Infinity
|
let latestDep = -Infinity
|
||||||
|
@ -271,21 +277,26 @@ const createClient = (profile, request = _request) => {
|
||||||
throw new Error('lineName must be a non-empty string.')
|
throw new Error('lineName must be a non-empty string.')
|
||||||
}
|
}
|
||||||
opt = Object.assign({
|
opt = Object.assign({
|
||||||
passedStations: true // return stations on the way?
|
passedStations: true, // return stations on the way?
|
||||||
|
polyline: false
|
||||||
}, opt)
|
}, opt)
|
||||||
opt.when = opt.when || new Date()
|
opt.when = new Date(opt.when || Date.now())
|
||||||
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
||||||
|
|
||||||
return request(profile, {
|
return request(profile, {
|
||||||
cfg: {polyEnc: 'GPA'},
|
cfg: {polyEnc: 'GPA'},
|
||||||
meth: 'JourneyDetails',
|
meth: 'JourneyDetails',
|
||||||
req: {
|
req: {
|
||||||
|
// todo: getTrainComposition
|
||||||
jid: ref,
|
jid: ref,
|
||||||
name: lineName,
|
name: lineName,
|
||||||
date: profile.formatDate(profile, opt.when)
|
date: profile.formatDate(profile, opt.when),
|
||||||
|
getPolyline: !!opt.polyline
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
const parse = profile.parseJourneyLeg(profile, d.locations, d.lines, d.remarks)
|
const polylines = opt.polyline && d.common.polyL || []
|
||||||
|
const parse = profile.parseJourneyLeg(profile, d.locations, d.lines, d.remarks, polylines)
|
||||||
|
|
||||||
const leg = { // pretend the leg is contained in a journey
|
const leg = { // pretend the leg is contained in a journey
|
||||||
type: 'JNY',
|
type: 'JNY',
|
||||||
|
@ -302,14 +313,18 @@ const createClient = (profile, request = _request) => {
|
||||||
if ('number' !== typeof west) throw new Error('west must be a number.')
|
if ('number' !== typeof west) throw new Error('west must be a number.')
|
||||||
if ('number' !== typeof south) throw new Error('south must be a number.')
|
if ('number' !== typeof south) throw new Error('south must be a number.')
|
||||||
if ('number' !== typeof east) throw new Error('east must be a number.')
|
if ('number' !== typeof east) throw new Error('east must be a number.')
|
||||||
|
if (north <= south) throw new Error('north must be larger than south.')
|
||||||
|
if (east <= west) throw new Error('east must be larger than west.')
|
||||||
|
|
||||||
opt = Object.assign({
|
opt = Object.assign({
|
||||||
results: 256, // maximum number of vehicles
|
results: 256, // maximum number of vehicles
|
||||||
duration: 30, // compute frames for the next n seconds
|
duration: 30, // compute frames for the next n seconds
|
||||||
frames: 3, // nr of frames to compute
|
frames: 3, // nr of frames to compute
|
||||||
products: null // optionally an object of booleans
|
products: null, // optionally an object of booleans
|
||||||
|
polylines: false // return a track shape for each vehicle?
|
||||||
}, opt || {})
|
}, opt || {})
|
||||||
opt.when = opt.when || new Date()
|
opt.when = new Date(opt.when || Date.now())
|
||||||
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
||||||
|
|
||||||
const durationPerStep = opt.duration / Math.max(opt.frames, 1) * 1000
|
const durationPerStep = opt.duration / Math.max(opt.frames, 1) * 1000
|
||||||
return request(profile, {
|
return request(profile, {
|
||||||
|
@ -333,7 +348,8 @@ const createClient = (profile, request = _request) => {
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
if (!Array.isArray(d.jnyL)) return []
|
if (!Array.isArray(d.jnyL)) return []
|
||||||
|
|
||||||
const parse = profile.parseMovement(profile, d.locations, d.lines, d.remarks)
|
const polylines = opt.polyline && d.common.polyL || []
|
||||||
|
const parse = profile.parseMovement(profile, d.locations, d.lines, d.remarks, polylines)
|
||||||
return d.jnyL.map(parse)
|
return d.jnyL.map(parse)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ const parseJourneyLeg = require('../parse/journey-leg')
|
||||||
const parseJourney = require('../parse/journey')
|
const parseJourney = require('../parse/journey')
|
||||||
const parseLine = require('../parse/line')
|
const parseLine = require('../parse/line')
|
||||||
const parseLocation = require('../parse/location')
|
const parseLocation = require('../parse/location')
|
||||||
|
const parsePolyline = require('../parse/polyline')
|
||||||
const parseMovement = require('../parse/movement')
|
const parseMovement = require('../parse/movement')
|
||||||
const parseNearby = require('../parse/nearby')
|
const parseNearby = require('../parse/nearby')
|
||||||
const parseOperator = require('../parse/operator')
|
const parseOperator = require('../parse/operator')
|
||||||
|
@ -42,6 +43,7 @@ const defaultProfile = {
|
||||||
parseLine,
|
parseLine,
|
||||||
parseStationName: id,
|
parseStationName: id,
|
||||||
parseLocation,
|
parseLocation,
|
||||||
|
parsePolyline,
|
||||||
parseMovement,
|
parseMovement,
|
||||||
parseNearby,
|
parseNearby,
|
||||||
parseOperator,
|
parseOperator,
|
||||||
|
|
|
@ -20,7 +20,7 @@ const request = (profile, data) => {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept-Encoding': 'gzip, deflate',
|
'Accept-Encoding': 'gzip, deflate',
|
||||||
'user-agent': 'https://github.com/derhuerst/hafas-client'
|
'user-agent': 'https://github.com/public-transport/hafas-client'
|
||||||
},
|
},
|
||||||
query: {}
|
query: {}
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,7 @@ const types = {
|
||||||
parseLine: 'function',
|
parseLine: 'function',
|
||||||
parseStationName: 'function',
|
parseStationName: 'function',
|
||||||
parseLocation: 'function',
|
parseLocation: 'function',
|
||||||
|
parsePolyline: 'function',
|
||||||
parseMovement: 'function',
|
parseMovement: 'function',
|
||||||
parseNearby: 'function',
|
parseNearby: 'function',
|
||||||
parseOperator: 'function',
|
parseOperator: 'function',
|
||||||
|
|
|
@ -68,7 +68,7 @@ module.exports = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'tram',
|
id: 'tram',
|
||||||
mode: 'tram',
|
mode: 'train',
|
||||||
bitmasks: [256],
|
bitmasks: [256],
|
||||||
name: 'Tram',
|
name: 'Tram',
|
||||||
short: 'T',
|
short: 'T',
|
||||||
|
|
|
@ -6,7 +6,7 @@ const vbbProfile = require('.')
|
||||||
const client = createClient(vbbProfile)
|
const client = createClient(vbbProfile)
|
||||||
|
|
||||||
// Hauptbahnhof to Charlottenburg
|
// Hauptbahnhof to Charlottenburg
|
||||||
client.journeys('900000003201', '900000024101', {results: 1})
|
client.journeys('900000003201', '900000024101', {results: 1, polylines: true})
|
||||||
// client.departures('900000013102', {duration: 1})
|
// client.departures('900000013102', {duration: 1})
|
||||||
// client.locations('Alexanderplatz', {results: 2})
|
// client.locations('Alexanderplatz', {results: 2})
|
||||||
// client.location('900000042101') // Spichernstr
|
// client.location('900000042101') // Spichernstr
|
||||||
|
@ -18,6 +18,10 @@ client.journeys('900000003201', '900000024101', {results: 1})
|
||||||
// east: 13.41709
|
// east: 13.41709
|
||||||
// }, {results: 10})
|
// }, {results: 10})
|
||||||
|
|
||||||
|
// .then(([journey]) => {
|
||||||
|
// const leg = journey.legs[0]
|
||||||
|
// return client.journeyLeg(leg.id, leg.line.name, {polyline: true})
|
||||||
|
// })
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log(require('util').inspect(data, {depth: null}))
|
console.log(require('util').inspect(data, {depth: null}))
|
||||||
})
|
})
|
||||||
|
|
|
@ -56,8 +56,8 @@ const parseLocation = (profile, l, lines) => {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const createParseJourney = (profile, stations, lines, remarks) => {
|
const createParseJourney = (profile, stations, lines, remarks, polylines) => {
|
||||||
const parseJourney = _createParseJourney(profile, stations, lines, remarks)
|
const parseJourney = _createParseJourney(profile, stations, lines, remarks, polylines)
|
||||||
|
|
||||||
const parseJourneyWithTickets = (j) => {
|
const parseJourneyWithTickets = (j) => {
|
||||||
const res = parseJourney(j)
|
const res = parseJourney(j)
|
||||||
|
|
10
package.json
10
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "hafas-client",
|
"name": "hafas-client",
|
||||||
"description": "JavaScript client for HAFAS public transport APIs.",
|
"description": "JavaScript client for HAFAS public transport APIs.",
|
||||||
"version": "2.5.0",
|
"version": "2.7.2",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
@ -16,9 +16,9 @@
|
||||||
"contributors": [
|
"contributors": [
|
||||||
"Julius Tens <mail@juliustens.eu>"
|
"Julius Tens <mail@juliustens.eu>"
|
||||||
],
|
],
|
||||||
"homepage": "https://github.com/derhuerst/hafas-client",
|
"homepage": "https://github.com/public-transport/hafas-client",
|
||||||
"repository": "derhuerst/hafas-client",
|
"repository": "public-transport/hafas-client",
|
||||||
"bugs": "https://github.com/derhuerst/hafas-client/issues",
|
"bugs": "https://github.com/public-transport/hafas-client/issues",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"hafas",
|
"hafas",
|
||||||
|
@ -32,8 +32,10 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mapbox/polyline": "^1.0.0",
|
||||||
"capture-stack-trace": "^1.0.0",
|
"capture-stack-trace": "^1.0.0",
|
||||||
"fetch-ponyfill": "^6.0.0",
|
"fetch-ponyfill": "^6.0.0",
|
||||||
|
"gps-distance": "0.0.4",
|
||||||
"lodash": "^4.17.5",
|
"lodash": "^4.17.5",
|
||||||
"luxon": "^0.5.8",
|
"luxon": "^0.5.8",
|
||||||
"p-throttle": "^1.1.0",
|
"p-throttle": "^1.1.0",
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
// todos from derhuerst/hafas-client#2
|
// todos from public-transport/hafas-client#2
|
||||||
// - stdStop.dPlatfS, stdStop.dPlatfR
|
// - stdStop.dPlatfS, stdStop.dPlatfR
|
||||||
// todo: what is d.jny.dirFlg?
|
// todo: what is d.jny.dirFlg?
|
||||||
// todo: d.stbStop.dProgType
|
// todo: d.stbStop.dProgType
|
||||||
// todo: d.freq, d.freq.jnyL, see https://github.com/derhuerst/hafas-client/blob/9203ed1481f08baacca41ac5e3c19bf022f01b0b/parse.js#L115
|
// todo: d.freq, d.freq.jnyL, see https://github.com/public-transport/hafas-client/blob/9203ed1481f08baacca41ac5e3c19bf022f01b0b/parse.js#L115
|
||||||
|
|
||||||
const createParseDeparture = (profile, stations, lines, remarks) => {
|
const createParseDeparture = (profile, stations, lines, remarks) => {
|
||||||
const findRemark = rm => remarks[parseInt(rm.remX)] || null
|
const findRemark = rm => remarks[parseInt(rm.remX)] || null
|
||||||
|
|
|
@ -4,7 +4,7 @@ const parseDateTime = require('./date-time')
|
||||||
|
|
||||||
const clone = obj => Object.assign({}, obj)
|
const clone = obj => Object.assign({}, obj)
|
||||||
|
|
||||||
const createParseJourneyLeg = (profile, stations, lines, remarks) => {
|
const createParseJourneyLeg = (profile, stations, lines, remarks, polylines) => {
|
||||||
// todo: finish parse/remark.js first
|
// todo: finish parse/remark.js first
|
||||||
const applyRemark = (j, rm) => {}
|
const applyRemark = (j, rm) => {}
|
||||||
|
|
||||||
|
@ -34,6 +34,14 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => {
|
||||||
res.arrivalDelay = Math.round((realtime - planned) / 1000)
|
res.arrivalDelay = Math.round((realtime - planned) / 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pt.jny && pt.jny.polyG) {
|
||||||
|
let p = pt.jny.polyG.polyXL
|
||||||
|
p = Array.isArray(p) && polylines[p[0]]
|
||||||
|
// todo: there can be >1 polyline
|
||||||
|
const parse = profile.parsePolyline(stations)
|
||||||
|
res.polyline = p && parse(p) || null
|
||||||
|
}
|
||||||
|
|
||||||
if (pt.type === 'WALK') {
|
if (pt.type === 'WALK') {
|
||||||
res.mode = 'walking'
|
res.mode = 'walking'
|
||||||
res.public = true
|
res.public = true
|
||||||
|
@ -41,7 +49,7 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => {
|
||||||
// todo: pull `public` value from `profile.products`
|
// todo: pull `public` value from `profile.products`
|
||||||
res.id = pt.jny.jid
|
res.id = pt.jny.jid
|
||||||
res.line = lines[parseInt(pt.jny.prodX)] || null
|
res.line = lines[parseInt(pt.jny.prodX)] || null
|
||||||
res.direction = profile.parseStationName(pt.jny.dirTxt)
|
res.direction = profile.parseStationName(pt.jny.dirTxt) || null
|
||||||
|
|
||||||
if (pt.dep.dPlatfS) res.departurePlatform = pt.dep.dPlatfS
|
if (pt.dep.dPlatfS) res.departurePlatform = pt.dep.dPlatfS
|
||||||
if (pt.arr.aPlatfS) res.arrivalPlatform = pt.arr.aPlatfS
|
if (pt.arr.aPlatfS) res.arrivalPlatform = pt.arr.aPlatfS
|
||||||
|
@ -56,18 +64,25 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => {
|
||||||
for (let remark of pt.jny.remL) applyRemark(j, remark)
|
for (let remark of pt.jny.remL) applyRemark(j, remark)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pt.jny.freq && pt.jny.freq.jnyL) {
|
const freq = pt.jny.freq || {}
|
||||||
|
if (freq.minC && freq.maxC) {
|
||||||
|
// todo: what is freq.numC?
|
||||||
|
res.cycle = {
|
||||||
|
min: freq.minC * 60,
|
||||||
|
max: freq.maxC * 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (freq.jnyL) {
|
||||||
const parseAlternative = (a) => {
|
const parseAlternative = (a) => {
|
||||||
const t = a.stopL[0].dTimeS || a.stopL[0].dTimeR
|
const t = a.stopL[0].dTimeR || a.stopL[0].dTimeS
|
||||||
const when = profile.parseDateTime(profile, j.date, t)
|
const when = profile.parseDateTime(profile, j.date, t)
|
||||||
|
// todo: expose a.stopL[0]
|
||||||
return {
|
return {
|
||||||
line: lines[parseInt(a.prodX)] || null,
|
line: lines[parseInt(a.prodX)] || null,
|
||||||
when: when.toISO()
|
when: when.toISO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.alternatives = pt.jny.freq.jnyL
|
res.alternatives = freq.jnyL.map(parseAlternative)
|
||||||
.filter(a => a.stopL[0].locX === pt.dep.locX)
|
|
||||||
.map(parseAlternative)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const createParseJourneyLeg = require('./journey-leg')
|
|
||||||
|
|
||||||
const clone = obj => Object.assign({}, obj)
|
const clone = obj => Object.assign({}, obj)
|
||||||
|
|
||||||
const createParseJourney = (profile, stations, lines, remarks) => {
|
const createParseJourney = (profile, stations, lines, remarks, polylines) => {
|
||||||
const parseLeg = createParseJourneyLeg(profile, stations, lines, remarks)
|
const parseLeg = profile.parseJourneyLeg(profile, stations, lines, remarks, polylines)
|
||||||
|
|
||||||
// todo: c.sDays
|
// todo: c.sDays
|
||||||
// todo: c.conSubscr
|
// todo: c.conSubscr
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const createParseMovement = (profile, locations, lines, remarks) => {
|
const createParseMovement = (profile, locations, lines, remarks, polylines = []) => {
|
||||||
// todo: what is m.dirGeo? maybe the speed?
|
// todo: what is m.dirGeo? maybe the speed?
|
||||||
// todo: what is m.stopL?
|
// todo: what is m.stopL?
|
||||||
// todo: what is m.proc? wut?
|
// todo: what is m.proc? wut?
|
||||||
// todo: what is m.pos?
|
// todo: what is m.pos?
|
||||||
// todo: what is m.ani.dirGeo[n]? maybe the speed?
|
// todo: what is m.ani.dirGeo[n]? maybe the speed?
|
||||||
// todo: what is m.ani.proc[n]? wut?
|
// todo: what is m.ani.proc[n]? wut?
|
||||||
// todo: how does m.ani.poly work?
|
|
||||||
const parseMovement = (m) => {
|
const parseMovement = (m) => {
|
||||||
const pStopover = profile.parseStopover(profile, locations, lines, remarks, m.date)
|
const pStopover = profile.parseStopover(profile, locations, lines, remarks, m.date)
|
||||||
|
|
||||||
const res = {
|
const res = {
|
||||||
direction: profile.parseStationName(m.dirTxt),
|
direction: profile.parseStationName(m.dirTxt),
|
||||||
|
journeyId: m.jid || null,
|
||||||
trip: m.jid && +m.jid.split('|')[1] || null, // todo: this seems brittle
|
trip: m.jid && +m.jid.split('|')[1] || null, // todo: this seems brittle
|
||||||
line: lines[m.prodX] || null,
|
line: lines[m.prodX] || null,
|
||||||
location: m.pos ? {
|
location: m.pos ? {
|
||||||
|
@ -24,13 +24,26 @@ const createParseMovement = (profile, locations, lines, remarks) => {
|
||||||
frames: []
|
frames: []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m.ani && Array.isArray(m.ani.mSec)) {
|
if (m.ani) {
|
||||||
for (let i = 0; i < m.ani.mSec.length; i++) {
|
if (Array.isArray(m.ani.mSec)) {
|
||||||
res.frames.push({
|
for (let i = 0; i < m.ani.mSec.length; i++) {
|
||||||
origin: locations[m.ani.fLocX[i]] || null,
|
res.frames.push({
|
||||||
destination: locations[m.ani.tLocX[i]] || null,
|
origin: locations[m.ani.fLocX[i]] || null,
|
||||||
t: m.ani.mSec[i]
|
destination: locations[m.ani.tLocX[i]] || null,
|
||||||
})
|
t: m.ani.mSec[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.ani.poly) {
|
||||||
|
const parse = profile.parsePolyline(locations)
|
||||||
|
res.polyline = parse(m.ani.poly)
|
||||||
|
} else if (m.ani.polyG) {
|
||||||
|
let p = m.ani.polyG.polyXL
|
||||||
|
p = Array.isArray(p) && polylines[p[0]]
|
||||||
|
// todo: there can be >1 polyline
|
||||||
|
const parse = profile.parsePolyline(locations)
|
||||||
|
res.polyline = p && parse(p) || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
53
parse/polyline.js
Normal file
53
parse/polyline.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const {toGeoJSON} = require('@mapbox/polyline')
|
||||||
|
const distance = require('gps-distance')
|
||||||
|
|
||||||
|
const createParsePolyline = (locations) => {
|
||||||
|
// todo: what is p.delta?
|
||||||
|
// todo: what is p.type?
|
||||||
|
// todo: what is p.crdEncS?
|
||||||
|
// todo: what is p.crdEncF?
|
||||||
|
const parsePolyline = (p) => {
|
||||||
|
const shape = toGeoJSON(p.crdEncYX)
|
||||||
|
if (shape.coordinates.length === 0) return null
|
||||||
|
|
||||||
|
const res = shape.coordinates.map(crd => ({
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: crd
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (Array.isArray(p.ppLocRefL)) {
|
||||||
|
for (let ref of p.ppLocRefL) {
|
||||||
|
const p = res[ref.ppIdx]
|
||||||
|
const loc = locations[ref.locX]
|
||||||
|
if (p && loc) p.properties = loc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Often there is one more point right next to each point at a station.
|
||||||
|
// We filter them here if they are < 5m from each other.
|
||||||
|
for (let i = 1; i < res.length; i++) {
|
||||||
|
const p1 = res[i - 1].geometry.coordinates
|
||||||
|
const p2 = res[i].geometry.coordinates
|
||||||
|
const d = distance(p1[1], p1[0], p2[1], p2[0])
|
||||||
|
if (d >= .005) continue
|
||||||
|
const l1 = Object.keys(res[i - 1].properties).length
|
||||||
|
const l2 = Object.keys(res[i].properties).length
|
||||||
|
if (l1 === 0 && l2 > 0) res.splice(i - 1, 1)
|
||||||
|
else if (l2 === 0 && l1 > 0) res.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsePolyline
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createParsePolyline
|
|
@ -28,12 +28,12 @@ const createParseStopover = (profile, stations, lines, remarks, date) => {
|
||||||
Object.defineProperty(res, 'canceled', {value: true})
|
Object.defineProperty(res, 'canceled', {value: true})
|
||||||
if (st.aCncl) {
|
if (st.aCncl) {
|
||||||
res.arrival = res.arrivalDelay = null
|
res.arrival = res.arrivalDelay = null
|
||||||
const arr = profile.parseDateTime(profile, d.date, st.aTimeS)
|
const arr = profile.parseDateTime(profile, date, st.aTimeS)
|
||||||
res.formerScheduledArrival = arr.toISO()
|
res.formerScheduledArrival = arr.toISO()
|
||||||
}
|
}
|
||||||
if (st.dCncl) {
|
if (st.dCncl) {
|
||||||
res.departure = res.departureDelay = null
|
res.departure = res.departureDelay = null
|
||||||
const arr = profile.parseDateTime(profile, d.date, st.dTimeS)
|
const arr = profile.parseDateTime(profile, date, st.dTimeS)
|
||||||
res.formerScheduledDeparture = arr.toISO()
|
res.formerScheduledDeparture = arr.toISO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
readme.md
10
readme.md
|
@ -7,13 +7,13 @@ HAFAS endpoint | wrapper library | docs | example code | source code
|
||||||
[Deutsche Bahn (DB)](https://en.wikipedia.org/wiki/Deutsche_Bahn) | [`db-hafas`](https://github.com/derhuerst/db-hafas) | [docs](p/db/readme.md) | [example code](p/db/example.js) | [src](p/db/index.js)
|
[Deutsche Bahn (DB)](https://en.wikipedia.org/wiki/Deutsche_Bahn) | [`db-hafas`](https://github.com/derhuerst/db-hafas) | [docs](p/db/readme.md) | [example code](p/db/example.js) | [src](p/db/index.js)
|
||||||
[Berlin & Brandenburg public transport (VBB)](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) | [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas) | [docs](p/vbb/readme.md) | [example code](p/vbb/example.js) | [src](p/vbb/index.js)
|
[Berlin & Brandenburg public transport (VBB)](https://en.wikipedia.org/wiki/Verkehrsverbund_Berlin-Brandenburg) | [`vbb-hafas`](https://github.com/derhuerst/vbb-hafas) | [docs](p/vbb/readme.md) | [example code](p/vbb/example.js) | [src](p/vbb/index.js)
|
||||||
[Österreichische Bundesbahnen (ÖBB)](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) | [`oebb-hafas`](https://github.com/juliuste/oebb-hafas) | [docs](p/oebb/readme.md) | [example code](p/oebb/example.js) | [src](p/oebb/index.js)
|
[Österreichische Bundesbahnen (ÖBB)](https://en.wikipedia.org/wiki/Austrian_Federal_Railways) | [`oebb-hafas`](https://github.com/juliuste/oebb-hafas) | [docs](p/oebb/readme.md) | [example code](p/oebb/example.js) | [src](p/oebb/index.js)
|
||||||
[Nahverkehr Sachsen-Anhalt (NASA)](https://de.wikipedia.org/wiki/Nahverkehrsservice_Sachsen-Anhalt)/[INSA](https://insa.de) | – | [docs](p/insa/readme.md) | [example code](p/insa/example.js) | [src](p/insa/index.js)
|
[Nahverkehr Sachsen-Anhalt (NASA)](https://de.wikipedia.org/wiki/Nahverkehrsservice_Sachsen-Anhalt)/[INSA](https://insa.de) | [`insa-hafas`](https://github.com/derhuerst/insa-hafas) | [docs](p/insa/readme.md) | [example code](p/insa/example.js) | [src](p/insa/index.js)
|
||||||
[Nahverkehrsverbund Schleswig-Holstein (NAH.SH)](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) | [`nahsh-hafas`](https://github.com/juliuste/nahsh-hafas) | [docs](p/nahsh/readme.md) | [example code](p/nahsh/example.js) | [src](p/nahsh/index.js)
|
[Nahverkehrsverbund Schleswig-Holstein (NAH.SH)](https://de.wikipedia.org/wiki/Nahverkehrsverbund_Schleswig-Holstein) | [`nahsh-hafas`](https://github.com/juliuste/nahsh-hafas) | [docs](p/nahsh/readme.md) | [example code](p/nahsh/example.js) | [src](p/nahsh/index.js)
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/hafas-client)
|
[](https://www.npmjs.com/package/hafas-client)
|
||||||
[](https://travis-ci.org/derhuerst/hafas-client)
|
[](https://travis-ci.org/public-transport/hafas-client)
|
||||||

|

|
||||||
[](https://gitter.im/derhuerst)
|
[](https://gitter.im/public-transport/Lobby)
|
||||||
[](https://patreon.com/derhuerst)
|
[](https://patreon.com/derhuerst)
|
||||||
|
|
||||||
|
|
||||||
|
@ -181,4 +181,4 @@ The returned [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
If you **have a question**, **found a bug** or want to **propose a feature**, have a look at [the issues page](https://github.com/derhuerst/hafas-client/issues).
|
If you **have a question**, **found a bug** or want to **propose a feature**, have a look at [the issues page](https://github.com/public-transport/hafas-client/issues).
|
||||||
|
|
|
@ -95,7 +95,7 @@ const assertValidPrice = (t, p) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: fix this upstream
|
// todo: fix this upstream
|
||||||
// see https://github.com/derhuerst/hafas-client/blob/c6e558be217667f1bcdac4a605898eb75ea80374/p/oebb/products.js#L71
|
// see https://github.com/public-transport/hafas-client/blob/c6e558be217667f1bcdac4a605898eb75ea80374/p/oebb/products.js#L71
|
||||||
const assertValidLine = (t, l) => { // with optional mode
|
const assertValidLine = (t, l) => { // with optional mode
|
||||||
const validators = Object.assign({}, validateFptf.defaultValidators, {
|
const validators = Object.assign({}, validateFptf.defaultValidators, {
|
||||||
line: validateLineWithoutMode
|
line: validateLineWithoutMode
|
||||||
|
|
|
@ -19,7 +19,7 @@ const validateLineWithoutMode = (validate, line, name) => {
|
||||||
a.ok(line.name.length > 0, name + '.name can\'t be empty')
|
a.ok(line.name.length > 0, name + '.name can\'t be empty')
|
||||||
|
|
||||||
// skipping line validation here
|
// skipping line validation here
|
||||||
// see https://github.com/derhuerst/hafas-client/issues/8#issuecomment-355839965
|
// see https://github.com/public-transport/hafas-client/issues/8#issuecomment-355839965
|
||||||
if (is.undefined(line.mode) || is.null(line.mode)) {
|
if (is.undefined(line.mode) || is.null(line.mode)) {
|
||||||
console.error(`ÖBB: Missing \`mode\` for line ${line.name} (at ${name}).`)
|
console.error(`ÖBB: Missing \`mode\` for line ${line.name} (at ${name}).`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue