UNPKG

geoportal-extensions-leaflet

Version:
1,421 lines (1,228 loc) 45.9 kB
import Gp from "geoportal-access-lib"; import L from "leaflet"; import Logger from "../../Common/Utils/LoggerByDefault"; import ID from "../../Common/Utils/SelectorID"; import LocationSelector from "./LocationSelector"; import RouteDOM from "../../Common/Controls/RouteDOM"; var logger = Logger.getLogger("route(plus)"); /** * @classdesc * * Leaflet Control Class to compute and display route between start and end points using routing service of the geoportal platform. * * Use {@link module:Controls.Route L.geoportalControl.Route()} factory to create instances of that class. * * **Extends** Leaflet <a href="http://leafletjs.com/reference.html#control" target="_blank">L.Control</a> native class. * * @namespace * @alias L.geoportalControl.Route */ var Route = L.Control.extend(/** @lends L.geoportalControl.Route.prototype */ { includes : RouteDOM, /** * Options du service * * @private */ options : { position : "topleft", collapsed : true, // plier ! graphs : ["Voiture", "Pieton"], exclusions : { toll : false, tunnel : false, bridge : false }, disableReverse : false, routeOptions : {}, // FIXME a t on besoin des options de ce service ? autocompleteOptions : {} }, /** * @constructor Route * @private * @param {Object} options - options for function call. * @param {String} [options.apiKey] - API key. The "calcul" key is used by default. * @param {Boolean} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol) * @param {String} [options.position = "topleft"] - position of component into the map, 'topleft' by default * @param {Boolean} [options.collapsed = false] - collapse mode, false by default * @param {Object} [options.exclusions = {"toll" : false, "tunnel" : false, "bridge" : false}] - list of exclusions with status * @param {Array} [options.graphs = ["Voiture", "Pieton"]] - list of resources, by default : ["Voiture", "Pieton"], and the first element is selected * @param {Boolean} [options.disableReverse = false] - whether to enable/disable the reverse geocoding * @param {Object} [options.autocompleteOptions = {}] - options of autocomplete service * @param {Object} [options.routeOptions = {}] - options of route service * @example * var route = L.geoportalControl.Route({ * position : "topright", * collapsed : true, * exclusions : { * "toll" : true, * "bridge" : false, * "tunnel" : true * }, * graphs : ['Pieton', 'Voiture'], * autocompleteOptions : {}, * routeOptions : {} * }); */ initialize : function (options) { // on transmet les options au controle L.Util.setOptions(this, options); /** uuid */ this._uid = ID.generate(); // initialisation this._initTransport(); this._initExclusions(); this._initComputation(); /** container principaux */ this._showRouteContainer = null; this._pictoRouteContainer = null; this._waitingContainer = null; this._formRouteContainer = null; this._resultsRouteContainer = null; /** detection du support : desktop ou tactile */ this._isDesktop = this._detectSupport(); /** liste de points selectionnée */ this._currentPoints = []; /** Mode de transport selectionné : 'Voiture' ou 'Pieton' */ this._currentTransport = null; /** Mode de calcul selectionné : 'Plus rapide' ou 'plus court' */ this._currentComputation = null; /** Exclusions selectionnées : Tunnel, Toll et Bridge */ this._currentExclusions = []; /** la geometrie du parcours */ this._geojsonRoute = null; /** la geometrie des troncons */ this._geojsonSections = null; /** si un calcul est en cours ou non */ this._waiting = false; /** timer pour cacher la patience après un certain temps */ this._timer = null; /** * reponse du service * Ex. { * totalTime, totalDistance, bbox, routeGeometry, * routeInstructions : [{duration, distance, code, instruction, bbox, geometry}] * } */ this._currentRouteInformations = null; /** * liste des ressources avec droits par service * Ex. { * "Route" : { * key : "ger4g456re45er456t4er5ge5", * resources : ["Pieton", "Voiture"] * } * } */ this._resources = {}; }, /** * this method is called by this.addTo(map) when the control is added on the map * and fills variable 'this._container = this.onAdd(map)', * and create or disable events on map. * * @param {Object} map - the map * * @returns {DOMElement} DOM element * * @private */ onAdd : function (map) { // initialisation du DOM du composant var container = this._container = this._initLayout(map); // deactivate of events that may interfere with the map L.DomEvent .disableClickPropagation(container) .disableScrollPropagation(container); return container; }, /** * TODO this method is called when the control is removed from the map * and removes events on map. * * @private */ onRemove : function (/* map */) {}, // ################################################################### // // ####################### init application ########################## // // ################################################################### // /** * this method is called by the constructor and initialize the ... * * @private */ _initTransport : function () { // Mode de transport selectionné this._currentTransport = "Voiture"; // par defaut // par defaut var transport = this.options.graphs; if (!transport || transport.length === 0) { this.options.graphs = ["Voiture", "Pieton"]; } // option if (L.Util.isArray(transport) && transport.length) { // FIXME pb si le 1er graphe n'est pas une ressource connue ! if (transport[0] === "Voiture" || transport[0] === "Pieton") { this._currentTransport = transport[0]; } } // TODO option sur le service var serviceOptions = this.options.routeOptions; if (serviceOptions.graph) { this._currentTransport = serviceOptions.graph; } }, /** * this method is called by the constructor and initialize the ... * * @private */ _initComputation : function () { // Mode de calcul selectionné this._currentComputation = "fastest"; // par defaut // TODO option sur le service var serviceOptions = this.options.routeOptions; if (serviceOptions.routePreference) { this._currentComputation = serviceOptions.routePreference; } }, /** * this method is called by the constructor and initialize the ... * * @private */ _initExclusions : function () { // Exclusions selectionnées : Tunnel, Toll et Bridge this._currentExclusions = []; // par defaut // par defaut var exclusion = this.options.exclusions; if (!exclusion || Object.keys(exclusion).length === 0) { this.options.exclusions = { toll : false, tunnel : false, bridge : false }; } // option if (exclusion && Object.keys(exclusion).length) { for (var k in exclusion) { if (exclusion.hasOwnProperty(k)) { if (exclusion.k) { this._currentExclusions.push(k); } } } } // TODO option sur le service var serviceOptions = this.options.routeOptions; if (L.Util.isArray(serviceOptions.exclusions)) { this._currentExclusions = serviceOptions.exclusions; } }, // ################################################################### // // ############################## other init ######################### // // ################################################################### // /** * TODO this method is called by the constructor. * this information is useful to switch to touch mode. * Detection : test for desktop or tactile * * @returns {Boolean} is desktop * * @private */ _detectSupport : function () { // TODO // Choix de gérer la détection dans le code du composant au lieu du DOM car : // Utilisation de l'implémentation Leaflet // http://leafletjs.com/reference.html#browser var isDesktop = true; var userAgent = window.navigator.userAgent.toLowerCase(); if (userAgent.indexOf("iphone") !== -1 || userAgent.indexOf("ipod") !== -1 || userAgent.indexOf("ipad") !== -1 || userAgent.indexOf("android") !== -1 || userAgent.indexOf("mobile") !== -1 || userAgent.indexOf("blackberry") !== -1 || userAgent.indexOf("tablet") !== -1 || userAgent.indexOf("phone") !== -1 || userAgent.indexOf("touch") !== -1) { isDesktop = false; } if (userAgent.indexOf("msie") !== -1 || userAgent.indexOf("trident") !== -1) { isDesktop = true; } return isDesktop; }, // ################################################################### // // ########################### init dom ############################## // // ################################################################### // /** * this method is called by this.onAdd(map) * and initialize the container HTMLElement * * @param {Object} map - the map * * @returns {DOMElement} DOM element * * @private */ _initLayout : function (map) { // create main container var container = this._createMainContainerElement(); var inputShow = this._showRouteContainer = this._createShowRouteElement(); container.appendChild(inputShow); // mode "collapsed" if (!this.options.collapsed) { inputShow.checked = true; } var picto = this._pictoRouteContainer = this._createShowRoutePictoElement(); container.appendChild(picto); var routePanel = this._createRoutePanelElement(); // header form var routeHeader = this._createRoutePanelHeaderElement(); routePanel.appendChild(routeHeader); // form var routeForm = this._formRouteContainer = this._createRoutePanelFormElement(); // form: menu des points var points = this._createRoutePanelFormPointsElement(map); for (var i = 0; i < points.length; i++) { routeForm.appendChild(points[i]); } // form: menu des modes var choice = this._createRoutePanelFormModeChoiceElement(); choice.appendChild(this._createRoutePanelFormModeChoiceTransportElement(this.options.graphs)); choice.appendChild(this._createRoutePanelFormModeChoiceComputeElement()); routeForm.appendChild(choice); // form: menu des exclusions routeForm.appendChild(this._createShowRouteExclusionsElement()); routeForm.appendChild(this._createShowRouteExclusionsPictoElement()); var exclusion = this._createRoutePanelFormExclusionsElement(); exclusion.appendChild(this._createRoutePanelFormExclusionOptionsElement(this.options.exclusions)); routeForm.appendChild(exclusion); // form: bouton du calcul var submit = this._createRouteSubmitFormElement(); routeForm.appendChild(submit); routePanel.appendChild(routeForm); // results var routeResults = this._resultsRouteContainer = this._createRoutePanelResultsElement(); routePanel.appendChild(routeResults); // waiting var waiting = this._waitingContainer = this._createRouteWaitingElement(); routePanel.appendChild(waiting); container.appendChild(routePanel); return container; }, // ################################################################### // // ############################## DOM ################################ // // ################################################################### // /** * Create List Points * FIXME OVERWRITTEN RouteDOM._createRoutePanelFormPointsElement() ! * * @param {Object} map - the map * * @returns {Array} List DOM element * * @private */ _createRoutePanelFormPointsElement : function (map) { var points = []; var count = 1; // point de depart var start = new LocationSelector({ apiKey : this.options.apiKey || null, tag : { id : count, unique : this._uid, label : "Départ", color : "blue", display : true }, disableReverse : this.options.disableReverse, autocompleteOptions : this.options.autocompleteOptions || null }); start.setMap(map); var opts = this.options.routeOptions; if (opts.startPoint) { start._inputAutoCompleteContainer.value = opts.startPoint.x + " , " + opts.startPoint.y; start.setCoordinate({ lng : opts.startPoint.x, lat : opts.startPoint.y }); } points.push(start.getContainer()); this._currentPoints.push(start); // points intermediaires for (count = 2; count < 7; count++) { var step = new LocationSelector({ apiKey : this.options.apiKey || null, tag : { id : count, unique : this._uid, label : "Etape", color : "green", display : false, removeOption : true }, disableReverse : this.options.disableReverse, autocompleteOptions : this.options.autocompleteOptions || null }); step.setMap(map); points.push(step.getContainer()); this._currentPoints.push(step); } // point d'arrivé var end = new LocationSelector({ apiKey : this.options.apiKey || null, tag : { id : count, unique : this._uid, label : "Arrivée", color : "red", display : true, addOption : true, removeOption : false }, disableReverse : this.options.disableReverse, autocompleteOptions : this.options.autocompleteOptions || null }); end.setMap(map); if (opts.endPoint) { end._inputAutoCompleteContainer.value = opts.endPoint.x + " , " + opts.endPoint.y; end.setCoordinate({ lng : opts.endPoint.x, lat : opts.endPoint.y }); } points.push(end.getContainer()); this._currentPoints.push(end); return points; }, // ################################################################### // // ####################### handlers events to dom #################### // // ################################################################### // /** * this method is called by event 'click' on '' * tag label (cf. this._createShowRoutePictoElement), * and it cleans all value of input. * * @param {Object} e - HTMLElement * * @private */ onShowRoutePanelClick : function (e) { logger.log("onShowRoutePanelClick", e); // clean ! if (!this._geojsonSections) { this._clear(); } }, /** * this method is called by event 'change' on '' tag select * (cf. this.). * this value is saved as a parameter for the service route. * * @param {Object} e - HTMLElement * * @private */ onRouteModeComputationChange : function (e) { logger.log("onRouteModeComputationChange", e); var idx = e.target.selectedIndex; var value = e.target.options[idx].value; if (!value) { return; } logger.log(value); this._currentComputation = value; }, /** * this method is called by event 'change' on '' tag select * (cf. this.). * this value is saved as a parameter for the service route, * and this launches the route request ! * * @param {Object} e - HTMLElement * * @private */ onRouteModeComputationChangeAndRun : function (e) { logger.log("onRouteModeComputationChangeAndRun", e); // event choice computation this.onRouteModeComputationChange(e); // clean avant un nouveau calcul ! this._clearRouteResultsDetails(); this._clearRouteResultsGeometry(); this._clearRouteResultsFeatureGeometry(); // submit request this.onRouteComputationSubmit({ computation : this._currentComputation, transport : this._currentTransport, exclusions : this._currentExclusions }); }, /** * this method is called by event 'change' on '' tag input * (cf. this.). * this value is saved as a parameter for the service route. * * @param {Object} e - HTMLElement * * @private */ onRouteModeTransportChange : function (e) { logger.log("onRouteModeTransportChange", e); var value = e.target.value; if (!value) { return; } logger.log(value); this._currentTransport = value; }, /** * this method is called by event 'click' on '' tag input * (cf. this.), and it displays the panel options of exclusions. * Not use ! * * @param {Object} e - HTMLElement * * @private */ onShowRouteExclusionsClick : function (e) { logger.log("onShowRouteExclusionsClick", e); // not use ! }, /** * this method is called by event 'change' on '' tag input * (cf. this.). * this value is saved as a parameter for the service route. * Not use ! * * @param {Object} e - HTMLElement * * @private */ onRouteExclusionsChange : function (e) { logger.log("onRouteExclusionsChange", e); var value = e.target.value; var checked = e.target.checked; if (!value) { return; } logger.log(value, checked); var bFound = false; var iFound = null; for (var i = 0; i < this._currentExclusions.length; i++) { if (this._currentExclusions[i] === value) { iFound = i; bFound = true; } } // on l'ajoute si la valeur n'existe pas et est selectionnée if (!bFound && checked) { this._currentExclusions.push(value); } // on la retire si la valeur existe et est desselectionnée if (bFound && !checked) { this._currentExclusions[iFound] = null; } }, /** * this method is called by event 'submit' on '' tag form * (cf. this.), and it displays the results. * * @param {Object} options - options * * @private */ onRouteComputationSubmit : function (options) { logger.log("onRouteComputationSubmit", options); // FIXME on lance une requête en EPSG:4326, les coordonnées // doivent donc être du type cad en lat/lon. // hors, BUG du service du calcul d'itineraire car les // coordonnées envoyées doivent être en lon/lat avec une SRS en EPSG:4326 !? // sinon, ça plante... // Liste des points var points = this._currentPoints; // - point de depart var start; if (points[0].getCoordinate) { var startCoordinate = points[0].getCoordinate(); start = { x : startCoordinate.lon || startCoordinate.lng, y : startCoordinate.lat }; } points[0].dragging(false); logger.log("start", start); // - point d'arrivée var end; if (points[points.length - 1] && points[points.length - 1].getCoordinate) { var endCoordinate = points[points.length - 1].getCoordinate(); end = { x : endCoordinate.lon || endCoordinate.lng, y : endCoordinate.lat }; } points[points.length - 1].dragging(false); logger.log("end", end); // - les étapes var step = []; for (var i = 1; i < points.length - 1; i++) { if (points[i] && points[i].getCoordinate) { var iCoordinate = points[i].getCoordinate(); if (iCoordinate) { var coordinate = { x : iCoordinate.lon || iCoordinate.lng, y : iCoordinate.lat }; logger.log("step", coordinate); step.push(coordinate); } } } // valeurs selectionnées this._currentTransport = options.transport; this._currentComputation = options.computation; this._currentExclusions = options.exclusions; // on recupere les éventuelles options du service passées par l'utilisateur var routeOptions = this.options.routeOptions; // OVERLOAD : la resource bd-topo-osrm ne gère pas le calcul piéton en mode fastest // dans ce cas, on utilise valhalla dans le cas d'une utilisation par défaut du widget // sans paramétrage de resource explicitement demandé var routeResource; if (!routeOptions.resource) { if (this._currentComputation === "fastest" && this._currentTransport === "Pieton") { routeResource = "bdtopo-valhalla"; } } else { routeResource = routeOptions.resource; } if (typeof this.options.routeOptions.geometryInInstructions === "undefined") { this.options.routeOptions.geometryInInstructions = true; } // mise en place de la patience this._displayWaitingContainer(); // on met en place l'affichage des resultats dans la fenetre de resultats. var context = this; this._requestRouting({ startPoint : start, endPoint : end, viaPoints : step, graph : this._currentTransport, routePreference : this._currentComputation, resource : routeResource, exclusions : this._currentExclusions, geometryInInstructions : this.options.routeOptions.geometryInInstructions, distanceUnit : "m", // surcharge obligatoire ! // callback onSuccess onSuccess : function (results) { logger.log(results); if (results) { context._fillRouteResultsDetails(results); if (context.options.routeOptions.onSuccess) { context.options.routeOptions.onSuccess(results); } } }, // callback onFailure onFailure : function (error) { // FIXME mise à jour du controle mais le service ne repond pas en 200 !? context._hideWaitingContainer(); context._clearRouteResultsDetails(); logger.log(error.message); } }); }, /** * this method is called by event 'click' on '' * tag label (cf. this.), * and it cleans the old route geometry. * * @param {Object} e - HTMLElement * * @private */ onShowRouteResultsNewClick : function (e) { logger.log("onShowRouteResultsNewClick", e); // on reactive le drag&drop var points = this._currentPoints; for (var i = 0; i < points.length; i++) { points[i].dragging(true); } // clean avant un nouveau calcul ! this._clearRouteResultsDetails(); this._clearRouteResultsGeometry(); this._clearRouteResultsFeatureGeometry(); }, /** * this method is called by event 'mouseover' on '' * tag label (cf. this.), * and it makes a style on feature route. * * @param {Object} e - HTMLElement * * @private */ onRouteResultsDetailsMouseOver : function (e) { logger.log("onRouteResultsDetailsMouseOver", e); var idx = ID.index(e.target.id); // valable uniquement pour le mode desktop ! if (!this._isDesktop) { return; } if (!this._geojsonSections) { return; } this._geojsonSections.eachLayer(function (layer) { if (layer.feature.id === parseInt(idx, 10)) { layer.setStyle({ weight : 10, color : "#0F9DE8", opacity : 0.5 }); } }); }, /** * this method is called by event 'mouseout' on '' * tag label (cf. this.), * and it deletes a style on feature route. * * @param {Object} e - HTMLElement * * @private */ onRouteResultsDetailsMouseOut : function (e) { logger.log("onRouteResultsDetailsMouseOut", e); var idx = ID.index(e.target.id); // valable uniquement pour le mode desktop ! if (!this._isDesktop) { return; } if (!this._geojsonSections) { return; } this._geojsonSections.eachLayer(function (layer) { if (layer.feature.id === parseInt(idx, 10)) { layer.setStyle({ color : "#ED7F10", weight : 5, opacity : 0.75 }); } }); }, /** * this method is called by event 'click' on '' * tag label (cf. this.), * and it deletes a style on feature route. * Only for mobile ! * * @param {Object} e - HTMLElement * * @private */ onRouteResultsDetailsClick : function (e) { logger.log("onRouteResultsDetailsClick", e); var idx = ID.index(e.target.id); var self = this; // valable uniquement pour le mode mobile ! if (this._isDesktop) { return; } if (!this._geojsonSections) { return; } // afficher le detail cumulé du parcours ! var newInstruction = e.target.title; var oldInstruction = e.target.innerHTML; this._geojsonSections.eachLayer(function (layer) { if (layer.feature.id === parseInt(idx, 10)) { e.target.innerHTML = newInstruction; layer.setStyle({ weight : 10, color : "#0F9DE8", opacity : 0.5 }); } }); clearTimeout(1000); setTimeout(function () { self._geojsonSections.eachLayer(function (layer) { if (layer.feature.id === parseInt(idx, 10)) { e.target.innerHTML = oldInstruction; layer.setStyle({ color : "#ED7F10", weight : 5, opacity : 0.75 }); } }); }, 1000); }, // ################################################################### // // ########################### Routing ############################### // // ############## (methods to request and results) ################### // /** * this method is called by this.onRouteComputationSubmit() * and executes a request to the service. * * @param {Object} settings - service settings * @param {Function} settings.onSuccess - callback * @param {Function} settings.onFailure - callback * * @private */ _requestRouting : function (settings) { // on ne fait pas de requête si on n'a pas renseigné de parametres ! if (!settings || Object.keys(settings).length === 0) { return; } // on ne fait pas de requête si // - la parametre 'startPoint' est vide ! if (!settings.startPoint) { return; } // - la parametre 'endPoint' est vide ! if (!settings.endPoint) { return; } logger.log(settings); var options = {}; // on recupere les options du service L.Util.extend(options, this.options.routeOptions); // ainsi que les parametres de saisie et les callbacks L.Util.extend(options, settings); // cas où la clef API n'est pas renseignée dans les options du service, // on utilise celle renseignée au niveau du controle ou la clé "calcul" par défaut L.Util.extend(options, { apiKey : this.options.routeOptions.apiKey || this.options.apiKey }); // si l'utilisateur a spécifié le paramètre ssl au niveau du control, on s'en sert // true par défaut (https) L.Util.extend(options, { ssl : this.options.ssl }); logger.log(options); Gp.Services.route(options); }, /** * this method is called by this.onRouteComputationSubmit() * and fills the container of the route instructions list, distance and time * information, aslo, constructs the geometry route. * * @param {Object} results - results of the route calculation * * @private */ _fillRouteResultsDetails : function (results) { // FIXME // gestion des temps de traitement avec des callback !? // Distance et Durée var distance = results.totalDistance; var duration = results.totalTime; // Détails avec simplifications des troncons var instructions = this._simplifiedInstructions(results.routeInstructions); // var instructions = results.routeInstructions; if (instructions) { this._fillRouteResultsDetailsContainer(distance, duration, instructions); } // Geometries simplifiées var geometry = results.routeGeometry; if (geometry) { this._fillRouteResultsDetailsGeometry(geometry); } if (this.options.routeOptions.geometryInInstructions) { // existe t il une geometrie pour chaque troncon de route ? var bGeometryInstructions = (instructions && Array.isArray(instructions) && instructions[0].geometry.length !== 0); // Geometries des tronçon if (instructions && bGeometryInstructions) { this._fillRouteResultsDetailsFeatureGeometry(instructions); } } // Emprise var bbox = results.bbox; if (bbox) { var map = this._map; var bounds = L.latLngBounds([bbox.bottom, bbox.left], [bbox.top, bbox.right]); map.fitBounds(bounds, { padding : [1, 1] }); } // sauvegarde de l'etat des resultats this._currentRouteInformations = results; // mise à jour du controle ! this._formRouteContainer.className = "GProuteComponentHidden"; this._hideWaitingContainer(); this._resultsRouteContainer.className = ""; }, /** * this method is called by this._fillRouteResultsDetails() * and fills the container of the route instructions list, distance and time * information. * * @param {Number} distance - distance * @param {Number} duration - duration * @param {Object[]} instructions - list of instructions * * @private */ _fillRouteResultsDetailsContainer : function (distance, duration, instructions) { // FIXME callback // Distance et Durée this._resultsRouteValuesContainer = this._addRouteResultsValuesElement(distance, duration, this._convertSecondsToTime); // Détails this._resultsRouteDetailsContainer = this._addRouteResultsDetailsElement(instructions, this._convertSecondsToTime); }, /** * this method is called by this._fillRouteResultsDetails() * and constructs the simplified geometry route. * * @param {Object} geometry - geometry * * @private */ _fillRouteResultsDetailsGeometry : function (geometry) { // FIXME callback this._clearRouteResultsGeometry(); var map = this._map; var _style = { color : "#ED7F10", weight : 5, opacity : 0.75 }; this._geojsonRoute = L.geoJson(geometry, { style : _style }).addTo(map); }, /** * this method is called by this._fillRouteResultsDetails() * and constructs the geometries street with informations. * * @param {Object[]} instructions - instructions * * @private */ _fillRouteResultsDetailsFeatureGeometry : function (instructions) { // FIXME callback this._clearRouteResultsFeatureGeometry(); var map = this._map; var _style = { color : "#ED7F10", weight : 5, opacity : 0.75 }; var _geometry = { type : "FeatureCollection", features : [] }; for (var i = 0; i < instructions.length; i++) { var o = instructions[i]; var id = i + 1; _geometry.features.push({ id : id, type : "Feature", geometry : o.geometry, properties : { popupContent : "(" + id + ") distance : " + this._convertDistance(o.distance) + " / temps : " + this._convertSecondsToTime(o.duration) } }); } var self = this; function resetHighlight (e) { var layer = e.target; self._geojsonSections.resetStyle(layer); var div = L.DomUtil.get("GProuteResultsDetailsInstruction_" + layer.feature.id + "-" + self._uid); L.DomUtil.removeClass(div, "GProuteResultsDetailsHighlight"); } function highlightFeature (e) { var layer = e.target; logger.log(layer); layer.setStyle({ weight : 10, color : "#0F9DE8", opacity : 0.5 }); var div = L.DomUtil.get("GProuteResultsDetailsInstruction_" + layer.feature.id + "-" + self._uid); L.DomUtil.addClass(div, "GProuteResultsDetailsHighlight"); } this._geojsonSections = L.geoJson(_geometry, { style : _style, // Function that will be called on each created feature layer. onEachFeature : function (feature, layer) { layer.on({ mouseover : highlightFeature, mouseout : resetHighlight }); layer.bindPopup(feature.properties.popupContent); } }).addTo(map); }, // ################################################################### // // ############################# Clean ############################### // // ################################################################### // /** * this method is called by this.onShowRoutePanelClick() * and it clears all elements (reinit). * * @private */ _clear : function () { this._currentTransport = null; this._currentExclusions = []; this._currentComputation = null; // les resultats this._clearRouteResultsDetails(); // la geometrie this._clearRouteResultsGeometry(); this._clearRouteResultsFeatureGeometry(); // les points for (var i = 0; i < this._currentPoints.length; i++) { this._currentPoints[i].clear(); } }, /** * this method is called by this.onRouteComputationSubmit() * and it clears all route instructions. * * @private */ _clearRouteResultsDetails : function () { this._currentRouteInformations = null; // doit on nettoyer le container "GProuteResultsDetails" ? // il sera de toute façon écrasé par la prochaine requête... if (this._resultsRouteDetailsContainer) { var divD = this._resultsRouteDetailsContainer; if (divD.childElementCount) { while (divD.firstChild) { divD.removeChild(divD.firstChild); } } } if (this._resultsRouteValuesContainer) { var divV = this._resultsRouteValuesContainer; if (divV.childElementCount) { while (divV.firstChild) { divV.removeChild(divV.firstChild); } } } }, /** * this method is called by this.onRouteComputationSubmit() * and it clears all route geometries. * * @private */ _clearRouteResultsGeometry : function () { var map = this._map; if (this._geojsonRoute != null) { map.removeLayer(this._geojsonRoute); this._geojsonRoute = null; } }, /** * this method is called by this.onRouteComputationSubmit() * and it clears all route geometries. * * @private */ _clearRouteResultsFeatureGeometry : function () { var map = this._map; if (this._geojsonSections != null) { map.removeLayer(this._geojsonSections); this._geojsonSections = null; } }, // ################################################################### // // ############################ Patience ############################# // // ################################################################### // /** * this method displays waiting container and sets a timeout * * @private */ _displayWaitingContainer : function () { this._waitingContainer.className = "GProuteCalcWaitingContainerVisible"; this._waiting = true; // mise en place d'un timeout pour réinitialiser le panel (cacher la patience) // si on est toujours en attente (si la requête est bloquée par exemple) if (this._timer) { clearTimeout(this._timer); this._timer = null; } var context = this; this._timer = setTimeout(function () { if (context._waiting === true) { context._hideWaitingContainer(); } else { if (context._timer) { clearTimeout(context._timer); } } }, 16000); }, /** * this method hides waiting container and clears timeout * * @private */ _hideWaitingContainer : function () { if (this._waiting) { this._waitingContainer.className = "GProuteCalcWaitingContainerHidden"; this._waiting = false; clearTimeout(this._timer); this._timer = null; } }, // ################################################################### // // ########################## Geometry ############################### // // ################################################################### // /** * simplifies instructions * * @param {Object[]} instructions - instructions * * @returns {Object[]} simplified instructions * * @private */ _simplifiedInstructions : function (instructions) { var newInstructions = []; var current = instructions[0]; // cas où... if (instructions.length === 1) { newInstructions.push(current); } for (var i = 1; i < instructions.length; i++) { var o = instructions[i]; if (o.instruction === current.instruction) { current.distance = (parseFloat(o.distance) + parseFloat(current.distance)).toString(); current.duration = (parseFloat(o.duration) + parseFloat(current.duration)).toString(); for (var j = 1; j < o.geometry.coordinates.length; j++) { current.geometry.coordinates.push(o.geometry.coordinates[j]); } // last if (i === instructions.length - 1) { newInstructions.push(current); current = null; } } else { newInstructions.push(current); current = o; // last if (i === instructions.length - 1) { newInstructions.push(o); current = null; } } } logger.log(newInstructions); return newInstructions; }, // ################################################################### // // ################# Utils for Distance/Duration ##################### // // ################################################################### // /** * convert seconds to time : HH:MM:SS * * @param {Number} duration - duration in seconds * * @returns {String} duration in HH:MM:SS * * @private */ _convertSecondsToTime : function (duration) { var time = ""; duration = Math.round(duration); var hours = Math.floor(duration / (60 * 60)); if (!hours) { hours = "00"; } var divisor4minutes = duration % (60 * 60); var minutes = Math.floor(divisor4minutes / 60); if (!minutes) { minutes = "00"; } var divisor4seconds = divisor4minutes % 60; var seconds = Math.ceil(divisor4seconds); if (!seconds) { seconds = "00"; } time = hours + "h " + minutes + "m " + seconds + "s"; return time; }, /** * convert distance in meters or kilometers * * @param {Number} distance - distance in meters * * @returns {String} distance in km * * @private */ _convertDistance : function (distance) { var d = ""; var distanceKm = parseInt(distance / 1000, 10); if (!distanceKm) { d = parseInt(distance, 10) + " m"; // arrondi ! } else { d = distanceKm + " km"; } return d; }, // ################################################################### // // ###### METHODES PUBLIQUES (INTERFACE AVEC LE CONTROLE) ############ // // ################################################################### // /** * This method is public. * It allows to control the execution of a traitment. * * @param {Object} positions - positions = [{lng: , lat: }] * @param {Object} options - options = {...} */ compute : function (positions, options) { if (!this._showRouteContainer.checked) { this._pictoRouteContainer.click(); } var map = this._map; if (!map) { return; } // Les options par defauts var settings = { computation : "fastest", transport : "Voiture", exclusions : [] }; // On recupere les options L.Util.extend(settings, options); // Liste des points ! var points = this._currentPoints; var start = 0; points[start].setCoordinate(positions[start]); var startInput = L.DomUtil.get("GPlocationOrigin_" + 1 + "-" + this._uid); startInput.value = positions[start].lng + " , " + positions[start].lat; var end = positions.length - 1; points[6].setCoordinate(positions[end]); var endInput = L.DomUtil.get("GPlocationOrigin_" + 7 + "-" + this._uid); endInput.value = positions[end].lng + " , " + positions[end].lat; for (var i = 1; i < positions.length - 1; i++) { points[i].setCoordinate(positions[i]); var stepInput = L.DomUtil.get("GPlocationOrigin_" + i + "-" + this._uid); stepInput.value = positions[i].lng + " , " + positions[i].lat; } (settings.transport === "Voiture") ? L.DomUtil.get("GProuteTransportCar-" + this._uid).checked = true : L.DomUtil.get("GProuteTransportPedestrian-" + this._uid).checked = true; (settings.computation === "fastest") ? L.DomUtil.get("GProuteComputationSelect-" + this._uid).selectedIndex = 0 : L.DomUtil.get("GProuteComputationSelect-" + this._uid).selectedIndex = 1; // TODO exclusion ! // Calcul this.onRouteComputationSubmit(settings); } }); export default Route;