Land-use mapping with Sentinel 1 & 2

Yeah! mapping with a 10m spatial resolution.

Both new Sentinel-1 and Sentinel-2 satellites take measurements of the earth with a very high spatial and good temporal resolution. These data are very useful for mapping landuse and landuse change. This tutorial show how simple algorithms can be used to identify areas with water, dense vegetation, settlements and rice paddies.

1. Import the Sentinel-1 and Sentinel-2 ImageCollection.

sentinel12

2. Define the geographic and temporal domain.

// Define period
var startdate = ee.Date.fromYMD(2014,1,1);
var enddate = ee.Date.fromYMD(2016,12,1);

// Define geograpic domain
var Ca = ee.FeatureCollection('ft:1T7GmJ0tJCu4QwclY5qiG9EGBFgNhhNjttq8F7gQm');
Map.centerObject(Ca,8);

3. Filter the data.

// filter s2 data</pre>
var Sentinel2 = s2.filterBounds(Ca)
.filterDate(startdate, enddate)
.filterBounds(Ca);

// filter s1 data
var Sentinel1 =  ee.ImageCollection('COPERNICUS/S1_GRD')
.filterBounds(Ca)
.filterDate(startdate, enddate)
.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV'))
.select('VV');

4. remove the clouds from Sentinel-2

// cloud function to remove clouds
var cloudfunction_ST2 = function(image){
  //use add the cloud likelihood band to the image
  var quality = image.select("QA60").unmask();
  //get pixels above the threshold
  var cloud01 = quality.gt(0);
  //create a mask from high likelihood pixels
  var cloudmask = image.mask().and(cloud01.not());
  //mask those pixels from the image
  return image.updateMask(cloudmask);
};

// remove the clouds
var ST2_nocloud = Sentinel2.map(cloudfunction_ST2);

5. Calculate the NDBI, NDVI and NDWI from the median of Sentinel-2

// take the median
var st2median = ST2_nocloud.median();

// the normalized difference bare index
var ndbi = st2median.normalizedDifference(['B12', 'B8']);

// the normalized difference vegetation index
var ndvi = st2median.normalizedDifference(['B8', 'B4']);

// the normalize difference water index
var ndwi = st2median.normalizedDifference(['B3', 'B8']);

6. Set the threshold for the indices.

// define thresholds
var bareThreshold = -0.32
var vegetationThreshold = 0.65
var waterThreshold = 0.2

7. Add the different layers to the canvas.

// show the urban area
var ndbi_th = ndbi.gt(bareThreshold)
var myndbi = ndbi_th.updateMask(ndbi_th).clip(Ca)
var ndbi_viz = {palette:"111101"};
Map.addLayer(myndbi, ndbi_viz, 'Urban');

// show the water areas
var ndwi_th = ndwi.gt(waterThreshold)
var myndwi = ndwi_th.updateMask(ndwi_th).clip(Ca)
var ndwi_viz = {palette:"24069b"};
Map.addLayer(myndwi, ndwi_viz, 'Water');

// show the forests
var ndvi_th = ndvi.gt(vegetationThreshold)
var myndvi = ndvi_th.updateMask(ndvi_th).clip(Ca)
var ndvi_viz = {palette:"006b0c"};
Map.addLayer(myndvi, ndvi_viz, 'Vegetation');

8. Compare the wet from the dry conditions using a reducer function on the sentinel-1 images.

// create a map of the wet and dry conditions from sentinel-1
var wet = Sentinel1.reduce(ee.Reducer.percentile([10]))
var dry = Sentinel1.reduce(ee.Reducer.percentile([90]))

9. Identify the rice paddies which are inundated in the wet season, but dry in the dry season.

// calculate the difference between wet and dry conditions
var paddies = wet.subtract(dry)

10. Mountains can be identified as rice paddies, so we remove all slopes greater than 2 degrees.

// remove the mountains from the data
var hydrosheds = ee.Image('WWF/HydroSHEDS/03VFDEM');
var terrain = ee.Algorithms.Terrain(hydrosheds);
var slope = terrain.select('slope');

// remove all slopes greater then 2 degrees
paddies = paddies.updateMask(slope.lt(2));

11. Also add the paddies to the map based on the threshold of -8.

// set the paddy threshold
var paddies_th = -8;

// select areas smaller than the threshold
var paddies_th = paddies.lt(paddies_th);

// mask the areas that are not rice paddies
var mypaddies = paddies_th.updateMask(paddies_th).clip(Ca)

var paddies_viz = {palette:"c2c64d"};
Map.addLayer(mypaddies, paddies_viz, 'Rice');

Find the full script here.

