Home  /  Blog  /  Post

Automate the wetland mapping

Mapping Wetlands from Space – A Step-by-Step Guide for GIS Users

No coding experience required

You know wetlands. You know GIS. You have probably used QGIS or ArcGIS to visualise satellite imagery, drawn polygons around floodplains, and stared at Landsat scenes trying to figure out which pixels are water and which are just muddy soil catching the sun at the wrong angle.

What if you could automate all of that – for any wetland on Earth, across 40 years of satellite data, and get a map showing not just where the wetland is but how it has changed – which areas are shrinking, which are recovering, which have appeared in the last five years?

That is what WetlandMapper does. This guide will take you from a fresh computer to a working wetland map, step by step.


What you will need


Part 1: Install Miniconda

Miniconda is a small programme that manages Python and all the tools WetlandMapper needs. Think of it like a clean room – it keeps everything organised and prevents conflicts with anything else on your computer.

Windows

  1. Go to https://docs.conda.io/en/latest/miniconda.html
  2. Click Miniconda3 Windows 64-bit and download the installer
  3. Run the .exe file. Accept all defaults. When asked about “Add Miniconda to PATH”, tick Yes
  4. Once installed, open the Start Menu and search for Anaconda Prompt. Open it. You will see a black window with (base) at the start of the line — this means Miniconda is working.

Mac

  1. Go to https://docs.conda.io/en/latest/miniconda.html
  2. Download Miniconda3 macOS Apple M1/M2 (for newer Macs) or macOS Intel (for older Macs)
  3. Open your Terminal (search for it in Spotlight with ⌘+Space)
  4. Run the downloaded .pkg installer
  5. Close and reopen Terminal. You should see (base) at the start of the line.

Linux

Open a terminal and run:

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh

Follow the prompts and accept the default location.


Part 2: Create a WetlandMapper Environment

A conda environment is like a separate workspace with its own set of tools. Creating one for WetlandMapper keeps it isolated from anything else on your computer.

Open your Anaconda Prompt (Windows) or Terminal (Mac/Linux) and type these commands one at a time, pressing Enter after each:

conda create -n wetlands python=3.11 -y

This creates a new environment called wetlands with Python 3.11. It will take a minute.

conda activate wetlands

Your prompt should now show (wetlands) instead of (base). You are now inside the wetlands workspace.

conda install -c conda-forge wetlandmapper jupyterlab -y

This installs WetlandMapper and JupyterLab (the notebook interface). It will take several minutes – this is normal.

pip install "wetlandmapper[gee]"

This adds the Google Earth Engine connection.

To verify the installation worked, type:

python -c "import wetlandmapper; print(wetlandmapper.__version__)"

You should see 1.0.5 (or similar) printed. If you see an error, check that you typed the commands exactly as shown.

Every time you open a new terminal, you need to run conda activate wetlands before using WetlandMapper. If you see ModuleNotFoundError, this is almost always because you forgot to activate the environment.


Part 3: Set Up Google Earth Engine

Google Earth Engine (GEE) is a cloud platform that gives you free access to the entire Landsat archive – every satellite image taken since 1972 – plus Sentinel-2, MODIS, and dozens of other datasets. WetlandMapper uses GEE to download the data it needs.

Step 1: Create a GEE account

  1. Go to https://earthengine.google.com
  2. Click Get Started
  3. Sign in with your Google account
  4. Click Register a Noncommercial/Research Project
  5. Choose Unpaid usageAcademia or Research
  6. Fill in the project details (your institution, use case – “wetland mapping research” is fine)
  7. Note your project ID – it will look like my-wetland-project or similar

Approval is usually instant but can take up to 24 hours.

Step 2: Authenticate on your computer

In your Anaconda Prompt / Terminal (with the wetlands environment active), run:

earthengine authenticate

This will open a browser window asking you to sign in with your Google account and grant permission. Click Allow. You will see a confirmation code – copy it and paste it back into the terminal.

You only need to do this once per computer.


Part 4: Launch JupyterLab

JupyterLab is a notebook interface – like a combination of a Word document and a calculator where you can write and run code cell by cell.

