2024-02-06 22:58:49 +01:00
|
|
|
|
import {parse} from 'qs';
|
|
|
|
|
import get from 'lodash/get.js';
|
2019-02-05 19:07:19 +01:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const POI = 'P';
|
|
|
|
|
const STATION = 'S';
|
|
|
|
|
const ADDRESS = 'A';
|
2017-11-11 22:35:41 +01:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const leadingZeros = /^0+/;
|
2018-10-15 17:31:28 +02:00
|
|
|
|
|
2020-03-18 21:35:43 +01:00
|
|
|
|
// todo: what is l.wt? is it "weight"?
|
|
|
|
|
// - `6733` for 8013074 with p/vmt
|
|
|
|
|
// - `3933` for 8012092 with p/vmt
|
|
|
|
|
// - `2062` for 8010168 with p/vmt
|
2021-11-18 18:29:25 +01:00
|
|
|
|
// todo: l.gidL (e.g. `["A×de:15088:8013414"]`)
|
|
|
|
|
// todo: `i` param in `lid` (e.g. `A=1@O=Zöberitz@X=12033455@Y=51504612@U=80@L=8013414@i=A×de:15088:8013414@`)
|
|
|
|
|
|
2019-10-20 00:19:11 +02:00
|
|
|
|
const parseLocation = (ctx, l) => {
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const {profile, opt} = ctx;
|
2019-10-20 00:19:11 +02:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const lid = parse(l.lid, {delimiter: '@'});
|
2019-02-07 17:46:49 +01:00
|
|
|
|
const res = {
|
|
|
|
|
type: 'location',
|
2024-02-06 22:58:49 +01:00
|
|
|
|
id: (l.extId || lid.L || '').replace(leadingZeros, '') || null,
|
|
|
|
|
};
|
2019-02-07 17:46:49 +01:00
|
|
|
|
|
2017-12-11 19:25:29 +01:00
|
|
|
|
if (l.crd) {
|
2024-02-06 22:58:49 +01:00
|
|
|
|
res.latitude = l.crd.y / 1000000;
|
|
|
|
|
res.longitude = l.crd.x / 1000000;
|
|
|
|
|
} else if ('X' in lid && 'Y' in lid) {
|
|
|
|
|
res.latitude = lid.Y / 1000000;
|
|
|
|
|
res.longitude = lid.X / 1000000;
|
2017-11-11 22:35:41 +01:00
|
|
|
|
}
|
2017-11-11 23:56:09 +01:00
|
|
|
|
|
2017-12-11 19:25:29 +01:00
|
|
|
|
if (l.type === STATION) {
|
2020-02-15 19:13:43 +00:00
|
|
|
|
// todo: https://github.com/public-transport/hafas-client/issues/151
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const locL = get(ctx.res, ['common', 'locL'], []);
|
2020-02-15 19:13:43 +00:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const mMastLocX = 'mMastLocX' in l
|
|
|
|
|
? l.mMastLocX
|
|
|
|
|
: NaN;
|
2020-02-15 19:13:43 +00:00
|
|
|
|
const subStops = (l.stopLocL || [])
|
2024-02-06 22:58:49 +01:00
|
|
|
|
.filter(locX => locX !== mMastLocX)
|
|
|
|
|
.map(locX => locL[locX])
|
|
|
|
|
.filter(s => Boolean(s))
|
|
|
|
|
.map(s => profile.parseLocation(ctx, s))
|
|
|
|
|
.filter(stop => Boolean(stop));
|
2020-02-15 19:13:43 +00:00
|
|
|
|
|
2018-07-10 23:32:34 +02:00
|
|
|
|
const stop = {
|
2024-02-06 22:58:49 +01:00
|
|
|
|
type: l.isMainMast || subStops.length > 0
|
|
|
|
|
? 'station'
|
|
|
|
|
: 'stop',
|
2019-02-07 17:46:49 +01:00
|
|
|
|
id: res.id,
|
2024-02-06 22:58:49 +01:00
|
|
|
|
name: l.name || lid.O
|
|
|
|
|
? profile.parseStationName(ctx, l.name || lid.O)
|
|
|
|
|
: null,
|
|
|
|
|
location: 'number' === typeof res.latitude
|
|
|
|
|
? res
|
|
|
|
|
: null, // todo: remove `.id`
|
|
|
|
|
};
|
|
|
|
|
if (opt.subStops && subStops.length > 0) {
|
|
|
|
|
stop.stops = subStops;
|
2017-12-11 19:25:29 +01:00
|
|
|
|
}
|
2018-01-26 16:25:13 +01:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
if ('pCls' in l) {
|
|
|
|
|
stop.products = profile.parseProductsBitmask(ctx, l.pCls);
|
|
|
|
|
}
|
|
|
|
|
if ('meta' in l) {
|
|
|
|
|
stop.isMeta = Boolean(l.meta);
|
|
|
|
|
}
|
2018-01-26 16:25:13 +01:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const mMastLoc = locL[mMastLocX];
|
2020-03-18 19:49:45 +01:00
|
|
|
|
if (mMastLoc) {
|
|
|
|
|
stop.station = {
|
|
|
|
|
...profile.parseLocation(ctx, mMastLoc),
|
|
|
|
|
type: 'station', // todo: this should be handled differently
|
2024-02-06 22:58:49 +01:00
|
|
|
|
};
|
2020-03-18 19:49:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 19:13:43 +00:00
|
|
|
|
if (opt.entrances) {
|
|
|
|
|
const entrances = (l.entryLocL || [])
|
2024-02-06 22:58:49 +01:00
|
|
|
|
.map(locX => locL[locX])
|
|
|
|
|
.filter(l => Boolean(l))
|
|
|
|
|
.map(l => profile.parseLocation(ctx, l))
|
|
|
|
|
.filter(loc => Boolean(loc))
|
|
|
|
|
.map(loc => loc.location);
|
|
|
|
|
if (entrances.length > 0) {
|
|
|
|
|
stop.entrances = entrances;
|
|
|
|
|
}
|
2020-02-15 19:13:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-23 16:06:21 +02:00
|
|
|
|
if (opt.linesOfStops && Array.isArray(l.lines)) {
|
2024-02-06 22:58:49 +01:00
|
|
|
|
stop.lines = l.lines;
|
2018-01-26 16:25:13 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-07 00:31:25 +01:00
|
|
|
|
const locHints = (l.remarkRefs || [])
|
2024-02-06 22:58:49 +01:00
|
|
|
|
.filter(ref => Boolean(ref.hint) && Array.isArray(ref.tagL))
|
|
|
|
|
.filter(({tagL}) => tagL.includes('RES_LOC')
|
|
|
|
|
|| tagL.find(t => t.slice(0, 8) === 'RES_LOC_'), // e.g. `RES_LOC_H3`
|
|
|
|
|
)
|
|
|
|
|
.map(ref => ref.hint);
|
2020-03-07 00:31:25 +01:00
|
|
|
|
const hints = [
|
2024-02-06 22:58:49 +01:00
|
|
|
|
...l.hints || [],
|
2020-03-07 00:31:25 +01:00
|
|
|
|
...locHints,
|
2024-02-06 22:58:49 +01:00
|
|
|
|
];
|
|
|
|
|
const byType = type => hints.find(h => h.type === type);
|
2020-03-07 00:28:47 +01:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const transitAuthority = (byType('transit-authority') || {}).text;
|
|
|
|
|
if (transitAuthority) {
|
|
|
|
|
stop.transitAuthority = transitAuthority;
|
|
|
|
|
}
|
2020-03-07 00:31:25 +01:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const dhid = (byType('stop-dhid') || {}).text;
|
2020-03-07 00:28:47 +01:00
|
|
|
|
if (dhid) {
|
2024-02-06 22:58:49 +01:00
|
|
|
|
if (!stop.ids) {
|
|
|
|
|
stop.ids = {};
|
|
|
|
|
}
|
|
|
|
|
stop.ids.dhid = dhid;
|
2020-03-07 00:28:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const otherIds = hints
|
2024-02-06 22:58:49 +01:00
|
|
|
|
.filter(h => h.type === 'foreign-id')
|
|
|
|
|
.filter(h => 'string' === typeof h.text && h.text.includes(':'))
|
|
|
|
|
.map(({text}) => {
|
|
|
|
|
const i = text.indexOf(':');
|
|
|
|
|
return [text.slice(0, i), text.slice(i + 1)];
|
|
|
|
|
})
|
|
|
|
|
.filter(([src]) => src !== 'NULL');
|
2020-03-07 00:28:47 +01:00
|
|
|
|
if (otherIds.length > 0) {
|
2024-02-06 22:58:49 +01:00
|
|
|
|
if (!stop.ids) {
|
|
|
|
|
stop.ids = {};
|
|
|
|
|
}
|
|
|
|
|
for (const [src, id] of otherIds) {
|
|
|
|
|
stop.ids[src] = id;
|
|
|
|
|
}
|
2020-03-07 00:28:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
return stop;
|
2017-12-11 19:25:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
if (l.type === ADDRESS) {
|
|
|
|
|
res.address = l.name;
|
|
|
|
|
} else {
|
|
|
|
|
res.name = l.name;
|
|
|
|
|
}
|
|
|
|
|
if (l.type === POI) {
|
|
|
|
|
res.poi = true;
|
|
|
|
|
}
|
2017-11-11 23:56:09 +01:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
return res;
|
|
|
|
|
};
|
2017-11-11 22:35:41 +01:00
|
|
|
|
|
2021-02-12 23:55:39 +01:00
|
|
|
|
// We use a "visited list" to prevent endless recursion.
|
2021-12-08 14:12:22 +01:00
|
|
|
|
// todo: can we use a WeakMap here?
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const seen = Symbol('parseLocation seen items');
|
2020-02-15 19:14:50 +00:00
|
|
|
|
const parseLocationWithoutCycles = (ctx, l, ...args) => {
|
2024-02-06 22:58:49 +01:00
|
|
|
|
if (ctx[seen] && ctx[seen].includes(l)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2020-02-15 19:14:50 +00:00
|
|
|
|
|
2024-02-06 22:58:49 +01:00
|
|
|
|
const newSeen = ctx[seen]
|
|
|
|
|
? [...ctx[seen], l]
|
|
|
|
|
: [l];
|
|
|
|
|
return parseLocation({...ctx, [seen]: newSeen}, l, ...args);
|
|
|
|
|
};
|
2020-02-15 19:14:50 +00:00
|
|
|
|
|
2022-05-07 16:17:37 +02:00
|
|
|
|
export {
|
|
|
|
|
parseLocationWithoutCycles as parseLocation,
|
2024-02-06 22:58:49 +01:00
|
|
|
|
};
|