geopf-extensions-openlayers
Version:
French Geoportal Extensions for OpenLayers libraries
1,253 lines (1,159 loc) • 94.7 kB
JavaScript
// 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