Source: globe.js

"use strict";
// web server: http://127.1.0.0:8000/


// Créé un object globe qui pertmet de manipuler cesium plus simplement

/**
* La classe Globe va permettre de manipuler Cesium plus facilement ; elle contient toutes les fonctions techniques,
* qui vont permettre d’ajouter des couches de données,
* de dessiner, de découper, d’ajouter des points de vues de caméra, etc. <br/>
* Les fonctions peuvent être réutilisées dans un autre projet
*
*
*/

class Globe {

  /**
  * Le constructeur de la classe globe, qui créé :  <br/>
  * - le viewer Cesium <br/>
  * - le handler c’est à dire l’objet qui va contenir les évènements liés à la souris <br/>
  * - le tableau contenant les dataSources <br/>
  * - la grille Raf09 pour les conversions hauteur ellipsoïdale/altitude <br/>
  * - le PinBuilder <br/>
  * - déclare toutes les checkbox de la boîte à outils pour l'affichage dynamique <br/>
  * - définit les valeurs par défaut pour le réglage du contraste et luminosité <br/>
  *
  * on supprime aussi le fond de plan, on définit la couleur de fond et on ajoute les logos en bas de l’écran
  *
  * @param  {String} elementId Le nom du contenant html dans lequel on l'ajoute
  * @param  {Object} geocoder Le Geocoder à associer
  */

  constructor(elementId, geocoder){
    // Activer cette ligne pour avoir accès aux différents fonds de plan dispo - accès vers mon compte Cesium
    Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyNDA3NDMwNi0zZGZmLTQ1MzEtOWZjOC1mNzE5YWM2MDkxNjkiLCJpZCI6ODEzNCwic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU1MTI4MTk1NH0.bj-9TqaOHDBD8sMBIeIWTH6-YVl-1Zp6fxjjgP3OXEg';

    // Créer le globe dans la div HTML qui a l'id cesiumContainer
    this.viewer = new Cesium.Viewer(elementId, {
      geocoder: geocoder, // connexion à la base de données d'adresses de l'EMS
      selectionIndicator: false, // enlève le carré vert lorsqu'on clique sur qqch
      requestRenderMode : true, // amélioration de performance: l'appli calcule uniquement quand on lui demande (https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/)
        maximumRenderTimeChange : Infinity,
        baseLayerPicker: false, // enlève le bouton qui permet de choisir le fond de plan
        //scene3DOnly : true, // enlève le bouton permettant de basculer la vue en 2D
        gamma : 5,
        highDynamicRange : true,
        skyBox : new Cesium.SkyBox({ // définit le ciel bleu
          sources : {
            positiveX : 'src/img/Sky.jpg',
            negativeX : 'src/img/Sky.jpg',
            positiveY : 'src/img/Sky.jpg',
            negativeY : 'src/img/Sky.jpg',
            positiveZ : 'src/img/Sky.jpg',
            negativeZ : 'src/img/Sky.jpg'
          }
        })
      });

      //this.viewer.extend(Cesium.viewerCesiumNavigationMixin, {}); // pour ajouter la flèche nord (plus compatible avec Cesium 1.67)


      //PHS
      // Supprime le terrain par défaut sur le globe
      this.viewer.scene.imageryLayers.removeAll();
      // Définit la couleur de fond du globe étant donné qu'on a supprimé le terrain
      this.viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString('#AB9B8B').withAlpha(0.4);
      // importe la grille de conversion pour hauteur ellispoïdale vers altitude IGN69
      this.raf09 = undefined;
      new Raf09('src/grilles/RAF09.mnt', (raf090) => {
        this.raf09 = raf090;
      });

      // Créer la liste des dataSource sous forme d'un object clé / valeur
      // Avec le nom de la source comme clé et la dataSource comme valeur
      this.dataSources = [];

      // insère les logos en bas
      this.viewer.bottomContainer.innerHTML = '<img src="src/img/logo/logo-strasbourg.png" alt="Logo strasbourg" />\
      <img src="src/img/logo/europe-sengage.jpg" alt="Logo strasbourg" />\
      <img src="src/img/logo/logo-ue.jpg" alt="Logo strasbourg" />\
      <span style="color: #BEC8D1;font-size:small;"> Icons created by : <a style="color: #BEC8D1;font-size:small;" href="https://icons8.com" target="_blank" > https://icons8.com </span>';

      // variable qui stocke les évenements liés à la souris
      this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.canvas);

      // Construction de pin pour les billboard
      this.pinBuilder = new Cesium.PinBuilder();

      /*
      * Div pour les affichage de coordonnées et mesures
      */
      // mesures de coords
      this.coordX = document.querySelector('#coordX');
      this.coordY = document.querySelector('#coordY');
      this.coordZ = document.querySelector('#coordZ');

      // mesures de distance
      this.distance = document.querySelector('#distance');
      this.distanceCumulee = document.querySelector('#distancecumulee');
      this.hauteur = document.querySelector('#hauteur');
      this.distanceInclinee = document.querySelector('#distanceinclinee');
      this.distanceInclineeC = document.querySelector('#distanceinclineecum');
      // mesures de surface
      this.aire = document.querySelector('#aire');
      // plan de coupe
      this.altitude = document.querySelector('#alticoupe');
      this.coupeX = document.querySelector('#X');
      this.coupeY = document.querySelector('#Y');
      this.coupeZ = document.querySelector('#hauteurcoupe');

