技術觀點・防災遙測・AI 協作

不會 GEE,也能做出土壤含水量監測工具:從 SMAP 衛星資料看見坡地防災的新視角

有些數位轉型,不是從一堂程式課開始的——它可能只是從一個很單純的問題開始:「如果我想知道某個山區過去的土壤有多濕,能不能直接在地圖上點一下,就看到它的變化?」

🛰️ NASA SMAP L3 / L4 🌏 Google Earth Engine ⛰️ 台灣坡地防災 🤖 AI 協作開發

這次我利用 Google Earth Engine(GEE),串接 NASA 的 SMAP 土壤含水量資料,在地圖上任意點選一個位置,系統就會自動抓出該地點的土壤含水量時間序列,並畫出圖表。

這聽起來像是遙測工程師或資料科學家才會做的事。但真正關鍵的是:我原本並不熟 Google Earth Engine 的 API 語法。

在 AI 協作時代,領域專家不一定要先變成程式設計師,才能開始使用大型遙測資料。真正重要的是,你能不能把問題問清楚,把業務邏輯講明白。


這套工具做了什麼?點地圖,看趨勢

簡單說,它做了三件事:打開 GEE、載入 NASA SMAP 土壤含水量資料,然後讓使用者在地圖上點一個位置,系統就自動畫出那個位置的土壤含水變化。

從第一張實作畫面可以看到,左邊是程式碼區,中間是衛星地圖,右側是互動式圖表面板。在地圖點選山區位置後,畫面上出現紅點,右側面板顯示經緯度並產生兩張土壤含水量圖表。

GEE 完整工具操作畫面
實作畫面一:完整工具介面。左側是 GEE JavaScript 程式碼,中間為台灣衛星地圖,右側互動面板在點擊後自動產出 L3 與 L4 兩張土壤含水量時序圖。紅點為查詢位置,座標 (120.66, 22.98) 對應高雄市六龜區「高市DF053」土石流潛勢溪流集水區。

這樣的操作對防災人員很直覺:不用下載資料、不用處理龐大衛星影像、不用一格一格查表——點地圖,就能看趨勢。這正是 GEE 加上 AI 協作最有價值的地方:把藏在 API 語法裡的資訊,變成領域專家可以直接理解的圖。


SMAP 不是在「看土壤」,而是在讀微波訊號

SMAP 的全名是 Soil Moisture Active Passive,是 NASA 觀測全球土壤含水量的衛星任務。它不是像一般相機那樣「拍到土壤有多濕」——它真正觀測的是地表發出的微波亮溫

可以這樣理解:乾土和濕土對微波的反應不一樣。水的介電特性很強,所以當土壤裡面的水變多,地表發出的微波訊號就會改變。衛星接收到這些變化後,再透過物理模型反推土壤含水量。

土壤含水量改變 介電常數改變 微波亮溫改變 衛星接收訊號 模型反演含水量

所以 SMAP 不是魔法,它背後是一套完整的物理反演邏輯。而這也是為什麼它對防災有價值——土壤含水量直接關係到坡地穩定、崩塌潛勢、集水區前期飽和狀態,以及豪雨來臨前土地還能不能繼續吸水。


L3 和 L4 差在哪裡?一個像「觀測」,一個像「推估」

這次實作同時使用了 SMAP 的兩種產品。一句話分辨:

L3 比較接近衛星觀測後的地表反演結果;L4 則是把觀測資料丟進水文模型後,產生更連續、更完整的估算結果。

L3 / 地表直接觀測

每日地表含水量(0–5 cm)

衛星過境時的實測亮溫,經地表粗糙度與植被覆蓋修正後反演而來。受軌道週期影響,資料呈離散散點,時空不一定連續。

L4 / 模型同化估算

三小時連續根系層含水量(0–100 cm)

將 L3 實測結合氣象資料與陸面水文模型,以 EnKF 演算法進行數據同化,向下推估深層滑動最關鍵的根系層含水狀態。

對防災工作而言,L4 的根系層(0–100 cm)含水量才是重點:它代表坡體已「吸了多少水」,直接影響崩塌的觸發門檻。地表濕了,可能一兩天就退掉;但根系層一旦濕了,代表水已進入更深的土體,接著若又遇到豪雨,坡地的安全邊際就會顯著下降。

