2024-02-06 22:58:49 +01:00
import { findRemarks } from './find-remarks.js' ;
2017-11-20 15:43:13 +01:00
2024-02-06 22:58:49 +01:00
const clone = obj => Object . assign ( { } , obj ) ;
2017-11-20 15:43:13 +01:00
2019-08-23 16:21:44 +02:00
const addRemark = ( stopoverOrLeg , remark ) => {
2024-02-06 22:58:49 +01:00
if ( ! Array . isArray ( stopoverOrLeg . remarks ) ) {
stopoverOrLeg . remarks = [ ] ;
}
stopoverOrLeg . remarks . push ( remark ) ;
} ;
2019-08-23 16:21:44 +02:00
const applyRemarks = ( leg , refs ) => {
for ( let [ remark , ref ] of findRemarks ( refs ) ) {
2024-02-06 22:58:49 +01:00
const { fromLocation , toLocation } = ref ;
2018-06-25 13:54:30 +02:00
2024-02-06 22:58:49 +01:00
let wholeLeg = true , fromI = 0 , toI = 0 ;
2020-11-15 14:42:46 +01:00
if ( Array . isArray ( leg . stopovers ) ) {
2024-02-06 22:58:49 +01:00
toI = leg . stopovers . length - 1 ;
2020-11-15 14:42:46 +01:00
// this fails if `s.stop` is a new object (not reference-equal)
// todo: do this index- or ID-based
if ( fromLocation ) {
2024-02-06 22:58:49 +01:00
fromI = leg . stopovers . findIndex ( s => s . stop === fromLocation ) ;
if ( fromI < 0 ) {
continue ;
}
2020-11-15 14:42:46 +01:00
}
if ( toLocation ) {
2024-02-06 22:58:49 +01:00
toI = leg . stopovers . findIndex ( s => s . stop === toLocation ) ;
if ( toI < 0 ) {
continue ;
}
2020-11-15 14:42:46 +01:00
}
2024-02-06 22:58:49 +01:00
wholeLeg = fromI === 0 && toI === leg . stopovers . length - 1 ;
2020-07-11 20:39:28 +02:00
}
2019-08-23 16:24:09 +02:00
2024-02-06 22:58:49 +01:00
if ( wholeLeg ) {
addRemark ( leg , remark ) ;
} else {
2019-08-23 16:24:09 +02:00
for ( let i = fromI ; i <= toI ; i ++ ) {
2024-02-06 22:58:49 +01:00
const stopover = leg . stopovers [ i ] ;
if ( stopover ) {
addRemark ( stopover , remark ) ;
}
2018-06-07 16:38:54 +02:00
}
}
2018-07-16 14:41:41 +02:00
2018-06-07 16:38:54 +02:00
// todo: `ref.tagL`
}
2024-02-06 22:58:49 +01:00
} ;
2017-11-20 15:43:13 +01:00
2019-10-20 00:19:11 +02:00
// todo: pt.status, pt.isPartCncl
2021-11-18 18:29:25 +01:00
// todo: pt.chRatingRT, pt.chgDurR, pt.minChg
2022-10-24 17:29:23 +02:00
// todo: pt.jny.dirFlg – https://github.com/alexander-albers/tripkit/blob/07047c6ddef24339ebd49a86a78158bca8047421/Sources/TripKit/Provider/AbstractHafasClientInterfaceProvider.swift#L347-L353 & https://github.com/alexander-albers/tripkit/commit/07047c6ddef24339ebd49a86a78158bca8047421#commitcomment-68471656
2019-10-20 00:19:11 +02:00
// todo: what is pt.recState?
// todo: what is `sty: 'UNDEF'`?
// todo: pt.prodL
// todo: pt.parJnyL (list of coupled trains)
2021-11-18 18:29:25 +01:00
// todo: pt.planrtTS
// todo: what is pt.jny.lPassSt?
2019-10-20 00:19:11 +02:00
const parseJourneyLeg = ( ctx , pt , date ) => { // pt = raw leg
2024-02-06 22:58:49 +01:00
const { profile , opt } = ctx ;
2019-10-20 00:19:11 +02:00
const res = {
origin : clone ( pt . dep . location ) || null ,
2024-02-06 22:58:49 +01:00
destination : clone ( pt . arr . location ) ,
} ;
2019-06-08 12:54:59 +02:00
2021-08-17 14:52:29 +02:00
// HAFAS seems to have a bug where a journey's first leg has a `dTZOffset` of `0`.
// https://github.com/public-transport/hafas-client/issues/237
if ( pt . type === 'WALK' && pt . dep . dTZOffset != pt . arr . aTZOffset ) {
2024-02-06 22:58:49 +01:00
if ( pt . dep . dTZOffset == 0 ) {
pt . dep . dTZOffset = pt . arr . aTZOffset ;
}
if ( pt . arr . aTZOffset == 0 ) {
pt . arr . aTZOffset = pt . dep . dTZOffset ;
}
2021-08-17 14:52:29 +02:00
}
2024-02-06 22:58:49 +01:00
const dep = profile . parseWhen ( ctx , date , pt . dep . dTimeS , pt . dep . dTimeR , pt . dep . dTZOffset , pt . dep . dCncl ) ;
res . departure = dep . when ;
res . plannedDeparture = dep . plannedWhen ;
2021-11-18 18:29:25 +01:00
// todo: pt.dep.dProgType
2024-02-06 22:58:49 +01:00
res . departureDelay = dep . delay ;
if ( dep . prognosedWhen ) {
res . prognosedDeparture = dep . prognosedWhen ;
}
add planned(Arrival|Departure|When), scheduled* -> planned*/prognosed* :boom:
when not cancelled:
{when, delay} -> {when, plannedWhen, delay}
{arrival, arrivalDelay} -> {arrival, plannedArrival, arrivalDelay}
{departure, departureDelay} -> {departure, plannedDeparture, departureDelay}
when cancelled:
{when: null, delay: null, scheduledWhen} -> {when: null, plannedWhen, prognosedWhen, delay}
{arrival: null, arrivalDelay: null, scheduledArrival, formerArrivalDelay} -> {arrival: null, plannedArrival, arrivalDelay, prognosedArrival}
{departure: null, departureDelay: null, scheduledDeparture, formerDepartureDelay} -> {departure: null, plannedDeparture, departureDelay, prognosedDeparture}
2019-08-23 18:51:03 +02:00
2024-02-06 22:58:49 +01:00
const arr = profile . parseWhen ( ctx , date , pt . arr . aTimeS , pt . arr . aTimeR , pt . arr . aTZOffset , pt . arr . aCncl ) ;
res . arrival = arr . when ;
res . plannedArrival = arr . plannedWhen ;
2021-11-18 18:29:25 +01:00
// todo: pt.arr.aProgType
2024-02-06 22:58:49 +01:00
res . arrivalDelay = arr . delay ;
if ( arr . prognosedWhen ) {
res . prognosedArrival = arr . prognosedWhen ;
}
2020-09-10 23:57:03 +02:00
2024-02-06 22:58:49 +01:00
if ( pt . jny && 'isRchbl' in pt . jny ) {
res . reachable = Boolean ( pt . jny . isRchbl ) ;
2019-10-20 00:19:11 +02:00
}
2017-11-20 15:43:13 +01:00
2019-10-20 00:19:11 +02:00
if ( pt . jny && pt . jny . polyline ) {
2024-02-06 22:58:49 +01:00
res . polyline = pt . jny . polyline || null ;
2020-07-26 13:20:15 +02:00
} else if ( pt . jny && pt . jny . poly ) {
2024-02-06 22:58:49 +01:00
res . polyline = profile . parsePolyline ( ctx , pt . jny . poly ) ;
2019-10-20 00:19:11 +02:00
}
2018-04-30 12:49:58 +02:00
2022-07-21 21:36:59 +02:00
if ( pt . type === 'WALK' || pt . type === 'TRSF' || pt . type === 'DEVI' || pt . type === 'CHKI' ) {
2024-02-06 22:58:49 +01:00
res . public = true ;
res . walking = true ;
res . distance = pt . gis && pt . gis . dist || null ;
if ( pt . type === 'TRSF' ) {
res . transfer = true ;
}
2020-04-18 17:22:56 +02:00
if ( pt . type === 'DEVI' ) {
2024-03-12 12:26:46 +01:00
// > DEVI Legs are Deviations.
// > It happens if you ask for a route to a specific stopPlace but HAFAS returns one to another stopPlace. Happens mainly in the night when there is no route to you destination for the forseeable future.
// > For Instance you want to go from Berlin Hbf to München Hbf. One of the only routes to München is to München Ost with a NJ at your time.
// > HAFAS returns that NJ and the last leg is a DEVI that just says "you wanted München HBF, this route ends in München Ost"
// > Bahn.de for instance ignores this completly, the DEVI leg also has no real information.
// https://github.com/public-transport/hafas-client/issues/301#issuecomment-1840820895
2020-04-18 17:22:56 +02:00
// todo: pt.resState, pt.resRecommendation
2024-02-06 22:58:49 +01:00
res . transfer = true ;
}
if ( pt . type === 'CHKI' ) {
res . checkin = true ;
2020-04-18 17:22:56 +02:00
}
2018-07-16 12:18:23 +02:00
2019-10-20 00:19:11 +02:00
// https://gist.github.com/derhuerst/426d4b95aeae701843b1e9c23105b8d4#file-tripsearch-2018-12-05-http-L4207-L4229
2020-04-18 17:22:56 +02:00
if ( opt . remarks && pt . gis && Array . isArray ( pt . gis . msgL ) ) {
2024-02-06 22:58:49 +01:00
applyRemarks ( res , pt . gis . msgL ) ;
2019-10-20 00:19:11 +02:00
}
} else if ( pt . type === 'JNY' ) {
// todo: pull `public` value from `profile.products`
2024-02-06 22:58:49 +01:00
res . tripId = pt . jny . jid ;
res . line = pt . jny . line || null ;
2021-12-08 14:12:22 +01:00
// todo [breaking]: don't call parseStationName() here, add parseDirection() hook
2021-12-29 13:51:37 +01:00
// todo: support pt.jny.dirL[]
2024-02-06 22:58:49 +01:00
res . direction = pt . jny . dirTxt && profile . parseStationName ( ctx , pt . jny . dirTxt ) || null ;
2021-10-12 17:21:50 +02:00
if ( pt . jny . pos ) {
res . currentLocation = {
type : 'location' ,
latitude : pt . jny . pos . y / 1000000 ,
longitude : pt . jny . pos . x / 1000000 ,
2024-02-06 22:58:49 +01:00
} ;
2021-10-12 17:21:50 +02:00
}
2024-02-06 22:58:49 +01:00
const arrPl = profile . parsePlatform ( ctx , pt . arr . aPlatfS || ( pt . arr . aPltfS !== undefined
? pt . arr . aPltfS . txt
: null ) , pt . arr . aPlatfR || ( pt . arr . aPltfR !== undefined
? pt . arr . aPltfR . txt
: null ) , pt . arr . aCncl ) ;
res . arrivalPlatform = arrPl . platform ;
res . plannedArrivalPlatform = arrPl . plannedPlatform ;
if ( arrPl . prognosedPlatform ) {
res . prognosedArrivalPlatform = arrPl . prognosedPlatform ;
}
res . arrivalPrognosisType = profile . parsePrognosisType ( ctx , pt . arr . aProgType ) || null ;
const depPl = profile . parsePlatform ( ctx , pt . dep . dPlatfS || ( pt . dep . dPltfS !== undefined
? pt . dep . dPltfS . txt
: null ) , pt . dep . dPlatfR || ( pt . dep . dPltfR !== undefined
? pt . dep . dPltfR . txt
: null ) , pt . dep . dCncl ) ;
res . departurePlatform = depPl . platform ;
res . plannedDeparturePlatform = depPl . plannedPlatform ;
if ( depPl . prognosedPlatform ) {
res . prognosedDeparturePlatform = depPl . prognosedPlatform ;
}
res . departurePrognosisType = profile . parsePrognosisType ( ctx , pt . dep . dProgType ) || null ;
2019-10-20 00:19:11 +02:00
if ( opt . stopovers && pt . jny . stopL ) {
2024-02-06 22:58:49 +01:00
const stopL = pt . jny . stopL ;
res . stopovers = stopL . map ( s => profile . parseStopover ( ctx , s , date ) ) ;
2019-10-20 00:19:11 +02:00
if ( opt . remarks && Array . isArray ( pt . jny . msgL ) ) {
2024-02-06 22:58:49 +01:00
applyRemarks ( res , pt . jny . msgL ) ;
2021-11-18 18:29:25 +01:00
// todo: parse & use `code: EXTERNAL_ID` remarks?
2018-07-16 12:18:23 +02:00
}
2018-06-25 13:54:30 +02:00
2019-10-20 00:19:11 +02:00
// filter stations the train passes without stopping, as this doesn't comply with fptf (yet)
2024-02-06 22:58:49 +01:00
res . stopovers = res . stopovers . filter ( ( x ) => ! x . passBy ) ;
2023-05-11 19:45:09 +00:00
} else if ( opt . remarks && Array . isArray ( pt . jny . msgL ) ) {
applyRemarks ( res , pt . jny . msgL ) ;
2019-10-20 00:19:11 +02:00
}
2017-11-20 15:43:13 +01:00
2024-02-06 22:58:49 +01:00
const freq = pt . jny . freq || { } ;
2019-10-20 00:19:11 +02:00
// todo: expose `res.cycle` even if only one field exists (breaking)
2024-01-18 15:27:35 +01:00
// todo [breaking]: rename to something more intuitive, e.g. res.headways.{min,max,nrOfTrips}
2019-10-20 00:19:11 +02:00
if ( freq . minC && freq . maxC ) {
res . cycle = {
min : freq . minC * 60 ,
2024-02-06 22:58:49 +01:00
max : freq . maxC * 60 ,
} ;
2019-10-20 00:19:11 +02:00
// nr of connections in this frequency, from now on
2024-02-06 22:58:49 +01:00
if ( freq . numC ) {
res . cycle . nr = freq . numC ;
}
2019-10-20 00:19:11 +02:00
}
2018-12-02 01:05:01 +01:00
2019-10-20 00:19:11 +02:00
if ( freq . jnyL ) {
const parseAlternative = ( a ) => {
// todo: parse this just like a `leg` (breaking)
// todo: parse `a.stopL`, `a.ctxRecon`, `a.msgL`
2024-02-06 22:58:49 +01:00
const st0 = Array . isArray ( a . stopL ) && a . stopL [ 0 ] || { } ;
2020-12-07 14:16:57 +01:00
const when = st0
? profile . parseWhen ( ctx , date , st0 . dTimeS , st0 . dTimeR , st0 . dTZOffset , st0 . dCncl )
2024-02-06 22:58:49 +01:00
: null ;
2019-10-20 00:19:11 +02:00
return {
tripId : a . jid ,
line : a . line || null ,
direction : a . dirTxt || null ,
2020-12-07 14:16:57 +01:00
... when ,
2024-02-06 22:58:49 +01:00
} ;
} ;
res . alternatives = freq . jnyL . map ( parseAlternative ) ;
2017-11-20 15:43:13 +01:00
}
2019-10-20 00:19:11 +02:00
}
2017-11-20 15:43:13 +01:00
2019-10-20 00:19:11 +02:00
if ( pt . arr . aCncl || pt . dep . dCncl ) {
2024-02-06 22:58:49 +01:00
res . cancelled = true ;
Object . defineProperty ( res , 'canceled' , { value : true } ) ;
2017-11-20 15:43:13 +01:00
}
2024-02-06 22:58:49 +01:00
return res ;
} ;
2017-11-20 15:43:13 +01:00
2022-05-07 16:17:37 +02:00
export {
parseJourneyLeg ,
2024-02-06 22:58:49 +01:00
} ;