Reservoir Levels · 8 Basins

Water-Scarcity-Early-Warning-Network

乾旱不像洪水那樣戲劇化——它緩慢、安靜,等到人們發現時,水庫已經見底。預警網絡要在水還夠用的時候就告訴你:六個月後可能會不夠。

Normal62%水庫蓄水量在歷史正常範圍內
Watch48%低於季節平均值,啟動節水宣導
Warning31%農業用水限制,跨區調度準備
Critical14%民生用水優先,緊急供水計畫
Forecast Lead6 months基於氣象與水文模型的預警提前量
Sensor Nodes1,240流域內水位、流量與土壤濕度節點
Accuracy89%季節性乾旱預測準確率
Response Gap4.2 days預警發布到調度決策的平均延遲

乾旱不是一天發生的

洪水會上頭條,但乾旱不會——它沒有震撼的畫面,只有緩慢下降的水位線。等到水庫蓄水量跌破 20%、農民開始休耕、民生用水啟動分區供水時,真正的問題早在六個月前就已經開始累積:一個異常乾燥的冬季、提前融化的積雪、或是上游水權分配中的隱性超抽。預警系統的任務,就是在那六個月的窗口期內發出足夠清晰的訊號。

現代水資源短缺預警整合了三條資料鏈:氣象預報(降水、蒸發散、雪水當量)、水文監測(河川基流量、水庫入流、地下水位),以及用水需求模型(農業灌溉曆、工業用水排程、人口季節性移動)。三者的交集才是真正的短缺風險——單看降水不足可能只是小旱,但如果同時碰上灌溉高峰和地下水補注不足,就是需要提前啟動調度的複合型事件。

跨區調度的政治算術

技術上可以算出哪個流域缺多少水、哪個流域有餘裕,但實際上跨區調度不只是水管和泵站的問題,更是不同行政區之間的水權談判。上游城市可能認為「我們的水先保障我們」,下游農業區則認為「沒有糧食安全哪來的城市」。預警系統如果只提供水文數據而不提供談判框架,就只是另一份沒人讀的報告。

實務上的突破來自「預先協議」機制——在豐水期就由各利害關係方簽訂分級調度協議,明確定義不同缺水等級下誰先限水、誰可以從哪裡調多少水、由誰支付調度成本。預警系統一旦觸及對應等級,調度行動自動啟動,不需要再開三個月的協調會。這種機制在西班牙的 Júcar 流域和美國的 Colorado 河流域都已有實際運作經驗。

Drought monitoring and water scarcity
Fig 1. 流域水資源監測:水位、流量與土壤濕度構成的短缺預警三角Source: Unsplash

地下水:看不見的緩衝

地表水庫的水位人人看得到,但地下水才是乾旱期間真正的緩衝——全球約 30% 的淡水用量在乾旱時期由地下水支應。問題是地下水超抽的後果往往在數年後才顯現:地層下陷、海水入侵、河川基流量永久性減少。預警系統必須同時監測地下水位的變化速率,而不只是絕對水位——一個水位還在正常範圍但每月穩定下降一公尺的含水層,比一個水位偏低但已經穩定的含水層更令人擔憂。

近年來,重力衛星(GRACE 任務)提供了大尺度地下水變化的獨立觀測來源,不受地面監測井密度不足的限制。將衛星資料與地面觀測結合,可以在傳統監測井稀疏的偏遠農業區發現隱性超抽,讓預警系統的空間覆蓋從「有井的地方」擴展到整個流域。

Water resource management technology
Fig 2. 地下水監測井與衛星重力資料的互補覆蓋:從點到面的擴展Source: Unsplash
drought_index.pyPYTHON
import numpy as np

class DroughtIndex:
    # Composite drought index from precipitation, streamflow, and groundwater.
    def __init__(self, w_precip=0.4, w_flow=0.35, w_gw=0.25):
        self.weights = (w_precip, w_flow, w_gw)
        self.baseline = {}

    def calibrate(self, hist_precip, hist_flow, hist_gw):
        self.baseline = {
            'precip': (np.mean(hist_precip), np.std(hist_precip)),
            'flow': (np.mean(hist_flow), np.std(hist_flow)),
            'gw': (np.mean(hist_gw), np.std(hist_gw))
        }

    def compute(self, precip, flow, gw):
        z = (
            (precip - self.baseline['precip'][0]) / self.baseline['precip'][1],
            (flow - self.baseline['flow'][0]) / self.baseline['flow'][1],
            (gw - self.baseline['gw'][0]) / self.baseline['gw'][1]
        )
        index = sum(z[i] * self.weights[i] for i in range(3))
        level = 'critical' if index < -2 else 'warning' if index < -1 else 'watch' if index < 0 else 'normal'
        return round(index, 2), level