geoportal-extensions-openlayers
Version:

1,340 lines (1,205 loc) • 83.5 kB
JavaScript
// import CSS
import "../CSS/Controls/Route/GProuteOpenLayers.css";
// import OpenLayers
import Control from "ol/control/Control";
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 "../../Common/Utils/LoggerByDefault";
import Utils from "../../Common/Utils";
import SelectorID from "../../Common/Utils/SelectorID";
import Markers from "./Utils/Markers";
import Draggable from "../../Common/Utils/Draggable";
import Interactions from "./Utils/Interactions";
// import local with ol dependencies
import LocationSelector from "./LocationSelector";
import ButtonExport from "./Export";
import LayerSwitcher from "./LayerSwitcher";
import GeoJSONExtended from "../Formats/GeoJSON";
// DOM
import RouteDOM from "../../Common/Controls/RouteDOM";
var logger = Logger.getLogger("route");
/**
* @classdesc
*
* Route Control.
*
* @constructor
* @alias ol.control.Route
* @type {ol.control.Route}
* @extends {ol.control.Control}
* @param {Object} options - route control options
* @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 ol.control.Export}
* @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 export:compute
* @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
* }
* });
*/
var Route = (function (Control) {
/**
* See {@link ol.control.Route}
* @module Route
* @alias module:~Controls/Route
* @param {*} options - options
* @example
* import Route from "src/OpenLayers/Controls/Route"
*/
function Route (options) {
options = options || {};
if (!(this instanceof Route)) {
throw new TypeError("ERROR CLASS_CONSTRUCTOR");
}
// initialisation du composant
this.initialize(options);
// Widget main DOM container
this._container = this._createMainContainerElement();
this._containerElement = null;
// on peut éventuellement encapsuler le composant dans une div passée par l'utilisateur
if (options.element && options.element.appendChild) {
// dans ce cas on stocke les deux container
options.element.appendChild(this._container);
this._containerElement = options.element;
}
// call ol.control.Control constructor
Control.call(this, {
element : this._containerElement || this._container,
target : options.target,
render : options.render
});
}
// Inherits from ol.control.Control
if (Control) Route.__proto__ = Control;
/**
* @lends module:Route
*/
Route.prototype = Object.create(Control.prototype, {});
// on récupère les méthodes de la classe commune RouteDOM
Utils.assign(Route.prototype, RouteDOM);
/**
* Constructor (alias)
* @private
*/
Route.prototype.constructor = Route;
/**
* Overwrite OpenLayers setMap method
*
* @param {ol.Map} map - Map.
*/
Route.prototype.setMap = function (map) {
if (map) {
// enrichissement du DOM du container
this._container = this._initContainer(map);
// 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("export:compute", (e) => {
self.dispatchEvent({
type : "export:compute",
content : e.content
});
});
}
// mode "draggable"
if (this.draggable) {
Draggable.dragElement(
this._panelRouteContainer,
this._panelHeaderRouteContainer,
map.getTargetElement()
);
}
}
// on appelle la méthode setMap originale d'OpenLayers
Control.prototype.setMap.call(this, map);
};
// ################################################################### //
// ##################### public methods ############################## //
// ################################################################### //
/**
* Returns true if widget is collapsed (minimized), false otherwise
*
* @returns {Boolean} collapsed - true if widget is collapsed
*/
Route.prototype.getCollapsed = function () {
return this.collapsed;
};
/**
* Collapse or display widget main container
*
* @param {Boolean} collapsed - True to collapse widget, False to display it
*/
Route.prototype.setCollapsed = function (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 {
document.getElementById("GPshowRoute-" + this._uid).click();
}
this.collapsed = collapsed;
};
/**
* Get vector layer where geoJson route is drawn
*
* @returns {Object} layer - ol.layer.Vector route layer
*/
Route.prototype.getLayer = function () {
return this._geojsonSections;
};
/**
* Set vector layer where route geometry is drawn
*
* @param {Object} layer - ol.layer.Vector route layer
*/
Route.prototype.setLayer = function (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
*/
Route.prototype.getGeoJSON = function () {
return JSON.stringify(this._geojsonObject);
};
/**
* Set vector layer
*
* @param {String} geojson - GeoJSON format layer
*/
Route.prototype.setGeoJSON = function (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
*/
Route.prototype.getData = function () {
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
*/
Route.prototype.setData = function (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;
// 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;
document.getElementById("GPlocationOriginCoords_" + id).value = "";
document.getElementById("GPlocationOrigin_" + id).value = "";
document.getElementById("GPlocationPoint_" + id).style.cssText = "";
if (i > 0 && i < 6) {
// on masque les points intermediaires
document.getElementById("GPlocationPoint_" + id).className = "GPflexInput GPlocationStageFlexInputHidden";
}
document.getElementById("GPlocationOriginPointer_" + id).checked = false;
document.getElementById("GPlocationOrigin_" + id).className = "GPlocationOriginVisible";
document.getElementById("GPlocationOriginCoords_" + id).className = "GPlocationOriginHidden";
}
}
// 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");
}
}
this._currentRouteInformations = data.results;
};
/**
* Get container
*
* @returns {DOMElement} container
*/
Route.prototype.getContainer = function () {
return this._container;
};
/**
* Get default style
*
* @returns {ol.style} style
*/
Route.prototype.getStyle = function () {
return this._defaultFeatureStyle;
};
/**
* This method is public.
* It allows to init the control.
*/
Route.prototype.init = function () {
// 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";
}
}
}
// 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 = "GProuteComponentHidden";
this._hideWaitingContainer();
this._resultsRouteContainer.className = "";
};
/**
* Clean UI : reinit control
*/
Route.prototype.clean = function () {
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 = "";
this._resultsRouteContainer.className = "GProuteComponentHidden";
};
// ################################################################### //
// ##################### init component ############################## //
// ################################################################### //
/**
* Initialize route control (called by Route constructor)
*
* @param {Object} options - constructor options
* @private
*/
Route.prototype.initialize = function (options) {
this._checkInputOptions(options);
// set default options
this.options = {
collapsed : true,
draggable : false,
export : false,
graphs : ["Voiture", "Pieton"],
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);
/** {Boolean} specify if Route control is collapsed (true) or not (false) */
this.collapsed = this.options.collapsed;
/** {Boolean} specify if Route control is draggable (true) or not (false) */
this.draggable = this.options.draggable;
this._uid = SelectorID.generate();
// containers principaux
this._panelRouteContainer = null;
this._panelHeaderRouteContainer = null;
this._waitingContainer = null;
this._formRouteContainer = null;
this._resultsRouteContainer = null;
this._showRouteExclusionsElement = null;
// liste de points selectionnée
this._currentPoints = [];
// Mode de transport selectionné : 'Voiture' ou 'Pieton'
this._currentTransport = null;
this._initTransport();
// Mode de calcul selectionné : 'Plus rapide' ou 'plus court'
this._currentComputation = null;
this._initComputation();
// Exclusions selectionnées : Tunnel, Toll et Bridge
this._currentExclusions = [];
this._initExclusions();
// si un calcul est en cours ou non
this._waiting = false;
// timer pour cacher la patience après un certain temps
this._timer = null;
// la geometrie du parcours
this._geojsonRoute = null;
// la geometrie des troncons
this._geojsonSections = null;
// la geometrie des troncons au format GeoJSON
this._geojsonObject = null;
// bouton export
this.export = null;
// le container de la popup (pour les troncons selectionnés)
this._popupContent = null;
this._popupDiv = this._initPopupDiv();
// l'overlay ol.Overlay correspondant à la popup (pour les troncons selectionnés)
this._popupOverlay = null;
// ol.interaction.Select associées à la couche des résultats (troncons)
this._resultsSelectInteraction = null;
this._resultsHoverInteraction = null;
// styles pour les sélections des features
this._defaultFeatureStyle = new Style({
stroke : new Stroke({
color : "rgba(0,183,152,0.9)",
width : 12
})
});
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}]
// }
this._currentRouteInformations = null;
// liste des ressources avec droits par service
// Ex. {
// "Route" : {
// key : "ger4g456re45er456t4er5ge5",
// resources : ["Pieton", "Voiture"]
// }
// }
this._resources = {};
// listener key for event on pointermove or moveend map
this.listenerKey = null;
};
/**
* this method is called by this.initialize()
*
* @param {Object} options - options
*
* @private
*/
Route.prototype._checkInputOptions = function (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 {DOMElement} DOM element
*
* @private
*/
Route.prototype._initContainer = function (map) {
// get main container
var container = this._container;
var inputShow = this._showRouteContainer = this._createShowRouteElement();
container.appendChild(inputShow);
// mode "collapsed"
if (!this.collapsed) {
inputShow.checked = true;
}
var picto = this._createShowRoutePictoElement();
container.appendChild(picto);
var routePanel = this._panelRouteContainer = this._createRoutePanelElement();
// header form
var routeHeader = this._panelHeaderRouteContainer = 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());
this._showRouteExclusionsElement = this._createShowRouteExclusionsPictoElement();
routeForm.appendChild(this._showRouteExclusionsElement);
var exclusion = this._createRoutePanelFormExclusionsElement();
exclusion.appendChild(this._createRoutePanelFormExclusionOptionsElement(this.options.exclusions));
routeForm.appendChild(exclusion);
var divReset = this._createRouteFormResetElement();
routeForm.appendChild(divReset);
// 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);
// 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
*/
Route.prototype._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 (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
*/
Route.prototype._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 exclusions
*
* @private
*/
Route.prototype._initExclusions = function () {
// 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)
*
* @return {Object} element - DOM element for popup
* @private
*/
Route.prototype._initPopupDiv = function () {
var context = this;
var element = document.createElement("div");
element.className = "gp-feature-info-div";
var closer = document.createElement("input");
closer.type = "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 {Object} map - the map
*
* @returns {Array} List DOM element
* @private
*/
Route.prototype._createRoutePanelFormPointsElement = function (map) {
var points = [];
var count = 1;
// point de depart
var start = new LocationSelector({
apiKey : this.options.apiKey || null,
tag : {
id : count,
groupId : this._uid,
markerOpts : this.options.markersOpts["departure"],
label : "Départ",
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(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,
groupId : this._uid,
label : "Etape",
markerOpts : this.options.markersOpts["stages"],
display : false,
removeOption : true
},
autocompleteOptions : this.options.autocompleteOptions || null
});
step.setMap(map);
this._addFormPointsEventListeners(step);
points.push(step._container);
this._currentPoints.push(step);
}
// point d'arrivée
var end = new LocationSelector({
apiKey : this.options.apiKey || null,
tag : {
id : count,
groupId : this._uid,
markerOpts : this.options.markersOpts["arrival"],
label : "Arrivée",
display : true,
addOption : true
},
autocompleteOptions : this.options.autocompleteOptions || null
});
end.setMap(map);
this._addFormPointsEventListeners(end);
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
*/
Route.prototype._addFormPointsEventListeners = function (formPoint) {
if (!formPoint) {
return;
}
if (formPoint._inputLabelContainer.addEventListener) {
// display form on origin label click
formPoint._inputLabelContainer.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._inputLabelContainer.attachEvent) {
// attachEvent: Internet explorer event listeners management
formPoint._inputLabelContainer.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
*/
Route.prototype.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.
// 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],
y : startCoordinate[1]
};
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],
y : endCoordinate[1]
};
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],
y : iCoordinate[1]
};
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
*/
Route.prototype.onRouteOriginLabelClick = function () {
this._formRouteContainer.className = "";
// 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 = "";
// }
// }
// );
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
*/
Route.prototype.onRouteOriginPointerClick = function (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";
// et au clic sur la carte, on réaffichera le formulaire "normal"
this.listenerKey = map.on(
"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 = "";
}
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 = "";
// }
// }
// );
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 {Object} e - HTMLElement
* @private
*/
Route.prototype.onShowRoutePanelClick = function (e) {
var map = this.getMap();
// on supprime toutes les interactions
Interactions.unset(map);
// clean !
if (!this._geojsonSections && !this._waiting) {
this._clear();
}
this.collapsed = document.getElementById("GPshowRoute-" + this._uid).checked;
// 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");
};
/**
* 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 {Object} e - HTMLElement
* @private
*/
Route.prototype.onRouteModeComputationChange = function (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 'GProuteResultsComputationSelect' tag select
* (cf. this._createRouteResultsElement).
* this value is saved as a parameter for the service route,
* and this launches the route request !
*
* @param {Object} e - HTMLElement
* @private
*/
Route.prototype.onRouteModeComputationChangeAndRun = function (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 {Object} e - HTMLElement
* @private
*/
Route.prototype.onRouteModeTransportChange = function (e) {
var value = e.target.value;
if (!value) {
return;
}
this._currentTransport = value;
};
/**
* TODO this method is called by event 'click' on 'GPshowRouteExclusionsPicto' tag input
* (cf. this._createShowRouteExclusionsPictoElement), and it displays the panel options of exclusions.
*
* @param {Object} e - HTMLElement
* @private
*/
Route.prototype.onShowRouteExclusionsClick = function (e) {
logger.log("onShowRouteExclusionsClick", e);
// FIXME not use ?!
};
/**
* this method is called by event 'change' on 'GProuteExclusionsToll'
* or 'GProuteExclusionsTunnel' or 'GProuteExclusionsBridge' tag input
* (cf. this._createRoutePanelFormExclusionOptionsElement).
* this value is saved as a parameter for the service route.
*
* @param {Object} e - HTMLElement
* @private
*/
Route.prototype.onRouteExclusionsChange = function (e) {
var value = e.target.value;
var checked = e.target.checked;