I am trying to map small surface water bodies in a desert. My issue is: water pixels don't cover full extent water bodies (see screenshot attached).

What threshold values would I need so that water bodies are mapped according to their shapes/full contours/full extent? Is it possible to play with thresholds with a slider or using some other method such as a histogram?

enter image description here

The code can be found here.

More about the Gond methodology (taken from Herndon et.al. 2020) here:

Gond and colleagues developed a simple decision tree method using the VEGETATION instrument for identifying water bodies in the Sahel. This method was developed specifically to address the wide range of water body types and the variation in the surrounding landscape associated with seasonal changes in the region. First, the difference between NDVI and the normalized difference moisture index (NDMI) is calculated and the average is computed using a moving window of 45 pixels-squared. Next, the difference between this average and the original difference is calculated and pixels with a value greater than 0.08 are kept as potential water bodies. Then, a moving average of 45 pixels-squared is calculated for SWIR1 and the difference between the average and the original SWIR1 band is calculated. Pixels with a value of 0.05 or greater are kept as potential water bodies. Finally, the two outputs are combined using an “AND” function, and any pixels that satisfy both are classified as water. One potential source of error identified in the original paper is confusion of clouds for water; the authors suggest using a separate cloud mask to address this issue. Additionally, they suggest that this method may not work on water bodies that are large enough to impact the regional average of the moving window average. The Gond simple decision tree does not perform well in areas with dense or moist vegetation because the contrast with water is diminished. This method was developed explicitly for dryland surface water detection.


1 Answer 1


I don't really have a complete answer for you, but a couple of points that might help you.

First, you're invoking reduceNeighborhood() with a 45 pixel square kernel. The size of your pixels depends on what scale you happen to be at. That means that when you zoom in or out, your results will change. I assume the algorithm expects a scale of 30 m. So, to verify your result, you either have to stay at zoom level 12, where the scale is about right, or you reproject your image to 30 m: image.reproject('EPSG:4326', null, 30).

It takes some time to display your results on the map, which makes it a really slow process to find the optimal thresholds for your mask. To speed this up, I would export your input for the mask as an asset. Once it's exported, using that asset to create your water mask would be really, really fast. At the same time, it would solve your scale problem.

  image: ndvi_ndwi_mean45_diff.rename('ndvi_ndwi_mean45_diff')
  description: 'water_mask_input',
  scale: 30,
  maxPixels: 1e13

After the export (which completed in 8 minutes for me):

var water_mask_input = ee.Image('users/yourUsername/water_mask_input')
var swir1_mean45_diff = water_mask_input.select('swir1_mean45_diff')
var ndvi_ndwi_water = water_mask_input.select('swir1_mean45_diff')
var water_mask = swir1_mean45_diff.gte(-0.05)

Map.addLayer(water_mask, {}, 'FINAL')

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.