mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-16 23:40:32 +00:00
378 lines
12 KiB
TypeScript
378 lines
12 KiB
TypeScript
import EventEmitter from "node:events";
|
|
import { ShuttleGetterSetterRepository } from "./ShuttleGetterSetterRepository";
|
|
import { IOrderedStop, IRoute, IShuttle, IStop, shuttleHasArrivedAtStop } from "../../entities/ShuttleRepositoryEntities";
|
|
import { IEntityWithId } from "../../entities/SharedEntities";
|
|
import {
|
|
ShuttleRepositoryEvent,
|
|
ShuttleRepositoryEventListener,
|
|
ShuttleRepositoryEventName,
|
|
ShuttleRepositoryEventPayloads,
|
|
ShuttleStopArrival,
|
|
ShuttleTravelTimeDataIdentifier,
|
|
ShuttleTravelTimeDateFilterArguments,
|
|
} from "./ShuttleGetterRepository";
|
|
|
|
/**
|
|
* An unoptimized in memory repository.
|
|
* (I would optimize it with actual data structures, but I'm
|
|
* switching to another data store later anyways)
|
|
*/
|
|
export class UnoptimizedInMemoryShuttleRepository
|
|
extends EventEmitter
|
|
implements ShuttleGetterSetterRepository {
|
|
|
|
constructor(
|
|
readonly shuttleStopArrivalDegreeDelta: number = 0.001,
|
|
) {
|
|
super()
|
|
}
|
|
|
|
public override on<T extends ShuttleRepositoryEventName>(
|
|
event: T,
|
|
listener: ShuttleRepositoryEventListener<T>,
|
|
): this;
|
|
public override on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
return super.on(event, listener);
|
|
}
|
|
|
|
public override once<T extends ShuttleRepositoryEventName>(
|
|
event: T,
|
|
listener: ShuttleRepositoryEventListener<T>,
|
|
): this;
|
|
public override once(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
return super.once(event, listener);
|
|
}
|
|
|
|
public override off<T extends ShuttleRepositoryEventName>(
|
|
event: T,
|
|
listener: ShuttleRepositoryEventListener<T>,
|
|
): this;
|
|
public override off(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
return super.off(event, listener);
|
|
}
|
|
|
|
public override addListener<T extends ShuttleRepositoryEventName>(
|
|
event: T,
|
|
listener: ShuttleRepositoryEventListener<T>,
|
|
): this;
|
|
public override addListener(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
return super.addListener(event, listener);
|
|
}
|
|
|
|
public override removeListener<T extends ShuttleRepositoryEventName>(
|
|
event: T,
|
|
listener: ShuttleRepositoryEventListener<T>,
|
|
): this;
|
|
public override removeListener(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
return super.removeListener(event, listener);
|
|
}
|
|
|
|
public override emit<T extends ShuttleRepositoryEventName>(
|
|
event: T,
|
|
payload: ShuttleRepositoryEventPayloads[T],
|
|
): boolean;
|
|
public override emit(event: string | symbol, ...args: any[]): boolean {
|
|
return super.emit(event, ...args);
|
|
}
|
|
|
|
private stops: IStop[] = [];
|
|
private routes: IRoute[] = [];
|
|
private shuttles: IShuttle[] = [];
|
|
private orderedStops: IOrderedStop[] = [];
|
|
private shuttleLastStopArrivals: Map<string, ShuttleStopArrival> = new Map();
|
|
private travelTimeData: Map<string, Array<{ timestamp: number; seconds: number }>> = new Map();
|
|
private shuttlesAtStop: Set<string> = new Set();
|
|
|
|
public async getStops(): Promise<IStop[]> {
|
|
return this.stops;
|
|
}
|
|
|
|
public async getStopById(stopId: string) {
|
|
return this.findEntityById(stopId, this.stops);
|
|
}
|
|
|
|
public async getRoutes(): Promise<IRoute[]> {
|
|
return this.routes;
|
|
}
|
|
|
|
public async getRouteById(routeId: string) {
|
|
return this.findEntityById(routeId, this.routes);
|
|
}
|
|
|
|
public async getShuttles(): Promise<IShuttle[]> {
|
|
return this.shuttles;
|
|
}
|
|
|
|
public async getShuttlesByRouteId(routeId: string) {
|
|
return this.shuttles.filter(shuttle => shuttle.routeId === routeId);
|
|
}
|
|
|
|
public async getShuttleById(shuttleId: string) {
|
|
return this.findEntityById(shuttleId, this.shuttles);
|
|
}
|
|
|
|
public async getOrderedStopByRouteAndStopId(routeId: string, stopId: string) {
|
|
return this.findEntityByMatcher<IOrderedStop>((value) => value.routeId === routeId && value.stopId === stopId, this.orderedStops)
|
|
}
|
|
|
|
public async getOrderedStopsByStopId(stopId: string) {
|
|
return this.orderedStops.filter((value) => value.stopId === stopId);
|
|
}
|
|
|
|
public async getOrderedStopsByRouteId(routeId: string) {
|
|
return this.orderedStops.filter((value) => value.routeId === routeId);
|
|
}
|
|
|
|
private findEntityById<T extends IEntityWithId>(entityId: string, arrayToSearchIn: T[]) {
|
|
return this.findEntityByMatcher((value) => value.id === entityId, arrayToSearchIn);
|
|
}
|
|
|
|
private findEntityByMatcher<T>(callback: (value: T) => boolean, arrayToSearchIn: T[]): T | null {
|
|
const entity = arrayToSearchIn.find(callback);
|
|
if (!entity) {
|
|
return null;
|
|
}
|
|
return entity;
|
|
}
|
|
|
|
public async addOrUpdateRoute(route: IRoute): Promise<void> {
|
|
const index = this.routes.findIndex((r) => r.id === route.id);
|
|
if (index !== -1) {
|
|
this.routes[index] = route;
|
|
} else {
|
|
this.routes.push(route);
|
|
}
|
|
}
|
|
|
|
public async addOrUpdateShuttle(
|
|
shuttle: IShuttle,
|
|
travelTimeTimestamp = Date.now(),
|
|
): Promise<void> {
|
|
const index = this.shuttles.findIndex((s) => s.id === shuttle.id);
|
|
if (index !== -1) {
|
|
this.shuttles[index] = shuttle;
|
|
} else {
|
|
this.shuttles.push(shuttle);
|
|
}
|
|
|
|
this.emit(ShuttleRepositoryEvent.SHUTTLE_UPDATED, shuttle);
|
|
|
|
await this.updateLastStopArrival(shuttle, travelTimeTimestamp);
|
|
}
|
|
|
|
public async addOrUpdateStop(stop: IStop): Promise<void> {
|
|
const index = this.stops.findIndex((s) => s.id === stop.id);
|
|
if (index !== -1) {
|
|
this.stops[index] = stop;
|
|
} else {
|
|
this.stops.push(stop);
|
|
}
|
|
}
|
|
|
|
public async addOrUpdateOrderedStop(orderedStop: IOrderedStop): Promise<void> {
|
|
const index = this.orderedStops.findIndex((value) => value.stopId === orderedStop.stopId && value.routeId === orderedStop.routeId);
|
|
if (index !== -1) {
|
|
this.orderedStops[index] = orderedStop;
|
|
} else {
|
|
this.orderedStops.push(orderedStop);
|
|
}
|
|
}
|
|
|
|
private async updateLastStopArrival(
|
|
shuttle: IShuttle,
|
|
travelTimeTimestamp = Date.now(),
|
|
) {
|
|
const isAtStop = await this.checkIfShuttleIsAtStop(shuttle.id);
|
|
|
|
let arrivedStop: IStop | undefined;
|
|
|
|
if (isAtStop) {
|
|
// Allow retrieval of the shuttle's current stop
|
|
// Will still return undefined when the shuttle leaves the stop
|
|
arrivedStop = await this.getArrivedStopIfNextStop(shuttle, true);
|
|
} else {
|
|
arrivedStop = await this.getArrivedStopIfNextStop(shuttle, false);
|
|
}
|
|
|
|
|
|
// Will not fire *any* events if the same stop
|
|
const lastStop = await this.getShuttleLastStopArrival(shuttle.id);
|
|
if (lastStop?.stopId === arrivedStop?.id) return;
|
|
|
|
if (isAtStop) {
|
|
if (lastStop) {
|
|
this.emit(ShuttleRepositoryEvent.SHUTTLE_WILL_LEAVE_STOP, {
|
|
stopArrivalThatShuttleIsLeaving: lastStop,
|
|
});
|
|
}
|
|
await this.markShuttleAsNotAtStop(shuttle.id);
|
|
}
|
|
|
|
if (arrivedStop) {
|
|
// stop if same stop
|
|
const shuttleArrival = {
|
|
stopId: arrivedStop.id,
|
|
timestamp: new Date(travelTimeTimestamp),
|
|
shuttleId: shuttle.id,
|
|
};
|
|
this.emit(ShuttleRepositoryEvent.SHUTTLE_WILL_ARRIVE_AT_STOP, {
|
|
lastStopArrival: lastStop,
|
|
willArriveAt: shuttleArrival,
|
|
});
|
|
await this.markShuttleAsAtStop(shuttleArrival.shuttleId);
|
|
await this.updateShuttleLastStopArrival(shuttleArrival);
|
|
}
|
|
}
|
|
|
|
private async markShuttleAsAtStop(shuttleId: string) {
|
|
this.shuttlesAtStop.add(shuttleId);
|
|
}
|
|
|
|
private async markShuttleAsNotAtStop(shuttleId: string) {
|
|
this.shuttlesAtStop.delete(shuttleId);
|
|
}
|
|
|
|
public async checkIfShuttleIsAtStop(shuttleId: string) {
|
|
return this.shuttlesAtStop.has(shuttleId);
|
|
}
|
|
|
|
|
|
private async updateShuttleLastStopArrival(lastStopArrival: ShuttleStopArrival) {
|
|
this.shuttleLastStopArrivals.set(lastStopArrival.shuttleId, lastStopArrival);
|
|
}
|
|
|
|
public async getAverageTravelTimeSeconds(
|
|
{ routeId, fromStopId, toStopId }: ShuttleTravelTimeDataIdentifier,
|
|
{ from, to }: ShuttleTravelTimeDateFilterArguments,
|
|
): Promise<number | undefined> {
|
|
const key = `${routeId}:${fromStopId}:${toStopId}`;
|
|
const dataPoints = this.travelTimeData.get(key);
|
|
|
|
if (!dataPoints || dataPoints.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
const fromTimestamp = from.getTime();
|
|
const toTimestamp = to.getTime();
|
|
|
|
const filteredPoints = dataPoints.filter(
|
|
(point) => point.timestamp >= fromTimestamp && point.timestamp <= toTimestamp
|
|
);
|
|
|
|
if (filteredPoints.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
const sum = filteredPoints.reduce((acc, point) => acc + point.seconds, 0);
|
|
return sum / filteredPoints.length;
|
|
}
|
|
|
|
public async getArrivedStopIfNextStop(
|
|
shuttle: IShuttle,
|
|
canReturnShuttleCurrentStop: boolean = false,
|
|
): Promise<IStop | undefined> {
|
|
const degreeDelta = this.shuttleStopArrivalDegreeDelta;
|
|
|
|
const lastStopArrival = await this.getShuttleLastStopArrival(shuttle.id);
|
|
if (lastStopArrival) {
|
|
// Return the shuttle's current stop depending on the flag
|
|
if (canReturnShuttleCurrentStop) {
|
|
const lastStop = await this.getStopById(lastStopArrival.stopId);
|
|
if (lastStop && shuttleHasArrivedAtStop(shuttle, lastStop, degreeDelta)) {
|
|
return lastStop;
|
|
}
|
|
}
|
|
|
|
const lastOrderedStop = await this.getOrderedStopByRouteAndStopId(shuttle.routeId, lastStopArrival.stopId);
|
|
const orderedStopAfter = lastOrderedStop?.nextStop;
|
|
if (orderedStopAfter) {
|
|
const stopAfter = await this.getStopById(orderedStopAfter.stopId);
|
|
if (stopAfter && shuttleHasArrivedAtStop(shuttle, stopAfter, degreeDelta)) {
|
|
return stopAfter;
|
|
}
|
|
}
|
|
} else {
|
|
const orderedStops = await this.getOrderedStopsByRouteId(shuttle.routeId);
|
|
|
|
for (const orderedStop of orderedStops) {
|
|
const stop = await this.getStopById(orderedStop.stopId);
|
|
if (stop != null && shuttleHasArrivedAtStop(shuttle, stop, degreeDelta)) {
|
|
return stop;
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
public async getShuttleLastStopArrival(shuttleId: string): Promise<ShuttleStopArrival | undefined> {
|
|
return this.shuttleLastStopArrivals.get(shuttleId);
|
|
}
|
|
|
|
private async removeEntityByMatcherIfExists<T>(callback: (value: T) => boolean, arrayToSearchIn: T[]) {
|
|
const index = arrayToSearchIn.findIndex(callback);
|
|
if (index > -1) {
|
|
const entityToReturn = arrayToSearchIn[index];
|
|
arrayToSearchIn.splice(index, 1);
|
|
return entityToReturn;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private async removeEntityByIdIfExists<T extends IEntityWithId>(entityId: string, arrayToSearchIn: T[]) {
|
|
return await this.removeEntityByMatcherIfExists((value) => value.id === entityId, arrayToSearchIn);
|
|
}
|
|
|
|
public async removeRouteIfExists(routeId: string): Promise<IRoute | null> {
|
|
return await this.removeEntityByIdIfExists(routeId, this.routes);
|
|
}
|
|
|
|
public async removeShuttleIfExists(shuttleId: string): Promise<IShuttle | null> {
|
|
const shuttle = await this.removeEntityByIdIfExists(shuttleId, this.shuttles);
|
|
if (shuttle != null) {
|
|
this.emit(ShuttleRepositoryEvent.SHUTTLE_REMOVED, shuttle);
|
|
this.shuttlesAtStop.delete(shuttleId);
|
|
await this.removeShuttleLastStopIfExists(shuttleId);
|
|
}
|
|
return shuttle;
|
|
}
|
|
|
|
public async removeStopIfExists(stopId: string): Promise<IStop | null> {
|
|
return await this.removeEntityByIdIfExists(stopId, this.stops);
|
|
}
|
|
|
|
public async removeOrderedStopIfExists(stopId: string, routeId: string): Promise<IOrderedStop | null> {
|
|
return await this.removeEntityByMatcherIfExists((orderedStop) => {
|
|
return orderedStop.stopId === stopId
|
|
&& orderedStop.routeId === routeId
|
|
}, this.orderedStops);
|
|
}
|
|
|
|
private async removeShuttleLastStopIfExists(shuttleId: string) {
|
|
this.shuttleLastStopArrivals.delete(shuttleId);
|
|
}
|
|
|
|
public async clearShuttleData(): Promise<void> {
|
|
this.shuttles = [];
|
|
this.shuttlesAtStop.clear();
|
|
await this.clearShuttleLastStopData();
|
|
}
|
|
|
|
public async clearOrderedStopData(): Promise<void> {
|
|
this.orderedStops = [];
|
|
}
|
|
|
|
public async clearRouteData(): Promise<void> {
|
|
this.routes = [];
|
|
}
|
|
|
|
public async clearStopData(): Promise<void> {
|
|
this.stops = [];
|
|
}
|
|
|
|
private async clearShuttleLastStopData(): Promise<void> {
|
|
this.shuttleLastStopArrivals.clear();
|
|
}
|
|
}
|