hk-bus-eta
Version:
Query the ETA (Estimated Time of Arrival) of HK Bus/Minibus/MTR/Lightrail
143 lines • 5.87 kB
JavaScript
import { formatInTimeZone } from "date-fns-tz";
const __HK_BUS_ETA_JT_CACHE__ = {};
async function fetchEstJourneyTimeBasedOnHistoricalData({ route, startSeq, endSeq, }) {
const requests = [];
const stops = Object.values(route.stops)[0];
for (let i = startSeq; i < endSeq; ++i) {
const start = stops[i];
const end = stops[i + 1];
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 = [];
const stops = Object.values(route.stops)[0];
for (let i = startSeq; i < endSeq; ++i) {
const start = stops[i];
const end = stops[i + 1];
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