I'm trying to use Google's Dynamic World classified rasters to get a land/water map for another tool I've built. I'm having trouble doing so. So far, I have essentially copied this code (in Python syntax) into a notebook, which is working well. For completeness.. It looks like this:

import ee
import geemap

Map = geemap.Map()

COL_FILTER = ee.Filter.And(
    ee.Filter.bounds(ee.Geometry.Point(-76.409826, 36.898152)),
    ee.Filter.date('2021-04-02', '2022-04-03'))
dwCol = ee.ImageCollection("GOOGLE/DYNAMICWORLD/V1").filter(COL_FILTER)
dwImage = ee.Image(dwCol.first())
    'water', 'trees', 'grass', 'flooded_vegetation', 'crops',
    'shrub_and_scrub', 'built', 'bare', 'snow_and_ice'];
    '#419BDF', '#397D49', '#88B053', '#7A87C6',
    '#E49635', '#DFC35A', '#C4281B', '#A59B8F',
dwRgb = dwImage.select('label').visualize(min=0, max=8, palette=VIS_PALETTE).divide(255)
top1Prob = dwImage.select(CLASS_NAMES).reduce(ee.Reducer.max());
top1ProbHillshade = ee.Terrain.hillshade(top1Prob.multiply(100)).divide(255);
dwRgbHillshade = dwRgb.multiply(top1ProbHillshade);
Map.setCenter(-76.409826, 36.898152, 12);
Map.addLayer(dwRgbHillshade, {min: 0, max: 0.65}, 'Dynamic World')

which creates a map that looks like this: enter image description here

I can tell visually that this is correct, where the water is blue, land is something else. The problem is when I try to get land/water maps out of this. I've been struggling to understand GEE's APIs in a way that can do this. I was looking at reducer, but it doesn't have much documentation on how I can reduce the bands into a single classification. Right now the shape is three dimensional, where the third dimension, the band (axis=2), all have values. At least.. it is for dwImage, which is what I was hoping to reduce on. I could not find any examples that created a custom reducing function to show how to do this in GEE.

I got desperate and tried writing this to a raster file which worked. Using the rectangular region I drew on the map above..

feature = Map.draw_last_feature
roi = feature.geometry()
clipped = dwImage.clip(roi).unmask()
out_dir = os.path.join(os.path.expanduser('~'), 'Downloads')
filename = os.path.join(out_dir, 'dwclasses.tif')
clipped = dwImage.clip(roi).unmask()
    clipped, filename=filename, scale=20, region=roi, file_per_band=False
# now to process it...
dataset = gdal.Open(filename, gdal.GA_ReadOnly)
bands = [
    dataset.GetRasterBand(k + 1).ReadAsArray() for k in range(dataset.RasterCount)
stacked = np.dstack(bands)[:,:,:9] # -1 to remove alpha band
band_with_highest = np.amax(stacked, axis=2)
water_land = band_with_highest == stacked[:, :, 1] # where the highest probability equalled water, set as True, else it is False and is land
mask = np.where(np.amax(stacked, axis=2) == np.zeros(stacked.shape[:2]), np.nan, 1)
fig, ax = plt.subplots(figsize=(15,15))
ax.imshow(water_land* mask)

Which outputs... (note, its inverted vertically) enter image description here but this is visually wrong too... I tried outputting the dwRgb and top1Prob rasters out but they weren't too helpful from what I can tell. The closest I got was by thresholding the water probability like..

fig, ax = plt.subplots(figsize=(15,15))
ax.imshow(stacked[:,:,1] > .05 * mask)

While that looks fine, its an unreliable cheat...

2 Answers 2


When working with a temporal stack of Dynamic World images, it is better to perform your analysis on the probability bands, and only convert to a categorical "label" as a last step (see top_probability in the following code example).

(By contrast, aggregating the categorical "label" band of individual images can produce noisy results if several classes have similar, high probabilities.)

# Copyright 2022 Google LLC.
# SPDX-License-Identifier: Apache-2.0

dw = ee.ImageCollection('GOOGLE/DYNAMICWORLD/V1')

probability_bands = [
  'water', 'trees', 'grass', 'flooded_vegetation', 'crops',
  'shrub_and_scrub', 'built', 'bare', 'snow_and_ice',
palette = [
  '#419BDF', '#397D49', '#88B053', '#7A87C6', '#E49635', 
  '#DFC35A', '#C4281B', '#A59B8F', '#B39FE1'

start_date = '2019-04-01'
end_date = '2019-07-01'

# Filter the Dynamic World image collection by time
dw_time_interval = dw.filter(ee.Filter.date(start_date, end_date))

# Select probability bands 
dw_time_series = dw_time_interval.select(probability_bands)

# Create a multi-band image summarizing probability 
# for each band across the time-period
mean_probability = dw_time_series.reduce(ee.Reducer.mean())

# Create a single band image containing the class with the top probability
top_probability = mean_probability.toArray().arrayArgmax().arrayGet(0).rename('label')

example Dynamic World output

Link to full code example: https://colab.research.google.com/gist/tylere/40aa0ea2f67653959dbef6b05a11b7c9/dynamic-world-top-probability.ipynb#scrollTo=rmrPAJoYbWZr

  • Amazingly helpful, thank you so much! If you've been involved on the DW project... let me take the opportunity to say THANK YOU. This dataset is allowing my small climate tech startup to quickly understand wave energy reliably to help design living shorelines. Cheers!
    – Nick Brady
    Commented Oct 12, 2022 at 19:37

Found in the documentation, for Dynamic World, exactly how to do this. The select('label') for the RGB image should have been a clue.

documenation: https://developers.google.com/earth-engine/tutorials/community/introduction-to-dynamic-world-pt-2

Basically, you select the label, and apparently each pixel is already labeled, as you just need to check where the label is equal to the class. For water in my case, this looks like this.

classification = dwImage.select('label')
dwComposite = classification.reduce(ee.Reducer.mode());
water = dwComposite.eq(0);

and its as simple as that.

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.