From 80195404bb4d1c2a488fb23b7978a4473026a9b6 Mon Sep 17 00:00:00 2001 From: Traines Date: Wed, 18 Dec 2024 00:08:52 +0000 Subject: [PATCH] enrich stations with db-hafas-stations --- api.js | 3 +- index.js | 54 ++++++++++++++-------- package-lock.json | 5 +- package.json | 1 + parse/location.js | 17 +++++-- test/fixtures/db-arrivals.js | 28 +++++------ test/fixtures/db-departures-regio-guide.js | 12 ++--- test/fixtures/db-journey.js | 12 ++--- test/fixtures/db-refresh-journey.js | 8 ++-- test/parse/location.js | 4 +- 10 files changed, 86 insertions(+), 58 deletions(-) diff --git a/api.js b/api.js index 2dfc1d1d..5d814d0c 100644 --- a/api.js +++ b/api.js @@ -37,6 +37,7 @@ const config = { openapiSpec: true, logging: true, aboutPage: true, + enrichStations: true, etags: 'strong', csp: 'default-src \'none\' style-src \'self\' \'unsafe-inline\' img-src https:', mapRouteParsers, @@ -44,7 +45,7 @@ const config = { const start = async () => { - const vendo = createClient(dbProfile, 'my-hafas-rest-api'); + const vendo = createClient(dbProfile, 'my-hafas-rest-api', config); const api = await createApi(vendo, config); api.listen(config.port, (err) => { diff --git a/index.js b/index.js index 9d5e3e24..a0a317b0 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ import isObj from 'lodash/isObject.js'; import sortBy from 'lodash/sortBy.js'; import omit from 'lodash/omit.js'; import distance from 'gps-distance'; +import readStations from 'db-hafas-stations' import {defaultProfile} from './lib/default-profile.js'; import {validateProfile} from './lib/validate-profile.js'; @@ -36,9 +37,30 @@ const validateWhen = (when, name = 'when') => { } }; +const loadEnrichedStationData = () => new Promise((resolve, reject) => { + const items = {} + readStations.full() + .on('data', (station) => { + items[station.id] = station; + }) + .once('end', () => { + console.info('Loaded station index.'); + resolve(items); + }) + .once('error', (err) => { + reject(err); + }); +}); + const createClient = (profile, userAgent, opt = {}) => { profile = Object.assign({}, defaultProfile, profile); validateProfile(profile); + const common = {}; + if (opt.enrichStations !== false) { + loadEnrichedStationData().then(locations => { + common.locations = locations; + }); + } if ('string' !== typeof userAgent) { throw new TypeError('userAgent must be a string'); @@ -87,7 +109,7 @@ const createClient = (profile, userAgent, opt = {}) => { const req = profile.formatStationBoardReq({profile, opt}, station, resultsField); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); const ctx = {profile, opt, common, res}; const results = (res[resultsField] || res.items).map(res => parse(ctx, res)); // todo sort? @@ -210,7 +232,7 @@ const createClient = (profile, userAgent, opt = {}) => { // TODO query.numF = opt.results; } const req = profile.transformJourneysQuery({profile, opt}, query); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); const ctx = {profile, opt, common, res}; const verbindungen = opt.results ? res.verbindungen.slice(0, opt.results) : res.verbindungen; const journeys = verbindungen @@ -241,7 +263,7 @@ const createClient = (profile, userAgent, opt = {}) => { const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); const ctx = {profile, opt, common, res}; return { @@ -355,7 +377,7 @@ const createClient = (profile, userAgent, opt = {}) => { getTariff: Boolean(opt.tickets), }; - const {res, common} = await profile.request({profile, opt}, userAgent, { + const {res, _} = await profile.request({profile, opt}, userAgent, { cfg: {polyEnc: 'GPA'}, meth: 'SearchOnTrip', req: query, @@ -411,7 +433,7 @@ const createClient = (profile, userAgent, opt = {}) => { }, opt); const req = profile.formatLocationsReq({profile, opt}, query); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); const ctx = {profile, opt, common, res}; @@ -436,7 +458,7 @@ const createClient = (profile, userAgent, opt = {}) => { const req = profile.formatStopReq({profile, opt}, stop); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); if (!res || !Array.isArray(res.locL) || !res.locL[0]) { throw new HafasError('invalid response, expected locL[0]', null, { // This problem occurs on invalid input. 🙄 @@ -462,7 +484,7 @@ const createClient = (profile, userAgent, opt = {}) => { }, opt); const req = profile.formatNearbyReq({profile, opt}, location); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); const ctx = {profile, opt, common, res}; const results = res.map(loc => { @@ -493,7 +515,7 @@ const createClient = (profile, userAgent, opt = {}) => { const req = profile.formatTripReq({profile, opt}, id); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); const ctx = {profile, opt, common, res}; const trip = profile.parseTrip(ctx, res.journey); @@ -575,7 +597,7 @@ const createClient = (profile, userAgent, opt = {}) => { } req.jnyFltrL = [...req.jnyFltrL, ...opt.additionalFilters]; - const {res, common} = await profile.request({profile, opt}, userAgent, { + const {res, _} = await profile.request({profile, opt}, userAgent, { cfg: {polyEnc: 'GPA'}, meth: 'JourneyMatch', req, @@ -635,7 +657,7 @@ const createClient = (profile, userAgent, opt = {}) => { const req = profile.formatRadarReq({profile, opt}, north, west, south, east); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); if (!Array.isArray(res.jnyL)) { return []; } @@ -669,7 +691,7 @@ const createClient = (profile, userAgent, opt = {}) => { const req = profile.formatReachableFromReq({profile, opt}, address); - const {res, common} = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); if (!Array.isArray(res.posL)) { throw new HafasError('invalid response, expected posL[0]', null, { shouldRetry: true, @@ -724,9 +746,7 @@ const createClient = (profile, userAgent, opt = {}) => { } const req = profile.formatRemarksReq({profile, opt}); - const { - res, common, - } = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); const ctx = {profile, opt, common, res}; const remarks = (res.msgL || []) @@ -746,9 +766,7 @@ const createClient = (profile, userAgent, opt = {}) => { } const req = profile.formatLinesReq({profile, opt}, query); - const { - res, common, - } = await profile.request({profile, opt}, userAgent, req); + const {res, _} = await profile.request({profile, opt}, userAgent, req); if (!Array.isArray(res.lineL)) { return []; @@ -783,7 +801,7 @@ const createClient = (profile, userAgent, opt = {}) => { ...opt, }; - const {res, common} = await profile.request({profile, opt}, userAgent, { + const {res, _} = await profile.request({profile, opt}, userAgent, { meth: 'ServerInfo', req: { getVersionInfo: opt.versionInfo, diff --git a/package-lock.json b/package-lock.json index 1e8cea8f..d0831419 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "db-vendo-client", + "name": "@public-transport/db-vendo-client", "version": "6.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "db-vendo-client", + "name": "@public-transport/db-vendo-client", "version": "6.0.0", "license": "ISC", "dependencies": { @@ -14,6 +14,7 @@ "content-type": "^1.0.4", "create-hash": "^1.2.0", "cross-fetch": "^4.0.0", + "db-hafas-stations": "^1.0.0", "google-polyline": "^1.0.3", "gps-distance": "0.0.4", "https-proxy-agent": "^7.0.0", diff --git a/package.json b/package.json index 556b9bf4..17c6c581 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "content-type": "^1.0.4", "create-hash": "^1.2.0", "cross-fetch": "^4.0.0", + "db-hafas-stations": "^1.0.0", "google-polyline": "^1.0.3", "gps-distance": "0.0.4", "https-proxy-agent": "^7.0.0", diff --git a/parse/location.js b/parse/location.js index 10c73d1a..1732e687 100644 --- a/parse/location.js +++ b/parse/location.js @@ -7,7 +7,7 @@ const ADDRESS = 'ADR'; const leadingZeros = /^0+/; const parseLocation = (ctx, l) => { - const {profile} = ctx; + const {profile, common} = ctx; if (!l) { return null; @@ -29,8 +29,8 @@ const parseLocation = (ctx, l) => { } if (l.type === STATION || l.extId || l.evaNumber || l.evaNo || lid.A == 1) { - const stop = { - type: 'stop', // TODO station + let stop = { + type: 'station', id: res.id, name: name, location: 'number' === typeof res.latitude @@ -43,9 +43,16 @@ const parseLocation = (ctx, l) => { stop.products = profile.parseProductsBitmask(ctx, l.products); } + if (common && common.locations && common.locations[stop.id]) { + delete stop.type; + stop = { + ...common.locations[stop.id], + ...stop, + }; + } + // TODO isMeta - // TODO station - // TODO entrances, lines, transitAuthority, dhid, ids + // TODO entrances, lines return stop; } diff --git a/test/fixtures/db-arrivals.js b/test/fixtures/db-arrivals.js index a8ced0af..11d3b08e 100644 --- a/test/fixtures/db-arrivals.js +++ b/test/fixtures/db-arrivals.js @@ -2,7 +2,7 @@ const dbArrivals = [ { tripId: '20241208-c33bba6c-a73a-3eec-8a64-76356c922ece', stop: { - type: 'stop', + type: 'station', id: '8089100', name: 'Berlin Jungfernheide (S)', location: null, @@ -34,7 +34,7 @@ const dbArrivals = [ }, ], origin: { - type: 'stop', + type: 'station', id: '8089118', name: 'Berlin Beusselstraße', location: null, @@ -44,7 +44,7 @@ const dbArrivals = [ { tripId: '20241208-89eeca5a-1768-3713-894a-dd088977f42b', stop: { - type: 'stop', + type: 'station', id: '730985', name: 'Jungfernheide Bahnhof (S+U), Berlin', location: null, @@ -82,7 +82,7 @@ const dbArrivals = [ }, ], origin: { - type: 'stop', + type: 'station', id: '732218', name: 'Rudow (U), Berlin', location: null, @@ -92,7 +92,7 @@ const dbArrivals = [ { tripId: '20241208-2dc4f2d4-a1e1-3bbf-a607-98ff71c927d0', stop: { - type: 'stop', + type: 'station', id: '730985', name: 'Jungfernheide Bahnhof (S+U), Berlin', location: null, @@ -130,7 +130,7 @@ const dbArrivals = [ }, ], origin: { - type: 'stop', + type: 'station', id: '730993', name: 'Goerdelersteg, Berlin', location: null, @@ -140,7 +140,7 @@ const dbArrivals = [ { tripId: '20241208-6fa6d37c-a1c0-3f84-bdac-0424705bffaf', stop: { - type: 'stop', + type: 'station', id: '8089100', name: 'Berlin Jungfernheide (S)', location: null, @@ -172,7 +172,7 @@ const dbArrivals = [ }, ], origin: { - type: 'stop', + type: 'station', id: '8089118', name: 'Berlin Beusselstraße', location: null, @@ -182,7 +182,7 @@ const dbArrivals = [ { tripId: '20241208-c4abf007-d667-3bf1-87a8-2d1b153c014d', stop: { - type: 'stop', + type: 'station', id: '730985', name: 'Jungfernheide Bahnhof (S+U), Berlin', location: null, @@ -220,7 +220,7 @@ const dbArrivals = [ }, ], origin: { - type: 'stop', + type: 'station', id: '732218', name: 'Rudow (U), Berlin', location: null, @@ -230,7 +230,7 @@ const dbArrivals = [ { tripId: '20241208-c8b6e3e4-6acb-3237-b89e-1fca72497555', stop: { - type: 'stop', + type: 'station', id: '8089100', name: 'Berlin Jungfernheide (S)', location: null, @@ -262,7 +262,7 @@ const dbArrivals = [ }, ], origin: { - type: 'stop', + type: 'station', id: '8089118', name: 'Berlin Beusselstraße', location: null, @@ -272,7 +272,7 @@ const dbArrivals = [ { tripId: '20241208-f9d83ab7-d603-3344-87c0-a65ecf0f8524', stop: { - type: 'stop', + type: 'station', id: '730985', name: 'Jungfernheide Bahnhof (S+U), Berlin', location: null, @@ -310,7 +310,7 @@ const dbArrivals = [ }, ], origin: { - type: 'stop', + type: 'station', id: '731176', name: 'Rathaus Spandau (S+U), Berlin', location: null, diff --git a/test/fixtures/db-departures-regio-guide.js b/test/fixtures/db-departures-regio-guide.js index c3b9c02c..8bd83e0f 100644 --- a/test/fixtures/db-departures-regio-guide.js +++ b/test/fixtures/db-departures-regio-guide.js @@ -2,7 +2,7 @@ const dbDepartures = [ { tripId: '20241212-d1494ce6-1a01-38de-bf84-c0bceb12f503', stop: { - type: 'stop', + type: 'station', id: '8000365', name: 'Dombühl', location: null, @@ -28,7 +28,7 @@ const dbDepartures = [ remarks: [], origin: null, destination: { - type: 'stop', + type: 'station', id: '8000284', name: 'Nürnberg Hbf', location: null, @@ -37,7 +37,7 @@ const dbDepartures = [ { tripId: '20241212-abd01ce0-cca3-3759-aa4b-410ea4d0a720', stop: { - type: 'stop', + type: 'station', id: '682943', name: 'Bahnhof, Dombühl', location: null, @@ -63,7 +63,7 @@ const dbDepartures = [ remarks: [], origin: null, destination: { - type: 'stop', + type: 'station', id: '676542', name: 'Gymnasium, Dinkelsbühl', location: null, @@ -72,7 +72,7 @@ const dbDepartures = [ { tripId: '20241212-ab6272a5-4bf6-32c1-9344-b47e1fc49eeb', stop: { - type: 'stop', + type: 'station', id: '682943', name: 'Bahnhof, Dombühl', location: null, @@ -98,7 +98,7 @@ const dbDepartures = [ remarks: [], origin: null, destination: { - type: 'stop', + type: 'station', id: '683407', name: 'Bahnhof, Rothenburg ob der Tauber', location: null, diff --git a/test/fixtures/db-journey.js b/test/fixtures/db-journey.js index 7ce87a49..a2519d76 100644 --- a/test/fixtures/db-journey.js +++ b/test/fixtures/db-journey.js @@ -3,7 +3,7 @@ const dbJourney = { legs: [ { origin: { - type: 'stop', + type: 'station', id: '8000207', name: 'Köln Hbf', location: { @@ -14,7 +14,7 @@ const dbJourney = { }, }, destination: { - type: 'stop', + type: 'station', id: '8003368', name: 'Köln Messe/Deutz', location: { @@ -167,7 +167,7 @@ const dbJourney = { }, { origin: { - type: 'stop', + type: 'station', id: '8003368', name: 'Köln Messe/Deutz', location: { @@ -178,7 +178,7 @@ const dbJourney = { }, }, destination: { - type: 'stop', + type: 'station', id: '8073368', name: 'Köln Messe/Deutz Gl.11-12', location: { @@ -200,7 +200,7 @@ const dbJourney = { }, { origin: { - type: 'stop', + type: 'station', id: '8073368', name: 'Köln Messe/Deutz Gl.11-12', location: { @@ -211,7 +211,7 @@ const dbJourney = { }, }, destination: { - type: 'stop', + type: 'station', id: '8000284', name: 'Nürnberg Hbf', location: { diff --git a/test/fixtures/db-refresh-journey.js b/test/fixtures/db-refresh-journey.js index 6d9724e3..b4bc78e7 100644 --- a/test/fixtures/db-refresh-journey.js +++ b/test/fixtures/db-refresh-journey.js @@ -4,7 +4,7 @@ const dbJourney = { legs: [ { origin: { - type: 'stop', + type: 'station', id: '8011160', name: 'Berlin Hbf', location: { @@ -15,7 +15,7 @@ const dbJourney = { }, }, destination: { - type: 'stop', + type: 'station', id: '8000080', name: 'Dortmund Hbf', location: { @@ -87,7 +87,7 @@ const dbJourney = { }, { origin: { - type: 'stop', + type: 'station', id: '8000080', name: 'Dortmund Hbf', location: { @@ -98,7 +98,7 @@ const dbJourney = { }, }, destination: { - type: 'stop', + type: 'station', id: '8000207', name: 'Köln Hbf', location: { diff --git a/test/parse/location.js b/test/parse/location.js index 15ab2452..17b601a6 100644 --- a/test/parse/location.js +++ b/test/parse/location.js @@ -100,7 +100,7 @@ tap.test('parses a stop correctly', (t) => { const stop = parse(ctx, input); t.same(stop, { - type: 'stop', + type: 'station', id: '8012622', name: 'Perleberg', location: { @@ -133,7 +133,7 @@ tap.test('falls back to coordinates from `lid', (t) => { const stop = parse(ctx, input); t.same(stop, { - type: 'stop', + type: 'station', id: '683407', name: 'Bahnhof, Rothenburg ob der Tauber', location: {