11 thoughts on “Land-use mapping with Sentinel 1 & 2”

  1. Hi. Loving this procedure. However I am getting the following error at step 7:

    Urban: Layer error: Feature, argument ‘geometry’: Invalid type. Expected: Geometry. Actual: Feature.
    Water: Layer error: Feature, argument ‘geometry’: Invalid type. Expected: Geometry. Actual: Feature.
    Vegetation: Layer error: Feature, argument ‘geometry’: Invalid type. Expected: Geometry. Actual: Feature.

    What could be the issue?

    Like

      1. Thanks for the speedy response! I am using the exact same script that you have here with some minor modifications at the names, and coordinates. See script below:

        //Import the Sentinel-1 and Sentinel-2 ImageCollection.
        var s2 = ee.ImageCollection(“COPERNICUS/S2”),
        s1 = ee.ImageCollection(“COPERNICUS/S1_GRD”);

        ee.ImageCollection(‘COPERNICUS/S2’);
        ee.ImageCollection(‘COPERNICUS/S1’);

        //Define the geographic and temporal domain

        //Define period

        var startdate = ee.Date.fromYMD(2016,1,1);
        var enddate = ee.Date.fromYMD(2016,12,1);

        // Define geographic domain.

        var rectangle = ee.Geometry.Rectangle(37.99,-0.403, 38.44,-1.081 );
        var Mwi = ee.Feature(rectangle);
        Map.centerObject(Mwi, 12);

        // filter s2 data
        var Sentinel2 = s2.filterBounds(Mwi)
        .filterDate(startdate, enddate)
        .filterBounds(Mwi);

        //// filter s1 data
        var Sentinel1 = ee.ImageCollection(‘COPERNICUS/S1_GRD’)
        .filterBounds(Mwi)
        .filterDate(startdate, enddate)
        .filter(ee.Filter.listContains(‘transmitterReceiverPolarisation’, ‘VV’))
        .select(‘VV’);

        // cloud function to remove clouds
        var cloudfunction_ST2 = function(image){
        //use add the cloud likelihood band to the image
        var quality = image.select(“QA60″).unmask();
        //get pixels above the threshold
        var cloud01 = quality.gt(0);
        //create a mask from high likelihood pixels
        var cloudmask = image.mask().and(cloud01.not());
        //mask those pixels from the image
        return image.updateMask(cloudmask);
        };

        // remove the clouds
        var ST2_nocloud = Sentinel2.map(cloudfunction_ST2);

        // take the median
        var st2median = ST2_nocloud.median();

        //Calculate the NDBI, NDVI and NDWI from the median of Sentinel-2

        // the normalized difference bare index
        var ndbi = st2median.normalizedDifference([‘B12’, ‘B8’]);

        // the normalized difference vegetation index
        var ndvi = st2median.normalizedDifference([‘B8’, ‘B4’]);

        // the normalize difference water index
        var ndwi = st2median.normalizedDifference([‘B3’, ‘B8’]);

        // Set the threshold for the indices
        var bareThreshold = -0.32;
        var vegetationThreshold = 0.65;
        var waterThreshold = 0.2;

        //Add the different layers to the canvas
        Map.addLayer(Mwi);

        // show the urban area
        var ndbi_th = ndbi.gt(bareThreshold);
        var myndbi = ndbi_th.updateMask(ndbi_th).clip(Mwi);
        var ndbi_viz = {palette:”111101″};
        Map.addLayer(myndbi, ndbi_viz, ‘Urban’);

        // show the water areas
        var ndwi_th = ndwi.gt(waterThreshold);
        var myndwi = ndwi_th.updateMask(ndwi_th).clip(Mwi);
        var ndwi_viz = {palette:”24069b”};
        Map.addLayer(myndwi, ndwi_viz, ‘Water’);

        // show the forests
        var ndvi_th = ndvi.gt(vegetationThreshold);
        var myndvi = ndvi_th.updateMask(ndvi_th).clip(Mwi);
        var ndvi_viz = {palette:”006b0c”};
        Map.addLayer(myndvi, ndvi_viz,’Vegetation’);

        Like

    1. There is a get link button in the GEE. This gives you an unique link for sharing your code. Can you share that one with me?

      It’s always hard to come up with an exact value for a threshold. I determined them based on trial and error.

      Like

    1. Also, would you mind troubleshooting another script that I am trying to run to classify land cover using Sentinel 2? I used training data from a Worldview 2 image of the same site and saved them into a csv format.

      https://code.earthengine.google.com/ddcb13c7b670431a319a7d5d71f544c9

      Brought in the training data as a fusion table to classify sentinel images. but i get the following error:

      ‘classification: Layer error: No data was found in classifier training input’

      where could I be going wrong?

      Regards,

      Pamela

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s