/**
 * feuillet.js
 *
 * Encapsulation spécialisé des fonctionnalités Leaflet 0.7.x
 * (C) Renaud LAURETTE 2017-2025
 */


class Feuillet {
	
	/**
	 * I - Propriétés de classe
	 * -------------------------
	 */
	
	 /**
	  * Tableau recevant la description de tous les polygones ayant la popup standard
	  * Ce tableau est utilisé pour mémoriser la description correspondance dans le localStorage
	  * quand l'utilisateur clique sur 'copier'. Si le localStorage n'est pas disponible, le tableau 
	  * n'est pas renseigné.
	  */
	static polygonSet = [];

    /**
	 * Jeu de couleurs pour différentier les couches qui utilisent les styles automatiques
	 */
	static colors = [ 
             "#000000", 						// noir 
             "#9933FF", "#FF00FF", 				// violets
             "#663300", "#990000", "#FF0000", 	// rouges
             "#009966", "#996600", 				//marrons
             "#FF9900", "#FFFF00", 				// jaune orange
             "#005500", "#336600", "#339900", "#00FF00", // verts
             "#33FFCC", "#00FFFF", "#0000FF" 	// bleus
    		];	
    	
    /**
     * User defined icons
     */
     static icons = {};
     
	 /**
	  * Dictionnaire des styles utilisateur.
	  * Seul un style par défaut est fourni de base
	  */
	static userStyles = {
		"default": {
			"color" : Feuillet.colors[0],
        	"weight" : 2,
        	"opacity" : .75,		// opacité du trait (stroke)
        	"fillOpacity" : .3 		// opacité de remplissage (fill) 
		}
	}; 
	
	
	/**
	 * Tuiles raster de fond de carte 
	 * La liste doit être identique à ce qui est proposé dans feuillet_fonctions.php
	 *
	 * Pour IGN, voir https://geoservices.ign.fr/services-web-decouverte
	 */	 
	static baseMaps = {
			"OSM": L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '&copy; OpenStreetMap'}),
			//"Plan IGN v2": L.tileLayer('https://igngp.geoapi.fr/tile.php/plan-ignv2/{z}/{x}/{y}.png', {attribution: '&copy; Institut Géographique National (France)'}),
			"Plan IGN v2": L.tileLayer('https://data.geopf.fr/wmts?'+
				'&REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&TILEMATRIXSET=PM'+
				'&LAYER=GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2&STYLE=normal&FORMAT=image/png'+
				'&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}', 
				{attribution: '&copy; IGN/Geoplateforme'}),
			//"IGN aérien": L.tileLayer('https://igngp.geoapi.fr/tile.php/orthos/{z}/{x}/{y}.png', {attribution: '&copy; Institut Géographique National (France)'}),				//"Google Hybrid": L.tileLayer('https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', {attribution: '&copy; Google Maps'}),
			"IGN aérien": L.tileLayer('https://data.geopf.fr/wmts?'+
				'&REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&TILEMATRIXSET=PM'+
				'&LAYER=ORTHOIMAGERY.ORTHOPHOTOS&STYLE=normal&FORMAT=image/jpeg'+
				'&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}', 
				{attribution: '&copy; IGN/Geoplateforme'}),				
			//"Google Hybrid": L.tileLayer('https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', {attribution: '&copy; Google Maps'}),
			"Esri Street Map": L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {attribution: 'Tiles &copy; Esri'}),
			"Esri Topo Map": L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {attribution: '&copy; Esri'}),
			"Esri Imagery": L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {attribution: '&copy; Esri'}),		
			"CartoDB": L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {attribution: '&copy; CartoDB, &copy; OpenStreetMap'})
			};

		
			
	/**
	 * II - Méthodes de classe
	 * -----------------------
	 */
	 
	static pushPolygon(polygon) {
		Feuillet.polygonSet.push(polygon);
		return Feuillet.polygonSet.length -1;
	}
	
	static storePolygon(idx) {
		if(idx < Feuillet.polygonSet.length) {
			let shape = Feuillet.polygonSet[idx].toGeoJSON();
			window.localStorage.setItem('feuilletPolygon',JSON.stringify(shape));
			alert('Polygone en mémoire');
		}
		return false;
	}
     
    /**
     * Enregistrement d'un ensemble de styles nommés qui pourront être référencés par les couches
     *
     * @param {object|array} udata		(Tableau d') Objet style à ajouter
     */
     static addStyles(uData) {
     	 if(uData instanceof Array) {
     	 	 for (let i = 0, len = uData.length; i < len; i++) {
     	 	 	 let uStyle = uData[i];
     	 	 	 Feuillet.userStyles[uStyle.name] = uStyle.style;
     	 	 	 //console.log(`Ajout du style '${uStyle.name}'`);
     	 	 }
     	 } else {
     	 	  Feuillet.userStyles[uData.name] = uData.style;
     	 	  //console.log(`Ajout du style '${uData.name}'`);
     	 }
     }	
     
     /**
      * Enregistrement d'icones utilisateur
      */
    static addIcons(data) {
    	if(data instanceof Array) {
    		for(let i=0, len = data.length; i<len; i++) {
    			let ico = data[i];
    			Feuillet.icons[ico.name] = ico.icon;
    		}
    	} else {
    		Feuillet.icons[data.name] = data.icon;
    	}
    }
	
	/**
	 * III - Méthodes et objets d'instance
	 * ----------------------------------
	 */
	 
	 /**
	  * Instantiation d'un Feuillet
	  *
	  *
	  * @param {string} div		Nom du <div> qui abritera la carte
	  * @param {float} lat		Latitude du centre (WGS84)
	  * @param {float} lng		Longitude du centre (WGS84}
	  * @param {integer} zoom	Niveau de zoom
	  * @param {string} base	Nom de la couche de tuiles à utilser comme fond
	  *							Le nom doit être connu de Feuillet.baseMaps
	  * @param {string} imgdir	Chemin relatif d'accès aux fichiers icone (depuis la racine du site)
	  */
	  
	constructor(div, lat, lng, zoom, base = "OSM", imgdir = 'plugins/feuillet/lib/leaflet/images') {
		
		/**
		 * Initialisation de la méthode qui retournera les tuiles utilisées
		 */
		this.defaultMap = function() {
			// lecture du local storage
			var userbase;
			if(window.localStorage && window.localStorage.getItem("feuilletBaseMap")) {
				userbase = window.localStorage.getItem("feuilletBaseMap");
			} else {
				userbase = base;
			}
			// activation de la basemap
			if(userbase in Feuillet.baseMaps) {
				return Feuillet.baseMaps[userbase];
			} else {
				return Feuillet.baseMaps["OSM"];
			}
		};
	
		/**
		 * Déclaration des différentes icones de marker
		 *//*
		 var that = this;
		 this.icons = {};
		 ['blue','green','grey','kaki','orange','purple','red','yellow'].forEach(
		 	 item => {
		 	 	 that.icons[item] = L.icon({
					iconSize: 		[25, 41],
					iconAnchor: 	[12, 41],
					popupAnchor: 	[1, -34],
					shadowSize: 	[41, 41],
					iconUrl: 		`${imgdir}/${item}-mkr.png`,
					shadowUrl:  	`${imgdir}/marker-shadow.png`
				 });
			 });
		*/
		/**
		 * Initialisations complémentaires
		 */
		 this.currentOverlay=1;
		 this.overlays = {};
		 this.controlLayer = null;
		 this.nbFeatures = 0;
		 this.map = L.map(div, {fullscreenControl: true}).setView([lat, lng], zoom);
		 this.defaultMap().addTo(this.map);
		 this.map.on('baselayerchange',this.onBaseLayerChange);
		 this.pointColorSelector = null;
		 this.drawLayer = null;

	} // constructor


	/**
	 * Methode d'accès à la carte courante
	 */	 
	getMap() {
		return this.map;
	}

    /**
     * Méthode retournant un style par défaut pour la gestion automatique des styles
     */  /*   
    defaultStyle() {
        return {
        		"color" : Feuillet.colors[0],
        		"weight" : 2,
        		"opacity" : .75,		// opacité du trait (stroke)
        		"fillOpacity" : .3 		// opacité de remplissage (fill)  
         };
     }
     */
     /**
      * Interception de l'évenement de changement de base map
      * Si le localStorage est actif, on y enregistre la nouvelle couche
      */
     onBaseLayerChange(e) {
     	 if(window.localStorage) {
     	 	 window.localStorage.setItem("feuilletBaseMap",e.name);
     	 }   	 	 
     }


     /**
      * Utilitaire pour charger dynamiquement une configuration spécifique depuis un fichier via une URL.
      *
      * Le fichier comprend un ensemble de styles nommés 
      * Syntaxe du fichier JSON :
      * { styles: [
      *		{ 
      *			"name": "nom-du-style", 
      *			"style": { "color": "#FF0000", "weight": 3.5, "opacity": .75, "fillOpacity": 0 }
      *		}, ...
      *   ]
      * }
      * Si on a besoin d'attendre le résultat du chargement avant de lancer la suite du 
      * traitement, on utilise la callback (sinon null)
      *
      * @param {string} url			URL absolue du fichier à charger
      * @param {function} callback	Callback optionnelle à appeler après chargement 
      * @return this
      */
     loadConfig(url, callback) {
     	 
     	 var onReturn = function(that) {
     	 	 return function(data) {
     	 	 	//console.log(`Réception de la configuration spécifique`);
     	 	 	if(data) {
     	 	 		//console.log(`Configuration reçue`);
     	 	 		
     	 	 		if (Object.hasOwn(data,'styles')){
     	 	 			console.log(`Ajout des styles`);
     	 	 			that.addStyle(data.styles);
     	 	 		}
     	 	 		
     	 	 	}
				
				// Enchainement avec la callback
				if(callback!=null)
					callback(that,data);
			 }
		 };
		 
		 console.log(`Chargement de la configuration depuis '${url}'`);				
     	 jQuery.getJSON(url, onReturn(this));
     	return this;	   	 	 	 
     }     
     
     /**
      * Identification des attributs de style
      *
      * Retourne le style correspondant au nom fourni si celui-ci est connu par l'instance
      * Sinon, retourne un style par défaut choisi à partir de l'index fourni.
      * On utilisera typiquement l'index de l'overlay dans le second cas pour faire tourner 
      * les styles dans l'affichage.
      *
      * @param {string} name	Nom du style à trouver
      * @param {integer} index	Index pour la sélection d'un style par défaut
      * @return {object}		Le style choisi
      */
     styleData(name, index) {
     	 if(Object.hasOwn(Feuillet.userStyles,name)) {
     	 	 // on choisit un style nommé défini par l'utilisateur
     	 	 var localStyle = Feuillet.userStyles[name];
     	 	 localStyle.allowProperties = false;
     	 } else {	 	 
     	 	 // Style par défaut. Alloue une couleur
     	 	 var localStyle = Feuillet.userStyles['default'];
     	 	 var nbColors = Feuillet.colors.length;
     	 	 localStyle.color = Feuillet.colors[index % nbColors];	
     	 	 localStyle.allowProperties = true;
     	 }
     	 // console.log(`styleData(${name},${index})={color:${localStyle.color}, properties:${localStyle.allowProperties}}`);
     	 return localStyle;
     }
     
     /**
      * Selection de la fonction de style
      *
      * Retourne une fonction qui sera appliquée à chaque feature de la couche.
      * Dans le cas d'un style nommé, le style sera le même pour tous les features
      * Pour un style par défaut, le style peut être ignoré si les propriétés du feature
      * contiennent une information de style `feature.properties.style`, auquel cas ce 
      * sera le style du feature qui s'appliquera.
      *
      * @param {string} name	Nom du style à rechercher
      * @param {int} index		Index de l'overlay courant
      * @return {function}		fn(feature) déterminant le style du feature
      */
     #selectStyle(name,index) {
     	 let that = this;

     	 let styler = function(feature) {
     	 	 let xStyle = that.styleData(name,index);
     	 	 // Quand le feature dispose d'un style, il l'impose
     	 	 // sauf si le style est nommé
     	 	 	 if( xStyle.allowProperties 
     	 	 	 	 && ('properties' in feature) 
     	 	 	 	 && ('style' in feature.properties)) 
     	 	 	 {
     	 	 	 	 let fStyle = feature.properties.style;
     	 	 	 	 for( let k in fStyle) { xStyle[k] = fStyle[k]; }
     	 	 	 } 
     	 	 	 return xStyle;
     	 }
        return styler;
     }

     /**
      * Association d'un popup à un feature
      * 
      * Contruit et retourne une fonction qui associera un popup à chaque feature d'une couche
      * La popup contiendra :
      * - le contenu de feature.properties.popupContent s'il existe ; ou
      * - l'ensemble des feature.properties sous la forme "clé: valeur"  précédé du nom de la couche ; ou
      * - uniquement le nom de la couche en l'absence de feature.properties
      * Si le feature est un polygone et si le localStorage est actif, alors un bouton 'copier' 
      * est inséré dans la popup pour permettre le stockage du polygone en Localstorage
      *
      * @param {string} name	Le nom de la couche
      */
     featurePopup(name) {
     	 return function (feature, layer) { 
     	 	 let s="<b>"+name+"</b>";
     	 	 if (feature.properties) {
     	 	 	 if(feature.properties.popupContent) {
     	 	 	 	 // this feature has a property named popupContent?
     	 	 	 	  s= feature.properties.popupContent;
     	 	 	 } else {
     	 	 	 	 // no popupContents : display all properties
     	 	 	 	 for(let p in feature.properties) {
     	 	 	 	 	 s += "</br>\n" + p + ": " + feature.properties[p];
     	 	 	 	 }
     	 	 	 }
     	 	 }   
     	 	 if((feature.geometry.type == 'Polygon')&& window.localStorage) {
     	 	 	 let layerIdx = Feuillet.pushPolygon(layer);
     	 	 	 s += "<br/> <button onclick='Feuillet.storePolygon("+layerIdx+")'>Copier</button>"; 
     	 	 }
     	 	 layer.bindPopup(s);

          };
     }
     
     /**
      * Paramétrage de la détermination de la couleur d'un marqueur
      *
      * Déclare une fonction retournant une couleur de marqueur en fonction 
      * d'un feature Geojson et d'un latlng.
      *
      * @param {function} selector	Fonction de détermination de la couleur : selector(feature, latlng)
      * @return this
      */
     setPointColorSelector(selector) {
     	 this.pointColorSelector = selector;
     	 return this;
     }

     // Association d'une icone à un Marker
     // Si le feature à une propriété 'confidential', on l'utilise pour choisir
     // la couleur de l'icone. Sinon on met la couleur par défaut.
     pointMapper(that) {
     	 return function(feature,latlng) {
     	 	 if(that.pointColorSelector) {
     	 	 	 let color = that.pointColorSelector(feature,latlng);
     	 	 	 return L.marker(latlng).setIcon(Feuillet.icons[color]);
     	 	 } else {
     	 	 	 return L.marker(latlng);
     	 	 }
     	 };
     }
     
     /**
      * Création interactive d'un marker sur la carte
      *
      * La fonction crée un marqueur aux coordonnées lat,lng fournies,  et active un event handler
      * pour détecter les nouveaux clics sur la carte. Ce handler déplacera (effacera et recréra)
      * le marqueur à la position du clic et enregistre la position nouvelle dans deux champs input 
      * de la page HTML.
      *
      * @param {string} latfield	DOM id de l'input recevant la latitude
      * @param {string} lngfield	DOM id de l'input recevant la longitude
      * @param {float} lat			Latitude initiale du marker (WGS 84)
      * @param {float} lng			Longitude initiale du marker (WGS 84)
      */
     recordMarker(latfield,lngfield,lat,lng) {
     	 let marker = new L.marker([lat, lng]);
     	 let carte = this.map;
     	 marker.addTo(carte);
  
     	 function onMapClick(e) {
     	 	 if(marker) {
     	 	 	 carte.removeLayer(marker);
     	 	 }
     	 	 marker = new L.Marker(e.latlng);
     	 	 marker.addTo(carte);
     	 	 document.getElementById(latfield).value = e.latlng.lat;
     	 	 document.getElementById(lngfield).value = e.latlng.lng;
     	 };
     	 carte.on('click', onMapClick);    	 
     }
     
     /**
      * Fonction interne d'ajout d'un overlay unique
      * Reçoit un objet décrivant les propriétés de l'overlay et l'ajoute aux overlays de la carte
      *
      * @param {objet} elem
      *
      * Les propriétés obligatoires de elem sont :
      * - type: 	le format de la source (csv|gpx|geojson|kml|wkt)
      * - name:		le nom de la couche à afficher dans la légende
      * - url:		la localisation de la ressource.
      * - data:		alternative à url pour les données fournies dans une chaine de caractères GeoJSON
      * - style:	Nom du style à utiliser
      *
      * Des propriétés optionnelles peuvent être fournies
      * - oef		Fonction alternative à this.featurePopup : fn(feature,layer)
      * - ptl		Fonction alternative à this.PointMapper : fn(feature,latlng)
      * - onAdd		Event handler à déclencher lors de l'ajout d'un feature
      * - onReady	Event handler à déclecher quand la couche est complètement chargée
      */
     #addOneOverlay(elem) {
       if(elem 
       	   	&& Object.hasOwn(elem,'type') && (elem.type != null)
       		&& Object.hasOwn(elem,'name')
       	    && (Object.hasOwn(elem,'url') || Object.hasOwn(elem,'data'))
       		&& Object.hasOwn(elem,'style')
       		) {
     	 // Fonctions par defaut
     	 var oef = this.featurePopup(elem.name); 	
     	 var ptl = this.pointMapper(this);
     	 // Surcharges possibles
     	 if(elem.oef)
     	 	 oef = elem.oef;
     	 if(elem.ptl)
     	 	 ptl = elem.ptl;
     	 // Preparation du layer GEOjson
     	 var cusLayer = L.geoJson(null, {
     	 	 	 	style: this.#selectStyle(elem.style,this.currentOverlay),
     	 	 	 	onEachFeature: oef,
     	 	 	 	pointToLayer: ptl
     	 });
     	 var olayer = null;
     	 if(elem.type == 'data') {
     	 	 // si le type est 'data', il n'y a pas de champ 'url'
     	 	 // mais un champs 'data' qui contient une string GEOJson.
     	 	 cusLayer.addData(elem.data);
     	 	 olayer = cusLayer;
     	 } else {
     	 	 olayer = omnivore[elem.type](elem.url,null,cusLayer);
     	 }
     	 if(elem.onAdd) {
     	 	 olayer.on("add",elem.onAdd);
     	 }
     	 if(elem.onReady) {
     	 	 olayer.on("ready",elem.onReady);
     	 }

     	 // Génération de la légende
     	 let localStyle = this.styleData(elem.style,this.currentOverlay);
     	 let legend;
     	 
     	 	 let svg = `<svg width="100%" height="100%" viewBox="0 0 100 100"\
     	 	 	xmlns="http://www.w3.org/2000/svg" version="1.1">\
     	 	 	<rect x="0" y="0" width="100" height="100" stroke-width="30" \
     	 	 		stroke="${localStyle.color}" stroke-opacity="${localStyle.opacity}"\
     	 	 		fill="${localStyle.color}" fill-opacity="${localStyle.fillOpacity}" />\
     	 	 	</svg>`;
     	 	 let size = 'width:0.9rem; height:0.9rem; display:inline-block; margin-right:5px;';
     	 	 legend = `<div style='${size}'>${svg}</div>${elem.name}`;
     	 	 // console.log(legend);
     	 
     	 this.overlays[legend] = olayer; 
     	 this.currentOverlay++;
       }
     }
     
     /**
      * Définition d'un ensemble d'overlays qui seront référencés dans le contrôle des overlays. 
      *
      * Convient à une définition statique faisant appel à des urls de fichiers. 
      * Différents types sont supportés par le plugin Omnivore de Leaflet (voir #addOneOverlay)
      *
      * @param {object|array} overlays	(Tableau d') objet décrivant un overlay
      * @return this
      */
     addOverlays(overlays) {
     	 if(overlays instanceof Array) {
     	 	 for (let i = 0, len = overlays.length; i < len; i++) {
     	 	 	let elem = overlays[i];
     	 	 	this.#addOneOverlay(elem);
     	 	 } 
     	 } else {
     	 	 this.#addOneOverlay(overlays);
     	 }
     	 return this;
     }
     
     /**
      * Ajout d'un overlay dont les données sous fournies dans une string
      *
      * @param {string} name		Nom de la couche à afficher dans la légende
      * @param {string} contents	Données géographiques dans une chaine de caractères unique
      * @param {string} type		Type de standard (gpx|geojson|kml)
      * @return this
      */
     addStringOverlay(name,contents,type) {
     	 switch(type) {
     	 case 'kml':
     	 case 'gpx':
     	 	this.controlLayer.addOverlay(omnivore[type].parse(contents), name);
     	 	return this; 
     	default:
     		this.controlLayer.addOverlay(L.geoJson(contents), name);
     		return this; 
     	 }
     }

     /**
      * Ajout d'un layer en tant qu'overlay
      *
      * Déclare un layer existant comme overlay nommé pour le faire apparaitre dans la légende
      * @param {layer} layer	Le layer Leaflet
      * @param {string} name	Le nom de la couche dans la légende
      * @return this
      */
     addLayerAsOverlay(layer,name) {
         this.overlays[name] = layer;
         this.currentOverlay++;
         return this;
     }

     /**
      * Ajuste le positionnement et le zoom de la carte pour afficher tous les overlays
      *
      * @return this
      */
      
     autofitOverlays() {
     	 var that = this;
     	 that.map.on('layeradd layerremove', function() {
     	 		 var bounds = new L.LatLngBounds();
     	 		 for(over in that.overlays) {
     	 		 	 if(that.map.hasLayer(that.overlays[over])) {
     	 		 	 	 bounds.extend(that.overlays[over].getBounds());
     	 		 	 }
     	 		 }
     	 		 if (bounds.isValid()) {
     	 		 	 // Valid, fit bounds
     	 		 	 that.map.fitBounds(bounds);
     	 		 } else {
     	 		 	 // Invalid, fit world
     	 		 	 // that.map.fitWorld();
     	 		 } 
     	 });
     	 return this;
     }    	 		 
     
     /**
      * Définit le centre et le zoom de la carte
      *
      * @param {float} lat		Latitude WGS84
      * @paral {float} lng		Longitude WGS84
      * @param {integer} zoom	Niveau de zoom
      */
      
     setView(lat,lng,zoom) {
     	 this.map.setView([lat, lng], zoom);
     }      
     
     /**
      * Affichage du controle des layers sur la carte
      *
      * Permet également de déterminer si les différentes couches doivent ou non être affichées par défaut
      * et de passer des options à L.controls.layers 
      * (voir : https://web.archive.org/web/20211103212702/https://leafletjs.com/reference-0.7.7.html#control )
      *
      * @param {boolean} show		true si les couches doivent être affichées par défaut
      * @param {object} options		optionnel : définition des options d'affichage du control layer
      * @return this
      */
      
     setLayers(show,options) {
     	 if(typeof options === 'object' && options !== null) {
     	 	 this.controlLayer = L.control.layers(Feuillet.baseMaps,this.overlays,options);
     	 } else {
     	 	 this.controlLayer = L.control.layers(Feuillet.baseMaps,this.overlays);
     	 }
     	 this.controlLayer.addTo(this.map);
     	 if(show) {
     	   for(let over in this.overlays ) {
     	   		this.overlays[over].addTo(this.map);
     	   }
     	 }
     	 return this;
     }
     
     /**
      * Controle l'affichage ou non d'un overlay donné
      *
      * @param {string} name	Nom de l'overlay considéré
      * @param {boolean} show	true si l'overlay doit être affiché
      */
      
     setLayerVisibility(name, show) {
     	 if(show) {
     	 	 this.overlays[name].addTo(this.map);
     	 } else {
     	 	 this.overlays[name].remove();
     	 }
     }
     
     /**
      * Ajout ASYNCHRONE d'un layer geojson chargé dynamiquement.
      *
      * L'url est celle d'un service web, et options est un objet json passé en POST
      * Le layer sera ajouté aux overlays sous le nom "nom"
      * En cas d'erreur, un message s'affiche dans le div "errdiv" 
      *
      * @param {string} url			URL d'un service web retournant une couche GeoJSON
      * @param {object} options		Options du service web
      * @param {string} name		Nom de l'entrée de légende pour le résultat
      * @param {string} errdiv		DOM id de la <div> dans laquelle afficher un éventuel message d'erreur
      * @uses JQuery
      */
      
     addDynamicGeojson(url, options, nom, errdiv) {
     	 var onReturn = function(that, name, errordiv) {
     	 	 return function(data) {
				if (data){
					var lay = L.geoJson(data, {
						onEachFeature: that.featurePopup(name),		// LINE CHANGED 1.5
						pointToLayer: that.pointMapper(that)
						}).addTo(that.map);	
					that.controlLayer.addOverlay(lay, name);
				} else {
					$(errordiv).html('aucune donn&eacute;e.');
				}
			};
		 };
     	 jQuery.getJSON(
     	 	 url, options,
     	 	 onReturn(this, nom, errdiv)
     	 	 );
     	   	 	 	 
     }
     
     /**
      * Ajout ASYNCHRONE d'un layer GeoJSON chargé dynamiquement
      * Variante avec Callback déclenchée après réception du layer
      * 
      * @param {string} url			URL d'un service web retournant une couche GeoJSON
      * @param {object} options		Options du service web
      * @param {string} name		Nom de l'entrée de légende pour le résultat
      * @param {string} errdiv		DOM id de la <div> dans laquelle afficher un éventuel message d'erreur
      * @param {function} callback	Callback appelée à la réception des données*=
      */
      addDynamicGeojsonCbk(url, options, nom, errdiv, callback) {
     	 var onReturn = function(that, name, errordiv, cbk) {
     	 	 return function(data) {
				if (data){
					var lay = L.geoJson(data, {
						onEachFeature: that.featurePopup(name), 	// LINE CHANGED 1.5
						pointToLayer: that.pointMapper(that)
						}).addTo(that.map);	
					that.controlLayer.addOverlay(lay, name);
					cbk(data, that, lay);
				} else {
					$(errordiv).html('aucune donn&eacute;e.');
				}
			};
		 };
     	 jQuery.getJSON(
     	 	 url, options,
     	 	 onReturn(this, nom, errdiv,callback)
     	 	 );
     	   	 	 	 
     }
     
     /**
      * Initialisation de draw avec un polygone décrit en tant que feature GeoJSON
      *
      * Le polygone est décrit sous forme d'une string geoJSON comprenant un seul feature
      * de geometry:'Polygon'. Cette string doit être le contenu d'un textarea dont l'Id est
      * 'dumparea'. Retourne true en cas de succès et false sinon.
      * La propriété `this.nbFeatures` assure qu'au plus un polygone est présent.
      *
      * @param {string} dumparea	Id du textarea contenant la string GeoJSON
      * @return {boolean}			true en cas de succès, false sinon.
      */

     initDrawnShape(dumparea) {
     	 var perimetre = document.getElementById(dumparea).value;
     	 if(perimetre && (perimetre.length > 10) && (this.nbFeatures == 0)) {
     	 	 const pshape = JSON.parse(perimetre);
     	 	 if(typeof pshape === 'object' && pshape != null && Object.hasOwn(pshape,'geometry')) {
     	 	 	 // console.log(pshape.geometry.coordinates[0]);
     	 	 	 // On crée explicitement un polygone en inversant les coordonnées
     	 	 	 // GeoJSON : [lon,lat] mais Leaflet : [lat,lon]
     	 	 	 // Un objet GeoJSON ne serait pas reconnu par leaflet.draw
     	 	 	 L.polygon(pshape.geometry.coordinates[0].map((x)=> x.reverse()))
     	 	 	 	.addTo(this.drawLayer)
     	 	 	 	.setStyle({color: '#ff0000'});
     	 	 	 this.nbFeatures = 1;
     	 	 	 return true;
     	 	 }
     	 }
     	 return false;
     }
     
     /**
      * Reset du layer décrivant la zone de recherche
      *
      * La fonction effectue un reset du layer, ainsi que du texterea décrivant textuellement
      * le feature.
      * 
      * @param {string} dumparea	Id du textarea contenant la string GeoJSON
      */
      
     resetDrawnShape(dumparea) {
     	 this.drawLayer.clearLayers();
     	 document.getElementById(dumparea).value = '';
     	 console.log('Layer de dessin vidé');
     	 this.nbFeatures = 0;
     }
     
     /**
      * Ajout d'un layer pour dessiner sur la carte grace à leaflet.draw
      *
      * La configuration ne permet que de dessiner un polygone unique pour
      * définir un lieu géographique. Un lieu est une restriction du fromat GeoJSON qui contient
      * un feature unique (pas de featureCollectiion) de type Polygon (pas de MultiPolygon)
      * --- NE FONCTIONNE PAS AVEC LEAFLET 1.9.4 ---
      *
      * @param {string} dumparea	DOM Id du textarea qui accueillera le GeoJSON décrivant le polygone
      * @param {string} label 		Nom du layer de dessin dans la légende
      * @param {string} url			Localisation d'un GeoJSON (lieu) d'initialsation du tracé
      * @param {string} descarea	DOM Id du textarea qui accueillera la description du feature chargé (si elle existe)
      */
     addDrawPolygonLayer(dumparea, label, url, descarea) {
     	var drawnItems = new L.FeatureGroup();     
		this.controlLayer.addOverlay(drawnItems, label);
		this.map.addLayer(drawnItems);
		this.drawLayer = drawnItems;
     	
		// Set the title to show on the polygon button
		L.drawLocal.draw.toolbar.buttons.polygon = 'Definir un lieu';

		var drawControl = new L.Control.Draw({
			draw: {
				polyline: false,
				rectangle:false,
				polygon: {
					allowIntersection: false,
					showArea: true,
					drawError: {
						color: '#b00b00',
						timeout: 1000
					},
					shapeOptions: {
						color: '#ff0000' 
					}
				},
				circle: false,
				marker: false
			},
			edit: {
				featureGroup: drawnItems,
				remove: false
			}
		});
		
		this.map.addControl(drawControl);

		var that = this;
		this.map.on('draw:created', function (e) {
				console.log("Creation event.");
				if(that.nbFeatures == 0) {
					console.log("Creation starts.");
					that.nbFeatures = 1;
					var type = e.layerType, layer = e.layer;
					var whr = document.getElementById(dumparea);
					whr.value = JSON.stringify(layer.toGeoJSON());
					drawnItems.addLayer(layer);
					console.log("Created layer.");
				}
		});

		this.map.on('draw:edited', function (e) {
			var layers = e.layers;
			var whr = document.getElementById(dumparea);
			whr.value = "";
			layers.eachLayer(function(layer) {
  				whr.value += JSON.stringify(layer.toGeoJSON());
			});
		});			

		if(url) {
			// Initialisation de la forme à éditer par le contenu d'une URL pointant 
			// vers un GeoJSON (ou lieu). Un seul feature doit être présent.
			 var whr = document.getElementById(dumparea);
			 var dsc = document.getElementById(descarea);
     	 	 var cusLayer = L.geoJson(null, {
     	 	  		  onEachFeature: function(feature,layer) {
     	 	  		  	  layer.props = feature.properties;
     	 	  		  	  drawnItems.addLayer(layer);
     	 	  		  	  whr.value = JSON.stringify(layer.toGeoJSON());
     	 	  		  	  if(feature.properties.descriptif) {
     	 	  		  	  	  dsc.value =  feature.properties.descriptif;
     	 	  		  	  }
     	 	  		  }
     	 	 });
     	 	 var olayer = omnivore.geojson(url,null,cusLayer);
     	 }
     }
};





