diff --git a/p/vbb/index.js b/p/vbb/index.js index 3f9f9ecd..f7ff4643 100644 --- a/p/vbb/index.js +++ b/p/vbb/index.js @@ -9,7 +9,6 @@ const getStations = require('vbb-stations') const _createParseLine = require('../../parse/line') const _parseLocation = require('../../parse/location') const _createParseJourney = require('../../parse/journey') -const _createParseStopover = require('../../parse/stopover') const _createParseDeparture = require('../../parse/departure') const _formatStation = require('../../format/station') const createParseBitmask = require('../../parse/products-bitmask') @@ -99,20 +98,6 @@ const createParseJourney = (profile, stations, lines, remarks) => { return parseJourneyWithTickets } -const createParseStopover = (profile, stations, lines, remarks, connection) => { - const parseStopover = _createParseStopover(profile, stations, lines, remarks, connection) - - const parseStopoverWithShorten = (st) => { - const res = parseStopover(st) - if (res.station && res.station.name) { - res.station.name = shorten(res.station.name) - } - return res - } - - return parseStopoverWithShorten -} - const createParseDeparture = (profile, stations, lines, remarks) => { const parseDeparture = _createParseDeparture(profile, stations, lines, remarks) @@ -184,7 +169,6 @@ const vbbProfile = { parseProducts: createParseBitmask(modes.allProducts, defaultProducts), parseJourney: createParseJourney, parseDeparture: createParseDeparture, - parseStopover: createParseStopover, formatStation, formatProducts, diff --git a/package.json b/package.json index 4d704a6a..248c7325 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "hafas-client", "description": "JavaScript client for HAFAS public transport APIs.", - "version": "2.4.0", + "version": "2.4.2", "main": "index.js", "files": [ "index.js", @@ -35,10 +35,10 @@ "capture-stack-trace": "^1.0.0", "fetch-ponyfill": "^6.0.0", "lodash": "^4.17.5", - "luxon": "^0.5.6", + "luxon": "^0.5.8", "p-throttle": "^1.1.0", "pinkie-promise": "^2.0.1", - "query-string": "^5.1.0", + "query-string": "^6.0.0", "slugg": "^1.2.0", "vbb-parse-line": "^0.3.1", "vbb-parse-ticket": "^0.2.1", @@ -52,7 +52,7 @@ "is-roughly-equal": "^0.1.0", "tap-spec": "^4.1.1", "tape": "^4.8.0", - "tape-promise": "^2.0.1", + "tape-promise": "^3.0.0", "validate-fptf": "^1.2.1", "vbb-stations-autocomplete": "^3.1.0" }, diff --git a/parse/departure.js b/parse/departure.js index b074b426..3868c988 100644 --- a/parse/departure.js +++ b/parse/departure.js @@ -22,14 +22,14 @@ const createParseDeparture = (profile, stations, lines, remarks) => { } // todo: res.trip from rawLine.prodCtx.num? + // todo: DRY with parseStopover + // todo: DRY with parseJourneyLeg if (d.stbStop.dTimeR && d.stbStop.dTimeS) { const realtime = profile.parseDateTime(profile, d.date, d.stbStop.dTimeR) const planned = profile.parseDateTime(profile, d.date, d.stbStop.dTimeS) res.delay = Math.round((realtime - planned) / 1000) } else res.delay = null - // todo: follow public-transport/friendly-public-transport-format#27 here - // see also derhuerst/vbb-rest#19 if (d.stbStop.aCncl || d.stbStop.dCncl) { res.cancelled = true Object.defineProperty(res, 'canceled', {value: true}) diff --git a/parse/journey-leg.js b/parse/journey-leg.js index 3c0651bc..30477270 100644 --- a/parse/journey-leg.js +++ b/parse/journey-leg.js @@ -47,7 +47,7 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => { if (pt.arr.aPlatfS) res.arrivalPlatform = pt.arr.aPlatfS if (passed && pt.jny.stopL) { - const parse = profile.parseStopover(profile, stations, lines, remarks, j) + const parse = profile.parseStopover(profile, stations, lines, remarks, j.date) const passedStations = pt.jny.stopL.map(parse) // filter stations the train passes without stopping, as this doesn't comply with fptf (yet) res.passed = passedStations.filter((x) => !x.passBy) @@ -71,21 +71,21 @@ const createParseJourneyLeg = (profile, stations, lines, remarks) => { } } - // todo: follow public-transport/friendly-public-transport-format#27 here - // see also derhuerst/vbb-rest#19 - if (pt.arr.aCncl) { + // todo: DRY with parseDeparture + // todo: DRY with parseStopover + if (pt.arr.aCncl || pt.dep.dCncl) { res.cancelled = true Object.defineProperty(res, 'canceled', {value: true}) - res.arrival = res.arrivalPlatform = res.arrivalDelay = null - const arr = profile.parseDateTime(profile, j.date, pt.arr.aTimeS) - res.formerScheduledArrival = arr.toISO() - } - if (pt.dep.dCncl) { - res.cancelled = true - Object.defineProperty(res, 'canceled', {value: true}) - res.departure = res.departurePlatform = res.departureDelay = null - const dep = profile.parseDateTime(profile, j.date, pt.dep.dTimeS) - res.formerScheduledDeparture = dep.toISO() + if (pt.arr.aCncl) { + res.arrival = res.arrivalPlatform = res.arrivalDelay = null + const arr = profile.parseDateTime(profile, j.date, pt.arr.aTimeS) + res.formerScheduledArrival = arr.toISO() + } + if (pt.dep.dCncl) { + res.departure = res.departurePlatform = res.departureDelay = null + const dep = profile.parseDateTime(profile, j.date, pt.dep.dTimeS) + res.formerScheduledDeparture = dep.toISO() + } } return res diff --git a/parse/location.js b/parse/location.js index e6689e6b..1659ea92 100644 --- a/parse/location.js +++ b/parse/location.js @@ -19,7 +19,7 @@ const parseLocation = (profile, l, lines) => { const station = { type: 'station', id: l.extId, - name: l.name, + name: profile.parseStationName(l.name), location: 'number' === typeof res.latitude ? res : null } diff --git a/parse/movement.js b/parse/movement.js index e03a3821..4a006026 100644 --- a/parse/movement.js +++ b/parse/movement.js @@ -9,32 +9,7 @@ const createParseMovement = (profile, locations, lines, remarks) => { // todo: what is m.ani.proc[n]? wut? // todo: how does m.ani.poly work? const parseMovement = (m) => { - const parseNextStop = (s) => { - const dep = s.dTimeR || s.dTimeS - ? profile.parseDateTime(profile, m.date, s.dTimeR || s.dTimeS) - : null - const arr = s.aTimeR || s.aTimeS - ? profile.parseDateTime(profile, m.date, s.aTimeR || s.aTimeS) - : null - - const res = { - station: locations[s.locX], - departure: dep ? dep.toISO() : null, - arrival: arr ? arr.toISO() : null - } - - if (m.dTimeR && m.dTimeS) { - const plannedDep = profile.parseDateTime(profile, m.date, s.dTimeS) - res.departureDelay = Math.round((dep - plannedDep) / 1000) - } else res.departureDelay = null - - if (m.aTimeR && m.aTimeS) { - const plannedArr = profile.parseDateTime(profile, m.date, s.aTimeS) - res.arrivalDelay = Math.round((arr - plannedArr) / 1000) - } else res.arrivalDelay = null - - return res - } + const pStopover = profile.parseStopover(profile, locations, lines, remarks, m.date) const res = { direction: profile.parseStationName(m.dirTxt), @@ -45,7 +20,7 @@ const createParseMovement = (profile, locations, lines, remarks) => { latitude: m.pos.y / 1000000, longitude: m.pos.x / 1000000 } : null, - nextStops: m.stopL.map(parseNextStop), + nextStops: m.stopL.map(pStopover), frames: [] } diff --git a/parse/stopover.js b/parse/stopover.js index e17e1be3..2a445bd6 100644 --- a/parse/stopover.js +++ b/parse/stopover.js @@ -2,32 +2,38 @@ // todo: arrivalDelay, departureDelay or only delay ? // todo: arrivalPlatform, departurePlatform -const createParseStopover = (profile, stations, lines, remarks, connection) => { +const createParseStopover = (profile, stations, lines, remarks, date) => { const parseStopover = (st) => { const res = { station: stations[parseInt(st.locX)] || null } if (st.aTimeR || st.aTimeS) { - const arr = profile.parseDateTime(profile, connection.date, st.aTimeR || st.aTimeS) + const arr = profile.parseDateTime(profile, date, st.aTimeR || st.aTimeS) res.arrival = arr.toISO() } if (st.dTimeR || st.dTimeS) { - const dep = profile.parseDateTime(profile, connection.date, st.dTimeR || st.dTimeS) + const dep = profile.parseDateTime(profile, date, st.dTimeR || st.dTimeS) res.departure = dep.toISO() } // mark stations the train passes without stopping if(st.dInS === false && st.aOutS === false) res.passBy = true - // todo: follow public-transport/friendly-public-transport-format#27 here - // see also derhuerst/vbb-rest#19 - if (st.aCncl) { + // todo: DRY with parseDeparture + // todo: DRY with parseJourneyLeg + if (st.aCncl || st.dCncl) { res.cancelled = true - res.arrival = null - } - if (st.dCncl) { - res.cancelled = true - res.departure = null + Object.defineProperty(res, 'canceled', {value: true}) + if (st.aCncl) { + res.arrival = res.arrivalDelay = null + const arr = profile.parseDateTime(profile, d.date, st.aTimeS) + res.formerScheduledArrival = arr.toISO() + } + if (st.dCncl) { + res.departure = res.departureDelay = null + const arr = profile.parseDateTime(profile, d.date, st.dTimeS) + res.formerScheduledDeparture = arr.toISO() + } } return res diff --git a/test/db.js b/test/db.js index ffaca334..62f39074 100644 --- a/test/db.js +++ b/test/db.js @@ -218,18 +218,44 @@ test('Berlin Jungfernheide to ATZE Musiktheater', co(function* (t) { t.end() })) -test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co(function* (t) { - const [journey] = yield client.journeys(berlinHbf, münchenHbf, { - via: hannoverHbf, - results: 1 +test('journeys: via works – with detour', co(function* (t) { + // Going from Westhafen to Wedding via Württembergalle without detour + // is currently impossible. We check if the routing engine computes a detour. + const westhafen = '008089116' + const wedding = '008089131' + const württembergallee = '731084' + const [journey] = yield client.journeys(westhafen, wedding, { + via: württembergallee, + results: 1, + when, + passedStations: true }) - const i = journey.legs.findIndex(leg => leg.destination.id === hannoverHbf) - t.ok(i >= 0, 'no leg with Hannover Hbf as destination') + t.ok(journey) - const nextLeg = journey.legs[i + 1] - t.ok(nextLeg) - t.equal(nextLeg.origin.id, hannoverHbf) + const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === württembergallee)) + t.ok(l, 'Württembergalle is not being passed') + + t.end() +})) + +test('journeys: via works – without detour', co(function* (t) { + // When going from Ruhleben to Zoo via Kastanienallee, there is *no need* + // to change trains / no need for a "detour". + const ruhleben = '000731058' + const zoo = '008010406' + const kastanienallee = '730983' + const [journey] = yield client.journeys(ruhleben, zoo, { + via: kastanienallee, + results: 1, + when, + passedStations: true + }) + + t.ok(journey) + + const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === kastanienallee)) + t.ok(l, 'Kastanienallee is not being passed') t.end() })) diff --git a/test/insa.js b/test/insa.js index f22581d6..28679120 100644 --- a/test/insa.js +++ b/test/insa.js @@ -189,22 +189,47 @@ test('Kloster Unser Lieben Frauen to Magdeburg Hbf', co(function*(t) { t.end() })) -test('Magdeburg-Buckau to Magdeburg-Neustadt with stopover at Magdeburg Hbf', co(function*(t) { - const magdeburgBuckau = '8013456' - const magdeburgNeustadt = '8010226' - const magdeburgHbf = '8010224' - const [journey] = yield client.journeys(magdeburgBuckau, magdeburgNeustadt, { - via: magdeburgHbf, +test('journeys: via works – with detour', co(function* (t) { + // Going from Magdeburg, Hasselbachplatz (Sternstr.) (Tram/Bus) to Stendal via Dessau without detour + // is currently impossible. We check if the routing engine computes a detour. + const hasselbachplatzSternstrasse = '000006545' + const stendal = '008010334' + const dessau = '008010077' + const dessauPassed = '8010077' + const [journey] = yield client.journeys(hasselbachplatzSternstrasse, stendal, { + via: dessau, results: 1, - when + when, + passedStations: true }) - const i1 = journey.legs.findIndex(leg => leg.destination.id === magdeburgHbf) - t.ok(i1 >= 0, 'no leg with Magdeburg Hbf as destination') + t.ok(journey) - const i2 = journey.legs.findIndex(leg => leg.origin.id === magdeburgHbf) - t.ok(i2 >= 0, 'no leg with Magdeburg Hbf as origin') - t.ok(i2 > i1, 'leg with Magdeburg Hbf as origin must be after leg to it') + const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === dessauPassed)) + t.ok(l, 'Dessau is not being passed') + + t.end() +})) + +test('journeys: via works – without detour', co(function* (t) { + // When going from Magdeburg, Hasselbachplatz (Sternstr.) (Tram/Bus) to Magdeburg, Universität via Magdeburg, Breiter Weg, there is *no need* + // to change trains / no need for a "detour". + const hasselbachplatzSternstrasse = '000006545' + const universitaet = '000019686' + const breiterWeg = '000013519' + const breiterWegPassed = '13519' + + const [journey] = yield client.journeys(hasselbachplatzSternstrasse, universitaet, { + via: breiterWeg, + results: 1, + when, + passedStations: true + }) + + t.ok(journey) + + const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === breiterWegPassed)) + t.ok(l, 'Magdeburg, Breiter Weg is not being passed') t.end() })) diff --git a/test/oebb.js b/test/oebb.js index 73d8c685..7427b6db 100644 --- a/test/oebb.js +++ b/test/oebb.js @@ -258,19 +258,47 @@ test('Albertina to Salzburg Hbf', co(function* (t) { t.end() })) -test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co(function* (t) { - const [journey] = yield client.journeys(wien, klagenfurtHbf, { - via: salzburgHbf, +test('journeys: via works – with detour', co(function* (t) { + // Going from Stephansplatz to Schottenring via Donauinsel without detour + // is currently impossible. We check if the routing engine computes a detour. + const stephansplatz = '001390167' + const schottenring = '001390163' + const donauinsel = '001392277' + const donauinselPassed = '922001' + const [journey] = yield client.journeys(stephansplatz, schottenring, { + via: donauinsel, results: 1, - when + when, + passedStations: true }) - const i1 = journey.legs.findIndex(leg => leg.destination.id === salzburgHbf) - t.ok(i1 >= 0, 'no leg with Salzburg Hbf as destination') + t.ok(journey) - const i2 = journey.legs.findIndex(leg => leg.origin.id === salzburgHbf) - t.ok(i2 >= 0, 'no leg with Salzburg Hbf as origin') - t.ok(i2 > i1, 'leg with Salzburg Hbf as origin must be after leg to it') + const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === donauinselPassed)) + t.ok(l, 'Donauinsel is not being passed') + + t.end() +})) + +test('journeys: via works – without detour', co(function* (t) { + // When going from Karlsplatz to Praterstern via Museumsquartier, there is *no need* + // to change trains / no need for a "detour". + const karlsplatz = '001390461' + const praterstern = '001290201' + const museumsquartier = '001390171' + const museumsquartierPassed = '901014' + + const [journey] = yield client.journeys(karlsplatz, praterstern, { + via: museumsquartier, + results: 1, + when, + passedStations: true + }) + + t.ok(journey) + + const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === museumsquartierPassed)) + t.ok(l, 'Weihburggasse is not being passed') t.end() })) diff --git a/test/vbb.js b/test/vbb.js index 29b41d63..778c432b 100644 --- a/test/vbb.js +++ b/test/vbb.js @@ -300,27 +300,47 @@ test('journeys – station to POI', co(function* (t) { t.end() })) - - -test('journeys – with stopover', co(function* (t) { - const halleschesTor = '900000012103' - const leopoldplatz = '900000009102' - const [journey] = yield client.journeys(spichernstr, halleschesTor, { - via: leopoldplatz, - results: 1 +test('journeys: via works – with detour', co(function* (t) { + // Going from Westhafen to Wedding via Württembergalle without detour + // is currently impossible. We check if the routing engine computes a detour. + const westhafen = '900000001201' + const wedding = '900000009104' + const württembergallee = '900000026153' + const [journey] = yield client.journeys(westhafen, wedding, { + via: württembergallee, + results: 1, + when, + passedStations: true }) - const i = journey.legs.findIndex(leg => leg.destination.id === leopoldplatz) - t.ok(i >= 0, 'no leg with Leopoldplatz as destination') + t.ok(journey) - const nextLeg = journey.legs[i + 1] - t.ok(nextLeg) - t.equal(nextLeg.origin.id, leopoldplatz) + const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === württembergallee)) + t.ok(l, 'Württembergalle is not being passed') t.end() })) +test('journeys: via works – without detour', co(function* (t) { + // When going from Ruhleben to Zoo via Kastanienallee, there is *no need* + // to change trains / no need for a "detour". + const ruhleben = '900000025202' + const zoo = '900000023201' + const kastanienallee = '900000020152' + const [journey] = yield client.journeys(ruhleben, zoo, { + via: kastanienallee, + results: 1, + when, + passedStations: true + }) + t.ok(journey) + + const l = journey.legs.some(l => l.passed && l.passed.some(p => p.station.id === kastanienallee)) + t.ok(l, 'Kastanienallee is not being passed') + + t.end() +})) test('departures', co(function* (t) { const deps = yield client.departures(spichernstr, {duration: 5, when})