Add route planner support for maps
authorMatthias Schmidt <gravatronics@live.com>
Sun, 2 Jul 2017 14:08:06 +0000 (16:08 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Sun, 2 Jul 2017 14:08:06 +0000 (16:08 +0200)
Close #2321

com.woltlab.wcf/templates/googleMapsJavaScript.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Map/Route/Planner.js [new file with mode: 0644]
wcfsetup/install/files/style/ui/googleMap.scss
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index f9ed67ed0048a5cb3cb5021e6b3962474aea501d..a475c25d7396c569e564dc906d25f51ce0d5f711 100644 (file)
@@ -5,6 +5,17 @@
        $(function() {
                WCF.Language.addObject({
                        'wcf.map.noLocationSuggestions': '{lang}wcf.map.noLocationSuggestions{/lang}',
+                       'wcf.map.route.error.not_found': '{lang}wcf.map.route.error.not_found{/lang}',
+                       'wcf.map.route.error.over_query_limit': '{lang}wcf.map.route.error.over_query_limit{/lang}',
+                       'wcf.map.route.error.request_denied': '{lang}wcf.map.route.error.request_denied{/lang}',
+                       'wcf.map.route.origin': '{lang}wcf.map.route.origin{/lang}',
+                       'wcf.map.route.planner': '{lang}wcf.map.route.planner{/lang}',
+                       'wcf.map.route.travelMode': '{lang}wcf.map.route.travelMode{/lang}',
+                       'wcf.map.route.travelMode.bicycling': '{lang}wcf.map.route.travelMode.bicycling{/lang}',
+                       'wcf.map.route.travelMode.driving': '{lang}wcf.map.route.travelMode.driving{/lang}',
+                       'wcf.map.route.travelMode.transit': '{lang}wcf.map.route.travelMode.transit{/lang}',
+                       'wcf.map.route.travelMode.walking': '{lang}wcf.map.route.travelMode.walking{/lang}',
+                       'wcf.map.route.viewOnGoogleMaps': '{lang}wcf.map.route.viewOnGoogleMaps{/lang}',
                        'wcf.map.showLocationSuggestions': '{lang}wcf.map.showLocationSuggestions{/lang}',
                        'wcf.map.useLocationSuggestion': '{lang}wcf.map.useLocationSuggestion{/lang}'
                });
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Map/Route/Planner.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Map/Route/Planner.js
new file mode 100644 (file)
index 0000000..4b5025a
--- /dev/null
@@ -0,0 +1,217 @@
+/**
+ * Map route planner based on Google Maps.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Controller/Map/Route/Planner
+ */
+define([
+       'Dom/Traverse',
+       'Dom/Util',
+       'Language',
+       'Ui/Dialog',
+       'WoltLabSuite/Core/Ajax/Status'
+], function(
+       DomTraverse,
+       DomUtil,
+       Language,
+       UiDialog,
+       AjaxStatus
+) {
+       /**
+        * @constructor
+        */
+       function Planner(buttonId, destination) {
+               this._button = elById(buttonId);
+               if (this._button === null) {
+                       throw new Error("Unknown button with id '" + buttonId + "'");
+               }
+               
+               this._button.addEventListener('click', this._openDialog.bind(this));
+               
+               this._destination = destination;
+       }
+       Planner.prototype = {
+               /**
+                * Sets up the route planner dialog.
+                */
+               _dialogSetup: function() {
+                       return {
+                               id: this._button.id + 'Dialog',
+                               options: {
+                                       onShow: this._initDialog.bind(this),
+                                       title: Language.get('wcf.map.route.planner')
+                               },
+                               source: '<div class="googleMapsDirectionsContainer" style="display: none;">' +
+                                               '<div class="googleMap"></div>' +
+                                               '<div class="googleMapsDirections"></div>' +
+                                       '</div>' +
+                                       '<small class="googleMapsDirectionsGoogleLinkContainer"><a href="' + this._getGoogleMapsLink() + '" class="googleMapsDirectionsGoogleLink" target="_blank" style="display: none;">' + Language.get('wcf.map.route.viewOnGoogleMaps') + '</a></small>' +
+                                       '<dl>' +
+                                               '<dt>' + Language.get('wcf.map.route.origin') + '</dt>' +
+                                               '<dd><input type="text" name="origin" class="long" autofocus /></dd>' +
+                                       '</dl>' +
+                                       '<dl style="display: none;">' +
+                                               '<dt>' + Language.get('wcf.map.route.travelMode') + '</dt>' +
+                                               '<dd>' +
+                                                       '<select name="travelMode">' +
+                                                               '<option value="driving">' + Language.get('wcf.map.route.travelMode.driving') + '</option>' + 
+                                                               '<option value="walking">' + Language.get('wcf.map.route.travelMode.walking') + '</option>' + 
+                                                               '<option value="bicycling">' + Language.get('wcf.map.route.travelMode.bicycling') + '</option>' +
+                                                               '<option value="transit">' + Language.get('wcf.map.route.travelMode.transit') + '</option>' +
+                                                       '</select>' +
+                                               '</dd>' +
+                                       '</dl>'
+                       }
+               },
+               
+               /**
+                * Calculates the route based on the given result of a location search.
+                * 
+                * @param       {object}        data
+                */
+               _calculateRoute: function(data) {
+                       var dialog = UiDialog.getDialog(this).dialog;
+                       
+                       if (data.label) {
+                               this._originInput.value = data.label;
+                       }
+                       
+                       if (this._map === undefined) {
+                               this._map = new google.maps.Map(elByClass('googleMap', dialog)[0], {
+                                       disableDoubleClickZoom: WCF.Location.GoogleMaps.Settings.get('disableDoubleClickZoom'),
+                                       draggable: WCF.Location.GoogleMaps.Settings.get('draggable'),
+                                       mapTypeId: google.maps.MapTypeId.ROADMAP,
+                                       scaleControl: WCF.Location.GoogleMaps.Settings.get('scaleControl'),
+                                       scrollwheel: WCF.Location.GoogleMaps.Settings.get('scrollwheel')
+                               });
+                               
+                               this._directionsService = new google.maps.DirectionsService();
+                               this._directionsRenderer = new google.maps.DirectionsRenderer();
+                               
+                               this._directionsRenderer.setMap(this._map);
+                               this._directionsRenderer.setPanel(elByClass('googleMapsDirections', dialog)[0]);
+                               
+                               this._googleLink = elByClass('googleMapsDirectionsGoogleLink', dialog)[0];
+                       }
+                       
+                       var request = {
+                               destination: this._destination,
+                               origin: data.location,
+                               provideRouteAlternatives: true,
+                               travelMode: google.maps.TravelMode[this._travelMode.value.toUpperCase()]
+                       };
+                       
+                       AjaxStatus.show();
+                       this._directionsService.route(request, this._setRoute.bind(this));
+                       
+                       elAttr(this._googleLink, 'href', this._getGoogleMapsLink(data.location, this._travelMode.value));
+                       
+                       this._lastOrigin = data.location;
+               },
+               
+               /**
+                * Returns the Google Maps link based on the given optional directions origin
+                * and optional travel mode.
+                * 
+                * @param       {google.maps.LatLng}    origin
+                * @param       {string}                travelMode
+                * @return      {string}
+                */
+               _getGoogleMapsLink: function(origin, travelMode) {
+                       if (origin) {
+                               var link = 'https://www.google.com/maps/dir/?api=1' +
+                                               '&origin=' + origin.lat() + ',' + origin.lng() + '' +
+                                               '&destination=' + this._destination.lat() + ',' + this._destination.lng();
+                               
+                               if (travelMode) {
+                                       link += '&travelmode=' + travelMode;
+                               }
+                               
+                               return link;
+                       }
+                       
+                       return 'https://www.google.com/maps/search/?api=1&query=' + this._destination.lat() + ',' + this._destination.lng();
+               },
+               
+               /**
+                * Initializes the route planning dialog.
+                */
+               _initDialog: function() {
+                       if (!this._didInitDialog) {
+                               var dialog = UiDialog.getDialog(this).dialog;
+                               
+                               // make input element a location search
+                               this._originInput = elBySel('input[name="origin"]', dialog);
+                               new WCF.Location.GoogleMaps.LocationSearch(this._originInput, this._calculateRoute.bind(this));
+                               
+                               this._travelMode = elBySel('select[name="travelMode"]', dialog);
+                               this._travelMode.addEventListener('change', this._updateRoute.bind(this));
+                               
+                               this._didInitDialog = true;
+                       }
+               },
+               
+               /**
+                * Opens the route planning dialog.
+                */
+               _openDialog: function() {
+                       UiDialog.open(this);
+               },
+               
+               /**
+                * Handles the response of the direction service.
+                * 
+                * @param       {object}        result
+                * @param       {string}        status
+                */
+               _setRoute: function(result, status) {
+                       AjaxStatus.hide();
+                       
+                       if (status == 'OK') {
+                               elShow(this._map.getDiv().parentNode);
+                               
+                               google.maps.event.trigger(this._map, 'resize');
+                               
+                               this._directionsRenderer.setDirections(result);
+                               
+                               elShow(DomTraverse.parentByTag(this._travelMode, 'DL'));
+                               elShow(this._googleLink);
+                               
+                               if (this._errorMessage) {
+                                       elHide(this._errorMessage);
+                               }
+                       }
+                       else {
+                               // map irrelevant errors to not found error
+                               if (status !== 'OVER_QUERY_LIMIT' && status !== 'REQUEST_DENIED') {
+                                       status = 'NOT_FOUND';
+                               }
+                               
+                               if (this._errorMessage === undefined) {
+                                       this._errorMessage = elCreate('small');
+                                       this._errorMessage.classList = 'innerError';
+                                       
+                                       DomUtil.insertAfter(this._errorMessage, this._originInput);
+                               }
+                               else {
+                                       elShow(this._errorMessage);
+                               }
+                               
+                               this._errorMessage.textContent = Language.get('wcf.map.route.error.' + status.toLowerCase());
+                       }
+               },
+               
+               /**
+                * Updates the route after the travel mode has been changed.
+                */
+               _updateRoute: function() {
+                       this._calculateRoute({
+                               location: this._lastOrigin
+                       });
+               }
+       };
+       
+       return Planner;
+});
index 09deb51776c2ae54d5867595b23fe92fee556335..9f7db6de30ef32097e4d5a807ade55d0b795eb80 100644 (file)
 .googleMapsUseLocationSuggestionLink {
        font-size: $wcfFontSizeSmall;
 }
+
+.googleMapsDirectionsContainer {
+       display: flex;
+       width: 100%;
+       
+       .googleMap {
+               flex: 0 0 50%;
+       }
+       
+       .googleMapsDirections {
+               flex: 0 0 50%;
+               height: 400px;
+               padding-left: 10px;
+               overflow-y: scroll;
+       }
+}
+
+.googleMapsDirectionsGoogleLinkContainer {
+       display: block;
+       margin-top: 5px;
+       text-align: right;
+}
index 479de0d8fd6686bfa024d0955f6014c701fb5b74..e3aec1dfd10ce686e4f7dce8775ad90991510500 100644 (file)
@@ -2881,6 +2881,18 @@ Fehler sind beispielsweise:
        
        <category name="wcf.map">
                <item name="wcf.map.noLocationSuggestions"><![CDATA[Für den aktuellen Kartenausschnitt liegen keine Ortsvorschläge vor.]]></item>
+               <item name="wcf.map.route.button.calculateRoute"><![CDATA[Route berechnen]]></item>
+               <item name="wcf.map.route.error.not_found"><![CDATA[Die Route konnte nicht berechnet werden.]]></item>
+               <item name="wcf.map.route.error.over_query_limit"><![CDATA[Die Route konnte nicht berechnet werden. Innerhalb des zulässigen Zeitraums wurden zu viele Anfragen an die Google Maps API gesendet.]]></item>
+               <item name="wcf.map.route.error.request_denied"><![CDATA[Die Route konnte nicht berechnet werden. Diese Webseite ist nicht berechtigt ist, den Google Maps Directions-Dienst zu nutzen]]></item>
+               <item name="wcf.map.route.origin"><![CDATA[Startpunkt]]></item>
+               <item name="wcf.map.route.planner"><![CDATA[Routenplaner]]></item>
+               <item name="wcf.map.route.travelMode"><![CDATA[Verkehrsmittel]]></item>
+               <item name="wcf.map.route.travelMode.bicycling"><![CDATA[Fahrrad]]></item>
+               <item name="wcf.map.route.travelMode.driving"><![CDATA[Auto]]></item>
+               <item name="wcf.map.route.travelMode.transit"><![CDATA[Öffentliche Verkehrsmittel]]></item>
+               <item name="wcf.map.route.travelMode.walking"><![CDATA[Zu Fuß]]></item>
+               <item name="wcf.map.route.viewOnGoogleMaps"><![CDATA[Direkt bei Google Maps anschauen]]></item>
                <item name="wcf.map.showLocationSuggestions"><![CDATA[Orte vorschlagen]]></item>
                <item name="wcf.map.useLocationSuggestion"><![CDATA[Ort verwenden]]></item>
        </category>
index 7f26692d27bde392192739f47ad061e033ebddbb..3b48bda7828181686119e4d741ad934f618394ad 100644 (file)
@@ -2823,6 +2823,18 @@ Errors are:
        
        <category name="wcf.map">
                <item name="wcf.map.noLocationSuggestions"><![CDATA[There are no location suggestions in the current map section.]]></item>
+               <item name="wcf.map.route.button.calculateRoute"><![CDATA[Calculate Route]]></item>
+               <item name="wcf.map.route.error.not_found"><![CDATA[The route could not be calculated.]]></item>
+               <item name="wcf.map.route.error.over_query_limit"><![CDATA[The route could not be calculated. Too many requests have been sent to Google Maps’ API within the allowed time period.]]></item>
+               <item name="wcf.map.route.error.request_denied"><![CDATA[The route could not be calculated. This website is not allowed to use Google Maps’ directions service.]]></item>
+               <item name="wcf.map.route.origin"><![CDATA[Starting Point]]></item>
+               <item name="wcf.map.route.planner"><![CDATA[Route Planner]]></item>
+               <item name="wcf.map.route.travelMode"><![CDATA[Travel Mode]]></item>
+               <item name="wcf.map.route.travelMode.bicycling"><![CDATA[Bicycling]]></item>
+               <item name="wcf.map.route.travelMode.driving"><![CDATA[Driving]]></item>
+               <item name="wcf.map.route.travelMode.transit"><![CDATA[Transit]]></item>
+               <item name="wcf.map.route.travelMode.walking"><![CDATA[Walking]]></item>
+               <item name="wcf.map.route.viewOnGoogleMaps"><![CDATA[View on Google Maps directly]]></item>
                <item name="wcf.map.showLocationSuggestions"><![CDATA[Suggest Locations]]></item>
                <item name="wcf.map.useLocationSuggestion"><![CDATA[Use Location]]></item>
        </category>