In your terminal, type:

jupyter lab

A browser window will open automatically. If it does not, copy the address starting with http://localhost:8888/... from the terminal and paste it into your browser.

You will see a file browser on the left. Click the + button (or File → New → Notebook) and select Python 3 when asked.


Part 5: Your First Wetland Map

A JupyterLab notebook is made of cells. You type code into a cell and press Shift+Enter to run it. Click on a cell to select it.

Cell 1: Check WetlandMapper is working

Click inside the first cell, type the following, and press Shift+Enter:

import wetlandmapper
print("WetlandMapper version:", wetlandmapper.__version__)
print("Ready to map wetlands!")

You should see the version number printed below the cell.

Cell 2: Connect to Google Earth Engine

import ee

# Replace with your GEE project ID
ee.Initialize(project='your-project-id-here')
print("Connected to Google Earth Engine")

Replace your-project-id-here with the project ID you noted in Part 3.

Cell 3: Define your wetland area

You can define your study area in two ways:

Option A – Draw a bounding box (simplest – use Google Maps to find your coordinates):

# Coordinates: West, South, East, North
# This example uses part of Chilika Lake, Odisha, India
aoi = {
    'type': 'Polygon',
    'coordinates': [[[85.05, 19.65],
                     [85.55, 19.65],
                     [85.55, 19.95],
                     [85.05, 19.95],
                     [85.05, 19.65]]]
}
print("Study area defined")

How to find your coordinates: Go to Google Maps, right-click on a corner of your study area, and click the coordinates at the top of the menu. Note the latitude (first number) and longitude (second number) for all four corners of a rectangle around your wetland.

Option B – Use a shapefile you already have:

import geopandas as gpd

# Load your shapefile (or .geojson, .gpkg — any format QGIS can open)
gdf = gpd.read_file('my_wetland_boundary.shp')

# Convert to the GeoJSON format WetlandMapper expects
aoi = gdf.dissolve().geometry.iloc[0].__geo_interface__
print("Shapefile loaded:", len(gdf), "features dissolved to 1 polygon")

Cell 4: Download MNDWI data

from wetlandmapper.gee import fetch_xee

print("Downloading data from Google Earth Engine...")
print("(This may take a few minutes depending on your area size)")

mndwi_lazy = fetch_xee(
    aoi,
    start='2000-01-01',
    end='2023-12-31',
    sensor='LandsatAll',     # uses all Landsat missions 1982-present
    index='MNDWI',
    scale=30,                # 30 metre pixels
    temporal_aggregation='annual',  # one image per year
)

# Rename coordinates
mndwi_lazy = mndwi_lazy.rename({'lat': 'y', 'lon': 'x'})
mndwi_lazy = mndwi_lazy.transpose('time', 'y', 'x')

# Download the actual data
mndwi = mndwi_lazy.compute()
print(f"Downloaded! {mndwi.time.size} annual images, {mndwi.sizes['y']}×{mndwi.sizes['x']} pixels")

Cell 5: Classify wetland dynamics

from wetlandmapper import classify_dynamics, DYNAMICS_CLASSES, DYNAMICS_COLORS

dynamics = classify_dynamics(
    mndwi,
    nYear=3,           # compare first 3 years vs last 3 years
    thresholdWet=25,   # pixel must be wet 25% of years to count as wetland
    thresholdPersis=75 # pixel wet 75%+ of years = Persistent wetland
)

# Count pixels in each class
import pandas as pd
px_km2 = 0.03 * 0.03  # area of one 30m pixel in km²
summary = pd.DataFrame([
    {
        'Class': DYNAMICS_CLASSES[c],
        'Pixels': int((dynamics == c).sum()),
        'Area (km²)': round(int((dynamics == c).sum()) * px_km2, 2)
    }
    for c in sorted(DYNAMICS_CLASSES.keys())
])
print(summary.to_string(index=False))

Cell 6: Make a map

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.colors as mcolors

