Process API and Leaflets tools to draw polygons

Hi Team!
I am developing a simple web application that I would be able to extract NDVI values for my area of interest. My area of interest will have to be input / entered by either drawing a polygon or importing a shapefile. I am looking for ways to possibly do so. I will appreciate any assistance. Below is my code.

Thanks!



#map{
width: 100vw;
height: 100vh;
}


    <link rel="stylesheet" href="https://unpkg.com/@geoman-io/leaflet-geoman-free@latest/dist/leaflet-geoman.css" />
    <script src="https://unpkg.com/@geoman-io/leaflet-geoman-free@latest/dist/leaflet-geoman.min.js"></script>
    
</head>
</body>

<div id="map"></div>

<script>
  /*************************

  !!! EVERYTHING ABOVE THIS IS AN ASSUMPTION !!!
  Because Markdown treats html tags as formatting if they are not encapsed with triple ` (backtick)).
  Also, I added just the <link ...> and <script src="..." /> tags for Leaflet.
  
  # Sentinel Hub OAuth2 + Process API Leaflet

  How to use:

    1) enter sentinelHubNDVI client ID and secret
      (go to SH Dashboard -> User settings -> OAuth clients -> "+")

    2) open this file in browser

  *************************/
  const CLIENT_ID = "369d8442-020e-40ee-Hidden";
  const CLIENT_SECRET = "L+m5(5/)%U/6Hidden";

  const fromDate = "2020-07-01T00:00:00.000Z";
  const toDate = "2021-05-01T00:00:00.000Z";
  const dataset = "S2L1C";
  const evalscript = `//VERSION=3
    //This script was converted from v1 to v3 using the converter API

    //NDVI EVALSCRIPT

    if (dataMask == 0) return [0,0,0,0];

    //ndvi
    var val = (B08-B04)/(B08+B04);

    if (val<-1.1) return [0,0,0,1];
    else if (val<-0.2) return [0.75,0.75,0.75,1];
    else if (val<-0.1) return [0.86,0.86,0.86,1];
    else if (val<0) return [1,1,0.88,1];
    else if (val<0.025) return [1,0.98,0.8,1];
    else if (val<0.05) return [0.93,0.91,0.71,1];
    else if (val<0.075) return [0.87,0.85,0.61,1];
    else if (val<0.1) return [0.8,0.78,0.51,1];
    else if (val<0.125) return [0.74,0.72,0.42,1];
    else if (val<0.15) return [0.69,0.76,0.38,1];
    else if (val<0.175) return [0.64,0.8,0.35,1];
    else if (val<0.2) return [0.57,0.75,0.32,1];
    else if (val<0.25) return [0.5,0.7,0.28,1];
    else if (val<0.3) return [0.44,0.64,0.25,1];
    else if (val<0.35) return [0.38,0.59,0.21,1];
    else if (val<0.4) return [0.31,0.54,0.18,1];
    else if (val<0.45) return [0.25,0.49,0.14,1];
    else if (val<0.5) return [0.19,0.43,0.11,1];
    else if (val<0.55) return [0.13,0.38,0.07,1];
    else if (val<0.6) return [0.06,0.33,0.04,1];
    else return [0,0.27,0,1];
  `;

  const evalscript1 = `//VERSION=3
    //TRUE COLOR

    let minVal = 0.0;
    let maxVal = 0.4;

    let viz = new HighlightCompressVisualizer(minVal, maxVal);

    function evaluatePixel(samples) {
      let val = [samples.B04, samples.B03, samples.B02];
      val = viz.processList(val);
      val.push(samples.dataMask);
      return val;
    }

    function setup() {
      return {
        input: [{
          bands: [
            "B02",
            "B03",
            "B04",
            "dataMask"
          ]
        }],
        output: {
          bands: 4
        }
      }
    }
  `;

  // Promise which will fetch Sentinel Hub authentication token:
  const authTokenPromise = fetch(
    "https://services.sentinel-hub.com/oauth/token",
    {
      method: "post",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: `grant_type=client_credentials&client_id=${encodeURIComponent(
        CLIENT_ID
      )}&client_secret=${encodeURIComponent(CLIENT_SECRET)}`,
    }
  )
  .then((response) => response.json())
  .then((auth) => auth["access_token"]);

  // We need to extend Leaflet's GridLayer to add support for loading images through
  // Sentinel Hub Process API:
  L.GridLayer.SHProcessLayer = L.GridLayer.extend({
    createTile: function (coords, done) {
      const tile = L.DomUtil.create("img", "leaflet-tile");
      const tileSize = this.options.tileSize;
      tile.width = tileSize;
      tile.height = tileSize;
      const nwPoint = coords.multiplyBy(tileSize);
      const sePoint = nwPoint.add([tileSize, tileSize]);
      const nw = L.CRS.EPSG4326.project(
        this._map.unproject(nwPoint, coords.z)
      );
      const se = L.CRS.EPSG4326.project(
        this._map.unproject(sePoint, coords.z)
      );

      authTokenPromise.then((authToken) => {
        // Construct Process API request payload:
        //   https://docs.sentinel-hub.com/api/latest/reference/#tag/process
        const payload = {
          input: {
            bounds: {
              bbox: [nw.x, nw.y, se.x, se.y], // a tile's bounding box
              geometry: { // remove to disable clipping
                type: "Polygon",
                coordinates: [
                  [
                    [
                    -0.373367, 
                    35.742975
                    ],
                    [
                    -0.373412, 
                    35.742907
                    ],
                    [
                    -0.373316, 
                    35.742966
                    ],
                    [
                    -0.373404, 
                    35.742977
                    ],
                    [
                    -0.383367, 
                    35.752975
                    ]
                  ]
                ]
              },
              properties: {
                crs: "http://www.opengis.net/def/crs/EPSG/0/4326",
              },
            },
            data: [
              {
                dataFilter: {
                  timeRange: {
                    from: fromDate,
                    to: toDate,
                  },
                  maxCloudCoverage: 10,
                  mosaickingOrder: "mostRecent",    
                },
                processing: {},
                type: dataset,
              },
            ],
          },
          output: {
            width: 512,
            height: 512,
            responses: [
              {
                identifier: "default",
                format: {
                  type: "image/png",
                },
              },
            ],
          },
          evalscript: this.options.evalscript, // CHANGED: using the evalscript that was passed 
        };

        // Fetch the image:
        fetch("https://services.sentinel-hub.com/api/v1/process", {
          method: "post",
          headers: {
            Authorization: "Bearer " + authToken,
            "Content-Type": "application/json",
            Accept: "*/*",
          },
          body: JSON.stringify(payload),
        })
        .then((response) => response.blob())
        .then((blob) => {
            const objectURL = URL.createObjectURL(blob);
            tile.onload = () => {
                URL.revokeObjectURL(objectURL);
                done(null, tile);
            };
            tile.src = objectURL;
        })
        .catch((err) => done(err, null));
      });
      return tile;
    },
  });

  L.gridLayer.shProcessLayer = function (opts) {
    return new L.GridLayer.SHProcessLayer(opts);
  };

  // CHANGED: passed the object with the correct evalscript to the L.gridLayer.shProcessLayer
  const sentinelHubNDVI = L.gridLayer.shProcessLayer({evalscript: evalscript});
  const sentinelHubTrueColor = L.gridLayer.shProcessLayer({evalscript: evalscript1});
    
  // OpenStreetMap layer:
  let osm = L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
  });
