diff --git a/index.js b/index.js index bc9fba6d..c2227c61 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,8 @@ 'use strict' +const minBy = require('lodash/minBy') +const maxBy = require('lodash/maxBy') + const defaultProfile = require('./lib/default-profile') const request = require('./lib/request') @@ -23,14 +26,14 @@ const createClient = (profile) => { return request(profile, { meth: 'StationBoard', req: { - type: 'DEP', + type: 'DEP', date: profile.formatDate(profile, opt.when), time: profile.formatTime(profile, opt.when), stbLoc: profile.formatStation(station), dirLoc: dir, jnyFltrL: [products], dur: opt.duration, - getPasslist: false + getPasslist: false } }) .then((d) => { @@ -156,7 +159,32 @@ const createClient = (profile) => { }) } - return {departures, journeys, locations, nearby} + const journeyPart = (ref, lineName, opt = {}) => { + opt.when = opt.when || new Date() + + return request(profile, { + cfg: {polyEnc: 'GPA'}, + meth: 'JourneyDetails', + req: { + jid: ref, + name: lineName, + date: profile.formatDate(profile, opt.when) + } + }) + .then((d) => { + const parse = profile.parseJourneyPart(profile, d.locations, d.lines, d.remarks) + + const part = { // pretend the part is contained in a journey + type: 'JNY', + dep: minBy(d.journey.stopL, 'idx'), + arr: maxBy(d.journey.stopL, 'idx'), + jny: d.journey + } + return parse(d.journey, part) + }) + } + + return {departures, journeys, locations, nearby, journeyPart} } module.exports = createClient diff --git a/lib/default-profile.js b/lib/default-profile.js index 5de45fd7..0eff0a20 100644 --- a/lib/default-profile.js +++ b/lib/default-profile.js @@ -2,6 +2,7 @@ const parseDateTime = require('../parse/date-time') const parseDeparture = require('../parse/departure') +const parseJourneyPart = require('../parse/journey-part') const parseJourney = require('../parse/journey') const parseLine = require('../parse/line') const parseLocation = require('../parse/location') @@ -32,6 +33,7 @@ const defaultProfile = { parseDateTime, parseDeparture, + parseJourneyPart, parseJourney, parseLine, parseStationName: id, diff --git a/package.json b/package.json index ba054276..fd0f038e 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "fetch-ponyfill": "^4.1.0", + "lodash": "^4.17.4", "moment-timezone": "^0.5.13", "pinkie-promise": "^2.0.1", "query-string": "^5.0.0", diff --git a/parse/index.js b/parse/index.js index 7c9c35bb..af61cf55 100644 --- a/parse/index.js +++ b/parse/index.js @@ -7,6 +7,7 @@ module.exports = { remark: require('./remark'), operator: require('./operator'), stopover: require('./stopover'), + journeyPart: require('./journey-part'), journey: require('./journey'), nearby: require('./nearby'), movement: require('./movement') diff --git a/parse/journey-part.js b/parse/journey-part.js new file mode 100644 index 00000000..a86d40e8 --- /dev/null +++ b/parse/journey-part.js @@ -0,0 +1,87 @@ +'use strict' + +const parseDateTime = require('./date-time') + +const clone = obj => Object.assign({}, obj) + +const createParseJourneyPart = (profile, stations, lines, remarks) => { + const tz = profile.timezone + + const parseStopover = (j, st) => { + const res = { + station: stations[parseInt(st.locX)] + } + if (st.aTimeR || st.aTimeS) { + const arr = parseDateTime(tz, j.date, st.aTimeR || st.aTimeS) + res.arrival = arr.format() + } + if (st.dTimeR || st.dTimeS) { + const dep = parseDateTime(tz, j.date, st.dTimeR || st.dTimeS) + res.departure = dep.format() + } + + return res + } + + // todo: finish parse/remark.js first + const applyRemark = (j, rm) => {} + + // todo: pt.sDays + // todo: pt.dep.dProgType, pt.arr.dProgType + // todo: what is pt.jny.dirFlg? + // todo: how does pt.freq work? + const parseJourneyPart = (j, pt) => { // j = journey, pt = part + const dep = profile.parseDateTime(tz, j.date, pt.dep.dTimeR || pt.dep.dTimeS) + const arr = profile.parseDateTime(tz, j.date, pt.arr.aTimeR || pt.arr.aTimeS) + const res = { + origin: clone(stations[parseInt(pt.dep.locX)]) || null, + destination: clone(stations[parseInt(pt.arr.locX)]), + departure: dep.format(), + arrival: arr.format() + } + + if (pt.dep.dTimeR && pt.dep.dTimeS) { + const realtime = profile.parseDateTime(tz, j.date, pt.dep.dTimeR) + const planned = profile.parseDateTime(tz, j.date, pt.dep.dTimeS) + res.delay = Math.round((realtime - planned) / 1000) + } + + if (pt.type === 'WALK') { + res.mode = 'walking' + } else if (pt.type === 'JNY') { + res.id = pt.jny.jid + res.line = lines[parseInt(pt.jny.prodX)] || null + res.direction = profile.parseStationName(pt.jny.dirTxt) + + if (pt.dep.dPlatfS) res.departurePlatform = pt.dep.dPlatfS + if (pt.arr.aPlatfS) res.arrivalPlatform = pt.arr.aPlatfS + + if (pt.jny.stopL) { + res.passed = pt.jny.stopL.map(stopover => parseStopover(j, stopover)) + } + if (Array.isArray(pt.jny.remL)) { + for (let remark of pt.jny.remL) applyRemark(j, remark) + } + + if (pt.jny.freq && pt.jny.freq.jnyL) { + const parseAlternative = (a) => { + // todo: realtime + const when = profile.parseDateTime(tz, j.date, a.stopL[0].dTimeS) + return { + line: lines[parseInt(a.prodX)] || null, + when: when.format() + } + } + res.alternatives = pt.jny.freq.jnyL + .filter(a => a.stopL[0].locX === pt.dep.locX) + .map(parseAlternative) + } + } + + return res + } + + return parseJourneyPart +} + +module.exports = createParseJourneyPart diff --git a/parse/journey.js b/parse/journey.js index f622c151..11b1b56b 100644 --- a/parse/journey.js +++ b/parse/journey.js @@ -1,85 +1,12 @@ 'use strict' const parseDateTime = require('./date-time') +const createParseJourneyPart = require('./journey-part') const clone = obj => Object.assign({}, obj) const createParseJourney = (profile, stations, lines, remarks) => { - const tz = profile.timezone - - const parseStopover = (j, st) => { - const res = { - station: stations[parseInt(st.locX)] - } - if (st.aTimeR || st.aTimeS) { - const arr = parseDateTime(tz, j.date, st.aTimeR || st.aTimeS) - res.arrival = arr.format() - } - if (st.dTimeR || st.dTimeS) { - const dep = parseDateTime(tz, j.date, st.dTimeR || st.dTimeS) - res.departure = dep.format() - } - - return res - } - - // todo: finish parse/remark.js first - const applyRemark = (j, rm) => {} - - // todo: pt.sDays - // todo: pt.dep.dProgType, pt.arr.dProgType - // todo: what is pt.jny.dirFlg? - // todo: how does pt.freq work? - const parsePart = (j, pt) => { // j = journey, pt = part - const dep = profile.parseDateTime(tz, j.date, pt.dep.dTimeR || pt.dep.dTimeS) - const arr = profile.parseDateTime(tz, j.date, pt.arr.aTimeR || pt.arr.aTimeS) - const res = { - origin: clone(stations[parseInt(pt.dep.locX)]) || null, - destination: clone(stations[parseInt(pt.arr.locX)]), - departure: dep.format(), - arrival: arr.format() - } - - if (pt.dep.dTimeR && pt.dep.dTimeS) { - const realtime = profile.parseDateTime(tz, j.date, pt.dep.dTimeR) - const planned = profile.parseDateTime(tz, j.date, pt.dep.dTimeS) - res.delay = Math.round((realtime - planned) / 1000) - } - - if (pt.type === 'WALK') { - res.mode = 'walking' - } else if (pt.type === 'JNY') { - res.id = pt.jny.jid - res.line = lines[parseInt(pt.jny.prodX)] || null - res.direction = profile.parseStationName(pt.jny.dirTxt) - - if (pt.dep.dPlatfS) res.departurePlatform = pt.dep.dPlatfS - if (pt.arr.aPlatfS) res.arrivalPlatform = pt.arr.aPlatfS - - if (pt.jny.stopL) { - res.passed = pt.jny.stopL.map(stopover => parseStopover(j, stopover)) - } - if (Array.isArray(pt.jny.remL)) { - for (let remark of pt.jny.remL) applyRemark(j, remark) - } - - if (pt.jny.freq && pt.jny.freq.jnyL) { - const parseAlternative = (a) => { - // todo: realtime - const when = profile.parseDateTime(tz, j.date, a.stopL[0].dTimeS) - return { - line: lines[parseInt(a.prodX)] || null, - when: when.format() - } - } - res.alternatives = pt.jny.freq.jnyL - .filter(a => a.stopL[0].locX === pt.dep.locX) - .map(parseAlternative) - } - } - - return res - } + const parsePart = createParseJourneyPart(profile, stations, lines, remarks) // todo: c.sDays // todo: c.dep.dProgType, c.arr.dProgType diff --git a/test/vbb.js b/test/vbb.js index c13c0119..f0f1eca1 100644 --- a/test/vbb.js +++ b/test/vbb.js @@ -122,8 +122,7 @@ test('journeys – fails with no product', async (t) => { } }) -// todo -test.skip('journey part details', async (t) => { +test('journey part details', async (t) => { const journeys = await client.journeys(spichernstr, amrumerStr, { results: 1, when })