From 8598e2c4fd5d0f4ff18fde3b9343216da7ebef51 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 10 Jan 2019 14:17:49 +0100 Subject: [PATCH 01/69] 1. added ReachableStopsFinderOnlySelf. 2. cleanup JourneyExtraction phases. --- src/demo.ts | 2 +- src/inversify.config.ts | 19 ++++---- src/planner/PlannerPhase.ts | 6 --- .../JourneyExtractionPhase.ts | 14 ------ .../PublicTransportPlannerCSAProfile.ts | 2 +- src/planner/road/RoadPlannerBirdsEye.ts | 43 ++++++++++++++----- .../stops/ReachableStopsFinderOnlySelf.ts | 18 ++++++++ 7 files changed, 60 insertions(+), 44 deletions(-) delete mode 100644 src/planner/PlannerPhase.ts delete mode 100644 src/planner/public-transport/JourneyExtractionPhase.ts create mode 100644 src/planner/stops/ReachableStopsFinderOnlySelf.ts diff --git a/src/demo.ts b/src/demo.ts index 40fc98de..1b688d78 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -51,7 +51,7 @@ export default async (logResults) => { // to: "https://data.delijn.be/stops/200455", // Deinze weg op Grammene +456 from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters - minimumDepartureTime: new Date("2019-01-10T08:13:20.530Z"), + minimumDepartureTime: new Date(), maximumTransferDuration: Units.fromHours(0.5), }) .then((publicTransportResult) => { diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 7209beb3..42ba3a8f 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -14,13 +14,13 @@ import StopsFetcherLDFetch from "./fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import StopsProviderDefault from "./fetcher/stops/StopsProviderDefault"; import IJourneyExtractor from "./planner/public-transport/IJourneyExtractor"; import IPublicTransportPlanner from "./planner/public-transport/IPublicTransportPlanner"; -import JourneyExtractionPhase from "./planner/public-transport/JourneyExtractionPhase"; import JourneyExtractorDefault from "./planner/public-transport/JourneyExtractorDefault"; import PublicTransportPlannerCSAProfile from "./planner/public-transport/PublicTransportPlannerCSAProfile"; import IRoadPlanner from "./planner/road/IRoadPlanner"; import RoadPlannerBirdsEye from "./planner/road/RoadPlannerBirdsEye"; import IReachableStopsFinder from "./planner/stops/IReachableStopsFinder"; -import ReachableStopsFinderBirdsEyeCached from "./planner/stops/ReachableStopsFinderBirdsEyeCached"; +import ReachableStopsFinderOnlySelf from "./planner/stops/ReachableStopsFinderOnlySelf"; +import ReachableStopsFinderRoadPlannerCached from "./planner/stops/ReachableStopsFinderRoadPlannerCached"; import ReachableStopsSearchPhase from "./planner/stops/ReachableStopsSearchPhase"; import QueryRunnerExponential from "./query-runner/exponential/QueryRunnerExponential"; import ILocationResolver from "./query-runner/ILocationResolver"; @@ -39,21 +39,18 @@ container.bind(TYPES.PublicTransportPlanner) container.bind>(TYPES.PublicTransportPlannerFactory) .toAutoFactory(TYPES.PublicTransportPlanner); +container.bind(TYPES.RoadPlanner) + .to(RoadPlannerBirdsEye); + container.bind(TYPES.JourneyExtractor) .to(JourneyExtractorDefault); -container.bind(TYPES.RoadPlanner) - .to(RoadPlannerBirdsEye).whenTargetTagged("phase", JourneyExtractionPhase.Initial); -container.bind(TYPES.RoadPlanner) - .to(RoadPlannerBirdsEye).whenTargetTagged("phase", JourneyExtractionPhase.Transfer); -container.bind(TYPES.RoadPlanner) - .to(RoadPlannerBirdsEye).whenTargetTagged("phase", JourneyExtractionPhase.Final); container.bind(TYPES.ReachableStopsFinder) - .to(ReachableStopsFinderBirdsEyeCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Initial); + .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Initial); container.bind(TYPES.ReachableStopsFinder) - .to(ReachableStopsFinderBirdsEyeCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Transfer); + .to(ReachableStopsFinderOnlySelf).whenTargetTagged("phase", ReachableStopsSearchPhase.Transfer); container.bind(TYPES.ReachableStopsFinder) - .to(ReachableStopsFinderBirdsEyeCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Final); + .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Final); container.bind(TYPES.ConnectionsProvider).to(ConnectionsProviderMerge).inSingletonScope(); container.bind(TYPES.ConnectionsFetcher).to(ConnectionsFetcherLazy); diff --git a/src/planner/PlannerPhase.ts b/src/planner/PlannerPhase.ts deleted file mode 100644 index f4b23ef5..00000000 --- a/src/planner/PlannerPhase.ts +++ /dev/null @@ -1,6 +0,0 @@ -import JourneyExtractionPhase from "./public-transport/JourneyExtractionPhase"; -import ReachableStopsSearchPhase from "./stops/ReachableStopsSearchPhase"; - -type PlannerPhase = JourneyExtractionPhase | ReachableStopsSearchPhase; - -export default PlannerPhase; diff --git a/src/planner/public-transport/JourneyExtractionPhase.ts b/src/planner/public-transport/JourneyExtractionPhase.ts deleted file mode 100644 index 603882da..00000000 --- a/src/planner/public-transport/JourneyExtractionPhase.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Signifies different phases of the journey extraction algorithm - * Dependencies of [[IJourneyExtractor]] implementations can be tagged with one of these phases, - * so the algorithm knows which dependency to use at which time. - * This is especially useful when the algorithm could potentially use different implementations of - * the same interface at each phase - */ -enum JourneyExtractionPhase { - Initial = "journeyExtractionInitial", - Transfer = "journeyExtractionTransfer", - Final = "journeyExtractionFinal", -} - -export default JourneyExtractionPhase; diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index 14db542c..fab82f2b 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -433,7 +433,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor }); } catch (e) { - this.context.emitWarning(e); + this.context.emitWarning(e); } } diff --git a/src/planner/road/RoadPlannerBirdsEye.ts b/src/planner/road/RoadPlannerBirdsEye.ts index 06cc4f09..636dce2e 100644 --- a/src/planner/road/RoadPlannerBirdsEye.ts +++ b/src/planner/road/RoadPlannerBirdsEye.ts @@ -8,13 +8,20 @@ import IResolvedQuery from "../../query-runner/IResolvedQuery"; import TravelMode from "../../TravelMode"; import Geo from "../../util/Geo"; import Units from "../../util/Units"; +import Path from "../Path"; import IRoadPlanner from "./IRoadPlanner"; @injectable() export default class RoadPlannerBirdsEye implements IRoadPlanner { public async plan(query: IResolvedQuery): Promise> { - const { from: fromLocations, to: toLocations, minimumWalkingSpeed, maximumWalkingSpeed} = query; + const { + from: fromLocations, + to: toLocations, + minimumWalkingSpeed, + maximumWalkingSpeed, + maximumWalkingDuration, + } = query; const paths = []; @@ -22,7 +29,18 @@ export default class RoadPlannerBirdsEye implements IRoadPlanner { for (const from of fromLocations) { for (const to of toLocations) { - paths.push(this.getPathBetweenLocations(from, to, minimumWalkingSpeed, maximumWalkingSpeed)); + + const path = this.getPathBetweenLocations( + from, + to, + minimumWalkingSpeed, + maximumWalkingSpeed, + maximumWalkingDuration, + ); + + if (path) { + paths.push(path); + } } } } @@ -35,6 +53,7 @@ export default class RoadPlannerBirdsEye implements IRoadPlanner { to: ILocation, minWalkingSpeed: SpeedkmH, maxWalkingSpeed: SpeedkmH, + maxWalkingDuration: DurationMs, ): IPath { const distance = Geo.getDistanceBetweenLocations(from, to); @@ -47,14 +66,16 @@ export default class RoadPlannerBirdsEye implements IRoadPlanner { average: (minDuration + maxDuration) / 2, }; - return { - steps: [{ - startLocation: from, - stopLocation: to, - duration, - distance, - travelMode: TravelMode.Walking, - }], - }; + if (duration.maximum > maxWalkingDuration) { + return; + } + + return new Path([{ + startLocation: from, + stopLocation: to, + duration, + distance, + travelMode: TravelMode.Walking, + }]); } } diff --git a/src/planner/stops/ReachableStopsFinderOnlySelf.ts b/src/planner/stops/ReachableStopsFinderOnlySelf.ts new file mode 100644 index 00000000..18be32d2 --- /dev/null +++ b/src/planner/stops/ReachableStopsFinderOnlySelf.ts @@ -0,0 +1,18 @@ +import { injectable } from "inversify"; +import IStop from "../../fetcher/stops/IStop"; +import { DurationMs, SpeedkmH } from "../../interfaces/units"; +import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; +import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; + +@injectable() +export default class ReachableStopsFinderOnlySelf implements IReachableStopsFinder { + + public async findReachableStops( + sourceOrTargetStop: IStop, + mode: ReachableStopsFinderMode, + maximumDuration: DurationMs, + minimumSpeed: SpeedkmH, + ): Promise { + return [{ stop: sourceOrTargetStop, duration: 0 }]; + } +} From fdcb374c454c02f3cbff31e1d257d36a21958b0e Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 10 Jan 2019 16:14:54 +0100 Subject: [PATCH 02/69] Improved SubqueryIterator and test --- src/Planner.ts | 4 -- src/demo.cli.ts | 2 +- src/demo.ts | 19 ++++++--- .../JourneyExtractorDefault.ts | 3 +- .../PublicTransportPlannerCSAProfile.ts | 2 + .../exponential/SubqueryIterator.test.ts | 40 +++++++++++++------ .../exponential/SubqueryIterator.ts | 4 +- src/util/iterators/AsyncArrayIterator.ts | 29 ++++++++++++++ src/util/iterators/UnbufferedIterator.ts | 0 9 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 src/util/iterators/AsyncArrayIterator.ts create mode 100644 src/util/iterators/UnbufferedIterator.ts diff --git a/src/Planner.ts b/src/Planner.ts index e287ce1e..caf918ad 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -11,10 +11,6 @@ import defaultContainer from "./inversify.config"; import IQueryRunner from "./query-runner/IQueryRunner"; import TYPES from "./types"; -if (!Symbol.asyncIterator) { - (Symbol as any).asyncIterator = Symbol.for("Symbol.asyncIterator"); -} - /** * Allows to ask route planning queries over our knowledge graphs */ diff --git a/src/demo.cli.ts b/src/demo.cli.ts index d9bb265e..6d8e2780 100644 --- a/src/demo.cli.ts +++ b/src/demo.cli.ts @@ -8,6 +8,6 @@ const isDebugging = process && process.argv.includes("--debug"); } runDemo(true) - .then(() => console.log("Success")) + .then((success) => console.log(success ? "Success" : "Fail")) .catch((e) => console.error(e)); })(); diff --git a/src/demo.ts b/src/demo.ts index 40fc98de..69330171 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -11,6 +11,8 @@ export default async (logResults) => { let scannedPages = 0; let scannedConnections = 0; + let logFetch = true; + planner .on(EventType.InvalidQuery, (error) => { console.log("InvalidQuery", error); @@ -24,13 +26,19 @@ export default async (logResults) => { .on(EventType.QueryExponential, (query) => { const { minimumDepartureTime, maximumArrivalTime } = query; + logFetch = true; + console.log("Total scanned pages", scannedPages); console.log("Total scanned connections", scannedConnections); console.log("[Subquery]", minimumDepartureTime, maximumArrivalTime, maximumArrivalTime - minimumDepartureTime); }) .on(EventType.LDFetchGet, (url, duration) => { scannedPages++; - console.log(`[GET] ${url} (${duration}ms)`); + + if (logFetch) { + console.log(`[GET] ${url} (${duration}ms)`); + logFetch = false; + } }) .on(EventType.ConnectionScan, (connection) => { scannedConnections++; @@ -51,24 +59,25 @@ export default async (logResults) => { // to: "https://data.delijn.be/stops/200455", // Deinze weg op Grammene +456 from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters - minimumDepartureTime: new Date("2019-01-10T08:13:20.530Z"), + minimumDepartureTime: new Date(), maximumTransferDuration: Units.fromHours(0.5), }) .then((publicTransportResult) => { + const amount = 3; let i = 0; - publicTransportResult.take(3) + publicTransportResult.take(amount) .on("data", (path: IPath) => { ++i; - if (logResults) { + if (logResults && false) { console.log(i); console.log(JSON.stringify(path, null, " ")); console.log("\n"); } - if (i === 3) { + if (i === amount) { resolve(true); } }) diff --git a/src/planner/public-transport/JourneyExtractorDefault.ts b/src/planner/public-transport/JourneyExtractorDefault.ts index e6140c62..b4175ecd 100644 --- a/src/planner/public-transport/JourneyExtractorDefault.ts +++ b/src/planner/public-transport/JourneyExtractorDefault.ts @@ -77,13 +77,14 @@ export default class JourneyExtractorDefault implements IJourneyExtractor { arrivalLocation, transferProfile.arrivalTime, ); + } catch (e) { this.context.emitWarning(e); } } } - } + return new ArrayIterator(paths.reverse()); } diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index 14db542c..efe0a44b 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -116,6 +116,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor return new Promise((resolve, reject) => { let isDone = false; + const done = () => { if (!isDone) { self.connectionsIterator.close(); @@ -124,6 +125,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor .then((resultIterator) => { resolve(resultIterator); }); + isDone = true; } }; diff --git a/src/query-runner/exponential/SubqueryIterator.test.ts b/src/query-runner/exponential/SubqueryIterator.test.ts index 4d6632b5..7c2583b3 100644 --- a/src/query-runner/exponential/SubqueryIterator.test.ts +++ b/src/query-runner/exponential/SubqueryIterator.test.ts @@ -1,28 +1,42 @@ import { ArrayIterator } from "asynciterator"; import "jest"; +import AsyncArrayIterator from "../../util/iterators/AsyncArrayIterator"; import SubqueryIterator from "./SubqueryIterator"; const ALPHABET = "abc"; +const expected = ["a", "b", "b", "c", "c", "c"]; -const queryIterator = new ArrayIterator([1, 2, 3]); +describe("[SubqueryIterator]", () => { -const subqueryIterator = new SubqueryIterator(queryIterator, (num) => { - return new Promise((resolve) => { - const array = Array(num).fill(ALPHABET[num - 1]); + const runTest = (QueryIterator, ResultIterator, done) => { + const queryIterator = new QueryIterator([1, 2, 3], 10); - resolve(new ArrayIterator(array)); - }); -}); + const subqueryIterator = new SubqueryIterator(queryIterator, (num) => { + return new Promise((resolve) => { + const array = Array(num).fill(ALPHABET[num - 1]); + + resolve(new ResultIterator(array, 10)); + }); + }); -test("[SubqueryIterator]", (done) => { + let current = 0; - let current = 0; - const expected = ["a", "b", "b", "c", "c", "c"]; + subqueryIterator.each((str) => { + expect(expected[current++]).toBe(str); + }); - subqueryIterator.each((str) => { - expect(expected[current++]).toBe(str); + subqueryIterator.on("end", () => done()); + }; + + it("Subqueries from ArrayIterator / Results from ArrayIterator", (done) => { + runTest(ArrayIterator, ArrayIterator, done); }); - subqueryIterator.on("end", () => done()); + it("Subqueries from ArrayIterator / Results from BufferedIterator", (done) => { + runTest(ArrayIterator, AsyncArrayIterator, done); + }); + it("Subqueries from BufferedIterator / Results from BufferedIterator", (done) => { + runTest(AsyncArrayIterator, AsyncArrayIterator, done); + }); }); diff --git a/src/query-runner/exponential/SubqueryIterator.ts b/src/query-runner/exponential/SubqueryIterator.ts index e749e5e3..61b9d713 100644 --- a/src/query-runner/exponential/SubqueryIterator.ts +++ b/src/query-runner/exponential/SubqueryIterator.ts @@ -9,7 +9,7 @@ export default class SubqueryIterator extends BufferedIterator { private isLastResultIterator = false; constructor(queryIterator: AsyncIterator, run: (query: Q) => Promise>) { - super(); + super({maxBufferSize: 1, autoStart: false}); this.queryIterator = queryIterator; this.callback = run; @@ -71,7 +71,7 @@ export default class SubqueryIterator extends BufferedIterator { } private pushItemsAsync(done) { - this.currentResultIterator.on("readable", () => { + this.currentResultIterator.once("readable", () => { this.pushItemsSync(); done(); }); diff --git a/src/util/iterators/AsyncArrayIterator.ts b/src/util/iterators/AsyncArrayIterator.ts new file mode 100644 index 00000000..9888fbce --- /dev/null +++ b/src/util/iterators/AsyncArrayIterator.ts @@ -0,0 +1,29 @@ +import { BufferedIterator } from "asynciterator"; +import { DurationMs } from "../../interfaces/units"; + +export default class AsyncArrayIterator extends BufferedIterator { + private currentIndex: number = 0; + private readonly array: T[]; + private readonly interval: DurationMs; + + constructor(array: T[], interval: DurationMs = 0) { + super(); + + this.array = array; + this.interval = interval; + } + + public _read(count: number, done: () => void): void { + if (this.currentIndex === this.array.length) { + this.close(); + return done(); + } + + const self = this; + + setTimeout(() => { + self._push(self.array[self.currentIndex++]); + done(); + }, this.interval); + } +} diff --git a/src/util/iterators/UnbufferedIterator.ts b/src/util/iterators/UnbufferedIterator.ts new file mode 100644 index 00000000..e69de29b From 4a97425a86fc9567ed4ba7f70c0c4de2db816b55 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 10 Jan 2019 16:26:29 +0100 Subject: [PATCH 03/69] Cleanup --- .../exponential/QueryRunnerExponential.ts | 12 +++--------- src/util/iterators/UnbufferedIterator.ts | 0 2 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 src/util/iterators/UnbufferedIterator.ts diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index ac25d7de..0002d81e 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -9,7 +9,6 @@ import IPath from "../../interfaces/IPath"; import IQuery from "../../interfaces/IQuery"; import IPublicTransportPlanner from "../../planner/public-transport/IPublicTransportPlanner"; import TYPES from "../../types"; -import Emiterator from "../../util/iterators/Emiterator"; import Units from "../../util/Units"; import ILocationResolver from "../ILocationResolver"; import IQueryRunner from "../IQueryRunner"; @@ -20,9 +19,9 @@ import SubqueryIterator from "./SubqueryIterator"; @injectable() export default class QueryRunnerExponential implements IQueryRunner { - private locationResolver: ILocationResolver; - private publicTransportPlannerFactory: interfaces.Factory; - private context: Context; + private readonly locationResolver: ILocationResolver; + private readonly publicTransportPlannerFactory: interfaces.Factory; + private readonly context: Context; constructor( @inject(TYPES.Context) @@ -42,11 +41,6 @@ export default class QueryRunnerExponential implements IQueryRunner { if (baseQuery.publicTransportOnly) { const queryIterator = new ExponentialQueryIterator(baseQuery, 15 * 60 * 1000); - // const emitQueryIterator = new Emiterator( - // queryIterator, - // this.context, - // EventType.QueryExponential, - // ); const subqueryIterator = new SubqueryIterator( queryIterator, diff --git a/src/util/iterators/UnbufferedIterator.ts b/src/util/iterators/UnbufferedIterator.ts deleted file mode 100644 index e69de29b..00000000 From 03133464e7aaa0285328848fd6b0228d06068c74 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 11 Jan 2019 09:45:52 +0100 Subject: [PATCH 04/69] Use FlatMapIterator to transform HydraPageIterator into ConnectionsIterator --- .../hydra/ConnectionsFetcherLazy.ts | 51 +++++++++++ .../hydra/ConnectionsIteratorLazy.ts | 45 ++++++++++ .../connections/hydra/HydraPageIterator.ts | 69 +++++++++++++++ .../connections/hydra/HydraPageParser.ts | 88 +++++++++++++++++++ src/fetcher/connections/hydra/IHydraPage.ts | 9 ++ .../hydra/IHydraPageIteratorConfig.ts | 4 + src/fetcher/stops/StopsPrefetcher.ts | 0 src/inversify.config.ts | 2 +- .../exponential/QueryRunnerExponential.ts | 4 +- .../iterators/FlatMapIterator.test.ts} | 12 +-- .../iterators/FlatMapIterator.ts} | 4 +- 11 files changed, 278 insertions(+), 10 deletions(-) create mode 100644 src/fetcher/connections/hydra/ConnectionsFetcherLazy.ts create mode 100644 src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts create mode 100644 src/fetcher/connections/hydra/HydraPageIterator.ts create mode 100644 src/fetcher/connections/hydra/HydraPageParser.ts create mode 100644 src/fetcher/connections/hydra/IHydraPage.ts create mode 100644 src/fetcher/connections/hydra/IHydraPageIteratorConfig.ts create mode 100644 src/fetcher/stops/StopsPrefetcher.ts rename src/{query-runner/exponential/SubqueryIterator.test.ts => util/iterators/FlatMapIterator.test.ts} (73%) rename src/{query-runner/exponential/SubqueryIterator.ts => util/iterators/FlatMapIterator.ts} (95%) diff --git a/src/fetcher/connections/hydra/ConnectionsFetcherLazy.ts b/src/fetcher/connections/hydra/ConnectionsFetcherLazy.ts new file mode 100644 index 00000000..f3df194e --- /dev/null +++ b/src/fetcher/connections/hydra/ConnectionsFetcherLazy.ts @@ -0,0 +1,51 @@ +import { AsyncIterator } from "asynciterator"; +import { inject, injectable } from "inversify"; +import LDFetch from "ldfetch"; +import TravelMode from "../../../TravelMode"; +import TYPES from "../../../types"; +import IConnection from "../IConnection"; +import IConnectionsFetcher from "../IConnectionsFetcher"; +import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import ConnectionsIteratorLazy from "./ConnectionsIteratorLazy"; + +/** + * Wraps the [[ConnectionsIteratorLazy]] + * @implements IConnectionsFetcher + */ +@injectable() +export default class ConnectionsFetcherLazy implements IConnectionsFetcher { + + protected readonly ldFetch: LDFetch; + protected config: IConnectionsFetcherConfig; + private travelMode: TravelMode; + private accessUrl: string; + + constructor(@inject(TYPES.LDFetch) ldFetch: LDFetch) { + this.ldFetch = ldFetch; + } + + public setTravelMode(travelMode: TravelMode) { + this.travelMode = travelMode; + } + + public setAccessUrl(accessUrl: string) { + this.accessUrl = accessUrl; + } + + public prefetchConnections(): void { + return; + } + + public createIterator(): AsyncIterator { + return new ConnectionsIteratorLazy( + this.accessUrl, + this.travelMode, + this.ldFetch, + this.config, + ); + } + + public setConfig(config: IConnectionsFetcherConfig): void { + this.config = config; + } +} diff --git a/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts b/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts new file mode 100644 index 00000000..4a327bf0 --- /dev/null +++ b/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts @@ -0,0 +1,45 @@ +import { ArrayIterator } from "asynciterator"; +import LdFetch from "ldfetch"; +import TravelMode from "../../../TravelMode"; +import FlatMapIterator from "../../../util/iterators/FlatMapIterator"; +import IConnection from "../IConnection"; +import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import ConnectionsPageParser from "../ld-fetch/ConnectionsPageParser"; +import HydraPageIterator from "./HydraPageIterator"; +import IHydraPage from "./IHydraPage"; +import IHydraPageIteratorConfig from "./IHydraPageIteratorConfig"; + +/** + * Base class for fetching linked connections with LDFetch and letting the caller iterate over them asynchronously + * through implementing the AsyncIterator protocol. + * LDFetch returns documents as an array of RDF triples. + * The meta Hydra triples are used for paginating to the next or previous page. + * The triples that describe linked connections get deserialized to instances of [[IConnection]] + */ +export default class ConnectionsIteratorLazy extends FlatMapIterator { + constructor( + baseUrl: string, + travelMode: TravelMode, + ldFetch: LdFetch, + config: IConnectionsFetcherConfig, + ) { + const departureTimeDate = config.backward ? + config.upperBoundDate : config.lowerBoundDate; + + const pageIteratorConfig: IHydraPageIteratorConfig = { + backward: config.backward, + initialTemplateVariables: { + departureTime: departureTimeDate.toISOString(), + }, + }; + + const pageIterator = new HydraPageIterator(baseUrl, ldFetch, pageIteratorConfig); + const parsePageConnections = (page: IHydraPage) => { + const connectionsParser = new ConnectionsPageParser(page.documentIri, page.triples); + + return Promise.resolve(new ArrayIterator(connectionsParser.getConnections(travelMode))); + }; + + super(pageIterator, parsePageConnections); + } +} diff --git a/src/fetcher/connections/hydra/HydraPageIterator.ts b/src/fetcher/connections/hydra/HydraPageIterator.ts new file mode 100644 index 00000000..ea962292 --- /dev/null +++ b/src/fetcher/connections/hydra/HydraPageIterator.ts @@ -0,0 +1,69 @@ +import { BufferedIterator } from "asynciterator"; +import LdFetch from "ldfetch"; +import UriTemplate from "uritemplate"; +import HydraPageParser2 from "./HydraPageParser"; +import IHydraPage from "./IHydraPage"; +import IHydraPageIteratorConfig from "./IHydraPageIteratorConfig"; + +export default class HydraPageIterator extends BufferedIterator { + private readonly baseUrl: string; + private readonly ldFetch: LdFetch; + private readonly config: IHydraPageIteratorConfig; + + private currentPage: IHydraPage; + + constructor( + baseUrl: string, + ldFetch: LdFetch, + config: IHydraPageIteratorConfig, + ) { + super({ + autoStart: true, + }); + + this.baseUrl = baseUrl; + this.ldFetch = ldFetch; + this.config = config; + } + + public _begin(done: () => void): void { + this.ldFetch.get(this.baseUrl) + .then((response) => { + const parser = new HydraPageParser2(response.triples); + const searchTemplate: UriTemplate = parser.getSearchTemplate(); + + const firstPageIri = searchTemplate.expand(this.config.initialTemplateVariables); + + this.loadPage(firstPageIri) + .then(() => done()); + }); + } + + public _read(count: number, done: () => void): void { + + const pageIri = this.config.backward ? + this.currentPage.previousPageIri : this.currentPage.nextPageIri; + + this.loadPage(pageIri) + .then(() => done()); + } + + private async loadPage(url: string) { + await this.ldFetch.get(url) + .then((response) => { + + const parser = new HydraPageParser2(response.triples); + const page = parser.getPage(0); + + if (this.config.backward) { + page.previousPageIri = parser.getPreviousPageIri(); + + } else { + page.nextPageIri = parser.getNextPageIri(); + } + + this.currentPage = page; + this._push(this.currentPage); + }); + } +} diff --git a/src/fetcher/connections/hydra/HydraPageParser.ts b/src/fetcher/connections/hydra/HydraPageParser.ts new file mode 100644 index 00000000..45ac8261 --- /dev/null +++ b/src/fetcher/connections/hydra/HydraPageParser.ts @@ -0,0 +1,88 @@ +import { Triple } from "rdf-js"; +import UriTemplate from "uritemplate"; +import Rdf from "../../../util/Rdf"; +import IHydraPage from "./IHydraPage"; + +/** + * Searches the given array of triples for hydra meta data, like the search template and next/previous page iris + * Also allows getting the contained [[IHydraPage]], which holds an array of [[IConnection]]s + */ +export default class HydraPageParser { + private readonly triples: Triple[]; + private readonly documentIri: string; + + constructor(triples: Triple[]) { + this.triples = triples; + this.documentIri = this.getDocumentIri(); + } + + public getPage(index: number): IHydraPage { + return { + index, + documentIri: this.documentIri, + triples: this.triples, + }; + } + + public getSearchTemplate(): UriTemplate { + const searchTriple = this.triples.find( + Rdf.matchesTriple(this.documentIri, "http://www.w3.org/ns/hydra/core#search", null), + ); + + const templateTriple = this.triples.find( + Rdf.matchesTriple(searchTriple.object.value, "http://www.w3.org/ns/hydra/core#template", null), + ); + + const template = templateTriple.object.value; + return UriTemplate.parse(template); + } + + public getNextPageIri(): string { + const nextPageTriple: Triple = this.triples.find( + Rdf.matchesTriple(this.documentIri, "http://www.w3.org/ns/hydra/core#next", null), + ); + + if (nextPageTriple && nextPageTriple.object.value.substr(0, 4) === "http") { + return nextPageTriple.object.value; + } + } + + public getPreviousPageIri(): string { + const previousPageTriple: Triple = this.triples.find( + Rdf.matchesTriple(this.documentIri, "http://www.w3.org/ns/hydra/core#previous", null), + ); + + if (previousPageTriple && previousPageTriple.object.value.substr(0, 4) === "http") { + return previousPageTriple.object.value; + } + } + + private getDocumentIri(): string { + // Type can be either http://www.w3.org/ns/hydra/core#PartialCollectionView + // or http://www.w3.org/ns/hydra/core#PagedCollection + + let typeTriple = this.triples.find( + Rdf.matchesTriple( + null, + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + "http://www.w3.org/ns/hydra/core#PartialCollectionView", + ), + ); + + if (!typeTriple) { + typeTriple = this.triples.find( + Rdf.matchesTriple( + null, + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + "http://www.w3.org/ns/hydra/core#PagedCollection", + ), + ); + } + + if (!typeTriple) { + throw new Error("Hydra page doesn`t have type triple"); + } + + return typeTriple.subject.value; + } +} diff --git a/src/fetcher/connections/hydra/IHydraPage.ts b/src/fetcher/connections/hydra/IHydraPage.ts new file mode 100644 index 00000000..a629686a --- /dev/null +++ b/src/fetcher/connections/hydra/IHydraPage.ts @@ -0,0 +1,9 @@ +import { Triple } from "rdf-js"; + +export default interface IHydraPage { + index: number; + documentIri: string; + nextPageIri?: string; + previousPageIri?: string; + triples: Triple[]; +} diff --git a/src/fetcher/connections/hydra/IHydraPageIteratorConfig.ts b/src/fetcher/connections/hydra/IHydraPageIteratorConfig.ts new file mode 100644 index 00000000..871acfe8 --- /dev/null +++ b/src/fetcher/connections/hydra/IHydraPageIteratorConfig.ts @@ -0,0 +1,4 @@ +export default interface IHydraPageIteratorConfig { + backward: boolean; + initialTemplateVariables: object; +} diff --git a/src/fetcher/stops/StopsPrefetcher.ts b/src/fetcher/stops/StopsPrefetcher.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 42ba3a8f..0730dd27 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -3,9 +3,9 @@ import Catalog from "./Catalog"; import catalogDeLijn from "./catalog.delijn"; import catalogNmbs from "./catalog.nmbs"; import Context from "./Context"; +import ConnectionsFetcherLazy from "./fetcher/connections/hydra/ConnectionsFetcherLazy"; import IConnectionsFetcher from "./fetcher/connections/IConnectionsFetcher"; import IConnectionsProvider from "./fetcher/connections/IConnectionsProvider"; -import ConnectionsFetcherLazy from "./fetcher/connections/ld-fetch/ConnectionsFetcherLazy"; import ConnectionsProviderMerge from "./fetcher/connections/merge/ConnectionsProviderMerge"; import LDFetch from "./fetcher/LDFetch"; import IStopsFetcher from "./fetcher/stops/IStopsFetcher"; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index 0002d81e..6c24959b 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -9,13 +9,13 @@ import IPath from "../../interfaces/IPath"; import IQuery from "../../interfaces/IQuery"; import IPublicTransportPlanner from "../../planner/public-transport/IPublicTransportPlanner"; import TYPES from "../../types"; +import FlatMapIterator from "../../util/iterators/FlatMapIterator"; import Units from "../../util/Units"; import ILocationResolver from "../ILocationResolver"; import IQueryRunner from "../IQueryRunner"; import IResolvedQuery from "../IResolvedQuery"; import ExponentialQueryIterator from "./ExponentialQueryIterator"; import FilterUniquePathsIterator from "./FilterUniquePathsIterator"; -import SubqueryIterator from "./SubqueryIterator"; @injectable() export default class QueryRunnerExponential implements IQueryRunner { @@ -42,7 +42,7 @@ export default class QueryRunnerExponential implements IQueryRunner { if (baseQuery.publicTransportOnly) { const queryIterator = new ExponentialQueryIterator(baseQuery, 15 * 60 * 1000); - const subqueryIterator = new SubqueryIterator( + const subqueryIterator = new FlatMapIterator( queryIterator, this.runSubquery.bind(this), ); diff --git a/src/query-runner/exponential/SubqueryIterator.test.ts b/src/util/iterators/FlatMapIterator.test.ts similarity index 73% rename from src/query-runner/exponential/SubqueryIterator.test.ts rename to src/util/iterators/FlatMapIterator.test.ts index 7c2583b3..dca3e0a4 100644 --- a/src/query-runner/exponential/SubqueryIterator.test.ts +++ b/src/util/iterators/FlatMapIterator.test.ts @@ -1,17 +1,17 @@ import { ArrayIterator } from "asynciterator"; import "jest"; -import AsyncArrayIterator from "../../util/iterators/AsyncArrayIterator"; -import SubqueryIterator from "./SubqueryIterator"; +import AsyncArrayIterator from "./AsyncArrayIterator"; +import FlatMapIterator from "./FlatMapIterator"; const ALPHABET = "abc"; const expected = ["a", "b", "b", "c", "c", "c"]; -describe("[SubqueryIterator]", () => { +describe("[FlatMapIterator]", () => { const runTest = (QueryIterator, ResultIterator, done) => { const queryIterator = new QueryIterator([1, 2, 3], 10); - const subqueryIterator = new SubqueryIterator(queryIterator, (num) => { + const flatMapIterator = new FlatMapIterator(queryIterator, (num) => { return new Promise((resolve) => { const array = Array(num).fill(ALPHABET[num - 1]); @@ -21,11 +21,11 @@ describe("[SubqueryIterator]", () => { let current = 0; - subqueryIterator.each((str) => { + flatMapIterator.each((str) => { expect(expected[current++]).toBe(str); }); - subqueryIterator.on("end", () => done()); + flatMapIterator.on("end", () => done()); }; it("Subqueries from ArrayIterator / Results from ArrayIterator", (done) => { diff --git a/src/query-runner/exponential/SubqueryIterator.ts b/src/util/iterators/FlatMapIterator.ts similarity index 95% rename from src/query-runner/exponential/SubqueryIterator.ts rename to src/util/iterators/FlatMapIterator.ts index 61b9d713..b7c666ec 100644 --- a/src/query-runner/exponential/SubqueryIterator.ts +++ b/src/util/iterators/FlatMapIterator.ts @@ -1,6 +1,6 @@ import { AsyncIterator, BufferedIterator } from "asynciterator"; -export default class SubqueryIterator extends BufferedIterator { +export default class FlatMapIterator extends BufferedIterator { private queryIterator: AsyncIterator; private callback: (query: Q) => Promise>; @@ -11,6 +11,8 @@ export default class SubqueryIterator extends BufferedIterator { constructor(queryIterator: AsyncIterator, run: (query: Q) => Promise>) { super({maxBufferSize: 1, autoStart: false}); + console.log("FlatMap"); + this.queryIterator = queryIterator; this.callback = run; From e86ad90d4d58ae694032c146821eb7f9627575bb Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 11 Jan 2019 10:00:31 +0100 Subject: [PATCH 05/69] Cleanup + prefetch wireframe --- src/Planner.ts | 27 ++++- src/demo.ts | 15 +-- .../{merge => }/ConnectionsProviderMerge.ts | 16 ++- .../ConnectionsProviderPassthrough.ts | 4 + .../connections/IConnectionsProvider.ts | 1 + .../ConnectionsIteratorLazy.test.ts | 0 .../hydra/ConnectionsIteratorLazy.ts | 2 +- .../ConnectionsPageParser.ts | 0 .../ld-fetch/ConnectionsFetcherLazy.ts | 47 -------- .../ld-fetch/ConnectionsIteratorLazy.ts | 106 ------------------ .../connections/ld-fetch/HydraPageParser.ts | 93 --------------- .../connections/ld-fetch/IHydraPage.ts | 9 -- .../tests/ConnectionsFetcherNMBSTest.ts | 4 + src/fetcher/stops/IStopsProvider.ts | 1 + src/fetcher/stops/StopsProviderDefault.ts | 6 + .../stops/ld-fetch/StopsFetcherLDFetch.ts | 4 + src/inversify.config.ts | 2 +- .../PublicTransportPlannerCSAProfile.test.ts | 2 +- .../QueryRunnerExponential.test.ts | 2 +- src/test/test-connections-iterator-2.ts | 2 +- src/util/iterators/FlatMapIterator.ts | 2 - 21 files changed, 67 insertions(+), 278 deletions(-) rename src/fetcher/connections/{merge => }/ConnectionsProviderMerge.ts (85%) rename src/fetcher/connections/{ld-fetch => hydra}/ConnectionsIteratorLazy.test.ts (100%) rename src/fetcher/connections/{ld-fetch => hydra}/ConnectionsPageParser.ts (100%) delete mode 100644 src/fetcher/connections/ld-fetch/ConnectionsFetcherLazy.ts delete mode 100644 src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.ts delete mode 100644 src/fetcher/connections/ld-fetch/HydraPageParser.ts delete mode 100644 src/fetcher/connections/ld-fetch/IHydraPage.ts diff --git a/src/Planner.ts b/src/Planner.ts index caf918ad..0b1f3b2f 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -3,6 +3,7 @@ import { AsyncIterator } from "asynciterator"; import { EventEmitter, Listener } from "events"; import Context from "./Context"; import EventType from "./EventType"; +import IConnectionsProvider from "./fetcher/connections/IConnectionsProvider"; import IStop from "./fetcher/stops/IStop"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; import IPath from "./interfaces/IPath"; @@ -104,13 +105,33 @@ export default class Planner implements EventEmitter { return this; } + public prefetchStops(): void { + const container = this.context.getContainer(); + const stopsProvider = container.get(TYPES.StopsProvider); + + if (stopsProvider) { + stopsProvider.prefetchStops(); + } + } + + public prefetchConnections(): void { + const container = this.context.getContainer(); + const connectionsProvider = container.get(TYPES.ConnectionsProvider); + + if (connectionsProvider) { + connectionsProvider.prefetchConnections(); + } + } + public getAllStops(): Promise { - const provider = this.context.getContainer().get(TYPES.StopsProvider); + const container = this.context.getContainer(); + const stopsProvider = container.get(TYPES.StopsProvider); - if (provider) { - return provider.getAllStops(); + if (stopsProvider) { + return stopsProvider.getAllStops(); } return Promise.reject(); } + } diff --git a/src/demo.ts b/src/demo.ts index 69330171..c569c851 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -11,7 +11,7 @@ export default async (logResults) => { let scannedPages = 0; let scannedConnections = 0; - let logFetch = true; + // let logFetch = true; planner .on(EventType.InvalidQuery, (error) => { @@ -26,7 +26,7 @@ export default async (logResults) => { .on(EventType.QueryExponential, (query) => { const { minimumDepartureTime, maximumArrivalTime } = query; - logFetch = true; + // logFetch = true; console.log("Total scanned pages", scannedPages); console.log("Total scanned connections", scannedConnections); @@ -34,11 +34,12 @@ export default async (logResults) => { }) .on(EventType.LDFetchGet, (url, duration) => { scannedPages++; + console.log(`[GET] ${url} (${duration}ms)`); - if (logFetch) { - console.log(`[GET] ${url} (${duration}ms)`); - logFetch = false; - } + // if (logFetch) { + // console.log(`[GET] ${url} (${duration}ms)`); + // logFetch = false; + // } }) .on(EventType.ConnectionScan, (connection) => { scannedConnections++; @@ -71,7 +72,7 @@ export default async (logResults) => { .on("data", (path: IPath) => { ++i; - if (logResults && false) { + if (logResults) { console.log(i); console.log(JSON.stringify(path, null, " ")); console.log("\n"); diff --git a/src/fetcher/connections/merge/ConnectionsProviderMerge.ts b/src/fetcher/connections/ConnectionsProviderMerge.ts similarity index 85% rename from src/fetcher/connections/merge/ConnectionsProviderMerge.ts rename to src/fetcher/connections/ConnectionsProviderMerge.ts index 9841fc99..9a67fb0e 100644 --- a/src/fetcher/connections/merge/ConnectionsProviderMerge.ts +++ b/src/fetcher/connections/ConnectionsProviderMerge.ts @@ -1,11 +1,11 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable } from "inversify"; -import Catalog from "../../../Catalog"; -import TYPES, { ConnectionsFetcherFactory } from "../../../types"; -import MergeIterator from "../../../util/iterators/MergeIterator"; -import IConnection from "../IConnection"; -import IConnectionsFetcher from "../IConnectionsFetcher"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import Catalog from "../../Catalog"; +import TYPES, { ConnectionsFetcherFactory } from "../../types"; +import MergeIterator from "../../util/iterators/MergeIterator"; +import IConnection from "./IConnection"; +import IConnectionsFetcher from "./IConnectionsFetcher"; +import IConnectionsFetcherConfig from "./IConnectionsFetcherConfig"; /** * Instantiates and merge sorts all registered connection fetchers @@ -65,6 +65,10 @@ export default class ConnectionsProviderMerge implements IConnectionsFetcher { } } + public prefetchConnections(): void { + return; + } + public createIterator(): AsyncIterator { const iterators = this.connectionsFetchers diff --git a/src/fetcher/connections/ConnectionsProviderPassthrough.ts b/src/fetcher/connections/ConnectionsProviderPassthrough.ts index e7c7c43c..fc97fb81 100644 --- a/src/fetcher/connections/ConnectionsProviderPassthrough.ts +++ b/src/fetcher/connections/ConnectionsProviderPassthrough.ts @@ -25,6 +25,10 @@ export default class ConnectionsProviderPassthrough implements IConnectionsProvi this.connectionsFetcher = connectionsFetcherFactory(accessUrl, travelMode); } + public prefetchConnections(): void { + this.connectionsFetcher.prefetchConnections(); + } + public createIterator(): AsyncIterator { return this.connectionsFetcher.createIterator(); } diff --git a/src/fetcher/connections/IConnectionsProvider.ts b/src/fetcher/connections/IConnectionsProvider.ts index 7d89c6c7..f6765eaa 100644 --- a/src/fetcher/connections/IConnectionsProvider.ts +++ b/src/fetcher/connections/IConnectionsProvider.ts @@ -8,6 +8,7 @@ import IConnectionsFetcherConfig from "./IConnectionsFetcherConfig"; * instances */ export default interface IConnectionsProvider { + prefetchConnections: () => void; createIterator: () => AsyncIterator; setConfig: (config: IConnectionsFetcherConfig) => void; } diff --git a/src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.test.ts b/src/fetcher/connections/hydra/ConnectionsIteratorLazy.test.ts similarity index 100% rename from src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.test.ts rename to src/fetcher/connections/hydra/ConnectionsIteratorLazy.test.ts diff --git a/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts b/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts index 4a327bf0..38bf6026 100644 --- a/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts +++ b/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts @@ -4,7 +4,7 @@ import TravelMode from "../../../TravelMode"; import FlatMapIterator from "../../../util/iterators/FlatMapIterator"; import IConnection from "../IConnection"; import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; -import ConnectionsPageParser from "../ld-fetch/ConnectionsPageParser"; +import ConnectionsPageParser from "./ConnectionsPageParser"; import HydraPageIterator from "./HydraPageIterator"; import IHydraPage from "./IHydraPage"; import IHydraPageIteratorConfig from "./IHydraPageIteratorConfig"; diff --git a/src/fetcher/connections/ld-fetch/ConnectionsPageParser.ts b/src/fetcher/connections/hydra/ConnectionsPageParser.ts similarity index 100% rename from src/fetcher/connections/ld-fetch/ConnectionsPageParser.ts rename to src/fetcher/connections/hydra/ConnectionsPageParser.ts diff --git a/src/fetcher/connections/ld-fetch/ConnectionsFetcherLazy.ts b/src/fetcher/connections/ld-fetch/ConnectionsFetcherLazy.ts deleted file mode 100644 index 18dd7e66..00000000 --- a/src/fetcher/connections/ld-fetch/ConnectionsFetcherLazy.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { AsyncIterator } from "asynciterator"; -import { inject, injectable } from "inversify"; -import LDFetch from "ldfetch"; -import TravelMode from "../../../TravelMode"; -import TYPES from "../../../types"; -import IConnection from "../IConnection"; -import IConnectionsFetcher from "../IConnectionsFetcher"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; -import ConnectionsIteratorLazy from "./ConnectionsIteratorLazy"; - -/** - * Wraps the [[ConnectionsIteratorLazy]] - * @implements IConnectionsFetcher - */ -@injectable() -export default class ConnectionsFetcherLazy implements IConnectionsFetcher { - - protected readonly ldFetch: LDFetch; - protected config: IConnectionsFetcherConfig; - private travelMode: TravelMode; - private accessUrl: string; - - constructor(@inject(TYPES.LDFetch) ldFetch: LDFetch) { - this.ldFetch = ldFetch; - } - - public setTravelMode(travelMode: TravelMode) { - this.travelMode = travelMode; - } - - public setAccessUrl(accessUrl: string) { - this.accessUrl = accessUrl; - } - - public createIterator(): AsyncIterator { - return new ConnectionsIteratorLazy( - this.accessUrl, - this.travelMode, - this.ldFetch, - this.config, - ); - } - - public setConfig(config: IConnectionsFetcherConfig): void { - this.config = config; - } -} diff --git a/src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.ts b/src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.ts deleted file mode 100644 index 5308d950..00000000 --- a/src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { BufferedIterator } from "asynciterator"; -import LdFetch from "ldfetch"; -import UriTemplate from "uritemplate"; -import TravelMode from "../../../TravelMode"; -import IConnection from "../IConnection"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; -import HydraPageParser from "./HydraPageParser"; -import IHydraPage from "./IHydraPage"; - -/** - * Base class for fetching linked connections with LDFetch and letting the caller iterate over them asynchronously - * through implementing the AsyncIterator protocol. - * LDFetch returns documents as an array of RDF triples. - * The meta Hydra triples are used for paginating to the next or previous page. - * The triples that describe linked connections get deserialized to instances of [[IConnection]] - */ -export default class ConnectionsIteratorLazy extends BufferedIterator { - private readonly baseUrl: string; - private readonly travelMode: TravelMode; - private readonly ldFetch: LdFetch; - private readonly config: IConnectionsFetcherConfig; - - private currentPage: IHydraPage; - - constructor( - baseUrl: string, - travelMode: TravelMode, - ldFetch: LdFetch, - config: IConnectionsFetcherConfig, - ) { - super({ - autoStart: true, - }); - - this.baseUrl = baseUrl; - this.travelMode = travelMode; - this.ldFetch = ldFetch; - this.config = config; - } - - public _begin(done: () => void): void { - this.ldFetch.get(this.baseUrl) - .then((response) => { - const parser = new HydraPageParser(response.triples); - const searchTemplate: UriTemplate = parser.getSearchTemplate(); - - const departureTimeDate = this.config.backward ? - this.config.upperBoundDate : this.config.lowerBoundDate; - - const firstPageIri = searchTemplate.expand({ - departureTime: departureTimeDate.toISOString(), - }); - - this.loadPage(firstPageIri) - .then(() => done()); - }); - } - - public _read(count: number, done: () => void): void { - - const pageIri = this.config.backward ? - this.currentPage.previousPageIri : this.currentPage.nextPageIri; - - this.loadPage(pageIri) - .then(() => done()); - } - - private async loadPage(url: string) { - await this.ldFetch.get(url) - .then((response) => { - - const parser = new HydraPageParser(response.triples); - const page = parser.getPage(0, this.travelMode); - - if (this.config.backward) { - page.previousPageIri = parser.getPreviousPageIri(); - - } else { - page.nextPageIri = parser.getNextPageIri(); - } - - this.currentPage = page; - this.pushCurrentPage(); - }); - } - - private pushCurrentPage(): void { - const { connections } = this.currentPage; - - if (this.config.backward) { - let c = connections.length - 1; - - while (c >= 0) { - this._push(connections[c]); - c--; - } - - // Forwards - } else { - for (const connection of connections) { - this._push(connection); - } - } - } - -} diff --git a/src/fetcher/connections/ld-fetch/HydraPageParser.ts b/src/fetcher/connections/ld-fetch/HydraPageParser.ts deleted file mode 100644 index e28779a3..00000000 --- a/src/fetcher/connections/ld-fetch/HydraPageParser.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Triple } from "rdf-js"; -import UriTemplate from "uritemplate"; -import TravelMode from "../../../TravelMode"; -import Rdf from "../../../util/Rdf"; -import ConnectionsPageParser from "./ConnectionsPageParser"; -import IHydraPage from "./IHydraPage"; - -/** - * Searches the given array of triples for hydra meta data, like the search template and next/previous page iris - * Also allows getting the contained [[IHydraPage]], which holds an array of [[IConnection]]s - */ -export default class HydraPageParser { - private readonly triples: Triple[]; - private readonly documentIri: string; - - constructor(triples: Triple[]) { - this.triples = triples; - this.documentIri = this.getDocumentIri(); - } - - public getPage(index: number, travelMode: TravelMode): IHydraPage { - const connectionsParser = new ConnectionsPageParser(this.documentIri, this.triples); - const connections = connectionsParser.getConnections(travelMode); - - return { - index, - documentIri: this.documentIri, - connections, - }; - } - - public getSearchTemplate(): UriTemplate { - const searchTriple = this.triples.find( - Rdf.matchesTriple(this.documentIri, "http://www.w3.org/ns/hydra/core#search", null), - ); - - const templateTriple = this.triples.find( - Rdf.matchesTriple(searchTriple.object.value, "http://www.w3.org/ns/hydra/core#template", null), - ); - - const template = templateTriple.object.value; - return UriTemplate.parse(template); - } - - public getNextPageIri(): string { - const nextPageTriple: Triple = this.triples.find( - Rdf.matchesTriple(this.documentIri, "http://www.w3.org/ns/hydra/core#next", null), - ); - - if (nextPageTriple && nextPageTriple.object.value.substr(0, 4) === "http") { - return nextPageTriple.object.value; - } - } - - public getPreviousPageIri(): string { - const previousPageTriple: Triple = this.triples.find( - Rdf.matchesTriple(this.documentIri, "http://www.w3.org/ns/hydra/core#previous", null), - ); - - if (previousPageTriple && previousPageTriple.object.value.substr(0, 4) === "http") { - return previousPageTriple.object.value; - } - } - - private getDocumentIri(): string { - // Type can be either http://www.w3.org/ns/hydra/core#PartialCollectionView - // or http://www.w3.org/ns/hydra/core#PagedCollection - - let typeTriple = this.triples.find( - Rdf.matchesTriple( - null, - "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", - "http://www.w3.org/ns/hydra/core#PartialCollectionView", - ), - ); - - if (!typeTriple) { - typeTriple = this.triples.find( - Rdf.matchesTriple( - null, - "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", - "http://www.w3.org/ns/hydra/core#PagedCollection", - ), - ); - } - - if (!typeTriple) { - throw new Error("Hydra page doesn`t have type triple"); - } - - return typeTriple.subject.value; - } -} diff --git a/src/fetcher/connections/ld-fetch/IHydraPage.ts b/src/fetcher/connections/ld-fetch/IHydraPage.ts deleted file mode 100644 index be94cad5..00000000 --- a/src/fetcher/connections/ld-fetch/IHydraPage.ts +++ /dev/null @@ -1,9 +0,0 @@ -import IConnection from "../IConnection"; - -export default interface IHydraPage { - index: number; - documentIri: string; - nextPageIri?: string; - previousPageIri?: string; - connections: IConnection[]; -} diff --git a/src/fetcher/connections/tests/ConnectionsFetcherNMBSTest.ts b/src/fetcher/connections/tests/ConnectionsFetcherNMBSTest.ts index f512dc0f..4e96f848 100644 --- a/src/fetcher/connections/tests/ConnectionsFetcherNMBSTest.ts +++ b/src/fetcher/connections/tests/ConnectionsFetcherNMBSTest.ts @@ -14,6 +14,10 @@ export default class ConnectionsFetcherNMBSTest implements IConnectionsFetcher { this.connections = connections; } + public prefetchConnections(): void { + return; + } + public setConfig(config: IConnectionsFetcherConfig): void { this.config = config; } diff --git a/src/fetcher/stops/IStopsProvider.ts b/src/fetcher/stops/IStopsProvider.ts index 95ef130f..6f467568 100644 --- a/src/fetcher/stops/IStopsProvider.ts +++ b/src/fetcher/stops/IStopsProvider.ts @@ -7,6 +7,7 @@ import IStop from "./IStop"; * @method getAllStops Returns concatenated array of [[IStop]]s from all [[IStopsFetcher]]s it mediates */ export default interface IStopsProvider { + prefetchStops: () => void; getStopById: (stopId: string) => Promise; getAllStops: () => Promise; } diff --git a/src/fetcher/stops/StopsProviderDefault.ts b/src/fetcher/stops/StopsProviderDefault.ts index ffe9b65a..0a84d8ee 100644 --- a/src/fetcher/stops/StopsProviderDefault.ts +++ b/src/fetcher/stops/StopsProviderDefault.ts @@ -21,6 +21,12 @@ export default class StopsProviderDefault implements IStopsProvider { } } + public prefetchStops(): void { + for (const stopsFetcher of this.stopsFetchers) { + stopsFetcher.prefetchStops(); + } + } + public async getStopById(stopId: string): Promise { return Promise.all(this.stopsFetchers .map((stopsFetcher: IStopsFetcher) => stopsFetcher.getStopById(stopId)), diff --git a/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.ts b/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.ts index d7ef11b9..8812fc53 100644 --- a/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.ts +++ b/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.ts @@ -34,6 +34,10 @@ export default class StopsFetcherLDFetch implements IStopsFetcher { this.accessUrl = accessUrl; } + public prefetchStops(): void { + this.ensureStopsLoaded(); + } + public async getStopById(stopId: string): Promise { await this.ensureStopsLoaded(); diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 0730dd27..86b9611a 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -3,10 +3,10 @@ import Catalog from "./Catalog"; import catalogDeLijn from "./catalog.delijn"; import catalogNmbs from "./catalog.nmbs"; import Context from "./Context"; +import ConnectionsProviderMerge from "./fetcher/connections/ConnectionsProviderMerge"; import ConnectionsFetcherLazy from "./fetcher/connections/hydra/ConnectionsFetcherLazy"; import IConnectionsFetcher from "./fetcher/connections/IConnectionsFetcher"; import IConnectionsProvider from "./fetcher/connections/IConnectionsProvider"; -import ConnectionsProviderMerge from "./fetcher/connections/merge/ConnectionsProviderMerge"; import LDFetch from "./fetcher/LDFetch"; import IStopsFetcher from "./fetcher/stops/IStopsFetcher"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts index 85927f8b..cf7be7f2 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts @@ -1,7 +1,7 @@ import "jest"; import LDFetch from "ldfetch"; import Defaults from "../../Defaults"; -import ConnectionsFetcherLazy from "../../fetcher/connections/ld-fetch/ConnectionsFetcherLazy"; +import ConnectionsFetcherLazy from "../../fetcher/connections/hydra/ConnectionsFetcherLazy"; import ConnectionsFetcherNMBSTest from "../../fetcher/connections/tests/ConnectionsFetcherNMBSTest"; import connectionsIngelmunsterGhent from "../../fetcher/connections/tests/data/ingelmunster-ghent"; import connectionsJoining from "../../fetcher/connections/tests/data/joining"; diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index 06985aef..ca1807cf 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -1,7 +1,7 @@ import "jest"; import LDFetch from "ldfetch"; import Context from "../../Context"; -import ConnectionsFetcherLazy from "../../fetcher/connections/ld-fetch/ConnectionsFetcherLazy"; +import ConnectionsFetcherLazy from "../../fetcher/connections/hydra/ConnectionsFetcherLazy"; import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import IPath from "../../interfaces/IPath"; import IStep from "../../interfaces/IStep"; diff --git a/src/test/test-connections-iterator-2.ts b/src/test/test-connections-iterator-2.ts index ac9a9cfb..24f82fba 100644 --- a/src/test/test-connections-iterator-2.ts +++ b/src/test/test-connections-iterator-2.ts @@ -1,5 +1,5 @@ import LDFetch from "ldfetch"; -import ConnectionsIteratorLazy from "../fetcher/connections/ld-fetch/ConnectionsIteratorLazy"; +import ConnectionsIteratorLazy from "../fetcher/connections/hydra/ConnectionsIteratorLazy"; import TravelMode from "../TravelMode"; const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); diff --git a/src/util/iterators/FlatMapIterator.ts b/src/util/iterators/FlatMapIterator.ts index b7c666ec..82061a3f 100644 --- a/src/util/iterators/FlatMapIterator.ts +++ b/src/util/iterators/FlatMapIterator.ts @@ -11,8 +11,6 @@ export default class FlatMapIterator extends BufferedIterator { constructor(queryIterator: AsyncIterator, run: (query: Q) => Promise>) { super({maxBufferSize: 1, autoStart: false}); - console.log("FlatMap"); - this.queryIterator = queryIterator; this.callback = run; From a7cdc3be0ecbb72049a23023263b642df6586420 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 11 Jan 2019 11:02:38 +0100 Subject: [PATCH 06/69] Generalise FilterUniqueIterator --- src/planner/Path.ts | 25 ++++++++-------- .../exponential/FilterUniquePathsIterator.ts | 29 ------------------- .../exponential/QueryRunnerExponential.ts | 5 ++-- .../iterators/FilterUniqueIterator.test.ts | 25 ++++++++++++++++ src/util/iterators/FilterUniqueIterator.ts | 29 +++++++++++++++++++ 5 files changed, 69 insertions(+), 44 deletions(-) delete mode 100644 src/query-runner/exponential/FilterUniquePathsIterator.ts create mode 100644 src/util/iterators/FilterUniqueIterator.test.ts create mode 100644 src/util/iterators/FilterUniqueIterator.ts diff --git a/src/planner/Path.ts b/src/planner/Path.ts index ac79d24c..c20cd874 100644 --- a/src/planner/Path.ts +++ b/src/planner/Path.ts @@ -10,6 +10,18 @@ export default class Path implements IPath { ); } + public static compareEquals(path: IPath, otherPath: IPath): boolean { + if (path.steps.length !== otherPath.steps.length) { + return false; + } + + return path.steps.every((step, stepIndex) => { + const otherStep = otherPath.steps[stepIndex]; + + return Step.compareEquals(step, otherStep); + }); + } + public steps: IStep[]; constructor(steps: IStep[]) { @@ -19,17 +31,4 @@ export default class Path implements IPath { public addStep(step: IStep): void { this.steps.push(step); } - - public equals(path: IPath): boolean { - - if (this.steps.length !== path.steps.length) { - return false; - } - - return this.steps.every((step, stepIndex) => { - const otherStep = path.steps[stepIndex]; - - return Step.compareEquals(step, otherStep); - }); - } } diff --git a/src/query-runner/exponential/FilterUniquePathsIterator.ts b/src/query-runner/exponential/FilterUniquePathsIterator.ts deleted file mode 100644 index bec79048..00000000 --- a/src/query-runner/exponential/FilterUniquePathsIterator.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AsyncIterator, SimpleTransformIterator } from "asynciterator"; -import IPath from "../../interfaces/IPath"; -import Path from "../../planner/Path"; - -export default class FilterUniquePathsIterator extends SimpleTransformIterator { - - private store: Path[]; - - constructor(source: AsyncIterator) { - super(source, { - maxBufferSize: 1, - autoStart: false, - }); - - this.store = []; - } - - public _filter(path: IPath): boolean { - - const isUnique = !this.store - .some((storedPath: Path) => storedPath.equals(path)); - - if (isUnique) { - this.store.push(path as Path); - } - - return isUnique; - } -} diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index 6c24959b..950b940e 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -7,15 +7,16 @@ import EventType from "../../EventType"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; import IQuery from "../../interfaces/IQuery"; +import Path from "../../planner/Path"; import IPublicTransportPlanner from "../../planner/public-transport/IPublicTransportPlanner"; import TYPES from "../../types"; +import FilterUniqueIterator from "../../util/iterators/FilterUniqueIterator"; import FlatMapIterator from "../../util/iterators/FlatMapIterator"; import Units from "../../util/Units"; import ILocationResolver from "../ILocationResolver"; import IQueryRunner from "../IQueryRunner"; import IResolvedQuery from "../IResolvedQuery"; import ExponentialQueryIterator from "./ExponentialQueryIterator"; -import FilterUniquePathsIterator from "./FilterUniquePathsIterator"; @injectable() export default class QueryRunnerExponential implements IQueryRunner { @@ -47,7 +48,7 @@ export default class QueryRunnerExponential implements IQueryRunner { this.runSubquery.bind(this), ); - return new FilterUniquePathsIterator(subqueryIterator); + return new FilterUniqueIterator(subqueryIterator, Path.compareEquals); } else { throw new InvalidQueryError("Query should have publicTransportOnly = true"); diff --git a/src/util/iterators/FilterUniqueIterator.test.ts b/src/util/iterators/FilterUniqueIterator.test.ts new file mode 100644 index 00000000..41bb127c --- /dev/null +++ b/src/util/iterators/FilterUniqueIterator.test.ts @@ -0,0 +1,25 @@ +import { ArrayIterator } from "asynciterator"; +import "jest"; +import FilterUniqueIterator from "./FilterUniqueIterator"; + +describe("[FilterUniqueIterator]", () => { + + it("basic", (done) => { + + const numberIterator = new ArrayIterator([1, 1, 2, 3, 4, 5, 5, 5, 5, 6, 1, 5, 3, 5, 8]); + const filterUniqueIterator = new FilterUniqueIterator( + numberIterator, + (a, b) => a === b, + ); + + let current = 0; + const expected = [1, 2, 3, 4, 5, 6, 8]; + + filterUniqueIterator.each((str: number) => { + expect(expected[current++]).toBe(str); + }); + + filterUniqueIterator.on("end", () => done()); + }); + +}); diff --git a/src/util/iterators/FilterUniqueIterator.ts b/src/util/iterators/FilterUniqueIterator.ts new file mode 100644 index 00000000..d8974074 --- /dev/null +++ b/src/util/iterators/FilterUniqueIterator.ts @@ -0,0 +1,29 @@ +import { AsyncIterator, SimpleTransformIterator } from "asynciterator"; + +export default class FilterUniqueIterator extends SimpleTransformIterator { + + private readonly comparator: (object: T, otherObject: T) => boolean; + private store: T[]; + + constructor(source: AsyncIterator, comparator: (object: T, otherObject: T) => boolean) { + super(source, { + maxBufferSize: 1, + autoStart: false, + }); + + this.comparator = comparator; + this.store = []; + } + + public _filter(object: T): boolean { + + const isUnique = !this.store + .some((storedObject: T) => this.comparator(object, storedObject)); + + if (isUnique) { + this.store.push(object); + } + + return isUnique; + } +} From 606ffc2bfbfde0f9a451c4f46fe0919514a50ffa Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 11 Jan 2019 11:21:00 +0100 Subject: [PATCH 07/69] Put all enums in one folder --- src/Catalog.ts | 2 +- src/Context.ts | 2 +- src/Planner.ts | 2 +- src/catalog.delijn.ts | 2 +- src/catalog.nmbs.ts | 2 +- src/demo.ts | 2 +- src/{fetcher/connections => enums}/DropOffType.ts | 0 src/{ => enums}/EventType.ts | 0 src/{fetcher/connections => enums}/PickupType.ts | 0 .../stops => enums}/ReachableStopsFinderMode.ts | 0 .../stops => enums}/ReachableStopsSearchPhase.ts | 0 src/{ => enums}/TravelMode.ts | 0 src/errors/InvalidQueryError.ts | 2 +- src/fetcher/LDFetch.ts | 2 +- src/fetcher/connections/IConnection.ts | 6 +++--- src/fetcher/connections/hydra/ConnectionsPageParser.ts | 6 +++--- .../{hydra => lazy}/ConnectionsFetcherLazy.ts | 2 +- .../{hydra => lazy}/ConnectionsIteratorLazy.test.ts | 2 +- .../{hydra => lazy}/ConnectionsIteratorLazy.ts | 10 +++++----- .../connections/prefetch/ConnectionsPrefetcher.ts | 0 .../connections/tests/data/ingelmunster-ghent.ts | 2 +- src/fetcher/connections/tests/data/joining.ts | 6 +++--- src/fetcher/connections/tests/data/splitting.ts | 6 +++--- src/interfaces/IStep.ts | 2 +- src/inversify.config.ts | 6 +++--- src/planner/Step.ts | 2 +- src/planner/public-transport/CSA/util/ProfileUtil.ts | 2 +- .../public-transport/JourneyExtractorDefault.ts | 2 +- .../PublicTransportPlannerCSAProfile.test.ts | 4 ++-- .../PublicTransportPlannerCSAProfile.ts | 10 +++++----- src/planner/road/RoadPlannerBirdsEye.ts | 2 +- src/planner/stops/IReachableStopsFinder.ts | 2 +- src/planner/stops/ReachableStopsFinderBirdsEye.test.ts | 2 +- src/planner/stops/ReachableStopsFinderBirdsEye.ts | 2 +- .../stops/ReachableStopsFinderBirdsEyeCached.ts | 2 +- src/planner/stops/ReachableStopsFinderOnlySelf.ts | 2 +- .../stops/ReachableStopsFinderRoadPlanner.test.ts | 2 +- src/planner/stops/ReachableStopsFinderRoadPlanner.ts | 2 +- .../stops/ReachableStopsFinderRoadPlannerCached.ts | 2 +- .../exponential/QueryRunnerExponential.test.ts | 4 ++-- src/query-runner/exponential/QueryRunnerExponential.ts | 2 +- src/test/test-connections-iterator-2.ts | 4 ++-- src/test/test-connections-iterator.ts | 2 +- src/types.ts | 2 +- src/util/iterators/Emiterator.ts | 2 +- 45 files changed, 59 insertions(+), 59 deletions(-) rename src/{fetcher/connections => enums}/DropOffType.ts (100%) rename src/{ => enums}/EventType.ts (100%) rename src/{fetcher/connections => enums}/PickupType.ts (100%) rename src/{planner/stops => enums}/ReachableStopsFinderMode.ts (100%) rename src/{planner/stops => enums}/ReachableStopsSearchPhase.ts (100%) rename src/{ => enums}/TravelMode.ts (100%) rename src/fetcher/connections/{hydra => lazy}/ConnectionsFetcherLazy.ts (96%) rename src/fetcher/connections/{hydra => lazy}/ConnectionsIteratorLazy.test.ts (95%) rename src/fetcher/connections/{hydra => lazy}/ConnectionsIteratorLazy.ts (84%) create mode 100644 src/fetcher/connections/prefetch/ConnectionsPrefetcher.ts diff --git a/src/Catalog.ts b/src/Catalog.ts index c02fdd6b..ebacd53d 100644 --- a/src/Catalog.ts +++ b/src/Catalog.ts @@ -1,4 +1,4 @@ -import TravelMode from "./TravelMode"; +import TravelMode from "./enums/TravelMode"; export default class Catalog { diff --git a/src/Context.ts b/src/Context.ts index f57d1e60..57cf66eb 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -1,7 +1,7 @@ // @ts-ignore import { EventEmitter, Listener } from "events"; import { Container, injectable } from "inversify"; -import EventType from "./EventType"; +import EventType from "./enums/EventType"; /** * The Context serves as event pass through and holder of the inversify container object. diff --git a/src/Planner.ts b/src/Planner.ts index 0b1f3b2f..997b732d 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -2,7 +2,7 @@ import { AsyncIterator } from "asynciterator"; // @ts-ignore import { EventEmitter, Listener } from "events"; import Context from "./Context"; -import EventType from "./EventType"; +import EventType from "./enums/EventType"; import IConnectionsProvider from "./fetcher/connections/IConnectionsProvider"; import IStop from "./fetcher/stops/IStop"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; diff --git a/src/catalog.delijn.ts b/src/catalog.delijn.ts index 95726afd..eeaec78a 100644 --- a/src/catalog.delijn.ts +++ b/src/catalog.delijn.ts @@ -1,5 +1,5 @@ import Catalog from "./Catalog"; -import TravelMode from "./TravelMode"; +import TravelMode from "./enums/TravelMode"; const catalog = new Catalog(); diff --git a/src/catalog.nmbs.ts b/src/catalog.nmbs.ts index f9edf2e8..554f04a7 100644 --- a/src/catalog.nmbs.ts +++ b/src/catalog.nmbs.ts @@ -1,5 +1,5 @@ import Catalog from "./Catalog"; -import TravelMode from "./TravelMode"; +import TravelMode from "./enums/TravelMode"; const catalog = new Catalog(); catalog.addStopsFetcher("https://irail.be/stations/NMBS"); diff --git a/src/demo.ts b/src/demo.ts index c569c851..8a3191c1 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -1,4 +1,4 @@ -import EventType from "./EventType"; +import EventType from "./enums/EventType"; import Planner from "./index"; import IPath from "./interfaces/IPath"; import Units from "./util/Units"; diff --git a/src/fetcher/connections/DropOffType.ts b/src/enums/DropOffType.ts similarity index 100% rename from src/fetcher/connections/DropOffType.ts rename to src/enums/DropOffType.ts diff --git a/src/EventType.ts b/src/enums/EventType.ts similarity index 100% rename from src/EventType.ts rename to src/enums/EventType.ts diff --git a/src/fetcher/connections/PickupType.ts b/src/enums/PickupType.ts similarity index 100% rename from src/fetcher/connections/PickupType.ts rename to src/enums/PickupType.ts diff --git a/src/planner/stops/ReachableStopsFinderMode.ts b/src/enums/ReachableStopsFinderMode.ts similarity index 100% rename from src/planner/stops/ReachableStopsFinderMode.ts rename to src/enums/ReachableStopsFinderMode.ts diff --git a/src/planner/stops/ReachableStopsSearchPhase.ts b/src/enums/ReachableStopsSearchPhase.ts similarity index 100% rename from src/planner/stops/ReachableStopsSearchPhase.ts rename to src/enums/ReachableStopsSearchPhase.ts diff --git a/src/TravelMode.ts b/src/enums/TravelMode.ts similarity index 100% rename from src/TravelMode.ts rename to src/enums/TravelMode.ts diff --git a/src/errors/InvalidQueryError.ts b/src/errors/InvalidQueryError.ts index e45a0ec2..13339a13 100644 --- a/src/errors/InvalidQueryError.ts +++ b/src/errors/InvalidQueryError.ts @@ -1,4 +1,4 @@ -import EventType from "../EventType"; +import EventType from "../enums/EventType"; export default class InvalidQueryError extends Error { public eventType = EventType.InvalidQuery; diff --git a/src/fetcher/LDFetch.ts b/src/fetcher/LDFetch.ts index fa5492a7..71ab21d2 100644 --- a/src/fetcher/LDFetch.ts +++ b/src/fetcher/LDFetch.ts @@ -2,7 +2,7 @@ import { inject, injectable } from "inversify"; import LDFetchBase from "ldfetch"; import { Triple } from "rdf-js"; import Context from "../Context"; -import EventType from "../EventType"; +import EventType from "../enums/EventType"; import TYPES from "../types"; export interface ILDFetchResponse { diff --git a/src/fetcher/connections/IConnection.ts b/src/fetcher/connections/IConnection.ts index 1be060e7..19c9669c 100644 --- a/src/fetcher/connections/IConnection.ts +++ b/src/fetcher/connections/IConnection.ts @@ -1,7 +1,7 @@ +import DropOffType from "../../enums/DropOffType"; +import PickupType from "../../enums/PickupType"; +import TravelMode from "../../enums/TravelMode"; import { DurationMs } from "../../interfaces/units"; -import TravelMode from "../../TravelMode"; -import DropOffType from "./DropOffType"; -import PickupType from "./PickupType"; /** * Interface for a Connection. This describes an actual transport vehicle going from its diff --git a/src/fetcher/connections/hydra/ConnectionsPageParser.ts b/src/fetcher/connections/hydra/ConnectionsPageParser.ts index a3a25fe9..084d34f2 100644 --- a/src/fetcher/connections/hydra/ConnectionsPageParser.ts +++ b/src/fetcher/connections/hydra/ConnectionsPageParser.ts @@ -1,10 +1,10 @@ import { Triple } from "rdf-js"; -import TravelMode from "../../../TravelMode"; +import DropOffType from "../../../enums/DropOffType"; +import PickupType from "../../../enums/PickupType"; +import TravelMode from "../../../enums/TravelMode"; import Rdf from "../../../util/Rdf"; import Units from "../../../util/Units"; -import DropOffType from "../DropOffType"; import IConnection from "../IConnection"; -import PickupType from "../PickupType"; interface IEntity { } diff --git a/src/fetcher/connections/hydra/ConnectionsFetcherLazy.ts b/src/fetcher/connections/lazy/ConnectionsFetcherLazy.ts similarity index 96% rename from src/fetcher/connections/hydra/ConnectionsFetcherLazy.ts rename to src/fetcher/connections/lazy/ConnectionsFetcherLazy.ts index f3df194e..ba5053d7 100644 --- a/src/fetcher/connections/hydra/ConnectionsFetcherLazy.ts +++ b/src/fetcher/connections/lazy/ConnectionsFetcherLazy.ts @@ -1,7 +1,7 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable } from "inversify"; import LDFetch from "ldfetch"; -import TravelMode from "../../../TravelMode"; +import TravelMode from "../../../enums/TravelMode"; import TYPES from "../../../types"; import IConnection from "../IConnection"; import IConnectionsFetcher from "../IConnectionsFetcher"; diff --git a/src/fetcher/connections/hydra/ConnectionsIteratorLazy.test.ts b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.test.ts similarity index 95% rename from src/fetcher/connections/hydra/ConnectionsIteratorLazy.test.ts rename to src/fetcher/connections/lazy/ConnectionsIteratorLazy.test.ts index 3195d75c..6db14111 100644 --- a/src/fetcher/connections/hydra/ConnectionsIteratorLazy.test.ts +++ b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.test.ts @@ -1,6 +1,6 @@ import "jest"; import LdFetch from "ldfetch"; -import TravelMode from "../../../TravelMode"; +import TravelMode from "../../../enums/TravelMode"; import IConnection from "../IConnection"; import ConnectionsIteratorLazy from "./ConnectionsIteratorLazy"; diff --git a/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts similarity index 84% rename from src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts rename to src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts index 38bf6026..f7e98658 100644 --- a/src/fetcher/connections/hydra/ConnectionsIteratorLazy.ts +++ b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts @@ -1,13 +1,13 @@ import { ArrayIterator } from "asynciterator"; import LdFetch from "ldfetch"; -import TravelMode from "../../../TravelMode"; +import TravelMode from "../../../enums/TravelMode"; import FlatMapIterator from "../../../util/iterators/FlatMapIterator"; +import ConnectionsPageParser from "../hydra/ConnectionsPageParser"; +import HydraPageIterator from "../hydra/HydraPageIterator"; +import IHydraPage from "../hydra/IHydraPage"; +import IHydraPageIteratorConfig from "../hydra/IHydraPageIteratorConfig"; import IConnection from "../IConnection"; import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; -import ConnectionsPageParser from "./ConnectionsPageParser"; -import HydraPageIterator from "./HydraPageIterator"; -import IHydraPage from "./IHydraPage"; -import IHydraPageIteratorConfig from "./IHydraPageIteratorConfig"; /** * Base class for fetching linked connections with LDFetch and letting the caller iterate over them asynchronously diff --git a/src/fetcher/connections/prefetch/ConnectionsPrefetcher.ts b/src/fetcher/connections/prefetch/ConnectionsPrefetcher.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/fetcher/connections/tests/data/ingelmunster-ghent.ts b/src/fetcher/connections/tests/data/ingelmunster-ghent.ts index 23ff7413..d4c5f019 100644 --- a/src/fetcher/connections/tests/data/ingelmunster-ghent.ts +++ b/src/fetcher/connections/tests/data/ingelmunster-ghent.ts @@ -1,4 +1,4 @@ -import TravelMode from "../../../../TravelMode"; +import TravelMode from "../../../../enums/TravelMode"; const connections = [ { diff --git a/src/fetcher/connections/tests/data/joining.ts b/src/fetcher/connections/tests/data/joining.ts index 99a31faa..f237967b 100644 --- a/src/fetcher/connections/tests/data/joining.ts +++ b/src/fetcher/connections/tests/data/joining.ts @@ -1,6 +1,6 @@ -import TravelMode from "../../../../TravelMode"; -import DropOffType from "../../DropOffType"; -import PickupType from "../../PickupType"; +import DropOffType from "../../../../enums/DropOffType"; +import PickupType from "../../../../enums/PickupType"; +import TravelMode from "../../../../enums/TravelMode"; const connections = [ { diff --git a/src/fetcher/connections/tests/data/splitting.ts b/src/fetcher/connections/tests/data/splitting.ts index 56fae16a..d625329d 100644 --- a/src/fetcher/connections/tests/data/splitting.ts +++ b/src/fetcher/connections/tests/data/splitting.ts @@ -1,6 +1,6 @@ -import TravelMode from "../../../../TravelMode"; -import DropOffType from "../../DropOffType"; -import PickupType from "../../PickupType"; +import DropOffType from "../../../../enums/DropOffType"; +import PickupType from "../../../../enums/PickupType"; +import TravelMode from "../../../../enums/TravelMode"; const connections = [ { diff --git a/src/interfaces/IStep.ts b/src/interfaces/IStep.ts index bf475b41..db6bbaa8 100644 --- a/src/interfaces/IStep.ts +++ b/src/interfaces/IStep.ts @@ -1,4 +1,4 @@ -import TravelMode from "../TravelMode"; +import TravelMode from "../enums/TravelMode"; import ILocation from "./ILocation"; import IProbabilisticValue from "./IProbabilisticValue"; import { DistanceM, DurationMs } from "./units"; diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 86b9611a..da6a6913 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -3,10 +3,12 @@ import Catalog from "./Catalog"; import catalogDeLijn from "./catalog.delijn"; import catalogNmbs from "./catalog.nmbs"; import Context from "./Context"; +import ReachableStopsSearchPhase from "./enums/ReachableStopsSearchPhase"; +import TravelMode from "./enums/TravelMode"; import ConnectionsProviderMerge from "./fetcher/connections/ConnectionsProviderMerge"; -import ConnectionsFetcherLazy from "./fetcher/connections/hydra/ConnectionsFetcherLazy"; import IConnectionsFetcher from "./fetcher/connections/IConnectionsFetcher"; import IConnectionsProvider from "./fetcher/connections/IConnectionsProvider"; +import ConnectionsFetcherLazy from "./fetcher/connections/lazy/ConnectionsFetcherLazy"; import LDFetch from "./fetcher/LDFetch"; import IStopsFetcher from "./fetcher/stops/IStopsFetcher"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; @@ -21,12 +23,10 @@ import RoadPlannerBirdsEye from "./planner/road/RoadPlannerBirdsEye"; import IReachableStopsFinder from "./planner/stops/IReachableStopsFinder"; import ReachableStopsFinderOnlySelf from "./planner/stops/ReachableStopsFinderOnlySelf"; import ReachableStopsFinderRoadPlannerCached from "./planner/stops/ReachableStopsFinderRoadPlannerCached"; -import ReachableStopsSearchPhase from "./planner/stops/ReachableStopsSearchPhase"; import QueryRunnerExponential from "./query-runner/exponential/QueryRunnerExponential"; import ILocationResolver from "./query-runner/ILocationResolver"; import IQueryRunner from "./query-runner/IQueryRunner"; import LocationResolverDefault from "./query-runner/LocationResolverDefault"; -import TravelMode from "./TravelMode"; import TYPES from "./types"; const container = new Container(); diff --git a/src/planner/Step.ts b/src/planner/Step.ts index d9a9b770..9d44315f 100644 --- a/src/planner/Step.ts +++ b/src/planner/Step.ts @@ -1,9 +1,9 @@ +import TravelMode from "../enums/TravelMode"; import IConnection from "../fetcher/connections/IConnection"; import ILocation from "../interfaces/ILocation"; import IProbabilisticValue from "../interfaces/IProbabilisticValue"; import IStep from "../interfaces/IStep"; import { DistanceM, DurationMs } from "../interfaces/units"; -import TravelMode from "../TravelMode"; export default class Step implements IStep { diff --git a/src/planner/public-transport/CSA/util/ProfileUtil.ts b/src/planner/public-transport/CSA/util/ProfileUtil.ts index f8317df9..2a178f4e 100644 --- a/src/planner/public-transport/CSA/util/ProfileUtil.ts +++ b/src/planner/public-transport/CSA/util/ProfileUtil.ts @@ -1,4 +1,4 @@ -import DropOffType from "../../../../fetcher/connections/DropOffType"; +import DropOffType from "../../../../enums/DropOffType"; import IConnection from "../../../../fetcher/connections/IConnection"; import { DurationMs } from "../../../../interfaces/units"; import IArrivalTimeByTransfers from "../data-structure/IArrivalTimeByTransfers"; diff --git a/src/planner/public-transport/JourneyExtractorDefault.ts b/src/planner/public-transport/JourneyExtractorDefault.ts index b4175ecd..74777c83 100644 --- a/src/planner/public-transport/JourneyExtractorDefault.ts +++ b/src/planner/public-transport/JourneyExtractorDefault.ts @@ -1,13 +1,13 @@ import { ArrayIterator, AsyncIterator } from "asynciterator"; import { inject, injectable } from "inversify"; import Context from "../../Context"; +import TravelMode from "../../enums/TravelMode"; import IConnection from "../../fetcher/connections/IConnection"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; import IStep from "../../interfaces/IStep"; import ILocationResolver from "../../query-runner/ILocationResolver"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; -import TravelMode from "../../TravelMode"; import TYPES from "../../types"; import Path from "../Path"; import Step from "../Step"; diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts index cf7be7f2..edcfb294 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts @@ -1,7 +1,8 @@ import "jest"; import LDFetch from "ldfetch"; import Defaults from "../../Defaults"; -import ConnectionsFetcherLazy from "../../fetcher/connections/hydra/ConnectionsFetcherLazy"; +import TravelMode from "../../enums/TravelMode"; +import ConnectionsFetcherLazy from "../../fetcher/connections/lazy/ConnectionsFetcherLazy"; import ConnectionsFetcherNMBSTest from "../../fetcher/connections/tests/ConnectionsFetcherNMBSTest"; import connectionsIngelmunsterGhent from "../../fetcher/connections/tests/data/ingelmunster-ghent"; import connectionsJoining from "../../fetcher/connections/tests/data/joining"; @@ -13,7 +14,6 @@ import IStep from "../../interfaces/IStep"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import LocationResolverDefault from "../../query-runner/LocationResolverDefault"; import QueryRunnerDefault from "../../query-runner/QueryRunnerDefault"; -import TravelMode from "../../TravelMode"; import Iterators from "../../util/Iterators"; import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBirdsEyeCached"; import JourneyExtractorDefault from "./JourneyExtractorDefault"; diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index b61c349c..b3da1a03 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -1,11 +1,13 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable, tagged } from "inversify"; import Context from "../../Context"; -import EventType from "../../EventType"; -import DropOffType from "../../fetcher/connections/DropOffType"; +import DropOffType from "../../enums/DropOffType"; +import EventType from "../../enums/EventType"; +import PickupType from "../../enums/PickupType"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; +import ReachableStopsSearchPhase from "../../enums/ReachableStopsSearchPhase"; import IConnection from "../../fetcher/connections/IConnection"; import IConnectionsProvider from "../../fetcher/connections/IConnectionsProvider"; -import PickupType from "../../fetcher/connections/PickupType"; import IStop from "../../fetcher/stops/IStop"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; @@ -15,8 +17,6 @@ import IResolvedQuery from "../../query-runner/IResolvedQuery"; import TYPES from "../../types"; import Vectors from "../../util/Vectors"; import IReachableStopsFinder, { IReachableStop } from "../stops/IReachableStopsFinder"; -import ReachableStopsFinderMode from "../stops/ReachableStopsFinderMode"; -import ReachableStopsSearchPhase from "../stops/ReachableStopsSearchPhase"; import IArrivalTimeByTransfers from "./CSA/data-structure/IArrivalTimeByTransfers"; import IProfilesByStop from "./CSA/data-structure/stops/IProfilesByStop"; import ITransferProfile from "./CSA/data-structure/stops/ITransferProfile"; diff --git a/src/planner/road/RoadPlannerBirdsEye.ts b/src/planner/road/RoadPlannerBirdsEye.ts index 636dce2e..43db4efb 100644 --- a/src/planner/road/RoadPlannerBirdsEye.ts +++ b/src/planner/road/RoadPlannerBirdsEye.ts @@ -1,11 +1,11 @@ import { ArrayIterator, AsyncIterator } from "asynciterator"; import { injectable } from "inversify"; +import TravelMode from "../../enums/TravelMode"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; import IProbabilisticValue from "../../interfaces/IProbabilisticValue"; import { DurationMs, SpeedkmH } from "../../interfaces/units"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; -import TravelMode from "../../TravelMode"; import Geo from "../../util/Geo"; import Units from "../../util/Units"; import Path from "../Path"; diff --git a/src/planner/stops/IReachableStopsFinder.ts b/src/planner/stops/IReachableStopsFinder.ts index 5ce8deb7..5d4ed34d 100644 --- a/src/planner/stops/IReachableStopsFinder.ts +++ b/src/planner/stops/IReachableStopsFinder.ts @@ -1,6 +1,6 @@ +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import { DurationMs, SpeedkmH } from "../../interfaces/units"; -import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; export default interface IReachableStopsFinder { findReachableStops: ( diff --git a/src/planner/stops/ReachableStopsFinderBirdsEye.test.ts b/src/planner/stops/ReachableStopsFinderBirdsEye.test.ts index 86523581..9abf9ea5 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEye.test.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEye.test.ts @@ -1,9 +1,9 @@ import "jest"; import LDFetch from "ldfetch"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import Units from "../../util/Units"; import ReachableStopsFinderBirdsEye from "./ReachableStopsFinderBirdsEye"; -import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; const DE_LIJN_STOPS_URLS = [ "http://openplanner.ilabt.imec.be/delijn/Antwerpen/stops", diff --git a/src/planner/stops/ReachableStopsFinderBirdsEye.ts b/src/planner/stops/ReachableStopsFinderBirdsEye.ts index defb77a9..bd420b96 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEye.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEye.ts @@ -1,4 +1,5 @@ import { inject, injectable } from "inversify"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import IStopsProvider from "../../fetcher/stops/IStopsProvider"; import { DurationMs, SpeedkmH } from "../../interfaces/units"; @@ -6,7 +7,6 @@ import TYPES from "../../types"; import Geo from "../../util/Geo"; import Units from "../../util/Units"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; -import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; @injectable() export default class ReachableStopsFinderBirdsEye implements IReachableStopsFinder { diff --git a/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts b/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts index cc290197..ac298bd3 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts @@ -1,11 +1,11 @@ import { inject, injectable } from "inversify"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import IStopsProvider from "../../fetcher/stops/IStopsProvider"; import { DurationMs, SpeedkmH } from "../../interfaces/units"; import TYPES from "../../types"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; import ReachableStopsFinderBirdsEye from "./ReachableStopsFinderBirdsEye"; -import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; @injectable() export default class ReachableStopsFinderBirdsEyeCached implements IReachableStopsFinder { diff --git a/src/planner/stops/ReachableStopsFinderOnlySelf.ts b/src/planner/stops/ReachableStopsFinderOnlySelf.ts index 18be32d2..332bbbac 100644 --- a/src/planner/stops/ReachableStopsFinderOnlySelf.ts +++ b/src/planner/stops/ReachableStopsFinderOnlySelf.ts @@ -1,8 +1,8 @@ import { injectable } from "inversify"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import { DurationMs, SpeedkmH } from "../../interfaces/units"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; -import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; @injectable() export default class ReachableStopsFinderOnlySelf implements IReachableStopsFinder { diff --git a/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts b/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts index e567b4a9..7fd4118e 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts @@ -1,9 +1,9 @@ import "jest"; import LDFetch from "ldfetch"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import Units from "../../util/Units"; import RoadPlannerBirdsEye from "../road/RoadPlannerBirdsEye"; -import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; import ReachableStopsFinderRoadPlanner from "./ReachableStopsFinderRoadPlanner"; const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); diff --git a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts index 9c9206dc..020d5c15 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts @@ -1,5 +1,6 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable } from "inversify"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import IStopsProvider from "../../fetcher/stops/IStopsProvider"; import ILocation from "../../interfaces/ILocation"; @@ -10,7 +11,6 @@ import TYPES from "../../types"; import Iterators from "../../util/Iterators"; import IRoadPlanner from "../road/IRoadPlanner"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; -import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; @injectable() export default class ReachableStopsFinderRoadPlanner implements IReachableStopsFinder { diff --git a/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts b/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts index 9a475f07..138a4343 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts @@ -1,11 +1,11 @@ import { inject, injectable } from "inversify"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import IStopsProvider from "../../fetcher/stops/IStopsProvider"; import { DurationMs, SpeedkmH } from "../../interfaces/units"; import TYPES from "../../types"; import IRoadPlanner from "../road/IRoadPlanner"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; -import ReachableStopsFinderMode from "./ReachableStopsFinderMode"; import ReachableStopsFinderRoadPlanner from "./ReachableStopsFinderRoadPlanner"; @injectable() diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index ca1807cf..614b0388 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -1,14 +1,14 @@ import "jest"; import LDFetch from "ldfetch"; import Context from "../../Context"; -import ConnectionsFetcherLazy from "../../fetcher/connections/hydra/ConnectionsFetcherLazy"; +import TravelMode from "../../enums/TravelMode"; +import ConnectionsFetcherLazy from "../../fetcher/connections/lazy/ConnectionsFetcherLazy"; import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import IPath from "../../interfaces/IPath"; import IStep from "../../interfaces/IStep"; import JourneyExtractorDefault from "../../planner/public-transport/JourneyExtractorDefault"; import PublicTransportPlannerCSAProfile from "../../planner/public-transport/PublicTransportPlannerCSAProfile"; import ReachableStopsFinderBirdsEyeCached from "../../planner/stops/ReachableStopsFinderBirdsEyeCached"; -import TravelMode from "../../TravelMode"; import Units from "../../util/Units"; import LocationResolverDefault from "../LocationResolverDefault"; import QueryRunnerExponential from "./QueryRunnerExponential"; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index 950b940e..128c570e 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -2,8 +2,8 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable, interfaces } from "inversify"; import Context from "../../Context"; import Defaults from "../../Defaults"; +import EventType from "../../enums/EventType"; import InvalidQueryError from "../../errors/InvalidQueryError"; -import EventType from "../../EventType"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; import IQuery from "../../interfaces/IQuery"; diff --git a/src/test/test-connections-iterator-2.ts b/src/test/test-connections-iterator-2.ts index 24f82fba..9172efd9 100644 --- a/src/test/test-connections-iterator-2.ts +++ b/src/test/test-connections-iterator-2.ts @@ -1,6 +1,6 @@ import LDFetch from "ldfetch"; -import ConnectionsIteratorLazy from "../fetcher/connections/hydra/ConnectionsIteratorLazy"; -import TravelMode from "../TravelMode"; +import TravelMode from "../enums/TravelMode"; +import ConnectionsIteratorLazy from "../fetcher/connections/lazy/ConnectionsIteratorLazy"; const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); diff --git a/src/test/test-connections-iterator.ts b/src/test/test-connections-iterator.ts index 19c9f10a..1f0534b6 100644 --- a/src/test/test-connections-iterator.ts +++ b/src/test/test-connections-iterator.ts @@ -1,6 +1,6 @@ import LDFetch from "ldfetch"; import "reflect-metadata"; -import TravelMode from "../TravelMode"; +import TravelMode from "../enums/TravelMode"; /* const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); diff --git a/src/types.ts b/src/types.ts index 146cd498..d8835c42 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ +import TravelMode from "./enums/TravelMode"; import IConnectionsFetcher from "./fetcher/connections/IConnectionsFetcher"; import IStopsFetcher from "./fetcher/stops/IStopsFetcher"; -import TravelMode from "./TravelMode"; const TYPES = { Context: Symbol("Context"), diff --git a/src/util/iterators/Emiterator.ts b/src/util/iterators/Emiterator.ts index 3b54f8a3..7d3d51d3 100644 --- a/src/util/iterators/Emiterator.ts +++ b/src/util/iterators/Emiterator.ts @@ -1,6 +1,6 @@ import { AsyncIterator, SimpleTransformIterator, SimpleTransformIteratorOptions } from "asynciterator"; import Context from "../../Context"; -import EventType from "../../EventType"; +import EventType from "../../enums/EventType"; /** * Lazily emits an event of specified type for each item that passes through source iterator From 35b2d65656eaf9d64ea1bec50b31b00eb49698c1 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 11 Jan 2019 14:08:56 +0100 Subject: [PATCH 08/69] Implemented first version of a ConnectionsStore and BinarySearch for use by ConnectionsProviderPrefetch --- .../prefetch/ConnectionsPrefetcher.ts | 0 .../prefetch/ConnectionsProviderPrefetch.ts | 65 +++++++++++++++ .../prefetch/ConnectionsStore.test.ts | 79 +++++++++++++++++++ .../connections/prefetch/ConnectionsStore.ts | 51 ++++++++++++ src/util/BinarySearch.test.ts | 37 +++++++++ src/util/BinarySearch.ts | 30 +++++++ 6 files changed, 262 insertions(+) delete mode 100644 src/fetcher/connections/prefetch/ConnectionsPrefetcher.ts create mode 100644 src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts create mode 100644 src/fetcher/connections/prefetch/ConnectionsStore.test.ts create mode 100644 src/fetcher/connections/prefetch/ConnectionsStore.ts create mode 100644 src/util/BinarySearch.test.ts create mode 100644 src/util/BinarySearch.ts diff --git a/src/fetcher/connections/prefetch/ConnectionsPrefetcher.ts b/src/fetcher/connections/prefetch/ConnectionsPrefetcher.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts new file mode 100644 index 00000000..3cac85af --- /dev/null +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -0,0 +1,65 @@ +import { AsyncIterator } from "asynciterator"; +import { inject, injectable } from "inversify"; +import Catalog from "../../../Catalog"; +import TYPES, { ConnectionsFetcherFactory } from "../../../types"; +import IConnection from "../IConnection"; +import IConnectionsFetcher from "../IConnectionsFetcher"; +import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import IConnectionsProvider from "../IConnectionsProvider"; +import ConnectionsStore from "./ConnectionsStore"; + +/** + * Passes through one [[IConnectionsFetcher]], the first one if there are multiple + * This provider is most/only useful if there is only one fetcher + */ +@injectable() +export default class ConnectionsProviderPrefetch implements IConnectionsProvider { + + private readonly connectionsFetcher: IConnectionsFetcher; + private readonly connectionsStore: ConnectionsStore; + + private startedPrefetching: boolean; + private connectionsIterator: AsyncIterator; + private connectionsFetcherConfig: IConnectionsFetcherConfig; + + constructor( + @inject(TYPES.ConnectionsFetcherFactory) connectionsFetcherFactory: ConnectionsFetcherFactory, + @inject(TYPES.Catalog) catalog: Catalog, + ) { + const { accessUrl, travelMode } = catalog.connectionsFetcherConfigs[0]; + + this.connectionsFetcher = connectionsFetcherFactory(accessUrl, travelMode); + this.connectionsStore = new ConnectionsStore(); + } + + public prefetchConnections(): void { + if (!this.startedPrefetching) { + this.startedPrefetching = true; + + setTimeout(() => { + const config: IConnectionsFetcherConfig = { + backward: false, + lowerBoundDate: new Date(), + }; + + this.connectionsFetcher.setConfig(config); + this.connectionsIterator = this.connectionsFetcher.createIterator(); + + this.connectionsIterator + .take(10000) + .each((connection: IConnection) => { + this.connectionsStore.append(connection); + }); + + }, 0); + } + } + + public createIterator(): AsyncIterator { + return this.connectionsStore.getIteratorView(this.connectionsFetcherConfig); + } + + public setConfig(config: IConnectionsFetcherConfig): void { + this.connectionsFetcherConfig = config; + } +} diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts new file mode 100644 index 00000000..fd491884 --- /dev/null +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -0,0 +1,79 @@ +import {AsyncIterator} from "asynciterator"; +import "jest"; +import IConnection from "../IConnection"; +import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import ConnectionsStore from "./ConnectionsStore"; + +describe("[ConnectionsStore]", () => { + + /** + * In this test, departureTime dates are substituted for numbers for simplicity + * Inside the ConnectionsStore, #valueOf() gets called on the connection.departureTime + * and both Dates and Numbers return a number + */ + + const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; + // @ts-ignore + const fakeConnections: IConnection[] = fakeDepartureTimes + .map((departureTime) => ({departureTime})); + + const connectionsStore = new ConnectionsStore(); + for (const connection of fakeConnections) { + connectionsStore.append(connection); + } + + it("iterator view: backward / upperBoundDate is loaded & exists in store", (done) => { + const fetcherConfig: IConnectionsFetcherConfig = { + backward: true, + upperBoundDate: (6 as unknown) as Date, + }; + const iteratorView: AsyncIterator = connectionsStore.getIteratorView(fetcherConfig); + + const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6]; + let current = expected.length - 1; + + iteratorView.each((str: IConnection) => { + console.log(str.departureTime); + expect(expected[current--]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => done()); + }); + + it("iterator view: backward / upperBoundDate is loaded but doesn\'t exist in store", (done) => { + const fetcherConfig: IConnectionsFetcherConfig = { + backward: true, + upperBoundDate: (4 as unknown) as Date, + }; + const iteratorView: AsyncIterator = connectionsStore.getIteratorView(fetcherConfig); + + const expected = [1, 2, 3, 3]; + let current = expected.length - 1; + + iteratorView.each((str: IConnection) => { + console.log(str.departureTime); + expect(expected[current--]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => done()); + }); + + it("iterator view: backward / upperBoundDate isn't loaded", (done) => { + const fetcherConfig: IConnectionsFetcherConfig = { + backward: true, + upperBoundDate: (10 as unknown) as Date, + }; + const iteratorView: AsyncIterator = connectionsStore.getIteratorView(fetcherConfig); + + const expected = [1, 2, 3, 3]; + let current = expected.length - 1; + + iteratorView.each((str: IConnection) => { + console.log(str.departureTime); + expect(expected[current--]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => done()); + }); + +}); diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts new file mode 100644 index 00000000..3edd1af3 --- /dev/null +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -0,0 +1,51 @@ +import { AsyncIterator, IntegerIterator, IntegerIteratorOptions } from "asynciterator"; +import BinarySearch from "../../../util/BinarySearch"; +import IConnection from "../IConnection"; +import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; + +// let lastDepartureTime; + +export default class ConnectionsStore { + private readonly store: IConnection[]; + + constructor() { + this.store = []; + } + + public append(connection: IConnection) { + // if (!lastDepartureTime) { + // lastDepartureTime = connection.departureTime; + // } + + // if (connection.departureTime > lastDepartureTime) { + // console.log(connection.departureTime); + // lastDepartureTime = connection.departureTime; + // } + + this.store.push(connection); + } + + public getIteratorView(fetcherConfig: IConnectionsFetcherConfig): AsyncIterator { + const {lowerBoundDate, upperBoundDate, backward} = fetcherConfig; + + if (backward && upperBoundDate) { + const upperBoundIndex = this.getUpperBoundIndex(upperBoundDate); + const lowerBoundIndex = 0; + + const indexIteratorOptions: IntegerIteratorOptions = { + start: upperBoundIndex, + end: lowerBoundIndex, + step: -1, + }; + + return new IntegerIterator(indexIteratorOptions) + .map((index) => this.store[index]); + } + } + + private getUpperBoundIndex(date: Date): number { + const binarySearch = new BinarySearch(this.store, (connection) => connection.departureTime.valueOf()); + + return binarySearch.findLastIndex(date.valueOf(), 0, this.store.length - 1); + } +} diff --git a/src/util/BinarySearch.test.ts b/src/util/BinarySearch.test.ts new file mode 100644 index 00000000..974c710b --- /dev/null +++ b/src/util/BinarySearch.test.ts @@ -0,0 +1,37 @@ +import "jest"; +import BinarySearch from "./BinarySearch"; + +describe("[BinarySearch]", () => { + + const array = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; + const search = new BinarySearch(array, (a) => a); + + it("findLastIndex: key exists", () => { + const resultIndex = search.findLastIndex(6); + const expectedIndex = 8; + + expect(resultIndex).toBe(expectedIndex); + }); + + it("findLastIndex: key doesn\'t exist / belongs at the start", () => { + const resultIndex = search.findLastIndex(0); + const expectedIndex = 0; + + expect(resultIndex).toBe(expectedIndex); + }); + + it("findLastIndex: key doesn\'t exist / belongs in middle", () => { + const resultIndex = search.findLastIndex(4); + const expectedIndex = 3; + + expect(resultIndex).toBe(expectedIndex); + }); + + it("findLastIndex: key doesn\'t exist / belongs at the end", () => { + const resultIndex = search.findLastIndex(8); + const expectedIndex = 10; + + expect(resultIndex).toBe(expectedIndex); + }); + +}); diff --git a/src/util/BinarySearch.ts b/src/util/BinarySearch.ts new file mode 100644 index 00000000..65343caf --- /dev/null +++ b/src/util/BinarySearch.ts @@ -0,0 +1,30 @@ +export default class BinarySearch { + private readonly array: T[]; + private readonly predicate: (item: T) => number; + + constructor(array: T[], predicate: (item: T) => number) { + this.array = array; + this.predicate = predicate; + } + + /** + * Find the last index of the given key, or the index after which that key would be hypothetically spliced in + * @param key + * @param start + * @param end + */ + public findLastIndex(key: number, start: number = 0, end: number = (this.array.length - 1)) { + while (start < end) { + const mid = start + Math.floor(((end - start) + 1) / 2); + + if (this.predicate(this.array[mid]) <= key) { + start = mid; + + } else { + end = mid - 1; + } + } + + return start; + } +} From d896c0fe0c5c80a8153d74937ef6ac6cc712e407 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 11 Jan 2019 15:48:13 +0100 Subject: [PATCH 09/69] Implemented ConnectionsProviderPrefetch and ConnectionsStore --- package-lock.json | 10 +- package.json | 1 + src/demo.ts | 94 ++++++++------ .../prefetch/ConnectionsProviderPrefetch.ts | 14 +- .../prefetch/ConnectionsStore.test.ts | 122 +++++++++++------- .../connections/prefetch/ConnectionsStore.ts | 82 +++++++++--- src/inversify.config.ts | 3 +- src/util/BinarySearch.ts | 1 + 8 files changed, 216 insertions(+), 111 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4164c09c..6917ae81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "plannerjs", - "version": "0.0.1-alpha", + "version": "0.0.2-alpha", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -843,6 +843,14 @@ "resolved": "https://registry.npmjs.org/asynciterator/-/asynciterator-2.0.1.tgz", "integrity": "sha512-aVLheZsDNU5qpOv6jZEHnFv79GfEi+N0w/OLmMmXZfGD8XFFmPsRhkSqleNl9jS6mqy/DNoV7tXGcI0S3cUvHQ==" }, + "asynciterator-promiseproxy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/asynciterator-promiseproxy/-/asynciterator-promiseproxy-2.0.0.tgz", + "integrity": "sha512-C0ub2jkCId4a66R9OuPa3Cvu+PF73yXEBErZc3NUlfLVXbYMmPF9vBUPzmCo3UuMdFmJLcfOjGxJpQ5a7z/G9A==", + "requires": { + "asynciterator": "^2.0.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", diff --git a/package.json b/package.json index d1f8ba83..f41785ae 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "asynciterator": "^2.0.1", + "asynciterator-promiseproxy": "^2.0.0", "haversine": "^1.1.0", "inversify": "^5.0.1", "isomorphic-fetch": "^2.2.1", diff --git a/src/demo.ts b/src/demo.ts index 8a3191c1..b1d7f5e9 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -7,12 +7,19 @@ export default async (logResults) => { const planner = new Planner(); + planner.prefetchStops(); + planner.prefetchConnections(); + if (logResults) { let scannedPages = 0; let scannedConnections = 0; // let logFetch = true; + if (logResults) { + console.log("Start prefetch"); + } + planner .on(EventType.InvalidQuery, (error) => { console.log("InvalidQuery", error); @@ -49,47 +56,54 @@ export default async (logResults) => { }); } - return new Promise((resolve, reject) => { - planner.query({ - publicTransportOnly: true, - // from: "https://data.delijn.be/stops/201657", - // to: "https://data.delijn.be/stops/205910", - // from: "https://data.delijn.be/stops/200455", // Deinze weg op Grammene +456 - // to: "https://data.delijn.be/stops/502481", // Tielt Metaalconstructie Goossens - // from: "https://data.delijn.be/stops/509927", // Tield Rameplein perron 1 - // to: "https://data.delijn.be/stops/200455", // Deinze weg op Grammene +456 - from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster - to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters - minimumDepartureTime: new Date(), - maximumTransferDuration: Units.fromHours(0.5), - }) - .then((publicTransportResult) => { - - const amount = 3; - let i = 0; - - publicTransportResult.take(amount) - .on("data", (path: IPath) => { - ++i; - - if (logResults) { - console.log(i); - console.log(JSON.stringify(path, null, " ")); - console.log("\n"); - } - - if (i === amount) { - resolve(true); - } - }) - .on("end", () => { - resolve(false); - }); + return wait(1000) + .then(() => new Promise((resolve, reject) => { + if (logResults) { + console.log("Start query"); + } + planner.query({ + publicTransportOnly: true, + // from: "https://data.delijn.be/stops/201657", + // to: "https://data.delijn.be/stops/205910", + // from: "https://data.delijn.be/stops/200455", // Deinze weg op Grammene +456 + // to: "https://data.delijn.be/stops/502481", // Tielt Metaalconstructie Goossens + // from: "https://data.delijn.be/stops/509927", // Tield Rameplein perron 1 + // to: "https://data.delijn.be/stops/200455", // Deinze weg op Grammene +456 + from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster + to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters + minimumDepartureTime: new Date(), + maximumTransferDuration: Units.fromHours(0.5), }) - .catch(() => { - resolve(false); - }); - }); + .then((publicTransportResult) => { + + const amount = 3; + let i = 0; + + publicTransportResult.take(amount) + .on("data", (path: IPath) => { + ++i; + + if (logResults) { + console.log(i); + console.log(JSON.stringify(path, null, " ")); + console.log("\n"); + } + + if (i === amount) { + resolve(true); + } + }) + .on("end", () => { + resolve(false); + }); + + }) + .catch(() => { + resolve(false); + }); + })); }; + +const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index 3cac85af..eeacdf9e 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -1,4 +1,5 @@ import { AsyncIterator } from "asynciterator"; +import { PromiseProxyIterator } from "asynciterator-promiseproxy"; import { inject, injectable } from "inversify"; import Catalog from "../../../Catalog"; import TYPES, { ConnectionsFetcherFactory } from "../../../types"; @@ -8,6 +9,8 @@ import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; import IConnectionsProvider from "../IConnectionsProvider"; import ConnectionsStore from "./ConnectionsStore"; +const MAX_CONNECTIONS = 20000; + /** * Passes through one [[IConnectionsFetcher]], the first one if there are multiple * This provider is most/only useful if there is only one fetcher @@ -46,17 +49,22 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.connectionsIterator = this.connectionsFetcher.createIterator(); this.connectionsIterator - .take(10000) + .take(MAX_CONNECTIONS) .each((connection: IConnection) => { this.connectionsStore.append(connection); }); - }, 0); } } public createIterator(): AsyncIterator { - return this.connectionsStore.getIteratorView(this.connectionsFetcherConfig); + if (this.startedPrefetching) { + return new PromiseProxyIterator(() => + this.connectionsStore.getIteratorView(this.connectionsFetcherConfig), + ); + } + + throw new Error("TODO"); } public setConfig(config: IConnectionsFetcherConfig): void { diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts index fd491884..96e2c4a3 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -12,68 +12,98 @@ describe("[ConnectionsStore]", () => { * and both Dates and Numbers return a number */ - const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; - // @ts-ignore - const fakeConnections: IConnection[] = fakeDepartureTimes - .map((departureTime) => ({departureTime})); - - const connectionsStore = new ConnectionsStore(); - for (const connection of fakeConnections) { - connectionsStore.append(connection); - } - - it("iterator view: backward / upperBoundDate is loaded & exists in store", (done) => { - const fetcherConfig: IConnectionsFetcherConfig = { - backward: true, - upperBoundDate: (6 as unknown) as Date, - }; - const iteratorView: AsyncIterator = connectionsStore.getIteratorView(fetcherConfig); + describe("All loaded", () => { + + const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; + // @ts-ignore + const fakeConnections: IConnection[] = fakeDepartureTimes + .map((departureTime) => ({departureTime})); + + const connectionsStore = new ConnectionsStore(); + for (const connection of fakeConnections) { + connectionsStore.append(connection); + } + + it("iterator view: backward / upperBoundDate is loaded & exists in store", async (done) => { + const fetcherConfig: IConnectionsFetcherConfig = { + backward: true, + upperBoundDate: (6 as unknown) as Date, + }; + const iteratorView: AsyncIterator = await connectionsStore.getIteratorView(fetcherConfig); - const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6]; - let current = expected.length - 1; + const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6]; + let current = expected.length - 1; - iteratorView.each((str: IConnection) => { - console.log(str.departureTime); - expect(expected[current--]).toBe(str.departureTime); + iteratorView.each((str: IConnection) => { + expect(expected[current--]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => done()); }); - iteratorView.on("end", () => done()); - }); + it("iterator view: backward / upperBoundDate is loaded but doesn\'t exist in store", async (done) => { + const fetcherConfig: IConnectionsFetcherConfig = { + backward: true, + upperBoundDate: (4 as unknown) as Date, + }; + const iteratorView: AsyncIterator = await connectionsStore.getIteratorView(fetcherConfig); - it("iterator view: backward / upperBoundDate is loaded but doesn\'t exist in store", (done) => { - const fetcherConfig: IConnectionsFetcherConfig = { - backward: true, - upperBoundDate: (4 as unknown) as Date, - }; - const iteratorView: AsyncIterator = connectionsStore.getIteratorView(fetcherConfig); + const expected = [1, 2, 3, 3]; + let current = expected.length - 1; - const expected = [1, 2, 3, 3]; - let current = expected.length - 1; + iteratorView.each((str: IConnection) => { + expect(expected[current--]).toBe(str.departureTime); + }); - iteratorView.each((str: IConnection) => { - console.log(str.departureTime); - expect(expected[current--]).toBe(str.departureTime); + iteratorView.on("end", () => done()); }); - iteratorView.on("end", () => done()); }); - it("iterator view: backward / upperBoundDate isn't loaded", (done) => { - const fetcherConfig: IConnectionsFetcherConfig = { - backward: true, - upperBoundDate: (10 as unknown) as Date, + describe("Loaded async", () => { + jest.setTimeout(10000); + + const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7, 8]; + // @ts-ignore + const fakeConnections: IConnection[] = fakeDepartureTimes + .map((departureTime) => ({departureTime})); + + const connectionsStore = new ConnectionsStore(); + + // Append first few connections sync + for (const connection of fakeConnections.slice(0, 6)) { + connectionsStore.append(connection); + } + + // Append remaining connections async + let i = 6; + const appendNext = () => { + connectionsStore.append(fakeConnections[i++]); + + if (i < fakeConnections.length) { + setTimeout(appendNext, 100); + } }; - const iteratorView: AsyncIterator = connectionsStore.getIteratorView(fetcherConfig); - const expected = [1, 2, 3, 3]; - let current = expected.length - 1; + setTimeout(appendNext, 100); + + it("iterator view: backward / upperBoundDate isn't loaded at first", async (done) => { + const fetcherConfig: IConnectionsFetcherConfig = { + backward: true, + upperBoundDate: (7 as unknown) as Date, + }; + const iteratorView: AsyncIterator = await connectionsStore.getIteratorView(fetcherConfig); + + const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; + let current = expected.length - 1; + + iteratorView.each((str: IConnection) => { + expect(expected[current--]).toBe(str.departureTime); + }); - iteratorView.each((str: IConnection) => { - console.log(str.departureTime); - expect(expected[current--]).toBe(str.departureTime); + iteratorView.on("end", () => done()); }); - iteratorView.on("end", () => done()); }); }); diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 3edd1af3..2b00fcb4 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -3,46 +3,88 @@ import BinarySearch from "../../../util/BinarySearch"; import IConnection from "../IConnection"; import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; -// let lastDepartureTime; +interface IBackwardPromise { + lowerBoundDate: Date; + upperBoundDate: Date; + resolve: (iterator: AsyncIterator) => void; +} export default class ConnectionsStore { private readonly store: IConnection[]; + private backwardPromises: IBackwardPromise[]; constructor() { this.store = []; + this.backwardPromises = []; } public append(connection: IConnection) { - // if (!lastDepartureTime) { - // lastDepartureTime = connection.departureTime; - // } + this.store.push(connection); - // if (connection.departureTime > lastDepartureTime) { - // console.log(connection.departureTime); - // lastDepartureTime = connection.departureTime; - // } + // Check if any backward promises are satisfied + if (this.backwardPromises.length) { + this.backwardPromises = this.backwardPromises + .filter(({ lowerBoundDate, upperBoundDate, resolve }) => { - this.store.push(connection); + if (connection.departureTime > upperBoundDate) { + const iteratorView = this.getBackwardIterator(lowerBoundDate, upperBoundDate); + + resolve(iteratorView); + return false; + } + + return true; + }); + } } - public getIteratorView(fetcherConfig: IConnectionsFetcherConfig): AsyncIterator { - const {lowerBoundDate, upperBoundDate, backward} = fetcherConfig; + public getIteratorView(fetcherConfig: IConnectionsFetcherConfig): Promise> { + const { lowerBoundDate, upperBoundDate, backward } = fetcherConfig; + + if (!backward) { + throw new Error("Backward is not yet supported"); + } if (backward && upperBoundDate) { - const upperBoundIndex = this.getUpperBoundIndex(upperBoundDate); - const lowerBoundIndex = 0; + const lastConnection = this.store[this.store.length - 1]; + const lastDepartureTime = lastConnection && lastConnection.departureTime; - const indexIteratorOptions: IntegerIteratorOptions = { - start: upperBoundIndex, - end: lowerBoundIndex, - step: -1, - }; + // If the store is still empty or the latest departure time isn't later than the upperBoundDate, + // then return a promise + if (!lastDepartureTime || lastDepartureTime <= upperBoundDate) { + const backwardPromise: Partial = { + lowerBoundDate, + upperBoundDate, + }; - return new IntegerIterator(indexIteratorOptions) - .map((index) => this.store[index]); + const promise = new Promise>((resolve, reject) => { + backwardPromise.resolve = resolve; + }); + + this.backwardPromises.push(backwardPromise as IBackwardPromise); + + return promise; + } + + // Else if the whole interval is available, return an iterator immediately + return Promise.resolve(this.getBackwardIterator(lowerBoundDate, upperBoundDate)); } } + private getBackwardIterator(lowerBoundDate, upperBoundDate): AsyncIterator { + const upperBoundIndex = this.getUpperBoundIndex(upperBoundDate); + const lowerBoundIndex = 0; + + const indexIteratorOptions: IntegerIteratorOptions = { + start: upperBoundIndex, + end: lowerBoundIndex, + step: -1, + }; + + return new IntegerIterator(indexIteratorOptions) + .map((index) => this.store[index]); + } + private getUpperBoundIndex(date: Date): number { const binarySearch = new BinarySearch(this.store, (connection) => connection.departureTime.valueOf()); diff --git a/src/inversify.config.ts b/src/inversify.config.ts index da6a6913..71536592 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -9,6 +9,7 @@ import ConnectionsProviderMerge from "./fetcher/connections/ConnectionsProviderM import IConnectionsFetcher from "./fetcher/connections/IConnectionsFetcher"; import IConnectionsProvider from "./fetcher/connections/IConnectionsProvider"; import ConnectionsFetcherLazy from "./fetcher/connections/lazy/ConnectionsFetcherLazy"; +import ConnectionsProviderPrefetch from "./fetcher/connections/prefetch/ConnectionsProviderPrefetch"; import LDFetch from "./fetcher/LDFetch"; import IStopsFetcher from "./fetcher/stops/IStopsFetcher"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; @@ -52,7 +53,7 @@ container.bind(TYPES.ReachableStopsFinder) container.bind(TYPES.ReachableStopsFinder) .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Final); -container.bind(TYPES.ConnectionsProvider).to(ConnectionsProviderMerge).inSingletonScope(); +container.bind(TYPES.ConnectionsProvider).to(ConnectionsProviderPrefetch).inSingletonScope(); container.bind(TYPES.ConnectionsFetcher).to(ConnectionsFetcherLazy); container.bind>(TYPES.ConnectionsFetcherFactory) .toFactory( diff --git a/src/util/BinarySearch.ts b/src/util/BinarySearch.ts index 65343caf..8b862124 100644 --- a/src/util/BinarySearch.ts +++ b/src/util/BinarySearch.ts @@ -9,6 +9,7 @@ export default class BinarySearch { /** * Find the last index of the given key, or the index after which that key would be hypothetically spliced in + * Adapted from: https://www.algorithmsandme.com/last-occurrence-of-element-with-binary-search/ * @param key * @param start * @param end From ab3e2aba6b78b26677d81a6c28eb5af27dcb1d2d Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 17 Jan 2019 08:55:11 +0100 Subject: [PATCH 10/69] 1. Quick CSA EAT implementation. --- src/demo.ts | 4 +- .../connections/prefetch/ConnectionsStore.ts | 2 +- src/inversify.config.ts | 3 +- src/planner/Path.ts | 4 + .../data-structure/stops/IProfileByStop.ts | 8 + .../trips/IEarliestArrivalByTrip.ts | 6 +- .../public-transport/IJourneyExtractor.ts | 5 +- .../JourneyExtractorDefault.ts | 2 +- .../JourneyExtractorEarliestArrivalTime.ts | 64 ++++ ...TransportPlannerCSAEarliestArrival.test.ts | 168 +++++++++++ ...ublicTransportPlannerCSAEarliestArrival.ts | 279 ++++++++++++++++++ .../PublicTransportPlannerCSAProfile.ts | 14 +- 12 files changed, 541 insertions(+), 18 deletions(-) create mode 100644 src/planner/public-transport/CSA/data-structure/stops/IProfileByStop.ts create mode 100644 src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts create mode 100644 src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.test.ts create mode 100644 src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.ts diff --git a/src/demo.ts b/src/demo.ts index b1d7f5e9..177191b2 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -27,8 +27,8 @@ export default async (logResults) => { .on(EventType.AbortQuery, (reason) => { console.log("AbortQuery", reason); }) - .on(EventType.Query, () => { - console.log("Query"); + .on(EventType.Query, (Query) => { + console.log("Query", Query); }) .on(EventType.QueryExponential, (query) => { const { minimumDepartureTime, maximumArrivalTime } = query; diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 2b00fcb4..0380d220 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -42,7 +42,7 @@ export default class ConnectionsStore { const { lowerBoundDate, upperBoundDate, backward } = fetcherConfig; if (!backward) { - throw new Error("Backward is not yet supported"); + throw new Error("Forward is not yet supported"); } if (backward && upperBoundDate) { diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 71536592..ab6cf3da 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -15,6 +15,7 @@ import IStopsFetcher from "./fetcher/stops/IStopsFetcher"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; import StopsFetcherLDFetch from "./fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import StopsProviderDefault from "./fetcher/stops/StopsProviderDefault"; +import IProfilesByStop from "./planner/public-transport/CSA/data-structure/stops/IProfilesByStop"; import IJourneyExtractor from "./planner/public-transport/IJourneyExtractor"; import IPublicTransportPlanner from "./planner/public-transport/IPublicTransportPlanner"; import JourneyExtractorDefault from "./planner/public-transport/JourneyExtractorDefault"; @@ -43,7 +44,7 @@ container.bind>(TYPES.PublicTranspor container.bind(TYPES.RoadPlanner) .to(RoadPlannerBirdsEye); -container.bind(TYPES.JourneyExtractor) +container.bind>(TYPES.JourneyExtractor) .to(JourneyExtractorDefault); container.bind(TYPES.ReachableStopsFinder) diff --git a/src/planner/Path.ts b/src/planner/Path.ts index c20cd874..9fb55e37 100644 --- a/src/planner/Path.ts +++ b/src/planner/Path.ts @@ -31,4 +31,8 @@ export default class Path implements IPath { public addStep(step: IStep): void { this.steps.push(step); } + + public reverse(): void { + this.steps.reverse(); + } } diff --git a/src/planner/public-transport/CSA/data-structure/stops/IProfileByStop.ts b/src/planner/public-transport/CSA/data-structure/stops/IProfileByStop.ts new file mode 100644 index 00000000..75da4cf8 --- /dev/null +++ b/src/planner/public-transport/CSA/data-structure/stops/IProfileByStop.ts @@ -0,0 +1,8 @@ +import ITransferProfile from "./ITransferProfile"; + +/** + * Stores multiple [[IProfile]]'s ordered by departure time for an [[IStop]]. + */ +export default interface IProfileByStop { + [stop: string]: ITransferProfile; +} diff --git a/src/planner/public-transport/CSA/data-structure/trips/IEarliestArrivalByTrip.ts b/src/planner/public-transport/CSA/data-structure/trips/IEarliestArrivalByTrip.ts index ac2c4bf7..d10e2bbe 100644 --- a/src/planner/public-transport/CSA/data-structure/trips/IEarliestArrivalByTrip.ts +++ b/src/planner/public-transport/CSA/data-structure/trips/IEarliestArrivalByTrip.ts @@ -1,8 +1,6 @@ -import IEarliestArrivalByTransfers from "./IEarliestArrivalByTransfers"; - /** * Stores for each gtfs:trip the earliest arrival [[IEarliestArrivalByTransfers]] to the target [[IStop]]. */ -export default interface IEarliestArrivalByTrip { - [trip: string]: IEarliestArrivalByTransfers; +export default interface IEarliestArrivalByTrip { + [trip: string]: T; } diff --git a/src/planner/public-transport/IJourneyExtractor.ts b/src/planner/public-transport/IJourneyExtractor.ts index 5febd168..14fdffd0 100644 --- a/src/planner/public-transport/IJourneyExtractor.ts +++ b/src/planner/public-transport/IJourneyExtractor.ts @@ -1,8 +1,7 @@ import { AsyncIterator } from "asynciterator"; import IPath from "../../interfaces/IPath"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; -import IProfilesByStop from "./CSA/data-structure/stops/IProfilesByStop"; -export default interface IJourneyExtractor { - extractJourneys: (profilesByStop: IProfilesByStop, query: IResolvedQuery) => Promise>; +export default interface IJourneyExtractor { + extractJourneys: (profilesByStop: T, query: IResolvedQuery) => Promise>; } diff --git a/src/planner/public-transport/JourneyExtractorDefault.ts b/src/planner/public-transport/JourneyExtractorDefault.ts index 74777c83..45ac9f37 100644 --- a/src/planner/public-transport/JourneyExtractorDefault.ts +++ b/src/planner/public-transport/JourneyExtractorDefault.ts @@ -25,7 +25,7 @@ import IJourneyExtractor from "./IJourneyExtractor"; * @property bestArrivalTime Stores the best arrival time for each pair of departure-arrival stops. */ @injectable() -export default class JourneyExtractorDefault implements IJourneyExtractor { +export default class JourneyExtractorDefault implements IJourneyExtractor { private readonly locationResolver: ILocationResolver; private bestArrivalTime: number[][] = []; diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts b/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts new file mode 100644 index 00000000..c3af7506 --- /dev/null +++ b/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts @@ -0,0 +1,64 @@ +import { ArrayIterator, AsyncIterator } from "asynciterator"; +import { inject, injectable } from "inversify"; +import Context from "../../Context"; +import IPath from "../../interfaces/IPath"; +import IStep from "../../interfaces/IStep"; +import ILocationResolver from "../../query-runner/ILocationResolver"; +import IResolvedQuery from "../../query-runner/IResolvedQuery"; +import TYPES from "../../types"; +import Path from "../Path"; +import Step from "../Step"; +import IProfileByStop from "./CSA/data-structure/stops/IProfileByStop"; +import ITransferProfile from "./CSA/data-structure/stops/ITransferProfile"; +import IJourneyExtractor from "./IJourneyExtractor"; + +/** + * Creates journeys based on the profiles and query from [[PublicTransportPlannerCSAProfile]]. + * A journey is an [[IPath]] that consist of several [[IStep]]s. + * The [[JourneyExtractor]] takes care of initial, intermediate and final footpaths. + * + * @property bestArrivalTime Stores the best arrival time for each pair of departure-arrival stops. + */ +@injectable() +export default class JourneyExtractorEarliestArrivalTime implements IJourneyExtractor { + private readonly locationResolver: ILocationResolver; + + private context: Context; + + constructor( + @inject(TYPES.LocationResolver) locationResolver: ILocationResolver, + @inject(TYPES.Context)context?: Context, + ) { + this.locationResolver = locationResolver; + this.context = context; + } + + public async extractJourneys( + profilesByStop: IProfileByStop, + query: IResolvedQuery, + ): Promise> { + const path: Path = Path.create(); + + const departureStopId: string = query.from[0].id; + + let currentStopId: string = query.to[0].id; + let currentProfile: ITransferProfile = profilesByStop[currentStopId]; + + while (currentStopId !== departureStopId) { + const step: IStep = Step.createFromConnections( + currentProfile.enterConnection, + currentProfile.exitConnection, + ); + + path.addStep(step); + + currentStopId = currentProfile.enterConnection.departureStop; + currentProfile = profilesByStop[currentStopId]; + } + + path.reverse(); + + return new ArrayIterator([path]); + } + +} diff --git a/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.test.ts b/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.test.ts new file mode 100644 index 00000000..689eb070 --- /dev/null +++ b/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.test.ts @@ -0,0 +1,168 @@ +import "jest"; +import LDFetch from "ldfetch"; +import Defaults from "../../Defaults"; +import ConnectionsFetcherNMBSTest from "../../fetcher/connections/tests/ConnectionsFetcherNMBSTest"; +import connectionsIngelmunsterGhent from "../../fetcher/connections/tests/data/ingelmunster-ghent"; +import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; +import IPath from "../../interfaces/IPath"; +import IResolvedQuery from "../../query-runner/IResolvedQuery"; +import LocationResolverDefault from "../../query-runner/LocationResolverDefault"; +import Iterators from "../../util/Iterators"; +import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBirdsEyeCached"; +import JourneyExtractorEarliestArrivalTime from "./JourneyExtractorEarliestArrivalTime"; +import PublicTransportPlannerCSAEarliestArrival from "./PublicTransportPlannerCSAEarliestArrival"; + +describe("[PublicTransportPlannerCSAProfile]", () => { + describe("mock data", () => { + jest.setTimeout(100000); + + const createCSA = (connections) => { + const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); + + const connectionFetcher = new ConnectionsFetcherNMBSTest(connections); + connectionFetcher.setConfig({ backward: false }); + + const stopsFetcher = new StopsFetcherLDFetch(ldFetch); + stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); + + const locationResolver = new LocationResolverDefault(stopsFetcher); + const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); + const journeyExtractor = new JourneyExtractorEarliestArrivalTime( + locationResolver, + ); + + return new PublicTransportPlannerCSAEarliestArrival( + connectionFetcher, + locationResolver, + reachableStopsFinder, + reachableStopsFinder, + reachableStopsFinder, + journeyExtractor, + ); + }; + + describe("basic test", () => { + let result: IPath[]; + + const query: IResolvedQuery = { + publicTransportOnly: true, + from: [{latitude: 50.914326, longitude: 3.255415, id: "http://irail.be/stations/NMBS/008896925" }], + to: [{ latitude: 51.035896, longitude: 3.710875, id: "http://irail.be/stations/NMBS/008892007" }], + minimumDepartureTime: new Date("2018-11-06T09:00:00.000Z"), + maximumArrivalTime: new Date("2018-11-06T19:00:00.000Z"), + maximumTransfers: 8, + minimumWalkingSpeed: Defaults.defaultMinimumWalkingSpeed, + maximumWalkingSpeed: Defaults.defaultMaximumWalkingSpeed, + maximumTransferDuration: Defaults.defaultMaximumTransferDuration, + }; + + beforeAll(async () => { + const CSA = createCSA(connectionsIngelmunsterGhent); + const iterator = await CSA.plan(query); + result = await Iterators.toArray(iterator); + console.log(JSON.stringify(result, null, " ")); + }); + + it("Correct departure and arrival stop", () => { + expect(result).toBeDefined(); + + for (const path of result) { + expect(path.steps).toBeDefined(); + expect(path.steps[0]).toBeDefined(); + expect(query.from.map((from) => from.id)).toContain(path.steps[0].startLocation.id); + expect(query.to.map((to) => to.id)).toContain(path.steps[path.steps.length - 1].stopLocation.id); + } + }); + }); + + /*describe("splitting", () => { + let result: IPath[]; + + const query: IResolvedQuery = { + publicTransportOnly: true, + from: [{ + id: "http://irail.be/stations/NMBS/008821006", + latitude: 51.2172, + longitude: 4.421101, + }], + to: [{ + id: "http://irail.be/stations/NMBS/008812005", + latitude: 50.859663, + longitude: 4.360846, + }], + minimumDepartureTime: new Date("2017-12-19T15:50:00.000Z"), + maximumArrivalTime: new Date("2017-12-19T16:50:00.000Z"), + maximumTransfers: 1, + minimumWalkingSpeed: Defaults.defaultMinimumWalkingSpeed, + maximumWalkingSpeed: Defaults.defaultMaximumWalkingSpeed, + maximumTransferDuration: Defaults.defaultMaximumTransferDuration, + }; + + beforeAll(async () => { + const CSA = createCSA(connectionsSplitting); + const iterator = await CSA.plan(query); + result = await Iterators.toArray(iterator); + }); + + it("Correct departure and arrival stop", () => { + expect(result).toBeDefined(); + expect(result.length).toBeGreaterThanOrEqual(1); + + for (const path of result) { + expect(path.steps).toBeDefined(); + + expect(path.steps.length).toEqual(1); + expect(path.steps[0]).toBeDefined(); + + expect(query.from.map((from) => from.id)).toContain(path.steps[0].startLocation.id); + expect(query.to.map((to) => to.id)).toContain(path.steps[0].stopLocation.id); + } + }); + }); + + describe("joining", () => { + let result: IPath[]; + + const query: IResolvedQuery = { + publicTransportOnly: true, + from: [{ + id: "http://irail.be/stations/NMBS/008812005", + latitude: 50.859663, + longitude: 4.360846, + }], + to: [{ + id: "http://irail.be/stations/NMBS/008821006", + latitude: 51.2172, + longitude: 4.421101, + }], + minimumDepartureTime: new Date("2017-12-19T16:20:00.000Z"), + maximumArrivalTime: new Date("2017-12-19T16:50:00.000Z"), + maximumTransfers: 1, + minimumWalkingSpeed: Defaults.defaultMinimumWalkingSpeed, + maximumWalkingSpeed: Defaults.defaultMaximumWalkingSpeed, + maximumTransferDuration: Defaults.defaultMaximumTransferDuration, + }; + + beforeAll(async () => { + const CSA = createCSA(connectionsJoining); + const iterator = await CSA.plan(query); + result = await Iterators.toArray(iterator); + }); + + it("Correct departure and arrival stop", () => { + expect(result).toBeDefined(); + expect(result.length).toBeGreaterThanOrEqual(1); + + for (const path of result) { + expect(path.steps).toBeDefined(); + + expect(path.steps.length).toEqual(1); + expect(path.steps[0]).toBeDefined(); + + expect(query.from.map((from) => from.id)).toContain(path.steps[0].startLocation.id); + expect(query.to.map((to) => to.id)).toContain(path.steps[0].stopLocation.id); + } + }); + });*/ + }); +}); diff --git a/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.ts b/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.ts new file mode 100644 index 00000000..c7153e0e --- /dev/null +++ b/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.ts @@ -0,0 +1,279 @@ +import { AsyncIterator } from "asynciterator"; +import { inject, injectable, tagged } from "inversify"; +import Context from "../../Context"; +import EventType from "../../enums/EventType"; +import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; +import ReachableStopsSearchPhase from "../../enums/ReachableStopsSearchPhase"; +import IConnection from "../../fetcher/connections/IConnection"; +import IConnectionsProvider from "../../fetcher/connections/IConnectionsProvider"; +import IStop from "../../fetcher/stops/IStop"; +import ILocation from "../../interfaces/ILocation"; +import IPath from "../../interfaces/IPath"; +import { DurationMs } from "../../interfaces/units"; +import ILocationResolver from "../../query-runner/ILocationResolver"; +import IResolvedQuery from "../../query-runner/IResolvedQuery"; +import TYPES from "../../types"; +import IReachableStopsFinder, { IReachableStop } from "../stops/IReachableStopsFinder"; +import IProfileByStop from "./CSA/data-structure/stops/IProfileByStop"; +import IEarliestArrival from "./CSA/data-structure/trips/IEarliestArrival"; +import IEarliestArrivalByTrip from "./CSA/data-structure/trips/IEarliestArrivalByTrip"; +import IJourneyExtractor from "./IJourneyExtractor"; +import IPublicTransportPlanner from "./IPublicTransportPlanner"; + +/** + * An implementation of the Connection Scan Algorithm (CSA). + * + * @implements [[IPublicTransportPlanner]] + * @property profilesByStop Describes the CSA profiles for each scanned stop. + * @property earliestArrivalByTrip Describes the earliest arrival time for each scanned trip. + * @property durationToTargetByStop Describes the walking duration to the target stop for a scanned stop. + * + * @returns multiple [[IPath]]s that consist of several [[IStep]]s. + */ +@injectable() +export default class PublicTransportPlannerCSAEarliestArrival implements IPublicTransportPlanner { + private readonly connectionsProvider: IConnectionsProvider; + private readonly locationResolver: ILocationResolver; + private readonly initialReachableStopsFinder: IReachableStopsFinder; + private readonly finalReachableStopsFinder: IReachableStopsFinder; + private readonly transferReachableStopsFinder: IReachableStopsFinder; + private readonly journeyExtractor: IJourneyExtractor; + private readonly context: Context; + + private profilesByStop: IProfileByStop = {}; // S + private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T + private durationToTargetByStop: DurationMs[] = []; + private gtfsTripByConnection = {}; + private initialReachableStops: IReachableStop[] = []; + + private query: IResolvedQuery; + private connectionsIterator: AsyncIterator; + + constructor( + @inject(TYPES.ConnectionsProvider) + connectionsProvider: IConnectionsProvider, + @inject(TYPES.LocationResolver) + locationResolver: ILocationResolver, + @inject(TYPES.ReachableStopsFinder) + @tagged("phase", ReachableStopsSearchPhase.Initial) + initialReachableStopsFinder: IReachableStopsFinder, + @inject(TYPES.ReachableStopsFinder) + @tagged("phase", ReachableStopsSearchPhase.Transfer) + transferReachableStopsFinder: IReachableStopsFinder, + @inject(TYPES.ReachableStopsFinder) + @tagged("phase", ReachableStopsSearchPhase.Final) + finalReachableStopsFinder: IReachableStopsFinder, + @inject(TYPES.JourneyExtractor) + journeyExtractor: IJourneyExtractor, + @inject(TYPES.Context) + context?: Context, + ) { + this.connectionsProvider = connectionsProvider; + this.locationResolver = locationResolver; + this.initialReachableStopsFinder = initialReachableStopsFinder; + this.transferReachableStopsFinder = transferReachableStopsFinder; + this.finalReachableStopsFinder = finalReachableStopsFinder; + this.journeyExtractor = journeyExtractor; + this.context = context; + } + + public async plan(query: IResolvedQuery): Promise> { + this.query = query; + + this.setBounds(); + + return this.calculateJourneys(); + } + + private setBounds() { + const { + minimumDepartureTime: lowerBoundDate, + maximumArrivalTime: upperBoundDate, + } = this.query; + + this.connectionsProvider.setConfig({ + upperBoundDate, + lowerBoundDate, + }); + } + + private async calculateJourneys(): Promise> { + // await this.initDurationToTargetByStop(); + await this.initInitialReachableStops(); + + this.connectionsIterator = this.connectionsProvider.createIterator(); + + const self = this; + + return new Promise((resolve, reject) => { + let isDone = false; + + const done = () => { + if (!isDone) { + self.connectionsIterator.close(); + + self.journeyExtractor.extractJourneys(self.profilesByStop, self.query) + .then((resultIterator) => { + resolve(resultIterator); + }); + + isDone = true; + } + }; + + this.connectionsIterator.on("readable", () => + self.processNextConnection(done), + ); + + this.connectionsIterator.on("end", () => done()); + + }) as Promise>; + } + + private async processNextConnection(done: () => void) { + const connection = this.connectionsIterator.read(); + + if (connection) { + this.discoverConnection(connection); + + const arrivalStopId: string = this.query.to[0].id; + if (this.profilesByStop[arrivalStopId].arrivalTime <= connection.departureTime.getTime()) { + done(); + } + + if (connection.departureTime < this.query.minimumDepartureTime) { + await this.maybeProcessNextConnection(done); + return; + } + + if ( + this.earliestArrivalByTrip[connection["gtfs:trip"]].connection || + this.profilesByStop[connection.departureStop].arrivalTime <= connection.departureTime.getTime() + ) { + this.updateTrips(connection); + + if (connection.arrivalTime.getTime() < this.profilesByStop[connection.arrivalStop].arrivalTime) { + await this.getReachableStops(connection); + } + } + + await this.maybeProcessNextConnection(done); + } + } + + private async maybeProcessNextConnection(done: () => void) { + if (!this.connectionsIterator.closed) { + await this.processNextConnection(done); + } + } + + private async initInitialReachableStops(): Promise { + const fromLocation: IStop = this.query.from[0] as IStop; + + this.initialReachableStops = await this.initialReachableStopsFinder.findReachableStops( + fromLocation, + ReachableStopsFinderMode.Source, + this.query.maximumWalkingDuration, + this.query.minimumWalkingSpeed, + ); + + let stopIndex = 0; + while (stopIndex < this.initialReachableStops.length && !fromLocation.id) { + const reachableStop = this.initialReachableStops[stopIndex]; + + if (reachableStop.duration === 0) { + this.query.from[0] = reachableStop.stop; + } + + stopIndex++; + } + + if (!fromLocation.id) { + this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; + this.query.from[0].name = "Departure location"; + } + + if (this.initialReachableStops.length === 0 && this.context) { + this.context.emit(EventType.AbortQuery, "No reachable stops at departure location"); + + return false; + } + + if (this.context) { + this.context.emit(EventType.InitialReachableStops, this.initialReachableStops); + } + + this.initialReachableStops.forEach(({ stop, duration }: IReachableStop) => { + this.profilesByStop[stop.id] = { + departureTime: this.query.minimumDepartureTime.getTime(), + arrivalTime: this.query.minimumDepartureTime.getTime() + duration, + exitConnection: undefined, + enterConnection: undefined, + }; + }); + + return true; + } + + private discoverConnection(connection: IConnection) { + [ + connection.departureStop, + connection.arrivalStop, + this.query.to[0].id, + ].forEach((stop) => { + if (!this.profilesByStop[stop]) { + this.profilesByStop[stop] = { + departureTime: Infinity, + arrivalTime: Infinity, + exitConnection: undefined, + enterConnection: undefined, + }; + } + }); + + if (!this.earliestArrivalByTrip[connection["gtfs:trip"]]) { + this.earliestArrivalByTrip[connection["gtfs:trip"]] = { + arrivalTime: Infinity, + connection: undefined, + }; + } + } + + private updateTrips(connection: IConnection): void { + if (!this.earliestArrivalByTrip[connection["gtfs:trip"]].connection) { + this.earliestArrivalByTrip[connection["gtfs:trip"]] = { + arrivalTime: connection.arrivalTime.getTime(), + connection, + }; + } + } + + private async getReachableStops(connection: IConnection): Promise { + try { + const departureStop: ILocation = await this.locationResolver.resolve(connection.arrivalStop); + const reachableStops: IReachableStop[] = await this.transferReachableStopsFinder.findReachableStops( + departureStop as IStop, + ReachableStopsFinderMode.Source, + this.query.maximumWalkingDuration, + this.query.minimumWalkingSpeed, + ); + + reachableStops.forEach((reachableStop: IReachableStop) => { + const reachableStopArrival = this.profilesByStop[reachableStop.stop.id].arrivalTime; + + if (reachableStopArrival > connection.arrivalTime.getTime() + reachableStop.duration) { + this.profilesByStop[reachableStop.stop.id] = { + departureTime: connection.departureTime.getTime(), + arrivalTime: connection.arrivalTime.getTime() + reachableStop.duration, + exitConnection: connection, + enterConnection: this.earliestArrivalByTrip[connection["gtfs:trip"]].connection, + }; + } + + }); + + } catch (e) { + this.context.emitWarning(e); + } + } +} diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index b3da1a03..71c8e2f9 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -45,11 +45,11 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor private readonly initialReachableStopsFinder: IReachableStopsFinder; private readonly finalReachableStopsFinder: IReachableStopsFinder; private readonly transferReachableStopsFinder: IReachableStopsFinder; - private readonly journeyExtractor: IJourneyExtractor; + private readonly journeyExtractor: IJourneyExtractor; private readonly context: Context; private profilesByStop: IProfilesByStop = {}; // S - private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T + private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T private durationToTargetByStop: DurationMs[] = []; private gtfsTripByConnection = {}; private initialReachableStops: IReachableStop[] = []; @@ -72,7 +72,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor @tagged("phase", ReachableStopsSearchPhase.Final) finalReachableStopsFinder: IReachableStopsFinder, @inject(TYPES.JourneyExtractor) - journeyExtractor: IJourneyExtractor, + journeyExtractor: IJourneyExtractor, @inject(TYPES.Context) context?: Context, ) { @@ -271,16 +271,18 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor this.query.minimumWalkingSpeed, ); - const stopIndex = 0; - while (stopIndex < this.initialReachableStops.length && !this.query.from[0].id) { + let stopIndex = 0; + while (stopIndex < this.initialReachableStops.length && !fromLocation.id) { const reachableStop = this.initialReachableStops[stopIndex]; if (reachableStop.duration === 0) { this.query.from[0] = reachableStop.stop; } + + stopIndex++; } if (!this.query.from[0].id) { - this.query.from[0].id = "geo:" + this.query.from[0].latitude + "," + this.query.from[0].longitude; + this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; this.query.from[0].name = "Departure location"; } From ca75d3f486699a11b50d4d318133040064f90fa7 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 17 Jan 2019 11:04:12 +0100 Subject: [PATCH 11/69] 1. rename CSA files. 2. addded splitting + joining to CSA EAT 3. added dropOff + pickup check to CSA EAT --- src/inversify.config.ts | 4 +- ...val.test.ts => CSAEarliestArrival.test.ts} | 11 +- ...rliestArrival.ts => CSAEarliestArrival.ts} | 121 +++++++++++++----- ...rCSAProfile.test.ts => CSAProfile.test.ts} | 6 +- ...portPlannerCSAProfile.ts => CSAProfile.ts} | 2 +- .../JourneyExtractorEarliestArrivalTime.ts | 4 +- .../QueryRunnerExponential.test.ts | 4 +- 7 files changed, 102 insertions(+), 50 deletions(-) rename src/planner/public-transport/{PublicTransportPlannerCSAEarliestArrival.test.ts => CSAEarliestArrival.test.ts} (95%) rename src/planner/public-transport/{PublicTransportPlannerCSAEarliestArrival.ts => CSAEarliestArrival.ts} (69%) rename src/planner/public-transport/{PublicTransportPlannerCSAProfile.test.ts => CSAProfile.test.ts} (98%) rename src/planner/public-transport/{PublicTransportPlannerCSAProfile.ts => CSAProfile.ts} (99%) diff --git a/src/inversify.config.ts b/src/inversify.config.ts index ab6cf3da..e8f7276f 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -16,10 +16,10 @@ import IStopsProvider from "./fetcher/stops/IStopsProvider"; import StopsFetcherLDFetch from "./fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import StopsProviderDefault from "./fetcher/stops/StopsProviderDefault"; import IProfilesByStop from "./planner/public-transport/CSA/data-structure/stops/IProfilesByStop"; +import CSAProfile from "./planner/public-transport/CSAProfile"; import IJourneyExtractor from "./planner/public-transport/IJourneyExtractor"; import IPublicTransportPlanner from "./planner/public-transport/IPublicTransportPlanner"; import JourneyExtractorDefault from "./planner/public-transport/JourneyExtractorDefault"; -import PublicTransportPlannerCSAProfile from "./planner/public-transport/PublicTransportPlannerCSAProfile"; import IRoadPlanner from "./planner/road/IRoadPlanner"; import RoadPlannerBirdsEye from "./planner/road/RoadPlannerBirdsEye"; import IReachableStopsFinder from "./planner/stops/IReachableStopsFinder"; @@ -37,7 +37,7 @@ container.bind(TYPES.QueryRunner).to(QueryRunnerExponential); container.bind(TYPES.LocationResolver).to(LocationResolverDefault); container.bind(TYPES.PublicTransportPlanner) - .to(PublicTransportPlannerCSAProfile); + .to(CSAProfile); container.bind>(TYPES.PublicTransportPlannerFactory) .toAutoFactory(TYPES.PublicTransportPlanner); diff --git a/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.test.ts b/src/planner/public-transport/CSAEarliestArrival.test.ts similarity index 95% rename from src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.test.ts rename to src/planner/public-transport/CSAEarliestArrival.test.ts index 689eb070..6dabe979 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.test.ts +++ b/src/planner/public-transport/CSAEarliestArrival.test.ts @@ -3,14 +3,16 @@ import LDFetch from "ldfetch"; import Defaults from "../../Defaults"; import ConnectionsFetcherNMBSTest from "../../fetcher/connections/tests/ConnectionsFetcherNMBSTest"; import connectionsIngelmunsterGhent from "../../fetcher/connections/tests/data/ingelmunster-ghent"; +import connectionsJoining from "../../fetcher/connections/tests/data/joining"; +import connectionsSplitting from "../../fetcher/connections/tests/data/splitting"; import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import IPath from "../../interfaces/IPath"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import LocationResolverDefault from "../../query-runner/LocationResolverDefault"; import Iterators from "../../util/Iterators"; import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBirdsEyeCached"; +import CSAEarliestArrival from "./CSAEarliestArrival"; import JourneyExtractorEarliestArrivalTime from "./JourneyExtractorEarliestArrivalTime"; -import PublicTransportPlannerCSAEarliestArrival from "./PublicTransportPlannerCSAEarliestArrival"; describe("[PublicTransportPlannerCSAProfile]", () => { describe("mock data", () => { @@ -31,7 +33,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { locationResolver, ); - return new PublicTransportPlannerCSAEarliestArrival( + return new CSAEarliestArrival( connectionFetcher, locationResolver, reachableStopsFinder, @@ -60,7 +62,6 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const CSA = createCSA(connectionsIngelmunsterGhent); const iterator = await CSA.plan(query); result = await Iterators.toArray(iterator); - console.log(JSON.stringify(result, null, " ")); }); it("Correct departure and arrival stop", () => { @@ -75,7 +76,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { }); }); - /*describe("splitting", () => { + describe("splitting", () => { let result: IPath[]; const query: IResolvedQuery = { @@ -163,6 +164,6 @@ describe("[PublicTransportPlannerCSAProfile]", () => { expect(query.to.map((to) => to.id)).toContain(path.steps[0].stopLocation.id); } }); - });*/ + }); }); }); diff --git a/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts similarity index 69% rename from src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.ts rename to src/planner/public-transport/CSAEarliestArrival.ts index c7153e0e..02e329d5 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -1,7 +1,9 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable, tagged } from "inversify"; import Context from "../../Context"; +import DropOffType from "../../enums/DropOffType"; import EventType from "../../enums/EventType"; +import PickupType from "../../enums/PickupType"; import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import ReachableStopsSearchPhase from "../../enums/ReachableStopsSearchPhase"; import IConnection from "../../fetcher/connections/IConnection"; @@ -31,7 +33,7 @@ import IPublicTransportPlanner from "./IPublicTransportPlanner"; * @returns multiple [[IPath]]s that consist of several [[IStep]]s. */ @injectable() -export default class PublicTransportPlannerCSAEarliestArrival implements IPublicTransportPlanner { +export default class CSAEarliestArrival implements IPublicTransportPlanner { private readonly connectionsProvider: IConnectionsProvider; private readonly locationResolver: ILocationResolver; private readonly initialReachableStopsFinder: IReachableStopsFinder; @@ -43,7 +45,7 @@ export default class PublicTransportPlannerCSAEarliestArrival implements IPublic private profilesByStop: IProfileByStop = {}; // S private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T private durationToTargetByStop: DurationMs[] = []; - private gtfsTripByConnection = {}; + private gtfsTripsByConnection = {}; private initialReachableStops: IReachableStop[] = []; private query: IResolvedQuery; @@ -98,7 +100,7 @@ export default class PublicTransportPlannerCSAEarliestArrival implements IPublic } private async calculateJourneys(): Promise> { - // await this.initDurationToTargetByStop(); + await this.initArrivalStopProfile(); await this.initInitialReachableStops(); this.connectionsIterator = this.connectionsProvider.createIterator(); @@ -146,15 +148,21 @@ export default class PublicTransportPlannerCSAEarliestArrival implements IPublic return; } - if ( - this.earliestArrivalByTrip[connection["gtfs:trip"]].connection || - this.profilesByStop[connection.departureStop].arrivalTime <= connection.departureTime.getTime() - ) { - this.updateTrips(connection); + const tripIds = this.getTripIdsFromConnection(connection); + for (const tripId of tripIds) { - if (connection.arrivalTime.getTime() < this.profilesByStop[connection.arrivalStop].arrivalTime) { - await this.getReachableStops(connection); + const canRemainSeated = this.earliestArrivalByTrip[tripId].connection; + const canTakeTransfer = (this.profilesByStop[connection.departureStop].arrivalTime <= + connection.departureTime.getTime() && connection["gtfs:pickupType"] !== PickupType.NotAvailable); + + if (canRemainSeated || canTakeTransfer) { + this.updateTrips(connection, tripId); + + if (connection["gtfs:dropOffType"] !== DropOffType.NotAvailable) { + await this.updateProfiles(connection, tripId); + } } + } await this.maybeProcessNextConnection(done); @@ -177,8 +185,9 @@ export default class PublicTransportPlannerCSAEarliestArrival implements IPublic this.query.minimumWalkingSpeed, ); + // Check if departure location is a stop. let stopIndex = 0; - while (stopIndex < this.initialReachableStops.length && !fromLocation.id) { + while (stopIndex < this.initialReachableStops.length && !this.query.from[0].id) { const reachableStop = this.initialReachableStops[stopIndex]; if (reachableStop.duration === 0) { @@ -188,11 +197,13 @@ export default class PublicTransportPlannerCSAEarliestArrival implements IPublic stopIndex++; } + // Making sure the departure location has an id if (!fromLocation.id) { this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; this.query.from[0].name = "Departure location"; } + // Abort when we can't reach a single stop. if (this.initialReachableStops.length === 0 && this.context) { this.context.emit(EventType.AbortQuery, "No reachable stops at departure location"); @@ -210,17 +221,27 @@ export default class PublicTransportPlannerCSAEarliestArrival implements IPublic exitConnection: undefined, enterConnection: undefined, }; + }); return true; } + private initArrivalStopProfile(): void { + const arrivalStopId: string = this.query.to[0].id; + + this.profilesByStop[arrivalStopId] = { + departureTime: Infinity, + arrivalTime: Infinity, + exitConnection: undefined, + enterConnection: undefined, + }; + } + private discoverConnection(connection: IConnection) { - [ - connection.departureStop, - connection.arrivalStop, - this.query.to[0].id, - ].forEach((stop) => { + this.setTripIdsByConnectionId(connection); + + [connection.departureStop, connection.arrivalStop].forEach((stop) => { if (!this.profilesByStop[stop]) { this.profilesByStop[stop] = { departureTime: Infinity, @@ -231,42 +252,72 @@ export default class PublicTransportPlannerCSAEarliestArrival implements IPublic } }); - if (!this.earliestArrivalByTrip[connection["gtfs:trip"]]) { - this.earliestArrivalByTrip[connection["gtfs:trip"]] = { - arrivalTime: Infinity, - connection: undefined, - }; + const tripIds: string[] = this.getTripIdsFromConnection(connection); + + for (const tripId of tripIds) { + if (!this.earliestArrivalByTrip[tripId]) { + this.earliestArrivalByTrip[tripId] = { + arrivalTime: Infinity, + connection: undefined, + }; + } } } - private updateTrips(connection: IConnection): void { - if (!this.earliestArrivalByTrip[connection["gtfs:trip"]].connection) { - this.earliestArrivalByTrip[connection["gtfs:trip"]] = { - arrivalTime: connection.arrivalTime.getTime(), - connection, + private getTripIdsFromConnection(connection: IConnection): string[] { + return this.gtfsTripsByConnection[connection.id]; + } + + private setTripIdsByConnectionId(connection: IConnection): void { + if (!this.gtfsTripsByConnection.hasOwnProperty(connection.id)) { + this.gtfsTripsByConnection[connection.id] = []; + } + + this.gtfsTripsByConnection[connection.id].push(connection["gtfs:trip"]); + + let nextConnectionIndex = 0; + while (connection.nextConnection && nextConnectionIndex < connection.nextConnection.length) { + const connectionId = connection.nextConnection[nextConnectionIndex]; + + if (!this.gtfsTripsByConnection.hasOwnProperty(connectionId)) { + this.gtfsTripsByConnection[connectionId] = []; + + } + + this.gtfsTripsByConnection[connectionId].push(connection["gtfs:trip"]); + nextConnectionIndex++; + } + + } + + private updateTrips(connection: IConnection, tripId: string): void { + if (!this.earliestArrivalByTrip[tripId].connection) { + this.earliestArrivalByTrip[tripId] = { + arrivalTime: Infinity, // don't need this + connection, // first connection of the trip we are taking }; } } - private async getReachableStops(connection: IConnection): Promise { + private async updateProfiles(connection: IConnection, tripId: string): Promise { try { - const departureStop: ILocation = await this.locationResolver.resolve(connection.arrivalStop); + const arrivalStop: ILocation = await this.locationResolver.resolve(connection.arrivalStop); const reachableStops: IReachableStop[] = await this.transferReachableStopsFinder.findReachableStops( - departureStop as IStop, + arrivalStop as IStop, ReachableStopsFinderMode.Source, this.query.maximumWalkingDuration, this.query.minimumWalkingSpeed, ); - reachableStops.forEach((reachableStop: IReachableStop) => { - const reachableStopArrival = this.profilesByStop[reachableStop.stop.id].arrivalTime; + reachableStops.forEach(({stop, duration}: IReachableStop) => { + const reachableStopArrival = this.profilesByStop[stop.id].arrivalTime; - if (reachableStopArrival > connection.arrivalTime.getTime() + reachableStop.duration) { - this.profilesByStop[reachableStop.stop.id] = { + if (reachableStopArrival > connection.arrivalTime.getTime() + duration) { + this.profilesByStop[stop.id] = { departureTime: connection.departureTime.getTime(), - arrivalTime: connection.arrivalTime.getTime() + reachableStop.duration, + arrivalTime: connection.arrivalTime.getTime() + duration, exitConnection: connection, - enterConnection: this.earliestArrivalByTrip[connection["gtfs:trip"]].connection, + enterConnection: this.earliestArrivalByTrip[tripId].connection, }; } diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts b/src/planner/public-transport/CSAProfile.test.ts similarity index 98% rename from src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts rename to src/planner/public-transport/CSAProfile.test.ts index edcfb294..a638ce95 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts +++ b/src/planner/public-transport/CSAProfile.test.ts @@ -16,8 +16,8 @@ import LocationResolverDefault from "../../query-runner/LocationResolverDefault" import QueryRunnerDefault from "../../query-runner/QueryRunnerDefault"; import Iterators from "../../util/Iterators"; import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBirdsEyeCached"; +import CSAProfile from "./CSAProfile"; import JourneyExtractorDefault from "./JourneyExtractorDefault"; -import PublicTransportPlannerCSAProfile from "./PublicTransportPlannerCSAProfile"; describe("[PublicTransportPlannerCSAProfile]", () => { describe("mock data", () => { @@ -38,7 +38,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { locationResolver, ); - return new PublicTransportPlannerCSAProfile( + return new CSAProfile( connectionFetcher, locationResolver, reachableStopsFinder, @@ -189,7 +189,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { locationResolver, ); - const CSA = new PublicTransportPlannerCSAProfile( + const CSA = new CSAProfile( connectionFetcher, locationResolver, reachableStopsFinder, diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/CSAProfile.ts similarity index 99% rename from src/planner/public-transport/PublicTransportPlannerCSAProfile.ts rename to src/planner/public-transport/CSAProfile.ts index 71c8e2f9..80c69fdf 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -39,7 +39,7 @@ import IPublicTransportPlanner from "./IPublicTransportPlanner"; * @returns multiple [[IPath]]s that consist of several [[IStep]]s. */ @injectable() -export default class PublicTransportPlannerCSAProfile implements IPublicTransportPlanner { +export default class CSAProfile implements IPublicTransportPlanner { private readonly connectionsProvider: IConnectionsProvider; private readonly locationResolver: ILocationResolver; private readonly initialReachableStopsFinder: IReachableStopsFinder; diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts b/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts index c3af7506..391365a5 100644 --- a/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts +++ b/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts @@ -1,4 +1,4 @@ -import { ArrayIterator, AsyncIterator } from "asynciterator"; +import { AsyncIterator, SingletonIterator } from "asynciterator"; import { inject, injectable } from "inversify"; import Context from "../../Context"; import IPath from "../../interfaces/IPath"; @@ -58,7 +58,7 @@ export default class JourneyExtractorEarliestArrivalTime implements IJourneyExtr path.reverse(); - return new ArrayIterator([path]); + return new SingletonIterator(path); } } diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index 614b0388..0b85fa43 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -6,8 +6,8 @@ import ConnectionsFetcherLazy from "../../fetcher/connections/lazy/ConnectionsFe import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import IPath from "../../interfaces/IPath"; import IStep from "../../interfaces/IStep"; +import CSAProfile from "../../planner/public-transport/CSAProfile"; import JourneyExtractorDefault from "../../planner/public-transport/JourneyExtractorDefault"; -import PublicTransportPlannerCSAProfile from "../../planner/public-transport/PublicTransportPlannerCSAProfile"; import ReachableStopsFinderBirdsEyeCached from "../../planner/stops/ReachableStopsFinderBirdsEyeCached"; import Units from "../../util/Units"; import LocationResolverDefault from "../LocationResolverDefault"; @@ -48,7 +48,7 @@ describe("[QueryRunnerExponential]", () => { }; const createPlanner = () => { - return new PublicTransportPlannerCSAProfile( + return new CSAProfile( connectionFetcher, locationResolver, reachableStopsFinder, From 372e43f325135d9d87aaa5827e17e0ed051277c5 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 17 Jan 2019 11:25:18 +0100 Subject: [PATCH 12/69] Dump: support backwards iterators in ConnectionsProviderPrefetch --- .../connections/ConnectionsProviderMerge.ts | 3 +- .../prefetch/ConnectionsProviderPrefetch.ts | 2 +- .../prefetch/ConnectionsStore.test.ts | 119 +++++++++++++----- .../connections/prefetch/ConnectionsStore.ts | 111 ++++++++++------ src/util/BinarySearch.test.ts | 42 ++++--- src/util/BinarySearch.ts | 14 +++ 6 files changed, 201 insertions(+), 90 deletions(-) diff --git a/src/fetcher/connections/ConnectionsProviderMerge.ts b/src/fetcher/connections/ConnectionsProviderMerge.ts index 9a67fb0e..c092e912 100644 --- a/src/fetcher/connections/ConnectionsProviderMerge.ts +++ b/src/fetcher/connections/ConnectionsProviderMerge.ts @@ -6,12 +6,13 @@ import MergeIterator from "../../util/iterators/MergeIterator"; import IConnection from "./IConnection"; import IConnectionsFetcher from "./IConnectionsFetcher"; import IConnectionsFetcherConfig from "./IConnectionsFetcherConfig"; +import IConnectionsProvider from "./IConnectionsProvider"; /** * Instantiates and merge sorts all registered connection fetchers */ @injectable() -export default class ConnectionsProviderMerge implements IConnectionsFetcher { +export default class ConnectionsProviderMerge implements IConnectionsProvider { private static forwardsConnectionSelector(connections: IConnection[]): number { if (connections.length === 1) { diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index eeacdf9e..d7b902f8 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -60,7 +60,7 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider public createIterator(): AsyncIterator { if (this.startedPrefetching) { return new PromiseProxyIterator(() => - this.connectionsStore.getIteratorView(this.connectionsFetcherConfig), + this.connectionsStore.getIterator(this.connectionsFetcherConfig), ); } diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts index 96e2c4a3..c226fb66 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -12,50 +12,107 @@ describe("[ConnectionsStore]", () => { * and both Dates and Numbers return a number */ - describe("All loaded", () => { + describe("Loaded sync", () => { - const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; - // @ts-ignore - const fakeConnections: IConnection[] = fakeDepartureTimes - .map((departureTime) => ({departureTime})); + let connectionsStore; + let createIterator; - const connectionsStore = new ConnectionsStore(); - for (const connection of fakeConnections) { - connectionsStore.append(connection); - } + beforeEach(() => { + const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; + // @ts-ignore + const fakeConnections: IConnection[] = fakeDepartureTimes + .map((departureTime) => ({departureTime})); - it("iterator view: backward / upperBoundDate is loaded & exists in store", async (done) => { - const fetcherConfig: IConnectionsFetcherConfig = { - backward: true, - upperBoundDate: (6 as unknown) as Date, + connectionsStore = new ConnectionsStore(); + for (const connection of fakeConnections) { + connectionsStore.append(connection); + } + + createIterator = async (backward, lowerBoundDate, upperBoundDate): Promise> => { + const fetcherConfig: IConnectionsFetcherConfig = { + backward, + }; + + if (lowerBoundDate) { + fetcherConfig.lowerBoundDate = (lowerBoundDate as unknown) as Date; + } + + if (upperBoundDate) { + fetcherConfig.upperBoundDate = (upperBoundDate as unknown) as Date; + } + + return connectionsStore.getIterator(fetcherConfig); }; - const iteratorView: AsyncIterator = await connectionsStore.getIteratorView(fetcherConfig); - const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6]; - let current = expected.length - 1; + }); - iteratorView.each((str: IConnection) => { - expect(expected[current--]).toBe(str.departureTime); + describe("backward", () => { + + it("upperBoundDate is loaded & exists in store", async (done) => { + const iteratorView = await createIterator(true, null, 6); + + const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6]; + let current = expected.length - 1; + + iteratorView.each((str: IConnection) => { + expect(expected[current--]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => done()); + }); + + it("upperBoundDate is loaded but doesn\'t exist in store", async (done) => { + const iteratorView = await createIterator(true, null, 4); + + const expected = [1, 2, 3, 3]; + let current = expected.length - 1; + + iteratorView.each((str: IConnection) => { + expect(expected[current--]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => done()); }); - iteratorView.on("end", () => done()); }); - it("iterator view: backward / upperBoundDate is loaded but doesn\'t exist in store", async (done) => { - const fetcherConfig: IConnectionsFetcherConfig = { - backward: true, - upperBoundDate: (4 as unknown) as Date, - }; - const iteratorView: AsyncIterator = await connectionsStore.getIteratorView(fetcherConfig); + describe("forward", () => { - const expected = [1, 2, 3, 3]; - let current = expected.length - 1; + it("lowerBoundDate is loaded & exists in store", async (done) => { + const iteratorView = await createIterator(false, 3, 6); - iteratorView.each((str: IConnection) => { - expect(expected[current--]).toBe(str.departureTime); + console.log("a"); + + const expected = [3, 3, 5, 6, 6, 6, 6]; + let current = 0; + + console.log("b"); + + iteratorView.each((str: IConnection) => { + console.log(str); + expect(expected[current++]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => { + expect(current).toBe(expected.length - 1); + console.log("c"); + done(); + }); + }); + + it("lowerBoundDate is loaded but doesn\'t exist in store", async (done) => { + const iteratorView = await createIterator(false, 4, null); + + const expected = [1, 2, 3, 3]; + let current = 0; + + iteratorView.each((str: IConnection) => { + expect(expected[current--]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => done()); }); - iteratorView.on("end", () => done()); }); }); @@ -92,7 +149,7 @@ describe("[ConnectionsStore]", () => { backward: true, upperBoundDate: (7 as unknown) as Date, }; - const iteratorView: AsyncIterator = await connectionsStore.getIteratorView(fetcherConfig); + const iteratorView: AsyncIterator = await connectionsStore.getIterator(fetcherConfig); const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; let current = expected.length - 1; diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 2b00fcb4..25b87bf8 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -3,7 +3,8 @@ import BinarySearch from "../../../util/BinarySearch"; import IConnection from "../IConnection"; import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; -interface IBackwardPromise { +interface IViewPromise { + backward: boolean; lowerBoundDate: Date; upperBoundDate: Date; resolve: (iterator: AsyncIterator) => void; @@ -11,23 +12,25 @@ interface IBackwardPromise { export default class ConnectionsStore { private readonly store: IConnection[]; - private backwardPromises: IBackwardPromise[]; + private readonly binarySearch: BinarySearch; + private viewPromises: IViewPromise[]; constructor() { this.store = []; - this.backwardPromises = []; + this.binarySearch = new BinarySearch(this.store, (connection) => connection.departureTime.valueOf()); + this.viewPromises = []; } public append(connection: IConnection) { this.store.push(connection); - // Check if any backward promises are satisfied - if (this.backwardPromises.length) { - this.backwardPromises = this.backwardPromises - .filter(({ lowerBoundDate, upperBoundDate, resolve }) => { + // Check if any view promises are satisfied + if (this.viewPromises.length) { + this.viewPromises = this.viewPromises + .filter(({ backward, lowerBoundDate, upperBoundDate, resolve }) => { if (connection.departureTime > upperBoundDate) { - const iteratorView = this.getBackwardIterator(lowerBoundDate, upperBoundDate); + const iteratorView = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); resolve(iteratorView); return false; @@ -38,56 +41,88 @@ export default class ConnectionsStore { } } - public getIteratorView(fetcherConfig: IConnectionsFetcherConfig): Promise> { - const { lowerBoundDate, upperBoundDate, backward } = fetcherConfig; + public getIterator(fetcherConfig: IConnectionsFetcherConfig): Promise> { + const { backward } = fetcherConfig; + let { lowerBoundDate, upperBoundDate } = fetcherConfig; - if (!backward) { - throw new Error("Backward is not yet supported"); - } + const firstConnection = this.store[0]; + const firstDepartureTime = firstConnection && firstConnection.departureTime; - if (backward && upperBoundDate) { - const lastConnection = this.store[this.store.length - 1]; - const lastDepartureTime = lastConnection && lastConnection.departureTime; + const lastConnection = this.store[this.store.length - 1]; + const lastDepartureTime = lastConnection && lastConnection.departureTime; - // If the store is still empty or the latest departure time isn't later than the upperBoundDate, - // then return a promise - if (!lastDepartureTime || lastDepartureTime <= upperBoundDate) { - const backwardPromise: Partial = { - lowerBoundDate, - upperBoundDate, - }; + if (!backward) { + if (!lowerBoundDate) { + throw new Error("Must supply lowerBoundDate when iterating forward"); + } - const promise = new Promise>((resolve, reject) => { - backwardPromise.resolve = resolve; - }); + if (!upperBoundDate) { + upperBoundDate = lastDepartureTime; + } + } - this.backwardPromises.push(backwardPromise as IBackwardPromise); + if (backward) { + if (!upperBoundDate) { + throw new Error("Must supply upperBoundDate when iterating backward"); + } - return promise; + if (!lowerBoundDate) { + lowerBoundDate = firstDepartureTime; } + } + + // If the store is still empty or the latest departure time isn't later than the upperBoundDate, + // then return a promise + if (!lastDepartureTime || lastDepartureTime <= upperBoundDate) { + const { viewPromise, promise } = this.createViewPromise(backward, lowerBoundDate, upperBoundDate); + + this.viewPromises.push(viewPromise); - // Else if the whole interval is available, return an iterator immediately - return Promise.resolve(this.getBackwardIterator(lowerBoundDate, upperBoundDate)); + return promise; } + + // Else if the whole interval is available, return an iterator immediately + return Promise.resolve(this.getIteratorView(backward, lowerBoundDate, upperBoundDate)); } - private getBackwardIterator(lowerBoundDate, upperBoundDate): AsyncIterator { + private createViewPromise(backward, lowerBoundDate, upperBoundDate): + { viewPromise: IViewPromise, promise: Promise> } { + + const viewPromise: Partial = { + backward, + lowerBoundDate, + upperBoundDate, + }; + + const promise = new Promise>((resolve) => { + viewPromise.resolve = resolve; + }); + + return { + viewPromise: viewPromise as IViewPromise, + promise, + }; + } + + private getIteratorView(backward: boolean, lowerBoundDate: Date, upperBoundDate: Date): AsyncIterator { + const lowerBoundIndex = this.getLowerBoundIndex(lowerBoundDate); const upperBoundIndex = this.getUpperBoundIndex(upperBoundDate); - const lowerBoundIndex = 0; const indexIteratorOptions: IntegerIteratorOptions = { - start: upperBoundIndex, - end: lowerBoundIndex, - step: -1, + start: backward ? upperBoundIndex : lowerBoundIndex, + end: backward ? lowerBoundIndex : upperBoundIndex, + step: backward ? -1 : 1, }; return new IntegerIterator(indexIteratorOptions) .map((index) => this.store[index]); } - private getUpperBoundIndex(date: Date): number { - const binarySearch = new BinarySearch(this.store, (connection) => connection.departureTime.valueOf()); + private getLowerBoundIndex(date: Date): number { + return this.binarySearch.findFirstIndex(date.valueOf(), 0, this.store.length - 1); + } - return binarySearch.findLastIndex(date.valueOf(), 0, this.store.length - 1); + private getUpperBoundIndex(date: Date): number { + return this.binarySearch.findLastIndex(date.valueOf(), 0, this.store.length - 1); } } diff --git a/src/util/BinarySearch.test.ts b/src/util/BinarySearch.test.ts index 974c710b..4f85218f 100644 --- a/src/util/BinarySearch.test.ts +++ b/src/util/BinarySearch.test.ts @@ -6,32 +6,36 @@ describe("[BinarySearch]", () => { const array = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; const search = new BinarySearch(array, (a) => a); - it("findLastIndex: key exists", () => { - const resultIndex = search.findLastIndex(6); - const expectedIndex = 8; + describe("findLastIndex", () => { - expect(resultIndex).toBe(expectedIndex); - }); + it("key exists", () => { + const resultIndex = search.findLastIndex(6); + const expectedIndex = 8; - it("findLastIndex: key doesn\'t exist / belongs at the start", () => { - const resultIndex = search.findLastIndex(0); - const expectedIndex = 0; + expect(resultIndex).toBe(expectedIndex); + }); - expect(resultIndex).toBe(expectedIndex); - }); + it("key doesn\'t exist / belongs at the start", () => { + const resultIndex = search.findLastIndex(0); + const expectedIndex = 0; - it("findLastIndex: key doesn\'t exist / belongs in middle", () => { - const resultIndex = search.findLastIndex(4); - const expectedIndex = 3; + expect(resultIndex).toBe(expectedIndex); + }); - expect(resultIndex).toBe(expectedIndex); - }); + it("key doesn\'t exist / belongs in middle", () => { + const resultIndex = search.findLastIndex(4); + const expectedIndex = 3; + + expect(resultIndex).toBe(expectedIndex); + }); + + it("key doesn\'t exist / belongs at the end", () => { + const resultIndex = search.findLastIndex(8); + const expectedIndex = 10; - it("findLastIndex: key doesn\'t exist / belongs at the end", () => { - const resultIndex = search.findLastIndex(8); - const expectedIndex = 10; + expect(resultIndex).toBe(expectedIndex); + }); - expect(resultIndex).toBe(expectedIndex); }); }); diff --git a/src/util/BinarySearch.ts b/src/util/BinarySearch.ts index 8b862124..ead68a9e 100644 --- a/src/util/BinarySearch.ts +++ b/src/util/BinarySearch.ts @@ -7,6 +7,19 @@ export default class BinarySearch { this.predicate = predicate; } + /** + * Find the first index of the given key, or the index after which that key would be hypothetically spliced in + * Adapted from: https://www.algorithmsandme.com/last-occurrence-of-element-with-binary-search/ + * @param key + * @param start + * @param end + */ + public findFirstIndex(key: number, start: number = 0, end: number = (this.array.length - 1)) { + while (start < end) { + const mid = start + Math.floor(((end - start) + 1) / 2); + } + } + /** * Find the last index of the given key, or the index after which that key would be hypothetically spliced in * Adapted from: https://www.algorithmsandme.com/last-occurrence-of-element-with-binary-search/ @@ -28,4 +41,5 @@ export default class BinarySearch { return start; } + } From 1eb59c9fc29cdc7980ba9fd85723887dfb9f242f Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 17 Jan 2019 11:47:30 +0100 Subject: [PATCH 13/69] Implement convenient LocationResolver that accepts stop names as input --- .../LocationResolverConvenience.test.ts | 60 +++++++++++++++++++ .../LocationResolverConvenience.ts | 53 ++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/query-runner/LocationResolverConvenience.test.ts create mode 100644 src/query-runner/LocationResolverConvenience.ts diff --git a/src/query-runner/LocationResolverConvenience.test.ts b/src/query-runner/LocationResolverConvenience.test.ts new file mode 100644 index 00000000..3da63c76 --- /dev/null +++ b/src/query-runner/LocationResolverConvenience.test.ts @@ -0,0 +1,60 @@ +import "jest"; +import LDFetch from "ldfetch"; +import StopsFetcherLDFetch from "../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; +import LocationResolverConvenience from "./LocationResolverConvenience"; + +const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); + +const stopsFetcher = new StopsFetcherLDFetch(ldFetch); +stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); + +const locationResolver = new LocationResolverConvenience(stopsFetcher); + +describe("[LocationResolverConvenience]", () => { + + it("Input 'Kortrijk' (exact stop name)", async () => { + + const location = await locationResolver.resolve("Kortrijk"); + + expect(location).toBeDefined(); + expect(location.latitude).toBeCloseTo(50.82, 2); + }); + + it("Input 'Narnia' (non-existent stop name)", async () => { + expect.assertions(1); + expect(locationResolver.resolve( + "Narnia", + )).rejects.toBeDefined(); + }); + + it("Input {id: 'http://...'}", async () => { + + const location = await locationResolver.resolve( + { id: "http://irail.be/stations/NMBS/008896008" }, + ); + + expect(location).toBeDefined(); + expect(location.latitude).toBeCloseTo(50.82, 2); + }); + + it("Input 'http://...'", async () => { + + const location = await locationResolver.resolve( + "http://irail.be/stations/NMBS/008896008", + ); + + expect(location).toBeDefined(); + expect(location.latitude).toBeCloseTo(50.82, 2); + }); + + it("Input {longitude: ..., latitude: ...}", async () => { + + const location = await locationResolver.resolve( + { latitude: 50.824506, longitude: 3.264549 }, + ); + + expect(location).toBeDefined(); + expect(location.latitude).toBeCloseTo(50.82, 2); + }); + +}); diff --git a/src/query-runner/LocationResolverConvenience.ts b/src/query-runner/LocationResolverConvenience.ts new file mode 100644 index 00000000..992bc859 --- /dev/null +++ b/src/query-runner/LocationResolverConvenience.ts @@ -0,0 +1,53 @@ +import { inject, injectable } from "inversify"; +import LocationResolverError from "../errors/LocationResolverError"; +import IStop from "../fetcher/stops/IStop"; +import IStopsProvider from "../fetcher/stops/IStopsProvider"; +import ILocation from "../interfaces/ILocation"; +import TYPES from "../types"; +import ILocationResolver from "./ILocationResolver"; +import LocationResolverDefault from "./LocationResolverDefault"; + +/** + * Location resolver that allows stop names as input + * Falls back to LocationResolverDefault + */ +@injectable() +export default class LocationResolverConvenience implements ILocationResolver { + private readonly stopsProvider: IStopsProvider; + private readonly defaultLocationResolver: ILocationResolver; + + private allStops: IStop[]; + + constructor( + @inject(TYPES.StopsProvider) stopsProvider: IStopsProvider, + ) { + this.stopsProvider = stopsProvider; + this.defaultLocationResolver = new LocationResolverDefault(this.stopsProvider); + } + + public async resolve(input: ILocation | IStop | string): Promise { + + if (typeof input === "string" && !this.isId(input)) { + + if (!this.allStops) { + this.allStops = await this.stopsProvider.getAllStops(); + } + + const matchingStop = this.allStops.find((stop: IStop) => stop.name === input); + + if (matchingStop) { + return matchingStop; + } + + return Promise.reject( + new LocationResolverError(`Location "${input}" is a string, but not an ID and not a valid stop name`), + ); + } + + return this.defaultLocationResolver.resolve(input); + } + + private isId(testString: string): boolean { + return testString.indexOf("http://") === 0 || testString.indexOf("https://") === 0; + } +} From 9c8871426a058fdd2c89bcd326b6e1d3c0ff0419 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 17 Jan 2019 11:52:13 +0100 Subject: [PATCH 14/69] Activate LocationResolverConvenience for demo --- src/demo.ts | 4 ++-- src/inversify.config.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/demo.ts b/src/demo.ts index b1d7f5e9..f2d8cec2 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -56,7 +56,7 @@ export default async (logResults) => { }); } - return wait(1000) + return wait(5000) .then(() => new Promise((resolve, reject) => { if (logResults) { console.log("Start query"); @@ -70,7 +70,7 @@ export default async (logResults) => { // to: "https://data.delijn.be/stops/502481", // Tielt Metaalconstructie Goossens // from: "https://data.delijn.be/stops/509927", // Tield Rameplein perron 1 // to: "https://data.delijn.be/stops/200455", // Deinze weg op Grammene +456 - from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster + from: "Ingelmunster", // Ingelmunster to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters minimumDepartureTime: new Date(), maximumTransferDuration: Units.fromHours(0.5), diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 71536592..366cf0ab 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -27,13 +27,13 @@ import ReachableStopsFinderRoadPlannerCached from "./planner/stops/ReachableStop import QueryRunnerExponential from "./query-runner/exponential/QueryRunnerExponential"; import ILocationResolver from "./query-runner/ILocationResolver"; import IQueryRunner from "./query-runner/IQueryRunner"; -import LocationResolverDefault from "./query-runner/LocationResolverDefault"; +import LocationResolverConvenience from "./query-runner/LocationResolverConvenience"; import TYPES from "./types"; const container = new Container(); container.bind(TYPES.Context).to(Context).inSingletonScope(); container.bind(TYPES.QueryRunner).to(QueryRunnerExponential); -container.bind(TYPES.LocationResolver).to(LocationResolverDefault); +container.bind(TYPES.LocationResolver).to(LocationResolverConvenience); container.bind(TYPES.PublicTransportPlanner) .to(PublicTransportPlannerCSAProfile); From 147e11b23c0090fa3eea4f03dbc13d87a30df59e Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 17 Jan 2019 13:05:32 +0100 Subject: [PATCH 15/69] Rename one use of "fetcher configs" to "source configs" --- src/Catalog.ts | 25 +++++++++++++------ src/catalog.delijn.ts | 20 +++++++-------- src/catalog.nmbs.ts | 4 +-- .../connections/ConnectionsProviderMerge.ts | 2 +- .../ConnectionsProviderPassthrough.ts | 2 +- .../prefetch/ConnectionsProviderPrefetch.ts | 2 +- src/fetcher/stops/StopsProviderDefault.ts | 2 +- 7 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/Catalog.ts b/src/Catalog.ts index ebacd53d..d0909288 100644 --- a/src/Catalog.ts +++ b/src/Catalog.ts @@ -6,21 +6,30 @@ export default class Catalog { const combinedCatalog = new Catalog(); for (const sourceCatalog of catalogs) { - combinedCatalog.stopsFetcherConfigs.push(...sourceCatalog.stopsFetcherConfigs); - combinedCatalog.connectionsFetcherConfigs.push(...sourceCatalog.connectionsFetcherConfigs); + combinedCatalog.stopsSourceConfigs.push(...sourceCatalog.stopsSourceConfigs); + combinedCatalog.connectionsSourceConfigs.push(...sourceCatalog.connectionsSourceConfigs); } return combinedCatalog; } - public stopsFetcherConfigs = []; - public connectionsFetcherConfigs = []; + public stopsSourceConfigs: IStopsSourceConfig[] = []; + public connectionsSourceConfigs: IConnectionsSourceConfig[] = []; - public addStopsFetcher(accessUrl: string) { - this.stopsFetcherConfigs.push({accessUrl}); + public addStopsSource(accessUrl: string) { + this.stopsSourceConfigs.push({accessUrl}); } - public addConnectionsFetcher(accessUrl: string, travelMode: TravelMode) { - this.connectionsFetcherConfigs.push({accessUrl, travelMode}); + public addConnectionsSource(accessUrl: string, travelMode: TravelMode) { + this.connectionsSourceConfigs.push({accessUrl, travelMode}); } } + +export interface IStopsSourceConfig { + accessUrl: string; +} + +export interface IConnectionsSourceConfig { + accessUrl: string; + travelMode: TravelMode; +} diff --git a/src/catalog.delijn.ts b/src/catalog.delijn.ts index eeaec78a..528a24d4 100644 --- a/src/catalog.delijn.ts +++ b/src/catalog.delijn.ts @@ -3,16 +3,16 @@ import TravelMode from "./enums/TravelMode"; const catalog = new Catalog(); -catalog.addStopsFetcher("http://openplanner.ilabt.imec.be/delijn/Antwerpen/stops"); -catalog.addStopsFetcher("http://openplanner.ilabt.imec.be/delijn/Limburg/stops"); -catalog.addStopsFetcher("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops"); -catalog.addStopsFetcher("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/stops"); -catalog.addStopsFetcher("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); +catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Antwerpen/stops"); +catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Limburg/stops"); +catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops"); +catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/stops"); +catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); -catalog.addConnectionsFetcher("http://openplanner.ilabt.imec.be/delijn/Antwerpen/connections", TravelMode.Bus); -catalog.addConnectionsFetcher("http://openplanner.ilabt.imec.be/delijn/Limburg/connections", TravelMode.Bus); -catalog.addConnectionsFetcher("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); -catalog.addConnectionsFetcher("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/connections", TravelMode.Bus); -catalog.addConnectionsFetcher("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/connections", TravelMode.Bus); +catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Antwerpen/connections", TravelMode.Bus); +catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Limburg/connections", TravelMode.Bus); +catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); +catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/connections", TravelMode.Bus); +catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/connections", TravelMode.Bus); export default catalog; diff --git a/src/catalog.nmbs.ts b/src/catalog.nmbs.ts index 554f04a7..1d2a19f9 100644 --- a/src/catalog.nmbs.ts +++ b/src/catalog.nmbs.ts @@ -2,7 +2,7 @@ import Catalog from "./Catalog"; import TravelMode from "./enums/TravelMode"; const catalog = new Catalog(); -catalog.addStopsFetcher("https://irail.be/stations/NMBS"); -catalog.addConnectionsFetcher("https://graph.irail.be/sncb/connections", TravelMode.Train); +catalog.addStopsSource("https://irail.be/stations/NMBS"); +catalog.addConnectionsSource("https://graph.irail.be/sncb/connections", TravelMode.Train); export default catalog; diff --git a/src/fetcher/connections/ConnectionsProviderMerge.ts b/src/fetcher/connections/ConnectionsProviderMerge.ts index 9a67fb0e..a73ed743 100644 --- a/src/fetcher/connections/ConnectionsProviderMerge.ts +++ b/src/fetcher/connections/ConnectionsProviderMerge.ts @@ -60,7 +60,7 @@ export default class ConnectionsProviderMerge implements IConnectionsFetcher { ) { this.connectionsFetchers = []; - for (const { accessUrl, travelMode } of catalog.connectionsFetcherConfigs) { + for (const { accessUrl, travelMode } of catalog.connectionsSourceConfigs) { this.connectionsFetchers.push(connectionsFetcherFactory(accessUrl, travelMode)); } } diff --git a/src/fetcher/connections/ConnectionsProviderPassthrough.ts b/src/fetcher/connections/ConnectionsProviderPassthrough.ts index fc97fb81..4673682a 100644 --- a/src/fetcher/connections/ConnectionsProviderPassthrough.ts +++ b/src/fetcher/connections/ConnectionsProviderPassthrough.ts @@ -20,7 +20,7 @@ export default class ConnectionsProviderPassthrough implements IConnectionsProvi @inject(TYPES.ConnectionsFetcherFactory) connectionsFetcherFactory: ConnectionsFetcherFactory, @inject(TYPES.Catalog) catalog: Catalog, ) { - const { accessUrl, travelMode } = catalog.connectionsFetcherConfigs[0]; + const { accessUrl, travelMode } = catalog.connectionsSourceConfigs[0]; this.connectionsFetcher = connectionsFetcherFactory(accessUrl, travelMode); } diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index eeacdf9e..00188999 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -29,7 +29,7 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider @inject(TYPES.ConnectionsFetcherFactory) connectionsFetcherFactory: ConnectionsFetcherFactory, @inject(TYPES.Catalog) catalog: Catalog, ) { - const { accessUrl, travelMode } = catalog.connectionsFetcherConfigs[0]; + const { accessUrl, travelMode } = catalog.connectionsSourceConfigs[0]; this.connectionsFetcher = connectionsFetcherFactory(accessUrl, travelMode); this.connectionsStore = new ConnectionsStore(); diff --git a/src/fetcher/stops/StopsProviderDefault.ts b/src/fetcher/stops/StopsProviderDefault.ts index 0a84d8ee..7df172c2 100644 --- a/src/fetcher/stops/StopsProviderDefault.ts +++ b/src/fetcher/stops/StopsProviderDefault.ts @@ -16,7 +16,7 @@ export default class StopsProviderDefault implements IStopsProvider { ) { this.stopsFetchers = []; - for (const { accessUrl } of catalog.stopsFetcherConfigs) { + for (const { accessUrl } of catalog.stopsSourceConfigs) { this.stopsFetchers.push(stopsFetcherFactory(accessUrl)); } } From 83e71c061155caa326349a56cc27bdad1c3bf57b Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 17 Jan 2019 14:21:32 +0100 Subject: [PATCH 16/69] Implement BinarySearch#findFirstIndex --- src/util/BinarySearch.test.ts | 32 ++++++++++++++++++++++++++++++++ src/util/BinarySearch.ts | 23 ++++++++++++++++++----- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/util/BinarySearch.test.ts b/src/util/BinarySearch.test.ts index 4f85218f..de946f39 100644 --- a/src/util/BinarySearch.test.ts +++ b/src/util/BinarySearch.test.ts @@ -6,6 +6,38 @@ describe("[BinarySearch]", () => { const array = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; const search = new BinarySearch(array, (a) => a); + describe("findFirstIndex", () => { + + it("key exists", () => { + const resultIndex = search.findFirstIndex(6); + const expectedIndex = 5; + + expect(resultIndex).toBe(expectedIndex); + }); + + it("key doesn\'t exist / belongs at the start", () => { + const resultIndex = search.findFirstIndex(0); + const expectedIndex = 0; + + expect(resultIndex).toBe(expectedIndex); + }); + + it("key doesn\'t exist / belongs in middle", () => { + const resultIndex = search.findFirstIndex(4); + const expectedIndex = 4; + + expect(resultIndex).toBe(expectedIndex); + }); + + it("key doesn\'t exist / belongs at the end", () => { + const resultIndex = search.findFirstIndex(8); + const expectedIndex = 10; + + expect(resultIndex).toBe(expectedIndex); + }); + + }); + describe("findLastIndex", () => { it("key exists", () => { diff --git a/src/util/BinarySearch.ts b/src/util/BinarySearch.ts index ead68a9e..bd34e169 100644 --- a/src/util/BinarySearch.ts +++ b/src/util/BinarySearch.ts @@ -1,3 +1,7 @@ +/** + * Util class with binary search procedures + * Assumes that array is in ascending order according to predicate + */ export default class BinarySearch { private readonly array: T[]; private readonly predicate: (item: T) => number; @@ -8,16 +12,25 @@ export default class BinarySearch { } /** - * Find the first index of the given key, or the index after which that key would be hypothetically spliced in - * Adapted from: https://www.algorithmsandme.com/last-occurrence-of-element-with-binary-search/ + * Find the first index of the given key, or the index before which that key would be hypothetically spliced in + * Adapted from: https://algorithmsandme.com/first-occurrence-of-element/ * @param key * @param start * @param end */ - public findFirstIndex(key: number, start: number = 0, end: number = (this.array.length - 1)) { + public findFirstIndex(key: number, start: number = 0, end: number = (this.array.length - 1)): number { while (start < end) { - const mid = start + Math.floor(((end - start) + 1) / 2); + const mid = start + Math.floor((end - start) / 2); + + if (this.predicate(this.array[mid]) >= key) { + end = mid; + + } else { + start = mid + 1; + } } + + return start; } /** @@ -27,7 +40,7 @@ export default class BinarySearch { * @param start * @param end */ - public findLastIndex(key: number, start: number = 0, end: number = (this.array.length - 1)) { + public findLastIndex(key: number, start: number = 0, end: number = (this.array.length - 1)): number { while (start < end) { const mid = start + Math.floor(((end - start) + 1) / 2); From bbe3ea46f12d50e002bf956cee1cc6b3026fd661 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 17 Jan 2019 15:25:45 +0100 Subject: [PATCH 17/69] Add "finish' signal to ConnectionsStore --- .../prefetch/ConnectionsProviderPrefetch.ts | 1 + .../prefetch/ConnectionsStore.test.ts | 41 ++++++++++++------- .../connections/prefetch/ConnectionsStore.ts | 20 +++++++-- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index 16006ac3..5637ed0c 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -50,6 +50,7 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.connectionsIterator .take(MAX_CONNECTIONS) + .on("end", () => this.connectionsStore.finish()) .each((connection: IConnection) => { this.connectionsStore.append(connection); }); diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts index c226fb66..463cfcb5 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -28,6 +28,8 @@ describe("[ConnectionsStore]", () => { connectionsStore.append(connection); } + connectionsStore.finish(); + createIterator = async (backward, lowerBoundDate, upperBoundDate): Promise> => { const fetcherConfig: IConnectionsFetcherConfig = { backward, @@ -58,7 +60,10 @@ describe("[ConnectionsStore]", () => { expect(expected[current--]).toBe(str.departureTime); }); - iteratorView.on("end", () => done()); + iteratorView.on("end", () => { + expect(current).toBe(-1); + done(); + }); }); it("upperBoundDate is loaded but doesn\'t exist in store", async (done) => { @@ -71,7 +76,10 @@ describe("[ConnectionsStore]", () => { expect(expected[current--]).toBe(str.departureTime); }); - iteratorView.on("end", () => done()); + iteratorView.on("end", () => { + expect(current).toBe(-1); + done(); + }); }); }); @@ -79,23 +87,17 @@ describe("[ConnectionsStore]", () => { describe("forward", () => { it("lowerBoundDate is loaded & exists in store", async (done) => { - const iteratorView = await createIterator(false, 3, 6); + const iteratorView = await createIterator(false, 3, null); - console.log("a"); - - const expected = [3, 3, 5, 6, 6, 6, 6]; + const expected = [3, 3, 5, 6, 6, 6, 6, 7, 7]; let current = 0; - console.log("b"); - iteratorView.each((str: IConnection) => { - console.log(str); expect(expected[current++]).toBe(str.departureTime); }); iteratorView.on("end", () => { - expect(current).toBe(expected.length - 1); - console.log("c"); + expect(current).toBe(expected.length); done(); }); }); @@ -103,14 +105,17 @@ describe("[ConnectionsStore]", () => { it("lowerBoundDate is loaded but doesn\'t exist in store", async (done) => { const iteratorView = await createIterator(false, 4, null); - const expected = [1, 2, 3, 3]; + const expected = [5, 6, 6, 6, 6, 7, 7]; let current = 0; iteratorView.each((str: IConnection) => { - expect(expected[current--]).toBe(str.departureTime); + expect(expected[current++]).toBe(str.departureTime); }); - iteratorView.on("end", () => done()); + iteratorView.on("end", () => { + expect(current).toBe(expected.length); + done(); + }); }); }); @@ -139,6 +144,9 @@ describe("[ConnectionsStore]", () => { if (i < fakeConnections.length) { setTimeout(appendNext, 100); + + } else { + connectionsStore.finish(); } }; @@ -158,7 +166,10 @@ describe("[ConnectionsStore]", () => { expect(expected[current--]).toBe(str.departureTime); }); - iteratorView.on("end", () => done()); + iteratorView.on("end", () => { + expect(current).toBe(-1); + done(); + }); }); }); diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 25b87bf8..1a74300e 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -1,4 +1,4 @@ -import { AsyncIterator, IntegerIterator, IntegerIteratorOptions } from "asynciterator"; +import { ArrayIterator, AsyncIterator, IntegerIterator, IntegerIteratorOptions } from "asynciterator"; import BinarySearch from "../../../util/BinarySearch"; import IConnection from "../IConnection"; import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; @@ -14,11 +14,13 @@ export default class ConnectionsStore { private readonly store: IConnection[]; private readonly binarySearch: BinarySearch; private viewPromises: IViewPromise[]; + private hasFinished: boolean; constructor() { this.store = []; this.binarySearch = new BinarySearch(this.store, (connection) => connection.departureTime.valueOf()); this.viewPromises = []; + this.hasFinished = false; } public append(connection: IConnection) { @@ -41,10 +43,22 @@ export default class ConnectionsStore { } } + /** + * Signals that the store will no longer be appended. + * [[getIterator]] never returns a view promise after this, because those would never get resolved + */ + public finish(): void { + this.hasFinished = true; + } + public getIterator(fetcherConfig: IConnectionsFetcherConfig): Promise> { const { backward } = fetcherConfig; let { lowerBoundDate, upperBoundDate } = fetcherConfig; + if (this.hasFinished && this.store.length === 0) { + return Promise.resolve(new ArrayIterator([])); + } + const firstConnection = this.store[0]; const firstDepartureTime = firstConnection && firstConnection.departureTime; @@ -73,7 +87,7 @@ export default class ConnectionsStore { // If the store is still empty or the latest departure time isn't later than the upperBoundDate, // then return a promise - if (!lastDepartureTime || lastDepartureTime <= upperBoundDate) { + if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { const { viewPromise, promise } = this.createViewPromise(backward, lowerBoundDate, upperBoundDate); this.viewPromises.push(viewPromise); @@ -81,7 +95,7 @@ export default class ConnectionsStore { return promise; } - // Else if the whole interval is available, return an iterator immediately + // Else if the whole interval is available, or the store has finished, return an iterator immediately return Promise.resolve(this.getIteratorView(backward, lowerBoundDate, upperBoundDate)); } From 7afeab2b4a1d3f6e707a73fc6c6734b46e7f1231 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 17 Jan 2019 15:42:55 +0100 Subject: [PATCH 18/69] 1. CSA EAT added initial + final walking pos. --- src/planner/Path.ts | 8 + .../data-structure/stops/ITransferProfile.ts | 6 +- .../trips/IEarliestArrivalByTrip.ts | 6 +- .../trips/IEnterConnectionByTrip.ts | 10 + .../CSAEarliestArrival.test.ts | 111 +++++++++++ .../public-transport/CSAEarliestArrival.ts | 182 ++++++++++++------ .../public-transport/CSAProfile.test.ts | 4 +- src/planner/public-transport/CSAProfile.ts | 30 ++- .../JourneyExtractorEarliestArrivalTime.ts | 33 +++- 9 files changed, 304 insertions(+), 86 deletions(-) create mode 100644 src/planner/public-transport/CSA/data-structure/trips/IEnterConnectionByTrip.ts diff --git a/src/planner/Path.ts b/src/planner/Path.ts index 9fb55e37..dbc81cdc 100644 --- a/src/planner/Path.ts +++ b/src/planner/Path.ts @@ -32,7 +32,15 @@ export default class Path implements IPath { this.steps.push(step); } + public addPath(path: IPath): void { + this.steps.push(...path.steps); + } + public reverse(): void { this.steps.reverse(); } + + public getStartLocationId(): string { + return (" " + this.steps[0].startLocation.id).slice(1); + } } diff --git a/src/planner/public-transport/CSA/data-structure/stops/ITransferProfile.ts b/src/planner/public-transport/CSA/data-structure/stops/ITransferProfile.ts index 9d8b65f9..06b021cf 100644 --- a/src/planner/public-transport/CSA/data-structure/stops/ITransferProfile.ts +++ b/src/planner/public-transport/CSA/data-structure/stops/ITransferProfile.ts @@ -1,4 +1,5 @@ import IConnection from "../../../../../fetcher/connections/IConnection"; +import Path from "../../../../Path"; /** * Interface for the CSA profile for a specific amount of transfers that can be made. @@ -11,6 +12,7 @@ import IConnection from "../../../../../fetcher/connections/IConnection"; export default interface ITransferProfile { departureTime: number; arrivalTime: number; - exitConnection: IConnection; - enterConnection: IConnection; + exitConnection?: IConnection; + enterConnection?: IConnection; + path?: Path; } diff --git a/src/planner/public-transport/CSA/data-structure/trips/IEarliestArrivalByTrip.ts b/src/planner/public-transport/CSA/data-structure/trips/IEarliestArrivalByTrip.ts index d10e2bbe..31cd4627 100644 --- a/src/planner/public-transport/CSA/data-structure/trips/IEarliestArrivalByTrip.ts +++ b/src/planner/public-transport/CSA/data-structure/trips/IEarliestArrivalByTrip.ts @@ -1,6 +1,8 @@ /** * Stores for each gtfs:trip the earliest arrival [[IEarliestArrivalByTransfers]] to the target [[IStop]]. */ -export default interface IEarliestArrivalByTrip { - [trip: string]: T; +import IEarliestArrivalByTransfers from "./IEarliestArrivalByTransfers"; + +export default interface IEarliestArrivalByTrip { + [trip: string]: IEarliestArrivalByTransfers; } diff --git a/src/planner/public-transport/CSA/data-structure/trips/IEnterConnectionByTrip.ts b/src/planner/public-transport/CSA/data-structure/trips/IEnterConnectionByTrip.ts new file mode 100644 index 00000000..78a3c402 --- /dev/null +++ b/src/planner/public-transport/CSA/data-structure/trips/IEnterConnectionByTrip.ts @@ -0,0 +1,10 @@ +import IConnection from "../../../../../fetcher/connections/IConnection"; + +/** + * @property arrivalTime Describes the earliest arrival time in milliseconds to the target [[IStop]]. + * @property connection Describes the [[IConnection]] that should be taken to arrive + * at the arrivalTime in the target location. + */ +export default interface IEnterConnectionByTrip { + [trip: string]: IConnection; +} diff --git a/src/planner/public-transport/CSAEarliestArrival.test.ts b/src/planner/public-transport/CSAEarliestArrival.test.ts index 6dabe979..7df98797 100644 --- a/src/planner/public-transport/CSAEarliestArrival.test.ts +++ b/src/planner/public-transport/CSAEarliestArrival.test.ts @@ -1,14 +1,19 @@ import "jest"; import LDFetch from "ldfetch"; import Defaults from "../../Defaults"; +import TravelMode from "../../enums/TravelMode"; +import ConnectionsFetcherLazy from "../../fetcher/connections/lazy/ConnectionsFetcherLazy"; import ConnectionsFetcherNMBSTest from "../../fetcher/connections/tests/ConnectionsFetcherNMBSTest"; import connectionsIngelmunsterGhent from "../../fetcher/connections/tests/data/ingelmunster-ghent"; import connectionsJoining from "../../fetcher/connections/tests/data/joining"; import connectionsSplitting from "../../fetcher/connections/tests/data/splitting"; import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import IPath from "../../interfaces/IPath"; +import IQuery from "../../interfaces/IQuery"; +import IStep from "../../interfaces/IStep"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import LocationResolverDefault from "../../query-runner/LocationResolverDefault"; +import QueryRunnerDefault from "../../query-runner/QueryRunnerDefault"; import Iterators from "../../util/Iterators"; import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBirdsEyeCached"; import CSAEarliestArrival from "./CSAEarliestArrival"; @@ -166,4 +171,110 @@ describe("[PublicTransportPlannerCSAProfile]", () => { }); }); }); + + describe("real-time data", () => { + const createQueryRunner = () => { + const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); + + const connectionFetcher = new ConnectionsFetcherLazy(ldFetch); + connectionFetcher.setTravelMode(TravelMode.Train); + connectionFetcher.setAccessUrl("https://graph.irail.be/sncb/connections"); + + const stopsFetcher = new StopsFetcherLDFetch(ldFetch); + stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); + + const locationResolver = new LocationResolverDefault(stopsFetcher); + const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); + const journeyExtractor = new JourneyExtractorEarliestArrivalTime( + locationResolver, + ); + + const CSA = new CSAEarliestArrival( + connectionFetcher, + locationResolver, + reachableStopsFinder, + reachableStopsFinder, + reachableStopsFinder, + journeyExtractor, + ); + + return new QueryRunnerDefault(locationResolver, CSA); + }; + + const checkStops = (result, query) => { + expect(result).toBeDefined(); + expect(result.length).toBeGreaterThanOrEqual(1); + + for (const path of result) { + expect(path.steps).toBeDefined(); + + expect(path.steps.length).toBeGreaterThanOrEqual(1); + + let currentLocation = query.from; + path.steps.forEach((step: IStep) => { + expect(step).toBeDefined(); + expect(currentLocation).toEqual(step.startLocation.id); + currentLocation = step.stopLocation.id; + }); + + expect(query.to).toEqual(currentLocation); + } + }; + + const checkTimes = (result, minimumDepartureTime) => { + expect(result).toBeDefined(); + expect(result.length).toBeGreaterThanOrEqual(1); + + for (const path of result) { + expect(path.steps).toBeDefined(); + + expect(path.steps.length).toBeGreaterThanOrEqual(1); + expect(path.steps[0]).toBeDefined(); + expect(path.steps[path.steps.length - 1]).toBeDefined(); + + let currentTime = minimumDepartureTime.getTime(); + path.steps.forEach((step: IStep) => { + expect(step).toBeDefined(); + if (step.travelMode === TravelMode.Walking) { + currentTime += step.duration.minimum; + } else { + expect(currentTime).toBeLessThanOrEqual(step.startTime.getTime()); + currentTime = step.stopTime.getTime(); + } + }); + + expect(path.steps[0].startTime.getTime()).toBeGreaterThanOrEqual(minimumDepartureTime.getTime()); + } + }; + + describe("Departure Time now - Arrival Time now+2h", () => { + jest.setTimeout(100000); + + const minimumDepartureTime = new Date(); + + const query: IQuery = { + publicTransportOnly: true, + from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster + to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters + minimumDepartureTime, + }; + let result: IPath[]; + + beforeAll(async () => { + const queryRunner = createQueryRunner(); + const iterator = await queryRunner.run(query); + + result = await Iterators.toArray(iterator); + console.log(JSON.stringify(result, null, " ")); + }); + + it("Correct departure and arrival stop", () => { + checkStops(result, query); + }); + + it("Correct departure and arrival time", () => { + checkTimes(result, minimumDepartureTime); + }); + }); + }); }); diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index 02e329d5..a4dc1d3f 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -6,19 +6,21 @@ import EventType from "../../enums/EventType"; import PickupType from "../../enums/PickupType"; import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import ReachableStopsSearchPhase from "../../enums/ReachableStopsSearchPhase"; +import TravelMode from "../../enums/TravelMode"; import IConnection from "../../fetcher/connections/IConnection"; import IConnectionsProvider from "../../fetcher/connections/IConnectionsProvider"; import IStop from "../../fetcher/stops/IStop"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; -import { DurationMs } from "../../interfaces/units"; +import IStep from "../../interfaces/IStep"; import ILocationResolver from "../../query-runner/ILocationResolver"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import TYPES from "../../types"; +import Path from "../Path"; +import Step from "../Step"; import IReachableStopsFinder, { IReachableStop } from "../stops/IReachableStopsFinder"; import IProfileByStop from "./CSA/data-structure/stops/IProfileByStop"; -import IEarliestArrival from "./CSA/data-structure/trips/IEarliestArrival"; -import IEarliestArrivalByTrip from "./CSA/data-structure/trips/IEarliestArrivalByTrip"; +import IEnterConnectionByTrip from "./CSA/data-structure/trips/IEnterConnectionByTrip"; import IJourneyExtractor from "./IJourneyExtractor"; import IPublicTransportPlanner from "./IPublicTransportPlanner"; @@ -27,8 +29,7 @@ import IPublicTransportPlanner from "./IPublicTransportPlanner"; * * @implements [[IPublicTransportPlanner]] * @property profilesByStop Describes the CSA profiles for each scanned stop. - * @property earliestArrivalByTrip Describes the earliest arrival time for each scanned trip. - * @property durationToTargetByStop Describes the walking duration to the target stop for a scanned stop. + * @property enterConnectionByTrip Describes the connection you should enter at a departure location for each trip. * * @returns multiple [[IPath]]s that consist of several [[IStep]]s. */ @@ -43,10 +44,11 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { private readonly context: Context; private profilesByStop: IProfileByStop = {}; // S - private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T - private durationToTargetByStop: DurationMs[] = []; + private enterConnectionByTrip: IEnterConnectionByTrip = {}; // T private gtfsTripsByConnection = {}; + private initialReachableStops: IReachableStop[] = []; + private finalReachableStops: IReachableStop[] = []; private query: IResolvedQuery; private connectionsIterator: AsyncIterator; @@ -100,8 +102,8 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } private async calculateJourneys(): Promise> { - await this.initArrivalStopProfile(); await this.initInitialReachableStops(); + await this.initFinalReachableStops(); this.connectionsIterator = this.connectionsProvider.createIterator(); @@ -151,7 +153,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const tripIds = this.getTripIdsFromConnection(connection); for (const tripId of tripIds) { - const canRemainSeated = this.earliestArrivalByTrip[tripId].connection; + const canRemainSeated = this.enterConnectionByTrip[tripId]; const canTakeTransfer = (this.profilesByStop[connection.departureStop].arrivalTime <= connection.departureTime.getTime() && connection["gtfs:pickupType"] !== PickupType.NotAvailable); @@ -178,6 +180,12 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { private async initInitialReachableStops(): Promise { const fromLocation: IStop = this.query.from[0] as IStop; + // Making sure the departure location has an id + if (!fromLocation.id) { + this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; + this.query.from[0].name = "Departure location"; + } + this.initialReachableStops = await this.initialReachableStopsFinder.findReachableStops( fromLocation, ReachableStopsFinderMode.Source, @@ -185,24 +193,6 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { this.query.minimumWalkingSpeed, ); - // Check if departure location is a stop. - let stopIndex = 0; - while (stopIndex < this.initialReachableStops.length && !this.query.from[0].id) { - const reachableStop = this.initialReachableStops[stopIndex]; - - if (reachableStop.duration === 0) { - this.query.from[0] = reachableStop.stop; - } - - stopIndex++; - } - - // Making sure the departure location has an id - if (!fromLocation.id) { - this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; - this.query.from[0].name = "Departure location"; - } - // Abort when we can't reach a single stop. if (this.initialReachableStops.length === 0 && this.context) { this.context.emit(EventType.AbortQuery, "No reachable stops at departure location"); @@ -210,32 +200,89 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { return false; } + // Check if departure location is a stop. + for (const reachableStop of this.initialReachableStops) { + if (reachableStop.duration === 0) { + this.query.from[0] = reachableStop.stop; + } + } + if (this.context) { this.context.emit(EventType.InitialReachableStops, this.initialReachableStops); } this.initialReachableStops.forEach(({ stop, duration }: IReachableStop) => { + const departureTime = this.query.minimumDepartureTime.getTime(); + const arrivalTime = this.query.minimumDepartureTime.getTime() + duration; + this.profilesByStop[stop.id] = { - departureTime: this.query.minimumDepartureTime.getTime(), - arrivalTime: this.query.minimumDepartureTime.getTime() + duration, - exitConnection: undefined, - enterConnection: undefined, + departureTime, + arrivalTime, }; + if (duration > 0) { + const path: Path = Path.create(); + + const footpath: IStep = Step.create( + this.query.from[0], + stop, + TravelMode.Walking, + { + minimum: arrivalTime - departureTime, + }, + new Date(departureTime), + new Date(arrivalTime), + ); + + path.addStep(footpath); + + this.profilesByStop[stop.id].path = path; + } + }); return true; } - private initArrivalStopProfile(): void { - const arrivalStopId: string = this.query.to[0].id; + private async initFinalReachableStops(): Promise { + const arrivalStop: IStop = this.query.to[0] as IStop; + + if (!this.query.to[0].id) { + this.query.to[0].id = "geo:" + arrivalStop.latitude + "," + arrivalStop.longitude; + this.query.to[0].name = "Arrival location"; + } - this.profilesByStop[arrivalStopId] = { + this.finalReachableStops = await this.finalReachableStopsFinder + .findReachableStops( + arrivalStop, + ReachableStopsFinderMode.Target, + this.query.maximumWalkingDuration, + this.query.minimumWalkingSpeed, + ); + + if (this.finalReachableStops.length === 0 && this.context) { + this.context.emit(EventType.AbortQuery, "No reachable stops at arrival location"); + + return false; + } + + if (this.context) { + this.context.emit(EventType.FinalReachableStops, this.finalReachableStops); + } + + // Check if arrival location is a stop. + for (const reachableStop of this.finalReachableStops) { + if (reachableStop.duration === 0) { + this.query.to[0] = reachableStop.stop; + } + } + + this.profilesByStop[this.query.to[0].id] = { departureTime: Infinity, arrivalTime: Infinity, - exitConnection: undefined, - enterConnection: undefined, }; + + return true; } private discoverConnection(connection: IConnection) { @@ -246,22 +293,9 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { this.profilesByStop[stop] = { departureTime: Infinity, arrivalTime: Infinity, - exitConnection: undefined, - enterConnection: undefined, }; } }); - - const tripIds: string[] = this.getTripIdsFromConnection(connection); - - for (const tripId of tripIds) { - if (!this.earliestArrivalByTrip[tripId]) { - this.earliestArrivalByTrip[tripId] = { - arrivalTime: Infinity, - connection: undefined, - }; - } - } } private getTripIdsFromConnection(connection: IConnection): string[] { @@ -291,11 +325,12 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } private updateTrips(connection: IConnection, tripId: string): void { - if (!this.earliestArrivalByTrip[tripId].connection) { - this.earliestArrivalByTrip[tripId] = { - arrivalTime: Infinity, // don't need this - connection, // first connection of the trip we are taking - }; + const isInitialReachableStop = this.initialReachableStops.find(({ stop }: IReachableStop) => + stop.id === connection.departureStop, + ); + + if (!this.enterConnectionByTrip[tripId] || isInitialReachableStop) { + this.enterConnectionByTrip[tripId] = connection; } } @@ -309,7 +344,8 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { this.query.minimumWalkingSpeed, ); - reachableStops.forEach(({stop, duration}: IReachableStop) => { + reachableStops.forEach((reachableStop: IReachableStop) => { + const { stop, duration } = reachableStop; const reachableStopArrival = this.profilesByStop[stop.id].arrivalTime; if (reachableStopArrival > connection.arrivalTime.getTime() + duration) { @@ -317,14 +353,48 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { departureTime: connection.departureTime.getTime(), arrivalTime: connection.arrivalTime.getTime() + duration, exitConnection: connection, - enterConnection: this.earliestArrivalByTrip[tripId].connection, + enterConnection: this.enterConnectionByTrip[tripId], }; } + this.checkIfArrivalStopIsReachable(connection, reachableStop); + }); } catch (e) { this.context.emitWarning(e); } } + + private checkIfArrivalStopIsReachable(connection: IConnection, { stop, duration }: IReachableStop): void { + const canReachArrivalStop = this.finalReachableStops.find((reachableStop: IReachableStop) => { + return reachableStop.stop.id === stop.id; + }); + + if (canReachArrivalStop && canReachArrivalStop.duration > 0) { + const departureTime = connection.arrivalTime.getTime() + duration; + const arrivalTime = connection.arrivalTime.getTime() + duration + canReachArrivalStop.duration; + + const path: Path = Path.create(); + + const footpath: IStep = Step.create( + stop, + this.query.to[0], + TravelMode.Walking, + { + minimum: arrivalTime - departureTime, + }, + new Date(departureTime), + new Date(arrivalTime), + ); + + path.addStep(footpath); + + this.profilesByStop[this.query.to[0].id] = { + departureTime, + arrivalTime, + path, + }; + } + } } diff --git a/src/planner/public-transport/CSAProfile.test.ts b/src/planner/public-transport/CSAProfile.test.ts index a638ce95..27601027 100644 --- a/src/planner/public-transport/CSAProfile.test.ts +++ b/src/planner/public-transport/CSAProfile.test.ts @@ -259,7 +259,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const query: IQuery = { publicTransportOnly: true, from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster - to: "http://irail.be/stations/NMBS/008892007", // Antwerpen + to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters maximumArrivalTime, minimumDepartureTime, }; @@ -291,7 +291,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const query: IQuery = { publicTransportOnly: true, from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster - to: "http://irail.be/stations/NMBS/008892007", // Antwerpen + to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters maximumArrivalTime, minimumDepartureTime, }; diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index 80c69fdf..098e8919 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -49,7 +49,7 @@ export default class CSAProfile implements IPublicTransportPlanner { private readonly context: Context; private profilesByStop: IProfilesByStop = {}; // S - private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T + private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T private durationToTargetByStop: DurationMs[] = []; private gtfsTripByConnection = {}; private initialReachableStops: IReachableStop[] = []; @@ -227,6 +227,11 @@ export default class CSAProfile implements IPublicTransportPlanner { private async initDurationToTargetByStop(): Promise { const arrivalStop: IStop = this.query.to[0] as IStop; + if (!this.query.to[0].id) { + this.query.to[0].id = "geo:" + this.query.to[0].latitude + "," + this.query.to[0].longitude; + this.query.to[0].name = "Arrival location"; + } + const reachableStops = await this.finalReachableStopsFinder .findReachableStops( arrivalStop, @@ -246,24 +251,24 @@ export default class CSAProfile implements IPublicTransportPlanner { } for (const reachableStop of reachableStops) { - if (!this.query.to[0].id && reachableStop.duration === 0) { + if (reachableStop.duration === 0) { this.query.to[0] = reachableStop.stop; } this.durationToTargetByStop[reachableStop.stop.id] = reachableStop.duration; } - if (!this.query.to[0].id) { - this.query.to[0].id = "geo:" + this.query.to[0].latitude + "," + this.query.to[0].longitude; - this.query.to[0].name = "Arrival location"; - } - return true; } private async initInitialReachableStops(): Promise { const fromLocation: IStop = this.query.from[0] as IStop; + if (!this.query.from[0].id) { + this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; + this.query.from[0].name = "Departure location"; + } + this.initialReachableStops = await this.initialReachableStopsFinder.findReachableStops( fromLocation, ReachableStopsFinderMode.Source, @@ -271,19 +276,10 @@ export default class CSAProfile implements IPublicTransportPlanner { this.query.minimumWalkingSpeed, ); - let stopIndex = 0; - while (stopIndex < this.initialReachableStops.length && !fromLocation.id) { - const reachableStop = this.initialReachableStops[stopIndex]; + for (const reachableStop of this.initialReachableStops) { if (reachableStop.duration === 0) { this.query.from[0] = reachableStop.stop; } - - stopIndex++; - } - - if (!this.query.from[0].id) { - this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; - this.query.from[0].name = "Departure location"; } if (this.initialReachableStops.length === 0 && this.context) { diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts b/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts index 391365a5..0cd4887f 100644 --- a/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts +++ b/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts @@ -1,6 +1,7 @@ import { AsyncIterator, SingletonIterator } from "asynciterator"; import { inject, injectable } from "inversify"; import Context from "../../Context"; +import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; import IStep from "../../interfaces/IStep"; import ILocationResolver from "../../query-runner/ILocationResolver"; @@ -45,15 +46,33 @@ export default class JourneyExtractorEarliestArrivalTime implements IJourneyExtr let currentProfile: ITransferProfile = profilesByStop[currentStopId]; while (currentStopId !== departureStopId) { - const step: IStep = Step.createFromConnections( - currentProfile.enterConnection, - currentProfile.exitConnection, - ); + const { enterConnection, exitConnection, path: profilePath } = currentProfile; - path.addStep(step); + if (currentProfile.enterConnection && currentProfile.exitConnection) { + + const enterLocation: ILocation = await this.locationResolver.resolve(enterConnection.departureStop); + const exitLocation: ILocation = await this.locationResolver.resolve(exitConnection.arrivalStop); + + const step: IStep = Step.createFromConnections( + enterConnection, + exitConnection, + ); + + step.startLocation = enterLocation; + step.stopLocation = exitLocation; + path.addStep(step); + + currentStopId = enterConnection.departureStop; + currentProfile = profilesByStop[currentStopId]; + } + + if (profilePath) { + path.addPath(profilePath); + + currentStopId = profilePath.getStartLocationId(); + currentProfile = profilesByStop[currentStopId]; + } - currentStopId = currentProfile.enterConnection.departureStop; - currentProfile = profilesByStop[currentStopId]; } path.reverse(); From 1a1a5bc52953093d4ae3982fbe79efede89d11aa Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 17 Jan 2019 15:44:22 +0100 Subject: [PATCH 19/69] 1. remove logs in csa eat tests. --- src/planner/public-transport/CSAEarliestArrival.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/planner/public-transport/CSAEarliestArrival.test.ts b/src/planner/public-transport/CSAEarliestArrival.test.ts index 7df98797..f17c9587 100644 --- a/src/planner/public-transport/CSAEarliestArrival.test.ts +++ b/src/planner/public-transport/CSAEarliestArrival.test.ts @@ -265,7 +265,6 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const iterator = await queryRunner.run(query); result = await Iterators.toArray(iterator); - console.log(JSON.stringify(result, null, " ")); }); it("Correct departure and arrival stop", () => { From deea70a13ea0d28c4600190a21255c668e520cdf Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 17 Jan 2019 15:47:12 +0100 Subject: [PATCH 20/69] 1. rename csa eat tests --- src/planner/public-transport/CSAEarliestArrival.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/planner/public-transport/CSAEarliestArrival.test.ts b/src/planner/public-transport/CSAEarliestArrival.test.ts index f17c9587..8014f242 100644 --- a/src/planner/public-transport/CSAEarliestArrival.test.ts +++ b/src/planner/public-transport/CSAEarliestArrival.test.ts @@ -19,7 +19,7 @@ import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBir import CSAEarliestArrival from "./CSAEarliestArrival"; import JourneyExtractorEarliestArrivalTime from "./JourneyExtractorEarliestArrivalTime"; -describe("[PublicTransportPlannerCSAProfile]", () => { +describe("[PublicTransportPlannerCSAEarliestArrival]", () => { describe("mock data", () => { jest.setTimeout(100000); From 4ae15d11fba182a856a3aa79a37f8cdb1df59a95 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 17 Jan 2019 16:01:20 +0100 Subject: [PATCH 21/69] 1. simplify IJourneyExtractor --- src/inversify.config.ts | 6 +++--- src/planner/public-transport/CSAEarliestArrival.test.ts | 6 +++--- src/planner/public-transport/CSAEarliestArrival.ts | 4 ++-- src/planner/public-transport/CSAProfile.test.ts | 6 +++--- src/planner/public-transport/CSAProfile.ts | 4 ++-- src/planner/public-transport/IJourneyExtractor.ts | 9 +++++++-- ...ArrivalTime.ts => JourneyExtractorEarliestArrival.ts} | 2 +- ...neyExtractorDefault.ts => JourneyExtractorProfile.ts} | 2 +- .../exponential/QueryRunnerExponential.test.ts | 4 ++-- 9 files changed, 24 insertions(+), 19 deletions(-) rename src/planner/public-transport/{JourneyExtractorEarliestArrivalTime.ts => JourneyExtractorEarliestArrival.ts} (96%) rename src/planner/public-transport/{JourneyExtractorDefault.ts => JourneyExtractorProfile.ts} (98%) diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 982940f3..b8eb1efd 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -19,7 +19,7 @@ import IProfilesByStop from "./planner/public-transport/CSA/data-structure/stops import CSAProfile from "./planner/public-transport/CSAProfile"; import IJourneyExtractor from "./planner/public-transport/IJourneyExtractor"; import IPublicTransportPlanner from "./planner/public-transport/IPublicTransportPlanner"; -import JourneyExtractorDefault from "./planner/public-transport/JourneyExtractorDefault"; +import JourneyExtractorProfile from "./planner/public-transport/JourneyExtractorProfile"; import IRoadPlanner from "./planner/road/IRoadPlanner"; import RoadPlannerBirdsEye from "./planner/road/RoadPlannerBirdsEye"; import IReachableStopsFinder from "./planner/stops/IReachableStopsFinder"; @@ -44,8 +44,8 @@ container.bind>(TYPES.PublicTranspor container.bind(TYPES.RoadPlanner) .to(RoadPlannerBirdsEye); -container.bind>(TYPES.JourneyExtractor) - .to(JourneyExtractorDefault); +container.bind(TYPES.JourneyExtractor) + .to(JourneyExtractorProfile); container.bind(TYPES.ReachableStopsFinder) .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Initial); diff --git a/src/planner/public-transport/CSAEarliestArrival.test.ts b/src/planner/public-transport/CSAEarliestArrival.test.ts index 8014f242..bf0f83d9 100644 --- a/src/planner/public-transport/CSAEarliestArrival.test.ts +++ b/src/planner/public-transport/CSAEarliestArrival.test.ts @@ -17,7 +17,7 @@ import QueryRunnerDefault from "../../query-runner/QueryRunnerDefault"; import Iterators from "../../util/Iterators"; import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBirdsEyeCached"; import CSAEarliestArrival from "./CSAEarliestArrival"; -import JourneyExtractorEarliestArrivalTime from "./JourneyExtractorEarliestArrivalTime"; +import JourneyExtractorEarliestArrival from "./JourneyExtractorEarliestArrival"; describe("[PublicTransportPlannerCSAEarliestArrival]", () => { describe("mock data", () => { @@ -34,7 +34,7 @@ describe("[PublicTransportPlannerCSAEarliestArrival]", () => { const locationResolver = new LocationResolverDefault(stopsFetcher); const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); - const journeyExtractor = new JourneyExtractorEarliestArrivalTime( + const journeyExtractor = new JourneyExtractorEarliestArrival( locationResolver, ); @@ -185,7 +185,7 @@ describe("[PublicTransportPlannerCSAEarliestArrival]", () => { const locationResolver = new LocationResolverDefault(stopsFetcher); const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); - const journeyExtractor = new JourneyExtractorEarliestArrivalTime( + const journeyExtractor = new JourneyExtractorEarliestArrival( locationResolver, ); diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index a4dc1d3f..4e66d0ca 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -40,7 +40,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { private readonly initialReachableStopsFinder: IReachableStopsFinder; private readonly finalReachableStopsFinder: IReachableStopsFinder; private readonly transferReachableStopsFinder: IReachableStopsFinder; - private readonly journeyExtractor: IJourneyExtractor; + private readonly journeyExtractor: IJourneyExtractor; private readonly context: Context; private profilesByStop: IProfileByStop = {}; // S @@ -68,7 +68,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { @tagged("phase", ReachableStopsSearchPhase.Final) finalReachableStopsFinder: IReachableStopsFinder, @inject(TYPES.JourneyExtractor) - journeyExtractor: IJourneyExtractor, + journeyExtractor: IJourneyExtractor, @inject(TYPES.Context) context?: Context, ) { diff --git a/src/planner/public-transport/CSAProfile.test.ts b/src/planner/public-transport/CSAProfile.test.ts index 27601027..68a6ed32 100644 --- a/src/planner/public-transport/CSAProfile.test.ts +++ b/src/planner/public-transport/CSAProfile.test.ts @@ -17,7 +17,7 @@ import QueryRunnerDefault from "../../query-runner/QueryRunnerDefault"; import Iterators from "../../util/Iterators"; import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBirdsEyeCached"; import CSAProfile from "./CSAProfile"; -import JourneyExtractorDefault from "./JourneyExtractorDefault"; +import JourneyExtractorProfile from "./JourneyExtractorProfile"; describe("[PublicTransportPlannerCSAProfile]", () => { describe("mock data", () => { @@ -34,7 +34,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const locationResolver = new LocationResolverDefault(stopsFetcher); const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); - const journeyExtractor = new JourneyExtractorDefault( + const journeyExtractor = new JourneyExtractorProfile( locationResolver, ); @@ -185,7 +185,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const locationResolver = new LocationResolverDefault(stopsFetcher); const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); - const journeyExtractor = new JourneyExtractorDefault( + const journeyExtractor = new JourneyExtractorProfile( locationResolver, ); diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index 098e8919..74ed778c 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -45,7 +45,7 @@ export default class CSAProfile implements IPublicTransportPlanner { private readonly initialReachableStopsFinder: IReachableStopsFinder; private readonly finalReachableStopsFinder: IReachableStopsFinder; private readonly transferReachableStopsFinder: IReachableStopsFinder; - private readonly journeyExtractor: IJourneyExtractor; + private readonly journeyExtractor: IJourneyExtractor; private readonly context: Context; private profilesByStop: IProfilesByStop = {}; // S @@ -72,7 +72,7 @@ export default class CSAProfile implements IPublicTransportPlanner { @tagged("phase", ReachableStopsSearchPhase.Final) finalReachableStopsFinder: IReachableStopsFinder, @inject(TYPES.JourneyExtractor) - journeyExtractor: IJourneyExtractor, + journeyExtractor: IJourneyExtractor, @inject(TYPES.Context) context?: Context, ) { diff --git a/src/planner/public-transport/IJourneyExtractor.ts b/src/planner/public-transport/IJourneyExtractor.ts index 14fdffd0..67a50362 100644 --- a/src/planner/public-transport/IJourneyExtractor.ts +++ b/src/planner/public-transport/IJourneyExtractor.ts @@ -1,7 +1,12 @@ import { AsyncIterator } from "asynciterator"; import IPath from "../../interfaces/IPath"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; +import IProfileByStop from "./CSA/data-structure/stops/IProfileByStop"; +import IProfilesByStop from "./CSA/data-structure/stops/IProfilesByStop"; -export default interface IJourneyExtractor { - extractJourneys: (profilesByStop: T, query: IResolvedQuery) => Promise>; +export default interface IJourneyExtractor { + extractJourneys: ( + profilesByStop: IProfilesByStop | IProfileByStop, + query: IResolvedQuery, + ) => Promise>; } diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts similarity index 96% rename from src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts rename to src/planner/public-transport/JourneyExtractorEarliestArrival.ts index 0cd4887f..c74b2cb5 100644 --- a/src/planner/public-transport/JourneyExtractorEarliestArrivalTime.ts +++ b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts @@ -21,7 +21,7 @@ import IJourneyExtractor from "./IJourneyExtractor"; * @property bestArrivalTime Stores the best arrival time for each pair of departure-arrival stops. */ @injectable() -export default class JourneyExtractorEarliestArrivalTime implements IJourneyExtractor { +export default class JourneyExtractorEarliestArrival implements IJourneyExtractor { private readonly locationResolver: ILocationResolver; private context: Context; diff --git a/src/planner/public-transport/JourneyExtractorDefault.ts b/src/planner/public-transport/JourneyExtractorProfile.ts similarity index 98% rename from src/planner/public-transport/JourneyExtractorDefault.ts rename to src/planner/public-transport/JourneyExtractorProfile.ts index 45ac9f37..e7e66e47 100644 --- a/src/planner/public-transport/JourneyExtractorDefault.ts +++ b/src/planner/public-transport/JourneyExtractorProfile.ts @@ -25,7 +25,7 @@ import IJourneyExtractor from "./IJourneyExtractor"; * @property bestArrivalTime Stores the best arrival time for each pair of departure-arrival stops. */ @injectable() -export default class JourneyExtractorDefault implements IJourneyExtractor { +export default class JourneyExtractorProfile implements IJourneyExtractor { private readonly locationResolver: ILocationResolver; private bestArrivalTime: number[][] = []; diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index 0b85fa43..32e71ada 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -7,7 +7,7 @@ import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetc import IPath from "../../interfaces/IPath"; import IStep from "../../interfaces/IStep"; import CSAProfile from "../../planner/public-transport/CSAProfile"; -import JourneyExtractorDefault from "../../planner/public-transport/JourneyExtractorDefault"; +import JourneyExtractorProfile from "../../planner/public-transport/JourneyExtractorProfile"; import ReachableStopsFinderBirdsEyeCached from "../../planner/stops/ReachableStopsFinderBirdsEyeCached"; import Units from "../../util/Units"; import LocationResolverDefault from "../LocationResolverDefault"; @@ -42,7 +42,7 @@ describe("[QueryRunnerExponential]", () => { const context = new Context(); const createJourneyExtractor = () => { - return new JourneyExtractorDefault( + return new JourneyExtractorProfile( locationResolver, ); }; From 0936458da962320a99c87358fe1afdf4141370ed Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 18 Jan 2019 09:37:07 +0100 Subject: [PATCH 22/69] 1. ReachableStopsFinderRoadPlanner: first check which stops are inside the reachable area --- .../ReachableStopsFinderRoadPlanner.test.ts | 2 +- .../stops/ReachableStopsFinderRoadPlanner.ts | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts b/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts index 7fd4118e..5d83f7b2 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts @@ -20,7 +20,7 @@ test("[ReachableStopsFinderRoadPlanner] reachable stops", async () => { expect(sourceStop).toBeDefined(); - // Get reachable stops in 50 km (10h at 5km/h) + // Get reachable stops in 5 km (1h at 5km/h) const reachableStops = await reachableStopsFinder.findReachableStops( sourceStop, ReachableStopsFinderMode.Source, diff --git a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts index 020d5c15..a895fa59 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts @@ -8,7 +8,9 @@ import IPath from "../../interfaces/IPath"; import { DurationMs, SpeedkmH } from "../../interfaces/units"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import TYPES from "../../types"; +import Geo from "../../util/Geo"; import Iterators from "../../util/Iterators"; +import Units from "../../util/Units"; import IRoadPlanner from "../road/IRoadPlanner"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; @@ -45,10 +47,21 @@ export default class ReachableStopsFinderRoadPlanner implements IReachableStopsF minimumWalkingSpeed: minimumSpeed, }; - const allStops = await this.stopsProvider.getAllStops(); + const allStops: IStop[] = await this.stopsProvider.getAllStops(); + + const stopsInsideCircleArea: IStop[] = []; + for (const stop of allStops) { + const distance = Geo.getDistanceBetweenStops(sourceOrTargetStop, stop); + const duration = Units.toDuration(distance, minimumSpeed); + + if (duration <= maximumDuration) { + stopsInsideCircleArea.push(stop); + } + } + const reachableStops: IReachableStop[] = [{stop: sourceOrTargetStop, duration: 0}]; - await Promise.all(allStops.map(async (possibleTarget: IStop) => { + await Promise.all(stopsInsideCircleArea.map(async (possibleTarget: IStop) => { if (possibleTarget.id !== sourceOrTargetStop.id) { const query = Object.assign({}, baseQuery, { From 28fc0a6ef473b6bf464620fe5b548b0eaf77fc89 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 18 Jan 2019 10:51:41 +0100 Subject: [PATCH 23/69] Update typescript and typedoc dependencies --- package-lock.json | 40 ++++++++++++++++++++-------------------- package.json | 6 +++--- typedoc.config.js | 15 +++++++++++++++ typedoc.js | 12 ------------ 4 files changed, 38 insertions(+), 35 deletions(-) create mode 100644 typedoc.config.js delete mode 100644 typedoc.js diff --git a/package-lock.json b/package-lock.json index 6917ae81..634f1dcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,9 +74,9 @@ } }, "@types/handlebars": { - "version": "4.0.39", - "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.39.tgz", - "integrity": "sha512-vjaS7Q0dVqFp85QhyPSZqDKnTTCemcSHNHFvDdalO1s0Ifz5KuE64jQD5xoUkfdWwF4WpqdJEl7LsWH8rzhKJA==", + "version": "4.0.40", + "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.40.tgz", + "integrity": "sha512-sGWNtsjNrLOdKha2RV1UeF8+UbQnPSG7qbe5wwbni0mw4h2gHXyPFUMOC+xwGirIiiydM/HSqjDO4rk6NFB18w==", "dev": true }, "@types/haversine": { @@ -98,9 +98,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.118", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.118.tgz", - "integrity": "sha512-iiJbKLZbhSa6FYRip/9ZDX6HXhayXLDGY2Fqws9cOkEQ6XeKfaxB0sC541mowZJueYyMnVUmmG+al5/4fCDrgw==", + "version": "4.14.120", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.120.tgz", + "integrity": "sha512-jQ21kQ120mo+IrDs1nFNVm/AsdFxIx2+vZ347DbogHJPd/JzKNMOqU6HCYin1W6v8l5R9XSO2/e9cxmn7HAnVw==", "dev": true }, "@types/marked": { @@ -131,9 +131,9 @@ } }, "@types/shelljs": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.0.tgz", - "integrity": "sha512-vs1hCC8RxLHRu2bwumNyYRNrU3o8BtZhLysH5A4I98iYmA2APl6R3uNQb5ihl+WiwH0xdC9LLO+vRrXLs/Kyxg==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.1.tgz", + "integrity": "sha512-1lQw+48BuVgp6c1+z8EMipp18IdnV2dLh6KQGwOm+kJy9nPjEkaqRKmwbDNEYf//EKBvKcwOC6V2cDrNxVoQeQ==", "dev": true, "requires": { "@types/glob": "*", @@ -5477,9 +5477,9 @@ "dev": true }, "progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", - "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "promise-inflight": { @@ -7655,9 +7655,9 @@ "dev": true }, "typedoc": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.13.0.tgz", - "integrity": "sha512-jQWtvPcV+0fiLZAXFEe70v5gqjDO6pJYJz4mlTtmGJeW2KRoIU/BEfktma6Uj8Xii7UakuZjbxFewl3UYOkU/w==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.14.2.tgz", + "integrity": "sha512-aEbgJXV8/KqaVhcedT7xG6d2r+mOvB5ep3eIz1KuB5sc4fDYXcepEEMdU7XSqLFO5hVPu0nllHi1QxX2h/QlpQ==", "dev": true, "requires": { "@types/fs-extra": "^5.0.3", @@ -7669,14 +7669,14 @@ "@types/shelljs": "^0.8.0", "fs-extra": "^7.0.0", "handlebars": "^4.0.6", - "highlight.js": "^9.0.0", + "highlight.js": "^9.13.1", "lodash": "^4.17.10", "marked": "^0.4.0", "minimatch": "^3.0.0", "progress": "^2.0.0", "shelljs": "^0.8.2", "typedoc-default-themes": "^0.5.0", - "typescript": "3.1.x" + "typescript": "3.2.x" } }, "typedoc-default-themes": { @@ -7686,9 +7686,9 @@ "dev": true }, "typescript": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", - "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index f41785ae..7eb21dae 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "lint": "./node_modules/tslint/bin/tslint --project .", "webpack": "webpack --config webpack.config.js --mode=production", "webpack-stats": "npm run webpack -- --display-modules --json > stats.json", - "typedoc": "typedoc --options typedoc.js", + "typedoc": "typedoc --options typedoc.config.js", "doc": "npm run typedoc & npm run browser && cp dist/bundle.js docs/js/planner-latest.js && cp dist/bundle.js.map docs/js/planner-latest.js.map" }, "dependencies": { @@ -49,8 +49,8 @@ "ts-jest": "^23.10.4", "ts-loader": "^5.3.0", "tslint": "^5.11.0", - "typedoc": "^0.13.0", - "typescript": "^3.1.6", + "typedoc": "^0.14.2", + "typescript": "^3.2.4", "webpack": "^4.25.1", "webpack-cli": "^3.1.2" } diff --git a/typedoc.config.js b/typedoc.config.js new file mode 100644 index 00000000..487a75b0 --- /dev/null +++ b/typedoc.config.js @@ -0,0 +1,15 @@ +module.exports = { + out: 'docs/code', + includes: 'src/', + exclude: [ + '**/*+(test).ts', + '**/src/test/*', + '**/fetcher/connections/tests/*' + ], + mode: 'file', + excludePrivate: true, + excludeNotExported: false, + excludeExternals: false, + includeDeclarations: false, + theme: 'minimal', +}; diff --git a/typedoc.js b/typedoc.js deleted file mode 100644 index f888992e..00000000 --- a/typedoc.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - out: 'docs/code', - includes: 'src/', - exclude: [ - '**/*+(config|test).ts', - ], - mode: 'file', - excludePrivate: true, - excludeNotExported: true, - excludeExternals: true, - theme: 'minimal', -}; From 788d00d1fbc07f0b7b1a505e3048f3f3412c2807 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 18 Jan 2019 11:28:39 +0100 Subject: [PATCH 24/69] Documented a bunch of util classes --- src/Planner.ts | 6 +-- src/interfaces/IQuery.ts | 8 ++-- src/interfaces/units.ts | 16 +++++-- src/planner/road/RoadPlannerBirdsEye.ts | 6 +-- src/planner/stops/IReachableStopsFinder.ts | 4 +- .../stops/ReachableStopsFinderBirdsEye.ts | 4 +- .../ReachableStopsFinderBirdsEyeCached.ts | 4 +- .../stops/ReachableStopsFinderOnlySelf.ts | 4 +- .../stops/ReachableStopsFinderRoadPlanner.ts | 4 +- .../ReachableStopsFinderRoadPlannerCached.ts | 4 +- src/query-runner/IResolvedQuery.ts | 6 +-- src/util/BinarySearch.ts | 6 --- src/util/Geo.ts | 12 +++++ src/util/Iterators.ts | 18 +++++-- src/util/Rdf.ts | 48 ++++++++++++++++++- src/util/Units.test.ts | 8 ++-- src/util/Units.ts | 12 +++-- 17 files changed, 123 insertions(+), 47 deletions(-) diff --git a/src/Planner.ts b/src/Planner.ts index 997b732d..a8820269 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -13,7 +13,7 @@ import IQueryRunner from "./query-runner/IQueryRunner"; import TYPES from "./types"; /** - * Allows to ask route planning queries over our knowledge graphs + * Allows to ask route planning queries. Emits events defined in [[EventType]] */ // @ts-ignore export default class Planner implements EventEmitter { @@ -33,9 +33,9 @@ export default class Planner implements EventEmitter { } /** - * Given an [[IQuery]], it will evaluate the query and eventually produce an [[IQueryResult]] + * Given an [[IQuery]], it will evaluate the query and return a promise for an AsyncIterator of [[IPath]] instances * @param query An [[IQuery]] specifying a route planning query - * @returns An AsyncIterator of [[IPath]]s + * @returns An AsyncIterator of [[IPath]] instances */ public async query(query: IQuery): Promise> { this.emit(EventType.Query, query); diff --git a/src/interfaces/IQuery.ts b/src/interfaces/IQuery.ts index 1142c149..f0cb7395 100644 --- a/src/interfaces/IQuery.ts +++ b/src/interfaces/IQuery.ts @@ -1,5 +1,5 @@ import ILocation from "./ILocation"; -import { DistanceM, DurationMs, SpeedkmH } from "./units"; +import { DistanceM, DurationMs, SpeedKmH } from "./units"; export default interface IQuery { from?: string | string[] | ILocation | ILocation[]; @@ -8,9 +8,9 @@ export default interface IQuery { maximumArrivalTime?: Date; roadOnly?: boolean; publicTransportOnly?: boolean; - walkingSpeed?: SpeedkmH; - minimumWalkingSpeed?: SpeedkmH; - maximumWalkingSpeed?: SpeedkmH; + walkingSpeed?: SpeedKmH; + minimumWalkingSpeed?: SpeedKmH; + maximumWalkingSpeed?: SpeedKmH; maximumWalkingDuration?: DurationMs; maximumWalkingDistance?: DistanceM; minimumTransferDuration?: DurationMs; diff --git a/src/interfaces/units.ts b/src/interfaces/units.ts index 23f52d27..9cb37ed0 100644 --- a/src/interfaces/units.ts +++ b/src/interfaces/units.ts @@ -1,6 +1,14 @@ -// duration ms +/** + * Represents duration in ms (milliseconds) + */ export type DurationMs = number; -// distance in m + +/** + * Represents distance in m (meters) + */ export type DistanceM = number; -// speed in km/h -export type SpeedkmH = number; + +/** + * Represents duration in km/h (kilometers per hour) + */ +export type SpeedKmH = number; diff --git a/src/planner/road/RoadPlannerBirdsEye.ts b/src/planner/road/RoadPlannerBirdsEye.ts index 43db4efb..fdf23eed 100644 --- a/src/planner/road/RoadPlannerBirdsEye.ts +++ b/src/planner/road/RoadPlannerBirdsEye.ts @@ -4,7 +4,7 @@ import TravelMode from "../../enums/TravelMode"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; import IProbabilisticValue from "../../interfaces/IProbabilisticValue"; -import { DurationMs, SpeedkmH } from "../../interfaces/units"; +import { DurationMs, SpeedKmH } from "../../interfaces/units"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import Geo from "../../util/Geo"; import Units from "../../util/Units"; @@ -51,8 +51,8 @@ export default class RoadPlannerBirdsEye implements IRoadPlanner { private getPathBetweenLocations( from: ILocation, to: ILocation, - minWalkingSpeed: SpeedkmH, - maxWalkingSpeed: SpeedkmH, + minWalkingSpeed: SpeedKmH, + maxWalkingSpeed: SpeedKmH, maxWalkingDuration: DurationMs, ): IPath { diff --git a/src/planner/stops/IReachableStopsFinder.ts b/src/planner/stops/IReachableStopsFinder.ts index 5d4ed34d..7f0fcdc1 100644 --- a/src/planner/stops/IReachableStopsFinder.ts +++ b/src/planner/stops/IReachableStopsFinder.ts @@ -1,13 +1,13 @@ import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; -import { DurationMs, SpeedkmH } from "../../interfaces/units"; +import { DurationMs, SpeedKmH } from "../../interfaces/units"; export default interface IReachableStopsFinder { findReachableStops: ( sourceOrTargetStop: IStop, mode: ReachableStopsFinderMode, maximumDuration: DurationMs, - minimumSpeed: SpeedkmH, + minimumSpeed: SpeedKmH, ) => Promise; } diff --git a/src/planner/stops/ReachableStopsFinderBirdsEye.ts b/src/planner/stops/ReachableStopsFinderBirdsEye.ts index bd420b96..4126f59e 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEye.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEye.ts @@ -2,7 +2,7 @@ import { inject, injectable } from "inversify"; import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import IStopsProvider from "../../fetcher/stops/IStopsProvider"; -import { DurationMs, SpeedkmH } from "../../interfaces/units"; +import { DurationMs, SpeedKmH } from "../../interfaces/units"; import TYPES from "../../types"; import Geo from "../../util/Geo"; import Units from "../../util/Units"; @@ -22,7 +22,7 @@ export default class ReachableStopsFinderBirdsEye implements IReachableStopsFind sourceOrTargetStop: IStop, mode: ReachableStopsFinderMode, maximumDuration: DurationMs, - minimumSpeed: SpeedkmH, + minimumSpeed: SpeedKmH, ): Promise { // Mode can be ignored since birds eye view distance is identical diff --git a/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts b/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts index ac298bd3..f8f081fc 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts @@ -2,7 +2,7 @@ import { inject, injectable } from "inversify"; import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import IStopsProvider from "../../fetcher/stops/IStopsProvider"; -import { DurationMs, SpeedkmH } from "../../interfaces/units"; +import { DurationMs, SpeedKmH } from "../../interfaces/units"; import TYPES from "../../types"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; import ReachableStopsFinderBirdsEye from "./ReachableStopsFinderBirdsEye"; @@ -23,7 +23,7 @@ export default class ReachableStopsFinderBirdsEyeCached implements IReachableSto sourceOrTargetStop: IStop, mode: ReachableStopsFinderMode, maximumDuration: DurationMs, - minimumSpeed: SpeedkmH, + minimumSpeed: SpeedKmH, ): Promise { // Mode can be ignored since birds eye view distance is identical diff --git a/src/planner/stops/ReachableStopsFinderOnlySelf.ts b/src/planner/stops/ReachableStopsFinderOnlySelf.ts index 332bbbac..ab9c30a4 100644 --- a/src/planner/stops/ReachableStopsFinderOnlySelf.ts +++ b/src/planner/stops/ReachableStopsFinderOnlySelf.ts @@ -1,7 +1,7 @@ import { injectable } from "inversify"; import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; -import { DurationMs, SpeedkmH } from "../../interfaces/units"; +import { DurationMs, SpeedKmH } from "../../interfaces/units"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; @injectable() @@ -11,7 +11,7 @@ export default class ReachableStopsFinderOnlySelf implements IReachableStopsFind sourceOrTargetStop: IStop, mode: ReachableStopsFinderMode, maximumDuration: DurationMs, - minimumSpeed: SpeedkmH, + minimumSpeed: SpeedKmH, ): Promise { return [{ stop: sourceOrTargetStop, duration: 0 }]; } diff --git a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts index 020d5c15..92571c41 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts @@ -5,7 +5,7 @@ import IStop from "../../fetcher/stops/IStop"; import IStopsProvider from "../../fetcher/stops/IStopsProvider"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; -import { DurationMs, SpeedkmH } from "../../interfaces/units"; +import { DurationMs, SpeedKmH } from "../../interfaces/units"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import TYPES from "../../types"; import Iterators from "../../util/Iterators"; @@ -29,7 +29,7 @@ export default class ReachableStopsFinderRoadPlanner implements IReachableStopsF sourceOrTargetStop: IStop, mode: ReachableStopsFinderMode, maximumDuration: DurationMs, - minimumSpeed: SpeedkmH, + minimumSpeed: SpeedKmH, ): Promise { const minimumDepartureTime = new Date(); diff --git a/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts b/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts index 138a4343..851afa60 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts @@ -2,7 +2,7 @@ import { inject, injectable } from "inversify"; import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import IStopsProvider from "../../fetcher/stops/IStopsProvider"; -import { DurationMs, SpeedkmH } from "../../interfaces/units"; +import { DurationMs, SpeedKmH } from "../../interfaces/units"; import TYPES from "../../types"; import IRoadPlanner from "../road/IRoadPlanner"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; @@ -25,7 +25,7 @@ export default class ReachableStopsFinderRoadPlannerCached implements IReachable sourceOrTargetStop: IStop, mode: ReachableStopsFinderMode, maximumDuration: DurationMs, - minimumSpeed: SpeedkmH, + minimumSpeed: SpeedKmH, ): Promise { const cacheKey = `${sourceOrTargetStop.id} ${mode} ${maximumDuration} ${minimumSpeed}`; diff --git a/src/query-runner/IResolvedQuery.ts b/src/query-runner/IResolvedQuery.ts index 210fe29f..d82492f9 100644 --- a/src/query-runner/IResolvedQuery.ts +++ b/src/query-runner/IResolvedQuery.ts @@ -1,5 +1,5 @@ import ILocation from "../interfaces/ILocation"; -import { DurationMs, SpeedkmH } from "../interfaces/units"; +import { DurationMs, SpeedKmH } from "../interfaces/units"; export default interface IResolvedQuery { from?: ILocation[]; @@ -8,8 +8,8 @@ export default interface IResolvedQuery { maximumArrivalTime?: Date; roadOnly?: boolean; publicTransportOnly?: boolean; - minimumWalkingSpeed?: SpeedkmH; - maximumWalkingSpeed?: SpeedkmH; + minimumWalkingSpeed?: SpeedKmH; + maximumWalkingSpeed?: SpeedKmH; maximumWalkingDuration?: DurationMs; minimumTransferDuration?: DurationMs; maximumTransferDuration?: DurationMs; diff --git a/src/util/BinarySearch.ts b/src/util/BinarySearch.ts index bd34e169..0e19e4db 100644 --- a/src/util/BinarySearch.ts +++ b/src/util/BinarySearch.ts @@ -14,9 +14,6 @@ export default class BinarySearch { /** * Find the first index of the given key, or the index before which that key would be hypothetically spliced in * Adapted from: https://algorithmsandme.com/first-occurrence-of-element/ - * @param key - * @param start - * @param end */ public findFirstIndex(key: number, start: number = 0, end: number = (this.array.length - 1)): number { while (start < end) { @@ -36,9 +33,6 @@ export default class BinarySearch { /** * Find the last index of the given key, or the index after which that key would be hypothetically spliced in * Adapted from: https://www.algorithmsandme.com/last-occurrence-of-element-with-binary-search/ - * @param key - * @param start - * @param end */ public findLastIndex(key: number, start: number = 0, end: number = (this.array.length - 1)): number { while (start < end) { diff --git a/src/util/Geo.ts b/src/util/Geo.ts index 583cd194..2cd4c26a 100644 --- a/src/util/Geo.ts +++ b/src/util/Geo.ts @@ -3,7 +3,15 @@ import IStop from "../fetcher/stops/IStop"; import ILocation from "../interfaces/ILocation"; import { DistanceM } from "../interfaces/units"; +/** + * Utility class with geographic functions + */ export default class Geo { + + /** + * Calculate the distance between two [[ILocation]] instances using the haversine formula + * @returns distance is meters ([[DistanceM]]) + */ public static getDistanceBetweenLocations(start: ILocation, stop: ILocation): DistanceM { const { longitude: depLongitude, latitude: depLatitude } = start; const { longitude: arrLongitude, latitude: arrLatitude } = stop; @@ -24,6 +32,10 @@ export default class Geo { }); } + /** + * Calculate tge distance between two [[IStop]] instances using the haversine formula + * @returns distance is meters ([[DistanceM]]) + */ public static getDistanceBetweenStops(start: IStop, stop: IStop) { return this.getDistanceBetweenLocations(start as ILocation, stop as ILocation); } diff --git a/src/util/Iterators.ts b/src/util/Iterators.ts index 7947ea5c..28147de0 100644 --- a/src/util/Iterators.ts +++ b/src/util/Iterators.ts @@ -1,9 +1,15 @@ import { AsyncIterator } from "asynciterator"; +/** + * Utility class with functions to operate on AsyncIterator instances + */ export default class Iterators { + /** + * Returns an array representation of an AsyncIterator. + * Assumes the iterator will end sometime + */ public static toArray(iterator: AsyncIterator): Promise { - const array = []; iterator.each((item: T) => array.push(item)); @@ -12,6 +18,9 @@ export default class Iterators { }); } + /** + * Returns the first element of an AsyncIterator. + */ public static getFirst(iterator: AsyncIterator): Promise { return new Promise((resolve) => { iterator.on("readable", () => { @@ -20,12 +29,15 @@ export default class Iterators { }); } - public static find(iterator: AsyncIterator, callback: (element: T) => boolean): Promise { + /** + * Iterates over elements of an AsyncIterator, returning the first element ´predicate´ returns truthy for. + */ + public static find(iterator: AsyncIterator, predicate: (element: T) => boolean): Promise { return new Promise((resolve, reject) => { iterator.on("readable", () => { let element = iterator.read(); - while (element && !callback(element)) { + while (element && !predicate(element)) { element = iterator.read(); } diff --git a/src/util/Rdf.ts b/src/util/Rdf.ts index f776afc3..f4b8d1b3 100644 --- a/src/util/Rdf.ts +++ b/src/util/Rdf.ts @@ -1,8 +1,27 @@ import { Triple } from "rdf-js"; +/** + * Utility class with functions dealing with rdf triples + */ export default class Rdf { - public static matchesTriple(subject: string, predicate: string, object: string): (triple: Triple) => boolean { + /** + * Creates a triple matcher callback function for use in e.g. an Array#filter() expression + * + * For example: + * ``` + * tripleArray.filter(Rdf.matchesTriple('someSubject', null, null)); + * ``` + * @param subject can be null if not wanting to match by subject + * @param predicate can be null if not wanting to match by predicate + * @param object can be null if not wanting to match by object + */ + public static matchesTriple( + subject: string | null, + predicate: string | null, + object: string | null, + ): (triple: Triple) => boolean { + return (triple: Triple) => { if (subject && triple.subject.value !== subject) { return false; @@ -20,6 +39,18 @@ export default class Rdf { }; } + /** + * Rename the predicate of a triple based on a map with original predicates as keys and + * replacement predicates as values + * + * For example: + * ``` + * const transformedTriple = Rdf.transformPredicate({ + * "oldPredicate1": "newPredicate1", + * "oldPredicate2": "newPredicate2", + * }, someTriple)); + * ``` + */ public static transformPredicate(transformMap: { [oldPredicate: string]: string }, triple: Triple): Triple { if (triple.predicate.value in transformMap) { triple.predicate.value = transformMap[triple.predicate.value]; @@ -27,6 +58,18 @@ export default class Rdf { return triple; } + /** + * Rename the object of a triple based on a map with original objects as keys and + * replacement objects as values + * + * For example: + * ``` + * const transformedTriple = Rdf.transformObject({ + * "oldObject1": "newObject1", + * "oldObject2": "newObject2", + * }, someTriple)); + * ``` + */ public static transformObject(transformMap: { [oldObject: string]: string }, triple: Triple): Triple { if (triple.object.value in transformMap) { triple.object.value = transformMap[triple.object.value]; @@ -34,6 +77,9 @@ export default class Rdf { return triple; } + /** + * Log an array of triples to the console as a table with three columns: subject, predicate and object + */ public static logTripleTable(triples: Triple[]): void { console.table(triples.map((triple: Triple) => ({ subject: triple.subject.value, diff --git a/src/util/Units.test.ts b/src/util/Units.test.ts index fc8ee6d1..7b6da3b5 100644 --- a/src/util/Units.test.ts +++ b/src/util/Units.test.ts @@ -1,12 +1,12 @@ import "jest"; -import { DistanceM, DurationMs, SpeedkmH } from "../interfaces/units"; +import { DistanceM, DurationMs, SpeedKmH } from "../interfaces/units"; import Units from "./Units"; test("[Units] toSpeed", () => { const distance: DistanceM = 10000; const duration: DurationMs = 3600000; - const speed: SpeedkmH = Units.toSpeed(distance, duration); + const speed: SpeedKmH = Units.toSpeed(distance, duration); expect(speed).toBeDefined(); expect(speed).toEqual(10); @@ -15,7 +15,7 @@ test("[Units] toSpeed", () => { test("[Units] toDuration", () => { const distance: DistanceM = 10000; - const speed: SpeedkmH = 10; + const speed: SpeedKmH = 10; const duration: DurationMs = Units.toDuration(distance, speed); expect(duration).toBeDefined(); @@ -25,7 +25,7 @@ test("[Units] toDuration", () => { test("[Units] toDistance", () => { const duration: DurationMs = 3600000; - const speed: SpeedkmH = 10; + const speed: SpeedKmH = 10; const distance: DistanceM = Units.toDistance(speed, duration); expect(distance).toBeDefined(); diff --git a/src/util/Units.ts b/src/util/Units.ts index 4a4337fb..efbda615 100644 --- a/src/util/Units.ts +++ b/src/util/Units.ts @@ -1,15 +1,19 @@ -import { DistanceM, DurationMs, SpeedkmH } from "../interfaces/units"; +import { DistanceM, DurationMs, SpeedKmH } from "../interfaces/units"; +/** + * Utility class with calculation functions dealing with [[DistanceM]], [[DurationMs]] and [[SpeedKmH]] + */ export default class Units { - public static toSpeed(distance: DistanceM, duration: DurationMs): SpeedkmH { + + public static toSpeed(distance: DistanceM, duration: DurationMs): SpeedKmH { return (distance / duration) * 3600; } - public static toDistance(duration: DurationMs, speed: SpeedkmH): DistanceM { + public static toDistance(duration: DurationMs, speed: SpeedKmH): DistanceM { return (speed * duration) / 3600; } - public static toDuration(distance: DistanceM, speed: SpeedkmH): DurationMs { + public static toDuration(distance: DistanceM, speed: SpeedKmH): DurationMs { // tslint:disable-next-line:no-bitwise return ((distance / speed) * 3600 | 0); } From b01db851c91299e988e51f898b0439a3b798ac8e Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 18 Jan 2019 11:55:21 +0100 Subject: [PATCH 25/69] Improved typedoc generated docs by excluding some files --- src/catalog.delijn.ts | 26 ++++++++++--------- src/catalog.nmbs.ts | 9 ++++--- .../prefetch/ConnectionsProviderPrefetch.ts | 6 ++--- typedoc.config.js | 4 ++- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/catalog.delijn.ts b/src/catalog.delijn.ts index 528a24d4..5db450d4 100644 --- a/src/catalog.delijn.ts +++ b/src/catalog.delijn.ts @@ -1,18 +1,20 @@ import Catalog from "./Catalog"; import TravelMode from "./enums/TravelMode"; -const catalog = new Catalog(); +/* tslint:disable:max-line-length */ -catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Antwerpen/stops"); -catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Limburg/stops"); -catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops"); -catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/stops"); -catalog.addStopsSource("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); +const catalogDeLijn = new Catalog(); -catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Antwerpen/connections", TravelMode.Bus); -catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Limburg/connections", TravelMode.Bus); -catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); -catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/connections", TravelMode.Bus); -catalog.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/connections", TravelMode.Bus); +catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Antwerpen/stops"); +catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Limburg/stops"); +catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops"); +catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/stops"); +catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); -export default catalog; +catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Antwerpen/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Limburg/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/connections", TravelMode.Bus); + +export default catalogDeLijn; diff --git a/src/catalog.nmbs.ts b/src/catalog.nmbs.ts index 1d2a19f9..7d4b1846 100644 --- a/src/catalog.nmbs.ts +++ b/src/catalog.nmbs.ts @@ -1,8 +1,9 @@ import Catalog from "./Catalog"; import TravelMode from "./enums/TravelMode"; -const catalog = new Catalog(); -catalog.addStopsSource("https://irail.be/stations/NMBS"); -catalog.addConnectionsSource("https://graph.irail.be/sncb/connections", TravelMode.Train); +const catalogNmbs = new Catalog(); -export default catalog; +catalogNmbs.addStopsSource("https://irail.be/stations/NMBS"); +catalogNmbs.addConnectionsSource("https://graph.irail.be/sncb/connections", TravelMode.Train); + +export default catalogNmbs; diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index 5637ed0c..5b615011 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -9,8 +9,6 @@ import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; import IConnectionsProvider from "../IConnectionsProvider"; import ConnectionsStore from "./ConnectionsStore"; -const MAX_CONNECTIONS = 20000; - /** * Passes through one [[IConnectionsFetcher]], the first one if there are multiple * This provider is most/only useful if there is only one fetcher @@ -18,6 +16,8 @@ const MAX_CONNECTIONS = 20000; @injectable() export default class ConnectionsProviderPrefetch implements IConnectionsProvider { + private static MAX_CONNECTIONS = 20000; + private readonly connectionsFetcher: IConnectionsFetcher; private readonly connectionsStore: ConnectionsStore; @@ -49,7 +49,7 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.connectionsIterator = this.connectionsFetcher.createIterator(); this.connectionsIterator - .take(MAX_CONNECTIONS) + .take(ConnectionsProviderPrefetch.MAX_CONNECTIONS) .on("end", () => this.connectionsStore.finish()) .each((connection: IConnection) => { this.connectionsStore.append(connection); diff --git a/typedoc.config.js b/typedoc.config.js index 487a75b0..fa7e335d 100644 --- a/typedoc.config.js +++ b/typedoc.config.js @@ -3,8 +3,10 @@ module.exports = { includes: 'src/', exclude: [ '**/*+(test).ts', + '**/src/demo.*', + '**/src/inversify.config.ts', '**/src/test/*', - '**/fetcher/connections/tests/*' + '**/fetcher/connections/tests/**/*', ], mode: 'file', excludePrivate: true, From 26b877f2c77e5f5940ea486c59bfaa80ff5e8fa3 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 18 Jan 2019 16:28:22 +0100 Subject: [PATCH 26/69] Documented a bunch of classes --- src/Catalog.ts | 5 +++++ src/Context.ts | 5 +++-- src/Defaults.ts | 3 +++ src/planner/IPlanner.ts | 4 ++++ src/planner/Path.ts | 8 ++++++++ src/planner/Step.ts | 8 ++++++++ src/planner/stops/IReachableStopsFinder.ts | 7 +++++++ .../stops/ReachableStopsFinderBirdsEye.ts | 4 ++++ .../ReachableStopsFinderBirdsEyeCached.ts | 4 ++++ .../stops/ReachableStopsFinderOnlySelf.ts | 5 +++++ .../stops/ReachableStopsFinderRoadPlanner.ts | 3 +++ .../ReachableStopsFinderRoadPlannerCached.ts | 4 ++++ src/query-runner/ILocationResolver.ts | 3 +++ src/query-runner/IQueryRunner.ts | 5 ++++- src/query-runner/IResolvedQuery.ts | 5 +++++ src/query-runner/LocationResolverDefault.ts | 7 +++++++ src/query-runner/QueryRunnerDefault.ts | 6 ++++++ .../exponential/ExponentialQueryIterator.ts | 5 ++++- .../exponential/QueryRunnerExponential.ts | 13 +++++++++++++ src/util/iterators/AsyncArrayIterator.ts | 6 ++++++ src/util/iterators/FilterUniqueIterator.ts | 6 ++++++ src/util/iterators/FlatMapIterator.ts | 18 ++++++++++++++++++ src/util/iterators/MergeIterator.ts | 4 ++-- 23 files changed, 132 insertions(+), 6 deletions(-) diff --git a/src/Catalog.ts b/src/Catalog.ts index d0909288..0d2aeb2c 100644 --- a/src/Catalog.ts +++ b/src/Catalog.ts @@ -1,5 +1,10 @@ import TravelMode from "./enums/TravelMode"; +/** + * A Catalog instance holds the stops source and connections source configs. + * These configs get passed to the [[StopsFetcherFactory]] and [[ConnectionsFetcherFactory]] to construct + * respectively [[IStopsFetcher]] and [[IConnectionsFetcher]] instances + */ export default class Catalog { public static combine(...catalogs: Catalog[]): Catalog { diff --git a/src/Context.ts b/src/Context.ts index 57cf66eb..38a6a079 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -5,8 +5,9 @@ import EventType from "./enums/EventType"; /** * The Context serves as event pass through and holder of the inversify container object. - * Proxies an internal EventEmitter because ´decorate(injectable(), EventEmitter)´ causes - * errors when running tests in Jest + * + * It proxies an internal EventEmitter (instead of extending EventEmitter) because + * ´decorate(injectable(), EventEmitter)´ causes errors when running tests in Jest */ @injectable() // @ts-ignore diff --git a/src/Defaults.ts b/src/Defaults.ts index 2be8ecc1..3b2d8709 100644 --- a/src/Defaults.ts +++ b/src/Defaults.ts @@ -1,5 +1,8 @@ import Units from "./util/Units"; +/** + * This class holds the default [[IQuery]]/[[IResolvedQuery]] parameters + */ export default class Defaults { public static readonly defaultMinimumWalkingSpeed = 3; public static readonly defaultMaximumWalkingSpeed = 6; diff --git a/src/planner/IPlanner.ts b/src/planner/IPlanner.ts index 28a85a84..d670860f 100644 --- a/src/planner/IPlanner.ts +++ b/src/planner/IPlanner.ts @@ -2,6 +2,10 @@ import { AsyncIterator } from "asynciterator"; import IPath from "../interfaces/IPath"; import IResolvedQuery from "../query-runner/IResolvedQuery"; +/** + * This interface functions as base interface for both the [[IPublicTransportPlanner]] + * and the [[IRoadPlanner]] interface + */ export default interface IPlanner { plan: (query: IResolvedQuery) => Promise>; } diff --git a/src/planner/Path.ts b/src/planner/Path.ts index dbc81cdc..0eecb3eb 100644 --- a/src/planner/Path.ts +++ b/src/planner/Path.ts @@ -2,6 +2,10 @@ import IPath from "../interfaces/IPath"; import IStep from "../interfaces/IStep"; import Step from "./Step"; +/** + * This Path class serves as an implementation of the [[IPath]] interface and as a home for some helper functions + * related to [[IPath]] instances + */ export default class Path implements IPath { public static create(): Path { @@ -10,6 +14,10 @@ export default class Path implements IPath { ); } + /** + * Compare two [[IPath]] instances + * @returns true if the two paths are the same + */ public static compareEquals(path: IPath, otherPath: IPath): boolean { if (path.steps.length !== otherPath.steps.length) { return false; diff --git a/src/planner/Step.ts b/src/planner/Step.ts index 9d44315f..bfd7af1b 100644 --- a/src/planner/Step.ts +++ b/src/planner/Step.ts @@ -5,6 +5,10 @@ import IProbabilisticValue from "../interfaces/IProbabilisticValue"; import IStep from "../interfaces/IStep"; import { DistanceM, DurationMs } from "../interfaces/units"; +/** + * This Step class serves as an implementation of the [[IStep]] interface and as a home for some helper functions + * related to [[IStep]] instances + */ export default class Step implements IStep { public static create( @@ -46,6 +50,10 @@ export default class Step implements IStep { ); } + /** + * Compare two [[IStep]] instances + * @returns true if the two steps are the same + */ public static compareEquals(step: IStep, otherStep: IStep): boolean { if (otherStep.travelMode !== step.travelMode) { return false; diff --git a/src/planner/stops/IReachableStopsFinder.ts b/src/planner/stops/IReachableStopsFinder.ts index 7f0fcdc1..99ed8105 100644 --- a/src/planner/stops/IReachableStopsFinder.ts +++ b/src/planner/stops/IReachableStopsFinder.ts @@ -2,6 +2,10 @@ import ReachableStopsFinderMode from "../../enums/ReachableStopsFinderMode"; import IStop from "../../fetcher/stops/IStop"; import { DurationMs, SpeedKmH } from "../../interfaces/units"; +/** + * A reachable stops finder searches for stops that are reachable "on foot" . This can mean e.g. a folding bike if + * that scenario is implemented by the registered [[IReachableStopsFinder]] + */ export default interface IReachableStopsFinder { findReachableStops: ( sourceOrTargetStop: IStop, @@ -11,6 +15,9 @@ export default interface IReachableStopsFinder { ) => Promise; } +/** + * An [[IReachableStop]] wraps an [[IStop]] instance and the estimated duration to get to that stop + */ export interface IReachableStop { stop: IStop; duration: DurationMs; diff --git a/src/planner/stops/ReachableStopsFinderBirdsEye.ts b/src/planner/stops/ReachableStopsFinderBirdsEye.ts index 4126f59e..6b1bc24c 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEye.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEye.ts @@ -8,6 +8,10 @@ import Geo from "../../util/Geo"; import Units from "../../util/Units"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; +/** + * This [[IReachableStopsFinder]] determines its reachable stops based on the birds's-eye distance + * to the source or target stop. + */ @injectable() export default class ReachableStopsFinderBirdsEye implements IReachableStopsFinder { private readonly stopsProvider: IStopsProvider; diff --git a/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts b/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts index f8f081fc..2f474856 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEyeCached.ts @@ -7,6 +7,10 @@ import TYPES from "../../types"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; import ReachableStopsFinderBirdsEye from "./ReachableStopsFinderBirdsEye"; +/** + * This [[IReachableStopsFinder]] forms a caching layer in front of a [[ReachableStopsFinderBirdsEye]] instance, + * so all the distances don't have to be calculated repeatedly + */ @injectable() export default class ReachableStopsFinderBirdsEyeCached implements IReachableStopsFinder { private readonly reachableStopsFinder: ReachableStopsFinderBirdsEye; diff --git a/src/planner/stops/ReachableStopsFinderOnlySelf.ts b/src/planner/stops/ReachableStopsFinderOnlySelf.ts index ab9c30a4..dd23ffa5 100644 --- a/src/planner/stops/ReachableStopsFinderOnlySelf.ts +++ b/src/planner/stops/ReachableStopsFinderOnlySelf.ts @@ -4,6 +4,11 @@ import IStop from "../../fetcher/stops/IStop"; import { DurationMs, SpeedKmH } from "../../interfaces/units"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; +/** + * This [[IReachableStopsFinder]] just returns the passed source or target stop. + * + * This can be a valid strategy to optimize speed if the user doesn't want to travel by foot to another stop + */ @injectable() export default class ReachableStopsFinderOnlySelf implements IReachableStopsFinder { diff --git a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts index 92571c41..a608f8d8 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts @@ -12,6 +12,9 @@ import Iterators from "../../util/Iterators"; import IRoadPlanner from "../road/IRoadPlanner"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; +/** + * This [[IReachableStopsFinder]] uses the registered [[IRoadPlanner]] to find reachable stops + */ @injectable() export default class ReachableStopsFinderRoadPlanner implements IReachableStopsFinder { private readonly stopsProvider: IStopsProvider; diff --git a/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts b/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts index 851afa60..f373f29a 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlannerCached.ts @@ -8,6 +8,10 @@ import IRoadPlanner from "../road/IRoadPlanner"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; import ReachableStopsFinderRoadPlanner from "./ReachableStopsFinderRoadPlanner"; +/** + * This [[IReachableStopsFinder]] forms a caching layer in front of a [[ReachableStopsFinderRoadPlanner]] instance, + * so all the queries don't have to be executed repeatedly + */ @injectable() export default class ReachableStopsFinderRoadPlannerCached implements IReachableStopsFinder { private readonly reachableStopsFinder: ReachableStopsFinderRoadPlanner; diff --git a/src/query-runner/ILocationResolver.ts b/src/query-runner/ILocationResolver.ts index a4f4f93c..1115a20e 100644 --- a/src/query-runner/ILocationResolver.ts +++ b/src/query-runner/ILocationResolver.ts @@ -1,6 +1,9 @@ import IStop from "../fetcher/stops/IStop"; import ILocation from "../interfaces/ILocation"; +/** + * A location resolver turns an [[ILocation]], [[IStop]] or a string into an [[ILocation]] + */ export default interface ILocationResolver { resolve: (location: ILocation | IStop | string) => Promise; } diff --git a/src/query-runner/IQueryRunner.ts b/src/query-runner/IQueryRunner.ts index 6103a303..c245f3ba 100644 --- a/src/query-runner/IQueryRunner.ts +++ b/src/query-runner/IQueryRunner.ts @@ -2,7 +2,10 @@ import { AsyncIterator } from "asynciterator"; import IPath from "../interfaces/IPath"; import IQuery from "../interfaces/IQuery"; +/** + * A query runner has a `run` method that turns an [[IQuery]] into an AsyncIterator of [[IPath]] instances. + * It does this by executing one or more algorithms on the query, depending on the implementation. + */ export default interface IQueryRunner { - run(query: IQuery): Promise>; } diff --git a/src/query-runner/IResolvedQuery.ts b/src/query-runner/IResolvedQuery.ts index d82492f9..ecec1252 100644 --- a/src/query-runner/IResolvedQuery.ts +++ b/src/query-runner/IResolvedQuery.ts @@ -1,6 +1,11 @@ import ILocation from "../interfaces/ILocation"; import { DurationMs, SpeedKmH } from "../interfaces/units"; +/** + * A resolved query is the result of transforming an input [[IQuery]] by adding defaults for any missing parameters and + * by resolving the endpoints (`from` and `to`). Classes using this resolved query don't have to worry about any missing + * or unrealistic parameters + */ export default interface IResolvedQuery { from?: ILocation[]; to?: ILocation[]; diff --git a/src/query-runner/LocationResolverDefault.ts b/src/query-runner/LocationResolverDefault.ts index e2303f7f..88513161 100644 --- a/src/query-runner/LocationResolverDefault.ts +++ b/src/query-runner/LocationResolverDefault.ts @@ -6,6 +6,13 @@ import ILocation from "../interfaces/ILocation"; import TYPES from "../types"; import ILocationResolver from "./ILocationResolver"; +/** + * This default location resolver resolves [[ILocation]] instances by their `id` (`http(s)://...`) + * + * If only an `id` string is passed, it returns an [[ILocation]] with all available information. + * + * If an incomplete [[ILocation]] (but with an `id`) is passed, it gets supplemented as well. + */ @injectable() export default class LocationResolverDefault implements ILocationResolver { private readonly stopsProvider: IStopsProvider; diff --git a/src/query-runner/QueryRunnerDefault.ts b/src/query-runner/QueryRunnerDefault.ts index 045bb75f..0fbcea0f 100644 --- a/src/query-runner/QueryRunnerDefault.ts +++ b/src/query-runner/QueryRunnerDefault.ts @@ -12,6 +12,12 @@ import ILocationResolver from "./ILocationResolver"; import IQueryRunner from "./IQueryRunner"; import IResolvedQuery from "./IResolvedQuery"; +/** + * This default query runner only accepts public transport queries (`publicTransportOnly = true`). + * It uses the registered [[IPublicTransportPlanner]] to execute them. + * + * The default `minimumDepartureTime` is *now*. The default `maximumArrivalTime` is `minimumDepartureTime + 2 hours`. + */ @injectable() export default class QueryRunnerDefault implements IQueryRunner { private locationResolver: ILocationResolver; diff --git a/src/query-runner/exponential/ExponentialQueryIterator.ts b/src/query-runner/exponential/ExponentialQueryIterator.ts index 5f874b8f..1e2d179b 100644 --- a/src/query-runner/exponential/ExponentialQueryIterator.ts +++ b/src/query-runner/exponential/ExponentialQueryIterator.ts @@ -2,7 +2,10 @@ import { AsyncIterator } from "asynciterator"; import { DurationMs } from "../../interfaces/units"; import IResolvedQuery from "../IResolvedQuery"; -// Inspired by IntegerIterator +/** + * This AsyncIterator emits [[IResolvedQuery]] instances with exponentially increasing `maximumArrivalTime`. + * For each emitted query, the time frame gets doubled (x2). + */ export default class ExponentialQueryIterator extends AsyncIterator { private readonly baseQuery: IResolvedQuery; private timespan: DurationMs; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index 128c570e..42d6ff04 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -18,6 +18,19 @@ import IQueryRunner from "../IQueryRunner"; import IResolvedQuery from "../IResolvedQuery"; import ExponentialQueryIterator from "./ExponentialQueryIterator"; +/** + * This exponential query runner only accepts public transport queries (`publicTransportOnly = true`). + * It uses the registered [[IPublicTransportPlanner]] to execute them. + * + * To improve the user perceived performance, the query gets split into subqueries + * with exponentially increasing time frames: + * + * ``` + * minimumDepartureTime + 15 minutes, 30 minutes, 60 minutes, 120 minutes... + * ``` + * + * In the current implementation, the `maximumArrivalTime` is ignored + */ @injectable() export default class QueryRunnerExponential implements IQueryRunner { private readonly locationResolver: ILocationResolver; diff --git a/src/util/iterators/AsyncArrayIterator.ts b/src/util/iterators/AsyncArrayIterator.ts index 9888fbce..243dfa55 100644 --- a/src/util/iterators/AsyncArrayIterator.ts +++ b/src/util/iterators/AsyncArrayIterator.ts @@ -1,6 +1,12 @@ import { BufferedIterator } from "asynciterator"; import { DurationMs } from "../../interfaces/units"; +/** + * An AsyncIterator that emits the items of a given array, asynchronously. + * Optionally accepts an interval (in ms) between each emitted item + * + * This class is most useful in tests + */ export default class AsyncArrayIterator extends BufferedIterator { private currentIndex: number = 0; private readonly array: T[]; diff --git a/src/util/iterators/FilterUniqueIterator.ts b/src/util/iterators/FilterUniqueIterator.ts index d8974074..14c8bec5 100644 --- a/src/util/iterators/FilterUniqueIterator.ts +++ b/src/util/iterators/FilterUniqueIterator.ts @@ -1,5 +1,11 @@ import { AsyncIterator, SimpleTransformIterator } from "asynciterator"; +/** + * An AsyncIterator that emits only the unique items emitted by a source iterator. + * Uniqueness is determined by a comparator callback function + * + * Note: All (unique) items get stored in an array internally + */ export default class FilterUniqueIterator extends SimpleTransformIterator { private readonly comparator: (object: T, otherObject: T) => boolean; diff --git a/src/util/iterators/FlatMapIterator.ts b/src/util/iterators/FlatMapIterator.ts index 82061a3f..e8f74912 100644 --- a/src/util/iterators/FlatMapIterator.ts +++ b/src/util/iterators/FlatMapIterator.ts @@ -1,5 +1,23 @@ import { AsyncIterator, BufferedIterator } from "asynciterator"; +/** + * This AsyncIterator maps every item of a query AsyncIterator to a result AsyncIterator by passing it through a + * `run` function. All result AsyncIterator get concatenated to form the FlatMapIterator + * + * ```javascript + * +-----+ +-----+ + * queryIterator |0 & 9| +---+ + |1 & 8| +---+ + ... + * +-----+ | +-----+ | + * v v + * +-----------------------+ +-----------------------+ + * resultIterators |01|02|04|05|06|07|08|09| + |11|12|13|14|15|16|17|18| + ... + * +-----------------------+ +-----------------------+ + * + * +-----------------------------------------------+ + * FlatMapIterator |01|02|04|05|06|07|08|09|11|12|13|14|15|16|17|18| ... + * +-----------------------------------------------+ + * ``` + */ export default class FlatMapIterator extends BufferedIterator { private queryIterator: AsyncIterator; private callback: (query: Q) => Promise>; diff --git a/src/util/iterators/MergeIterator.ts b/src/util/iterators/MergeIterator.ts index 384ff3a7..8416a79a 100644 --- a/src/util/iterators/MergeIterator.ts +++ b/src/util/iterators/MergeIterator.ts @@ -1,10 +1,10 @@ import { AsyncIterator, BufferedIterator } from "asynciterator"; /** - * Asynciterator that merges a number of source asynciterators based on the passed selector function. + * AsyncIterator that merges a number of source asynciterators based on the passed selector function. * The selector function gets passed an array of values read from each of the asynciterators. * Values can be undefined if their respective source iterator has ended. - * It should return the index in that array of the value to select. + * The selector function should return the index in that array of the value to select. * @param condensed When true, undefined values are filtered from the array passed to the selector function */ export default class MergeIterator extends BufferedIterator { From d8539e292812214a02eebab932ffd4fb85fa990e Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 18 Jan 2019 16:33:07 +0100 Subject: [PATCH 27/69] Update ReachableStopsFinderRoadPlanner docs --- src/planner/stops/ReachableStopsFinderRoadPlanner.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts index 9b36871e..c1327d83 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlanner.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlanner.ts @@ -15,7 +15,9 @@ import IRoadPlanner from "../road/IRoadPlanner"; import IReachableStopsFinder, { IReachableStop } from "./IReachableStopsFinder"; /** - * This [[IReachableStopsFinder]] uses the registered [[IRoadPlanner]] to find reachable stops + * This [[IReachableStopsFinder]] uses the registered [[IRoadPlanner]] to find reachable stops. + * It makes an initial selection of stops based on bird's-eye distance, after which a road planner query gets executed + * for each of these stops. */ @injectable() export default class ReachableStopsFinderRoadPlanner implements IReachableStopsFinder { From 254fe6091afbadd6054163d78ea5a01167a16862 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 18 Jan 2019 17:00:08 +0100 Subject: [PATCH 28/69] Rename IConnectionsFetcherConfig to IConnectionsIteratorOptions --- .../connections/ConnectionsProviderMerge.ts | 12 ++++++------ .../connections/ConnectionsProviderPassthrough.ts | 6 +++--- .../connections/IConnectionsFetcherConfig.ts | 5 ----- .../connections/IConnectionsIteratorOptions.ts | 8 ++++++++ src/fetcher/connections/IConnectionsProvider.ts | 4 ++-- .../connections/lazy/ConnectionsFetcherLazy.ts | 10 +++++----- .../lazy/ConnectionsIteratorLazy.test.ts | 5 +++-- .../connections/lazy/ConnectionsIteratorLazy.ts | 10 +++++----- .../prefetch/ConnectionsProviderPrefetch.ts | 14 +++++++------- .../connections/prefetch/ConnectionsStore.test.ts | 14 +++++++------- .../connections/prefetch/ConnectionsStore.ts | 8 ++++---- .../tests/ConnectionsFetcherNMBSTest.ts | 10 +++++----- src/planner/public-transport/CSAEarliestArrival.ts | 2 +- src/planner/public-transport/CSAProfile.ts | 2 +- 14 files changed, 57 insertions(+), 53 deletions(-) delete mode 100644 src/fetcher/connections/IConnectionsFetcherConfig.ts create mode 100644 src/fetcher/connections/IConnectionsIteratorOptions.ts diff --git a/src/fetcher/connections/ConnectionsProviderMerge.ts b/src/fetcher/connections/ConnectionsProviderMerge.ts index 1fe229b9..7e0fb64b 100644 --- a/src/fetcher/connections/ConnectionsProviderMerge.ts +++ b/src/fetcher/connections/ConnectionsProviderMerge.ts @@ -5,7 +5,7 @@ import TYPES, { ConnectionsFetcherFactory } from "../../types"; import MergeIterator from "../../util/iterators/MergeIterator"; import IConnection from "./IConnection"; import IConnectionsFetcher from "./IConnectionsFetcher"; -import IConnectionsFetcherConfig from "./IConnectionsFetcherConfig"; +import IConnectionsIteratorOptions from "./IConnectionsIteratorOptions"; import IConnectionsProvider from "./IConnectionsProvider"; /** @@ -52,7 +52,7 @@ export default class ConnectionsProviderMerge implements IConnectionsProvider { return latestIndex; } - private config: IConnectionsFetcherConfig; + private options: IConnectionsIteratorOptions; private connectionsFetchers: IConnectionsFetcher[]; constructor( @@ -75,7 +75,7 @@ export default class ConnectionsProviderMerge implements IConnectionsProvider { const iterators = this.connectionsFetchers .map((fetcher) => fetcher.createIterator()); - const selector = this.config.backward ? + const selector = this.options.backward ? ConnectionsProviderMerge.backwardsConnectionsSelector : ConnectionsProviderMerge.forwardsConnectionSelector; @@ -83,10 +83,10 @@ export default class ConnectionsProviderMerge implements IConnectionsProvider { return new MergeIterator(iterators, selector, true); } - public setConfig(config: IConnectionsFetcherConfig): void { - this.config = config; + public setIteratorOptions(options: IConnectionsIteratorOptions): void { + this.options = options; this.connectionsFetchers.forEach((fetcher) => { - fetcher.setConfig(config); + fetcher.setIteratorOptions(options); }); } } diff --git a/src/fetcher/connections/ConnectionsProviderPassthrough.ts b/src/fetcher/connections/ConnectionsProviderPassthrough.ts index 4673682a..1513107a 100644 --- a/src/fetcher/connections/ConnectionsProviderPassthrough.ts +++ b/src/fetcher/connections/ConnectionsProviderPassthrough.ts @@ -4,7 +4,7 @@ import Catalog from "../../Catalog"; import TYPES, { ConnectionsFetcherFactory } from "../../types"; import IConnection from "./IConnection"; import IConnectionsFetcher from "./IConnectionsFetcher"; -import IConnectionsFetcherConfig from "./IConnectionsFetcherConfig"; +import IConnectionsIteratorOptions from "./IConnectionsIteratorOptions"; import IConnectionsProvider from "./IConnectionsProvider"; /** @@ -33,7 +33,7 @@ export default class ConnectionsProviderPassthrough implements IConnectionsProvi return this.connectionsFetcher.createIterator(); } - public setConfig(config: IConnectionsFetcherConfig): void { - this.connectionsFetcher.setConfig(config); + public setIteratorOptions(options: IConnectionsIteratorOptions): void { + this.connectionsFetcher.setIteratorOptions(options); } } diff --git a/src/fetcher/connections/IConnectionsFetcherConfig.ts b/src/fetcher/connections/IConnectionsFetcherConfig.ts deleted file mode 100644 index cbb27f2d..00000000 --- a/src/fetcher/connections/IConnectionsFetcherConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface IConnectionsFetcherConfig { - upperBoundDate?: Date; - lowerBoundDate?: Date; - backward?: boolean; -} diff --git a/src/fetcher/connections/IConnectionsIteratorOptions.ts b/src/fetcher/connections/IConnectionsIteratorOptions.ts new file mode 100644 index 00000000..2c65fb9a --- /dev/null +++ b/src/fetcher/connections/IConnectionsIteratorOptions.ts @@ -0,0 +1,8 @@ +/** + * Options passed to [[IConnectionsProvider]] and [[IConnectionsFetcherInstances]] instances to + */ +export default interface IConnectionsIteratorOptions { + upperBoundDate?: Date; + lowerBoundDate?: Date; + backward?: boolean; +} diff --git a/src/fetcher/connections/IConnectionsProvider.ts b/src/fetcher/connections/IConnectionsProvider.ts index f6765eaa..054c1c53 100644 --- a/src/fetcher/connections/IConnectionsProvider.ts +++ b/src/fetcher/connections/IConnectionsProvider.ts @@ -1,6 +1,6 @@ import { AsyncIterator } from "asynciterator"; import IConnection from "./IConnection"; -import IConnectionsFetcherConfig from "./IConnectionsFetcherConfig"; +import IConnectionsIteratorOptions from "./IConnectionsIteratorOptions"; /** * A IConnectionsProvider serves as interface to other classes that want to use [[IConnection]] instances @@ -10,5 +10,5 @@ import IConnectionsFetcherConfig from "./IConnectionsFetcherConfig"; export default interface IConnectionsProvider { prefetchConnections: () => void; createIterator: () => AsyncIterator; - setConfig: (config: IConnectionsFetcherConfig) => void; + setIteratorOptions: (options: IConnectionsIteratorOptions) => void; } diff --git a/src/fetcher/connections/lazy/ConnectionsFetcherLazy.ts b/src/fetcher/connections/lazy/ConnectionsFetcherLazy.ts index ba5053d7..8e36dbd6 100644 --- a/src/fetcher/connections/lazy/ConnectionsFetcherLazy.ts +++ b/src/fetcher/connections/lazy/ConnectionsFetcherLazy.ts @@ -5,7 +5,7 @@ import TravelMode from "../../../enums/TravelMode"; import TYPES from "../../../types"; import IConnection from "../IConnection"; import IConnectionsFetcher from "../IConnectionsFetcher"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; import ConnectionsIteratorLazy from "./ConnectionsIteratorLazy"; /** @@ -16,7 +16,7 @@ import ConnectionsIteratorLazy from "./ConnectionsIteratorLazy"; export default class ConnectionsFetcherLazy implements IConnectionsFetcher { protected readonly ldFetch: LDFetch; - protected config: IConnectionsFetcherConfig; + protected options: IConnectionsIteratorOptions; private travelMode: TravelMode; private accessUrl: string; @@ -41,11 +41,11 @@ export default class ConnectionsFetcherLazy implements IConnectionsFetcher { this.accessUrl, this.travelMode, this.ldFetch, - this.config, + this.options, ); } - public setConfig(config: IConnectionsFetcherConfig): void { - this.config = config; + public setIteratorOptions(options: IConnectionsIteratorOptions): void { + this.options = options; } } diff --git a/src/fetcher/connections/lazy/ConnectionsIteratorLazy.test.ts b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.test.ts index 6db14111..4366f1ed 100644 --- a/src/fetcher/connections/lazy/ConnectionsIteratorLazy.test.ts +++ b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.test.ts @@ -2,6 +2,7 @@ import "jest"; import LdFetch from "ldfetch"; import TravelMode from "../../../enums/TravelMode"; import IConnection from "../IConnection"; +import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; import ConnectionsIteratorLazy from "./ConnectionsIteratorLazy"; const CONNECTIONS_TO_LOAD = 500; // Should be more than contained on first page @@ -9,7 +10,7 @@ const CONNECTIONS_TO_LOAD = 500; // Should be more than contained on first page test("[ConnectionsIteratorLazy] iterate forwards", (done) => { jest.setTimeout(90000); - const config = { + const options: IConnectionsIteratorOptions = { backward: false, lowerBoundDate: new Date(2018, 10, 22, 10), }; @@ -17,7 +18,7 @@ test("[ConnectionsIteratorLazy] iterate forwards", (done) => { "https://graph.irail.be/sncb/connections", TravelMode.Train, new LdFetch(), - config, + options, ); let i = 0; diff --git a/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts index f7e98658..9cbf5d2e 100644 --- a/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts +++ b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts @@ -7,7 +7,7 @@ import HydraPageIterator from "../hydra/HydraPageIterator"; import IHydraPage from "../hydra/IHydraPage"; import IHydraPageIteratorConfig from "../hydra/IHydraPageIteratorConfig"; import IConnection from "../IConnection"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; /** * Base class for fetching linked connections with LDFetch and letting the caller iterate over them asynchronously @@ -21,13 +21,13 @@ export default class ConnectionsIteratorLazy extends FlatMapIterator; - private connectionsFetcherConfig: IConnectionsFetcherConfig; + private connectionsIteratorOptions: IConnectionsIteratorOptions; constructor( @inject(TYPES.ConnectionsFetcherFactory) connectionsFetcherFactory: ConnectionsFetcherFactory, @@ -40,12 +40,12 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.startedPrefetching = true; setTimeout(() => { - const config: IConnectionsFetcherConfig = { + const options: IConnectionsIteratorOptions = { backward: false, lowerBoundDate: new Date(), }; - this.connectionsFetcher.setConfig(config); + this.connectionsFetcher.setIteratorOptions(options); this.connectionsIterator = this.connectionsFetcher.createIterator(); this.connectionsIterator @@ -61,14 +61,14 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider public createIterator(): AsyncIterator { if (this.startedPrefetching) { return new PromiseProxyIterator(() => - this.connectionsStore.getIterator(this.connectionsFetcherConfig), + this.connectionsStore.getIterator(this.connectionsIteratorOptions), ); } throw new Error("TODO"); } - public setConfig(config: IConnectionsFetcherConfig): void { - this.connectionsFetcherConfig = config; + public setIteratorOptions(options: IConnectionsIteratorOptions): void { + this.connectionsIteratorOptions = options; } } diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts index 463cfcb5..fab4d406 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -1,7 +1,7 @@ import {AsyncIterator} from "asynciterator"; import "jest"; import IConnection from "../IConnection"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; import ConnectionsStore from "./ConnectionsStore"; describe("[ConnectionsStore]", () => { @@ -31,19 +31,19 @@ describe("[ConnectionsStore]", () => { connectionsStore.finish(); createIterator = async (backward, lowerBoundDate, upperBoundDate): Promise> => { - const fetcherConfig: IConnectionsFetcherConfig = { + const iteratorOptions: IConnectionsIteratorOptions = { backward, }; if (lowerBoundDate) { - fetcherConfig.lowerBoundDate = (lowerBoundDate as unknown) as Date; + iteratorOptions.lowerBoundDate = (lowerBoundDate as unknown) as Date; } if (upperBoundDate) { - fetcherConfig.upperBoundDate = (upperBoundDate as unknown) as Date; + iteratorOptions.upperBoundDate = (upperBoundDate as unknown) as Date; } - return connectionsStore.getIterator(fetcherConfig); + return connectionsStore.getIterator(iteratorOptions); }; }); @@ -153,11 +153,11 @@ describe("[ConnectionsStore]", () => { setTimeout(appendNext, 100); it("iterator view: backward / upperBoundDate isn't loaded at first", async (done) => { - const fetcherConfig: IConnectionsFetcherConfig = { + const iteratorOptions: IConnectionsIteratorOptions = { backward: true, upperBoundDate: (7 as unknown) as Date, }; - const iteratorView: AsyncIterator = await connectionsStore.getIterator(fetcherConfig); + const iteratorView: AsyncIterator = await connectionsStore.getIterator(iteratorOptions); const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; let current = expected.length - 1; diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 1a74300e..fc382e5c 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -1,7 +1,7 @@ import { ArrayIterator, AsyncIterator, IntegerIterator, IntegerIteratorOptions } from "asynciterator"; import BinarySearch from "../../../util/BinarySearch"; import IConnection from "../IConnection"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; interface IViewPromise { backward: boolean; @@ -51,9 +51,9 @@ export default class ConnectionsStore { this.hasFinished = true; } - public getIterator(fetcherConfig: IConnectionsFetcherConfig): Promise> { - const { backward } = fetcherConfig; - let { lowerBoundDate, upperBoundDate } = fetcherConfig; + public getIterator(iteratorOptions: IConnectionsIteratorOptions): Promise> { + const { backward } = iteratorOptions; + let { lowerBoundDate, upperBoundDate } = iteratorOptions; if (this.hasFinished && this.store.length === 0) { return Promise.resolve(new ArrayIterator([])); diff --git a/src/fetcher/connections/tests/ConnectionsFetcherNMBSTest.ts b/src/fetcher/connections/tests/ConnectionsFetcherNMBSTest.ts index 4e96f848..5be1c39e 100644 --- a/src/fetcher/connections/tests/ConnectionsFetcherNMBSTest.ts +++ b/src/fetcher/connections/tests/ConnectionsFetcherNMBSTest.ts @@ -2,13 +2,13 @@ import { ArrayIterator, AsyncIterator } from "asynciterator"; import { injectable } from "inversify"; import IConnection from "../IConnection"; import IConnectionsFetcher from "../IConnectionsFetcher"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; @injectable() export default class ConnectionsFetcherNMBSTest implements IConnectionsFetcher { private connections: Array> = []; - private config: IConnectionsFetcherConfig = {}; + private options: IConnectionsIteratorOptions = {}; constructor(connections: Array>) { this.connections = connections; @@ -18,15 +18,15 @@ export default class ConnectionsFetcherNMBSTest implements IConnectionsFetcher { return; } - public setConfig(config: IConnectionsFetcherConfig): void { - this.config = config; + public setIteratorOptions(options: IConnectionsIteratorOptions): void { + this.options = options; } public createIterator(): AsyncIterator { let array = this.connections .map((r) => r.value); - if (this.config.backward) { + if (this.options.backward) { array = array.reverse(); } diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index 4e66d0ca..d64f6b47 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -95,7 +95,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { maximumArrivalTime: upperBoundDate, } = this.query; - this.connectionsProvider.setConfig({ + this.connectionsProvider.setIteratorOptions({ upperBoundDate, lowerBoundDate, }); diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index 74ed778c..597b5313 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -99,7 +99,7 @@ export default class CSAProfile implements IPublicTransportPlanner { maximumArrivalTime: upperBoundDate, } = this.query; - this.connectionsProvider.setConfig({ + this.connectionsProvider.setIteratorOptions({ backward: true, upperBoundDate, lowerBoundDate, From e7f94fbe14578407dafeb4f34973ab26a1b82768 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 24 Jan 2019 09:36:03 +0100 Subject: [PATCH 29/69] Some more docs + Small bugfixes --- .../connections/ConnectionsProviderPassthrough.ts | 2 +- .../connections/IConnectionsIteratorOptions.ts | 3 ++- .../prefetch/ConnectionsProviderPrefetch.ts | 7 +++++-- .../connections/prefetch/ConnectionsStore.ts | 14 ++++++++++++++ .../public-transport/CSAEarliestArrival.test.ts | 2 +- src/planner/public-transport/CSAProfile.test.ts | 2 +- src/planner/public-transport/CSAProfile.ts | 4 +++- src/test/test-connections-iterator.ts | 2 +- 8 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/fetcher/connections/ConnectionsProviderPassthrough.ts b/src/fetcher/connections/ConnectionsProviderPassthrough.ts index 1513107a..c6718081 100644 --- a/src/fetcher/connections/ConnectionsProviderPassthrough.ts +++ b/src/fetcher/connections/ConnectionsProviderPassthrough.ts @@ -8,7 +8,7 @@ import IConnectionsIteratorOptions from "./IConnectionsIteratorOptions"; import IConnectionsProvider from "./IConnectionsProvider"; /** - * Passes through one [[IConnectionsFetcher]], the first one if there are multiple + * Passes through any method calls to a *single* [[IConnectionsFetcher]], the first if there are multiple source configs * This provider is most/only useful if there is only one fetcher */ @injectable() diff --git a/src/fetcher/connections/IConnectionsIteratorOptions.ts b/src/fetcher/connections/IConnectionsIteratorOptions.ts index 2c65fb9a..0060e196 100644 --- a/src/fetcher/connections/IConnectionsIteratorOptions.ts +++ b/src/fetcher/connections/IConnectionsIteratorOptions.ts @@ -1,5 +1,6 @@ /** - * Options passed to [[IConnectionsProvider]] and [[IConnectionsFetcherInstances]] instances to + * Options passed to [[IConnectionsProvider]] and [[IConnectionsFetcher]] instances + * for creating AsyncIterators of [[IConnection]] instances. */ export default interface IConnectionsIteratorOptions { upperBoundDate?: Date; diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index 647e278e..3640a08d 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -10,8 +10,11 @@ import IConnectionsProvider from "../IConnectionsProvider"; import ConnectionsStore from "./ConnectionsStore"; /** - * Passes through one [[IConnectionsFetcher]], the first one if there are multiple - * This provider is most/only useful if there is only one fetcher + * This connections provider implements the [[IConnectionsProvider.prefetchConnections]] method. + * When called, it asks an AsyncIterator from the instantiated [[IConnectionsFetcher]]. + * All items from that iterator get appended to a [[ConnectionsStore]] + * + * When [[IConnectionsProvider.createIterator]] is called, it returns an iterator *view* from the [[ConnectionsStore]] */ @injectable() export default class ConnectionsProviderPrefetch implements IConnectionsProvider { diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index fc382e5c..6e03502c 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -10,6 +10,14 @@ interface IViewPromise { resolve: (iterator: AsyncIterator) => void; } +/** + * Class used while prefetching [[IConnection]] instances. It allows appending connections + * and creating iterator *views*. Iterator *views* are AsyncIterators that emit references to connections in the store. + * + * It is assumed that all connections are appended in ascending order by `departureTime`. + * + * Consequently this connections store serves as an in-memory cache for connections + */ export default class ConnectionsStore { private readonly store: IConnection[]; private readonly binarySearch: BinarySearch; @@ -23,6 +31,12 @@ export default class ConnectionsStore { this.hasFinished = false; } + /** + * Add a new [[IConnection]] to the store. + * + * Additionally, this method checks if any forward iterator views can be pushed to or if any backward iterator can be + * resolved + */ public append(connection: IConnection) { this.store.push(connection); diff --git a/src/planner/public-transport/CSAEarliestArrival.test.ts b/src/planner/public-transport/CSAEarliestArrival.test.ts index bf0f83d9..9a6f05b8 100644 --- a/src/planner/public-transport/CSAEarliestArrival.test.ts +++ b/src/planner/public-transport/CSAEarliestArrival.test.ts @@ -27,7 +27,7 @@ describe("[PublicTransportPlannerCSAEarliestArrival]", () => { const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); const connectionFetcher = new ConnectionsFetcherNMBSTest(connections); - connectionFetcher.setConfig({ backward: false }); + connectionFetcher.setIteratorOptions({ backward: false }); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); diff --git a/src/planner/public-transport/CSAProfile.test.ts b/src/planner/public-transport/CSAProfile.test.ts index 68a6ed32..8bcba17e 100644 --- a/src/planner/public-transport/CSAProfile.test.ts +++ b/src/planner/public-transport/CSAProfile.test.ts @@ -27,7 +27,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); const connectionFetcher = new ConnectionsFetcherNMBSTest(connections); - connectionFetcher.setConfig({ backward: true }); + connectionFetcher.setIteratorOptions({ backward: true }); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index 597b5313..6edb3349 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -433,7 +433,9 @@ export default class CSAProfile implements IPublicTransportPlanner { }); } catch (e) { - this.context.emitWarning(e); + if (this.context) { + this.context.emitWarning(e); + } } } diff --git a/src/test/test-connections-iterator.ts b/src/test/test-connections-iterator.ts index 1f0534b6..decba271 100644 --- a/src/test/test-connections-iterator.ts +++ b/src/test/test-connections-iterator.ts @@ -16,7 +16,7 @@ const config = { const connectionsFetcher = new ConnectionsFetcherLDFetch(ldFetch); connectionsFetcher.setTravelMode(TravelMode.Train); connectionsFetcher.setAccessUrl("https://graph.irail.be/sncb/connections"); -connectionsFetcher.setConfig(config); +connectionsFetcher.setIteratorOptions(config); // iterator.setLowerBound(new Date(2018, 10, 2, 10)); (async () => { From 4f497797ada7c668ea724e2cd6114480237f408a Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 24 Jan 2019 11:55:30 +0100 Subject: [PATCH 30/69] Reduce bundle size by excluding ldfetch dependencies --- package-lock.json | 73 ++++++++++++++++++++++++------------------- package.json | 1 - webpack.config.js | 25 +++++++++++---- webpack.mockModule.js | 1 + 4 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 webpack.mockModule.js diff --git a/package-lock.json b/package-lock.json index 634f1dcd..5c1c2b47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,9 +33,22 @@ } }, "@rdfjs/data-model": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rdfjs/data-model/-/data-model-1.1.0.tgz", - "integrity": "sha512-vK7TlSFBJQihqMoMgXK/VtcRV+rCQSOLB9VGAfv4Pr6BzUbBcR0CeM/RpkD4bHGX3816eaaURH1CNgN7togWdw==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rdfjs/data-model/-/data-model-1.1.1.tgz", + "integrity": "sha512-4jb1zc77f27u/MLVhpE/zHR1uvdH4XElXG63rJP/kVnvKoHtVfyJSEqN9oRLANgqHJ9SNwKj9FXeNFZ4+GGfiw==", + "requires": { + "@types/rdf-js": "^2.0.1" + }, + "dependencies": { + "@types/rdf-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/rdf-js/-/rdf-js-2.0.1.tgz", + "integrity": "sha512-x3Qct8TPilUos4znM1gANmtTvjOFdDRItmpEM2Nu9QgAx258FN9k22OvOu2TmPzOlx8a1FLdEW3o33UXHQt5ow==", + "requires": { + "@types/node": "*" + } + } + } }, "@types/asynciterator": { "version": "1.1.1", @@ -118,8 +131,7 @@ "@types/node": { "version": "10.12.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz", - "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==", - "dev": true + "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==" }, "@types/rdf-js": { "version": "1.0.1", @@ -1212,11 +1224,6 @@ "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", "dev": true }, - "bindings": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", - "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" - }, "bluebird": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", @@ -2531,9 +2538,9 @@ } }, "follow-redirects": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", - "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.6.1.tgz", + "integrity": "sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ==", "requires": { "debug": "=3.1.0" } @@ -4404,13 +4411,13 @@ } }, "jsonld": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.1.0.tgz", - "integrity": "sha512-tx87xNtu2hGabr7mhSyXTI8q+Fz3pl+50B/JislFPwAz8ud0KTTQpNjU74tJIegFAHve9UFYzzj4YVTIrac0Hw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.5.0.tgz", + "integrity": "sha512-7jF9WXK4nuHvhz/qT6A4DEZ58tUYgrV98xBJEgHFhQ6GQaNT+oU1zqkFXKtDZsKsiEs/1K/VShNnat6SISb3jg==", "requires": { - "rdf-canonize": "^0.2.1", - "request": "^2.83.0", - "semver": "^5.5.0", + "rdf-canonize": "^1.0.1", + "request": "^2.88.0", + "semver": "^5.6.0", "xmldom": "0.1.19" }, "dependencies": { @@ -4843,7 +4850,9 @@ "nan": { "version": "2.11.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", - "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==" + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", + "dev": true, + "optional": true }, "nanomatch": { "version": "1.2.13", @@ -5622,14 +5631,12 @@ } }, "rdf-canonize": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-0.2.4.tgz", - "integrity": "sha512-xwAEHJt8FTe4hP9CjFgwQPFdszu4iwEintk31+9eh0rljC28vm9EhoaIlC1rQx5LaCB5oHom4+yoei4+DTdRjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-1.0.1.tgz", + "integrity": "sha512-vQq6q7BIUwrVQijKRYdunxlodkn0Btjv2MnJ4S3rOUELsghq7fGuDaWuqBNbXca3KRbcRS6HwTIT2hJbJej2UA==", "requires": { - "bindings": "^1.3.0", - "nan": "^2.10.0", - "node-forge": "^0.7.1", - "semver": "^5.4.1" + "node-forge": "^0.7.6", + "semver": "^5.6.0" } }, "rdfa-processor": { @@ -5641,9 +5648,9 @@ }, "dependencies": { "n3": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/n3/-/n3-1.0.0-beta.2.tgz", - "integrity": "sha512-dY0Q21JhNJozPMSijKI9phfgocXb7hF9F8zOPnsRu0Dy61eQwQVKcFEw59V0sn2F/neJjNg7WHmNtGLMkPpKPA==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.0.3.tgz", + "integrity": "sha512-IdcCNqb/1gj9fX63hoVEn3/Z1u9iLJiYLSpesmqT3+5UrxcYG5YldUP6T2okNRZKzyVdqz+I0PExGkWkz9gcZw==" } } }, @@ -5656,9 +5663,9 @@ }, "dependencies": { "n3": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/n3/-/n3-1.0.0-beta.2.tgz", - "integrity": "sha512-dY0Q21JhNJozPMSijKI9phfgocXb7hF9F8zOPnsRu0Dy61eQwQVKcFEw59V0sn2F/neJjNg7WHmNtGLMkPpKPA==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.0.3.tgz", + "integrity": "sha512-IdcCNqb/1gj9fX63hoVEn3/Z1u9iLJiYLSpesmqT3+5UrxcYG5YldUP6T2okNRZKzyVdqz+I0PExGkWkz9gcZw==" } } }, diff --git a/package.json b/package.json index 7eb21dae..22fea393 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "inversify": "^5.0.1", "isomorphic-fetch": "^2.2.1", "ldfetch": "^1.1.1-alpha", - "n3": "0.11.2", "reflect-metadata": "^0.1.12", "uritemplate": "^0.3.4" }, diff --git a/webpack.config.js b/webpack.config.js index cd163ddc..444513e7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,17 @@ const path = require("path"); +// These modules are imported by ldfetch, but are never actually used because (right now) we only fetch jsonld files +const excludeModules = [ + "rdfa-processor", + "rdf-canonize", + "rdfxmlprocessor", + "xmldom", + 'n3' +]; + module.exports = { entry: "./src/index.ts", - devtool: 'cheap-module-source-map', + devtool: "cheap-module-source-map", module: { rules: [ { @@ -13,13 +22,17 @@ module.exports = { ] }, resolve: { - extensions: [".ts", ".js"] + extensions: [".ts", ".js"], + alias: excludeModules.reduce((alias, moduleName) => { + alias[moduleName] = path.resolve(__dirname, "webpack.mockModule.js"); + return alias; + }, {}) }, output: { filename: "bundle.js", - path: path.resolve(__dirname, 'dist'), - library: 'Planner', - libraryTarget: 'umd', - libraryExport: 'default', + path: path.resolve(__dirname, "dist"), + library: "Planner", + libraryTarget: "umd", + libraryExport: "default" } }; diff --git a/webpack.mockModule.js b/webpack.mockModule.js new file mode 100644 index 00000000..f053ebf7 --- /dev/null +++ b/webpack.mockModule.js @@ -0,0 +1 @@ +module.exports = {}; From 520b31a43427ab355ba3b3914fad969fd16dc596 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 24 Jan 2019 12:11:30 +0100 Subject: [PATCH 31/69] Add shim for q.defer() --- webpack.config.js | 13 +++++++++---- webpack.mockModule.js => webpack/mockModule.js | 0 webpack/shimQ.js | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) rename webpack.mockModule.js => webpack/mockModule.js (100%) create mode 100644 webpack/shimQ.js diff --git a/webpack.config.js b/webpack.config.js index 444513e7..94c6df72 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -9,6 +9,11 @@ const excludeModules = [ 'n3' ]; +const excludeAlias = excludeModules.reduce((alias, moduleName) => { + alias[moduleName] = path.resolve(__dirname, "webpack/mockModule.js"); + return alias; +}, {}); + module.exports = { entry: "./src/index.ts", devtool: "cheap-module-source-map", @@ -23,10 +28,10 @@ module.exports = { }, resolve: { extensions: [".ts", ".js"], - alias: excludeModules.reduce((alias, moduleName) => { - alias[moduleName] = path.resolve(__dirname, "webpack.mockModule.js"); - return alias; - }, {}) + alias: { + ...excludeAlias, + "q": path.resolve(__dirname, "webpack/shimQ.js") + } }, output: { filename: "bundle.js", diff --git a/webpack.mockModule.js b/webpack/mockModule.js similarity index 100% rename from webpack.mockModule.js rename to webpack/mockModule.js diff --git a/webpack/shimQ.js b/webpack/shimQ.js new file mode 100644 index 00000000..174bd4d0 --- /dev/null +++ b/webpack/shimQ.js @@ -0,0 +1,14 @@ +// Mocks the (only) user of the q promise library by ldfetch + +module.exports = { + defer: () => { + const deferred = {}; + + deferred.promise = new Promise((resolve, reject) => { + deferred.resolve = resolve; + deferred.reject = reject; + }); + + return deferred; + } +}; From 5b760640c13bc438dc2f8c0ff1213d87549cc843 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 24 Jan 2019 14:03:43 +0100 Subject: [PATCH 32/69] 1. added a new query runner which does EAT first and uses the results timespan for the next subqueries. --- src/inversify.config.ts | 4 +- .../EarliestArrivalFirstIterator.ts | 34 ++++ .../QueryRunnerEarliestArrivalFirst.test.ts | 112 +++++++++++ .../QueryRunnerEarliestArrivalFirst.ts | 174 ++++++++++++++++++ 4 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 src/query-runner/earliest-arrival-first/EarliestArrivalFirstIterator.ts create mode 100644 src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.test.ts create mode 100644 src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts diff --git a/src/inversify.config.ts b/src/inversify.config.ts index b8eb1efd..23b9a747 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -15,7 +15,6 @@ import IStopsFetcher from "./fetcher/stops/IStopsFetcher"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; import StopsFetcherLDFetch from "./fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import StopsProviderDefault from "./fetcher/stops/StopsProviderDefault"; -import IProfilesByStop from "./planner/public-transport/CSA/data-structure/stops/IProfilesByStop"; import CSAProfile from "./planner/public-transport/CSAProfile"; import IJourneyExtractor from "./planner/public-transport/IJourneyExtractor"; import IPublicTransportPlanner from "./planner/public-transport/IPublicTransportPlanner"; @@ -23,7 +22,6 @@ import JourneyExtractorProfile from "./planner/public-transport/JourneyExtractor import IRoadPlanner from "./planner/road/IRoadPlanner"; import RoadPlannerBirdsEye from "./planner/road/RoadPlannerBirdsEye"; import IReachableStopsFinder from "./planner/stops/IReachableStopsFinder"; -import ReachableStopsFinderOnlySelf from "./planner/stops/ReachableStopsFinderOnlySelf"; import ReachableStopsFinderRoadPlannerCached from "./planner/stops/ReachableStopsFinderRoadPlannerCached"; import QueryRunnerExponential from "./query-runner/exponential/QueryRunnerExponential"; import ILocationResolver from "./query-runner/ILocationResolver"; @@ -50,7 +48,7 @@ container.bind(TYPES.JourneyExtractor) container.bind(TYPES.ReachableStopsFinder) .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Initial); container.bind(TYPES.ReachableStopsFinder) - .to(ReachableStopsFinderOnlySelf).whenTargetTagged("phase", ReachableStopsSearchPhase.Transfer); + .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Transfer); container.bind(TYPES.ReachableStopsFinder) .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Final); diff --git a/src/query-runner/earliest-arrival-first/EarliestArrivalFirstIterator.ts b/src/query-runner/earliest-arrival-first/EarliestArrivalFirstIterator.ts new file mode 100644 index 00000000..ed349a65 --- /dev/null +++ b/src/query-runner/earliest-arrival-first/EarliestArrivalFirstIterator.ts @@ -0,0 +1,34 @@ +import { AsyncIterator } from "asynciterator"; +import { DurationMs } from "../../interfaces/units"; +import IResolvedQuery from "../IResolvedQuery"; + +// Inspired by IntegerIterator +export default class EarliestArrivalFirstIterator extends AsyncIterator { + private readonly baseQuery: IResolvedQuery; + private timespan: DurationMs; + private readonly initialTimespan: DurationMs; + + constructor(baseQuery: IResolvedQuery, initialTimespan: DurationMs) { + super(); + + this.baseQuery = baseQuery; + this.initialTimespan = initialTimespan; + this.timespan = initialTimespan * 2; + + this.readable = true; + } + + public read(): IResolvedQuery { + if (this.closed) { + return null; + } + + const {minimumDepartureTime} = this.baseQuery; + const maximumArrivalTime = new Date(minimumDepartureTime.getTime() + this.timespan); + + this.timespan += this.initialTimespan; + + return Object.assign({}, this.baseQuery, {maximumArrivalTime}); + } + +} diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.test.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.test.ts new file mode 100644 index 00000000..8414b1c2 --- /dev/null +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.test.ts @@ -0,0 +1,112 @@ +import "jest"; +import LDFetch from "ldfetch"; +import Context from "../../Context"; +import TravelMode from "../../enums/TravelMode"; +import ConnectionsFetcherLazy from "../../fetcher/connections/lazy/ConnectionsFetcherLazy"; +import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; +import IPath from "../../interfaces/IPath"; +import IStep from "../../interfaces/IStep"; +import CSAProfile from "../../planner/public-transport/CSAProfile"; +import JourneyExtractorProfile from "../../planner/public-transport/JourneyExtractorProfile"; +import ReachableStopsFinderBirdsEyeCached from "../../planner/stops/ReachableStopsFinderBirdsEyeCached"; +import Units from "../../util/Units"; +import LocationResolverDefault from "../LocationResolverDefault"; +import QueryRunnerEarliestArrivalFirst from "./QueryRunnerEarliestArrivalFirst"; +describe("[QueryRunnerExponential]", () => { + jest.setTimeout(100000); + + let publicTransportResult; + + const query = { + publicTransportOnly: true, + from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster + to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters + minimumDepartureTime: new Date(), + maximumTransferDuration: Units.fromHours(.5), + }; + + const createEarliestArrivalQueryRunner = () => { + const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); + + const connectionFetcher = new ConnectionsFetcherLazy(ldFetch); + connectionFetcher.setTravelMode(TravelMode.Train); + connectionFetcher.setAccessUrl("https://graph.irail.be/sncb/connections"); + + const stopsFetcher = new StopsFetcherLDFetch(ldFetch); + stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); + + const locationResolver = new LocationResolverDefault(stopsFetcher); + const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); + + const context = new Context(); + + const createJourneyExtractor = () => { + return new JourneyExtractorProfile( + locationResolver, + ); + }; + + const createPlanner = () => { + return new CSAProfile( + connectionFetcher, + locationResolver, + reachableStopsFinder, + reachableStopsFinder, + reachableStopsFinder, + createJourneyExtractor(), + ); + }; + + return new QueryRunnerEarliestArrivalFirst( + context, + connectionFetcher, + locationResolver, + createPlanner, + reachableStopsFinder, + reachableStopsFinder, + reachableStopsFinder, + ); + }; + + const result: IPath[] = []; + + beforeAll(async (done) => { + + const queryRunner = createEarliestArrivalQueryRunner(); + + publicTransportResult = await queryRunner.run(query); + + await publicTransportResult.take(3) + .on("data", (path: IPath) => { + console.log(path); + result.push(path); + }) + .on("end", () => { + done(); + }); + }); + + it("Correct departure and arrival stop", () => { + checkStops(result, query); + }); +}); + +const checkStops = (result, query) => { + expect(result).toBeDefined(); + expect(result.length).toBeGreaterThanOrEqual(1); + + for (const path of result) { + expect(path.steps).toBeDefined(); + + expect(path.steps.length).toBeGreaterThanOrEqual(1); + + let currentLocation = query.from; + path.steps.forEach((step: IStep) => { + expect(step).toBeDefined(); + expect(currentLocation).toEqual(step.startLocation.id); + currentLocation = step.stopLocation.id; + }); + + expect(query.to).toEqual(currentLocation); + } +}; diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts new file mode 100644 index 00000000..f6f48979 --- /dev/null +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts @@ -0,0 +1,174 @@ +import { AsyncIterator } from "asynciterator"; +import { inject, injectable, interfaces, tagged } from "inversify"; +import Context from "../../Context"; +import Defaults from "../../Defaults"; +import EventType from "../../enums/EventType"; +import ReachableStopsSearchPhase from "../../enums/ReachableStopsSearchPhase"; +import InvalidQueryError from "../../errors/InvalidQueryError"; +import IConnectionsProvider from "../../fetcher/connections/IConnectionsProvider"; +import ILocation from "../../interfaces/ILocation"; +import IPath from "../../interfaces/IPath"; +import IQuery from "../../interfaces/IQuery"; +import { DurationMs } from "../../interfaces/units"; +import Path from "../../planner/Path"; +import CSAEarliestArrival from "../../planner/public-transport/CSAEarliestArrival"; +import IPublicTransportPlanner from "../../planner/public-transport/IPublicTransportPlanner"; +import JourneyExtractorEarliestArrival from "../../planner/public-transport/JourneyExtractorEarliestArrival"; +import IReachableStopsFinder from "../../planner/stops/IReachableStopsFinder"; +import TYPES from "../../types"; +import FilterUniqueIterator from "../../util/iterators/FilterUniqueIterator"; +import FlatMapIterator from "../../util/iterators/FlatMapIterator"; +import Units from "../../util/Units"; +import ILocationResolver from "../ILocationResolver"; +import IQueryRunner from "../IQueryRunner"; +import IResolvedQuery from "../IResolvedQuery"; +import EarliestArrivalFirstIterator from "./EarliestArrivalFirstIterator"; + +@injectable() +export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { + private readonly locationResolver: ILocationResolver; + private readonly publicTransportPlannerFactory: interfaces.Factory; + private readonly context: Context; + private earliestArrivalPlanner: CSAEarliestArrival; + + constructor( + @inject(TYPES.Context) + context: Context, + @inject(TYPES.ConnectionsProvider) + connectionsProvider: IConnectionsProvider, + @inject(TYPES.LocationResolver) + locationResolver: ILocationResolver, + @inject(TYPES.PublicTransportPlannerFactory) + publicTransportPlannerFactory: interfaces.Factory, + @inject(TYPES.ReachableStopsFinder) + @tagged("phase", ReachableStopsSearchPhase.Initial) + initialReachableStopsFinder: IReachableStopsFinder, + @inject(TYPES.ReachableStopsFinder) + @tagged("phase", ReachableStopsSearchPhase.Transfer) + transferReachableStopsFinder: IReachableStopsFinder, + @inject(TYPES.ReachableStopsFinder) + @tagged("phase", ReachableStopsSearchPhase.Final) + finalReachableStopsFinder: IReachableStopsFinder, + ) { + this.context = context; + this.locationResolver = locationResolver; + this.publicTransportPlannerFactory = publicTransportPlannerFactory; + + const journeyExtractorEarliestArrival = new JourneyExtractorEarliestArrival( + locationResolver, + context, + ); + + this.earliestArrivalPlanner = new CSAEarliestArrival( + connectionsProvider, + locationResolver, + initialReachableStopsFinder, + transferReachableStopsFinder, + finalReachableStopsFinder, + journeyExtractorEarliestArrival, + context, + ); + } + + public async run(query: IQuery): Promise> { + const baseQuery: IResolvedQuery = await this.resolveBaseQuery(query); + + if (baseQuery.publicTransportOnly) { + + const earliestArrivalIterator = await this.earliestArrivalPlanner.plan(baseQuery); + + const path: IPath = await new Promise((resolve, reject) => { + earliestArrivalIterator + .take(1) + .on("data", (result: IPath) => { + if (!result || !result.steps || result.steps.length === 0) { + reject(); + } + + resolve(result); + }) + .on("end", () => { + reject(); + }); + }); + + const initialTimeSpan: DurationMs = path.steps[path.steps.length - 1].stopTime.getTime() - + baseQuery.minimumDepartureTime.getTime(); + + console.log(initialTimeSpan); + + const queryIterator = new EarliestArrivalFirstIterator(baseQuery, initialTimeSpan || 15 * 60 * 1000); + + const subQueryIterator = new FlatMapIterator( + queryIterator, + this.runSubquery.bind(this), + ); + + const prependedIterator = subQueryIterator.prepend([path]); + + return new FilterUniqueIterator(prependedIterator, Path.compareEquals); + + } else { + throw new InvalidQueryError("Query should have publicTransportOnly = true"); + } + } + + private async runSubquery(query: IResolvedQuery): Promise> { + this.context.emit(EventType.QueryExponential, query); + + const planner = this.publicTransportPlannerFactory() as IPublicTransportPlanner; + + return planner.plan(query); + } + + private async resolveEndpoint(endpoint: string | string[] | ILocation | ILocation[]): Promise { + + if (Array.isArray(endpoint)) { + const promises = (endpoint as Array) + .map((singleEndpoint: string | ILocation) => + this.locationResolver.resolve(singleEndpoint), + ); + + return await Promise.all(promises); + + } else { + return [await this.locationResolver.resolve(endpoint)]; + } + } + + private async resolveBaseQuery(query: IQuery): Promise { + // tslint:disable:trailing-comma + const { + from, to, + minimumWalkingSpeed, maximumWalkingSpeed, walkingSpeed, + maximumWalkingDuration, maximumWalkingDistance, + minimumTransferDuration, maximumTransferDuration, maximumTransfers, + minimumDepartureTime, + ...other + } = query; + // tslint:enable:trailing-comma + + const resolvedQuery: IResolvedQuery = Object.assign({}, other); + + resolvedQuery.minimumDepartureTime = minimumDepartureTime || new Date(); + + try { + resolvedQuery.from = await this.resolveEndpoint(from); + resolvedQuery.to = await this.resolveEndpoint(to); + + } catch (e) { + return Promise.reject(new InvalidQueryError(e)); + } + + resolvedQuery.minimumWalkingSpeed = minimumWalkingSpeed || walkingSpeed || Defaults.defaultMinimumWalkingSpeed; + resolvedQuery.maximumWalkingSpeed = maximumWalkingSpeed || walkingSpeed || Defaults.defaultMaximumWalkingSpeed; + resolvedQuery.maximumWalkingDuration = maximumWalkingDuration || + Units.toDuration(maximumWalkingDistance, resolvedQuery.minimumWalkingSpeed) || Defaults.defaultWalkingDuration; + + resolvedQuery.minimumTransferDuration = minimumTransferDuration || Defaults.defaultMinimumTransferDuration; + resolvedQuery.maximumTransferDuration = maximumTransferDuration || Defaults.defaultMaximumTransferDuration; + resolvedQuery.maximumTransfers = maximumTransfers || Defaults.defaultMaximumTransfers; + + return resolvedQuery; + } +} From 28686e2521a0e6f31350dbca76ce6e955cfdd014 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 24 Jan 2019 14:48:49 +0100 Subject: [PATCH 33/69] Undo exclusion of critical dependency --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 94c6df72..ba99769a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,7 +3,7 @@ const path = require("path"); // These modules are imported by ldfetch, but are never actually used because (right now) we only fetch jsonld files const excludeModules = [ "rdfa-processor", - "rdf-canonize", + //"rdf-canonize", "rdfxmlprocessor", "xmldom", 'n3' From ad3f8a63cfc79a9d1ea8daef78a67bdef8d82916 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 24 Jan 2019 14:53:49 +0100 Subject: [PATCH 34/69] 1. Eat emit AddedNewTransferProfle. --- .../public-transport/CSAEarliestArrival.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index d64f6b47..ff06ca51 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -20,6 +20,7 @@ import Path from "../Path"; import Step from "../Step"; import IReachableStopsFinder, { IReachableStop } from "../stops/IReachableStopsFinder"; import IProfileByStop from "./CSA/data-structure/stops/IProfileByStop"; +import ITransferProfile from "./CSA/data-structure/stops/ITransferProfile"; import IEnterConnectionByTrip from "./CSA/data-structure/trips/IEnterConnectionByTrip"; import IJourneyExtractor from "./IJourneyExtractor"; import IPublicTransportPlanner from "./IPublicTransportPlanner"; @@ -349,12 +350,19 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const reachableStopArrival = this.profilesByStop[stop.id].arrivalTime; if (reachableStopArrival > connection.arrivalTime.getTime() + duration) { - this.profilesByStop[stop.id] = { + + const transferProfile = { departureTime: connection.departureTime.getTime(), arrivalTime: connection.arrivalTime.getTime() + duration, exitConnection: connection, enterConnection: this.enterConnectionByTrip[tripId], }; + + if (this.context && this.context.listenerCount(EventType.AddedNewTransferProfile) > 0) { + this.emitTransferProfile(transferProfile); + } + + this.profilesByStop[stop.id] = transferProfile; } this.checkIfArrivalStopIsReachable(connection, reachableStop); @@ -362,7 +370,9 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { }); } catch (e) { - this.context.emitWarning(e); + if (this.context) { + this.context.emitWarning(e); + } } } @@ -397,4 +407,19 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { }; } } + + private async emitTransferProfile(transferProfile: ITransferProfile): Promise { + try { + const departureStop = await this.locationResolver.resolve(transferProfile.enterConnection.departureStop); + const arrivalStop = await this.locationResolver.resolve(transferProfile.exitConnection.arrivalStop); + + this.context.emit(EventType.AddedNewTransferProfile, { + departureStop, + arrivalStop, + }); + + } catch (e) { + this.context.emitWarning(e); + } + } } From 5d3fc9da28918c84eb009c5a41ad2a91cf93752b Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 24 Jan 2019 15:04:49 +0100 Subject: [PATCH 35/69] 1. remove logs --- .../QueryRunnerEarliestArrivalFirst.test.ts | 1 - .../earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.test.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.test.ts index 8414b1c2..2d86bcc8 100644 --- a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.test.ts +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.test.ts @@ -78,7 +78,6 @@ describe("[QueryRunnerExponential]", () => { await publicTransportResult.take(3) .on("data", (path: IPath) => { - console.log(path); result.push(path); }) .on("end", () => { diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts index f6f48979..38b9c54a 100644 --- a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts @@ -95,8 +95,6 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { const initialTimeSpan: DurationMs = path.steps[path.steps.length - 1].stopTime.getTime() - baseQuery.minimumDepartureTime.getTime(); - console.log(initialTimeSpan); - const queryIterator = new EarliestArrivalFirstIterator(baseQuery, initialTimeSpan || 15 * 60 * 1000); const subQueryIterator = new FlatMapIterator( From fb0094ee4c8ac45c1bcbc37980514ff2b2c05743 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 24 Jan 2019 16:14:59 +0100 Subject: [PATCH 36/69] 1. rename EarliestArrivalFirstIterator to LinearQueryIterator --- src/demo.ts | 2 +- src/enums/EventType.ts | 2 +- .../public-transport/CSAEarliestArrival.ts | 8 ++++++++ ...irstIterator.ts => LinearQueryIterator.ts} | 19 +++++++++++++------ .../QueryRunnerEarliestArrivalFirst.ts | 18 +++++++++--------- .../exponential/QueryRunnerExponential.ts | 2 +- 6 files changed, 33 insertions(+), 18 deletions(-) rename src/query-runner/earliest-arrival-first/{EarliestArrivalFirstIterator.ts => LinearQueryIterator.ts} (56%) diff --git a/src/demo.ts b/src/demo.ts index b5906116..b2ce1bcb 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -30,7 +30,7 @@ export default async (logResults) => { .on(EventType.Query, (Query) => { console.log("Query", Query); }) - .on(EventType.QueryExponential, (query) => { + .on(EventType.SubQuery, (query) => { const { minimumDepartureTime, maximumArrivalTime } = query; // logFetch = true; diff --git a/src/enums/EventType.ts b/src/enums/EventType.ts index f80da91f..5b7efa25 100644 --- a/src/enums/EventType.ts +++ b/src/enums/EventType.ts @@ -1,6 +1,6 @@ enum EventType { Query = "query", - QueryExponential = "query-exponential", + SubQuery = "sub-query", AbortQuery = "abort-query", InvalidQuery = "invalid-query", diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index ff06ca51..af4061c3 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -347,6 +347,14 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { reachableStops.forEach((reachableStop: IReachableStop) => { const { stop, duration } = reachableStop; + + if (!this.profilesByStop[stop.id]) { + this.profilesByStop[stop.id] = { + departureTime: Infinity, + arrivalTime: Infinity, + }; + } + const reachableStopArrival = this.profilesByStop[stop.id].arrivalTime; if (reachableStopArrival > connection.arrivalTime.getTime() + duration) { diff --git a/src/query-runner/earliest-arrival-first/EarliestArrivalFirstIterator.ts b/src/query-runner/earliest-arrival-first/LinearQueryIterator.ts similarity index 56% rename from src/query-runner/earliest-arrival-first/EarliestArrivalFirstIterator.ts rename to src/query-runner/earliest-arrival-first/LinearQueryIterator.ts index ed349a65..b951f732 100644 --- a/src/query-runner/earliest-arrival-first/EarliestArrivalFirstIterator.ts +++ b/src/query-runner/earliest-arrival-first/LinearQueryIterator.ts @@ -3,17 +3,22 @@ import { DurationMs } from "../../interfaces/units"; import IResolvedQuery from "../IResolvedQuery"; // Inspired by IntegerIterator -export default class EarliestArrivalFirstIterator extends AsyncIterator { +export default class LinearQueryIterator extends AsyncIterator { private readonly baseQuery: IResolvedQuery; private timespan: DurationMs; - private readonly initialTimespan: DurationMs; + private index: number; + private readonly a: DurationMs; + private readonly b: DurationMs; - constructor(baseQuery: IResolvedQuery, initialTimespan: DurationMs) { + constructor(baseQuery: IResolvedQuery, a: DurationMs, b: DurationMs) { super(); this.baseQuery = baseQuery; - this.initialTimespan = initialTimespan; - this.timespan = initialTimespan * 2; + this.index = 1; + this.a = a; + this.b = b; + + this.timespan = a * this.index + b; this.readable = true; } @@ -26,8 +31,10 @@ export default class EarliestArrivalFirstIterator extends AsyncIterator { - if (!result || !result.steps || result.steps.length === 0) { - reject(); - } - resolve(result); }) .on("end", () => { @@ -92,10 +88,14 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { }); }); - const initialTimeSpan: DurationMs = path.steps[path.steps.length - 1].stopTime.getTime() - - baseQuery.minimumDepartureTime.getTime(); + let initialTimeSpan: DurationMs = 15 * 60 * 1000; + + if (path && path.steps && path.steps.length > 0) { + initialTimeSpan = path.steps[path.steps.length - 1].stopTime.getTime() - + baseQuery.minimumDepartureTime.getTime(); + } - const queryIterator = new EarliestArrivalFirstIterator(baseQuery, initialTimeSpan || 15 * 60 * 1000); + const queryIterator = new LinearQueryIterator(baseQuery, initialTimeSpan / 2, initialTimeSpan); const subQueryIterator = new FlatMapIterator( queryIterator, @@ -112,7 +112,7 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { } private async runSubquery(query: IResolvedQuery): Promise> { - this.context.emit(EventType.QueryExponential, query); + this.context.emit(EventType.SubQuery, query); const planner = this.publicTransportPlannerFactory() as IPublicTransportPlanner; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index 42d6ff04..bc961c90 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -70,7 +70,7 @@ export default class QueryRunnerExponential implements IQueryRunner { private async runSubquery(query: IResolvedQuery): Promise> { // TODO investigate if publicTransportPlanner can be reused or reuse some of its aggregated data - this.context.emit(EventType.QueryExponential, query); + this.context.emit(EventType.SubQuery, query); const planner = this.publicTransportPlannerFactory() as IPublicTransportPlanner; From 9417cb587944710affd6f3ae8bb14068f8c823ea Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 24 Jan 2019 16:33:55 +0100 Subject: [PATCH 37/69] Copy example from codepen --- example/css/style.css | 82 +++++++++++ example/index.html | 23 +++ example/js/index.js | 335 ++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 4 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 example/css/style.css create mode 100644 example/index.html create mode 100644 example/js/index.js diff --git a/example/css/style.css b/example/css/style.css new file mode 100644 index 00000000..f3af5501 --- /dev/null +++ b/example/css/style.css @@ -0,0 +1,82 @@ +html, +body { + margin: 0; + height: 100%; + width: 100%; +} + +#mapid { + height: 100%; +} + +#results { + position: absolute; + left: 10px; + bottom: 10px; + border-radius: 4px; + display: flex; + flex-direction: column; + font-family: sans-serif; + max-height: 500px; + z-index: 500; + overflow-y: scroll; + width: 500px; + display: flex; + flex-direction: column; +} + +.path { + margin: 10px; + background: rgba(255,255,255,.8); + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 4px 10px -2px rgba(0, 0, 0, 0.2); + flex-wrap: wrap; +} + +.step { + display: flex; + flex-direction: row; + margin: 10px; +} + +.travelMode { + width: 45px; + background-size: 20px 20px; + background-repeat: no-repeat; +} + +.details { +} + +.travelMode.walking { + background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ3YWxraW5nIiBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtd2Fsa2luZyBmYS13LTEwIiByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDMyMCA1MTIiPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTIwOCA5NmMyNi41IDAgNDgtMjEuNSA0OC00OFMyMzQuNSAwIDIwOCAwcy00OCAyMS41LTQ4IDQ4IDIxLjUgNDggNDggNDh6bTk0LjUgMTQ5LjFsLTIzLjMtMTEuOC05LjctMjkuNGMtMTQuNy00NC42LTU1LjctNzUuOC0xMDIuMi03NS45LTM2LS4xLTU1LjkgMTAuMS05My4zIDI1LjItMjEuNiA4LjctMzkuMyAyNS4yLTQ5LjcgNDYuMkwxNy42IDIxM2MtNy44IDE1LjgtMS41IDM1IDE0LjIgNDIuOSAxNS42IDcuOSAzNC42IDEuNSA0Mi41LTE0LjNMODEgMjI4YzMuNS03IDkuMy0xMi41IDE2LjUtMTUuNGwyNi44LTEwLjgtMTUuMiA2MC43Yy01LjIgMjAuOC40IDQyLjkgMTQuOSA1OC44bDU5LjkgNjUuNGM3LjIgNy45IDEyLjMgMTcuNCAxNC45IDI3LjdsMTguMyA3My4zYzQuMyAxNy4xIDIxLjcgMjcuNiAzOC44IDIzLjMgMTcuMS00LjMgMjcuNi0yMS43IDIzLjMtMzguOGwtMjIuMi04OWMtMi42LTEwLjMtNy43LTE5LjktMTQuOS0yNy43bC00NS41LTQ5LjcgMTcuMi02OC43IDUuNSAxNi41YzUuMyAxNi4xIDE2LjcgMjkuNCAzMS43IDM3bDIzLjMgMTEuOGMxNS42IDcuOSAzNC42IDEuNSA0Mi41LTE0LjMgNy43LTE1LjcgMS40LTM1LjEtMTQuMy00M3pNNzMuNiAzODUuOGMtMy4yIDguMS04IDE1LjQtMTQuMiAyMS41bC01MCA1MC4xYy0xMi41IDEyLjUtMTIuNSAzMi44IDAgNDUuM3MzMi43IDEyLjUgNDUuMiAwbDU5LjQtNTkuNGM2LjEtNi4xIDEwLjktMTMuNCAxNC4yLTIxLjVsMTMuNS0zMy44Yy01NS4zLTYwLjMtMzguNy00MS44LTQ3LjQtNTMuN2wtMjAuNyA1MS41eiI+PC9wYXRoPjwvc3ZnPg==); +} + +.travelMode.train { + background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ0cmFpbiIgY2xhc3M9InN2Zy1pbmxpbmUtLWZhIGZhLXRyYWluIGZhLXctMTQiIHJvbGU9ImltZyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDQ4IDUxMiI+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNDQ4IDk2djI1NmMwIDUxLjgxNS02MS42MjQgOTYtMTMwLjAyMiA5Nmw2Mi45OCA0OS43MjFDMzg2LjkwNSA1MDIuNDE3IDM4My41NjIgNTEyIDM3NiA1MTJINzJjLTcuNTc4IDAtMTAuODkyLTkuNTk0LTQuOTU3LTE0LjI3OUwxMzAuMDIyIDQ0OEM2MS44MiA0NDggMCA0MDMuOTU0IDAgMzUyVjk2QzAgNDIuOTgxIDY0IDAgMTI4IDBoMTkyYzY1IDAgMTI4IDQyLjk4MSAxMjggOTZ6bS00OCAxMzZWMTIwYzAtMTMuMjU1LTEwLjc0NS0yNC0yNC0yNEg3MmMtMTMuMjU1IDAtMjQgMTAuNzQ1LTI0IDI0djExMmMwIDEzLjI1NSAxMC43NDUgMjQgMjQgMjRoMzA0YzEzLjI1NSAwIDI0LTEwLjc0NSAyNC0yNHptLTE3NiA2NGMtMzAuOTI4IDAtNTYgMjUuMDcyLTU2IDU2czI1LjA3MiA1NiA1NiA1NiA1Ni0yNS4wNzIgNTYtNTYtMjUuMDcyLTU2LTU2LTU2eiI+PC9wYXRoPjwvc3ZnPg==); +} + +.duration { + margin-bottom: 5px; + margin-top: 5px; +} + +.enterConnectionId, +.exitConnectionId { + opacity: 0.5; +} + +#actions { + position: absolute; + top: 10px; + right: 10px; + z-index: 500; +} + +#actions > button { + border-radius: 100px; + background: rgba(255,255,255,.8); + font-size: 2em; + padding: .25em 1em; +} \ No newline at end of file diff --git a/example/index.html b/example/index.html new file mode 100644 index 00000000..71b442b0 --- /dev/null +++ b/example/index.html @@ -0,0 +1,23 @@ + + + + + Map + + + + + +
+
+
+ +
+ + + + diff --git a/example/js/index.js b/example/js/index.js new file mode 100644 index 00000000..564ba783 --- /dev/null +++ b/example/js/index.js @@ -0,0 +1,335 @@ +const map = L.map("mapid").setView([51.050043, 3.719926], 10); + +L.tileLayer( + "https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", + { + attribution: + 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', + maxZoom: 18, + id: "mapbox.streets", + accessToken: + "pk.eyJ1IjoibWF4aW10bWFydGluIiwiYSI6ImNqcHdqbjdhaDAzYzc0Mm04eDFhamkzenMifQ.0uNbKJ2WHATkKBBSADuhyQ" + } +).addTo(map); + +const planner = new Planner(); + +planner.prefetchStops(); +planner.prefetchConnections(); + +let plannerResult; +const resetButton = document.querySelector("#reset"); +const results = document.querySelector("#results"); + +let lines = []; +let polyLines = []; +let resultObjects = []; +let query = []; +let allStops = []; + +const removeLines = () => { + for (const line of polyLines) { + line.remove(); + } + + polyLines = []; +}; + +const removeResultObjects = () => { + for (const obj of resultObjects) { + obj.remove(); + } + + resultObjects = []; +}; + +resetButton.onclick = e => { + removeLines(); + removeResultObjects(); + query = []; + results.innerHTML = ""; + + for (const stop of allStops) { + stop.addTo(map); + } + + if (plannerResult) { + plannerResult.close(); + } +}; + +planner.getAllStops().then(stops => { + for (const stop of stops) { + if (stop["http://semweb.mmlab.be/ns/stoptimes#avgStopTimes"] > 100) { + const marker = L.marker([stop.latitude, stop.longitude]).addTo(map); + + marker.bindPopup(stop.name); + + allStops.push(marker); + + marker.on("click", e => { + selectRoute(e, stop.id); + }); + } + } +}); + +planner + .on("query", query => { + console.log("Query", query); + }) + .on("query-exponential", query => { + const { minimumDepartureTime, maximumArrivalTime } = query; + + console.log( + "[Subquery]", + minimumDepartureTime, + maximumArrivalTime, + maximumArrivalTime - minimumDepartureTime + ); + + removeLines(); + }) + .on("initial-reachable-stops", reachableStops => { + console.log("initial", reachableStops); + reachableStops.map(({ stop }) => { + const startMarker = L.marker([stop.latitude, stop.longitude]).addTo(map); + + startMarker.bindPopup("initialreachable: " + stop.name); + + resultObjects.push(startMarker); + }); + }) + .on("final-reachable-stops", reachableStops => { + console.log("final", reachableStops); + + reachableStops.map(({ stop }) => { + const startMarker = L.marker([stop.latitude, stop.longitude]).addTo(map); + + startMarker.bindPopup("finalreachable: " + stop.name); + + resultObjects.push(startMarker); + }); + }) + .on("added-new-transfer-profile", ({ departureStop, arrivalStop, amountOfTransfers }) => { + + const newLine = [ + [departureStop.latitude, departureStop.longitude], + [arrivalStop.latitude, arrivalStop.longitude] + ]; + + let lineExists = lines.length > 0 && lines + .some((line) => + line[0][0] === newLine[0][0] + && line[0][1] === newLine[0][1] + && line[1][0] === newLine[1][0] + && line[1][1] === newLine[1][1] + ); + + if (!lineExists) { + const polyline = new L.Polyline(newLine, { + color: "#000", + weight: 1, + smoothFactor: 1, + opacity: 0.5, + dashArray: "10 10" + }).addTo(map); + + lines.push(newLine); + polyLines.push(polyline); + } + } + ); + +function onMapClick(e) { + selectRoute(e); +} + +function selectRoute(e, id) { + if (query.length === 2) { + return; + } + + let marker = L.marker(e.latlng).addTo(map); + + resultObjects.push(marker); + + if (query.length < 2) { + const { lat, lng } = e.latlng; + + let item = { + latitude: lat, + longitude: lng + }; + + if (id) { + item.id = id; + } + + query.push(item); + } + + if (query.length === 2) { + runQuery(query); + + for (const marker of allStops) { + marker.remove(); + } + } +} + +map.on("click", onMapClick); + +function getRandomColor() { + var letters = "0123456789ABCDEF"; + var color = "#"; + for (var i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +} + +function runQuery(query) { + console.log(query); + + const departureCircle = L.circle([query[0].latitude, query[0].longitude], { + color: "limegreen", + fillColor: "limegreen", + fillOpacity: 0.5, + radius: 200 + }).addTo(map); + + const arrivalCircle = L.circle([query[1].latitude, query[1].longitude], { + color: "red", + fillColor: "red", + fillOpacity: 0.5, + radius: 200 + }).addTo(map); + + resultObjects.push(departureCircle, arrivalCircle); + + planner + .query({ + publicTransportOnly: true, + from: query[0], // Brussels North + to: query[1], // Ghent-Sint-Pieters + minimumDepartureTime: new Date(), + maximumWalkingDistance: 200, + maximumTransferDuration: 30 * 60 * 1000, // 30 minutes + minimumWalkingSpeed: 3 + }) + .then(publicTransportResult => { + plannerResult = publicTransportResult; + publicTransportResult.take(4).on("data", path => { + console.log("path", path); + + const color = getRandomColor(); + + const pathElement = document.createElement("div"); + pathElement.className = "path"; + + path.steps.forEach(step => { + const stepElement = document.createElement("div"); + stepElement.className = "step"; + + const travelMode = document.createElement("div"); + travelMode.className = "travelMode " + step.travelMode; + stepElement.appendChild(travelMode); + + const details = document.createElement("div"); + details.className = "details"; + stepElement.appendChild(details); + + const startLocation = document.createElement("div"); + startLocation.className = "startLocation"; + startLocation.innerHTML = + "Start location: " + step.startLocation.name; + details.appendChild(startLocation); + + if (step.startTime) { + const startTime = document.createElement("div"); + startTime.className = "startTime"; + startTime.innerHTML = step.startTime; + details.appendChild(startTime); + } + + if (step.enterConnectionId) { + const enterConnectionId = document.createElement("div"); + enterConnectionId.className = "enterConnectionId"; + enterConnectionId.innerHTML = + "Enter connection: " + step.enterConnectionId; + details.appendChild(enterConnectionId); + } + + if (step.duration) { + const duration = document.createElement("div"); + duration.className = "duration"; + duration.innerHTML = + "Duration: minimum " + + step.duration.minimum / (60 * 1000) + + "min"; + details.appendChild(duration); + } + + const stopLocation = document.createElement("div"); + stopLocation.className = "stopLocation"; + stopLocation.innerHTML = "Stop location: " + step.stopLocation.name; + details.appendChild(stopLocation); + + if (step.stopTime) { + const stopTime = document.createElement("div"); + stopTime.className = "stopTime"; + stopTime.innerHTML = step.stopTime; + details.appendChild(stopTime); + } + + if (step.exitConnectionId) { + const exitConnectionId = document.createElement("div"); + exitConnectionId.className = "exitConnectionId"; + exitConnectionId.innerHTML = + "Exit connection: " + step.exitConnectionId; + details.appendChild(exitConnectionId); + } + + pathElement.style.borderLeft = "5px solid " + color; + + pathElement.appendChild(stepElement); + }); + + results.appendChild(pathElement); + + path.steps.forEach(step => { + const { startLocation, stopLocation, travelMode } = step; + + const startMarker = L.marker([ + startLocation.latitude, + startLocation.longitude + ]).addTo(map); + + startMarker.bindPopup(startLocation.name); + + const stopMarker = L.marker([ + stopLocation.latitude, + stopLocation.longitude + ]).addTo(map); + + stopMarker.bindPopup(stopLocation.name); + const line = [ + [startLocation.latitude, startLocation.longitude], + [stopLocation.latitude, stopLocation.longitude] + ]; + + const polyline = new L.Polyline(line, { + color, + weight: 5, + smoothFactor: 1, + opacity: 0.7, + dashArray: travelMode === "walking" ? "8 8" : null + }).addTo(map); + + resultObjects.push(startMarker, stopMarker, polyline); + }); + }); + }) + .catch((error) => console.error(error)); +} diff --git a/package.json b/package.json index 22fea393..a5653f5c 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "webpack": "webpack --config webpack.config.js --mode=production", "webpack-stats": "npm run webpack -- --display-modules --json > stats.json", "typedoc": "typedoc --options typedoc.config.js", - "doc": "npm run typedoc & npm run browser && cp dist/bundle.js docs/js/planner-latest.js && cp dist/bundle.js.map docs/js/planner-latest.js.map" + "doc": "npm run typedoc && npm run browser && cp dist/bundle.js docs/js/planner-latest.js && cp dist/bundle.js.map docs/js/planner-latest.js.map", + "example": "npm run browser && cp dist/bundle.js example/js/bundle.js && cp dist/bundle.js.map example/js/bundle.js.map" }, "dependencies": { "asynciterator": "^2.0.1", From 5cff978a7cd19f5a171fddfa3f6eade4d70aaefc Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 24 Jan 2019 16:56:14 +0100 Subject: [PATCH 38/69] 1. remove logs --- src/query-runner/earliest-arrival-first/LinearQueryIterator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/query-runner/earliest-arrival-first/LinearQueryIterator.ts b/src/query-runner/earliest-arrival-first/LinearQueryIterator.ts index b951f732..3f6abd73 100644 --- a/src/query-runner/earliest-arrival-first/LinearQueryIterator.ts +++ b/src/query-runner/earliest-arrival-first/LinearQueryIterator.ts @@ -34,7 +34,6 @@ export default class LinearQueryIterator extends AsyncIterator { this.index++; this.timespan = this.a * this.index + this.b; - console.log(Object.assign({}, this.baseQuery, {maximumArrivalTime})); return Object.assign({}, this.baseQuery, {maximumArrivalTime}); } From 4e2585d85ae8090cff124e486e157dc7171fd808 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 24 Jan 2019 17:05:55 +0100 Subject: [PATCH 39/69] Added ConnectionPrefetch event + visualisation for example --- example/css/style.css | 21 ++++++++++++++- example/index.html | 1 + example/js/index.js | 19 ++++++++++++- src/enums/EventType.ts | 2 ++ .../prefetch/ConnectionsProviderPrefetch.ts | 27 +++++++++++++++++++ 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/example/css/style.css b/example/css/style.css index f3af5501..988ef03e 100644 --- a/example/css/style.css +++ b/example/css/style.css @@ -79,4 +79,23 @@ body { background: rgba(255,255,255,.8); font-size: 2em; padding: .25em 1em; -} \ No newline at end of file +} + +#prefetch { + position: absolute; + top: 10px; + left: 54px; + z-index: 500; + height: 18px; + border-radius: 30px; + background: #3556a9; + color: #fff; + font-family: sans-serif; + padding: 3px 5px; +} + +#prefetch::after { + content: attr(data-last); + color: #fff; + float: right; +} diff --git a/example/index.html b/example/index.html index 71b442b0..ab596309 100644 --- a/example/index.html +++ b/example/index.html @@ -17,6 +17,7 @@
+
diff --git a/example/js/index.js b/example/js/index.js index 564ba783..b69c0028 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -20,6 +20,7 @@ planner.prefetchConnections(); let plannerResult; const resetButton = document.querySelector("#reset"); const results = document.querySelector("#results"); +const prefetchBar = document.querySelector("#prefetch"); let lines = []; let polyLines = []; @@ -27,6 +28,8 @@ let resultObjects = []; let query = []; let allStops = []; +let firstPrefetch; + const removeLines = () => { for (const line of polyLines) { line.remove(); @@ -139,7 +142,21 @@ planner polyLines.push(polyline); } } - ); + ) + .on("connection-prefetch", (departureTime) => { + if (!firstPrefetch) { + firstPrefetch = departureTime; + + prefetchBar.innerHTML = departureTime.toLocaleTimeString(); + + } else { + const pxPerMs = .00005; + const width = (departureTime.valueOf() - firstPrefetch.valueOf()) * pxPerMs; + + prefetchBar.style.width = `${width}px`; + prefetchBar.setAttribute('data-last', departureTime.toLocaleTimeString()); + } + }); function onMapClick(e) { selectRoute(e); diff --git a/src/enums/EventType.ts b/src/enums/EventType.ts index f80da91f..2cf58def 100644 --- a/src/enums/EventType.ts +++ b/src/enums/EventType.ts @@ -8,6 +8,8 @@ enum EventType { Warning = "warning", + ConnectionPrefetch = "connection-prefetch", + ConnectionScan = "connection-scan", InitialReachableStops = "initial-reachable-stops", FinalReachableStops = "final-reachable-stops", diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index 3640a08d..74fbf488 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -2,7 +2,11 @@ import { AsyncIterator } from "asynciterator"; import { PromiseProxyIterator } from "asynciterator-promiseproxy"; import { inject, injectable } from "inversify"; import Catalog from "../../../Catalog"; +import Context from "../../../Context"; +import EventType from "../../../enums/EventType"; +import { DurationMs } from "../../../interfaces/units"; import TYPES, { ConnectionsFetcherFactory } from "../../../types"; +import Units from "../../../util/Units"; import IConnection from "../IConnection"; import IConnectionsFetcher from "../IConnectionsFetcher"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; @@ -21,19 +25,23 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider private static MAX_CONNECTIONS = 20000; + private readonly context: Context; private readonly connectionsFetcher: IConnectionsFetcher; private readonly connectionsStore: ConnectionsStore; private startedPrefetching: boolean; private connectionsIterator: AsyncIterator; private connectionsIteratorOptions: IConnectionsIteratorOptions; + private lastReportedDepartureTime: Date; constructor( @inject(TYPES.ConnectionsFetcherFactory) connectionsFetcherFactory: ConnectionsFetcherFactory, @inject(TYPES.Catalog) catalog: Catalog, + @inject(TYPES.Context) context: Context, ) { const { accessUrl, travelMode } = catalog.connectionsSourceConfigs[0]; + this.context = context; this.connectionsFetcher = connectionsFetcherFactory(accessUrl, travelMode); this.connectionsStore = new ConnectionsStore(); } @@ -51,10 +59,29 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.connectionsFetcher.setIteratorOptions(options); this.connectionsIterator = this.connectionsFetcher.createIterator(); + const reportingThreshold = Units.fromHours(.25); + this.connectionsIterator .take(ConnectionsProviderPrefetch.MAX_CONNECTIONS) .on("end", () => this.connectionsStore.finish()) .each((connection: IConnection) => { + if (this.context) { + + if (!this.lastReportedDepartureTime) { + this.lastReportedDepartureTime = connection.departureTime; + + this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); + + } else if ( + connection.departureTime.valueOf() - this.lastReportedDepartureTime.valueOf() > reportingThreshold + ) { + this.lastReportedDepartureTime = connection.departureTime; + + this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); + } + + } + this.connectionsStore.append(connection); }); }, 0); From 4947fd02d7d2231e758926df56c26e55e57a34f0 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 25 Jan 2019 09:34:21 +0100 Subject: [PATCH 40/69] Dump --- example/css/style.css | 1 + example/js/index.js | 57 +++++++++++++++++++++++-------------------- webpack.config.js | 2 +- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/example/css/style.css b/example/css/style.css index 988ef03e..360b8afb 100644 --- a/example/css/style.css +++ b/example/css/style.css @@ -92,6 +92,7 @@ body { color: #fff; font-family: sans-serif; padding: 3px 5px; + transition: 50ms width ease-in-out; } #prefetch::after { diff --git a/example/js/index.js b/example/js/index.js index b69c0028..dc962f7d 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -4,7 +4,7 @@ L.tileLayer( "https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", { attribution: - 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', + "Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox", maxZoom: 18, id: "mapbox.streets", accessToken: @@ -116,31 +116,34 @@ planner }) .on("added-new-transfer-profile", ({ departureStop, arrivalStop, amountOfTransfers }) => { - const newLine = [ - [departureStop.latitude, departureStop.longitude], - [arrivalStop.latitude, arrivalStop.longitude] - ]; - - let lineExists = lines.length > 0 && lines - .some((line) => - line[0][0] === newLine[0][0] - && line[0][1] === newLine[0][1] - && line[1][0] === newLine[1][0] - && line[1][1] === newLine[1][1] - ); - - if (!lineExists) { - const polyline = new L.Polyline(newLine, { - color: "#000", - weight: 1, - smoothFactor: 1, - opacity: 0.5, - dashArray: "10 10" - }).addTo(map); - - lines.push(newLine); - polyLines.push(polyline); - } + requestAnimationFrame(() => { + const newLine = [ + [departureStop.latitude, departureStop.longitude], + [arrivalStop.latitude, arrivalStop.longitude] + ]; + + let lineExists = lines.length > 0 && lines + .some((line) => + line[0][0] === newLine[0][0] + && line[0][1] === newLine[0][1] + && line[1][0] === newLine[1][0] + && line[1][1] === newLine[1][1] + ); + + if (!lineExists) { + const polyline = new L.Polyline(newLine, { + color: "#000", + weight: 1, + smoothFactor: 1, + opacity: 0.5, + dashArray: "10 10" + }).addTo(map); + + lines.push(newLine); + polyLines.push(polyline); + } + }); + } ) .on("connection-prefetch", (departureTime) => { @@ -154,7 +157,7 @@ planner const width = (departureTime.valueOf() - firstPrefetch.valueOf()) * pxPerMs; prefetchBar.style.width = `${width}px`; - prefetchBar.setAttribute('data-last', departureTime.toLocaleTimeString()); + prefetchBar.setAttribute("data-last", departureTime.toLocaleTimeString()); } }); diff --git a/webpack.config.js b/webpack.config.js index ba99769a..0a356e30 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,7 +16,7 @@ const excludeAlias = excludeModules.reduce((alias, moduleName) => { module.exports = { entry: "./src/index.ts", - devtool: "cheap-module-source-map", + devtool: "source-map",//"cheap-module-source-map", module: { rules: [ { From 422a6652782e5e74f8109de6813c0c3757f13833 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 25 Jan 2019 09:41:01 +0100 Subject: [PATCH 41/69] Update tsc target to es2017 --- src/query-runner/exponential/ExponentialQueryIterator.ts | 1 - tsconfig.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/query-runner/exponential/ExponentialQueryIterator.ts b/src/query-runner/exponential/ExponentialQueryIterator.ts index 1e2d179b..a5525a2b 100644 --- a/src/query-runner/exponential/ExponentialQueryIterator.ts +++ b/src/query-runner/exponential/ExponentialQueryIterator.ts @@ -31,5 +31,4 @@ export default class ExponentialQueryIterator extends AsyncIterator Date: Fri, 25 Jan 2019 10:06:00 +0100 Subject: [PATCH 42/69] Renamed IViewPromise to IDeferredBackwardView --- .../connections/prefetch/ConnectionsStore.ts | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 6e03502c..d84a211f 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -3,8 +3,7 @@ import BinarySearch from "../../../util/BinarySearch"; import IConnection from "../IConnection"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; -interface IViewPromise { - backward: boolean; +interface IDeferredBackwardView { lowerBoundDate: Date; upperBoundDate: Date; resolve: (iterator: AsyncIterator) => void; @@ -21,13 +20,13 @@ interface IViewPromise { export default class ConnectionsStore { private readonly store: IConnection[]; private readonly binarySearch: BinarySearch; - private viewPromises: IViewPromise[]; + private deferredBackwardViews: IDeferredBackwardView[]; private hasFinished: boolean; constructor() { this.store = []; this.binarySearch = new BinarySearch(this.store, (connection) => connection.departureTime.valueOf()); - this.viewPromises = []; + this.deferredBackwardViews = []; this.hasFinished = false; } @@ -40,13 +39,13 @@ export default class ConnectionsStore { public append(connection: IConnection) { this.store.push(connection); - // Check if any view promises are satisfied - if (this.viewPromises.length) { - this.viewPromises = this.viewPromises - .filter(({ backward, lowerBoundDate, upperBoundDate, resolve }) => { + // Check if any deferred backward view are satisfied + if (this.deferredBackwardViews.length) { + this.deferredBackwardViews = this.deferredBackwardViews + .filter(({ lowerBoundDate, upperBoundDate, resolve }) => { if (connection.departureTime > upperBoundDate) { - const iteratorView = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); + const iteratorView = this.getIteratorView(true, lowerBoundDate, upperBoundDate); resolve(iteratorView); return false; @@ -59,7 +58,7 @@ export default class ConnectionsStore { /** * Signals that the store will no longer be appended. - * [[getIterator]] never returns a view promise after this, because those would never get resolved + * [[getIterator]] never returns a deferred backward view after this, because those would never get resolved */ public finish(): void { this.hasFinished = true; @@ -102,9 +101,9 @@ export default class ConnectionsStore { // If the store is still empty or the latest departure time isn't later than the upperBoundDate, // then return a promise if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { - const { viewPromise, promise } = this.createViewPromise(backward, lowerBoundDate, upperBoundDate); + const { deferred, promise } = this.createDeferredBackwardView(lowerBoundDate, upperBoundDate); - this.viewPromises.push(viewPromise); + this.deferredBackwardViews.push(deferred); return promise; } @@ -113,21 +112,20 @@ export default class ConnectionsStore { return Promise.resolve(this.getIteratorView(backward, lowerBoundDate, upperBoundDate)); } - private createViewPromise(backward, lowerBoundDate, upperBoundDate): - { viewPromise: IViewPromise, promise: Promise> } { + private createDeferredBackwardView(lowerBoundDate, upperBoundDate): + { deferred: IDeferredBackwardView, promise: Promise> } { - const viewPromise: Partial = { - backward, + const deferred: Partial = { lowerBoundDate, upperBoundDate, }; const promise = new Promise>((resolve) => { - viewPromise.resolve = resolve; + deferred.resolve = resolve; }); return { - viewPromise: viewPromise as IViewPromise, + deferred: deferred as IDeferredBackwardView, promise, }; } From c4e7228d1eb09a6b956347a5959df3d603c746fc Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 25 Jan 2019 13:40:50 +0100 Subject: [PATCH 43/69] 1. Implement ExpandingIterator for growing forward iterators 2. Add asynciterator ts definitions 3. Add visualisation for getting iterator views from connections store --- example/css/style.css | 12 +- example/index.html | 4 +- example/js/index.js | 40 +++- package-lock.json | 15 -- package.json | 1 - src/asynciterator.d.ts | 194 ++++++++++++++++++ src/enums/EventType.ts | 1 + .../prefetch/ConnectionsProviderPrefetch.ts | 7 +- .../prefetch/ConnectionsStore.test.ts | 88 +++++--- .../connections/prefetch/ConnectionsStore.ts | 141 ++++++++++--- .../prefetch/ExpandingIterator.test.ts | 98 +++++++++ .../connections/prefetch/ExpandingIterator.ts | 31 +++ tslint.json | 8 +- 13 files changed, 558 insertions(+), 82 deletions(-) create mode 100755 src/asynciterator.d.ts create mode 100644 src/fetcher/connections/prefetch/ExpandingIterator.test.ts create mode 100644 src/fetcher/connections/prefetch/ExpandingIterator.ts diff --git a/example/css/style.css b/example/css/style.css index 360b8afb..9f712f27 100644 --- a/example/css/style.css +++ b/example/css/style.css @@ -86,7 +86,11 @@ body { top: 10px; left: 54px; z-index: 500; - height: 18px; +} + +.prefetch-view { + height: 6px; + margin-bottom: 3px; border-radius: 30px; background: #3556a9; color: #fff; @@ -95,8 +99,12 @@ body { transition: 50ms width ease-in-out; } -#prefetch::after { +.prefetch-view::after { content: attr(data-last); color: #fff; float: right; } + +#prefetch-bar { + height: 18px; +} diff --git a/example/index.html b/example/index.html index ab596309..12ee0426 100644 --- a/example/index.html +++ b/example/index.html @@ -17,7 +17,9 @@
-
+
+
+
diff --git a/example/js/index.js b/example/js/index.js index dc962f7d..f8d44b52 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -20,13 +20,15 @@ planner.prefetchConnections(); let plannerResult; const resetButton = document.querySelector("#reset"); const results = document.querySelector("#results"); -const prefetchBar = document.querySelector("#prefetch"); +const prefetchWrapper = document.querySelector("#prefetch"); +const prefetchBar = document.querySelector("#prefetch-bar"); let lines = []; let polyLines = []; let resultObjects = []; let query = []; let allStops = []; +let prefetchViews = []; let firstPrefetch; @@ -61,6 +63,11 @@ resetButton.onclick = e => { } }; +const getPrefetchViewWidth = (start, stop) => { + const pxPerMs = .00005; + return (stop.valueOf() - start.valueOf()) * pxPerMs; +}; + planner.getAllStops().then(stops => { for (const stop of stops) { if (stop["http://semweb.mmlab.be/ns/stoptimes#avgStopTimes"] > 100) { @@ -153,12 +160,39 @@ planner prefetchBar.innerHTML = departureTime.toLocaleTimeString(); } else { - const pxPerMs = .00005; - const width = (departureTime.valueOf() - firstPrefetch.valueOf()) * pxPerMs; + const width = getPrefetchViewWidth(firstPrefetch, departureTime); prefetchBar.style.width = `${width}px`; prefetchBar.setAttribute("data-last", departureTime.toLocaleTimeString()); } + }) + .on("connection-iterator-view", (lowerBound, upperBound, completed) => { + + if (!completed) { + const width = getPrefetchViewWidth(lowerBound, upperBound); + const offset = getPrefetchViewWidth(firstPrefetch, lowerBound); + + const prefetchView = document.createElement('div'); + prefetchView.className = 'prefetch-view'; + prefetchView.style.marginLeft = `${offset}px`; + prefetchView.style.width = `${width}px`; + prefetchView.style.backgroundColor = 'red'; + + prefetchWrapper.appendChild(prefetchView); + prefetchViews.push({lowerBound, upperBound, elem: prefetchView}); + + } else { + const {elem} = prefetchViews + .find((view) => view.lowerBound === lowerBound && view.upperBound === upperBound); + + if (!elem) { + console.warn('Wut'); + return; + } + + elem.style.backgroundColor = 'limegreen'; + } + }); function onMapClick(e) { diff --git a/package-lock.json b/package-lock.json index 5c1c2b47..86eb6fa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,16 +50,6 @@ } } }, - "@types/asynciterator": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/asynciterator/-/asynciterator-1.1.1.tgz", - "integrity": "sha512-KgjXxTtWbMW7UA4oZauIfg2rCl5+5LbsoVF5DwwpXVQxzSbez2PQ9NAlbJlBjIHqKLOpWcdjqL+wyrNepvTZOg==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/node": "*" - } - }, "@types/events": { "version": "1.2.0", "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", @@ -4842,11 +4832,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "n3": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/n3/-/n3-0.11.2.tgz", - "integrity": "sha512-ICSiOmFLbZ4gI35+4H3e2vYGHDC944WZkCa1iVNRAx/mRZESEevQNFhfHaui/lhqynoZYvBVDNjM/2Tfd3TICQ==" - }, "nan": { "version": "2.11.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", diff --git a/package.json b/package.json index a5653f5c..a0a03db2 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "lint" ], "devDependencies": { - "@types/asynciterator": "^1.1.1", "@types/haversine": "^1.1.0", "@types/jest": "^23.3.7", "@types/rdf-js": "^1.0.1", diff --git a/src/asynciterator.d.ts b/src/asynciterator.d.ts new file mode 100755 index 00000000..92df5ced --- /dev/null +++ b/src/asynciterator.d.ts @@ -0,0 +1,194 @@ +/* tslint:disable */ + +// Type definitions for asynciterator 2.0.1 + +declare module "asynciterator" { + + import { EventEmitter } from "events"; + + export abstract class AsyncIterator extends EventEmitter { + static STATES: ['INIT', 'OPEN', 'CLOSING', 'CLOSED', 'ENDED']; + static INIT: 0; + static OPEN: 1; + static CLOSING: 2; + static CLOSED: 3; + static ENDED: 4; + + _state: number; + _readable: boolean; + _destination?: AsyncIterator; + + readable: boolean; + closed: boolean; + ended: boolean; + + constructor(); + + read(): T; + + each(callback: (data: T) => void, self?: any): void; + + close(): void; + + _changeState(newState: number, eventAsync?: boolean): void; + + private _hasListeners(eventName: string | symbol): boolean; + + // tslint:disable-next-line ban-types + private _addSingleListener(eventName: string | symbol, listener: Function): void; + + _end(): void; + + getProperty(propertyName: string, callback?: (value: any) => void): any; + + setProperty(propertyName: string, value: any): void; + + getProperties(): { [id: string]: any }; + + setProperties(properties: { [id: string]: any }): void; + + copyProperties(source: AsyncIterator, propertyNames: string[]): void; + + toString(): string; + + _toStringDetails(): string; + + transform(options?: SimpleTransformIteratorOptions): SimpleTransformIterator; + + map(mapper: (item: T) => T2, self?: object): SimpleTransformIterator; + + filter(filter: (item: T) => boolean, self?: object): SimpleTransformIterator; + + prepend(items: T[] | AsyncIterator): SimpleTransformIterator; + + append(items: T[] | AsyncIterator): SimpleTransformIterator; + + surround(prepend: T[] | AsyncIterator, append: T[] | AsyncIterator): SimpleTransformIterator; + + skip(offset: number): SimpleTransformIterator; + + take(limit: number): SimpleTransformIterator; + + range(start: number, end: number): SimpleTransformIterator; + + clone(): ClonedIterator; + + static range(start?: number, end?: number, step?: number): IntegerIterator; + } + + export class EmptyIterator extends AsyncIterator { + _state: 4; + } + + export class SingletonIterator extends AsyncIterator { + constructor(item?: T); + } + + export class ArrayIterator extends AsyncIterator { + constructor(items?: T[]); + } + + export interface IntegerIteratorOptions { + step?: number; + end?: number; + start?: number; + } + + export class IntegerIterator extends AsyncIterator { + _step: number; + _last: number; + _next: number; + + constructor(options?: IntegerIteratorOptions); + } + + export interface BufferedIteratorOptions { + maxBufferSize?: number; + autoStart?: boolean; + } + + export class BufferedIterator extends AsyncIterator { + maxBufferSize: number; + _pushedCount: number; + _buffer: T[]; + + _init(autoStart: boolean): void; + + _begin(done: () => void): void; + + _read(count: number, done: () => void): void; + + _push(item: T): void; + + _fillBuffer(): void; + + _completeClose(): void; + + _flush(done: () => void): void; + + constructor(options?: BufferedIteratorOptions); + } + + export interface TransformIteratorOptions extends BufferedIteratorOptions { + optional?: boolean; + source?: AsyncIterator; + } + + export class TransformIterator extends BufferedIterator { + _optional: boolean; + source: AsyncIterator; + + _validateSource(source: AsyncIterator, allowDestination?: boolean): void; + + _transform(item: S, done: (result: T) => void): void; + + _closeWhenDone(): void; + + constructor(source?: AsyncIterator | TransformIteratorOptions, options?: TransformIteratorOptions); + } + + export interface SimpleTransformIteratorOptions extends TransformIteratorOptions { + offset?: number; + limit?: number; + prepend?: T[]; + append?: T[]; + + filter?(item: S): boolean; + + map?(item: S): T; + + transform?(item: S, callback: (result: T) => void): void; + } + + export class SimpleTransformIterator extends TransformIterator { + _offset: number; + _limit: number; + _prepender?: ArrayIterator; + _appender?: ArrayIterator; + + _filter?(item: S): boolean; + + _map?(item: S): T; + + _transform(item: S, done: (result: T) => void): void; + + _insert(inserter: AsyncIterator, done: () => void): void; + + constructor(source?: AsyncIterator | SimpleTransformIteratorOptions, + options?: SimpleTransformIteratorOptions); + } + + export class MultiTransformIterator extends TransformIterator { + _transformerQueue: S[]; + + _createTransformer(element: S): AsyncIterator; + + constructor(source?: AsyncIterator | TransformIteratorOptions, options?: TransformIteratorOptions); + } + + export class ClonedIterator extends TransformIterator { + _readPosition: number; + + constructor(source?: AsyncIterator); + } +} diff --git a/src/enums/EventType.ts b/src/enums/EventType.ts index 5edfe497..f4e324fe 100644 --- a/src/enums/EventType.ts +++ b/src/enums/EventType.ts @@ -9,6 +9,7 @@ enum EventType { Warning = "warning", ConnectionPrefetch = "connection-prefetch", + ConnectionIteratorView = "connection-iterator-view", ConnectionScan = "connection-scan", InitialReachableStops = "initial-reachable-stops", diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index 74fbf488..b962d8c3 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -43,7 +43,7 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.context = context; this.connectionsFetcher = connectionsFetcherFactory(accessUrl, travelMode); - this.connectionsStore = new ConnectionsStore(); + this.connectionsStore = new ConnectionsStore(context); } public prefetchConnections(): void { @@ -90,9 +90,8 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider public createIterator(): AsyncIterator { if (this.startedPrefetching) { - return new PromiseProxyIterator(() => - this.connectionsStore.getIterator(this.connectionsIteratorOptions), - ); + return this.connectionsStore + .getIterator(this.connectionsIteratorOptions); } throw new Error("TODO"); diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts index fab4d406..f29381e5 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -1,4 +1,4 @@ -import {AsyncIterator} from "asynciterator"; +import { AsyncIterator } from "asynciterator"; import "jest"; import IConnection from "../IConnection"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; @@ -21,7 +21,7 @@ describe("[ConnectionsStore]", () => { const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; // @ts-ignore const fakeConnections: IConnection[] = fakeDepartureTimes - .map((departureTime) => ({departureTime})); + .map((departureTime) => ({ departureTime })); connectionsStore = new ConnectionsStore(); for (const connection of fakeConnections) { @@ -30,7 +30,7 @@ describe("[ConnectionsStore]", () => { connectionsStore.finish(); - createIterator = async (backward, lowerBoundDate, upperBoundDate): Promise> => { + createIterator = (backward, lowerBoundDate, upperBoundDate): Promise> => { const iteratorOptions: IConnectionsIteratorOptions = { backward, }; @@ -51,7 +51,7 @@ describe("[ConnectionsStore]", () => { describe("backward", () => { it("upperBoundDate is loaded & exists in store", async (done) => { - const iteratorView = await createIterator(true, null, 6); + const iteratorView = createIterator(true, null, 6); const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6]; let current = expected.length - 1; @@ -67,7 +67,7 @@ describe("[ConnectionsStore]", () => { }); it("upperBoundDate is loaded but doesn\'t exist in store", async (done) => { - const iteratorView = await createIterator(true, null, 4); + const iteratorView = createIterator(true, null, 4); const expected = [1, 2, 3, 3]; let current = expected.length - 1; @@ -87,7 +87,7 @@ describe("[ConnectionsStore]", () => { describe("forward", () => { it("lowerBoundDate is loaded & exists in store", async (done) => { - const iteratorView = await createIterator(false, 3, null); + const iteratorView = createIterator(false, 3, null); const expected = [3, 3, 5, 6, 6, 6, 6, 7, 7]; let current = 0; @@ -103,7 +103,7 @@ describe("[ConnectionsStore]", () => { }); it("lowerBoundDate is loaded but doesn\'t exist in store", async (done) => { - const iteratorView = await createIterator(false, 4, null); + const iteratorView = createIterator(false, 4, null); const expected = [5, 6, 6, 6, 6, 7, 7]; let current = 0; @@ -123,41 +123,46 @@ describe("[ConnectionsStore]", () => { }); describe("Loaded async", () => { - jest.setTimeout(10000); + jest.setTimeout(1000000); - const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7, 8]; - // @ts-ignore - const fakeConnections: IConnection[] = fakeDepartureTimes - .map((departureTime) => ({departureTime})); - - const connectionsStore = new ConnectionsStore(); + let connectionsStore; - // Append first few connections sync - for (const connection of fakeConnections.slice(0, 6)) { - connectionsStore.append(connection); - } + beforeEach(() => { - // Append remaining connections async - let i = 6; - const appendNext = () => { - connectionsStore.append(fakeConnections[i++]); + const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7, 8]; + // @ts-ignore + const fakeConnections: IConnection[] = fakeDepartureTimes + .map((departureTime) => ({ departureTime })); - if (i < fakeConnections.length) { - setTimeout(appendNext, 100); + connectionsStore = new ConnectionsStore(); - } else { - connectionsStore.finish(); + // Append first few connections sync + for (const connection of fakeConnections.slice(0, 6)) { + connectionsStore.append(connection); } - }; - setTimeout(appendNext, 100); + // Append remaining connections async + let i = 6; + const appendNext = () => { + connectionsStore.append(fakeConnections[i++]); - it("iterator view: backward / upperBoundDate isn't loaded at first", async (done) => { + if (i < fakeConnections.length) { + setTimeout(appendNext, 100); + + } else { + connectionsStore.finish(); + } + }; + + setTimeout(appendNext, 100); + }); + + it("backward", async (done) => { const iteratorOptions: IConnectionsIteratorOptions = { backward: true, upperBoundDate: (7 as unknown) as Date, }; - const iteratorView: AsyncIterator = await connectionsStore.getIterator(iteratorOptions); + const iteratorView: AsyncIterator = connectionsStore.getIterator(iteratorOptions); const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; let current = expected.length - 1; @@ -172,6 +177,29 @@ describe("[ConnectionsStore]", () => { }); }); + it("forward", async (done) => { + const iteratorOptions: IConnectionsIteratorOptions = { + backward: false, + lowerBoundDate: (2 as unknown) as Date, + upperBoundDate: (7 as unknown) as Date, + }; + const iteratorView: AsyncIterator = connectionsStore.getIterator(iteratorOptions); + + const expected = [2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; + let current = 0; + + iteratorView.each((str: IConnection) => { + console.log("Read", str.departureTime); + + expect(expected[current++]).toBe(str.departureTime); + }); + + iteratorView.on("end", () => { + expect(current).toBe(expected.length); + done(); + }); + }); + }); }); diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index d84a211f..612841df 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -1,7 +1,11 @@ import { ArrayIterator, AsyncIterator, IntegerIterator, IntegerIteratorOptions } from "asynciterator"; +import { PromiseProxyIterator } from "asynciterator-promiseproxy"; +import Context from "../../../Context"; +import EventType from "../../../enums/EventType"; import BinarySearch from "../../../util/BinarySearch"; import IConnection from "../IConnection"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; +import ExpandingIterator from "./ExpandingIterator"; interface IDeferredBackwardView { lowerBoundDate: Date; @@ -9,6 +13,12 @@ interface IDeferredBackwardView { resolve: (iterator: AsyncIterator) => void; } +interface IExpandingForwardView { + lowerBoundDate: Date; + upperBoundDate: Date; + tryExpand: (connection: IConnection, index: number) => boolean; +} + /** * Class used while prefetching [[IConnection]] instances. It allows appending connections * and creating iterator *views*. Iterator *views* are AsyncIterators that emit references to connections in the store. @@ -18,15 +28,20 @@ interface IDeferredBackwardView { * Consequently this connections store serves as an in-memory cache for connections */ export default class ConnectionsStore { + + private readonly context: Context; private readonly store: IConnection[]; private readonly binarySearch: BinarySearch; private deferredBackwardViews: IDeferredBackwardView[]; + private expandingForwardViews: IExpandingForwardView[]; private hasFinished: boolean; - constructor() { + constructor(context?: Context) { + this.context = context; this.store = []; this.binarySearch = new BinarySearch(this.store, (connection) => connection.departureTime.valueOf()); this.deferredBackwardViews = []; + this.expandingForwardViews = []; this.hasFinished = false; } @@ -39,21 +54,33 @@ export default class ConnectionsStore { public append(connection: IConnection) { this.store.push(connection); - // Check if any deferred backward view are satisfied + // Check if any deferred backward views are satisfied if (this.deferredBackwardViews.length) { this.deferredBackwardViews = this.deferredBackwardViews .filter(({ lowerBoundDate, upperBoundDate, resolve }) => { if (connection.departureTime > upperBoundDate) { - const iteratorView = this.getIteratorView(true, lowerBoundDate, upperBoundDate); + const { iterator } = this.getIteratorView(true, lowerBoundDate, upperBoundDate); + + if (this.context) { + this.context.emit(EventType.ConnectionIteratorView, lowerBoundDate, upperBoundDate, true); + } - resolve(iteratorView); + resolve(iterator); return false; } return true; }); } + + // Check if any forward views can be expanded + if (this.expandingForwardViews.length) { + this.expandingForwardViews = this.expandingForwardViews + .filter(({ tryExpand }) => + tryExpand(connection, this.store.length - 1), + ); + } } /** @@ -64,12 +91,16 @@ export default class ConnectionsStore { this.hasFinished = true; } - public getIterator(iteratorOptions: IConnectionsIteratorOptions): Promise> { + public getIterator(iteratorOptions: IConnectionsIteratorOptions): AsyncIterator { const { backward } = iteratorOptions; let { lowerBoundDate, upperBoundDate } = iteratorOptions; + if (this.context) { + this.context.emit(EventType.ConnectionIteratorView, lowerBoundDate, upperBoundDate, false); + } + if (this.hasFinished && this.store.length === 0) { - return Promise.resolve(new ArrayIterator([])); + return new ArrayIterator([]); } const firstConnection = this.store[0]; @@ -78,7 +109,28 @@ export default class ConnectionsStore { const lastConnection = this.store[this.store.length - 1]; const lastDepartureTime = lastConnection && lastConnection.departureTime; - if (!backward) { + if (backward) { + + if (!upperBoundDate) { + throw new Error("Must supply upperBoundDate when iterating backward"); + } + + if (!lowerBoundDate) { + lowerBoundDate = firstDepartureTime; + } + + // If the store is still empty or the latest departure time isn't later than the upperBoundDate, + // then return a promise proxy iterator + if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { + const { deferred, promise } = this.createDeferredBackwardView(lowerBoundDate, upperBoundDate); + + this.deferredBackwardViews.push(deferred); + + return new PromiseProxyIterator(() => promise); + } + + } else { + if (!lowerBoundDate) { throw new Error("Must supply lowerBoundDate when iterating forward"); } @@ -86,30 +138,26 @@ export default class ConnectionsStore { if (!upperBoundDate) { upperBoundDate = lastDepartureTime; } - } - if (backward) { - if (!upperBoundDate) { - throw new Error("Must supply upperBoundDate when iterating backward"); - } + // If the store is still empty or the latest departure time isn't later than the upperBoundDate, + // then return a an expanding iterator view + if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { + const { view, iterator } = this.createExpandingForwardView(lowerBoundDate, upperBoundDate); - if (!lowerBoundDate) { - lowerBoundDate = firstDepartureTime; + this.expandingForwardViews.push(view); + + return iterator; } } - // If the store is still empty or the latest departure time isn't later than the upperBoundDate, - // then return a promise - if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { - const { deferred, promise } = this.createDeferredBackwardView(lowerBoundDate, upperBoundDate); - - this.deferredBackwardViews.push(deferred); + // Else if the whole interval is available, or the store has finished, return an iterator immediately + const { iterator } = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); - return promise; + if (this.context) { + this.context.emit(EventType.ConnectionIteratorView, lowerBoundDate, upperBoundDate, true); } - // Else if the whole interval is available, or the store has finished, return an iterator immediately - return Promise.resolve(this.getIteratorView(backward, lowerBoundDate, upperBoundDate)); + return iterator; } private createDeferredBackwardView(lowerBoundDate, upperBoundDate): @@ -130,7 +178,48 @@ export default class ConnectionsStore { }; } - private getIteratorView(backward: boolean, lowerBoundDate: Date, upperBoundDate: Date): AsyncIterator { + private createExpandingForwardView(lowerBoundDate, upperBoundDate): + { view: IExpandingForwardView, iterator: AsyncIterator } { + + const { iterator: existingIterator, upperBoundIndex } = this.getIteratorView(false, lowerBoundDate, upperBoundDate); + const expandingIterator = new ExpandingIterator(); + + const iterator = expandingIterator.prepend(existingIterator); + + let lastStoreIndex = upperBoundIndex; + + const view: IExpandingForwardView = { + lowerBoundDate, + upperBoundDate, + tryExpand: (connection: IConnection, storeIndex: number): boolean => { + + if (storeIndex - lastStoreIndex > 1) { + // No idea if this can happen + console.warn("Skipped", storeIndex - lastStoreIndex); + } + + lastStoreIndex = storeIndex; + + if (connection.departureTime <= upperBoundDate) { + expandingIterator.write(connection); + + return true; // Keep in expanding forward views + + } else { + expandingIterator.close(); + iterator.close(); + + return false; // Remove from expanding forward views + } + }, + }; + + return { view, iterator }; + } + + private getIteratorView(backward: boolean, lowerBoundDate: Date, upperBoundDate: Date): + { iterator: AsyncIterator, lowerBoundIndex: number, upperBoundIndex: number } { + const lowerBoundIndex = this.getLowerBoundIndex(lowerBoundDate); const upperBoundIndex = this.getUpperBoundIndex(upperBoundDate); @@ -140,8 +229,10 @@ export default class ConnectionsStore { step: backward ? -1 : 1, }; - return new IntegerIterator(indexIteratorOptions) + const iterator = new IntegerIterator(indexIteratorOptions) .map((index) => this.store[index]); + + return { iterator, lowerBoundIndex, upperBoundIndex }; } private getLowerBoundIndex(date: Date): number { diff --git a/src/fetcher/connections/prefetch/ExpandingIterator.test.ts b/src/fetcher/connections/prefetch/ExpandingIterator.test.ts new file mode 100644 index 00000000..75f1aea3 --- /dev/null +++ b/src/fetcher/connections/prefetch/ExpandingIterator.test.ts @@ -0,0 +1,98 @@ +import "jest"; +import ExpandingIterator from "./ExpandingIterator"; + +describe("[ExpandingIterator]", () => { + + it("async push", (done) => { + + const expandingIterator = new ExpandingIterator(); + const expected = [1, 2, 3, 4, 5, 6, 8]; + + let currentWrite = 0; + let currentRead = 0; + + const interval = setInterval(() => { + // console.log("Writing", expected[currentWrite]); + expandingIterator.write(expected[currentWrite]); + + if (++currentWrite === expected.length) { + // console.log("Closing"); + expandingIterator.close(); + clearInterval(interval); + } + }, 1); + + expandingIterator.each((str: number) => { + // console.log("Reading", str); + expect(expected[currentRead++]).toBe(str); + }); + + expandingIterator.on("end", () => done()); + }); + + it("sync push", (done) => { + + const expandingIterator = new ExpandingIterator(); + const expected = [1, 2, 3, 4, 5, 6, 8]; + + let currentWrite = 0; + let currentRead = 0; + + for (; currentWrite < expected.length; currentWrite++) { + // console.log("Writing", expected[currentWrite]); + expandingIterator.write(expected[currentWrite]); + } + + expandingIterator.close(); + + expandingIterator.each((str: number) => { + // console.log("Reading", str); + expect(expected[currentRead++]).toBe(str); + }); + + expandingIterator.on("end", () => done()); + }); + + it("mixed push", (done) => { + + const expandingIterator = new ExpandingIterator(); + const expected = [1, 2, 3, 4, 5, 6, 8]; + + let currentWrite = 0; + let currentRead = 0; + + for (; currentWrite < 3; currentWrite++) { + // console.log("Writing", expected[currentWrite]); + expandingIterator.write(expected[currentWrite]); + } + + const interval = setInterval(() => { + // console.log("Writing", expected[currentWrite]); + expandingIterator.write(expected[currentWrite]); + + if (++currentWrite < expected.length) { + // console.log("Writing", expected[currentWrite]); + expandingIterator.write(expected[currentWrite]); + + } else { + // console.log("Closing"); + expandingIterator.close(); + clearInterval(interval); + } + + if (++currentWrite === expected.length) { + // console.log("Closing"); + expandingIterator.close(); + clearInterval(interval); + } + }, 1); + + expandingIterator.each((str: number) => { + // console.log("Reading", str); + expect(expected[currentRead++]).toBe(str); + }); + + expandingIterator.on("end", () => done()); + }); + +}); diff --git a/src/fetcher/connections/prefetch/ExpandingIterator.ts b/src/fetcher/connections/prefetch/ExpandingIterator.ts new file mode 100644 index 00000000..60f9a7c0 --- /dev/null +++ b/src/fetcher/connections/prefetch/ExpandingIterator.ts @@ -0,0 +1,31 @@ +import { AsyncIterator } from "asynciterator"; + +export default class ExpandingIterator extends AsyncIterator { + + private buffer: T[]; + + constructor() { + super(); + + this.buffer = []; + } + + public read(): T { + let item; + + if (this.buffer.length) { + item = this.buffer.shift(); + + } else { + item = null; + this.readable = false; + } + + return item; + } + + public write(item: T): void { + this.buffer.push(item); + this.readable = true; + } +} diff --git a/tslint.json b/tslint.json index 8d17f7e4..81d71924 100644 --- a/tslint.json +++ b/tslint.json @@ -7,7 +7,13 @@ "rules": { "no-console": false, "object-literal-sort-keys": false, - "no-empty-interface": false + "no-empty-interface": false, + "no-shadowed-variable": [ + true, + { + "temporalDeadZone": false + } + ] }, "rulesDirectory": [] } From 0704c5543af674111c790850c3e9acd3bf1f61af Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 25 Jan 2019 13:52:13 +0100 Subject: [PATCH 44/69] Keep test under 90s on Travis --- src/inversify.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 23b9a747..a5ec3b50 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -22,6 +22,7 @@ import JourneyExtractorProfile from "./planner/public-transport/JourneyExtractor import IRoadPlanner from "./planner/road/IRoadPlanner"; import RoadPlannerBirdsEye from "./planner/road/RoadPlannerBirdsEye"; import IReachableStopsFinder from "./planner/stops/IReachableStopsFinder"; +import ReachableStopsFinderOnlySelf from "./planner/stops/ReachableStopsFinderOnlySelf"; import ReachableStopsFinderRoadPlannerCached from "./planner/stops/ReachableStopsFinderRoadPlannerCached"; import QueryRunnerExponential from "./query-runner/exponential/QueryRunnerExponential"; import ILocationResolver from "./query-runner/ILocationResolver"; @@ -48,7 +49,7 @@ container.bind(TYPES.JourneyExtractor) container.bind(TYPES.ReachableStopsFinder) .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Initial); container.bind(TYPES.ReachableStopsFinder) - .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Transfer); + .to(ReachableStopsFinderOnlySelf).whenTargetTagged("phase", ReachableStopsSearchPhase.Transfer); container.bind(TYPES.ReachableStopsFinder) .to(ReachableStopsFinderRoadPlannerCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Final); From ebef01b4b32ffa880c7f3f00a534767485cd1d7b Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 25 Jan 2019 14:05:36 +0100 Subject: [PATCH 45/69] 1. fix CSA: no reachableStops -> abort; 2. fix queryRunnerEATFirst -> always create new CSAEat planner. --- example/js/index.js | 2 +- src/Planner.ts | 3 +- .../public-transport/CSAEarliestArrival.ts | 21 +++++--- src/planner/public-transport/CSAProfile.ts | 24 +++++++--- .../JourneyExtractorEarliestArrival.ts | 2 +- src/query-runner/IResolvedQuery.ts | 1 + .../QueryRunnerEarliestArrivalFirst.ts | 48 +++++++++++++------ 7 files changed, 69 insertions(+), 32 deletions(-) diff --git a/example/js/index.js b/example/js/index.js index dc962f7d..f64cabb2 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -81,7 +81,7 @@ planner .on("query", query => { console.log("Query", query); }) - .on("query-exponential", query => { + .on("sub-query", query => { const { minimumDepartureTime, maximumArrivalTime } = query; console.log( diff --git a/src/Planner.ts b/src/Planner.ts index a8820269..91c3f8e3 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -48,8 +48,9 @@ export default class Planner implements EventEmitter { }); return iterator; + } catch (e) { - if (e.eventType) { + if (e && e.eventType) { this.context.emit(e.eventType, e.message); } diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index af4061c3..fce7368c 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -1,4 +1,4 @@ -import { AsyncIterator } from "asynciterator"; +import { ArrayIterator, AsyncIterator } from "asynciterator"; import { inject, injectable, tagged } from "inversify"; import Context from "../../Context"; import DropOffType from "../../enums/DropOffType"; @@ -103,8 +103,12 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } private async calculateJourneys(): Promise> { - await this.initInitialReachableStops(); - await this.initFinalReachableStops(); + const hasInitialReachableStops: boolean = await this.initInitialReachableStops(); + const hasFinalReachableStops: boolean = await this.initFinalReachableStops(); + + if (!hasInitialReachableStops || !hasFinalReachableStops) { + return Promise.resolve(new ArrayIterator([])); + } this.connectionsIterator = this.connectionsProvider.createIterator(); @@ -143,6 +147,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const arrivalStopId: string = this.query.to[0].id; if (this.profilesByStop[arrivalStopId].arrivalTime <= connection.departureTime.getTime()) { + this.connectionsIterator.close(); done(); } @@ -182,8 +187,9 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const fromLocation: IStop = this.query.from[0] as IStop; // Making sure the departure location has an id + const geoId = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; if (!fromLocation.id) { - this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; + this.query.from[0].id = geoId; this.query.from[0].name = "Departure location"; } @@ -195,7 +201,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { ); // Abort when we can't reach a single stop. - if (this.initialReachableStops.length === 0 && this.context) { + if (this.initialReachableStops.length <= 1 && this.initialReachableStops[0].stop.id === geoId && this.context) { this.context.emit(EventType.AbortQuery, "No reachable stops at departure location"); return false; @@ -248,8 +254,9 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { private async initFinalReachableStops(): Promise { const arrivalStop: IStop = this.query.to[0] as IStop; + const geoId = "geo:" + arrivalStop.latitude + "," + arrivalStop.longitude; if (!this.query.to[0].id) { - this.query.to[0].id = "geo:" + arrivalStop.latitude + "," + arrivalStop.longitude; + this.query.to[0].id = geoId; this.query.to[0].name = "Arrival location"; } @@ -261,7 +268,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { this.query.minimumWalkingSpeed, ); - if (this.finalReachableStops.length === 0 && this.context) { + if (this.finalReachableStops.length <= 1 && this.finalReachableStops[0].stop.id === geoId && this.context) { this.context.emit(EventType.AbortQuery, "No reachable stops at arrival location"); return false; diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index 6edb3349..e07365f4 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -1,4 +1,4 @@ -import { AsyncIterator } from "asynciterator"; +import { ArrayIterator, AsyncIterator } from "asynciterator"; import { inject, injectable, tagged } from "inversify"; import Context from "../../Context"; import DropOffType from "../../enums/DropOffType"; @@ -107,8 +107,12 @@ export default class CSAProfile implements IPublicTransportPlanner { } private async calculateJourneys(): Promise> { - await this.initDurationToTargetByStop(); - await this.initInitialReachableStops(); + const hasInitialReachableStops: boolean = await this.initDurationToTargetByStop(); + const hasFinalReachableStops: boolean = await this.initInitialReachableStops(); + + if (!hasInitialReachableStops || !hasFinalReachableStops) { + return Promise.resolve(new ArrayIterator([])); + } this.connectionsIterator = this.connectionsProvider.createIterator(); @@ -227,8 +231,9 @@ export default class CSAProfile implements IPublicTransportPlanner { private async initDurationToTargetByStop(): Promise { const arrivalStop: IStop = this.query.to[0] as IStop; + const geoId = "geo:" + this.query.to[0].latitude + "," + this.query.to[0].longitude; if (!this.query.to[0].id) { - this.query.to[0].id = "geo:" + this.query.to[0].latitude + "," + this.query.to[0].longitude; + this.query.to[0].id = geoId; this.query.to[0].name = "Arrival location"; } @@ -240,7 +245,7 @@ export default class CSAProfile implements IPublicTransportPlanner { this.query.minimumWalkingSpeed, ); - if (reachableStops.length === 0 && this.context) { + if (reachableStops.length <= 1 && reachableStops[0].stop.id === geoId && this.context) { this.context.emit(EventType.AbortQuery, "No reachable stops at arrival location"); return false; @@ -264,8 +269,9 @@ export default class CSAProfile implements IPublicTransportPlanner { private async initInitialReachableStops(): Promise { const fromLocation: IStop = this.query.from[0] as IStop; + const geoId = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; if (!this.query.from[0].id) { - this.query.from[0].id = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; + this.query.from[0].id = geoId; this.query.from[0].name = "Departure location"; } @@ -282,7 +288,7 @@ export default class CSAProfile implements IPublicTransportPlanner { } } - if (this.initialReachableStops.length === 0 && this.context) { + if (this.initialReachableStops.length <= 1 && this.initialReachableStops[0].stop.id === geoId && this.context) { this.context.emit(EventType.AbortQuery, "No reachable stops at departure location"); return false; @@ -494,7 +500,11 @@ export default class CSAProfile implements IPublicTransportPlanner { const possibleExitConnection = this.earliestArrivalByTrip[connection["gtfs:trip"]] [amountOfTransfers].connection || connection; + const isValidRoute = !this.query.maximumTravelDuration || + (arrivalTimeByTransfers[amountOfTransfers].arrivalTime - departureTime <= this.query.maximumTravelDuration); + if ( + isValidRoute && arrivalTimeByTransfers[amountOfTransfers].arrivalTime < transferProfile.arrivalTime && connection["gtfs:pickupType"] !== PickupType.NotAvailable && possibleExitConnection["gtfs:dropOfType"] !== DropOffType.NotAvailable diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts index c74b2cb5..9f2eea49 100644 --- a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts +++ b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts @@ -45,7 +45,7 @@ export default class JourneyExtractorEarliestArrival implements IJourneyExtracto let currentStopId: string = query.to[0].id; let currentProfile: ITransferProfile = profilesByStop[currentStopId]; - while (currentStopId !== departureStopId) { + while (currentStopId !== departureStopId && (currentProfile.enterConnection || currentProfile.path)) { const { enterConnection, exitConnection, path: profilePath } = currentProfile; if (currentProfile.enterConnection && currentProfile.exitConnection) { diff --git a/src/query-runner/IResolvedQuery.ts b/src/query-runner/IResolvedQuery.ts index ecec1252..f8f625dd 100644 --- a/src/query-runner/IResolvedQuery.ts +++ b/src/query-runner/IResolvedQuery.ts @@ -19,4 +19,5 @@ export default interface IResolvedQuery { minimumTransferDuration?: DurationMs; maximumTransferDuration?: DurationMs; maximumTransfers?: number; + maximumTravelDuration?: DurationMs; } diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts index 7f1172a9..d6ac6e39 100644 --- a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts @@ -26,10 +26,17 @@ import LinearQueryIterator from "./LinearQueryIterator"; @injectable() export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { + + private readonly context: Context; + private readonly connectionsProvider: IConnectionsProvider; private readonly locationResolver: ILocationResolver; private readonly publicTransportPlannerFactory: interfaces.Factory; - private readonly context: Context; - private earliestArrivalPlanner: CSAEarliestArrival; + + private readonly journeyExtractorEarliestArrival: JourneyExtractorEarliestArrival; + + private readonly initialReachableStopsFinder: IReachableStopsFinder; + private readonly transferReachableStopsFinder: IReachableStopsFinder; + private readonly finalReachableStopsFinder: IReachableStopsFinder; constructor( @inject(TYPES.Context) @@ -51,21 +58,16 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { finalReachableStopsFinder: IReachableStopsFinder, ) { this.context = context; + this.connectionsProvider = connectionsProvider; this.locationResolver = locationResolver; this.publicTransportPlannerFactory = publicTransportPlannerFactory; - const journeyExtractorEarliestArrival = new JourneyExtractorEarliestArrival( - locationResolver, - context, - ); + this.initialReachableStopsFinder = initialReachableStopsFinder; + this.transferReachableStopsFinder = transferReachableStopsFinder; + this.finalReachableStopsFinder = finalReachableStopsFinder; - this.earliestArrivalPlanner = new CSAEarliestArrival( - connectionsProvider, + this.journeyExtractorEarliestArrival = new JourneyExtractorEarliestArrival( locationResolver, - initialReachableStopsFinder, - transferReachableStopsFinder, - finalReachableStopsFinder, - journeyExtractorEarliestArrival, context, ); } @@ -75,7 +77,17 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { if (baseQuery.publicTransportOnly) { - const earliestArrivalIterator = await this.earliestArrivalPlanner.plan(baseQuery); + const earliestArrivalPlanner = new CSAEarliestArrival( + this.connectionsProvider, + this.locationResolver, + this.initialReachableStopsFinder, + this.transferReachableStopsFinder, + this.finalReachableStopsFinder, + this.journeyExtractorEarliestArrival, + this.context, + ); + + const earliestArrivalIterator = await earliestArrivalPlanner.plan(baseQuery); const path: IPath = await new Promise((resolve, reject) => { earliestArrivalIterator @@ -88,14 +100,20 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { }); }); - let initialTimeSpan: DurationMs = 15 * 60 * 1000; + let initialTimeSpan: DurationMs = Units.fromHours(1); + let maximumTravelDuration: DurationMs; if (path && path.steps && path.steps.length > 0) { initialTimeSpan = path.steps[path.steps.length - 1].stopTime.getTime() - baseQuery.minimumDepartureTime.getTime(); + + maximumTravelDuration = path.steps[path.steps.length - 1].stopTime.getTime() - + path.steps[0].startTime.getTime(); } - const queryIterator = new LinearQueryIterator(baseQuery, initialTimeSpan / 2, initialTimeSpan); + baseQuery.maximumTravelDuration = maximumTravelDuration * 2; + + const queryIterator = new LinearQueryIterator(baseQuery, Units.fromHours(1.5), initialTimeSpan); const subQueryIterator = new FlatMapIterator( queryIterator, From 712b126d6344514b6b1ce9ff8cea20351867e67c Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 25 Jan 2019 14:12:29 +0100 Subject: [PATCH 46/69] Cleanup --- .../prefetch/ConnectionsProviderPrefetch.ts | 38 ++++++++++--------- .../prefetch/ConnectionsStore.test.ts | 2 - .../connections/prefetch/ConnectionsStore.ts | 23 +++++------ 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index b962d8c3..4cfc2551 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -1,10 +1,8 @@ import { AsyncIterator } from "asynciterator"; -import { PromiseProxyIterator } from "asynciterator-promiseproxy"; import { inject, injectable } from "inversify"; import Catalog from "../../../Catalog"; import Context from "../../../Context"; import EventType from "../../../enums/EventType"; -import { DurationMs } from "../../../interfaces/units"; import TYPES, { ConnectionsFetcherFactory } from "../../../types"; import Units from "../../../util/Units"; import IConnection from "../IConnection"; @@ -24,6 +22,7 @@ import ConnectionsStore from "./ConnectionsStore"; export default class ConnectionsProviderPrefetch implements IConnectionsProvider { private static MAX_CONNECTIONS = 20000; + private static REPORTING_THRESHOLD = Units.fromHours(.25); private readonly context: Context; private readonly connectionsFetcher: IConnectionsFetcher; @@ -59,27 +58,13 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.connectionsFetcher.setIteratorOptions(options); this.connectionsIterator = this.connectionsFetcher.createIterator(); - const reportingThreshold = Units.fromHours(.25); - this.connectionsIterator .take(ConnectionsProviderPrefetch.MAX_CONNECTIONS) .on("end", () => this.connectionsStore.finish()) .each((connection: IConnection) => { - if (this.context) { - - if (!this.lastReportedDepartureTime) { - this.lastReportedDepartureTime = connection.departureTime; - - this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); - - } else if ( - connection.departureTime.valueOf() - this.lastReportedDepartureTime.valueOf() > reportingThreshold - ) { - this.lastReportedDepartureTime = connection.departureTime; - - this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); - } + if (this.context) { + this.maybeEmitPrefetchEvent(connection); } this.connectionsStore.append(connection); @@ -100,4 +85,21 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider public setIteratorOptions(options: IConnectionsIteratorOptions): void { this.connectionsIteratorOptions = options; } + + private maybeEmitPrefetchEvent(connection: IConnection): void { + if (!this.lastReportedDepartureTime) { + this.lastReportedDepartureTime = connection.departureTime; + + this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); + return; + } + + const timeSinceLastEvent = connection.departureTime.valueOf() - this.lastReportedDepartureTime.valueOf(); + + if (timeSinceLastEvent > ConnectionsProviderPrefetch.REPORTING_THRESHOLD) { + this.lastReportedDepartureTime = connection.departureTime; + + this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); + } + } } diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts index f29381e5..a61a4e43 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -189,8 +189,6 @@ describe("[ConnectionsStore]", () => { let current = 0; iteratorView.each((str: IConnection) => { - console.log("Read", str.departureTime); - expect(expected[current++]).toBe(str.departureTime); }); diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 612841df..0d8f6e32 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -48,7 +48,7 @@ export default class ConnectionsStore { /** * Add a new [[IConnection]] to the store. * - * Additionally, this method checks if any forward iterator views can be pushed to or if any backward iterator can be + * Additionally, this method checks if any forward iterator views can be expanded or if any backward iterator can be * resolved */ public append(connection: IConnection) { @@ -62,9 +62,7 @@ export default class ConnectionsStore { if (connection.departureTime > upperBoundDate) { const { iterator } = this.getIteratorView(true, lowerBoundDate, upperBoundDate); - if (this.context) { - this.context.emit(EventType.ConnectionIteratorView, lowerBoundDate, upperBoundDate, true); - } + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); resolve(iterator); return false; @@ -95,9 +93,7 @@ export default class ConnectionsStore { const { backward } = iteratorOptions; let { lowerBoundDate, upperBoundDate } = iteratorOptions; - if (this.context) { - this.context.emit(EventType.ConnectionIteratorView, lowerBoundDate, upperBoundDate, false); - } + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, false); if (this.hasFinished && this.store.length === 0) { return new ArrayIterator([]); @@ -152,10 +148,7 @@ export default class ConnectionsStore { // Else if the whole interval is available, or the store has finished, return an iterator immediately const { iterator } = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); - - if (this.context) { - this.context.emit(EventType.ConnectionIteratorView, lowerBoundDate, upperBoundDate, true); - } + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); return iterator; } @@ -209,6 +202,8 @@ export default class ConnectionsStore { expandingIterator.close(); iterator.close(); + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); + return false; // Remove from expanding forward views } }, @@ -242,4 +237,10 @@ export default class ConnectionsStore { private getUpperBoundIndex(date: Date): number { return this.binarySearch.findLastIndex(date.valueOf(), 0, this.store.length - 1); } + + private emitConnectionViewEvent(lowerBoundDate: Date, upperBoundDate: Date, completed: boolean) { + if (this.context) { + this.context.emit(EventType.ConnectionIteratorView, lowerBoundDate, upperBoundDate, completed); + } + } } From 41ea09d175484c8a54327b85683ae0313e026057 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 25 Jan 2019 14:26:12 +0100 Subject: [PATCH 47/69] 1. reachableStopsFinderBirdsEye return currentLocation. --- src/planner/public-transport/CSAEarliestArrival.ts | 5 +++-- src/planner/public-transport/CSAProfile.ts | 5 +++-- src/planner/stops/ReachableStopsFinderBirdsEye.ts | 14 +++++++------- src/util/Geo.ts | 9 +++++++++ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index fce7368c..d61d8be6 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -16,6 +16,7 @@ import IStep from "../../interfaces/IStep"; import ILocationResolver from "../../query-runner/ILocationResolver"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import TYPES from "../../types"; +import Geo from "../../util/Geo"; import Path from "../Path"; import Step from "../Step"; import IReachableStopsFinder, { IReachableStop } from "../stops/IReachableStopsFinder"; @@ -187,7 +188,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const fromLocation: IStop = this.query.from[0] as IStop; // Making sure the departure location has an id - const geoId = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; + const geoId = Geo.getId(this.query.from[0]); if (!fromLocation.id) { this.query.from[0].id = geoId; this.query.from[0].name = "Departure location"; @@ -254,7 +255,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { private async initFinalReachableStops(): Promise { const arrivalStop: IStop = this.query.to[0] as IStop; - const geoId = "geo:" + arrivalStop.latitude + "," + arrivalStop.longitude; + const geoId = Geo.getId(this.query.to[0]); if (!this.query.to[0].id) { this.query.to[0].id = geoId; this.query.to[0].name = "Arrival location"; diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index e07365f4..3613ec07 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -15,6 +15,7 @@ import { DurationMs } from "../../interfaces/units"; import ILocationResolver from "../../query-runner/ILocationResolver"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import TYPES from "../../types"; +import Geo from "../../util/Geo"; import Vectors from "../../util/Vectors"; import IReachableStopsFinder, { IReachableStop } from "../stops/IReachableStopsFinder"; import IArrivalTimeByTransfers from "./CSA/data-structure/IArrivalTimeByTransfers"; @@ -231,7 +232,7 @@ export default class CSAProfile implements IPublicTransportPlanner { private async initDurationToTargetByStop(): Promise { const arrivalStop: IStop = this.query.to[0] as IStop; - const geoId = "geo:" + this.query.to[0].latitude + "," + this.query.to[0].longitude; + const geoId = Geo.getId(this.query.to[0]); if (!this.query.to[0].id) { this.query.to[0].id = geoId; this.query.to[0].name = "Arrival location"; @@ -269,7 +270,7 @@ export default class CSAProfile implements IPublicTransportPlanner { private async initInitialReachableStops(): Promise { const fromLocation: IStop = this.query.from[0] as IStop; - const geoId = "geo:" + fromLocation.latitude + "," + fromLocation.longitude; + const geoId = Geo.getId(this.query.from[0]); if (!this.query.from[0].id) { this.query.from[0].id = geoId; this.query.from[0].name = "Departure location"; diff --git a/src/planner/stops/ReachableStopsFinderBirdsEye.ts b/src/planner/stops/ReachableStopsFinderBirdsEye.ts index 6b1bc24c..f306d005 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEye.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEye.ts @@ -31,19 +31,19 @@ export default class ReachableStopsFinderBirdsEye implements IReachableStopsFind // Mode can be ignored since birds eye view distance is identical - const allStops = await this.stopsProvider.getAllStops(); + const reachableStops: IReachableStop[] = [{stop: sourceOrTargetStop, duration: 0}]; - return allStops.map((possibleTarget: IStop): IReachableStop => { - if (possibleTarget.id === sourceOrTargetStop.id) { - return {stop: sourceOrTargetStop, duration: 0}; - } + const allStops = await this.stopsProvider.getAllStops(); + allStops.forEach((possibleTarget: IStop) => { const distance = Geo.getDistanceBetweenStops(sourceOrTargetStop, possibleTarget); const duration = Units.toDuration(distance, minimumSpeed); if (duration <= maximumDuration) { - return {stop: possibleTarget, duration}; + reachableStops.push({stop: possibleTarget, duration}); } - }).filter((reachableStop) => !!reachableStop); + }); + + return reachableStops; } } diff --git a/src/util/Geo.ts b/src/util/Geo.ts index 2cd4c26a..6ab118a5 100644 --- a/src/util/Geo.ts +++ b/src/util/Geo.ts @@ -39,4 +39,13 @@ export default class Geo { public static getDistanceBetweenStops(start: IStop, stop: IStop) { return this.getDistanceBetweenLocations(start as ILocation, stop as ILocation); } + + /** + * Get the geo id of an [[ILocation]] + * @param location + * @returns geo id string + */ + public static getId(location: ILocation): string { + return `geo:${location.latitude},${location.longitude}`; + } } From 3167627b80968ccbf1f6f3f469cf043fbb5bfd97 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 25 Jan 2019 15:18:27 +0100 Subject: [PATCH 48/69] Return empty iterator when eat finds no path --- example/js/index.js | 244 ++++++++++-------- .../JourneyExtractorEarliestArrival.ts | 8 +- 2 files changed, 136 insertions(+), 116 deletions(-) diff --git a/example/js/index.js b/example/js/index.js index e11ad4aa..0c6c1abd 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -172,25 +172,25 @@ planner const width = getPrefetchViewWidth(lowerBound, upperBound); const offset = getPrefetchViewWidth(firstPrefetch, lowerBound); - const prefetchView = document.createElement('div'); - prefetchView.className = 'prefetch-view'; + const prefetchView = document.createElement("div"); + prefetchView.className = "prefetch-view"; prefetchView.style.marginLeft = `${offset}px`; prefetchView.style.width = `${width}px`; - prefetchView.style.backgroundColor = 'red'; + prefetchView.style.backgroundColor = "red"; prefetchWrapper.appendChild(prefetchView); - prefetchViews.push({lowerBound, upperBound, elem: prefetchView}); + prefetchViews.push({ lowerBound, upperBound, elem: prefetchView }); } else { - const {elem} = prefetchViews + const { elem } = prefetchViews .find((view) => view.lowerBound === lowerBound && view.upperBound === upperBound); if (!elem) { - console.warn('Wut'); + console.warn("Wut"); return; } - elem.style.backgroundColor = 'limegreen'; + elem.style.backgroundColor = "limegreen"; } }); @@ -274,116 +274,132 @@ function runQuery(query) { }) .then(publicTransportResult => { plannerResult = publicTransportResult; - publicTransportResult.take(4).on("data", path => { - console.log("path", path); - const color = getRandomColor(); - - const pathElement = document.createElement("div"); - pathElement.className = "path"; - - path.steps.forEach(step => { - const stepElement = document.createElement("div"); - stepElement.className = "step"; - - const travelMode = document.createElement("div"); - travelMode.className = "travelMode " + step.travelMode; - stepElement.appendChild(travelMode); - - const details = document.createElement("div"); - details.className = "details"; - stepElement.appendChild(details); - - const startLocation = document.createElement("div"); - startLocation.className = "startLocation"; - startLocation.innerHTML = - "Start location: " + step.startLocation.name; - details.appendChild(startLocation); - - if (step.startTime) { - const startTime = document.createElement("div"); - startTime.className = "startTime"; - startTime.innerHTML = step.startTime; - details.appendChild(startTime); - } - - if (step.enterConnectionId) { - const enterConnectionId = document.createElement("div"); - enterConnectionId.className = "enterConnectionId"; - enterConnectionId.innerHTML = - "Enter connection: " + step.enterConnectionId; - details.appendChild(enterConnectionId); - } - - if (step.duration) { - const duration = document.createElement("div"); - duration.className = "duration"; - duration.innerHTML = - "Duration: minimum " + - step.duration.minimum / (60 * 1000) + - "min"; - details.appendChild(duration); - } - - const stopLocation = document.createElement("div"); - stopLocation.className = "stopLocation"; - stopLocation.innerHTML = "Stop location: " + step.stopLocation.name; - details.appendChild(stopLocation); - - if (step.stopTime) { - const stopTime = document.createElement("div"); - stopTime.className = "stopTime"; - stopTime.innerHTML = step.stopTime; - details.appendChild(stopTime); - } - - if (step.exitConnectionId) { - const exitConnectionId = document.createElement("div"); - exitConnectionId.className = "exitConnectionId"; - exitConnectionId.innerHTML = - "Exit connection: " + step.exitConnectionId; - details.appendChild(exitConnectionId); + let i = 0; + let amount = 4; + + publicTransportResult.take(amount) + .on("data", path => { + console.log("path", i, path); + i++; + + const color = getRandomColor(); + + const pathElement = document.createElement("div"); + pathElement.className = "path"; + + path.steps.forEach(step => { + const stepElement = document.createElement("div"); + stepElement.className = "step"; + + const travelMode = document.createElement("div"); + travelMode.className = "travelMode " + step.travelMode; + stepElement.appendChild(travelMode); + + const details = document.createElement("div"); + details.className = "details"; + stepElement.appendChild(details); + + const startLocation = document.createElement("div"); + startLocation.className = "startLocation"; + startLocation.innerHTML = + "Start location: " + step.startLocation.name; + details.appendChild(startLocation); + + if (step.startTime) { + const startTime = document.createElement("div"); + startTime.className = "startTime"; + startTime.innerHTML = step.startTime; + details.appendChild(startTime); + } + + if (step.enterConnectionId) { + const enterConnectionId = document.createElement("div"); + enterConnectionId.className = "enterConnectionId"; + enterConnectionId.innerHTML = + "Enter connection: " + step.enterConnectionId; + details.appendChild(enterConnectionId); + } + + if (step.duration) { + const duration = document.createElement("div"); + duration.className = "duration"; + duration.innerHTML = + "Duration: minimum " + + step.duration.minimum / (60 * 1000) + + "min"; + details.appendChild(duration); + } + + const stopLocation = document.createElement("div"); + stopLocation.className = "stopLocation"; + stopLocation.innerHTML = "Stop location: " + step.stopLocation.name; + details.appendChild(stopLocation); + + if (step.stopTime) { + const stopTime = document.createElement("div"); + stopTime.className = "stopTime"; + stopTime.innerHTML = step.stopTime; + details.appendChild(stopTime); + } + + if (step.exitConnectionId) { + const exitConnectionId = document.createElement("div"); + exitConnectionId.className = "exitConnectionId"; + exitConnectionId.innerHTML = + "Exit connection: " + step.exitConnectionId; + details.appendChild(exitConnectionId); + } + + pathElement.style.borderLeft = "5px solid " + color; + + pathElement.appendChild(stepElement); + }); + + results.appendChild(pathElement); + + path.steps.forEach(step => { + const { startLocation, stopLocation, travelMode } = step; + + const startMarker = L.marker([ + startLocation.latitude, + startLocation.longitude + ]).addTo(map); + + startMarker.bindPopup(startLocation.name); + + const stopMarker = L.marker([ + stopLocation.latitude, + stopLocation.longitude + ]).addTo(map); + + stopMarker.bindPopup(stopLocation.name); + const line = [ + [startLocation.latitude, startLocation.longitude], + [stopLocation.latitude, stopLocation.longitude] + ]; + + const polyline = new L.Polyline(line, { + color, + weight: 5, + smoothFactor: 1, + opacity: 0.7, + dashArray: travelMode === "walking" ? "8 8" : null + }).addTo(map); + + resultObjects.push(startMarker, stopMarker, polyline); + }); + }) + .on("end", () => { + if (i < amount) { + const noMore = document.createElement('div'); + noMore.className = 'path'; + noMore.style.padding = '10px'; + noMore.innerHTML = 'No more results'; + + results.appendChild(noMore); } - - pathElement.style.borderLeft = "5px solid " + color; - - pathElement.appendChild(stepElement); - }); - - results.appendChild(pathElement); - - path.steps.forEach(step => { - const { startLocation, stopLocation, travelMode } = step; - - const startMarker = L.marker([ - startLocation.latitude, - startLocation.longitude - ]).addTo(map); - - startMarker.bindPopup(startLocation.name); - - const stopMarker = L.marker([ - stopLocation.latitude, - stopLocation.longitude - ]).addTo(map); - - stopMarker.bindPopup(stopLocation.name); - const line = [ - [startLocation.latitude, startLocation.longitude], - [stopLocation.latitude, stopLocation.longitude] - ]; - - const polyline = new L.Polyline(line, { - color, - weight: 5, - smoothFactor: 1, - opacity: 0.7, - dashArray: travelMode === "walking" ? "8 8" : null - }).addTo(map); - - resultObjects.push(startMarker, stopMarker, polyline); }); - }); }) .catch((error) => console.error(error)); } diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts index 9f2eea49..3bd63c7e 100644 --- a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts +++ b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts @@ -1,4 +1,4 @@ -import { AsyncIterator, SingletonIterator } from "asynciterator"; +import { ArrayIterator, AsyncIterator, SingletonIterator } from "asynciterator"; import { inject, injectable } from "inversify"; import Context from "../../Context"; import ILocation from "../../interfaces/ILocation"; @@ -75,9 +75,13 @@ export default class JourneyExtractorEarliestArrival implements IJourneyExtracto } + if (!path.steps.length) { + return new ArrayIterator([]); + } + path.reverse(); - return new SingletonIterator(path); + return new SingletonIterator(path); } } From 6d8cfe0b56457e35694b99f6d1366fe71baa2944 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 25 Jan 2019 15:51:44 +0100 Subject: [PATCH 49/69] Bugfix --- example/js/index.js | 13 +++++++++++-- src/Planner.ts | 2 +- .../connections/prefetch/ConnectionsStore.ts | 6 ++++-- .../QueryRunnerEarliestArrivalFirst.ts | 12 ++++++------ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/example/js/index.js b/example/js/index.js index 0c6c1abd..63ecf628 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -63,8 +63,12 @@ resetButton.onclick = e => { } }; +const pxPerMs = .00005; const getPrefetchViewWidth = (start, stop) => { - const pxPerMs = .00005; + if (!start || !stop) { + return 0; + } + return (stop.valueOf() - start.valueOf()) * pxPerMs; }; @@ -167,6 +171,9 @@ planner } }) .on("connection-iterator-view", (lowerBound, upperBound, completed) => { + if (!lowerBound || !upperBound) { + return; + } if (!completed) { const width = getPrefetchViewWidth(lowerBound, upperBound); @@ -401,5 +408,7 @@ function runQuery(query) { } }); }) - .catch((error) => console.error(error)); + .catch((error) => { + console.error(error) + }); } diff --git a/src/Planner.ts b/src/Planner.ts index 91c3f8e3..b8afea25 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -54,7 +54,7 @@ export default class Planner implements EventEmitter { this.context.emit(e.eventType, e.message); } - return Promise.reject(); + return Promise.reject(e); } } diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 0d8f6e32..2d8e4ee0 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -93,8 +93,6 @@ export default class ConnectionsStore { const { backward } = iteratorOptions; let { lowerBoundDate, upperBoundDate } = iteratorOptions; - this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, false); - if (this.hasFinished && this.store.length === 0) { return new ArrayIterator([]); } @@ -115,6 +113,8 @@ export default class ConnectionsStore { lowerBoundDate = firstDepartureTime; } + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, false); + // If the store is still empty or the latest departure time isn't later than the upperBoundDate, // then return a promise proxy iterator if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { @@ -135,6 +135,8 @@ export default class ConnectionsStore { upperBoundDate = lastDepartureTime; } + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, false); + // If the store is still empty or the latest departure time isn't later than the upperBoundDate, // then return a an expanding iterator view if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts index d6ac6e39..8e51f5a6 100644 --- a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts @@ -101,17 +101,17 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { }); let initialTimeSpan: DurationMs = Units.fromHours(1); - let maximumTravelDuration: DurationMs; + let travelDuration: DurationMs; if (path && path.steps && path.steps.length > 0) { - initialTimeSpan = path.steps[path.steps.length - 1].stopTime.getTime() - - baseQuery.minimumDepartureTime.getTime(); + const firstStep = path.steps[0]; + const lastStep = path.steps[path.steps.length - 1]; - maximumTravelDuration = path.steps[path.steps.length - 1].stopTime.getTime() - - path.steps[0].startTime.getTime(); + initialTimeSpan = lastStep.stopTime.getTime() - baseQuery.minimumDepartureTime.getTime(); + travelDuration = lastStep.stopTime.getTime() - firstStep.startTime.getTime(); } - baseQuery.maximumTravelDuration = maximumTravelDuration * 2; + baseQuery.maximumTravelDuration = travelDuration * 2; const queryIterator = new LinearQueryIterator(baseQuery, Units.fromHours(1.5), initialTimeSpan); From 2d3d73777384a42c0d84cfb04c08d4a7cb17bfed Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 25 Jan 2019 16:06:11 +0100 Subject: [PATCH 50/69] 1. csa eat: take care of minimumTransferDuration. --- src/planner/public-transport/CSAEarliestArrival.test.ts | 3 +++ src/planner/public-transport/CSAEarliestArrival.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/planner/public-transport/CSAEarliestArrival.test.ts b/src/planner/public-transport/CSAEarliestArrival.test.ts index 9a6f05b8..ac04174a 100644 --- a/src/planner/public-transport/CSAEarliestArrival.test.ts +++ b/src/planner/public-transport/CSAEarliestArrival.test.ts @@ -61,6 +61,7 @@ describe("[PublicTransportPlannerCSAEarliestArrival]", () => { minimumWalkingSpeed: Defaults.defaultMinimumWalkingSpeed, maximumWalkingSpeed: Defaults.defaultMaximumWalkingSpeed, maximumTransferDuration: Defaults.defaultMaximumTransferDuration, + minimumTransferDuration: Defaults.defaultMinimumTransferDuration, }; beforeAll(async () => { @@ -102,6 +103,7 @@ describe("[PublicTransportPlannerCSAEarliestArrival]", () => { minimumWalkingSpeed: Defaults.defaultMinimumWalkingSpeed, maximumWalkingSpeed: Defaults.defaultMaximumWalkingSpeed, maximumTransferDuration: Defaults.defaultMaximumTransferDuration, + minimumTransferDuration: Defaults.defaultMinimumTransferDuration, }; beforeAll(async () => { @@ -147,6 +149,7 @@ describe("[PublicTransportPlannerCSAEarliestArrival]", () => { minimumWalkingSpeed: Defaults.defaultMinimumWalkingSpeed, maximumWalkingSpeed: Defaults.defaultMaximumWalkingSpeed, maximumTransferDuration: Defaults.defaultMaximumTransferDuration, + minimumTransferDuration: Defaults.defaultMinimumTransferDuration, }; beforeAll(async () => { diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index d61d8be6..ef05b18b 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -364,9 +364,9 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } const reachableStopArrival = this.profilesByStop[stop.id].arrivalTime; + const arrivalTime = connection.arrivalTime.getTime() + duration + this.query.minimumTransferDuration; - if (reachableStopArrival > connection.arrivalTime.getTime() + duration) { - + if (reachableStopArrival > arrivalTime) { const transferProfile = { departureTime: connection.departureTime.getTime(), arrivalTime: connection.arrivalTime.getTime() + duration, From 99eab9aa692eb41aa6065a813bed681345365ced Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 31 Jan 2019 11:03:04 +0100 Subject: [PATCH 51/69] Use own ArrayViewIterator instead of IntegerIterator#map --- .../prefetch/ArrayViewIterator.test.ts | 80 +++++++++++++++++++ .../connections/prefetch/ArrayViewIterator.ts | 49 ++++++++++++ .../connections/prefetch/ConnectionsStore.ts | 12 ++- 3 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 src/fetcher/connections/prefetch/ArrayViewIterator.test.ts create mode 100644 src/fetcher/connections/prefetch/ArrayViewIterator.ts diff --git a/src/fetcher/connections/prefetch/ArrayViewIterator.test.ts b/src/fetcher/connections/prefetch/ArrayViewIterator.test.ts new file mode 100644 index 00000000..e0fdfed6 --- /dev/null +++ b/src/fetcher/connections/prefetch/ArrayViewIterator.test.ts @@ -0,0 +1,80 @@ +import "jest"; +import ArrayViewIterator from "./ArrayViewIterator"; + +describe("[ArrayViewIterator]", () => { + + const sourceArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + it("step +1 and start < stop", (done) => { + + const viewIterator = new ArrayViewIterator( + sourceArray, 1, 5, +1, + ); + + const expected = [2, 3, 4, 5, 6]; + let currentRead = 0; + + viewIterator.each((str: number) => { + expect(expected[currentRead++]).toBe(str); + }); + + viewIterator.on("end", () => { + expect(currentRead).toBe(expected.length); + + done(); + }); + }); + + it("step +1 and start > stop", (done) => { + + const viewIterator = new ArrayViewIterator( + sourceArray, 5, 1, +1, + ); + + const each = jest.fn(); + viewIterator.each(each); + + viewIterator.on("end", () => { + expect(each).not.toHaveBeenCalled(); + + done(); + }); + }); + + it("step -1 and start > stop", (done) => { + + const viewIterator = new ArrayViewIterator( + sourceArray, 5, 1, -1, + ); + + const expected = [2, 3, 4, 5, 6]; + let currentRead = expected.length - 1; + + viewIterator.each((str: number) => { + expect(expected[currentRead--]).toBe(str); + }); + + viewIterator.on("end", () => { + expect(currentRead).toBe(-1); + + done(); + }); + }); + + it("step -1 and start < stop", (done) => { + + const viewIterator = new ArrayViewIterator( + sourceArray, 1, 5, -1, + ); + + const each = jest.fn(); + viewIterator.each(each); + + viewIterator.on("end", () => { + expect(each).not.toHaveBeenCalled(); + + done(); + }); + }); + +}); diff --git a/src/fetcher/connections/prefetch/ArrayViewIterator.ts b/src/fetcher/connections/prefetch/ArrayViewIterator.ts new file mode 100644 index 00000000..f0b855db --- /dev/null +++ b/src/fetcher/connections/prefetch/ArrayViewIterator.ts @@ -0,0 +1,49 @@ +import { AsyncIterator } from "asynciterator"; + +export default class ArrayViewIterator extends AsyncIterator { + + private readonly source: T[]; + private readonly startIndex: number; + private readonly stopIndex: number; + private readonly step: number; + + private currentIndex: number; + + constructor(source: T[], startIndex: number, stopIndex: number, step: -1 | 1) { + super(); + + console.log("ArrayViewIterator"); + + this.source = source; + this.startIndex = startIndex; + this.stopIndex = stopIndex; + this.step = step; + + if (step > 0 ? stopIndex < startIndex : stopIndex > startIndex) { + this.close(); + return; + } + + this.currentIndex = startIndex; + this.readable = true; + } + + public read(): T { + if (this.closed) { + return null; + } + + const {source, step, currentIndex, stopIndex} = this; + + if (step > 0 ? currentIndex > stopIndex : currentIndex < stopIndex) { + this.close(); + return null; + } + + const item = source[currentIndex]; + + this.currentIndex += step; + + return item; + } +} diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 2d8e4ee0..c42a2b7e 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -5,6 +5,7 @@ import EventType from "../../../enums/EventType"; import BinarySearch from "../../../util/BinarySearch"; import IConnection from "../IConnection"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; +import ArrayViewIterator from "./ArrayViewIterator"; import ExpandingIterator from "./ExpandingIterator"; interface IDeferredBackwardView { @@ -220,14 +221,11 @@ export default class ConnectionsStore { const lowerBoundIndex = this.getLowerBoundIndex(lowerBoundDate); const upperBoundIndex = this.getUpperBoundIndex(upperBoundDate); - const indexIteratorOptions: IntegerIteratorOptions = { - start: backward ? upperBoundIndex : lowerBoundIndex, - end: backward ? lowerBoundIndex : upperBoundIndex, - step: backward ? -1 : 1, - }; + const start = backward ? upperBoundIndex : lowerBoundIndex; + const stop = backward ? lowerBoundIndex : upperBoundIndex; + const step = backward ? -1 : 1; - const iterator = new IntegerIterator(indexIteratorOptions) - .map((index) => this.store[index]); + const iterator = new ArrayViewIterator(this.store, start, stop, step); return { iterator, lowerBoundIndex, upperBoundIndex }; } From 221a03278849beceac36639e640a91d4559c619b Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 31 Jan 2019 12:17:03 +0100 Subject: [PATCH 52/69] 1. catalog delijn -> https 2. example -> remove green lines 3. csa EAT -> added max transfer duration. --- example/js/index.js | 31 ++++++++++++++----- src/catalog.delijn.ts | 20 ++++++------ .../public-transport/CSAEarliestArrival.ts | 28 ++++++++++++++--- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/example/js/index.js b/example/js/index.js index 63ecf628..afb9ead5 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -48,6 +48,20 @@ const removeResultObjects = () => { resultObjects = []; }; +const removePrefetchView = () => { + const view = document.getElementById("prefetch"); + + if(!view.hasChildNodes()) { + return; + } + + for (const child of [...view.childNodes]) { + if (!child.id) { + child.parentNode.removeChild(child); + } + } +}; + resetButton.onclick = e => { removeLines(); removeResultObjects(); @@ -61,6 +75,8 @@ resetButton.onclick = e => { if (plannerResult) { plannerResult.close(); } + + removePrefetchView(); }; const pxPerMs = .00005; @@ -93,13 +109,14 @@ planner console.log("Query", query); }) .on("sub-query", query => { - const { minimumDepartureTime, maximumArrivalTime } = query; + const { minimumDepartureTime, maximumArrivalTime, maximumTravelDuration } = query; console.log( "[Subquery]", minimumDepartureTime, maximumArrivalTime, - maximumArrivalTime - minimumDepartureTime + maximumArrivalTime - minimumDepartureTime, + maximumTravelDuration / 1,66667e-5, ); removeLines(); @@ -399,16 +416,16 @@ function runQuery(query) { }) .on("end", () => { if (i < amount) { - const noMore = document.createElement('div'); - noMore.className = 'path'; - noMore.style.padding = '10px'; - noMore.innerHTML = 'No more results'; + const noMore = document.createElement("div"); + noMore.className = "path"; + noMore.style.padding = "10px"; + noMore.innerHTML = "No more results"; results.appendChild(noMore); } }); }) .catch((error) => { - console.error(error) + console.error(error); }); } diff --git a/src/catalog.delijn.ts b/src/catalog.delijn.ts index 5db450d4..386a6ab1 100644 --- a/src/catalog.delijn.ts +++ b/src/catalog.delijn.ts @@ -5,16 +5,16 @@ import TravelMode from "./enums/TravelMode"; const catalogDeLijn = new Catalog(); -catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Antwerpen/stops"); -catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Limburg/stops"); -catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops"); -catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/stops"); -catalogDeLijn.addStopsSource("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); +catalogDeLijn.addStopsSource("https://openplanner.ilabt.imec.be/delijn/Antwerpen/stops"); +catalogDeLijn.addStopsSource("https://openplanner.ilabt.imec.be/delijn/Limburg/stops"); +catalogDeLijn.addStopsSource("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops"); +catalogDeLijn.addStopsSource("https://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/stops"); +catalogDeLijn.addStopsSource("https://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); -catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Antwerpen/connections", TravelMode.Bus); -catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Limburg/connections", TravelMode.Bus); -catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); -catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/connections", TravelMode.Bus); -catalogDeLijn.addConnectionsSource("http://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("https://openplanner.ilabt.imec.be/delijn/Antwerpen/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("https://openplanner.ilabt.imec.be/delijn/Limburg/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("https://openplanner.ilabt.imec.be/delijn/Vlaams-Brabant/connections", TravelMode.Bus); +catalogDeLijn.addConnectionsSource("https://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/connections", TravelMode.Bus); export default catalogDeLijn; diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index ef05b18b..14496087 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -146,13 +146,15 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { if (connection) { this.discoverConnection(connection); - const arrivalStopId: string = this.query.to[0].id; + const {to, minimumDepartureTime, minimumTransferDuration, maximumTransferDuration} = this.query; + + const arrivalStopId: string = to[0].id; if (this.profilesByStop[arrivalStopId].arrivalTime <= connection.departureTime.getTime()) { this.connectionsIterator.close(); done(); } - if (connection.departureTime < this.query.minimumDepartureTime) { + if (connection.departureTime < minimumDepartureTime) { await this.maybeProcessNextConnection(done); return; } @@ -161,8 +163,24 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { for (const tripId of tripIds) { const canRemainSeated = this.enterConnectionByTrip[tripId]; - const canTakeTransfer = (this.profilesByStop[connection.departureStop].arrivalTime <= - connection.departureTime.getTime() && connection["gtfs:pickupType"] !== PickupType.NotAvailable); + + const departure = connection.departureTime.getTime(); + let arrival = this.profilesByStop[connection.departureStop].arrivalTime; + + const isInitialStop = this.initialReachableStops.findIndex(({stop}) => + stop.id === connection.departureStop, + ) > -1; + + if (isInitialStop) { + arrival = departure - minimumTransferDuration; + } + + const transferDuration = departure - arrival; + + const canTakeTransfer = ( + transferDuration >= minimumTransferDuration && transferDuration <= maximumTransferDuration && + connection["gtfs:pickupType"] !== PickupType.NotAvailable + ); if (canRemainSeated || canTakeTransfer) { this.updateTrips(connection, tripId); @@ -364,7 +382,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } const reachableStopArrival = this.profilesByStop[stop.id].arrivalTime; - const arrivalTime = connection.arrivalTime.getTime() + duration + this.query.minimumTransferDuration; + const arrivalTime = connection.arrivalTime.getTime() + duration; if (reachableStopArrival > arrivalTime) { const transferProfile = { From 7f66b6101ad157e84570ce938f51fbda67d67e2e Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 31 Jan 2019 14:26:44 +0100 Subject: [PATCH 53/69] Rewrite FlatMapIterator to not use BufferedIterator --- example/js/index.js | 4 +- .../lazy/ConnectionsIteratorLazy.ts | 2 +- .../connections/prefetch/ArrayViewIterator.ts | 2 - .../connections/prefetch/ConnectionsStore.ts | 4 +- .../QueryRunnerEarliestArrivalFirst.ts | 5 +- .../exponential/QueryRunnerExponential.ts | 5 +- .../iterators}/ExpandingIterator.test.ts | 0 .../iterators}/ExpandingIterator.ts | 0 src/util/iterators/FlatMapIterator.test.ts | 6 +- src/util/iterators/FlatMapIterator.ts | 98 +++++++------------ 10 files changed, 50 insertions(+), 76 deletions(-) rename src/{fetcher/connections/prefetch => util/iterators}/ExpandingIterator.test.ts (100%) rename src/{fetcher/connections/prefetch => util/iterators}/ExpandingIterator.ts (100%) diff --git a/example/js/index.js b/example/js/index.js index 63ecf628..27c2b867 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -38,6 +38,7 @@ const removeLines = () => { } polyLines = []; + lines = []; }; const removeResultObjects = () => { @@ -127,7 +128,6 @@ planner }) .on("added-new-transfer-profile", ({ departureStop, arrivalStop, amountOfTransfers }) => { - requestAnimationFrame(() => { const newLine = [ [departureStop.latitude, departureStop.longitude], [arrivalStop.latitude, arrivalStop.longitude] @@ -153,8 +153,6 @@ planner lines.push(newLine); polyLines.push(polyline); } - }); - } ) .on("connection-prefetch", (departureTime) => { diff --git a/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts index 9cbf5d2e..c67fbe78 100644 --- a/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts +++ b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts @@ -37,7 +37,7 @@ export default class ConnectionsIteratorLazy extends FlatMapIterator { const connectionsParser = new ConnectionsPageParser(page.documentIri, page.triples); - return Promise.resolve(new ArrayIterator(connectionsParser.getConnections(travelMode))); + return new ArrayIterator(connectionsParser.getConnections(travelMode)); }; super(pageIterator, parsePageConnections); diff --git a/src/fetcher/connections/prefetch/ArrayViewIterator.ts b/src/fetcher/connections/prefetch/ArrayViewIterator.ts index f0b855db..b8d0f856 100644 --- a/src/fetcher/connections/prefetch/ArrayViewIterator.ts +++ b/src/fetcher/connections/prefetch/ArrayViewIterator.ts @@ -12,8 +12,6 @@ export default class ArrayViewIterator extends AsyncIterator { constructor(source: T[], startIndex: number, stopIndex: number, step: -1 | 1) { super(); - console.log("ArrayViewIterator"); - this.source = source; this.startIndex = startIndex; this.stopIndex = stopIndex; diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index c42a2b7e..2eb5f6d3 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -1,12 +1,12 @@ -import { ArrayIterator, AsyncIterator, IntegerIterator, IntegerIteratorOptions } from "asynciterator"; +import { ArrayIterator, AsyncIterator } from "asynciterator"; import { PromiseProxyIterator } from "asynciterator-promiseproxy"; import Context from "../../../Context"; import EventType from "../../../enums/EventType"; import BinarySearch from "../../../util/BinarySearch"; +import ExpandingIterator from "../../../util/iterators/ExpandingIterator"; import IConnection from "../IConnection"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; import ArrayViewIterator from "./ArrayViewIterator"; -import ExpandingIterator from "./ExpandingIterator"; interface IDeferredBackwardView { lowerBoundDate: Date; diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts index 8e51f5a6..789fc497 100644 --- a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts @@ -1,4 +1,5 @@ import { AsyncIterator } from "asynciterator"; +import { PromiseProxyIterator } from "asynciterator-promiseproxy"; import { inject, injectable, interfaces, tagged } from "inversify"; import Context from "../../Context"; import Defaults from "../../Defaults"; @@ -129,12 +130,12 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { } } - private async runSubquery(query: IResolvedQuery): Promise> { + private runSubquery(query: IResolvedQuery): AsyncIterator { this.context.emit(EventType.SubQuery, query); const planner = this.publicTransportPlannerFactory() as IPublicTransportPlanner; - return planner.plan(query); + return new PromiseProxyIterator(() => planner.plan(query)); } private async resolveEndpoint(endpoint: string | string[] | ILocation | ILocation[]): Promise { diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index bc961c90..60f55bf1 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -1,4 +1,5 @@ import { AsyncIterator } from "asynciterator"; +import { PromiseProxyIterator } from "asynciterator-promiseproxy"; import { inject, injectable, interfaces } from "inversify"; import Context from "../../Context"; import Defaults from "../../Defaults"; @@ -68,13 +69,13 @@ export default class QueryRunnerExponential implements IQueryRunner { } } - private async runSubquery(query: IResolvedQuery): Promise> { + private runSubquery(query: IResolvedQuery): AsyncIterator { // TODO investigate if publicTransportPlanner can be reused or reuse some of its aggregated data this.context.emit(EventType.SubQuery, query); const planner = this.publicTransportPlannerFactory() as IPublicTransportPlanner; - return planner.plan(query); + return new PromiseProxyIterator(() => planner.plan(query)); } private async resolveEndpoint(endpoint: string | string[] | ILocation | ILocation[]): Promise { diff --git a/src/fetcher/connections/prefetch/ExpandingIterator.test.ts b/src/util/iterators/ExpandingIterator.test.ts similarity index 100% rename from src/fetcher/connections/prefetch/ExpandingIterator.test.ts rename to src/util/iterators/ExpandingIterator.test.ts diff --git a/src/fetcher/connections/prefetch/ExpandingIterator.ts b/src/util/iterators/ExpandingIterator.ts similarity index 100% rename from src/fetcher/connections/prefetch/ExpandingIterator.ts rename to src/util/iterators/ExpandingIterator.ts diff --git a/src/util/iterators/FlatMapIterator.test.ts b/src/util/iterators/FlatMapIterator.test.ts index dca3e0a4..c9cb4d80 100644 --- a/src/util/iterators/FlatMapIterator.test.ts +++ b/src/util/iterators/FlatMapIterator.test.ts @@ -12,11 +12,9 @@ describe("[FlatMapIterator]", () => { const queryIterator = new QueryIterator([1, 2, 3], 10); const flatMapIterator = new FlatMapIterator(queryIterator, (num) => { - return new Promise((resolve) => { - const array = Array(num).fill(ALPHABET[num - 1]); + const array = Array(num).fill(ALPHABET[num - 1]); - resolve(new ResultIterator(array, 10)); - }); + return new ResultIterator(array, 10); }); let current = 0; diff --git a/src/util/iterators/FlatMapIterator.ts b/src/util/iterators/FlatMapIterator.ts index e8f74912..59eb7f4d 100644 --- a/src/util/iterators/FlatMapIterator.ts +++ b/src/util/iterators/FlatMapIterator.ts @@ -1,4 +1,4 @@ -import { AsyncIterator, BufferedIterator } from "asynciterator"; +import { AsyncIterator } from "asynciterator"; /** * This AsyncIterator maps every item of a query AsyncIterator to a result AsyncIterator by passing it through a @@ -18,16 +18,15 @@ import { AsyncIterator, BufferedIterator } from "asynciterator"; * +-----------------------------------------------+ * ``` */ -export default class FlatMapIterator extends BufferedIterator { +export default class FlatMapIterator extends AsyncIterator { private queryIterator: AsyncIterator; - private callback: (query: Q) => Promise>; + private callback: (query: Q) => AsyncIterator; private currentResultIterator: AsyncIterator; - private currentResultPushed: number; private isLastResultIterator = false; - constructor(queryIterator: AsyncIterator, run: (query: Q) => Promise>) { - super({maxBufferSize: 1, autoStart: false}); + constructor(queryIterator: AsyncIterator, run: (query: Q) => AsyncIterator) { + super(); this.queryIterator = queryIterator; this.callback = run; @@ -35,78 +34,57 @@ export default class FlatMapIterator extends BufferedIterator { this.queryIterator.once("end", () => { this.isLastResultIterator = true; }); + + this.readable = true; } - public _read(count: number, done: () => void) { + public read(): R { + if (this.closed) { + return null; + } + if (!this.currentResultIterator) { - const query = this.queryIterator.read(); + const query: Q = this.queryIterator.read(); if (query) { - this.runSubquery(query, done); + this.runQuery(query); } else { - this.waitForSubquery(done); + this.readable = false; + this.queryIterator.once("readable", () => { + this.readable = true; + }); } - - return; } - this.pushItemsAsync(done); - } + if (this.currentResultIterator) { + const item = this.currentResultIterator.read(); - private runSubquery(subquery: Q, done: () => void) { - const self = this; - - this.callback(subquery) - .then((resultIterator: AsyncIterator) => { - self.currentResultIterator = resultIterator; - self.currentResultPushed = 0; - - self.currentResultIterator.once("end", () => { - delete self.currentResultIterator; - - // Close if last iterator - if (self.isLastResultIterator) { - self.close(); - } + if (!item) { + this.readable = false; + } - // Iterator was empty - if (self.currentResultPushed === 0 && !this.closed) { - self._read(null, done); - } - }); + return item; + } - this.pushItemsAsync(done); - }); + return null; } - private waitForSubquery(done: () => void) { - this.queryIterator.once("readable", () => { - const query = this.queryIterator.read(); + private runQuery(query: Q) { + this.currentResultIterator = this.callback(query); + this.readable = this.currentResultIterator.readable; - this.runSubquery(query, done); + this.currentResultIterator.on("readable", () => { + this.readable = true; }); - } - - private pushItemsAsync(done) { - this.currentResultIterator.once("readable", () => { - this.pushItemsSync(); - done(); - }); - } - private pushItemsSync(): boolean { - let item = this.currentResultIterator.read(); - let hasPushed = false; - - while (item) { - this._push(item); - this.currentResultPushed++; - hasPushed = true; - - item = this.currentResultIterator.read(); - } + this.currentResultIterator.on("end", () => { + if (this.isLastResultIterator) { + this.close(); + } - return hasPushed; + this.currentResultIterator = null; + this.readable = true; + }); } } From d713d9060083f184043d95c71461d420c7acce04 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 31 Jan 2019 14:45:43 +0100 Subject: [PATCH 54/69] 1. csa EAT possible initial and arrival walking fixes. --- example/js/index.js | 7 +- .../public-transport/CSAEarliestArrival.ts | 69 ++++++++++++++----- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/example/js/index.js b/example/js/index.js index afb9ead5..45f0ebd8 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -37,6 +37,7 @@ const removeLines = () => { line.remove(); } + lines = []; polyLines = []; }; @@ -259,9 +260,9 @@ function selectRoute(e, id) { map.on("click", onMapClick); function getRandomColor() { - var letters = "0123456789ABCDEF"; - var color = "#"; - for (var i = 0; i < 6; i++) { + const letters = "0123456789ABCDEF"; + let color = "#"; + for (let i = 0; i < 6; i++) { color += letters[Math.floor(Math.random() * 16)]; } return color; diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index 14496087..e0e2bc8b 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -146,7 +146,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { if (connection) { this.discoverConnection(connection); - const {to, minimumDepartureTime, minimumTransferDuration, maximumTransferDuration} = this.query; + const { to, minimumDepartureTime, minimumTransferDuration, maximumTransferDuration } = this.query; const arrivalStopId: string = to[0].id; if (this.profilesByStop[arrivalStopId].arrivalTime <= connection.departureTime.getTime()) { @@ -164,17 +164,17 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const canRemainSeated = this.enterConnectionByTrip[tripId]; - const departure = connection.departureTime.getTime(); - let arrival = this.profilesByStop[connection.departureStop].arrivalTime; - - const isInitialStop = this.initialReachableStops.findIndex(({stop}) => + const initialStop = this.initialReachableStops.find(({ stop }) => stop.id === connection.departureStop, - ) > -1; + ); - if (isInitialStop) { - arrival = departure - minimumTransferDuration; + if (initialStop) { + await this.updateInitialStopProfile(connection, initialStop, done); } + const departure = connection.departureTime.getTime(); + const arrival = this.profilesByStop[connection.departureStop].arrivalTime; + const transferDuration = departure - arrival; const canTakeTransfer = ( @@ -202,6 +202,44 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } } + private async updateInitialStopProfile(connection: IConnection, initialStop: IReachableStop, done: () => void) { + const { minimumDepartureTime, minimumTransferDuration } = this.query; + + const departureTime = connection.departureTime.getTime() - minimumTransferDuration - initialStop.duration; + + if (connection.departureTime < minimumDepartureTime) { + await this.maybeProcessNextConnection(done); + return; + } + + const arrivalTime = connection.departureTime.getTime() - minimumTransferDuration; + + this.profilesByStop[initialStop.stop.id] = { + departureTime, + arrivalTime, + }; + + if (initialStop.duration > 0) { + const path: Path = Path.create(); + + const footpath: IStep = Step.create( + this.query.from[0], + initialStop.stop, + TravelMode.Walking, + { + minimum: arrivalTime - departureTime, + }, + new Date(departureTime + minimumTransferDuration), + new Date(arrivalTime + minimumTransferDuration), + ); + + path.addStep(footpath); + + this.profilesByStop[initialStop.stop.id].path = path; + } + + } + private async initInitialReachableStops(): Promise { const fromLocation: IStop = this.query.from[0] as IStop; @@ -398,11 +436,10 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { this.profilesByStop[stop.id] = transferProfile; } - - this.checkIfArrivalStopIsReachable(connection, reachableStop); - }); + this.checkIfArrivalStopIsReachable(connection, arrivalStop); + } catch (e) { if (this.context) { this.context.emitWarning(e); @@ -410,19 +447,19 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } } - private checkIfArrivalStopIsReachable(connection: IConnection, { stop, duration }: IReachableStop): void { + private checkIfArrivalStopIsReachable(connection: IConnection, arrivalStop: ILocation): void { const canReachArrivalStop = this.finalReachableStops.find((reachableStop: IReachableStop) => { - return reachableStop.stop.id === stop.id; + return reachableStop.stop.id === arrivalStop.id; }); if (canReachArrivalStop && canReachArrivalStop.duration > 0) { - const departureTime = connection.arrivalTime.getTime() + duration; - const arrivalTime = connection.arrivalTime.getTime() + duration + canReachArrivalStop.duration; + const departureTime = connection.arrivalTime.getTime(); + const arrivalTime = connection.arrivalTime.getTime() + canReachArrivalStop.duration; const path: Path = Path.create(); const footpath: IStep = Step.create( - stop, + arrivalStop, this.query.to[0], TravelMode.Walking, { From 4d1a3f11d2a05c9703b2445213fbfbc64bebbfc6 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 31 Jan 2019 16:17:35 +0100 Subject: [PATCH 55/69] 1. fix connectionsIteratorLazy --- src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts index c67fbe78..d3c27ec3 100644 --- a/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts +++ b/src/fetcher/connections/lazy/ConnectionsIteratorLazy.ts @@ -37,7 +37,13 @@ export default class ConnectionsIteratorLazy extends FlatMapIterator { const connectionsParser = new ConnectionsPageParser(page.documentIri, page.triples); - return new ArrayIterator(connectionsParser.getConnections(travelMode)); + const connections = connectionsParser.getConnections(travelMode); + + if (options.backward) { + connections.reverse(); + } + + return new ArrayIterator(connections); }; super(pageIterator, parsePageConnections); From 9c52e08011fc39c7b338ea7a58a1429d3a5a42c7 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 31 Jan 2019 17:09:51 +0100 Subject: [PATCH 56/69] Immediately return asynciterator when calling Planner#query. Fixes #39 --- example/js/index.js | 306 ++++++++++++++++++++++---------------------- src/Planner.ts | 22 ++-- src/demo.ts | 44 +++---- 3 files changed, 181 insertions(+), 191 deletions(-) diff --git a/example/js/index.js b/example/js/index.js index 27c2b867..fe0d9cdf 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -128,31 +128,31 @@ planner }) .on("added-new-transfer-profile", ({ departureStop, arrivalStop, amountOfTransfers }) => { - const newLine = [ - [departureStop.latitude, departureStop.longitude], - [arrivalStop.latitude, arrivalStop.longitude] - ]; - - let lineExists = lines.length > 0 && lines - .some((line) => - line[0][0] === newLine[0][0] - && line[0][1] === newLine[0][1] - && line[1][0] === newLine[1][0] - && line[1][1] === newLine[1][1] - ); - - if (!lineExists) { - const polyline = new L.Polyline(newLine, { - color: "#000", - weight: 1, - smoothFactor: 1, - opacity: 0.5, - dashArray: "10 10" - }).addTo(map); - - lines.push(newLine); - polyLines.push(polyline); - } + const newLine = [ + [departureStop.latitude, departureStop.longitude], + [arrivalStop.latitude, arrivalStop.longitude] + ]; + + let lineExists = lines.length > 0 && lines + .some((line) => + line[0][0] === newLine[0][0] + && line[0][1] === newLine[0][1] + && line[1][0] === newLine[1][0] + && line[1][1] === newLine[1][1] + ); + + if (!lineExists) { + const polyline = new L.Polyline(newLine, { + color: "#000", + weight: 1, + smoothFactor: 1, + opacity: 0.5, + dashArray: "10 10" + }).addTo(map); + + lines.push(newLine); + polyLines.push(polyline); + } } ) .on("connection-prefetch", (departureTime) => { @@ -267,6 +267,9 @@ function runQuery(query) { resultObjects.push(departureCircle, arrivalCircle); + let i = 0; + let amount = 4; + planner .query({ publicTransportOnly: true, @@ -277,136 +280,129 @@ function runQuery(query) { maximumTransferDuration: 30 * 60 * 1000, // 30 minutes minimumWalkingSpeed: 3 }) - .then(publicTransportResult => { - plannerResult = publicTransportResult; - - let i = 0; - let amount = 4; - - publicTransportResult.take(amount) - .on("data", path => { - console.log("path", i, path); - i++; - - const color = getRandomColor(); - - const pathElement = document.createElement("div"); - pathElement.className = "path"; - - path.steps.forEach(step => { - const stepElement = document.createElement("div"); - stepElement.className = "step"; - - const travelMode = document.createElement("div"); - travelMode.className = "travelMode " + step.travelMode; - stepElement.appendChild(travelMode); - - const details = document.createElement("div"); - details.className = "details"; - stepElement.appendChild(details); - - const startLocation = document.createElement("div"); - startLocation.className = "startLocation"; - startLocation.innerHTML = - "Start location: " + step.startLocation.name; - details.appendChild(startLocation); - - if (step.startTime) { - const startTime = document.createElement("div"); - startTime.className = "startTime"; - startTime.innerHTML = step.startTime; - details.appendChild(startTime); - } - - if (step.enterConnectionId) { - const enterConnectionId = document.createElement("div"); - enterConnectionId.className = "enterConnectionId"; - enterConnectionId.innerHTML = - "Enter connection: " + step.enterConnectionId; - details.appendChild(enterConnectionId); - } - - if (step.duration) { - const duration = document.createElement("div"); - duration.className = "duration"; - duration.innerHTML = - "Duration: minimum " + - step.duration.minimum / (60 * 1000) + - "min"; - details.appendChild(duration); - } - - const stopLocation = document.createElement("div"); - stopLocation.className = "stopLocation"; - stopLocation.innerHTML = "Stop location: " + step.stopLocation.name; - details.appendChild(stopLocation); - - if (step.stopTime) { - const stopTime = document.createElement("div"); - stopTime.className = "stopTime"; - stopTime.innerHTML = step.stopTime; - details.appendChild(stopTime); - } - - if (step.exitConnectionId) { - const exitConnectionId = document.createElement("div"); - exitConnectionId.className = "exitConnectionId"; - exitConnectionId.innerHTML = - "Exit connection: " + step.exitConnectionId; - details.appendChild(exitConnectionId); - } - - pathElement.style.borderLeft = "5px solid " + color; - - pathElement.appendChild(stepElement); - }); - - results.appendChild(pathElement); - - path.steps.forEach(step => { - const { startLocation, stopLocation, travelMode } = step; - - const startMarker = L.marker([ - startLocation.latitude, - startLocation.longitude - ]).addTo(map); - - startMarker.bindPopup(startLocation.name); - - const stopMarker = L.marker([ - stopLocation.latitude, - stopLocation.longitude - ]).addTo(map); - - stopMarker.bindPopup(stopLocation.name); - const line = [ - [startLocation.latitude, startLocation.longitude], - [stopLocation.latitude, stopLocation.longitude] - ]; - - const polyline = new L.Polyline(line, { - color, - weight: 5, - smoothFactor: 1, - opacity: 0.7, - dashArray: travelMode === "walking" ? "8 8" : null - }).addTo(map); - - resultObjects.push(startMarker, stopMarker, polyline); - }); - }) - .on("end", () => { - if (i < amount) { - const noMore = document.createElement('div'); - noMore.className = 'path'; - noMore.style.padding = '10px'; - noMore.innerHTML = 'No more results'; - - results.appendChild(noMore); - } - }); + .take(amount) + .on("error", (error) => { + console.error(error); + }) + .on("data", path => { + console.log("path", i, path); + i++; + + const color = getRandomColor(); + + const pathElement = document.createElement("div"); + pathElement.className = "path"; + + path.steps.forEach(step => { + const stepElement = document.createElement("div"); + stepElement.className = "step"; + + const travelMode = document.createElement("div"); + travelMode.className = "travelMode " + step.travelMode; + stepElement.appendChild(travelMode); + + const details = document.createElement("div"); + details.className = "details"; + stepElement.appendChild(details); + + const startLocation = document.createElement("div"); + startLocation.className = "startLocation"; + startLocation.innerHTML = + "Start location: " + step.startLocation.name; + details.appendChild(startLocation); + + if (step.startTime) { + const startTime = document.createElement("div"); + startTime.className = "startTime"; + startTime.innerHTML = step.startTime; + details.appendChild(startTime); + } + + if (step.enterConnectionId) { + const enterConnectionId = document.createElement("div"); + enterConnectionId.className = "enterConnectionId"; + enterConnectionId.innerHTML = + "Enter connection: " + step.enterConnectionId; + details.appendChild(enterConnectionId); + } + + if (step.duration) { + const duration = document.createElement("div"); + duration.className = "duration"; + duration.innerHTML = + "Duration: minimum " + + step.duration.minimum / (60 * 1000) + + "min"; + details.appendChild(duration); + } + + const stopLocation = document.createElement("div"); + stopLocation.className = "stopLocation"; + stopLocation.innerHTML = "Stop location: " + step.stopLocation.name; + details.appendChild(stopLocation); + + if (step.stopTime) { + const stopTime = document.createElement("div"); + stopTime.className = "stopTime"; + stopTime.innerHTML = step.stopTime; + details.appendChild(stopTime); + } + + if (step.exitConnectionId) { + const exitConnectionId = document.createElement("div"); + exitConnectionId.className = "exitConnectionId"; + exitConnectionId.innerHTML = + "Exit connection: " + step.exitConnectionId; + details.appendChild(exitConnectionId); + } + + pathElement.style.borderLeft = "5px solid " + color; + + pathElement.appendChild(stepElement); + }); + + results.appendChild(pathElement); + + path.steps.forEach(step => { + const { startLocation, stopLocation, travelMode } = step; + + const startMarker = L.marker([ + startLocation.latitude, + startLocation.longitude + ]).addTo(map); + + startMarker.bindPopup(startLocation.name); + + const stopMarker = L.marker([ + stopLocation.latitude, + stopLocation.longitude + ]).addTo(map); + + stopMarker.bindPopup(stopLocation.name); + const line = [ + [startLocation.latitude, startLocation.longitude], + [stopLocation.latitude, stopLocation.longitude] + ]; + + const polyline = new L.Polyline(line, { + color, + weight: 5, + smoothFactor: 1, + opacity: 0.7, + dashArray: travelMode === "walking" ? "8 8" : null + }).addTo(map); + + resultObjects.push(startMarker, stopMarker, polyline); + }); }) - .catch((error) => { - console.error(error) + .on("end", () => { + if (i < amount) { + const noMore = document.createElement("div"); + noMore.className = "path"; + noMore.style.padding = "10px"; + noMore.innerHTML = "No more results"; + + results.appendChild(noMore); + } }); } diff --git a/src/Planner.ts b/src/Planner.ts index b8afea25..ef9108e5 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -1,4 +1,5 @@ import { AsyncIterator } from "asynciterator"; +import { PromiseProxyIterator } from "asynciterator-promiseproxy"; // @ts-ignore import { EventEmitter, Listener } from "events"; import Context from "./Context"; @@ -37,25 +38,22 @@ export default class Planner implements EventEmitter { * @param query An [[IQuery]] specifying a route planning query * @returns An AsyncIterator of [[IPath]] instances */ - public async query(query: IQuery): Promise> { + public query(query: IQuery): AsyncIterator { this.emit(EventType.Query, query); - try { - const iterator = await this.queryRunner.run(query); + const iterator = new PromiseProxyIterator(() => this.queryRunner.run(query)); - this.once(EventType.AbortQuery, () => { - iterator.close(); - }); + this.once(EventType.AbortQuery, () => { + iterator.close(); + }); - return iterator; - - } catch (e) { + iterator.on("error", (e) => { if (e && e.eventType) { - this.context.emit(e.eventType, e.message); + this.emit(e.eventType, e.message); } + }); - return Promise.reject(e); - } + return iterator; } public addListener(type: string | symbol, listener: Listener): this { diff --git a/src/demo.ts b/src/demo.ts index b2ce1bcb..d62fec00 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -62,6 +62,9 @@ export default async (logResults) => { console.log("Start query"); } + const amount = 3; + let i = 0; + planner.query({ publicTransportOnly: true, // from: "https://data.delijn.be/stops/201657", @@ -75,31 +78,24 @@ export default async (logResults) => { minimumDepartureTime: new Date(), maximumTransferDuration: Units.fromHours(0.5), }) - .then((publicTransportResult) => { - - const amount = 3; - let i = 0; - - publicTransportResult.take(amount) - .on("data", (path: IPath) => { - ++i; - - if (logResults) { - console.log(i); - console.log(JSON.stringify(path, null, " ")); - console.log("\n"); - } - - if (i === amount) { - resolve(true); - } - }) - .on("end", () => { - resolve(false); - }); - + .take(amount) + .on("error", (error) => { + resolve(false); + }) + .on("data", (path: IPath) => { + ++i; + + if (logResults) { + console.log(i); + console.log(JSON.stringify(path, null, " ")); + console.log("\n"); + } + + if (i === amount) { + resolve(true); + } }) - .catch(() => { + .on("end", () => { resolve(false); }); })); From a0292761f0b9533b12a83e5660f6e22786c3d12f Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 31 Jan 2019 17:17:45 +0100 Subject: [PATCH 57/69] 1. revert previous csa eat fix -> fixed initial footpaths inside journeyExtractor. --- src/planner/Path.ts | 9 ++++ .../public-transport/CSAEarliestArrival.ts | 45 +------------------ .../JourneyExtractorEarliestArrival.ts | 10 ++++- 3 files changed, 20 insertions(+), 44 deletions(-) diff --git a/src/planner/Path.ts b/src/planner/Path.ts index 0eecb3eb..3174b223 100644 --- a/src/planner/Path.ts +++ b/src/planner/Path.ts @@ -1,5 +1,6 @@ import IPath from "../interfaces/IPath"; import IStep from "../interfaces/IStep"; +import { DurationMs } from "../interfaces/units"; import Step from "./Step"; /** @@ -51,4 +52,12 @@ export default class Path implements IPath { public getStartLocationId(): string { return (" " + this.steps[0].startLocation.id).slice(1); } + + public addTime(duration: DurationMs): void { + this.steps = this.steps.map((step: IStep) => ({ + ...step, + startTime: new Date(step.startTime.getTime() + duration), + stopTime: new Date(step.stopTime.getTime() + duration), + })); + } } diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index e0e2bc8b..cc1d48f2 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -168,17 +168,14 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { stop.id === connection.departureStop, ); - if (initialStop) { - await this.updateInitialStopProfile(connection, initialStop, done); - } - const departure = connection.departureTime.getTime(); const arrival = this.profilesByStop[connection.departureStop].arrivalTime; const transferDuration = departure - arrival; const canTakeTransfer = ( - transferDuration >= minimumTransferDuration && transferDuration <= maximumTransferDuration && + (transferDuration >= minimumTransferDuration || initialStop && transferDuration >= 0) && + transferDuration <= maximumTransferDuration && connection["gtfs:pickupType"] !== PickupType.NotAvailable ); @@ -202,44 +199,6 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } } - private async updateInitialStopProfile(connection: IConnection, initialStop: IReachableStop, done: () => void) { - const { minimumDepartureTime, minimumTransferDuration } = this.query; - - const departureTime = connection.departureTime.getTime() - minimumTransferDuration - initialStop.duration; - - if (connection.departureTime < minimumDepartureTime) { - await this.maybeProcessNextConnection(done); - return; - } - - const arrivalTime = connection.departureTime.getTime() - minimumTransferDuration; - - this.profilesByStop[initialStop.stop.id] = { - departureTime, - arrivalTime, - }; - - if (initialStop.duration > 0) { - const path: Path = Path.create(); - - const footpath: IStep = Step.create( - this.query.from[0], - initialStop.stop, - TravelMode.Walking, - { - minimum: arrivalTime - departureTime, - }, - new Date(departureTime + minimumTransferDuration), - new Date(arrivalTime + minimumTransferDuration), - ); - - path.addStep(footpath); - - this.profilesByStop[initialStop.stop.id].path = path; - } - - } - private async initInitialReachableStops(): Promise { const fromLocation: IStop = this.query.from[0] as IStop; diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts index 3bd63c7e..45305f02 100644 --- a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts +++ b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts @@ -67,9 +67,17 @@ export default class JourneyExtractorEarliestArrival implements IJourneyExtracto } if (profilePath) { + currentStopId = profilePath.getStartLocationId(); + + if (currentStopId === departureStopId) { + const lastStep = path.steps[path.steps.length - 1]; + const timeToAdd = lastStep.startTime.getTime() - profilePath.steps[0].stopTime.getTime(); + + profilePath.addTime(timeToAdd); + } + path.addPath(profilePath); - currentStopId = profilePath.getStartLocationId(); currentProfile = profilesByStop[currentStopId]; } From ba07e8cc0e0871542ed3166402ad8c422bd9fff0 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 1 Feb 2019 09:32:06 +0100 Subject: [PATCH 58/69] 1. don't check maxTransferDuration on initial stop --- example/css/style.css | 4 ++++ src/planner/public-transport/CSAEarliestArrival.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/example/css/style.css b/example/css/style.css index 9f712f27..a3968201 100644 --- a/example/css/style.css +++ b/example/css/style.css @@ -57,6 +57,10 @@ body { background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ0cmFpbiIgY2xhc3M9InN2Zy1pbmxpbmUtLWZhIGZhLXRyYWluIGZhLXctMTQiIHJvbGU9ImltZyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDQ4IDUxMiI+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNDQ4IDk2djI1NmMwIDUxLjgxNS02MS42MjQgOTYtMTMwLjAyMiA5Nmw2Mi45OCA0OS43MjFDMzg2LjkwNSA1MDIuNDE3IDM4My41NjIgNTEyIDM3NiA1MTJINzJjLTcuNTc4IDAtMTAuODkyLTkuNTk0LTQuOTU3LTE0LjI3OUwxMzAuMDIyIDQ0OEM2MS44MiA0NDggMCA0MDMuOTU0IDAgMzUyVjk2QzAgNDIuOTgxIDY0IDAgMTI4IDBoMTkyYzY1IDAgMTI4IDQyLjk4MSAxMjggOTZ6bS00OCAxMzZWMTIwYzAtMTMuMjU1LTEwLjc0NS0yNC0yNC0yNEg3MmMtMTMuMjU1IDAtMjQgMTAuNzQ1LTI0IDI0djExMmMwIDEzLjI1NSAxMC43NDUgMjQgMjQgMjRoMzA0YzEzLjI1NSAwIDI0LTEwLjc0NSAyNC0yNHptLTE3NiA2NGMtMzAuOTI4IDAtNTYgMjUuMDcyLTU2IDU2czI1LjA3MiA1NiA1NiA1NiA1Ni0yNS4wNzIgNTYtNTYtMjUuMDcyLTU2LTU2LTU2eiI+PC9wYXRoPjwvc3ZnPg==); } +.travelMode.bus { + background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJidXMiIGNsYXNzPSJzdmctaW5saW5lLS1mYSBmYS1idXMgZmEtdy0xNiIgcm9sZT0iaW1nIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik00ODggMTI4aC04VjgwYzAtNDQuOC05OS4yLTgwLTIyNC04MFMzMiAzNS4yIDMyIDgwdjQ4aC04Yy0xMy4yNSAwLTI0IDEwLjc0LTI0IDI0djgwYzAgMTMuMjUgMTAuNzUgMjQgMjQgMjRoOHYxNjBjMCAxNy42NyAxNC4zMyAzMiAzMiAzMnYzMmMwIDE3LjY3IDE0LjMzIDMyIDMyIDMyaDMyYzE3LjY3IDAgMzItMTQuMzMgMzItMzJ2LTMyaDE5MnYzMmMwIDE3LjY3IDE0LjMzIDMyIDMyIDMyaDMyYzE3LjY3IDAgMzItMTQuMzMgMzItMzJ2LTMyaDYuNGMxNiAwIDI1LjYtMTIuOCAyNS42LTI1LjZWMjU2aDhjMTMuMjUgMCAyNC0xMC43NSAyNC0yNHYtODBjMC0xMy4yNi0xMC43NS0yNC0yNC0yNHpNMTEyIDQwMGMtMTcuNjcgMC0zMi0xNC4zMy0zMi0zMnMxNC4zMy0zMiAzMi0zMiAzMiAxNC4zMyAzMiAzMi0xNC4zMyAzMi0zMiAzMnptMTYtMTEyYy0xNy42NyAwLTMyLTE0LjMzLTMyLTMyVjEyOGMwLTE3LjY3IDE0LjMzLTMyIDMyLTMyaDI1NmMxNy42NyAwIDMyIDE0LjMzIDMyIDMydjEyOGMwIDE3LjY3LTE0LjMzIDMyLTMyIDMySDEyOHptMjcyIDExMmMtMTcuNjcgMC0zMi0xNC4zMy0zMi0zMnMxNC4zMy0zMiAzMi0zMiAzMiAxNC4zMyAzMiAzMi0xNC4zMyAzMi0zMiAzMnoiPjwvcGF0aD48L3N2Zz4=); +} + .duration { margin-bottom: 5px; margin-top: 5px; diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index cc1d48f2..a5419052 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -175,7 +175,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const canTakeTransfer = ( (transferDuration >= minimumTransferDuration || initialStop && transferDuration >= 0) && - transferDuration <= maximumTransferDuration && + (transferDuration <= maximumTransferDuration || initialStop && transferDuration >= 0) && connection["gtfs:pickupType"] !== PickupType.NotAvailable ); From cc6cacfd1d606d383468409021fb9195ee1ee086 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 1 Feb 2019 11:11:26 +0100 Subject: [PATCH 59/69] 1. simplify CSA processNextConnection 2. StopsProvider cache stops. --- example/css/style.css | 1 + example/js/index.js | 62 ++++++++++--------- src/fetcher/stops/StopsProviderDefault.ts | 12 +++- .../public-transport/CSAEarliestArrival.ts | 43 +++++++------ src/planner/public-transport/CSAProfile.ts | 29 ++++----- 5 files changed, 81 insertions(+), 66 deletions(-) diff --git a/example/css/style.css b/example/css/style.css index a3968201..ee5b7fb0 100644 --- a/example/css/style.css +++ b/example/css/style.css @@ -42,6 +42,7 @@ body { .travelMode { width: 45px; + flex-shrink: 0; background-size: 20px 20px; background-repeat: no-repeat; } diff --git a/example/js/index.js b/example/js/index.js index 9f530153..515ac5c0 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -53,7 +53,7 @@ const removeResultObjects = () => { const removePrefetchView = () => { const view = document.getElementById("prefetch"); - if(!view.hasChildNodes()) { + if (!view.hasChildNodes()) { return; } @@ -118,7 +118,7 @@ planner minimumDepartureTime, maximumArrivalTime, maximumArrivalTime - minimumDepartureTime, - maximumTravelDuration / 1,66667e-5, + maximumTravelDuration / 1, 66667e-5 ); removeLines(); @@ -146,31 +146,31 @@ planner }) .on("added-new-transfer-profile", ({ departureStop, arrivalStop, amountOfTransfers }) => { - const newLine = [ - [departureStop.latitude, departureStop.longitude], - [arrivalStop.latitude, arrivalStop.longitude] - ]; - - let lineExists = lines.length > 0 && lines - .some((line) => - line[0][0] === newLine[0][0] - && line[0][1] === newLine[0][1] - && line[1][0] === newLine[1][0] - && line[1][1] === newLine[1][1] - ); - - if (!lineExists) { - const polyline = new L.Polyline(newLine, { - color: "#000", - weight: 1, - smoothFactor: 1, - opacity: 0.5, - dashArray: "10 10" - }).addTo(map); - - lines.push(newLine); - polyLines.push(polyline); - } + const newLine = [ + [departureStop.latitude, departureStop.longitude], + [arrivalStop.latitude, arrivalStop.longitude] + ]; + + let lineExists = lines.length > 0 && lines + .some((line) => + line[0][0] === newLine[0][0] + && line[0][1] === newLine[0][1] + && line[1][0] === newLine[1][0] + && line[1][1] === newLine[1][1] + ); + + if (!lineExists) { + const polyline = new L.Polyline(newLine, { + color: "#000", + weight: 1, + smoothFactor: 1, + opacity: 0.5, + dashArray: "10 10" + }).addTo(map); + + lines.push(newLine); + polyLines.push(polyline); + } } ) .on("connection-prefetch", (departureTime) => { @@ -269,18 +269,20 @@ function getRandomColor() { function runQuery(query) { console.log(query); + const maximumWalkingDistance = 200; + const departureCircle = L.circle([query[0].latitude, query[0].longitude], { color: "limegreen", fillColor: "limegreen", fillOpacity: 0.5, - radius: 200 + radius: maximumWalkingDistance }).addTo(map); const arrivalCircle = L.circle([query[1].latitude, query[1].longitude], { color: "red", fillColor: "red", fillOpacity: 0.5, - radius: 200 + radius: maximumWalkingDistance }).addTo(map); resultObjects.push(departureCircle, arrivalCircle); @@ -291,7 +293,7 @@ function runQuery(query) { from: query[0], // Brussels North to: query[1], // Ghent-Sint-Pieters minimumDepartureTime: new Date(), - maximumWalkingDistance: 200, + maximumWalkingDistance, maximumTransferDuration: 30 * 60 * 1000, // 30 minutes minimumWalkingSpeed: 3 }) diff --git a/src/fetcher/stops/StopsProviderDefault.ts b/src/fetcher/stops/StopsProviderDefault.ts index 7df172c2..a7fa1cea 100644 --- a/src/fetcher/stops/StopsProviderDefault.ts +++ b/src/fetcher/stops/StopsProviderDefault.ts @@ -9,12 +9,14 @@ import IStopsProvider from "./IStopsProvider"; export default class StopsProviderDefault implements IStopsProvider { private readonly stopsFetchers: IStopsFetcher[]; + private cachedStops: IStop[]; constructor( @inject(TYPES.StopsFetcherFactory) stopsFetcherFactory: StopsFetcherFactory, @inject(TYPES.Catalog) catalog: Catalog, ) { this.stopsFetchers = []; + this.cachedStops = []; for (const { accessUrl } of catalog.stopsSourceConfigs) { this.stopsFetchers.push(stopsFetcherFactory(accessUrl)); @@ -34,8 +36,16 @@ export default class StopsProviderDefault implements IStopsProvider { } public async getAllStops(): Promise { + if (this.cachedStops.length > 0) { + return Promise.resolve(this.cachedStops); + } + return Promise.all(this.stopsFetchers .map((stopsFetcher: IStopsFetcher) => stopsFetcher.getAllStops()), - ).then((results: IStop[][]) => [].concat(...results)); + ).then((results: IStop[][]) => { + this.cachedStops = [].concat(...results); + + return this.cachedStops; + }); } } diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index a5419052..714f9113 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -141,9 +141,9 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } private async processNextConnection(done: () => void) { - const connection = this.connectionsIterator.read(); + let connection = this.connectionsIterator.read(); - if (connection) { + while (connection) { this.discoverConnection(connection); const { to, minimumDepartureTime, minimumTransferDuration, maximumTransferDuration } = this.query; @@ -152,11 +152,12 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { if (this.profilesByStop[arrivalStopId].arrivalTime <= connection.departureTime.getTime()) { this.connectionsIterator.close(); done(); + break; } - if (connection.departureTime < minimumDepartureTime) { - await this.maybeProcessNextConnection(done); - return; + if (connection.departureTime < minimumDepartureTime && !this.connectionsIterator.closed) { + connection = this.connectionsIterator.read(); + continue; } const tripIds = this.getTripIdsFromConnection(connection); @@ -189,13 +190,12 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } - await this.maybeProcessNextConnection(done); - } - } + if (!this.connectionsIterator.closed) { + connection = this.connectionsIterator.read(); + continue; + } - private async maybeProcessNextConnection(done: () => void) { - if (!this.connectionsIterator.closed) { - await this.processNextConnection(done); + connection = undefined; } } @@ -312,14 +312,19 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { private discoverConnection(connection: IConnection) { this.setTripIdsByConnectionId(connection); - [connection.departureStop, connection.arrivalStop].forEach((stop) => { - if (!this.profilesByStop[stop]) { - this.profilesByStop[stop] = { - departureTime: Infinity, - arrivalTime: Infinity, - }; - } - }); + if (!this.profilesByStop[connection.departureStop]) { + this.profilesByStop[connection.departureStop] = { + departureTime: Infinity, + arrivalTime: Infinity, + }; + } + + if (!this.profilesByStop[connection.arrivalStop]) { + this.profilesByStop[connection.arrivalStop] = { + departureTime: Infinity, + arrivalTime: Infinity, + }; + } } private getTripIdsFromConnection(connection: IConnection): string[] { diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index 3613ec07..ba0b4666 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -144,19 +144,19 @@ export default class CSAProfile implements IPublicTransportPlanner { }) as Promise>; } - private processNextConnection(done: () => void) { - const connection = this.connectionsIterator.read(); + private async processNextConnection(done: () => void) { + let connection = this.connectionsIterator.read(); - if (connection) { - if (connection.arrivalTime > this.query.maximumArrivalTime) { - this.maybeProcessNextConnection(done); - return; + while (connection) { + if (connection.arrivalTime > this.query.maximumArrivalTime && !this.connectionsIterator.closed) { + connection = this.connectionsIterator.read(); + continue; } if (connection.departureTime < this.query.minimumDepartureTime) { this.connectionsIterator.close(); done(); - return; + break; } if (this.context) { @@ -169,18 +169,15 @@ export default class CSAProfile implements IPublicTransportPlanner { this.updateEarliestArrivalByTrip(connection, earliestArrivalTime); if (!this.isDominated(connection, earliestArrivalTime)) { - this.getFootpathsForDepartureStop(connection, earliestArrivalTime) - .then(() => this.maybeProcessNextConnection(done)); + await this.getFootpathsForDepartureStop(connection, earliestArrivalTime); + } - } else { - this.maybeProcessNextConnection(done); + if (!this.connectionsIterator.closed) { + connection = this.connectionsIterator.read(); + continue; } - } - } - private maybeProcessNextConnection(done: () => void) { - if (!this.connectionsIterator.closed) { - this.processNextConnection(done); + connection = undefined; } } From 8ac7792acd73e30bc2ebaedede0fa5c6e8c54e18 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 1 Feb 2019 11:36:16 +0100 Subject: [PATCH 60/69] Added some path details to result panel in example --- example/css/style.css | 134 +++++++++++----------- example/js/index.js | 257 +++++++++++++++++++++++++----------------- 2 files changed, 224 insertions(+), 167 deletions(-) diff --git a/example/css/style.css b/example/css/style.css index 9f712f27..fe0f3900 100644 --- a/example/css/style.css +++ b/example/css/style.css @@ -1,110 +1,116 @@ html, body { - margin: 0; - height: 100%; - width: 100%; + margin: 0; + height: 100%; + width: 100%; } #mapid { - height: 100%; + height: 100%; } #results { - position: absolute; - left: 10px; - bottom: 10px; - border-radius: 4px; - display: flex; - flex-direction: column; - font-family: sans-serif; - max-height: 500px; - z-index: 500; - overflow-y: scroll; - width: 500px; - display: flex; - flex-direction: column; -} - -.path { - margin: 10px; - background: rgba(255,255,255,.8); - border: 1px solid #ccc; - border-radius: 4px; - box-shadow: 0 4px 10px -2px rgba(0, 0, 0, 0.2); - flex-wrap: wrap; -} - -.step { - display: flex; - flex-direction: row; - margin: 10px; + position: absolute; + left: 10px; + bottom: 10px; + border-radius: 4px; + display: flex; + flex-direction: column; + font-family: sans-serif; + max-height: 500px; + z-index: 500; + overflow-y: scroll; + width: 500px; +} + +#results > .path { + margin: 10px; + background: rgba(255, 255, 255, .8); + border: 1px solid #ccc; + border-top: none; + border-radius: 4px; + box-shadow: 0 4px 10px -2px rgba(0, 0, 0, 0.2); + flex-wrap: wrap; +} + +#results > .path > .header { + padding: 10px; + background: rgba(0, 0, 0, .25); + color: #fff; + text-shadow: 0 0 1px black; +} + +#results > .path > .step { + display: flex; + flex-direction: row; + margin: 10px; } .travelMode { - width: 45px; - background-size: 20px 20px; - background-repeat: no-repeat; + width: 45px; + background-size: 20px 20px; + background-repeat: no-repeat; } .details { } .travelMode.walking { - background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ3YWxraW5nIiBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtd2Fsa2luZyBmYS13LTEwIiByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDMyMCA1MTIiPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTIwOCA5NmMyNi41IDAgNDgtMjEuNSA0OC00OFMyMzQuNSAwIDIwOCAwcy00OCAyMS41LTQ4IDQ4IDIxLjUgNDggNDggNDh6bTk0LjUgMTQ5LjFsLTIzLjMtMTEuOC05LjctMjkuNGMtMTQuNy00NC42LTU1LjctNzUuOC0xMDIuMi03NS45LTM2LS4xLTU1LjkgMTAuMS05My4zIDI1LjItMjEuNiA4LjctMzkuMyAyNS4yLTQ5LjcgNDYuMkwxNy42IDIxM2MtNy44IDE1LjgtMS41IDM1IDE0LjIgNDIuOSAxNS42IDcuOSAzNC42IDEuNSA0Mi41LTE0LjNMODEgMjI4YzMuNS03IDkuMy0xMi41IDE2LjUtMTUuNGwyNi44LTEwLjgtMTUuMiA2MC43Yy01LjIgMjAuOC40IDQyLjkgMTQuOSA1OC44bDU5LjkgNjUuNGM3LjIgNy45IDEyLjMgMTcuNCAxNC45IDI3LjdsMTguMyA3My4zYzQuMyAxNy4xIDIxLjcgMjcuNiAzOC44IDIzLjMgMTcuMS00LjMgMjcuNi0yMS43IDIzLjMtMzguOGwtMjIuMi04OWMtMi42LTEwLjMtNy43LTE5LjktMTQuOS0yNy43bC00NS41LTQ5LjcgMTcuMi02OC43IDUuNSAxNi41YzUuMyAxNi4xIDE2LjcgMjkuNCAzMS43IDM3bDIzLjMgMTEuOGMxNS42IDcuOSAzNC42IDEuNSA0Mi41LTE0LjMgNy43LTE1LjcgMS40LTM1LjEtMTQuMy00M3pNNzMuNiAzODUuOGMtMy4yIDguMS04IDE1LjQtMTQuMiAyMS41bC01MCA1MC4xYy0xMi41IDEyLjUtMTIuNSAzMi44IDAgNDUuM3MzMi43IDEyLjUgNDUuMiAwbDU5LjQtNTkuNGM2LjEtNi4xIDEwLjktMTMuNCAxNC4yLTIxLjVsMTMuNS0zMy44Yy01NS4zLTYwLjMtMzguNy00MS44LTQ3LjQtNTMuN2wtMjAuNyA1MS41eiI+PC9wYXRoPjwvc3ZnPg==); + background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ3YWxraW5nIiBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtd2Fsa2luZyBmYS13LTEwIiByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDMyMCA1MTIiPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTIwOCA5NmMyNi41IDAgNDgtMjEuNSA0OC00OFMyMzQuNSAwIDIwOCAwcy00OCAyMS41LTQ4IDQ4IDIxLjUgNDggNDggNDh6bTk0LjUgMTQ5LjFsLTIzLjMtMTEuOC05LjctMjkuNGMtMTQuNy00NC42LTU1LjctNzUuOC0xMDIuMi03NS45LTM2LS4xLTU1LjkgMTAuMS05My4zIDI1LjItMjEuNiA4LjctMzkuMyAyNS4yLTQ5LjcgNDYuMkwxNy42IDIxM2MtNy44IDE1LjgtMS41IDM1IDE0LjIgNDIuOSAxNS42IDcuOSAzNC42IDEuNSA0Mi41LTE0LjNMODEgMjI4YzMuNS03IDkuMy0xMi41IDE2LjUtMTUuNGwyNi44LTEwLjgtMTUuMiA2MC43Yy01LjIgMjAuOC40IDQyLjkgMTQuOSA1OC44bDU5LjkgNjUuNGM3LjIgNy45IDEyLjMgMTcuNCAxNC45IDI3LjdsMTguMyA3My4zYzQuMyAxNy4xIDIxLjcgMjcuNiAzOC44IDIzLjMgMTcuMS00LjMgMjcuNi0yMS43IDIzLjMtMzguOGwtMjIuMi04OWMtMi42LTEwLjMtNy43LTE5LjktMTQuOS0yNy43bC00NS41LTQ5LjcgMTcuMi02OC43IDUuNSAxNi41YzUuMyAxNi4xIDE2LjcgMjkuNCAzMS43IDM3bDIzLjMgMTEuOGMxNS42IDcuOSAzNC42IDEuNSA0Mi41LTE0LjMgNy43LTE1LjcgMS40LTM1LjEtMTQuMy00M3pNNzMuNiAzODUuOGMtMy4yIDguMS04IDE1LjQtMTQuMiAyMS41bC01MCA1MC4xYy0xMi41IDEyLjUtMTIuNSAzMi44IDAgNDUuM3MzMi43IDEyLjUgNDUuMiAwbDU5LjQtNTkuNGM2LjEtNi4xIDEwLjktMTMuNCAxNC4yLTIxLjVsMTMuNS0zMy44Yy01NS4zLTYwLjMtMzguNy00MS44LTQ3LjQtNTMuN2wtMjAuNyA1MS41eiI+PC9wYXRoPjwvc3ZnPg==); } .travelMode.train { - background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ0cmFpbiIgY2xhc3M9InN2Zy1pbmxpbmUtLWZhIGZhLXRyYWluIGZhLXctMTQiIHJvbGU9ImltZyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDQ4IDUxMiI+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNDQ4IDk2djI1NmMwIDUxLjgxNS02MS42MjQgOTYtMTMwLjAyMiA5Nmw2Mi45OCA0OS43MjFDMzg2LjkwNSA1MDIuNDE3IDM4My41NjIgNTEyIDM3NiA1MTJINzJjLTcuNTc4IDAtMTAuODkyLTkuNTk0LTQuOTU3LTE0LjI3OUwxMzAuMDIyIDQ0OEM2MS44MiA0NDggMCA0MDMuOTU0IDAgMzUyVjk2QzAgNDIuOTgxIDY0IDAgMTI4IDBoMTkyYzY1IDAgMTI4IDQyLjk4MSAxMjggOTZ6bS00OCAxMzZWMTIwYzAtMTMuMjU1LTEwLjc0NS0yNC0yNC0yNEg3MmMtMTMuMjU1IDAtMjQgMTAuNzQ1LTI0IDI0djExMmMwIDEzLjI1NSAxMC43NDUgMjQgMjQgMjRoMzA0YzEzLjI1NSAwIDI0LTEwLjc0NSAyNC0yNHptLTE3NiA2NGMtMzAuOTI4IDAtNTYgMjUuMDcyLTU2IDU2czI1LjA3MiA1NiA1NiA1NiA1Ni0yNS4wNzIgNTYtNTYtMjUuMDcyLTU2LTU2LTU2eiI+PC9wYXRoPjwvc3ZnPg==); + background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ0cmFpbiIgY2xhc3M9InN2Zy1pbmxpbmUtLWZhIGZhLXRyYWluIGZhLXctMTQiIHJvbGU9ImltZyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDQ4IDUxMiI+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNDQ4IDk2djI1NmMwIDUxLjgxNS02MS42MjQgOTYtMTMwLjAyMiA5Nmw2Mi45OCA0OS43MjFDMzg2LjkwNSA1MDIuNDE3IDM4My41NjIgNTEyIDM3NiA1MTJINzJjLTcuNTc4IDAtMTAuODkyLTkuNTk0LTQuOTU3LTE0LjI3OUwxMzAuMDIyIDQ0OEM2MS44MiA0NDggMCA0MDMuOTU0IDAgMzUyVjk2QzAgNDIuOTgxIDY0IDAgMTI4IDBoMTkyYzY1IDAgMTI4IDQyLjk4MSAxMjggOTZ6bS00OCAxMzZWMTIwYzAtMTMuMjU1LTEwLjc0NS0yNC0yNC0yNEg3MmMtMTMuMjU1IDAtMjQgMTAuNzQ1LTI0IDI0djExMmMwIDEzLjI1NSAxMC43NDUgMjQgMjQgMjRoMzA0YzEzLjI1NSAwIDI0LTEwLjc0NSAyNC0yNHptLTE3NiA2NGMtMzAuOTI4IDAtNTYgMjUuMDcyLTU2IDU2czI1LjA3MiA1NiA1NiA1NiA1Ni0yNS4wNzIgNTYtNTYtMjUuMDcyLTU2LTU2LTU2eiI+PC9wYXRoPjwvc3ZnPg==); } .duration { - margin-bottom: 5px; - margin-top: 5px; + margin-bottom: 5px; + margin-top: 5px; } .enterConnectionId, .exitConnectionId { - opacity: 0.5; + opacity: 0.5; } #actions { - position: absolute; - top: 10px; - right: 10px; - z-index: 500; + position: absolute; + top: 10px; + right: 10px; + z-index: 500; } #actions > button { - border-radius: 100px; - background: rgba(255,255,255,.8); - font-size: 2em; - padding: .25em 1em; + border-radius: 100px; + background: rgba(255, 255, 255, .8); + font-size: 2em; + padding: .25em 1em; } #prefetch { - position: absolute; - top: 10px; - left: 54px; - z-index: 500; + position: absolute; + top: 10px; + left: 54px; + z-index: 500; } .prefetch-view { - height: 6px; - margin-bottom: 3px; - border-radius: 30px; - background: #3556a9; - color: #fff; - font-family: sans-serif; - padding: 3px 5px; - transition: 50ms width ease-in-out; + height: 6px; + margin-bottom: 3px; + border-radius: 30px; + background: #3556a9; + color: #fff; + font-family: sans-serif; + padding: 3px 5px; + transition: 50ms width ease-in-out; } .prefetch-view::after { - content: attr(data-last); - color: #fff; - float: right; + content: attr(data-last); + color: #fff; + float: right; } #prefetch-bar { - height: 18px; + height: 18px; } diff --git a/example/js/index.js b/example/js/index.js index fe0d9cdf..d64a309c 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -237,6 +237,12 @@ function selectRoute(e, id) { } } +function dateToTimeString(date) { + const hours = date.getHours(); + const minutes = date.getMinutes(); + return `${hours < 10 ? '0' + hours : hours}:${minutes < 10 ? '0' + minutes : minutes}` +} + map.on("click", onMapClick); function getRandomColor() { @@ -248,6 +254,152 @@ function getRandomColor() { return color; } +function getTravelTime(path) { + return path.steps.reduce((time, step) => time + step.duration.minimum, 0) / 60000; +} + +function getTransferTime(path) { + let time = 0; + + if (path.steps.length < 2) { + return time; + } + + for(let i = 0; i < path.steps.length - 1; i++) { + let stepX = path.steps[i]; + let stepY = path.steps[i + 1]; + + time += stepY.startTime - stepX.stopTime; + } + + return time / 60000; +} + +function addResultPanel(path, color) { + const pathElement = document.createElement("div"); + pathElement.className = "path"; + + const firstStep = path.steps[0]; + const lastStep = path.steps[path.steps.length - 1]; + + const headerElement = document.createElement("div"); + headerElement.className = "header"; + + console.log(path, firstStep, lastStep); + headerElement.innerHTML = ` + Departure: ${dateToTimeString(firstStep.startTime)}
+ Arrival: ${dateToTimeString(lastStep.stopTime)}
+ Travel time: ${getTravelTime(path)} min
+ Transfer time: ${getTransferTime(path)} min + `; + + pathElement.appendChild(headerElement); + + path.steps.forEach(step => { + const stepElement = document.createElement("div"); + stepElement.className = "step"; + + const travelMode = document.createElement("div"); + travelMode.className = "travelMode " + step.travelMode; + stepElement.appendChild(travelMode); + + const details = document.createElement("div"); + details.className = "details"; + stepElement.appendChild(details); + + const startLocation = document.createElement("div"); + startLocation.className = "startLocation"; + startLocation.innerHTML = + "Start location: " + step.startLocation.name; + details.appendChild(startLocation); + + if (step.startTime) { + const startTime = document.createElement("div"); + startTime.className = "startTime"; + startTime.innerHTML = step.startTime; + details.appendChild(startTime); + } + + if (step.enterConnectionId) { + const enterConnectionId = document.createElement("div"); + enterConnectionId.className = "enterConnectionId"; + enterConnectionId.innerHTML = + "Enter connection: " + step.enterConnectionId; + details.appendChild(enterConnectionId); + } + + if (step.duration) { + const duration = document.createElement("div"); + duration.className = "duration"; + duration.innerHTML = + "Duration: minimum " + + step.duration.minimum / (60 * 1000) + + "min"; + details.appendChild(duration); + } + + const stopLocation = document.createElement("div"); + stopLocation.className = "stopLocation"; + stopLocation.innerHTML = "Stop location: " + step.stopLocation.name; + details.appendChild(stopLocation); + + if (step.stopTime) { + const stopTime = document.createElement("div"); + stopTime.className = "stopTime"; + stopTime.innerHTML = step.stopTime; + details.appendChild(stopTime); + } + + if (step.exitConnectionId) { + const exitConnectionId = document.createElement("div"); + exitConnectionId.className = "exitConnectionId"; + exitConnectionId.innerHTML = + "Exit connection: " + step.exitConnectionId; + details.appendChild(exitConnectionId); + } + + pathElement.style.borderLeft = "5px solid " + color; + + pathElement.appendChild(stepElement); + }); + + results.appendChild(pathElement); +} + +function addResultToMap(path, color) { + path.steps.forEach(step => { + const { startLocation, stopLocation, travelMode } = step; + + const startMarker = L.marker([ + startLocation.latitude, + startLocation.longitude + ]).addTo(map); + + startMarker.bindPopup(startLocation.name); + + const stopMarker = L.marker([ + stopLocation.latitude, + stopLocation.longitude + ]).addTo(map); + + stopMarker.bindPopup(stopLocation.name); + const line = [ + [startLocation.latitude, startLocation.longitude], + [stopLocation.latitude, stopLocation.longitude] + ]; + + const polyline = new L.Polyline(line, { + color, + weight: 5, + smoothFactor: 1, + opacity: 0.7, + dashArray: travelMode === "walking" ? "8 8" : null + }).addTo(map); + + resultObjects.push(startMarker, stopMarker, polyline); + }); +} + function runQuery(query) { console.log(query); @@ -290,110 +442,9 @@ function runQuery(query) { const color = getRandomColor(); - const pathElement = document.createElement("div"); - pathElement.className = "path"; - - path.steps.forEach(step => { - const stepElement = document.createElement("div"); - stepElement.className = "step"; - - const travelMode = document.createElement("div"); - travelMode.className = "travelMode " + step.travelMode; - stepElement.appendChild(travelMode); - - const details = document.createElement("div"); - details.className = "details"; - stepElement.appendChild(details); - - const startLocation = document.createElement("div"); - startLocation.className = "startLocation"; - startLocation.innerHTML = - "Start location: " + step.startLocation.name; - details.appendChild(startLocation); - - if (step.startTime) { - const startTime = document.createElement("div"); - startTime.className = "startTime"; - startTime.innerHTML = step.startTime; - details.appendChild(startTime); - } - - if (step.enterConnectionId) { - const enterConnectionId = document.createElement("div"); - enterConnectionId.className = "enterConnectionId"; - enterConnectionId.innerHTML = - "Enter connection: " + step.enterConnectionId; - details.appendChild(enterConnectionId); - } - - if (step.duration) { - const duration = document.createElement("div"); - duration.className = "duration"; - duration.innerHTML = - "Duration: minimum " + - step.duration.minimum / (60 * 1000) + - "min"; - details.appendChild(duration); - } - - const stopLocation = document.createElement("div"); - stopLocation.className = "stopLocation"; - stopLocation.innerHTML = "Stop location: " + step.stopLocation.name; - details.appendChild(stopLocation); - - if (step.stopTime) { - const stopTime = document.createElement("div"); - stopTime.className = "stopTime"; - stopTime.innerHTML = step.stopTime; - details.appendChild(stopTime); - } - - if (step.exitConnectionId) { - const exitConnectionId = document.createElement("div"); - exitConnectionId.className = "exitConnectionId"; - exitConnectionId.innerHTML = - "Exit connection: " + step.exitConnectionId; - details.appendChild(exitConnectionId); - } - - pathElement.style.borderLeft = "5px solid " + color; - - pathElement.appendChild(stepElement); - }); + addResultPanel(path, color); + addResultToMap(path, color); - results.appendChild(pathElement); - - path.steps.forEach(step => { - const { startLocation, stopLocation, travelMode } = step; - - const startMarker = L.marker([ - startLocation.latitude, - startLocation.longitude - ]).addTo(map); - - startMarker.bindPopup(startLocation.name); - - const stopMarker = L.marker([ - stopLocation.latitude, - stopLocation.longitude - ]).addTo(map); - - stopMarker.bindPopup(stopLocation.name); - const line = [ - [startLocation.latitude, startLocation.longitude], - [stopLocation.latitude, stopLocation.longitude] - ]; - - const polyline = new L.Polyline(line, { - color, - weight: 5, - smoothFactor: 1, - opacity: 0.7, - dashArray: travelMode === "walking" ? "8 8" : null - }).addTo(map); - - resultObjects.push(startMarker, stopMarker, polyline); - }); }) .on("end", () => { if (i < amount) { From 6991197ff1a92a6d649abf51d291faa3f03fd0f1 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 7 Feb 2019 12:10:20 +0100 Subject: [PATCH 61/69] 1. added maximumTransferDistance. 1. CSA EAT improvements. --- example/js/index.js | 2 - src/interfaces/IQuery.ts | 1 + .../public-transport/CSAEarliestArrival.ts | 112 +++++++++++++----- src/planner/public-transport/CSAProfile.ts | 2 +- .../JourneyExtractorEarliestArrival.ts | 31 +++-- .../JourneyExtractorProfile.ts | 24 ++-- src/query-runner/QueryRunnerDefault.ts | 9 +- .../QueryRunnerEarliestArrivalFirst.ts | 9 +- .../exponential/QueryRunnerExponential.ts | 9 +- 9 files changed, 135 insertions(+), 64 deletions(-) diff --git a/example/js/index.js b/example/js/index.js index 6212f84f..ad53f3a4 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -301,7 +301,6 @@ function addResultPanel(path, color) { const headerElement = document.createElement("div"); headerElement.className = "header"; - console.log(path, firstStep, lastStep); headerElement.innerHTML = ` Departure: ${dateToTimeString(firstStep.startTime)}
Arrival: ${dateToTimeString(lastStep.stopTime)}
@@ -455,7 +454,6 @@ function runQuery(query) { console.error(error); }) .on("data", path => { - console.log("path", i, path); i++; const color = getRandomColor(); diff --git a/src/interfaces/IQuery.ts b/src/interfaces/IQuery.ts index f0cb7395..78bbbc7e 100644 --- a/src/interfaces/IQuery.ts +++ b/src/interfaces/IQuery.ts @@ -15,5 +15,6 @@ export default interface IQuery { maximumWalkingDistance?: DistanceM; minimumTransferDuration?: DurationMs; maximumTransferDuration?: DurationMs; + maximumTransferDistance?: DistanceM; maximumTransfers?: number; } diff --git a/src/planner/public-transport/CSAEarliestArrival.ts b/src/planner/public-transport/CSAEarliestArrival.ts index 714f9113..ff997909 100644 --- a/src/planner/public-transport/CSAEarliestArrival.ts +++ b/src/planner/public-transport/CSAEarliestArrival.ts @@ -146,7 +146,13 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { while (connection) { this.discoverConnection(connection); - const { to, minimumDepartureTime, minimumTransferDuration, maximumTransferDuration } = this.query; + const { + to, + minimumDepartureTime, + maximumWalkingDuration, + minimumTransferDuration, + maximumTransferDuration, + } = this.query; const arrivalStopId: string = to[0].id; if (this.profilesByStop[arrivalStopId].arrivalTime <= connection.departureTime.getTime()) { @@ -175,8 +181,9 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const transferDuration = departure - arrival; const canTakeTransfer = ( + transferDuration > -Infinity && (transferDuration >= minimumTransferDuration || initialStop && transferDuration >= 0) && - (transferDuration <= maximumTransferDuration || initialStop && transferDuration >= 0) && + (transferDuration <= maximumTransferDuration || initialStop && transferDuration <= maximumWalkingDuration) && connection["gtfs:pickupType"] !== PickupType.NotAvailable ); @@ -369,7 +376,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { const reachableStops: IReachableStop[] = await this.transferReachableStopsFinder.findReachableStops( arrivalStop as IStop, ReachableStopsFinderMode.Source, - this.query.maximumWalkingDuration, + this.query.maximumTransferDuration, this.query.minimumWalkingSpeed, ); @@ -383,17 +390,42 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { }; } + if (stop.id === this.query.to[0].id) { + return; + } + const reachableStopArrival = this.profilesByStop[stop.id].arrivalTime; + + const departureTime = connection.departureTime.getTime(); const arrivalTime = connection.arrivalTime.getTime() + duration; if (reachableStopArrival > arrivalTime) { - const transferProfile = { - departureTime: connection.departureTime.getTime(), - arrivalTime: connection.arrivalTime.getTime() + duration, + const transferProfile: ITransferProfile = { + departureTime, + arrivalTime, exitConnection: connection, enterConnection: this.enterConnectionByTrip[tripId], }; + if (duration > 0) { + const path: Path = Path.create(); + + const footpath: IStep = Step.create( + arrivalStop, + stop, + TravelMode.Walking, + { + minimum: duration, + }, + new Date(arrivalTime - duration), + new Date(arrivalTime), + ); + + path.addStep(footpath); + + transferProfile.path = path; + } + if (this.context && this.context.listenerCount(EventType.AddedNewTransferProfile) > 0) { this.emitTransferProfile(transferProfile); } @@ -402,7 +434,7 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } }); - this.checkIfArrivalStopIsReachable(connection, arrivalStop); + this.checkIfArrivalStopIsReachable(connection, tripId, arrivalStop); } catch (e) { if (this.context) { @@ -411,35 +443,57 @@ export default class CSAEarliestArrival implements IPublicTransportPlanner { } } - private checkIfArrivalStopIsReachable(connection: IConnection, arrivalStop: ILocation): void { - const canReachArrivalStop = this.finalReachableStops.find((reachableStop: IReachableStop) => { - return reachableStop.stop.id === arrivalStop.id; - }); + private checkIfArrivalStopIsReachable(connection: IConnection, tripId: string, arrivalStop: ILocation): void { + const canReachArrivalStop = this.finalReachableStops.find((reachableStop: IReachableStop) => + reachableStop.stop.id === arrivalStop.id, + ); + + if (canReachArrivalStop) { + const finalLocationId = this.query.to[0].id; + + if (canReachArrivalStop.stop.id === finalLocationId) { + const departureTime = connection.departureTime.getTime(); + const arrivalTime = connection.arrivalTime.getTime(); + const reachableStopArrival = this.profilesByStop[connection.arrivalStop].arrivalTime; + + if (reachableStopArrival > arrivalTime) { + this.profilesByStop[finalLocationId] = { + departureTime, + arrivalTime, + exitConnection: connection, + enterConnection: this.enterConnectionByTrip[tripId], + }; + } + + } + + const finalProfile = this.profilesByStop[finalLocationId]; - if (canReachArrivalStop && canReachArrivalStop.duration > 0) { const departureTime = connection.arrivalTime.getTime(); const arrivalTime = connection.arrivalTime.getTime() + canReachArrivalStop.duration; - const path: Path = Path.create(); + if ((!finalProfile || finalProfile.arrivalTime > arrivalTime) && canReachArrivalStop.duration > 0) { + const path: Path = Path.create(); - const footpath: IStep = Step.create( - arrivalStop, - this.query.to[0], - TravelMode.Walking, - { - minimum: arrivalTime - departureTime, - }, - new Date(departureTime), - new Date(arrivalTime), - ); + const footpath: IStep = Step.create( + arrivalStop, + this.query.to[0], + TravelMode.Walking, + { + minimum: arrivalTime - departureTime, + }, + new Date(departureTime), + new Date(arrivalTime), + ); - path.addStep(footpath); + path.addStep(footpath); - this.profilesByStop[this.query.to[0].id] = { - departureTime, - arrivalTime, - path, - }; + this.profilesByStop[finalLocationId] = { + departureTime, + arrivalTime, + path, + }; + } } } diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index ba0b4666..e1c487c6 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -421,7 +421,7 @@ export default class CSAProfile implements IPublicTransportPlanner { const reachableStops: IReachableStop[] = await this.transferReachableStopsFinder.findReachableStops( departureStop as IStop, ReachableStopsFinderMode.Source, - this.query.maximumWalkingDuration, + this.query.maximumTransferDuration, this.query.minimumWalkingSpeed, ); diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts index 45305f02..fa5a9490 100644 --- a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts +++ b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts @@ -48,8 +48,20 @@ export default class JourneyExtractorEarliestArrival implements IJourneyExtracto while (currentStopId !== departureStopId && (currentProfile.enterConnection || currentProfile.path)) { const { enterConnection, exitConnection, path: profilePath } = currentProfile; - if (currentProfile.enterConnection && currentProfile.exitConnection) { + if (profilePath) { + currentStopId = profilePath.getStartLocationId(); + + if (currentStopId === departureStopId) { + const lastStep = path.steps[path.steps.length - 1]; + const timeToAdd = lastStep.startTime.getTime() - profilePath.steps[0].stopTime.getTime(); + profilePath.addTime(timeToAdd); + } + + path.addPath(profilePath); + } + + if (currentProfile.enterConnection && currentProfile.exitConnection) { const enterLocation: ILocation = await this.locationResolver.resolve(enterConnection.departureStop); const exitLocation: ILocation = await this.locationResolver.resolve(exitConnection.arrivalStop); @@ -63,24 +75,9 @@ export default class JourneyExtractorEarliestArrival implements IJourneyExtracto path.addStep(step); currentStopId = enterConnection.departureStop; - currentProfile = profilesByStop[currentStopId]; - } - - if (profilePath) { - currentStopId = profilePath.getStartLocationId(); - - if (currentStopId === departureStopId) { - const lastStep = path.steps[path.steps.length - 1]; - const timeToAdd = lastStep.startTime.getTime() - profilePath.steps[0].stopTime.getTime(); - - profilePath.addTime(timeToAdd); - } - - path.addPath(profilePath); - - currentProfile = profilesByStop[currentStopId]; } + currentProfile = profilesByStop[currentStopId]; } if (!path.steps.length) { diff --git a/src/planner/public-transport/JourneyExtractorProfile.ts b/src/planner/public-transport/JourneyExtractorProfile.ts index e7e66e47..37eebe48 100644 --- a/src/planner/public-transport/JourneyExtractorProfile.ts +++ b/src/planner/public-transport/JourneyExtractorProfile.ts @@ -132,7 +132,7 @@ export default class JourneyExtractorProfile implements IJourneyExtractor { const path: Path = Path.create(); let currentTransferProfile: ITransferProfile = transferProfile; - let departureTime: Date = new Date(transferProfile.departureTime); + let departureTime: number = transferProfile.departureTime; let remainingTransfers: number = transfers; @@ -146,18 +146,24 @@ export default class JourneyExtractorProfile implements IJourneyExtractor { const exitLocation: ILocation = await this.locationResolver.resolve(exitConnection.arrivalStop); // Initial or transfer footpath. - const transferDepartureTime: Date = enterConnection.departureTime; + const transferDepartureTime: number = enterConnection.departureTime.getTime(); + + if (departureTime !== transferDepartureTime) { + + let timeToSubtract = 0; + if (path.steps.length > 0) { + timeToSubtract = departureTime - path.steps[path.steps.length - 1].stopTime.getTime(); + } - if (departureTime.getTime() !== transferDepartureTime.getTime()) { const footpath: IStep = Step.create( currentLocation, enterLocation, TravelMode.Walking, { - minimum: transferDepartureTime.getTime() - departureTime.getTime(), + minimum: transferDepartureTime - departureTime, }, - departureTime, - transferDepartureTime, + new Date(departureTime - timeToSubtract), + new Date(transferDepartureTime - timeToSubtract), ); path.addStep(footpath); @@ -183,11 +189,11 @@ export default class JourneyExtractorProfile implements IJourneyExtractor { let profileIndex: number = currentProfiles.length - 1; currentTransferProfile = currentProfiles[profileIndex].transferProfiles[remainingTransfers]; - departureTime = new Date(currentTransferProfile.departureTime); + departureTime = currentTransferProfile.departureTime; - while (profileIndex >= 0 && departureTime < exitConnection.arrivalTime) { + while (profileIndex >= 0 && departureTime < exitConnection.arrivalTime.getTime()) { currentTransferProfile = currentProfiles[--profileIndex].transferProfiles[remainingTransfers]; - departureTime = new Date(currentTransferProfile.departureTime); + departureTime = currentTransferProfile.departureTime; } if (profileIndex === -1) { diff --git a/src/query-runner/QueryRunnerDefault.ts b/src/query-runner/QueryRunnerDefault.ts index 0fbcea0f..0b2e05ea 100644 --- a/src/query-runner/QueryRunnerDefault.ts +++ b/src/query-runner/QueryRunnerDefault.ts @@ -63,7 +63,8 @@ export default class QueryRunnerDefault implements IQueryRunner { from, to, minimumWalkingSpeed, maximumWalkingSpeed, walkingSpeed, maximumWalkingDuration, maximumWalkingDistance, - minimumTransferDuration, maximumTransferDuration, maximumTransfers, + minimumTransferDuration, maximumTransferDuration, maximumTransferDistance, + maximumTransfers, minimumDepartureTime, maximumArrivalTime, ...other } = query; @@ -93,11 +94,15 @@ export default class QueryRunnerDefault implements IQueryRunner { resolvedQuery.minimumWalkingSpeed = minimumWalkingSpeed || walkingSpeed || Defaults.defaultMinimumWalkingSpeed; resolvedQuery.maximumWalkingSpeed = maximumWalkingSpeed || walkingSpeed || Defaults.defaultMaximumWalkingSpeed; + resolvedQuery.maximumWalkingDuration = maximumWalkingDuration || Units.toDuration(maximumWalkingDistance, resolvedQuery.minimumWalkingSpeed) || Defaults.defaultWalkingDuration; resolvedQuery.minimumTransferDuration = minimumTransferDuration || Defaults.defaultMinimumTransferDuration; - resolvedQuery.maximumTransferDuration = maximumTransferDuration || Defaults.defaultMaximumTransferDuration; + resolvedQuery.maximumTransferDuration = maximumTransferDuration || + Units.toDuration(maximumTransferDistance, resolvedQuery.minimumWalkingSpeed) || + Defaults.defaultMaximumTransferDuration; + resolvedQuery.maximumTransfers = maximumTransfers || Defaults.defaultMaximumTransfers; return resolvedQuery; diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts index 789fc497..cdf36ba7 100644 --- a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts @@ -159,7 +159,8 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { from, to, minimumWalkingSpeed, maximumWalkingSpeed, walkingSpeed, maximumWalkingDuration, maximumWalkingDistance, - minimumTransferDuration, maximumTransferDuration, maximumTransfers, + minimumTransferDuration, maximumTransferDuration, maximumTransferDistance, + maximumTransfers, minimumDepartureTime, ...other } = query; @@ -179,11 +180,15 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { resolvedQuery.minimumWalkingSpeed = minimumWalkingSpeed || walkingSpeed || Defaults.defaultMinimumWalkingSpeed; resolvedQuery.maximumWalkingSpeed = maximumWalkingSpeed || walkingSpeed || Defaults.defaultMaximumWalkingSpeed; + resolvedQuery.maximumWalkingDuration = maximumWalkingDuration || Units.toDuration(maximumWalkingDistance, resolvedQuery.minimumWalkingSpeed) || Defaults.defaultWalkingDuration; resolvedQuery.minimumTransferDuration = minimumTransferDuration || Defaults.defaultMinimumTransferDuration; - resolvedQuery.maximumTransferDuration = maximumTransferDuration || Defaults.defaultMaximumTransferDuration; + resolvedQuery.maximumTransferDuration = maximumTransferDuration || + Units.toDuration(maximumTransferDistance, resolvedQuery.minimumWalkingSpeed) || + Defaults.defaultMaximumTransferDuration; + resolvedQuery.maximumTransfers = maximumTransfers || Defaults.defaultMaximumTransfers; return resolvedQuery; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index 60f55bf1..c834d12e 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -99,7 +99,8 @@ export default class QueryRunnerExponential implements IQueryRunner { from, to, minimumWalkingSpeed, maximumWalkingSpeed, walkingSpeed, maximumWalkingDuration, maximumWalkingDistance, - minimumTransferDuration, maximumTransferDuration, maximumTransfers, + minimumTransferDuration, maximumTransferDuration, maximumTransferDistance, + maximumTransfers, minimumDepartureTime, ...other } = query; @@ -119,11 +120,15 @@ export default class QueryRunnerExponential implements IQueryRunner { resolvedQuery.minimumWalkingSpeed = minimumWalkingSpeed || walkingSpeed || Defaults.defaultMinimumWalkingSpeed; resolvedQuery.maximumWalkingSpeed = maximumWalkingSpeed || walkingSpeed || Defaults.defaultMaximumWalkingSpeed; + resolvedQuery.maximumWalkingDuration = maximumWalkingDuration || Units.toDuration(maximumWalkingDistance, resolvedQuery.minimumWalkingSpeed) || Defaults.defaultWalkingDuration; resolvedQuery.minimumTransferDuration = minimumTransferDuration || Defaults.defaultMinimumTransferDuration; - resolvedQuery.maximumTransferDuration = maximumTransferDuration || Defaults.defaultMaximumTransferDuration; + resolvedQuery.maximumTransferDuration = maximumTransferDuration || + Units.toDuration(maximumTransferDistance, resolvedQuery.minimumWalkingSpeed) || + Defaults.defaultMaximumTransferDuration; + resolvedQuery.maximumTransfers = maximumTransfers || Defaults.defaultMaximumTransfers; return resolvedQuery; From 80b179272546ae00aa5d20c3ee35813374f2b045 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 7 Feb 2019 14:23:31 +0100 Subject: [PATCH 62/69] 1. QueryRunnerEarliestArrivalFirst: abort when no EAT results. --- example/js/index.js | 2 +- .../public-transport/JourneyExtractorEarliestArrival.ts | 4 ++-- .../QueryRunnerEarliestArrivalFirst.ts | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/example/js/index.js b/example/js/index.js index ad53f3a4..a0343ce5 100644 --- a/example/js/index.js +++ b/example/js/index.js @@ -118,7 +118,7 @@ planner minimumDepartureTime, maximumArrivalTime, maximumArrivalTime - minimumDepartureTime, - maximumTravelDuration / 1, 66667e-5 + maximumTravelDuration, ); removeLines(); diff --git a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts index fa5a9490..30f9bc89 100644 --- a/src/planner/public-transport/JourneyExtractorEarliestArrival.ts +++ b/src/planner/public-transport/JourneyExtractorEarliestArrival.ts @@ -1,4 +1,4 @@ -import { ArrayIterator, AsyncIterator, SingletonIterator } from "asynciterator"; +import { AsyncIterator, EmptyIterator, SingletonIterator } from "asynciterator"; import { inject, injectable } from "inversify"; import Context from "../../Context"; import ILocation from "../../interfaces/ILocation"; @@ -81,7 +81,7 @@ export default class JourneyExtractorEarliestArrival implements IJourneyExtracto } if (!path.steps.length) { - return new ArrayIterator([]); + return new EmptyIterator(); } path.reverse(); diff --git a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts index cdf36ba7..62527c1d 100644 --- a/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts +++ b/src/query-runner/earliest-arrival-first/QueryRunnerEarliestArrivalFirst.ts @@ -90,17 +90,21 @@ export default class QueryRunnerEarliestArrivalFirst implements IQueryRunner { const earliestArrivalIterator = await earliestArrivalPlanner.plan(baseQuery); - const path: IPath = await new Promise((resolve, reject) => { + const path: IPath = await new Promise((resolve) => { earliestArrivalIterator .take(1) .on("data", (result: IPath) => { resolve(result); }) .on("end", () => { - reject(); + resolve(null); }); }); + if (path === null && this.context) { + this.context.emit(EventType.AbortQuery, "This query has no results"); + } + let initialTimeSpan: DurationMs = Units.fromHours(1); let travelDuration: DurationMs; From 334d7ae7b927eeff42c060aa59c7da15abf4b100 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 7 Feb 2019 14:32:39 +0100 Subject: [PATCH 63/69] Split connection store appending in 1 primary and 0+ secondary pushes --- src/asynciterator.d.ts | 243 +++++++++--------- .../prefetch/ConnectionsProviderPrefetch.ts | 34 +-- .../connections/prefetch/ConnectionsStore.ts | 224 ++++++++++++---- src/fetcher/stops/StopsPrefetcher.ts | 0 4 files changed, 297 insertions(+), 204 deletions(-) delete mode 100644 src/fetcher/stops/StopsPrefetcher.ts diff --git a/src/asynciterator.d.ts b/src/asynciterator.d.ts index 92df5ced..bcbc70b7 100755 --- a/src/asynciterator.d.ts +++ b/src/asynciterator.d.ts @@ -4,191 +4,192 @@ declare module "asynciterator" { - import { EventEmitter } from "events"; + import { EventEmitter } from "events"; - export abstract class AsyncIterator extends EventEmitter { - static STATES: ['INIT', 'OPEN', 'CLOSING', 'CLOSED', 'ENDED']; - static INIT: 0; - static OPEN: 1; - static CLOSING: 2; - static CLOSED: 3; - static ENDED: 4; + export abstract class AsyncIterator extends EventEmitter { + static STATES: ["INIT", "OPEN", "CLOSING", "CLOSED", "ENDED"]; + static INIT: 0; + static OPEN: 1; + static CLOSING: 2; + static CLOSED: 3; + static ENDED: 4; - _state: number; - _readable: boolean; - _destination?: AsyncIterator; + _state: number; + _readable: boolean; + _destination?: AsyncIterator; - readable: boolean; - closed: boolean; - ended: boolean; + readable: boolean; + closed: boolean; + ended: boolean; - constructor(); + constructor(); - read(): T; + read(): T; - each(callback: (data: T) => void, self?: any): void; + each(callback: (data: T) => void, self?: any): void; - close(): void; + close(): void; - _changeState(newState: number, eventAsync?: boolean): void; + _changeState(newState: number, eventAsync?: boolean): void; - private _hasListeners(eventName: string | symbol): boolean; + private _hasListeners(eventName: string | symbol): boolean; - // tslint:disable-next-line ban-types - private _addSingleListener(eventName: string | symbol, listener: Function): void; + // tslint:disable-next-line ban-types + private _addSingleListener(eventName: string | symbol, listener: Function): void; - _end(): void; + _end(): void; - getProperty(propertyName: string, callback?: (value: any) => void): any; + getProperty(propertyName: string, callback?: (value: any) => void): any; - setProperty(propertyName: string, value: any): void; + setProperty(propertyName: string, value: any): void; - getProperties(): { [id: string]: any }; + getProperties(): { [id: string]: any }; - setProperties(properties: { [id: string]: any }): void; + setProperties(properties: { [id: string]: any }): void; - copyProperties(source: AsyncIterator, propertyNames: string[]): void; + copyProperties(source: AsyncIterator, propertyNames: string[]): void; - toString(): string; + toString(): string; - _toStringDetails(): string; + _toStringDetails(): string; - transform(options?: SimpleTransformIteratorOptions): SimpleTransformIterator; + transform(options?: SimpleTransformIteratorOptions): SimpleTransformIterator; - map(mapper: (item: T) => T2, self?: object): SimpleTransformIterator; + map(mapper: (item: T) => T2, self?: object): SimpleTransformIterator; - filter(filter: (item: T) => boolean, self?: object): SimpleTransformIterator; + filter(filter: (item: T) => boolean, self?: object): SimpleTransformIterator; - prepend(items: T[] | AsyncIterator): SimpleTransformIterator; + prepend(items: T[] | AsyncIterator): SimpleTransformIterator; - append(items: T[] | AsyncIterator): SimpleTransformIterator; + append(items: T[] | AsyncIterator): SimpleTransformIterator; - surround(prepend: T[] | AsyncIterator, append: T[] | AsyncIterator): SimpleTransformIterator; + surround(prepend: T[] | AsyncIterator, append: T[] | AsyncIterator): SimpleTransformIterator; - skip(offset: number): SimpleTransformIterator; + skip(offset: number): SimpleTransformIterator; - take(limit: number): SimpleTransformIterator; + take(limit: number): SimpleTransformIterator; - range(start: number, end: number): SimpleTransformIterator; + range(start: number, end: number): SimpleTransformIterator; - clone(): ClonedIterator; + clone(): ClonedIterator; - static range(start?: number, end?: number, step?: number): IntegerIterator; - } + static range(start?: number, end?: number, step?: number): IntegerIterator; + } - export class EmptyIterator extends AsyncIterator { - _state: 4; - } + export class EmptyIterator extends AsyncIterator { + _state: 4; + } - export class SingletonIterator extends AsyncIterator { - constructor(item?: T); - } + export class SingletonIterator extends AsyncIterator { + constructor(item?: T); + } - export class ArrayIterator extends AsyncIterator { - constructor(items?: T[]); - } + export class ArrayIterator extends AsyncIterator { + constructor(items?: T[]); + } - export interface IntegerIteratorOptions { - step?: number; - end?: number; - start?: number; - } + export interface IntegerIteratorOptions { + step?: number; + end?: number; + start?: number; + } - export class IntegerIterator extends AsyncIterator { - _step: number; - _last: number; - _next: number; + export class IntegerIterator extends AsyncIterator { + _step: number; + _last: number; + _next: number; - constructor(options?: IntegerIteratorOptions); - } + constructor(options?: IntegerIteratorOptions); + } - export interface BufferedIteratorOptions { - maxBufferSize?: number; - autoStart?: boolean; - } + export interface BufferedIteratorOptions { + maxBufferSize?: number; + autoStart?: boolean; + } - export class BufferedIterator extends AsyncIterator { - maxBufferSize: number; - _pushedCount: number; - _buffer: T[]; + export class BufferedIterator extends AsyncIterator { + maxBufferSize: number; + _pushedCount: number; + _buffer: T[]; - _init(autoStart: boolean): void; + _init(autoStart: boolean): void; - _begin(done: () => void): void; + _begin(done: () => void): void; - _read(count: number, done: () => void): void; + _read(count: number, done: () => void): void; - _push(item: T): void; + _push(item: T): void; - _fillBuffer(): void; + _fillBuffer(): void; - _completeClose(): void; + _completeClose(): void; - _flush(done: () => void): void; + _flush(done: () => void): void; - constructor(options?: BufferedIteratorOptions); - } + constructor(options?: BufferedIteratorOptions); + } - export interface TransformIteratorOptions extends BufferedIteratorOptions { - optional?: boolean; - source?: AsyncIterator; - } + export interface TransformIteratorOptions extends BufferedIteratorOptions { + optional?: boolean; + source?: AsyncIterator; + destroySource?: boolean; + } - export class TransformIterator extends BufferedIterator { - _optional: boolean; - source: AsyncIterator; + export class TransformIterator extends BufferedIterator { + _optional: boolean; + source: AsyncIterator; - _validateSource(source: AsyncIterator, allowDestination?: boolean): void; + _validateSource(source: AsyncIterator, allowDestination?: boolean): void; - _transform(item: S, done: (result: T) => void): void; + _transform(item: S, done: (result: T) => void): void; - _closeWhenDone(): void; + _closeWhenDone(): void; - constructor(source?: AsyncIterator | TransformIteratorOptions, options?: TransformIteratorOptions); - } + constructor(source?: AsyncIterator | TransformIteratorOptions, options?: TransformIteratorOptions); + } - export interface SimpleTransformIteratorOptions extends TransformIteratorOptions { - offset?: number; - limit?: number; - prepend?: T[]; - append?: T[]; + export interface SimpleTransformIteratorOptions extends TransformIteratorOptions { + offset?: number; + limit?: number; + prepend?: T[]; + append?: T[]; - filter?(item: S): boolean; + filter?(item: S): boolean; - map?(item: S): T; + map?(item: S): T; - transform?(item: S, callback: (result: T) => void): void; - } + transform?(item: S, callback: (result: T) => void): void; + } - export class SimpleTransformIterator extends TransformIterator { - _offset: number; - _limit: number; - _prepender?: ArrayIterator; - _appender?: ArrayIterator; + export class SimpleTransformIterator extends TransformIterator { + _offset: number; + _limit: number; + _prepender?: ArrayIterator; + _appender?: ArrayIterator; - _filter?(item: S): boolean; + _filter?(item: S): boolean; - _map?(item: S): T; + _map?(item: S): T; - _transform(item: S, done: (result: T) => void): void; + _transform(item: S, done: (result: T) => void): void; - _insert(inserter: AsyncIterator, done: () => void): void; + _insert(inserter: AsyncIterator, done: () => void): void; - constructor(source?: AsyncIterator | SimpleTransformIteratorOptions, - options?: SimpleTransformIteratorOptions); - } + constructor(source?: AsyncIterator | SimpleTransformIteratorOptions, + options?: SimpleTransformIteratorOptions); + } - export class MultiTransformIterator extends TransformIterator { - _transformerQueue: S[]; + export class MultiTransformIterator extends TransformIterator { + _transformerQueue: S[]; - _createTransformer(element: S): AsyncIterator; + _createTransformer(element: S): AsyncIterator; - constructor(source?: AsyncIterator | TransformIteratorOptions, options?: TransformIteratorOptions); - } + constructor(source?: AsyncIterator | TransformIteratorOptions, options?: TransformIteratorOptions); + } - export class ClonedIterator extends TransformIterator { - _readPosition: number; + export class ClonedIterator extends TransformIterator { + _readPosition: number; - constructor(source?: AsyncIterator); - } + constructor(source?: AsyncIterator); + } } diff --git a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts index 4cfc2551..bb2b8bdb 100644 --- a/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts +++ b/src/fetcher/connections/prefetch/ConnectionsProviderPrefetch.ts @@ -2,9 +2,7 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable } from "inversify"; import Catalog from "../../../Catalog"; import Context from "../../../Context"; -import EventType from "../../../enums/EventType"; import TYPES, { ConnectionsFetcherFactory } from "../../../types"; -import Units from "../../../util/Units"; import IConnection from "../IConnection"; import IConnectionsFetcher from "../IConnectionsFetcher"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; @@ -22,7 +20,6 @@ import ConnectionsStore from "./ConnectionsStore"; export default class ConnectionsProviderPrefetch implements IConnectionsProvider { private static MAX_CONNECTIONS = 20000; - private static REPORTING_THRESHOLD = Units.fromHours(.25); private readonly context: Context; private readonly connectionsFetcher: IConnectionsFetcher; @@ -31,7 +28,6 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider private startedPrefetching: boolean; private connectionsIterator: AsyncIterator; private connectionsIteratorOptions: IConnectionsIteratorOptions; - private lastReportedDepartureTime: Date; constructor( @inject(TYPES.ConnectionsFetcherFactory) connectionsFetcherFactory: ConnectionsFetcherFactory, @@ -57,18 +53,8 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.connectionsFetcher.setIteratorOptions(options); this.connectionsIterator = this.connectionsFetcher.createIterator(); - - this.connectionsIterator - .take(ConnectionsProviderPrefetch.MAX_CONNECTIONS) - .on("end", () => this.connectionsStore.finish()) - .each((connection: IConnection) => { - - if (this.context) { - this.maybeEmitPrefetchEvent(connection); - } - - this.connectionsStore.append(connection); - }); + this.connectionsStore.setSourceIterator(this.connectionsIterator); + this.connectionsStore.startPrimaryPush(ConnectionsProviderPrefetch.MAX_CONNECTIONS); }, 0); } } @@ -86,20 +72,4 @@ export default class ConnectionsProviderPrefetch implements IConnectionsProvider this.connectionsIteratorOptions = options; } - private maybeEmitPrefetchEvent(connection: IConnection): void { - if (!this.lastReportedDepartureTime) { - this.lastReportedDepartureTime = connection.departureTime; - - this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); - return; - } - - const timeSinceLastEvent = connection.departureTime.valueOf() - this.lastReportedDepartureTime.valueOf(); - - if (timeSinceLastEvent > ConnectionsProviderPrefetch.REPORTING_THRESHOLD) { - this.lastReportedDepartureTime = connection.departureTime; - - this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); - } - } } diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 2eb5f6d3..6fcb63ec 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -1,9 +1,10 @@ -import { ArrayIterator, AsyncIterator } from "asynciterator"; +import { AsyncIterator, EmptyIterator } from "asynciterator"; import { PromiseProxyIterator } from "asynciterator-promiseproxy"; import Context from "../../../Context"; import EventType from "../../../enums/EventType"; import BinarySearch from "../../../util/BinarySearch"; import ExpandingIterator from "../../../util/iterators/ExpandingIterator"; +import Units from "../../../util/Units"; import IConnection from "../IConnection"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; import ArrayViewIterator from "./ArrayViewIterator"; @@ -30,12 +31,19 @@ interface IExpandingForwardView { */ export default class ConnectionsStore { + private static REPORTING_THRESHOLD = Units.fromHours(.25); + private readonly context: Context; private readonly store: IConnection[]; private readonly binarySearch: BinarySearch; + + private sourceIterator: AsyncIterator; private deferredBackwardViews: IDeferredBackwardView[]; private expandingForwardViews: IExpandingForwardView[]; - private hasFinished: boolean; + + private hasFinishedPrimary: boolean; + private isContinuing: boolean; + private lastReportedDepartureTime: Date; constructor(context?: Context) { this.context = context; @@ -43,59 +51,36 @@ export default class ConnectionsStore { this.binarySearch = new BinarySearch(this.store, (connection) => connection.departureTime.valueOf()); this.deferredBackwardViews = []; this.expandingForwardViews = []; - this.hasFinished = false; + this.hasFinishedPrimary = false; } - /** - * Add a new [[IConnection]] to the store. - * - * Additionally, this method checks if any forward iterator views can be expanded or if any backward iterator can be - * resolved - */ - public append(connection: IConnection) { - this.store.push(connection); - - // Check if any deferred backward views are satisfied - if (this.deferredBackwardViews.length) { - this.deferredBackwardViews = this.deferredBackwardViews - .filter(({ lowerBoundDate, upperBoundDate, resolve }) => { - - if (connection.departureTime > upperBoundDate) { - const { iterator } = this.getIteratorView(true, lowerBoundDate, upperBoundDate); - - this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); - - resolve(iterator); - return false; - } - - return true; - }); - } - - // Check if any forward views can be expanded - if (this.expandingForwardViews.length) { - this.expandingForwardViews = this.expandingForwardViews - .filter(({ tryExpand }) => - tryExpand(connection, this.store.length - 1), - ); - } + public setSourceIterator(iterator: AsyncIterator): void { + this.sourceIterator = iterator; } - /** - * Signals that the store will no longer be appended. - * [[getIterator]] never returns a deferred backward view after this, because those would never get resolved - */ - public finish(): void { - this.hasFinished = true; + public startPrimaryPush(maxConnections: number): void { + + this.sourceIterator + .transform({ + limit: maxConnections, + destroySource: false, + }) + .on("end", () => this.finishPrimaryPush()) + .each((connection: IConnection) => { + if (this.context) { + this.maybeEmitPrefetchEvent(connection); + } + + this.append(connection); + }); } public getIterator(iteratorOptions: IConnectionsIteratorOptions): AsyncIterator { const { backward } = iteratorOptions; let { lowerBoundDate, upperBoundDate } = iteratorOptions; - if (this.hasFinished && this.store.length === 0) { - return new ArrayIterator([]); + if (this.hasFinishedPrimary && this.store.length === 0) { + return new EmptyIterator(); } const firstConnection = this.store[0]; @@ -104,6 +89,10 @@ export default class ConnectionsStore { const lastConnection = this.store[this.store.length - 1]; const lastDepartureTime = lastConnection && lastConnection.departureTime; + if (lowerBoundDate && lowerBoundDate < firstDepartureTime) { + throw new Error("Must supply a lowerBoundDate after the first prefetched connection"); + } + if (backward) { if (!upperBoundDate) { @@ -118,11 +107,21 @@ export default class ConnectionsStore { // If the store is still empty or the latest departure time isn't later than the upperBoundDate, // then return a promise proxy iterator - if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { + const notFinishedScenario = !this.hasFinishedPrimary + && (!lastDepartureTime || lastDepartureTime <= upperBoundDate); + + const finishedScenario = this.hasFinishedPrimary + && lastDepartureTime < upperBoundDate; + + if (notFinishedScenario || finishedScenario) { const { deferred, promise } = this.createDeferredBackwardView(lowerBoundDate, upperBoundDate); this.deferredBackwardViews.push(deferred); + if (this.hasFinishedPrimary) { + this.continueAfterFinishing(); + } + return new PromiseProxyIterator(() => promise); } @@ -140,20 +139,126 @@ export default class ConnectionsStore { // If the store is still empty or the latest departure time isn't later than the upperBoundDate, // then return a an expanding iterator view - if (!this.hasFinished && (!lastDepartureTime || lastDepartureTime <= upperBoundDate)) { + const notFinishedScenario = !this.hasFinishedPrimary + && (!lastDepartureTime || lastDepartureTime <= upperBoundDate); + + const finishedScenario = this.hasFinishedPrimary + && lastDepartureTime < upperBoundDate; + + if (notFinishedScenario || finishedScenario) { const { view, iterator } = this.createExpandingForwardView(lowerBoundDate, upperBoundDate); this.expandingForwardViews.push(view); + if (this.hasFinishedPrimary) { + this.continueAfterFinishing(); + } + return iterator; } } - // Else if the whole interval is available, or the store has finished, return an iterator immediately - const { iterator } = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); - this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); + if (this.hasFinishedPrimary) { + + // If the whole interval is part of the prefetched window, return an iterator view + // [------ prefetch window ------] + // [-- requested iterator --] + if (lowerBoundDate >= firstDepartureTime && upperBoundDate < lastDepartureTime) { + const { iterator } = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); + + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); + + return iterator; + } + } + + throw new Error("This shouln\'t happen"); + } + + /** + * Add a new [[IConnection]] to the store. + * + * Additionally, this method checks if any forward iterator views can be expanded or if any backward iterator can be + * resolved + * + * @returns the number of unsatisfied views + */ + private append(connection: IConnection): number { + this.store.push(connection); + + // Check if any deferred backward views are satisfied + if (this.deferredBackwardViews.length) { + this.deferredBackwardViews = this.deferredBackwardViews + .filter(({ lowerBoundDate, upperBoundDate, resolve }) => { + + if (connection.departureTime > upperBoundDate) { + const { iterator } = this.getIteratorView(true, lowerBoundDate, upperBoundDate); + + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); + + resolve(iterator); + return false; + } + + return true; + }); + } + + // Check if any forward views can be expanded + if (this.expandingForwardViews.length) { + this.expandingForwardViews = this.expandingForwardViews + .filter(({ tryExpand }) => tryExpand(connection, this.store.length - 1)); + } + + return this.deferredBackwardViews.length + this.expandingForwardViews.length; + } + + /** + * Signals that the store will no longer be appended. + * [[getIterator]] never returns a deferred backward view after this, because those would never get resolved + */ + private finishPrimaryPush(): void { + this.hasFinishedPrimary = true; + console.log("Finish"); + + if (this.deferredBackwardViews.length || this.expandingForwardViews.length) { + this.continueAfterFinishing(); + } + } + + private finishSecondaryPush(): void { + this.isContinuing = false; + } + + private continueAfterFinishing(): void { + if (!this.isContinuing) { + this.isContinuing = true; + + setTimeout(() => { + console.log("Red", this.deferredBackwardViews); + this.startSecondaryPush(); + }, 0); + } + } - return iterator; + private startSecondaryPush(): void { + console.log(this.sourceIterator.closed, this.sourceIterator.ended); + + const secondaryPushIterator = this.sourceIterator + .transform({}) + .on("end", () => this.finishSecondaryPush()); + + secondaryPushIterator.each((connection: IConnection) => { + if (this.context) { + this.maybeEmitPrefetchEvent(connection); + } + + const unsatisfiedViewCount = this.append(connection); + + if (unsatisfiedViewCount === 0) { + secondaryPushIterator.close(); + } + }); } private createDeferredBackwardView(lowerBoundDate, upperBoundDate): @@ -243,4 +348,21 @@ export default class ConnectionsStore { this.context.emit(EventType.ConnectionIteratorView, lowerBoundDate, upperBoundDate, completed); } } + + private maybeEmitPrefetchEvent(connection: IConnection): void { + if (!this.lastReportedDepartureTime) { + this.lastReportedDepartureTime = connection.departureTime; + + this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); + return; + } + + const timeSinceLastEvent = connection.departureTime.valueOf() - this.lastReportedDepartureTime.valueOf(); + + if (timeSinceLastEvent > ConnectionsStore.REPORTING_THRESHOLD) { + this.lastReportedDepartureTime = connection.departureTime; + + this.context.emit(EventType.ConnectionPrefetch, this.lastReportedDepartureTime); + } + } } diff --git a/src/fetcher/stops/StopsPrefetcher.ts b/src/fetcher/stops/StopsPrefetcher.ts deleted file mode 100644 index e69de29b..00000000 From 06f5bf768f3d7080774048994648b4948673befb Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 7 Feb 2019 15:33:33 +0100 Subject: [PATCH 64/69] Fix ConnectionStore tests --- .../prefetch/ConnectionsStore.test.ts | 196 ++++++------------ .../connections/prefetch/ConnectionsStore.ts | 12 +- src/util/iterators/ExpandingIterator.ts | 18 +- 3 files changed, 78 insertions(+), 148 deletions(-) diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts index a61a4e43..0f931ff0 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -1,4 +1,4 @@ -import { AsyncIterator } from "asynciterator"; +import { ArrayIterator, AsyncIterator } from "asynciterator"; import "jest"; import IConnection from "../IConnection"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; @@ -12,180 +12,102 @@ describe("[ConnectionsStore]", () => { * and both Dates and Numbers return a number */ - describe("Loaded sync", () => { + let connectionsStore; + let createIterator; - let connectionsStore; - let createIterator; + beforeEach(() => { + const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; + // @ts-ignore + const fakeConnections: IConnection[] = fakeDepartureTimes + .map((departureTime) => ({ departureTime })); - beforeEach(() => { - const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; - // @ts-ignore - const fakeConnections: IConnection[] = fakeDepartureTimes - .map((departureTime) => ({ departureTime })); + const fakeSourceIterator = new ArrayIterator(fakeConnections); - connectionsStore = new ConnectionsStore(); - for (const connection of fakeConnections) { - connectionsStore.append(connection); - } + connectionsStore = new ConnectionsStore(); - connectionsStore.finish(); + connectionsStore.setSourceIterator(fakeSourceIterator); + connectionsStore.startPrimaryPush(500); - createIterator = (backward, lowerBoundDate, upperBoundDate): Promise> => { - const iteratorOptions: IConnectionsIteratorOptions = { - backward, - }; + createIterator = (backward, lowerBoundDate, upperBoundDate): Promise> => { + return new Promise((resolve) => { + // Primary push start async, so get iterator async + setTimeout(() => { + const iteratorOptions: IConnectionsIteratorOptions = { + backward, + }; - if (lowerBoundDate) { - iteratorOptions.lowerBoundDate = (lowerBoundDate as unknown) as Date; - } + if (lowerBoundDate) { + iteratorOptions.lowerBoundDate = (lowerBoundDate as unknown) as Date; + } - if (upperBoundDate) { - iteratorOptions.upperBoundDate = (upperBoundDate as unknown) as Date; - } + if (upperBoundDate) { + iteratorOptions.upperBoundDate = (upperBoundDate as unknown) as Date; + } - return connectionsStore.getIterator(iteratorOptions); - }; - - }); - - describe("backward", () => { - - it("upperBoundDate is loaded & exists in store", async (done) => { - const iteratorView = createIterator(true, null, 6); - - const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6]; - let current = expected.length - 1; - - iteratorView.each((str: IConnection) => { - expect(expected[current--]).toBe(str.departureTime); - }); - - iteratorView.on("end", () => { - expect(current).toBe(-1); - done(); - }); + resolve(connectionsStore.getIterator(iteratorOptions)); + }, 100); }); + }; + }); - it("upperBoundDate is loaded but doesn\'t exist in store", async (done) => { - const iteratorView = createIterator(true, null, 4); + describe("backward", () => { - const expected = [1, 2, 3, 3]; - let current = expected.length - 1; + it("upperBoundDate is loaded & exists in store", async (done) => { + const iteratorView = await createIterator(true, null, 6); - iteratorView.each((str: IConnection) => { - expect(expected[current--]).toBe(str.departureTime); - }); + const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6]; + let current = expected.length - 1; - iteratorView.on("end", () => { - expect(current).toBe(-1); - done(); - }); + iteratorView.each((str: IConnection) => { + expect(expected[current--]).toBe(str.departureTime); }); + iteratorView.on("end", () => { + expect(current).toBe(-1); + done(); + }); }); - describe("forward", () => { - - it("lowerBoundDate is loaded & exists in store", async (done) => { - const iteratorView = createIterator(false, 3, null); - - const expected = [3, 3, 5, 6, 6, 6, 6, 7, 7]; - let current = 0; + it("upperBoundDate is loaded but doesn\'t exist in store", async (done) => { + const iteratorView = await createIterator(true, null, 4); - iteratorView.each((str: IConnection) => { - expect(expected[current++]).toBe(str.departureTime); - }); + const expected = [1, 2, 3, 3]; + let current = expected.length - 1; - iteratorView.on("end", () => { - expect(current).toBe(expected.length); - done(); - }); + iteratorView.each((str: IConnection) => { + expect(expected[current--]).toBe(str.departureTime); }); - it("lowerBoundDate is loaded but doesn\'t exist in store", async (done) => { - const iteratorView = createIterator(false, 4, null); - - const expected = [5, 6, 6, 6, 6, 7, 7]; - let current = 0; - - iteratorView.each((str: IConnection) => { - expect(expected[current++]).toBe(str.departureTime); - }); - - iteratorView.on("end", () => { - expect(current).toBe(expected.length); - done(); - }); + iteratorView.on("end", () => { + expect(current).toBe(-1); + done(); }); - }); }); - describe("Loaded async", () => { - jest.setTimeout(1000000); - - let connectionsStore; - - beforeEach(() => { - - const fakeDepartureTimes = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7, 8]; - // @ts-ignore - const fakeConnections: IConnection[] = fakeDepartureTimes - .map((departureTime) => ({ departureTime })); - - connectionsStore = new ConnectionsStore(); - - // Append first few connections sync - for (const connection of fakeConnections.slice(0, 6)) { - connectionsStore.append(connection); - } - - // Append remaining connections async - let i = 6; - const appendNext = () => { - connectionsStore.append(fakeConnections[i++]); + describe("forward", () => { - if (i < fakeConnections.length) { - setTimeout(appendNext, 100); + it("lowerBoundDate is loaded & exists in store", async (done) => { + const iteratorView = await createIterator(false, 3, 6); - } else { - connectionsStore.finish(); - } - }; - - setTimeout(appendNext, 100); - }); - - it("backward", async (done) => { - const iteratorOptions: IConnectionsIteratorOptions = { - backward: true, - upperBoundDate: (7 as unknown) as Date, - }; - const iteratorView: AsyncIterator = connectionsStore.getIterator(iteratorOptions); - - const expected = [1, 2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; - let current = expected.length - 1; + const expected = [3, 3, 5, 6, 6, 6, 6]; + let current = 0; iteratorView.each((str: IConnection) => { - expect(expected[current--]).toBe(str.departureTime); + expect(expected[current++]).toBe(str.departureTime); }); iteratorView.on("end", () => { - expect(current).toBe(-1); + expect(current).toBe(expected.length); done(); }); }); - it("forward", async (done) => { - const iteratorOptions: IConnectionsIteratorOptions = { - backward: false, - lowerBoundDate: (2 as unknown) as Date, - upperBoundDate: (7 as unknown) as Date, - }; - const iteratorView: AsyncIterator = connectionsStore.getIterator(iteratorOptions); + it("lowerBoundDate is loaded but doesn\'t exist in store", async (done) => { + const iteratorView = await createIterator(false, 4, 6); - const expected = [2, 3, 3, 5, 6, 6, 6, 6, 7, 7]; + const expected = [5, 6, 6, 6, 6]; let current = 0; iteratorView.each((str: IConnection) => { diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 6fcb63ec..e3888c6c 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -219,7 +219,6 @@ export default class ConnectionsStore { */ private finishPrimaryPush(): void { this.hasFinishedPrimary = true; - console.log("Finish"); if (this.deferredBackwardViews.length || this.expandingForwardViews.length) { this.continueAfterFinishing(); @@ -234,16 +233,11 @@ export default class ConnectionsStore { if (!this.isContinuing) { this.isContinuing = true; - setTimeout(() => { - console.log("Red", this.deferredBackwardViews); - this.startSecondaryPush(); - }, 0); + setTimeout(() => this.startSecondaryPush(), 0); } } private startSecondaryPush(): void { - console.log(this.sourceIterator.closed, this.sourceIterator.ended); - const secondaryPushIterator = this.sourceIterator .transform({}) .on("end", () => this.finishSecondaryPush()); @@ -307,8 +301,8 @@ export default class ConnectionsStore { return true; // Keep in expanding forward views } else { - expandingIterator.close(); - iterator.close(); + expandingIterator.closeAfterFlush(); + // iterator.close(); this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); diff --git a/src/util/iterators/ExpandingIterator.ts b/src/util/iterators/ExpandingIterator.ts index 60f9a7c0..28153cb4 100644 --- a/src/util/iterators/ExpandingIterator.ts +++ b/src/util/iterators/ExpandingIterator.ts @@ -3,11 +3,13 @@ import { AsyncIterator } from "asynciterator"; export default class ExpandingIterator extends AsyncIterator { private buffer: T[]; + private shouldClose: boolean; constructor() { super(); this.buffer = []; + this.shouldClose = false; } public read(): T { @@ -18,6 +20,12 @@ export default class ExpandingIterator extends AsyncIterator { } else { item = null; + + if (this.shouldClose) { + this.close(); + + } + this.readable = false; } @@ -25,7 +33,13 @@ export default class ExpandingIterator extends AsyncIterator { } public write(item: T): void { - this.buffer.push(item); - this.readable = true; + if (!this.shouldClose) { + this.buffer.push(item); + this.readable = true; + } + } + + public closeAfterFlush(): void { + this.shouldClose = true; } } From 94e0666a5975adf3f129e7f52c30e5fad68ba142 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 7 Feb 2019 15:50:58 +0100 Subject: [PATCH 65/69] Fix demo test --- .../prefetch/ConnectionsStore.test.ts | 1 + .../connections/prefetch/ConnectionsStore.ts | 19 ++++++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts index 0f931ff0..4348e148 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.test.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.test.ts @@ -31,6 +31,7 @@ describe("[ConnectionsStore]", () => { createIterator = (backward, lowerBoundDate, upperBoundDate): Promise> => { return new Promise((resolve) => { // Primary push start async, so get iterator async + // Running a query is most often initiated by a user event, while prefetching start automatically setTimeout(() => { const iteratorOptions: IConnectionsIteratorOptions = { backward, diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index e3888c6c..87f40e09 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -158,21 +158,18 @@ export default class ConnectionsStore { } } - if (this.hasFinishedPrimary) { + // If the whole interval is part of the prefetched window, return an iterator view + // [------ prefetch window ------] + // [-- requested iterator --] + if (lowerBoundDate >= firstDepartureTime && upperBoundDate < lastDepartureTime) { + const { iterator } = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); - // If the whole interval is part of the prefetched window, return an iterator view - // [------ prefetch window ------] - // [-- requested iterator --] - if (lowerBoundDate >= firstDepartureTime && upperBoundDate < lastDepartureTime) { - const { iterator } = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); + this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); - this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, true); - - return iterator; - } + return iterator; } - throw new Error("This shouln\'t happen"); + throw new Error("This shouldn\'t happen"); } /** From 28ac009ba67af68269284223c488b0d41800f9e5 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 7 Feb 2019 16:25:42 +0100 Subject: [PATCH 66/69] Fix QueryRunnerEarliestArrivalFirst --- .../connections/prefetch/ConnectionsStore.ts | 28 ++++++++----------- .../prefetch/IDeferredBackwardView.ts | 8 ++++++ .../prefetch/IExpandingForwardView.ts | 7 +++++ 3 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 src/fetcher/connections/prefetch/IDeferredBackwardView.ts create mode 100644 src/fetcher/connections/prefetch/IExpandingForwardView.ts diff --git a/src/fetcher/connections/prefetch/ConnectionsStore.ts b/src/fetcher/connections/prefetch/ConnectionsStore.ts index 87f40e09..33612d79 100644 --- a/src/fetcher/connections/prefetch/ConnectionsStore.ts +++ b/src/fetcher/connections/prefetch/ConnectionsStore.ts @@ -8,18 +8,8 @@ import Units from "../../../util/Units"; import IConnection from "../IConnection"; import IConnectionsIteratorOptions from "../IConnectionsIteratorOptions"; import ArrayViewIterator from "./ArrayViewIterator"; - -interface IDeferredBackwardView { - lowerBoundDate: Date; - upperBoundDate: Date; - resolve: (iterator: AsyncIterator) => void; -} - -interface IExpandingForwardView { - lowerBoundDate: Date; - upperBoundDate: Date; - tryExpand: (connection: IConnection, index: number) => boolean; -} +import IDeferredBackwardView from "./IDeferredBackwardView"; +import IExpandingForwardView from "./IExpandingForwardView"; /** * Class used while prefetching [[IConnection]] instances. It allows appending connections @@ -132,7 +122,8 @@ export default class ConnectionsStore { } if (!upperBoundDate) { - upperBoundDate = lastDepartureTime; + // Mock +infinity + upperBoundDate = new Date(lowerBoundDate.valueOf() + Units.fromHours(24)); } this.emitConnectionViewEvent(lowerBoundDate, upperBoundDate, false); @@ -158,9 +149,7 @@ export default class ConnectionsStore { } } - // If the whole interval is part of the prefetched window, return an iterator view - // [------ prefetch window ------] - // [-- requested iterator --] + // If the whole interval fits inside the prefetched window, return an iterator view if (lowerBoundDate >= firstDepartureTime && upperBoundDate < lastDepartureTime) { const { iterator } = this.getIteratorView(backward, lowerBoundDate, upperBoundDate); @@ -292,6 +281,13 @@ export default class ConnectionsStore { lastStoreIndex = storeIndex; + // No need to keep trying to expand if the consumer has closed it + if (iterator.closed) { + expandingIterator.close(); + + return false; // Remove from expanding forward views + } + if (connection.departureTime <= upperBoundDate) { expandingIterator.write(connection); diff --git a/src/fetcher/connections/prefetch/IDeferredBackwardView.ts b/src/fetcher/connections/prefetch/IDeferredBackwardView.ts new file mode 100644 index 00000000..781878ea --- /dev/null +++ b/src/fetcher/connections/prefetch/IDeferredBackwardView.ts @@ -0,0 +1,8 @@ +import { AsyncIterator } from "asynciterator"; +import IConnection from "../IConnection"; + +export default interface IDeferredBackwardView { + lowerBoundDate: Date; + upperBoundDate: Date; + resolve: (iterator: AsyncIterator) => void; +} diff --git a/src/fetcher/connections/prefetch/IExpandingForwardView.ts b/src/fetcher/connections/prefetch/IExpandingForwardView.ts new file mode 100644 index 00000000..7e376e1a --- /dev/null +++ b/src/fetcher/connections/prefetch/IExpandingForwardView.ts @@ -0,0 +1,7 @@ +import IConnection from "../IConnection"; + +export default interface IExpandingForwardView { + lowerBoundDate: Date; + upperBoundDate: Date; + tryExpand: (connection: IConnection, index: number) => boolean; +} From ea38ab289f1a9cad6b6be697cbda7c6e3b12ff3f Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 7 Feb 2019 16:53:19 +0100 Subject: [PATCH 67/69] 1. CSA profile emit if context exists. --- src/planner/public-transport/CSA/util/ProfileUtil.ts | 4 ++++ src/planner/public-transport/CSAProfile.ts | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/planner/public-transport/CSA/util/ProfileUtil.ts b/src/planner/public-transport/CSA/util/ProfileUtil.ts index 2a178f4e..a8b38705 100644 --- a/src/planner/public-transport/CSA/util/ProfileUtil.ts +++ b/src/planner/public-transport/CSA/util/ProfileUtil.ts @@ -17,6 +17,10 @@ export default class ProfileUtil { result[stop] = profilesByStop[stop].filter((profile) => profile.departureTime !== Infinity, ); + + if (result[stop].length === 0) { + delete result[stop]; + } } } return result; diff --git a/src/planner/public-transport/CSAProfile.ts b/src/planner/public-transport/CSAProfile.ts index e1c487c6..25eb5d64 100644 --- a/src/planner/public-transport/CSAProfile.ts +++ b/src/planner/public-transport/CSAProfile.ts @@ -243,8 +243,10 @@ export default class CSAProfile implements IPublicTransportPlanner { this.query.minimumWalkingSpeed, ); - if (reachableStops.length <= 1 && reachableStops[0].stop.id === geoId && this.context) { - this.context.emit(EventType.AbortQuery, "No reachable stops at arrival location"); + if (reachableStops.length <= 1 && reachableStops[0].stop.id === geoId) { + if (this.context) { + this.context.emit(EventType.AbortQuery, "No reachable stops at arrival location"); + } return false; } @@ -286,8 +288,10 @@ export default class CSAProfile implements IPublicTransportPlanner { } } - if (this.initialReachableStops.length <= 1 && this.initialReachableStops[0].stop.id === geoId && this.context) { - this.context.emit(EventType.AbortQuery, "No reachable stops at departure location"); + if (this.initialReachableStops.length <= 1 && this.initialReachableStops[0].stop.id === geoId) { + if (this.context) { + this.context.emit(EventType.AbortQuery, "No reachable stops at departure location"); + } return false; } From b35a566071a9a0112cbbef91040fb8317f9cb3c9 Mon Sep 17 00:00:00 2001 From: SimonVanneste <38662306+SimonVanneste@users.noreply.github.com> Date: Fri, 8 Feb 2019 09:35:03 +0100 Subject: [PATCH 68/69] Update package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a0a03db2..a7a71109 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plannerjs", - "version": "0.0.2-alpha", + "version": "0.0.3-alpha", "description": "The JavaScript framework for journey planning.", "main": "lib/index.js", "license": "MIT", From 42d365afe4847b079bfea95d049b66699dadc3da Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Fri, 8 Feb 2019 09:57:22 +0100 Subject: [PATCH 69/69] Restored some things lost by merge --- src/errors/LocationResolverError.ts | 3 +++ src/query-runner/LocationResolverDefault.ts | 9 ++++++--- src/query-runner/QueryRunnerDefault.ts | 14 +++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 src/errors/LocationResolverError.ts diff --git a/src/errors/LocationResolverError.ts b/src/errors/LocationResolverError.ts new file mode 100644 index 00000000..65175f81 --- /dev/null +++ b/src/errors/LocationResolverError.ts @@ -0,0 +1,3 @@ +export default class LocationResolverError extends Error { + +} diff --git a/src/query-runner/LocationResolverDefault.ts b/src/query-runner/LocationResolverDefault.ts index 86259b6e..88513161 100644 --- a/src/query-runner/LocationResolverDefault.ts +++ b/src/query-runner/LocationResolverDefault.ts @@ -1,4 +1,5 @@ import { inject, injectable } from "inversify"; +import LocationResolverError from "../errors/LocationResolverError"; import IStop from "../fetcher/stops/IStop"; import IStopsProvider from "../fetcher/stops/IStopsProvider"; import ILocation from "../interfaces/ILocation"; @@ -30,7 +31,7 @@ export default class LocationResolverDefault implements ILocationResolver { return this.resolveById(input); } - return Promise.reject(`Location "${input}" is a string, but not an ID`); + return Promise.reject(new LocationResolverError(`Location "${input}" is a string, but not an ID`)); } const location: ILocation = input; @@ -44,7 +45,9 @@ export default class LocationResolverDefault implements ILocationResolver { } if (!hasCoords) { - return Promise.reject(`Location "${JSON.stringify(input)}" should have latitude and longitude`); + return Promise.reject( + new LocationResolverError(`Location "${JSON.stringify(input)}" should have latitude and longitude`), + ); } return location; @@ -62,7 +65,7 @@ export default class LocationResolverDefault implements ILocationResolver { }; } - throw new Error(`No fetcher for id ${id}`); + return Promise.reject(new LocationResolverError(`No fetcher for id ${id}`)); } private isId(testString: string): boolean { diff --git a/src/query-runner/QueryRunnerDefault.ts b/src/query-runner/QueryRunnerDefault.ts index 635d3830..829e47da 100644 --- a/src/query-runner/QueryRunnerDefault.ts +++ b/src/query-runner/QueryRunnerDefault.ts @@ -1,6 +1,8 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable } from "inversify"; +import { cat } from "shelljs"; import Defaults from "../Defaults"; +import InvalidQueryError from "../errors/InvalidQueryError"; import ILocation from "../interfaces/ILocation"; import IPath from "../interfaces/IPath"; import IQuery from "../interfaces/IQuery"; @@ -37,7 +39,7 @@ export default class QueryRunnerDefault implements IQueryRunner { return this.publicTransportPlanner.plan(resolvedQuery); } else { - return Promise.reject("Query not supported"); + throw new InvalidQueryError("Query should have publicTransportOnly = true"); } } @@ -83,8 +85,14 @@ export default class QueryRunnerDefault implements IQueryRunner { resolvedQuery.maximumArrivalTime = newMaximumArrivalTime; } - resolvedQuery.from = await this.resolveEndpoint(from); - resolvedQuery.to = await this.resolveEndpoint(to); + try { + resolvedQuery.from = await this.resolveEndpoint(from); + resolvedQuery.to = await this.resolveEndpoint(to); + + } catch (e) { + return Promise.reject(new InvalidQueryError(e)); + } + resolvedQuery.minimumWalkingSpeed = minimumWalkingSpeed || walkingSpeed || Defaults.defaultMinimumWalkingSpeed; resolvedQuery.maximumWalkingSpeed = maximumWalkingSpeed || walkingSpeed || Defaults.defaultMaximumWalkingSpeed;