//   http://{s}.tile.osm.org/{z}/{x}/{y}.png
  
  // configure Leaflet:
  let baseMaps = {
    OpenStreetMap: osm,
  };
  let overlayMaps = {
    "NDVI": sentinelHubNDVI,
    "True": sentinelHubTrueColor,
  };

  let map = L.map("map", {
    center: [-0.373467, 35.742575], // lat/lng in EPSG:4326
    zoom: 15,
    layers: [osm, sentinelHubNDVI],
  });
  L.control.layers(baseMaps, overlayMaps).addTo(map);
  L.marker([51.50915, -0.096112], { pmIgnore: false }).addTo(map);
</script>
</body>
</html>
1 Like

Hello!

Since you’re using Leaflet, Leaflet Draw would be a great choice for drawing polygons.

They have quite a bit of documentation available on this page: Leaflet Draw Documentation

This tutorial does a great job at explaining how you get up and running with it in 8 minutes.

When it comes to importing shapefiles, this is a good choice.

Let me know if you need any further assistance!

// Pontus

1 Like

Hey @pontus.berg and thanks for your great assistance. I did finally manage to add the drawing tools, however, it has been struggle trying to add the shapefile plugin, and haven’t been successful. Could there be something that would guide me step by step? And another thing is that I would like once someone draws a polygon, NDVI of the area under study is generated and shown as a graph/chart. Is there a way I could also implement that? I have added the code below for the listening events for drawing the polygon, but I haven’t gotten it to work.
I will appreciate your assistance!

