4

I have a code that classifies polygons based on their NDVI values (based on mean and std, classes into 3 ranges: high, medium and low).

The code suppose to do this process for each polygon, but I hace found that it calculates it for each polygon only if I draw the polygons manually, otherwise, if I use an uploaded shapefile it calculates the statistics for all the polygons and not only for one.

Here is an example:

This is when I draw the polygons manually:

enter image description here

and this is when I use my uploaded shapefile:

enter image description here

So as you can see, it seems like when I use my shapefile which is a FeatureCollection, it doesn't work. I have tried to change my FeatureCollection to polygon. using at the beginning:

var geometry=mytable.geometry()

But I still got the same results

This is my code:

Map.centerObject(geometry,13);

var ndviClassImg = geometry instanceof ee.Geometry.MultiPolygon
  ? ee.ImageCollection(
      geometry.coordinates().map(function (coordinates) {
        return getNdviClassImg(ee.Geometry.Polygon(coordinates));
      })
    ).mosaic()
  : getNdviClassImg(geometry);

Map.addLayer(ndviClassImg, {palette: ['FFF933', '3368FF', 'FF3368'], min: 1, max: 3},'mosaic');

function getNdviClassImg(geometry) {
  /**
   * Function to mask clouds using the Sentinel-2 QA band
   * @param {ee.Image} image Sentinel-2 image
   * @return {ee.Image} cloud masked Sentinel-2 image
   */
  function maskS2clouds(image) {
    var qa = image.select('QA60');

    // Bits 10 and 11 are clouds and cirrus, respectively.
    var cloudBitMask = 1 << 10;
    var cirrusBitMask = 1 << 11;

    // Both flags should be set to zero, indicating clear conditions.
    var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
        .and(qa.bitwiseAnd(cirrusBitMask).eq(0));

    return image.updateMask(mask).divide(10000)
    .copyProperties(image, ['system:time_start']);
  }

  // Map the function over one year of data and take the median.
  // Load Sentinel-2 TOA reflectance data.
  var dataset = ee.ImageCollection('COPERNICUS/S2')
                    .filterDate('2019-06-25','2019-07-06')
                    // Pre-filter to get less cloudy granules.
                    .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 100))
                    .select('B1','B2','B3','B4','B8','QA60')
                    .filterBounds(geometry)
                    .map(maskS2clouds);


  //var count=dataset.size();
  //print('number of images in dataset:',count);

  var clippedCol=dataset.map(function(im){ 
    return im.clip(geometry);
  });

  //function to calculate NDVI
  var addNDVI = function(image) {
    var ndvi = image.normalizedDifference(['B8', 'B4'])
    .rename('NDVI')
    .copyProperties(image,['system:time_start']);
    return image.addBands(ndvi);

  };

  //NDVI to the clipped image collection
  var withNDVI = clippedCol.map(addNDVI).select('NDVI');



  var listOfImages =(withNDVI.toList(withNDVI.size()));
  var MyImage=ee.Image(withNDVI.first());

  var tableWithStats = MyImage.reduceRegion({
    geometry: geometry,
    reducer: ee.Reducer.mean().combine({
    reducer2: ee.Reducer.stdDev(),
    sharedInputs: true
    }),
    scale: 20
  });



  //calculation
  var std2 = ee.Number(tableWithStats.get('NDVI_stdDev')).divide(4);
  var mean1 = ee.Number(tableWithStats.get("NDVI_mean"));

  // the classes borders
  var negBorder=mean1.subtract(std2);
  var posBorder=mean1.add(std2);

  //create the layers
  var imageNDVI=MyImage.select('NDVI');
  var gtPOS=MyImage.gt(posBorder).selfMask().rename('range');
  var ltNEG=MyImage.lt(negBorder).selfMask().rename('range');
  var betMEAN=MyImage.gt(negBorder).and(imageNDVI.lt(posBorder)).selfMask().rename('range');

  return ee.Image([betMEAN, ltNEG, gtPOS])
    .selfMask()  // Mask 0's
    .multiply(ee.Image([1, 2, 3])) // Assign values to the classes
    .reduce(ee.Reducer.firstNonNull()); // Pick first class
}

