[gnome-maps/wip/mlundblad/transit-service-discovery: 12/15] WIP: Add transit router



commit d6232389229ed173f1137380604169b35db37885
Author: Marcus Lundblad <ml update uu se>
Date:   Tue Aug 20 22:07:58 2019 +0200

    WIP: Add transit router
    
    Dispatches routing requests to matching
    transit provider.

 src/mapView.js                        |   2 +-
 src/org.gnome.Maps.src.gresource.xml  |   1 +
 src/printOperation.js                 |   2 +-
 src/routingDelegator.js               |  16 ++-
 src/sidebar.js                        |  16 +--
 src/transitRouter.js                  | 199 ++++++++++++++++++++++++++++++++++
 src/transitplugins/openTripPlanner.js |  20 +---
 7 files changed, 219 insertions(+), 37 deletions(-)
---
diff --git a/src/mapView.js b/src/mapView.js
index 3610272..e206cfd 100644
--- a/src/mapView.js
+++ b/src/mapView.js
@@ -262,7 +262,7 @@ var MapView = GObject.registerClass({
 
     _connectRouteSignals() {
         let route = Application.routingDelegator.graphHopper.route;
-        let transitPlan = Application.routingDelegator.openTripPlanner.plan;
+        let transitPlan = Application.routingDelegator.transitRouter.plan;
         let query = Application.routeQuery;
 
         route.connect('update', () => {
diff --git a/src/org.gnome.Maps.src.gresource.xml b/src/org.gnome.Maps.src.gresource.xml
index 0ca0fc2..94e5099 100644
--- a/src/org.gnome.Maps.src.gresource.xml
+++ b/src/org.gnome.Maps.src.gresource.xml
@@ -90,6 +90,7 @@
     <file>transitOptionsPanel.js</file>
     <file>transitPlan.js</file>
     <file>transitPrintLayout.js</file>
+    <file>transitRouter.js</file>
     <file>transitRouteLabel.js</file>
     <file>transitStopRow.js</file>
     <file>transitWalkMarker.js</file>
diff --git a/src/printOperation.js b/src/printOperation.js
index 89462a5..b3307c0 100644
--- a/src/printOperation.js
+++ b/src/printOperation.js
@@ -57,7 +57,7 @@ var PrintOperation = class PrintOperation {
     _beginPrint(operation, context, data) {
         let route = Application.routingDelegator.graphHopper.route;
         let selectedTransitItinerary =
-            Application.routingDelegator.openTripPlanner.plan.selectedItinerary;
+            Application.routingDelegator.transitRouter.plan.selectedItinerary;
         let width = context.get_width();
         let height = context.get_height();
 
diff --git a/src/routingDelegator.js b/src/routingDelegator.js
index 57e956f..37728c7 100644
--- a/src/routingDelegator.js
+++ b/src/routingDelegator.js
@@ -20,7 +20,7 @@
  */
 
 const GraphHopper = imports.graphHopper;
-const OpenTripPlanner = imports.transitplugins.openTripPlanner;
+const TransitRouter = imports.transitRouter;
 const RouteQuery = imports.routeQuery;
 
 const _FALLBACK_TRANSPORTATION = RouteQuery.Transportation.PEDESTRIAN;
@@ -32,16 +32,14 @@ var RoutingDelegator = class RoutingDelegator {
 
         this._transitRouting = false;
         this._graphHopper = new GraphHopper.GraphHopper({ query: this._query });
-        this._openTripPlanner =
-            new OpenTripPlanner.OpenTripPlanner({ query: this._query,
-                                                  graphHopper: this._graphHopper });
+        this._transitRouter = new TransitRouter.TransitRouter({ query: this._query });
         this._query.connect('notify::points', this._onQueryChanged.bind(this));
 
         /* if the query is set to transit mode when it's not available, revert
          * to a fallback mode
          */
         if (this._query.transportation === RouteQuery.Transportation.TRANSIT &&
-            !this._openTripPlanner.enabled) {
+            !this._transitRouter.enabled) {
             this._query.transportation = _FALLBACK_TRANSPORTATION;
         }
     }
@@ -50,8 +48,8 @@ var RoutingDelegator = class RoutingDelegator {
         return this._graphHopper;
     }
 
-    get openTripPlanner() {
-        return this._openTripPlanner;
+    get transitRouter() {
+        return this._transitRouter;
     }
 
     set useTransit(useTransit) {
@@ -60,7 +58,7 @@ var RoutingDelegator = class RoutingDelegator {
 
     reset() {
         if (this._transitRouting)
-            this._openTripPlanner.plan.reset();
+            this._transitRouter.plan.reset();
         else
             this._graphHopper.route.reset();
     }
@@ -68,7 +66,7 @@ var RoutingDelegator = class RoutingDelegator {
     _onQueryChanged() {
         if (this._query.isValid()) {
             if (this._transitRouting) {
-                this._openTripPlanner.fetchFirstResults();
+                this._transitRouter.fetchFirstResults();
             } else {
                 this._graphHopper.fetchRoute(this._query.filledPoints,
                                              this._query.transportation);
diff --git a/src/sidebar.js b/src/sidebar.js
index 1b546a2..1ab1acf 100644
--- a/src/sidebar.js
+++ b/src/sidebar.js
@@ -95,13 +95,13 @@ var Sidebar = GObject.registerClass({
         this._query.addPoint(1);
         this._switchRoutingMode(Application.routeQuery.transportation);
         /* Enable/disable transit mode switch based on the presence of
-         * OpenTripPlanner.
+         * public transit providers.
          * For some reason, setting visible to false in the UI file and
          * dynamically setting visible false here doesn't work, maybe because
          * it's part of a radio group? As a workaround, just remove the button
          * instead.
          */
-        if (!Application.routingDelegator.openTripPlanner.enabled)
+        if (!Application.routingDelegator.transitRouter.enabled)
             this._modeTransitToggle.destroy();
     }
 
@@ -154,7 +154,7 @@ var Sidebar = GObject.registerClass({
             Application.routingDelegator.useTransit = false;
             this._linkButtonStack.visible_child_name = 'graphHopper';
             this._transitRevealer.reveal_child = false;
-            Application.routingDelegator.openTripPlanner.plan.deselectItinerary();
+            Application.routingDelegator.transitRouter.plan.deselectItinerary();
         }
         this._clearInstructions();
     }
@@ -214,7 +214,7 @@ var Sidebar = GObject.registerClass({
 
     _initInstructionList() {
         let route = Application.routingDelegator.graphHopper.route;
-        let transitPlan = Application.routingDelegator.openTripPlanner.plan;
+        let transitPlan = Application.routingDelegator.transitRouter.plan;
 
         route.connect('reset', () => {
             this._clearInstructions();
@@ -341,7 +341,7 @@ var Sidebar = GObject.registerClass({
     }
 
     _showTransitOverview() {
-        let plan = Application.routingDelegator.openTripPlanner.plan;
+        let plan = Application.routingDelegator.transitRouter.plan;
 
         this._transitListStack.visible_child_name = 'overview';
         this._transitHeader.visible_child_name = 'options';
@@ -354,7 +354,7 @@ var Sidebar = GObject.registerClass({
     }
 
     _populateTransitItineraryOverview() {
-        let plan = Application.routingDelegator.openTripPlanner.plan;
+        let plan = Application.routingDelegator.transitRouter.plan;
 
         plan.itineraries.forEach((itinerary) => {
             let row =
@@ -371,7 +371,7 @@ var Sidebar = GObject.registerClass({
     }
 
     _onItineraryActivated(itinerary) {
-        let plan = Application.routingDelegator.openTripPlanner.plan;
+        let plan = Application.routingDelegator.transitRouter.plan;
 
         this._populateTransitItinerary(itinerary);
         this._showTransitItineraryView();
@@ -380,7 +380,7 @@ var Sidebar = GObject.registerClass({
 
     _onMoreActivated(row) {
         row.startLoading();
-        Application.routingDelegator.openTripPlanner.fetchMoreResults();
+        Application.routingDelegator.transitRouter.fetchMoreResults();
     }
 
     _onItineraryOverviewRowActivated(listBox, row) {
diff --git a/src/transitRouter.js b/src/transitRouter.js
new file mode 100644
index 0000000..d38f462
--- /dev/null
+++ b/src/transitRouter.js
@@ -0,0 +1,199 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2019 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+const Champlain = imports.gi.Champlain;
+
+const Service = imports.service;
+const TransitPlan = imports.transitPlan;
+const Utils = imports.utils;
+
+var TransitRouter = class TransitRoute {
+    constructor(params) {
+        this._plan = new TransitPlan.Plan();
+        this._query = params.query;
+        this._providers = Service.getService().transitProviders;
+        this._providerCache = [];
+        this._language = Utils.getLanguage();
+    }
+
+    get enabled() {
+        return this._providers !== undefined;
+    }
+
+    get plan() {
+        return this._plan;
+    }
+
+    fetchFirstResults() {
+        let bestProvider = this._getBestProviderForQuery();
+
+        if (bestProvider) {
+            let provider = bestProvider[0];
+
+            this._currPluginInstance = bestProvider[1];
+            this._plan.attribution = this._getAttributionForProvider(provider);
+            if (provider.attributionUrl)
+                this._plan.attributionUrl = provider.attributionUrl;
+            this._currPluginInstance.fetchFirstResults();
+        } else {
+            this._plan.reset();
+            this._query.reset();
+            this._plan.noProvider();
+        }
+    }
+
+    fetchMoreResults() {
+        if (this._currPluginInstance)
+            this._currPluginInstance.fetchMoreResults();
+        else
+            throw new Error('No previous provider');
+    }
+
+    _getAttributionForProvider(provider) {
+        Utils.debug('getAttribution: ' + JSON.stringify(provider, '', 2));
+        if (provider['attribution:' + this._language])
+            return provider['attribution:' + this._language];
+        else if (provider.attribution)
+            return provider.attribution;
+        else
+            return null;
+    }
+
+    _getBestProviderForQuery() {
+        let startLocation = this._query.filledPoints[0].place.location;
+        let endLocation =
+            this._query.filledPoints[this._query.points.length - 1].place.location;
+        let startCountry =
+            Utils.getCountryCodeForCoordinates(startLocation.latitude,
+                                               startLocation.longitude);
+        let endCountry =
+            Utils.getCountryCodeForCoordinates(endLocation.latitude,
+                                               endLocation.longitude);
+
+        Utils.debug('country of start and end: ' + startCountry + ', ' + endCountry);
+
+        let matchingProviders = [];
+
+        this._providers.forEach((p) => {
+            let provider = p.provider;
+            let areas = provider.areas;
+
+            Utils.debug('checking provider ' + provider.name);
+
+            if (!areas) {
+                Utils.debug('No coverage info for provider ' + provider.name);
+                return;
+            }
+
+            areas.forEach((area) => {
+                /* if the area has a specified priority, override the
+                 * overall area priority, this allows sub-areas of of
+                 * coverage for a provider to have higher or lowe priorities
+                 * than other providers (e.g. one "native" to that area
+                 */
+                if (area.priority)
+                    provider.priority = area.priority;
+
+                let countries = area.countries;
+
+                if (countries) {
+                    Utils.debug('Number of countries covered: ' + countries.length);
+
+                    if (countries.includes(startCountry) &&
+                        countries.includes(endCountry)) {
+                        Utils.debug('Provider matches on country');
+                        matchingProviders.push(provider);
+                        return;
+                    }
+                }
+
+                let bbox = area.bbox;
+
+                if (bbox) {
+                    if (bbox.length !== 4) {
+                        Utils.debug('malformed bounding box for provider ' + provider.name);
+                        return;
+                    }
+
+                    let [x1, y1, x2, y2] = bbox;
+                    let cbbox = new Champlain.BoundingBox({ bottom: x1,
+                                                            left: y1,
+                                                            top: x2,
+                                                            right: y2 });
+
+                    if (cbbox.covers(startLocation.latitude,
+                                     startLocation.longitude) &&
+                        cbbox.covers(endLocation.latitude,
+                                     endLocation.longitude)) {
+                        Utils.debug('Provider matches on bbox: ' + bbox);
+                        matchingProviders.push(provider);
+                        return;
+                    }
+                }
+            });
+        });
+
+        Utils.debug('Number of matching providers: ' + matchingProviders.length);
+
+        if (matchingProviders.length === 0)
+            return null;
+
+        matchingProviders.sort(this._sortProviders);
+
+        for (let i = 0; i < matchingProviders.length; i++) {
+            let provider = matchingProviders[i];
+            let plugin = provider.plugin;
+
+            if (this._providerCache[plugin])
+                return [provider, this._providerCache[plugin]];
+
+            let module =
+                plugin[0].toLowerCase() + plugin.substring(1, plugin.length);
+
+            try {
+                let params = provider.params;
+                let instance =
+                    params ? new imports.transitplugins[module][plugin](params) :
+                             new imports.transitplugins[module][plugin]();
+
+                this._providerCache[plugin] = instance;
+
+                return [provider, instance];
+            } catch (e) {
+                Utils.debug('Failed to load plugin: ' + plugin + ": " + e);
+            }
+        }
+
+        Utils.debug('No suitable provider found');
+        return null;
+    }
+
+    _sortProviders(p1, p2) {
+        if (p1.priority && p2.priority)
+            return p1.priority - p2.priority;
+        else if (p1.priority)
+            return -1;
+        else if (p2.priority)
+            return 1;
+        else
+            return 0;
+    }
+};
diff --git a/src/transitplugins/openTripPlanner.js b/src/transitplugins/openTripPlanner.js
index d42e71b..b10b644 100644
--- a/src/transitplugins/openTripPlanner.js
+++ b/src/transitplugins/openTripPlanner.js
@@ -130,7 +130,8 @@ var OpenTripPlanner = class OpenTripPlanner {
         this._routersUpdatedTimestamp = 0;
         this._plan = Application.routingDelegator.transitRouter.plan;
         this._query = Application.routeQuery;
-        this._baseUrl = this._getBaseUrl();
+        this._baseUrl = params.baseUrl;
+        Utils.debug('baseUrl: ' + this._baseUrl);
         this._walkingRoutes = [];
         this._extendPrevious = false;
     }
@@ -153,23 +154,6 @@ var OpenTripPlanner = class OpenTripPlanner {
         this._fetchRoute();
     }
 
-    _getBaseUrl() {
-        let debugUrl = GLib.getenv('OTP_BASE_URL');
-
-        if (debugUrl) {
-            return debugUrl;
-        } else {
-            let otp = Service.getService().openTripPlanner
-
-            if (otp && otp.baseUrl) {
-                return otp.baseUrl;
-            } else {
-                Utils.debug('No OpenTripPlanner URL defined in service file');
-                return null;
-            }
-        }
-    }
-
     _getRouterUrl(router) {
         if (!router || router.length === 0)
             router = 'default';


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]