<script>

  

  const CLIENT_ID = "369d8442-020e-40ee-Hidden";

  const CLIENT_SECRET = "L+m5(5/)%U/6Hidden";

  const fromDate = "2020-07-01T00:00:00.000Z";

  const toDate = "2021-05-01T00:00:00.000Z";

  const dataset = "S2L1C";

  const evalscript = `//VERSION=3

    //This script was converted from v1 to v3 using the converter API

    //NDVI EVALSCRIPT

    if (dataMask == 0) return [0,0,0,0];

    //ndvi

    var val = (B08-B04)/(B08+B04);

    if (val<-1.1) return [0,0,0,1];

    else if (val<-0.2) return [0.75,0.75,0.75,1];

    else if (val<-0.1) return [0.86,0.86,0.86,1];

    else if (val<0) return [1,1,0.88,1];

    else if (val<0.025) return [1,0.98,0.8,1];

    else if (val<0.05) return [0.93,0.91,0.71,1];

    else if (val<0.075) return [0.87,0.85,0.61,1];

    else if (val<0.1) return [0.8,0.78,0.51,1];

    else if (val<0.125) return [0.74,0.72,0.42,1];

    else if (val<0.15) return [0.69,0.76,0.38,1];

    else if (val<0.175) return [0.64,0.8,0.35,1];

    else if (val<0.2) return [0.57,0.75,0.32,1];

    else if (val<0.25) return [0.5,0.7,0.28,1];

    else if (val<0.3) return [0.44,0.64,0.25,1];

    else if (val<0.35) return [0.38,0.59,0.21,1];

    else if (val<0.4) return [0.31,0.54,0.18,1];

    else if (val<0.45) return [0.25,0.49,0.14,1];

    else if (val<0.5) return [0.19,0.43,0.11,1];

    else if (val<0.55) return [0.13,0.38,0.07,1];

    else if (val<0.6) return [0.06,0.33,0.04,1];

    else return [0,0.27,0,1];

  `;

  const evalscript1 = `//VERSION=3

    //TRUE COLOR

    let minVal = 0.0;

    let maxVal = 0.4;

    let viz = new HighlightCompressVisualizer(minVal, maxVal);

    function evaluatePixel(samples) {

      let val = [samples.B04, samples.B03, samples.B02];

      val = viz.processList(val);

      val.push(samples.dataMask);

      return val;

    }

    function setup() {

      return {

        input: [{

          bands: [

            "B02",

            "B03",

            "B04",

            "dataMask"

          ]

        }],

        output: {

          bands: 4

        }

      }

    }

  `;

  // Promise which will fetch Sentinel Hub authentication token:

  const authTokenPromise = fetch(

    "https://services.sentinel-hub.com/oauth/token",

    {

      method: "post",

      headers: { "Content-Type": "application/x-www-form-urlencoded" },

      body: `grant_type=client_credentials&client_id=${encodeURIComponent(

        CLIENT_ID

      )}&client_secret=${encodeURIComponent(CLIENT_SECRET)}`,

    }

  )

  .then((response) => response.json())

  .then((auth) => auth["access_token"]);

  // We need to extend Leaflet's GridLayer to add support for loading images through

  // Sentinel Hub Process API:

  L.GridLayer.SHProcessLayer = L.GridLayer.extend({

    createTile: function (coords, done) {

      const tile = L.DomUtil.create("img", "leaflet-tile");

      const tileSize = this.options.tileSize;

      tile.width = tileSize;

      tile.height = tileSize;

      const nwPoint = coords.multiplyBy(tileSize);

      const sePoint = nwPoint.add([tileSize, tileSize]);

      const nw = L.CRS.EPSG4326.project(

        this._map.unproject(nwPoint, coords.z)

      );

      const se = L.CRS.EPSG4326.project(

        this._map.unproject(sePoint, coords.z)

      );

      authTokenPromise.then((authToken) => {

        // Construct Process API request payload:

        //   https://docs.sentinel-hub.com/api/latest/reference/#tag/process

        const payload = {

          input: {

            bounds: {

              bbox: [nw.x, nw.y, se.x, se.y], // a tile's bounding box

              geometry: { // remove to disable clipping

                type: "Polygon",

                coordinates: [

                  [

                    [

                    -0.373367, 

                    35.742975

                    ],

                    [

                    -0.373412, 

                    35.742907

                    ],

                    [

                    -0.373316, 

                    35.742966

                    ],

                    [

                    -0.373404, 

                    35.742977

                    ],

                    [

                    -0.383367, 

                    35.752975

                    ]

                  ]

                ]

              },

              properties: {

                crs: "http://www.opengis.net/def/crs/EPSG/0/4326",

              },

            },

            data: [

              {

                dataFilter: {

                  timeRange: {

                    from: fromDate,

                    to: toDate,

                  },

                  maxCloudCoverage: 10,

                  mosaickingOrder: "mostRecent",    

                },

                processing: {},

                type: dataset,

              },

            ],

          },

          output: {

            width: 512,

            height: 512,

            responses: [

              {

                identifier: "default",

                format: {

                  type: "image/png",

                },

              },

            ],

          },

          evalscript: this.options.evalscript, // CHANGED: using the evalscript that was passed 

        };

        // Fetch the image:

        fetch("https://services.sentinel-hub.com/api/v1/process", {

          method: "post",

          headers: {

            Authorization: "Bearer " + authToken,

            "Content-Type": "application/json",

            Accept: "*/*",

          },

          body: JSON.stringify(payload),

        })

        .then((response) => response.blob())

        .then((blob) => {

            const objectURL = URL.createObjectURL(blob);

            tile.onload = () => {

                URL.revokeObjectURL(objectURL);

                done(null, tile);

            };

            tile.src = objectURL;

        })

        .catch((err) => done(err, null));

      });

      return tile;

    },

  });

  L.gridLayer.shProcessLayer = function (opts) {

    return new L.GridLayer.SHProcessLayer(opts);

  };

  // CHANGED: passed the object with the correct evalscript to the L.gridLayer.shProcessLayer

  const sentinelHubNDVI = L.gridLayer.shProcessLayer({evalscript: evalscript});

  const sentinelHubTrueColor = L.gridLayer.shProcessLayer({evalscript: evalscript1});

    

  // OpenStreetMap layer:

  let osm = L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {

    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'

  });

