UNPKG

geopf-extensions-openlayers

Version:

French Geoportal Extensions for OpenLayers libraries

1,253 lines (1,159 loc) 94.7 kB
// import CSS import "../../CSS/Controls/Drawing/GPFdrawing.css"; // import "../../CSS/Controls/Drawing/GPFdrawingStyle.css"; // import OpenLayers // import Control from "ol/control/Control"; import Widget from "../Widget"; import Control from "../Control"; import { unByKey as olObservableUnByKey } from "ol/Observable"; import Collection from "ol/Collection"; import Overlay from "ol/Overlay"; import { transform as olTransformProj } from "ol/proj"; import Map from "ol/Map"; import VectorSource from "ol/source/Vector"; import VectorLayer from "ol/layer/Vector"; import Feature from "ol/Feature"; import { Fill, Icon, Stroke, Style, Text, Circle } from "ol/style"; import { LineString, LinearRing, Point, Polygon, MultiLineString, MultiPoint, MultiPolygon } from "ol/geom"; import { Select as SelectInteraction, Modify as ModifyInteraction, Draw as DrawInteraction } from "ol/interaction"; import { singleClick as eventSingleClick, pointerMove as eventPointerMove } from "ol/events/condition"; import { getArea as olGetAreaSphere, getDistance as olGetDistanceSphere } from "ol/sphere"; // import local import Logger from "../../Utils/LoggerByDefault"; import Interactions from "../Utils/Interactions"; import MarkersOther from "../Utils/MarkersOther"; import Draggable from "../../Utils/Draggable"; import SelectorID from "../../Utils/SelectorID"; import Color from "../../Utils/ColorUtils"; // DOM import DrawingDOM from "./DrawingDOM"; // import local with ol dependencies import KMLExtended from "../../Formats/KML"; import GeoJSONExtended from "../../Formats/GeoJSON"; import GPXExtended from "../../Formats/GPX"; import LayerSwitcher from "../LayerSwitcher/LayerSwitcher"; var logger = Logger.getLogger("Drawing"); /** * @typedef {Object} DrawingOptions * @property {string|number} [id] - Identifiant unique du widget. * @property {boolean} [collapsed=true] - Définit si le widget est replié au démarrage. * @property {boolean} [draggable=false] - Permet de déplacer le panneau du widget. * @property {ol.layer.Vector} [layer] - Couche vecteur OpenLayers pour héberger les objets dessinés. * @property {Object} [popup] - Options de la popup d’édition. * @property {boolean} [popup.display=true] - Affiche la popup à la création d’un dessin. * @property {Function} [popup.function] - Fonction personnalisée pour afficher la popup. * @property {Object} [layerDescription] - Métadonnées pour le LayerSwitcher. * @property {string} [layerDescription.title="Croquis"] - Titre de la couche de dessin. * @property {string} [layerDescription.description="Mon croquis"] - Description de la couche de dessin. * @property {Object} [tools] - Outils à afficher dans la barre d’outils. * @property {boolean} [tools.points=true] - Outil point. * @property {boolean} [tools.lines=true] - Outil ligne. * @property {boolean} [tools.polygons=true] - Outil polygone. * @property {boolean} [tools.holes=false] - Outil polygone avec trous. * @property {boolean} [tools.text=true] - Outil texte. * @property {boolean} [tools.remove=true] - Outil suppression. * @property {boolean} [tools.display=true] - Outil style. * @property {boolean} [tools.tooltip=true] - Outil info-bulle. * @property {boolean} [tools.edit=true] - Outil édition. * @property {boolean} [tools.export=true] - Outil export. * @property {boolean} [tools.measure=false] - Outil mesure. * @property {Object} [labels] - Libellés personnalisés pour les outils et le contrôle. * @property {Array<Object>} [markersList] - Liste des marqueurs personnalisés (src, anchor, etc.). * @property {Object} [defaultStyles] - Styles par défaut pour les objets dessinés. * @property {Object} [cursorStyle] - Style du curseur lors du dessin. * @property {string} [position] - Position CSS du widget sur la carte. * @property {boolean} [gutter] - Ajoute ou retire l’espace autour du panneau. */ /** * @classdesc * * Drawing Control. * * @alias ol.control.Drawing * @module Drawing * */ class Drawing extends Control { /** * @constructor * @param {Object} options - options for function call. * @param {Number} [options.id] - Ability to add an identifier on the widget (advanced option) * @param {Boolean} [options.collapsed = true] - Specify if Drawing control should be collapsed at startup. Default is true. * @param {Boolean} [options.draggable = false] - Specify if widget is draggable * @param {Object} [options.layer = {}] - Openlayers layer that will hosts created features. If none, an empty vector layer will be created. * @param {Object} [options.popup = {}] - Popup informations * @param {Boolean} [options.popup.display = true] - Specify if popup is displayed when create a drawing * @param {Function} [options.popup.function] - Function to display popup informations if you want to cutomise it. You may also provide your own function with params : {geomType / feature / saveFunc(message) / closeFunc()}. This function must return the DOM object of the popup content. * @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 = "Croquis"] - Layer title to be displayed in LayerSwitcher * @param {String} [options.layerDescription.description = "Mon croquis"] - Layer description to be displayed in LayerSwitcher * @param {Object} options.tools - Tools to display in the drawing toolbox. All by default. * @param {Boolean} [options.tools.points = true] - Display points drawing tool * @param {Boolean} [options.tools.lines = true] - Display lines drawing tool * @param {Boolean} [options.tools.polygons = true] - Display polygons drawing tool * @param {Boolean} [options.tools.holes = false] - Display polygons with holes drawing tool * @param {Boolean} [options.tools.text = true] - Display text drawing tool * @param {Boolean} [options.tools.remove = true] - Display feature removing tool * @param {Boolean} [options.tools.display = true] - Display style editing tool * @param {Boolean} [options.tools.tooltip = true] - Display text editing tool * @param {Boolean} [options.tools.edit = true] - Display editing tool * @param {Boolean} [options.tools.export = true] - Display exporting tool * @param {Boolean} [options.tools.measure = false] - Display measure drawing into popup info * @param {String} [options.labels] - Labels for Control * @param {String} [options.labels.control] - Label for Control * @param {String} [options.labels.points] - Label for points drawing tool * @param {String} [options.labels.lines] - Label for lines drawing tool * @param {String} [options.labels.polygons] - Label for polygons drawing tool * @param {String} [options.labels.holes] - Label for polygons with holes drawing tool * @param {String} [options.labels.text] - Label for text drawing tool * @param {String} [options.labels.edit] - Label for editing tool * @param {String} [options.labels.display] - Label for style editing tool * @param {String} [options.labels.tooltip] - Label for text editing tool * @param {String} [options.labels.remove] - Label for feature removing tool * @param {String} [options.labels.export] - Label for exporting tool. * @param {String} [options.labels.exportTitle] - Title for exporting tool. * @param {String} [options.labels.applyToObject] - Label for apply to object button. * @param {String} [options.labels.saveDescription] - Label for save description button. * @param {String} [options.labels.setAsDefault] - Label for set as default style button. * @param {String} [options.labels.strokeColor] - Label for stroke color. * @param {String} [options.labels.strokeWidth] - Label for stroke width. * @param {String} [options.labels.fillColor] - Label for fill color. * @param {String} [options.labels.fillOpacity] - Label for fillOpacity. * @param {String} [options.labels.markerSize] - Label for markerSize. * @param {Array.<Object>} [options.markersList = [{"src" : "data:image/png;base64,xxxx", "anchor" : [0.5,1]}]] - List of markers src to be used for points with their anchor offsets See {@link http://openlayers.org/en/latest/apidoc/ol.style.Icon.html OpenLayers params} for anchor offset options. * @param {Object} options.defaultStyles - Default styles applying to geometries (labels, lines and polygons). * @param {String} [options.defaultStyles.textFillColor = "#000000"] - Text fill color for labels (RGB hex value). * @param {String} [options.defaultStyles.textStrokeColor = "#ffffff"] - Text surrounding color for labels (RGB hex value). * @param {String} [options.defaultStyles.strokeColor = "#ffcc33"] - Stroke color (RGB hex value). * @param {Number} [options.defaultStyles.strokeWidth = 2] - Stroke width in pixels. * @param {String} [options.defaultStyles.polyStrokeColor = "#ffcc33"] - Stroke color (RGB hex value) for polygons. * @param {Number} [options.defaultStyles.polyStrokeWidth = 2] - Stroke width in pixels for polygons. * @param {String} [options.defaultStyles.polyFillColor = "#ffffff"] - Polygons fill color (RGB hex value). * @param {Number} [options.defaultStyles.polyFillOpacity = 0.2] - Polygon fill opacity (alpha value between 0:transparent and 1:opaque). * @param {Object} options.cursorStyle - cursor (circle) style when drawing or editing. * @param {String} [options.cursorStyle.fillColor = "rgba(0, 153, 255, 1)"] - Cursor fill color. * @param {String} [options.cursorStyle.strokeColor = "#ffffff"] - Cursor stroke color. * @param {String} [options.cursorStyle.strokeWidth = 1] - Cursor surrounding stroke width. * @param {String} [options.cursorStyle.radius = 6] - Cursor radius. * @fires drawing:add:before - event triggered before an layer is added * @fires drawing:add:after - event triggered after an layer is added * @example * var drawing = new ol.control.Drawing({ * collapsed : false, * draggable : true, * layerswitcher : { * title : "Dessins", * description : "Mes dessins..." * }, * markersList : [{ * src : "http://api.ign.fr/api/images/api/markers/marker_01.png", * anchor : [0.5, 1] * }], * defaultStyles : {}, * cursorStyle : {}, * tools : { * points : true, * lines : true, * polygons :true, * holes : true, * text : false, * remove : true, * display : true, * tooltip : true, * export : true, * measure : true * }, * popup : { * display : true, * function : function (params) { * var container = document.createElement("div"); * // - params.geomType; * // - params.feature; * // Les 2 fonctions ferment la popup avec ou sans sauvegarde des informations * // dans les properties de la feature (key : description) * // - params.saveFunc(message); * // - params.closeFunc(); * return container; * } * }); */ constructor (options) { options = options || {}; // call ol.control.Control constructor super(options); if (!(this instanceof Drawing)) { throw new TypeError("ERROR CLASS_CONSTRUCTOR"); } /** * Nom de la classe (heritage) * @private */ this.CLASSNAME = "Drawing"; this._initialize(options); // init control DOM container this._container = this._initContainer(); // ajout du container (this.element) ? this.element.appendChild(this._container) : this.element = this._container; return this; } /** * Default tools to display for widget * * @private */ static DefaultTools = { points : true, lines : true, polygons : true, holes : false, text : true, remove : true, display : true, tooltip : true, edit : true, export : true, measure : false }; /** * Default labels for widget * * @private */ static DefaultLabels = { control : "Annoter la carte", creatingTools : "Créer", points : "Placer des points", lines : "Dessiner des lignes", polygons : "Dessiner des polygones", holes : "Créer des trous sur un polygone", text : "Ecrire sur la carte", editingTools : "Éditer", edit : "Editer les tracés", display : "Modifier l'apparence des objets", tooltip : "Modifier les textes / infos-bulles", remove : "Supprimer des objets", export : "Exporter", exportTitle : "Exporter en KML", applyToObject : "Appliquer à l'objet", saveDescription : "Enregistrer", setAsDefault : "Définir par défaut", strokeColor : "Couleur du trait : ", strokeWidth : "Epaisseur du trait : ", fillColor : "Couleur de remplissage : ", fillOpacity : "Opacité du remplissage : ", markerSize : "Taille du pictogramme : ", markerColor : "Couleur du pictogramme : ", labelDisplay : "Afficher le label : " }; /** * Default styles applyied to drawn features. * * @private */ static DefaultStyles = { textFillColor : "#000000", textStrokeColor : "#FFFFFF", textStrokeWidth : 6, // INFO : cette option n'est pas surchargeable via les options du constructeur ! textIcon1x1 : { src : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNiYAAAAAkAAxkR2eQAAAAASUVORK5CYII=", anchor : [0, 0] }, polyFillColor : "#000091", polyFillOpacity : 0.7, polyStrokeColor : "#000091", polyStrokeWidth : 4, strokeColor : "#FF7F00", strokeWidth : 4, markerSize : 1, markerColor : "#ffcc33", // INFO : cette option n'est pas surchargeable via les options du constructeur ! labelDisplay : true }; /** * Default styles when drawing * * @private */ static DefaultCursorStyle = { radius : 6, strokeColor : "#FFF", strokeWidth : 1, fillColor : "rgba(0, 153, 255, 1)" }; /** * Overload of {@link http://openlayers.org/en/latest/apidoc/ol.control.Control.html#setMap ol.control.Control.setMap()} method, called when control is added to or removed from map. * * @param {Map} map - {@link http://openlayers.org/en/latest/apidoc/ol.Map.html ol.Map} object. */ setMap (map) { // call original setMap method super.setMap(map); if (this.getMap() && this.eventKey) { olObservableUnByKey(this.eventKey); } // nothing else to do if map == null if (map == null) { return; } // 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"); } // mode "draggable" if (this.draggable) { Draggable.dragElement( this._drawingPanel, this._drawingPanelHeader, map.getTargetElement() ); } // mode "collapsed" if (!this.collapsed) { this._showDrawingButton.setAttribute("aria-pressed", true); } if (this.layer) { // ajout du layer de dessin à la carte s'il n'y est pas déjà this.setLayer(this.layer); } // gestion des suppressions "externes" de la couche de dessin. this.eventKey = this.getMap().getLayers().on("remove", (evtRm) => { if (evtRm.element === this.layer) { // FIXME object comparison // found layer removed. this.layer = null; // on supprime l'interaction en cours si besoin if (this.interactionCurrent) { this.getMap().removeInteraction(this.interactionCurrent); this.interactionCurrent = null; } } }); } /** * Export features of current drawing layer (KML by default). * * @returns {String} a representation of drawn features (KML, GPX or GeoJSON) or null if not possible. */ exportFeatures () { var result = null; if (Control.prototype.getMap.call(this) == null) { logger.log("Impossible to export : control isn't attached to any map."); return result; } if (!this.layer) { logger.log("Impossible to export : no layer is hosting features."); return result; } if (!this.layer.getSource() || !this.layer.getSource().getFeatures() || !this.layer.getSource().getFeatures().length) { logger.log("Impossible to export : no features found."); return result; } // on invalide les features... if (this.featuresCollectionSelected) { this.featuresCollectionSelected.clear(); } var ClassName = null; switch (this.getExportFormat()) { case "KML": ClassName = new KMLExtended({ writeStyles : true }); break; case "GPX": ClassName = new GPXExtended({ // readExtensions : function (ext) {/* only extensions nodes from wpt, rte and trk can be processed */ } }); break; case "GEOJSON": ClassName = new GeoJSONExtended({}); break; default: break; } if (!ClassName) { logger.log("Impossible to export : format unknown !?"); return result; } var featProj = this.layer.getSource().getProjection(); featProj = featProj || this.getMap().getView().getProjection(); result = ClassName.writeFeatures(this.layer.getSource().getFeatures(), { dataProjection : "EPSG:4326", featureProjection : featProj }); return result; } // ################################################################### // // #################### user interface methods ####################### // // ################################################################### // /** * Collapse or display control main container * * @param {Boolean} collapsed - True to collapse control, False to display it */ setCollapsed (collapsed) { if (collapsed === undefined) { logger.error("[ERROR] Drawing:setCollapsed - missing collapsed parameter"); return; } if ((collapsed && this.collapsed) || (!collapsed && !this.collapsed)) { return; } // on simule l'ouverture du panneau après un click this.onShowDrawingClick(); this._showDrawingButton.click(); } /** * Setter for Export Name. * * @param {String} name - Export Name. By default, "Croquis". */ setExportName (name) { this._exportName = name; } /** * getter for Export Name. * * @returns {String} export name */ getExportName () { return this._exportName; } /** * Setter for Export format (KML, GPX or GeoJSON). * * @param {String} format - Export format. By default, "KML". */ setExportFormat (format) { this._exportFormat = (format) ? format.toUpperCase() : "KML"; switch (format.toUpperCase()) { case "KML": this._exportExt = ".kml"; this._exportMimeType = "application/vnd.google-earth.kml+xml"; break; case "GPX": this._exportExt = ".gpx"; this._exportMimeType = "application/gpx+xml"; break; case "GEOJSON": this._exportExt = ".geojson"; this._exportMimeType = "application/geo+json"; break; default: // redefine format by default ! this._exportFormat = "KML"; break; } } /** * getter for Export format. * * @returns {String} export format */ getExportFormat () { return this._exportFormat; } /** * Sets vector layer to hosts feature. * * @param {VectorLayer} vlayer - vector layer */ setLayer (vlayer) { if (!vlayer) { this.layer = null; return; } if (!(vlayer instanceof VectorLayer)) { logger.log("no valid layer given for hosting drawn features."); return; } // ajout du layer de dessin à la carte s'il n'y est pas déjà var layers = this.getMap().getLayers(); if (layers) { var found = false; layers.forEach((mapLayer) => { if (mapLayer === vlayer) { logger.trace("layer already in map. Not adding."); found = true; } }); // si le layer n'est pas sur la carte, on l'ajoute. if (!found) { this.getMap().addLayer(vlayer); /** * event triggered after an layer is added */ this.dispatchEvent({ type : this.ADD_AFTER_DRAWING_LAYER_EVENT, layer : vlayer }); } // style par defaut ! // application des styles ainsi que ceux par defaut de ol sur le layer vlayer.getSource().getFeatures().forEach((feature) => { var style = feature.getStyle(); if (typeof style !== "function") { return; } var featureStyleFunction = feature.getStyleFunction(); if (featureStyleFunction) { var styles = featureStyleFunction.call(this, feature, 0); if (styles && styles.length !== 0) { feature.setStyle((Array.isArray(styles)) ? styles[0] : styles); } } }); this.layer = vlayer; // Si un layer switcher est présent dans la carte, on lui affecte des informations pour cette couche this.getMap().getControls().forEach( (control) => { if (control instanceof LayerSwitcher) { // un layer switcher est présent dans la carte var layerId = this.layer.gpLayerId; // INFO // dans des cas "très" particuliers, le titre peut être un identifiant // car le titre n'a pas été renseigné. // donc, on force le croquis à être renommé avec une description // dans le gestionnaire de couche. if (layerId && control._layers[layerId].title === layerId) { control.addLayer( this.layer, { title : this.options.layerDescription.title, description : this.options.layerDescription.description } ); } } } ); } } /** * Get vector layer * * @returns {VectorLayer} layer - isocurve layer */ getLayer () { return this.layer; } /** * Get container * * @returns {HTMLElement} container */ getContainer () { return this._container; } /** Disable interaction */ disable () { var map = this.getMap(); // on supprime toutes les interactions Interactions.unset(map); // on deselectionne les Tools for (var toolsType in this.dtOptions) { if (this.dtOptions.hasOwnProperty(toolsType)) { if (this.dtOptions[toolsType].active) { var toolsId = this._addUID("drawing-tool-" + this.dtOptions[toolsType].id); document.getElementById(toolsId).className = "drawing-tool fr-btn fr-btn--tertiary gpf-btn--tertiary"; this.dtOptions[toolsType].active = false; } } } } // ################################################################### // // ######################## initialize control ####################### // // ################################################################### // /** * Gets marker options in options.markersList given its src. * * @param {String} src - marker image URI, * @returns {Object} markers options * @private */ _getsMarkersOptionsFromSrc (src) { var markerOptions = null; for (var i = 0; i < this.options.markersList.length; i++) { if (src && this.options.markersList[i].src === src) { markerOptions = this.options.markersList[i]; return markerOptions; } } return markerOptions; } /** * Converts markerElement options into Openlayers IconStyles options. * * @param {Object} markerElement - marker element * @returns {Object} ol.Style.Icon constructor options. * @private */ _getIconStyleOptions (markerElement) { var iconOptions = {}; Object.keys(markerElement).forEach((key) => { switch (key) { case "src": case "size": case "scale": case "color": case "anchor": case "anchorOrigin": case "anchorXUnits": case "anchorYUnits": iconOptions[key] = markerElement[key]; break; } }); return iconOptions; } /** * Initialize control (called by Drawing constructor) * * @method _initialize * @param {Object} options - control options (set by user) * @private */ _initialize (options) { // determination d'un uid /** @private */ this._uid = options.id || SelectorID.generate(); // export name / format / ... /** @private */ this._exportName = "Croquis"; /** @private */ this._exportFormat = "KML"; /** @private */ this._exportMimeType = "application/vnd.google-earth.kml+xml"; /** @private */ this._exportExt = ".kml"; options = options || {}; // Set default options this.options = options; this.options.layerDescription = Object.assign({ title : "Croquis", description : "Mon croquis"}, options.layerDescription); // applying default tools if (!this.options.tools) { this.options.tools = {}; } Object.keys(Drawing.DefaultTools).forEach((key) => { if (!this.options.tools.hasOwnProperty(key)) { this.options.tools[key] = Drawing.DefaultTools[key]; } }); // styles par defaut lors du dessin if (!this.options.cursorStyle) { this.options.cursorStyle = {}; } Object.keys(Drawing.DefaultCursorStyle).forEach((key) => { if (!this.options.cursorStyle.hasOwnProperty(key)) { this.options.cursorStyle[key] = Drawing.DefaultCursorStyle[key]; } }); this.options.collapsed = (options.collapsed !== undefined) ? options.collapsed : true; /** * @type {Boolean} * specify if Drawing control is collapsed (true) or not (false) */ this.collapsed = this.options.collapsed; this.options.draggable = (options.draggable !== undefined) ? options.draggable : false; /** * @type {Boolean} * specify if Drawing control is draggable (true) or not (false) */ this.draggable = this.options.draggable; this.options.markersList = options.markersList || MarkersOther["drawing_api"]; // applying default labels if (!this.options.labels) { this.options.labels = {}; } Object.keys(Drawing.DefaultLabels).forEach((key) => { if (!this.options.labels.hasOwnProperty(key)) { this.options.labels[key] = Drawing.DefaultLabels[key]; } }); // applying default styles if (!this.options.defaultStyles) { this.options.defaultStyles = {}; } Object.keys(Drawing.DefaultStyles).forEach((key) => { if (!options.defaultStyles.hasOwnProperty(key)) { this.options.defaultStyles[key] = Drawing.DefaultStyles[key]; return; } if (key === "polyFillOpacity" && (options.defaultStyles[key] < 0 || options.defaultStyles[key] > 1)) { logger.log("Wrong value (" + options.defaultStyles[key] + ") for defaultStyles.polyFillOpactity. Must be between 0 and 1"); this.options.defaultStyles[key] = Drawing.DefaultStyles[key]; return; } if (key === "strokeWidth" || key === "polyStrokeWidth") { var intValue = parseInt(options.defaultStyles[key], 10); if (isNaN(intValue) || intValue < 0) { logger.log("Wrong value (" + options.defaultStyles[key] + ") for defaultStyles.strokeWidth. Must be a positive interger value."); this.options.defaultStyles[key] = Drawing.DefaultStyles[key]; return; } this.options.defaultStyles[key] = intValue; } if (key === "markerSize") { var floatValue = parseFloat(options.defaultStyles[key]); if (isNaN(floatValue) || floatValue < 0) { logger.log("Wrong value (" + options.defaultStyles[key] + ") for defaultStyles.markerSize. Must be a positive value."); this.options.defaultStyles[key] = Drawing.DefaultStyles[key]; return; } this.options.defaultStyles[key] = floatValue; } }); /** @private */ this.interactionCurrent = null; /** @private */ this.interactionSelectEdit = null; /** @private */ this.featuresCollectionSelected = null; /** @private */ this.stylingOvl = null; /** @private */ this.popupOvl = null; /** @private */ this.tooltipOvl = null; /** @private */ this.tooltipElem = null; this.layer = null; if (this.options.layer && this.options.layer instanceof VectorLayer) { this.layer = this.options.layer; } // detection du support : desktop ou tactile // FIXME : utile ? /** @private */ this._isDesktop = this._detectSupport(); // applying default popup if (!this.options.popup) { this.options.popup = { display : true, apply : null }; } /** * event triggered after an layer is added * * @event drawing:add:after * @defaultValue "drawing:add:after" * @group Events * @property {Object} type - event * @property {Object} layer - layer * @property {Object} target - instance Drawing * @example * Drawing.on("drawing:add:after", function (e) { * console.log(e.layer); * }) */ this.ADD_AFTER_DRAWING_LAYER_EVENT = "drawing:add:after"; /** * event triggered before an layer is added * * @event drawing:add:before * @defaultValue "drawing:add:before" * @group Events * @property {Object} type - event * @property {Object} layer - layer * @property {Object} target - instance Drawing * @example * Drawing.on("drawing:add:before", function (e) { * console.log(e.layer); * }) */ this.ADD_BEFORE_DRAWING_LAYER_EVENT = "drawing:add:before"; } /** * Creates empty layer to host features * * @private */ _createEmptyLayer () { var features = new Collection(); var layer = new VectorLayer({ source : new VectorSource({ features : features }), title : this.options.layerDescription.title, description : this.options.layerDescription.description }); // on rajoute le champ gpResultLayerId permettant d'identifier une couche crée par le composant. layer.gpResultLayerId = "drawing"; /** * event triggered before an layer is added */ this.dispatchEvent({ type : this.ADD_BEFORE_DRAWING_LAYER_EVENT, layer : layer }); // on le rajoute au controle (et à la carte) this.setLayer(layer); } /** * this method is called by the constructor. * this information is useful to switch to touch mode. * Detection : test for desktop or tactile * * @method _detectSupport * * @returns {Boolean} is desktop * @private */ _detectSupport () { // TODO // Choix de gérer la détection dans le code du composant au lieu du DOM car : // Utilisation de l'implémentation Leaflet // http://leafletjs.com/reference.html#browser var isDesktop = true; var userAgent = window.navigator.userAgent.toLowerCase(); if (userAgent.indexOf("iphone") !== -1 || userAgent.indexOf("ipod") !== -1 || userAgent.indexOf("ipad") !== -1 || userAgent.indexOf("android") !== -1 || userAgent.indexOf("mobile") !== -1 || userAgent.indexOf("blackberry") !== -1 || userAgent.indexOf("tablet") !== -1 || userAgent.indexOf("phone") !== -1 || userAgent.indexOf("touch") !== -1) { isDesktop = false; } if (userAgent.indexOf("msie") !== -1 || userAgent.indexOf("trident") !== -1) { isDesktop = true; } return isDesktop; } // ################################################################### // // ######################## methods handle dom ####################### // // ################################################################### // /** * Create control main container (called by Drawing constructor) * * @method _initContainer * * @returns {HTMLElement} DOM element * @private */ _initContainer () { // creation du container principal var container = this._createMainContainerElement(); var picto = this._showDrawingButton =this._createShowDrawingPictoElement(); container.appendChild(picto); var panel = this._drawingPanel = this._createDrawingPanelElement(); var panelDiv = this._createDrawingPanelDivElement(); panel.appendChild(panelDiv); var header = this._drawingPanelHeader = this._createDrawingPanelHeaderElement(); panelDiv.appendChild(header); var sections = this._createDrawingToolsDivSections(); panelDiv.appendChild(sections); var tools = this._createDrawingToolsSections(); for (var i = 0; i < tools.length; i++) { sections.appendChild(tools[i]); } var containerButtonsPlugin = this._createDrawingButtonsPluginDiv(); sections.appendChild(containerButtonsPlugin); container.appendChild(panel); return container; } // ################################################################### // // ##################### handlers events to control ################## // // ################################################################### // /** * Callback de fin de dessin de geometrie * @param {Feature} feature - ol feature * @param {String} geomType - geometry type * @param {Boolean} clean - clean last feature * * @private */ _drawEndFeature (feature, geomType) { // application des styles par defaut. var style = null; switch (geomType) { case "Point": style = new Style({ image : new Icon(this._getIconStyleOptions(this.options.markersList[0])) }); break; case "LineString": style = new Style({ stroke : new Stroke({ color : this.options.defaultStyles.strokeColor, width : this.options.defaultStyles.strokeWidth }) }); break; case "Polygon": style = new Style({ fill : new Fill({ color : Color.hexToRgba( this.options.defaultStyles.polyFillColor, this.options.defaultStyles.polyFillOpacity ) }), stroke : new Stroke({ color : this.options.defaultStyles.polyStrokeColor, width : this.options.defaultStyles.polyStrokeWidth }) }); break; } feature.setStyle(style); // gestion des mesures this._updateMeasure(feature, geomType); if (this.options.popup.display) { // creation overlay pour saisie du label // contexte var context = this; /** * Enregistrement de la valeur saisie dans l'input. * * @param {String} key - clef de l'attribut. * @param {String} value - valeur de l'attribut. * @param {Boolean} save - true si on garde le label. */ var setAttValue = function (key, value, save) { context.getMap().removeOverlay(context.popupOvl); context.popupOvl = null; if (save && value && value.trim().length > 0) { var obj = {}; obj[key] = value.replace(/\n/g, "<br>"); feature.setProperties(obj); } }; var popup = null; var popupByDefault = true; var displayFunction = this.options.popup.function; if (displayFunction && typeof displayFunction === "function") { // la sauvegarde et la fermeture sont des actions à implementer par l'utilisateur // par contre, la destruction est à gerer en interne popup = displayFunction.call(context, { feature : feature, geomType : geomType, closeFunc : function () { setAttValue(null, false); }, saveFunc : function (message) { setAttValue(message, true); } }); if (popup) { // on est sûr que la popup customisée existe, // donc on n'utilise pas celle par defaut... popupByDefault = false; // FIXME comment forcer le focus sur une div ? popup.tabIndex = -1; // hack sur le focus sur une div ? popup.onblur = function () { context.getMap().removeOverlay(context.popupOvl); context.popupOvl = null; }; } } // use popup by default if (popupByDefault) { // function by default popup = this._createLabelDiv({ applyFunc : setAttValue, inputId : this._addUID("att-input"), placeholder : "Saisir une description... (facultatif)", measure : (this.options.tools.measure) ? feature.getProperties().measure : null, geomType : geomType, key : "description" }); } // un peu de menage... if (this.popupOvl) { this.getMap().removeOverlay(this.popupOvl); this.popupOvl = null; } // creation de l'overlay this.popupOvl = new Overlay({ element : popup, // FIXME : autres valeurs. positioning : "top-center" // stopEvent : false }); this.getMap().addOverlay(this.popupOvl); var geomExtent = feature.getGeometry().getExtent(); this.popupOvl.setPosition([ (geomExtent[0] + geomExtent[2]) / 2, (geomExtent[1] + geomExtent[3]) / 2 ]); if (document.getElementById(this._addUID("att-input"))) { document.getElementById(this._addUID("att-input")).focus(); } } } /** * Creates Interaction for features removal. * * @returns {SelectInteraction} created interaction. * @private */ _createRemoveInteraction () { var interaction = new SelectInteraction({ // features : this.layer.getSource().getFeaturesCollection(), layers : [this.layer], style : false }); interaction.on("select", (seEv) => { if (!seEv || !seEv.selected || seEv.selected.length === 0) { return; } this.layer.getSource().removeFeature(seEv.selected[0]); // suppression puis rajout de l'interaction pour appliquer le changement tout de suite... this.getMap().removeInteraction(this.interactionCurrent); this.interactionCurrent = this._createRemoveInteraction(); this.getMap().addInteraction(this.interactionCurrent); }); return interaction; } /** * Creates Interaction for features style definition. * * @returns {SelectInteraction} created interaction. * @private */ _createStylingInteraction () { var interaction = new SelectInteraction({ layers : [this.layer], style : false }); interaction.on("select", (seEv) => { // suppression de toute popup existante if (this.stylingOvl) { this.getMap().removeOverlay(this.stylingOvl); } if (!seEv || !seEv.selected || seEv.selected.length === 0) { return; } var valuesColor = null; var hexColor = null; var popupOvl = null; var geomType = null; var initValues = {}; // FIXME // l'appel feature.getStyle() est parfois nul pour des geometries Point // avec un style par defaut ! var geom = seEv.selected[0].getGeometry(); var style = seEv.selected[0].getStyle(); if (geom instanceof Point || geom instanceof MultiPoint) { // INFO // on determine si c'est un marker (ou cercle), un label ou les 2. // un label a un pixel transparent comme icone if (style && style.getImage() && typeof style.getImage().getSrc === "function" && style.getImage().getSrc() !== this.options.defaultStyles.textIcon1x1.src) { geomType = "Point"; // on traite un marker // mais si c'est un cercle !? if (typeof style.getImage().getSrc === "function") { initValues.markerSrc = style.getImage().getSrc(); initValues.markerSize = style.getImage().getScale() || 1; initValues.markerAnchor = style.getImage().getAnchor(); if (style.getImage().getColor()) { valuesColor = style.getImage().getColor(); if (Array.isArray(valuesColor)) { // FIXME Array !? valuesColor = "rgba(" + valuesColor.join() + ")"; } else { initValues.markerColor = valuesColor; } hexColor = Color.isRGB(valuesColor) ? Color.rgbaToHex(valuesColor) : { hex : valuesColor, opacity : 1 }; initValues.markerColor = hexColor.hex; initValues.markerOpacity = hexColor.opacity; } else { initValues.markerColor = this.options.markersList[0].color || "#ffffff"; } } else { initValues.markerSrc = this.options.markersList[0].src; initValues.markerSize = this.options.markersList[0].scale || 1; initValues.markerColor = this.options.markersList[0].color || "#ffffff"; initValues.markerAnchor = this.options.markersList[0].anchor; } initValues.markerCustom = !(this._getsMarkersOptionsFromSrc(initValues.markerSrc)); } if (style && style.getText()) { var labelName = seEv.selected[0].getProperties().name; if (labelName) { // test si on a un marker avec un label geomType = (geomType === "Point") ? "Point&Text" : "Text"; if (style.getText().getStroke() && style.getText().getStroke().getColor()) { valuesColor = style.getText().getStroke().getColor(); if (Array.isArray(valuesColor)) { // FIXME Array !? valuesColor = "rgba(" + valuesColor.join() + ")"; } else { initValues.strokeColor = valuesColor; } hexColor = Color.isRGB(valuesColor) ? Color.rgbaToHex(valuesColor) : { hex : valuesColor, opacity : 1 }; initValues.strokeColor = hexColor.hex; initValues.strokeOpacity = hexColor.opacity; } if (style.getText().getStroke() && style.getText().getStroke().getWidth()) { initValues.strokeWidth = style.getText().getStroke().getWidth(); } if (style.getText().getFill() && style.getText().getFill().getColor()) { valuesColor = style.getText().getFill().getColor(); if (Array.isArray(valuesColor)) { valuesColor = "rgba(" + valuesColor.join() + ")"; } else { initValues.fillColor = valuesColor; } hexColor = Color.isRGB(valuesColor) ? Color.rgbaToHex(valuesColor) : { hex : valuesColor, opacity : 1 }; initValues.fillColor = hexColor.hex; initValues.fillOpacity = hexColor.opacity; } initValues.strokeColor = initValues.hasOwnProperty("strokeColor") ? initValues.strokeColor : this.options.defaultStyles.textStrokeColor; initValues.strokeWidth = initValues.hasOwnProperty("strokeWidth") ? initValues.strokeWidth : this.options.defaultStyles.textStrokeWidth; initValues.fillColor = initValues.hasOwnProperty("fillColor") ? initValues.fillColor : this.options.defaultStyles.textFillColor; // Par defaut, pour un marker avec un label, on affiche le label si le tag "name" est renseigné. if (geomType === "Point&Text") { var value = style.getText().getText(); if (!value) { style.getText().setText(labelName); } var checked = seEv.selected[0].get("checked"); initValues.labelDisplay = (checked === undefined) ? this.options.defaultStyles.labelDisplay : checked; } } } } else if (geom instanceof LineString || geom instanceof MultiLineStrin