CEREMA Barentin, ville verte et perméable
  • Accueil
  • L’essentiel
  • Résultats
    • Déclinaison locale de la trame verte et bleue
    • Nature en ville et cadre de vie
    • Spatialisation des enjeux et changement climatique
    • Ciblage des potentiels de désimperméabilisation
    • Ciblage des potentiels de renaturation
    • Pour aller plus loin : solutions et acceptabilité
  • Cartes interactives
    • Ciblage du potentiel de désimperméabilisation
    • Ciblage du potentiel de renaturation
  • Cartothèque
  • Méthodologie
    • Déclinaison locale de la trameverte et bleue
    • Nature en ville, enjeux, changement climatique
    • Ciblage de potentiel de désimperméabilisation
    • Ciblage de potentiel renaturation
  • Annexes
    • Bibliographie
    • Données utilisées (source, millésime)
  • Qui sommes-nous ?
Barentin, ville verte
  • Carte interactive
  • Méthodo
  • Hypothèses
viewof map = {
    const entete = document.getElementById('quarto-header');
    const headerHeight = entete ? entete.getBoundingClientRect().height : 0;
    const availableHeight = window.innerHeight - headerHeight - 120; // forfait de ?px pour prendre de la marge

    const container = html`<div style="width: 100%; height: ${availableHeight}px; margin: 0; padding: 0;">`;
    let resolve;
    container.value = new Promise(_ => resolve = _);
    yield container
    
    // création du conteneur 
    /*const container = html`<div style="height:600px;">`;
    let resolve;
    container.value = new Promise(_ => resolve = _);
    yield container; // Give the container dimensions.*/

    // dans emptystyle je met les terrains, à voir si j'arrive à les sortir un jour... 
    const emptyStyle = {
    version: 8,
    sources: {'terrainSource': {
                  type: 'raster-dem',
                  tiles: [
                      `dem://data.geopf.fr/wms-r/wms?bbox={bbox-epsg-3857}&format=image/x-bil;bits=32&service=WMS&version=1.3.0&request=GetMap&crs=EPSG:3857&width=256&height=256&styles=normal&layers=ELEVATION.ELEVATIONGRIDCOVERAGE.HIGHRES`
                  ],
                  minzoom: 6,
                  maxzoom: 14,
                  tileSize: 256
              }},
    layers: [],
     terrain: {
              source: 'terrainSource',
              exaggeration: 1
          }
    };

    // fonctions pour terrain 
    const fetchAndParseXBil = async (url) => {
        const response = await fetch(url);
        const arrayBuffer = await response.arrayBuffer();
        const dataView = new DataView(arrayBuffer);
        const width = Math.sqrt(dataView.byteLength / 4); // Assuming square tiles
        const height = width;
        const elevations = new Float32Array(width * height);

        for (let i = 0; i < width * height; i++) {
            elevations[i] = dataView.getFloat32(i * 4, true);
            if (elevations[i] < -10 || elevations[i] > 4900) {
                elevations[i] = 0;
            }
        }

        // Appliquer un filtre gaussien pour lisser les données
        // Taille du noyau (kernelSize) : Vous pouvez ajuster la taille du noyau pour obtenir un lissage plus ou moins prononcé. Une taille plus grande donnera un lissage plus prononcé.
        // Écart-type (sigma) : L'écart-type détermine la largeur de la distribution gaussienne. Un écart-type plus grand donnera un lissage plus doux.
        // const smoothedElevations = applyGaussianFilter(elevations, width, height, 3, 1.0);
        const smoothedElevations = applyGaussianFilter(elevations, width, height, 5, 2.0);

        return { elevations: smoothedElevations, width, height };
    };

    const applyGaussianFilter = (elevations, width, height, kernelSize, sigma) => {
        const kernel = createGaussianKernel(kernelSize, sigma);
        const smoothedElevations = new Float32Array(width * height);

        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                let sum = 0;
                let weightSum = 0;
                for (let ky = -Math.floor(kernelSize / 2); ky <= Math.floor(kernelSize / 2); ky++) {
                    for (let kx = -Math.floor(kernelSize / 2); kx <= Math.floor(kernelSize / 2); kx++) {
                        const nx = Math.min(Math.max(x + kx, 0), width - 1);
                        const ny = Math.min(Math.max(y + ky, 0), height - 1);
                        const weight = kernel[ky + Math.floor(kernelSize / 2)][kx + Math.floor(kernelSize / 2)];
                        sum += elevations[ny * width + nx] * weight;
                        weightSum += weight;
                    }
                }
                smoothedElevations[y * width + x] = sum / weightSum;
            }
        }

        return smoothedElevations;
    };

    const createGaussianKernel = (size, sigma) => {
        const kernel = [];
        const halfSize = Math.floor(size / 2);
        const sigmaSquared = sigma * sigma;
        let sum = 0;

        for (let y = -halfSize; y <= halfSize; y++) {
            const row = [];
            for (let x = -halfSize; x <= halfSize; x++) {
                const value = Math.exp(-(x * x + y * y) / (2 * sigmaSquared)) / (2 * Math.PI * sigmaSquared);
                row.push(value);
                sum += value;
            }
            kernel.push(row);
        }

        // Normaliser le noyau
        for (let y = 0; y < size; y++) {
            for (let x = 0; x < size; x++) {
                kernel[y][x] /= sum;
            }
        }

        return kernel;
    };

    maplibregl.addProtocol("dem", async (params) => {
        try {
            const { elevations, width, height } = await fetchAndParseXBil(`https://${params.url.split("://")[1]}`);
            const data = new Uint8ClampedArray(width * height * 4);

            for (let i = 0; i < elevations.length; i++) {
                let elevation = Math.round(elevations[i] * 10) / 10;
                // reverse https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data
                const baseElevationValue = 10 * (elevation + 10000);
                const red = Math.floor(baseElevationValue / (256 * 256)) % 256;
                const green = Math.floor((baseElevationValue - red * 256 * 256) / 256) % 256;
                const blue = baseElevationValue - red * 256 * 256 - green * 256;
                data[4 * i] = red;
                data[4 * i + 1] = green;
                data[4 * i + 2] = blue;
                data[4 * i + 3] = 255;
            }

            const imageData = new ImageData(data, width, height);
            const imageBitmap = await createImageBitmap(imageData);
            return {
                data: imageBitmap
            };
        } catch (error) {
            console.error(error);
            throw error;
        }
    });

    // on initialise une map
    //const map = (container.value = new maplibregl.Map({
    const map = new maplibregl.Map({
        container,
        center: [0.95444, 49.54228],
        zoom: 12,
        pitch: 0,
        maxPitch: 80,    
        style: emptyStyle
    });

    // on ajoute des controls

    map.addControl(new maplibregl.NavigationControl(
        {
            visualizePitch: true,
            //showZoom: true,
            //showCompass: true
        }),
        'top-left'
    );

    map.addControl(new maplibregl.FullscreenControl('top'));

    map.addControl(
        new maplibregl.TerrainControl({
            source: 'terrainSource',
            exaggeration: 1
        }),
        'top-right'
    )

    const scale = new maplibregl.ScaleControl({
        maxWidth: 80,
        unit: 'metric'
    });
    map.addControl(scale);

    // quand la carte est chargée on résoud ce chunk
    map.on('load', () => {
        console.log("Carte chargée !");
        // Ici, tu peux ajouter des marqueurs, des sources, ou déclencher un flyTo
        resolve(map); // Expose the Map instance as the view’s value.
    });

    // et une seule fois, on ajoute les autres éléments
    map.once("load", () => {
        //console.log("ajout du ciel");
        map.setSky({
            "sky-color": "#199EF3",
            "sky-horizon-blend": 0.5,
            "horizon-color": "#ffffff",
            "horizon-fog-blend": 0.9,
            "fog-color": "#0000ff",
            "fog-ground-blend": 0.95,
        });

        // conteneur pour mes menus

        // TOGGLE FOND DE PLAN
        const mapMenuDiv = document.createElement('div');
        mapMenuDiv.classList.add('mapFondDePlan');
        map.getContainer().appendChild(mapMenuDiv);

        const customDivHTML = `
                            <strong>Fond de plan :</strong>
                            <div class="form-check form-switch">
                                <input class="form-check-input" type="checkbox" role="switch" id="orthoCheckbox" checked>
                                <label class="form-check-label" for="orthoCheckbox">Photos aériennes</label>
                            </div>
                        `;
        mapMenuDiv.innerHTML += customDivHTML;

        const checkbox = document.getElementById('orthoCheckbox');

        ["click", "input", "change"].forEach(evt => {
        checkbox.addEventListener(evt, e => {
            // ici ça permet de ne pas déclencher en cascade la mise à jour des chunc qui dépendent de viewof map
            e.stopImmediatePropagation(); // empêche tous les autres handlers
            e.stopPropagation();           // bloque la propagation classique
            //e.preventDefault();            // bloque le comportement par défaut
            //console.log("Toggle ortho :", e.target.checked);
            const checkboxValue = document.getElementById('orthoCheckbox').checked;
            if (map.getLayer('raster-ortho') && map.getLayer('raster-planign') ) {
                map.setLayoutProperty('raster-ortho', 'visibility', checkboxValue ? 'visible' : 'none');
                map.setLayoutProperty('raster-planign', 'visibility', checkboxValue ? 'none' : 'visible');
            }
        }, true); // le true active le mode capture
        });

        // Menu layer !
        const mapLayersDiv = document.createElement('div');
        mapLayersDiv.classList.add('mapLayers');
        //mapMenuDiv.innerHTML = '<strong>Fond de plan :</strong>';
        map.getContainer().appendChild(mapLayersDiv);

        // HTML du bouton + menu
        mapLayersDiv.innerHTML = `
        <button id="toggleMapMenuButton" class="btn btn-light">
            <img id="layersIcon" src="images/icons/layers-half.svg" alt="Icône SVG" width="25" height="25">
        </button>
        <div id="layersList" style="display:none; background:rgba(255,255,255,0.95); padding:10px; border-radius:6px; margin-top:5px; max-width:50000;">
            <h4>Légende</h4>
            <p>Voici le contenu du menu.</p>

        </div>
        `;

        // toggle ouverture / fermeture
        const toggleMapMenuButton = mapLayersDiv.querySelector('#toggleMapMenuButton');
        const layersList = mapLayersDiv.querySelector('#layersList');
        const layersIcon = mapLayersDiv.querySelector('#layersIcon');

        toggleMapMenuButton.addEventListener('click', e => {
            e.stopPropagation(); 
            e.preventDefault();

            const isHidden = layersList.style.display === 'none';
            layersList.style.display = isHidden ? 'block' : 'none';

            // 💡 Change l'icône selon l'état
            layersIcon.src = isHidden 
                ? 'images/icons/layers-close.svg' 
                : 'images/icons/layers-half.svg';
        });

        // RECHERCHE D'ADRESSE
        const searchContainer = document.createElement('div');
        //mapMenuDiv.innerHTML = '<strong>Fond de plan :</strong>';
        map.getContainer().appendChild(searchContainer);
        searchContainer.innerHTML = `
        <div id="search-container">
            <input id="search" type="search" placeholder="Rechercher une adresse..." autocomplete="off" />
            <ul id="results" class="autocomplete-results"></ul>
        </div>
        `;

    });

    invalidation.then(() => map.remove());


}
{
   // codage perso sur l'api data.gouv.fr
  // === Autocomplétion avec l'API Adresse de data.gouv.fr ===
  const input = document.getElementById("search");
  const resultsContainer = document.getElementById("results");
  let currentResults = [];
  let currentIndex = -1;
  let timer;

  input.addEventListener("input", function (e) {
    e.stopImmediatePropagation();
    e.stopPropagation();
    clearTimeout(timer);

    // Vérification si le champ est vide
    if (input.value.trim() === "") {
        console.log("Le champ est vide !");
        // Supprime l'ancien marqueur s'il existe
        if (window.currentMarker) {
            window.currentMarker.remove();
        }
    }

    timer = setTimeout(() => search(input.value), 300);
  });

  input.addEventListener("keydown", function(e) {
    e.stopImmediatePropagation();
    e.stopPropagation();

    if (!currentResults.length) return;
    switch(e.key) {
      case "ArrowDown":
        currentIndex = Math.min(currentIndex + 1, currentResults.length - 1);
        highlight();
        e.preventDefault();
        break;
      case "ArrowUp":
        currentIndex = Math.max(currentIndex - 1, 0);
        highlight();
        e.preventDefault();
        break;
      case "Enter":
        if(currentIndex >= 0) {
          select(currentResults[currentIndex]);
        } else if(currentResults.length > 0) {
          select(currentResults[0]); // sélection du premier résultat si aucun surligné
        }
        e.preventDefault();        
        break;
      case "Escape":
        hide();
        break;
    }
  });

  function search(query) {
    if (!query || query.length < 3) return hide();
    fetch(`https://api-adresse.data.gouv.fr/search/?q=${encodeURIComponent(query)}&limit=10`)
      .then(res => res.json())
      .then(data => {
        currentResults = data.features;
        render();
      });
  }

  function render() {
    resultsContainer.innerHTML = "";
    if (!currentResults.length) return hide();
    currentResults.forEach((feature, i) => {
      const li = document.createElement("li");
      li.textContent = feature.properties.label;
      li.addEventListener("click", (e) => { e.stopImmediatePropagation(); e.stopPropagation(); select(feature); });
      li.addEventListener("mouseover", (e) => { e.stopImmediatePropagation(); e.stopPropagation(); currentIndex = i; highlight(); });
      resultsContainer.appendChild(li);
    });
    resultsContainer.style.display = "block";
    currentIndex = -1;
  }

  function highlight() {
    Array.from(resultsContainer.children).forEach((li, i) => {
      li.classList.toggle("on", i === currentIndex);
    });
  }

  function select(feature) {
    input.value = feature.properties.label;
    hide();
    console.log("Adresse sélectionnée :", feature);
    //alert("Adresse sélectionnée : " + feature.properties.label); // <-- ici tu peux mettre ton code personnalisé
    const [lon, lat] = feature.geometry.coordinates;
    //map.flyTo({ center: [lon, lat], zoom: 16 });
    map.flyTo({
            center: [lon, lat],
            zoom: 16,
            pitch: 45
        });

        // Supprime l'ancien marqueur s'il existe
        if (window.currentMarker) {
            window.currentMarker.remove();
        }

        // Crée un nouveau marqueur et l'ajoute à la carte
        window.currentMarker = new maplibregl.Marker()
            .setLngLat(feature.geometry.coordinates)
            .addTo(map);
  }

  function hide() {
    resultsContainer.style.display = "none";
    currentResults = [];
    currentIndex = -1;
    
  }
}
// Bloc pour fabriquer la liste des couches avec légende, widget, 
{    
  // On ajoute le texte dans la layer list
  map; // pour s'assurer que map est résolu...
  myLayers;

  const layersList = document.getElementById('layersList');
  if (!layersList) return;
  // Construire le HTML
  const htmlContent = `
  <h6>Contrôle des couches</h6>
  <div style="display:flex; flex-direction:column; gap:0px;">
    ${myLayers
      .filter(d => !["raster-planign", "raster-ortho", "hillshadeSource"].includes(d.id))
      .map(layer => {
        const paint = layer.paint || {};
        const colorKey = Object.keys(paint).find(k => k.includes("color"));
        const colorValue = paint[colorKey];
        const opacityKey = Object.keys(paint).find(k => k.includes("opacity"));
        const opacityValue = paint[opacityKey] ?? 1;
        const initialChecked = layer.layout.visibility === "visible" ? "checked" : "";

        // 🟢 Cas 0a et 0b : RASTER → inchangé
        if (layer.type === "raster" && !rasterColors.find(obj => obj.id === layer.id)) {
          return `
            <div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:4px;">
              <div style="display:flex; align-items:center; gap:6px;">
                <input type="checkbox" id="checkbox_${layer.id}" name="oneCheckbox" style="height:24px;" ${initialChecked}/>
                <span style="font-weight:bold;">${aliasLayerIds[layer.id] ?? layer.id}</span>
              </div>
              <input type="range" id="slider_${layer.id}" name="${opacityKey}" min="0" max="1" step="0.01" style="width:100px" value="${opacityValue}"/>
            </div>
          `;
        }

        if (layer.type === "raster" && rasterColors.find(obj => obj.id === layer.id)) {
          const customColorValue = rasterColors.find(obj => obj.id === layer.id)["paint"]["fill-color"];
          const property = customColorValue[1][1];
          const pairs = [];
          for (let i = 2; i < customColorValue.length - 1; i += 2) {
            pairs.push({ val: customColorValue[i], col: customColorValue[i + 1] });
          }
          const defaultColor = customColorValue[customColorValue.length - 1];
          const hasDefaultColor = false;

          return `
            <div style="display:flex; flex-direction:column; gap:4px;">
              <div style="display:flex; align-items:center; justify-content:space-between;">
                <div style="display:flex; align-items:center; gap:6px;">
                  <input type="checkbox" id="checkbox_${layer.id}" name="oneCheckbox" style="height:24px;" ${initialChecked}/>
                  <span style="font-weight:bold;">${aliasLayerIds[layer.id] ?? layer.id}</span>
                </div>
                <input type="range" id="slider_${layer.id}" name="${opacityKey}" min="0" max="1" step="0.01" style="width:100px" value="${opacityValue}"/>
              </div>

              <div style="margin-left:12px; display:flex; flex-direction:column; gap:2px;">
                ${pairs
                  .map(
                    p => `
                    <div style="display:flex; align-items:center; gap:6px;">
                      <span style="display:inline-block; width:14px; height:14px; background:${p.col}; border:1px solid #555;"></span>
                      <span>${p.val}</span>
                    </div>`
                  )
                  .join("")}
                ${hasDefaultColor
                  ? `
                  <div style="display:flex; align-items:center; gap:6px;">
                    <span style="display:inline-block; width:14px; height:14px; background:${defaultColor}; border:1px solid #555;"></span>
                    <span>Autres valeurs</span>
                  </div>`
                  : ""}
              </div>
            </div>
          `;
        }

        // 🟢 Cas 1 : couleur simple
        if (typeof colorValue === "string") {
          const isFill = colorKey?.includes("fill-color") || colorKey?.includes("fill-extrusion-color");
          const isLine = colorKey?.includes("line-color");

          const style = `
            display:inline-block;
            width:12px;
            height:12px;
            margin-right:6px;
            ${isLine
              ? `background:none; border:2px solid ${colorValue};`
              : `background:${colorValue}; border:1px solid #555;`}
          `;

          return `
            <div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:4px;">
              <div style="display:flex; align-items:center; gap:6px;">
                <input type="checkbox" id="checkbox_${layer.id}" name="oneCheckbox" style="height:24px;" ${initialChecked}/>
                <span style="${style}"></span>
                <span style="font-weight:bold;">${aliasLayerIds[layer.id] ?? layer.id}</span>
              </div>
              <input type="range" id="slider_${layer.id}" name="${opacityKey}" min="0" max="1" step="0.01" style="width:100px" value="${opacityValue}"/>
            </div>
          `;
        }

        // 🟣 Cas 2 : expression "match"
        if (Array.isArray(colorValue) && colorValue[0] === "match") {
          const property = colorValue[1][1];
          const pairs = [];
          for (let i = 2; i < colorValue.length - 1; i += 2) {
            pairs.push({ val: colorValue[i], col: colorValue[i + 1] });
          }
          const defaultColor = colorValue[colorValue.length - 1];
          const hasDefaultColor = false;
          const isLine = colorKey?.includes("line-color");
          const isFill = colorKey?.includes("fill-color") || colorKey?.includes("fill-extrusion-color");

          return `
            <div style="display:flex; flex-direction:column; gap:4px;">
              <div style="display:flex; align-items:center; justify-content:space-between;">
                <div style="display:flex; align-items:center; gap:6px;">
                  <input type="checkbox" id="checkbox_${layer.id}" name="oneCheckbox" style="height:24px;" ${initialChecked}/>
                  <span style="font-weight:bold;">${aliasLayerIds[layer.id] ?? layer.id}</span>
                </div>
                <input type="range" id="slider_${layer.id}" name="${opacityKey}" min="0" max="1" step="0.01" style="width:100px" value="${opacityValue}"/>
              </div>

              <div style="margin-left:12px; display:flex; flex-direction:column; gap:2px;">
                ${pairs
                  .map(
                    p => `
                    <div style="display:flex; align-items:center; gap:6px;">
                      <span style="
                        display:inline-block;
                        width:14px;
                        height:14px;
                        ${
                          isLine
                            ? `background:none; border:2px solid ${p.col};`
                            : `background:${p.col}; border:1px solid #555;`
                        }
                      "></span>
                      <span>${p.val}</span>
                    </div>`
                  )
                  .join("")}
                ${hasDefaultColor
                  ? `
                  <div style="display:flex; align-items:center; gap:6px;">
                    <span style="display:inline-block; width:14px; height:14px; background:${defaultColor}; border:1px solid #555;"></span>
                    <span>Autres valeurs</span>
                  </div>`
                  : ""}
              </div>
            </div>
          `;
        }

        return "";
      })
      .join("")}
  </div>