SMAP L4 三小時連續土壤含水量折線圖
實作畫面二:L4 三小時連續雙線圖。紅線為表層(0–5 cm),對降雨反應迅速、跳動劇烈;藍線為根系層(0–100 cm),變動平緩卻累積效應顯著。颱風過境後藍線「退水仍維持高位」的窗口,正是遲發性崩塌的關鍵危險期。
SMAP L3 每日地表土壤含水量散點圖
實作畫面三:L3 每日地表散點圖。橫軸從 2024-06 到 2024-12,每個綠色散點代表衛星過境時的地表含水量反演值。散點間距不均勻,正反映衛星軌道週期與資料品質旗標過濾的真實情況。2024 年 7 月下旬颱風季前後可見數值明顯起伏。

9 公里網格的防災治理取捨:能看大勢,不能看單一邊坡

SMAP L4 的空間解析度約為 9 公里,這個數字一定要講清楚,否則很容易誤用。一個 9×9 km 的網格,面積高達 81 平方公里——在台灣山區,可能已涵蓋一整個大型集水區,甚至跨過好幾個村里或不同坡向的山坡。

⚠️ 空間盲區

無法反映百公尺等級的局部崩塌地差異、向陽/背陽坡微氣候,以及特定邊坡的地下水突湧。絕不能拿來做單一邊坡的即時示警指標。

✅ 治理優勢

極適合評估颱風前整個大流域(如濁水溪、高屏溪)的集水區前期土壤飽和度(ASI),作為戰略層級的整體環境背景研判。

關鍵洞見在於:同樣是 300 mm降雨,落在乾土上和落在已飽和的土壤上,風險完全不同。當某集水區在颱風前幾週,根系層含水量就已偏高,代表土體吸納雨水的空間已很小——即使後續降雨沒破紀錄,觸發大規模崩塌的機率也會飆升。

SMAP 的防災定位:不是「盯著一個點看即時警報」,而是掌握區域尺度的前期土壤含水背景,作為災害潛勢判斷的補充因子。


AI 協作三步驟:專家負責判斷,AI 負責翻譯成程式

這次開發過程中,AI 最有價值的角色不是「幫我寫幾行程式」,而是把我的防災邏輯翻譯成 GEE 能執行的操作。我不需要一開始就知道所有 API 函數——我只需要清楚描述我要什麼。

步驟一 定義物理邊界與業務情境(不要叫 AI「寫程式」,要說清楚你要什麼)
「我是防災監測專家,想在 GEE 分析台灣地區的土壤含水量。需要調用 NASA SMAP L3 每日觀測(NASA/SMAP/SPL3SMP_E/006)與 L4 三小時連續同化數據(NASA/SMAP/SPL4SMGP/007)。空間邊界設為台灣本島,時間範圍設為可調整的變數,請以繁體中文撰寫代碼註解。」
步驟二 模組化建構互動視覺化介面(把每一個細節說清楚)
「加上互動功能:點地圖後在點擊處標紅點,右側展開 400px 面板。面板內同時繪製兩張折線圖——上方是 L3 地表散點圖(綠色),下方是 L4 表層與深層(0–100 cm)雙線圖(紅藍),橫軸強制以年-月格式清晰拉開。資料品質旗標要先過濾,不能把不良資料混入。」
步驟三 閉環式除錯:把問題現象直接丟回給 AI,不要自己動程式碼
「L4 半年有逾 1,400 筆三小時資料,橫軸日期標籤擠在一起根本看不清楚。」→ AI 給出防禦性修法:用 image.date().format('yyyy-MM-dd') 將時間戳正規化為純日期,從根本解決圖表引擎的渲染缺陷。問題不是程式壞掉,而是資料時間解析度太高、圖表需要重新處理時間格式——AI 能把「現象」翻譯成「技術解法」。

AI 協作的精髓:不是讓 AI 自由發揮,而是由領域專家把「資料該怎麼用」、「圖表該怎麼看」、「什麼結果才合理」講清楚。

AI 負責產生程式,專家負責判斷方向。它降低了工具開發的門檻,但沒有取代專業判斷——剛好相反,AI 越強,專業判斷越重要。


最大盲區:GEE 的資料同步時效落差,讓它不能做即時防災監測

