UNPKG

hk-bus-eta

Version:

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

141 lines 5.88 kB
import { formatInTimeZone } from "date-fns-tz"; const __HK_BUS_ETA_JT_CACHE__ = {}; async function fetchEstJourneyTimeBasedOnHistoricalData({ route, startSeq, endSeq, }) { 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(formatInTimeZone(new Date(), "Asia/Hong_Kong", "i"), 10) - 1; const hour = 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)); } async function fetchEstJourneyTimeBasedOnHistoricalAvgData({ route, startSeq, endSeq, }) { 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 */ export async function fetchEstJourneyTime({ route, stopList, startSeq, endSeq, batchSize = 4, signal, }) { 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 = await 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; } //# sourceMappingURL=journeyTime.js.map