`;


  // Ajouter le HTML dans l'élément
  layersList.innerHTML = htmlContent;

  // Sélectionner tous les checkboxes et sliders dans layersList
  const checkboxes = layersList.querySelectorAll('input[type="checkbox"]');
  const sliders = layersList.querySelectorAll('input[type="range"]');

  // Fonction pour gérer l'événement (stop + prevent)
  const stopEvent = (e) => {
    e.stopImmediatePropagation(); // empêche tous les autres handlers
    e.stopPropagation();
    // e.preventDefault();
  };

  // Pour tous les checkboxes, écouter click, input et change
  checkboxes.forEach(cb => {
    ["click", "input", "change"].forEach(evt => {
      cb.addEventListener(evt, (e) => {
        stopEvent(e);

        // Ici tu peux lancer ta fonction spécifique pour ce checkbox
        //console.log(`Checkbox ${cb.id} : événement ${evt}`);
        const layerId = cb.id.split("_").slice(1).join("_");
        const isChecked = cb.checked; // ← ici ton booléen
        setLayoutPropertyIfExists(layerId, 'visibility', isChecked ? 'visible' : 'none');

        // Exemple : toggleLayerVisibility(cb.id);
      });
    });
  });

  // Ajouter les écouteurs à tous les sliders
  sliders.forEach(slider => {
    slider.addEventListener('input', (e) => {
      stopEvent(e);

      // Ici tu peux lancer ta fonction spécifique pour ce slider
      //console.log('Slider changé :', slider.id, slider.name, 'valeur:', slider.value);
      // Exemple : setLayerOpacity(slider.id, slider.value);
      const layerId = slider.id.split("_").slice(1).join("_");
      const opacityKey = slider.name; // cf astuce plus haut
      //console.log(opacityKey);

      if (opacityKey && opacityKey !== undefined && opacityKey !== null && opacityKey !== "") {
        // bizarre j'ai un bug si je prend ma fonction avec IfExists
        map.setPaintProperty(layerId, opacityKey+'-transition', { duration: 0 }); // important sinon BUG de réactivité
        setPaintPropertyIfExists(layerId, opacityKey, parseFloat(slider.value));
      }
    });
  });

  //console.log('mouchard: layersList mis à jour');

}
mutable myLayers = {}
{
  // on replace tout correctement, dans l'ordre qu'on veut quand tout a été chargée
  // à voir à la pratique...
  map.once('styledata', () => {
      console.log("🧱 réorganisation des couches dans l'ordre de orderLayerIds");

        moveToFront("raster-planign")
        moveToFront("raster-ortho")
        moveToFront("hillshadeSource")

      for (const id of  orderLayerIds){
        moveToFront(id)
        //console.log("move to front : " + id)
      }

      mutable myLayers = map.getStyle().layers;
      console.log("myLayers:", mutable myLayers);

      // [TODO : à tester car c'est pas propre là doit y avoir un prb plus profond]
      // à creuser : contrôler ici le nombre de couche ? en comparant la togglelist + fond de plan ?
      console.log(mutable myLayers.length)
      if (mutable myLayers.length < orderLayerIds.length + 3){
        alert("Problème au chargement des couches, la page va se recharger");
        location.reload();
      }
  });
}
function moveToFront(idLayer) {
    // null en deuxième param veut dire couche du 1er param tout devant
    if (map.getLayer(idLayer)) {
        map.moveLayer(idLayer, null);
    }
}
/*function setPaintPropertyIfExists(idLayer, fillProp, opacityValue) {
    // null en deuxième param veut dire couche du 1er param tout devant
    if (map.getLayer(idLayer)) {
        map.setPaintProperty(idLayer, fillProp, opacityValue);
    }
}*/
// version amléiorée pour éviter de touiller des propriétés qui n'existent pas
function setPaintPropertyIfExists(layerId, property, value) {
  const layer = map.getLayer(layerId);
  if (!layer) {
    console.warn(`Layer ${layerId} introuvable`);
    return;
  }

  const paint = myLayers.find(l => l.id === layerId)?.paint || {};

  // si la propriété n’existe pas dans le style du layer, on quitte
  if (!(property in paint)) {
    // facultatif : message pour debug
    // console.warn(`Propriété "${property}" inexistante pour le layer "${layerId}"`);
    return;
  }

  try {
    map.setPaintProperty(layerId, property, value);
  } catch (err) {
    console.warn(`Erreur lors de setPaintProperty(${layerId}, ${property}) :`, err);
  }
}
function setLayoutPropertyIfExists(idLayer, fillProp, opacityValue) {
    // null en deuxième param veut dire couche du 1er param tout devant
    if (map.getLayer(idLayer)) {
        map.setLayoutProperty(idLayer, fillProp, opacityValue);
    }
}
// AJOUT DE LA SOURCE RASTER ORTHOPHOTO
{
  // objet source pour attaquer la geoplateforme
  const sourceOrtho = { style: 'normal', format: 'image/jpeg', layer: 'HR.ORTHOIMAGERY.ORTHOPHOTOS' };  

  // Configuration de la source orthophoto IGN
  const orthoSource = {
    type: 'raster',
    tiles: [
        `https://data.geopf.fr/wmts?SERVICE=WMTS&style=${sourceOrtho.style}&VERSION=1.0.0&REQUEST=GetTile&format=${sourceOrtho.format}&layer=${sourceOrtho.layer}&tilematrixset=PM&TileMatrix={z}&TileCol={x}&TileRow={y}`
    ],
    tileSize: 256,
    attribution: '© <a href="https://www.ign.fr/">IGN</a>',
    minzoom: 0,
    maxzoom: 22
  };

  // Configuration du layer orthophoto
  const orthoLayer = {
    id: 'raster-ortho',
    type: 'raster',
    source: 'raster-ortho',
    layout: { visibility: 'visible' }
  };

  // Ajoute la source et le layer une fois la carte chargée

  map.addSource('raster-ortho', orthoSource);
  map.addLayer(orthoLayer);
  console.log("Orthophoto IGN ajoutée !");

}
// AJOUT DE LA SOURCE RASTER PLAN IGN
{
  // objet source pour attaquer la geoplateforme
  const sourcePlanIGN = { style: 'normal', format: 'image/png', layer: 'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2' };

  // Configuration de la source orthophoto IGN
  const planignSource = {
    type: 'raster',
    tiles: [
      `https://data.geopf.fr/wmts?SERVICE=WMTS&style=${sourcePlanIGN.style}&VERSION=1.0.0&REQUEST=GetTile&format=${sourcePlanIGN.format}&layer=${sourcePlanIGN.layer}&tilematrixset=PM&TileMatrix={z}&TileCol={x}&TileRow={y}`
    ],
    tileSize: 256,
    attribution: '© <a href="https://www.ign.fr/">IGN</a>',
    minzoom: 0,
    maxzoom: 22
  };

  // Configuration du layer orthophoto
  const planignLayer = {
    id: 'raster-planign',
    type: 'raster',
    source: 'raster-planign',
    layout: { visibility: 'none' }
  };

  // Ajoute la source et le layer une fois la carte chargée

  map.addSource('raster-planign', planignSource);
  map.addLayer(planignLayer);
  console.log("Plan IGN ajoutée !");

}
// AJOUT DE LA SOURCE HILLSHADE
{
  // objet source pour attaquer la geoplateforme
  const sourceHillShade = { style: 'estompage_grayscale', format: 'image/png', layer: 'ELEVATION.ELEVATIONGRIDCOVERAGE.SHADOW' };

  // Configuration de la source orthophoto IGN
  const hillshadeSource = {
    type: 'raster',
    tiles: [
        `https://data.geopf.fr/wmts?SERVICE=WMTS&style=${sourceHillShade.style}&VERSION=1.0.0&REQUEST=GetTile&format=${sourceHillShade.format}&layer=${sourceHillShade.layer}&tilematrixset=PM&TileMatrix={z}&TileCol={x}&TileRow={y}`
    ],
    tileSize: 256,
    attribution: '© <a href="https://www.ign.fr/">IGN</a>',
    minzoom: 0,
    maxzoom: 22
  };

  // Configuration du layer orthophoto
  const orthoLayer = {
    id: 'hillshadeSource',
    type: 'raster',
    source: 'hillshadeSource',
    layout: { visibility: 'visible' }
  };

  // Ajoute la source et le layer une fois la carte chargée

  map.addSource('hillshadeSource', hillshadeSource);
  map.addLayer(orthoLayer);
  console.log("Ombrage IGN ajoutée !");
}
protocol = new pmtiles.Protocol(); // pour les vecteur tuilés
maplibregl.addProtocol('pmtiles', (request) => {
    return new Promise((resolve, reject) => {
        const callback = (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve({ data });
            }
        };
        protocol.tile(request, callback);
    });
});
maplibregl.addProtocol('cog', MaplibreCOGProtocol.cogProtocol); // pour les rasters
{
    const PMTILES_URL_C = `${LAYERS_PATH}/barentin.pmtiles`;
    const c = new pmtiles.PMTiles(PMTILES_URL_C);
    protocol.add(c);

    const barentinSource = {
        type: 'vector',
        url: `pmtiles://${PMTILES_URL_C}`
    };
    const barentinLayer = {
                  id: "barentin",
                  source: "barentinSource",
                  'source-layer': "barentin_4326",
                  type: "line",
                  paint: {
                      'line-color': 'black',
                      'line-width': 4,
                      'line-opacity':1
                  },
                  layout: {visibility: 'visible'}
    };
    map.addSource('barentinSource', barentinSource);
    map.addLayer(barentinLayer);
    console.log("Barentin (contour commune) ajoutée !");
}
{
  const PMTILES_URL_enjeux_tvb_tu = `${LAYERS_PATH}/enjeux_tvb_tu.pmtiles`;
  const enjeux_tvb_tu = new pmtiles.PMTiles(PMTILES_URL_enjeux_tvb_tu);
  protocol.add(enjeux_tvb_tu);

  const enjeux_tvb_tu_Source = {
                 type: 'vector',
                  url: `pmtiles://${PMTILES_URL_enjeux_tvb_tu}`
  };
  const enjeux_tvb_tu_Layer = {
                  id: "enjeux_tvb_tu",
                  source: "enjeux_tvb_tu_Source",
                  'source-layer': "enjeux_tvb_tu",
                  type: "fill",
                  paint: {
                      'fill-color': 'green',
                      'fill-opacity': 0.5
                  },
                  layout: {
                      // Make the layer visible by default.
                      visibility: 'none' //ou none
                  },
  };
  map.addSource('enjeux_tvb_tu_Source', enjeux_tvb_tu_Source);
  map.addLayer(enjeux_tvb_tu_Layer);
  console.log("enjeux tvb tu ajoutée !");
}
{
  const PMTILES_URL_enjeux_nev_cadre_de_vie = `${LAYERS_PATH}/enjeux_nev_cadre_de_vie.pmtiles`;
  const enjeux_nev_cadre_de_vie = new pmtiles.PMTiles(PMTILES_URL_enjeux_nev_cadre_de_vie);
  protocol.add(enjeux_nev_cadre_de_vie); 

  const enjeux_nev_cadre_de_vie_Source = {
                  type: 'vector',
                  url: `pmtiles://${PMTILES_URL_enjeux_nev_cadre_de_vie}`
  };
  const enjeux_nev_cadre_de_vie_Layer = {
                  id: "enjeux_nev_cadre_de_vie",
                  source: "enjeux_nev_cadre_de_vie_Source",
                  'source-layer': "enjeux_nev_cadre_de_vie",
                  type: "fill",
                  paint: {
                      'fill-color': 'purple',
                      'fill-opacity': 0.5
                  },
                  layout: {
                      // Make the layer visible by default.
                      visibility: 'none' //ou none
                  },
  };
  map.addSource('enjeux_nev_cadre_de_vie_Source', enjeux_nev_cadre_de_vie_Source);
  map.addLayer(enjeux_nev_cadre_de_vie_Layer);
  console.log("enjeux nev cadre de vie tu ajoutée !");
}
{
  const PMTILES_URL_enjeux_ilots_de_chaleur = `${LAYERS_PATH}/enjeux_ilots_de_chaleur.pmtiles`;
  const enjeux_ilots_de_chaleur = new pmtiles.PMTiles(PMTILES_URL_enjeux_ilots_de_chaleur);
  protocol.add(enjeux_ilots_de_chaleur);   

    const enjeux_ilots_de_chaleur_Source = {
                  type: 'vector',
                  url: `pmtiles://${PMTILES_URL_enjeux_ilots_de_chaleur}`
  };
  const enjeux_ilots_de_chaleur_Layer = {
                  id: "enjeux_ilots_de_chaleur",
                  source: "enjeux_ilots_de_chaleur_Source",
                  'source-layer': "enjeux_ilots_de_chaleur",
                  type: "fill",
                  paint: {
                      'fill-color': 'red',
                      'fill-opacity': 0.5
                  },
                  layout: {
                      // Make the layer visible by default.
                      visibility: 'none' //ou none
                  },
  };
  map.addSource('enjeux_ilots_de_chaleur_Source', enjeux_ilots_de_chaleur_Source);
  map.addLayer(enjeux_ilots_de_chaleur_Layer);
  console.log("enjeux ilot de chaleur tu ajoutée !"); 
}
{
      const PMTILES_URL_enjeux_inondation = `${LAYERS_PATH}/enjeux_inondation.pmtiles`;
  const enjeux_inondation = new pmtiles.PMTiles(PMTILES_URL_enjeux_inondation);
  protocol.add(enjeux_inondation);

    const enjeux_inondation_Source = {
                    type: 'vector',
                  url: `pmtiles://${PMTILES_URL_enjeux_inondation}`
  };
  const enjeux_inondation_Layer = {
                 id: "enjeux_inondation",
                  source: "enjeux_inondation_Source",
                  'source-layer': "enjeux_inondation",
                  type: "fill",
                  paint: {
                      'fill-color': 'blue',
                      'fill-opacity': 0.5
                  },
                  layout: {
                      // Make the layer visible by default.
                      visibility: 'none' //ou none
                  },
  };
  map.addSource('enjeux_inondation_Source', enjeux_inondation_Source);
  map.addLayer(enjeux_inondation_Layer);
  console.log("enjeux inondation ajoutée !"); 
}
{
      const PMTILES_URL_enjeux_renat_superposes = `${LAYERS_PATH}/enjeux_renat_superposes.pmtiles`;
  const enjeux_renat_superposes = new pmtiles.PMTiles(PMTILES_URL_enjeux_renat_superposes);
  protocol.add(enjeux_renat_superposes);

      const enjeux_renat_superposes_Source = {
                 type: 'vector',
                  url: `pmtiles://${PMTILES_URL_enjeux_renat_superposes}`
  };
  const enjeux_renat_superposes_Layer = {
                  id: "enjeux_renat_superposes",
                  source: "enjeux_renat_superposes_Source",
                  'source-layer': "enjeux_renat_superposes",
                  type: "fill",
                  paint: {
                      'fill-color': [
                          'match',
                          ['get', 'nb_enjeux'],
                          1, '#fee5d9',   // orange très clair
                          2, '#fcae91',   // orange moyen
                          3, '#d7301f',   // orange foncé
                          4, '#4d0000',   // rouge foncé
                          '#cccccc'       // couleur par défaut si aucune valeur correspond
                          ],
                      'fill-opacity': 0.75
                  },
                  layout: {
                      // Make the layer visible by default.
                      visibility: 'visible' //ou none
                  },
  };
  map.addSource('enjeux_renat_superposes_Source', enjeux_renat_superposes_Source);
  map.addLayer(enjeux_renat_superposes_Layer);
  console.log("enjeux renat superposés ajoutée !"); 
}
// EXEMPLE D'INTERACTIVITE SUR Le CLIC DE LA SOURIS ------------------------------------------------------------------------------------------------
// on le déclare dans un autre bloc
{
  map.on('click', 'enjeux_renat_superposes', (e) => {
      // Center the map on the coordinates of any clicked symbol from the 'symbols' layer.
      // pas essentiel, zoomé sur le point cliqué
      const centroid = turf.centroid(e.features[0].geometry);
      map.flyTo({
          center: centroid.geometry.coordinates,
          zoom: 15,
          pitch: 45
      });

      // POP UP on click
      const p = e.features[0].properties;
      const html_txt =`<strong>Nombre d'enjeux : ${p.nb_enjeux} </strong><br>
                      ${p.enjeux_ilots_de_chaleur_lib ? p.enjeux_ilots_de_chaleur_lib + "<br>" : ''}
                      ${p.enjeux_inondation_lib ?  p.enjeux_inondation_lib + "<br>" : ''}
                      ${p.enjeux_nev_cadre_de_vie_lib ? p.enjeux_nev_cadre_de_vie_lib + "<br>" : ''}
                      ${p.enjeux_tvb_tu_lib ? p.enjeux_tvb_tu_lib + "<br>" : ''}`

      new maplibregl.Popup()
          .setLngLat(e.lngLat)
          .setHTML(html_txt)
          .addTo(map);

      // avoir une variable accessible dans tout observable ! parfait pour faire des plot et table
      //mutable myId = e.features[0].properties.id;
      //myId = e.features[0].properties.id;
      //alert(myId);

      // test highlight NEW !
      // Mettre à jour le filtre de la couche de surbrillance pour montrer uniquement l'élément cliqué
      // nécessite une couche en plus
      //map.setFilter('selectedQ', ['==', 'id', myId]);

  });


  //------------------------------------------------------------------------------------------------
  // Change the cursor to a pointer when the it enters a feature in the 'symbols' layer.

  map.on('mouseenter', 'enjeux_renat_superposes', () => {
      map.getCanvas().style.cursor = 'pointer';
  });

  // Change it back to a pointer when it leaves.
  map.on('mouseleave', 'enjeux_renat_superposes', () => {
      map.getCanvas().style.cursor = '';
  });

}
{
      const PMTILES_URL_foncier_espaces_difficilement_mutables = `${LAYERS_PATH}/foncier_espaces_difficilement_mutables.pmtiles`;
  const foncier_espaces_difficilement_mutables = new pmtiles.PMTiles(PMTILES_URL_foncier_espaces_difficilement_mutables);
  protocol.add(foncier_espaces_difficilement_mutables);

      const foncier_espaces_difficilement_mutables_Source = {
                  type: 'vector',
                  url: `pmtiles://${PMTILES_URL_foncier_espaces_difficilement_mutables}`
  };
  const foncier_espaces_difficilement_mutables_Layer = {
                  id: "foncier_espaces_difficilement_mutables",
                  source: "foncier_espaces_difficilement_mutables_Source",
                  'source-layer': "foncier_espaces_difficilement_mutables",
                  type: "fill",
                  paint: {
                      'fill-color': '#917388',
                      'fill-opacity': 0.75
                  },
                  layout: {
                      // Make the layer visible by default.
                      visibility: 'visible' //ou none
                  },
  };
  map.addSource('foncier_espaces_difficilement_mutables_Source', foncier_espaces_difficilement_mutables_Source);
  map.addLayer(foncier_espaces_difficilement_mutables_Layer);
  console.log("espace difficilement mutable ajoutée !"); 
}
{
      const PMTILES_URL_sols_a_preserver = `${LAYERS_PATH}/sols_a_preserver.pmtiles`;
  const sols_a_preserver = new pmtiles.PMTiles(PMTILES_URL_sols_a_preserver);
  protocol.add(sols_a_preserver);

        const sols_a_preserver_Source = {
                  type: 'vector',
                  url: `pmtiles://${PMTILES_URL_sols_a_preserver}`
  };
  const sols_a_preserver_Layer = {
                  id: "sols_a_preserver",
                  source: "sols_a_preserver_Source",
                  'source-layer': "sols_a_preserver",
                  type: "fill",
                  paint: {
                      'fill-color': '#73a800',
                      'fill-opacity': 0.75
                  },
                  layout: {
                      // Make the layer visible by default.
                      visibility: 'visible' //ou none
                  },
  };
  map.addSource('sols_a_preserver_Source', sols_a_preserver_Source);
  map.addLayer(sols_a_preserver_Layer);
  console.log("sols àpréserver ajoutée !"); 
}
{
    const PMTILES_URL_B1 = `${LAYERS_PATH}/batiments.pmtiles`;
    const b1 = new pmtiles.PMTiles(PMTILES_URL_B1);
    protocol.add(b1);

    const batimentsSource = {
        type: 'vector',
        url: `pmtiles://${PMTILES_URL_B1}`
    };

  // Configuration du layer orthophoto
    const batimentsLayer = {
        id: 'batiments',
        source: 'batimentsSource',
        'source-layer': 'batiments_4326',
        type: "fill-extrusion", // Changer le type de couche en "fill-extrusion"
        paint: {
            "fill-extrusion-color": "lightgrey", // Couleur de remplissage extrudée
            "fill-extrusion-height": ["coalesce", ["get", "hauteur"], 0],
            "fill-extrusion-base": 0, // Hauteur de base de l'extrusion
            "fill-extrusion-opacity": 0.9
        },
        layout: {visibility: 'visible'},
    };

    // Ajoute la source et le layer une fois la carte chargée

    map.addSource('batimentsSource', batimentsSource);
    map.addLayer(batimentsLayer);
    console.log("bâtiments ajoutée !");
}
{
    const PMTILES_URL_E_RASTER = `${LAYERS_PATH}/epodes_rendered_image_cog.tif`
    const epodesSource = {
                            type: 'raster',
                            url: `cog://${PMTILES_URL_E_RASTER}`,
                            tileSize: 256
    };

  // Configuration du layer orthophoto
    const epodesLayer = {
                            id: 'cogLayerZPI',
                            source: 'epodesSource',
                            type: 'raster',
                            layout: {
                                // Make the layer visible by default.
                                visibility: 'visible' //ou none
                            },
                            paint: {
                                'raster-opacity': 0.5  // opacité entre 0 (transparent) et 1 (opaque)
                            }
    };

    // Ajoute la source et le layer une fois la carte chargée

    map.addSource('epodesSource', epodesSource);
    map.addLayer(epodesLayer);
    console.log("EPODES ajoutée !");
}
{
    const PMTILES_URL_I_RASTER = `${LAYERS_PATH}/imperviousness_seuil_50pct_rendered_image_cog.tif`
    const zisSource = {
                            type: 'raster',
                            url: `cog://${PMTILES_URL_I_RASTER}`,
                            tileSize: 256
    };

  // Configuration du layer orthophoto
    const zisLayer = {
                            id: 'cogLayerZIS',
                            source: 'zisSource',
                            type: 'raster',
                            layout: {
                                // Make the layer visible by default.
                                visibility: 'none' //ou none
                            },
                            paint: {
                                'raster-opacity': 1  // opacité entre 0 (transparent) et 1 (opaque)
                            }
    };

    // Ajoute la source et le layer une fois la carte chargée

    map.addSource('zisSource', zisSource);
    map.addLayer(zisLayer);
    console.log("ZIS ajoutée !");
}
// ce tableau sera un complément de myLayers pour les styles des rasters, là faut tout écrire à la mano
// ici on mettra tous les rasters
rasterColors = [
    {
        id:'cogLayerZPI', 
        paint: {
            // on se calque sur la structure des vecteurs pour rester dans l'esprit de maplibre
            'fill-color': [
                          'match',
                          ['get', ''], // '' pour ne pas avoir de nom de propriété, on fait du factice ici...
                          'Sols Très Perméables ou Perméables, Absence de contraintes', '#0b1704',   
                          'Sols Très Perméables ou Perméables, 1 contraintes moyenne', '#346a13',  
                          'Sols Très Perméables ou Perméables, 2 contraintes moyennes', '#93df18', 
                          'Sols Très Perméables ou Perméables, 3 contraintes moyennes', '#dbffa3',
                          'Sols Moyennement Perméables, 0 à 3 contraintes moyennes', '#dbffa3',
                          'Sols Moyennement Perméables à Très Perméables, <br>≥ 1 contrainte forte ou ≥ 4 contraintes moyennes', '#ffe48b',
                          'Sols Peu Perméables (peu importe le nombre de contraintes)', '#ffe48b',
                          '#cccccc'
                          ],
        }
    }
]
// les alias dans l'ordre des couches qui vont de l'arrière plan vers le premier plan
// mettez bien toutes les couches autres que les 3 fonds de plan
aliasLayerIds = {return {
    "enjeux_tvb_tu": "E1 - Trame verte et bleue",
    "enjeux_nev_cadre_de_vie": "E2 - Nature en ville, cadre de vie",
    "enjeux_inondation": "E3 - Inondation",
    "enjeux_ilots_de_chaleur": "E4 - Ilots de chaleur",
    "enjeux_renat_superposes": "Σ - Nombre d'enjeux superposés",
    "foncier_espaces_difficilement_mutables": "∅ Espaces difficilement mutables",
    "sols_a_preserver": "∅ Sols à préserver",
    "batiments": "🏢 Bâtiments",
    //"cogLayerZPI":"Zones Potentielles d'Infiltration (ZPI)",
    //"cogLayerZIS":"Masquer les zones < 50% d'imperméabilisation",
    "barentin": "⭔ Limites communales",
}}
// On déduit l'ordre des couches du tableau précédent
orderLayerIds = Object.keys(aliasLayerIds)