# Set up colours for each class
dyn_codes  = sorted(DYNAMICS_CLASSES.keys())
dyn_colors = [DYNAMICS_COLORS[c] for c in dyn_codes]
cmap       = mcolors.ListedColormap(dyn_colors)
norm       = mcolors.BoundaryNorm(
    [c - 0.5 for c in dyn_codes] + [dyn_codes[-1] + 0.5],
    cmap.N
)

fig, ax = plt.subplots(figsize=(10, 8))
ax.imshow(
    dynamics.values,
    cmap=cmap, norm=norm,
    origin='upper',
    extent=[float(mndwi.x.min()), float(mndwi.x.max()),
            float(mndwi.y.min()), float(mndwi.y.max())]
)
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_title('Wetland Dynamics 2000–2023', fontsize=14, fontweight='bold')

# Add legend
patches = [
    mpatches.Patch(color=DYNAMICS_COLORS[c], label=DYNAMICS_CLASSES[c])
    for c in dyn_codes
]
ax.legend(handles=patches, loc='lower right', fontsize=9)

plt.tight_layout()
plt.savefig('wetland_dynamics_map.png', dpi=200, bbox_inches='tight')
plt.show()
print("Map saved as wetland_dynamics_map.png")

Your first wetland dynamics map will appear directly in the notebook.


Part 6: Save Your Results as GeoTIFF

A GeoTIFF is a standard raster format that QGIS, ArcGIS, and most GIS software can open directly. It preserves the geographic coordinates so your map will line up correctly with other data.

import rioxarray  # adds GIS capabilities to our data

# Add coordinate system (WGS84 — standard geographic coordinates)
dynamics_save = dynamics.astype('int8')
dynamics_save = dynamics_save.rio.write_crs('EPSG:4326')
dynamics_save = dynamics_save.rio.write_nodata(-128)

# Save as GeoTIFF
dynamics_save.rio.to_raster('wetland_dynamics.tif')
print("Saved: wetland_dynamics.tif")

# Also save wet frequency map
from wetlandmapper import compute_wet_frequency
wet_freq = compute_wet_frequency(mndwi)
wet_freq_save = wet_freq.rio.write_crs('EPSG:4326')
wet_freq_save.rio.to_raster('wet_frequency.tif')
print("Saved: wet_frequency.tif")

Part 7: Open Your Maps in QGIS

  1. Open QGIS
  2. Go to Layer → Add Layer → Add Raster Layer
  3. Click Browse and find wetland_dynamics.tif (it will be in the same folder where you launched JupyterLab – usually your home folder or Documents)
  4. Click Add

The map will load as a single-band raster. To apply the correct colours:

  1. Right-click the layer in the Layers panel → Properties
  2. Go to Symbology
  3. Change the render type from Singleband gray to Paletted/Unique values
  4. Click Classify – QGIS will detect the class values (0, 2, 3, 4, 5, 6, 10)
  5. Double-click each colour swatch to match the WetlandMapper colour scheme:
Value Class Suggested colour
0 Non-wetland Light grey #f2f3f4
2 New Purple #8e44ad
3 Lost Red #c0392b
4 Diminishing Orange #e67e22
5 Intensifying Green #2ecc71
6 Intermittent Teal #76d7c4
10 Persistent Dark blue #1a5276
  1. Click OK

You can now add basemaps (Google Satellite, OpenStreetMap) using the QuickMapServices plugin in QGIS to see your wetland map in geographic context.

Adding a basemap in QGIS

  1. Go to Plugins → Manage and Install Plugins
  2. Search for QuickMapServices and click Install
  3. Go to Web → QuickMapServices → Google → Google Satellite

Your dynamics map will now be draped over satellite imagery.


Part 8: Wetland Cover Types (WCT)

Beyond dynamics, WetlandMapper can tell you what is at the wetland surface — open water, turbid water, submerged vegetation, emergent vegetation, or moist soil. This is based on three indices combined.

from wetlandmapper.gee import fetch_xee
from wetlandmapper import classify_wct_ema, WCT_CLASSES, WCT_COLORS