摸清工具的能耐後,更要看清數據的硬限制。這是最關鍵的資料治理認知:GEE 平台上的 SMAP 資料,存在不可忽視的同步時間落差。

L3 產品

基準時間 2026 年 6 月,GEE 上最新資料僅至 2025 年 12 月底——落後約半年。

L4 產品

更嚴峻:GEE 上最新資料僅至 2025 年 6 月底——落後整整一年,後續在台灣上空存在物理斷流。

原因在於 L4 是高階同化產品,NASA 必須等全球氣象觀測(GPM 降雨、地表氣壓等)完整收錄並率定後,才能進行多維矩陣計算。這層製程帶有極高的時間滯後,GEE 後台自然無法即時同步。

正確定位:不是即時防汛工具,而是「歷史災害科學回溯」與「AI 防災模型訓練基石」。實務展示或計畫推動時,應採用資料最完整的歷史年度(如 2024 年)。


未來三條最值得投入的佈局路線

📈

「降雨量 + 土壤含水量」雙指標崩塌臨界線(I-D-S Curve)

傳統 I-D 曲線只看降雨,常因無法掌握土體初始含水狀態而空報。引入 L4 深層飽和度,讓分析從「降雨條件」走向「降雨條件 × 前期含水狀態 × 地質地形」,更接近真實災害機制。

🧠

以 LSTM 時間序列網路分析颱風後退水速度,找出危險窗口期

很多坡地災害不發生在雨最大的一刻,而是在雨停後深層土壤仍飽和時。利用 L4 三小時資料訓練長短期記憶網路,學習不同地質分區(板岩、砂頁岩互層)的退水係數,對災後警戒解除、道路巡查決策提供科學依據。

🌱

流域尺度乾濕循環資料庫:看「太乾」和看「太濕」一樣重要

長時間旱季造成土壤裂隙、植生弱化,等梅雨首波強降雨來時,雨水可能沿裂隙快速入滲誘發淺層崩塌。長期累積下來,可建立台灣不同流域的乾濕循環背景資料庫,作為防災、保育與水資源治理的共同基礎。


這次用 GEE 實作 SMAP 土壤含水量監測工具,最大的收穫不是「做出了一個圖表」。真正的收穫是看清楚三件事:

第一,國際遙測資料已經成熟到可以被公共治理實際使用。第二,AI 可以大幅降低領域專家使用這些資料的門檻。第三,資料一定有尺度與時效限制,不能為了展示效果而過度解讀。

SMAP 的 9 公里網格不是單一邊坡警報器,GEE 上的資料也不是即時防汛系統。但它是一個非常好的歷史資料庫、區域背景分析工具,以及未來 AI 防災模型的重要訓練素材。

這次實作的經驗,另一個重點是我們可以透過 GEE 的實作,完整表達最終想要呈現的東西。之後就可以把這樣的想法經驗,移植到 BigGIS平臺上面,更能完整整合防災方面的需求以及資料的即時性。

用過去的環境資料,訓練未來的防災判斷。


附錄

完整 GEE 程式碼:直接複製貼上即可使用

以下是本次實作的完整 Google Earth Engine JavaScript 程式碼,可直接複製到 code.earthengine.google.com 貼上執行。時間範圍(startDate / endDate)可自行調整。

JavaScript · Google Earth Engine
/**
 * =========================================================================
 * SMAP L3 & L4 台灣土壤含水量動態監測互動工具
 * =========================================================================
 */
// 1. 設定空間與時間範圍
var centerPoint = ee.Geometry.Point([120.9605, 23.6978]); 
Map.setCenter(120.9605, 23.6978, 8);
Map.style().set('cursor', 'crosshair'); 
var startDate = '2024-06-01'; //每次執行前,記得更新所需要的起始及結束日期
var endDate = '2024-11-30';

// 2. 載入並處理 SMAP L3 數據 (優化篩選,徹底移除第 27 行的黃色警告)
var smapL3 = ee.ImageCollection('NASA/SMAP/SPL3SMP_E/006')
  .filterDate(startDate, endDate)
  .map(function(img) {
    var sm = img.select('soil_moisture_pm'); 
    var qFlag = img.select('retrieval_qual_flag_pm');
    // 直接在回傳時進行遮罩,不建立多餘的變數,避開 GEE 語法檢查器的臭蟲
    return sm.updateMask(qFlag.lte(1)).rename('L3_Soil_Moisture_AM')
             .copyProperties(img, ['system:time_start']);
  });

