UNPKG

hk-bus-eta

Version:

Query the ETA (Estimated Time of Arrival) of HK Bus/Minibus/MTR/Lightrail

160 lines 7.44 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.fetchEstJourneyTime = void 0; const date_fns_tz_1 = require("date-fns-tz"); const __HK_BUS_ETA_JT_CACHE__ = {}; function fetchEstJourneyTimeBasedOnHistoricalData({ route, startSeq, endSeq, }) { return __awaiter(this, void 0, void 0, function* () { const requests = []; for (let i = startSeq; i < endSeq; ++i) { const start = Object.values(route.stops)[0][startSeq]; const end = Object.values(route.stops)[0][endSeq]; const day = parseInt((0, date_fns_tz_1.formatInTimeZone)(new Date(), "Asia/Hong_Kong", "i"), 10) - 1; const hour = (0, date_fns_tz_1.formatInTimeZone)(new Date(), "Asia/Hong_Kong", "HH"); requests.push(fetch(`https://raw.githubusercontent.com/HK-Bus-ETA/hk-bus-time-between-stops/refs/heads/pages/times_hourly/${day}/${hour}/${start.slice(0, 2)}.json`) .then((r) => r.json()) .then((r) => { if (r[start][end]) { return r[start][end]; } throw new Error("not found"); })); } return Promise.all(requests).then((seconds) => Math.ceil(seconds.reduce((acc, cur) => acc + cur, 0) / 60)); }); } function fetchEstJourneyTimeBasedOnHistoricalAvgData({ route, startSeq, endSeq, }) { return __awaiter(this, void 0, void 0, function* () { const requests = []; for (let i = startSeq; i < endSeq; ++i) { const start = Object.values(route.stops)[0][startSeq]; const end = Object.values(route.stops)[0][endSeq]; requests.push(fetch(`https://raw.githubusercontent.com/HK-Bus-ETA/hk-bus-time-between-stops/refs/heads/pages/times/${start.slice(0, 2)}.json`) .then((r) => r.json()) .then((r) => { if (r[start][end]) { return r[start][end]; } throw new Error("not found"); })); } return Promise.all(requests).then((seconds) => Math.ceil(seconds.reduce((acc, cur) => acc + cur, 0) / 60)); }); } /** * Fetch Journey time in minute for a route * @param {Object} * @returns {number} journey time in minute */ function fetchEstJourneyTime({ route, stopList, startSeq, endSeq, batchSize = 4, signal, }) { return __awaiter(this, void 0, void 0, function* () { const stops = Object.values(route.stops)[0]; if (!["kmb", "nlb", "ctb", "lrtfeeder", "gmb"].includes(Object.keys(route.stops)[0])) throw new Error("Support vehicle transport only"); if (startSeq < 0) throw new Error("startSeq should be ≥ 0"); if (endSeq >= stops.length) throw new Error("endSeq should be < number of stops"); if (startSeq >= endSeq) throw new Error("startSeq should be < endSeq"); let ts = Date.now(); let ret = 0; let payloads = []; for (let i = startSeq; i < endSeq; ++i) { payloads.push([ `${stops[i]}-${stops[i + 1]}`, JSON.stringify({ start: { lat: stopList[stops[i]].location.lat, long: stopList[stops[i]].location.lng, }, end: { lat: stopList[stops[i + 1]].location.lat, long: stopList[stops[i + 1]].location.lng, }, departIn: Math.round(ret / 15) * 15, }), { startSeq: i, endSeq: i + 1, }, ]); if (payloads.length < batchSize && i !== endSeq - 1) { // skip fetching until whole batch filled continue; } const seconds = yield Promise.all(payloads.map(([key, payload, { startSeq, endSeq }]) => { // load from cache if it is query within 15 minutes if (key in __HK_BUS_ETA_JT_CACHE__ && __HK_BUS_ETA_JT_CACHE__[key].ts + 15 * 60 * 1000 >= ts) { return Promise.resolve(__HK_BUS_ETA_JT_CACHE__[key].s); } return fetchEstJourneyTimeBasedOnHistoricalData({ route, startSeq, endSeq, }) .catch(() => fetchEstJourneyTimeBasedOnHistoricalAvgData({ route, startSeq, endSeq, })) .catch(() => fetch("https://tdas-api.hkemobility.gov.hk/tdas/api/route", { method: "POST", headers: { "Content-Type": "application/json", }, body: payload, signal, }) .then((r) => r.json()) .then(({ eta, distM, jSpeed }) => { var _a; const speedStr = (_a = /(\d+)公里\/小時/g.exec(jSpeed)) === null || _a === void 0 ? void 0 : _a[1]; if (speedStr !== null && speedStr !== undefined && !isNaN(parseInt(speedStr, 10))) { // Set the speed limit as 70 km/h return ((distM / Math.min(parseInt(speedStr, 10), 70) / 1000) * 60); } console.warn("Unable to parse TDAS response for more precise journey time. Falling back."); const [hh, mm] = eta .split(":") .map((v) => parseInt(v, 10)); return hh * 60 + mm; }) .catch(() => { // for any error, assume 4 minutes journey time blindly return 4; }) .then((s) => { __HK_BUS_ETA_JT_CACHE__[key] = { s: s * 1.1, ts }; // margin for car accelerating, decelerating, and passenger picking up and dropping off return s * 1.1; })); })); seconds.forEach((s) => { ret += s; }); payloads.length = 0; } for (const k in __HK_BUS_ETA_JT_CACHE__) { // clean up cache if (__HK_BUS_ETA_JT_CACHE__[k].ts + 15 * 60 * 1000 < ts) { delete __HK_BUS_ETA_JT_CACHE__[k]; } } return ret; }); } exports.fetchEstJourneyTime = fetchEstJourneyTime; //# sourceMappingURL=journeyTime.js.map