# Fetch all three required indices for a single reference year
print("Fetching MNDWI, NDVI, NDTI for 2022...")
indices_lazy = fetch_xee(
    aoi,
    start='2022-01-01',
    end='2022-12-31',
    sensor='LandsatAll',
    index=['MNDWI', 'NDVI', 'NDTI'],
    scale=30,
    temporal_aggregation='annual',
)
indices_lazy = indices_lazy.rename({'lat': 'y', 'lon': 'x'})
indices_lazy = indices_lazy.transpose('time', 'y', 'x')
indices = indices_lazy.compute()

# Single year composite
indices_2022 = indices.isel(time=0)

# Classify
wct_result = classify_wct_ema(indices_2022)
wct_map    = wct_result['wetland_cover_type']
combo_map  = wct_result['combination_code']

# Quick summary
for c in sorted(WCT_CLASSES.keys()):
    n = int((wct_map == c).sum())
    print(f"  {WCT_CLASSES[c]}: {n} pixels ({n * px_km2:.2f} km²)")
# Plot WCT map
wct_codes   = sorted(WCT_CLASSES.keys())
wct_clrs    = [WCT_COLORS[c] for c in wct_codes]
cmap_wct    = mcolors.ListedColormap(wct_clrs)
norm_wct    = mcolors.BoundaryNorm(
    [c - 0.5 for c in wct_codes] + [wct_codes[-1] + 0.5], cmap_wct.N
)

fig, ax = plt.subplots(figsize=(10, 8))
ax.imshow(wct_map.values, cmap=cmap_wct, norm=norm_wct,
          origin='upper',
          extent=[float(indices.x.min()), float(indices.x.max()),
                  float(indices.y.min()), float(indices.y.max())])
ax.set_title('Wetland Cover Types — 2022', fontsize=14, fontweight='bold')
ax.set_xlabel('Longitude'); ax.set_ylabel('Latitude')
patches_wct = [mpatches.Patch(color=WCT_COLORS[c], label=WCT_CLASSES[c])
               for c in wct_codes]
ax.legend(handles=patches_wct, loc='lower right', fontsize=8)
plt.tight_layout()
plt.savefig('wct_2022.png', dpi=200, bbox_inches='tight')
plt.show()

# Save as GeoTIFF for QGIS
wct_save = wct_map.astype('int8').rio.write_crs('EPSG:4326')
wct_save.rio.to_raster('wct_2022.tif')
print("Saved: wct_2022.tif")

Troubleshooting

“ModuleNotFoundError: No module named ‘wetlandmapper’” You forgot to activate the conda environment. Run conda activate wetlands and try again.

“No such file or directory: earthengine” Run pip install earthengine-api and then earthengine authenticate.

The map is blank or all zeros Your AOI may have no data. Check that your coordinates are correct (longitude first, then latitude in GeoJSON format). Also try increasing max_cloud_cover=40.

“EEException: Computation timed out” Your area is too large. Try a smaller bounding box or use scale=100 for a coarser resolution that downloads faster.

Jupyter shows the map but it looks wrong (stretched or in wrong place) Make sure you ran the .rename({'lat': 'y', 'lon': 'x'}) and .transpose() lines after fetch_xee.

“CLOUD_COVER: No such property” You are using Sentinel-2 but the cloud property name is different. Add max_cloud_cover=20 explicitly to your fetch_xee call.


Quick Reference Card

Every new session:
  1. Open Anaconda Prompt / Terminal
  2. conda activate wetlands
  3. jupyter lab

Key parameters:
  sensor:        'LandsatAll'  1982-present, best for long records
                 'Landsat8'    2013-present, single mission
                 'Sentinel2'   2015-present, 10 m resolution
                 'MODISAll'    2000-present, 500 m, large areas

  scale:         30  (Landsat/Sentinel-2, 30 m)
                 10  (Sentinel-2 only, highest detail)
                 500 (MODIS, continental scale)

  thresholdWet:  25  (25% of years must be wet to count as wetland)
  thresholdPersis: 75  (75%+ of years = Persistent)
  nYear:         3-5  (years compared historic vs recent)

Further Reading


WetlandMapper is free and open-source. Install it with: conda install -c conda-forge wetlandmapper

← Back to all posts