Geoskill: Water Body Extraction

Automatically extract water bodies from Landsat 8/9 or Sentinel-2 satellite images using NDWI/MNDWI indices with optional Otsu thresholding and vector output.

ruiduobao

@ruiduobao

What This Skill Does

Command-line tool that extracts water bodies from multi-band satellite imagery (Landsat 8/9, Sentinel-2) using NDWI or MNDWI spectral indices with automatic Otsu thresholding. Outputs binary raster masks and optional GeoJSON vector boundaries.

Replaces manual digitization of water features in GIS software by automating water detection through spectral index computation and adaptive thresholding.

When to Use It

  • Map surface water extent from a single Landsat or Sentinel-2 scene
  • Batch-process a folder of satellite images to generate water masks for time-series analysis
  • Extract water body boundaries as vector polygons for GIS overlay or area calculation
  • Compare NDWI vs MNDWI results to determine best index for an urban vs rural scene
  • Filter out small water features below a minimum area threshold to reduce noise
  • Generate water pixel statistics (count, percentage) for a given satellite image

Install

$ openclaw skills install @ruiduobao/water-body-extraction

Water Body Extraction

Automatically extract water bodies from satellite imagery using spectral indices (NDWI / MNDWI). Designed for Landsat 8/9 and Sentinel-2 multi-band GeoTIFF inputs.

Features

  • NDWI (Normalized Difference Water Index): (Green - NIR) / (Green + NIR)
  • MNDWI (Modified NDWI): (Green - SWIR) / (Green + SWIR)
  • Otsu automatic thresholding — no manual threshold needed
  • Manual threshold mode for fine control
  • Batch processing of multiple images
  • Vector output — GeoJSON boundaries alongside raster mask
  • Sensor-aware band selection for Landsat 8/9 and Sentinel-2

Band Configuration

SensorGreenNIRSWIR
Landsat 8/9B3B5B6
Sentinel-2B3B8B11

Usage

Single image extraction

python scripts\water-body-extraction.py extract \
  --input image.tif \
  --sensor landsat8 \
  --index mndwi \
  --output water_mask.tif \
  --vector water_boundary.geojson

NEW (v0.2.0): one-shot by place name

# Provide just --place; the skill auto-fetches a Sentinel-2 scene for you
python scripts\water-body-extraction.py extract \
  --place "成都市锦江区" \
  --preset water-urban \
  --start-date 2023-06-01 --end-date 2023-09-30 --max-cloud 20 \
  --output water_mask.tif \
  --vector water_boundary.geojson \
  --qa

Available presets:

  • water-urban (Sentinel-2 + MNDWI)
  • water-rural (Sentinel-2 + NDWI)
  • water-landsat (Landsat 8/9 + MNDWI)

Otsu threshold optimization

python scripts\water-body-extraction.py threshold \
  --input image.tif \
  --sensor landsat8 \
  --index ndwi \
  --method otsu

Batch processing

python scripts\water-body-extraction.py batch \
  --input-dir ./images/ \
  --sensor sentinel2 \
  --index mndwi \
  --output-dir ./masks/ \
  --vector-dir ./vectors/

Installation

pip install rasterio>=1.3.0 numpy>=1.21.0 scipy>=1.7.0 shapely>=1.8.0 fiona>=1.8.0 tqdm>=4.64.0
# Or: pip install -r scripts/requirements.txt

Parameters

  • --input: Path to multi-band GeoTIFF
  • --sensor: Sensor type (landsat8, landsat9, sentinel2)
  • --index: Index to compute (ndwi, mndwi)
  • --threshold: Manual threshold value (default: auto via Otsu)
  • --output: Output raster mask path
  • --vector: Output vector GeoJSON path (optional)
  • --input-dir: Directory for batch processing
  • --output-dir: Output directory for batch masks
  • --vector-dir: Output directory for batch vectors
  • --method: Threshold method (otsu, manual)
  • --json: Output statistics as JSON

Output

  • Raster mask: Binary GeoTIFF (1=water, 0=non-water), same CRS/resolution as input
  • Vector: GeoJSON polygon boundaries of water bodies
  • Statistics: Water pixel count, percentage, threshold used

Dependencies

rasterio>=1.3.0
numpy>=1.21.0
scipy>=1.7.0
shapely>=1.8.0
fiona>=1.8.0
tqdm>=4.64.0

NDWI vs MNDWI Guidance

IndexBest ForCaution
NDWIOpen water, rural areasBuilt-up areas may show false positives
MNDWIUrban areas, mixed land coverSuppresses built-up noise; may miss turbid water

Recommendation: Use MNDWI for urban/suburban scenes; NDWI for natural landscapes.

Cloud Masking Guidance

