hvv-client 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +8 -0
- package/.eslintignore +1 -0
- package/.eslintrc.json +3 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc.js +3 -0
- package/package.json +29 -0
- package/src/client/hvvClient.ts +206 -0
- package/src/client/hvvClientOptions.ts +6 -0
- package/src/converters/attributes.converter.ts +13 -0
- package/src/converters/date.converter.ts +96 -0
- package/src/converters/line.converter.ts +90 -0
- package/src/converters/lineDepartureConverter.ts +42 -0
- package/src/converters/routePoint.converter.ts +164 -0
- package/src/index.ts +2 -0
- package/src/models/attribute.ts +16 -0
- package/src/models/coordinate.ts +4 -0
- package/src/models/direction.ts +1 -0
- package/src/models/filter.ts +1 -0
- package/src/models/line.ts +95 -0
- package/src/models/lineDeparture.ts +14 -0
- package/src/models/routePoint.ts +34 -0
- package/src/models/service.ts +25 -0
- package/src/models/stationDepartureInfo.ts +6 -0
- package/src/models/timeRange.ts +4 -0
- package/src/models/timeRealtime.ts +20 -0
- package/src/validators/apiResponse/gtiCheckName.ts +24 -0
- package/src/validators/apiResponse/gtiDepartureList.ts +10 -0
- package/src/validators/gtiAttribute.ts +20 -0
- package/src/validators/gtiCoordinate.ts +6 -0
- package/src/validators/gtiDeparture.ts +40 -0
- package/src/validators/gtiDirection.ts +9 -0
- package/src/validators/gtiFilterEntry.ts +8 -0
- package/src/validators/gtiFilterServiceType.ts +18 -0
- package/src/validators/gtiSDName.ts +75 -0
- package/src/validators/gtiService.ts +111 -0
- package/src/validators/gtiServiceType.ts +13 -0
- package/src/validators/gtiTariffDetails.ts +16 -0
- package/tsconfig.json +12 -0
package/.editorconfig
ADDED
package/.eslintignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
build/
|
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/hvv-client.iml" filepath="$PROJECT_DIR$/.idea/hvv-client.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
package/.idea/vcs.xml
ADDED
package/.prettierrc.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hvv-client",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"lint": "gts lint",
|
|
9
|
+
"clean": "gts clean",
|
|
10
|
+
"compile": "tsc",
|
|
11
|
+
"fix": "gts fix",
|
|
12
|
+
"prepare": "npm run compile",
|
|
13
|
+
"pretest": "npm run compile",
|
|
14
|
+
"posttest": "npm run lint"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"crypto-js": "^4.2.0",
|
|
18
|
+
"date-fns": "^4.1.0",
|
|
19
|
+
"date-fns-tz": "^3.2.0",
|
|
20
|
+
"zod": "^3.24.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/crypto-js": "^4.2.2",
|
|
24
|
+
"@types/node": "^22.7.5",
|
|
25
|
+
"gts": "^6.0.2",
|
|
26
|
+
"typescript": "^5.6.3"
|
|
27
|
+
},
|
|
28
|
+
"private": false
|
|
29
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {ZodSchema} from 'zod';
|
|
2
|
+
import hmacSHA1 from 'crypto-js/hmac-sha1';
|
|
3
|
+
import Base64 from 'crypto-js/enc-base64';
|
|
4
|
+
|
|
5
|
+
import {HvvClientOptions} from './hvvClientOptions';
|
|
6
|
+
import {RoutePointStation, RoutePointStationSimple} from '../models/routePoint';
|
|
7
|
+
import {Filter} from '../models/filter';
|
|
8
|
+
import {Service} from '../models/service';
|
|
9
|
+
import {StationDepartureInfo} from '../models/stationDepartureInfo';
|
|
10
|
+
import {dateConverter} from '../converters/date.converter';
|
|
11
|
+
import {gtiDepartureListResponseSchema} from '../validators/apiResponse/gtiDepartureList';
|
|
12
|
+
import {lineDepartureConverter} from '../converters/lineDepartureConverter';
|
|
13
|
+
import {routePointConverter} from '../converters/routePoint.converter';
|
|
14
|
+
import {Coordinate} from '../models/coordinate';
|
|
15
|
+
import {gtiCheckNameCoordinatesResponseSchema} from '../validators/apiResponse/gtiCheckName';
|
|
16
|
+
|
|
17
|
+
type AllHvvClientOptions = Required<HvvClientOptions>;
|
|
18
|
+
|
|
19
|
+
const defaultOptions: AllHvvClientOptions = {
|
|
20
|
+
user: '',
|
|
21
|
+
key: '',
|
|
22
|
+
version: 59,
|
|
23
|
+
host: 'https://gti.geofox.de',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export class HvvClient {
|
|
27
|
+
private readonly options: AllHvvClientOptions;
|
|
28
|
+
|
|
29
|
+
public constructor(options: HvvClientOptions) {
|
|
30
|
+
this.options = {
|
|
31
|
+
...defaultOptions,
|
|
32
|
+
...options,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
*/
|
|
39
|
+
public async getNearbyStations({
|
|
40
|
+
coordinate,
|
|
41
|
+
maxDistanceMeters = 800,
|
|
42
|
+
returnTariffDetails = false,
|
|
43
|
+
}: {
|
|
44
|
+
coordinate: Coordinate;
|
|
45
|
+
maxDistanceMeters: number;
|
|
46
|
+
returnTariffDetails?: boolean;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
stations: {
|
|
49
|
+
routePoint: RoutePointStation;
|
|
50
|
+
distanceMeters: number;
|
|
51
|
+
walkingDistanceMinutes: number;
|
|
52
|
+
}[];
|
|
53
|
+
}> {
|
|
54
|
+
const body = {
|
|
55
|
+
theName: {
|
|
56
|
+
type: 'STATION',
|
|
57
|
+
coordinate: {
|
|
58
|
+
x: coordinate.long,
|
|
59
|
+
y: coordinate.lat,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
maxDistance: maxDistanceMeters,
|
|
63
|
+
coordinateType: 'EPSG_4326',
|
|
64
|
+
tariffDetails: returnTariffDetails,
|
|
65
|
+
allowTypeSwitch: false,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return await this.makeRequest({
|
|
69
|
+
endpoint: 'checkName',
|
|
70
|
+
body,
|
|
71
|
+
validator: gtiCheckNameCoordinatesResponseSchema,
|
|
72
|
+
converter: data => {
|
|
73
|
+
// filter stations
|
|
74
|
+
const filtered = data.results.filter(item => {
|
|
75
|
+
return item.type === 'STATION';
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
stations: filtered.map(result => ({
|
|
80
|
+
routePoint: routePointConverter.stationToDto(result),
|
|
81
|
+
distanceMeters: result.distance,
|
|
82
|
+
walkingDistanceMinutes: result.time,
|
|
83
|
+
})),
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get departures of a given station.
|
|
91
|
+
*/
|
|
92
|
+
public async getDepartures({
|
|
93
|
+
stations,
|
|
94
|
+
time,
|
|
95
|
+
filters,
|
|
96
|
+
serviceFilters,
|
|
97
|
+
realtime = true,
|
|
98
|
+
maxResults = 4,
|
|
99
|
+
maxOffsetMinutes = 60,
|
|
100
|
+
allStationsInChangingNode = true,
|
|
101
|
+
returnFilters = false,
|
|
102
|
+
}: {
|
|
103
|
+
stations: RoutePointStationSimple[];
|
|
104
|
+
time: Date;
|
|
105
|
+
filters?: Filter[];
|
|
106
|
+
serviceFilters?: Service[];
|
|
107
|
+
realtime?: boolean;
|
|
108
|
+
maxResults?: number;
|
|
109
|
+
maxOffsetMinutes?: number;
|
|
110
|
+
allStationsInChangingNode?: boolean;
|
|
111
|
+
returnFilters?: boolean;
|
|
112
|
+
}): Promise<{departures: StationDepartureInfo[]}> {
|
|
113
|
+
const body: Record<string, unknown> = {
|
|
114
|
+
stations: stations.map(routePointConverter.dtoToGti),
|
|
115
|
+
time: dateConverter.dateToGtiTime(time),
|
|
116
|
+
maxList: maxResults,
|
|
117
|
+
maxTimeOffset: maxOffsetMinutes,
|
|
118
|
+
allStationsInChangingNode: allStationsInChangingNode,
|
|
119
|
+
returnFilters: returnFilters,
|
|
120
|
+
useRealtime: realtime,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (filters !== undefined) {
|
|
124
|
+
body.filter = filters;
|
|
125
|
+
}
|
|
126
|
+
if (serviceFilters !== undefined) {
|
|
127
|
+
body.serviceTypes = serviceFilters;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return await this.makeRequest({
|
|
131
|
+
endpoint: 'departureList',
|
|
132
|
+
body,
|
|
133
|
+
validator: gtiDepartureListResponseSchema,
|
|
134
|
+
converter: data => {
|
|
135
|
+
const departures = data.departures.map(departure => ({
|
|
136
|
+
stationId: departure.station?.id ?? null,
|
|
137
|
+
departure: lineDepartureConverter.toDto(departure, time),
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
departures: departures,
|
|
142
|
+
// TODO: other fields?
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---
|
|
149
|
+
|
|
150
|
+
private async makeRequest<ValidatorOutput, ConverterOutput>({
|
|
151
|
+
endpoint,
|
|
152
|
+
body,
|
|
153
|
+
validator,
|
|
154
|
+
converter,
|
|
155
|
+
}: {
|
|
156
|
+
endpoint: string;
|
|
157
|
+
body: Record<string, unknown>;
|
|
158
|
+
validator: ZodSchema<ValidatorOutput>;
|
|
159
|
+
converter: (data: ValidatorOutput) => ConverterOutput;
|
|
160
|
+
}): Promise<ConverterOutput> {
|
|
161
|
+
const fullBody = {
|
|
162
|
+
version: this.options.version,
|
|
163
|
+
...body,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const headers = {
|
|
167
|
+
...this.getAuthHeaders(fullBody),
|
|
168
|
+
'Content-Type': 'application/json',
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
console.log('full API request body', fullBody);
|
|
172
|
+
|
|
173
|
+
const response = await fetch(
|
|
174
|
+
`${this.options.host}/gti/public/${endpoint}`,
|
|
175
|
+
{
|
|
176
|
+
method: 'POST',
|
|
177
|
+
body: JSON.stringify(fullBody),
|
|
178
|
+
headers,
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const responseBody = await response.json();
|
|
183
|
+
|
|
184
|
+
console.dir(responseBody);
|
|
185
|
+
|
|
186
|
+
const {success, data, error} = validator.safeParse(responseBody);
|
|
187
|
+
|
|
188
|
+
if (!success) {
|
|
189
|
+
// eslint-disable-next-line no-debugger
|
|
190
|
+
debugger;
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return converter(data);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private getAuthHeaders(requestBody: Record<string, unknown>) {
|
|
198
|
+
const hmac = hmacSHA1(JSON.stringify(requestBody), this.options.key);
|
|
199
|
+
const signature = Base64.stringify(hmac);
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
'geofox-auth-user': this.options.user,
|
|
203
|
+
'geofox-auth-signature': signature,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {GtiAttribute} from '../validators/gtiAttribute';
|
|
2
|
+
import {Attribute} from '../models/attribute';
|
|
3
|
+
|
|
4
|
+
export const attributeConverter = {
|
|
5
|
+
toDto(gti: GtiAttribute): Attribute {
|
|
6
|
+
return {
|
|
7
|
+
id: gti.id ?? null,
|
|
8
|
+
isPlanned: gti.isPlanned,
|
|
9
|
+
value: gti.value,
|
|
10
|
+
types: gti.types,
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {format, toZonedTime, fromZonedTime} from 'date-fns-tz';
|
|
2
|
+
import {addSeconds} from 'date-fns';
|
|
3
|
+
import {TimeRealtime} from '../models/timeRealtime';
|
|
4
|
+
import {TimeRange} from '../models/timeRange';
|
|
5
|
+
|
|
6
|
+
export type GtiTime = {date: string; time: string};
|
|
7
|
+
export type GtiTimeRange = {begin: string; end: string};
|
|
8
|
+
|
|
9
|
+
const GTI_TIMEZONE = 'Europe/Berlin';
|
|
10
|
+
|
|
11
|
+
export const dateConverter = {
|
|
12
|
+
/**
|
|
13
|
+
* Converts a Date into the DateTime object required by the Geofox API (GTI).
|
|
14
|
+
* @param date
|
|
15
|
+
*/
|
|
16
|
+
dateToGtiTime(date: Date): GtiTime {
|
|
17
|
+
// Geofox dates must always be in german timezone as it does not accept ISO dates.
|
|
18
|
+
// Figure out german timezone and respect that when converting to GtiTime.
|
|
19
|
+
|
|
20
|
+
// convert to germany time
|
|
21
|
+
const germanyTime = toZonedTime(date, GTI_TIMEZONE);
|
|
22
|
+
|
|
23
|
+
const gti = {
|
|
24
|
+
date: format(germanyTime, 'dd.MM.y'),
|
|
25
|
+
time: format(germanyTime, 'HH:mm'),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return gti;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Converts a date into a `DateTime` string used by the Geofox API.
|
|
33
|
+
* This is not a standard ISO string, as the docs require a timezone offset at the end.
|
|
34
|
+
* The default `.toISOString()` generates something like:
|
|
35
|
+
* `2024-11-02T09:58:00.000Z`, however the Geofox API expects something like
|
|
36
|
+
* `2024-11-02T09:58:00.000+0000`
|
|
37
|
+
* @param date
|
|
38
|
+
*/
|
|
39
|
+
dateToApiDateTime(date: Date): string {
|
|
40
|
+
const isoString = date.toISOString();
|
|
41
|
+
// remove last letter from isoString
|
|
42
|
+
return isoString.slice(0, -1) + '+0000';
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
apiDateTimeToDate(apiDateTime: string): Date {
|
|
46
|
+
return new Date(apiDateTime);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
gtiTimeToDate(gtiTime: GtiTime): Date {
|
|
50
|
+
const dateParts = gtiTime.date.split('.');
|
|
51
|
+
|
|
52
|
+
const germanyTime = new Date(
|
|
53
|
+
`${dateParts[2]}-${dateParts[1]}-${dateParts[0]}T${gtiTime.time}`,
|
|
54
|
+
);
|
|
55
|
+
const utc = fromZonedTime(germanyTime, GTI_TIMEZONE);
|
|
56
|
+
|
|
57
|
+
return utc;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
gtiTimeRangeToDto(gtiTimeRange: GtiTimeRange): TimeRange {
|
|
61
|
+
const fromDateGermanyTime = new Date(gtiTimeRange.begin);
|
|
62
|
+
const toDateGermanyTime = new Date(gtiTimeRange.end);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
from: fromZonedTime(fromDateGermanyTime, GTI_TIMEZONE),
|
|
66
|
+
to: fromZonedTime(toDateGermanyTime, GTI_TIMEZONE),
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
gtiTimeRealtimeCancelToDto(
|
|
71
|
+
gtiTime: GtiTime,
|
|
72
|
+
delaySeconds: number | null,
|
|
73
|
+
isCancelled: boolean,
|
|
74
|
+
): TimeRealtime {
|
|
75
|
+
const planned = dateConverter.gtiTimeToDate(gtiTime);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
planned,
|
|
79
|
+
actual: isCancelled ? null : addSeconds(planned, delaySeconds ?? 0),
|
|
80
|
+
delaySeconds,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
apiTimeRealtimeToDto(
|
|
85
|
+
apiDateTime: string,
|
|
86
|
+
delaySeconds: number | null,
|
|
87
|
+
): TimeRealtime {
|
|
88
|
+
const planned = dateConverter.apiDateTimeToDate(apiDateTime);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
planned,
|
|
92
|
+
actual: addSeconds(planned, delaySeconds ?? 0),
|
|
93
|
+
delaySeconds,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GtiService,
|
|
3
|
+
GtiSimpleServiceType,
|
|
4
|
+
isGtiServicePublicTransport,
|
|
5
|
+
} from '../validators/gtiService';
|
|
6
|
+
import {GtiDirection} from '../validators/gtiDirection';
|
|
7
|
+
import {Line} from '../models/line';
|
|
8
|
+
|
|
9
|
+
function getLineImageUrl(lineId: string): string {
|
|
10
|
+
return `https://cloud.geofox.de/icon/line?height=28&lineKey=${lineId}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Converter for a `Line`.
|
|
15
|
+
*
|
|
16
|
+
* In Geofox, this is called a `Service`.
|
|
17
|
+
*/
|
|
18
|
+
export const lineConverter = {
|
|
19
|
+
toDto(gti: GtiService): Line {
|
|
20
|
+
const base = {
|
|
21
|
+
name: gti.name,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (isGtiServicePublicTransport(gti)) {
|
|
25
|
+
switch (gti.type.simpleType) {
|
|
26
|
+
case GtiSimpleServiceType.BUS:
|
|
27
|
+
return {
|
|
28
|
+
...base,
|
|
29
|
+
type: 'bus',
|
|
30
|
+
direction: gti.direction,
|
|
31
|
+
directionType:
|
|
32
|
+
gti.directionId === GtiDirection.FORWARDS
|
|
33
|
+
? 'forward'
|
|
34
|
+
: 'backward',
|
|
35
|
+
id: gti.id,
|
|
36
|
+
iconUrl: getLineImageUrl(gti.id),
|
|
37
|
+
};
|
|
38
|
+
case GtiSimpleServiceType.TRAIN:
|
|
39
|
+
return {
|
|
40
|
+
...base,
|
|
41
|
+
type: 'train',
|
|
42
|
+
direction: gti.direction,
|
|
43
|
+
directionType:
|
|
44
|
+
gti.directionId === GtiDirection.FORWARDS
|
|
45
|
+
? 'forward'
|
|
46
|
+
: 'backward',
|
|
47
|
+
id: gti.id,
|
|
48
|
+
iconUrl: getLineImageUrl(gti.id),
|
|
49
|
+
};
|
|
50
|
+
case GtiSimpleServiceType.SHIP:
|
|
51
|
+
return {
|
|
52
|
+
...base,
|
|
53
|
+
type: 'ship',
|
|
54
|
+
direction: gti.direction,
|
|
55
|
+
directionType:
|
|
56
|
+
gti.directionId === GtiDirection.FORWARDS
|
|
57
|
+
? 'forward'
|
|
58
|
+
: 'backward',
|
|
59
|
+
id: gti.id,
|
|
60
|
+
iconUrl: getLineImageUrl(gti.id),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
switch (gti.type.simpleType) {
|
|
66
|
+
case GtiSimpleServiceType.BICYCLE:
|
|
67
|
+
return {
|
|
68
|
+
...base,
|
|
69
|
+
type: 'bicycle',
|
|
70
|
+
};
|
|
71
|
+
case GtiSimpleServiceType.FOOTPATH:
|
|
72
|
+
return {
|
|
73
|
+
...base,
|
|
74
|
+
type: 'footwalk',
|
|
75
|
+
};
|
|
76
|
+
case GtiSimpleServiceType.CHANGE:
|
|
77
|
+
return {
|
|
78
|
+
...base,
|
|
79
|
+
type: 'changeStation',
|
|
80
|
+
};
|
|
81
|
+
case GtiSimpleServiceType.CHANGE_SAME_PLATFORM:
|
|
82
|
+
return {
|
|
83
|
+
...base,
|
|
84
|
+
type: 'changePlatform',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error(`unknown line type: ${gti.type.simpleType}`);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {addMinutes, addSeconds} from 'date-fns';
|
|
2
|
+
|
|
3
|
+
import {lineConverter} from './line.converter';
|
|
4
|
+
|
|
5
|
+
import {GtiDeparture} from '../validators/gtiDeparture';
|
|
6
|
+
import {LineDeparture} from '../models/lineDeparture';
|
|
7
|
+
import {isLinePublicTransport} from '../models/line';
|
|
8
|
+
import {attributeConverter} from './attributes.converter';
|
|
9
|
+
|
|
10
|
+
export const lineDepartureConverter = {
|
|
11
|
+
toDto(gti: GtiDeparture, referenceTime: Date): LineDeparture {
|
|
12
|
+
const isCancelled = gti.cancelled ?? false;
|
|
13
|
+
const delaySeconds = gti.delay ?? null;
|
|
14
|
+
const planned = addMinutes(referenceTime, gti.timeOffset);
|
|
15
|
+
const actual = isCancelled ? null : addSeconds(planned, delaySeconds ?? 0);
|
|
16
|
+
|
|
17
|
+
const line = lineConverter.toDto({
|
|
18
|
+
directionId: gti.directionId, // for some reason the directionId is not in the line object for this request...
|
|
19
|
+
...gti.line,
|
|
20
|
+
});
|
|
21
|
+
if (!isLinePublicTransport(line)) {
|
|
22
|
+
throw new Error('Invalid line: not public transport');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
line,
|
|
27
|
+
departure: {
|
|
28
|
+
planned,
|
|
29
|
+
actual,
|
|
30
|
+
delaySeconds,
|
|
31
|
+
},
|
|
32
|
+
departurePlatformPlanned: gti.platform ?? null,
|
|
33
|
+
departurePlatformActual: gti.realtimePlatform ?? null,
|
|
34
|
+
attributes: (gti.attributes ?? []).map(att =>
|
|
35
|
+
attributeConverter.toDto(att),
|
|
36
|
+
),
|
|
37
|
+
isExtra: gti.extra ?? false,
|
|
38
|
+
isCancelled,
|
|
39
|
+
uniqueServiceId: gti.serviceId,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|