      //paramètres de départ de l'amélioration du contraste et de la luminosité
      this.viewModel = {
        show : true,
        contrast : -98,
        brightness : 0.54,
        delta : 3.0,
        sigma : 5,
        stepSize : 0
      };

    }
    /*
    * Fin du constructor
    */

    /*
    *
    *  Contrôle de la caméra/de la vue affichée
    *
    */

    /**
    *
    * Permet de paramétrer la vue de base de l’application ainsi que le zoom par défaut sur lequel le viewer se repositionne lorsqu’on clique sur le bouton « maison » <br/>
    * Elle ne prend pas de paramètres mais considère deux cas différents : <br/>
    * Si l’URL ne contient pas de paramètres, elle définit le zoom sur le photomaillage global pour la vue de départ et le bouton maison <br/>
    * Si l’URL contient des paramètres spécifiques (outil partage de liens), elle lit ces paramètres pour zoomer à l’endroit voulu et définit le zoom par défaut sur cet endroit
    *
    */
    setHome(){
      var params = this.getAllUrlParams(window.location.href);
      let X = params.x;
      let Y = params.y;
      let Z = params.z;
      let heading = params.heading;
      let pitch = params.pitch;
      let roll = params.roll;

      // si l'URL ne contient pas de paramètres
      if(X === undefined || Y === undefined || Z === undefined || heading === undefined || pitch === undefined || roll === undefined) {
        let position = new Cesium.Cartesian3(4227894, 573584, 4758748);
        this.viewer.camera.setView({
          destination : position,
          orientation: {
            heading : 0.0,
            pitch : -0.808,
            roll : 0
          }
        });
      } else {
        // sinon on lit les paramètres présents dans l'URL
        let position = new Cesium.Cartesian3(X,Y,Z);
        this.viewer.camera.setView({
          destination : position,
          orientation: {
            heading : heading,
            pitch : pitch,
            roll : roll
          }
        });
      }
      // Définir ce qu'il se passe lorsqu'on clique sur le bouton "maison" (ici retour à l'écran d'accueil)
      this.viewer.homeButton.viewModel.command.beforeExecute.addEventListener((e) => {
        e.cancel = true;
        if(X === undefined || Y === undefined || Z === undefined || heading === undefined || pitch === undefined || roll === undefined) {
          let position = new Cesium.Cartesian3(4227894, 573584, 4758748)
          this.fly(position, 0.0, -0.808, 0);

        } else {
          let position = new Cesium.Cartesian3(X,Y,Z);
          this.fly(position, heading, pitch, roll);
        }
      });
    }

    /**
    * lit et retourne les paramètres présents dans l'URL <br/>
    *
    * @param  {String} url L'url à analyser
    * @return  {Object} obj Un objet contenant les paramètres de l'url
    */
    getAllUrlParams(url) {
      var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
      var obj = {};

      if (queryString) {
        queryString = queryString.split('#')[0];
        var arr = queryString.split('&');

        for (var i = 0; i < arr.length; i++) {
          var a = arr[i].split('=');
          var paramName = a[0];
          var paramValue = typeof (a[1]) === 'undefined' ? true : a[1];

          paramName = paramName.toLowerCase();
          if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase();

          if (paramName.match(/\[(\d+)?\]$/)) {
            var key = paramName.replace(/\[(\d+)?\]/, '');
            if (!obj[key]) obj[key] = [];

            if (paramName.match(/\[\d+\]$/)) {
              var index = /\[(\d+)\]/.exec(paramName)[1];
              obj[key][index] = paramValue;
            } else {
              obj[key].push(paramValue);
            }
          } else {
            if (!obj[paramName]) {
              obj[paramName] = paramValue;
            } else if (obj[paramName] && typeof obj[paramName] === 'string'){
              obj[paramName] = [obj[paramName]];
              obj[paramName].push(paramValue);
            } else {
              obj[paramName].push(paramValue);
            }
          }
        }
      }
      return obj;
    }

    /**
    * Définit le point de vue de caméra en fonction de la position de la caméra (Cartesian3) et des 3 paramètres d'orientation
    *
    * @param  {Cartesian3} position La position de la caméra
    * @param  {Number} lacet Le paramètre lacet d'orientation de la caméra
    * @param  {Number} tangage Le paramètre tangage d'orientation de la caméra
    * @param  {Number} roulis Le paramètre roulis d'orientation de la caméra
    */
    fly(position, lacet, tangage, roulis) {
      this.viewer.camera.flyTo({
        destination : position,
        orientation: {
          heading : lacet,
          pitch : tangage,
          roll : roulis
        }
      });
    }

    /**
    * Ajoute un bouton HTML qui enregistre un point de vue de caméra
    *
    * @param  {String} nom Le nom qu'on souhaite donner au point de vue
    * @return  {BoutonHTML} viewPoint Le bouton HTML avec le nom saisi
    */
    addViewPoint(nom) {
      var viewPoint = document.createElement("BUTTON");
      viewPoint.innerHTML = nom;
      viewPoint.classList.add('nowrap');
      document.getElementById("camera-content").appendChild(viewPoint);

      return viewPoint;

    }

    /**
    * récupère les paramètres actuels de position et orientation de la caméra ainsi que les couches cochées
    * pour créer un lien et l’afficher dans le format html correspondant
    */
    createLink() {
      // On récupère les paramètres de la caméra
      let X = globe.viewer.camera.positionWC.x;
      let Y = globe.viewer.camera.positionWC.y;
      let Z = globe.viewer.camera.positionWC.z;
      let heading = globe.viewer.camera.heading;
      let pitch = globe.viewer.camera.pitch;
      let roll = globe.viewer.camera.roll;

      // le premier paramètre doit débuter avec un "?" et les autres paramètres doivent être séparés par un "&"

      // On teste si le lien contient le paramètre open
      if (window.location.search.indexOf('open') > -1) {
        // on récupère l'url et on le sépare à chaque '?'
          var url = window.location.href.split('&');

          // si l'url contient déjà des paramètres d'orientation, on coupe la fin de l'url pour pouvoir en remettre des nouveaux après
          if (window.location.search.indexOf('X') > -1) {
            url.splice(url.length-6, 6);
          }

          var string = '&';
          for (let i = 1; i < url.length; i++) {
            var minuscule = url[i].toLowerCase();
            string += minuscule + '&';
          }

          // on créé le lein
          document.getElementById('nomlink').value = url[0] + string +'X='+X+'&Y='+Y+'&Z='+Z+'&heading='+heading+'&pitch='+pitch+'&roll='+roll;

      }  else { // si le lien ne contient rien du tout on regarde si les couches sont cochées
        var url = window.location.href.split('?');
        var couches = [];
        var open = [];

        // on récupère tous les id dans les panel-content
        $('.panel-content input').each(function(){
          if(this.id !== null) {
            couches.push(this.id);
          }
        });

        // si l'id est défini et coché, on le met dans un tableau
        for (let j = 0; j < couches.length; j++) {
          if(document.getElementById(couches[j]) !== null) {
            if (document.getElementById(couches[j]).checked) {
              open.push(couches[j]);
            }
          }
        }

        // on créé la chaine de caractère qui va permettre d'ouvrir toutes les couches
        var string = '?';
        for (let k = 0; k < open.length; k++) {
          var minuscule = open[k].toLowerCase();
          string += 'open' + k + '=enable' + minuscule + '&';

        }

        document.getElementById('nomlink').value = url[0] + string +'X='+X+'&Y='+Y+'&Z='+Z+'&heading='+heading+'&pitch='+pitch+'&roll='+roll;

      }

    }

    /*
    *
    * Chargement de données 3DTiles
    *
    */

    /**
    * Permet de charger et d'enregister le tileset au format 3DTiles <br/>
    * Ce format est important dans la fonction holePlanes, où on ajoute une collection de ‘clippingPlanes’ qui vont découper le photomaillage ;
    * l’ajout de cette collection n’est effectif qu’avec un format tileset <br/>
    *
    * @param  {String} link Le lien vers le fichier
    * @param  {Number} maxError 'The maximum screen space error used to drive level of detail refinement', différent pour les deux photomaillages 2018 et PSMV
    * @param  {Object} options facultatif - Les options pour le chargement
    * @return  {tileset} tileset Le 3DTileset
    */
    loadPhotomaillage(link, maxError, options = {}){
      // Chargement du photo maillage au format 3D tiles
      let tileset = new Cesium.Cesium3DTileset({
        url : link, // URL vers le ficher JSON "racine"
        maximumScreenSpaceError : maxError,
        maximumNumberOfLoadedTiles : 1000, // Nombre maximum de dalles chargées simultanément
        lightColor : new Cesium.Cartesian3(3,2.8,2.4),
        imageBasedLightingFactor : new Cesium.Cartesian2(2,2),
        luminanceAtZenith : 0.5,
        immediatelyLoadDesiredLevelOfDetail : false,
        foveatedConeSize : 0.5,
        skipLevelOfDetail: true
      });
      return tileset;
    }

    /**
    * ajoute le tileset sous forme d'entités  et garde une structure asynchrone
    * qui permet de pouvoir naviguer dans l’application et d’interagir avec le photomaillage sans que celui-ci ne soit entièrement chargé
    *
    * @param  {tileset} tileset Le 3DTileset à ajouter
    * @return  {tileset} tilesetPrimitive l'entité contenant le tileset
    */
    addPhotomaillage(tileset) {
      var tilesetPrimitive = this.viewer.scene.primitives.add(tileset);
      return tilesetPrimitive.readyPromise;
    }


    /**
    * combine les deux addPhotomaillage et loadPhotomaillage : elle charge le 3DTiles et l’ajoute sous forme d’entités. <br/>
    * Le format tileset est perdue pour les modèles chargés avec cette fonction ;
    * elle permet entres autres de charger des projets 3D sans que ceux-ci ne soient impactés par les découpes effectuées dans le photomaillage
    *
    * @param  {String} link Le lien vers le fichier
    * @param  {Number} maxError 'The maximum screen space error used to drive level of detail refinement', différent pour les deux photomaillages 2018 et PSMV
    * @param  {Object} options facultatif - Les options pour le chargement
    * @return  {tileset} tilesetPrimitive l'entité contenant le tileset
    */
    load3DTiles(link, maxError, options = {}){
      let tileset = globe.viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
        url : link,
        maximumScreenSpaceError : maxError,
        maximumNumberOfLoadedTiles : 1000

      }));
      return tileset.readyPromise;
    }

    /**
    *
    * Afficher ou masquer la source de données 3DTiles "name" en fonction de la valeur de "show" <br/>
    * Si elle n'a pas enore été affiché, la fonction va télécharger les données avec le lien "link" passé en parametre <br/>
    * Permet de visualiser les attributs au clic pour la donnée 3DTiles
    *
    * @param  {String} show le paramètre qui spécifie quand l'affichage doit être actif - prend la valeur e.target.checked ou non
    * @param  {String} link Le lien vers le fichier
    * @param  {String} name Le nom qu'on donne au json
    * @param  {Number} maxError 'The maximum screen space error used to drive level of detail refinement', différent pour les deux photomaillages 2018 et PSMV
    * @param  {Object} options facultatif - Les options pour le chargement
    */
    show3DTiles(show, name, link, maxError, options = {}){
      var scene = this.viewer.scene;
      var handler3D = new Cesium.ScreenSpaceEventHandler(globe.viewer.canvas);

      let selected = {
        feature: undefined,
        originalColor: new Cesium.Color(),
        selectedEntity: new Cesium.Entity() // Une entité qui contient les attributs du batiments selectionné
      };

      if(show){
        if(this.dataSources[name] === undefined){
          globe.showLoader();

          globe.load3DTiles(link, maxError, options).then((dataSource) => {
            this.dataSources[name] = dataSource;

            handler3D.setInputAction(function(movement) {
              // Récuperer la forme sur laquelle on a cliqué
              let pickedFeature = scene.pick(movement.position);
              selected.feature = pickedFeature;
              selected.selectedEntity.name = pickedFeature.getProperty('name');
              selected.selectedEntity.description = '<table class="cesium-infoBox-defaultTable"><tbody>';

              // Générer les lignes du tableau
              let propertyNames = pickedFeature.getPropertyNames();
              for(let i = 0; i < propertyNames.length; i++){
                selected.selectedEntity.description += '<tr><th>' + propertyNames[i] + '</th><td>' + pickedFeature.getProperty(propertyNames[i]) + '</td></tr>';
              }
              selected.selectedEntity.description += '</tbody></table>';

              // Afficher le tableau en haut à droite
              globe.viewer.selectedEntity = selected.selectedEntity;

            }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

            if(options.couleur !== undefined) {
              // Construire et appliquer le style au tileset
              dataSource.style = new Cesium.Cesium3DTileStyle({
                color: options.couleur
              });
            }

            globe.hideLoader();
          });

        } else{
          this.dataSources[name].show = true;
          this.viewer.scene.requestRender();
        }
      } else{
        if(this.dataSources[name] !== undefined){
          this.dataSources[name].show = false;
          this.viewer.scene.requestRender();
          handler3D.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
        }
      }
    }

    /**
    * Affiche une icône de chargement sur l'écran
    */
    showLoader(){
      document.querySelector('#loadingIndicator').classList.remove('hidden');
    }
    /**
    * Retire l'icône de chargement sur l'écran
    */
    hideLoader(){
      document.querySelector('#loadingIndicator').classList.add('hidden');
    }

    /**
    *
    * Afficher ou masquer les ombres
    *
    * @param  {String} enabled le paramètre qui spécifie quand l'affichage doit être actif - prend la valeur e.target.checked ou non
    */
    shadow(enabled){
      this.handler.globe = this; // pour les problèmes de scope
      this.viewer.shadows = enabled;
      if(enabled) {
        document.addEventListener("mousemove", function() {
          globe.viewer.scene.requestRender();
        });
      } else {
        this.supprSouris();
        globe.viewer.scene.requestRender();
      }
    }

    /*
    * Coordonnées
    */

    /**
    *
    * récupère lat/lon/hauteur à chaque clic gauche, les convertit en CC48 / IGN69 et les affiche
    */
    showCoords(){
      let scene = this.viewer.scene;
      this.handler.globe = this; // pour les problèmes de scope

      this.handler.setInputAction(function(event) {
        let cartesian = scene.pickPosition(event.position);
        if (Cesium.defined(cartesian)) {
          let cartographic = Cesium.Cartographic.fromCartesian(cartesian); // cartesian = coords géometriques de l'écran
          // cartographic est en radians

          let longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(7); // conversion en degrés décimaux
          let latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(7);
          let height = cartographic.height.toFixed(3);
          var coords = proj4('EPSG:4326','EPSG:3948', [longitude, latitude]);
          globe.coordX.innerHTML = coords[0].toFixed(2);
          globe.coordY.innerHTML = coords[1].toFixed(2);
          globe.coordZ.innerHTML = (Number(height) - Number(globe.raf09.getGeoide(latitude, longitude))).toFixed(2);
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    }

    /*
    * Plan de coupe horizontal
    */

    /**
    *
    * ajoute un plan de coupe horizontal <br/>
    * les paramètres de la fonction vont être lus dans le formulaire avant de cliquer sur 'ajouter'
    *
    * @param  {Number} X la coordonnée X du point au centre du plan
    * @param  {Number} Y la coordonnée Y du point au centre du plan
    * @param  {Number} hauteurCoupe la coordonnée Z du point au centre du plan
    * @param  {Number} longueurCoupe la largeur du plan
    * @param  {Number} largeurCoupe la longueur du plan
    * @param  {String} couleurCoupe la couleur du plan
    * @param  {Object} planeEntities les entités de plans
    * @param  {ClippingPlaneCollection} clippingPlanes la collection de plan (array) dans laquelle stocker les plans
    */
    addClippingPlanes(X, Y, hauteurCoupe, longueurCoupe, largeurCoupe, couleurCoupe, planeEntities, clippingPlanes) {
      // on n'associe pas les clippingPlanes au tileset: c'est pour ça qu'ils ne coupent pas le tileset mais passent à travers
      var clippingPlanes = new Cesium.ClippingPlaneCollection({
        planes : [
          new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 0, -1), 0.0) // donne l'orientation du clippingPlanes
        ]
      });

      for (var i = 0; i < clippingPlanes.length; i=+1) {
        var coords = proj4('EPSG:3948','EPSG:4326', [Number(X), Number(Y)]);
        var a = Number(this.raf09.getGeoide(coords[1], coords[0]));

        var y = coords[1];
        var x = coords[0];
        var z = (Number(hauteurCoupe) + a);

        var plane = clippingPlanes.get(i);
        var planeEntity = this.viewer.entities.add({
          position : Cesium.Cartesian3.fromDegrees(x, y, z),
          plane : {
            dimensions : new Cesium.Cartesian2(longueurCoupe, largeurCoupe),
            material : Cesium.Color.fromCssColorString(couleurCoupe).withAlpha(0.4),
            plane : new Cesium.CallbackProperty(this.planeUpdate(plane, couleurCoupe), false),
            outline : true,
            outlineColor : Cesium.Color.WHITE
          }
        });
        planeEntities.push(planeEntity);
      }
      this.viewer.scene.requestRender();
    }

    /**
    *  Récupère les coordonnées au clic et les affiche dans le formulaire du plan de coupe horizontal
    */
    coordCoupe(){
      let scene = this.viewer.scene;
      this.handler.globe = this;

      this.handler.setInputAction(function(event) {
        let cartesian = scene.pickPosition(event.position);
        if (Cesium.defined(cartesian)) {
          let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
          let longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(7);
          let latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(7);
          let height = cartographic.height.toFixed(3);

          var coords = proj4('EPSG:4326','EPSG:3948', [longitude, latitude]);
          document.getElementById("X").value = (coords[0].toFixed(2));
          document.getElementById("Y").value = (coords[1].toFixed(2));
          document.getElementById("hauteurcoupe").value = ((Number(height) - Number(globe.raf09.getGeoide(latitude, longitude))).toFixed(2));
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    }

    /**
    *
    *  permet de gérer les mouvements du plan de coupe avec la souris
    *
    * @param  {Object} plane l'entité de plan
    * @param  {String} couleurCoupe la couleur du plan à chaque évenement de souris
    * @return {Entity} shape l'entité plan avec les évènements associés
    */
    planeUpdate(plane, couleurCoupe) {
      var targetY = 0.0;
      var selectedPlane;
      var scene = this.viewer.scene;
      this.altitude.innerHTML = 0;
      this.handler.globe = this;

      // Select plane when mouse down
      this.handler.setInputAction(function(movement) {
        var pickedObject = scene.pick(movement.position);
        if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id) && Cesium.defined(pickedObject.id.plane)) {
          selectedPlane = pickedObject.id.plane;
          selectedPlane.material = Cesium.Color.fromCssColorString(couleurCoupe).withAlpha(0.4);
          selectedPlane.outlineColor = Cesium.Color.WHITE;
          scene.screenSpaceCameraController.enableInputs = false;
        }
      }, Cesium.ScreenSpaceEventType.LEFT_DOWN);

      // Release plane on mouse up
      this.handler.setInputAction(function() {
        if (Cesium.defined(selectedPlane)) {
          selectedPlane.material = Cesium.Color.fromCssColorString(couleurCoupe).withAlpha(0.4);
          selectedPlane.outlineColor = Cesium.Color.WHITE;
          selectedPlane = undefined;
        }
        scene.screenSpaceCameraController.enableInputs = true;
      }, Cesium.ScreenSpaceEventType.LEFT_UP);

      // Update plane on mouse move
      this.handler.setInputAction(function(movement) {
        if (Cesium.defined(selectedPlane)) {
          var deltaY = movement.startPosition.y - movement.endPosition.y;
          targetY += deltaY;
        }
        globe.altitude.innerHTML = targetY; // affiche la différence entre l'altitude actuelle et l'altitude de départ (pas métrique)
        globe.viewer.scene.requestRender();
      }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

      return function () {
        plane.distance = targetY;
        return plane;
      };
    }

    /*
    *
    *
    *  Outils de dessin
    *
    *
    */

    /**
    *
    *  permet de créer un point: entité uniquement technique qui sert à dessiner les autres figures
    * (chaque point est affiché transparent)
    *
    * @param  {Cartesian3} worldPosition la position du point
    */
    createPoint(worldPosition) {
      var point = this.viewer.entities.add({
        position : worldPosition,
        point : {
          color : Cesium.Color.TRANSPARENT,
          pixelSize : 1,
          heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND // plaque au 3dtiles
        }
      });
      return point;
    }

    /**
    *
    * Construit un marqueur maki (pin) à l'aide du symbole (url) et de la couleur <br/>
    * On ajoute directement les entités dans le tableau dans la fonction ici à cause de la structure asynchrone
    *
    * @param  {Array} billboard le tableau où stocker les entités billboard
    * @param  {Cartesian3} worldPosition la position du point
    * @param  {String} url le lien vers l'image à utiliser
    * @param  {String} couleur la couleur du pin
    * @param  {Number} height la hauteur du pin
    * @param  {Boolean} size true si on veut la taille en mètre, false si on veut la taille en pixels
    * @return {Entity} shape l'entité ajoutée au viewer
    */
    createPinBillboard(billboard, worldPosition, url, couleur, height, size) {
      this.handler.globe = this;
      var url = Cesium.buildModuleUrl(url);
      Cesium.when(globe.pinBuilder.fromUrl(url, Cesium.Color.fromCssColorString(couleur), height), function(canvas) {
        var shape = globe.viewer.entities.add({
          position : worldPosition,
          billboard : {
            image : canvas.toDataURL(),
            height: height,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
            sizeInMeters: size
          }
        });
        billboard.push(shape);
        return shape;
      });
    }

    /**
    *
    *  Ajoute un billboard simple (une image) à une position spécifiée (structure synchrone)
    *
    * @param  {Cartesian3} worldPosition la position du point
    * @param  {String} url le lien vers l'image à utiliser
    * @param  {Boolean} size true si on veut la taille en mètre, false si on veut la taille en pixels
    * @return {Entity} shape l'entité ajoutée au viewer
    */
    createBillboard(worldPosition, url, size) {
      var symbol = this.viewer.entities.add({
        position : worldPosition,
        billboard : {
          image: url,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          sizeInMeters: size,
          scaleByDistance : new Cesium.NearFarScalar(10000, 1, 150000, 0)
        }
      });
      return symbol;
    }

    /**
    *
    *  Ajoute une polyligne
    *
    * @param  {Cartesian3} positionData les coordonnées des sommets de la ligne
    * @param  {Number} largeur la largeur de la ligne
    * @param  {String} couleur le couleur de la ligne
    * @param  {Number} transparence la transparence de la ligne
    * @param  {Boolean} clamp true si on veut que la ligne soit collée au photomaillage, false si pas collée
    * @return {Entity} shape l'entité ajoutée au viewer
    */
    drawLine(positionData, largeur, couleur, transparence, clamp, name) {
      var shape = this.viewer.entities.add({
        name: name,
        polyline : {
          positions : positionData,
          material : Cesium.Color.fromCssColorString(couleur).withAlpha(transparence),
          clampToGround : clamp,
          width : largeur
        }
      });
      return shape;
    }

    /**
    *
    *  Ajoute une polyligne avec une flèche au bout
    *
    * @param  {Cartesian3} positionData les coordonnées des sommets de la ligne
    * @param  {Number} largeur la largeur de la ligne
    * @param  {String} couleur le couleur de la ligne
    * @param  {Number} transparence la transparence de la ligne
    * @param  {Boolean} clamp true si on veut que la ligne soit collée au photomaillage, false si pas collée
    * @return {Entity} shape l'entité ajoutée au viewer
    */
    drawArrowLine(positionData, largeur, couleur, transparence, clamp) {
      var shape = this.viewer.entities.add({
        polyline : {
          positions : positionData,
          material : new Cesium.PolylineArrowMaterialProperty(Cesium.Color.fromCssColorString(couleur).withAlpha(transparence)),
          clampToGround : clamp,
          width : largeur
        }
      });
      return shape;
    }

    /**
    *
    *  Ajoute une polyligne en pointillés
    *
    * @param  {Cartesian3} positionData les coordonnées des sommets de la ligne
    * @param  {Number} largeur la largeur de la ligne
    * @param  {String} couleur le couleur de la ligne
    * @param  {Number} transparence la transparence de la ligne
    * @param  {Boolean} clamp true si on veut que la ligne soit collée au photomaillage, false si pas collée
    * @return {Entity} shape l'entité ajoutée au viewer
    */
    drawDashLine(positionData, largeur, couleur, transparence, clamp) {
      var shape = this.viewer.entities.add({
        polyline : {
          positions : positionData,
          material : new Cesium.PolylineDashMaterialProperty({
            color : Cesium.Color.fromCssColorString(couleur).withAlpha(transparence)
          }),
          clampToGround : clamp,
          width : largeur
        }
      });
      return shape;
    }

    /**
    *
    *  Ajoute un polygone pour les entités rectangle --> interprète les coordonnées en degrés
    *
    * @param  {Cartesian3} positionData les coordonnées des sommets du rectangle en lat/lon WGS84 degrés
    * @param  {String} couleur le couleur du rectangle
    * @param  {Number} transparence la transparence du rectangle
    * @return {Entity} shape l'entité ajoutée au viewer
    */
    drawRectangle(positionData, couleur, transparence) {
      var shape = this.viewer.entities.add({
        polygon: {
          hierarchy: new Cesium.Cartesian3.fromDegreesArray(positionData),
          material : Cesium.Color.fromCssColorString(couleur).withAlpha(transparence)
        }
      });
      return shape;
    }

    /**
    *
    *  Ajoute une surface
    *
    * @param  {Cartesian3} positionData les coordonnées des sommets de la surface
    * @param  {String} couleur le couleur de la surface
    * @param  {Number} transparence la transparence de la surface
    * @return {Entity} shape l'entité ajoutée au viewer
    */
    drawPolygon(positionData, couleur, transparence) {
      var shape = this.viewer.entities.add({
        polygon: {
          hierarchy: positionData,
          material : Cesium.Color.fromCssColorString(couleur).withAlpha(transparence)
        }
      });
      return shape;
    }

    /**
    *
    *  Ajoute une surface extrudée, ie une boîte pour laquelle on précise la hauteur
    *
    * @param  {Cartesian3} positionData les coordonnées des sommets de la boîte
    * @param  {String} couleur le couleur de la boîte
    * @param  {Number} transparence la transparence de la boîte
    * @param  {Number} hauteurVol la hauteur de la boîte
    * @return {Entity} shape l'entité ajoutée au viewer
    */
    drawVolume(positionData, couleur, transparence, hauteurVol) {
      var shape = this.viewer.entities.add({
        polygon: {
          hierarchy: positionData,
          material : Cesium.Color.fromCssColorString(couleur).withAlpha(transparence),
          extrudedHeight: hauteurVol,
          shadows : Cesium.ShadowMode.ENABLED
          //extrudedHeightReference : Cesium.HeightReference.CLAMP_TO_GROUND
        }
      });
      return shape;
    }

    /**
    *
    * La fonction qui permet de tout dessiner <br/>
    * le paramètre choice designe si on mesure (dessins temporaires) ou si on dessine
    * (dessins qui restent après fermeture de la fonction de dessin) <br/>
    * le paramètre choice2 désigne le type de dessin (line, surface, volume etc) <br/>
    * On met tous les tableaux d'entités en paramètres options de la fonction car ils seront définis dans la classe Menu
    * pour garder une trace des entités et permettre leur annulation/exportation <br/>
    * Le reste des paramètres en options correspond aux paramètres de personnalisation définis par l'utilisateur dans les formulaires
    *
    * @param  {String} choice prend la valeur 'dessin' ou 'mesure'
    * @param  {String} choice2 le type d'entités à dessiner: 'point', 'line', 'polygon' ou 'volume'
    * @param  {String} couleur le couleur de l'entité
    * @param  {Object} options facultatif - Les options pour le chargement
    * @param  {Number} options.largeur la largeur de l'entité
    * @param  {Number} options.transparence la transparence de l'entité
    * @param  {Number} options.hauteurVol la hauteur de l'entité
    * @param  {Number} options.distance la distance de décalage pour les entités rectangle
    * @param  {String} options.url le lien vers les images pour les entités billboard
    * @param  {Array} options.billboard le tableau où stocker les entités billboard
    * @param  {Array} options.line le tableau où stocker les entités lignes
    * @param  {Array} options.rectangle le tableau où stocker les entités rectangles
    * @param  {Array} options.surface le tableau où stocker les entités surface
    * @param  {Array} options.volume le tableau où stocker les entités boîte
    * @param  {Array} options.dline le tableau où stocker les entités lignes pour les mesures
    * @param  {Array} options.dsurface tableau où stocker les entités surface pour les mesures
    * @param  {Array} options.label tableau où stocker les étiquettes pour les mesures de lignes
    */
    updateShape(choice, choice2, couleur, options = {}) {
      var activeShapePoints = [];
      var coordsline = [];
      var coordsRectangle = [];

      var activeShape;
      var floatingPoint;
      var z;
      var distanceList;
      var distanceListDebut = $("#distanceList").clone();

      var scene = this.viewer.scene;
      this.handler.globe = this; // problème de scope à l'intérieur du this.handler

      this.handler.setInputAction(function(event) {
        var earthPosition = scene.pickPosition(event.position);
        if(Cesium.defined(earthPosition)) {
          coordsline.push(earthPosition);
          if(activeShapePoints.length === 0) {
            // on ajoute 2 fois un point au début pour permettre l'affichage de la ligne/surface
            // le dernier point correspond au point flottant du mouvement de la souris
            floatingPoint = globe.createPoint(earthPosition);
            activeShapePoints.push(earthPosition);
            activeShapePoints.push(earthPosition);
            var dynamicPositions = new Cesium.CallbackProperty(function () {
              if (choice === 'polygon' || choice === 'volume' || choice === 'rectangle') {
                return new Cesium.PolygonHierarchy(activeShapePoints);
              }
              return activeShapePoints;
            }, false);
            options.largeur = parseFloat(options.largeur);
            options.transparence = parseFloat(options.transparence);
            if(choice === 'point') {
              activeShape = globe.createPoint(dynamicPositions);
              if($('#taille').val() === 'metre') {
                floatingPoint = globe.createPinBillboard(options.billboard, earthPosition, options.url, couleur, options.hauteurVol, true);
                options.billboard.pop();
              } else if($('#taille').val() === 'pixel') {
                floatingPoint = globe.createPinBillboard(options.billboard, earthPosition, options.url, couleur, options.hauteurVol, false);
              }
            } else if(choice === 'polygon') {
              activeShape = globe.drawPolygon(dynamicPositions, couleur, options.transparence);
            } else if(choice === 'volume') {
              z = globe.getHauteur(activeShapePoints, options.hauteurVol);
              activeShape = globe.drawVolume(dynamicPositions, couleur, options.transparence, z);
            } else if(choice === 'line') {
              if(choice2 === 'mesure') {
                activeShape = globe.drawLine(dynamicPositions, options.largeur, couleur, options.transparence, false); // 1ère ligne non collée au sol pour la distance inclinée
                options.largeur = parseFloat(options.largeur);
                activeShape = globe.drawLine(dynamicPositions, options.largeur, '#000000', '0.5', true); // 2ème collée au sol pour distance horizontale
              } else if(choice2 === 'dessin') {
                couleur = couleur.toString();
                if($('#clampligne').val() === 'colle') { // clamp to ground ou pas
                  if($('#styleligne').val() === 'simple') { // style normal
                    activeShape = globe.drawLine(dynamicPositions, options.largeur, couleur, options.transparence, true);
                  } else if($('#styleligne').val() === 'pointille') { // style pointillé
                    activeShape = globe.drawDashLine(dynamicPositions, options.largeur, couleur, options.transparence, true);
                  } else if($('#styleligne').val() === 'fleche') { // avec une flèche à la fin
                    activeShape = globe.drawArrowLine(dynamicPositions, options.largeur, couleur, options.transparence, true);
                  }
                } else if($('#clampligne').val() === 'noncolle'){ // mêmes instructions avec la ligne non collée au sol
                  if($('#styleligne').val() === 'simple') {
                    activeShape = globe.drawLine(dynamicPositions, options.largeur, couleur, options.transparence, false);
                  } else if($('#styleligne').val() === 'pointille') {
                    activeShape = globe.drawDashLine(dynamicPositions, options.largeur, couleur, options.transparence, false);
                  } else if($('#styleligne').val() === 'fleche') {
                    activeShape = globe.drawArrowLine(dynamicPositions, options.largeur, couleur, options.transparence, false);
                  }
                }
              }
            }
          } else {
            activeShapePoints.push(earthPosition);
            if(choice === 'point'){
              globe.createPoint(earthPosition);
              if($('#taille').val() === 'metre') {
                globe.createPinBillboard(options.billboard, earthPosition, options.url, couleur, options.hauteurVol, true);
              } else if($('#taille').val() === 'pixel') {
                globe.createPinBillboard(options.billboard, earthPosition, options.url, couleur, options.hauteurVol, false);
              }
            } else {
              globe.createPoint(earthPosition);
            }
          }
        }
        // affichage des labels pour les distances
        if(choice === 'line' && choice2 === 'mesure') {
          // on créé le texte à afficher
          var labeldistance = 'Dh : ' + $("#distance").text() + ' m \n Di : ' + $("#distanceinclinee").text() + ' m \n Da : ' +$("#hauteur").text() + ' m';
          // on récupère les coordonnées du point central
          var positionarray = globe.getMiddlePoint(coordsline);

          // dès qu'on a 2 points on ajoute le label
          if(positionarray[0] != undefined) {
            options.label.push(globe.viewer.entities.add({
              position: positionarray[1],
              label: {
                text: labeldistance,
                font: "20px sans-serif",
                scaleByDistance : new Cesium.NearFarScalar(10000, 1, 120000, 0),
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                showBackground: true,
                backgroundColor: Cesium.Color.fromCssColorString('#1a1b1c')
              }
            }));

            // on trace la ligne qui part du label jusqu'à la ligne qu'on mesure
            options.dline.push(globe.drawLine(positionarray, 1, '#000000', 0.5, false));
            globe.viewer.scene.requestRender();
          }



        }

        if(choice === 'polygon'&& choice2 === 'mesure') {
          globe.measureSurface(activeShapePoints); // mesure l'aire du polygone à chaque clic gauche
        }
        globe.viewer.scene.requestRender();
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

      this.handler.setInputAction(function(event) {
        if(Cesium.defined(floatingPoint)) {
          var newPosition = scene.pickPosition(event.endPosition);
          if (Cesium.defined(newPosition)) {
            floatingPoint.position.setValue(newPosition);
            activeShapePoints.pop();
            activeShapePoints.push(newPosition);
          }
        }
        if(choice === 'line' && choice2 === 'mesure') {
          var tabldistance = {};
          tabldistance = globe.measureDistance(activeShapePoints); // mesure la distance à chaque mouvement de souris
          if(tabldistance.distance != undefined) {
            $("#distance").text((tabldistance.distance.toFixed(2).toString()));
            $("#distanceinclinee").text((tabldistance.distanceIncl.toFixed(2).toString()));
            $("#hauteur").text((tabldistance.difference.toString()));
            $("#distancecumulee").text(tabldistance.distCumul);
            $("#distanceinclineecum").text(tabldistance.distInclCumul);

            tabldistance.distance = 0;
            tabldistance.distanceIncl = 0;
            tabldistance.difference = 0;
            tabldistance.distCumul = 0;
            tabldistance.distInclCumul = 0;
          }
          distanceList = $("#distanceList").clone();

        }
        globe.viewer.scene.requestRender();
      }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

      this.handler.setInputAction(function(event) {
        options.largeur = parseFloat(options.largeur);
        options.transparence = parseFloat(options.transparence);
        // on supprime le dernier point flottant
        activeShapePoints.pop();

        // on ajoute les entités dans le taleau d'entités correspondant
        if(choice2 === 'dessin'){
          if(choice === 'point') {
            globe.createPoint(activeShapePoints);
            if($('#taille').val() === 'metre') {
              globe.createPinBillboard(options.billboard, activeShapePoints, options.url, couleur, options.hauteurVol, true);
            } else if($('#taille').val() === 'pixel') {
              globe.createPinBillboard(options.billboard, activeShapePoints, options.url, couleur, options.hauteurVol, false);
            }
          } else if(choice === 'line') {
            if($('#clampligne').val() === 'colle') {
              if($('#styleligne').val() === 'simple') {
                options.line.push(globe.drawLine(activeShapePoints, options.largeur, couleur, options.transparence, true));
              } else if($('#styleligne').val() === 'pointille') {
                options.line.push(globe.drawDashLine(activeShapePoints, options.largeur, couleur, options.transparence, true));
              } else if($('#styleligne').val() === 'fleche') {
                options.line.push(globe.drawArrowLine(activeShapePoints, options.largeur, couleur, options.transparence, true));
              }
            } else if($('#clampligne').val() === 'noncolle') {
              if($('#styleligne').val() === 'simple') {
                options.line.push(globe.drawLine(activeShapePoints, options.largeur, couleur, options.transparence, false));
              } else if($('#styleligne').val() === 'pointille') {
                options.line.push(globe.drawDashLine(activeShapePoints, options.largeur, couleur, options.transparence, false));
              } else if($('#styleligne').val() === 'fleche') {
                options.line.push(globe.drawArrowLine(activeShapePoints, options.largeur, couleur, options.transparence, false));
              }
            }

          } else if( choice === 'polygon') {
            options.surface.push(globe.drawPolygon(activeShapePoints, couleur, options.transparence));
          } else if( choice === 'rectangle') {
            coordsRectangle = globe.getRectangle(activeShapePoints, options.distance);
            options.rectangle.push(globe.drawRectangle(coordsRectangle, couleur, options.transparence));
          } else if( choice === 'volume') {
            options.volume.push(globe.drawVolume(activeShapePoints, couleur, options.transparence, z));
          }
        } else if(choice2 === 'mesure'){
          if(choice === 'line') {
            options.dline.push(globe.drawLine(activeShapePoints, options.largeur, couleur, options.transparence, false));
            options.dline.push(globe.drawLine(activeShapePoints, options.largeur, '#000000', '0.5', true));
          } else if( choice === 'polygon') {
            options.dsurface.push(globe.drawPolygon(activeShapePoints, couleur, options.transparence));
          }
        }
        globe.viewer.entities.remove(floatingPoint);
        globe.viewer.entities.remove(activeShape);
        floatingPoint = undefined;
        activeShape = undefined;
        activeShapePoints = [];
        coordsline = [];

        if(choice === 'line' && choice2 === 'mesure') {
          // permet de garder l'affichage des mesures actif
          $("#distanceList").replaceWith(distanceList);
        }

        globe.viewer.scene.requestRender();
        //options.billboard.pop(); // quand on clique droit avec le billboard Cesium ajoute un billboard à la position (0,0,0)
      }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);

    }

    /**
    *
    *  Enlève la dernière entité dans le tableau spécifié en paramètre
    *
    * @param  {String} element l'élément HTML sur lequel ajouter l'évènement
    * @param  {Array} figure le tableau d'entités à impacter
    */
    annulFigure(element, figure) {
      document.querySelector(element).addEventListener('click', (e) => {
        var lastLine = figure.pop();
        this.viewer.entities.remove(lastLine);
        this.viewer.scene.requestRender();
      });
    }

    /**
    *
    *  Supprime toutes les entités par catégorie (vide le tableau correspondant)
    *
    * @param  {String} element l'élément HTML sur lequel ajouter l'évènement
    * @param  {Array} figure le tableau d'entités à impacter
    */
    supprFigure(element, figure) {
      document.querySelector(element).addEventListener('click', (e) => {
        for(var i = 0; i < figure.length; i++){
          this.viewer.entities.remove(figure[i]);
        }
        for(var j = 0; j <= figure.length+1; j++){
          figure.pop();
        }
        this.viewer.scene.requestRender();
      });
      this.viewer.scene.requestRender();
    }

    /**
    *
    *  Permet de mesurer la distance horizontale et inclinée entre deux points
    *
    * @param  {Array} activeShapePoints le tableau de coordonnées cartésiennes x y z des points à partir duquel calculer la distance
    * @return  {Object} un objet qui contient les 5 valeurs calculées (Dh, Dh cumulé, Di, Di cumulé, Dh)
    */
    measureDistance(activeShapePoints)  {
      var coordsX = [];
      var coordsY = [];
      var coordsZ = [];
      var distance;
      var distanceIncl;
      var difference;
      var distCumul =0;
      var distInclCumul =0;

      for (let i=0; i < activeShapePoints.length; i+=1) {
        // convertit les coordonnées cartésiennes en lat/lon, puis en CC48
        var cartesian = new Cesium.Cartesian3(activeShapePoints[i].x, activeShapePoints[i].y, activeShapePoints[i].z);
        let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        let longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(7);
        let latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(7);
        let height = cartographic.height.toFixed(3);

        var coords = proj4('EPSG:4326','EPSG:3948', [longitude, latitude]);
        // on stocke chque coordonnée dans un tableau séparé pour faliciter le calcul
        coordsX.push(coords[0]);
        coordsY.push(coords[1]);
        var z = (Number(height) - Number(this.raf09.getGeoide(latitude, longitude))); // conversion des hauteurs
        coordsZ.push(z);
      }

      for (let i=0; i < coordsX.length-1; i+=1) {
        // calcul de distances et différences d'alti
        var a = (coordsX[i+1]-coordsX[i])*(coordsX[i+1]-coordsX[i]);
        var b = (coordsY[i+1]-coordsY[i])*(coordsY[i+1]-coordsY[i]);
        var c = (coordsZ[i+1]-coordsZ[i])*(coordsZ[i+1]-coordsZ[i]);

        distance = Number(Math.sqrt(a+b).toFixed(3));
        distanceIncl = Number(Math.sqrt(a+b+c).toFixed(3));
        difference = Number(coordsZ[i+1]-coordsZ[i]).toFixed(2);

        if(distance !== undefined) {
          distCumul = Number((distCumul + distance).toFixed(2));
          distInclCumul = Number((distInclCumul + distanceIncl).toFixed(2));

        }
      }
      return {distance, distCumul, distanceIncl, distInclCumul, difference};

      if(distance !== undefined) {
        distance = 0;
        distanceIncl = 0;
        difference = 0;
        distCumul = 0;
        distInclCumul = 0;
      }
    }

    /**
    *
    *  Calcule et retourne les coordonnées du point au milieu du segment (pour l'affichage des mesures de distances)
    *
    * @param  {Array} activeShapePoints le tableau de coordonnées cartésiennes x y z des points à partir duquel calculer la distance
    * @return  {Array} Une array de longeur 2 avec les coordonnées au sol et en l'air de l'étiquette à placer pour les mesures
    */
    getMiddlePoint(activeShapePoints) {
      var coordsX = [];
      var coordsY = [];
      var coordsZ = [];

      for (let i=0; i < activeShapePoints.length; i+=1) {
        // convertit les coordonnées cartésiennes en lat/lon, puis en CC48
        var cartesian = new Cesium.Cartesian3(activeShapePoints[i].x, activeShapePoints[i].y, activeShapePoints[i].z);
        let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        let longitude = Cesium.Math.toDegrees(cartographic.longitude);
        let latitude = Cesium.Math.toDegrees(cartographic.latitude);

        coordsX.push(longitude);
        coordsY.push(latitude);
        coordsZ.push(cartographic.height);
      }
      if(coordsX.length > 1) {
        for (let i=0; i < coordsX.length-1; i+=1) {
          // calcul de distances et différences d'alti
          var a = (coordsX[i+1]+coordsX[i])/2;
          var b = (coordsY[i+1]+coordsY[i])/2;
          var c = (coordsZ[i+1]+coordsZ[i])/2;
          var d = c + 20;

          var coordlabel = new Cesium.Cartesian3.fromDegrees(a,b,d);
          var coordsol = new Cesium.Cartesian3.fromDegrees(a,b,c);

        }
      }

      return [coordsol, coordlabel];
    }

    /**
    *
    *  Mesure l'aire plaquée au sol (2D) du polygone dessiné (le calcul de surface s'effectue uniquement avec les coordonnées XY)
    *
    * @param  {Array} activeShapePoints le tableau de coordonnées cartésiennes x y z des points à partir duquel calculer l'aire
    */
    measureSurface(activeShapePoints) {
      var coordsX = [];
      var coordsY = [];
      var aire = 0;
      this.aire.innerHTML = 0;

      for (let i=0; i < activeShapePoints.length-1; i+=1) {
        // convertit les coordonnées cartésiennes en lat/lon, puis en CC48
        var cartesian = new Cesium.Cartesian3(activeShapePoints[i].x, activeShapePoints[i].y, activeShapePoints[i].z);
        let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        let longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(7);
        let latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(7);

        var coords = proj4('EPSG:4326','EPSG:3948', [longitude, latitude]);
        coordsX.push(coords[0]);
        coordsY.push(coords[1]);
      }

      // dès qu'on a au moins 3 sommets
      if(coordsX.length > 2){
        for (let i=0; i < coordsX.length; i+=1) {
          // le % est un modulo qui permet de faire une boucle des sommets (ie sommet n+1 = sommet 1)
          var a = (coordsX[(i+1) % coordsX.length] - coordsX[i]);
          var b = (coordsY[(i+1) % coordsX.length] + coordsY[i] - (2 * coordsY[0]));
          var c = ((Number(a) * Number(b))/2);
          aire = (Number(aire) + Number(c)).toFixed(1);
        }
      }
      this.aire.innerHTML = Math.abs(aire);
    }

    /**
    *
    * Retourne les coordonnées des 4 points du sommets du rectangle à partir de 2 points cliqués et d'une distance de décalage précisée
    * par l'utilisateur
    *
    * @param  {Array} activeShapePoints le tableau de coordonnées cartésiennes x y z des points à partir duquel calculer l'aire
    * @param  {Number} distance la distance de décalage en mètres spécifiée par l'utilisateur
    * @return  {Array} coordfinal le tableau des 4 couples de coordonnées des sommets du rectangle
    */
    getRectangle(activeShapePoints, distance) {
      var coordsX = [];
      var coordsY = [];
      var coordsZ = [];
      var latlon = [];

      for (let i=0; i < activeShapePoints.length; i+=1) {
        // convertit les coordonnées cartésiennes en lat/lon, puis en CC48
        var cartesian = new Cesium.Cartesian3(activeShapePoints[i].x, activeShapePoints[i].y, activeShapePoints[i].z); // cartesian est en coord cartesiennes
        let cartographic = Cesium.Cartographic.fromCartesian(cartesian); // cartographic en radians
        let longitude = Cesium.Math.toDegrees(cartographic.longitude); // lon et lat en degrés
        let latitude = Cesium.Math.toDegrees(cartographic.latitude);
        latlon.push(longitude, latitude);

        var coords = proj4('EPSG:4326','EPSG:3948', [longitude, latitude]);
        coordsX.push(coords[0]); // coord en CC48
        coordsY.push(coords[1]);
        coordsZ.push(cartographic.height); // hauteur ellipsoïdale
      }
      if(coordsX.length > 1) {
        for (let i=0; i < coordsX.length-1; i+=1) {
          // chaque appel à la fonction computeCoord donne les coordonnées d'un sommet
          var coordfinal1 = this.computeCoord(distance, coordsX[i+1],coordsX[i], coordsY[i+1], coordsY[i]);
          var coordfinal2 = this.computeCoord(distance, coordsX[i],coordsX[i+1], coordsY[i], coordsY[i+1]);
          var coordfinal = coordfinal1.concat(coordfinal2); // on combine les tableaux pour avoir les 2 sommets calculés dans un seul tableau

        }
      }
      coordfinal = latlon.concat(coordfinal); // on ajoute les coordonnées des 2 points cliqués
      return coordfinal;
    }

    /**
    *
    * Retourne les coordonnées d'un point du sommet du rectangle à partir de 2 points cliqués et d'une distance de décalage précisée
    * par l'utilisateur <br/>
    * Elle est appelée 2 fois dans la fonction getRectangle en inversant les deux points pour calculer les deux points inconnus
    *
    * @param  {Number} distance la distance de décalage en mètres spécifiée par l'utilisateur
    * @param  {Number} coord1 la coordonnée X du premier point
    * @param  {Number} coord2 la coordonnée X du deuxième point
    * @param  {Number} coord3 la coordonnée Y du premier point
    * @param  {Number} coord4 la coordonnée Y du deuxième point
    * @return  {Array} latlon un tableau avec la longitude et la latitude calculée
    */
    computeCoord(distance, coord1, coord2, coord3, coord4) {
      var a = coord1 - coord2;
      var b = coord3 - coord4;

      // calcul du gisement entre le point 1 et 2
      var g12 = Math.atan2(a, b); // la fonction atan retourne toujours un angle en radians --> g12 est en radians
      g12 = g12* 180 / Math.PI; // conversion --> g12 en degrés

      // on corrige les valeurs du gisement en fonction de la valeur de la soustraction des coordonnées
      if(b > 0 && a <0 ) {
        var g12 = g12 + 360;
      } else if(b < 0){
        var g12 = g12 + 180;
      }

      // on enlève l'angle droit dans les coins du rectangle pour avoir le gisement du point 2 vers le point inconnu 3
      if(g12 < 90) {
        var g23 = (g12 + 270) %360;
      } else if(g12 > 90) {
        var g23 = ((g12 - 90) +360) %360;
      }

      // calcul des nouvelles coordonnées
      var e = Math.sin(g23*Math.PI/180); // les fonctions sin et cos de la librairie js ne prennent que des angles en radians en arguments
      var f = Math.cos(g23*Math.PI/180);
      var g = distance * e;
      var h = distance * f;
      var x3 = coord1 + g;
      var y3 = coord3 + h;

      // on convertit à nouveau les coordonnées CC48 en WGS84 pour avoir lon/lat en degrés
      var latlon = proj4('EPSG:3948','EPSG:4326', [x3, y3]);

      return latlon;
    }

    /**
    *
    *  Récupère la hauteur d'un point en coords cartésiennes et la transforme en hauteur ellipsoïdale (pour le dessin de volumes)
    *
    * @param  {Array} activeShapePoints le tableau de points à partir duquel calculer l'aire
    * @param  {Number} hauteurVol la hauteur de l'entité
    * @return {Number} z la hauteur ellipsoïdale du point
    */
    getHauteur(activeShapePoints, hauteurVol){
      var cartesian = new Cesium.Cartesian3(activeShapePoints[0].x, activeShapePoints[0].y, activeShapePoints[0].z);
      let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      let alti = cartographic.height.toFixed(3);

      var z = (Number(hauteurVol) + Number(alti));
      return z;
    }

    /*
    *
    * Découpe dans le photomaillage
    *
    */

    /**
    * Découpe un trou dans le photomaillage - ajoute les points blancs visuellement et coupe selon la forme définie <br/>
    * La forme définie doit impérativement être convexe pour que la découpe soit cohérente
    *
    * @param  {tileset} viewModel le modèle 3D à impacter
    */
    createHole(viewModel) {
      var dig_point = [];
      var hole_pts = [];
      var coordsX = [];
      var coordsY = [];
      var aire = 0;
      var scene = this.viewer.scene;
      this.handler.globe = this;
      var points = globe.viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection());

      this.handler.setInputAction(function(event) {
        var earthPosition = scene.pickPosition(event.position);
        let cartographic = Cesium.Cartographic.fromCartesian(earthPosition);
        let longitudeString = Cesium.Math.toDegrees(cartographic.longitude);
        let latitudeString = Cesium.Math.toDegrees(cartographic.latitude);

        var coords = proj4('EPSG:4326','EPSG:3948', [longitudeString, latitudeString]);
        coordsX.push(coords[0]);
        coordsY.push(coords[1]);

        // on ajoute visuellement des points pour la découpe qu'on supprimera au clic droit
        points.add({
          position : earthPosition,
          color : Cesium.Color.WHITE
        });

        dig_point.push(new Cesium.Cartesian3.fromDegrees(longitudeString, latitudeString));
        globe.viewer.scene.requestRender();
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

      this.handler.setInputAction(function(event) {
        hole_pts = Array.from(dig_point);

        for (let i=0; i < coordsX.length; i+=1) {
          var a = (coordsX[(i+1) % coordsX.length] - coordsX[i]);
          var b = (coordsY[(i+1) % coordsX.length] + coordsY[i] - (2 * coordsY[0]));
          var c = ((Number(a) * Number(b))/2);
          aire = (Number(aire) + Number(c)).toFixed(3);
        }

        // si l'aire est négative (ie si l'utilisateur a dessiné sa figure dans le sens trigo)
        // on inverse le tableau de points pour que la découpe marche
        if(aire > 0) {
          globe.holePlanes(viewModel, hole_pts);
        } else if(aire < 0) {
          hole_pts = hole_pts.reverse();
          globe.holePlanes(viewModel, hole_pts);
        }
        points.removeAll();
        dig_point = [];
        globe.viewer.scene.requestRender();
      }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);

      // définit les actions sur les 2 options du formulaire (afficher ou inverser la découpe)
      Cesium.knockout.getObservable(viewModel, 'affich').subscribe(function(value) {
        tileset.clippingPlanes.enabled = value;
      });
      Cesium.knockout.getObservable(viewModel, 'trou').subscribe(function(value) {
        globe.holePlanes(viewModel, hole_pts);
      });
    }

    /**
    * Ajoute les plans de coupes nécessaire à la visualisation de la découpe dans la fonction createHole
    *
    * @param  {tileset} viewModel le modèle 3D à impacter
    * @param  {Array} hole_pts les coordonnées de la forme à découper
    */
    holePlanes(viewModel, hole_pts) {
      var pointsLength = hole_pts.length;
      var clippingPlanes = [];

      for (var i = 0; i < pointsLength; ++i) {
        var nextIndex = (i + 1) % pointsLength;
        var midpoint = Cesium.Cartesian3.add(hole_pts[i], hole_pts[nextIndex], new Cesium.Cartesian3());
        midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint);
        var up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3());
        var right = Cesium.Cartesian3.subtract(hole_pts[nextIndex], midpoint, new Cesium.Cartesian3());
        right = Cesium.Cartesian3.normalize(right, right);
        var normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3());
        normal = Cesium.Cartesian3.normalize(normal, normal);
        if (!viewModel.trou){
          normal = Cesium.Cartesian3.multiplyByScalar(normal, -1 ,normal);
        }
        var plane = new Cesium.Plane.fromPointNormal(midpoint, normal);
        var clippingPlane = new Cesium.ClippingPlane.fromPlane(plane);
        clippingPlanes.push(clippingPlane);
      }

      // pour couper le globe
      /*this.viewer.scene.globe.depthTestAgainstTerrain = true;
      this.viewer.scene.globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
      planes : clippingPlanes,
      unionClippingRegions : union,
      edgeColor: Cesium.Color.WHITE,
    });*/

    // pour couper le photomaillage
    tileset.clippingPlanes = new Cesium.ClippingPlaneCollection({
      planes : clippingPlanes,
      unionClippingRegions: viewModel.trou, //si true: coupe tout ce qui est à l'extérieur de la zone cliquée
      enabled: viewModel.affich,
      edgeColor: Cesium.Color.WHITE,
      modelMatrix: Cesium.Matrix4.inverse(tileset._initialClippingPlanesOriginMatrix, new Cesium.Matrix4()) // ligne importante: on est obligés de passer par cette transfo de matrice car notre tileset n'a pas de matrice de transfo à la base
    });
  }

  /**
  * permet de cliquer les attributs sur le 3DTiles <br/>
  * colorise en vert les contours de la zone cliquée
  *
  * @param  {String} enabled le paramètre qui spécifie quand l'affichage doit être actif - prend la valeur e.target.checked ou non
  * @param  {tileset} tileset le modèle 3D à impacter
  */
  handleBatimentClick(enabled, tileset){
    var scene = this.viewer.scene;
    this.handler.globe = this;

    // Informations sur le batiment séléctionné
    let selected = {
      feature: undefined,
      originalColor: new Cesium.Color(),
      selectedEntity: new Cesium.Entity() // Une entité qui contient les attributs du batiments selectionné
    };

    let defaultClickHandler = this.viewer.screenSpaceEventHandler.getInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);

    if (Cesium.PostProcessStageLibrary.isSilhouetteSupported(this.viewer.scene)) {
      // Créer la bordure verte
      let silhouetteGreen = Cesium.PostProcessStageLibrary.createEdgeDetectionStage();
      silhouetteGreen.uniforms.color = Cesium.Color.fromCssColorString('#E20000').withAlpha(0.7);
      silhouetteGreen.uniforms.length = 0.01;
      silhouetteGreen.selected = [];
      // Enregistrer les bordures dans cesium
      this.viewer.scene.postProcessStages.add(Cesium.PostProcessStageLibrary.createSilhouetteStage([silhouetteGreen]));

      this.handler.setInputAction(function(movement) {
        // Supprimer toutes les bordures verte
        silhouetteGreen.selected = [];
        // Récuperer la forme sur laquelle on a cliqué
        let pickedFeature = scene.pick(movement.position);
        // Si on clique sur un element qui n'appartient pas à tileset on ne met pas de bordure verte
        if (!Cesium.defined(pickedFeature) || !Cesium.defined(pickedFeature.content) || pickedFeature.content._tileset != tileset) {
          selected.feature = undefined;
          defaultClickHandler(movement);
          return;
        }
        // Ajouter le bord vert sur la forme selectionnée
        if (pickedFeature !== silhouetteGreen.selected[0]) {
          silhouetteGreen.selected = [pickedFeature];

          selected.feature = pickedFeature;
          selected.selectedEntity.name = pickedFeature.getProperty('name');
          selected.selectedEntity.description = '<table class="cesium-infoBox-defaultTable"><tbody>';

          // Générer les lignes du tableau
          let propertyNames = pickedFeature.getPropertyNames();
          for(let i = 0; i < propertyNames.length; i++){
            selected.selectedEntity.description += '<tr><th>' + propertyNames[i] + '</th><td>' + pickedFeature.getProperty(propertyNames[i]) + '</td></tr>';
          }
          selected.selectedEntity.description += '</tbody></table>';

          // Afficher le tableau en haut à droite
          globe.viewer.selectedEntity = selected.selectedEntity;
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

      // Quitter la fonction pour desactiver la selection de batiments
      if(!enabled){
        silhouetteGreen.selected = [];
        return;
      }
    }
  }

  /**
  * supprime toutes les actions liées à la souris sur le handler de Cesium (clic gauche, droit et mouvement de souris)
  */
  supprSouris(){
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  }

  /**
  * Gere la luminosité et le contraste
  */
  updatePostProcess() {
    var bloom = globe.viewer.scene.postProcessStages.bloom;
    bloom.enabled = Boolean(globe.viewModel.show);
    bloom.uniforms.contrast = Number(globe.viewModel.contrast);
    bloom.uniforms.brightness = Number(globe.viewModel.brightness);
    bloom.uniforms.delta = Number(globe.viewModel.delta);
    bloom.uniforms.sigma = Number(globe.viewModel.sigma);
    bloom.uniforms.stepSize = Number(globe.viewModel.stepSize);
    globe.viewer.scene.requestRender();

  }


  /*
  *
  *
  *
  * Les fonctions de chargements des données opendata
  * Surfaciques, linéaires, ponctuelles, temporelles
  * Chaque fonction load a une fonction show associées pour simplifier les ajouts de couches et les arguments en paramètres
  *
  *
  *
  */

  /**
  * Permet de charger les dessins exportés depuis Cesium <br/>
  * va chercher les propriétés de dessin (couleur, épaisseur de ligne, hauteur de volume, etc)
  * dans le json pour garder les propriétés à l'affichage <br/>
  * Utilise la fonction showJson pour afficher les données <br/>
  * Les points seront affichés simplement avec le même billboard pour tous
  *
  * @param  {String} link Le lien vers le fichier
  * @param  {String} name Le nom qu'on donne au json
  * @param  {Object} options facultatif - Les options pour le chargement
  * @return  {GeoJsonDataSource} le json une fois que tout est chargé
  */
  loadDrawing(link, name){
    let promise = Cesium.GeoJsonDataSource.load(link, {
      clampToGround: true
    });
    this.showLoader(); // fonction qui affiche un symbole de chargement sur la page

    promise.then((dataSource) => {
      this.viewer.dataSources.add(dataSource);
      this.dataSources[name] = dataSource;
      this.hideLoader();

      // Get the array of entities
      var entities = dataSource.entities.values;
      for (let i = 0; i < entities.length; i++) {
        let entity = entities[i];
        if(Cesium.defined(entity.billboard)) {
          entity.billboard.height = entity.properties.height;
          entity.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
          entity.billboard.image = entity.properties.image;

        } else if (Cesium.defined(entity.polygon)) {
          let rouge = entity.properties.color._value.red;
          let vert = entity.properties.color._value.green;
          let bleu = entity.properties.color._value.blue;
          let alpha = entity.properties.color._value.alpha;
          let couleur = new Cesium.Color(rouge, vert, bleu, alpha);

          entity.polygon.material =  couleur;
          entity.polygon.outline = false;
          entity.polygon.extrudedHeight = entity.properties.extrudedHeight;
          entity.polygon.classificationType = Cesium.ClassificationType.CESIUM_3D_TILE;

        } else if(Cesium.defined(entity.polyline)) {
          let rouge = entity.properties.color._value.red;
          let vert = entity.properties.color._value.green;
          let bleu = entity.properties.color._value.blue;
          let alpha = entity.properties.color._value.alpha;
          let couleur = new Cesium.Color(rouge, vert, bleu, alpha);

          entity.polyline.material = couleur;
          entity.polyline.width = entity.properties.width;
          entity.polyline.classificationType = Cesium.ClassificationType.CESIUM_3D_TILE;

        }
      }
    });
    return promise;
  }

  /**
  *
  * permet de charger des fichiers geojson surfaciques et linéaires <br/>
  * il est conseillé de donner un nom compréhensible à la variable choice: par défaut, c'est cette variable qui donne son nom aux entités <br/>
  * Optionnel: trace un contour autour des surfaces
  *
  * @param  {String} link Le lien vers le fichier
  * @param  {String} name Le nom qu'on donne au json
  * @param  {String} choice permet de donner un nom aux entités
  * @param  {Object} options facultatif - Les options pour le chargement
  * @param  {String} options.typeDonnee spécifie le type de donnée (ici surface ou ligne)
  * @param  {Boolean} options.clamp true pour clampToGround et false sinon (vaut true si non défini)
  * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
  * @param  {Object} options.colors un objet qui contient les valeurs que peut prendre le classificationField et les couleurs à associer
  * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
  * @param  {Array} options.line Le tableau d'entités pour stocker les lignes de contours des polygones
  * @param  {Number} options.alpha La transparence de la couleur des entités à afficher
  * @param  {String} options.couleurLigne La couleur des lignes de contour au format '#FFFFFF'
  * @param  {Number} options.tailleLigne La largeur des lignes de contour
  * @param  {String} options.nameLigne La nom des lignes de contour
  * @param  {Number} options.epaisseur L'épaisseur de la ligne pour les entités polylignes
  * @return  {GeoJsonDataSource} le json une fois que tout est chargé
  */
  loadGeoJson(link, name, choice, options = {}) {
    if(options.clamp !== undefined) {
      var clamp = options.clamp;
    } else {
      var clamp = true;
    }
    let promise = Cesium.GeoJsonDataSource.load(link, {
      clampToGround: clamp
    });
    this.viewer.scene.globe.depthTestAgainstTerrain = true; // test pour voir si les json arrête de baver
    this.viewer.scene.logarithmicDepthBuffer = false; // idem
    this.showLoader(); // fonction qui affiche un symbole de chargement sur la page

    promise.then((dataSource) => {
      // Ajoute le json dans la liste des dataSource
      this.viewer.dataSources.add(dataSource);
      this.dataSources[name] = dataSource;
      this.hideLoader();
      // permet de classifier les json
      if(options.classificationField !== undefined){
        // Get the array of entities
        let entities = dataSource.entities.values;
        if(options.colors != undefined){
          Object.keys(options.colors).forEach(function(c){
            options.colors[c] = Cesium.Color.fromCssColorString(options.colors[c]);
            options.colors[c].alpha = options.alpha || 0.8;
          })
        }
        let colors = options.colors || {};

        for (let i = 0; i < entities.length; i++) {
          let entity = entities[i];

          let color = colors[entity.properties[options.classificationField]];
          if(!color){
            color = Cesium.Color.fromRandom({ alpha : options.alpha || 0.8 });
            colors[entity.properties[options.classificationField]] = color;
          }

          if(options.choiceTableau !== undefined) {
            var tabl = new TableauAttribut();

            // l'attribut choiceTableau permet de classifier entre les différentes données et de charger le tableau d'attributs au bon format
            var tablAttribut = 'createTableau' + options.choiceTableau;
            tabl[tablAttribut](entity, dataSource);
          }

          if (options.typeDonnee === 'surface') {
            //Dessine le contour des limites des entités
            if(options.couleurLigne !== undefined) {
              options.line.push(this.drawLine(entity.polygon.hierarchy._value.positions, options.tailleLigne, options.couleurLigne, 1, true, options.nameLigne));
            }
            // on classifie les entités par couleur
            entity.polygon.material = color;
            entity.polygon.classificationType = Cesium.ClassificationType.CESIUM_3D_TILE;
            entity.polygon.arcType = Cesium.ArcType.GEODESIC;

          } else if(options.typeDonnee === 'ligne') {
            entity.polyline.material = color;
            if(options.epaisseur !== undefined){
              entity.polyline.width = options.epaisseur;
            } else {
              entity.polyline.width = 4.0;
            }
            entity.polyline.classificationType = Cesium.ClassificationType.CESIUM_3D_TILE;
            entity.polyline.arcType = Cesium.ArcType.GEODESIC;
          }
          // si la donnée n'a pas de tableau d'attributs particulier, on change juste le nom des entités
          entity.name = choice;

        }
        globe.viewer.scene.requestRender();
      }

    })
    return promise;

  }


  /**
  * La fonction show associée à loadGeoJson <br/>
  * Charger ou dé-charger la donnée "name" en fonction de la valeur de "show" <br/>
  * optionnel: peut mettre un highlight sur les entités sélectionnées en cliquant
  *
  * @param  {String} show le paramètre qui spécifie quand l'affichage doit être actif - prend la valeur e.target.checked ou non
  * @param  {String} link Le lien vers le fichier
  * @param  {String} name Le nom qu'on donne au json
  * @param  {String} choice permet de donner un nom aux entités
  * @param  {Object} options facultatif - Les options pour le chargement
  * @param  {String} options.typeDonnee spécifie le type de donnée (ici surface ou ligne)
  * @param  {Boolean} options.clamp true pour clampToGround et false sinon (vaut true si non défini)
  * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
  * @param  {Object} options.colors un objet qui contient les valeurs que peut prendre le classificationField et les couleurs à associer
  * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
  * @param  {Array} options.line Le tableau d'entités pour stocker les lignes de contours des polygones
  * @param  {Number} options.alpha La transparence de la couleur des entités à afficher
  * @param  {String} options.couleurLigne La couleur des lignes de contour au format '#FFFFFF'
  * @param  {Number} options.tailleLigne La largeur des lignes de contour
  * @param  {String} options.nameLigne La nom des lignes de contour
  * @param  {Number} options.epaisseur L'épaisseur de la ligne pour les entités polylignes
  * @param  {String} options.couleurHighlight La couleur du highlight quand on clique sur la ligne au format '#FFFFFF'
  * @param  {Number} options.alphaHighlight La transparence du highlight
  */
  showJson(show, name, link, choice, options = {}){
    var handler = new Cesium.ScreenSpaceEventHandler(globe.viewer.canvas);
    var highlight = options.couleurHighlight || undefined;

    if(this.dataSources[name] == undefined) {
      if(highlight !== undefined) {
        var highlighted = {
          feature : undefined,
          originalMaterial : new Cesium.Color()
        };
        // when we click on the entity change its scale and color
        handler.setInputAction(function(movement) {
          var pickedObject = globe.viewer.scene.pick(movement.position);
          if (!Cesium.defined(pickedObject)) {
            return;
          }
          // If a feature was previously highlighted, undo the highlight
          if (Cesium.defined(highlighted.feature)) {
            if (options.typeDonnee === 'surface') {

              highlighted.feature.id.polygon.material = highlighted.originalMaterial;
              highlighted.feature = undefined;

            } else if (options.typeDonnee === 'ligne') {
              highlighted.feature.id.polyline.material = highlighted.originalMaterial;
              highlighted.feature = undefined;
            }

            globe.viewer.scene.requestRender();
          }
          // colorer la zone cliquée dans une couleur précise
          if (Cesium.defined(pickedObject)) {
            if (pickedObject.id.name === choice ) {
              highlighted.feature = pickedObject;

              if (options.typeDonnee === 'surface') {
                highlighted.originalMaterial = pickedObject.id.polygon.material;
                pickedObject.id.polygon.material = Cesium.Color.fromCssColorString(options.couleurHighlight).withAlpha(options.alphaHighlight);
              } else if (options.typeDonnee === 'ligne') {
                highlighted.originalMaterial = pickedObject.id.polyline.material;
                pickedObject.id.polyline.material = Cesium.Color.fromCssColorString(options.couleurHighlight).withAlpha(options.alphaHighlight);
              }

              globe.viewer.scene.requestRender();
            }
          }
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
      }
    }

    if(show){
      if(this.dataSources[name] !== undefined){
        this.viewer.dataSources.remove(this.dataSources[name]);
        this.viewer.scene.requestRender();
      }
      if(options.typeDonnee === 'dessin') {
        globe.loadDrawing(link, name)
      } else {
        globe.loadGeoJson(link, name, choice, options);
      }
      if(options.couleurLigne !== undefined) {
        for(var i = 0; i < options.line.length; i++){
          options.line[i].show = true;
        }
      }
    } else{

      if(this.dataSources[name] !== undefined){
        this.viewer.dataSources.remove(this.dataSources[name]);

        if(options.couleurLigne !== undefined) {
          for(var i = 0; i < options.line.length; i++){
            options.line[i].show = false;
          }
        }

        handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);

        this.viewer.scene.requestRender();
      }
    }
  }

  /**
  * permet de charger des fichiers geojson ponctuels 2D ou 3D et de les cluster ou non <br/>
  *
  *
  * @param  {String} link Le lien vers le fichier
  * @param  {String} name Le nom qu'on donne au json
  * @param  {String} image L'image à utiliser pour les billboard des entités ponctuelles
  * @param  {Array} billboard l'objet dans lequel on stocke le CustomDataSource
  * @param  {Boolean} point3D true si les points ont une composante 3D, false sinon
  * @param  {Boolean} cluster true si les points doivent être clusterisés, false sinon
  * @param  {Object} options facultatif - Les options pour le chargement
  * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
  * @param  {Array} options.line Le tableau d'entités où stocker les lignes qu'on trace depuis le bas du billbard jusqu'au sol
  * @param  {String} options.couleur La couleur de la ligne et de la puce pour le cluster au format '#FFFFFF'
  * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
  * @return  {GeoJsonDataSource} le json une fois que tout est chargé
  */
  loadPoint(link, name, image, billboard, point3D, cluster, options = {}){
    let promise = Cesium.GeoJsonDataSource.load(link, {
      markerSize: 0 //pour que l'épingle n'apparaisse pas
    });

    this.showLoader(); // fonction qui affiche un symbole de chargement sur la page

    // on crée un CustomDataSource car les entités ne peuvent pas être clusterisées
    // on ne peut que cluster des dataSource, et l'altitude des points dans la dataSource en peut pas être modifié
    var billboardData = new Cesium.CustomDataSource();

    promise.then((dataSource) => {
      // Ajoute le json dans la liste des dataSource
      this.viewer.dataSources.add(dataSource);
      this.dataSources[name] = dataSource;
      this.hideLoader();
      let entities = dataSource.entities.values;

      for(let i = 0; i < entities.length; i++) {
        let entity = entities[i];

        // on récupère les coordonnées des points importés
        var X = (dataSource._entityCollection._entities._array[i]._position._value.x);
        var Y = (dataSource._entityCollection._entities._array[i]._position._value.y);
        var Z = (dataSource._entityCollection._entities._array[i]._position._value.z);

        var position = new Cesium.Cartesian3(X,Y,Z); // en coords cartesiennes (système ECEF)

        // créé un billboard pour chaque entité ponctuelle (en précisant l'image à utiliser dans les paramètres)
        // l'entité billboard ne conserve pas les attributs
        // des billboard sont disponibles dans le dossier src/img/billboard sous le nom marker_'color' (10 couleurs)
        var billboardEntity = billboardData.entities.add({
          billboard : {
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
            sizeInMeters: false,
            scaleByDistance : new Cesium.NearFarScalar(1000, 2, 150000, 0)
          }
        });

        if(options.classificationField !== undefined) {
          var symbol = image[entity.properties[options.classificationField]];
          if(symbol === undefined) {
            var symbol = 'src/img/billboard/marker_black.png'
          }
          billboardEntity.billboard.image = symbol;
        } else {
          billboardEntity.billboard.image = image;
        }

        if(point3D === false) {
          // nécessité de convertir en lon/lat car la coordonnée Z en ECEF ne correspond pas à la hauteur
          let cartographic = Cesium.Cartographic.fromCartesian(position); // conversion en radians
          let longitude = cartographic.longitude;
          let latitude = cartographic.latitude;

          // on augmente la hauteur des points pour qu'ils apparaissent au dessus du photomaillage
          let randomHeight = Math.floor(Math.random() * 20) + 230; // hauteur aléatoire pour rendre les doublons visibles
          let height = Number(randomHeight + cartographic.height);

          var coordHauteur = new Cesium.Cartesian3.fromRadians(longitude, latitude, height);

          //on trace une ligne partant du sol jusqu'à la base du billboard
          var coordLigne = [position, coordHauteur];
          var lineEntity = this.drawLine(coordLigne, 2, options.couleur, 1, false);
          options.line.push(lineEntity);

          billboardEntity.position = coordHauteur;

        } else if(point3D === true) {
          billboardEntity.position = position;
        }

        if(options.choiceTableau !== undefined) {
          var tabl = new TableauAttribut();
          // on lie les attributs des points au nouvelles entités billboard et lignes
          // l'attribut choiceTableau permet de classifier entre les différentes données
          var tablBillboard = 'createTableau' + options.choiceTableau;
          tabl[tablBillboard](billboardEntity, entity);

          // si on a tracé une ligne depuis le billboard jusqu'au sol on ajoute les attributs sur la ligne aussi
          if(point3D === false) {
            var tablLine = 'createTableau' + options.choiceTableau;
            tabl[tablLine](lineEntity, entity);
          }
        }

      } // fin du for entities

      // on ajoute les billboard dans le dataSource
      this.viewer.dataSources.add(billboardData);
      billboard.push(billboardData);

      if(cluster === true) {
        // on zoome sur les entités pour que le cluster apparaisse (apparait seulement une fois que tout est chargé)
        //this.viewer.zoomTo(billboardData);

        if(point3D === false) {
          // on masque les lignes par défaut car on est à un haut niveau de zoom
          for(var i = 0; i < options.line.length; i++){
            options.line[i].show = false;
          }
        }

        // Paramètres pour le cluster
        billboardData.clustering.enabled = true;
        billboardData.clustering.pixelRange = 80;
        billboardData.clustering.minimumClusterSize = 5;

        // on créé les puces pour l'affichage du cluster
        var pinBuilder = new Cesium.PinBuilder();
        var pin50 = pinBuilder.fromText("50+", Cesium.Color.fromCssColorString(options.couleur), 80).toDataURL();
        var pin40 = pinBuilder.fromText("40+", Cesium.Color.fromCssColorString(options.couleur), 70).toDataURL();
        var pin30 = pinBuilder.fromText("30+", Cesium.Color.fromCssColorString(options.couleur), 60).toDataURL();
        var pin20 = pinBuilder.fromText("20+", Cesium.Color.fromCssColorString(options.couleur), 50).toDataURL();
        var pin10 = pinBuilder.fromText("10+", Cesium.Color.fromCssColorString(options.couleur), 40).toDataURL();

        var singleDigitPins = new Array(8);
        for (var i = 0; i < singleDigitPins.length; ++i) {
          singleDigitPins[i] = pinBuilder.fromText("" + (i), Cesium.Color.fromCssColorString(options.couleur), 30).toDataURL();
        }

        billboardData.clustering.clusterEvent.addEventListener(
          function (clusteredEntities, cluster) {

            var position = cluster.billboard._position;
            let cartographic = Cesium.Cartographic.fromCartesian(position); // conversion en radians
            let longitude = cartographic.longitude;
            let latitude = cartographic.latitude;

            // on augmente la hauteur des points pour qu'ils apparaissent au dessus du photomaillage
            let height = Number(150 + cartographic.height);
            var coordHauteur = new Cesium.Cartesian3.fromRadians(longitude, latitude, height);

            // on monte la position du cluster pour qu'il apparaisse au dessus du photomaillage
            cluster.billboard.position = coordHauteur;
            cluster.label.show = false;
            cluster.billboard.show = true;
            cluster.billboard.id = cluster.label.id;
            cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;

            if(clusteredEntities.length <= 60 && clusteredEntities.length > 5) {
              if(options.line !== undefined) {
                for(var i = 0; i < options.line.length; i++){
                  options.line[i].show = false;
                }
              }
            }

            if (clusteredEntities.length >= 50) {
              cluster.billboard.image = pin50;
            } else if (clusteredEntities.length >= 40) {
              cluster.billboard.image = pin40;
            } else if (clusteredEntities.length >= 30) {
              cluster.billboard.image = pin30;
            } else if (clusteredEntities.length >= 20) {
              cluster.billboard.image = pin20;
            } else if (clusteredEntities.length >= 10) {
              cluster.billboard.image = pin10;
            } else if (clusteredEntities.length > 5) {
              cluster.billboard.image = singleDigitPins[clusteredEntities.length];
            } else if (clusteredEntities.length = 5) {
              cluster.billboard.image = singleDigitPins[clusteredEntities.length];
              // on montre les lignes quand on s'approche du point
              for(var i = 0; i < options.line.length; i++){
                options.line[i].show = true;
              }
            }
            globe.viewer.scene.requestRender();

          });

          // force a re-cluster with the new styling
          var pixelRange = billboardData.clustering.pixelRange;
          billboardData.clustering.pixelRange = 0;
          billboardData.clustering.pixelRange = pixelRange;
        }

      });
      return promise;

    }

    /**
    * permet de charger un fichier de données géographiques (geojson) ainsi qu'un 2ème fichier attributaire au format json <br/>
    * On lie ensuite les attributs à la donnée géographique <br/>
    * utilise la fonction showPoint pour afficher la donnée
    *
    * @param  {String} link Le lien vers le fichier
    * @param  {String} name Le nom qu'on donne au json
    * @param  {String} image L'image à utiliser pour les billboard des entités ponctuelles
    * @param  {Array} billboard Le tableau d'entités où stocker les billboards
    * @param  {Boolean} point3D true si les points ont une composante 3D, false sinon
    * @param  {Object} options facultatif - Les options pour le chargement
    * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
    * @param  {Array} options.line Le tableau d'entités où stocker les lignes qu'on trace depuis le bas du billbard jusqu'au sol
    * @param  {String} options.couleur La couleur de la ligne au format '#FFFFFF'
    * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
    * @return  {GeoJsonDataSource} le json une fois que tout est chargé
    */
    loadJsonAttribut(link, linkAttribut, name, image, billboard, point3D, options = {}){
      let promise = Cesium.GeoJsonDataSource.load(link, {
        markerSize: 0 //pour que l'épingle n'apparaisse pas
      });
      this.showLoader(); // fonction qui affiche un symbole de chargement sur la page

      promise.then((dataSource) => {
        // Ajoute le json dans la liste des dataSource
        this.viewer.dataSources.add(dataSource);
        this.dataSources[name] = dataSource;
        this.hideLoader();
        let entities = dataSource.entities.values;

        // on est obligés de s'arrêter au nombre précis d'entités car ensuite les labels sont rajoutés à la liste des entités
        // ce qui entraine des bugs d'affichage et des erreurs au moment de la création du tableau d'attributs
        var stop = dataSource._entityCollection._entities.length;
        // on récupère le fichier json de la fréquentation en temps réel
        var lienJson = linkAttribut;
        var xmlhttp = new XMLHttpRequest();
        this.xmlhttp = this;
        xmlhttp.open('GET', lienJson);
        xmlhttp.responseType = 'json';
        xmlhttp.send();

        // une fois que le fichier est bien chargé
        xmlhttp.onreadystatechange = function () {
          if(xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            // on récupère le json chargé
            var jsonAttribut = xmlhttp.response;
            var billboardData = new Cesium.CustomDataSource();
            // le lien avec la classe tableau pour l'ajout du tableau d'attributs au bon format
            var tabl = new TableauAttribut();

            for(let i = 0; i < stop; i++) {
              let entity = entities[i];
              // on récupère les coordonnées des points importés
              var X = (dataSource._entityCollection._entities._array[i]._position._value.x);
              var Y = (dataSource._entityCollection._entities._array[i]._position._value.y);
              var Z = (dataSource._entityCollection._entities._array[i]._position._value.z);

              var position = new Cesium.Cartesian3(X,Y,Z); // en coords cartesiennes (système ECEF)

              let cartographic = Cesium.Cartographic.fromCartesian(position); // conversion en radians
              let longitude = cartographic.longitude;
              let latitude = cartographic.latitude;
              // coordonnées 15m en dessous pour la lisibilité du texte
              let heightLabel = Number(210 + cartographic.height);
              var coordLabel = new Cesium.Cartesian3.fromRadians(longitude, latitude, heightLabel);

              var billboardEntity = billboardData.entities.add({
                billboard : {
                  verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                  scaleByDistance : new Cesium.NearFarScalar(10000, 1, 150000, 0)
                }
              });

              if(point3D === false) {
                // on augmente la hauteur des points pour qu'ils apparaissent au dessus du photomaillage
                let height = Number(225 + cartographic.height); // on rajoute 225m à la hauteur ellipsoïdale
                var coordHauteur = new Cesium.Cartesian3.fromRadians(longitude, latitude, height);
                // on ajoute une entité billboard à chaque point, 225m plus haut
                // l'entité billboard ne conserve pas les attributs

                billboardEntity.position = coordHauteur;
                // des billboard sont disponibles dans le dossier src/img/billboard sous le nom marker_'color' (10 couleurs)

                //on trace une ligne partant du sol jusqu'à la base du billboard
                var coordLigne = [position, coordHauteur];
                var lineEntity = globe.drawLine(coordLigne, 2, options.couleur, 1, false)
                options.line.push(lineEntity);

                if(options.choiceTableau !== undefined){
                  // on lie les attributs des points au nouvelles entités billboard et lignes
                  // l'attribut choiceTableau permet de classifier entre les différentes données
                  var tablBillboard = 'createTableau' + options.choiceTableau;
                  tabl[tablBillboard](entity, jsonAttribut, billboardEntity, coordLabel, dataSource);
                  tabl[tablBillboard](entity, jsonAttribut, lineEntity, coordLabel, dataSource);
                }

              } else if(point3D === true) {
                billboardEntity.position = position;
                if(options.choiceTableau !== undefined) {
                  var tablBillboard = 'createTableau' + options.choiceTableau;
                  tabl[tablBillboard](entity, jsonAttribut, billboardEntity, coordLabel, dataSource);
                }

              }

              if(options.classificationField !== undefined) {
                var symbol = image[entity.properties[options.classificationField]];
                if(symbol === undefined) {
                  var symbol = 'src/img/billboard/marker_black.png'
                }
                billboardEntity.billboard.image = symbol;
              } else {
                billboardEntity.billboard.image = image;
              }

              globe.viewer.scene.requestRender();

            } // fin du for(i < entities.length)
            globe.viewer.dataSources.add(billboardData);
            billboard.push(billboardData)

          } // fin de la requête xmlhttp
        } // fin de la requête xmlhttp

      });
      return promise;

    }

    /**
    * La fonction show associée à loadPoint <br/>
    * permet d'afficher ou de masquer la donnée ponctuelle en fonction de la valeur de show
    *
    * @param  {String} show le paramètre qui spécifie quand l'affichage doit être actif - prend la valeur e.target.checked ou non
    * @param  {String} name Le nom qu'on donne au json
    * @param  {String} link Le lien vers le fichier
    * @param  {String} linkAttribut Le lien vers le fichier json attributaires sans géométrie
    * @param  {String} image L'image à utiliser pour les billboard des entités ponctuelles
    * @param  {Array} billboard l'objet dans lequel on stocke le CustomDataSource
    * @param  {Boolean} point3D true si les points ont une composante 3D, false sinon
    * @param  {Boolean} cluster true si les points doivent être clusterisés, false sinon
    * @param  {Object} options facultatif - Les options pour le chargement
    * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
    * @param  {Array} options.line Le tableau d'entités où stocker les lignes qu'on trace depuis le bas du billbard jusqu'au sol
    * @param  {String} options.couleur La couleur de la ligne et de la puce pour le cluster au format '#FFFFFF'
    * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
    */
    showPoint(show, name, link, linkAttribut, image, billboard, point3D, cluster, options = {}){
      if(show){
        if(this.dataSources[name] === undefined){
          if(linkAttribut != undefined) {
            globe.loadJsonAttribut(link, linkAttribut, name, image, billboard, point3D, options);
            this.viewer.scene.requestRender();
          } else {
            globe.loadPoint(link, name, image, billboard, point3D, cluster, options);
            this.viewer.scene.requestRender();
          }

        } else{
          this.dataSources[name].show = true;
          for(var i = 0; i < billboard.length; i++){
            billboard[i].show = true;
          }

          if(point3D === false) {
            for(var i = 0; i < options.line.length; i++){
              options.line[i].show = true;
            }
          }

          this.viewer.scene.requestRender(); // dit à Cesium de recalculer la page
        }
      } else{
        if(this.dataSources[name] !== undefined){
          this.dataSources[name].show = false;
          for(var i = 0; i < billboard.length; i++){
            billboard[i].show = false;
          }
          if(point3D === false) {
            for(var i = 0; i < options.line.length; i++){
              options.line[i].show = false;
            }
          }
          this.viewer.scene.requestRender();
        }
      }
    }


    /**
    *
    * Permet de re-charger la donnée ponctuelle (temps réel)
    *
    * @param  {String} link Le lien vers le fichier
    * @param  {String} linkAttribut Le lien vers le fichier json attributaires sans géométrie
    * @param  {String} name Le nom qu'on donne au json
    * @param  {String} image L'image à utiliser pour les billboard des entités ponctuelles
    * @param  {Array} billboard l'objet dans lequel on stocke le CustomDataSource
    * @param  {Boolean} point3D true si les points ont une composante 3D, false sinon
    * @param  {Boolean} cluster true si les points doivent être clusterisés, false sinon
    * @param  {Object} options facultatif - Les options pour le chargement
    * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
    * @param  {Array} options.line Le tableau d'entités où stocker les lignes qu'on trace depuis le bas du billbard jusqu'au sol
    * @param  {String} options.couleur La couleur de la ligne au format '#FFFFFF'
    * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
    */
    updatePoint(link, linkAttribut, name, image, billboard, point3D, cluster, options = {}) {
      if(this.dataSources[name] !== undefined){
        this.viewer.dataSources.remove(this.dataSources[name]);
        this.viewer.scene.requestRender();
        if(linkAttribut != undefined) {
          globe.loadJsonAttribut(link, linkAttribut, name, image, billboard, point3D, options= {
            classificationField: options.classificationField,
            line: options.line,
            couleur: options.couleur,
            choiceTableau: options.choiceTableau
          });
        } else {
          globe.loadPoint(link, name, image, billboard, point3D, cluster, options= {
            classificationField: options.classificationField,
            line: options.line,
            couleur: options.couleur,
            choiceTableau: options.choiceTableau
          });
        }

        this.viewer.scene.requestRender();
      }

    }

    /**
    * permet de charger des fichiers geojson temporels surfaciques (donnée dynamique qui va s'actualiser lorsqu'on bouge le curseur temps de Cesium)
    *
    * @param  {String} link Le lien vers le fichier
    * @param  {String} name Le nom qu'on donne au json
    * @param  {String} choice spécifique à la donnée, permet de charger l'attribut dans lequel on stocke la date de la donnée
    * @param  {Object} options facultatif - Les options pour le chargement
    * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
    * @param  {Object} options.colors un objet qui contient les valeurs que peut prendre le classificationField et les couleurs à associer
    * @param  {Number} options.alpha La transparence de la couleur des entités à afficher
    * @param  {Array} options.line Le tableau d'entités où stocker les contours des polygones
    * @param  {String} options.nameLigne La nom des lignes de contour
    * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
    * @return  {GeoJsonDataSource} le json une fois que tout est chargé
    */
    loadTimeSurf(link, name, choice, options = {}){
      let promise = Cesium.GeoJsonDataSource.load(link, {
        clampToGround: true
      });
      this.viewer.scene.globe.depthTestAgainstTerrain = true; // test pour voir si les json arrête de baver
      this.viewer.scene.logarithmicDepthBuffer = false; // idem
      this.showLoader(); // fonction qui affiche un symbole de chargement sur la page

      promise.then((dataSource) => {
        // Ajoute le json dans la liste des dataSource
        this.viewer.dataSources.add(dataSource);
        this.dataSources[name] = dataSource;
        let entities = dataSource.entities.values;
        this.hideLoader();

        // l'évenement qui actualise la valeur de l'horloge quand on clique sur la timeline
        this.viewer.clock.onTick.addEventListener(function () {
          // on garde seulement les 10 premiers chiffres pour avoir le jour (sans l'heure et secondes)
          let updateTime = Cesium.JulianDate.toIso8601(globe.viewer.clock.currentTime).substring(0, 10);

          for (let i = 0; i < entities.length; i++) {
            let entity = entities[i];

            // attention aux formats de date, ici updateTime et dateValidite sont au format AAAA-MM-JJ
            // spécifique à la donnée, condition à rajouter pour récupérer le champ dans lequel on stocke la date
            // on récupère la date dans les attributs et on enlève un jour car date échéance = plus valable le jour même
            if(choice === 'default') {
              var dateIso = Cesium.JulianDate.fromIso8601(entity.properties._date._value);
              var dateValidite = Cesium.JulianDate.toIso8601(date).substring(0, 10);
            } else {
              var dateIso = Cesium.JulianDate.fromIso8601(entity.properties._date_echeance._value);
              var date = Cesium.JulianDate.addDays(dateIso, -1, new Cesium.JulianDate());
              var dateValidite = Cesium.JulianDate.toIso8601(date).substring(0, 10);
            }

            // si la date des attributs correspond à la date de la timeline on affiche l'entité
            if(dateValidite == updateTime) {
              entity.show = true;
            } else {
              entity.show = false;
            }
            // on demande d'actualiser à chaque changement d'horloge pour voir les couleurs défiler en bougeant la timeline
            globe.viewer.scene.requestRender();
          }

        });

        // permet de classifier les json
        if(options.classificationField !== undefined){
          let entities = dataSource.entities.values;

          if(options.colors != undefined){
            Object.keys(options.colors).forEach(function(c){
              options.colors[c] = Cesium.Color.fromCssColorString(options.colors[c]);
              options.colors[c].alpha = options.alpha || 0.8;
            })
          }
          let colors = options.colors || {};

          for (let i = 0; i < entities.length; i++) {

            let entity = entities[i];
            if (Cesium.defined(entity.polygon)) {
              let color = colors[entity.properties[options.classificationField]];
              entity.polygon.material = color;
              entity.polygon.classificationType = Cesium.ClassificationType.CESIUM_3D_TILE;
              entity.polygon.arcType = Cesium.ArcType.GEODESIC;
            }

            entity.name = choice;

            if(options.choiceTableau !== undefined) {
              var tabl = new TableauAttribut();

              var tablEntity = 'createTableau' + options.choiceTableau;
              tabl[tablEntity](entity);

            }

            if(options.nameLigne !== undefined) {
              // on trace les contours des entités
              options.line.push(this.drawLine(entity.polygon.hierarchy._value.positions, 3, '#FFFFFF', 1, true, options.nameLigne));
            }

          }
        }

      });
      return promise;
    }

    /**
    * permet de charger des fichiers geojson temporels ponctuels (donnée dynamique qui va s'actualiser lorsqu'on bouge le curseur temps de Cesium) <br/>
    * Si la donnée est 2D, la fonction va créer une primitive billboard à chaque point puis le relever de 200m. On stocke ensuite la valeur de date_écheance dans
    * la description du billboard pour pouvoir ensuite comparer cette valeur avec la date actuelle sur la timeline. Les primitives ne se suppriment pas quand on décoche
    * la checkbox et n'affichent pas de tableau d'attributs au clic, même si on a défini une variable choiceTableau <br>
    * Si la donnée est 3D, la fonction récupère simplement les entités et compare la valeur de l'attribut date_écheance avec la date de la timeline
    *
    * @param  {String} link Le lien vers le fichier
    * @param  {String} name Le nom qu'on donne au json
    * @param  {String} choice spécifique à la donnée, permet de charger l'attribut dans lequel on stocke la date de la donnée
    * @param  {String} image L'image à utiliser pour les billboard des entités ponctuelles
    * @param  {Array} billboard Le tableau d'entités où stocker les billboards
    * @param  {Boolean} point3D true si les points ont une composante 3D, false sinon
    * @param  {Object} options facultatif - Les options pour le chargement
    * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
    * @param  {Array} options.line Le tableau d'entités où stocker les lignes qu'on trace depuis le bas du billbard jusqu'au sol
    * @param  {String} options.couleur La couleur de la ligne au format '#FFFFFF'
    * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
    * @return  {GeoJsonDataSource} le json une fois que tout est chargé
    */
    loadTimePoint(link, name, choice, image, billboardData, point3D, options = {}){
      let promise = Cesium.GeoJsonDataSource.load(link, {
        markerSize: 0 //pour que l'épingle n'apparaisse pas
      });

      this.showLoader(); // fonction qui affiche un symbole de chargement sur la page

      //var billboardData = new Cesium.CustomDataSource();
      //var billboardData = globe.viewer.scene.primitives.add(new Cesium.BillboardCollection());

      promise.then((dataSource) => {
        // Ajoute le json dans la liste des dataSource
        this.viewer.dataSources.add(dataSource);
        this.dataSources[name] = dataSource;
        let entities = dataSource.entities.values;
        this.hideLoader();

        for(let i = 0; i < entities.length; i++) {
          let entity = entities[i];
          // on récupère les coordonnées des points importés
          var X = (dataSource._entityCollection._entities._array[i]._position._value.x);
          var Y = (dataSource._entityCollection._entities._array[i]._position._value.y);
          var Z = (dataSource._entityCollection._entities._array[i]._position._value.z);

          var position = new Cesium.Cartesian3(X,Y,Z); // en coords cartesiennes (système ECEF)

          if(point3D === false) {

            // créé un billboard pour chaque entité ponctuelle (en précisant l'image à utiliser dans les paramètres)
            // l'entité billboard ne conserve pas les attributs
            // des billboard sont disponibles dans le dossier src/img/billboard sous le nom marker_'color' (10 couleurs)
            var billboardEntity = billboardData.add({
              billboard : {
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                sizeInMeters: false,
                scaleByDistance : new Cesium.NearFarScalar(1000, 10, 150000, 0)
              }
            });
            // nécessité de convertir en lon/lat car la coordonnée Z en ECEF ne correspond pas à la hauteur
            let cartographic = Cesium.Cartographic.fromCartesian(position); // conversion en radians
            let longitude = cartographic.longitude;
            let latitude = cartographic.latitude;
            // on augmente la hauteur des points pour qu'ils apparaissent au dessus du photomaillage
            let height = Number(230 + cartographic.height);

            var coordHauteur = new Cesium.Cartesian3.fromRadians(longitude, latitude, height);

            //on trace une ligne partant du sol jusqu'à la base du billboard
            var coordLigne = [position, coordHauteur];
            var lineEntity = this.drawLine(coordLigne, 2, options.couleur, 1, false);
            options.line.push(lineEntity);

            billboardEntity.description = entity.properties._date_echeance._value;
            billboardEntity.position = coordHauteur;

            if(options.classificationField !== undefined) {
              var symbol = image[entity.properties[options.classificationField]];
              if(symbol === undefined) {
                var symbol = 'src/img/billboard/marker_black.png'
              }
              billboardEntity.image = symbol;
            } else {
              billboardEntity.image = image;
            }

          } else if(point3D === true) {
            if(options.classificationField !== undefined) {
              var symbol = image[entity.properties[options.classificationField]];
              entity.billboard.image = symbol;
            } else {
              entity.markerColor = '#FFFFFF';
            }
          }

        } // fin du for entities

        // l'évenement qui actualise la valeur de l'horloge quand on clique sur la timeline
        this.viewer.clock.onTick.addEventListener(function test() {
          // on garde seulement les 10 premiers chiffres pour avoir le jour (sans l'heure et secondes)
          var updateTime = Cesium.JulianDate.toIso8601(globe.viewer.clock.currentTime).substring(0, 10);

          if(point3D === false) {
            for (let i = 0; i < billboardData.length; i++) {
              let billboardtemp = billboardData.get(i);

              // attention aux formats de date, ici updateTime et dateValidite sont au format AAAA-MM-JJ
              // spécifique à la donnée, condition à rajouter pour récupérer le champ dans lequel on stocke la date
              // on récupère la date dans les attributs et on enlève un jour car date échéance = plus valable le jour même
              if(choice === 'default') {
                var dateIso = Cesium.JulianDate.fromIso8601(billboardtemp.description);
                var dateValidite = Cesium.JulianDate.toIso8601(date).substring(0, 10);
              } else {
                var dateIso = Cesium.JulianDate.fromIso8601(billboardtemp.description);
                var date = Cesium.JulianDate.addDays(dateIso, -1, new Cesium.JulianDate());
                var dateValidite = Cesium.JulianDate.toIso8601(date).substring(0, 10);
              }

              // si la date des attributs correspond à la date de la timeline on affiche l'entité
              if(dateValidite === updateTime) {
                billboardtemp.show = true;
              } else {
                billboardtemp.show = false;
              }
              // on demande d'actualiser à chaque changement d'horloge pour voir les couleurs défiler en bougeant la timeline
              globe.viewer.scene.requestRender();
            }
          } else if(point3D === true) {
            for (let i = 0; i < entities.length; i++) {
              let entity = entities[i];

              if(choice === 'default') {
                var dateIso = Cesium.JulianDate.fromIso8601(entity.properties._date._value);
                var dateValidite = Cesium.JulianDate.toIso8601(date).substring(0, 10);
              } else {
                var dateIso = Cesium.JulianDate.fromIso8601(entity.properties._date_echeance._value);
                var date = Cesium.JulianDate.addDays(dateIso, -1, new Cesium.JulianDate());
                var dateValidite = Cesium.JulianDate.toIso8601(date).substring(0, 10);
              }

              // si la date des attributs correspond à la date de la timeline on affiche l'entité
              if(dateValidite == updateTime) {
                entity.show = true;
              } else {
                entity.show = false;
              }
              // on demande d'actualiser à chaque changement d'horloge pour voir les couleurs défiler en bougeant la timeline
              globe.viewer.scene.requestRender();
            }

          }
        });

        for(let i = 0; i < entities.length; i++) {
          if(options.choiceTableau !== undefined) {
            var tabl = new TableauAttribut();
            // on lie les attributs des points au nouvelles entités billboard et lignes
            // l'attribut choiceTableau permet de classifier entre les différentes données
            var tablBillboard = 'createTableau' + options.choiceTableau;
            tabl[tablBillboard](billboardEntity, entity);

            // si on a tracé une ligne depuis le billboard jusqu'au sol on ajoute les attributs sur la ligne aussi
            if(point3D === false) {
              var tablLine = 'createTableau' + options.choiceTableau;
              tabl[tablLine](lineEntity, entity);
            }
          }
        }

      });
      return promise;
    }


    /**
    *
    * La fonction show associée à loadTimeSurf et loadTimePoint <br/>
    * permet d'afficher ou de masquer la donnée temporelle en fonction de la valeur de show
    *
    * @param  {String} show le paramètre qui spécifie quand l'affichage doit être actif - prend la valeur e.target.checked ou non
    * @param  {String} link Le lien vers le fichier
    * @param  {String} name Le nom qu'on donne au json
    * @param  {String} choice spécifique à la donnée, permet de charger l'attribut dans lequel on stocke la date de la donnée
    * @param  {JulianDate} start La date de début de l'intervalle de temps qu'on souhaite afficher dans la timeline
    * @param  {JulianDate} end La date de fin de l'intervalle de temps qu'on souhaite afficher dans la timeline
    * @param  {Object} options facultatif - Les options pour le chargement
    * @param  {String} options.typeDonnee spécifie le type de donnée (ici surface ou point)
    * @param  {String} options.classificationField le champ de la donnée selon lesquelles les données seront classifiées
    * @param  {Object} options.colors un objet qui contient les valeurs que peut prendre le classificationField et les couleurs à associer
    * @param  {Number} options.alpha La transparence de la couleur des entités à afficher
    * @param  {Array} options.line Le tableau d'entités où stocker les contours des polygones ou lignes qu'on trace depuis le bas du billbard jusqu'au sol
    * @param  {String} options.nameLigne La nom des lignes de contour
    * @param  {String} options.image L'image à utiliser pour les billboard des entités ponctuelles
    * @param  {Array} options.billboard Le tableau d'entités où stocker les billboards
    * @param  {Boolean} options.point3D true si les points ont une composante 3D, false sinon
    * @param  {String} options.couleur La couleur de la ligne au format '#FFFFFF'
    * @param  {String} options.choiceTableau la chaine de caractère à rajouter à createTableau pour appeler la bonne fonction de mise en forme du tableau d'attributs
    */
    showTimeJson(show, name, link, choice, billboard, start, end, options = {}){
      var today = Cesium.JulianDate.now();
      var demain = Cesium.JulianDate.addDays(today, 1, new Cesium.JulianDate());
      var billboardData = this.viewer.scene.primitives.add(new Cesium.BillboardCollection());

      if(show){
        // on zoome la timeline sur l'intervalle souhaité
        this.viewer.timeline.zoomTo(start, end);

        if(this.dataSources[name] === undefined){
          if(options.typeDonnee === 'surface') {
            globe.loadTimeSurf(link, name, choice, options);
          } else if(options.typeDonnee === 'point') {
            globe.loadTimePoint(link, name, choice, options.image, billboardData, options.point3D, options);
          }
        } else{
          this.dataSources[name].show = true;
          if(options.nameLigne !== undefined) {
            for(var i = 0; i < options.line.length; i++){
              options.line[i].show = true;
            }
          }
        }
        this.viewer.scene.requestRender(); // dit à Cesium de recalculer la page
      } else{
        if(this.dataSources[name] !== undefined){
          // on rezoome la timeline sur aujourd'hui et on reset les paramètres de l'horloge
          var today = Cesium.JulianDate.now();
          var demain = Cesium.JulianDate.addDays(today, 1, new Cesium.JulianDate());
          globe.viewer.clock.shouldAnimate = false;
          globe.viewer.clock.currentTime = today;
          globe.viewer.clock.startTime = Cesium.JulianDate.addDays(today, -10, new Cesium.JulianDate());
          globe.viewer.clock.stopTime = Cesium.JulianDate.addDays(today, 10, new Cesium.JulianDate());
          globe.viewer.clock.multiplier = 1.0;
          globe.viewer.timeline.zoomTo(today, demain);

          this.dataSources[name].show = false;

          if(options.line !== undefined) {
            for(var i = 0; i < options.line.length; i++){
              options.line[i].show = false;
            }
          }
          this.viewer.scene.requestRender();
        }
      }
    }


    /**
    * Récupère les 9 tuiles du plu détaillé à afficher en fonction des coordonnées du centre de l'écran
    *
    * @param  {Number} taille la taille des tuiles en pixel (souvent 256)
    * @param  {Number} zoom le niveau de zoom à utiliser (ici 17)
    * @param  {Array} pluTiles le tableau qui contient les polygones texturés
    * @param  {Array} linePLUdetaille le tableau qui contient les contours des 9 dalles
    */
    pluDetaille(taille, zoom, pluTiles, linePLUdetaille) {
      var tuile = new Tile(taille);

      // coordonnées du centre de l'écran
      var windowPosition = new Cesium.Cartesian2(globe.viewer.container.clientWidth / 2, globe.viewer.container.clientHeight / 2);
      var centre = globe.viewer.scene.pickPosition(windowPosition);

      let cartographic = Cesium.Cartographic.fromCartesian(centre);
      var latlong = {
        lat: Cesium.Math.toDegrees(cartographic.latitude),
        lng: Cesium.Math.toDegrees(cartographic.longitude)
      }

      // on récupère la tuile qui correspond aux coordonnées du centre de l'écran
      var coordTile = tuile.getCoord(latlong, zoom);

      // puis on récupère les 8 tuiles autour
      var coord1 = {
        x: coordTile.x - 1,
        y: coordTile.y - 1,
        z: zoom
      }
      var coord2 = {
        x: coordTile.x ,
        y: coordTile.y - 1,
        z: zoom
      }
      var coord3 = {
        x: coordTile.x + 1,
        y: coordTile.y - 1,
        z: zoom
      }
      var coord4 = {
        x: coordTile.x - 1,
        y: coordTile.y,
        z: zoom
      }
      var coord5 = {
        x: coordTile.x + 1,
        y: coordTile.y,
        z: zoom
      }
      var coord6 = {
        x: coordTile.x - 1,
        y: coordTile.y + 1,
        z: zoom
      }
      var coord7 = {
        x: coordTile.x,
        y: coordTile.y + 1,
        z: zoom
      }
      var coord8 = {
        x: coordTile.x + 1,
        y: coordTile.y + 1,
        z: zoom
      }

      var coords = [coordTile, coord1, coord2, coord3, coord4, coord5, coord6, coord7, coord8];
      var coordLine = [];

      // on récupère les tuiles aux coordonnées tuiles qu'on a définies
      for (var i = 0; i < 9; i++) {
        var temp = coords[i];
        var latlon = tuile.getTile(temp, zoom);
        coordLine.push(latlon);

        // on ajoute le polygone texturé
        var pludetaille = this.viewer.entities.add({
          polygon: {
            hierarchy: Cesium.Cartesian3.fromDegreesArray([latlon[3], latlon[2], latlon[1], latlon[2], latlon[1], latlon[0], latlon[3], latlon[0]]),
            material: "https://3d.strasbourg.eu/CESIUM_OPENDATA/data/plu/"+ zoom + "/" + temp.x + "/" + temp.y + ".png",
            classificationType: Cesium.ClassificationType.CESIUM_3D_TILE
          },
        });

        pluTiles.push(pludetaille);

      }

      // on trace le contour des 9 tuiles
      var coordContour = [coordLine[1][1], coordLine[1][2], coordLine[3][3], coordLine[3][2], coordLine[8][3], coordLine[8][0], coordLine[6][1], coordLine[6][0], coordLine[1][1], coordLine[1][2]];
      linePLUdetaille. push(this.drawLine(Cesium.Cartesian3.fromDegreesArray(coordContour), 2, "#FFFFFF", 1, true, 'Visibilité PLU détaillé'));

      this.viewer.scene.requestRender();

    }

  } // fin de la classe Globe