enrich stations with db-hafas-stations

This commit is contained in:
Traines 2024-12-18 00:08:52 +00:00
parent 073d33e12f
commit 80195404bb
10 changed files with 86 additions and 58 deletions

3
api.js
View file

@ -37,6 +37,7 @@ const config = {
openapiSpec: true, openapiSpec: true,
logging: true, logging: true,
aboutPage: true, aboutPage: true,
enrichStations: true,
etags: 'strong', etags: 'strong',
csp: 'default-src \'none\' style-src \'self\' \'unsafe-inline\' img-src https:', csp: 'default-src \'none\' style-src \'self\' \'unsafe-inline\' img-src https:',
mapRouteParsers, mapRouteParsers,
@ -44,7 +45,7 @@ const config = {
const start = async () => { 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); const api = await createApi(vendo, config);
api.listen(config.port, (err) => { api.listen(config.port, (err) => {

View file

@ -2,6 +2,7 @@ import isObj from 'lodash/isObject.js';
import sortBy from 'lodash/sortBy.js'; import sortBy from 'lodash/sortBy.js';
import omit from 'lodash/omit.js'; import omit from 'lodash/omit.js';
import distance from 'gps-distance'; import distance from 'gps-distance';
import readStations from 'db-hafas-stations'
import {defaultProfile} from './lib/default-profile.js'; import {defaultProfile} from './lib/default-profile.js';
import {validateProfile} from './lib/validate-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 = {}) => { const createClient = (profile, userAgent, opt = {}) => {
profile = Object.assign({}, defaultProfile, profile); profile = Object.assign({}, defaultProfile, profile);
validateProfile(profile); validateProfile(profile);
const common = {};
if (opt.enrichStations !== false) {
loadEnrichedStationData().then(locations => {
common.locations = locations;
});
}
if ('string' !== typeof userAgent) { if ('string' !== typeof userAgent) {
throw new TypeError('userAgent must be a string'); 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 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 ctx = {profile, opt, common, res};
const results = (res[resultsField] || res.items).map(res => parse(ctx, res)); // todo sort? 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; // TODO query.numF = opt.results;
} }
const req = profile.transformJourneysQuery({profile, opt}, query); 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 ctx = {profile, opt, common, res};
const verbindungen = opt.results ? res.verbindungen.slice(0, opt.results) : res.verbindungen; const verbindungen = opt.results ? res.verbindungen.slice(0, opt.results) : res.verbindungen;
const journeys = verbindungen const journeys = verbindungen
@ -241,7 +263,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken); 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}; const ctx = {profile, opt, common, res};
return { return {
@ -355,7 +377,7 @@ const createClient = (profile, userAgent, opt = {}) => {
getTariff: Boolean(opt.tickets), getTariff: Boolean(opt.tickets),
}; };
const {res, common} = await profile.request({profile, opt}, userAgent, { const {res, _} = await profile.request({profile, opt}, userAgent, {
cfg: {polyEnc: 'GPA'}, cfg: {polyEnc: 'GPA'},
meth: 'SearchOnTrip', meth: 'SearchOnTrip',
req: query, req: query,
@ -411,7 +433,7 @@ const createClient = (profile, userAgent, opt = {}) => {
}, opt); }, opt);
const req = profile.formatLocationsReq({profile, opt}, query); 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}; const ctx = {profile, opt, common, res};
@ -436,7 +458,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatStopReq({profile, opt}, stop); 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]) { if (!res || !Array.isArray(res.locL) || !res.locL[0]) {
throw new HafasError('invalid response, expected locL[0]', null, { throw new HafasError('invalid response, expected locL[0]', null, {
// This problem occurs on invalid input. 🙄 // This problem occurs on invalid input. 🙄
@ -462,7 +484,7 @@ const createClient = (profile, userAgent, opt = {}) => {
}, opt); }, opt);
const req = profile.formatNearbyReq({profile, opt}, location); 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 ctx = {profile, opt, common, res};
const results = res.map(loc => { const results = res.map(loc => {
@ -493,7 +515,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatTripReq({profile, opt}, id); 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 ctx = {profile, opt, common, res};
const trip = profile.parseTrip(ctx, res.journey); const trip = profile.parseTrip(ctx, res.journey);
@ -575,7 +597,7 @@ const createClient = (profile, userAgent, opt = {}) => {
} }
req.jnyFltrL = [...req.jnyFltrL, ...opt.additionalFilters]; 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'}, cfg: {polyEnc: 'GPA'},
meth: 'JourneyMatch', meth: 'JourneyMatch',
req, req,
@ -635,7 +657,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatRadarReq({profile, opt}, north, west, south, east); 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)) { if (!Array.isArray(res.jnyL)) {
return []; return [];
} }
@ -669,7 +691,7 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatReachableFromReq({profile, opt}, address); 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)) { if (!Array.isArray(res.posL)) {
throw new HafasError('invalid response, expected posL[0]', null, { throw new HafasError('invalid response, expected posL[0]', null, {
shouldRetry: true, shouldRetry: true,
@ -724,9 +746,7 @@ const createClient = (profile, userAgent, opt = {}) => {
} }
const req = profile.formatRemarksReq({profile, opt}); const req = profile.formatRemarksReq({profile, opt});
const { const {res, _} = await profile.request({profile, opt}, userAgent, req);
res, common,
} = await profile.request({profile, opt}, userAgent, req);
const ctx = {profile, opt, common, res}; const ctx = {profile, opt, common, res};
const remarks = (res.msgL || []) const remarks = (res.msgL || [])
@ -746,9 +766,7 @@ const createClient = (profile, userAgent, opt = {}) => {
} }
const req = profile.formatLinesReq({profile, opt}, query); const req = profile.formatLinesReq({profile, opt}, query);
const { const {res, _} = await profile.request({profile, opt}, userAgent, req);
res, common,
} = await profile.request({profile, opt}, userAgent, req);
if (!Array.isArray(res.lineL)) { if (!Array.isArray(res.lineL)) {
return []; return [];
@ -783,7 +801,7 @@ const createClient = (profile, userAgent, opt = {}) => {
...opt, ...opt,
}; };
const {res, common} = await profile.request({profile, opt}, userAgent, { const {res, _} = await profile.request({profile, opt}, userAgent, {
meth: 'ServerInfo', meth: 'ServerInfo',
req: { req: {
getVersionInfo: opt.versionInfo, getVersionInfo: opt.versionInfo,

5
package-lock.json generated
View file

@ -1,11 +1,11 @@
{ {
"name": "db-vendo-client", "name": "@public-transport/db-vendo-client",
"version": "6.0.0", "version": "6.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "db-vendo-client", "name": "@public-transport/db-vendo-client",
"version": "6.0.0", "version": "6.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@ -14,6 +14,7 @@
"content-type": "^1.0.4", "content-type": "^1.0.4",
"create-hash": "^1.2.0", "create-hash": "^1.2.0",
"cross-fetch": "^4.0.0", "cross-fetch": "^4.0.0",
"db-hafas-stations": "^1.0.0",
"google-polyline": "^1.0.3", "google-polyline": "^1.0.3",
"gps-distance": "0.0.4", "gps-distance": "0.0.4",
"https-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0",

View file

@ -60,6 +60,7 @@
"content-type": "^1.0.4", "content-type": "^1.0.4",
"create-hash": "^1.2.0", "create-hash": "^1.2.0",
"cross-fetch": "^4.0.0", "cross-fetch": "^4.0.0",
"db-hafas-stations": "^1.0.0",
"google-polyline": "^1.0.3", "google-polyline": "^1.0.3",
"gps-distance": "0.0.4", "gps-distance": "0.0.4",
"https-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0",

View file

@ -7,7 +7,7 @@ const ADDRESS = 'ADR';
const leadingZeros = /^0+/; const leadingZeros = /^0+/;
const parseLocation = (ctx, l) => { const parseLocation = (ctx, l) => {
const {profile} = ctx; const {profile, common} = ctx;
if (!l) { if (!l) {
return null; return null;
@ -29,8 +29,8 @@ const parseLocation = (ctx, l) => {
} }
if (l.type === STATION || l.extId || l.evaNumber || l.evaNo || lid.A == 1) { if (l.type === STATION || l.extId || l.evaNumber || l.evaNo || lid.A == 1) {
const stop = { let stop = {
type: 'stop', // TODO station type: 'station',
id: res.id, id: res.id,
name: name, name: name,
location: 'number' === typeof res.latitude location: 'number' === typeof res.latitude
@ -43,9 +43,16 @@ const parseLocation = (ctx, l) => {
stop.products = profile.parseProductsBitmask(ctx, l.products); 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 isMeta
// TODO station // TODO entrances, lines
// TODO entrances, lines, transitAuthority, dhid, ids
return stop; return stop;
} }

View file

@ -2,7 +2,7 @@ const dbArrivals = [
{ {
tripId: '20241208-c33bba6c-a73a-3eec-8a64-76356c922ece', tripId: '20241208-c33bba6c-a73a-3eec-8a64-76356c922ece',
stop: { stop: {
type: 'stop', type: 'station',
id: '8089100', id: '8089100',
name: 'Berlin Jungfernheide (S)', name: 'Berlin Jungfernheide (S)',
location: null, location: null,
@ -34,7 +34,7 @@ const dbArrivals = [
}, },
], ],
origin: { origin: {
type: 'stop', type: 'station',
id: '8089118', id: '8089118',
name: 'Berlin Beusselstraße', name: 'Berlin Beusselstraße',
location: null, location: null,
@ -44,7 +44,7 @@ const dbArrivals = [
{ {
tripId: '20241208-89eeca5a-1768-3713-894a-dd088977f42b', tripId: '20241208-89eeca5a-1768-3713-894a-dd088977f42b',
stop: { stop: {
type: 'stop', type: 'station',
id: '730985', id: '730985',
name: 'Jungfernheide Bahnhof (S+U), Berlin', name: 'Jungfernheide Bahnhof (S+U), Berlin',
location: null, location: null,
@ -82,7 +82,7 @@ const dbArrivals = [
}, },
], ],
origin: { origin: {
type: 'stop', type: 'station',
id: '732218', id: '732218',
name: 'Rudow (U), Berlin', name: 'Rudow (U), Berlin',
location: null, location: null,
@ -92,7 +92,7 @@ const dbArrivals = [
{ {
tripId: '20241208-2dc4f2d4-a1e1-3bbf-a607-98ff71c927d0', tripId: '20241208-2dc4f2d4-a1e1-3bbf-a607-98ff71c927d0',
stop: { stop: {
type: 'stop', type: 'station',
id: '730985', id: '730985',
name: 'Jungfernheide Bahnhof (S+U), Berlin', name: 'Jungfernheide Bahnhof (S+U), Berlin',
location: null, location: null,
@ -130,7 +130,7 @@ const dbArrivals = [
}, },
], ],
origin: { origin: {
type: 'stop', type: 'station',
id: '730993', id: '730993',
name: 'Goerdelersteg, Berlin', name: 'Goerdelersteg, Berlin',
location: null, location: null,
@ -140,7 +140,7 @@ const dbArrivals = [
{ {
tripId: '20241208-6fa6d37c-a1c0-3f84-bdac-0424705bffaf', tripId: '20241208-6fa6d37c-a1c0-3f84-bdac-0424705bffaf',
stop: { stop: {
type: 'stop', type: 'station',
id: '8089100', id: '8089100',
name: 'Berlin Jungfernheide (S)', name: 'Berlin Jungfernheide (S)',
location: null, location: null,
@ -172,7 +172,7 @@ const dbArrivals = [
}, },
], ],
origin: { origin: {
type: 'stop', type: 'station',
id: '8089118', id: '8089118',
name: 'Berlin Beusselstraße', name: 'Berlin Beusselstraße',
location: null, location: null,
@ -182,7 +182,7 @@ const dbArrivals = [
{ {
tripId: '20241208-c4abf007-d667-3bf1-87a8-2d1b153c014d', tripId: '20241208-c4abf007-d667-3bf1-87a8-2d1b153c014d',
stop: { stop: {
type: 'stop', type: 'station',
id: '730985', id: '730985',
name: 'Jungfernheide Bahnhof (S+U), Berlin', name: 'Jungfernheide Bahnhof (S+U), Berlin',
location: null, location: null,
@ -220,7 +220,7 @@ const dbArrivals = [
}, },
], ],
origin: { origin: {
type: 'stop', type: 'station',
id: '732218', id: '732218',
name: 'Rudow (U), Berlin', name: 'Rudow (U), Berlin',
location: null, location: null,
@ -230,7 +230,7 @@ const dbArrivals = [
{ {
tripId: '20241208-c8b6e3e4-6acb-3237-b89e-1fca72497555', tripId: '20241208-c8b6e3e4-6acb-3237-b89e-1fca72497555',
stop: { stop: {
type: 'stop', type: 'station',
id: '8089100', id: '8089100',
name: 'Berlin Jungfernheide (S)', name: 'Berlin Jungfernheide (S)',
location: null, location: null,
@ -262,7 +262,7 @@ const dbArrivals = [
}, },
], ],
origin: { origin: {
type: 'stop', type: 'station',
id: '8089118', id: '8089118',
name: 'Berlin Beusselstraße', name: 'Berlin Beusselstraße',
location: null, location: null,
@ -272,7 +272,7 @@ const dbArrivals = [
{ {
tripId: '20241208-f9d83ab7-d603-3344-87c0-a65ecf0f8524', tripId: '20241208-f9d83ab7-d603-3344-87c0-a65ecf0f8524',
stop: { stop: {
type: 'stop', type: 'station',
id: '730985', id: '730985',
name: 'Jungfernheide Bahnhof (S+U), Berlin', name: 'Jungfernheide Bahnhof (S+U), Berlin',
location: null, location: null,
@ -310,7 +310,7 @@ const dbArrivals = [
}, },
], ],
origin: { origin: {
type: 'stop', type: 'station',
id: '731176', id: '731176',
name: 'Rathaus Spandau (S+U), Berlin', name: 'Rathaus Spandau (S+U), Berlin',
location: null, location: null,

View file

@ -2,7 +2,7 @@ const dbDepartures = [
{ {
tripId: '20241212-d1494ce6-1a01-38de-bf84-c0bceb12f503', tripId: '20241212-d1494ce6-1a01-38de-bf84-c0bceb12f503',
stop: { stop: {
type: 'stop', type: 'station',
id: '8000365', id: '8000365',
name: 'Dombühl', name: 'Dombühl',
location: null, location: null,
@ -28,7 +28,7 @@ const dbDepartures = [
remarks: [], remarks: [],
origin: null, origin: null,
destination: { destination: {
type: 'stop', type: 'station',
id: '8000284', id: '8000284',
name: 'Nürnberg Hbf', name: 'Nürnberg Hbf',
location: null, location: null,
@ -37,7 +37,7 @@ const dbDepartures = [
{ {
tripId: '20241212-abd01ce0-cca3-3759-aa4b-410ea4d0a720', tripId: '20241212-abd01ce0-cca3-3759-aa4b-410ea4d0a720',
stop: { stop: {
type: 'stop', type: 'station',
id: '682943', id: '682943',
name: 'Bahnhof, Dombühl', name: 'Bahnhof, Dombühl',
location: null, location: null,
@ -63,7 +63,7 @@ const dbDepartures = [
remarks: [], remarks: [],
origin: null, origin: null,
destination: { destination: {
type: 'stop', type: 'station',
id: '676542', id: '676542',
name: 'Gymnasium, Dinkelsbühl', name: 'Gymnasium, Dinkelsbühl',
location: null, location: null,
@ -72,7 +72,7 @@ const dbDepartures = [
{ {
tripId: '20241212-ab6272a5-4bf6-32c1-9344-b47e1fc49eeb', tripId: '20241212-ab6272a5-4bf6-32c1-9344-b47e1fc49eeb',
stop: { stop: {
type: 'stop', type: 'station',
id: '682943', id: '682943',
name: 'Bahnhof, Dombühl', name: 'Bahnhof, Dombühl',
location: null, location: null,
@ -98,7 +98,7 @@ const dbDepartures = [
remarks: [], remarks: [],
origin: null, origin: null,
destination: { destination: {
type: 'stop', type: 'station',
id: '683407', id: '683407',
name: 'Bahnhof, Rothenburg ob der Tauber', name: 'Bahnhof, Rothenburg ob der Tauber',
location: null, location: null,

View file

@ -3,7 +3,7 @@ const dbJourney = {
legs: [ legs: [
{ {
origin: { origin: {
type: 'stop', type: 'station',
id: '8000207', id: '8000207',
name: 'Köln Hbf', name: 'Köln Hbf',
location: { location: {
@ -14,7 +14,7 @@ const dbJourney = {
}, },
}, },
destination: { destination: {
type: 'stop', type: 'station',
id: '8003368', id: '8003368',
name: 'Köln Messe/Deutz', name: 'Köln Messe/Deutz',
location: { location: {
@ -167,7 +167,7 @@ const dbJourney = {
}, },
{ {
origin: { origin: {
type: 'stop', type: 'station',
id: '8003368', id: '8003368',
name: 'Köln Messe/Deutz', name: 'Köln Messe/Deutz',
location: { location: {
@ -178,7 +178,7 @@ const dbJourney = {
}, },
}, },
destination: { destination: {
type: 'stop', type: 'station',
id: '8073368', id: '8073368',
name: 'Köln Messe/Deutz Gl.11-12', name: 'Köln Messe/Deutz Gl.11-12',
location: { location: {
@ -200,7 +200,7 @@ const dbJourney = {
}, },
{ {
origin: { origin: {
type: 'stop', type: 'station',
id: '8073368', id: '8073368',
name: 'Köln Messe/Deutz Gl.11-12', name: 'Köln Messe/Deutz Gl.11-12',
location: { location: {
@ -211,7 +211,7 @@ const dbJourney = {
}, },
}, },
destination: { destination: {
type: 'stop', type: 'station',
id: '8000284', id: '8000284',
name: 'Nürnberg Hbf', name: 'Nürnberg Hbf',
location: { location: {

View file

@ -4,7 +4,7 @@ const dbJourney = {
legs: [ legs: [
{ {
origin: { origin: {
type: 'stop', type: 'station',
id: '8011160', id: '8011160',
name: 'Berlin Hbf', name: 'Berlin Hbf',
location: { location: {
@ -15,7 +15,7 @@ const dbJourney = {
}, },
}, },
destination: { destination: {
type: 'stop', type: 'station',
id: '8000080', id: '8000080',
name: 'Dortmund Hbf', name: 'Dortmund Hbf',
location: { location: {
@ -87,7 +87,7 @@ const dbJourney = {
}, },
{ {
origin: { origin: {
type: 'stop', type: 'station',
id: '8000080', id: '8000080',
name: 'Dortmund Hbf', name: 'Dortmund Hbf',
location: { location: {
@ -98,7 +98,7 @@ const dbJourney = {
}, },
}, },
destination: { destination: {
type: 'stop', type: 'station',
id: '8000207', id: '8000207',
name: 'Köln Hbf', name: 'Köln Hbf',
location: { location: {

View file

@ -100,7 +100,7 @@ tap.test('parses a stop correctly', (t) => {
const stop = parse(ctx, input); const stop = parse(ctx, input);
t.same(stop, { t.same(stop, {
type: 'stop', type: 'station',
id: '8012622', id: '8012622',
name: 'Perleberg', name: 'Perleberg',
location: { location: {
@ -133,7 +133,7 @@ tap.test('falls back to coordinates from `lid', (t) => {
const stop = parse(ctx, input); const stop = parse(ctx, input);
t.same(stop, { t.same(stop, {
type: 'stop', type: 'station',
id: '683407', id: '683407',
name: 'Bahnhof, Rothenburg ob der Tauber', name: 'Bahnhof, Rothenburg ob der Tauber',
location: { location: {