Le schéma ci-dessous présente la démarche pour aboutir à une présélection d’espaces à renaturer en priorité :

source : Cerema Pour la cartographie interactive, les couches suivantes correspondant au schéma ci-dessous sont représentées :

source : Cerema
  • 🟤Sols à préserver
  • 🦌E1 - TVB TU
  • 🌳️E2 - NeV, cadre de vie
  • 💧E3 - Inondation
  • 🌡️E4 - Îlots de chaleur
  • 📃Espaces difficilement mutables


Il est proposé de considérer les zones suivantes comme des sols à préserver :

  • Les sols présentant une capacité maximale à fonctionner issue de la méthode “pleine terre”
  • Les réservoirs de biodiversité de la trame verte et bleue


L’agrégation des zones suivantes des conclusions de la phase 1 de l’étude sont proposées :

  • Trame verte et bleue : continuité à restaurer, continuité à renforcer.
  • Trame urbaine : continuité à restaurer, continuité à renforcer.


La phase 2 de l’étude a croisé plusieurs approches, les zones ayant au moins un de ces indicateurs défavorables sont proposées :

  • Les zones comprises dans des périmètres U ou AU du PLU présentant moins de 20% de végétation saine et ou active, calcul effectué avec une `moyenne mobile` dans un cercle de rayon 150 mètres (hors parcelles agricole).

  • Les zones comprises dans des périmètres U ou AU du PLU à plus de 10 minutes à pied d’un parc ou jardin public, d’une entrée forêt ou de la voie verte (méthode isochrones).

  • Les espaces publics à améliorer qualitativement (espaces verts, parcs et jardins, berges de l’austreberthe)


