2016-06-22 01:39:04 +02:00
|
|
|
'use strict'
|
|
|
|
|
2017-11-20 15:43:13 +01:00
|
|
|
const minBy = require('lodash/minBy')
|
|
|
|
const maxBy = require('lodash/maxBy')
|
2018-03-18 00:13:44 +01:00
|
|
|
const isObj = require('lodash/isObject')
|
2017-11-20 15:43:13 +01:00
|
|
|
|
2017-11-12 14:52:04 +01:00
|
|
|
const defaultProfile = require('./lib/default-profile')
|
2018-03-16 15:38:16 +01:00
|
|
|
const createParseBitmask = require('./parse/products-bitmask')
|
2018-03-16 16:11:32 +01:00
|
|
|
const createFormatProductsFilter = require('./format/products-filter')
|
2018-03-16 15:38:16 +01:00
|
|
|
const validateProfile = require('./lib/validate-profile')
|
2018-01-23 00:37:09 +01:00
|
|
|
const _request = require('./lib/request')
|
2016-06-22 02:09:02 +02:00
|
|
|
|
2018-03-04 19:53:53 +01:00
|
|
|
const isNonEmptyString = str => 'string' === typeof str && str.length > 0
|
2018-02-28 16:09:23 +01:00
|
|
|
|
2018-01-23 00:37:09 +01:00
|
|
|
const createClient = (profile, request = _request) => {
|
2017-11-11 22:49:04 +01:00
|
|
|
profile = Object.assign({}, defaultProfile, profile)
|
2018-03-18 00:17:10 +01:00
|
|
|
if (!profile.parseProducts) {
|
|
|
|
profile.parseProducts = createParseBitmask(profile)
|
|
|
|
}
|
|
|
|
if (!profile.formatProductsFilter) {
|
|
|
|
profile.formatProductsFilter = createFormatProductsFilter(profile)
|
|
|
|
}
|
2017-12-12 03:21:12 +01:00
|
|
|
validateProfile(profile)
|
2017-10-03 17:36:42 +02:00
|
|
|
|
2018-06-26 17:11:25 +02:00
|
|
|
const _stationBoard = (station, type, parser, opt = {}) => {
|
2018-02-28 16:09:23 +01:00
|
|
|
if (isObj(station)) station = profile.formatStation(station.id)
|
2018-01-04 16:19:42 +01:00
|
|
|
else if ('string' === typeof station) station = profile.formatStation(station)
|
|
|
|
else throw new Error('station must be an object or a string.')
|
2017-11-12 18:06:16 +01:00
|
|
|
|
2018-06-26 15:49:50 +02:00
|
|
|
if ('string' !== typeof type || !type) {
|
|
|
|
throw new Error('type must be a non-empty string.')
|
|
|
|
}
|
|
|
|
|
2017-11-12 18:06:16 +01:00
|
|
|
opt = Object.assign({
|
|
|
|
direction: null, // only show departures heading to this station
|
|
|
|
duration: 10 // show departures for the next n minutes
|
|
|
|
}, opt)
|
2018-05-21 17:10:42 +02:00
|
|
|
opt.when = new Date(opt.when || Date.now())
|
|
|
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
2018-03-16 16:11:32 +01:00
|
|
|
const products = profile.formatProductsFilter(opt.products || {})
|
2017-11-12 18:06:16 +01:00
|
|
|
|
|
|
|
const dir = opt.direction ? profile.formatStation(opt.direction) : null
|
2018-06-13 19:30:58 +02:00
|
|
|
return request(profile, opt, {
|
2017-11-12 18:06:16 +01:00
|
|
|
meth: 'StationBoard',
|
|
|
|
req: {
|
2018-06-26 17:11:25 +02:00
|
|
|
type,
|
2017-11-12 18:06:16 +01:00
|
|
|
date: profile.formatDate(profile, opt.when),
|
|
|
|
time: profile.formatTime(profile, opt.when),
|
2018-01-04 16:19:42 +01:00
|
|
|
stbLoc: station,
|
2017-11-12 18:06:16 +01:00
|
|
|
dirLoc: dir,
|
2017-11-12 20:02:32 +01:00
|
|
|
jnyFltrL: [products],
|
2017-11-12 18:06:16 +01:00
|
|
|
dur: opt.duration,
|
2018-06-27 11:13:55 +02:00
|
|
|
getPasslist: false // todo: what is this?
|
2017-11-12 18:06:16 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.then((d) => {
|
2018-06-26 17:11:25 +02:00
|
|
|
if (!Array.isArray(d.jnyL)) return []
|
|
|
|
const parse = parser(profile, opt, {
|
2018-06-13 18:59:56 +02:00
|
|
|
locations: d.locations,
|
|
|
|
lines: d.lines,
|
2018-06-26 12:52:33 +02:00
|
|
|
hints: d.hints,
|
|
|
|
warnings: d.warnings
|
2018-06-13 18:59:56 +02:00
|
|
|
})
|
2017-11-12 18:06:16 +01:00
|
|
|
return d.jnyL.map(parse)
|
2017-11-18 10:14:17 +01:00
|
|
|
.sort((a, b) => new Date(a.when) - new Date(b.when))
|
2017-11-12 18:06:16 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-06-26 17:11:25 +02:00
|
|
|
const departures = (station, opt = {}) => {
|
|
|
|
return _stationBoard(station, 'DEP', profile.parseDeparture, opt)
|
|
|
|
}
|
|
|
|
const arrivals = (station, opt = {}) => {
|
|
|
|
return _stationBoard(station, 'ARR', profile.parseArrival, opt)
|
|
|
|
}
|
|
|
|
|
2017-11-12 20:02:32 +01:00
|
|
|
const journeys = (from, to, opt = {}) => {
|
2018-04-17 18:43:42 +02:00
|
|
|
from = profile.formatLocation(profile, from, 'from')
|
|
|
|
to = profile.formatLocation(profile, to, 'to')
|
2017-11-12 20:02:32 +01:00
|
|
|
|
2018-03-04 19:53:53 +01:00
|
|
|
if (('earlierThan' in opt) && ('laterThan' in opt)) {
|
2018-05-30 16:15:26 +02:00
|
|
|
throw new Error('opt.earlierThan and opt.laterThan are mutually exclusive.')
|
|
|
|
}
|
|
|
|
if (('departure' in opt) && ('arrival' in opt)) {
|
|
|
|
throw new Error('opt.departure and opt.arrival are mutually exclusive.')
|
2018-03-04 19:53:53 +01:00
|
|
|
}
|
|
|
|
let journeysRef = null
|
|
|
|
if ('earlierThan' in opt) {
|
|
|
|
if (!isNonEmptyString(opt.earlierThan)) {
|
|
|
|
throw new Error('opt.earlierThan must be a non-empty string.')
|
|
|
|
}
|
2018-05-28 20:34:24 +02:00
|
|
|
if (('departure' in opt) || ('arrival' in opt)) {
|
|
|
|
throw new Error('opt.earlierThan and opt.departure/opt.arrival are mutually exclusive.')
|
2018-03-04 19:53:53 +01:00
|
|
|
}
|
|
|
|
journeysRef = opt.earlierThan
|
|
|
|
}
|
|
|
|
if ('laterThan' in opt) {
|
|
|
|
if (!isNonEmptyString(opt.laterThan)) {
|
|
|
|
throw new Error('opt.laterThan must be a non-empty string.')
|
|
|
|
}
|
2018-05-28 20:34:24 +02:00
|
|
|
if (('departure' in opt) || ('arrival' in opt)) {
|
|
|
|
throw new Error('opt.laterThan and opt.departure/opt.arrival are mutually exclusive.')
|
2018-03-04 19:53:53 +01:00
|
|
|
}
|
|
|
|
journeysRef = opt.laterThan
|
|
|
|
}
|
|
|
|
|
2017-11-12 20:02:32 +01:00
|
|
|
opt = Object.assign({
|
|
|
|
results: 5, // how many journeys?
|
|
|
|
via: null, // let journeys pass this station?
|
2018-06-13 20:39:33 +02:00
|
|
|
stopovers: false, // return stations on the way?
|
2017-11-12 20:02:32 +01:00
|
|
|
transfers: 5, // maximum of 5 transfers
|
|
|
|
transferTime: 0, // minimum time for a single transfer in minutes
|
|
|
|
// todo: does this work with every endpoint?
|
|
|
|
accessibility: 'none', // 'none', 'partial' or 'complete'
|
|
|
|
bike: false, // only bike-friendly journeys
|
2017-12-11 15:41:27 +01:00
|
|
|
tickets: false, // return tickets?
|
2018-06-25 18:33:22 +02:00
|
|
|
polylines: false, // return leg shapes?
|
|
|
|
// Consider walking to nearby stations at the beginning of a journey?
|
|
|
|
startWithWalking: true
|
2017-11-12 20:02:32 +01:00
|
|
|
}, opt)
|
2018-04-17 18:43:42 +02:00
|
|
|
if (opt.via) opt.via = profile.formatLocation(profile, opt.via, 'opt.via')
|
2018-05-28 20:34:24 +02:00
|
|
|
|
|
|
|
if (opt.when !== undefined) {
|
|
|
|
throw new Error('opt.when is not supported anymore. Use opt.departure/opt.arrival.')
|
|
|
|
}
|
|
|
|
let when = new Date(), outFrwd = true
|
|
|
|
if (opt.departure !== undefined && opt.departure !== null) {
|
|
|
|
when = new Date(opt.departure)
|
|
|
|
if (Number.isNaN(+when)) throw new Error('opt.departure is invalid')
|
|
|
|
} else if (opt.arrival !== undefined && opt.arrival !== null) {
|
|
|
|
when = new Date(opt.arrival)
|
|
|
|
if (Number.isNaN(+when)) throw new Error('opt.arrival is invalid')
|
|
|
|
outFrwd = false
|
|
|
|
}
|
2017-12-18 20:01:12 +01:00
|
|
|
|
|
|
|
const filters = [
|
2018-03-16 16:11:32 +01:00
|
|
|
profile.formatProductsFilter(opt.products || {})
|
2017-12-18 20:01:12 +01:00
|
|
|
]
|
|
|
|
if (
|
|
|
|
opt.accessibility &&
|
|
|
|
profile.filters &&
|
|
|
|
profile.filters.accessibility &&
|
|
|
|
profile.filters.accessibility[opt.accessibility]
|
|
|
|
) {
|
|
|
|
filters.push(profile.filters.accessibility[opt.accessibility])
|
|
|
|
}
|
2017-11-12 20:02:32 +01:00
|
|
|
|
2018-03-05 00:23:17 +01:00
|
|
|
// With protocol version `1.16`, the VBB endpoint fails with
|
|
|
|
// `CGI_READ_FAILED` if you pass `numF`, the parameter for the number
|
|
|
|
// of results. To circumvent this, we loop here, collecting journeys
|
|
|
|
// until we have enough.
|
2018-03-19 22:34:08 +01:00
|
|
|
// see https://github.com/public-transport/hafas-client/pull/23#issuecomment-370246163
|
2018-03-05 00:23:17 +01:00
|
|
|
// todo: check if `numF` is supported again, revert this change
|
|
|
|
const journeys = []
|
|
|
|
const more = (when, journeysRef) => {
|
|
|
|
const query = {
|
2018-03-05 00:51:11 +01:00
|
|
|
outDate: profile.formatDate(profile, when),
|
|
|
|
outTime: profile.formatTime(profile, when),
|
|
|
|
ctxScr: journeysRef,
|
2018-06-13 20:39:33 +02:00
|
|
|
getPasslist: !!opt.stopovers,
|
2018-03-05 00:23:17 +01:00
|
|
|
maxChg: opt.transfers,
|
|
|
|
minChgTime: opt.transferTime,
|
|
|
|
depLocL: [from],
|
|
|
|
viaLocL: opt.via ? [{loc: opt.via}] : null,
|
|
|
|
arrLocL: [to],
|
|
|
|
jnyFltrL: filters,
|
|
|
|
getTariff: !!opt.tickets,
|
2018-05-28 20:34:24 +02:00
|
|
|
outFrwd,
|
2018-06-25 18:33:22 +02:00
|
|
|
ushrp: !!opt.startWithWalking,
|
2018-03-05 00:23:17 +01:00
|
|
|
|
|
|
|
// todo: what is req.gisFltrL?
|
|
|
|
getPT: true, // todo: what is this?
|
|
|
|
getIV: false, // todo: walk & bike as alternatives?
|
2018-04-30 12:49:33 +02:00
|
|
|
getPolyline: !!opt.polylines
|
2018-03-05 00:23:17 +01:00
|
|
|
}
|
2018-03-07 13:32:40 +01:00
|
|
|
if (profile.journeysNumF) query.numF = opt.results
|
2018-03-05 00:23:17 +01:00
|
|
|
|
2018-06-13 19:30:58 +02:00
|
|
|
return request(profile, opt, {
|
2018-03-05 00:23:17 +01:00
|
|
|
cfg: {polyEnc: 'GPA'},
|
|
|
|
meth: 'TripSearch',
|
|
|
|
req: profile.transformJourneysQuery(query, opt)
|
|
|
|
})
|
|
|
|
.then((d) => {
|
|
|
|
if (!Array.isArray(d.outConL)) return []
|
2018-04-30 12:49:58 +02:00
|
|
|
|
2018-06-13 19:59:44 +02:00
|
|
|
const parse = profile.parseJourney(profile, opt, {
|
2018-06-13 18:59:56 +02:00
|
|
|
locations: d.locations,
|
|
|
|
lines: d.lines,
|
2018-06-26 12:52:33 +02:00
|
|
|
hints: d.hints,
|
|
|
|
warnings: d.warnings,
|
2018-06-13 18:59:56 +02:00
|
|
|
polylines: opt.polylines && d.common.polyL || []
|
|
|
|
})
|
2018-04-30 12:49:58 +02:00
|
|
|
|
2018-03-05 00:23:17 +01:00
|
|
|
if (!journeys.earlierRef) journeys.earlierRef = d.outCtxScrB
|
|
|
|
|
2018-03-05 00:51:11 +01:00
|
|
|
let latestDep = -Infinity
|
2018-03-05 00:23:17 +01:00
|
|
|
for (let j of d.outConL) {
|
2018-03-05 00:51:11 +01:00
|
|
|
j = parse(j)
|
|
|
|
journeys.push(j)
|
|
|
|
|
2018-03-29 02:45:15 +02:00
|
|
|
if (journeys.length >= opt.results) { // collected enough
|
2018-03-05 00:23:17 +01:00
|
|
|
journeys.laterRef = d.outCtxScrF
|
|
|
|
return journeys
|
|
|
|
}
|
2018-03-21 01:15:43 +01:00
|
|
|
const dep = +new Date(j.legs[0].departure)
|
2018-03-05 00:51:11 +01:00
|
|
|
if (dep > latestDep) latestDep = dep
|
2018-03-05 00:23:17 +01:00
|
|
|
}
|
2018-03-05 00:51:11 +01:00
|
|
|
|
|
|
|
const when = new Date(latestDep)
|
|
|
|
return more(when, d.outCtxScrF) // otherwise continue
|
2018-03-05 00:23:17 +01:00
|
|
|
})
|
|
|
|
}
|
2018-03-04 19:53:53 +01:00
|
|
|
|
2018-05-28 20:34:24 +02:00
|
|
|
return more(when, journeysRef)
|
2017-11-12 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2017-11-12 20:19:33 +01:00
|
|
|
const locations = (query, opt = {}) => {
|
2018-03-04 19:53:53 +01:00
|
|
|
if (!isNonEmptyString(query)) {
|
2018-02-28 16:09:23 +01:00
|
|
|
throw new Error('query must be a non-empty string.')
|
|
|
|
}
|
2017-11-12 20:19:33 +01:00
|
|
|
opt = Object.assign({
|
|
|
|
fuzzy: true, // find only exact matches?
|
|
|
|
results: 10, // how many search results?
|
|
|
|
stations: true,
|
|
|
|
addresses: true,
|
|
|
|
poi: true // points of interest
|
|
|
|
}, opt)
|
|
|
|
|
|
|
|
const f = profile.formatLocationFilter(opt.stations, opt.addresses, opt.poi)
|
2018-06-13 19:30:58 +02:00
|
|
|
return request(profile, opt, {
|
2017-11-12 20:19:33 +01:00
|
|
|
cfg: {polyEnc: 'GPA'},
|
|
|
|
meth: 'LocMatch',
|
|
|
|
req: {input: {
|
|
|
|
loc: {
|
|
|
|
type: f,
|
|
|
|
name: opt.fuzzy ? query + '?' : query
|
|
|
|
},
|
|
|
|
maxLoc: opt.results,
|
|
|
|
field: 'S' // todo: what is this?
|
|
|
|
}}
|
|
|
|
})
|
|
|
|
.then((d) => {
|
|
|
|
if (!d.match || !Array.isArray(d.match.locL)) return []
|
|
|
|
const parse = profile.parseLocation
|
2018-06-13 19:59:44 +02:00
|
|
|
return d.match.locL.map(loc => parse(profile, opt, {lines: d.lines}, loc))
|
2017-11-12 20:19:33 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-06-04 19:00:19 +02:00
|
|
|
const station = (station) => {
|
2018-01-26 17:08:07 +01:00
|
|
|
if ('object' === typeof station) station = profile.formatStation(station.id)
|
|
|
|
else if ('string' === typeof station) station = profile.formatStation(station)
|
|
|
|
else throw new Error('station must be an object or a string.')
|
|
|
|
|
2018-06-26 11:48:30 +02:00
|
|
|
const opt = {}
|
2018-06-13 19:30:58 +02:00
|
|
|
return request(profile, opt, {
|
2018-01-26 17:08:07 +01:00
|
|
|
meth: 'LocDetails',
|
|
|
|
req: {
|
|
|
|
locL: [station]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.then((d) => {
|
|
|
|
if (!d || !Array.isArray(d.locL) || !d.locL[0]) {
|
|
|
|
// todo: proper stack trace?
|
|
|
|
throw new Error('invalid response')
|
|
|
|
}
|
2018-06-13 19:59:44 +02:00
|
|
|
return profile.parseLocation(profile, opt, {lines: d.lines}, d.locL[0])
|
2018-01-26 17:08:07 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-01-05 14:53:03 +01:00
|
|
|
const nearby = (location, opt = {}) => {
|
2018-02-28 16:09:23 +01:00
|
|
|
if (!isObj(location)) {
|
2018-01-05 14:53:03 +01:00
|
|
|
throw new Error('location must be an object.')
|
|
|
|
} else if (location.type !== 'location') {
|
|
|
|
throw new Error('invalid location object.')
|
|
|
|
} else if ('number' !== typeof location.latitude) {
|
|
|
|
throw new Error('location.latitude must be a number.')
|
|
|
|
} else if ('number' !== typeof location.longitude) {
|
|
|
|
throw new Error('location.longitude must be a number.')
|
|
|
|
}
|
|
|
|
|
2017-11-12 20:29:57 +01:00
|
|
|
opt = Object.assign({
|
|
|
|
results: 8, // maximum number of results
|
|
|
|
distance: null, // maximum walking distance in meters
|
|
|
|
poi: false, // return points of interest?
|
|
|
|
stations: true, // return stations?
|
|
|
|
}, opt)
|
|
|
|
|
2018-06-13 19:30:58 +02:00
|
|
|
return request(profile, opt, {
|
2017-11-12 20:29:57 +01:00
|
|
|
cfg: {polyEnc: 'GPA'},
|
|
|
|
meth: 'LocGeoPos',
|
|
|
|
req: {
|
|
|
|
ring: {
|
|
|
|
cCrd: {
|
2018-01-05 14:53:03 +01:00
|
|
|
x: profile.formatCoord(location.longitude),
|
|
|
|
y: profile.formatCoord(location.latitude)
|
2017-11-12 20:29:57 +01:00
|
|
|
},
|
|
|
|
maxDist: opt.distance || -1,
|
|
|
|
minDist: 0
|
|
|
|
},
|
|
|
|
getPOIs: !!opt.poi,
|
|
|
|
getStops: !!opt.stations,
|
|
|
|
maxLoc: opt.results
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.then((d) => {
|
|
|
|
if (!Array.isArray(d.locL)) return []
|
|
|
|
const parse = profile.parseNearby
|
2018-06-26 11:48:30 +02:00
|
|
|
return d.locL.map(loc => parse(profile, opt, d, loc))
|
2017-11-12 20:29:57 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-12-28 16:56:27 +01:00
|
|
|
const journeyLeg = (ref, lineName, opt = {}) => {
|
2018-03-04 19:53:53 +01:00
|
|
|
if (!isNonEmptyString(ref)) {
|
2018-02-28 16:09:23 +01:00
|
|
|
throw new Error('ref must be a non-empty string.')
|
|
|
|
}
|
2018-03-04 19:53:53 +01:00
|
|
|
if (!isNonEmptyString(lineName)) {
|
2018-02-28 16:09:23 +01:00
|
|
|
throw new Error('lineName must be a non-empty string.')
|
|
|
|
}
|
2017-12-17 20:33:04 +01:00
|
|
|
opt = Object.assign({
|
2018-06-13 20:39:33 +02:00
|
|
|
stopovers: true, // return stations on the way?
|
2018-04-30 13:12:05 +02:00
|
|
|
polyline: false
|
2017-12-17 20:33:04 +01:00
|
|
|
}, opt)
|
2018-05-21 17:10:42 +02:00
|
|
|
opt.when = new Date(opt.when || Date.now())
|
|
|
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
2017-11-20 15:43:13 +01:00
|
|
|
|
2018-06-13 19:30:58 +02:00
|
|
|
return request(profile, opt, {
|
2017-11-20 15:43:13 +01:00
|
|
|
cfg: {polyEnc: 'GPA'},
|
|
|
|
meth: 'JourneyDetails',
|
|
|
|
req: {
|
2018-04-30 13:14:19 +02:00
|
|
|
// todo: getTrainComposition
|
2017-11-20 15:43:13 +01:00
|
|
|
jid: ref,
|
|
|
|
name: lineName,
|
2018-04-30 13:12:05 +02:00
|
|
|
date: profile.formatDate(profile, opt.when),
|
|
|
|
getPolyline: !!opt.polyline
|
2017-11-20 15:43:13 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.then((d) => {
|
2018-06-13 19:59:44 +02:00
|
|
|
const parse = profile.parseJourneyLeg(profile, opt, {
|
2018-06-13 18:59:56 +02:00
|
|
|
locations: d.locations,
|
|
|
|
lines: d.lines,
|
2018-06-26 12:52:33 +02:00
|
|
|
hints: d.hints,
|
|
|
|
warnings: d.warnings,
|
2018-06-13 18:59:56 +02:00
|
|
|
polylines: opt.polyline && d.common.polyL || []
|
|
|
|
})
|
2017-11-20 15:43:13 +01:00
|
|
|
|
2017-12-28 16:56:27 +01:00
|
|
|
const leg = { // pretend the leg is contained in a journey
|
2017-11-20 15:43:13 +01:00
|
|
|
type: 'JNY',
|
|
|
|
dep: minBy(d.journey.stopL, 'idx'),
|
|
|
|
arr: maxBy(d.journey.stopL, 'idx'),
|
|
|
|
jny: d.journey
|
|
|
|
}
|
2018-06-13 20:39:33 +02:00
|
|
|
return parse(d.journey, leg, !!opt.stopovers)
|
2017-11-20 15:43:13 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-03-17 20:58:03 +01:00
|
|
|
const radar = ({north, west, south, east}, opt) => {
|
2017-11-20 17:37:08 +01:00
|
|
|
if ('number' !== typeof north) throw new Error('north 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 east) throw new Error('east must be a number.')
|
2018-05-16 21:53:33 +02:00
|
|
|
if (north <= south) throw new Error('north must be larger than south.')
|
|
|
|
if (east <= west) throw new Error('east must be larger than west.')
|
2017-11-20 17:37:08 +01:00
|
|
|
|
|
|
|
opt = Object.assign({
|
|
|
|
results: 256, // maximum number of vehicles
|
|
|
|
duration: 30, // compute frames for the next n seconds
|
2018-06-13 20:25:56 +02:00
|
|
|
// todo: what happens with `frames: 0`?
|
2017-12-12 03:28:54 +01:00
|
|
|
frames: 3, // nr of frames to compute
|
2018-05-15 19:39:28 +02:00
|
|
|
products: null, // optionally an object of booleans
|
|
|
|
polylines: false // return a track shape for each vehicle?
|
2017-11-20 17:37:08 +01:00
|
|
|
}, opt || {})
|
2018-05-21 17:10:42 +02:00
|
|
|
opt.when = new Date(opt.when || Date.now())
|
|
|
|
if (Number.isNaN(+opt.when)) throw new Error('opt.when is invalid')
|
2017-11-20 17:37:08 +01:00
|
|
|
|
|
|
|
const durationPerStep = opt.duration / Math.max(opt.frames, 1) * 1000
|
2018-06-13 19:30:58 +02:00
|
|
|
return request(profile, opt, {
|
2017-11-20 17:37:08 +01:00
|
|
|
meth: 'JourneyGeoPos',
|
|
|
|
req: {
|
|
|
|
maxJny: opt.results,
|
|
|
|
onlyRT: false, // todo: does this mean "only realtime"?
|
|
|
|
date: profile.formatDate(profile, opt.when),
|
|
|
|
time: profile.formatTime(profile, opt.when),
|
|
|
|
// todo: would a ring work here as well?
|
|
|
|
rect: profile.formatRectangle(profile, north, west, south, east),
|
|
|
|
perSize: opt.duration * 1000,
|
|
|
|
perStep: Math.round(durationPerStep),
|
|
|
|
ageOfReport: true, // todo: what is this?
|
|
|
|
jnyFltrL: [
|
2018-03-16 16:11:32 +01:00
|
|
|
profile.formatProductsFilter(opt.products || {})
|
2017-11-20 17:37:08 +01:00
|
|
|
],
|
|
|
|
trainPosMode: 'CALC' // todo: what is this? what about realtime?
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.then((d) => {
|
|
|
|
if (!Array.isArray(d.jnyL)) return []
|
|
|
|
|
2018-06-13 19:59:44 +02:00
|
|
|
const parse = profile.parseMovement(profile, opt, {
|
2018-06-13 18:59:56 +02:00
|
|
|
locations: d.locations,
|
|
|
|
lines: d.lines,
|
2018-06-26 12:52:33 +02:00
|
|
|
hints: d.hints,
|
|
|
|
warnings: d.warnings,
|
2018-06-13 18:59:56 +02:00
|
|
|
polylines: opt.polyline && d.common.polyL || []
|
|
|
|
})
|
2017-11-20 17:37:08 +01:00
|
|
|
return d.jnyL.map(parse)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-06-26 17:11:25 +02:00
|
|
|
const client = {departures, arrivals, journeys, locations, station, nearby}
|
2017-12-28 16:56:27 +01:00
|
|
|
if (profile.journeyLeg) client.journeyLeg = journeyLeg
|
2017-11-27 19:45:33 +01:00
|
|
|
if (profile.radar) client.radar = radar
|
2017-12-16 07:54:39 +01:00
|
|
|
Object.defineProperty(client, 'profile', {value: profile})
|
2017-11-27 19:45:33 +01:00
|
|
|
return client
|
2016-06-22 01:39:59 +02:00
|
|
|
}
|
|
|
|
|
2017-11-12 14:52:04 +01:00
|
|
|
module.exports = createClient
|