From f6211c91f8477ca76286d7fa88206488203130ca Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Mon, 3 Dec 2018 16:36:29 +0100 Subject: [PATCH 01/20] Hydra parser fix for De Lijn connections --- src/demo.ts | 23 +++++++++++++++---- .../connections/ld-fetch/HydraPageParser.ts | 15 +++++++++++- src/inversify.config.ts | 11 +++++++-- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/demo.ts b/src/demo.ts index 3d770876..4dbd74f7 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -21,10 +21,11 @@ export default async (logResults) => { const publicTransportResult = await planner.query({ publicTransportOnly: true, - from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster - to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters + from: "https://data.delijn.be/stops/201657", // Ingelmunster + to: "https://data.delijn.be/stops/205910", // Ghent-Sint-Pieters minimumDepartureTime: new Date(), - maximumTransferDuration: Units.fromHours(.5), + maximumTransfers: 2, + maximumTransferDuration: Units.fromHours(.05), }); console.timeEnd("Public transport planner"); @@ -35,7 +36,21 @@ export default async (logResults) => { let path = publicTransportResult.read(); while (path && i < 5) { - console.log(i++, path); + // console.log(i++, path); + + console.log(i++); + + path.steps.forEach((step) => { + console.log(step.startTime); + console.log(step.startLocation.name); + console.log(step.travelMode); + console.log(step.stopTime); + console.log(step.stopLocation.name); + console.log(""); + }); + + console.log(""); + console.log(""); path = publicTransportResult.read(); } diff --git a/src/fetcher/connections/ld-fetch/HydraPageParser.ts b/src/fetcher/connections/ld-fetch/HydraPageParser.ts index 7a0adcf0..e28779a3 100644 --- a/src/fetcher/connections/ld-fetch/HydraPageParser.ts +++ b/src/fetcher/connections/ld-fetch/HydraPageParser.ts @@ -63,7 +63,10 @@ export default class HydraPageParser { } private getDocumentIri(): string { - const typeTriple = this.triples.find( + // 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", @@ -71,6 +74,16 @@ export default class HydraPageParser { ), ); + 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"); } diff --git a/src/inversify.config.ts b/src/inversify.config.ts index ad05a770..e790b646 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -82,9 +82,16 @@ container.bind>(TYPES.StopsFetcherFactory) ); // Init catalog +// const catalog = new Catalog(); +// catalog.addStopsFetcher("http://irail.be/stations/NMBS/", "https://irail.be/stations/NMBS"); +// catalog.addConnectionsFetcher("https://graph.irail.be/sncb/connections", TravelMode.Train); + const catalog = new Catalog(); -catalog.addStopsFetcher("http://irail.be/stations/NMBS/", "https://irail.be/stations/NMBS"); -catalog.addConnectionsFetcher("https://graph.irail.be/sncb/connections", TravelMode.Train); +catalog.addStopsFetcher( + "https://data.delijn.be/stops/", + "https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops", +); +catalog.addConnectionsFetcher("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); container.bind(TYPES.Catalog).toConstantValue(catalog); From 1121d89d0a04c4155edcbae757de9d40dc055082 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Tue, 4 Dec 2018 12:00:55 +0100 Subject: [PATCH 02/20] Implement MergeIterator --- src/demo.ts | 3 +- ...erator.ts => FilterUniquePathsIterator.ts} | 2 +- .../exponential/QueryRunnerExponential.ts | 6 +- src/util/MergeIterator.test.ts | 70 ++++++++++ src/util/MergeIterator.ts | 125 ++++++++++++++++++ 5 files changed, 200 insertions(+), 6 deletions(-) rename src/query-runner/exponential/{FilterUniqueIterator.ts => FilterUniquePathsIterator.ts} (84%) create mode 100644 src/util/MergeIterator.test.ts create mode 100644 src/util/MergeIterator.ts diff --git a/src/demo.ts b/src/demo.ts index 4dbd74f7..1cf6e61d 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -24,8 +24,7 @@ export default async (logResults) => { from: "https://data.delijn.be/stops/201657", // Ingelmunster to: "https://data.delijn.be/stops/205910", // Ghent-Sint-Pieters minimumDepartureTime: new Date(), - maximumTransfers: 2, - maximumTransferDuration: Units.fromHours(.05), + maximumTransferDuration: Units.fromHours(.01), }); console.timeEnd("Public transport planner"); diff --git a/src/query-runner/exponential/FilterUniqueIterator.ts b/src/query-runner/exponential/FilterUniquePathsIterator.ts similarity index 84% rename from src/query-runner/exponential/FilterUniqueIterator.ts rename to src/query-runner/exponential/FilterUniquePathsIterator.ts index d937d256..6478b97d 100644 --- a/src/query-runner/exponential/FilterUniqueIterator.ts +++ b/src/query-runner/exponential/FilterUniquePathsIterator.ts @@ -2,7 +2,7 @@ import { AsyncIterator, SimpleTransformIterator } from "asynciterator"; import IPath from "../../interfaces/IPath"; import Path from "../../planner/Path"; -export default class FilterUniqueIterator extends SimpleTransformIterator { +export default class FilterUniquePathsIterator extends SimpleTransformIterator { private store: Path[]; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index d5e2f3f2..2397234f 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -11,7 +11,7 @@ import ILocationResolver from "../ILocationResolver"; import IQueryRunner from "../IQueryRunner"; import IResolvedQuery from "../IResolvedQuery"; import ExponentialQueryIterator from "./ExponentialQueryIterator"; -import FilterUniqueIterator from "./FilterUniqueIterator"; +import FilterUniquePathsIterator from "./FilterUniquePathsIterator"; import SubqueryIterator from "./SubqueryIterator"; @injectable() @@ -40,7 +40,7 @@ export default class QueryRunnerExponential implements IQueryRunner { const queryIterator = new ExponentialQueryIterator(baseQuery, 15 * 60 * 1000); const subqueryIterator = new SubqueryIterator(queryIterator, this.runSubquery.bind(this)); - return new FilterUniqueIterator(subqueryIterator); + return new FilterUniquePathsIterator(subqueryIterator); } else { return Promise.reject("Query not supported"); @@ -48,7 +48,7 @@ export default class QueryRunnerExponential implements IQueryRunner { } private async runSubquery(query: IResolvedQuery): Promise> { - // TODO investigate publicTransportPlanner + // TODO investigate if publicTransportPlanner can be reused or reuse some of its aggregated data const planner = this.context.getContainer().get(TYPES.PublicTransportPlanner); return planner.plan(query); diff --git a/src/util/MergeIterator.test.ts b/src/util/MergeIterator.test.ts new file mode 100644 index 00000000..6e84a808 --- /dev/null +++ b/src/util/MergeIterator.test.ts @@ -0,0 +1,70 @@ +import { ArrayIterator } from "asynciterator"; +import "jest"; +import MergeIterator from "./MergeIterator"; + +const createSources = () => { + const firstIterator = new ArrayIterator([1, 8, 10, 13]); + const secondIterator = new ArrayIterator([4, 11]); + const thirdIterator = new ArrayIterator([3, 5, 6, 12, 18]); + + return [firstIterator, secondIterator, thirdIterator]; +}; + +describe("[MergeIterator]", () => { + + it("uncondensed", (done) => { + + const mergeIterator = new MergeIterator(createSources(), (numbers: number[]) => { + + let smallestIndex = -1; + + for (let i = 0; i < numbers.length; i++) { + if (smallestIndex < 0 && numbers[i] !== undefined) { + smallestIndex = i; + continue; + } + + if (numbers[i] !== undefined && numbers[i] < numbers[smallestIndex]) { + smallestIndex = i; + } + } + + return smallestIndex; + }); + + let current = 0; + const expected = [1, 3, 4, 5, 6, 8, 10, 11, 12, 13, 18]; + + mergeIterator.each((str) => { + expect(expected[current++]).toBe(str); + }); + + mergeIterator.on("end", () => done()); + }); + + it("condensed", (done) => { + + const mergeIterator = new MergeIterator(createSources(), (numbers: number[]) => { + + let smallestIndex = 0; + + for (let i = 1; i < numbers.length; i++) { + if (numbers[i] !== undefined && numbers[i] < numbers[smallestIndex]) { + smallestIndex = i; + } + } + + return smallestIndex; + }, true); + + let current = 0; + const expected = [1, 3, 4, 5, 6, 8, 10, 11, 12, 13, 18]; + + mergeIterator.each((str) => { + expect(expected[current++]).toBe(str); + }); + + mergeIterator.on("end", () => done()); + }); + +}); diff --git a/src/util/MergeIterator.ts b/src/util/MergeIterator.ts new file mode 100644 index 00000000..9b4d9674 --- /dev/null +++ b/src/util/MergeIterator.ts @@ -0,0 +1,125 @@ +import { AsyncIterator, BufferedIterator } from "asynciterator"; + +type MergeIteratorSelector = (values: T[]) => number; + +export default class MergeIterator extends BufferedIterator { + private readonly sourceIterators: Array>; + private readonly selector: MergeIteratorSelector; + private readonly condensed: boolean; + + private values: T[]; + private lastPushedIndex: number; + private endedSources: number; + private shouldClose: boolean; + + constructor(sourceIterators: Array>, selector: MergeIteratorSelector, condensed?: boolean) { + super(); + + this.sourceIterators = sourceIterators; + this.selector = selector; + this.condensed = condensed; + + this.endedSources = 0; + + this.addListeners(); + } + + public _read(count: number, done: () => void): void { + if (!this.values) { + this.fillFirstValues(done); + return; + } + + this.fillValue(this.lastPushedIndex, () => { + this.doPush(done); + }); + } + + private fillFirstValues(done) { + this.values = Array(this.sourceIterators.length).fill(undefined); + let filledValues = 0; + + const filled = () => { + filledValues++; + + if (filledValues === this.sourceIterators.length) { + this.doPush(done); + } + }; + + for (let i = 0; i < this.sourceIterators.length; i++) { + this.fillValue(i, filled); + } + } + + private fillValue(sourceIndex: number, filled: () => void) { + const iterator = this.sourceIterators[sourceIndex]; + + if (iterator.ended) { + filled(); + return; + } + + const value = iterator.read(); + + if (value) { + this.values[sourceIndex] = value; + filled(); + + } else { + iterator.once("readable", () => { + this.values[sourceIndex] = iterator.read(); + filled(); + }); + } + } + + private doPush(done: () => void) { + if (this.condensed) { + const { values, indexMap } = this.getCondensedValues(); + + this.lastPushedIndex = indexMap[this.selector(values)]; + + } else { + this.lastPushedIndex = this.selector(this.values); + } + + this._push(this.values[this.lastPushedIndex]); + this.values[this.lastPushedIndex] = undefined; + + done(); + + if (this.shouldClose) { + this.close(); + } + } + + private getCondensedValues() { + const values = []; + const indexMap = []; + + this.values + .forEach((value: T, originalIndex: number) => { + if (value !== undefined) { + values.push(value); + indexMap.push(originalIndex); + } + }, {}); + + return { values, indexMap }; + } + + private addListeners() { + const self = this; + + for (const iterator of this.sourceIterators) { + iterator.on("end", () => { + self.endedSources++; + + if (self.endedSources === self.sourceIterators.length) { + self.shouldClose = true; + } + }); + } + } +} From 06f5e53e862cd03fcf3cb7af8a845bae8b962c82 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Tue, 4 Dec 2018 14:26:05 +0100 Subject: [PATCH 03/20] Implement ConnectionsProviderMerge --- src/catalog.delijn.ts | 11 +++ src/catalog.nmbs.ts | 8 ++ src/demo.ts | 6 +- .../merge/ConnectionsIteratorMerge.ts | 79 ------------------- .../merge/ConnectionsProviderMerge.ts | 67 ++++++++++++++-- src/inversify.config.ts | 18 +---- 6 files changed, 88 insertions(+), 101 deletions(-) create mode 100644 src/catalog.delijn.ts create mode 100644 src/catalog.nmbs.ts delete mode 100644 src/fetcher/connections/merge/ConnectionsIteratorMerge.ts diff --git a/src/catalog.delijn.ts b/src/catalog.delijn.ts new file mode 100644 index 00000000..6f6dbaa0 --- /dev/null +++ b/src/catalog.delijn.ts @@ -0,0 +1,11 @@ +import Catalog from "./Catalog"; +import TravelMode from "./TravelMode"; + +const catalog = new Catalog(); +catalog.addStopsFetcher( + "https://data.delijn.be/stops/", + "https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops", +); +catalog.addConnectionsFetcher("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); + +export default catalog; diff --git a/src/catalog.nmbs.ts b/src/catalog.nmbs.ts new file mode 100644 index 00000000..33204c55 --- /dev/null +++ b/src/catalog.nmbs.ts @@ -0,0 +1,8 @@ +import Catalog from "./Catalog"; +import TravelMode from "./TravelMode"; + +const catalog = new Catalog(); +catalog.addStopsFetcher("http://irail.be/stations/NMBS/", "https://irail.be/stations/NMBS"); +catalog.addConnectionsFetcher("https://graph.irail.be/sncb/connections", TravelMode.Train); + +export default catalog; diff --git a/src/demo.ts b/src/demo.ts index 1cf6e61d..bb1b5ca0 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -21,8 +21,10 @@ export default async (logResults) => { const publicTransportResult = await planner.query({ publicTransportOnly: true, - from: "https://data.delijn.be/stops/201657", // Ingelmunster - to: "https://data.delijn.be/stops/205910", // Ghent-Sint-Pieters + from: "https://data.delijn.be/stops/201657", + to: "https://data.delijn.be/stops/205910", + // from: "http://irail.be/stations/NMBS/008896008", // Kortrijk + // to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters minimumDepartureTime: new Date(), maximumTransferDuration: Units.fromHours(.01), }); diff --git a/src/fetcher/connections/merge/ConnectionsIteratorMerge.ts b/src/fetcher/connections/merge/ConnectionsIteratorMerge.ts deleted file mode 100644 index 3eea26ab..00000000 --- a/src/fetcher/connections/merge/ConnectionsIteratorMerge.ts +++ /dev/null @@ -1,79 +0,0 @@ -import IConnection from "../IConnection"; -import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; - -export default class ConnectionsIteratorMerge implements AsyncIterator { - - private iterators: Array>; - private config: IConnectionsFetcherConfig; - - private nextConnections: IConnection[]; - - constructor(iterators: Array>, config: IConnectionsFetcherConfig) { - this.iterators = iterators; - this.config = config; - - this.nextConnections = Array(iterators.length); - } - - public async next(): Promise> { - await this.replenishNextConnections(); - - // console.log("Next connections", this.nextConnections.map((c) => `${c.departureTime} ${c["@id"]}`)); - - if (!this.config.backward) { - // Get next connection with earliest departure time - - const earliestConnection = this.findNextConnection(this.earliestPredicate); - return {value: earliestConnection, done: false}; - - } else { - // Get next connection with latest departure time - - const latestConnection = this.findNextConnection(this.latestPredicate); - return {value: latestConnection, done: false}; - } - } - - public return(value?: any): Promise> { - return undefined; - } - - public throw(e?: any): Promise> { - return undefined; - } - - private findNextConnection(predicate: (matching: IConnection, current: IConnection) => boolean): IConnection { - let matchingConnectionIndex = 0; - let matchingConnection = this.nextConnections[matchingConnectionIndex]; - - for (let i = 1; i < this.nextConnections.length; i++) { - const currentConnection = this.nextConnections[i]; - - if (predicate(matchingConnection, currentConnection)) { - matchingConnectionIndex = i; - matchingConnection = currentConnection; - } - } - - this.nextConnections[matchingConnectionIndex] = null; - return matchingConnection; - } - - private earliestPredicate(matching: IConnection, current: IConnection): boolean { - return matching.departureTime.valueOf() > current.departureTime.valueOf(); - } - - private latestPredicate(matching: IConnection, current: IConnection): boolean { - return matching.departureTime.valueOf() < current.departureTime.valueOf(); - } - - private async replenishNextConnections() { - for (let i = 0; i < this.nextConnections.length; i++) { - if (!this.nextConnections[i]) { - const result = await this.iterators[i].next(); - this.nextConnections[i] = result.value; - } - } - } - -} diff --git a/src/fetcher/connections/merge/ConnectionsProviderMerge.ts b/src/fetcher/connections/merge/ConnectionsProviderMerge.ts index ae8b53e1..f6fdd021 100644 --- a/src/fetcher/connections/merge/ConnectionsProviderMerge.ts +++ b/src/fetcher/connections/merge/ConnectionsProviderMerge.ts @@ -1,23 +1,78 @@ import { AsyncIterator } from "asynciterator"; -import { injectable, multiInject, tagged } from "inversify"; -import TYPES from "../../../types"; +import { inject, injectable, multiInject } from "inversify"; +import Catalog from "../../../Catalog"; +import TYPES, { ConnectionsFetcherFactory } from "../../../types"; +import MergeIterator from "../../../util/MergeIterator"; import IConnection from "../IConnection"; import IConnectionsFetcher from "../IConnectionsFetcher"; import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; -import ConnectionsIteratorMerge from "./ConnectionsIteratorMerge"; @injectable() export default class ConnectionsProviderMerge implements IConnectionsFetcher { - public createIterator: () => AsyncIterator; + private static forwardsConnectionSelector(connections: IConnection[]): number { + if (connections.length === 1) { + return 0; + } + + let earliestIndex = 0; + const earliest = connections[earliestIndex]; + + for (let i = 1; i < connections.length; i++) { + const connection = connections[i]; + + if (connection.departureTime < earliest.departureTime) { + earliestIndex = i; + } + } + + return earliestIndex; + } + + private static backwardsConnectionsSelector(connections: IConnection[]): number { + if (connections.length === 1) { + return 0; + } + + let latestIndex = 0; + const latest = connections[latestIndex]; + + for (let i = 1; i < connections.length; i++) { + const connection = connections[i]; + + if (connection.departureTime > latest.departureTime) { + latestIndex = i; + } + } + + return latestIndex; + } private config: IConnectionsFetcherConfig; private connectionsFetchers: IConnectionsFetcher[]; constructor( - @multiInject(TYPES.ConnectionsFetcher) @tagged("type", "source") connectionsFetchers: IConnectionsFetcher[], + @inject(TYPES.ConnectionsFetcherFactory) connectionsFetcherFactory: ConnectionsFetcherFactory, + @inject(TYPES.Catalog) catalog: Catalog, ) { - this.connectionsFetchers = connectionsFetchers; + this.connectionsFetchers = []; + + for (const { accessUrl, travelMode } of catalog.connectionsFetcherConfigs) { + this.connectionsFetchers.push(connectionsFetcherFactory(accessUrl, travelMode)); + } + } + + public createIterator(): AsyncIterator { + + const iterators = this.connectionsFetchers + .map((fetcher) => fetcher.createIterator()); + + const selector = this.config.backward ? + ConnectionsProviderMerge.backwardsConnectionsSelector + : + ConnectionsProviderMerge.forwardsConnectionSelector; + + return new MergeIterator(iterators, selector, true); } public setConfig(config: IConnectionsFetcherConfig): void { diff --git a/src/inversify.config.ts b/src/inversify.config.ts index e790b646..18cc6bce 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -1,11 +1,12 @@ import { Container, interfaces } from "inversify"; import LDFetch from "ldfetch"; import Catalog from "./Catalog"; +import catalog from "./catalog.delijn"; import Context from "./Context"; -import ConnectionsProviderPassthrough from "./fetcher/connections/ConnectionsProviderPassthrough"; 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 IStopsFetcher from "./fetcher/stops/IStopsFetcher"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; import StopsFetcherLDFetch from "./fetcher/stops/ld-fetch/StopsFetcherLDFetch"; @@ -51,7 +52,7 @@ container.bind(TYPES.ReachableStopsFinder) container.bind(TYPES.ReachableStopsFinder) .to(ReachableStopsFinderBirdsEyeCached).whenTargetTagged("phase", ReachableStopsSearchPhase.Final); -container.bind(TYPES.ConnectionsProvider).to(ConnectionsProviderPassthrough).inSingletonScope(); +container.bind(TYPES.ConnectionsProvider).to(ConnectionsProviderMerge).inSingletonScope(); container.bind(TYPES.ConnectionsFetcher).to(ConnectionsFetcherLazy); container.bind>(TYPES.ConnectionsFetcherFactory) .toFactory( @@ -81,18 +82,7 @@ container.bind>(TYPES.StopsFetcherFactory) }, ); -// Init catalog -// const catalog = new Catalog(); -// catalog.addStopsFetcher("http://irail.be/stations/NMBS/", "https://irail.be/stations/NMBS"); -// catalog.addConnectionsFetcher("https://graph.irail.be/sncb/connections", TravelMode.Train); - -const catalog = new Catalog(); -catalog.addStopsFetcher( - "https://data.delijn.be/stops/", - "https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops", -); -catalog.addConnectionsFetcher("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); - +// Bind catalog container.bind(TYPES.Catalog).toConstantValue(catalog); // Init LDFetch From 067d02f7fa5ef72343a0444af12519f56f8ad0c2 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Tue, 4 Dec 2018 16:42:36 +0100 Subject: [PATCH 04/20] Remove prefixes from StopsFetchers --- src/Catalog.ts | 4 ++-- src/catalog.delijn.ts | 9 ++++---- src/catalog.nmbs.ts | 2 +- src/demo.ts | 8 +++++-- src/fetcher/stops/IStopsFetcher.ts | 2 -- src/fetcher/stops/StopsProviderDefault.ts | 21 +++++-------------- .../ld-fetch/StopsFetcherLDFetch.test.ts | 2 -- .../stops/ld-fetch/StopsFetcherLDFetch.ts | 5 ----- src/inversify.config.ts | 5 +---- .../PublicTransportPlannerCSAProfile.test.ts | 2 -- src/planner/road/RoadPlannerBirdsEye.test.ts | 1 - .../ReachableStopsFinderBirdsEye.test.ts | 1 - .../ReachableStopsFinderRoadPlanner.test.ts | 1 - .../LocationResolverDefault.test.ts | 1 - .../QueryRunnerExponential.test.ts | 1 - src/types.ts | 2 +- 16 files changed, 21 insertions(+), 46 deletions(-) diff --git a/src/Catalog.ts b/src/Catalog.ts index 6bfebad8..4a18dee8 100644 --- a/src/Catalog.ts +++ b/src/Catalog.ts @@ -4,8 +4,8 @@ export default class Catalog { public stopsFetcherConfigs = []; public connectionsFetcherConfigs = []; - public addStopsFetcher(prefix: string, accessUrl: string) { - this.stopsFetcherConfigs.push({prefix, accessUrl}); + public addStopsFetcher(accessUrl: string) { + this.stopsFetcherConfigs.push({accessUrl}); } public addConnectionsFetcher(accessUrl: string, travelMode: TravelMode) { diff --git a/src/catalog.delijn.ts b/src/catalog.delijn.ts index 6f6dbaa0..d33e64d2 100644 --- a/src/catalog.delijn.ts +++ b/src/catalog.delijn.ts @@ -2,10 +2,11 @@ import Catalog from "./Catalog"; import TravelMode from "./TravelMode"; const catalog = new Catalog(); -catalog.addStopsFetcher( - "https://data.delijn.be/stops/", - "https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops", -); +catalog.addStopsFetcher("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops"); +catalog.addStopsFetcher("https://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); +catalog.addStopsFetcher("https://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); + catalog.addConnectionsFetcher("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); +catalog.addConnectionsFetcher("https://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 33204c55..f9edf2e8 100644 --- a/src/catalog.nmbs.ts +++ b/src/catalog.nmbs.ts @@ -2,7 +2,7 @@ import Catalog from "./Catalog"; import TravelMode from "./TravelMode"; const catalog = new Catalog(); -catalog.addStopsFetcher("http://irail.be/stations/NMBS/", "https://irail.be/stations/NMBS"); +catalog.addStopsFetcher("https://irail.be/stations/NMBS"); catalog.addConnectionsFetcher("https://graph.irail.be/sncb/connections", TravelMode.Train); export default catalog; diff --git a/src/demo.ts b/src/demo.ts index bb1b5ca0..70b4fff2 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -21,8 +21,12 @@ export default async (logResults) => { const publicTransportResult = await planner.query({ publicTransportOnly: true, - from: "https://data.delijn.be/stops/201657", - to: "https://data.delijn.be/stops/205910", + // 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/008896008", // Kortrijk // to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters minimumDepartureTime: new Date(), diff --git a/src/fetcher/stops/IStopsFetcher.ts b/src/fetcher/stops/IStopsFetcher.ts index d2c9d3a1..098ac036 100644 --- a/src/fetcher/stops/IStopsFetcher.ts +++ b/src/fetcher/stops/IStopsFetcher.ts @@ -2,9 +2,7 @@ import IStopsProvider from "./IStopsProvider"; /** * Represents one data source of stops, e.g. De Lijn or NMBS - * Has a prefix for potential use by a [[IStopsProvider]] * Extends [[IStopsProvider]] because it should implement the same methods */ export default interface IStopsFetcher extends IStopsProvider { - prefix: string; } diff --git a/src/fetcher/stops/StopsProviderDefault.ts b/src/fetcher/stops/StopsProviderDefault.ts index 4398c39d..ffe9b65a 100644 --- a/src/fetcher/stops/StopsProviderDefault.ts +++ b/src/fetcher/stops/StopsProviderDefault.ts @@ -16,17 +16,15 @@ export default class StopsProviderDefault implements IStopsProvider { ) { this.stopsFetchers = []; - for (const {prefix, accessUrl} of catalog.stopsFetcherConfigs) { - this.stopsFetchers.push(stopsFetcherFactory(prefix, accessUrl)); + for (const { accessUrl } of catalog.stopsFetcherConfigs) { + this.stopsFetchers.push(stopsFetcherFactory(accessUrl)); } } public async getStopById(stopId: string): Promise { - const fetcher = this.determineStopFetcher(stopId); - - if (fetcher) { - return fetcher.getStopById(stopId); - } + return Promise.all(this.stopsFetchers + .map((stopsFetcher: IStopsFetcher) => stopsFetcher.getStopById(stopId)), + ).then((results: IStop[]) => results.find((stop) => stop !== undefined)); } public async getAllStops(): Promise { @@ -34,13 +32,4 @@ export default class StopsProviderDefault implements IStopsProvider { .map((stopsFetcher: IStopsFetcher) => stopsFetcher.getAllStops()), ).then((results: IStop[][]) => [].concat(...results)); } - - private determineStopFetcher(stopId: string): IStopsFetcher { - if (!this.stopsFetchers || !this.stopsFetchers.length) { - return null; - } - - return this.stopsFetchers - .find((fetcher) => stopId.indexOf(fetcher.prefix) === 0); - } } diff --git a/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.test.ts b/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.test.ts index da6b6fda..14e10cf9 100644 --- a/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.test.ts +++ b/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.test.ts @@ -14,11 +14,9 @@ const DE_LIJN_STOPS_URLS = [ const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); const deLijnFetcher = new StopsFetcherLDFetch(ldFetch); -deLijnFetcher.setPrefix("https://data.delijn.be/stops/"); deLijnFetcher.setAccessUrl(DE_LIJN_STOPS_URLS[2]); const nmbsFetcher = new StopsFetcherLDFetch(ldFetch); -nmbsFetcher.setPrefix("http://irail.be/stations/NMBS/"); nmbsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); test("[StopsFetcherLDFetch] De Lijn first stop", async () => { diff --git a/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.ts b/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.ts index d47fdb63..d7ef11b9 100644 --- a/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.ts +++ b/src/fetcher/stops/ld-fetch/StopsFetcherLDFetch.ts @@ -17,7 +17,6 @@ interface IStopMap { @injectable() export default class StopsFetcherLDFetch implements IStopsFetcher { - public prefix: string; private accessUrl: string; private ldFetch: LDFetch; @@ -31,10 +30,6 @@ export default class StopsFetcherLDFetch implements IStopsFetcher { this.loadStops(); } - public setPrefix(prefix: string) { - this.prefix = prefix; - } - public setAccessUrl(accessUrl: string) { this.accessUrl = accessUrl; } diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 18cc6bce..fae0acfb 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -72,12 +72,9 @@ container.bind(TYPES.StopsFetcher).to(StopsFetcherLDFetch); container.bind>(TYPES.StopsFetcherFactory) .toFactory( (context: interfaces.Context) => - (prefix: string, accessUrl: string) => { + (accessUrl: string) => { const fetcher = context.container.get(TYPES.StopsFetcher); - - fetcher.setPrefix(prefix); fetcher.setAccessUrl(accessUrl); - return fetcher; }, ); diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts index 44255b4a..f86de78a 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts @@ -30,7 +30,6 @@ describe("[PublicTransportPlannerCSAProfile]", () => { connectionFetcher.setConfig({ backward: true }); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); - stopsFetcher.setPrefix("http://irail.be/stations/NMBS/"); stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); const locationResolver = new LocationResolverDefault(stopsFetcher); @@ -160,7 +159,6 @@ describe("[PublicTransportPlannerCSAProfile]", () => { connectionFetcher.setAccessUrl("https://graph.irail.be/sncb/connections"); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); - stopsFetcher.setPrefix("http://irail.be/stations/NMBS/"); stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); const locationResolver = new LocationResolverDefault(stopsFetcher); diff --git a/src/planner/road/RoadPlannerBirdsEye.test.ts b/src/planner/road/RoadPlannerBirdsEye.test.ts index 7f6201a2..b15577ca 100644 --- a/src/planner/road/RoadPlannerBirdsEye.test.ts +++ b/src/planner/road/RoadPlannerBirdsEye.test.ts @@ -11,7 +11,6 @@ const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); const planner: IRoadPlanner = new RoadPlannerBirdsEye(); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); -stopsFetcher.setPrefix("http://irail.be/stations/NMBS/"); stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); const locationResolver = new LocationResolverDefault(stopsFetcher); diff --git a/src/planner/stops/ReachableStopsFinderBirdsEye.test.ts b/src/planner/stops/ReachableStopsFinderBirdsEye.test.ts index c503f602..86523581 100644 --- a/src/planner/stops/ReachableStopsFinderBirdsEye.test.ts +++ b/src/planner/stops/ReachableStopsFinderBirdsEye.test.ts @@ -16,7 +16,6 @@ const DE_LIJN_STOPS_URLS = [ const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); -stopsFetcher.setPrefix("https://data.delijn.be/stops/"); stopsFetcher.setAccessUrl(DE_LIJN_STOPS_URLS[2]); const reachableStopsFinder = new ReachableStopsFinderBirdsEye(stopsFetcher); diff --git a/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts b/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts index 9013d2e5..e567b4a9 100644 --- a/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts +++ b/src/planner/stops/ReachableStopsFinderRoadPlanner.test.ts @@ -9,7 +9,6 @@ import ReachableStopsFinderRoadPlanner from "./ReachableStopsFinderRoadPlanner"; const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); -stopsFetcher.setPrefix("http://irail.be/stations/NMBS/"); stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); const roadPlanner = new RoadPlannerBirdsEye(); diff --git a/src/query-runner/LocationResolverDefault.test.ts b/src/query-runner/LocationResolverDefault.test.ts index 5f3cd9a8..1ec1a03d 100644 --- a/src/query-runner/LocationResolverDefault.test.ts +++ b/src/query-runner/LocationResolverDefault.test.ts @@ -6,7 +6,6 @@ import LocationResolverDefault from "./LocationResolverDefault"; const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); -stopsFetcher.setPrefix("http://irail.be/stations/NMBS/"); stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); const locationResolver = new LocationResolverDefault(stopsFetcher); diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index 8adf0aba..5b247881 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -46,7 +46,6 @@ describe("[QueryRunnerExponential]", () => { connectionFetcher.setAccessUrl("https://graph.irail.be/sncb/connections"); const stopsFetcher = new StopsFetcherLDFetch(ldFetch); - stopsFetcher.setPrefix("http://irail.be/stations/NMBS/"); stopsFetcher.setAccessUrl("https://irail.be/stations/NMBS"); const locationResolver = new LocationResolverDefault(stopsFetcher); diff --git a/src/types.ts b/src/types.ts index 1874889d..157ebd76 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,5 +25,5 @@ const TYPES = { export default TYPES; -export type StopsFetcherFactory = (prefix: string, accessUrl: string) => IStopsFetcher; +export type StopsFetcherFactory = (accessUrl: string) => IStopsFetcher; export type ConnectionsFetcherFactory = (accessUrl: string, travelMode: TravelMode) => IConnectionsFetcher; From 190d0fdc073753dc716e6e3a93b51b9f368c316e Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Tue, 4 Dec 2018 16:58:18 +0100 Subject: [PATCH 05/20] Added IPublicTransportPlanner factory for use in QueryRunnerExponential --- .../ConnectionsProviderPassthrough.ts | 1 + .../merge/ConnectionsProviderMerge.ts | 5 ++++- src/inversify.config.ts | 2 ++ .../QueryRunnerExponential.test.ts | 13 +----------- .../exponential/QueryRunnerExponential.ts | 20 ++++++++----------- src/types.ts | 4 ++++ 6 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/fetcher/connections/ConnectionsProviderPassthrough.ts b/src/fetcher/connections/ConnectionsProviderPassthrough.ts index 6c3e754b..e7c7c43c 100644 --- a/src/fetcher/connections/ConnectionsProviderPassthrough.ts +++ b/src/fetcher/connections/ConnectionsProviderPassthrough.ts @@ -9,6 +9,7 @@ import IConnectionsProvider from "./IConnectionsProvider"; /** * 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 ConnectionsProviderPassthrough implements IConnectionsProvider { diff --git a/src/fetcher/connections/merge/ConnectionsProviderMerge.ts b/src/fetcher/connections/merge/ConnectionsProviderMerge.ts index f6fdd021..010d91e7 100644 --- a/src/fetcher/connections/merge/ConnectionsProviderMerge.ts +++ b/src/fetcher/connections/merge/ConnectionsProviderMerge.ts @@ -1,5 +1,5 @@ import { AsyncIterator } from "asynciterator"; -import { inject, injectable, multiInject } from "inversify"; +import { inject, injectable } from "inversify"; import Catalog from "../../../Catalog"; import TYPES, { ConnectionsFetcherFactory } from "../../../types"; import MergeIterator from "../../../util/MergeIterator"; @@ -7,6 +7,9 @@ import IConnection from "../IConnection"; import IConnectionsFetcher from "../IConnectionsFetcher"; import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; +/** + * Instantiates and merge sorts all registered connection fetchers + */ @injectable() export default class ConnectionsProviderMerge implements IConnectionsFetcher { diff --git a/src/inversify.config.ts b/src/inversify.config.ts index fae0acfb..662c3d39 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -35,6 +35,8 @@ container.bind(TYPES.LocationResolver).to(LocationResolverDef container.bind(TYPES.PublicTransportPlanner) .to(PublicTransportPlannerCSAProfile); +container.bind>(TYPES.PublicTransportPlannerFactory) + .toAutoFactory(TYPES.PublicTransportPlanner); container.bind(TYPES.JourneyExtractor) .to(JourneyExtractorDefault); diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index 5b247881..7b8c69a2 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -71,18 +71,7 @@ describe("[QueryRunnerExponential]", () => { ); }; - const fakeContext = { - getContainer() { - return { - get() { - return createPlanner(); - }, - }; - }, - }; - - // @ts-ignore - return new QueryRunnerExponential(fakeContext as Context, locationResolver, null); + return new QueryRunnerExponential(locationResolver, createPlanner); }; const result: IPath[] = []; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index 2397234f..e55a5583 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -1,6 +1,5 @@ import { AsyncIterator } from "asynciterator"; -import { inject, injectable } from "inversify"; -import Context from "../../Context"; +import { inject, injectable, interfaces } from "inversify"; import Defaults from "../../Defaults"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; @@ -16,20 +15,17 @@ import SubqueryIterator from "./SubqueryIterator"; @injectable() export default class QueryRunnerExponential implements IQueryRunner { - public private; private locationResolver: ILocationResolver; - private publicTransportPlanner: IPublicTransportPlanner; - - private context: Context; + private publicTransportPlannerFactory: interfaces.Factory; constructor( - @inject(TYPES.Context) context: Context, - @inject(TYPES.LocationResolver) locationResolver: ILocationResolver, - @inject(TYPES.PublicTransportPlanner) publicTransportPlanner: IPublicTransportPlanner, + @inject(TYPES.LocationResolver) + locationResolver: ILocationResolver, + @inject(TYPES.PublicTransportPlannerFactory) + publicTransportPlannerFactory: interfaces.Factory, ) { this.locationResolver = locationResolver; - this.publicTransportPlanner = publicTransportPlanner; - this.context = context; + this.publicTransportPlannerFactory = publicTransportPlannerFactory; } public async run(query: IQuery): Promise> { @@ -49,7 +45,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 - const planner = this.context.getContainer().get(TYPES.PublicTransportPlanner); + const planner = this.publicTransportPlannerFactory() as IPublicTransportPlanner; return planner.plan(query); } diff --git a/src/types.ts b/src/types.ts index 157ebd76..146cd498 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,7 +16,11 @@ const TYPES = { StopsFetcherFactory: Symbol("StopsFetcherFactory"), PublicTransportPlanner: Symbol("PublicTransportPlanner"), + PublicTransportPlannerFactory: Symbol("PublicTransportPlannerFactory"), + RoadPlanner: Symbol("RoadPlanner"), + RoadPlannerFactory: Symbol("RoadPlannerFactory"), + ReachableStopsFinder: Symbol("ReachableStopsFinder"), JourneyExtractor: Symbol("JourneyExtractor"), LDFetch: Symbol("LDFetch"), From 68abc3944ebe5ec71b6bcf59c5d97533946eb70d Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Tue, 4 Dec 2018 17:09:13 +0100 Subject: [PATCH 06/20] Complete De Lijn catalog (breaks demo test) --- src/catalog.delijn.ts | 16 +++++++++++----- .../exponential/QueryRunnerExponential.test.ts | 12 ------------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/catalog.delijn.ts b/src/catalog.delijn.ts index d33e64d2..95726afd 100644 --- a/src/catalog.delijn.ts +++ b/src/catalog.delijn.ts @@ -2,11 +2,17 @@ import Catalog from "./Catalog"; import TravelMode from "./TravelMode"; const catalog = new Catalog(); -catalog.addStopsFetcher("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/stops"); -catalog.addStopsFetcher("https://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); -catalog.addStopsFetcher("https://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/stops"); -catalog.addConnectionsFetcher("https://openplanner.ilabt.imec.be/delijn/Oost-Vlaanderen/connections", TravelMode.Bus); -catalog.addConnectionsFetcher("https://openplanner.ilabt.imec.be/delijn/West-Vlaanderen/connections", TravelMode.Bus); +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.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); export default catalog; diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index 7b8c69a2..eb8829a8 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -1,6 +1,5 @@ import "jest"; import LDFetch from "ldfetch"; -import Context from "../../Context"; import ConnectionsFetcherLazy from "../../fetcher/connections/ld-fetch/ConnectionsFetcherLazy"; import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import IPath from "../../interfaces/IPath"; @@ -30,17 +29,6 @@ describe("[QueryRunnerExponential]", () => { const createExponentialQueryRunner = () => { const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); - const httpStartTimes = {}; - - ldFetch.on("request", (url) => httpStartTimes[url] = new Date()); - - ldFetch.on("redirect", (obj) => httpStartTimes[obj.to] = httpStartTimes[obj.from]); - - ldFetch.on("response", (url) => { - const difference = (new Date()).getTime() - httpStartTimes[url].getTime(); - console.log(`HTTP GET - ${url} (${difference}ms)`); - }); - const connectionFetcher = new ConnectionsFetcherLazy(ldFetch); connectionFetcher.setTravelMode(TravelMode.Train); connectionFetcher.setAccessUrl("https://graph.irail.be/sncb/connections"); From 6fe7e984cc94ff03a088088f29af24a36348dd6a Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Wed, 5 Dec 2018 16:36:47 +0100 Subject: [PATCH 07/20] 1. refactor journey extractor. Calculate journeys based + footpaths on the profiles. #44 2. Added departureTime to ITransferProfiles. (pareto optimization p22. paper CSA March 2017 - KIT, +p20 example) 3. update csa (refactor in next commit). 4. update demo. --- src/demo.ts | 16 +- .../data-structure/stops/ITransferProfile.ts | 1 + .../JourneyExtractorDefault.ts | 307 +++++++----------- .../PublicTransportPlannerCSAProfile.test.ts | 10 +- .../PublicTransportPlannerCSAProfile.ts | 71 +++- 5 files changed, 188 insertions(+), 217 deletions(-) diff --git a/src/demo.ts b/src/demo.ts index 3d770876..52035885 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -1,4 +1,6 @@ import Planner from "./index"; +import IPath from "./interfaces/IPath"; +import IStep from "./interfaces/IStep"; import Units from "./util/Units"; export default async (logResults) => { @@ -31,14 +33,12 @@ export default async (logResults) => { let i = 0; - publicTransportResult.on("readable", () => { - let path = publicTransportResult.read(); - - while (path && i < 5) { - console.log(i++, path); - - path = publicTransportResult.read(); - } + publicTransportResult.take(3).on("data", (path: IPath) => { + console.log(++i); + path.steps.forEach((step: IStep) => { + console.log(JSON.stringify(step, null, " ")); + }); + console.log("\n"); }); return true; 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 e2fa68f2..9d8b65f9 100644 --- a/src/planner/public-transport/CSA/data-structure/stops/ITransferProfile.ts +++ b/src/planner/public-transport/CSA/data-structure/stops/ITransferProfile.ts @@ -9,6 +9,7 @@ import IConnection from "../../../../../fetcher/connections/IConnection"; * to arrive at the arrivalTime in the target [[IStop]]. */ export default interface ITransferProfile { + departureTime: number; arrivalTime: number; exitConnection: IConnection; enterConnection: IConnection; diff --git a/src/planner/public-transport/JourneyExtractorDefault.ts b/src/planner/public-transport/JourneyExtractorDefault.ts index 24dde2e2..e987b17a 100644 --- a/src/planner/public-transport/JourneyExtractorDefault.ts +++ b/src/planner/public-transport/JourneyExtractorDefault.ts @@ -1,7 +1,6 @@ import { ArrayIterator, AsyncIterator } from "asynciterator"; -import { inject, injectable, tagged } from "inversify"; +import { inject, injectable } from "inversify"; import IConnection from "../../fetcher/connections/IConnection"; -import IStop from "../../fetcher/stops/IStop"; import ILocation from "../../interfaces/ILocation"; import IPath from "../../interfaces/IPath"; import IStep from "../../interfaces/IStep"; @@ -9,19 +8,13 @@ import ILocationResolver from "../../query-runner/ILocationResolver"; import IResolvedQuery from "../../query-runner/IResolvedQuery"; import TravelMode from "../../TravelMode"; import TYPES from "../../types"; -import Iterators from "../../util/Iterators"; import Path from "../Path"; -import IRoadPlanner from "../road/IRoadPlanner"; import Step from "../Step"; -import IReachableStopsFinder, { IReachableStop } from "../stops/IReachableStopsFinder"; -import ReachableStopsFinderMode from "../stops/ReachableStopsFinderMode"; -import ReachableStopsSearchPhase from "../stops/ReachableStopsSearchPhase"; +import IProfile from "./CSA/data-structure/stops/IProfile"; import IProfilesByStop from "./CSA/data-structure/stops/IProfilesByStop"; import ITransferProfile from "./CSA/data-structure/stops/ITransferProfile"; -import Profile from "./CSA/data-structure/stops/Profile"; import ProfileUtil from "./CSA/util/ProfileUtil"; import IJourneyExtractor from "./IJourneyExtractor"; -import JourneyExtractionPhase from "./JourneyExtractionPhase"; /** * Creates journeys based on the profiles and query from [[PublicTransportPlannerCSAProfile]]. @@ -32,30 +25,12 @@ import JourneyExtractionPhase from "./JourneyExtractionPhase"; */ @injectable() export default class JourneyExtractorDefault implements IJourneyExtractor { - private readonly transferRoadPlanner: IRoadPlanner; - private readonly finalRoadPlanner: IRoadPlanner; - private readonly initialReachableStopsFinder: IReachableStopsFinder; - private readonly locationResolver: ILocationResolver; private bestArrivalTime: number[][] = []; - constructor( - @inject(TYPES.RoadPlanner) - @tagged("phase", JourneyExtractionPhase.Transfer) - transferRoadPlanner: IRoadPlanner, - @inject(TYPES.RoadPlanner) - @tagged("phase", JourneyExtractionPhase.Final) - finalRoadPlanner: IRoadPlanner, - @inject(TYPES.ReachableStopsFinder) - @tagged("phase", ReachableStopsSearchPhase.Initial) - initialReachableStopsFinder: IReachableStopsFinder, - @inject(TYPES.LocationResolver) locationResolver: ILocationResolver, - ) { - this.transferRoadPlanner = transferRoadPlanner; - this.finalRoadPlanner = finalRoadPlanner; + constructor(@inject(TYPES.LocationResolver) locationResolver: ILocationResolver) { this.locationResolver = locationResolver; - this.initialReachableStopsFinder = initialReachableStopsFinder; } public async extractJourneys( @@ -64,67 +39,52 @@ export default class JourneyExtractorDefault implements IJourneyExtractor { ): Promise> { const filteredProfilesByStop: IProfilesByStop = ProfileUtil.filterInfinity(profilesByStop); - const departureLocation: IStop = query.from[0] as IStop; - - const reachableStops = await this.initialReachableStopsFinder.findReachableStops( - departureLocation, - ReachableStopsFinderMode.Source, - query.maximumTransferDuration, - query.minimumWalkingSpeed, - ); + const departureLocation: ILocation = query.from[0]; + const arrivalLocation: ILocation = query.to[0]; const paths: IPath[] = []; - for (const reachableStop of reachableStops) { - const departureStop: IStop = reachableStop.stop; + const departureLocationProfiles: IProfile[] = filteredProfilesByStop[departureLocation.id]; - if (!filteredProfilesByStop[departureStop.id]) { - console.warn("Can't find departure stop: " + departureStop.id); - continue; - } - - for (const profile of filteredProfilesByStop[departureStop.id]) { - if (profile.departureTime >= query.minimumDepartureTime.getTime()) { - const arrivalStop = query.to[0]; - - for (let amountOfTransfers = 0; amountOfTransfers < profile.transferProfiles.length; amountOfTransfers++) { - const transferProfile = profile.transferProfiles[amountOfTransfers]; - const enterStop = transferProfile.enterConnection && transferProfile.enterConnection.departureStop; - - if (enterStop !== departureStop.id) { - continue; - } - - if (this.checkBestArrivalTime(transferProfile, departureStop, arrivalStop)) { - try { - paths.push(await this.extractJourney( - arrivalStop, - reachableStop, - profile, - amountOfTransfers, - filteredProfilesByStop, - query, - )); - - this.setBestArrivalTime(departureStop, arrivalStop, transferProfile.arrivalTime); - } catch (e) { - console.warn(e); - } - } + // Can't find departure stop; + if (!departureLocationProfiles) { + return new ArrayIterator(paths); + } + for (const profile of departureLocationProfiles) { + + for (let amountOfTransfers = 0; amountOfTransfers < profile.transferProfiles.length; amountOfTransfers++) { + const transferProfile: ITransferProfile = profile.transferProfiles[amountOfTransfers]; + + if (this.checkBestArrivalTime(transferProfile, departureLocation, arrivalLocation)) { + try { + paths.push(await this.extractJourney( + departureLocation, + arrivalLocation, + transferProfile, + amountOfTransfers, + filteredProfilesByStop, + )); + + this.setBestArrivalTime( + departureLocation, + arrivalLocation, + transferProfile.arrivalTime, + ); + } catch (e) { + console.warn(e); } - } } - } + } return new ArrayIterator(paths.reverse()); } private checkBestArrivalTime( transferProfile: ITransferProfile, - departureStop: ILocation, - arrivalStop: ILocation, + departureLocation: ILocation, + arrivalLocation: ILocation, ): boolean { const canArrive = transferProfile.arrivalTime < Infinity; @@ -133,13 +93,13 @@ export default class JourneyExtractorDefault implements IJourneyExtractor { return false; } - const bestArrivalTimesOfDepartureStop = this.bestArrivalTime[departureStop.id]; + const bestArrivalTimesOfDepartureStop = this.bestArrivalTime[departureLocation.id]; if (!bestArrivalTimesOfDepartureStop) { return true; } - const bestArrivalTime = bestArrivalTimesOfDepartureStop[arrivalStop.id]; + const bestArrivalTime = bestArrivalTimesOfDepartureStop[arrivalLocation.id]; if (!bestArrivalTime) { return true; @@ -148,151 +108,116 @@ export default class JourneyExtractorDefault implements IJourneyExtractor { return transferProfile.arrivalTime < bestArrivalTime; } - private setBestArrivalTime(departureStop: ILocation, arrivalStop: ILocation, arrivalTime: number): void { - if (!this.bestArrivalTime[departureStop.id]) { - this.bestArrivalTime[departureStop.id] = []; + private setBestArrivalTime(departureLocation: ILocation, arrivalLocation: ILocation, arrivalTime: number): void { + if (!this.bestArrivalTime[departureLocation.id]) { + this.bestArrivalTime[departureLocation.id] = []; } - this.bestArrivalTime[departureStop.id][arrivalStop.id] = arrivalTime; + this.bestArrivalTime[departureLocation.id][arrivalLocation.id] = arrivalTime; } private async extractJourney( - arrivalStop: ILocation, - reachableStop: IReachableStop, - profile: Profile, + departureLocation: ILocation, + arrivalLocation: ILocation, + transferProfile: ITransferProfile, transfers: number, profilesByStop: IProfilesByStop, - query: IResolvedQuery, ): Promise { - let shouldReturn = true; - - // Extract journey for amount of transfers const path: Path = Path.create(); - let currentProfile = profile; - let remainingTransfers = transfers; + let currentTransferProfile: ITransferProfile = transferProfile; + let departureTime: Date = new Date(transferProfile.departureTime); - if (reachableStop.duration > 0) { - const currentTransferProfile = profile.transferProfiles[remainingTransfers]; + let remainingTransfers: number = transfers; - this.addInitialFootpath( - path, - currentTransferProfile, - reachableStop, - query.from[0], - ); - } + let currentLocation: ILocation = departureLocation; while (remainingTransfers >= 0) { - // Construct and push step - const currentTransferProfile = currentProfile.transferProfiles[remainingTransfers]; - const enterConnection: IConnection = currentTransferProfile.enterConnection; const exitConnection: IConnection = currentTransferProfile.exitConnection; - const step: IStep = Step.createFromConnections(enterConnection, exitConnection); - - step.startLocation = await this.locationResolver.resolve(step.startLocation.id); - step.stopLocation = await this.locationResolver.resolve(step.stopLocation.id); + const enterLocation: ILocation = await this.locationResolver.resolve(enterConnection.departureStop); + const exitLocation: ILocation = await this.locationResolver.resolve(exitConnection.arrivalStop); + + // Initial or transfer footpath. + const transferDepartureTime: Date = enterConnection.departureTime; + + if (departureTime.getTime() !== transferDepartureTime.getTime()) { + const footpath: IStep = Step.create( + currentLocation, + enterLocation, + TravelMode.Walking, + { + minimum: transferDepartureTime.getTime() - departureTime.getTime(), + }, + departureTime, + transferDepartureTime, + ); + + path.addStep(footpath); + } + // Public transport step. + const step: IStep = Step.createFromConnections(enterConnection, exitConnection); + step.startLocation = enterLocation; + step.stopLocation = exitLocation; path.addStep(step); + currentLocation = exitLocation; remainingTransfers--; + + // Stop if we already arrived. + if (path.steps[path.steps.length - 1].stopLocation.id === arrivalLocation.id) { + break; + } + + // Get next profile based on the arrival time at the current location. if (remainingTransfers >= 0) { - const nextProfile = profilesByStop[step.stopLocation.id]; - - let i = nextProfile.length - 1; - let found = false; - - while (!found && i >= 0) { - const nextTransferProfile = nextProfile[i].transferProfiles[remainingTransfers]; - const connection = nextTransferProfile.enterConnection; - - if (connection) { - const from = await this.locationResolver.resolve(step.stopLocation.id); - const to = await this.locationResolver.resolve(connection.departureStop); - - const walkingResult = await this.transferRoadPlanner.plan({ - from: [from], - to: [to], - minimumWalkingSpeed: query.minimumWalkingSpeed, - maximumWalkingSpeed: query.maximumWalkingSpeed, - }); - - // Get first path - const walkingPath: IPath = await Iterators.getFirst(walkingResult); - - if (walkingPath && walkingPath.steps[0] && - connection.departureTime.getTime() >= step.stopTime.getTime() + - walkingPath.steps[0].duration.minimum - ) { - found = true; - path.addStep(walkingPath.steps[0]); - currentProfile = nextProfile[i]; - } - } + const currentProfiles: IProfile[] = profilesByStop[currentLocation.id]; + let i: number = currentProfiles.length - 1; - i--; + currentTransferProfile = currentProfiles[i].transferProfiles[remainingTransfers]; + departureTime = new Date(currentTransferProfile.departureTime); + + while (i >= 0 && departureTime < exitConnection.arrivalTime) { + currentTransferProfile = currentProfiles[--i].transferProfiles[remainingTransfers]; + departureTime = new Date(currentTransferProfile.departureTime); } - if (!found) { - return Promise.reject("Can't reach arrival stop: " + arrivalStop.id + ", transfers: " + transfers); + if (i === -1) { + // This should never happen. + return Promise.reject("Can't find next connection"); } + } - } - if (path.steps[path.steps.length - 1].stopLocation.id !== arrivalStop.id) { - shouldReturn = await this.addFinalFootpath(path, arrivalStop, query); + // Final footpath. + if (remainingTransfers === -1) { + const transferArrivalTime: Date = exitConnection.arrivalTime; + const arrivalTime: Date = new Date(currentTransferProfile.arrivalTime); + + if (arrivalTime.getTime() !== transferArrivalTime.getTime()) { + const footpath: IStep = Step.create( + currentLocation, + arrivalLocation, + TravelMode.Walking, + { + minimum: arrivalTime.getTime() - transferArrivalTime.getTime(), + }, + transferArrivalTime, + arrivalTime, + ); + + path.addStep(footpath); + } + } } - if (!shouldReturn) { - return Promise.reject("Can't reach arrival stop: " + arrivalStop.id + ", transfers: " + transfers); + // Check if path ends in the arrival location. + if (path.steps[path.steps.length - 1].stopLocation.id !== arrivalLocation.id) { + // This should never happen. + return Promise.reject("Can't reach arrival stop:"); } return path as IPath; } - - private addInitialFootpath( - path: Path, - transferProfile: ITransferProfile, - reachableStop: IReachableStop, - startLocation: ILocation, - ): void { - const enterConnection: IConnection = transferProfile.enterConnection; - const stopTime = enterConnection.departureTime; - const startTime = new Date(stopTime.valueOf() - reachableStop.duration); - - const step = Step.create( - startLocation, - reachableStop.stop as ILocation, - TravelMode.Walking, - { minimum: reachableStop.duration }, - startTime, - enterConnection.departureTime, - undefined, - ); - - path.addStep(step); - } - - private async addFinalFootpath(path: Path, arrivalStop: ILocation, query: IResolvedQuery): Promise { - const lastStep = path.steps[path.steps.length - 1]; - - const fromLocation = await this.locationResolver.resolve(lastStep.stopLocation.id); - const toLocation = await this.locationResolver.resolve(arrivalStop); - - const walkingResult = await this.finalRoadPlanner.plan({ - from: [fromLocation], - to: [toLocation], - minimumWalkingSpeed: query.minimumWalkingSpeed, - maximumWalkingSpeed: query.maximumWalkingSpeed, - }); - - if (walkingResult && walkingResult[0] && walkingResult[0].steps[0] && - query.maximumArrivalTime.getTime() >= lastStep.stopTime.getTime() + - walkingResult[0].steps[0].duration.minimum) { - path.addStep(walkingResult[0].steps[0]); - return true; - } - return false; - } } diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts index 760564ad..ba27eaa2 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts @@ -37,11 +37,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const locationResolver = new LocationResolverDefault(stopsFetcher); const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); - const roadPlanner = new RoadPlannerBirdsEye(); const journeyExtractor = new JourneyExtractorDefault( - roadPlanner, - roadPlanner, - reachableStopsFinder, locationResolver, ); @@ -50,6 +46,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { locationResolver, reachableStopsFinder, reachableStopsFinder, + reachableStopsFinder, journeyExtractor, ); }; @@ -167,11 +164,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const locationResolver = new LocationResolverDefault(stopsFetcher); const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); - const roadPlanner = new RoadPlannerBirdsEye(); const journeyExtractor = new JourneyExtractorDefault( - roadPlanner, - roadPlanner, - reachableStopsFinder, locationResolver, ); @@ -180,6 +173,7 @@ describe("[PublicTransportPlannerCSAProfile]", () => { locationResolver, reachableStopsFinder, reachableStopsFinder, + reachableStopsFinder, journeyExtractor, ); diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index b2e696a5..8d5476c4 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -3,13 +3,14 @@ import { inject, injectable, tagged } from "inversify"; 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 Vectors from "../../util/Vectors"; -import IReachableStopsFinder from "../stops/IReachableStopsFinder"; +import IReachableStopsFinder, { IReachableStop } from "../stops/IReachableStopsFinder"; import ReachableStopsFinderMode from "../stops/ReachableStopsFinderMode"; import ReachableStopsSearchPhase from "../stops/ReachableStopsSearchPhase"; import IArrivalTimeByTransfers from "./CSA/data-structure/IArrivalTimeByTransfers"; @@ -37,6 +38,7 @@ import IPublicTransportPlanner from "./IPublicTransportPlanner"; export default class PublicTransportPlannerCSAProfile 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; @@ -45,6 +47,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T private durationToTargetByStop: DurationMs[] = []; private gtfsTripByConnection = {}; + private initialReachableStops: IReachableStop[] = []; private query: IResolvedQuery; private connectionsIterator: AsyncIterator; @@ -53,6 +56,9 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor @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) @@ -62,6 +68,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor ) { this.connectionsProvider = connectionsProvider; this.locationResolver = locationResolver; + this.initialReachableStopsFinder = initialReachableStopsFinder; this.transferReachableStopsFinder = transferReachableStopsFinder; this.finalReachableStopsFinder = finalReachableStopsFinder; this.journeyExtractor = journeyExtractor; @@ -89,6 +96,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor private async calculateJourneys(): Promise> { await this.initDurationToTargetByStop(); + await this.initInitialReachableStops(); this.connectionsIterator = this.connectionsProvider.createIterator(); @@ -209,10 +217,24 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor } } + private async initInitialReachableStops(): Promise { + const fromLocation: IStop = this.query.from[0] as IStop; + + this.initialReachableStops = await this.initialReachableStopsFinder.findReachableStops( + fromLocation, + ReachableStopsFinderMode.Source, + this.query.maximumTransferDuration, + this.query.minimumWalkingSpeed, + ); + } + private walkToTarget(connection: IConnection): IArrivalTimeByTransfers { const walkingTimeToTarget = this.durationToTargetByStop[connection.arrivalStop]; - if (walkingTimeToTarget === undefined) { + if ( + walkingTimeToTarget === undefined || + connection.arrivalTime.getTime() + walkingTimeToTarget > this.query.maximumArrivalTime.getTime() + ) { return Array(this.query.maximumTransfers + 1).fill({ "arrivalTime": Infinity, "gtfs:trip": connection["gtfs:trip"], @@ -291,25 +313,46 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor currentArrivalTimeByTransfers: IArrivalTimeByTransfers, ): Promise { const depProfile: Profile[] = this.profilesByStop[connection.departureStop]; - const earliestProfileEntry = depProfile[depProfile.length - 1]; + const earliestProfileEntry: Profile = depProfile[depProfile.length - 1]; - const earliestArrivalTimeByTransfers = Vectors.minVector( + const earliestArrivalTimeByTransfers: IArrivalTimeByTransfers = Vectors.minVector( (c) => c.arrivalTime, currentArrivalTimeByTransfers, earliestProfileEntry.getArrivalTimeByTransfers(), ); - const departureStop = await this.locationResolver.resolve(connection.departureStop); - const reachableStops = await this.transferReachableStopsFinder.findReachableStops( + const canReachInitialStop: IReachableStop = this.initialReachableStops.find((reachable: IReachableStop) => + reachable.stop.id === connection.departureStop, + ); + + if (canReachInitialStop) { + const fromLocation: IStop = this.query.from[0] as IStop; + + this.incorporateInProfile( + connection, + canReachInitialStop.duration, + fromLocation, + earliestArrivalTimeByTransfers, + ); + } + + const departureStop: ILocation = await this.locationResolver.resolve(connection.departureStop); + const reachableStops: IReachableStop[] = await this.transferReachableStopsFinder.findReachableStops( departureStop as IStop, ReachableStopsFinderMode.Source, this.query.maximumTransferDuration, this.query.minimumWalkingSpeed, ); - reachableStops.forEach((reachableStop) => { - // Incorporate (c_dep_time - f_dur, t_c) into profile of S[f_dep_stop] - this.incorporateInProfile(connection, reachableStop.duration, reachableStop.stop, earliestArrivalTimeByTransfers); + reachableStops.forEach((reachableStop: IReachableStop) => { + if (reachableStop.stop.id !== this.query.from[0].id) { + this.incorporateInProfile( + connection, + reachableStop.duration, + reachableStop.stop, + earliestArrivalTimeByTransfers, + ); + } }); } @@ -319,6 +362,12 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor stop: IStop, arrivalTimeByTransfers: IArrivalTimeByTransfers, ) { + const departureTime = connection.departureTime.getTime() - duration; + + if (departureTime < this.query.minimumDepartureTime.getTime()) { + return; + } + let profilesByDepartureStop = this.profilesByStop[stop.id]; if (!profilesByDepartureStop) { @@ -340,6 +389,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor exitConnection: undefined, enterConnection: undefined, arrivalTime: Infinity, + departureTime: Infinity, }; const possibleExitConnection = this.earliestArrivalByTrip[connection["gtfs:trip"]] @@ -352,9 +402,11 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor ) { newTransferProfile.enterConnection = connection; newTransferProfile.exitConnection = possibleExitConnection; + newTransferProfile.departureTime = departureTime; } else { newTransferProfile.enterConnection = transferProfile.enterConnection; newTransferProfile.exitConnection = transferProfile.exitConnection; + newTransferProfile.departureTime = transferProfile.departureTime; } if (newTransferProfile.exitConnection && newTransferProfile.enterConnection) { @@ -364,7 +416,6 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor transferProfiles.push(newTransferProfile); } - const departureTime = connection.departureTime.getTime() - duration; const newProfile: Profile = Profile.createFromTransfers(departureTime, transferProfiles); let i = profilesByDepartureStop.length - 1; From cf986ab78bb0fc7c98170a3462feab143a1156cc Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Wed, 5 Dec 2018 17:21:39 +0100 Subject: [PATCH 08/20] 1. Make sure there is a profile for the departure location. 2. Split transfer reachable stops into initial and transfer reachable stops. #45 #44 --- .../PublicTransportPlannerCSAProfile.test.ts | 16 ++++++++--- .../PublicTransportPlannerCSAProfile.ts | 27 ++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts index ba27eaa2..c17d1b38 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts @@ -1,5 +1,6 @@ import "jest"; import LDFetch from "ldfetch"; +import Defaults from "../../Defaults"; import ConnectionsFetcherLazy from "../../fetcher/connections/ld-fetch/ConnectionsFetcherLazy"; import ConnectionsFetcherNMBSTest from "../../fetcher/connections/tests/ConnectionsFetcherNMBSTest"; import connectionsIngelmunsterGhent from "../../fetcher/connections/tests/data/ingelmunster-ghent"; @@ -16,7 +17,6 @@ import QueryRunnerDefault from "../../query-runner/QueryRunnerDefault"; import TravelMode from "../../TravelMode"; import Iterators from "../../util/Iterators"; import Units from "../../util/Units"; -import RoadPlannerBirdsEye from "../road/RoadPlannerBirdsEye"; import ReachableStopsFinderBirdsEyeCached from "../stops/ReachableStopsFinderBirdsEyeCached"; import JourneyExtractorDefault from "./JourneyExtractorDefault"; import PublicTransportPlannerCSAProfile from "./PublicTransportPlannerCSAProfile"; @@ -56,17 +56,21 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const query: IResolvedQuery = { publicTransportOnly: true, - from: [{ id: "http://irail.be/stations/NMBS/008896925", latitude: 50.914326, longitude: 3.255416 }], - to: [{ id: "http://irail.be/stations/NMBS/008892007", latitude: 51.035896, longitude: 3.710675 }], + from: [{latitude: 50.914326, longitude: 3.255415 }], + to: [{ latitude: 51.035896, longitude: 3.710875 }], 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(result); }); it("Correct departure and arrival stop", () => { @@ -91,6 +95,9 @@ describe("[PublicTransportPlannerCSAProfile]", () => { 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 () => { @@ -125,6 +132,9 @@ describe("[PublicTransportPlannerCSAProfile]", () => { 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 () => { diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index 8d5476c4..4439ecdc 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -76,6 +76,19 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor public async plan(query: IResolvedQuery): Promise> { this.query = query; + + const departureLocation = this.query.from[0]; + if (!departureLocation.id) { + departureLocation.id = "geo:" + departureLocation.latitude + "," + departureLocation.longitude; + departureLocation.name = "Departure location"; + } + + const arrivalLocation = this.query.to[0]; + if (!arrivalLocation.id) { + arrivalLocation.id = "geo:" + arrivalLocation.latitude + "," + arrivalLocation.longitude; + arrivalLocation.name = "Arrival location"; + } + this.setBounds(); return this.calculateJourneys(); @@ -312,6 +325,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor connection: IConnection, currentArrivalTimeByTransfers: IArrivalTimeByTransfers, ): Promise { + const departureLocation: IStop = this.query.from[0] as IStop; const depProfile: Profile[] = this.profilesByStop[connection.departureStop]; const earliestProfileEntry: Profile = depProfile[depProfile.length - 1]; @@ -321,17 +335,16 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor earliestProfileEntry.getArrivalTimeByTransfers(), ); - const canReachInitialStop: IReachableStop = this.initialReachableStops.find((reachable: IReachableStop) => + const initialReachableStop: IReachableStop = this.initialReachableStops.find( + (reachable: IReachableStop) => reachable.stop.id === connection.departureStop, ); - if (canReachInitialStop) { - const fromLocation: IStop = this.query.from[0] as IStop; - + if (initialReachableStop) { this.incorporateInProfile( connection, - canReachInitialStop.duration, - fromLocation, + initialReachableStop.duration, + departureLocation, earliestArrivalTimeByTransfers, ); } @@ -345,7 +358,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor ); reachableStops.forEach((reachableStop: IReachableStop) => { - if (reachableStop.stop.id !== this.query.from[0].id) { + if (reachableStop.stop.id !== departureLocation.id) { this.incorporateInProfile( connection, reachableStop.duration, From c77776a25d28f734128ffe4c9af1ddbc184113c0 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 20 Dec 2018 09:47:11 +0100 Subject: [PATCH 09/20] Revert to nmbs demo --- src/demo.ts | 8 ++++---- src/inversify.config.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/demo.ts b/src/demo.ts index 70b4fff2..4e053d31 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -25,10 +25,10 @@ export default async (logResults) => { // 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/008896008", // Kortrijk - // to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters + // 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/008896008", // Kortrijk + to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters minimumDepartureTime: new Date(), maximumTransferDuration: Units.fromHours(.01), }); diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 662c3d39..8367d9e1 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -1,7 +1,7 @@ import { Container, interfaces } from "inversify"; import LDFetch from "ldfetch"; import Catalog from "./Catalog"; -import catalog from "./catalog.delijn"; +import catalog from "./catalog.nmbs"; import Context from "./Context"; import IConnectionsFetcher from "./fetcher/connections/IConnectionsFetcher"; import IConnectionsProvider from "./fetcher/connections/IConnectionsProvider"; From 735f874a073f29ab386d9a415c19c225f4bafbfe Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 20 Dec 2018 09:47:14 +0100 Subject: [PATCH 10/20] 1. fix joining/splitting tests. --- .../PublicTransportPlannerCSAProfile.test.ts | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts index c17d1b38..2c48688e 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.test.ts @@ -70,7 +70,6 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const CSA = createCSA(connectionsIngelmunsterGhent); const iterator = await CSA.plan(query); result = await Iterators.toArray(iterator); - console.log(result); }); it("Correct departure and arrival stop", () => { @@ -90,8 +89,16 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const query: IResolvedQuery = { publicTransportOnly: true, - from: [{ id: "http://irail.be/stations/NMBS/008821006" }], - to: [{ id: "http://irail.be/stations/NMBS/008812005" }], + 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, @@ -127,8 +134,16 @@ describe("[PublicTransportPlannerCSAProfile]", () => { const query: IResolvedQuery = { publicTransportOnly: true, - from: [{ id: "http://irail.be/stations/NMBS/008812005" }], - to: [{ id: "http://irail.be/stations/NMBS/008821006" }], + 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, From 337128447223c6c7a1ad749a7e71c0685a552caf Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Thu, 20 Dec 2018 11:17:35 +0100 Subject: [PATCH 11/20] 1. added minimumTransferDuration. Default 1min. --- src/Defaults.ts | 1 + src/interfaces/IQuery.ts | 1 + .../public-transport/CSA/util/ProfileUtil.ts | 25 ++++++++++++++----- .../PublicTransportPlannerCSAProfile.ts | 12 +++++++-- src/query-runner/IResolvedQuery.ts | 1 + src/query-runner/QueryRunnerDefault.ts | 3 ++- .../exponential/QueryRunnerExponential.ts | 3 ++- 7 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/Defaults.ts b/src/Defaults.ts index 0406b800..58ef7c2e 100644 --- a/src/Defaults.ts +++ b/src/Defaults.ts @@ -3,6 +3,7 @@ import Units from "./util/Units"; export default class Defaults { public static readonly defaultMinimumWalkingSpeed = 3; public static readonly defaultMaximumWalkingSpeed = 6; + public static readonly defaultMinimumTransferDuration = Units.fromSeconds(60); public static readonly defaultMaximumTransferDuration = Units.fromHours(.4); public static readonly defaultMaximumTransfers = 8; } diff --git a/src/interfaces/IQuery.ts b/src/interfaces/IQuery.ts index 3041e945..48612005 100644 --- a/src/interfaces/IQuery.ts +++ b/src/interfaces/IQuery.ts @@ -11,6 +11,7 @@ export default interface IQuery { walkingSpeed?: SpeedkmH; minimumWalkingSpeed?: SpeedkmH; maximumWalkingSpeed?: SpeedkmH; + minimumTransferDuration?: DurationMs; maximumTransferDuration?: DurationMs; maximumTransfers?: number; } diff --git a/src/planner/public-transport/CSA/util/ProfileUtil.ts b/src/planner/public-transport/CSA/util/ProfileUtil.ts index 5ae0a827..94c51828 100644 --- a/src/planner/public-transport/CSA/util/ProfileUtil.ts +++ b/src/planner/public-transport/CSA/util/ProfileUtil.ts @@ -1,4 +1,5 @@ import IConnection from "../../../../fetcher/connections/IConnection"; +import IArrivalTimeByTransfers from "../data-structure/IArrivalTimeByTransfers"; import IProfilesByStop from "../data-structure/stops/IProfilesByStop"; /** @@ -19,16 +20,28 @@ export default class ProfileUtil { return result; } - public static getTransferTimes(profilesByStop: IProfilesByStop, connection: IConnection, maxLegs) { + public static getTransferTimes( + profilesByStop: IProfilesByStop, + connection: IConnection, + maxLegs, + minimumTransferDuration, + ): IArrivalTimeByTransfers { const { arrivalStop, arrivalTime } = connection; + const trip = connection["gtfs:trip"]; - let i = profilesByStop[arrivalStop].length - 1; - while (i >= 0) { - if (profilesByStop[arrivalStop][i].departureTime >= arrivalTime.getTime()) { - return profilesByStop[arrivalStop][i].getArrivalTimeByTransfers(connection["gtfs:trip"]).slice(); + if (connection["gtfs:dropOffType"] !== "gtfs:NotAvailable") { + + let i = profilesByStop[arrivalStop].length - 1; + while (i >= 0) { + if (profilesByStop[arrivalStop][i].departureTime >= arrivalTime.getTime() + minimumTransferDuration) { + const arrivalTimeByTransfers = profilesByStop[arrivalStop][i].getArrivalTimeByTransfers(trip); + return arrivalTimeByTransfers.slice() as IArrivalTimeByTransfers; + } + i--; } - i--; + } + return Array(maxLegs + 1).fill({ "arrivalTime": Infinity, "gtfs:trip": connection["gtfs:trip"], diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index 4439ecdc..542f260e 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -245,7 +245,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor const walkingTimeToTarget = this.durationToTargetByStop[connection.arrivalStop]; if ( - walkingTimeToTarget === undefined || + walkingTimeToTarget === undefined || connection["gtfs:dropOfType"] === "gtfs:NotAvailable" || connection.arrivalTime.getTime() + walkingTimeToTarget > this.query.maximumArrivalTime.getTime() ) { return Array(this.query.maximumTransfers + 1).fill({ @@ -286,8 +286,16 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor } private takeTransfer(connection: IConnection): IArrivalTimeByTransfers { + + const transferTimes: IArrivalTimeByTransfers = ProfileUtil.getTransferTimes( + this.profilesByStop, + connection, + this.query.maximumTransfers, + this.query.minimumTransferDuration, + ); + return Vectors.shiftVector( - ProfileUtil.getTransferTimes(this.profilesByStop, connection, this.query.maximumTransfers), + transferTimes, { "arrivalTime": Infinity, "gtfs:trip": connection["gtfs:trip"] }, ); } diff --git a/src/query-runner/IResolvedQuery.ts b/src/query-runner/IResolvedQuery.ts index db2f1710..2d8ed37b 100644 --- a/src/query-runner/IResolvedQuery.ts +++ b/src/query-runner/IResolvedQuery.ts @@ -10,6 +10,7 @@ export default interface IResolvedQuery { publicTransportOnly?: boolean; minimumWalkingSpeed?: SpeedkmH; maximumWalkingSpeed?: SpeedkmH; + minimumTransferDuration?: DurationMs; maximumTransferDuration?: DurationMs; maximumTransfers?: number; } diff --git a/src/query-runner/QueryRunnerDefault.ts b/src/query-runner/QueryRunnerDefault.ts index dbd78739..3703bf00 100644 --- a/src/query-runner/QueryRunnerDefault.ts +++ b/src/query-runner/QueryRunnerDefault.ts @@ -54,7 +54,7 @@ export default class QueryRunnerDefault implements IQueryRunner { const { from, to, minimumWalkingSpeed, maximumWalkingSpeed, walkingSpeed, - maximumTransferDuration, maximumTransfers, + minimumTransferDuration, maximumTransferDuration, maximumTransfers, minimumDepartureTime, maximumArrivalTime, ...other } = query; @@ -79,6 +79,7 @@ export default class QueryRunnerDefault implements IQueryRunner { resolvedQuery.minimumWalkingSpeed = minimumWalkingSpeed || walkingSpeed || Defaults.defaultMinimumWalkingSpeed; resolvedQuery.maximumWalkingSpeed = maximumWalkingSpeed || walkingSpeed || Defaults.defaultMaximumWalkingSpeed; + resolvedQuery.minimumTransferDuration = minimumTransferDuration || Defaults.defaultMinimumTransferDuration; resolvedQuery.maximumTransferDuration = maximumTransferDuration || Defaults.defaultMaximumTransferDuration; resolvedQuery.maximumTransfers = maximumTransfers || Defaults.defaultMaximumTransfers; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index d5e2f3f2..e1947b94 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -74,7 +74,7 @@ export default class QueryRunnerExponential implements IQueryRunner { const { from, to, minimumWalkingSpeed, maximumWalkingSpeed, walkingSpeed, - maximumTransferDuration, maximumTransfers, + minimumTransferDuration, maximumTransferDuration, maximumTransfers, minimumDepartureTime, ...other } = query; @@ -89,6 +89,7 @@ export default class QueryRunnerExponential implements IQueryRunner { resolvedQuery.minimumWalkingSpeed = minimumWalkingSpeed || walkingSpeed || Defaults.defaultMinimumWalkingSpeed; resolvedQuery.maximumWalkingSpeed = maximumWalkingSpeed || walkingSpeed || Defaults.defaultMaximumWalkingSpeed; + resolvedQuery.minimumTransferDuration = minimumTransferDuration || Defaults.defaultMinimumTransferDuration; resolvedQuery.maximumTransferDuration = maximumTransferDuration || Defaults.defaultMaximumTransferDuration; resolvedQuery.maximumTransfers = maximumTransfers || Defaults.defaultMaximumTransfers; From 8626b0f1d0690356def52296b2362496e0b10f40 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 20 Dec 2018 11:34:39 +0100 Subject: [PATCH 12/20] Event emitter POC --- src/Context.ts | 7 ++++-- src/EventTypes.ts | 6 +++++ src/Planner.ts | 56 ++++++++++++++++++++++++++++++++++++++++- src/demo.ts | 9 +++++++ src/fetcher/LDFetch.ts | 48 +++++++++++++++++++++++++++++++++++ src/inversify.config.ts | 16 ++---------- 6 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 src/EventTypes.ts create mode 100644 src/fetcher/LDFetch.ts diff --git a/src/Context.ts b/src/Context.ts index ad8f2992..2c0fe5cd 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -1,7 +1,10 @@ -import { Container, injectable } from "inversify"; +import { EventEmitter } from "events"; +import { Container, decorate, injectable } from "inversify"; + +decorate(injectable(), EventEmitter); @injectable() -export default class Context { +export default class Context extends EventEmitter { private container: Container; public setContainer(container: Container) { diff --git a/src/EventTypes.ts b/src/EventTypes.ts new file mode 100644 index 00000000..c8f8079e --- /dev/null +++ b/src/EventTypes.ts @@ -0,0 +1,6 @@ +enum EventTypes { + Query = "query", + LDFetchGet = "ldfetch-get", +} + +export default EventTypes; diff --git a/src/Planner.ts b/src/Planner.ts index d20bcc79..72f1c07e 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -1,5 +1,8 @@ import { AsyncIterator } from "asynciterator"; +// @ts-ignore +import { EventEmitter, Listener } from "events"; import Context from "./Context"; +import EventTypes from "./EventTypes"; import IPath from "./interfaces/IPath"; import IQuery from "./interfaces/IQuery"; import defaultContainer from "./inversify.config"; @@ -13,7 +16,8 @@ if (!Symbol.asyncIterator) { /** * Allows to ask route planning queries over our knowledge graphs */ -export default class Planner { +// @ts-ignore +export default class Planner implements EventEmitter { private context: Context; private queryRunner: IQueryRunner; @@ -35,6 +39,56 @@ export default class Planner { * @returns An AsyncIterator of [[IPath]]s */ public async query(query: IQuery): Promise> { + this.emit(EventTypes.Query, query); + return this.queryRunner.run(query); } + + public addListener(type: string | symbol, listener: Listener): this { + this.context.addListener(type, listener); + + return this; + } + + public emit(type: string | symbol, ...args: any[]): boolean { + return this.context.emit(type, ...args); + } + + public listenerCount(type: string | symbol): number { + return this.context.listenerCount(type); + } + + public listeners(type: string | symbol): Listener[] { + return this.context.listeners(type); + } + + public on(type: string | symbol, listener: Listener): this { + this.context.on(type, listener); + + return this; + } + + public once(type: string | symbol, listener: Listener): this { + this.context.once(type, listener); + + return this; + } + + public removeAllListeners(type?: string | symbol): this { + this.context.removeAllListeners(type); + + return this; + } + + public removeListener(type: string | symbol, listener: Listener): this { + this.context.removeListener(type, listener); + + return this; + } + + public setMaxListeners(n: number): this { + this.context.setMaxListeners(n); + + return this; + } } diff --git a/src/demo.ts b/src/demo.ts index 4e053d31..fffa1eab 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -1,3 +1,4 @@ +import EventTypes from "./EventTypes"; import Planner from "./index"; import Units from "./util/Units"; @@ -19,6 +20,14 @@ export default async (logResults) => { console.time("Public transport planner"); + planner.on(EventTypes.Query, (...args) => { + console.log("Query", args); + }); + + planner.on(EventTypes.LDFetchGet, (url, duration) => { + console.log(`[GET] ${url} (${duration}ms)`); + }); + const publicTransportResult = await planner.query({ publicTransportOnly: true, // from: "https://data.delijn.be/stops/201657", diff --git a/src/fetcher/LDFetch.ts b/src/fetcher/LDFetch.ts new file mode 100644 index 00000000..b22d269d --- /dev/null +++ b/src/fetcher/LDFetch.ts @@ -0,0 +1,48 @@ +import { inject, injectable } from "inversify"; +import LDFetchBase from "ldfetch"; +import { Triple } from "rdf-js"; +import Context from "../Context"; +import EventTypes from "../EventTypes"; +import TYPES from "../types"; + +export interface ILDFetchResponse { + triples: Triple[]; + prefixes: object; + statusCode: string; + url: string; +} + +/** + * Proxies an ldfetch instance to transform its events + */ +@injectable() +export default class LDFetch implements LDFetchBase { + private context: Context; + private ldFetchBase: LDFetchBase; + private httpStartTimes: { [url: string]: Date }; + + constructor( + @inject(TYPES.Context) context: Context, + ) { + this.ldFetchBase = new LDFetchBase({ headers: { Accept: "application/ld+json" } }); + this.context = context; + + this.setupEvents(); + } + + public get(url: string): Promise { + return this.ldFetchBase.get(url); + } + + private setupEvents(): void { + this.httpStartTimes = {}; + + this.ldFetchBase.on("request", (url) => this.httpStartTimes[url] = new Date()); + this.ldFetchBase.on("redirect", (obj) => this.httpStartTimes[obj.to] = this.httpStartTimes[obj.from]); + this.ldFetchBase.on("response", (url) => { + const duration = (new Date()).getTime() - this.httpStartTimes[url].getTime(); + + this.context.emit(EventTypes.LDFetchGet, url, duration); + }); + } +} diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 8367d9e1..de2f9394 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -1,5 +1,4 @@ import { Container, interfaces } from "inversify"; -import LDFetch from "ldfetch"; import Catalog from "./Catalog"; import catalog from "./catalog.nmbs"; import Context from "./Context"; @@ -7,6 +6,7 @@ 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"; import IStopsProvider from "./fetcher/stops/IStopsProvider"; import StopsFetcherLDFetch from "./fetcher/stops/ld-fetch/StopsFetcherLDFetch"; @@ -85,18 +85,6 @@ container.bind>(TYPES.StopsFetcherFactory) container.bind(TYPES.Catalog).toConstantValue(catalog); // Init LDFetch -const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); -const httpStartTimes = {}; - -ldFetch.on("request", (url) => httpStartTimes[url] = new Date()); - -ldFetch.on("redirect", (obj) => httpStartTimes[obj.to] = httpStartTimes[obj.from]); - -ldFetch.on("response", (url) => { - const difference = (new Date()).getTime() - httpStartTimes[url].getTime(); - console.log(`HTTP GET - ${url} (${difference}ms)`); -}); - -container.bind(TYPES.LDFetch).toConstantValue(ldFetch); +container.bind(TYPES.LDFetch).to(LDFetch).inSingletonScope(); export default container; From e01840c220eb9c8d7aab0a289cfe802a22d40d7c Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 20 Dec 2018 11:40:03 +0100 Subject: [PATCH 13/20] Test chaining #on()'s --- src/demo.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/demo.ts b/src/demo.ts index fffa1eab..9934125f 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -20,13 +20,13 @@ export default async (logResults) => { console.time("Public transport planner"); - planner.on(EventTypes.Query, (...args) => { - console.log("Query", args); - }); - - planner.on(EventTypes.LDFetchGet, (url, duration) => { - console.log(`[GET] ${url} (${duration}ms)`); - }); + planner + .on(EventTypes.Query, (...args) => { + console.log("Query", args); + }) + .on(EventTypes.LDFetchGet, (url, duration) => { + console.log(`[GET] ${url} (${duration}ms)`); + }); const publicTransportResult = await planner.query({ publicTransportOnly: true, From 0d6a62b635f824ae27a72aa3181ace3e175dc6af Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 20 Dec 2018 13:02:59 +0100 Subject: [PATCH 14/20] Implement emiterator --- src/EventType.ts | 7 ++++++ src/EventTypes.ts | 6 ----- src/Planner.ts | 4 +-- src/demo.ts | 13 +++++++--- src/fetcher/LDFetch.ts | 4 +-- .../merge/ConnectionsProviderMerge.ts | 2 +- .../exponential/QueryRunnerExponential.ts | 14 ++++++++++- src/util/ReduceIterator.ts | 0 src/util/iterators/Emiterator.ts | 25 +++++++++++++++++++ .../{ => iterators}/MergeIterator.test.ts | 0 src/util/{ => iterators}/MergeIterator.ts | 0 11 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 src/EventType.ts delete mode 100644 src/EventTypes.ts delete mode 100644 src/util/ReduceIterator.ts create mode 100644 src/util/iterators/Emiterator.ts rename src/util/{ => iterators}/MergeIterator.test.ts (100%) rename src/util/{ => iterators}/MergeIterator.ts (100%) diff --git a/src/EventType.ts b/src/EventType.ts new file mode 100644 index 00000000..034c045b --- /dev/null +++ b/src/EventType.ts @@ -0,0 +1,7 @@ +enum EventType { + Query = "query", + QueryExponential = "query-exponential", + LDFetchGet = "ldfetch-get", +} + +export default EventType; diff --git a/src/EventTypes.ts b/src/EventTypes.ts deleted file mode 100644 index c8f8079e..00000000 --- a/src/EventTypes.ts +++ /dev/null @@ -1,6 +0,0 @@ -enum EventTypes { - Query = "query", - LDFetchGet = "ldfetch-get", -} - -export default EventTypes; diff --git a/src/Planner.ts b/src/Planner.ts index 72f1c07e..65c87b28 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 EventTypes from "./EventTypes"; +import EventType from "./EventType"; import IPath from "./interfaces/IPath"; import IQuery from "./interfaces/IQuery"; import defaultContainer from "./inversify.config"; @@ -39,7 +39,7 @@ export default class Planner implements EventEmitter { * @returns An AsyncIterator of [[IPath]]s */ public async query(query: IQuery): Promise> { - this.emit(EventTypes.Query, query); + this.emit(EventType.Query, query); return this.queryRunner.run(query); } diff --git a/src/demo.ts b/src/demo.ts index 5fe59a3b..d03d2bc2 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -1,4 +1,4 @@ -import EventTypes from "./EventTypes"; +import EventType from "./EventType"; import Planner from "./index"; import IPath from "./interfaces/IPath"; import IStep from "./interfaces/IStep"; @@ -23,10 +23,15 @@ export default async (logResults) => { console.time("Public transport planner"); planner - .on(EventTypes.Query, (...args) => { - console.log("Query", args); + .on(EventType.Query, (query) => { + console.log("Query", query); }) - .on(EventTypes.LDFetchGet, (url, duration) => { + .on(EventType.QueryExponential, (query) => { + const {minimumDepartureTime, maximumArrivalTime} = query; + + console.log("[Subquery]", minimumDepartureTime, maximumArrivalTime, maximumArrivalTime - minimumDepartureTime); + }) + .on(EventType.LDFetchGet, (url, duration) => { console.log(`[GET] ${url} (${duration}ms)`); }); diff --git a/src/fetcher/LDFetch.ts b/src/fetcher/LDFetch.ts index b22d269d..fa5492a7 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 EventTypes from "../EventTypes"; +import EventType from "../EventType"; import TYPES from "../types"; export interface ILDFetchResponse { @@ -42,7 +42,7 @@ export default class LDFetch implements LDFetchBase { this.ldFetchBase.on("response", (url) => { const duration = (new Date()).getTime() - this.httpStartTimes[url].getTime(); - this.context.emit(EventTypes.LDFetchGet, url, duration); + this.context.emit(EventType.LDFetchGet, url, duration); }); } } diff --git a/src/fetcher/connections/merge/ConnectionsProviderMerge.ts b/src/fetcher/connections/merge/ConnectionsProviderMerge.ts index 010d91e7..9841fc99 100644 --- a/src/fetcher/connections/merge/ConnectionsProviderMerge.ts +++ b/src/fetcher/connections/merge/ConnectionsProviderMerge.ts @@ -2,7 +2,7 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable } from "inversify"; import Catalog from "../../../Catalog"; import TYPES, { ConnectionsFetcherFactory } from "../../../types"; -import MergeIterator from "../../../util/MergeIterator"; +import MergeIterator from "../../../util/iterators/MergeIterator"; import IConnection from "../IConnection"; import IConnectionsFetcher from "../IConnectionsFetcher"; import IConnectionsFetcherConfig from "../IConnectionsFetcherConfig"; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index e0fc7990..f7c7c70a 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -1,11 +1,14 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable, interfaces } from "inversify"; +import Context from "../../Context"; import Defaults from "../../Defaults"; +import EventType from "../../EventType"; import ILocation from "../../interfaces/ILocation"; 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 ILocationResolver from "../ILocationResolver"; import IQueryRunner from "../IQueryRunner"; import IResolvedQuery from "../IResolvedQuery"; @@ -17,13 +20,17 @@ import SubqueryIterator from "./SubqueryIterator"; export default class QueryRunnerExponential implements IQueryRunner { private locationResolver: ILocationResolver; private publicTransportPlannerFactory: interfaces.Factory; + private context: Context; constructor( + @inject(TYPES.Context) + context: Context, @inject(TYPES.LocationResolver) locationResolver: ILocationResolver, @inject(TYPES.PublicTransportPlannerFactory) publicTransportPlannerFactory: interfaces.Factory, ) { + this.context = context; this.locationResolver = locationResolver; this.publicTransportPlannerFactory = publicTransportPlannerFactory; } @@ -34,7 +41,12 @@ export default class QueryRunnerExponential implements IQueryRunner { if (baseQuery.publicTransportOnly) { const queryIterator = new ExponentialQueryIterator(baseQuery, 15 * 60 * 1000); - const subqueryIterator = new SubqueryIterator(queryIterator, this.runSubquery.bind(this)); + const emitQueryIterator = new Emiterator(queryIterator, this.context, EventType.QueryExponential); + + const subqueryIterator = new SubqueryIterator( + emitQueryIterator, + this.runSubquery.bind(this), + ); return new FilterUniquePathsIterator(subqueryIterator); diff --git a/src/util/ReduceIterator.ts b/src/util/ReduceIterator.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/util/iterators/Emiterator.ts b/src/util/iterators/Emiterator.ts new file mode 100644 index 00000000..d7910073 --- /dev/null +++ b/src/util/iterators/Emiterator.ts @@ -0,0 +1,25 @@ +import { AsyncIterator, SimpleTransformIterator, SimpleTransformIteratorOptions } from "asynciterator"; +import Context from "../../Context"; +import EventType from "../../EventType"; + +/** + * Lazily emits an event of specified type for each item that passes through source iterator + * This doesn't put the source iterator in flow mode + */ +export default class Emiterator extends SimpleTransformIterator { + constructor(source: AsyncIterator, context: Context, eventType: EventType) { + if (!context || !eventType) { + super(source); + } + + const options: SimpleTransformIteratorOptions = { + map: (item: T) => { + context.emit(eventType, item); + + return item; + }, + }; + + super(source, options); + } +} diff --git a/src/util/MergeIterator.test.ts b/src/util/iterators/MergeIterator.test.ts similarity index 100% rename from src/util/MergeIterator.test.ts rename to src/util/iterators/MergeIterator.test.ts diff --git a/src/util/MergeIterator.ts b/src/util/iterators/MergeIterator.ts similarity index 100% rename from src/util/MergeIterator.ts rename to src/util/iterators/MergeIterator.ts From 30a06ae3c39467cab07a62fbf274914a3a228570 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 20 Dec 2018 14:02:16 +0100 Subject: [PATCH 15/20] Fix tests --- src/Context.ts | 68 +++++++++++++++++-- .../QueryRunnerExponential.test.ts | 5 +- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/Context.ts b/src/Context.ts index 2c0fe5cd..00fb1d6e 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -1,12 +1,22 @@ -import { EventEmitter } from "events"; -import { Container, decorate, injectable } from "inversify"; - -decorate(injectable(), EventEmitter); +// @ts-ignore +import { EventEmitter, Listener } from "events"; +import { Container, injectable } from "inversify"; +/** + * 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 + */ @injectable() -export default class Context extends EventEmitter { +// @ts-ignore +export default class Context implements EventEmitter { + private emitter: EventEmitter; private container: Container; + constructor() { + this.emitter = new EventEmitter(); + } + public setContainer(container: Container) { this.container = container; } @@ -14,4 +24,52 @@ export default class Context extends EventEmitter { public getContainer() { return this.container; } + + public addListener(type: string | symbol, listener: Listener): this { + this.emitter.addListener(type, listener); + + return this; + } + + public emit(type: string | symbol, ...args: any[]): boolean { + return this.emitter.emit(type, ...args); + } + + public listenerCount(type: string | symbol): number { + return this.emitter.listenerCount(type); + } + + public listeners(type: string | symbol): Listener[] { + return this.emitter.listeners(type); + } + + public on(type: string | symbol, listener: Listener): this { + this.emitter.on(type, listener); + + return this; + } + + public once(type: string | symbol, listener: Listener): this { + this.emitter.once(type, listener); + + return this; + } + + public removeAllListeners(type?: string | symbol): this { + this.emitter.removeAllListeners(type); + + return this; + } + + public removeListener(type: string | symbol, listener: Listener): this { + this.emitter.removeListener(type, listener); + + return this; + } + + public setMaxListeners(n: number): this { + this.emitter.setMaxListeners(n); + + return this; + } } diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index bbbe9068..1e000033 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -1,5 +1,6 @@ import "jest"; import LDFetch from "ldfetch"; +import Context from "../../Context"; import ConnectionsFetcherLazy from "../../fetcher/connections/ld-fetch/ConnectionsFetcherLazy"; import StopsFetcherLDFetch from "../../fetcher/stops/ld-fetch/StopsFetcherLDFetch"; import IPath from "../../interfaces/IPath"; @@ -38,6 +39,8 @@ describe("[QueryRunnerExponential]", () => { const locationResolver = new LocationResolverDefault(stopsFetcher); const reachableStopsFinder = new ReachableStopsFinderBirdsEyeCached(stopsFetcher); + const context = new Context(); + const createJourneyExtractor = () => { return new JourneyExtractorDefault( locationResolver, @@ -55,7 +58,7 @@ describe("[QueryRunnerExponential]", () => { ); }; - return new QueryRunnerExponential(locationResolver, createPlanner); + return new QueryRunnerExponential(context, locationResolver, createPlanner); }; const result: IPath[] = []; From 02501d8a54f40659c73a629292628bb262360f4e Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 20 Dec 2018 14:25:33 +0100 Subject: [PATCH 16/20] Fix demo test --- src/demo.ts | 71 +++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/src/demo.ts b/src/demo.ts index d03d2bc2..1a9d5e84 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -8,32 +8,20 @@ export default async (logResults) => { const planner = new Planner(); - // const roadOnlyResult = await planner.query({ - // roadOnly: true, - // from: "http://irail.be/stations/NMBS/008896008", // Kortrijk - // to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters - // }); -// - // roadOnlyResult.each((path) => { - // if (logResults) { - // console.log(path); - // } - // }); - - console.time("Public transport planner"); - - planner - .on(EventType.Query, (query) => { - console.log("Query", query); - }) - .on(EventType.QueryExponential, (query) => { - const {minimumDepartureTime, maximumArrivalTime} = query; - - console.log("[Subquery]", minimumDepartureTime, maximumArrivalTime, maximumArrivalTime - minimumDepartureTime); - }) - .on(EventType.LDFetchGet, (url, duration) => { - console.log(`[GET] ${url} (${duration}ms)`); - }); + if (logResults) { + planner + .on(EventType.Query, (query) => { + console.log("Query", query); + }) + .on(EventType.QueryExponential, (query) => { + const { minimumDepartureTime, maximumArrivalTime } = query; + + console.log("[Subquery]", minimumDepartureTime, maximumArrivalTime, maximumArrivalTime - minimumDepartureTime); + }) + .on(EventType.LDFetchGet, (url, duration) => { + console.log(`[GET] ${url} (${duration}ms)`); + }); + } const publicTransportResult = await planner.query({ publicTransportOnly: true, @@ -49,17 +37,24 @@ export default async (logResults) => { maximumTransferDuration: Units.fromHours(.01), }); - console.timeEnd("Public transport planner"); - - let i = 0; - - publicTransportResult.take(3).on("data", (path: IPath) => { - console.log(++i); - path.steps.forEach((step: IStep) => { - console.log(JSON.stringify(step, null, " ")); - }); - console.log("\n"); + return new Promise((resolve, reject) => { + let i = 0; + + publicTransportResult.take(3) + .on("data", (path: IPath) => { + ++i; + + if (logResults) { + console.log(++i); + path.steps.forEach((step: IStep) => { + console.log(JSON.stringify(step, null, " ")); + }); + console.log("\n"); + } + + if (i === 3) { + resolve(true); + } + }); }); - - return true; }; From f895fc5d8bdf71286694264f1d6dc9e1cba5d188 Mon Sep 17 00:00:00 2001 From: Simon Vanneste Date: Thu, 20 Dec 2018 14:56:07 +0100 Subject: [PATCH 17/20] Combine catalogs method --- src/Catalog.ts | 12 ++++++++++++ src/inversify.config.ts | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Catalog.ts b/src/Catalog.ts index 4a18dee8..c02fdd6b 100644 --- a/src/Catalog.ts +++ b/src/Catalog.ts @@ -1,6 +1,18 @@ import TravelMode from "./TravelMode"; export default class Catalog { + + public static combine(...catalogs: Catalog[]): Catalog { + const combinedCatalog = new Catalog(); + + for (const sourceCatalog of catalogs) { + combinedCatalog.stopsFetcherConfigs.push(...sourceCatalog.stopsFetcherConfigs); + combinedCatalog.connectionsFetcherConfigs.push(...sourceCatalog.connectionsFetcherConfigs); + } + + return combinedCatalog; + } + public stopsFetcherConfigs = []; public connectionsFetcherConfigs = []; diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 8367d9e1..c087f876 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -1,7 +1,8 @@ import { Container, interfaces } from "inversify"; import LDFetch from "ldfetch"; import Catalog from "./Catalog"; -import catalog from "./catalog.nmbs"; +import catalogDeLijn from "./catalog.delijn"; +import catalogNmbs from "./catalog.nmbs"; import Context from "./Context"; import IConnectionsFetcher from "./fetcher/connections/IConnectionsFetcher"; import IConnectionsProvider from "./fetcher/connections/IConnectionsProvider"; @@ -82,7 +83,10 @@ container.bind>(TYPES.StopsFetcherFactory) ); // Bind catalog -container.bind(TYPES.Catalog).toConstantValue(catalog); +container.bind(TYPES.Catalog).toConstantValue(catalogNmbs); + +// const combinedCatalog = Catalog.combine(catalogNmbs, catalogDeLijn); +// container.bind(TYPES.Catalog).toConstantValue(combinedCatalog); // Init LDFetch const ldFetch = new LDFetch({ headers: { Accept: "application/ld+json" } }); From 733d4cd1f6e80976dfd3a110d7dd336be9aca563 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 21 Dec 2018 10:02:39 +0100 Subject: [PATCH 18/20] 1. Abort event when query is impossible #53 --- src/EventType.ts | 2 + src/Planner.ts | 8 +++- src/demo.ts | 22 ++++++--- .../PublicTransportPlannerCSAProfile.ts | 45 +++++++++++++++---- .../exponential/FilterUniquePathsIterator.ts | 5 ++- .../QueryRunnerExponential.test.ts | 23 +++------- .../exponential/QueryRunnerExponential.ts | 10 ++++- .../exponential/SubqueryIterator.ts | 2 +- src/util/iterators/Emiterator.ts | 2 + 9 files changed, 83 insertions(+), 36 deletions(-) diff --git a/src/EventType.ts b/src/EventType.ts index 034c045b..17dcb13b 100644 --- a/src/EventType.ts +++ b/src/EventType.ts @@ -1,7 +1,9 @@ enum EventType { Query = "query", QueryExponential = "query-exponential", + QueryAbort = "query-abort", LDFetchGet = "ldfetch-get", + ConnectionScan = "connection-scan", } export default EventType; diff --git a/src/Planner.ts b/src/Planner.ts index 65c87b28..ae3ea6ec 100644 --- a/src/Planner.ts +++ b/src/Planner.ts @@ -41,7 +41,13 @@ export default class Planner implements EventEmitter { public async query(query: IQuery): Promise> { this.emit(EventType.Query, query); - return this.queryRunner.run(query); + const iterator = await this.queryRunner.run(query); + + this.once(EventType.QueryAbort, () => { + iterator.close(); + }); + + return iterator; } public addListener(type: string | symbol, listener: Listener): this { diff --git a/src/demo.ts b/src/demo.ts index 1a9d5e84..a742f2ea 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -9,6 +9,9 @@ export default async (logResults) => { const planner = new Planner(); if (logResults) { + let scannedPages = 0; + let scannedConnections = 0; + planner .on(EventType.Query, (query) => { console.log("Query", query); @@ -16,10 +19,16 @@ export default async (logResults) => { .on(EventType.QueryExponential, (query) => { const { minimumDepartureTime, maximumArrivalTime } = query; + 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)`); + }) + .on(EventType.ConnectionScan, (connection) => { + scannedConnections++; }); } @@ -31,8 +40,10 @@ 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/008896008", // Kortrijk + from: "http://irail.be/stations/NMBS/008896925", // Ingelmunster to: "http://irail.be/stations/NMBS/008892007", // Ghent-Sint-Pieters + /* from: {longitude: 3.707352, latitude: 51.011877}, + to: {longitude: 3.707353, latitude: 51.011877},*/ minimumDepartureTime: new Date(), maximumTransferDuration: Units.fromHours(.01), }); @@ -45,16 +56,17 @@ export default async (logResults) => { ++i; if (logResults) { - console.log(++i); - path.steps.forEach((step: IStep) => { - console.log(JSON.stringify(step, null, " ")); - }); + console.log(i); + console.log(JSON.stringify(path, null, " ")); console.log("\n"); } if (i === 3) { resolve(true); } + }) + .on("end", () => { + resolve(false); }); }); }; diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index 542f260e..889fab7d 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -1,5 +1,7 @@ import { AsyncIterator } from "asynciterator"; import { inject, injectable, tagged } from "inversify"; +import Context from "../../Context"; +import EventType from "../../EventType"; import IConnection from "../../fetcher/connections/IConnection"; import IConnectionsProvider from "../../fetcher/connections/IConnectionsProvider"; import IStop from "../../fetcher/stops/IStop"; @@ -42,6 +44,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor private readonly finalReachableStopsFinder: IReachableStopsFinder; private readonly transferReachableStopsFinder: IReachableStopsFinder; private readonly journeyExtractor: IJourneyExtractor; + private readonly context: Context; private profilesByStop: IProfilesByStop = {}; // S private earliestArrivalByTrip: IEarliestArrivalByTrip = {}; // T @@ -53,8 +56,10 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor private connectionsIterator: AsyncIterator; constructor( - @inject(TYPES.ConnectionsProvider) connectionsProvider: IConnectionsProvider, - @inject(TYPES.LocationResolver) locationResolver: ILocationResolver, + @inject(TYPES.ConnectionsProvider) + connectionsProvider: IConnectionsProvider, + @inject(TYPES.LocationResolver) + locationResolver: ILocationResolver, @inject(TYPES.ReachableStopsFinder) @tagged("phase", ReachableStopsSearchPhase.Initial) initialReachableStopsFinder: IReachableStopsFinder, @@ -64,7 +69,10 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor @inject(TYPES.ReachableStopsFinder) @tagged("phase", ReachableStopsSearchPhase.Final) finalReachableStopsFinder: IReachableStopsFinder, - @inject(TYPES.JourneyExtractor) journeyExtractor: IJourneyExtractor, + @inject(TYPES.JourneyExtractor) + journeyExtractor: IJourneyExtractor, + @inject(TYPES.Context) + context?: Context, ) { this.connectionsProvider = connectionsProvider; this.locationResolver = locationResolver; @@ -72,6 +80,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor this.transferReachableStopsFinder = transferReachableStopsFinder; this.finalReachableStopsFinder = finalReachableStopsFinder; this.journeyExtractor = journeyExtractor; + this.context = context; } public async plan(query: IResolvedQuery): Promise> { @@ -117,18 +126,18 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor return new Promise((resolve, reject) => { - const done = () => { + const done = () => { self.journeyExtractor.extractJourneys(self.profilesByStop, self.query) .then((resultIterator) => { resolve(resultIterator); }); }; - this.connectionsIterator.on("readable", () => + this.connectionsIterator.on("readable", () => self.processNextConnection(done), ); - this.connectionsIterator.on("end", () => done()); + this.connectionsIterator.on("end", () => done()); }) as Promise>; } @@ -148,6 +157,10 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor return; } + if (this.context) { + this.context.emit(EventType.ConnectionScan, connection); + } + this.discoverConnection(connection); const earliestArrivalTime = this.calculateEarliestArrivalTime(connection); @@ -214,7 +227,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor return Vectors.minVector((c) => c.arrivalTime, walkToTargetTime, remainSeatedTime, takeTransferTime); } - private async initDurationToTargetByStop(): Promise { + private async initDurationToTargetByStop(): Promise { const arrivalStop: IStop = this.query.to[0] as IStop; const reachableStops = await this.finalReachableStopsFinder @@ -225,12 +238,20 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor this.query.minimumWalkingSpeed, ); + if (reachableStops.length === 0 && this.context) { + this.context.emit(EventType.QueryAbort); + + return false; + } + for (const reachableStop of reachableStops) { this.durationToTargetByStop[reachableStop.stop.id] = reachableStop.duration; } + + return true; } - private async initInitialReachableStops(): Promise { + private async initInitialReachableStops(): Promise { const fromLocation: IStop = this.query.from[0] as IStop; this.initialReachableStops = await this.initialReachableStopsFinder.findReachableStops( @@ -239,6 +260,14 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor this.query.maximumTransferDuration, this.query.minimumWalkingSpeed, ); + + if (this.initialReachableStops.length === 0 && this.context) { + this.context.emit(EventType.QueryAbort); + + return false; + } + + return true; } private walkToTarget(connection: IConnection): IArrivalTimeByTransfers { diff --git a/src/query-runner/exponential/FilterUniquePathsIterator.ts b/src/query-runner/exponential/FilterUniquePathsIterator.ts index 6478b97d..bec79048 100644 --- a/src/query-runner/exponential/FilterUniquePathsIterator.ts +++ b/src/query-runner/exponential/FilterUniquePathsIterator.ts @@ -7,7 +7,10 @@ export default class FilterUniquePathsIterator extends SimpleTransformIterator) { - super(source); + super(source, { + maxBufferSize: 1, + autoStart: false, + }); this.store = []; } diff --git a/src/query-runner/exponential/QueryRunnerExponential.test.ts b/src/query-runner/exponential/QueryRunnerExponential.test.ts index 1e000033..06985aef 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.test.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.test.ts @@ -69,26 +69,13 @@ describe("[QueryRunnerExponential]", () => { publicTransportResult = await queryRunner.run(query); - let i = 0; - publicTransportResult.on("readable", () => { - let path = publicTransportResult.read(); - if (path) { + await publicTransportResult.take(3) + .on("data", (path: IPath) => { result.push(path); - } - - while (path && i < 10) { - i++; - path = publicTransportResult.read(); - - if (path) { - result.push(path); - } - } - - if (i === 10) { + }) + .on("end", () => { done(); - } - }); + }); }); it("Correct departure and arrival stop", () => { diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index f7c7c70a..68c70678 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -41,10 +41,14 @@ 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 emitQueryIterator = new Emiterator( + // queryIterator, + // this.context, + // EventType.QueryExponential, + // ); const subqueryIterator = new SubqueryIterator( - emitQueryIterator, + queryIterator, this.runSubquery.bind(this), ); @@ -57,6 +61,8 @@ 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); + const planner = this.publicTransportPlannerFactory() as IPublicTransportPlanner; return planner.plan(query); diff --git a/src/query-runner/exponential/SubqueryIterator.ts b/src/query-runner/exponential/SubqueryIterator.ts index 10485a6e..e749e5e3 100644 --- a/src/query-runner/exponential/SubqueryIterator.ts +++ b/src/query-runner/exponential/SubqueryIterator.ts @@ -53,7 +53,7 @@ export default class SubqueryIterator extends BufferedIterator { } // Iterator was empty - if (self.currentResultPushed === 0) { + if (self.currentResultPushed === 0 && !this.closed) { self._read(null, done); } }); diff --git a/src/util/iterators/Emiterator.ts b/src/util/iterators/Emiterator.ts index d7910073..3b54f8a3 100644 --- a/src/util/iterators/Emiterator.ts +++ b/src/util/iterators/Emiterator.ts @@ -18,6 +18,8 @@ export default class Emiterator extends SimpleTransformIterator { return item; }, + maxBufferSize: 1, + autoStart: false, }; super(source, options); From db68853af237a1d6b6420e9ff793eeebf8ae106f Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 21 Dec 2018 13:45:18 +0100 Subject: [PATCH 19/20] 1. Added maximumWalkingDuration and maximumWalkingDistance #42 2. close iterators. --- src/Defaults.ts | 3 +- src/EventType.ts | 3 + src/demo.ts | 5 +- .../ld-fetch/ConnectionsIteratorLazy.ts | 24 +++---- src/interfaces/IQuery.ts | 4 +- .../public-transport/CSA/util/ProfileUtil.ts | 25 +++++--- .../PublicTransportPlannerCSAProfile.ts | 64 ++++++++++++++----- src/query-runner/IResolvedQuery.ts | 1 + src/query-runner/QueryRunnerDefault.ts | 4 ++ .../exponential/QueryRunnerExponential.ts | 4 ++ src/util/iterators/MergeIterator.ts | 8 +++ 11 files changed, 103 insertions(+), 42 deletions(-) diff --git a/src/Defaults.ts b/src/Defaults.ts index 58ef7c2e..2be8ecc1 100644 --- a/src/Defaults.ts +++ b/src/Defaults.ts @@ -3,7 +3,8 @@ import Units from "./util/Units"; export default class Defaults { public static readonly defaultMinimumWalkingSpeed = 3; public static readonly defaultMaximumWalkingSpeed = 6; + public static readonly defaultWalkingDuration = Units.fromSeconds(10 * 60); public static readonly defaultMinimumTransferDuration = Units.fromSeconds(60); public static readonly defaultMaximumTransferDuration = Units.fromHours(.4); - public static readonly defaultMaximumTransfers = 8; + public static readonly defaultMaximumTransfers = 4; } diff --git a/src/EventType.ts b/src/EventType.ts index 17dcb13b..d7cfe1a7 100644 --- a/src/EventType.ts +++ b/src/EventType.ts @@ -4,6 +4,9 @@ enum EventType { QueryAbort = "query-abort", LDFetchGet = "ldfetch-get", ConnectionScan = "connection-scan", + FinalReachableStops = "final-reachable-stops", + InitialReachableStops = "initial-reachable-stops", + AddedNewTransferProfile = "added-new-transfer-profile", } export default EventType; diff --git a/src/demo.ts b/src/demo.ts index a742f2ea..240f8605 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -1,7 +1,6 @@ import EventType from "./EventType"; import Planner from "./index"; import IPath from "./interfaces/IPath"; -import IStep from "./interfaces/IStep"; import Units from "./util/Units"; export default async (logResults) => { @@ -42,10 +41,8 @@ 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 - /* from: {longitude: 3.707352, latitude: 51.011877}, - to: {longitude: 3.707353, latitude: 51.011877},*/ minimumDepartureTime: new Date(), - maximumTransferDuration: Units.fromHours(.01), + maximumTransferDuration: Units.fromHours(0.5), }); return new Promise((resolve, reject) => { diff --git a/src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.ts b/src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.ts index d1760dd0..5308d950 100644 --- a/src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.ts +++ b/src/fetcher/connections/ld-fetch/ConnectionsIteratorLazy.ts @@ -85,22 +85,22 @@ export default class ConnectionsIteratorLazy extends BufferedIterator= 0) { - this._push(connections[c]); - c--; - } + while (c >= 0) { + this._push(connections[c]); + c--; + } - // Forwards - } else { - for (const connection of connections) { - this._push(connection); - } + // Forwards + } else { + for (const connection of connections) { + this._push(connection); } } + } } diff --git a/src/interfaces/IQuery.ts b/src/interfaces/IQuery.ts index 48612005..1142c149 100644 --- a/src/interfaces/IQuery.ts +++ b/src/interfaces/IQuery.ts @@ -1,5 +1,5 @@ import ILocation from "./ILocation"; -import { DurationMs, SpeedkmH } from "./units"; +import { DistanceM, DurationMs, SpeedkmH } from "./units"; export default interface IQuery { from?: string | string[] | ILocation | ILocation[]; @@ -11,6 +11,8 @@ export default interface IQuery { walkingSpeed?: SpeedkmH; minimumWalkingSpeed?: SpeedkmH; maximumWalkingSpeed?: SpeedkmH; + maximumWalkingDuration?: DurationMs; + maximumWalkingDistance?: DistanceM; minimumTransferDuration?: DurationMs; maximumTransferDuration?: DurationMs; maximumTransfers?: number; diff --git a/src/planner/public-transport/CSA/util/ProfileUtil.ts b/src/planner/public-transport/CSA/util/ProfileUtil.ts index 94c51828..f8317df9 100644 --- a/src/planner/public-transport/CSA/util/ProfileUtil.ts +++ b/src/planner/public-transport/CSA/util/ProfileUtil.ts @@ -1,4 +1,6 @@ +import DropOffType from "../../../../fetcher/connections/DropOffType"; import IConnection from "../../../../fetcher/connections/IConnection"; +import { DurationMs } from "../../../../interfaces/units"; import IArrivalTimeByTransfers from "../data-structure/IArrivalTimeByTransfers"; import IProfilesByStop from "../data-structure/stops/IProfilesByStop"; @@ -23,21 +25,28 @@ export default class ProfileUtil { public static getTransferTimes( profilesByStop: IProfilesByStop, connection: IConnection, - maxLegs, - minimumTransferDuration, + maxLegs: number, + minimumTransferDuration: DurationMs, + maximumTransferDuration: DurationMs, ): IArrivalTimeByTransfers { const { arrivalStop, arrivalTime } = connection; const trip = connection["gtfs:trip"]; - if (connection["gtfs:dropOffType"] !== "gtfs:NotAvailable") { + if (connection["gtfs:dropOffType"] !== DropOffType.NotAvailable) { - let i = profilesByStop[arrivalStop].length - 1; - while (i >= 0) { - if (profilesByStop[arrivalStop][i].departureTime >= arrivalTime.getTime() + minimumTransferDuration) { - const arrivalTimeByTransfers = profilesByStop[arrivalStop][i].getArrivalTimeByTransfers(trip); + let profileIndex = profilesByStop[arrivalStop].length - 1; + while (profileIndex >= 0) { + + const departure: number = profilesByStop[arrivalStop][profileIndex].departureTime; + const arrival: number = arrivalTime.getTime(); + + const transferDuration = departure - arrival; + + if (transferDuration >= minimumTransferDuration && transferDuration <= maximumTransferDuration) { + const arrivalTimeByTransfers = profilesByStop[arrivalStop][profileIndex].getArrivalTimeByTransfers(trip); return arrivalTimeByTransfers.slice() as IArrivalTimeByTransfers; } - i--; + profileIndex--; } } diff --git a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts index 889fab7d..e8c7db71 100644 --- a/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts +++ b/src/planner/public-transport/PublicTransportPlannerCSAProfile.ts @@ -2,8 +2,10 @@ 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 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"; @@ -125,19 +127,24 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor const self = this; return new Promise((resolve, reject) => { - - const done = () => { - self.journeyExtractor.extractJourneys(self.profilesByStop, self.query) - .then((resultIterator) => { - resolve(resultIterator); - }); + 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", () => + this.connectionsIterator.on("readable", () => self.processNextConnection(done), ); - this.connectionsIterator.on("end", () => done()); + this.connectionsIterator.on("end", () => done()); }) as Promise>; } @@ -234,7 +241,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor .findReachableStops( arrivalStop, ReachableStopsFinderMode.Target, - this.query.maximumTransferDuration, + this.query.maximumWalkingDuration, this.query.minimumWalkingSpeed, ); @@ -244,6 +251,10 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor return false; } + if (this.context) { + this.context.emit(EventType.FinalReachableStops, reachableStops); + } + for (const reachableStop of reachableStops) { this.durationToTargetByStop[reachableStop.stop.id] = reachableStop.duration; } @@ -257,7 +268,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor this.initialReachableStops = await this.initialReachableStopsFinder.findReachableStops( fromLocation, ReachableStopsFinderMode.Source, - this.query.maximumTransferDuration, + this.query.maximumWalkingDuration, this.query.minimumWalkingSpeed, ); @@ -267,6 +278,10 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor return false; } + if (this.context) { + this.context.emit(EventType.InitialReachableStops, this.initialReachableStops); + } + return true; } @@ -321,7 +336,8 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor connection, this.query.maximumTransfers, this.query.minimumTransferDuration, - ); + this.query.maximumTransferDuration, + ); return Vectors.shiftVector( transferTimes, @@ -374,7 +390,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor const initialReachableStop: IReachableStop = this.initialReachableStops.find( (reachable: IReachableStop) => - reachable.stop.id === connection.departureStop, + reachable.stop.id === connection.departureStop, ); if (initialReachableStop) { @@ -390,7 +406,7 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor const reachableStops: IReachableStop[] = await this.transferReachableStopsFinder.findReachableStops( departureStop as IStop, ReachableStopsFinderMode.Source, - this.query.maximumTransferDuration, + this.query.maximumWalkingDuration, this.query.minimumWalkingSpeed, ); @@ -406,12 +422,23 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor }); } + private async emitTransferProfile(transferProfile: ITransferProfile, amountOfTransfers: number): Promise { + 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, + amountOfTransfers, + }); + } + private incorporateInProfile( connection: IConnection, duration: DurationMs, stop: IStop, arrivalTimeByTransfers: IArrivalTimeByTransfers, - ) { + ): void { const departureTime = connection.departureTime.getTime() - duration; if (departureTime < this.query.minimumDepartureTime.getTime()) { @@ -447,12 +474,17 @@ export default class PublicTransportPlannerCSAProfile implements IPublicTranspor if ( arrivalTimeByTransfers[amountOfTransfers].arrivalTime < transferProfile.arrivalTime && - connection["gtfs:pickupType"] !== "gtfs:NotAvailable" && - possibleExitConnection["gtfs:dropOfType"] !== "gtfs:NotAvailable" + connection["gtfs:pickupType"] !== PickupType.NotAvailable && + possibleExitConnection["gtfs:dropOfType"] !== DropOffType.NotAvailable ) { newTransferProfile.enterConnection = connection; newTransferProfile.exitConnection = possibleExitConnection; newTransferProfile.departureTime = departureTime; + + if (this.context && this.context.listenerCount(EventType.AddedNewTransferProfile) > 0) { + this.emitTransferProfile(newTransferProfile, amountOfTransfers); + } + } else { newTransferProfile.enterConnection = transferProfile.enterConnection; newTransferProfile.exitConnection = transferProfile.exitConnection; diff --git a/src/query-runner/IResolvedQuery.ts b/src/query-runner/IResolvedQuery.ts index 2d8ed37b..210fe29f 100644 --- a/src/query-runner/IResolvedQuery.ts +++ b/src/query-runner/IResolvedQuery.ts @@ -10,6 +10,7 @@ export default interface IResolvedQuery { publicTransportOnly?: boolean; minimumWalkingSpeed?: SpeedkmH; maximumWalkingSpeed?: SpeedkmH; + maximumWalkingDuration?: DurationMs; minimumTransferDuration?: DurationMs; maximumTransferDuration?: DurationMs; maximumTransfers?: number; diff --git a/src/query-runner/QueryRunnerDefault.ts b/src/query-runner/QueryRunnerDefault.ts index 3703bf00..d78e60cf 100644 --- a/src/query-runner/QueryRunnerDefault.ts +++ b/src/query-runner/QueryRunnerDefault.ts @@ -6,6 +6,7 @@ import IPath from "../interfaces/IPath"; import IQuery from "../interfaces/IQuery"; import IPublicTransportPlanner from "../planner/public-transport/IPublicTransportPlanner"; import TYPES from "../types"; +import Units from "../util/Units"; import ILocationResolver from "./ILocationResolver"; import IQueryRunner from "./IQueryRunner"; import IResolvedQuery from "./IResolvedQuery"; @@ -54,6 +55,7 @@ export default class QueryRunnerDefault implements IQueryRunner { const { from, to, minimumWalkingSpeed, maximumWalkingSpeed, walkingSpeed, + maximumWalkingDuration, maximumWalkingDistance, minimumTransferDuration, maximumTransferDuration, maximumTransfers, minimumDepartureTime, maximumArrivalTime, ...other @@ -78,6 +80,8 @@ export default class QueryRunnerDefault implements IQueryRunner { resolvedQuery.to = await this.resolveEndpoint(to); 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; diff --git a/src/query-runner/exponential/QueryRunnerExponential.ts b/src/query-runner/exponential/QueryRunnerExponential.ts index 68c70678..cde5f40b 100644 --- a/src/query-runner/exponential/QueryRunnerExponential.ts +++ b/src/query-runner/exponential/QueryRunnerExponential.ts @@ -9,6 +9,7 @@ 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"; import IResolvedQuery from "../IResolvedQuery"; @@ -88,6 +89,7 @@ export default class QueryRunnerExponential implements IQueryRunner { const { from, to, minimumWalkingSpeed, maximumWalkingSpeed, walkingSpeed, + maximumWalkingDuration, maximumWalkingDistance, minimumTransferDuration, maximumTransferDuration, maximumTransfers, minimumDepartureTime, ...other @@ -102,6 +104,8 @@ export default class QueryRunnerExponential implements IQueryRunner { resolvedQuery.to = await this.resolveEndpoint(to); 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; diff --git a/src/util/iterators/MergeIterator.ts b/src/util/iterators/MergeIterator.ts index 169bf868..384ff3a7 100644 --- a/src/util/iterators/MergeIterator.ts +++ b/src/util/iterators/MergeIterator.ts @@ -40,6 +40,14 @@ export default class MergeIterator extends BufferedIterator { }); } + public close() { + for (const iterator of this.sourceIterators) { + iterator.close(); + } + + super.close(); + } + private fillFirstValues(done) { this.values = Array(this.sourceIterators.length).fill(undefined); let filledValues = 0; From fe167a1ae507df31bd4d1ded6fd19eaa8991b221 Mon Sep 17 00:00:00 2001 From: Maxim Martin Date: Fri, 21 Dec 2018 15:52:02 +0100 Subject: [PATCH 20/20] Add map demo to CodePen in docs #47 --- docs/index.html | 14 ++++++++++++++ src/Planner.ts | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/docs/index.html b/docs/index.html index 8ff8f211..061f60d4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -27,6 +27,7 @@

Planner.js

Demo

+

Table

Demo See the Pen Using the new JavaScript route planner on CodePen.

+

Map

+

+ See the Pen Using the new JavaScript route + planner on CodePen. +