L’agrégation des emprises suivantes est proposée :

  • Les zones réglementées du Plan de Prévention des Risques Inondations (PPRI) :
    • Ruissellement
    • Débordement
    • Remontée de nappe
  • Zones tampon autour des axes de ruissellement identifiés par le SMBVAS
  • 1ère modélisation du projet RESIST (pluie intense)


LCZ = Local Climate Zone, les typologie retenues sont :

  • très forte sensibilité : LCZ 1 / LCZ 2
  • forte sensibilité : LCZ 3
  • sensibilité moyenne : LCZ 4 / LCZ 5
  • sensibilité faible : LCZ 6
  • sensibilité variable : LCZ 7 / LCZ8 / LCZ E
Carte interactive nationale des LCZ


Les espaces difficilement mutables proposés sont les parcelles ne présentant aucune des caractéristiques suivantes :

  • Propriétaire public, exploitant de réseau ou établissement public foncier
  • Vacance résidentielle > 5 ans
  • Friche industrielle
  • Monopropriété privé (avec condition parcelle < 1500m² pour l’habitat individuel)
  • Dernière mutation > 3 ans
  • Espace non bâti ou ayant un coefficient d’occupation du sol < 0.2
  • Contraintes à la construction (PPRI, sites et sols pollués, cavités souterraines)
  • Espace non cadastré