//   http://{s}.tile.osm.org/{z}/{x}/{y}.png

  

  // configure Leaflet:

  let baseMaps = {

    OpenStreetMap: osm,

  };

  let overlayMaps = {

    "NDVI": sentinelHubNDVI,

    "True": sentinelHubTrueColor,

  };

  let map = L.map("map", {

    center: [-0.373467, 35.742575], // lat/lng in EPSG:4326

    zoom: 15,

    layers: [osm, sentinelHubNDVI],

  });

  L.control.layers(baseMaps, overlayMaps).addTo(map);

  // add leaflet-geoman controls with some options to the map  

  map.pm.addControls({  

    position: 'topleft',  

    // drawPolygon: true,  

  }); 

  // listen to vertexes being added to currently drawn layer (called workingLayer)  

  map.on('pm:drawstart', ({ workingLayer }) => {  

  workingLayer.on('pm:create', e => {  

    console.log(e);  

    });

  });

  'use strict';

</script>

Hey again!

Take a look at this post. It has some more detailed information of how to upload shape files to leaflet. javascript - Display shapeFile on leaflet map using an uploaded file.zip - Stack Overflow

For creating charts you can use the histogram feature of our statistical API. We have documentation and examples available on this page .

I have added the code below for the listening events for drawing the polygon, but I haven’t gotten it to work.

Do you get some kind of error message here?

Best regards
Pontus