# Mekong Land Cover — RLCMS Primitives Approach
#
# Land cover classification inspired by the Regional Land Cover Monitoring
# System (RLCMS) from SIG/SERVIR-Mekong (Poortinga et al. 2019).
#
# Method: Compute biophysical "primitives" (continuous indices) from
# Landsat imagery, then combine via a threshold decision tree.
# Each threshold is a slider — tune the classification in real time.
#
# Reference:
#   Poortinga A, et al. (2019). Mapping Plantations in Myanmar by Fusing
#   Landsat-8, Sentinel-2 and Sentinel-1 Data along with Systematic Error
#   Quantification. Remote Sensing 11(7):831.
#   https://landcovermapping.org
#
# Pipeline:
#
#   landsat ──→ NDVI  ──┐
#   landsat ──→ NDWI  ──┼──→ decision tree ──→ land cover (5 classes)
#   landsat ──→ NDBI  ──┤
#   landsat ──→ BSI   ──┘  (display only)
#
# Classes:
#   1 = Water    (NDWI > threshold)
#   2 = Forest   (NDVI > threshold)
#   3 = Cropland (NDVI > threshold)
#   4 = Built-up (NDBI > threshold)
#   5 = Bare soil (default)

name: "Mekong Land Cover — RLCMS Primitives"
description: >
  Land cover classification using the RLCMS primitives approach
  (Poortinga et al. 2019, SIG/SERVIR-Mekong). Biophysical indices
  (NDVI, NDWI, NDBI, BSI) from Landsat 8 are combined via a threshold
  decision tree into 5 land cover classes — running entirely in the browser.

settings:
  # Siem Reap area, Cambodia — water, forest, cropland, built-up, bare
  # Small bbox within a single Landsat WRS path/row for clean edges
  default_bbox: [103.82, 13.30, 103.95, 13.40]
  theme: light

layers:

  # ============================================================
  # SOURCE: Landsat 8 Surface Reflectance
  #
  # Single least-cloudy scene, 2023 dry season (Jan–Jun).
  # Scale factors convert DN → physical reflectance (0–1).
  # ============================================================

  landsat:
    type: raster
    uri: "stac://microsoft-pc/landsat-c2-l2"
    bands: [blue, green, red, nir08, swir16]
    params:
      temporal: ["2023-01-01", "2023-06-30"]
      composite: median
      limit: 30
      sort: "eo:cloud_cover"
      query:
        platform: { eq: "landsat-8" }
      scale:
        factor: 0.0000275
        offset: -0.2
        nodata: 0
    style:
      rgb: [red, green, blue]
      clim: [0, 0.3]

  # ============================================================
  # PRIMITIVES: Biophysical Indices
  #
  # Each primitive is a continuous map (-1 to 1) representing
  # one biophysical property. These are the building blocks —
  # combined downstream into discrete land cover classes.
  # ============================================================

  primitive/ndvi:
    type: raster
    description: >
      Normalized Difference Vegetation Index.
      High (>0.6) = dense canopy. Moderate (0.2–0.6) = crops/sparse.
    compute:
      op: raster_calc
      inputs:
        raster: { layer: landsat }
      params:
        expression: "(nir08 - red) / (nir08 + red)"
    style:
      palette: ylgn
      clim: [-0.2, 0.9]

  primitive/ndwi:
    type: raster
    description: >
      Normalized Difference Water Index (McFeeters 1996).
      Positive values indicate open water surfaces.
    compute:
      op: raster_calc
      inputs:
        raster: { layer: landsat }
      params:
        expression: "(green - nir08) / (green + nir08)"
    style:
      palette: blues
      clim: [-0.5, 0.5]

  primitive/ndbi:
    type: raster
    description: >
      Normalized Difference Built-up Index (Zha et al. 2003).
      Positive values indicate impervious surfaces.
    compute:
      op: raster_calc
      inputs:
        raster: { layer: landsat }
      params:
        expression: "(swir16 - nir08) / (swir16 + nir08)"
    style:
      palette: oranges
      clim: [-0.5, 0.5]

  primitive/bsi:
    type: raster
    description: >
      Bare Soil Index. High values = exposed soil, sand, rock.
    compute:
      op: raster_calc
      inputs:
        raster: { layer: landsat }
      params:
        expression: "((swir16 + red) - (nir08 + blue)) / ((swir16 + red) + (nir08 + blue))"
    style:
      palette: ylorrd
      clim: [-0.3, 0.3]

  # ============================================================
  # CLASSIFICATION: Decision Tree on Primitives
  #
  # RLCMS approach: combine primitives using thresholds.
  # Each slider controls one decision boundary. The tree is
  # evaluated top-down — first match wins.
  # ============================================================

  land-cover:
    type: raster
    compute:
      op: raster_calc
      inputs:
        ndvi: { layer: primitive/ndvi }
        ndwi: { layer: primitive/ndwi }
        ndbi: { layer: primitive/ndbi }
      params:
        expression: "where(ndwi > water_thresh, 1, where(ndvi > forest_thresh, 2, where(ndvi > crop_thresh, 3, where(ndbi > built_thresh, 4, 5))))"
        water_thresh: 0.1
        forest_thresh: 0.55
        crop_thresh: 0.25
        built_thresh: 0.0
    style:
      clim: [1, 5]
      colorRamp:
        - { value: 1, color: "#2196F3", label: "Water" }
        - { value: 2, color: "#1B5E20", label: "Forest" }
        - { value: 3, color: "#8BC34A", label: "Cropland" }
        - { value: 4, color: "#F44336", label: "Built-up" }
        - { value: 5, color: "#D7CCC8", label: "Bare soil" }
      form:
        water_thresh:
          type: slider
          label: "Water (NDWI)"
          min: -0.1
          max: 0.5
          step: 0.05
          default: 0.1
          description: "Pixels with NDWI above this → Water"
        forest_thresh:
          type: slider
          label: "Forest (NDVI)"
          min: 0.3
          max: 0.8
          step: 0.05
          default: 0.55
          description: "Pixels with NDVI above this → Forest"
        crop_thresh:
          type: slider
          label: "Cropland (NDVI)"
          min: 0.1
          max: 0.5
          step: 0.05
          default: 0.25
          description: "Pixels with NDVI above this → Cropland"
        built_thresh:
          type: slider
          label: "Built-up (NDBI)"
          min: -0.3
          max: 0.3
          step: 0.05
          default: 0.0
          description: "Pixels with NDBI above this → Built-up"

views:
  - name: "Land Cover"
    layers: [land-cover]
  - name: "Primitives"
    layers: [primitive/ndvi, primitive/ndwi, primitive/ndbi, primitive/bsi]
  - name: "True Color"
    layers: [landsat]