<!-- console.log(LAYERS_PATH); -->
html`
    <div>
        <h4>Légende</h4>
        <div style="font-size:14px">

            <div id="legend_enjeux_tvb_tu">
                <span class="badge" style="background:green;opacity:1"> </span> E1 - Zones à enjeux pour renforcer ou restorer les continuités écologiques (trames verte et bleue et urbaine)
                d'inondation<br>
            </div>
            <div id="legend_enjeux_nev_cadre_de_vie">
                <span class="badge" style="background:purple;opacity:1"> </span> E2 - Zones à enjeux pour améliorer le cadre de vie et développer la nature en ville<br>
            </div>
            <div id="legend_enjeux_inondation">
                <span class="badge" style="background:blue;opacity:1"> </span> E3 - Zones à enjeux pour diminuer les risques d'inondation<br>
            </div>
            <div id="legend_enjeux_ilots_de_chaleur">
                <span class="badge" style="background:red;opacity:1"> </span> E4 - Zones à enjeux pour diminuer les effets des îlots de chaleur<br>
            </div>
            <div id="legend_enjeux_renat_superposes">
                <strong>Nombre d'enjeux superposés</strong><br>
                <em>Guide lecture : une couleur foncée traduit une superposition d'enjeux</em><br>
                <span class="badge" style="background:#fee5d9;opacity:1"> </span> 1 enjeux<br>
                <span class="badge" style="background:#fcae91;opacity:1"> </span> 2 enjeux<br>
                <span class="badge" style="background:#d7301f;opacity:1"> </span> 3 enjeux<br>
                <span class="badge" style="background:#4d0000;opacity:1"> </span> 4 enjeux<br>

            </div>

            <strong>Couches de masques</strong><br>
            <div id="legend_foncier_espaces_difficilement_mutables">
                <span class="badge" style="background:#22172c;opacity:1"> </span> Espaces difficilement mutables<br>
            </div>
            <div id="legend_sols_a_preserver">
                <span class="badge" style="background:#2b2205;opacity:1"> </span> Sols à préserver<br>
            </div>

        </div>
    </div>
`

©CEREMA