UNPKG

geopf-extensions-openlayers

Version:

French Geoportal Extensions for OpenLayers libraries

1,206 lines (1,111 loc) 54.1 kB
// import openlayers import olKML from "ol/format/KML"; import { Fill, Icon, Stroke, Style, Text, Circle } from "ol/style"; import Feature from "ol/Feature"; // import local import Styling from "./Styling"; import Color from "../Utils/ColorUtils"; import Logger from "../Utils/LoggerByDefault"; import Parser from "../Utils/Parser"; import Utils from "../Utils/Helper"; var logger = Logger.getLogger("extended KML format"); /** * @classdesc * * Extended Styles KML format to export (internal use only !) * * INFO * only ol.Control is a user-extendable class. * Everything else requires integration with the original openlayers source and a new ol.js * to be built with your new classes incorporated. * * SPEC * cf. https://developers.google.com/kml/forum/advanced * * ISSUES * cf. https://github.com/openlayers/openlayers/issues/4829 * cf. https://github.com/openlayers/openlayers/issues/4460 * cf. https://github.com/openlayers/openlayers/pull/5590 * cf. https://github.com/openlayers/openlayers/issues/5229 * cf. https://github.com/openlayers/openlayers/issues/3371 * * @alias ol.format.KMLExtended * @module KMLExtended * */ class KML extends olKML { /** * @constructor * @param {Object} options - Options * @param {Object} [options.extensions] - Add properties to file root */ constructor (options) { super(options); if (!(this instanceof KML)) { throw new TypeError("ERROR CLASS_CONSTRUCTOR"); } options = options || {}; // INFO // source DOM (Document ou Node) this.source = null; // INFO // gestion des extensions this.extensions = options.extensions || null; // INFO // defaultStyle est un tableau d'objet de type Style if (options.defaultStyle && !Array.isArray(options.defaultStyle)) { options.defaultStyle = [options.defaultStyle]; } if (options.defaultStyle === null || typeof options.defaultStyle === "undefined") { options.defaultStyle = []; } return this; } /** * * En lecture, on surcharge la méthode readFeatures. * ✔️ In : kml string + features du format original * ✔️ Out : features étendus avec des styles, et des metadatas (name ou extendData) * > on modifie les features du format original avec les fonctionnalités non gérées. * * En écriture, on surcharge la méthode writeFearures. * ✔️ In : kml du format original + features étendus * ✔️ Out : kml étendu avec des styles, et des metadatas (name ou extendData) * > on modifie le kml généré par le format original avec les fonctionnalités que nous avons ajoutées aux features. * * Le principe * On parse le kml, et on lit (get) ou on ajoute (set) des fonctionnalités. * * Les getters vont lire le kml (ex. LabelExtendStyle), et ajouter le style ainsi que le nom du label dans le feature original. * getLabelIconStyle (appel des 2 fonctions suivantes) * getLabelExtendStyle (New) * getHotSpotIconStyle (Bug sur la lecture du hotspot) * getExtendData (New) * * Les setters vont écrire dans le dom du kml original les fonctionnalités ajoutées dans les features. * setLabelExtendStyle (New) * setHotSpotIconStyle (Bug sur l'écriture du hotspot) * setNameData (Bug suppression de cette balise du format par défaut). * */ /** * Fonction de lecture du KML avec fonction de traitement en fonction du type * PlaceMark (Label ou Marker). * Les traitements sont de 2 types : * - creation de styles étendus ou correctifs sur le KML * - ajout de styles étendus sur les features * * @param {HTMLElement} kmlNode - kml nodes * @param {Feature[]} features - features * @param {Object} process - process * * @example * // ajoute des fonctionnalités dans le KML * _processKml(kmlDoc, { * labelStyle : createStyleLabel, * iconStyle : createStyleIcon * }); * * // lit des fonctionnalités du KML non impl. par OpenLayers * _processKml(kmlNode, { * labelStyle : getStyleToFeatureLabel, * iconStyle : getStyleToFeatureIcon, * extendedData : getExtendedData * }); */ _processKml (kmlNode, features, process) { var firstNodeLevel = (kmlNode.nodeName === "#document") ? kmlNode.childNodes[0].childNodes : kmlNode.childNodes; // Si le DOM contient un seul objet, le noeud est directement un PlaceMark // sinon, c'est un ensemble de noeuds PlaceMark contenus dans le noeud Document. var nodes = firstNodeLevel; for (var ik = 0; ik < firstNodeLevel.length; ik++) { const element = firstNodeLevel[ik]; if (element.nodeName === "Document") { nodes = element.childNodes; break; } if (element.nodeName === "Placemark") { nodes = [element]; break; } } // On recherche les PlaceMark de type Point ayant un Style... // Le style peut être placé directement dans le PlaceMark // ou lié avec un id (share) var stylesUrl = {}; // listes des styles var index = -1; // index du features... for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; switch (node.nodeName) { case "Style": // INFO // pour le traitement des balises Styles liées avec styleUrl, // elles doivent être toujours déclarées avant les PlaceMark ! // On ne prend en compte que celles qui sont identifiées via un ID ! var id = node.attributes[0]; if (id && id.nodeName === "id") { var _k = id.nodeValue; var _v = node; stylesUrl[_k] = _v; } break; case "Placemark": index++; var types = node.childNodes; // Point, LineString, Polygon, Style, ... var point = false; var line = false; var poly = false; var domStyles = null; // dom var hdlDomStyle = null; // dom var domExtendedData = null; // dom var domNameTag = node; // dom for (var j = 0; j < types.length; j++) { switch (types[j].nodeName) { case "Polygon": poly = true; break; case "LineString": line = true; break; case "Point": point = true; break; case "Style": hdlDomStyle = types[j]; domStyles = types[j].childNodes; // liste de styles break; case "styleUrl": // style avec lien vers... var _idStyle = types[j].textContent.slice(1); if (stylesUrl[_idStyle]) { domStyles = stylesUrl[_idStyle].childNodes; } break; case "ExtendedData": domExtendedData = types[j].childNodes; break; case "name": domNameTag = null; break; default: // on ne traite pas les autres informations ... // car elles seront gérées par defaut par le format standard... } } // On traite les balises kml:extendedData pour tous les objets ! if (domExtendedData) { logger.log("ExtendedData :", domExtendedData); var fctExtend = process.extendedData; if (fctExtend && typeof fctExtend === "function") { fctExtend(features[index], domExtendedData); } } // On traite la balise kml:name if (domNameTag) { logger.log("Name :", domNameTag); var fctName = process.nameData; if (fctName && typeof fctName === "function") { fctName(features[index], domNameTag); } } // On a un Marker avec un Style. // Il peut être associé avec un Label ! // Les markers sans styles ne doivent pas être gérées par les styles par defaut // car le KML met en place une punaise google ! if (point && domStyles && domStyles.length !== 0) { var labelStyleDom = null; var iconStyleDom = null; // On recherche le type de Style for (var k = 0; k < domStyles.length; k++) { switch (domStyles[k].nodeName) { case "LabelStyle": labelStyleDom = domStyles[k]; break; case "IconStyle": iconStyleDom = domStyles[k]; break; default: // on ne traite pas les autres informations ... } } // Pour un label, il nous faut un titre ! var labelName = features[index].getProperties().name; var labelDescription = features[index].getProperties().description; var value = labelName || labelDescription; logger.trace(value); // C'est uniquement un Label ! if (!iconStyleDom && labelStyleDom) { var fctLabel = process.labelStyle; if (fctLabel && typeof fctLabel === "function") { fctLabel(features[index], labelStyleDom); } // C'est uniquement un marker ! } else if (iconStyleDom && !labelStyleDom) { var fctIcon = process.iconStyle; if (fctIcon && typeof fctIcon === "function") { fctIcon(features[index], iconStyleDom); } // C'est un marker avec un label ! } else if (iconStyleDom && labelStyleDom) { var fctIconLabel = process.iconLabelStyle; if (fctIconLabel && typeof fctIconLabel === "function") { fctIconLabel(features[index], iconStyleDom, labelStyleDom); } } else { // ... } } else { var feature = features[index]; var style = feature.getStyle(); if (style && typeof style === "function") { var fstyles = style.call(this, feature, 0); if (fstyles && fstyles.length !== 0) { style = fstyles[0]; } } if (poly) { var fctPoly = process.polygonStyle; if (fctPoly && typeof fctPoly === "function") { fctPoly(features[index], domStyles); } } if (line) { var fctLine = process.lineStringStyle; if (fctLine && typeof fctLine === "function") { fctLine(features[index], domStyles); } } // INFO // On est sur un Point mais sans style dans le DOM. // On regarde le style dans le Feature : Icon ou Circle ? if (point && style) { var image = style.getImage(); if (image && image instanceof Circle) { var fctCircle = process.circleStyle; if (fctCircle && typeof fctCircle === "function") { fctCircle(features[index], hdlDomStyle); } } else if (image && image instanceof Icon) { var fctPoint = process.pointStyle; if (fctPoint && typeof fctPoint === "function") { fctPoint(features[index], hdlDomStyle); } } else { // ... } } } break; default: logger.trace("tag is not processing !"); } } } /** * Write Extend for Features. * This function overloads ol.format.KML.writeFeatures ... * * @see olKML#writeFeatures * @param {Feature[]} features - Features. * @param {Object} options - Options. * * @returns {String} kml string formatted */ writeFeatures (features, options) { logger.log("overload : ol.format.KML.writeFeatures"); var kmlNode = super.writeFeaturesNode(features, options); if (kmlNode === null) { return null; } // on ajoute les extensions à la racine pour les metadonnées de calcul if (this.hasOwnProperty("extensions")) { this._writeRootExtensions(kmlNode, this.extensions); } // On ajoute les styles étendus var kmlStringExtended = this._writeExtendStylesFeatures(kmlNode, features, options); // On realise un formattage du KML var kmlStringFormatted = Parser.format(kmlStringExtended); if (kmlStringFormatted === "") { return null; } return kmlStringFormatted; }; /** * Write Extended Styles for each features * * @param {HTMLElement} kmlNode - kml nodes * @param {Feature[]} features - features * @param {Object} options - options * * @returns {String} kml string extended * * @private */ _writeExtendStylesFeatures (kmlNode, features, options) { // RGB Colors (RRGGBB) To KML Colors (AABBGGRR) function __convertRGBColorsToKML (data, opacity) { var strColor = data.toString(16); if (strColor.charAt(0) === "#") { strColor = strColor.slice(1); } opacity = opacity || 1; opacity = parseInt(opacity * 255, 10); opacity = opacity.toString(16); var color = opacity; color = color + strColor.substr(4, 2); color = color + strColor.substr(2, 2); color = color + strColor.substr(0, 2); return color.toLowerCase(); } /** * C'est un Label ! * On va donc y ajouter qq styles sur le Label (police, halo, ...) : * Insertion : PlaceMark>Style>LabelStyle * * @param {Object} feature - feature * @param {HTMLElement} node - node * * @example * <LabelStyleSimpleExtensionGroup fontFamily="Arial" haloColor="16777215" haloRadius="2" haloOpacity="1"/> */ var __createExtendedStyleLabel = function (feature, node) { logger.trace("label with style :", node); if (!feature) { return; } // Si pas de style defini, c'est donc que l'on va utiliser celui par defaut... if (feature.getStyle() instanceof Style) { var textStyle = feature.getStyle().getText(); if (!textStyle) { return; } var _fontFamily = "Sans"; var _fontSize = "16px"; var _font = textStyle.getFont(); if (_font) { var splits = _font.split(" ", 2); _fontSize = splits[0]; _fontFamily = splits[1]; } var strokeTextStyle = feature.getStyle().getText().getStroke(); if (!strokeTextStyle) { return; } if (strokeTextStyle instanceof Stroke) { var _haloColor = __convertRGBColorsToKML("#FFFFFF"); // Par defaut var color = strokeTextStyle.getColor(); // array ? if (Array.isArray(color)) { var cf = "rgba("; cf += color[0] + ","; cf += color[1] + ","; cf += color[2] + ","; cf += color[3] + ")"; color = cf; } if (Color.isRGB(color)) { var colorHex = Color.rgbaToHex(color); _haloColor = __convertRGBColorsToKML(colorHex.hex, colorHex.opacity); } else { _haloColor = __convertRGBColorsToKML(color); } var _haloRadius = strokeTextStyle.getWidth() || "0"; var _haloOpacity = "1"; // TODO lire param if (node && node.getElementsByTagName("LabelStyleSimpleExtensionGroup").length === 0) { var labelExtended = document.createElementNS(kmlNode.namespaceURI, "LabelStyleSimpleExtensionGroup"); labelExtended.setAttribute("fontSize", _fontSize); labelExtended.setAttribute("fontFamily", _fontFamily); labelExtended.setAttribute("haloColor", _haloColor); labelExtended.setAttribute("haloRadius", _haloRadius); labelExtended.setAttribute("haloOpacity", _haloOpacity); node.appendChild(labelExtended); } } var fImageStyle = feature.getStyle().getImage(); if (!fImageStyle) { return; } if (fImageStyle instanceof Circle) { var strokeColor = null; var strokeWidth = null; if (fImageStyle.getStroke()) { strokeWidth = fImageStyle.getStroke().getWidth(); strokeColor = fImageStyle.getStroke().getColor(); // array ? if (Array.isArray(strokeColor)) { var cfs = "rgba("; cfs += strokeColor[0] + ","; cfs += strokeColor[1] + ","; cfs += strokeColor[2] + ","; cfs += strokeColor[3] + ")"; strokeColor = cfs; } if (Color.isRGB(strokeColor)) { var strokeColorHex = Color.rgbaToHex(strokeColor); strokeColor = __convertRGBColorsToKML(strokeColorHex.hex, strokeColorHex.opacity); } else { strokeColor = __convertRGBColorsToKML(strokeColor); } } var fillColor = null; if (fImageStyle.getFill()) { fillColor = fImageStyle.getFill().getColor(); // array ? if (Array.isArray(fillColor)) { var cff = "rgba("; cff += fillColor[0] + ","; cff += fillColor[1] + ","; cff += fillColor[2] + ","; cff += fillColor[3] + ")"; fillColor = cff; } if (Color.isRGB(fillColor)) { var fillColorHex = Color.rgbaToHex(fillColor); fillColor = __convertRGBColorsToKML(fillColorHex.hex, fillColorHex.opacity); } else { fillColor = __convertRGBColorsToKML(fillColor); } } if (node && node.getElementsByTagName("ObjectSimpleExtensionGroup").length === 0) { var iconExtended = document.createElementNS(kmlNode.namespaceURI, "ObjectSimpleExtensionGroup"); iconExtended.setAttribute("type", "circle"); // FIXME type circle only ! iconExtended.setAttribute("radius", fImageStyle.getRadius()); iconExtended.setAttribute("fillColor", fillColor); iconExtended.setAttribute("strokeColor", strokeColor); iconExtended.setAttribute("strokeWidth", strokeWidth); node.appendChild(iconExtended); } } } }; /** * C'est un marker ! * On va donc ajouter la balise hotspot : * Traiter le cas où les unités sont de type * - FRACTION * - PIXELS * Insertion du correctif dans le noeud : <PlaceMark><Style>IconStyle * * @param {Object} feature - ol feature * @param {HTMLElement} node - node * * @example * <Style><IconStyle> * <hotSpot x="0.5" y="1" xunits="fraction" yunits="fraction"/> * </IconStyle></Style> */ var __createExtendedStyleIcon = function (feature, node) { logger.trace("marker with style (hotspot):", node); if (!feature) { return; } // Si pas de style defini, c'est donc que l'on va utiliser celui par defaut... if (feature.getStyle() instanceof Style) { var fImageStyle = feature.getStyle().getImage(); if (!fImageStyle) { return; } if (fImageStyle instanceof Icon) { var x = 0; var y = 0; var xunits = "pixels"; var yunits = "pixels"; var size = fImageStyle.getSize(); var anchor = fImageStyle.getAnchor(); // pixels ! but anchor_ in the current unit ! if (anchor.length) { x = anchor[0]; y = anchor[1]; if (yunits === "fraction") { y = (y === 1) ? 0 : 1 - y; // cf. fixme contribution à faire ! } else { y = (yunits === "pixels" && y === size[1]) ? 0 : size[1] - y; // cf. fixme contribution à faire ! } } if (node && node.getElementsByTagName("hotSpot").length === 0) { var hotspot = document.createElementNS(kmlNode.namespaceURI, "hotSpot"); hotspot.setAttribute("x", x); hotspot.setAttribute("y", y); hotspot.setAttribute("xunits", xunits); hotspot.setAttribute("yunits", yunits); node.appendChild(hotspot); } } } }; /** * ... * @param {*} feature - feature * @param {HTMLElement} node - node */ var __createExtendedStyleToCircle = function (feature, node) { if (!feature) { return; } // Si pas de style defini, c'est donc que l'on va utiliser celui par defaut... if (feature.getStyle() instanceof Style) { var fImageStyle = feature.getStyle().getImage(); if (!fImageStyle) { return; } if (fImageStyle instanceof Circle) { var strokeColor = null; var strokeWidth = null; if (fImageStyle.getStroke()) { strokeWidth = fImageStyle.getStroke().getWidth(); strokeColor = fImageStyle.getStroke().getColor(); // array ? if (Array.isArray(strokeColor)) { var cf = "rgba("; cf += strokeColor[0] + ","; cf += strokeColor[1] + ","; cf += strokeColor[2] + ","; cf += strokeColor[3] + ")"; strokeColor = cf; } if (Color.isRGB(strokeColor)) { var colorHex = Color.rgbaToHex(strokeColor); strokeColor = __convertRGBColorsToKML(colorHex.hex, colorHex.opacity); } else { strokeColor = __convertRGBColorsToKML(strokeColor); } } var fillColor = null; if (fImageStyle.getFill()) { fillColor = fImageStyle.getFill().getColor(); // array ? if (Array.isArray(fillColor)) { var cfi = "rgba("; cfi += fillColor[0] + ","; cfi += fillColor[1] + ","; cfi += fillColor[2] + ","; cfi += fillColor[3] + ")"; fillColor = cfi; } if (Color.isRGB(fillColor)) { var fillColorImgHex = Color.rgbaToHex(fillColor); fillColor = __convertRGBColorsToKML(fillColorImgHex.hex, fillColorImgHex.opacity); } else { fillColor = __convertRGBColorsToKML(fillColor); } } if (node && node.getElementsByTagName("ObjectSimpleExtensionGroup").length === 0) { var labelStyle = document.createElementNS(kmlNode.namespaceURI, "LabelStyle"); var circleExtended = document.createElementNS(kmlNode.namespaceURI, "ObjectSimpleExtensionGroup"); circleExtended.setAttribute("type", "circle"); // FIXME type circle only ! circleExtended.setAttribute("radius", fImageStyle.getRadius()); circleExtended.setAttribute("fillColor", fillColor); circleExtended.setAttribute("strokeColor", strokeColor); circleExtended.setAttribute("strokeWidth", strokeWidth); labelStyle.appendChild(circleExtended); node.appendChild(labelStyle); } } } }; /** * ... * @param {*} feature - feature * @param {HTMLElement} node - node */ var __createExtendedStyleToPoint = function (feature, node) {}; // TODO var __createExtendedStyleToIconLabel = function (feature, nodeIconStyle, nodeLabelStyle) { logger.trace("write an icon with a label"); __createExtendedStyleIcon(feature, nodeIconStyle); __createExtendedStyleLabel(feature, nodeLabelStyle); }; // TODO var __setNameData = function (feature, tags) { for (var i = 0; i < tags.length; i++) { var tag = tags[i]; if (tag.nodeName === "name") { return; } } var labelName = feature.getProperties().name; if (labelName) { var name = document.createElement("name"); name.innerHTML = labelName; tags.appendChild(name); } }; // TODO var _setExtendedDataStyle = function (feature, node) { if (node && node.length) { var removeNodes = []; for (var k = 0; k < node.length; k++) { const element = node[k]; if (element.nodeName === "Data") { var key = element.getAttribute("name"); if (Styling.getListTags().includes(key)) { removeNodes.push(element); } } } if (removeNodes && removeNodes.length) { removeNodes.forEach(e => { e.remove(); }); } } }; // On ajoute les styles étendus dans le DOM this._processKml(kmlNode, features, { labelStyle : __createExtendedStyleLabel, iconStyle : __createExtendedStyleIcon, iconLabelStyle : __createExtendedStyleToIconLabel, circleStyle : __createExtendedStyleToCircle, pointStyle : __createExtendedStyleToPoint, nameData : __setNameData, extendedData : _setExtendedDataStyle }); // On convertit le DOM en String... var kmlStringExtended = Parser.toString(kmlNode); if (!kmlStringExtended) { return null; } return kmlStringExtended; }; /** * ... * * @param {*} kmlNode - ... * @param {*} extensions - ... */ _writeRootExtensions (kmlNode, extensions) { var extendDataElement = document.createElementNS(kmlNode.namespaceURI, "ExtendedData"); // on boucle sur toutes les clefs for (const key in extensions) { if (Object.hasOwnProperty.call(extensions, key)) { const value = extensions[key]; var dataElement = document.createElementNS(kmlNode.namespaceURI, "Data"); dataElement.setAttribute("name", key); var data = document.createTextNode(JSON.stringify(value)); dataElement.appendChild(data); extendDataElement.appendChild(dataElement); } } // insertion en 1ere place ! var firstChild = kmlNode.firstChild; kmlNode.insertBefore(extendDataElement, firstChild); } /** * Read Extend for Features. * This function overloads ol.format.KML.readFeatures ... * * @see olKML#readFeatures * @param {Document|Node} source - Source. * @param {Object} options - options. see olx.format.ReadOptions * @returns {Feature[]} Features. */ readFeatures (source, options) { logger.log("overload : ol.format.KML.readFeatures"); // String ou Dom if (typeof source === "string") { this.source = Parser.parse(source); } else if (source !== null) { this.source = source; } var features = this._readExtendStylesFeatures(source, options); logger.trace("Styles étendus", features); // On met à jour les attributs de style dans les features features.forEach(feature => { Styling.definePropertiesFromStyle(feature); }); return features; }; /** * Read Extended Styles for each features * * @param {(Document|Node|ArrayBuffer|Object|String)} source - source * @param {Object} options - options see olx.format.ReadOptions * * @returns {Feature[]} features * * @private */ _readExtendStylesFeatures (source, options) { var features = super.readFeatures(source, options); var kmlDoc = null; var kmlString = ""; if (typeof source === "string") { kmlString = source; } else { kmlString = source.documentElement.outerHTML; } // On 'deformatte' le KML afin d'eviter des pb de parsing... kmlString = kmlString.replace(/\n/g, ""); kmlString = kmlString.replace(/(>)\s*(<)/g, "$1$2"); // On met en place un Parser sur le KML kmlDoc = Parser.parse(kmlString); if (kmlDoc === null) { // au cas où... return features; } // KML Colors (AABBGGRR) To RGB Colors (RRGGBB) function __convertKMLColorsToRGB (data) { var color = ""; color = color + data.substr(6, 2); color = color + data.substr(4, 2); color = color + data.substr(2, 2); var hex = parseInt(color, 16).toString(16); var comp = ""; var len = hex.length || 0; for (var i = 0; i < (6 - len); i++) { comp += "0"; } hex = "#" + comp + hex; return hex; } /** * Gestion des styles étendus sur le Label * * @param {Object} feature - ol feature * @param {HTMLElement} node - node * * @example * <Placemark> * <description>Un label</description> * <name>C'est un label étendu !</name> * <Style> * <IconStyle> * <Icon> * <href>data:image/png;base64,...</href> * </Icon> * </IconStyle> * <LabelStyle> * <color>ffffffff</color> * <colorMode>normal</colorMode> * <scale>1.85</scale> * <LabelStyleSimpleExtensionGroup haloColor="16711680" haloRadius="5" haloOpacity="1"/> * </LabelStyle> * </Style> * <Point> * <coordinates>2,48</coordinates> * </Point> * </Placemark> */ var __getExtendedStyleToFeatureLabel = function (feature, node) { logger.trace("label with style :", node); if (!feature) { return; } // label var _text = feature.getProperties().name; var _color = __convertKMLColorsToRGB("ff000000"); // "#000000" var _colorHalo = "#FFFFFF"; var _radiusHalo = 0; // var _opacityHalo = 1; // TODO var _font = "Sans"; var _fontSize = "16px"; // cercle var _circleType = null; var _circleRadius = 5; var _circleFillColor = "#000000"; var _circleStrokeColor = "#ffffff"; var _circleStrokeWidth = 1; // On recherche les balises du Style var bLabelStyleSimpleExtensionGroup = false; var bObjectSimpleExtensionGroup = false; var nodeStyles = node.childNodes; for (var k = 0; k < nodeStyles.length; k++) { switch (nodeStyles[k].nodeName) { case "scale": // TODO break; case "colorMode": // TODO break; case "color": _color = __convertKMLColorsToRGB(nodeStyles[k].textContent); break; case "LabelStyleSimpleExtensionGroup": bLabelStyleSimpleExtensionGroup = true; var attributs = nodeStyles[k].attributes; for (var l = 0; l < attributs.length; l++) { switch (attributs[l].nodeName) { case "fontFamily": _font = attributs[l].nodeValue; break; case "fontSize": _fontSize = attributs[l].nodeValue; break; case "haloColor": _colorHalo = __convertKMLColorsToRGB(attributs[l].nodeValue); break; case "haloRadius": _radiusHalo = parseInt(attributs[l].nodeValue, 10); break; case "haloOpacity": // _opacityHalo = parseFloat(attributs[l].nodeValue); // TODO opacité ! // if (_opacityHalo !== 1) { // _colorHalo = Color.hexToRgba(_colorHalo, _opacityHalo); // } break; default: } } break; case "ObjectSimpleExtensionGroup": bObjectSimpleExtensionGroup = true; var attributsExt = nodeStyles[k].attributes; for (var ll = 0; ll < attributsExt.length; ll++) { // type="circle" radius="15" fillColor="7f3737a0" strokeColor="cc000000" strokeWidth="2" switch (attributsExt[ll].nodeName) { case "type": _circleType = attributsExt[ll].nodeValue; break; case "radius": _circleRadius = parseInt(attributsExt[ll].nodeValue, 10); break; case "fillColor": var fillColorValue = attributsExt[ll].nodeValue; var fillOpacity = Math.round((Color.num(fillColorValue.substr(0, 2)) / 255) * 10) / 10; var fillColorHexa = __convertKMLColorsToRGB(fillColorValue); _circleFillColor = Color.hexToRgba(fillColorHexa, fillOpacity); break; case "strokeColor": var strokeColorValue = attributsExt[ll].nodeValue; var strokeOpacity = Math.round((Color.num(strokeColorValue.substr(0, 2)) / 255) * 10) / 10; var strokeColorHexa = __convertKMLColorsToRGB(strokeColorValue); _circleStrokeColor = Color.hexToRgba(strokeColorHexa, strokeOpacity); break; case "strokeWidth": _circleStrokeWidth = parseInt(attributsExt[ll].nodeValue, 10); break; default: } } break; default: // on ne traite pas les autres informations ... } } var StyleInstance = null; if (bObjectSimpleExtensionGroup && _circleType === "circle") { StyleInstance = new Circle({ radius : _circleRadius, fill : new Fill({ color : _circleFillColor }), stroke : new Stroke({ color : _circleStrokeColor, width : _circleStrokeWidth }) }); } else if (bLabelStyleSimpleExtensionGroup) { // INFO // on ajoute une image magique 1x1 pixel invisible // afin d'eviter l'affichage d'une punaise google ! StyleInstance = new Icon({ src : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNiYAAAAAkAAxkR2eQAAAAASUVORK5CYII=", size : [51, 38], anchor : [25.5, 38], anchorOrigin : "top-left", anchorXUnits : "pixels", anchorYUnits : "pixels" }); } else { // ... } // On reconstruit le style ! feature.setStyle(new Style({ image : StyleInstance, text : new Text({ font : _fontSize + " " + _font, textAlign : "left", text : _text, // offsetX : 5, // FIXME valeur arbitraire MAIS esthétique ! fill : new Fill({ color : _color }), stroke : new Stroke({ color : _colorHalo, width : _radiusHalo }) }) })); }; /** * Gestion des styles étendus sur un Marker * * > correctif sur la balise kml:hostSpot * - problème avec 'hotspot y === 0' (?) * * @param {Object} feature - ol feature * @param {HTMLElement} node - node * * @example * <Placemark> * <Style> * <IconStyle> * <Icon> * <href>data:image/png;base64,...</href> * </Icon> * <hotSpot x="25.5" y="0" xunits="pixels" yunits="pixels"/> * </IconStyle> * </Style> * <Point> * <coordinates>2,48</coordinates> * </Point> * </Placemark> */ var __getExtendedStyleToFeatureIcon = function (feature, node) { logger.trace("hotspot :", node); // marker var _src = null; var _scale = null; var _color = __convertKMLColorsToRGB("ffffffff"); var _bSizeIcon = false; var _sizeW = 51; var _sizeH = 38; var _bHotSpot = false; var _anchorX = 25.5; var _anchorXUnits = "pixels"; var _anchorY = 38; var _anchorYUnits = "pixels"; var nodeStyles = node.childNodes; var bIconStyle = false; for (var k = 0; k < nodeStyles.length; k++) { switch (nodeStyles[k].nodeName) { case "Icon": bIconStyle = true; var nodes = nodeStyles[k].childNodes; for (var i = 0; i < nodes.length; i++) { switch (nodes[i].nodeName) { case "href": _src = nodes[i].textContent; break; case "gx:w": _bSizeIcon = true; _sizeW = parseFloat(nodes[i].textContent); break; case "gx:h": _bSizeIcon = true; _sizeH = parseFloat(nodes[i].textContent); break; default: } } break; case "hotSpot": _bHotSpot = true; var attributs = nodeStyles[k].attributes; for (var l = 0; l < attributs.length; l++) { switch (attributs[l].nodeName) { case "x": _anchorX = parseFloat(attributs[l].nodeValue); break; case "y": _anchorY = parseFloat(attributs[l].nodeValue); break; case "yunits": _anchorXUnits = attributs[l].nodeValue; break; case "xunits": _anchorYUnits = attributs[l].nodeValue; break; default: } } break; case "scale": _scale = parseFloat(nodeStyles[k].textContent); break; case "color": _color = __convertKMLColorsToRGB(nodeStyles[k].textContent); break; default: // on ne traite pas les autres informations ... } } var StyleInstance = null; if (bIconStyle) { // une image magique 1x1 pixel invisible var optionsIcon = { src : _src || "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNiYAAAAAkAAxkR2eQAAAAASUVORK5CYII=", color : _color, crossOrigin : "anonymous", // cf. https://gis.stackexchange.com/questions/121555/wms-server-with-cors-enabled/147403#147403 scale : _scale || 1 }; if (_bSizeIcon) { Utils.mergeParams(optionsIcon, { size : [_sizeW, _sizeH] }); } if (_bHotSpot) { Utils.mergeParams(optionsIcon, { anchor : [_anchorX, _anchorY], anchorOrigin : "bottom-left", anchorXUnits : _anchorXUnits || "pixels", anchorYUnits : _anchorYUnits || "pixels" }); } StyleInstance = new Icon(optionsIcon); } // existe il déjà le style du label ? var featureStyleFunction = feature.getStyleFunction(); if (featureStyleFunction) { var _styles = featureStyleFunction(feature, 0); if (_styles && !Array.isArray(_styles)) { _styles = [_styles]; } if (_styles && _styles.length !== 0) { var _style = (_styles.length === 1) ? _styles[0] : _styles[_styles.length - 1]; // on écrase l'icone magic du label ! feature.setStyle(new Style({ image : StyleInstance, text : _style.getText() })); } } }; /** * Gestion de la balise kml:ExtendedData * * @param {Object} feature - ol feature * @param {HTMLElement[]} extend - extend * * @example * //--> Marker (Point), LineString, Polygon * <ExtendedData> * <Data name="attributetitle"> * <displayName>title</displayName> * <value>Titre à concatener avec la valeur de la balise "kml:description"</value> * </Data> * </ExtendedData> * //--> Label * <ExtendedData> * <Data name="label"> * <value>PARIS</value> // valeur à remplacer dans "kml:name" * </Data> * <Data name="attributetitle"> * <displayName>title</displayName> * <value>Titre à concatener avec la valeur de la balise "kml:description"</value> * </Data> * </ExtendedData> */ var __getExtendedData = function (feature, extend) { logger.trace("extendData :", extend); if (!feature) { return; } var props = {}; var _fname = feature.get("name") || ""; var _fdescription = feature.get("description") || ""; var _ftitle = null; for (var i = 0; i < extend.length; i++) { var data = extend[i]; var name = data.attributes[0]; // 1 seul attribut ! var nodes = data.childNodes; if (name.nodeName === "name") { switch (name.nodeValue) { // compatibilité ancien geoportail !