From ebfea946e40312b7a61bc355389d61c7dce2ea6a Mon Sep 17 00:00:00 2001
From: Keith Cheng <15365495+chengkeith@users.noreply.github.com>
Date: Thu, 4 Jul 2024 19:57:11 +0800
Subject: [PATCH 1/7] Add Stop Grouping
---
.../bookmarked-stop/StopRouteList.tsx | 9 +-
.../bookmarked-stop/SwipeableStopList.tsx | 1 +
src/components/route-eta/StopDialog.tsx | 7 +-
src/hooks/useStopGroup.tsx | 202 ++++++++++++++++++
src/pages/RouteEta.tsx | 1 +
src/utils.ts | 13 ++
6 files changed, 227 insertions(+), 6 deletions(-)
create mode 100644 src/hooks/useStopGroup.tsx
diff --git a/src/components/bookmarked-stop/StopRouteList.tsx b/src/components/bookmarked-stop/StopRouteList.tsx
index 6f0f0957fd10..023be0d916ac 100644
--- a/src/components/bookmarked-stop/StopRouteList.tsx
+++ b/src/components/bookmarked-stop/StopRouteList.tsx
@@ -1,15 +1,18 @@
import { Box, CircularProgress, List, SxProps, Theme } from "@mui/material";
import SuccinctTimeReport from "../home/SuccinctTimeReport";
+import { useStopGroup } from "../../hooks/useStopGroup";
import { useStopEtas } from "../../hooks/useStopEtas";
-import { Company } from "hk-bus-eta";
+import { Company, RouteListEntry } from "hk-bus-eta";
interface StopRouteListProps {
stops: Array<[Company, string]>; // [[co, stopId]]
+ routeId : string | undefined;
isFocus: boolean;
}
-const StopRouteList = ({ stops, isFocus }: StopRouteListProps) => {
- const stopEtas = useStopEtas({ stopKeys: stops, disabled: !isFocus });
+const StopRouteList = ({ stops, routeId = undefined, isFocus }: StopRouteListProps) => {
+ const stopGroup = useStopGroup({ stopKeys: stops, routeId : routeId });
+ const stopEtas = useStopEtas({ stopKeys: stopGroup, disabled: !isFocus });
if (stopEtas.length === 0) {
return (
diff --git a/src/components/bookmarked-stop/SwipeableStopList.tsx b/src/components/bookmarked-stop/SwipeableStopList.tsx
index d40bd8298c2e..6d82abc54ac0 100644
--- a/src/components/bookmarked-stop/SwipeableStopList.tsx
+++ b/src/components/bookmarked-stop/SwipeableStopList.tsx
@@ -72,6 +72,7 @@ const SwipeableStopList = React.forwardRef<
))}
diff --git a/src/components/route-eta/StopDialog.tsx b/src/components/route-eta/StopDialog.tsx
index 5ac165c7d6cf..50c11ad078ca 100644
--- a/src/components/route-eta/StopDialog.tsx
+++ b/src/components/route-eta/StopDialog.tsx
@@ -16,18 +16,19 @@ import {
} from "@mui/material";
import { useCallback, useContext, useMemo } from "react";
import StopRouteList from "../bookmarked-stop/StopRouteList";
-import { Company } from "hk-bus-eta";
+import { Company, RouteListEntry } from "hk-bus-eta";
import useLanguage from "../../hooks/useTranslation";
import DbContext from "../../context/DbContext";
import CollectionContext from "../../CollectionContext";
interface StopDialogProps {
open: boolean;
+ routeId : string;
stops: Array<[Company, string]>;
onClose: () => void;
}
-const StopDialog = ({ open, stops, onClose }: StopDialogProps) => {
+const StopDialog = ({ open, routeId, stops, onClose }: StopDialogProps) => {
const {
db: { stopList },
} = useContext(DbContext);
@@ -83,7 +84,7 @@ const StopDialog = ({ open, stops, onClose }: StopDialogProps) => {
-
+
);
diff --git a/src/hooks/useStopGroup.tsx b/src/hooks/useStopGroup.tsx
new file mode 100644
index 000000000000..e33c176ff2ec
--- /dev/null
+++ b/src/hooks/useStopGroup.tsx
@@ -0,0 +1,202 @@
+import {
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { Company, Eta, fetchEtas } from "hk-bus-eta";
+import type { RouteListEntry, StopListEntry } from "hk-bus-eta";
+import AppContext from "../context/AppContext";
+import { isRouteAvaliable } from "../timetable";
+import useLanguage from "./useTranslation";
+import DbContext from "../context/DbContext";
+import { getDistance, getBearing } from "../utils";
+
+interface useStopGroupProps {
+ stopKeys: Array<[Company, string]>;
+ routeId : string | undefined;
+}
+interface StopListEntryExtended extends StopListEntry {
+ id : string;
+ distance : number;
+}
+interface RouteKey {
+ routeKey : string;
+ co : Company;
+ seq : number;
+}
+
+// stopKey in format "|", e.g., "lightRail|LR140"
+export const useStopGroup = ({
+ stopKeys, routeId
+}: useStopGroupProps) => {
+ const {
+ db: { routeList, stopList, serviceDayMap },
+ isTodayHoliday,
+ } = useContext(DbContext);
+ const { isRouteFilter } = useContext(AppContext);
+
+ // TODO: put it in AppContext user preference
+ const DISTANCE_THRESHOLD = 50;
+ const BEARING_THRESHOLD = 45;
+ const MAX_STOPS_LIMIT = 50;
+
+ const getDistanceStop = (a : StopListEntry, b : StopListEntry) => {
+ return getDistance(a.location, b.location);
+ };
+ const getBearingStops = (a : StopListEntry, b : StopListEntry) => {
+ return getBearing(a.location, b.location);
+ };
+
+ const findRouteStopBearings = (routeStops : RouteKey[]) => {
+ // routeStop example: {"routeKey":"101+1+KENNEDY TOWN+KWUN TONG (YUE MAN SQUARE)","co":"ctb","seq":12}
+ return routeStops.map(routeStop => {
+ const { routeKey, co, seq } = routeStop;
+ const stopLength = routeList[routeKey].stops[co].length;
+ let stopA, stopB;
+ if(seq == stopLength - 1) { // last stop
+ // stopA = stopList[routeList[routeKey].stops[co][seq - 1]];
+ // stopB = stopList[routeList[routeKey].stops[co][seq]];
+ return -1;
+ } else {
+ stopA = stopList[routeList[routeKey].stops[co][seq]];
+ stopB = stopList[routeList[routeKey].stops[co][seq + 1]];
+ }
+ return getBearingStops(stopA, stopB);
+ }).filter(brng => brng !== -1);
+ }
+
+ const routeStops : RouteKey[]= [];
+ if(routeId !== undefined) {
+ // StopDialog
+ let targetRouteStops = routeList[routeId].stops;
+ stopKeys.forEach(([co, stopId]) => {
+ let seq = targetRouteStops[co].indexOf(stopId);
+ if(seq != -1) {
+ routeStops.push({
+ routeKey : routeId,
+ co : co,
+ seq : seq
+ });
+ }
+ });
+ } else {
+ // SwipableStopList (saved stop list)
+ stopKeys.forEach(([co, stopId]) => {
+ Object.keys(routeList).forEach(routeKey => {
+ let seq = routeList[routeKey].stops[co].indexOf(stopId);
+ if(seq != -1) {
+ routeStops.push({
+ routeKey : routeKey,
+ co : co,
+ seq : seq
+ });
+ }
+ })
+ });
+ }
+ const bearingTargets = findRouteStopBearings(routeStops);
+ const isBearingAccepted = (bearing : number) => {
+ if(BEARING_THRESHOLD >= 180 || bearingTargets.length == 0) {
+ return true;
+ } else {
+ for(let i = 0; i < bearingTargets.length; ++i) {
+ let bearingMin = bearingTargets[i] - BEARING_THRESHOLD;
+ let bearingMax = bearingTargets[i] + BEARING_THRESHOLD;
+ if(bearingMin < 0)
+ bearingMin += 360;
+ if(bearingMax > 360)
+ bearingMax -= 360;
+ if((bearingMin <= bearingMax && bearingMin <= bearing && bearing <= bearingMax)
+ || (bearingMin > bearingMax && (bearingMin <= bearing || bearing <= bearingMax))) // crossing 0/360 degress, eg min=340,max=020
+ return true;
+ }
+ return false;
+ }
+ }
+ const findNearbyStops = (targetId : string, excludeList : string[]) => {
+ let targetStop = stopList[targetId];
+
+ return Object.keys(stopList).filter((stopId) => {
+ // find stops that are within X metres of target stop and along similar direction
+ return getDistanceStop(targetStop, stopList[stopId]) <= DISTANCE_THRESHOLD &&
+ // filter out stops that have not been added into excludeList before
+ !excludeList.includes(stopId);
+ })
+ .reduce( (acc, stopId) => {
+ // get all the routes that has stop with this stopId
+ const rs = Object.entries(routeList).map(([routeKey, routeListEntry]) => {
+ const stops = routeListEntry.stops ?? {};
+ const companies = Object.keys(stops) as Company[];
+ for(let co of companies) {
+ let stopPos = stops[co].indexOf(stopId);
+ if(stopPos > -1)
+ return { routeKey : routeKey, co : co, seq : stopPos } as RouteKey;
+ }
+ return { routeKey : routeKey, co : 'ctb', seq : -1 } as RouteKey; // use ctb as dummy value and seq = -1, will be discarded in next filter
+ })
+ .filter((obj) => obj.seq != -1);
+ // if any of the stops is within acceptable bearing range, add to the list
+ const bearings = findRouteStopBearings(rs);
+ if(bearings.find(b => isBearingAccepted(b)) !== undefined) {
+ const thisStop : StopListEntryExtended = {
+ ...stopList[stopId],
+ id : stopId,
+ distance : 0 // dummy value
+ };
+ acc.push(thisStop);
+ }
+ return acc
+ }, [] as Array);
+ }
+
+ const stopGroup = useMemo>(() => {
+ const stopGroup : Array<[Company, string]> = [];
+ let stopListEntries : StopListEntryExtended[] = [];
+
+ stopKeys.forEach((stopKey) => {
+ const [co, stopId] = stopKey;
+ stopListEntries = stopListEntries.concat(findNearbyStops(stopId, stopListEntries.map((stop) => stop.id)));
+ for(let i = 0; i < stopListEntries.length; ++i) {
+ stopListEntries = stopListEntries.concat(findNearbyStops(stopListEntries[i].id, stopListEntries.map((stop) => stop.id)));
+ if(stopListEntries.length >= MAX_STOPS_LIMIT)
+ break;
+ }
+ });
+
+ // sort by distance from first stop in stopMap (stopKeys[0])
+ if(stopKeys.length > 0) {
+ let [, stopId] = stopKeys[0];
+ stopListEntries = stopListEntries.map(stop => {
+ return {
+ ... stop,
+ distance : getDistanceStop(stopList[stopId], stop)
+ };
+ }).sort((stopA, stopB) => {
+ return stopA.distance - stopB.distance
+ });
+ stopListEntries.forEach((stopListEntry) => {
+ // find co of stop id from routeList
+ let found = false;
+ for(let [,routeListEntry] of Object.entries(routeList)) {
+ const companies = Object.keys(routeListEntry.stops) as Company[];
+ for(let co of companies) {
+ if(routeListEntry.stops[co]?.includes(stopListEntry.id)) {
+ stopGroup.push([co, stopListEntry.id]);
+ found = true;
+ break;
+ }
+ }
+ if(found) {
+ break;
+ }
+ }
+ });
+ }
+ return stopGroup;
+ }, [stopKeys, routeList, stopList, findNearbyStops]);
+
+ return stopGroup;
+};
diff --git a/src/pages/RouteEta.tsx b/src/pages/RouteEta.tsx
index 71ce826999f4..63ecffe6e47f 100644
--- a/src/pages/RouteEta.tsx
+++ b/src/pages/RouteEta.tsx
@@ -253,6 +253,7 @@ const RouteEta = () => {
/>
diff --git a/src/utils.ts b/src/utils.ts
index 3ac44629645c..f2a519c342ee 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -34,6 +34,19 @@ export const getDistanceWithUnit = (distanceInMetre: number) => {
};
};
+export const getBearing = (a: GeoLocation, b: GeoLocation) => {
+ // Reference: https://www.movable-type.co.uk/scripts/latlong.html
+ const φ1 = a.lat * Math.PI / 180; // φ, λ = lat, lon in radians
+ const φ2 = b.lat * Math.PI / 180;
+ const λ1 = a.lng * Math.PI / 180; // φ, λ = lat, lon in radians
+ const λ2 = b.lng * Math.PI / 180;
+ const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
+ const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2-λ1);
+ const θ = Math.atan2(y, x);
+ const brng = (θ * 180 / Math.PI + 360) % 360; // in degrees
+ return brng;
+};
+
export const DEFAULT_GEOLOCATION: GeoLocation = {
lat: 22.302711,
lng: 114.177216,
From 3accae15d7d6114d41fc11b6b0a8dc91e9aa7dad Mon Sep 17 00:00:00 2001
From: Keith Cheng
Date: Fri, 5 Jul 2024 01:26:02 +0800
Subject: [PATCH 2/7] Performance tuning - move findNearByStop inside useMemo
to avoid refresh every render
---
src/hooks/useStopGroup.tsx | 78 +++++++++++++++++++-------------------
1 file changed, 39 insertions(+), 39 deletions(-)
diff --git a/src/hooks/useStopGroup.tsx b/src/hooks/useStopGroup.tsx
index e33c176ff2ec..5826d5749bb8 100644
--- a/src/hooks/useStopGroup.tsx
+++ b/src/hooks/useStopGroup.tsx
@@ -49,7 +49,7 @@ export const useStopGroup = ({
const getBearingStops = (a : StopListEntry, b : StopListEntry) => {
return getBearing(a.location, b.location);
};
-
+
const findRouteStopBearings = (routeStops : RouteKey[]) => {
// routeStop example: {"routeKey":"101+1+KENNEDY TOWN+KWUN TONG (YUE MAN SQUARE)","co":"ctb","seq":12}
return routeStops.map(routeStop => {
@@ -73,7 +73,7 @@ export const useStopGroup = ({
// StopDialog
let targetRouteStops = routeList[routeId].stops;
stopKeys.forEach(([co, stopId]) => {
- let seq = targetRouteStops[co].indexOf(stopId);
+ let seq = targetRouteStops[co]?.indexOf(stopId) ?? -1;
if(seq != -1) {
routeStops.push({
routeKey : routeId,
@@ -86,7 +86,7 @@ export const useStopGroup = ({
// SwipableStopList (saved stop list)
stopKeys.forEach(([co, stopId]) => {
Object.keys(routeList).forEach(routeKey => {
- let seq = routeList[routeKey].stops[co].indexOf(stopId);
+ let seq = routeList[routeKey]?.stops[co]?.indexOf(stopId) ?? -1;
if(seq != -1) {
routeStops.push({
routeKey : routeKey,
@@ -116,45 +116,45 @@ export const useStopGroup = ({
return false;
}
}
- const findNearbyStops = (targetId : string, excludeList : string[]) => {
- let targetStop = stopList[targetId];
-
- return Object.keys(stopList).filter((stopId) => {
- // find stops that are within X metres of target stop and along similar direction
- return getDistanceStop(targetStop, stopList[stopId]) <= DISTANCE_THRESHOLD &&
- // filter out stops that have not been added into excludeList before
- !excludeList.includes(stopId);
- })
- .reduce( (acc, stopId) => {
- // get all the routes that has stop with this stopId
- const rs = Object.entries(routeList).map(([routeKey, routeListEntry]) => {
- const stops = routeListEntry.stops ?? {};
- const companies = Object.keys(stops) as Company[];
- for(let co of companies) {
- let stopPos = stops[co].indexOf(stopId);
- if(stopPos > -1)
- return { routeKey : routeKey, co : co, seq : stopPos } as RouteKey;
- }
- return { routeKey : routeKey, co : 'ctb', seq : -1 } as RouteKey; // use ctb as dummy value and seq = -1, will be discarded in next filter
- })
- .filter((obj) => obj.seq != -1);
- // if any of the stops is within acceptable bearing range, add to the list
- const bearings = findRouteStopBearings(rs);
- if(bearings.find(b => isBearingAccepted(b)) !== undefined) {
- const thisStop : StopListEntryExtended = {
- ...stopList[stopId],
- id : stopId,
- distance : 0 // dummy value
- };
- acc.push(thisStop);
- }
- return acc
- }, [] as Array);
- }
const stopGroup = useMemo>(() => {
const stopGroup : Array<[Company, string]> = [];
let stopListEntries : StopListEntryExtended[] = [];
+ const findNearbyStops = (targetId : string, excludeList : string[]) => {
+ let targetStop = stopList[targetId];
+
+ return Object.keys(stopList).filter((stopId) => {
+ // find stops that are within X metres of target stop and along similar direction
+ return getDistanceStop(targetStop, stopList[stopId]) <= DISTANCE_THRESHOLD &&
+ // filter out stops that have not been added into excludeList before
+ !excludeList.includes(stopId);
+ })
+ .reduce( (acc, stopId) => {
+ // get all the routes that has stop with this stopId
+ const _routeStop = Object.entries(routeList).map(([routeKey, routeListEntry]) => {
+ const stops = routeListEntry.stops ?? {};
+ const companies = Object.keys(stops) as Company[];
+ for(let co of companies) {
+ let stopPos = stops[co]?.indexOf(stopId) ?? -1;
+ if(stopPos > -1)
+ return { routeKey : routeKey, co : co, seq : stopPos } as RouteKey;
+ }
+ return { routeKey : routeKey, co : 'ctb', seq : -1 } as RouteKey; // use ctb as dummy value and seq = -1, will be discarded in next filter
+ })
+ .filter((obj) => obj.seq != -1);
+ // if any of the stops is within acceptable bearing range, add to the list
+ const bearings = findRouteStopBearings(_routeStop);
+ if(bearings.find(b => isBearingAccepted(b)) !== undefined) {
+ const thisStop : StopListEntryExtended = {
+ ...stopList[stopId],
+ id : stopId,
+ distance : 0 // dummy value
+ };
+ acc.push(thisStop);
+ }
+ return acc
+ }, [] as Array);
+ }
stopKeys.forEach((stopKey) => {
const [co, stopId] = stopKey;
@@ -196,7 +196,7 @@ export const useStopGroup = ({
});
}
return stopGroup;
- }, [stopKeys, routeList, stopList, findNearbyStops]);
+ }, [stopKeys, routeList, stopList]);
return stopGroup;
};
From 0037a83627c5f08954b75f33eee28ac1e91c87a1 Mon Sep 17 00:00:00 2001
From: Keith Cheng <15365495+chengkeith@users.noreply.github.com>
Date: Fri, 5 Jul 2024 14:19:56 +0800
Subject: [PATCH 3/7] - Remove unused declarations - Add comments for code
readability - Rename variables and methods with more meaningful names
---
.../bookmarked-stop/StopRouteList.tsx | 2 +-
src/components/route-eta/StopDialog.tsx | 2 +-
src/hooks/useStopGroup.tsx | 115 +++++++++---------
3 files changed, 57 insertions(+), 62 deletions(-)
diff --git a/src/components/bookmarked-stop/StopRouteList.tsx b/src/components/bookmarked-stop/StopRouteList.tsx
index 023be0d916ac..6842e81ed52f 100644
--- a/src/components/bookmarked-stop/StopRouteList.tsx
+++ b/src/components/bookmarked-stop/StopRouteList.tsx
@@ -2,7 +2,7 @@ import { Box, CircularProgress, List, SxProps, Theme } from "@mui/material";
import SuccinctTimeReport from "../home/SuccinctTimeReport";
import { useStopGroup } from "../../hooks/useStopGroup";
import { useStopEtas } from "../../hooks/useStopEtas";
-import { Company, RouteListEntry } from "hk-bus-eta";
+import { Company } from "hk-bus-eta";
interface StopRouteListProps {
stops: Array<[Company, string]>; // [[co, stopId]]
diff --git a/src/components/route-eta/StopDialog.tsx b/src/components/route-eta/StopDialog.tsx
index 50c11ad078ca..37efd40aa293 100644
--- a/src/components/route-eta/StopDialog.tsx
+++ b/src/components/route-eta/StopDialog.tsx
@@ -16,7 +16,7 @@ import {
} from "@mui/material";
import { useCallback, useContext, useMemo } from "react";
import StopRouteList from "../bookmarked-stop/StopRouteList";
-import { Company, RouteListEntry } from "hk-bus-eta";
+import { Company } from "hk-bus-eta";
import useLanguage from "../../hooks/useTranslation";
import DbContext from "../../context/DbContext";
import CollectionContext from "../../CollectionContext";
diff --git a/src/hooks/useStopGroup.tsx b/src/hooks/useStopGroup.tsx
index 5826d5749bb8..f2b7cc25f927 100644
--- a/src/hooks/useStopGroup.tsx
+++ b/src/hooks/useStopGroup.tsx
@@ -1,16 +1,6 @@
-import {
- useCallback,
- useContext,
- useEffect,
- useMemo,
- useRef,
- useState,
-} from "react";
-import { Company, Eta, fetchEtas } from "hk-bus-eta";
-import type { RouteListEntry, StopListEntry } from "hk-bus-eta";
-import AppContext from "../context/AppContext";
-import { isRouteAvaliable } from "../timetable";
-import useLanguage from "./useTranslation";
+import { useContext, useMemo } from "react";
+import { Company } from "hk-bus-eta";
+import type { StopListEntry } from "hk-bus-eta";
import DbContext from "../context/DbContext";
import { getDistance, getBearing } from "../utils";
@@ -21,8 +11,9 @@ interface useStopGroupProps {
interface StopListEntryExtended extends StopListEntry {
id : string;
distance : number;
+ routeStops : RouteStopCoSeq[];
}
-interface RouteKey {
+interface RouteStopCoSeq {
routeKey : string;
co : Company;
seq : number;
@@ -33,15 +24,13 @@ export const useStopGroup = ({
stopKeys, routeId
}: useStopGroupProps) => {
const {
- db: { routeList, stopList, serviceDayMap },
- isTodayHoliday,
+ db: { routeList, stopList },
} = useContext(DbContext);
- const { isRouteFilter } = useContext(AppContext);
- // TODO: put it in AppContext user preference
- const DISTANCE_THRESHOLD = 50;
- const BEARING_THRESHOLD = 45;
- const MAX_STOPS_LIMIT = 50;
+ // TODO: put these constants in AppContext user preference
+ const DISTANCE_THRESHOLD = 50; // in metres, will recursively find stops within this number of metres, so keep it as small as possible. Never choose larger than 300m.
+ const BEARING_THRESHOLD = 45; // in degrees (°), acceptable deviation to the left or right of current bearing
+ const STOP_LIST_LIMIT = 50; // max number of stops in a group, if more than that, the ETA list will be too long and meaningless
const getDistanceStop = (a : StopListEntry, b : StopListEntry) => {
return getDistance(a.location, b.location);
@@ -50,7 +39,7 @@ export const useStopGroup = ({
return getBearing(a.location, b.location);
};
- const findRouteStopBearings = (routeStops : RouteKey[]) => {
+ const getAllRouteStopsBearings = (routeStops : RouteStopCoSeq[]) => {
// routeStop example: {"routeKey":"101+1+KENNEDY TOWN+KWUN TONG (YUE MAN SQUARE)","co":"ctb","seq":12}
return routeStops.map(routeStop => {
const { routeKey, co, seq } = routeStop;
@@ -68,7 +57,7 @@ export const useStopGroup = ({
}).filter(brng => brng !== -1);
}
- const routeStops : RouteKey[]= [];
+ const routeStops : RouteStopCoSeq[]= [];
if(routeId !== undefined) {
// StopDialog
let targetRouteStops = routeList[routeId].stops;
@@ -97,8 +86,8 @@ export const useStopGroup = ({
})
});
}
- const bearingTargets = findRouteStopBearings(routeStops);
- const isBearingAccepted = (bearing : number) => {
+ const bearingTargets = getAllRouteStopsBearings(routeStops);
+ const isBearingInRange = (bearing : number) => {
if(BEARING_THRESHOLD >= 180 || bearingTargets.length == 0) {
return true;
} else {
@@ -110,7 +99,7 @@ export const useStopGroup = ({
if(bearingMax > 360)
bearingMax -= 360;
if((bearingMin <= bearingMax && bearingMin <= bearing && bearing <= bearingMax)
- || (bearingMin > bearingMax && (bearingMin <= bearing || bearing <= bearingMax))) // crossing 0/360 degress, eg min=340,max=020
+ || (bearingMin > bearingMax && (bearingMin <= bearing || bearing <= bearingMax))) // crossing 0/360° mark, eg min=340°,max=020°
return true;
}
return false;
@@ -118,36 +107,37 @@ export const useStopGroup = ({
}
const stopGroup = useMemo>(() => {
- const stopGroup : Array<[Company, string]> = [];
let stopListEntries : StopListEntryExtended[] = [];
- const findNearbyStops = (targetId : string, excludeList : string[]) => {
- let targetStop = stopList[targetId];
+ const searchNearbyStops = (targetStopId : string, excludedStopIdList : string[]) => {
+ const targetStop = stopList[targetStopId];
return Object.keys(stopList).filter((stopId) => {
- // find stops that are within X metres of target stop and along similar direction
+ // find stops that are within DISTANCE_THRESHOLD metres of target stop and along similar direction
return getDistanceStop(targetStop, stopList[stopId]) <= DISTANCE_THRESHOLD &&
// filter out stops that have not been added into excludeList before
- !excludeList.includes(stopId);
+ !excludedStopIdList.includes(stopId);
})
.reduce( (acc, stopId) => {
// get all the routes that has stop with this stopId
- const _routeStop = Object.entries(routeList).map(([routeKey, routeListEntry]) => {
+ const _routeStops = Object.entries(routeList).map(([routeKey, routeListEntry]) => {
const stops = routeListEntry.stops ?? {};
const companies = Object.keys(stops) as Company[];
for(let co of companies) {
let stopPos = stops[co]?.indexOf(stopId) ?? -1;
if(stopPos > -1)
- return { routeKey : routeKey, co : co, seq : stopPos } as RouteKey;
+ return { routeKey : routeKey, co : co, seq : stopPos } as RouteStopCoSeq;
}
- return { routeKey : routeKey, co : 'ctb', seq : -1 } as RouteKey; // use ctb as dummy value and seq = -1, will be discarded in next filter
+ return { routeKey : routeKey, co : 'ctb', seq : -1 } as RouteStopCoSeq; // use ctb as dummy value and seq = -1, will be discarded in next filter
})
- .filter((obj) => obj.seq != -1);
- // if any of the stops is within acceptable bearing range, add to the list
- const bearings = findRouteStopBearings(_routeStop);
- if(bearings.find(b => isBearingAccepted(b)) !== undefined) {
+ .filter((_rs) => _rs.seq != -1);
+ // if any of the routes passing this stop is facing same direction (+/- BEARING_THRESHOLD), add the stop to the list
+ // Note: once the stop is added, other routes not facing same direction but passing this stop will also be shown in ETA (most commonly seen in railway lines)
+ const bearings = getAllRouteStopsBearings(_routeStops);
+ if(bearings.find(b => isBearingInRange(b)) !== undefined) {
const thisStop : StopListEntryExtended = {
...stopList[stopId],
id : stopId,
+ routeStops : _routeStops, // _routeStops.length must be > 0 here, as bearings.length must be > 0 to reach into this if-condition
distance : 0 // dummy value
};
acc.push(thisStop);
@@ -156,19 +146,22 @@ export const useStopGroup = ({
}, [] as Array);
}
+ // recursively search for nearby stops within thresholds (distance and bearing)
+ // stop searching when no new stops are found within range, or when stop list is getting too large
stopKeys.forEach((stopKey) => {
- const [co, stopId] = stopKey;
- stopListEntries = stopListEntries.concat(findNearbyStops(stopId, stopListEntries.map((stop) => stop.id)));
- for(let i = 0; i < stopListEntries.length; ++i) {
- stopListEntries = stopListEntries.concat(findNearbyStops(stopListEntries[i].id, stopListEntries.map((stop) => stop.id)));
- if(stopListEntries.length >= MAX_STOPS_LIMIT)
+ const [, stopId] = stopKey; // [co, stopId]
+ stopListEntries = stopListEntries.concat(searchNearbyStops(stopId, stopListEntries.map((stop) => stop.id)));
+ for(let i = 0; i < stopListEntries.length; ++i) { // use traditional for-loop as the length keeps expanding
+ stopListEntries = stopListEntries.concat(searchNearbyStops(stopListEntries[i].id, stopListEntries.map((stop) => stop.id)));
+ if(stopListEntries.length >= STOP_LIST_LIMIT)
break;
}
});
// sort by distance from first stop in stopMap (stopKeys[0])
+ const _stopGroup : Array<[Company, string]> = [];
if(stopKeys.length > 0) {
- let [, stopId] = stopKeys[0];
+ let [, stopId] = stopKeys[0]; // [co, stopId] but don't use this co
stopListEntries = stopListEntries.map(stop => {
return {
... stop,
@@ -178,24 +171,26 @@ export const useStopGroup = ({
return stopA.distance - stopB.distance
});
stopListEntries.forEach((stopListEntry) => {
- // find co of stop id from routeList
- let found = false;
- for(let [,routeListEntry] of Object.entries(routeList)) {
- const companies = Object.keys(routeListEntry.stops) as Company[];
- for(let co of companies) {
- if(routeListEntry.stops[co]?.includes(stopListEntry.id)) {
- stopGroup.push([co, stopListEntry.id]);
- found = true;
- break;
- }
- }
- if(found) {
- break;
- }
- }
+ if(stopListEntry.routeStops.length > 0)
+ _stopGroup.push([stopListEntry.routeStops[0].co, stopListEntry.id]);
+ // // find co of stop id from routeList
+ // let found = false;
+ // for(let [,routeListEntry] of Object.entries(routeList)) {
+ // const companies = Object.keys(routeListEntry.stops) as Company[];
+ // for(let co of companies) {
+ // if(routeListEntry.stops[co]?.includes(stopListEntry.id)) {
+ // _stopGroup.push([co, stopListEntry.id]);
+ // found = true;
+ // break;
+ // }
+ // }
+ // if(found) {
+ // break;
+ // }
+ // }
});
}
- return stopGroup;
+ return _stopGroup;
}, [stopKeys, routeList, stopList]);
return stopGroup;
From e72f154ccd8016b099fc48de66485e088a5c82d5 Mon Sep 17 00:00:00 2001
From: Keith Cheng <15365495+chengkeith@users.noreply.github.com>
Date: Fri, 5 Jul 2024 14:24:00 +0800
Subject: [PATCH 4/7] remove deprecated code, add more meaningul comments
---
src/hooks/useStopGroup.tsx | 18 +-----------------
1 file changed, 1 insertion(+), 17 deletions(-)
diff --git a/src/hooks/useStopGroup.tsx b/src/hooks/useStopGroup.tsx
index f2b7cc25f927..b79a3b3de9b1 100644
--- a/src/hooks/useStopGroup.tsx
+++ b/src/hooks/useStopGroup.tsx
@@ -46,8 +46,7 @@ export const useStopGroup = ({
const stopLength = routeList[routeKey].stops[co].length;
let stopA, stopB;
if(seq == stopLength - 1) { // last stop
- // stopA = stopList[routeList[routeKey].stops[co][seq - 1]];
- // stopB = stopList[routeList[routeKey].stops[co][seq]];
+ // no next stop, hence no forward bearing, just use -1 as dummy value then discard it later
return -1;
} else {
stopA = stopList[routeList[routeKey].stops[co][seq]];
@@ -173,21 +172,6 @@ export const useStopGroup = ({
stopListEntries.forEach((stopListEntry) => {
if(stopListEntry.routeStops.length > 0)
_stopGroup.push([stopListEntry.routeStops[0].co, stopListEntry.id]);
- // // find co of stop id from routeList
- // let found = false;
- // for(let [,routeListEntry] of Object.entries(routeList)) {
- // const companies = Object.keys(routeListEntry.stops) as Company[];
- // for(let co of companies) {
- // if(routeListEntry.stops[co]?.includes(stopListEntry.id)) {
- // _stopGroup.push([co, stopListEntry.id]);
- // found = true;
- // break;
- // }
- // }
- // if(found) {
- // break;
- // }
- // }
});
}
return _stopGroup;
From 24427df3b2428a5fa10349fa7da2325288ef108c Mon Sep 17 00:00:00 2001
From: Keith Cheng
Date: Tue, 9 Jul 2024 00:52:56 +0800
Subject: [PATCH 5/7] added useCallback to prevent re-render
---
src/hooks/useStopGroup.tsx | 87 +++++++++++++++++++-------------------
1 file changed, 44 insertions(+), 43 deletions(-)
diff --git a/src/hooks/useStopGroup.tsx b/src/hooks/useStopGroup.tsx
index b79a3b3de9b1..d74741e1a727 100644
--- a/src/hooks/useStopGroup.tsx
+++ b/src/hooks/useStopGroup.tsx
@@ -1,4 +1,4 @@
-import { useContext, useMemo } from "react";
+import { useCallback, useContext, useMemo } from "react";
import { Company } from "hk-bus-eta";
import type { StopListEntry } from "hk-bus-eta";
import DbContext from "../context/DbContext";
@@ -39,7 +39,7 @@ export const useStopGroup = ({
return getBearing(a.location, b.location);
};
- const getAllRouteStopsBearings = (routeStops : RouteStopCoSeq[]) => {
+ const getAllRouteStopsBearings = useCallback((routeStops : RouteStopCoSeq[]) => {
// routeStop example: {"routeKey":"101+1+KENNEDY TOWN+KWUN TONG (YUE MAN SQUARE)","co":"ctb","seq":12}
return routeStops.map(routeStop => {
const { routeKey, co, seq } = routeStop;
@@ -54,7 +54,7 @@ export const useStopGroup = ({
}
return getBearingStops(stopA, stopB);
}).filter(brng => brng !== -1);
- }
+ }, [routeList, stopList]);
const routeStops : RouteStopCoSeq[]= [];
if(routeId !== undefined) {
@@ -86,7 +86,7 @@ export const useStopGroup = ({
});
}
const bearingTargets = getAllRouteStopsBearings(routeStops);
- const isBearingInRange = (bearing : number) => {
+ const isBearingInRange = useCallback((bearing : number) => {
if(BEARING_THRESHOLD >= 180 || bearingTargets.length == 0) {
return true;
} else {
@@ -103,47 +103,48 @@ export const useStopGroup = ({
}
return false;
}
- }
+ }, [bearingTargets]);
- const stopGroup = useMemo>(() => {
- let stopListEntries : StopListEntryExtended[] = [];
- const searchNearbyStops = (targetStopId : string, excludedStopIdList : string[]) => {
- const targetStop = stopList[targetStopId];
+ const searchNearbyStops = useCallback((targetStopId : string, excludedStopIdList : string[]) => {
+ const targetStop = stopList[targetStopId];
- return Object.keys(stopList).filter((stopId) => {
- // find stops that are within DISTANCE_THRESHOLD metres of target stop and along similar direction
- return getDistanceStop(targetStop, stopList[stopId]) <= DISTANCE_THRESHOLD &&
- // filter out stops that have not been added into excludeList before
- !excludedStopIdList.includes(stopId);
- })
- .reduce( (acc, stopId) => {
- // get all the routes that has stop with this stopId
- const _routeStops = Object.entries(routeList).map(([routeKey, routeListEntry]) => {
- const stops = routeListEntry.stops ?? {};
- const companies = Object.keys(stops) as Company[];
- for(let co of companies) {
- let stopPos = stops[co]?.indexOf(stopId) ?? -1;
- if(stopPos > -1)
- return { routeKey : routeKey, co : co, seq : stopPos } as RouteStopCoSeq;
- }
- return { routeKey : routeKey, co : 'ctb', seq : -1 } as RouteStopCoSeq; // use ctb as dummy value and seq = -1, will be discarded in next filter
- })
- .filter((_rs) => _rs.seq != -1);
- // if any of the routes passing this stop is facing same direction (+/- BEARING_THRESHOLD), add the stop to the list
- // Note: once the stop is added, other routes not facing same direction but passing this stop will also be shown in ETA (most commonly seen in railway lines)
- const bearings = getAllRouteStopsBearings(_routeStops);
- if(bearings.find(b => isBearingInRange(b)) !== undefined) {
- const thisStop : StopListEntryExtended = {
- ...stopList[stopId],
- id : stopId,
- routeStops : _routeStops, // _routeStops.length must be > 0 here, as bearings.length must be > 0 to reach into this if-condition
- distance : 0 // dummy value
- };
- acc.push(thisStop);
+ return Object.keys(stopList).filter((stopId) => {
+ // find stops that are within DISTANCE_THRESHOLD metres of target stop and along similar direction
+ return getDistanceStop(targetStop, stopList[stopId]) <= DISTANCE_THRESHOLD &&
+ // filter out stops that have not been added into excludeList before
+ !excludedStopIdList.includes(stopId);
+ })
+ .reduce( (acc, stopId) => {
+ // get all the routes that has stop with this stopId
+ const _routeStops = Object.entries(routeList).map(([routeKey, routeListEntry]) => {
+ const stops = routeListEntry.stops ?? {};
+ const companies = Object.keys(stops) as Company[];
+ for(let co of companies) {
+ let stopPos = stops[co]?.indexOf(stopId) ?? -1;
+ if(stopPos > -1)
+ return { routeKey : routeKey, co : co, seq : stopPos } as RouteStopCoSeq;
}
- return acc
- }, [] as Array);
- }
+ return { routeKey : routeKey, co : 'ctb', seq : -1 } as RouteStopCoSeq; // use ctb as dummy value and seq = -1, will be discarded in next filter
+ })
+ .filter((_rs) => _rs.seq != -1);
+ // if any of the routes passing this stop is facing same direction (+/- BEARING_THRESHOLD), add the stop to the list
+ // Note: once the stop is added, other routes not facing same direction but passing this stop will also be shown in ETA (most commonly seen in railway lines)
+ const bearings = getAllRouteStopsBearings(_routeStops);
+ if(bearings.find(b => isBearingInRange(b)) !== undefined) {
+ const thisStop : StopListEntryExtended = {
+ ...stopList[stopId],
+ id : stopId,
+ routeStops : _routeStops, // _routeStops.length must be > 0 here, as bearings.length must be > 0 to reach into this if-condition
+ distance : 0 // dummy value
+ };
+ acc.push(thisStop);
+ }
+ return acc
+ }, [] as Array);
+ }, [routeList, stopList, getAllRouteStopsBearings, isBearingInRange]);
+
+ const stopGroup = useMemo>(() => {
+ let stopListEntries : StopListEntryExtended[] = [];
// recursively search for nearby stops within thresholds (distance and bearing)
// stop searching when no new stops are found within range, or when stop list is getting too large
@@ -175,7 +176,7 @@ export const useStopGroup = ({
});
}
return _stopGroup;
- }, [stopKeys, routeList, stopList]);
+ }, [stopKeys, stopList, searchNearbyStops]);
return stopGroup;
};
From eb5313f9f4147e0cee83ba1f0314f1c7da2f1532 Mon Sep 17 00:00:00 2001
From: Keith Cheng
Date: Tue, 9 Jul 2024 01:09:02 +0800
Subject: [PATCH 6/7] rename getDistanceStop to getDistanceStops add useMemo
for routeStops
---
src/hooks/useStopGroup.tsx | 57 ++++++++++++++++++++------------------
1 file changed, 30 insertions(+), 27 deletions(-)
diff --git a/src/hooks/useStopGroup.tsx b/src/hooks/useStopGroup.tsx
index d74741e1a727..d0911f281a52 100644
--- a/src/hooks/useStopGroup.tsx
+++ b/src/hooks/useStopGroup.tsx
@@ -32,7 +32,7 @@ export const useStopGroup = ({
const BEARING_THRESHOLD = 45; // in degrees (°), acceptable deviation to the left or right of current bearing
const STOP_LIST_LIMIT = 50; // max number of stops in a group, if more than that, the ETA list will be too long and meaningless
- const getDistanceStop = (a : StopListEntry, b : StopListEntry) => {
+ const getDistanceStops = (a : StopListEntry, b : StopListEntry) => {
return getDistance(a.location, b.location);
};
const getBearingStops = (a : StopListEntry, b : StopListEntry) => {
@@ -56,35 +56,38 @@ export const useStopGroup = ({
}).filter(brng => brng !== -1);
}, [routeList, stopList]);
- const routeStops : RouteStopCoSeq[]= [];
- if(routeId !== undefined) {
- // StopDialog
- let targetRouteStops = routeList[routeId].stops;
- stopKeys.forEach(([co, stopId]) => {
- let seq = targetRouteStops[co]?.indexOf(stopId) ?? -1;
- if(seq != -1) {
- routeStops.push({
- routeKey : routeId,
- co : co,
- seq : seq
- });
- }
- });
- } else {
- // SwipableStopList (saved stop list)
- stopKeys.forEach(([co, stopId]) => {
- Object.keys(routeList).forEach(routeKey => {
- let seq = routeList[routeKey]?.stops[co]?.indexOf(stopId) ?? -1;
+ const routeStops : RouteStopCoSeq[]= useMemo(() => {
+ let _routeStops: RouteStopCoSeq[] = [];
+ if(routeId !== undefined) {
+ // StopDialog
+ let targetRouteStops = routeList[routeId].stops;
+ stopKeys.forEach(([co, stopId]) => {
+ let seq = targetRouteStops[co]?.indexOf(stopId) ?? -1;
if(seq != -1) {
- routeStops.push({
- routeKey : routeKey,
+ _routeStops.push({
+ routeKey : routeId,
co : co,
seq : seq
});
}
- })
- });
- }
+ });
+ } else {
+ // SwipableStopList (saved stop list)
+ stopKeys.forEach(([co, stopId]) => {
+ Object.keys(routeList).forEach(routeKey => {
+ let seq = routeList[routeKey]?.stops[co]?.indexOf(stopId) ?? -1;
+ if(seq != -1) {
+ _routeStops.push({
+ routeKey : routeKey,
+ co : co,
+ seq : seq
+ });
+ }
+ })
+ });
+ }
+ return _routeStops;
+ }, [stopKeys, routeId, routeList]);
const bearingTargets = getAllRouteStopsBearings(routeStops);
const isBearingInRange = useCallback((bearing : number) => {
if(BEARING_THRESHOLD >= 180 || bearingTargets.length == 0) {
@@ -110,7 +113,7 @@ export const useStopGroup = ({
return Object.keys(stopList).filter((stopId) => {
// find stops that are within DISTANCE_THRESHOLD metres of target stop and along similar direction
- return getDistanceStop(targetStop, stopList[stopId]) <= DISTANCE_THRESHOLD &&
+ return getDistanceStops(targetStop, stopList[stopId]) <= DISTANCE_THRESHOLD &&
// filter out stops that have not been added into excludeList before
!excludedStopIdList.includes(stopId);
})
@@ -165,7 +168,7 @@ export const useStopGroup = ({
stopListEntries = stopListEntries.map(stop => {
return {
... stop,
- distance : getDistanceStop(stopList[stopId], stop)
+ distance : getDistanceStops(stopList[stopId], stop)
};
}).sort((stopA, stopB) => {
return stopA.distance - stopB.distance
From 4f70eaaeeee55783411d6ba2b1205cd348b5d172 Mon Sep 17 00:00:00 2001
From: Keith Cheng
Date: Thu, 11 Jul 2024 00:57:47 +0800
Subject: [PATCH 7/7] - remove hook usage - rename useStopGroup method as
simple util method getStopGroup - use useMemo to cache result of getStopGroup
to prevent infinite rendering
---
.../bookmarked-stop/StopRouteList.tsx | 10 +-
src/{hooks/useStopGroup.tsx => stopGroup.ts} | 172 +++++++++---------
2 files changed, 89 insertions(+), 93 deletions(-)
rename src/{hooks/useStopGroup.tsx => stopGroup.ts} (50%)
diff --git a/src/components/bookmarked-stop/StopRouteList.tsx b/src/components/bookmarked-stop/StopRouteList.tsx
index 6842e81ed52f..5441dd42bec8 100644
--- a/src/components/bookmarked-stop/StopRouteList.tsx
+++ b/src/components/bookmarked-stop/StopRouteList.tsx
@@ -1,8 +1,10 @@
import { Box, CircularProgress, List, SxProps, Theme } from "@mui/material";
import SuccinctTimeReport from "../home/SuccinctTimeReport";
-import { useStopGroup } from "../../hooks/useStopGroup";
+import { getStopGroup } from "../../stopGroup";
import { useStopEtas } from "../../hooks/useStopEtas";
import { Company } from "hk-bus-eta";
+import DbContext from "../../context/DbContext";
+import { useContext, useMemo } from "react";
interface StopRouteListProps {
stops: Array<[Company, string]>; // [[co, stopId]]
@@ -11,7 +13,11 @@ interface StopRouteListProps {
}
const StopRouteList = ({ stops, routeId = undefined, isFocus }: StopRouteListProps) => {
- const stopGroup = useStopGroup({ stopKeys: stops, routeId : routeId });
+ const { db: { routeList, stopList } } = useContext(DbContext);
+ const stopGroup = useMemo(
+ () => getStopGroup({ routeList, stopList, stopKeys: stops, routeId }),
+ [routeList, stopList, stops, routeId]
+ );
const stopEtas = useStopEtas({ stopKeys: stopGroup, disabled: !isFocus });
if (stopEtas.length === 0) {
diff --git a/src/hooks/useStopGroup.tsx b/src/stopGroup.ts
similarity index 50%
rename from src/hooks/useStopGroup.tsx
rename to src/stopGroup.ts
index d0911f281a52..fc4f5f85464e 100644
--- a/src/hooks/useStopGroup.tsx
+++ b/src/stopGroup.ts
@@ -1,10 +1,10 @@
-import { useCallback, useContext, useMemo } from "react";
import { Company } from "hk-bus-eta";
-import type { StopListEntry } from "hk-bus-eta";
-import DbContext from "../context/DbContext";
-import { getDistance, getBearing } from "../utils";
+import type { RouteList, StopList, StopListEntry } from "hk-bus-eta";
+import { getDistance, getBearing } from "./utils";
interface useStopGroupProps {
+ routeList : RouteList;
+ stopList : StopList;
stopKeys: Array<[Company, string]>;
routeId : string | undefined;
}
@@ -20,12 +20,9 @@ interface RouteStopCoSeq {
}
// stopKey in format "|", e.g., "lightRail|LR140"
-export const useStopGroup = ({
- stopKeys, routeId
+export const getStopGroup = ({
+ routeList, stopList, stopKeys, routeId
}: useStopGroupProps) => {
- const {
- db: { routeList, stopList },
- } = useContext(DbContext);
// TODO: put these constants in AppContext user preference
const DISTANCE_THRESHOLD = 50; // in metres, will recursively find stops within this number of metres, so keep it as small as possible. Never choose larger than 300m.
@@ -39,76 +36,71 @@ export const useStopGroup = ({
return getBearing(a.location, b.location);
};
- const getAllRouteStopsBearings = useCallback((routeStops : RouteStopCoSeq[]) => {
+ const getAllRouteStopsBearings = (routeStops : RouteStopCoSeq[]) => {
// routeStop example: {"routeKey":"101+1+KENNEDY TOWN+KWUN TONG (YUE MAN SQUARE)","co":"ctb","seq":12}
return routeStops.map(routeStop => {
const { routeKey, co, seq } = routeStop;
const stopLength = routeList[routeKey].stops[co].length;
- let stopA, stopB;
if(seq == stopLength - 1) { // last stop
// no next stop, hence no forward bearing, just use -1 as dummy value then discard it later
return -1;
- } else {
- stopA = stopList[routeList[routeKey].stops[co][seq]];
- stopB = stopList[routeList[routeKey].stops[co][seq + 1]];
}
+ const stopA = stopList[routeList[routeKey].stops[co][seq]];
+ const stopB = stopList[routeList[routeKey].stops[co][seq + 1]];
return getBearingStops(stopA, stopB);
}).filter(brng => brng !== -1);
- }, [routeList, stopList]);
+ };
- const routeStops : RouteStopCoSeq[]= useMemo(() => {
- let _routeStops: RouteStopCoSeq[] = [];
- if(routeId !== undefined) {
- // StopDialog
- let targetRouteStops = routeList[routeId].stops;
- stopKeys.forEach(([co, stopId]) => {
- let seq = targetRouteStops[co]?.indexOf(stopId) ?? -1;
+ const routeStops : RouteStopCoSeq[] = [];
+ if(routeId !== undefined) {
+ // StopDialog
+ let targetRouteStops = routeList[routeId].stops;
+ stopKeys.forEach(([co, stopId]) => {
+ let seq = targetRouteStops[co]?.indexOf(stopId) ?? -1;
+ if(seq != -1) {
+ routeStops.push({
+ routeKey : routeId,
+ co : co,
+ seq : seq
+ });
+ }
+ });
+ } else {
+ // SwipableStopList (saved stop list)
+ stopKeys.forEach(([co, stopId]) => {
+ Object.keys(routeList).forEach(routeKey => {
+ let seq = routeList[routeKey]?.stops[co]?.indexOf(stopId) ?? -1;
if(seq != -1) {
- _routeStops.push({
- routeKey : routeId,
+ routeStops.push({
+ routeKey : routeKey,
co : co,
seq : seq
});
}
- });
- } else {
- // SwipableStopList (saved stop list)
- stopKeys.forEach(([co, stopId]) => {
- Object.keys(routeList).forEach(routeKey => {
- let seq = routeList[routeKey]?.stops[co]?.indexOf(stopId) ?? -1;
- if(seq != -1) {
- _routeStops.push({
- routeKey : routeKey,
- co : co,
- seq : seq
- });
- }
- })
- });
- }
- return _routeStops;
- }, [stopKeys, routeId, routeList]);
+ })
+ });
+ }
+
const bearingTargets = getAllRouteStopsBearings(routeStops);
- const isBearingInRange = useCallback((bearing : number) => {
+ const isBearingInRange = (bearing : number) => {
if(BEARING_THRESHOLD >= 180 || bearingTargets.length == 0) {
return true;
- } else {
- for(let i = 0; i < bearingTargets.length; ++i) {
- let bearingMin = bearingTargets[i] - BEARING_THRESHOLD;
- let bearingMax = bearingTargets[i] + BEARING_THRESHOLD;
- if(bearingMin < 0)
- bearingMin += 360;
- if(bearingMax > 360)
- bearingMax -= 360;
- if((bearingMin <= bearingMax && bearingMin <= bearing && bearing <= bearingMax)
- || (bearingMin > bearingMax && (bearingMin <= bearing || bearing <= bearingMax))) // crossing 0/360° mark, eg min=340°,max=020°
- return true;
- }
- return false;
}
- }, [bearingTargets]);
+ for(let i = 0; i < bearingTargets.length; ++i) {
+ let bearingMin = bearingTargets[i] - BEARING_THRESHOLD;
+ let bearingMax = bearingTargets[i] + BEARING_THRESHOLD;
+ if(bearingMin < 0)
+ bearingMin += 360;
+ if(bearingMax > 360)
+ bearingMax -= 360;
+ if((bearingMin <= bearingMax && bearingMin <= bearing && bearing <= bearingMax)
+ || (bearingMin > bearingMax && (bearingMin <= bearing || bearing <= bearingMax))) // crossing 0/360° mark, eg min=340°,max=020°
+ return true;
+ }
+ return false;
+ };
- const searchNearbyStops = useCallback((targetStopId : string, excludedStopIdList : string[]) => {
+ const searchNearbyStops = (targetStopId : string, excludedStopIdList : string[]) => {
const targetStop = stopList[targetStopId];
return Object.keys(stopList).filter((stopId) => {
@@ -144,42 +136,40 @@ export const useStopGroup = ({
}
return acc
}, [] as Array);
- }, [routeList, stopList, getAllRouteStopsBearings, isBearingInRange]);
-
- const stopGroup = useMemo>(() => {
- let stopListEntries : StopListEntryExtended[] = [];
+ };
- // recursively search for nearby stops within thresholds (distance and bearing)
- // stop searching when no new stops are found within range, or when stop list is getting too large
- stopKeys.forEach((stopKey) => {
- const [, stopId] = stopKey; // [co, stopId]
- stopListEntries = stopListEntries.concat(searchNearbyStops(stopId, stopListEntries.map((stop) => stop.id)));
- for(let i = 0; i < stopListEntries.length; ++i) { // use traditional for-loop as the length keeps expanding
- stopListEntries = stopListEntries.concat(searchNearbyStops(stopListEntries[i].id, stopListEntries.map((stop) => stop.id)));
- if(stopListEntries.length >= STOP_LIST_LIMIT)
- break;
- }
- });
+ const stopGroup : Array<[Company, string]> = [];
+
+ let stopListEntries : StopListEntryExtended[] = [];
- // sort by distance from first stop in stopMap (stopKeys[0])
- const _stopGroup : Array<[Company, string]> = [];
- if(stopKeys.length > 0) {
- let [, stopId] = stopKeys[0]; // [co, stopId] but don't use this co
- stopListEntries = stopListEntries.map(stop => {
- return {
- ... stop,
- distance : getDistanceStops(stopList[stopId], stop)
- };
- }).sort((stopA, stopB) => {
- return stopA.distance - stopB.distance
- });
- stopListEntries.forEach((stopListEntry) => {
- if(stopListEntry.routeStops.length > 0)
- _stopGroup.push([stopListEntry.routeStops[0].co, stopListEntry.id]);
- });
+ // recursively search for nearby stops within thresholds (distance and bearing)
+ // stop searching when no new stops are found within range, or when stop list is getting too large
+ stopKeys.forEach((stopKey) => {
+ const [, stopId] = stopKey; // [co, stopId]
+ stopListEntries = stopListEntries.concat(searchNearbyStops(stopId, stopListEntries.map((stop) => stop.id)));
+ for(let i = 0; i < stopListEntries.length; ++i) { // use traditional for-loop as the length keeps expanding
+ stopListEntries = stopListEntries.concat(searchNearbyStops(stopListEntries[i].id, stopListEntries.map((stop) => stop.id)));
+ if(stopListEntries.length >= STOP_LIST_LIMIT)
+ break;
}
- return _stopGroup;
- }, [stopKeys, stopList, searchNearbyStops]);
+ });
+
+ // sort by distance from first stop in stopMap (stopKeys[0])
+ if(stopKeys.length > 0) {
+ let [, stopId] = stopKeys[0]; // [co, stopId] but don't use this co
+ stopListEntries = stopListEntries.map(stop => {
+ return {
+ ... stop,
+ distance : getDistanceStops(stopList[stopId], stop)
+ };
+ }).sort((stopA, stopB) => {
+ return stopA.distance - stopB.distance
+ });
+ stopListEntries.forEach((stopListEntry) => {
+ if(stopListEntry.routeStops.length > 0)
+ stopGroup.push([stopListEntry.routeStops[0].co, stopListEntry.id]);
+ });
+ }
return stopGroup;
};