Clouds and cloud shadows cause false water detections. Best practices:

  • Pre-filter: Use only images with <10% cloud cover (check metadata)
  • QA band: Use the Landsat/Sentinel QA_PIXEL band to mask clouds before extraction
  • Post-processing: Apply morphological opening to remove small cloud-shadow false positives

Minimum Area Filtering

Remove small noise polygons with --min-area:

python scripts\water-body-extraction.py extract \
  --input image.tif --sensor landsat8 --index mndwi \
  --output water_mask.tif --vector water_boundary.geojson \
  --min-area 1000

--min-area is in map units (m² for projected CRS). Typical values: 500–5000 m².

Large File Guidance

For rasters >1 GB:

  • Process in tiles using --tile-size 4096 --overlap 256
  • Use --compress lzw to reduce output size
  • Ensure sufficient RAM: ~2× the uncompressed raster size

Vector Output Formats

In addition to GeoJSON, Shapefile and GeoPackage output are supported:

python scripts\water-body-extraction.py extract \
  --input image.tif --sensor landsat8 --index mndwi \
  --output water_mask.tif --vector water_boundary.gpkg --vector-format gpkg

Accuracy Assessment

Validate results against reference data:

  1. Collect ground-truth points or polygons
  2. Compute confusion matrix: overall accuracy, kappa coefficient, F1 score
  3. Compare with existing water datasets (e.g., JRC Global Surface Water, GlobeLand30)

Visualization

Visualize results in QGIS or Python:

  • Raster mask: Apply blue color map (water=blue, non-water=transparent)
  • Vector overlay: Semi-transparent blue fill with dark blue boundary
  • Before/after: Side-by-side comparison of original imagery and water mask
import rasterio
import matplotlib.pyplot as plt

with rasterio.open('water_mask.tif') as src:
    mask = src.read(1)
plt.imshow(mask, cmap='Blues')
plt.title('Water Body Extraction Result')
plt.show()

Citation

If this tool supports your research, please cite:

@software{water_body_extraction,
  author  = {ruiduobao},
  title   = {Water Body Extraction Tool},
  url     = {https://github.com/ruiduobao/water-body-extraction},
  version = {0.1.0},
  year    = {2024},
}

Troubleshooting

ErrorCauseSolution
ConnectionErrorNetwork issueCheck internet, retry
HTTP 429Rate limitWait 60s, retry
ValueErrorInvalid inputCheck parameter format
Empty outputNo data / all cloudTry different image or adjust threshold
ModuleNotFoundErrorMissing depRun pip install
MemoryErrorRaster too largeUse tiling (--tile-size) or increase RAM
All water / no waterThreshold issueTry manual threshold or different index

Data Source

Local raster processing — uses pre-downloaded satellite imagery. No external API calls.


Advanced Usage

Batch Processing with Shell Loop

for img in scenes/*.tif; do
  python scripts\water-body-extraction.py extract     --input "$img" --index MNDWI --sensor sentinel2     --output "water_$(basename $img)"
  sleep 1
done

CI/CD Integration (GitHub Actions)

# .github/workflows/monthly-water.yml
name: Monthly Water Extraction
on:
  schedule:
    - cron: '0 0 1 * *'  # Monthly
jobs:
  extract:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install numpy rasterio scipy
      - run: |
          python scripts\water-body-extraction.py extract \
            --input data/latest_scene.tif \
            --index MNDWI --sensor sentinel2 --otsu \
            --output data/water_latest.tif

Vector Export & PostGIS Import

# Extract water and export as GeoJSON vector
python scripts\water-body-extraction.py extract   --input scene.tif --index MNDWI --vector --output water.geojson

# Import to PostGIS
ogr2ogr -f PostgreSQL PG:"dbname=gis_db" water.geojson -nln water_bodies

Performance Tips

  • Otsu thresholding is automatic; use --threshold 0.0 for manual control
  • Add --min-area 100 to filter small noise polygons
  • Use --cloud-mask with QA_PIXEL to improve accuracy in cloudy scenes

中文说明

使用光谱指数(NDWI / MNDWI)从卫星影像中自动提取水体。支持 Landsat 8/9 和 Sentinel-2 多波段 GeoTIFF 输入。

功能特性

  • NDWI(归一化水体指数):(Green - NIR) / (Green + NIR)
  • MNDWI(改进型水体指数):(Green - SWIR) / (Green + SWIR)
  • Otsu 自动阈值 — 无需手动设置阈值
  • 手动阈值模式,精细控制
  • 批量处理多张影像
  • 矢量输出 — 同时输出 GeoJSON 边界和栅格掩膜
  • 传感器感知 — 自动选择对应波段

波段配置

传感器GreenNIRSWIR
Landsat 8/9B3B5B6
Sentinel-2B3B8B11

使用示例

单幅影像提取

python scripts\water-body-extraction.py extract \
  --input image.tif \
  --sensor landsat8 \
  --index mndwi \
  --output water_mask.tif \
  --vector water_boundary.geojson

Otsu 阈值优化

python scripts\water-body-extraction.py threshold \
  --input image.tif \
  --sensor landsat8 \
  --index ndwi \
  --method otsu

批量处理

python scripts\water-body-extraction.py batch \
  --input-dir ./images/ \
  --sensor sentinel2 \
  --index mndwi \
  --output-dir ./masks/ \
  --vector-dir ./vectors/

安装

pip install rasterio>=1.3.0 numpy>=1.21.0 scipy>=1.7.0 shapely>=1.8.0 fiona>=1.8.0 tqdm>=4.64.0
# 或: pip install -r scripts/requirements.txt

参数说明

  • --input: 多波段 GeoTIFF 路径
  • --sensor: 传感器类型(landsat8, landsat9, sentinel2
  • --index: 计算指数(ndwi, mndwi
  • --threshold: 手动阈值(默认 Otsu 自动)
  • --output: 输出栅格掩膜路径
  • --vector: 输出矢量 GeoJSON 路径(可选)
  • --input-dir: 批量处理输入目录
  • --output-dir: 批量输出掩膜目录
  • --vector-dir: 批量输出矢量目录
  • --method: 阈值方法(otsu, manual
  • --json: 以 JSON 格式输出统计信息

输出结果

  • 栅格掩膜: 二值 GeoTIFF(1=水体, 0=非水体),与输入相同的 CRS/分辨率
  • 矢量: GeoJSON 多边形水体边界
  • 统计信息: 水体像素数、占比、使用的阈值

依赖库

rasterio>=1.3.0
numpy>=1.21.0
scipy>=1.7.0
shapely>=1.8.0
fiona>=1.8.0
tqdm>=4.64.0

NDWI vs MNDWI 选择指南

指数适用场景注意事项
NDWI开阔水体、农村地区建筑区可能产生假阳性
MNDWI城市区域、混合用地抑制建筑噪声;可能漏检浑浊水体

建议:城市/郊区场景用 MNDWI;自然地貌用 NDWI。

云掩膜指导

云和云影会导致虚假水体检测。最佳实践:

  • 预处理:仅使用云量 <10% 的影像(查看元数据)
  • QA 波段:使用 Landsat/Sentinel QA_PIXEL 波段在提取前掩膜云
  • 后处理:形态学开运算去除小云影假阳性

最小面积过滤

使用 --min-area 去除小面积噪声多边形:

python scripts\water-body-extraction.py extract \
  --input image.tif --sensor landsat8 --index mndwi \
  --output water_mask.tif --vector water_boundary.geojson \
  --min-area 1000

--min-area 单位为地图单位(投影 CRS 为 m²)。典型值:500–5000 m²。

大文件处理

对于 >1 GB 的栅格:

  • 使用 --tile-size 4096 --overlap 256 分块处理
  • 使用 --compress lzw 减小输出
  • 确保足够内存:约为未压缩栅格大小的 2 倍

矢量输出格式

除 GeoJSON 外,还支持 Shapefile 和 GeoPackage 输出:

python scripts\water-body-extraction.py extract \
  --input image.tif --sensor landsat8 --index mndwi \
  --output water_mask.tif --vector water_boundary.gpkg --vector-format gpkg

精度评估

使用参考数据验证结果:

  1. 采集地面真实点或多边形
  2. 计算混淆矩阵:总体精度、Kappa 系数、F1 分数
  3. 与现有水体数据集对比(如 JRC 全球地表水、GlobeLand30)

可视化

在 QGIS 或 Python 中可视化结果:

  • 栅格掩膜:蓝色配色(水体=蓝色,非水体=透明)
  • 矢量叠加:半透明蓝色填充 + 深蓝色边界
  • 对比:原始影像与水体掩膜并排对比

引用格式

如果本工具支持您的研究,请引用:

@software{water_body_extraction,
  author  = {ruiduobao},
  title   = {Water Body Extraction Tool},
  url     = {https://github.com/ruiduobao/water-body-extraction},
  version = {0.1.0},
  year    = {2024},
}

故障排除

错误原因解决方案
ConnectionError网络问题检查网络,重试
HTTP 429速率限制等待 60 秒后重试
ValueError无效输入检查参数格式
无输出无数据 / 全是云更换影像或调整阈值
ModuleNotFoundError缺少依赖运行 pip install
MemoryError栅格过大使用分块处理或增加内存
全水体 / 无水阈值问题尝试手动阈值或更换指数

数据来源

本地栅格处理 — 使用预下载的卫星影像,无外部 API 调用。

Top skills in this category