UNPKG

geopf-extensions-openlayers

Version:

French Geoportal Extensions for OpenLayers libraries

1,354 lines (1,219 loc) 82.9 kB
// import CSS import "../../CSS/Controls/Route/GPFroute.css"; // import "../../CSS/Controls/Route/GPFrouteStyle.css"; // import OpenLayers // import Control from "ol/control/Control"; import Widget from "../Widget"; import Control from "../Control"; import Map from "ol/Map"; import { unByKey as olObservableUnByKey } from "ol/Observable"; import Overlay from "ol/Overlay"; import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; // import GeoJSON from "ol/format/GeoJSON"; import { pointerMove as eventPointerMove } from "ol/events/condition"; import { Select as SelectInteraction } from "ol/interaction"; import { Stroke, Style } from "ol/style"; import { transformExtent as olTransformExtentProj } from "ol/proj"; // import geoportal library access import Gp from "geoportal-access-lib"; // import local import Logger from "../../Utils/LoggerByDefault"; import Utils from "../../Utils/Helper"; import SelectorID from "../../Utils/SelectorID"; import Markers from "../Utils/Markers"; import Draggable from "../../Utils/Draggable"; import Interactions from "../Utils/Interactions"; import MathUtils from "../../Utils/MathUtils"; // import local with ol dependencies import LocationSelector from "../LocationSelector/LocationSelector"; import ButtonExport from "../Export/Export"; import LayerSwitcher from "../LayerSwitcher/LayerSwitcher"; import GeoJSONExtended from "../../Formats/GeoJSON"; // DOM import RouteDOM from "./RouteDOM"; import checkDsfr from "../Utils/CheckDsfr"; var logger = Logger.getLogger("route"); /** * @classdesc * * Route Control. * * @alias ol.control.Route * @module Route */ class Route extends Control { /** * @constructor * @param {Object} options - route control options * @param {Number} [options.id] - Ability to add an identifier on the widget (advanced option) * @param {String} [options.apiKey] - API key for services call (route and autocomplete services). The key "calcul" is used by default. * @param {Boolean} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol) * @param {Boolean} [options.collapsed = true] - Specify if widget has to be collapsed (true) or not (false) on map loading. Default is true. * @param {Boolean} [options.draggable = false] - Specify if widget is draggable * @param {Boolean|Object} [options.export = false] - Specify if button "Export" is displayed. For the use of the options of the "Export" control, see {@link packages/Controls/Export/Export.default} * @param {Object} [options.exclusions = {"toll" : false, "tunnel" : false, "bridge" : false}] - list of exclusions with status (true = checked). By default : no exclusions checked. * @param {Array} [options.graphs = ["Voiture", "Pieton"]] - list of resources, by default : ["Voiture", "Pieton"]. The first element is selected. * @param {Object} [options.routeOptions = {}] - route service options. see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~route Gp.Services.route()} to know all route options. * @param {Object} [options.autocompleteOptions = {}] - autocomplete service options. see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~autoComplete Gp.Services.autoComplete()} to know all autocomplete options * @param {Object} [options.markersOpts] - options to use your own markers. Object properties can be "departure", "stages" or "arrival". Corresponding value is an object with following properties : * @param {String} [options.markersOpts.url] - marker base64 encoded url (ex "data:image/png;base64,...""). Mandatory for a custom marker * @param {Array} [options.markersOpts.offset] - Offsets in pixels used when positioning the overlay. The first element in the array is the horizontal offset. A positive value shifts the overlay right. The second element in the array is the vertical offset. A positive value shifts the overlay down. Default is [0, 0]. (see http://openlayers.org/en/latest/apidoc/ol.Overlay.html) * @param {Object} [options.layerDescription = {}] - Layer informations to be displayed in LayerSwitcher widget (only if a LayerSwitcher is also added to the map) * @param {String} [options.layerDescription.title = "Itinéraire"] - Layer title to be displayed in LayerSwitcher * @param {String} [options.layerDescription.description = "Itinéraire basé sur un graphe"] - Layer description to be displayed in LayerSwitcher * @fires route:drawstart * @fires route:drawend * @fires route:compute * @fires route:compute * @fires route:newresults * @example * var route = ol.control.Route({ * "collapsed" : true * "draggable" : true, * "export" : false, * "exclusions" : { * "toll" : true, * "bridge" : false, * "tunnel" : true * }, * "graphs" : ['Pieton', 'Voiture'], * "markersOpts" : { * "departure" : { * "url" : "...", * "offset" : [0,0] * }, * "stages" : { * "url" : "...", * "offset" : [0,0] * }, * "arrival" : { * "url" : "...", * "offset" : [0,0] * } * } * "autocompleteOptions" : {}, * "routeOptions" : {} * }); * * // if you want to pluggued the control Export with options : * var route = new ol.control.Route({ * export : { * name : "export", * format : "geojson", * title : "Exporter", * menu : false * } * }); */ constructor (options) { options = options || {}; // call ol.control.Control constructor super(options); if (!(this instanceof Route)) { throw new TypeError("ERROR CLASS_CONSTRUCTOR"); } /** * Nom de la classe (heritage) * @private */ this.CLASSNAME = "Route"; // initialisation du composant this.initialize(options); // Widget main DOM container this._container = this._createMainContainerElement(); // ajout du container (this.element) ? this.element.appendChild(this._container) : this.element = this._container; return this; } /** * Overwrite OpenLayers setMap method * * @param {Map} map - Map. */ setMap (map) { if (map) { // enrichissement du DOM du container this._container = this._initContainer(map); this.element = this._container; // ajout d'un bouton d'export if (this.options.export) { var opts = Utils.assign({ control : this }, this.options.export); this.export = new ButtonExport(opts); this.export.render(); var self = this; this.export.on("button:clicked", (e) => { self.dispatchEvent({ type : "export:compute", content : e.content }); }); } // mode "draggable" if (this.draggable) { Draggable.dragElement( this._panelRouteContainer, this._panelHeaderRouteContainer, map.getTargetElement() ); } // mode "collapsed" if (!this.collapsed) { this._showRouteButton.setAttribute("aria-pressed", true); } } // on appelle la méthode setMap originale d'OpenLayers super.setMap(map); // position if (this.options.position) { this.setPosition(this.options.position); } // reunion du bouton avec le précédent if (this.options.gutter === false) { this.getContainer().classList.add("gpf-button-no-gutter"); } }; // ################################################################### // // ##################### public methods ############################## // // ################################################################### // /** * Returns true if widget is collapsed (minimized), false otherwise * * @returns {Boolean} collapsed - true if widget is collapsed */ getCollapsed () { return this.collapsed; } /** * Collapse or display widget main container * * @param {Boolean} collapsed - True to collapse widget, False to display it */ setCollapsed (collapsed) { if (collapsed === undefined) { logger.log("[ERROR] Route:setCollapsed - missing collapsed parameter"); return; } if ((collapsed && this.collapsed) || (!collapsed && !this.collapsed)) { return; } if (collapsed) { document.getElementById("GProutePanelClose-" + this._uid).click(); } else { this._showRouteButton.click(); } this.collapsed = collapsed; } /** * Get vector layer where geoJson route is drawn * * @returns {VectorLayer} layer - ol.layer.Vector route layer */ getLayer () { return this._geojsonSections; } /** * Set vector layer where route geometry is drawn * * @param {VectorLayer} layer - ol.layer.Vector route layer */ setLayer (layer) { if (!layer) { this._geojsonSections = null; return; } if (!(layer instanceof VectorLayer)) { logger.log("no valid layer given for hosting drawn features."); return; } // application des styles layer.setStyle(this._defaultFeatureStyle); // sauvegarde this._geojsonSections = layer; } /** * Get vector layer * * @returns {String} geojson - GeoJSON format layer */ getGeoJSON () { return JSON.stringify(this._geojsonObject); } /** * Set vector layer * * @param {String} geojson - GeoJSON format layer */ setGeoJSON (geojson) { try { this._geojsonObject = JSON.parse(geojson); } catch (e) { logger.log("no valid geojson given :" + e.message); } } /** * Get route informations * * @returns {Object} data - route informations */ getData () { var points = []; for (let index = 0; index < this._currentPoints.length; index++) { const p = this._currentPoints[index]; points.push(p.getCoordinate()); } var data = { type : "route", points : points, transport : this._currentTransport, exclusions : this._currentExclusions, computation : this._currentComputation, results : {} }; Utils.assign(data.results, this._currentRouteInformations); return data; } /** * Set route data * * @param {Object} data - control informations * @param {String} data.transport - transport type * @param {String} data.computation - computation type * @param {Array} data.exclusions - list of exclusions * @param {Array} data.points - list of points : [[lon, lat]] * @param {Object} data.results - service response */ setData (data) { // INFO // transmettre toutes les informations necessaires pour reconstruire le panneau de resultats this._currentTransport = data.transport; this._currentComputation = data.computation; this._currentExclusions = data.exclusions; // ajout des nouvelles coordonnnées for (var j = 0; j < data.points.length; j++) { const c = data.points[j]; if (c) { this._currentPoints[j].setCoordinate(c, "EPSG:4326"); } } // INFO // nettoyer les points du calcul précedent for (var i = 0; i < this._currentPoints.length; i++) { var point = this._currentPoints[i]; if (point.getCoordinate()) { // clean de l'objet sans declencher les evenements qui suppriment la couche précedente ! // /!\ point.clear() // point.clearResults(); // clean du dom var id = (i + 1) + "-" + this._uid; var coordinate = point.getCoordinate()[1].toFixed(4) + " / " + point.getCoordinate()[0].toFixed(4); document.getElementById("GPlocationOriginCoords_" + id).value = coordinate; document.getElementById("GPlocationOrigin_" + id).value = coordinate; // document.getElementById("GPlocationPoint_" + id).style.cssText = ""; if (i > 0 && i < 6) { // on masque les points intermediaires document.getElementById("GPlocationPoint_" + id).className = "GPflexInput GPelementHidden gpf-flex gpf-hidden "; } } } this._currentRouteInformations = data.results; } /** * Get container * * @returns {HTMLElement} container */ getContainer () { return this._container; } /** * Get default style * * @returns {Style} style */ getStyle () { return this._defaultFeatureStyle; } /** * This method is public. * It allows to init the control. */ init () { // INFO // reconstruire le panneau de resultats sans lancer de calcul // * construire la liste des points (cf. RouteDOM._createRoutePanelFormElement()) // * construire les resultats // init points for (let index = 0; index < this._currentPoints.length; index++) { const point = this._currentPoints[index]; var id = index + 1; var coordinate = point.getCoordinate(); if (coordinate) { var input = document.getElementById("GPlocationOrigin_" + id + "-" + this._uid); input.value = coordinate[1].toFixed(4) + " / " + coordinate[0].toFixed(4); if (index > 0 && index < 6) { document.getElementById("GPlocationPoint_" + id + "-" + this._uid).className = "GPflexInput GPlocationStageFlexInput gpf-flex"; } } } // add points into panel var points = document.getElementsByClassName("GPlocationPoint-" + this._uid); this._addRouteResultsStagesValuesElement(points); // set transport mode var transportdiv; if (this._currentTransport === "Pieton") { transportdiv = document.getElementById("GProuteTransportPedestrian-" + this._uid); if (transportdiv) { transportdiv.checked = "true"; } } else { transportdiv = document.getElementById("GProuteTransportCar-" + this._uid); if (transportdiv) { transportdiv.checked = "true"; } } // set computation mode var computationdiv = document.getElementById("GProuteComputationSelect-" + this._uid); if (computationdiv) { computationdiv.value = this._currentComputation; } // set exclusions var tollInput = document.getElementById("GProuteExclusionsToll-" + this._uid); if (tollInput) { if (this._currentExclusions.indexOf("toll") !== -1) { tollInput.checked = false; } else { tollInput.checked = true; } } var tunnelInput = document.getElementById("GProuteExclusionsTunnel-" + this._uid); if (tunnelInput) { if (this._currentExclusions.indexOf("tunnel") !== -1) { tunnelInput.checked = false; } else { tunnelInput.checked = true; } } var bridgeInput = document.getElementById("GProuteExclusionsBridge-" + this._uid); if (bridgeInput) { if (this._currentExclusions.indexOf("bridge") !== -1) { bridgeInput.checked = false; } else { bridgeInput.checked = true; } } var distance = this._currentRouteInformations.totalDistance; var duration = this._currentRouteInformations.totalTime; // Détails avec simplifications des troncons var instructions = this._simplifiedInstructions(this._currentRouteInformations.routeInstructions); if (instructions) { this._fillRouteResultsDetailsContainer(distance, duration, instructions); } // affichage du panneau de details du controle ! this._formRouteContainer.className = "GPelementHidden gpf-hidden gpf-panel__content fr-modal__content"; this._hideWaitingContainer(); this._resultsRouteContainer.className = ""; } /** * Clean UI : reinit control */ clean () { this._currentTransport = null; this._currentExclusions = []; this._currentComputation = null; for (var i = 0; i < this._currentPoints.length; i++) { this._currentPoints[i].clear(); } this._removeRouteStepLocations(); this._clearRouteInputOptions(); this._clearRouteResultsDetails(); this.setLayer(); this._formRouteContainer.className = "gpf-panel__content fr-modal__content"; this._resultsRouteContainer.className = "GPelementHidden gpf-hidden"; } // ################################################################### // // ##################### init component ############################## // // ################################################################### // /** * Initialize route control (called by Route constructor) * * @param {Object} options - constructor options * @private */ initialize (options) { this._checkInputOptions(options); // set default options this.options = { collapsed : true, draggable : false, export : false, graphs : ["Pieton", "Voiture"], exclusions : { toll : false, tunnel : false, bridge : false }, routeOptions : {}, autocompleteOptions : {}, layerDescription : { title : "Itinéraire", description : "Itinéraire basé sur un graphe" } }; // merge with user options Utils.assign(this.options, options); // cas particulier des markers par défaut var defaultMarkersOpts = { departure : { url : Markers["red"], offset : Markers.defaultOffset }, stages : { url : Markers["lightOrange"], offset : Markers.defaultOffset }, arrival : { url : Markers["darkOrange"], offset : Markers.defaultOffset } }; // on récupère les options de chaque type de marker si spécifié this.options.markersOpts = Utils.assign(defaultMarkersOpts, options.markersOpts); /** * @type {Boolean} * specify if Route control is collapsed (true) or not (false) */ this.collapsed = this.options.collapsed; /** * @type {Boolean} * specify if Route control is draggable (true) or not (false) */ this.draggable = this.options.draggable; /** @private */ this._uid = this.options.id || SelectorID.generate(); // containers principaux /** @private */ this._showRouteButton = null; /** @private */ this._panelRouteContainer = null; /** @private */ this._panelHeaderRouteContainer = null; /** @private */ this._waitingContainer = null; /** @private */ this._formRouteContainer = null; /** @private */ this._resultsRouteContainer = null; /** @private */ this._showRouteExclusionsElement = null; // liste de points selectionnée /** @private */ this._currentPoints = []; // Mode de transport selectionné : 'Voiture' ou 'Pieton' /** @private */ this._currentTransport = null; this._initTransport(); // Mode de calcul selectionné : 'Plus rapide' ou 'plus court' /** @private */ this._currentComputation = null; this._initComputation(); // Exclusions selectionnées : Tunnel, Toll et Bridge /** @private */ this._currentExclusions = []; this._initExclusions(); // si un calcul est en cours ou non /** @private */ this._waiting = false; // timer pour cacher la patience après un certain temps /** @private */ this._timer = null; // la geometrie du parcours /** @private */ this._geojsonRoute = null; // la geometrie des troncons /** @private */ this._geojsonSections = null; // la geometrie des troncons au format GeoJSON /** @private */ this._geojsonObject = null; // bouton export /** @private */ this.export = null; // le container de la popup (pour les troncons selectionnés) /** @private */ this._popupContent = null; /** @private */ this._popupDiv = this._initPopupDiv(); // l'overlay ol.Overlay correspondant à la popup (pour les troncons selectionnés) /** @private */ this._popupOverlay = null; // ol.interaction.Select associées à la couche des résultats (troncons) /** @private */ this._resultsSelectInteraction = null; /** @private */ this._resultsHoverInteraction = null; // styles pour les sélections des features /** @type {Style} */ this._defaultFeatureStyle = new Style({ stroke : new Stroke({ color : "rgba(0,183,152,0.9)", width : 12 }) }); /** @type {Style} */ this._selectedFeatureStyle = new Style({ stroke : new Stroke({ color : "rgba(255,102,0,0.9)", width : 12 }) }); // reponse du service // Ex. { // totalTime, totalDistance, bbox, routeGeometry, // routeInstructions : [{duration, distance, code, instruction, bbox, geometry}] // } /** @private */ this._currentRouteInformations = null; // liste des ressources avec droits par service // Ex. { // "Route" : { // key : "ger4g456re45er456t4er5ge5", // resources : ["Pieton", "Voiture"] // } // } /** @private */ this._resources = {}; // listener key for event on pointermove or moveend map /** @private */ this.listenerKey = null; } /** * this method is called by this.initialize() * * @param {Object} options - options * * @private */ _checkInputOptions (options) { // vérification des options // mode de transport if (options.graphs) { // on ne permet pas de passer un tableau vide : on spécifie au moins un graph if (Array.isArray(options.graphs) && options.graphs.length) { for (var i = 0; i < options.graphs.length; i++) { if (typeof options.graphs[i] === "string") { if (options.graphs[i].toLowerCase() === "pieton") { options.graphs[i] = "Pieton"; } if (options.graphs[i].toLowerCase() === "voiture") { options.graphs[i] = "Voiture"; } } else { logger.log("[ol.control.Route] ERROR : parameter 'graphs' elements should be of type 'string'"); options.graphs[i] = null; } } } else { logger.warn("'graphs' parameter should be an array"); options.graphs = null; } } // collapsed if (options.collapsed === "true") { options.collapsed = true; } if (options.collapsed === "false") { options.collapsed = false; } } /** * initialize component container (DOM) * * @param {Object} map - the map * * @returns {HTMLElement} DOM element * * @private */ _initContainer (map) { // get main container var container = this._container; if (container.childElementCount > 0) { return container; } var picto = this._showRouteButton = this._createShowRoutePictoElement(); container.appendChild(picto); var routePanel = this._panelRouteContainer = this._createRoutePanelElement(); var routePanelDiv = this._createRoutePanelDivElement(); routePanel.appendChild(routePanelDiv); // header form var routeHeader = this._panelHeaderRouteContainer = this._createRoutePanelHeaderElement(); routePanelDiv.appendChild(routeHeader); // form var routeForm = this._formRouteContainer = this._createRoutePanelFormElement(); // form: menu des modes routeForm.appendChild(this._createRoutePanelFormModeChoiceTransportElement(this.options.graphs)); // form: menu des points var points = this._createRoutePanelFormPointsElement(map); for (var i = 0; i < points.length; i++) { routeForm.appendChild(points[i]); } routeForm.appendChild(this._createRoutePanelFormModeChoiceComputeElement()); // form: menu des exclusions this._showRouteExclusionsElement = this._createShowRouteExclusionsPictoElement(); routeForm.appendChild(this._showRouteExclusionsElement); var exclusion = this._createRoutePanelFormExclusionsElement(); exclusion.appendChild(this._createRoutePanelFormExclusionOptionsElement(this.options.exclusions)); routeForm.appendChild(exclusion); var panelFooter = this._createRoutePanelFooterElement(); routeForm.appendChild(panelFooter); if (!checkDsfr()) { var buttonReset = this._createRouteFormResetElement(); panelFooter.appendChild(buttonReset); } // form: bouton du calcul var buttonSubmit = this._createRouteSubmitFormElement(); panelFooter.appendChild(buttonSubmit); routePanelDiv.appendChild(routeForm); // results var routeResults = this._resultsRouteContainer = this._createRoutePanelResultsElement(); routePanelDiv.appendChild(routeResults); var plugin = this._createDrawingButtonsPluginDiv(); routePanelDiv.appendChild(plugin); // waiting var waiting = this._waitingContainer = this._createRouteWaitingElement(); routePanelDiv.appendChild(waiting); container.appendChild(routePanel); // hide autocomplete suggested locations on container click if (container.addEventListener) { container.addEventListener("click", (e) => this._hideRouteSuggestedLocations(e)); } return container; } // ################################################################### // // ####################### init application ########################## // // ################################################################### // /** * this method is called by the constructor and initialize transport mode * ("Voiture" ou "Pieton") * * @private */ _initTransport () { // Mode de transport selectionné this._currentTransport = "Pieton"; // par defaut // par defaut var transport = this.options.graphs; if (!transport || transport.length === 0) { this.options.graphs = ["Pieton", "Voiture"]; } // option if (Array.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 computation mode * (fastest or shortest) * * @private */ _initComputation () { // 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 exclusions * * @private */ _initExclusions () { // Exclusions selectionnées : Tunnel, Toll et Bridge this._currentExclusions = []; // par defaut // par defaut var exclusion = this.options.exclusions; if (!exclusion || (typeof exclusion === "object" && Object.keys(exclusion).length === 0)) { this.options.exclusions = { toll : false, tunnel : false, bridge : false }; } // option if (exclusion && typeof exclusion === "object" && 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 (Array.isArray(serviceOptions.exclusions)) { this._currentExclusions = serviceOptions.exclusions; } } /** * this method is called by this.initialize() and initialize popup div * (to display results information on route result click) * * @returns {Object} element - DOM element for popup * @private */ _initPopupDiv () { var context = this; var element = document.createElement("div"); element.className = "gp-feature-info-div"; var closer = document.createElement("button"); closer.className = "gp-styling-button closer"; // on closer click : remove popup closer.onclick = function () { if (context._popupOverlay != null) { context._popupOverlay.setPosition(undefined); } return false; }; this._popupContent = document.createElement("div"); this._popupContent.className = "gp-features-content-div"; element.appendChild(this._popupContent); element.appendChild(closer); return element; } // ################################################################### // // ############################## DOM ################################ // // ################################################################### // /** * Create List Points * Overwrite RouteDOM method ! * * @param {Map} map - the map * * @returns {Array} List DOM element * @private */ _createRoutePanelFormPointsElement (map) { var points = []; var count = 1; // point de depart var start = new LocationSelector({ apiKey : this.options.apiKey || null, tag : { id : count, label : "Départ", groupId : this._uid, markerOpts : this.options.markersOpts["departure"], display : true }, autocompleteOptions : this.options.autocompleteOptions || null }); start.setMap(map); // on ajoute des écouteurs d'évènements (en plus de ceux de LocationSelector), // pour prendre en compte les CSS spécifiques de GProuteForm this._addFormPointsEventListeners(start); points.push(this._createRoutePanelFormPointLabel("Départ")); points.push(start._container); this._currentPoints.push(start); // points intermediaires for (count = 2; count < 7; count++) { var step = new LocationSelector({ apiKey : this.options.apiKey || null, tag : { id : count, label : "Etape " + (count-1), groupId : this._uid, markerOpts : this.options.markersOpts["stages"], display : false, removeOption : true }, autocompleteOptions : this.options.autocompleteOptions || null }); step.setMap(map); this._addFormPointsEventListeners(step); points.push(this._createRoutePanelFormPointLabel("Étape", false)); points.push(step._container); this._currentPoints.push(step); } // point d'arrivée var end = new LocationSelector({ apiKey : this.options.apiKey || null, tag : { id : count, label : "Arrivée", groupId : this._uid, markerOpts : this.options.markersOpts["arrival"], display : true, addOption : true }, autocompleteOptions : this.options.autocompleteOptions || null }); end.setMap(map); this._addFormPointsEventListeners(end); points.push(this._createRoutePanelFormPointLabel("Arrivée")); points.push(end._container); this._currentPoints.push(end); return points; } /** * Attach events listeners to route form points (locationSelector) * * @param {Object} formPoint - route form point (locationSelector) * @private */ _addFormPointsEventListeners (formPoint) { if (!formPoint) { return; } if (formPoint._buttonLabel.addEventListener) { // display form on origin label click formPoint._buttonLabel.addEventListener( "click", (e) => this.onRouteOriginLabelClick(e) ); // minimize form on input show pointer, and set map event listeners (see this.onRouteOriginPointerClick) formPoint._inputShowPointer.addEventListener( "click", (e) => this.onRouteOriginPointerClick(e, formPoint) ); if (formPoint._removePointElement) { formPoint._removePointElement.addEventListener( "click", (e) => { logger.trace("click on _removePointElement", e); // Moving up exclusions picto // var exclusionsPictoTop = context._showRouteExclusionsElement.style.top; // context._showRouteExclusionsElement.style.top = (parseInt(exclusionsPictoTop, 10) - 33).toString() + "px"; } ); } if (formPoint._addPointElement) { formPoint._addPointElement.addEventListener( "click", (e) => { logger.trace("click on _addPointElement", e); // Moving down exclusions picto // var exclusionsPictoTop = context._showRouteExclusionsElement.style.top; // context._showRouteExclusionsElement.style.top = (parseInt(exclusionsPictoTop, 10) + 33).toString() + "px"; } ); } } else if (formPoint._buttonLabel.attachEvent) { // attachEvent: Internet explorer event listeners management formPoint._buttonLabel.attachEvent( "onclick", (e) => this.onRouteOriginLabelClick(e) ); formPoint._inputShowPointer.attachEvent( "onclick", (e) => this.onRouteOriginPointerClick(e, formPoint) ); if (formPoint._removePointElement) { formPoint._removePointElement.attachEvent( "onclick", (e) => { // Moving up exclusions picto // var exclusionsPictoTop = context._showRouteExclusionsElement.style.top; // context._showRouteExclusionsElement.style.top = (parseInt(exclusionsPictoTop, 10) - 33).toString() + "px"; } ); } if (formPoint._addPointElement) { formPoint._addPointElement.attachEvent( "onclick", (e) => { // Moving down exclusions picto // var exclusionsPictoTop = context._showRouteExclusionsElement.style.top; // context._showRouteExclusionsElement.style.top = (parseInt(exclusionsPictoTop, 10) + 33).toString() + "px"; } ); } } } // ################################################################### // // ####################### handlers events to dom #################### // // ################################################################### // /** * this method is called by event 'submit' on 'GProuteForm' tag form * (cf. this._createRoutePanelFormElement), and it displays the results. * * @param {Object} options - options * @private */ onRouteComputationSubmit (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. // or, 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 (info: points[0].getCoordinate est du type [lon, lat], en EPSG:4326) var start; if (points[0] && points[0].getCoordinate) { var startCoordinate = points[0].getCoordinate(); start = { x : startCoordinate[0].toFixed(5), y : startCoordinate[1].toFixed(5) }; logger.log("start", start); } // - point d'arrivée var end; var endPoint = points[points.length - 1]; if (endPoint && endPoint.getCoordinate) { var endCoordinate = endPoint.getCoordinate(); end = { x : endCoordinate[0].toFixed(5), y : endCoordinate[1].toFixed(5) }; 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[0].toFixed(5), y : iCoordinate[1].toFixed(5) }; 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; } // gestion du protocole et du timeout // le timeout est indispensable sur le protocole JSONP. var _protocol = routeOptions.protocol || "XHR"; var _timeout = routeOptions.timeOut || 0; if (_protocol === "JSONP" && _timeout === 0) { // FIXME le timeout est obligatoire pour ce type de protocole... _timeout = 15000; } // gestion des callback var bOnFailure = !!(routeOptions.onFailure !== null && typeof routeOptions.onFailure === "function"); // cast variable to boolean var bOnSuccess = !!(routeOptions.onSuccess !== null && typeof routeOptions.onSuccess === "function"); // 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 : routeOptions.graph || this._currentTransport, routePreference : routeOptions.routePreference || this._currentComputation, exclusions : routeOptions.exclusions || this._currentExclusions, geometryInInstructions : true, distanceUnit : "m", timeOut : _timeout, protocol : _protocol, resource : routeResource, // callback onSuccess onSuccess : function (results) { logger.log(results); if (results) { context._fillRouteResultsDetails(results); } if (bOnSuccess) { routeOptions.onSuccess.call(context, results); } }, // callback onFailure onFailure : function (error) { context._hideWaitingContainer(); context._clearRouteResultsDetails(); logger.log(error.message); if (bOnFailure) { routeOptions.onFailure.call(context, error); } } }); } /** * this method is called by event 'click' on 'GPlocationOriginLabel' label * and set 'GProuteForm' CSS class to "" (normal) * * @param {Object} routeControl - context : route Control (this) * @private */ onRouteOriginLabelClick () { this._formRouteContainer.className = "gpf-panel__content gpf-mobile-form fr-modal__content"; // on désactive l'écouteur d'événements sur la carte (pour ne pas placer un marker au clic) // map.un( // "click", // () => { // // on ne rétablit pas le mode "normal" si on est dans le panel des résultats (où className = "GProuteComponentHidden") // if (this._formRouteContainer.className === "GProuteFormMini") { // this._formRouteContainer.className = "gpf-panel__content fr-modal__content"; // } // } // ); olObservableUnByKey(this.listenerKey); this.dispatchEvent("route:drawend"); } /** * this method is called by event 'click' on 'GPlocationOriginPointerImg' label * and display or minimize 'GProuteForm', using CSS class ("GProuteFormMini" or "") * * @param {Object} e - context : route Control (equivalent to this) * @param {Object} locationSelector - context : locationSelector input (one of this._currentPoints) * @private */ onRouteOriginPointerClick (e, locationSelector) { var map = this.getMap(); if (locationSelector._inputShowPointerContainer.checked) { // au click sur l'input pour pointer sur la carte: on minimise le formulaire this._formRouteContainer.className = "GProuteFormMini gpf-panel__content fr-modal__content"; e.target.parentElement.parentElement.classList.add("selected"); // et au clic sur la carte, on réaffichera le formulaire "normal" this.listenerKey = map.on( "click", () => { e.target.parentElement.parentElement.classList.remove("selected"); // on ne rétablit pas le mode "normal" si on est dans le panel des résultats (où className = "GProuteComponentHidden") if (this._formRouteContainer.className === "GProuteFormMini gpf-panel__content fr-modal__content") { this._formRouteContainer.className = "gpf-panel__content fr-modal__content"; } olObservableUnByKey(this.listenerKey); /** * event triggered at the end of drawing input * * @event route:drawend */ this.dispatchEvent("route:drawend"); } ); /** * event triggered at the start of drawing input * * @event route:drawstart */ this.dispatchEvent("route:drawstart"); } else { // si on déselectionne le pointer, on rétablit le formulaire en mode normal this._formRouteContainer.className = ""; // et on enlève l'écouteur d'évènement sur la carte // map.un( // "click", // () => { // // on ne rétablit pas le mode "normal" si on est dans le panel des résultats (où className = "GProuteComponentHidden") // if (this._formRouteContainer.className === "GProuteFormMini") { // this._formRouteContainer.className = "gpf-panel__content fr-modal__content"; // } // } // ); olObservableUnByKey(this.listenerKey); this.dispatchEvent("route:drawend"); } } /** * this method is called by event 'click' on 'GPshowRoutePicto' * tag label (cf. this._createShowRoutePictoElement), * and it cleans all value of input. * * @param {Event} e - HTMLElement * @private */ onShowRoutePanelClick (e) { var opened = this._showRouteButton.ariaPressed; if (opened === "true") { this.onPanelOpen(); } var map = this.getMap(); // on supprime toutes les interactions Interactions.unset(map); // clean ! if (!this._geojsonSections && !this._waiting) { this._clear(); } this.collapsed = !(opened === "true"); // on génère nous même l'evenement OpenLayers de changement de pté // (utiliser ol.control.Route.on("change:collapsed", function ) pour s'abonner à cet évènement) this.dispatchEvent("change:collapsed"); // on recalcule la position if (this.options.position && !this.collapsed) { this.updatePosition(this.options.position); } } /** * this method is called by event 'change' on 'GProuteComputationSelect' tag select * (cf. this._createRoutePanelFormModeChoiceComputeElement). * this value is saved as a parameter for the service route. * * @param {Event} e - HTMLElement * @private */ onRouteModeComputationChange (e) { var value = e.target.value; if (!value) { return; } logger.log(value); this._currentComputation = value; } /** * this method is called by event 'change' on 'GProuteResultsComputationSelect' tag select * (cf. this._createRouteResultsElement). * this value is saved as a parameter for the service route, * and this launches the route request ! * * @param {Event} e - HTMLElement * @private */ 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 'GProuteTransportCar' or 'GProuteTransportPedestrian' tag input * (cf. this._createRoutePanelFormModeChoiceTransportElement). * this value is saved as a parameter for the service route. * * @param {ObjecEventt} e - HTMLElement * @private */ onRouteModeTransportChange (e) { var value = e.target.value;