// 3. 載入並處理 SMAP L4 數據 (將時間戳記正規化)
var smapL4 = ee.ImageCollection('NASA/SMAP/SPL4SMGP/007')
  .filterDate(startDate, endDate)
  .map(function(img) {
    var smSurface = img.select('sm_surface').rename('L4_Surface_SM');
    var smRootzone = img.select('sm_rootzone').rename('L4_Rootzone_SM');
    var dStr = img.date().format('yyyy-MM-dd');
    var tMillis = ee.Date(dStr).millis();
    return img.addBands([smSurface, smRootzone])
              .select(['L4_Surface_SM', 'L4_Rootzone_SM'])
              .set('system:time_start', tMillis);
  });

// 4. 計算平均值作為地圖底圖背景
var l3Mean = smapL3.select('L3_Soil_Moisture_AM').mean();
var l4SurfaceMean = smapL4.select('L4_Surface_SM').mean();
var vizParams = {
  min: 0.0,
  max: 0.5,
  palette: ['#f59e0b', '#ffffff', '#2563eb'] 
};
Map.addLayer(l4SurfaceMean, vizParams, 'SMAP L4 根系層平均土壤含水量 (0-100cm)', true);
Map.addLayer(l3Mean, vizParams, 'SMAP L3 地表平均土壤含水量 (0-5cm)', false);

// 5. 建立互動式 UI 面板
var panel = ui.Panel({
  style: {width: '400px', padding: '10px'}
});
var intro = ui.Label({
  value: '點擊地圖任意網格,查看 L3 與 L4 土壤含水量時間序列折線圖',
  style: {fontSize: '14px', fontWeight: 'bold', color: '#374151'}
});
panel.add(intro);
var lonLabel = ui.Label('經度: --');
var latLabel = ui.Label('緯度: --');
panel.add(lonLabel).add(latLabel);
ui.root.insert(1, panel);

// 6. 設定地圖點擊監聽事件
Map.onClick(function(coords) {
  lonLabel.setValue('經度: ' + coords.lon.toFixed(4));
  latLabel.setValue('緯度: ' + coords.lat.toFixed(4));
  
  var clickedPoint = ee.Geometry.Point([coords.lon, coords.lat]);
  var dot = ui.Map.Layer(clickedPoint, {color: 'FF0000'}, '當前選取點');
  Map.layers().set(2, dot);
  
  while (panel.widgets().length() > 3) {
    panel.remove(panel.widgets().get(3));
  }
  
  // 繪製 L3 時間序列 (散點圖)
  var chartL3 = ui.Chart.image.series({
    imageCollection: smapL3.select('L3_Soil_Moisture_AM'),
    region: clickedPoint,
    reducer: ee.Reducer.mean(),
    scale: 9000
  }).setChartType('ScatterChart')
    .setOptions({
      title: 'SMAP L3 每日地表土壤含水量 (0-5 cm)',
      vAxis: {title: '體積含水量 (cm³/cm³)', viewWindow: {min: 0, max: 0.6}},
      hAxis: {title: '時間軸 (年-月)', format: 'yyyy-MM'},
      pointSize: 5,
      colors: ['#059669']
    });
  panel.add(chartL3);

  // 繪製 L4 時間序列 (LineChart 折線圖)
  var chartL4 = ui.Chart.image.series({
    imageCollection: smapL4.select(['L4_Surface_SM', 'L4_Rootzone_SM']),
    region: clickedPoint,
    reducer: ee.Reducer.mean(),
    scale: 9000
  }).setChartType('LineChart')
    .setOptions({
      title: 'SMAP L4 三小時連續土壤含水量 (0-5cm vs 0-100cm)',
      vAxis: {title: '體積含水量 (cm³/cm³)', viewWindow: {min: 0, max: 0.6}},
      hAxis: {
        title: '觀測時間 (年-月)',
        format: 'yyyy-MM',
        gridlines: {color: '#e5e7eb', count: 6},
        minorGridlines: {count: 0}
      },
      focusTarget: 'category',
      lineWidth: 1.5,
      pointSize: 0,
      colors: ['#2563eb', '#dc2626']
    });
  panel.add(chartL4);
});