UNPKG

geoportal-extensions-openlayers

Version:

![GitHub package.json version](https://img.shields.io/github/package-json/v/IGNF/geoportal-extensions?filename=build%2Fscripts%2Frelease%2Fpackage-openlayers.json)

1,378 lines (1,201 loc) 62 kB
/* globals AmCharts, d3 */ // import CSS import "../CSS/Controls/ElevationPath/GPelevationPathOpenLayers.css"; // import OpenLayers import Control from "ol/control/Control"; import { Fill, Icon, Stroke, Style, Image, Circle } from "ol/style"; import { Point } from "ol/geom"; import { Draw as DrawInteraction } from "ol/interaction"; import { transform as olTransformProj } from "ol/proj"; import { getDistance as olGetDistanceSphere } from "ol/sphere"; import Feature from "ol/Feature"; import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; // import geoportal library access import Gp from "geoportal-access-lib"; // import local import Utils from "../../Common/Utils"; import Logger from "../../Common/Utils/LoggerByDefault"; import ID from "../../Common/Utils/SelectorID"; import Markers from "./Utils/Markers"; // import local with ol dependencies import Interactions from "./Utils/Interactions"; import MeasureToolBox from "./MeasureToolBox"; import Measures from "./Measures/Measures"; import LayerSwitcher from "./LayerSwitcher"; import ButtonExport from "./Export"; import GeoJSONExtended from "../Formats/GeoJSON"; // DOM import ElevationPathDOM from "../../Common/Controls/ElevationPathDOM"; import ProfileElevationPathDOM from "../../Common/Controls/ProfileElevationPathDOM"; var logger = Logger.getLogger("elevationpath"); /** * @classdesc * * Elevation Path Control. Allows users to draw a path on a Openlayers map see the elevation profile computed with geoportal elevation path web service along that path. * * @constructor * @alias ol.control.ElevationPath * @type {ol.control.ElevationPath} * @extends ol.control.Control * @param {Object} options - options for function call. * @param {String} [options.apiKey] - API key for services call (isocurve and autocomplete services). The key "calcul" is used by default. * @param {Boolean} [options.active = false] - specify if control should be actived at startup. Default is false. * @param {Boolean} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol) * @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.elevationOptions = {}] - elevation path service options. See {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~getAltitude Gp.Services.getAltitude()} for available options * @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 = "Profil altimétrique"] - Layer title to be displayed in LayerSwitcher * @param {String} [options.layerDescription.description = "Mon profil altimétrique"] - Layer description to be displayed in LayerSwitcher * @param {Object} [options.stylesOptions] - styles management * @param {Object} [options.stylesOptions.marker = {}] - styles management of marker displayed on map when the user follows the elevation path. Specified with an {@link https://openlayers.org/en/latest/apidoc/module-ol_style_Image-ImageStyle.html ol.style.Image} subclass object * @param {Object} [options.stylesOptions.draw = {}] - styles used when drawing. Specified with following properties. * @param {Object} [options.stylesOptions.draw.pointer = {}] - Style for mouse pointer when drawing the line. Specified with an {@link https://openlayers.org/en/latest/apidoc/module-ol_style_Image-ImageStyle.html ol.style.Image} subclass object. * @param {Object} [options.stylesOptions.draw.start = {}] - Line Style when drawing. Specified with an {@link https://openlayers.org/en/latest/apidoc/module-ol_style_Stroke-Stroke.html ol.style.Stroke} object. * @param {Object} [options.stylesOptions.draw.finish = {}] - Line Style when finished drawing. Specified with an {@link https://openlayers.org/en/latest/apidoc/module-ol_style_Stroke-Stroke.html ol.style.Stroke} object. * @param {Object} [options.displayProfileOptions = {}] - profile options. * @param {Boolean} [options.displayProfileOptions.totalDistance = true] - display the total distance of the path * @param {Boolean} [options.displayProfileOptions.greaterSlope = true] - display the greater slope into the graph * @param {Boolean} [options.displayProfileOptions.meanSlope = true] - display the mean slope into the graph * @param {Boolean} [options.displayProfileOptions.ascendingElevation = true] - display the ascending elevation into the graph * @param {Boolean} [options.displayProfileOptions.descendingElevation = true] - display the descending elevation into the graph * @param {Boolean} [options.displayProfileOptions.currentSlope = true] - display current slope value on profile mouseover * @param {Function} [options.displayProfileOptions.apply] - function to display profile if you want to cutomise it. By default, ([DISPLAY_PROFILE_BY_DEFAULT()](./ol.control.ElevationPath.html#.DISPLAY_PROFILE_BY_DEFAULT)) is used. Helper functions to use with D3 ([DISPLAY_PROFILE_LIB_D3()](./ol.control.ElevationPath.html#.DISPLAY_PROFILE_LIB_D3)) or AmCharts ([DISPLAY_PROFILE_LIB_AMCHARTS()](./ol.control.ElevationPath.html#.DISPLAY_PROFILE_LIB_AMCHARTS)) frameworks are also provided. You may also provide your own function. * @param {Object} [options.displayProfileOptions.target] - DOM container to use to display the profile. * @fires elevationpath:drawstart * @fires elevationpath:drawend * @fires elevationpath:compute * @fires export:compute * @example * * var measure = new ol.control.ElevationPath({ * export : false, * stylesOptions : { * draw : { * finish : new ol.style.Stroke({ * color : "rgba(0, 0, 0, 0.5)", * width : 2 * }) * }, * } * displayProfileOptions : { * apply : ol.control.ElevationPath.DISPLAY_PROFILE_RAW, * } * }); * * // if you want to pluggued the control Export with options : * var measure = new ol.control.ElevationPath({ * export : { * name : "export", * format : "geojson", * title : "Exporter", * menu : false * } * }); * * Exemples : * - displayProfileOptions.apply : null * - displayProfileOptions.apply : function (elevations, container, context) { // do some stuff... } * - displayProfileOptions.apply : ol.control.ElevationPath.DISPLAY_PROFILE_{LIB_AMCHARTS | LIB_D3 | RAW} * */ var ElevationPath = (function (Control) { /** * See {@link ol.control.ElevationPath} * @module ElevationPath * @alias module:~Controls/ElevationPath * @param {*} options - options * @example * import ElevationPath from "src/OpenLayers/Controls/ElevationPath" */ function ElevationPath (options) { logger.trace("ElevationPath()"); /** * options * @private */ options = options || {}; if (!(this instanceof ElevationPath)) { throw new TypeError("ERROR CLASS_CONSTRUCTOR"); } /** * Nom de la classe (heritage) * @private */ this.CLASSNAME = "ElevationPath"; // uuid this._uid = ID.generate(); // container : HTMLElement this._showContainer = null; this._pictoContainer = null; this._panelContainer = null; this._profileContainer = null; this._waitingContainer = null; this._infoContainer = null; // timer sur la fenetre d'informations des données this._timerHdlr = null; // objet de type "ol.style" this._drawStyleStart = null; this._drawStyleFinish = null; this._markerStyle = null; // graph this._profile = null; // data elevations this._data = {}; /* objet de type "ol.source.Vector", "ol.layer.Vector", "ol.interaction.Draw" */ this._measureSource = null; this._measureVector = null; this._measureDraw = null; // objet de type ol.feature, saisie en cours this._lastSketch = null; this._currentSketch = null; // objet de type ol.feature, marker this._marker = null; // initialisation du composant this._initialize(options); // creation du DOM container this._container = (options.element) ? options.element : this._initializeContainer(); // heritage Control.call(this, { element : this._container, target : options.target, render : options.render }); } // heritage avec ol.control.Control if (Control) ElevationPath.__proto__ = Control; /** * @lends module:ElevationPath */ ElevationPath.prototype = Object.create(Control.prototype, {}); // on récupère les mixins de la classe "ElevationPathDOM" Utils.assign(ElevationPath.prototype, ElevationPathDOM); /** * suppression du marker * * @param {Object} context - context * * @private */ ElevationPath.__removeProfileMarker = function (context) { var self = context; // suppression de l'ancien marker if (self._marker) { self._measureSource.removeFeature(self._marker); self._marker = null; } }; /** * suppression du marker * * @param {Object} context - context * @param {Object} d - d * * @private */ ElevationPath.__createProfileMarker = function (context, d) { var self = context; // suppression de l'ancien marker if (self._marker) { self._measureSource.removeFeature(self._marker); self._marker = null; } var map = self.getMap(); var proj = map.getView().getProjection(); var _coordinate = olTransformProj([d.lon, d.lat], "EPSG:4326", proj); var _coordinateProj = self._measureSource .getFeatures()[0] .getGeometry() .getClosestPoint(_coordinate); var _geometry = new Point(_coordinateProj); self._marker = new Feature({ geometry : _geometry }); logger.trace(_geometry); // style self._marker.setStyle(self._markerStyle); // ajout du marker sur la map self._measureSource.addFeature(self._marker); }; /** * mise à jour du marker * * @param {Object} context - context * @param {Object} d - data * * @private */ ElevationPath.__updateProfileMarker = function (context, d) { var self = context; ElevationPath.__removeProfileMarker(self); ElevationPath.__createProfileMarker(self, d); }; /** * TODO : customisation possible d'une opération sur le profil * * @param {Object} context - context * @param {Object} d - data * * @private */ ElevationPath.__customRawProfileOperation = function (context, d) { logger.log("__customRawProfileOperation"); var self = context; var _pts = d.points; var _proj = self.getMap().getView().getProjection(); for (var i = 0; i < _pts.length; i++) { var obj = _pts[i]; var _coordinate = olTransformProj([obj.lon, obj.lat], "EPSG:4326", _proj); var _geometry = new Point(_coordinate); self._marker = new Feature({ geometry : _geometry }); logger.trace(_geometry); // TODO style en options ? var styles = ElevationPath.DEFAULT_STYLES.RESULTS; var _image = new Circle({ radius : styles.imageRadius, stroke : new Stroke({ color : styles.imageStrokeColor, width : styles.imageStrokeWidth }), fill : new Fill({ color : styles.imageFillColor }) }); self._marker.setStyle(new Style({ image : _image })); // ajout du marker sur la map self._measureSource.addFeature(self._marker); } }; /** * TODO : customisation possible d'une opération sur le profil * Ex. Methode appélée dans le DOM : ProfileElevationPathDOM * * @param {Object} context - context * @param {Object} e - event * @private */ ElevationPath.__customRawProfileMouseOverEvent = function (context, e) { logger.log("__customRawProfileMouseOverEvent", context, e); }; /** * display Profile using Amcharts framework. This method needs AmCharts libraries to be loaded. * * @param {Object} data - collection elevations * @param {HTMLElement} container - container * @param {Object} context - this control object */ ElevationPath.DISPLAY_PROFILE_LIB_AMCHARTS = function (data, container, context) { logger.trace("ElevationPath.DISPLAY_PROFILE_LIB_AMCHARTS"); // Calcul du profile if (typeof AmCharts === "undefined") { logger.log("Lib. AmCharts is not loaded !"); return; } var profile = ProfileElevationPathDOM.displayProfileLibAmCharts(data, container, context, ElevationPath); // on sauvegarde le profil du container dans l'objet if (profile) { this._profile = profile; } }; /** * display Profile using D3 javascript framework. This method needs D3 libraries to be loaded. * * @param {Object} data - elevations values for profile * @param {HTMLElement} container - html container where to display profile * @param {Object} context - this control object */ ElevationPath.DISPLAY_PROFILE_LIB_D3 = function (data, container, context) { logger.trace("ElevationPath.DISPLAY_PROFILE_LIB_D3"); // Calcul du profile if (typeof d3 === "undefined") { logger.log("Lib. D3 is not loaded !"); return; } var profile = ProfileElevationPathDOM.displayProfileLibD3(data, container, context, ElevationPath); // on sauvegarde le profil du container dans l'objet if (profile) { this._profile = profile; } }; /** * display Profile without graphical rendering (raw service response) * * @param {Object} data - elevations values for profile * @param {HTMLElement} container - html container where to display profile * @param {Object} context - this control object */ ElevationPath.DISPLAY_PROFILE_RAW = function (data, container, context) { logger.trace("ElevationPath.DISPLAY_PROFILE_RAW"); var profile = ProfileElevationPathDOM.displayProfileRaw(data, container, context, ElevationPath); // on sauvegarde le profil du container dans l'objet if (profile) { this._profile = profile; } }; /** * Display Profile function used by default : no additonal framework needed. * * @param {Object} data - elevations values for profile * @param {HTMLElement} container - html container where to display profile * @param {Object} context - this control object */ ElevationPath.DISPLAY_PROFILE_BY_DEFAULT = function (data, container, context) { logger.trace("ElevationPath.DISPLAY_PROFILE_BY_DEFAULT"); var profile = ProfileElevationPathDOM.displayProfileByDefault(data, container, context, ElevationPath); // on sauvegarde le profil du container dans l'objet if (profile) { this._profile = profile; } }; /** * Styles applied by default if stylesOptions property is not set. */ ElevationPath.DEFAULT_STYLES = { // styling drawing by default // see => Measures.DEFAULTS_STYLES // stying marker to the profile by default MARKER : new Icon({ src : Markers["lightOrange"], // image avec un mauvais ratio size 51/38 pixels // src : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAsCAYAAAAATWqyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABTtJREFUeNq8WGtsFUUU/rb3gtdCAykFG9AUDTQUKimhxUewEusrJYoBo4FfEgoqotHERH6oP9TGmJhIrIlWAf9hjAaEiME2pgFfVVpFii8sWqIQLLSx3EJLW7p+Z2Z2b2l7d/b23vZLTmZ2duacb2fmnDk7DlKA67rXs1hJKacsohRQppjXFygnKT9TDlH2O47zFzIFGnco91EOuqnjoBnr2Ow4FhIlLN6m3DykFTh3BGj/Doj/CfSe082xPCDnBmDWTUBeyXDVjZTHOUNHUiZCEs+weI0ySTV0/w0c2wa07gIungn+vOx8YN46oPhpYOp1Xms/5TmSeSMUERKImFnYqBoGuPRNL5LEW8BgX2rrmjWZZLYApS8BUW8r4T0zO5eTEjFr+S6lSjV0HgPqVwNdf6S30abNB+7aDeQWey3bKZtIxvU5DxvyrE/izJfAvuXpkxCIDtElOjWqjK2RM8LZWMbiG0oEnUc5kB7a14WMYvI04H56du5ieZKluZWz8r0/IyQh5TuKRH8cqFuTeRIC0Sm6xYbYok1j21+ahyhLVO3wC8D5VowbRLfY0FhibOulIavDLEoRZyD8sJDeMWBXKG5ZsIobsdDsg+OMq3u1m1u9KQo8zP45EqjRxOUpk6i50IRl4FuGjpZtwUoiMYa314GFj/EzIsN8n8v+C1e4kfvwcm+wnhsZY27xQ8oiWZpKrWRQB6tAElfxpKnjsCdGklDzG9HvpI/0DYLYEpsalVnmAAM6fgR62oMHl70C5N9mn3rpI32DILbEpkZ5ljlFgbPNFtebzij5VPhNKX1lTBASNtXSzPZ3cxCuvVOH7FTCu4yxeZDGbCES0z5+PniQ3uGpwTYmYTOWCPGTpgYP6u9OnYhtzBCbQkSH0NiM4EEdP6VOxDYmYbNLiJxQ1elFwYPaG3XQCn3QHddjgpCweUKI6K2bvzw4YROf//rJob6fZl/H2FRoFiINfqo3qyzYwD8MVIeYLw32J+8j76SP9A2C2BKbGg1CZL+EF/W4YKP9a3/fCeyhkrY9DOOXEu1SlzZ5J31sSNjqURm/OfQkY9qgvkYOvXhbuH0g505Oga7HT9rPF9+t5+pDL0ulwzt46FV5ROax+JUSRRtP0LoHMK64+xNg7iqVEVOKSKRVxRGpsKhRnaRD4SPjR0J0axKCGmP7ilQxm4X8d8xXmfvHJZlPkCR3WfODl9FLMlxCIhevSJ5Nwzo1XdKxYpe3hpmB6BKdmoS43VqPxIgsni+aWOg8biZ3f+nLmSMiuvKWek/P01az7QdLyNVT7lC/l59WAKcb0iMxhzpW1nvmvpDtSiKD1l9OkpnDgv8UyMWFU9wvTP8vdY6NhJwnD1JVtso2OiiLSeL0iJUbNfg6zikVVwRTyOn2HWOfjfLtHgnBhtFIJCViyNDZUatdmnGlaFPqJIoe1WM1aqlz71ivJbLNobgAA9zgu7nZ/vstHAk5WVdzaPRqmGC5lER6kjpV4OWJdq+1kkshSk4VH9izcy/bV66qSPQZV+0J9G7rTY6+XNmqHmYwyJVV24kse1X31dhKHdasygkzy+a64oC4nWr47F4e858nSbLv4V/KAe9JKpVDrx/SImLIXMOiRUKdujESl+49O8xVZxpXzVc/C/I/RxL/hgq8YYkYhev9q6kVO4d9B+sr3vdICNaHJTHWW8Ya/87wqy2uWwstUk/gTYw3aCRGOarMDfS67kfFWqSuIe9imAjQEC272nJHixYNaSvGRIIGN49ywbsZEw1zI11N6TZSHeaGORn+F2AAJtRIMx4t+hUAAAAASUVORK5CYII=", anchor : [0.5, 1], snapToPixel : true }), // styling service results points by default RESULTS : { // INFO orienté maintenance ! imageRadius : 5, imageFillColor : "rgba(128, 128, 128, 0.2)", imageStrokeColor : "rgba(0, 0, 0, 0.7)", imageStrokeWidth : 2 } // FIXME ??? // PROFILE : { // type : "serial", // pathToImages : "http://cdn.amcharts.com/lib/3/images/", // categoryField : "dist", // autoMarginOffset : 0, // marginRight : 10, // marginTop : 10, // startDuration : 0, // color : "#5E5E5E", // fontSize : 10, // theme : "light", // thousandsSeparator : "", // categoryAxis : { // color : "#5E5E5E", // gridPosition : "start", // minHorizontalGap : 40, // tickPosition : "start", // title : "Distance (km)", // titleColor : "#5E5E5E", // startOnAxis : true // }, // chartCursor : { // animationDuration : 0, // bulletsEnabled : true, // bulletSize : 10, // categoryBalloonEnabled : false, // cursorColor : "#F90", // graphBulletAlpha : 1, // graphBulletSize : 1, // zoomable : false // }, // trendLines : [], // graphs : [ // { // balloonColor : "#CCCCCC", // balloonText : "<span class='altiPathValue'>[[title]] : [[value]]m</span><br/><span class='altiPathCoords'>(lat: [[lat]] / lon:[[lon]])</span>", // bullet : "round", // bulletAlpha : 0, // bulletBorderColor : "#FFF", // bulletBorderThickness : 2,currentSlope // bulletColor : "#F90", // bulletSize : 6, // hidden : false, // id : "AmGraph-1", // fillAlphas : 0.4, // fillColors : "#C77A04", // lineAlpha : 1, // lineColor : "#C77A04", // lineThickness : 1, // title : "Altitude", // valueField : "z" // } // ], // guides : [], // valueAxes : [ // { // id : "ValueAxis-1", // minVerticalGap : 20, // title : "Altitude (m)" // } // ], // allLabels : [], // balloon : { // borderColor : "#CCCCCC", // borderThickness : 1, // fillColor : "#FFFFFF", // showBullet : true // }, // titles : [] // } }; /** * Constructor (alias) * @private */ ElevationPath.prototype.constructor = ElevationPath; // ################################################################### // // ##################### public methods ############################## // // ################################################################### // /** * Attach control to map. Overloaded ol.control.Control.setMap() method. * * @param {ol.Map} map - Map. */ ElevationPath.prototype.setMap = function (map) { logger.trace("ElevationPath::setMap"); if (map) { // activation des interactions de dessin selon la valeur de // l'option active if (this.options.active) { // on n'affiche pas la fenetre de profile s'il n'existe pas... if (this._profile === null) { this._panelContainer.style.display = "none"; // this._panelContainer.style.visibility = "hidden"; } this._initMeasureInteraction(map); this._addMeasureInteraction(map); } // ajout du composant dans une toolbox if (!this.options.target) { MeasureToolBox.add(map, this); } // 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 }); }); } } // on appelle la méthode setMap originale d'OpenLayers Control.prototype.setMap.call(this, map); }; /** * Returns true if widget is actived (drawing), * false otherwise * * @returns {Boolean} active - true or false */ ElevationPath.prototype.getActive = function () { logger.trace("ElevationPath::getActive"); return this.options.active; }; /** * Actived widget drawing or not * * @param {Boolean} active - true / false */ ElevationPath.prototype.setActive = function (active) { logger.trace("ElevationPath::setActive"); this.options.active = active; }; /** * Get elevation data * * @returns {Object} data - elevations * @example * { * type // "elevationpath" * greaterSlope // pente max * meanSlope // pente moyenne * distancePlus // distance cumulée positive * distanceMinus // distance cumulée négative * ascendingElevation // dénivelé cumulée positive * descendingElevation // dénivelé cumulée négative * altMin // altitude min * altMax // altitude max * distance // distance totale * unit // unité des mesures de distance * points // elevations * } */ ElevationPath.prototype.getData = function () { return Utils.assign({ type : "elevationpath" }, this._data); }; /** * Set profile data * * @param {*} data - elevations * @example * { * greaterSlope // pente max * meanSlope // pente moyenne * distancePlus // distance cumulée positive * distanceMinus // distance cumulée négative * ascendingElevation // dénivelé cumulée positive * descendingElevation // dénivelé cumulée négative * altMin // altitude min * altMax // altitude max * distance // distance totale * unit // unité des mesures de distance * points // elevations * } */ ElevationPath.prototype.setData = function (data) { this._data = data; }; /** * Get container * * @returns {DOMElement} container */ ElevationPath.prototype.getContainer = function () { return this._container; }; /** * Get layer * * @returns {ol.layer.Vector} layer */ ElevationPath.prototype.getLayer = function () { return this._measureVector; }; /** * Set layer * * @param {Object} layer - ol.layer.Vector profil layer */ ElevationPath.prototype.setLayer = function (layer) { if (!layer) { this._measureVector = null; return; } if (!(layer instanceof VectorLayer)) { logger.log("no valid layer given for hosting drawn features."); return; } // application des styles layer.setStyle(this._drawStyleFinish); // sauvegarde this._measureVector = layer; this._measureSource = layer.getSource(); }; /** * Get vector layer * * @returns {String} geojson - GeoJSON format layer */ ElevationPath.prototype.getGeoJSON = function () { var features = this._measureVector.getSource().getFeatures(); var Format = new GeoJSONExtended({ defaultStyle : this._drawStyleFinish }); // INFO // par defaut, webmercator ou "EPSG:3857" var geojson = Format.writeFeatures(features, { dataProjection : "EPSG:4326", featureProjection : "EPSG:3857" }); return geojson; }; /** * Get default style * * @returns {ol.style} style */ ElevationPath.prototype.getStyle = function () { return this._drawStyleFinish; }; /** * clean * @param {Boolean} remove - remove layer */ ElevationPath.prototype.clean = function (remove) { logger.trace("ElevationPath::clean"); var map = this.getMap(); // fenetre du profil this._panelContainer.style.display = "none"; // this._panelContainer.style.visibility = "hidden"; // picto this._showContainer.checked = false; // this._removeMeasure(); this._removeProfile(); this._removeMeasureInteraction(map, typeof remove !== "undefined" ? remove : false); this.setLayer(); }; /** * This method is public. * It allows to init the control. * @fixme */ ElevationPath.prototype.init = function () { // FIXME // le panneau du profil ne peut pas afficher un profil si il est caché // car le profil est calculé en fonction de la taille du panneau (clientHeight / clientWidth), // et ces valeurs sont à 0 !? this._showContainer.checked = true; this._panelContainer.style.display = "block"; this._displayProfile(this._data); this._waitingContainer.className = "GPelevationPathCalcWaitingContainerHidden"; }; // ################################################################### // // ##################### init component ############################## // // ################################################################### // /** * Initialize control (called by constructor) * * @param {Object} options - options * * @private */ ElevationPath.prototype._initialize = function (options) { logger.trace("ElevationPath::_initialize : ", options); // liste des options this.options = { target : null, render : null, active : false, apiKey : null, export : false, elevationOptions : { outputFormat : "json" }, layerDescription : { title : "Profil altimétrique", description : "Mon profil altimétrique" }, displayProfileOptions : { totalDistance : true, greaterSlope : true, meanSlope : true, ascendingElevation : true, descendingElevation : true, currentSlope : true, apply : null, target : null }, stylesOptions : { profile : null, draw : null, marker : null } }; // merge with user options Utils.mergeParams(this.options, options); this.options.target = options.target || null; // this.options.render = options.render || null; // cle API sur le service this.options.apiKey = options.apiKey; // gestion de l'affichage du profil var _profile = options.displayProfileOptions || {}; // bouton export this.export = null; // gestion de la fonction du profil var displayFunction = _profile.apply; this.options.displayProfileOptions.apply = (typeof displayFunction === "function") ? displayFunction : ElevationPath.DISPLAY_PROFILE_BY_DEFAULT; // gestion du container du profil var displayContainer = _profile.target; this.options.displayProfileOptions.target = (typeof displayContainer !== "undefined") ? displayContainer : null; // gestion des styles var _styles = options.stylesOptions || {}; // FIXME ??? // gestion du style du profil // var profileStyle = _styles.profile; // this.options.stylesOptions.profile = ( typeof profileStyle === "undefined" || Object.keys(profileStyle).length === 0 ) ? // ElevationPath.DEFAULT_STYLES.PROFILE : profileStyle; // this._createStylingProfile(); // gestion des styles du tracé this.options.stylesOptions.draw = _styles.draw || {}; this._createStylingDraw(); // gestion des styles du marker this.options.stylesOptions.marker = _styles.marker || {}; this._createStylingMarker(); }; /** * initialize component container (DOM) * * @returns {DOMElement} DOM element * * @private */ ElevationPath.prototype._initializeContainer = function () { logger.trace("ElevationPath::_initializeContainer : ", this._uid); // create main container var container = this._createMainContainerElement(); var inputShow = this._showContainer = this._createShowElevationPathElement(); container.appendChild(inputShow); var picto = this._pictoContainer = this._createShowElevationPathPictoElement(); container.appendChild(picto); // mode "active" if (this.options.active) { this._showContainer.checked = true; } // panneau var panel = this._panelContainer = this._createElevationPathPanelElement(); // header var header = this._createElevationPathPanelHeaderElement(); panel.appendChild(header); // profile var profile = this._profileContainer = this._createElevationPathPanelProfilElement(); panel.appendChild(profile); // waiting var waiting = this._waitingContainer = this._createElevationPathWaitingElement(); panel.appendChild(waiting); // info var info = this._infoContainer = this._createElevationPathInformationsElement(); panel.appendChild(info); if (this.options.displayProfileOptions.target === null) { container.appendChild(panel); } return container; }; // ################################################################### // // ###################### init styles ################################ // // ################################################################### // /** * create style marker object : "ol.style" * * @private */ ElevationPath.prototype._createStylingMarker = function () { logger.trace("ElevationPath::_createStylingMarker "); var marker = ElevationPath.DEFAULT_STYLES.MARKER; logger.trace("style marker", marker); // si marker n'est pas un objet ol.style.Image, on applique le // style par défaut. if (this.options.stylesOptions.marker instanceof Image) { marker = this.options.stylesOptions.marker; } this._markerStyle = new Style({ image : marker }); }; /** * create style draw object : "ol.style" * * @private */ ElevationPath.prototype._createStylingDraw = function () { logger.trace("ElevationPath::_createStylingDraw"); // on interprete les params pour y creer un objet ol.Style var styles = this.options.stylesOptions.draw; // style de depart logger.trace("style start", styles.start); // Creation à partir des styles par défaut var startStyleOpts = { image : Measures.DEFAULT_POINTER_STYLE, stroke : Measures.DEFAULT_DRAW_START_STYLE.getStroke() }; // ecrasement à partir des propriétés renseignées if (styles.hasOwnProperty("pointer") && styles.pointer instanceof Image) { startStyleOpts.image = styles.pointer; } if (styles.hasOwnProperty("start") && styles.start instanceof Stroke) { startStyleOpts.stroke = styles.start; } this._drawStyleStart = new Style(startStyleOpts); // style de fin logger.trace("style finish", styles.finish); var finishStyleOpts = { stroke : Measures.DEFAULT_DRAW_FINISH_STYLE.getStroke() }; // ecrasement à partir des propriétés renseignées if (styles.hasOwnProperty("finish") && styles.finish instanceof Stroke) { finishStyleOpts.stroke = styles.finish; } this._drawStyleFinish = new Style(finishStyleOpts); }; /** * create style graph * FIXME : à revoir car ne sert que pour AmCharts !? * * @private */ ElevationPath.prototype._createStylingProfile = function () { logger.trace("ElevationPath::_createStylingProfile"); var userStyles = this.options.stylesOptions.profile; logger.trace("style profile", userStyles); var defaultStyle = ElevationPath.DEFAULT_STYLES.PROFILE; Object.keys(defaultStyle).forEach((key) => { if (!userStyles.hasOwnProperty(key)) { // si le style user n'existe pas, on ajoute celui par defaut userStyles[key] = defaultStyle[key]; } else { var _defaultStyle = defaultStyle[key]; if (typeof _defaultStyle === "object") { // on merge avec un objet, // les styles user ecrasent ceux par defaut... Utils.mergeParams(_defaultStyle, userStyles[key]); userStyles[key] = _defaultStyle; } } }); }; // ################################################################### // // ################### Map interactions management ################### // // ################################################################### // /** * this method is called by this.onShowElevationPathClick, * and initialize a vector layer, if widget is active. * * @param {ol.Map} map - Map * @private */ ElevationPath.prototype._initMeasureInteraction = function (map) { logger.trace("ElevationPath::_initMeasureInteraction()"); // var map = this.getMap(); if (!map) { return; } this._measureSource = new VectorSource(); this._measureVector = new VectorLayer({ source : this._measureSource, style : this._drawStyleFinish }); // on rajoute le champ gpResultLayerId permettant d'identifier une couche crée par le composant. this._measureVector.gpResultLayerId = "measure:profil"; map.addLayer(this._measureVector); // Si un layer switcher est présent dans la carte, on lui affecte des informations pour cette couche map.getControls().forEach( (control) => { if (control instanceof LayerSwitcher) { // un layer switcher est présent dans la carte var layerId = this._measureVector.gpLayerId; // on n'ajoute des informations que s'il n'y en a pas déjà (si le titre est le numéro par défaut) if (control._layers[layerId].title === layerId) { control.addLayer( this._measureVector, { title : this.options.layerDescription.title, description : this.options.layerDescription.description } ); } } } ); }; /** * this method is called by this.onShowElevationPathClick, * and add draw interaction to map, if widget is not active. * * @param {ol.Map} map - Map * @private */ ElevationPath.prototype._addMeasureInteraction = function (map) { logger.trace("ElevationPath::_addMeasureInteraction()"); // var map = this.getMap(); if (!map) { return; } // Creates and adds the interaction this._measureDraw = new DrawInteraction({ source : this._measureSource, type : "LineString", style : this._drawStyleStart, stopClick : true }); this._measureDraw.setProperties({ name : "ElevationPath", source : this }); map.addInteraction(this._measureDraw); // Event start this._measureDraw.on("drawstart", (evt) => { logger.trace("drawstart", evt); // delete marker current if (this._marker !== null) { this._measureSource.removeFeature(this._marker); this._marker = null; } // set new feature and remove last feature if (this._lastSketch !== null) { this._measureSource.removeFeature(this._lastSketch); this._lastSketch = null; } this._currentSketch = evt.feature; // and, all features var _features = this._measureSource.getFeatures(); for (var i = 0; i < _features.length; i++) { this._measureSource.removeFeature(_features[i]); } /** * event triggered at the start of drawing input * @event elevationpath:drawstart */ this.dispatchEvent("elevationpath:drawstart"); }); // Event end this._measureDraw.on("drawend", (evt) => { logger.trace("drawend", evt); /** * event triggered at the end of drawing input * @event elevationpath:drawend */ this.dispatchEvent("elevationpath:drawend"); // set feature this._lastSketch = this._currentSketch; // Si il n'y a pas de surcharge utilisateur de la fonction de recuperation des // resultats, on realise l'affichage du panneau if (typeof this.options.elevationOptions.onSuccess === "undefined" && this.options.displayProfileOptions.target === null) { this._panelContainer.style.display = "block"; // self._panelContainer.style.visibility = "visible"; } // set an alti request and display results this._measureDraw.setActive(false); this._requestService(); }); }; /** * this method is called by this.onShowElevationPathClick, * and removes draw interaction from map (if exists) * And removes layer too... * * @param {ol.Map} map - Map * @param {Boolean} remove - Remove layer * @private */ ElevationPath.prototype._removeMeasureInteraction = function (map, remove) { logger.trace("ElevationPath::_removeMeasureInteraction()"); // var map = this.getMap(); if (!map) { return; } if (remove) { if (this._measureVector) { map.removeLayer(this._measureVector); this._measureVector = null; } } if (this._measureDraw) { map.removeInteraction(this._measureDraw); this._measureDraw = null; } }; // ################################################################### // // ############################ Alti request ######################### // // ################################################################### // /** * transforme geometry feature to position coordinate (service) * * @returns {Object[]} geometry * * @private */ ElevationPath.prototype._getGeometry = function () { // INFO // on transmet toujours des coordonnées au service en EPSG:4326 if (this._currentSketch === null) { logger.warn("Current Feature undefined !?"); return; } var geometry = []; var map = this.getMap(); var projSrc = map.getView().getProjection(); var projDest = "EPSG:4326"; var coordinates = this._currentSketch.getGeometry().getCoordinates(); for (var i = 0; i < coordinates.length; i++) { var xy = coordinates[i]; var ll = xy; // on transmet au service des coordonnées en EPSG:4326 if (projSrc !== projDest) { ll = olTransformProj(xy, projSrc, projDest); } geometry.push({ lon : Math.round(ll[0] * 1e8) / 1e8, lat : Math.round(ll[1] * 1e8) / 1e8 }); } return geometry; }; /** * get geometry feature length * * @returns {Integer} length * * @private */ ElevationPath.prototype._getLength = function () { if (this._currentSketch === null) { logger.warn("Current Feature undefined !?"); return; } var length = 0; var map = this.getMap(); var projSrc = map.getView().getProjection(); var projDest = "EPSG:4326"; var coordinates = this._currentSketch.getGeometry().getCoordinates(); for (var i = 0, ii = coordinates.length - 1; i < ii; ++i) { var c1 = olTransformProj(coordinates[i], projSrc, projDest); var c2 = olTransformProj(coordinates[i + 1], projSrc, projDest); c1[0] = Math.round(c1[0] * 1e8) / 1e8; c1[1] = Math.round(c1[1] * 1e8) / 1e8; c2[0] = Math.round(c2[0] * 1e8) / 1e8; c2[1] = Math.round(c2[1] * 1e8) / 1e8; length += olGetDistanceSphere(c1, c2); } return length; }; /** * get geometry feature point coords in EPSG:4326 [lon, lat] * * @returns {Array} point coords in EPSG:4326 [lon, lat] * * @private */ ElevationPath.prototype._getSketchCoords = function () { if (this._currentSketch === null) { logger.warn("Current Feature undefined !?"); return; } var map = this.getMap(); var projSrc = map.getView().getProjection(); var projDest = "EPSG:4326"; var pointCoords = []; var coordinates = this._currentSketch.getGeometry().getCoordinates(); for (var i = 0; i < coordinates.length; i++) { var c1 = olTransformProj(coordinates[i], projSrc, projDest); c1[0] = Math.round(c1[0] * 1e8) / 1e8; c1[1] = Math.round(c1[1] * 1e8) / 1e8; pointCoords.push(c1); } return pointCoords; }; /** * this method is called at the end of the path, * it generates and sends alti request, then displays results * * @private */ ElevationPath.prototype._requestService = function () { logger.trace("ElevationPath::_requestService"); // les coordonnées sont obligatoires var geometry = this._getGeometry(); logger.trace("geometry", geometry); if (!geometry) { logger.warn("missing geometry !?"); return; } // on construit les options pour la requête var options = {}; // on surcharge avec les options de l'utilisateur Utils.mergeParams(options, this.options.elevationOptions); // au cas où ... Utils.mergeParams(options, { apiKey : options.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) if (typeof options.ssl !== "boolean") { if (typeof this.options.ssl === "boolean") { options.ssl = this.options.ssl; } else { options.ssl = true; } } Utils.mergeParams(options, { ssl : options.ssl }); // les callbacks var self = this; // gestion des callback var bOnFailure = !!(this.options.elevationOptions.onFailure !== null && typeof this.options.elevationOptions.onFailure === "function"); // cast variable to boolean var bOnSuccess = !!(this.options.elevationOptions.onSuccess !== null && typeof this.options.elevationOptions.onSuccess === "function"); // callback _requestServiceOnSuccess var _requestServiceOnSuccess = function (result) { logger.trace(result); if (result) { self._panelContainer.style.display = "block"; // self._panelContainer.style.visibility = "visible"; if (self._data) { self._data = {}; } self._data = self._computeElevationMeasure(result.elevations); self._displayProfile(self._data); self._waitingContainer.className = "GPelevationPathCalcWaitingContainerHidden"; self._waiting = false; self._measureDraw.setActive(true); } if (bOnSuccess) { self.options.elevationOptions.onSuccess.call(self, self.getData()); } }; // callback _requestServiceOnFailure var _requestServiceOnFailure = function (error) { // on ferme le panneau en cas d'erreur ! self._panelContainer.style.display = "none"; // self._panelContainer.style.visibility = "hidden"; logger.error(error.message); self._waitingContainer.className = "GPelevationPathCalcWaitingContainerHidden"; self._waiting = false; self._measureDraw.setActive(true); if (bOnFailure) { self.options.elevationOptions.onFailure.call(self, error); } }; Utils.mergeParams(options, { onSuccess : _requestServiceOnSuccess, onFailure : _requestServiceOnFailure }); // le sampling est soit defini par l'utilisateur (opts), // ou soit calculé dynamiquement... var sampling = options.sampling || 200; var p = Math.max(50, Math.floor(this._getLength()) / 5); if (p > 200) { sampling = 200; } else { sampling = Math.floor(p); } if (sampling > 0) { Utils.mergeParams(options, { sampling : sampling }); } // et enfin, la geometrie Utils.mergeParams(options, { positions : geometry }); logger.trace("options du service", options); // mise en place de la patience this._waitingContainer.className = "GPelevationPathCalcWaitingContainerVisible"; // Request altitude service Gp.Services.getAltitude(options); }; // ################################################################### // // ########################## Profil display ######################### // // ################################################################### // /** * this method computes results elevations (Z and distance) * * @param {Array} elevations - array of elevation * @return {Array} elevations * @private */ ElevationPath.protot