All Downloads are FREE. Search and download functionalities are using the official Maven repository.

client.js.otp.modules.planner.PlannerModule.js Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
/* This program is free software: you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.

   This program 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 this program.  If not, see .
*/

otp.namespace("otp.modules.planner");

otp.modules.planner.defaultQueryParams = {
    startPlace                      : null,
    endPlace                        : null,
    time                            : moment().format(otp.config.locale.time.time_format),
    date                            : moment().format(otp.config.locale.time.date_format),
    arriveBy                        : false,
    wheelchair                      : false,
    mode                            : "TRANSIT,WALK",
    maxWalkDistance                 : 804.672, // 1/2 mi.
    metricDefaultMaxWalkDistance    : 750, // meters
    imperialDefaultMaxWalkDistance  : 804.672, // 0.5 mile
    preferredRoutes                 : null,
    otherThanPreferredRoutesPenalty : 300,
    bannedTrips                     : null,
    optimize                        : null,
    triangleTimeFactor              : 0.333,
    triangleSlopeFactor             : 0.333,
    triangleSafetyFactor            : 0.334,
}

otp.modules.planner.PlannerModule =
    otp.Class(otp.modules.Module, {

    moduleName  : "Trip Planner",

    markerLayer     : null,
    pathLayer       : null,
    pathMarkerLayer : null,
    highlightLayer  : null,

    startMarker     : null,
    endMarker       : null,

    tipWidget       : null,
    noTripWidget    : null,
    tipStep         : 0,

    currentRequest  : null,
    currentHash : null,

    itinMarkers : [],

    planTripFunction : null,

    // current trip query parameters:
    /*
    startName               : null,
    endName                 : null,
    startLatLng             : null,
    endLatLng               : null,
    time                    : null,
    date                    : null,
    arriveBy                : false,
    mode                    : "TRANSIT,WALK",
    maxWalkDistance         : null,
    preferredRoutes         : null,
    bannedTrips             : null,
    optimize                : null,
    triangleTimeFactor      : 0.333,
    triangleSlopeFactor     : 0.333,
    triangleSafetyFactor    : 0.334,
    */

    startName       : null,
    endName         : null,
    startLatLng     : null,
    endLatLng       : null,

    // the defaults params, as modified in the module-specific config
    defaultQueryParams  : null,

    startTimePadding    : 0,

    // copy of query param set from last /plan request
    lastQueryParams : null,

    icons       : null,

    // this messages are used in noTripFound localization. Values are copied
    // from Java source and Message properties.
    error_messages : {
        500 : _tr("We're sorry. The trip planner is temporarily unavailable. Please try again later."),
        503 : _tr("We're sorry. The trip planner is temporarily unavailable. Please try again later."),
        400 : _tr("Trip is not possible.  You might be trying to plan a trip outside the map data boundary."),
        404 : _tr("No trip found. There may be no transit service within the maximum specified distance or at the specified time, or your start or end point might not be safely accessible."),
        406 : _tr("No transit times available. The date may be past or too far in the future or there may not be transit service for your trip at the time you chose."),
        408 : _tr("The trip planner is taking way too long to process your request. Please try again later."),
        413 : _tr("The request has errors that the server is not willing or able to process."),
        440 : _tr("Origin is unknown. Can you be a bit more descriptive?"),
        450 : _tr("Destination is unknown.  Can you be a bit more descriptive?"),
        460 : _tr("Both origin and destination are unknown. Can you be a bit more descriptive?"),
        470 : _tr("Both origin and destination are not wheelchair accessible"),
        409 : _tr("Origin is within a trivial distance of the destination."),

        340 : _tr("The trip planner is unsure of the location you want to start from. Please select from the following options, or be more specific."),
        350 : _tr("The trip planner is unsure of the destination you want to go to. Please select from the following options, or be more specific."),
        360 : _tr("Both origin and destination are ambiguous. Please select from the following options, or be more specific."),

        370 : _tr("All of triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must be set if any are"),
        371 : _tr("The values of triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must sum to 1"),
        372 : _tr("If triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor are provided, OptimizeType must be TRIANGLE"),
        373 : _tr("If OptimizeType is TRIANGLE, triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must be set"),
    },




    //templateFile : 'otp/modules/planner/planner-templates.html',

    initialize : function(webapp, id, options) {
        otp.modules.Module.prototype.initialize.apply(this, arguments);
        this.templateFiles.push('otp/modules/planner/planner-templates.html');

        this.icons = new otp.modules.planner.IconFactory();

        this.planTripFunction = this.planTrip;

        this.defaultQueryParams = _.clone(otp.modules.planner.defaultQueryParams);

        if (otp.config.metric) {
            this.defaultQueryParams.maxWalkDistance = this.defaultQueryParams.metricDefaultMaxWalkDistance;
        } else {
            this.defaultQueryParams.maxWalkDistance = this.defaultQueryParams.imperialDefaultMaxWalkDistance;
        }

        _.extend(this.defaultQueryParams, this.getExtendedQueryParams());

        if(_.has(this.options, 'defaultQueryParams')) {
            _.extend(this.defaultQueryParams, this.options.defaultQueryParams);
        }

        _.extend(this, _.clone(otp.modules.planner.defaultQueryParams));
    },

    activate : function() {
        if(this.activated) return;
        var this_ = this;

        // set up layers
        this.markerLayer = new L.LayerGroup();
        this.pathLayer = new L.LayerGroup();
        this.pathMarkerLayer = new L.LayerGroup();
        this.highlightLayer = new L.LayerGroup();

        this.addLayer("Highlights", this.highlightLayer);
        this.addLayer("Start/End Markers", this.markerLayer);
        this.addLayer("Paths", this.pathLayer);
        this.addLayer("Path Markers", this.pathMarkerLayer);

        //this.webapp.indexApi.loadAgencies(this);
        this.webapp.indexApi.loadRoutes(this, function() {
            this.routesLoaded();
        });

        this.activated = true;

        // set up primary widgets (TODO: move to bike planner module)
        /*this.tipWidget = this.createWidget("otp-tipWidget", "", this);
        this.addWidget(this.tipWidget);
        this.updateTipStep(1);

        this.bikestationsWidget = new otp.widgets.BikeStationsWidget('otp-bikestationsWidget', this);
        this.addWidget(this.bikestationsWidget);

        this.noTripWidget = new otp.widgets.Widget('otp-noTripWidget', this);
        this.addWidget(this.noTripWidget);*/
    },

    restore : function() {
        // check URL params for restored trip
        if("fromPlace" in this.webapp.urlParams && "toPlace" in this.webapp.urlParams) {
            if("itinIndex" in this.webapp.urlParams) this.restoredItinIndex = this.webapp.urlParams["itinIndex"];
            this.restoreTrip(_.omit(this.webapp.urlParams, ["module", "itinIndex"]));
        }
    },

    addMapContextMenuItems : function() {
        var this_ = this;
        //TRANSLATORS: Context menu
        this.webapp.map.addContextMenuItem(_tr("Set as Start Location"), function(latlng) {
            this_.setStartPoint(latlng, true);
        });
        //TRANSLATORS: Context menu
        this.webapp.map.addContextMenuItem(_tr("Set as End Location"), function(latlng) {
            this_.setEndPoint(latlng, true);
        });
    },

    handleClick : function(event) {
        if(this.startLatLng == null) {
        	this.setStartPoint(new L.LatLng(event.latlng.lat, event.latlng.lng), true);
        }

        else if(this.endLatLng == null) {
        	this.setEndPoint(new L.LatLng(event.latlng.lat, event.latlng.lng), true);
        }
    },

    setStartPoint : function(latlng, update, name) {
        this.startName = (typeof name !== 'undefined') ? name : null;
        this.startLatLng = latlng;
        if(this.startMarker == null) {
            this.startMarker = new L.Marker(this.startLatLng, {icon: this.icons.startFlag, draggable: true});
            //TRANSLATORS: Shown in a popup on first point of a path in a map
            this.startMarker.bindPopup('' + pgettext('popup', 'Start') + '');
            this.startMarker.on('dragend', $.proxy(function() {
                this.webapp.hideSplash();
                this.setStartPoint(this.startMarker.getLatLng(), false);
                // start flag has beenpicked up, clear any name that was set
                this.startName=null;
                this.invokeHandlers("startChanged", [this.startLatLng]);
                if(typeof this.userPlanTripStart == 'function') this.userPlanTripStart();
                this.planTripFunction.apply(this);//planTrip();
            }, this));
            this.markerLayer.addLayer(this.startMarker);
        }
        else { // marker already exists
            this.startMarker.setLatLng(latlng);
        }

        this.invokeHandlers("startChanged", [latlng, name]);

        if(update) {
            this.updateTipStep(2);
            if(this.endLatLng) {
                if(typeof this.userPlanTripStart == 'function') this.userPlanTripStart();
                this.planTripFunction.apply(this);//this.planTrip();
            }
        }
    },

    setEndPoint : function(latlng, update, name) {
        this.endName = (typeof name !== 'undefined') ? name : null;
        this.endLatLng = latlng;
        if(this.endMarker == null) {
            this.endMarker = new L.Marker(this.endLatLng, {icon: this.icons.endFlag, draggable: true});
            //TRANSLATORS: shown in a popup on last point of a path in a map
            this.endMarker.bindPopup('' + _tr('Destination') + '');
            this.endMarker.on('dragend', $.proxy(function() {
                this.webapp.hideSplash();
                this.setEndPoint(this.endMarker.getLatLng(), false);
                // end flag has beenpicked up, clear any name that was set
                this.endName=null;
                this.invokeHandlers("endChanged", [this.endLatLng]);
                if(typeof this.userPlanTripStart == 'function') this.userPlanTripStart();
                this.planTripFunction.apply(this);//this_.planTrip();
            }, this));
            this.markerLayer.addLayer(this.endMarker);
        }
        else { // marker already exists
            this.endMarker.setLatLng(latlng);
        }

        this.invokeHandlers("endChanged", [latlng, name]);

        if(update) {
            if(this.startLatLng) {
                if(typeof this.userPlanTripStart == 'function') this.userPlanTripStart();
                this.planTripFunction.apply(this);//this.planTrip();
            }
        }
    },

    getStartOTPString : function() {
        return (this.startName !== null ? this.startName + "::" : "")
                 + this.startLatLng.lat + ',' + this.startLatLng.lng;
    },

    getEndOTPString : function() {
        return (this.endName !== null ? this.endName + "::" : "")
                + this.endLatLng.lat+','+this.endLatLng.lng;
    },

    restoreTrip : function(queryParams) {
        this.restoreMarkers(queryParams);
        this.planTripFunction.call(this, queryParams);
    },

    restoreMarkers : function(queryParams) {
        this.startLatLng = otp.util.Geo.stringToLatLng(otp.util.Itin.getLocationPlace(queryParams.fromPlace));
        this.setStartPoint(this.startLatLng, false,this.startName);

        this.endLatLng = otp.util.Geo.stringToLatLng(otp.util.Itin.getLocationPlace(queryParams.toPlace));
        this.setEndPoint(this.endLatLng, false,this.endName);
    },

    planTrip : function(existingQueryParams, apiMethod) {

        if(typeof this.planTripStart == 'function') this.planTripStart();

        //this.noTripWidget.hide();

    	if(this.currentRequest !== null)
        {
    		//console.log("Canceling current request.");
        	this.currentRequest.abort();
        	this.currentRequest = null;
        }

    	apiMethod = apiMethod || 'plan';
        var url = otp.config.hostname + '/' + otp.config.restService + '/' + apiMethod;
        this.pathLayer.clearLayers();

        var this_ = this;

        var queryParams = null;

        if(existingQueryParams) {
        	queryParams = existingQueryParams;
        }
        else
        {
            if(this.startLatLng == null || this.endLatLng == null) {
                // TODO: alert user
                return;
            }

            var addToStart = this.arriveBy ? 0 : this.startTimePadding;
       	    queryParams = {
                fromPlace: this.getStartOTPString(),
                toPlace: this.getEndOTPString(),
                time : (this.time) ? otp.util.Time.correctAmPmTimeString(this.time) : moment().format("h:mma"),
                //time : (this.time) ? moment(this.time).add("s", addToStart).format("h:mma") : moment().add("s", addToStart).format("h:mma"),
                date : (this.date) ? moment(this.date, otp.config.locale.time.date_format).format("MM-DD-YYYY") : moment().format("MM-DD-YYYY"),
                mode: this.mode,
                maxWalkDistance: this.maxWalkDistance
            };
            if(this.arriveBy !== null) _.extend(queryParams, { arriveBy : this.arriveBy } );
            if(this.wheelchair !== null) _.extend(queryParams, { wheelchair : this.wheelchair });
            if(this.preferredRoutes !== null) {
                queryParams.preferredRoutes = this.preferredRoutes;
                if(this.otherThanPreferredRoutesPenalty !== null)
                    queryParams.otherThanPreferredRoutesPenalty = this.otherThanPreferredRoutesPenalty;
            }
            if(this.bannedRoutes !== null) _.extend(queryParams, { bannedRoutes : this.bannedRoutes } );
            if(this.bannedTrips !== null) _.extend(queryParams, { bannedTrips : this.bannedTrips } );
            if(this.optimize !== null) _.extend(queryParams, { optimize : this.optimize } );
            if(this.optimize === 'TRIANGLE') {
                _.extend(queryParams, {
                    triangleTimeFactor: this_.triangleTimeFactor,
                    triangleSlopeFactor: this_.triangleSlopeFactor,
                    triangleSafetyFactor: this_.triangleSafetyFactor
                });
            }
            if(this.maxHours) queryParams.maxHours = this.maxHours;
            if(this.numItineraries) queryParams.numItineraries = this.numItineraries;
            if(this.minTransferTime) queryParams.minTransferTime = this.minTransferTime;
            if(this.showIntermediateStops) queryParams.showIntermediateStops = this.showIntermediateStops;

            if(otp.config.routerId !== undefined) {
                queryParams.routerId = otp.config.routerId;
            }
        }
        $('#otp-spinner').show();

        //sends wanted translation to server
        _.extend(queryParams, {locale : otp.config.locale.config.locale_short} );

        this.lastQueryParams = queryParams;

        this.planTripRequestCount = 0;

        this.planTripRequest(url, queryParams, function(tripPlan) {
            var restoring = (existingQueryParams !== undefined)
            this_.processPlan(tripPlan, restoring);

            this_.updateTipStep(3);
        });
    },

    planTripRequest : function(url, queryParams, successCallback) {
        var this_ = this;
        this.currentRequest = $.ajax(url, {
            data:       queryParams,
            dataType:   'JSON',
            //Sends arrays as &b=1&b=2 instead of b[]=1&b[]=2 which is what
            //Jersey expects
            traditional: true,

            success: function(data) {
                $('#otp-spinner').hide();

                if (otp.config.debug) {
                    otp.debug.processRequest(data)
                }

                if(data.plan) {
                    // allow for optional pre-processing step (used by Fieldtrip module)
                    if(typeof this_.preprocessPlan == 'function') {
                        this_.preprocessPlan(data.plan, queryParams, function() {
                            this_.planReceived(data.plan, url, queryParams, successCallback);
                        });
                    }
                    else {
                        this_.planReceived(data.plan, url, queryParams, successCallback);
                    }
                }
                else {
                    this_.noTripFound(data.error);
                    //this_.noTripWidget.setContent(data.error.msg);
                    //this_.noTripWidget.show();
                }
            }
        });

    },

    planReceived : function(plan, url, queryParams, successCallback) {
        // compare returned plan.date to sent date/time to determine timezone offset (unless set explicitly in config.js)
        otp.config.timeOffset = (otp.config.timeOffset) ||
            (moment(queryParams.date+" "+queryParams.time, "MM-DD-YYYY h:mma") - moment(plan.date))/3600000;

        var tripPlan = new otp.modules.planner.TripPlan(plan, queryParams);

        var invalidTrips = [];

        // check trip validity
        if(typeof this.checkTripValidity == 'function') {
            for(var i = 0; i < tripPlan.itineraries.length; i++) {
                var itin = tripPlan.itineraries[i];
                for(var l = 0; l < itin.itinData.legs.length; l++) {
                    var leg = itin.itinData.legs[l];
                    if(otp.util.Itin.isTransit(leg.mode)) {
                        if(!this.checkTripValidity(leg.tripId, leg, itin)) {
                            invalidTrips.push(leg.tripId);
                        }
                    }
                }
            }
        }

        if(invalidTrips.length == 0) { // all trips are valid; proceed with this tripPlan
            successCallback.call(this, tripPlan);
        }
        else { // run planTrip again w/ invalid trips banned
            this.planTripRequestCount++;
            if(this.planTripRequestCount > 10) {
                this.noTripFound({ 'msg' : 'Number of trip requests exceeded without valid results'});
            }
            else {
                if(queryParams.bannedTrips && queryParams.bannedTrips.length > 0) {
                    queryParams.bannedTrips += ',' + invalidTrips.join(',');
                }
                else {
                    queryParams.bannedTrips = invalidTrips.join(',');
                }
                this.planTripRequest(url, queryParams, successCallback);
            }
        }
    },

    getExtendedQueryParams : function() {
        return { };
    },

    processPlan : function(tripPlan, restoring) {
    },

    noTripFound : function(error) {
        var msg = error.msg;
        if (error.id in this.error_messages) {
            msg = this.error_messages[error.id];
        }
        //TRANSLATORS: Used in showing why trip wasn't found
        if(error.id) msg += ' (' + _tr('Error %(error_id)d', {'error_id': error.id}) + ')';
        //TRANSLATORS: Title of no trip dialog
        otp.widgets.Dialogs.showOkDialog(msg, _tr('No Trip Found'));
    },

    drawItinerary : function(itin) {
        var this_ = this;

        this.pathLayer.clearLayers();
        this.pathMarkerLayer.clearLayers();

        var queryParams = itin.tripPlan.queryParams;

        console.log(itin.itinData);
        for(var i=0; i < itin.itinData.legs.length; i++) {
            var leg = itin.itinData.legs[i];

            // draw the polyline
            var polyline = new L.Polyline(otp.util.Geo.decodePolyline(leg.legGeometry.points));
            var weight = 8;
            polyline.setStyle({ color : this.getModeColor(leg.mode), weight: weight});
            this.pathLayer.addLayer(polyline);
            polyline.leg = leg;
            polyline.bindPopup("("+leg.routeShortName+") "+leg.routeLongName);

            /* Attempt at hover functionality for trip segments on map; disabled due to "flickering" problem
               Alt. future approach: create invisible polygon buffers around polylines

            polyline.on('mouseover', function(e) {
                if(e.target.hover) return;
                console.log('mouseover');
                this_.highlightLeg(e.target.leg);
                this_.pathMarkerLayer.clearLayers();
                this_.drawStartBubble(e.target.leg, true);
                this_.drawEndBubble(e.target.leg, true);
                e.target.hover = true;
            });
            polyline.on('mouseout', function(e) {
                var lpt = e.layerPoint, minDist = 100;
                for(var p=0; p




© 2015 - 2025 Weber Informatics LLC | Privacy Policy