Planner.js

The ultimate JavaScript framework for journey planning. A level playing field for Mobility as a Service actors.

Everyone should be able to set up their own route planner with their specific needs. Before Planner.js, it used to be expensive to set up your own route planner: you first need to find the right data dumps, need to integrate them manually into your own format, and then you need to connect them to a route planning system. The real-time data then should be handled with a different system if is available at all.

Getting Started

Demo

See the Pen Planner.js results NMBS by Julian Rojas (@julianrojas87) on CodePen.

Architecture

Planner.js makes heavy use of inversify, a dependency injection framework. This allows anyone to make a quick custom build of a route planner, with specific needs. Configuration of what classes should be injected where, happens by inside an inversify Container. The default Container (used by the CDN version) can be found in inversify.config.ts

Different Interfaces

For a full overview, please consult our code documentation. The interfaces for the Planner are split in:

Providers

Providers serve as data interfaces for all fetchers of the same type (registered in the inversify Container). When a class needs some data, it injects a Provider of the right type. The Provider determines the right fetcher and passes any data requests through to that fetcher.

Right now, there are two types of data, so there are two types of providers: connections providers and stops providers

Fetchers

These are ways to fetch data from different sources. Each fetcher corresponds to one source. We will use the Comunica framework to fetch the data as an intelligent agent. For now, we are just using the Linked Data Fetch NPM package and manually implement the routable tiles and Linked Connections specification.

Right now, there are two types of data, so there are two types of fetchers: connections fetchers and stops fetchers

Planners

These represent the core algorithms of this library. There are road planners and public-transport planners. Additionally, there are reachable stops finders, which are used in certain steps of public-transport planner algorithms

Creating your own Planner version

We still need to properly write this part of the documentation. However, the code may help you out understanding how it currently works. By default, the file inversify.config.ts is going to be used to build the Planner. The Planner instance must be instantiated with the dependencies container. If you bundle a different dependencies container with your Planner, your specific Planner will be able to act differently.

Phases

To allow maximum flexibility, some algorithms allow injecting multiple implementations of the same interface, depending on the phase of the algorithm. For example:

container.bind<IJourneyExtractor>(TYPES.JourneyExtractor).to(JourneyExtractorDefault);
container.bind<IRoadPlanner>(TYPES.RoadPlanner).to(RoadPlannerBirdsEye).whenTargetTagged("phase", JourneyExtractionPhase.Initial);
container.bind<IRoadPlanner>(TYPES.RoadPlanner).to(RoadPlannerBirdsEye).whenTargetTagged("phase", JourneyExtractionPhase.Transfer);
container.bind<IRoadPlanner>(TYPES.RoadPlanner).to(RoadPlannerBirdsEye).whenTargetTagged("phase", JourneyExtractionPhase.Final);

This example is pointless right now, because only one road planner is implemented

Catalog

Each container should have a Catalog which holds the access URLs (and other meta data) of all data sources. For example, a planner that should only plan NMBS routes could have this 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);

container.bind<Catalog>(TYPES.Catalog).toConstantValue(catalog);

Factories

Providers create all the necessary fetchers based on the data sources configured in the catalog. Factories form the glue between all these parts: they create a fetcher based on a catalog entry on behalf of a provider. Warning: subject to change

For example:

container.bind<IConnectionsProvider>(TYPES.ConnectionsProvider).to(ConnectionsProviderPassthrough).inSingletonScope();
container.bind<IConnectionsFetcher>(TYPES.ConnectionsFetcher).to(ConnectionsFetcherLazy);
container.bind<interfaces.Factory<IConnectionsFetcher>>(TYPES.ConnectionsFetcherFactory)
  .toFactory<IConnectionsFetcher>(
    (context: interfaces.Context) =>
      (travelMode: TravelMode) => {
        const fetcher = context.container.get<ConnectionsFetcherLazy>(TYPES.ConnectionsFetcher);
        fetcher.setTravelMode(travelMode);
        return fetcher;
      },
  );

Possibilities

One could make a dependency container specifically for shared bikes and public transport.

Another example would be to create a dependency container for public transport systems only. In this case, we would change the RoadPlanner to just using RoadPlannerBirdsEye, in order to understand where we can transfer.

License

This project is licensed under the MIT License - see the LICENSE.md file for details