-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
375 lines (346 loc) · 14.6 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
const Haltestellen = require("./src/haltestellen");
const Abfahrten = require("./src/abfahrten");
const Fahrten = require("./src/fahrten");
const WebProcessor = require("./src/web_processor");
const routen = require("./src/routen");
const mapandroute = require("./src/mapandroute");
const { Fuhrpark_Bus, Fuhrpark_Tram, Fuhrpark_PVU, Steighoehen_Tram, StopInfo_Tram, StopInfo_Ubahn } = require("./static");
const Fuhrpark_Total = {...Fuhrpark_Bus, ...Fuhrpark_Tram, ...Fuhrpark_PVU};
const allowed_apiparameter = {
Departures: ["product", "timespan", "timedelay", "limitcount"],
Stops: ["name", "lon", "lat", "distance"],
Trips: ["timespan"],
Locations: ["name"]
};
class openvgn {
/**
* Initialize VGN API
* @param {string} [api_url] Open Data VAG API URL
* @param {string} [vag_url] EFA API
*/
constructor(api_url, vag_url) {
this.api_url = api_url || "https://start.vag.de/dm/api";
this.vag_url = vag_url || "https://efa-gateway.vag.de";
this.map_and_route_url = "https://iw.mapandroute.de/MapAPI-1.4/servlet/FrontController";
this.vag_livemap_url = "https://livemap.vag.de"; // VAG often forgets to update SSL for couple of weeks, be warned (Downtime about 5 Weeks a year (2022 & 2023)) - Also IPv4 only
};
/**
* This will replacae ä,ö,ü with the correct url encoding.
* @param {String} value String to be encoded
* @returns {String} Encoded String
*/
#urlReformat(value) {
value = value.replace(/ä/g, "%C3%A4");
value = value.replace(/Ä/g, "%C3%84");
value = value.replace(/ö/g, "%C3%B6");
value = value.replace(/Ö/g, "%C3%96");
value = value.replace(/ü/g, "%C3%BC");
value = value.replace(/Ü/g, "%C3%9C");
value = value.replace(/ß/g, "%C3%9F");
return value;
};
/**
* This will encode the given parameter to a query string.
* @param {String[]} data
* @param {String} endpoint Departures, Stops, Trips
* @returns {String} Encoded String
*/
#encodeQueryData(data, endpoint) {
const ret = [];
for (let d in data) {
if (allowed_apiparameter[endpoint].includes(encodeURIComponent(d).toLowerCase())) {
ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
};
};
return ret.join("&");
};
#XYtoWGS84(ll) {
//Constats for convertion
const D2R = Math.PI / 180;
const A = 6378137.0;
const MAXEXTENT = 20037508.342789244;
const xy = [
A * ll[0] * D2R,
A * Math.log(Math.tan((Math.PI * 0.25) + (0.5 * ll[1] * D2R)))
];
// if xy value is beyond maxextent (e.g. poles), return maxextent.
(xy[0] > MAXEXTENT) && (xy[0] = MAXEXTENT);
(xy[0] < -MAXEXTENT) && (xy[0] = -MAXEXTENT);
(xy[1] > MAXEXTENT) && (xy[1] = MAXEXTENT);
(xy[1] < -MAXEXTENT) && (xy[1] = -MAXEXTENT);
return xy;
};
#WGS84toXY(xy) {
// Constants for conversion
const D2R = Math.PI / 180;
const R2D = 180 / Math.PI;
const A = 6378137.0;
// Inverse transformations:
// λ=x/(A⋅D2R)
// ϕ=2⋅tan^−1(e*(y/A))−π/2
const lon = xy[0] / (A * D2R);
const lat = (Math.atan(Math.exp(xy[1] / A)) * 2 - Math.PI / 2) * R2D;
return [lon, lat];
}
/**
* Transform coordinates into a string that can be used for routes.
* @param {Number} lat
* @param {Number} lon
* @returns
*/
getCordString(lat, lon) {
if (!lat || !lon) { return new Error("getDeparturesbygps: Coordinates can´t be empty.") }
return `${lat}:${lon}:WGS84[DD.DDDDD]`
}
/**
* Fetches the vehicle data from the merged object based on the given ID.
* @param {number} id - The ID of the vehicle to fetch data for.
* @returns {object} The data associated with the vehicle ID, or an empty object if not found.
*/
getVehicleDataById = (id) => {
return Fuhrpark_Total[id] || {};
}
/**
* This will get you all known data about a stop.
* @param {String} target Stop name
* @param {Object} parameter Quary parameter
* @param {Number} [parameter.limit] Max amount of stops returned
*/
getStops(target, parameter) {
if (!target) { return new Error("getDepartures: Target can´t be empty.") }
const url = `${this.api_url}/haltestellen.json/vgn?name=${this.#urlReformat(target.trim())}`;
return Haltestellen.getStops(url, parameter, { Steighoehen_Tram, StopInfo_Tram, StopInfo_Ubahn }).then(function (Haltestellen) {
return Haltestellen
}).catch(function (err) {
return err;
});
};
/**
* This will list all stops in a given radius.
* @param {String} lat GPS Lat
* @param {String} lon GPS Lon
* @param {Object} parameter Quary parameter
* @param {Number} [parameter.limit] Max amount of stops returned
* @param {Number} [parameter.distance] Max distance to given GPS Position
* @param {String} [parameter.sort] Sort your stops by distance or alphabetically
*/
getStopsbygps(lat, lon, parameter) {
if (!lat || !lon) { return new Error("getDeparturesbygps: Coordinates can´t be empty.") }
if (!parameter.distance) {
parameter.distance = 500;
};
if (!parameter.sort) {
parameter.sort = "Distance";
};
const url = `${this.api_url}/haltestellen.json/vgn?lon=${lon}&lat=${lat}&Distance=${parameter.distance}`;
return Haltestellen.getStopsbygps(url, lat, lon, parameter, { Steighoehen_Tram, StopInfo_Tram, StopInfo_Ubahn }).then(function (Haltestellen) {
return Haltestellen;
}).catch(function (err) {
return err;
});
};
/**
* This will list all departures from a given stop.
* @param {String} target Stop ID or VAG haltid
* @param {Object} parameter Quary parameter
* @param {String} [parameter.Line] Only return departures of one line
* @param {String} [parameter.Product] Only return departures of one or multiple products
* @param {Number} [parameter.TimeSpan] Return departures until that time
* @param {Number} [parameter.TimeDelay] Look for now + x in minutes
* @param {Number} [parameter.LimitCount] Max amount of departures returned
*/
getDepartures(target, parameter) {
if (!target) { return new Error("getDepartures: Target can´t be empty.") }
let source = "vgn";
if (isNaN(target)) {
source = "vag";
};
let url = `${this.api_url}/abfahrten.json/${source}/${target}`;
if(parameter.Line) url = `${url}/${parameter.Line}`;
if (parameter) {
url = `${url}?${this.#encodeQueryData(parameter, "Departures")}`;
};
return Abfahrten.getDepartures(url, { Fuhrpark_Tram, Fuhrpark_Bus }).then(function (Abfahrten) {
return Abfahrten;
}).catch(function (err) {
return err;
});
};
/**
* This will list all stops with departure data in a given radius.
* @param {Number} lat GPS Lat
* @param {Number} lon GPS Lon
* @param {Object} parameter Quary parameter
* @param {Number} [parameter.limit] Max amount of stops returned
* @param {Number} [parameter.distance] Max distance to given GPS Position
* @param {String} [parameter.sort] Sort your stops by distance or alphabetically
* @param {String} [parameter.Product] Only return departures of one or multiple products
* @param {Number} [parameter.TimeSpan] Return departures until that time
* @param {Number} [parameter.TimeDelay] Look for now + x in minutes
* @param {Number} [parameter.LimitCount] Max amount of departures returned
*/
getDeparturesbygps(lat, lon, parameter) {
if (!lat || !lon) { return new Error("getDeparturesbygps: Coordinates can´t be empty.") }
if (!parameter.distance) {
parameter.distance = 500;
};
if (!parameter.sort) {
parameter.sort = "Distance";
};
const url = `${this.api_url}/haltestellen.json/vgn?lon=${lon}&lat=${lat}&Distance=${parameter.distance}`;
return Abfahrten.getDeparturesbygps(url, lat, lon, parameter, this.api_url, this.#encodeQueryData, { Fuhrpark_Tram, Fuhrpark_Bus, Fuhrpark_PVU }).then(function (Abfahrten) {
return Abfahrten;
}).catch(function (err) {
return err;
});
};
/**
* This will display all stations that the product has and will pass from start to finish.
* You can get the number (Fahrtnummer) from a getDepartures call.
* @param {Number} Fahrtnummer Fahrtnummer
* @param {Object} parameter parameter
* @param {String} [parameter.Product] Only return departures of one or multiple products
*/
getTrip(Fahrtnummer, parameter) {
if (!Fahrtnummer) { return new Error("getTrip: Fahrtnummer can´t be empty.") }
let url;
if (parameter.date) {
url = `${this.api_url}/fahrten.json/${parameter.product}/${parameter.date}/${Fahrtnummer}`;
} else {
url = `${this.api_url}/fahrten.json/${parameter.product}/${Fahrtnummer}`;
};
return Fahrten.getTrips(url).then(function (Fahrten) {
return Fahrten;
}).catch(function (err) {
return err;
});
};
/**
* This will list all trips of the given product (Ubahn, Tram, Bus) in a given timespan.
* @param {Number} product Fahrtnummer
* @param {Object} parameter Quary parameter
* @param {Number} [parameter.TimeSpan] Return departures until that time
*/
getTrips(product, parameter) {
if (!product) { return new Error("getTrips: Product can´t be empty.") }
let url = `${this.api_url}/fahrten.json/${product}`;
if (parameter) {
url = `${url}?${this.#encodeQueryData(parameter, "Trips")}`;
};
return Fahrten.getTrips(url).then(function (Fahrten) {
return Fahrten;
}).catch(function (err) {
return err;
});
};
/**
* Function to scrape the VAG Webpage to return all ongoing delays, elevator outages and planned events as a object
* @param {String} [test] You can give it a old HTML file to parse, if nothing is passed it will scrape the VAG Webpage
* @returns Object
*/
getVagWebpageDisturbances(test) {
return WebProcessor.getVagWebpageDisturbances(test).then(function (Oobject) {
return Oobject;
}).catch(function (err) {
return err;
});
};
getLocations(name) {
if (!name) { return new Error("getLocations: Name can´t be empty.") }
const url = `${this.vag_url}/api/v1/locations?name=${name}`
return routen.getLocations(url).then(function (locations) {
return locations;
}).catch(function (err) {
return err;
});
}
/**
* Will convert a given GPS location to a Adress
* @param {Number} lat GPS Lat
* @param {Number} lon GPS Lon
*/
reverseGeocode(lat, lon) {
if (!lat || !lon) { return new Error("reverseGeocode: Coordinates can´t be empty.") }
const xy = this.#XYtoWGS84([lon, lat]);
const url = `${this.map_and_route_url}?cmd=reverseGeocode&VNR=0&PNR=0&country=EU&x=${xy[0]}&y=${xy[1]}&hits=1`;
return mapandroute.reverseGeocode(url).then(function (locations) {
return locations;
}).catch(function (err) {
return err;
});
}
/**
* Will reutrn the exact geomoetry of a given line in lat and lon
* @param {String} line
* @returns
*/
geoLines(line) {
const possibleLines = [ "4", "5", "6", "7", "8", "10", "11", "U1", "U2", "U3" ]
if (!line) { return new Error("geoLines: Line can´t be empty.") }
if (!possibleLines.includes(line)) { return new Error("geoLines: Line not found.") }
const url = `${this.vag_livemap_url}/lines.xhr?${new Date().getTime()}`;
return mapandroute.geoLines(url, line).then(cords => {
for (let i = 0; i < cords.Cords.length; i++) {
cords.Cords[i] = this.#WGS84toXY(cords.Cords[i]);
}
return cords;
}).catch(function (err) {
return err;
});
}
/**
* Will calculate the time it takes to get from A to B from a getTrip response
* Uses Expected (SOLL) data
* @param {Object} trip getTrip response
* @param {Number|String} start Start Station
* @param {Number|String} end End Station
* @returns {Number} Time in seconds
*/
calculateTripTime(trip, start, end) {
if (!trip) { return new Error("calculateTripTime: Trip can´t be empty.") }
let start_time, end_time;
trip.Fahrt.Fahrtverlauf.forEach(function (stop) {
if (stop.VAGKennung === start || stop.VGNKennung == start) {
start_time = new Date(stop.AbfahrtszeitSoll || stop.AnkunftszeitSoll).getTime();
};
if (stop.VAGKennung === end || stop.VGNKennung == end) {
end_time = new Date(stop.AnkunftszeitSoll || stop.AbfahrtszeitSoll).getTime();
};
});
const time = end_time - start_time;
if (time.isNaN) {
return new Error("calculateTripTime: Start or End Station not found.");
} else {
return time / 1000;
}
}
/**
* Will calculate the time it actualy took to get from A to B from a getTrip response
* Uses Actual (IST) data
* @param {Object} trip getTrip response
* @param {Number|String} start Start Station
* @param {Number|String} end End Station
* @returns {Number} Time in seconds
*/
calculateActualTripTime(trip, start, end) {
if (!trip) { return new Error("calculateTripTime: Trip can´t be empty.") }
let start_time, end_time;
trip.Fahrt.Fahrtverlauf.forEach(function (stop) {
if (stop.VAGKennung === start || stop.VGNKennung == start) {
start_time = new Date(stop.AbfahrtszeitIst || stop.AnkunftszeitIst).getTime();
};
if (stop.VAGKennung === end || stop.VGNKennung == end) {
end_time = new Date(stop.AnkunftszeitIst || stop.AbfahrtszeitIst).getTime();
};
});
const time = end_time - start_time;
if (time.isNaN) {
return new Error("calculateTripTime: Start or End Station not found.");
} else {
return time / 1000;
}
}
};
module.exports = {
openvgn
};