var visulaiztion={palette: ['00ff00', 'ff0000', '006622'], min: 1, max: 3};
//var toexport=ndviClassImg.visualize(visulaiztion).addBands(ndviClassImg);

Export.image.toDrive({
    image: ndviClassImg.toFloat(),
    description:'mosaic',
    scale: 20,
    crs: 'EPSG:4326',
    region: geometry,
  });


Unfortunately, I can't share the geometry, but it is supposed to be the same if you use any layer with a few polygons that you have uploaded and then draw it manually.

EDIT: Please find the code here:

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

I have uploaded two geometries: one shapefile and one manually drawn, you can see the differences

8
  • 1
    Can you share a dummy drawn featurecollection where the same error occurs? And share that with the code using a link?
    – Kuik
    Commented Jan 30, 2020 at 19:45
  • @Kulik I have just added the link
    – ReutKeller
    Commented Feb 2, 2020 at 15:27
  • It's not a shared asset
    – Kuik
    Commented Feb 4, 2020 at 3:56
  • @Kulik how can I share it?
    – ReutKeller
    Commented Feb 4, 2020 at 10:02
  • what about a quick google search? developers.google.com/earth-engine/asset_manager
    – Kuik
    Commented Feb 4, 2020 at 16:16

2 Answers 2

2
+50

When you use .geometry() on a FeatureCollection, you're getting back a Geometry object, not a Geometry.MultiPolygon object. Try these in your code at the beginning:

print(shapefile.geometry() instanceof ee.Geometry.MultiPolygon);
print(Manual_test instanceof ee.Geometry.MultiPolygon);

You'll get false and then true. shapefile.geometry() is an instanceof ee.Geometry.

You can then turn around and pass that to the MultiPolygon constructor which expects a list of coordinates:

var geometry=ee.Geometry.MultiPolygon(shapefile.geometry().coordinates());

Your Shapefile has now been successfully converted to a MultiPolygon and you get the mosaiced result that you're looking for.

I think more broadly, the issue you're running into is that when you have a Multipolygon you're mapping a function onto each distinct polygon and mosaicing the result. That's not what happens with a FeatureCollection - it's considering the geometries of the collection in aggregate and producing one result. If you consider how you're handling the distinct object types in your code:

var ndviClassImg = geometry instanceof ee.Geometry.MultiPolygon
  ? ee.ImageCollection(
      geometry.coordinates().map(function (coordinates) {
        return getNdviClassImg(ee.Geometry.Polygon(coordinates));
      })
    ).mosaic()
  : getNdviClassImg(geometry);

You can see where the mosaicing happens for the MultiPolygon.

2
  • 1
    I know that it's not common here but I want to thank you a lot for this great explaination
    – ReutKeller
    Commented Feb 13, 2020 at 7:50
  • 1
    You're welcome! I'm glad I could help
    – derivative
    Commented Feb 18, 2020 at 1:50
0

Your two geometries are slightly different. Look at the map:

// These polygon should have been identical
var polygonFromShapefile = ee.Geometry(shapefile.geometry().geometries().get(1))
var polygonFromManual = ee.Geometry(Manual_test.geometries().get(0))

var pointFromShapefile = ee.Geometry.Point(ee.List(polygonFromShapefile.coordinates().get(0)).get(1))
var pointFromManual = ee.Geometry.Point(ee.List(polygonFromManual.coordinates().get(0)).get(0))

Map.addLayer(pointFromShapefile, {color: 'green'}, 'pointFromShapefile')
Map.addLayer(pointFromManual, {color: 'red'}, 'pointFromManual')

Map.addLayer(polygonFromShapefile, {color: 'green'}, 'polygonFromShapefile')
Map.addLayer(polygonFromManual, {color: 'red'}, 'polygonFromManual')

Map.centerObject(pointFromManual, 24)

https://code.earthengine.google.com/6de209fceff71d8a8b3dcfdda4377923

1
  • but even if they are slightly different, it doesn't make any sense that some plots get only values that aare lower than the mean or only higher than the mean without have those 3 levels of colores, because the code suppose to calculte for each polygon its' mean and std, so just can't have one polygon with only 2 colores and only one pixel that will classify as mean.
    – ReutKeller
    Commented Feb 10, 2020 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.