Skip to content

Commit

Permalink
Group ETA on same routes with same ETA times (remote duplicate ones) (#…
Browse files Browse the repository at this point in the history
…194)

* reduce duplicate ETA for routes with special entry

* add fare to hash

* revert using hash, new method to filter duplicate eta by trying to best match current available route after valid eta is returned

* use score-based heuristics to find best routes when multiple routes with same route no and same eta is returned

* add more comments explaining code

---------

Co-authored-by: Keith Cheng <[email protected]>
  • Loading branch information
chengkeith and chengkeith authored Aug 19, 2024
1 parent 8c07449 commit 3b0955e
Showing 1 changed file with 74 additions and 1 deletion.
75 changes: 74 additions & 1 deletion src/hooks/useStopEtas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,87 @@ export const useStopEtas = ({
)
).then((_etas) => {
if (isMounted.current) {
const tempEtas : [string[], Eta[]][] = []; // [routeKey array, Etas array]
setStopEtas(
_etas
.map((e, idx): [string, Eta[]] => [
routeKeys[idx].join("/"),
e.filter(({ co, dest }) => {
if (co !== "mtr") return true;
return dest.zh === routeList[routeKeys[idx][0]].dest.zh;
// return dest.zh === routeList[routeKeys[idx][0]].dest.zh; // checking just dest (Lo Wu vs Lok Ma Chau) is not enough
return routeList[routeKeys[idx][0]].stops[co].map(stopCode => stopList[stopCode]?.name.zh).includes(dest.zh);
// Note: some trains terinate mid-way along the line (e.g. Some TCL train terminate at Tsing Yi, some EAL trains terminate at Sha Tin/Tai Po Market)
// Hence we need to check stop list of whole route. This solves Tsing Yi ETA in TCL, but still does not solve North Point ETA at LOHAS Park in TKL (as the stop is not even in route's stopList)
}),
])
// try to remove duplicate routes
.reduce((acc, [_routeKey, _etas]) => {
if(_etas.length == 0) {
// no eta, add anyway
acc.push([_routeKey, _etas]);
} else {
// append routeKey to temp if same route no by same company
// otherwise add new record to temp
const tempEta = tempEtas.find(_tempEta => {
try {
const tempEtaTimes = _tempEta[1].map(eta => eta.eta).join('|');
const thisEtaTimes = _etas.map(eta => eta.eta).join('|');
const tempEtaRouteId = _tempEta[0][0].split("/")[0];
const thisEtaRouteId = _routeKey.split("/")[0];
const tempEtaRouteNo = routeList[tempEtaRouteId].route;
const thisEtaRouteNo = routeList[thisEtaRouteId].route;
const tempEtaCompanyList = routeList[tempEtaRouteId].co;
const thisEtaCompany = _etas[0].co;
return tempEtaTimes === thisEtaTimes && tempEtaCompanyList.includes(thisEtaCompany) && tempEtaRouteNo === thisEtaRouteNo;
} catch (e) {
return false; // just let it die;
}
});
if(tempEta !== undefined) {
tempEta[0].push(_routeKey);
} else {
tempEtas.push([[_routeKey], _etas]);
}
// just add to temp first, do not accumulate into acc yet
}
return acc;
}, [] as [string, Eta[]][])
.concat(
// among the routes with same Etas and same routeNo, find the best routeKey
tempEtas.map(([routeKeys, etas]) : [string, Eta[]] => {
if(routeKeys.length == 1) {
// if only one route with this eta list, return it straight away
return [routeKeys[0], etas];
} else {
// find the best route using score-based heuristics (the smaller the score, the better)
const routeScores = routeKeys.map((routeKey) : [string, number] => {
const [_routeId, ] = routeKey.split("/");
const _freq = routeList[_routeId]?.freq ?? null;
const _fares = routeList[_routeId]?.fares ?? null;
const _serviceType = Number(routeList[_routeId]?.serviceType ?? "16");
const _bounds = Object.entries(routeList[_routeId]?.bound).map(([, bound]) => bound);
const _available = isRouteAvaliable(_routeId, _freq, isTodayHoliday, serviceDayMap);
let routeScore = 0;
routeScore += (_available ? 0 : 256); // route not available, add 256
routeScore += (_freq !== null ? 0 : 128); // no freq table, add 128
routeScore += (_fares !== null ? 0 : 128); // no fares table, add 128
routeScore += (_bounds.includes("IO") || _bounds.includes("OI") ? 0 : 32); // if bounds is one-way, add 32 - this make the heuristics to prefer circular route
routeScore += _serviceType; // add serviceType, normal route have lower serviceType (preferred), while special routes have higher values
return [routeKey, routeScore];
});
// choose the routeKey with lowest score
const [bestRouteKey, ] = routeScores.reduce(([bestRouteKey, minScore], [currentRouteKey, currentScore]) => {
if(currentScore < minScore) {
bestRouteKey = currentRouteKey;
minScore = currentScore;
}
return [bestRouteKey, minScore];
}, ["", 999999]);
return [bestRouteKey, etas];
}
})
)
// sort to display the earliest arrival transport first
.sort(([keyA, a], [keyB, b]) => {
if (a.length === 0) return 1;
if (b.length === 0) return -1;
Expand All @@ -120,6 +192,7 @@ export const useStopEtas = ({
routeKeys,
isLightRail,
holidays,
isTodayHoliday,
serviceDayMap,
]);

Expand Down

0 comments on commit 3b0955e

Please sign in to comment.