From 844fa7d7194bd693c89c1846da54244e89569dd4 Mon Sep 17 00:00:00 2001 From: wzy-warehouse <18135009705@163.com> Date: Fri, 5 Jun 2026 16:10:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9E=84=E5=BB=BA=E6=9A=B4=E9=9B=A8=E7=81=BE?= =?UTF-8?q?=E5=AE=B3=E9=93=BE=E5=92=8C=E5=9C=B0=E9=9C=87=E7=81=BE=E5=AE=B3?= =?UTF-8?q?=E9=93=BEDBN=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/dbn/__init__.py | 0 app/config/dbn/discretization.yaml | 194 ++++++++ app/config/dbn/earthquake_cpt_params.yaml | 266 ++++++++++ app/config/dbn/earthquake_dbn_graph.yaml | 168 +++++++ app/config/dbn/rainfall_cpt_params.yaml | 362 ++++++++++++++ app/config/dbn/rainfall_dbn_graph.yaml | 168 +++++++ app/config/paths.py | 31 ++ app/models/dbn/__init__.py | 3 + app/models/dbn/earthquake/__init__.py | 8 + app/models/dbn/earthquake/earthquake_dbn.py | 395 +++++++++++++++ app/models/dbn/rainfall/__init__.py | 11 + app/models/dbn/rainfall/rainfall_dbn.py | 394 +++++++++++++++ app/repositories/dbn_repository.py | 515 ++++++++++++++++++++ app/utils/discretizer.py | 471 ++++++++++++++++++ app/utils/spatial_utils.py | 69 +++ 15 files changed, 3055 insertions(+) create mode 100644 app/config/dbn/__init__.py create mode 100644 app/config/dbn/discretization.yaml create mode 100644 app/config/dbn/earthquake_cpt_params.yaml create mode 100644 app/config/dbn/earthquake_dbn_graph.yaml create mode 100644 app/config/dbn/rainfall_cpt_params.yaml create mode 100644 app/config/dbn/rainfall_dbn_graph.yaml create mode 100644 app/config/paths.py create mode 100644 app/models/dbn/__init__.py create mode 100644 app/models/dbn/earthquake/__init__.py create mode 100644 app/models/dbn/earthquake/earthquake_dbn.py create mode 100644 app/models/dbn/rainfall/__init__.py create mode 100644 app/models/dbn/rainfall/rainfall_dbn.py create mode 100644 app/repositories/dbn_repository.py create mode 100644 app/utils/discretizer.py create mode 100644 app/utils/spatial_utils.py diff --git a/app/config/dbn/__init__.py b/app/config/dbn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/config/dbn/discretization.yaml b/app/config/dbn/discretization.yaml new file mode 100644 index 0000000..ad45e6d --- /dev/null +++ b/app/config/dbn/discretization.yaml @@ -0,0 +1,194 @@ +# 离散化规则配置 +# 定义所有连续因子的分箱规则 +# 包含暴雨灾害链和地震灾害链的全部因子 + +# ============================================ +# 暴雨触发层离散化规则 +# ============================================ + +rain_intensity: + description: "降雨强度等级" + unit: "mm/h" + bins: [0, 0.2, 5, 15, 30, 70, 140, 99999] + labels: [no_rain, light, moderate, heavy, storm, downpour, extreme] + +duration: + description: "持续时间" + unit: "h" + bins: [1, 3, 12, 99999] + labels: [short, medium, long] + +accum_rain: + description: "累计降雨量" + unit: "mm" + bins: [0, 10, 25, 50, 100, 99999] + labels: [trace, light, moderate, heavy, extreme] + +# ============================================ +# 地震触发层离散化规则 +# ============================================ + +magnitude: + description: "地震震级" + unit: "Richter" + # 基于Keefer (1984) 地震触发地质灾害的震级阈值 + # M<4.0: 无显著地质灾害; M4.0-4.9: 轻微; M5.0-5.9: 中等 + # M6.0-6.9: 显著; M7.0-7.9: 严重; M≥8.0: 灾难性 + bins: [0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0] + labels: [minor, light, moderate, strong, major, great] + +epicenter_distance: + description: "震中距" + unit: "km" + # 地震地质灾害影响范围(Keefer 1984): + # M5.0: ~10km; M6.0: ~30km; M7.0: ~100km; M8.0: ~300km + # 分级取各震级影响范围的中位值 + bins: [0, 30, 100, 300, 99999] + labels: [very_near, near, moderate, far] + +seismic_intensity: + description: "地震烈度" + unit: "中国烈度表" + # GB 18306-2015 中国地震动参数区划图 + # I-V: 无显著地质灾害; VI-VII: 轻微; VIII-IX: 显著; X-XII: 严重; >XII: 灾难性 + # 输入为数值烈度(1-12),映射到离散等级 + bins: [0, 5, 7, 9, 12, 99] + labels: [minor, light, moderate, severe, extreme] + +# ============================================ +# 环境层离散化规则(暴雨/地震共享) +# ============================================ + +elevation: + description: "高程" + unit: "m" + bins: [0, 400, 500, 700, 1000, 1500, 99999] + labels: [basin, plain_urban, transition, low_mountain, mid_mountain, high_mountain] + +slope: + description: "坡度" + unit: "度" + bins: [0, 5, 15, 25, 35, 45, 90] + labels: [flat, gentle, moderate, steep, very_steep, extreme_steep] + +aspect: + description: "坡向" + unit: "度" + bins: [0, 45, 135, 225, 315, 360] + labels: [north, east, south, west, north_loop] + +soil_type: + description: "土壤分类(中国土壤分类系统)" + unit: "分类代码" + # 数据库实际编码(xian_soil 表 value 字段,来源:pg_description) + mapping: + 0: ultisol # 老成土 + 6: entisol # 初育土 + 11: fluvo_aquic # 潮土 + 18: yellow_brown # 黄棕壤 + default: entisol + +lithology: + description: "岩性(中国地质分类)" + unit: "分类代码" + # 数据库实际编码(xian_lithology 表 value 字段,来源:pg_description) + # 工程地质分组:按 SiO₂ 含量 + 成因合并同类岩性 + mapping: + 1: acid_rock # 酸性侵入岩(花岗岩等,SiO₂>66%,100条) + 3: basic_rock # 基性侵入岩(辉长岩等,SiO₂ 45-52%,5条) + 4: basic_rock # 基性火山岩(玄武岩等,合并入基性岩,17条) + 5: carbonate # 碳酸盐岩(石灰岩、白云岩,142条) + 10: metamorphic # 变质岩(片麻岩、大理岩,156条) + 11: mixed_clastic # 混合碎屑沉积岩(砂岩+泥岩互层,35条) + 13: terrigenous # 陆源碎屑岩(砂岩、粉砂岩,180条) + 14: unconsolidated # 松散堆积物(黄土、冲洪积,566条) + default: unconsolidated + +landuse: + description: "土地利用类型" + unit: "分类代码" + # 数据库实际编码(GLC FCS30 分类体系) + mapping: + 10: forest # 林地(377条) + 30: farmland # 农田(190条) + 40: urban # 城市(105条) + 50: water # 水域(505条) + 60: barren # 裸地(23条) + 80: farmland # 耕地(1条,合并入农田) + default: farmland + +terrain: + description: "地形分类(中国地形分类体系)" + unit: "分类代码" + # 数据库实际编码(xian_landform 表 value 字段,来源:pg_description) + # 工程地质分组:按坡度 + 地貌特征合并 + mapping: + 1: mountain # 断裂山麓地带(秦岭北麓,276条) + 2: plain # 平坦平原(渭河平原,218条) + 3: deep_valley # 高山深峡谷(秦岭腹地,11条) + 4: hill # 丘陵(黄土塬,250条) + 5: gentle_hill # 低缓丘陵(塬边过渡带,86条) + 6: low_mountain # 低山(骊山等,261条) + 7: flat_plain # 平缓平原(冲积平原,99条) + default: hill + +impervious: + description: "不透水面" + unit: "比例" + bins: [0, 0.3, 0.6, 1.0] + labels: [low, medium, high] + +ndvi: + description: "植被指数" + unit: "NDVI值" + bins: [-1, 0, 0.1, 0.3, 0.5, 0.8, 1.0] + labels: [water, bare, sparse, moderate, dense, very_dense] + +sand_content: + description: "土壤含沙量" + unit: "百分比" + bins: [0, 20, 40, 100] + labels: [low, medium, high] + +ph: + description: "土壤PH值" + unit: "PH值" + bins: [0, 6.5, 7.5, 14] + labels: [acidic, neutral, alkaline] + +soil_moisture: + description: "土壤湿度" + unit: "百分比" + bins: [0, 20, 40, 80, 100] + labels: [dry, moist, wet, saturated] + +organic_carbon: + description: "有机碳" + unit: "百分比" + bins: [0, 1, 2, 100] + labels: [low, medium, high] + +dist_to_river: + description: "距离河道距离" + unit: "米" + bins: [0, 50, 200, 500, 99999] + labels: [very_close, close, moderate, far] + +dist_to_fault: + description: "距离断裂带距离" + unit: "米" + bins: [0, 500, 1500, 3000, 99999] + labels: [very_close, close, moderate, far] + +pipe_density: + description: "供水管网密度" + unit: "m/m²" + # 默认规则 + default: + bins: [0, 0.001, 0.01, 0.05, 99999] + labels: [none, low, medium, high] + # 区域覆盖规则 + region_overrides: + 610100: # 西安市 + bins: [0, 0.002, 0.015, 0.04, 99999] + labels: [none, low, medium, high] diff --git a/app/config/dbn/earthquake_cpt_params.yaml b/app/config/dbn/earthquake_cpt_params.yaml new file mode 100644 index 0000000..f0e2450 --- /dev/null +++ b/app/config/dbn/earthquake_cpt_params.yaml @@ -0,0 +1,266 @@ +# ================================================================ +# 地震灾害链 条件概率表(CPT)配置 +# ================================================================ +# 地震触发地质灾害:滑坡、泥石流、崩塌 +# 参考: +# - Keefer (1984) Landslides caused by earthquakes +# - Rodriguez et al. (1999) Earthquake-induced landslides +# - 1556年华县M8.0地震诱发滑坡史料 +# - GB 18306-2015 中国地震动参数区划图 + +# 推理配置 +match_strategy: first +default_strategy: use_default + +# ============================================ +# 触发层(地震参数先验概率) +# ============================================ +# 主要发震构造:秦岭北缘断裂(M7.0+)、渭河断裂(M6.5+) +# 历史最大地震:1556年华县M8.0(距西安约80km) + +magnitude: + type: prior + # 西安地区地震危险性分析: + # - M<4.0(minor):频繁但无显著地质灾害 + # - M4.0-4.9(light):偶发,轻微地质灾害 + # - M5.0-5.9(moderate):可能触发近场滑坡 + # - M6.0-6.9(strong):显著地质灾害 + # - M7.0-7.9(major):区域性地质灾害 + # - M≥8.0(great):灾难性(如1556年华县地震) + # [minor, light, moderate, strong, major, great] + probabilities: [0.40, 0.25, 0.18, 0.10, 0.05, 0.02] + +epicenter_distance: + type: prior + # 西安地区可能的震中距分布: + # - <30km(very_near):本地断裂发震 + # - 30-100km(near):渭河断裂、秦岭北缘断裂 + # - 100-300km(moderate):汾渭地震带 + # - >300km(far):远场地震 + # [very_near, near, moderate, far] + probabilities: [0.10, 0.25, 0.35, 0.30] + +seismic_intensity: + type: prior + # 西安地区地震烈度分布: + # - I-V(minor):无显著地质灾害 + # - VI-VII(light):轻微地质灾害 + # - VIII-IX(moderate):显著地质灾害 + # - X-XII(severe):严重地质灾害 + # > XII(extreme):灾难性(1556年华县地震西安烈度约IX-X) + # [minor, light, moderate, severe, extreme] + probabilities: [0.35, 0.30, 0.20, 0.10, 0.05] + +# ============================================ +# 灾害层(条件概率) +# 规则匹配策略:首条匹配,从上到下扫描,命中即返回 +# 高风险规则在前,低风险兜底规则在后 +# ============================================ + +# ---------------------------------------- +# 滑坡(Landslide)- 地震触发 +# 西安特征: +# - 黄土滑坡:黄土台塬塬边(白鹿塬、神禾塬、少陵塬),地震时黄土震陷 +# - 岩质滑坡:秦岭北坡变质岩/花岗岩区,节理面在地震荷载下失稳 +# - 工程滑坡:城区开挖坡脚,地震加剧变形 +# - 断裂带控制:秦岭北缘断裂沿线近场效应显著 +# - 1556年华县地震:触发了渭河平原大规模黄土滑坡 +# ---------------------------------------- +landslide: + type: conditional + parents: [magnitude, epicenter_distance, slope, lithology, soil_moisture, dist_to_fault, ndvi, soil_type, landuse, terrain] + default_probability: 0.02 + rules: + # === 极高风险(≥0.70)=== + # 大地震+近场+陡坡:最大规模地震滑坡 + # 参考:M7.0+近场(<30km)陡坡区滑坡概率>80%(Keefer 1984) + - condition: {magnitude: [major, great], epicenter_distance: [very_near], slope: [steep, very_steep, extreme_steep]} + probability: 0.85 + # 大地震+近场+饱和土体:黄土震陷+液化 + - condition: {magnitude: [major, great], epicenter_distance: [very_near], soil_moisture: [wet, saturated]} + probability: 0.82 + # 强震+近场+陡坡+断裂带附近:近场效应+岩体破碎 + - condition: {magnitude: [strong, major, great], epicenter_distance: [very_near], slope: [steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.80 + # 大地震+近场+松散堆积物:黄土滑坡(1556年华县地震典型场景) + - condition: {magnitude: [major, great], epicenter_distance: [very_near, near], lithology: [unconsolidated, mixed_clastic]} + probability: 0.78 + + # === 高风险(0.50-0.65)=== + # 强震+近场+陡坡 + - condition: {magnitude: [strong, major], epicenter_distance: [very_near, near], slope: [steep, very_steep, extreme_steep]} + probability: 0.65 + # 大地震+中距+陡坡+断裂带 + - condition: {magnitude: [major, great], epicenter_distance: [near, moderate], slope: [steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.62 + # 强震+中陡坡+松散堆积物:黄土塬边滑坡 + - condition: {magnitude: [strong, major], epicenter_distance: [very_near, near], slope: [moderate, steep, very_steep, extreme_steep], lithology: [unconsolidated]} + probability: 0.60 + # 强震+陡坡+饱和土体 + - condition: {magnitude: [strong, major], slope: [steep, very_steep, extreme_steep], soil_moisture: [wet, saturated]} + probability: 0.58 + # 大地震+近场+丘陵地形:黄土塬边 + - condition: {magnitude: [major, great], epicenter_distance: [very_near, near], terrain: [hill, gentle_hill]} + probability: 0.55 + # 强震+近场+初育土(黄土) + - condition: {magnitude: [strong, major], epicenter_distance: [very_near, near], soil_type: [entisol]} + probability: 0.52 + + # === 中风险(0.20-0.40)=== + # 中强震+近场+陡坡 + - condition: {magnitude: [moderate, strong], epicenter_distance: [very_near, near], slope: [steep, very_steep, extreme_steep]} + probability: 0.40 + # 强震+中距+陡坡 + - condition: {magnitude: [strong, major], epicenter_distance: [near, moderate], slope: [steep, very_steep, extreme_steep]} + probability: 0.35 + # 强震+中坡+松散堆积物 + - condition: {magnitude: [strong, major], slope: [moderate, steep], lithology: [unconsolidated]} + probability: 0.30 + # 大地震+远距(仍有影响,1556年华县地震影响范围>300km) + - condition: {magnitude: [major, great], epicenter_distance: [moderate, far], slope: [steep, very_steep, extreme_steep]} + probability: 0.28 + # 断裂带+陡坡(即使中小地震也有蠕变风险) + - condition: {dist_to_fault: [very_close], slope: [steep, very_steep, extreme_steep]} + probability: 0.25 + # 中强震+中坡 + - condition: {magnitude: [moderate, strong], slope: [moderate, steep]} + probability: 0.22 + + # === 低风险兜底(≤0.05)=== + - condition: {magnitude: [minor]} + probability: 0.02 + - condition: {epicenter_distance: [far]} + probability: 0.02 + - condition: {slope: [flat, gentle]} + probability: 0.01 + +# ---------------------------------------- +# 泥石流(Debris Flow)- 地震触发 +# 西安特征: +# - 秦岭北麓沟道:地震松动物源→后续降雨触发泥石流 +# - 地震直接触发:强震+陡坡+松散物源可直接启动泥石流 +# - 震后效应:地震后数年内泥石流频率显著增加(物源松动) +# - 典型场景:M6.0+秦岭北坡沟道区 +# ---------------------------------------- +debris_flow: + type: conditional + parents: [magnitude, epicenter_distance, slope, elevation, lithology, sand_content, soil_moisture, dist_to_fault, ndvi] + default_probability: 0.01 + rules: + # === 极高风险(≥0.70)=== + # 大地震+近场+陡坡+高含沙量:物源充足+强震动 + - condition: {magnitude: [major, great], epicenter_distance: [very_near], slope: [steep, very_steep, extreme_steep], sand_content: [medium, high]} + probability: 0.80 + # 大地震+近场+中高山区+松散堆积物:秦岭北麓沟道 + - condition: {magnitude: [major, great], epicenter_distance: [very_near, near], elevation: [mid_mountain, high_mountain], lithology: [unconsolidated, mixed_clastic]} + probability: 0.78 + # 强震+近场+陡坡+断裂带:破碎岩体提供物源 + - condition: {magnitude: [strong, major, great], epicenter_distance: [very_near], slope: [steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.75 + # 大地震+饱和+陡坡:液化+滑坡→泥石流 + - condition: {magnitude: [major, great], soil_moisture: [wet, saturated], slope: [steep, very_steep, extreme_steep]} + probability: 0.72 + + # === 高风险(0.50-0.65)=== + # 强震+近场+陡坡 + - condition: {magnitude: [strong, major], epicenter_distance: [very_near, near], slope: [steep, very_steep, extreme_steep]} + probability: 0.62 + # 强震+中高山区+松散堆积物 + - condition: {magnitude: [strong, major], elevation: [mid_mountain, high_mountain], lithology: [unconsolidated, mixed_clastic]} + probability: 0.58 + # 大地震+中距+陡坡+高含沙量 + - condition: {magnitude: [major, great], epicenter_distance: [near, moderate], slope: [steep, very_steep, extreme_steep], sand_content: [medium, high]} + probability: 0.55 + # 强震+陡坡+松散堆积物 + - condition: {magnitude: [strong, major], slope: [steep, very_steep, extreme_steep], lithology: [unconsolidated, mixed_clastic]} + probability: 0.52 + + # === 中风险(0.20-0.40)=== + # 中强震+近场+陡坡 + - condition: {magnitude: [moderate, strong], epicenter_distance: [very_near, near], slope: [steep, very_steep, extreme_steep]} + probability: 0.38 + # 强震+中高山区(物源区本身不稳定) + - condition: {magnitude: [strong, major], elevation: [mid_mountain, high_mountain], slope: [moderate, steep]} + probability: 0.30 + # 大地震+远距+陡坡 + - condition: {magnitude: [major, great], epicenter_distance: [moderate, far], slope: [steep, very_steep, extreme_steep]} + probability: 0.28 + # 断裂带+陡坡+松散堆积物(无强震也有蠕变崩塌→物源积累) + - condition: {dist_to_fault: [very_close], slope: [steep, very_steep, extreme_steep], lithology: [unconsolidated]} + probability: 0.22 + + # === 低风险兜底(≤0.05)=== + - condition: {magnitude: [minor]} + probability: 0.01 + - condition: {slope: [flat, gentle]} + probability: 0.01 + - condition: {elevation: [basin, plain_urban]} + probability: 0.01 + +# ---------------------------------------- +# 崩塌(Collapse)- 地震触发 +# 西安特征: +# - 黄土崩塌:渭河、灞河、浐河侧蚀黄土塬边+地震触发 +# - 岩质崩塌:秦岭北坡陡崖(>45°),花岗岩/变质岩节理面失稳 +# - 崩塌是地震触发最敏感的地质灾害类型 +# - 1556年华县地震:秦岭北坡大规模崩塌 +# - 断裂带控制:秦岭北缘断裂沿线崩塌密度最高 +# ---------------------------------------- +collapse: + type: conditional + parents: [magnitude, epicenter_distance, slope, lithology, dist_to_fault, dist_to_river, soil_moisture] + default_probability: 0.02 + rules: + # === 极高风险(≥0.70)=== + # 大地震+近场+极陡坡+硬岩(花岗岩/变质岩):节理面失稳 + - condition: {magnitude: [major, great], epicenter_distance: [very_near], slope: [very_steep, extreme_steep], lithology: [acid_rock, metamorphic, basic_rock]} + probability: 0.88 + # 大地震+近场+陡坡+断裂带:岩体极度破碎 + - condition: {magnitude: [major, great], epicenter_distance: [very_near], slope: [steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.85 + # 大地震+近场+松散堆积物+陡坡:黄土塬边崩塌 + - condition: {magnitude: [major, great], epicenter_distance: [very_near], slope: [steep, very_steep, extreme_steep], lithology: [unconsolidated, terrigenous, mixed_clastic]} + probability: 0.80 + # 强震+近场+极陡坡 + - condition: {magnitude: [strong, major], epicenter_distance: [very_near], slope: [very_steep, extreme_steep]} + probability: 0.75 + + # === 高风险(0.50-0.65)=== + # 强震+近场+陡坡+断裂带 + - condition: {magnitude: [strong, major], epicenter_distance: [very_near, near], slope: [steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.68 + # 大地震+中距+陡坡+硬岩 + - condition: {magnitude: [major, great], epicenter_distance: [near, moderate], slope: [steep, very_steep, extreme_steep], lithology: [acid_rock, metamorphic]} + probability: 0.62 + # 强震+近场+陡坡+松散堆积物:黄土崩塌 + - condition: {magnitude: [strong, major], epicenter_distance: [very_near, near], slope: [steep, very_steep, extreme_steep], lithology: [unconsolidated]} + probability: 0.60 + # 近河道+陡坡+松散堆积物+地震:河流侧蚀+地震触发 + - condition: {dist_to_river: [very_close, close], slope: [steep, very_steep, extreme_steep], lithology: [unconsolidated], magnitude: [strong, major, great]} + probability: 0.58 + # 强震+中陡坡+断裂带 + - condition: {magnitude: [strong, major], slope: [moderate, steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.55 + + # === 中风险(0.20-0.40)=== + # 中强震+近场+陡坡 + - condition: {magnitude: [moderate, strong], epicenter_distance: [very_near, near], slope: [steep, very_steep, extreme_steep]} + probability: 0.40 + # 强震+中距+陡坡 + - condition: {magnitude: [strong, major], epicenter_distance: [near, moderate], slope: [steep, very_steep, extreme_steep]} + probability: 0.35 + # 近河道+中坡+松散堆积物(无强震也有侧蚀崩塌) + - condition: {dist_to_river: [very_close, close], lithology: [unconsolidated], slope: [moderate, steep]} + probability: 0.30 + # 断裂带+陡坡(无强震也有蠕变崩塌) + - condition: {dist_to_fault: [very_close], slope: [steep, very_steep, extreme_steep]} + probability: 0.28 + # 大地震+远距+陡坡 + - condition: {magnitude: [major, great], epicenter_distance: [moderate, far], slope: [steep, very_steep, extreme_steep]} + probability: 0.25 + + # === 低风险兜底(≤0.05)=== + - condition: {magnitude: [minor]} + probability: 0.01 + - condition: {slope: [flat, gentle]} + probability: 0.01 diff --git a/app/config/dbn/earthquake_dbn_graph.yaml b/app/config/dbn/earthquake_dbn_graph.yaml new file mode 100644 index 0000000..185b85d --- /dev/null +++ b/app/config/dbn/earthquake_dbn_graph.yaml @@ -0,0 +1,168 @@ +# 地震灾害链 DBN 图结构配置 +# 定义节点、各层节点列表、父子关系 +# 地震触发:滑坡、泥石流、崩塌 + +# 推理配置 +inference_config: + match_strategy: first # first=首条匹配(规则优先级从上到下),max=取最大概率 + default_strategy: use_default # 无规则匹配时使用 default_probability + max_cpt_parents: 20 # CPT 最大父节点数 + +layers: + # 触发层(3个节点) + trigger: + - magnitude # 震级(Richter scale) + - epicenter_distance # 震中距(km) + - seismic_intensity # 地震烈度(中国烈度表 I-XII) + + # 环境层(14个节点) + # 去掉暴雨模型中的 impervious(不透水面)和 pipe_density(管网密度) + # 这两个因子主要影响城市内涝,与地震地质灾害关系不大 + environment: + - elevation # 高程 + - slope # 坡度 + - aspect # 坡向 + - soil_type # 土壤分类 + - lithology # 岩性 + - landuse # 土地利用类型 + - terrain # 地形分类 + - ndvi # 植被指数 + - sand_content # 土壤含沙量 + - ph # 土壤PH值 + - soil_moisture # 土壤湿度 + - organic_carbon # 有机碳 + - dist_to_river # 距离河道距离 + - dist_to_fault # 距离断裂带距离 + + # 灾害层(3个节点) + # 地震触发的地质灾害:滑坡、泥石流、崩塌 + # 不含山洪(flash_flood)和内涝(waterlogging),这两个主要由暴雨触发 + hazard: + - landslide # 滑坡 + - debris_flow # 泥石流 + - collapse # 崩塌 + +edges: + # ============================================ + # 触发层 → 灾害层 + # 地震参数直接影响所有地质灾害类型 + # ============================================ + + # 震级 → 灾害 + - [magnitude, landslide] + - [magnitude, debris_flow] + - [magnitude, collapse] + + # 震中距 → 灾害 + - [epicenter_distance, landslide] + - [epicenter_distance, debris_flow] + - [epicenter_distance, collapse] + + # 地震烈度 → 灾害 + - [seismic_intensity, landslide] + - [seismic_intensity, debris_flow] + - [seismic_intensity, collapse] + + # ============================================ + # 环境层 → 灾害层 + # 场地条件影响地震波放大效应和地质灾害易发性 + # ============================================ + + # 高程影响泥石流 + # - 泥石流:高程反映沟道纵坡和物源区高差(秦岭北麓800-1500m高发) + - [elevation, debris_flow] + + # 坡度影响滑坡、泥石流、崩塌(最核心的场地因子) + # - 滑坡:Newmark位移与坡度直接相关 + # - 泥石流:沟道纵坡决定物源运移能力 + # - 崩塌:陡坡+地震是崩塌的典型触发条件 + - [slope, landslide] + - [slope, debris_flow] + - [slope, collapse] + + # 坡向影响滑坡 + # - 阳坡冻融风化强烈,岩体更破碎,地震时更易失稳 + - [aspect, landslide] + + # 土壤类型影响滑坡、泥石流 + # - 初育土(黄土)地震液化和震陷风险高 + - [soil_type, landslide] + - [soil_type, debris_flow] + + # 岩性影响滑坡、泥石流、崩塌(关键地质因子) + # - 松散堆积物(黄土):地震时易发生震陷和液化 + # - 变质岩/花岗岩:节理发育时崩塌风险高 + # - 碎屑岩:软硬互层界面是滑动面 + - [lithology, landslide] + - [lithology, debris_flow] + - [lithology, collapse] + + # 土地利用类型影响滑坡 + # - 城市:工程开挖坡脚/弃土加载降低边坡稳定性 + # - 农田:梯田改造改变坡体应力状态 + - [landuse, landslide] + + # 地形分类影响滑坡、泥石流 + # - 山地/深谷:地震波放大效应显著 + # - 丘陵:黄土塬边地震滑坡 + - [terrain, landslide] + - [terrain, debris_flow] + + # 植被指数影响滑坡、泥石流 + # - 根系加固作用:植被好→滑坡风险降低 + # - 但地震烈度高时植被保护作用有限 + - [ndvi, landslide] + - [ndvi, debris_flow] + + # 土壤含沙量影响泥石流 + # - 高含沙量→松散物源丰富→地震触发泥石流 + - [sand_content, debris_flow] + + # 土壤湿度影响滑坡、泥石流 + # - 饱和土体在地震荷载下更易发生液化和失稳 + # - 孔隙水压力升高降低有效应力 + - [soil_moisture, landslide] + - [soil_moisture, debris_flow] + + # 距离河道距离影响泥石流、崩塌 + # - 泥石流:沟道物源供给 + # - 崩塌:河流侧蚀黄土塬边 + 地震触发 + - [dist_to_river, debris_flow] + - [dist_to_river, collapse] + + # 距离断裂带距离影响滑坡、崩塌、泥石流 + # - 发震断裂附近地震动强度大(近场效应) + # - 断裂带岩体破碎,地震时更易失稳 + # - 西安:秦岭北缘断裂、渭河断裂为主要发震构造 + - [dist_to_fault, landslide] + - [dist_to_fault, collapse] + - [dist_to_fault, debris_flow] + +# 节点状态定义 +# 与 discretization.yaml 保持一致 +node_states: + # 触发层(地震参数) + magnitude: [minor, light, moderate, strong, major, great] + epicenter_distance: [very_near, near, moderate, far] + seismic_intensity: [minor, light, moderate, severe, extreme] + + # 环境层(与暴雨模型共享,状态名一致) + elevation: [basin, plain_urban, transition, low_mountain, mid_mountain, high_mountain] + slope: [flat, gentle, moderate, steep, very_steep, extreme_steep] + aspect: [north, east, south, west, north_loop] + soil_type: [ultisol, entisol, fluvo_aquic, yellow_brown] + lithology: [acid_rock, basic_rock, carbonate, metamorphic, mixed_clastic, terrigenous, unconsolidated] + landuse: [forest, farmland, urban, water, barren] + terrain: [mountain, plain, deep_valley, hill, gentle_hill, low_mountain, flat_plain] + ndvi: [water, bare, sparse, moderate, dense, very_dense] + sand_content: [low, medium, high] + ph: [acidic, neutral, alkaline] + soil_moisture: [dry, moist, wet, saturated] + organic_carbon: [low, medium, high] + dist_to_river: [very_close, close, moderate, far] + dist_to_fault: [very_close, close, moderate, far] + + # 灾害层 + landslide: [none, low, medium, high, very_high] + debris_flow: [none, low, medium, high, very_high] + collapse: [none, low, medium, high, very_high] diff --git a/app/config/dbn/rainfall_cpt_params.yaml b/app/config/dbn/rainfall_cpt_params.yaml new file mode 100644 index 0000000..713e1cc --- /dev/null +++ b/app/config/dbn/rainfall_cpt_params.yaml @@ -0,0 +1,362 @@ +# ================================================================ +# 条件概率表(CPT)配置 +# ================================================================ + +# 推理配置 +match_strategy: first # 首条匹配(规则优先级从上到下,max为最大匹配) +default_strategy: use_default # 无匹配时使用 default_probability + +# ============================================ +# 触发层 +# ============================================ + +rain_intensity: + type: prior + # 西安气象站统计:无雨~小雨占60%,中雨~大雨25%,暴雨+15% + # [无雨, 小雨, 中雨, 大雨, 暴雨, 大暴雨, 特大暴雨] + probabilities: [0.35, 0.25, 0.15, 0.10, 0.08, 0.05, 0.02] + +duration: + type: prior + # 西安暴雨特征:短历时(<3h)占多数,长历时(>12h)与华西秋雨相关 + # [短, 中, 长] + probabilities: [0.50, 0.30, 0.20] + +accum_rain: + type: prior + # 西安年均降水550-700mm,汛期集中60-70% + # [微量, 小雨, 中雨, 大雨, 极端] + probabilities: [0.30, 0.25, 0.20, 0.15, 0.10] + +# ============================================ +# 灾害层(条件概率) +# 规则匹配策略:首条匹配,从上到下扫描,命中即返回 +# 高风险规则在前,低风险兜底规则在后 +# ============================================ + +# ---------------------------------------- +# 滑坡(Landslide) +# 西安特征: +# - 黄土滑坡:黄土台塬塬边(白鹿塬、神禾塬、少陵塬),坡度15-35° +# - 岩质滑坡:秦岭北坡变质岩/花岗岩区,坡度>25° +# - 工程滑坡:城区开挖坡脚(地铁/道路建设)、弃土加载 +# - 触发因素:前期降雨饱和+短历时高强度暴雨 +# - 断裂带控制:秦岭北缘断裂沿线滑坡密度最高 +# ---------------------------------------- +landslide: + type: conditional + parents: [rain_intensity, duration, slope, soil_type, lithology, ndvi, soil_moisture, aspect, dist_to_river, dist_to_fault, landuse] + default_probability: 0.02 + rules: + # === 极高风险(≥0.70)=== + # 暴雨+陡坡+前期饱和:秦岭北坡典型触发条件 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [steep, very_steep, extreme_steep], soil_moisture: [wet, saturated]} + probability: 0.75 + # 暴雨+陡坡+断裂带附近:秦岭北缘断裂沿线,岩体破碎 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.72 + # 长历时暴雨+陡坡+饱和:持续降雨导致深层渗流 + - condition: {duration: [long], rain_intensity: [heavy, storm, downpour, extreme], slope: [steep, very_steep, extreme_steep], soil_moisture: [wet, saturated]} + probability: 0.70 + + # === 高风险(0.50-0.65)=== + # 暴雨+中陡坡:黄土塬边典型触发条件 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [moderate, steep, very_steep, extreme_steep]} + probability: 0.60 + # 大雨+陡坡+松散堆积物:黄土滑坡物质条件充分 + - condition: {rain_intensity: [heavy, storm, downpour], slope: [steep, very_steep, extreme_steep], lithology: [unconsolidated]} + probability: 0.58 + # 暴雨+中陡坡+断裂带附近 + - condition: {rain_intensity: [heavy, storm, downpour], slope: [moderate, steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.55 + # 城区开挖坡脚/弃土加载+暴雨:工程滑坡 + - condition: {landuse: [urban], slope: [moderate, steep, very_steep, extreme_steep], rain_intensity: [heavy, storm, downpour, extreme]} + probability: 0.52 + # 大雨+陡坡+初育土(黄土):黄土塬边滑坡 + - condition: {rain_intensity: [heavy, storm, downpour], slope: [steep, very_steep, extreme_steep], soil_type: [entisol]} + probability: 0.50 + + # === 中风险(0.20-0.40)=== + # 大雨+中坡+饱和 + - condition: {rain_intensity: [heavy], slope: [moderate, steep], soil_moisture: [wet, saturated]} + probability: 0.40 + # 中雨+陡坡 + - condition: {rain_intensity: [moderate, heavy], slope: [steep, very_steep, extreme_steep]} + probability: 0.35 + # 暴雨+缓坡+松散堆积物:塬面黄土 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [gentle, moderate], lithology: [unconsolidated]} + probability: 0.30 + # 断裂带+陡坡(无强降雨也有基岩蠕变风险) + - condition: {dist_to_fault: [very_close], slope: [steep, very_steep, extreme_steep]} + probability: 0.25 + # 城区+中坡:工程扰动 + - condition: {landuse: [urban], slope: [moderate, steep]} + probability: 0.20 + + # === 低风险兜底(≤0.05)=== + - condition: {rain_intensity: [no_rain, light]} + probability: 0.02 + - condition: {slope: [flat, gentle]} + probability: 0.02 + +# ---------------------------------------- +# 泥石流(Debris Flow) +# 西安特征: +# - 集中在秦岭北麓沟道(周至、户县、长安、蓝田) +# - 物源:秦岭北缘断裂破碎带+坡面松散堆积物+崩塌堆积体 +# - 沟道纵坡大(>15°),高差500-2000m,势能充足 +# - 触发:短历时高强度暴雨(3h降雨>50mm) +# - 高发区海拔800-1500m(中山带,物源最丰富) +# ---------------------------------------- +debris_flow: + type: conditional + parents: [rain_intensity, duration, slope, soil_type, lithology, sand_content, ndvi, soil_moisture, dist_to_river, dist_to_fault, elevation] + default_probability: 0.02 + rules: + # === 极高风险(≥0.70)=== + # 暴雨+陡坡+高含沙量:典型泥石流触发条件 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [steep, very_steep, extreme_steep], sand_content: [medium, high]} + probability: 0.80 + # 暴雨+陡坡+断裂带附近:破碎岩体提供丰富物源 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.75 + # 暴雨+中高山区+松散堆积物:秦岭北麓沟道泥石流 + - condition: {rain_intensity: [storm, downpour, extreme], elevation: [mid_mountain, high_mountain], lithology: [unconsolidated, mixed_clastic]} + probability: 0.75 + # 长历时暴雨+陡坡+饱和:深层渗透触发 + - condition: {duration: [long], rain_intensity: [heavy, storm, downpour, extreme], slope: [steep, very_steep, extreme_steep], soil_moisture: [wet, saturated]} + probability: 0.72 + + # === 高风险(0.50-0.65)=== + # 暴雨+中陡坡:沟道汇流 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [moderate, steep, very_steep, extreme_steep]} + probability: 0.60 + # 大雨+陡坡+高含沙量 + - condition: {rain_intensity: [heavy, storm, downpour], slope: [steep, very_steep, extreme_steep], sand_content: [medium, high]} + probability: 0.55 + # 中高山区+中等降雨:物源区本身不稳定 + - condition: {elevation: [mid_mountain, high_mountain], slope: [moderate, steep, very_steep, extreme_steep], rain_intensity: [heavy, storm, downpour, extreme]} + probability: 0.55 + # 松散堆积物+陡坡+大雨:黄土泥石流 + - condition: {lithology: [unconsolidated, mixed_clastic], slope: [steep, very_steep, extreme_steep], rain_intensity: [heavy, storm, downpour, extreme]} + probability: 0.52 + + # === 中风险(0.20-0.40)=== + # 大雨+中坡 + - condition: {rain_intensity: [moderate, heavy], slope: [moderate, steep]} + probability: 0.30 + # 中高山区+中等降雨:物源区稳定性差 + - condition: {elevation: [mid_mountain, high_mountain], slope: [moderate, steep], rain_intensity: [moderate, heavy]} + probability: 0.28 + # 暴雨+缓坡+松散堆积物 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [gentle, moderate], lithology: [unconsolidated]} + probability: 0.25 + # 断裂带+陡坡(无强降雨,崩塌堆积物也可启动) + - condition: {dist_to_fault: [very_close], slope: [steep, very_steep, extreme_steep]} + probability: 0.22 + + # === 低风险兜底(≤0.05)=== + - condition: {rain_intensity: [no_rain, light]} + probability: 0.01 + - condition: {slope: [flat, gentle]} + probability: 0.02 + - condition: {elevation: [basin, plain_urban]} + probability: 0.02 + +# ---------------------------------------- +# 山洪(Flash Flood) +# 西安特征: +# - 秦岭北坡溪流:短沟、陡坡、汇流快,暴雨后1-2h即达峰值 +# - 城区河流(灞河、浐河、沣河、涝河):暴雨叠加城市径流 +# - 渭河平原低洼区:排水不畅+河道顶托 +# - 关键控制因子:降雨强度、前期土壤湿度、地形坡度 +# - 典型事件:2016年7月长安区秦岭北坡山洪,2021年8月蓝田山洪 +# ---------------------------------------- +flash_flood: + type: conditional + parents: [rain_intensity, duration, accum_rain, elevation, terrain, dist_to_river, soil_moisture] + default_probability: 0.04 + rules: + # === 极高风险(≥0.80)=== + # 暴雨+饱和土壤+近河道:入渗率接近零,全部转为地表径流 + - condition: {rain_intensity: [storm, downpour, extreme], soil_moisture: [saturated], dist_to_river: [very_close, close]} + probability: 0.90 + # 暴雨+饱和+山地地形:秦岭北坡典型山洪条件 + - condition: {rain_intensity: [storm, downpour, extreme], soil_moisture: [wet, saturated], terrain: [mountain, deep_valley, low_mountain]} + probability: 0.85 + # 大暴雨+近河道+低洼区:渭河平原洪水 + - condition: {rain_intensity: [downpour, extreme], dist_to_river: [very_close, close], elevation: [basin, plain_urban]} + probability: 0.82 + + # === 高风险(0.55-0.75)=== + # 暴雨+近河道 + - condition: {rain_intensity: [storm, downpour, extreme], dist_to_river: [very_close, close]} + probability: 0.70 + # 暴雨+山地地形(远离河道但汇流快) + - condition: {rain_intensity: [storm, downpour, extreme], terrain: [mountain, deep_valley, low_mountain]} + probability: 0.65 + # 大雨+饱和土壤+近河道 + - condition: {rain_intensity: [heavy, storm, downpour], soil_moisture: [wet, saturated], dist_to_river: [very_close, close]} + probability: 0.60 + # 长历时暴雨+山地:持续汇流 + - condition: {duration: [long], rain_intensity: [heavy, storm, downpour, extreme], terrain: [mountain, deep_valley, low_mountain]} + probability: 0.58 + # 极端累计降雨+山地地形 + - condition: {accum_rain: [extreme], terrain: [mountain, deep_valley, low_mountain]} + probability: 0.55 + + # === 中风险(0.25-0.45)=== + # 大雨+近河道 + - condition: {rain_intensity: [heavy], dist_to_river: [very_close, close]} + probability: 0.45 + # 暴雨+湿土(非饱和) + - condition: {rain_intensity: [storm, downpour, extreme], soil_moisture: [wet]} + probability: 0.40 + # 大雨+山地地形 + - condition: {rain_intensity: [heavy], terrain: [mountain, deep_valley, low_mountain]} + probability: 0.35 + # 中雨+饱和+近河道 + - condition: {rain_intensity: [moderate, heavy], soil_moisture: [saturated], dist_to_river: [very_close, close]} + probability: 0.40 + # 极端累计+低洼区 + - condition: {accum_rain: [extreme], elevation: [basin, plain_urban]} + probability: 0.35 + # 暴雨+缓坡+近河道(平原河道溢出) + - condition: {rain_intensity: [storm, downpour, extreme], dist_to_river: [very_close], terrain: [plain, flat_plain]} + probability: 0.30 + + # === 低风险兜底(≤0.05)=== + - condition: {rain_intensity: [no_rain, light]} + probability: 0.01 + - condition: {dist_to_river: [far]} + probability: 0.03 + +# ---------------------------------------- +# 内涝(Waterlogging) +# 西安特征: +# - 主城区(碑林、莲湖、新城):不透水面率>60%,老城区管网密度不足 +# - 高新区/经开区:新建区管网较好,但不透水面率高 +# - 渭河平原低洼区:排水出口受河道水位顶托 +# - 典型事件:2016年7月城区内涝(1h降雨46mm),2018年7月暴雨内涝 +# - 关键控制因子:降雨强度+不透水面+管网密度+地形低洼 +# ---------------------------------------- +waterlogging: + type: conditional + parents: [rain_intensity, duration, accum_rain, elevation, landuse, impervious, soil_moisture, dist_to_river, pipe_density] + default_probability: 0.06 + rules: + # === 极高风险(≥0.80)=== + # 暴雨+高不透水面+管网不足:老城区典型内涝条件 + - condition: {rain_intensity: [storm, downpour, extreme], impervious: [high], pipe_density: [none, low]} + probability: 0.90 + # 极端累计降雨+长历时+高不透水面:持续强降雨 + - condition: {accum_rain: [heavy, extreme], duration: [long], impervious: [medium, high]} + probability: 0.82 + # 极端累计+低洼区+管网不足:积水无处排出 + - condition: {accum_rain: [heavy, extreme], elevation: [basin, plain_urban], pipe_density: [none, low]} + probability: 0.80 + + # === 高风险(0.55-0.75)=== + # 暴雨+高不透水面 + - condition: {rain_intensity: [storm, downpour, extreme], impervious: [high]} + probability: 0.72 + # 大雨+长历时+高不透水面 + - condition: {rain_intensity: [heavy], duration: [long], impervious: [high]} + probability: 0.65 + # 暴雨+城市用地+管网不足 + - condition: {rain_intensity: [storm, downpour, extreme], landuse: [urban], pipe_density: [none, low]} + probability: 0.68 + # 大雨+中高不透水面 + - condition: {rain_intensity: [heavy, storm, downpour], impervious: [medium, high]} + probability: 0.60 + # 极端累计+低洼区(不透水面不高也会积水) + - condition: {accum_rain: [extreme], elevation: [basin, plain_urban]} + probability: 0.58 + # 饱和土壤+暴雨+低洼区:入渗饱和后地表积水 + - condition: {soil_moisture: [saturated], rain_intensity: [heavy, storm, downpour, extreme], elevation: [basin, plain_urban]} + probability: 0.55 + + # === 中风险(0.25-0.45)=== + # 大雨+中高不透水面 + - condition: {rain_intensity: [moderate, heavy], impervious: [medium, high]} + probability: 0.40 + # 中等累计+管网不足 + - condition: {accum_rain: [moderate, heavy], pipe_density: [none, low], impervious: [medium]} + probability: 0.38 + # 暴雨+低不透水面(非城区但排水不畅) + - condition: {rain_intensity: [storm, downpour, extreme], impervious: [low]} + probability: 0.30 + # 近河道+暴雨:排水出口受河道水位顶托 + - condition: {dist_to_river: [very_close, close], rain_intensity: [heavy, storm, downpour, extreme], elevation: [basin, plain_urban]} + probability: 0.35 + # 农田积水:低洼农田排水不畅 + - condition: {landuse: [farmland], rain_intensity: [heavy, storm, downpour, extreme], elevation: [basin, plain_urban]} + probability: 0.30 + + # === 低风险兜底(≤0.05)=== + # 老城区管网堵塞可在无雨时积水 + - condition: {rain_intensity: [no_rain], pipe_density: [none, low], landuse: [urban]} + probability: 0.05 + - condition: {rain_intensity: [no_rain, light]} + probability: 0.02 + - condition: {impervious: [low], pipe_density: [medium, high]} + probability: 0.03 + +# ---------------------------------------- +# 崩塌(Collapse) +# 西安特征: +# - 黄土崩塌:渭河、灞河、浐河、沣河侧蚀黄土塬边,形成陡坎 +# - 岩质崩塌:秦岭北坡陡崖(>45°),节理发育的花岗岩/变质岩 +# - 工程崩塌:道路切坡、采石场边坡 +# - 触发因素:降雨入渗软化+冻融风化+河流侧蚀 +# - 断裂带控制:秦岭北缘断裂沿线岩体破碎,崩塌密度高 +# ---------------------------------------- +collapse: + type: conditional + parents: [rain_intensity, duration, slope, lithology, soil_moisture, dist_to_fault, dist_to_river] + default_probability: 0.02 + rules: + # === 极高风险(≥0.70)=== + # 暴雨+陡坡+松散岩性(黄土/碎屑岩):黄土塬边崩塌 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [steep, very_steep, extreme_steep], lithology: [terrigenous, unconsolidated, mixed_clastic]} + probability: 0.72 + # 近河道+松散堆积物+陡坡:河流侧蚀致崩塌(渭河/灞河/浐河) + - condition: {dist_to_river: [very_close, close], lithology: [unconsolidated], slope: [steep, very_steep, extreme_steep]} + probability: 0.70 + + # === 高风险(0.50-0.65)=== + # 暴雨+陡坡+断裂带附近:岩体破碎 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [moderate, steep, very_steep, extreme_steep], dist_to_fault: [very_close, close]} + probability: 0.65 + # 暴雨+中陡坡+饱和:岩土体强度降低 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [moderate, steep, very_steep, extreme_steep], soil_moisture: [wet, saturated]} + probability: 0.60 + # 近河道+暴雨+中陡坡:岸坡浸泡+洪水冲刷 + - condition: {dist_to_river: [very_close, close], rain_intensity: [storm, downpour, extreme], slope: [moderate, steep, very_steep, extreme_steep]} + probability: 0.58 + # 暴雨+中陡坡 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [moderate, steep, very_steep, extreme_steep]} + probability: 0.50 + # 长历时+大雨+陡坡+松散岩性:持续入渗软化 + - condition: {duration: [long], rain_intensity: [heavy, storm, downpour, extreme], slope: [steep, very_steep, extreme_steep], lithology: [terrigenous, unconsolidated, mixed_clastic]} + probability: 0.55 + + # === 中风险(0.20-0.40)=== + # 近河道+缓坡+松散堆积物:侧蚀累积效应 + - condition: {dist_to_river: [very_close, close], lithology: [unconsolidated], slope: [moderate]} + probability: 0.35 + # 大雨+中坡 + - condition: {rain_intensity: [heavy], slope: [moderate, steep]} + probability: 0.30 + # 断裂带+陡坡(无强降雨也有蠕变崩塌风险) + - condition: {dist_to_fault: [very_close], slope: [steep, very_steep, extreme_steep]} + probability: 0.25 + # 暴雨+缓坡+松散岩性 + - condition: {rain_intensity: [storm, downpour, extreme], slope: [gentle, moderate], lithology: [unconsolidated]} + probability: 0.22 + + # === 低风险兜底(≤0.05)=== + - condition: {rain_intensity: [no_rain, light]} + probability: 0.01 + - condition: {slope: [flat, gentle]} + probability: 0.01 diff --git a/app/config/dbn/rainfall_dbn_graph.yaml b/app/config/dbn/rainfall_dbn_graph.yaml new file mode 100644 index 0000000..da4185c --- /dev/null +++ b/app/config/dbn/rainfall_dbn_graph.yaml @@ -0,0 +1,168 @@ +# DBN 图结构配置 +# 定义节点、各层节点列表、父子关系 + +# 推理配置 +inference_config: + match_strategy: first # first=首条匹配(规则优先级从上到下),max=取最大概率 + default_strategy: use_default # 无规则匹配时使用 default_probability + max_cpt_parents: 20 # CPT 最大父节点数(超过则采用规则加权融合) + +layers: + # 触发层(3个节点) + trigger: + - rain_intensity # 降雨强度 + - duration # 持续时间 + - accum_rain # 累计降雨量 + + # 环境层(16个节点) + environment: + - elevation # 高程 + - slope # 坡度 + - aspect # 坡向 + - soil_type # 土壤分类 + - lithology # 岩性 + - landuse # 土地利用类型 + - terrain # 地形分类 + - impervious # 不透水面 + - ndvi # 植被指数 + - sand_content # 土壤含沙量 + - ph # 土壤PH值(辅助因子,不入CPT) + - soil_moisture # 土壤湿度 + - organic_carbon # 有机碳(辅助因子,不入CPT) + - dist_to_river # 距离河道距离 + - dist_to_fault # 距离断裂带距离 + - pipe_density # 供水管网密度 + + # 灾害层(5个节点) + hazard: + - landslide # 滑坡 + - debris_flow # 泥石流 + - flash_flood # 山洪 + - waterlogging # 内涝 + - collapse # 崩塌 + +edges: + # 触发层 → 灾害层 + # 降雨强度 + - [rain_intensity, landslide] + - [rain_intensity, debris_flow] + - [rain_intensity, flash_flood] + - [rain_intensity, waterlogging] + - [rain_intensity, collapse] + + # 持续时间 + - [duration, landslide] + - [duration, debris_flow] + - [ duration, flash_flood ] + - [duration, waterlogging] + - [duration, collapse] + + # 累计降雨量 + - [accum_rain, landslide] + - [accum_rain, debris_flow] + - [accum_rain, flash_flood] + - [accum_rain, waterlogging] + - [accum_rain, collapse] + + # 环境层 → 灾害层 + # 高程影响山洪、内涝、泥石流 + # - 山洪:高程影响汇流面积和河道坡降 + # - 内涝:低洼处积水 + # - 泥石流:高程反映沟道纵坡和物源区高差(秦岭北麓1000m+高发) + - [elevation, flash_flood] + - [elevation, waterlogging] + - [elevation, debris_flow] + + # 坡度影响滑坡、泥石流、崩塌 + - [slope, landslide] + - [slope, debris_flow] + - [slope, collapse] + + # 坡向影响滑坡(阳坡冻融风化强烈) + - [aspect, landslide] + + # 土壤类型影响滑坡、泥石流 + - [soil_type, landslide] + - [soil_type, debris_flow] + + # 岩性影响滑坡、泥石流、崩塌 + - [lithology, landslide] + - [lithology, debris_flow] + - [lithology, collapse] + + # 土地利用类型影响内涝、滑坡 + # - 内涝:城市地表不透水 + # - 滑坡:开挖坡脚/弃土加载/梯田改造 + - [landuse, waterlogging] + - [landuse, landslide] + + # 地形分类影响山洪、内涝 + - [terrain, flash_flood] + - [terrain, waterlogging] + + # 不透水面影响内涝 + - [impervious, waterlogging] + + # 植被指数影响滑坡、泥石流 + - [ndvi, landslide] + - [ndvi, debris_flow] + + # 土壤含沙量影响泥石流 + - [sand_content, debris_flow] + + # 土壤湿度影响滑坡、泥石流、内涝、山洪 + # - 山洪:前期土壤湿度决定入渗率,饱和土壤几乎全部转为地表径流 + - [soil_moisture, landslide] + - [soil_moisture, debris_flow] + - [soil_moisture, waterlogging] + - [soil_moisture, flash_flood] + + # 距离河道距离影响山洪、泥石流、内涝、崩塌 + # - 山洪:河道溢出 → [dist_to_river, flash_flood] + # - 泥石流:沟道物源供给 → [dist_to_river, debris_flow] + # - 内涝:排水出口受阻 → [dist_to_river, waterlogging] + # - 崩塌:河流侧蚀黄土塬边(渭河/灞河/浐河/沣河)→ [dist_to_river, collapse] + - [dist_to_river, flash_flood] + - [dist_to_river, debris_flow] + - [dist_to_river, waterlogging] + - [dist_to_river, collapse] + + # 距离断裂带距离影响滑坡、崩塌、泥石流 + - [dist_to_fault, landslide] + - [dist_to_fault, collapse] + - [dist_to_fault, debris_flow] + + # 供水管网密度影响内涝 + - [pipe_density, waterlogging] + +# 节点状态定义(与 discretization.yaml 保持一致) +node_states: + # 触发层 + rain_intensity: [no_rain, light, moderate, heavy, storm, downpour, extreme] + duration: [short, medium, long] + accum_rain: [trace, light, moderate, heavy, extreme] + + # 环境层(离散字段状态名与数据库编码一一对应,连续字段用工程分级) + elevation: [basin, plain_urban, transition, low_mountain, mid_mountain, high_mountain] + slope: [flat, gentle, moderate, steep, very_steep, extreme_steep] + aspect: [north, east, south, west, north_loop] + soil_type: [ultisol, entisol, fluvo_aquic, yellow_brown] + lithology: [acid_rock, basic_rock, carbonate, metamorphic, mixed_clastic, terrigenous, unconsolidated] + landuse: [forest, farmland, urban, water, barren] + terrain: [mountain, plain, deep_valley, hill, gentle_hill, low_mountain, flat_plain] + impervious: [low, medium, high] + ndvi: [water, bare, sparse, moderate, dense, very_dense] + sand_content: [low, medium, high] + ph: [acidic, neutral, alkaline] + soil_moisture: [dry, moist, wet, saturated] + organic_carbon: [low, medium, high] + dist_to_river: [very_close, close, moderate, far] + dist_to_fault: [very_close, close, moderate, far] + pipe_density: [none, low, medium, high] + + # 灾害层 + landslide: [none, low, medium, high, very_high] + debris_flow: [none, low, medium, high, very_high] + flash_flood: [none, low, medium, high, very_high] + waterlogging: [none, low, medium, high, very_high] + collapse: [none, low, medium, high, very_high] diff --git a/app/config/paths.py b/app/config/paths.py new file mode 100644 index 0000000..65410a8 --- /dev/null +++ b/app/config/paths.py @@ -0,0 +1,31 @@ +""" +项目配置模块 +提供统一的项目路径和配置管理 +""" +from pathlib import Path + +# 项目根目录(app/config/ 的上两级) +PROJECT_ROOT = Path(__file__).parent.parent.parent + +# 日志目录 +LOG_DIR = PROJECT_ROOT / "logs" + +# 配置文件目录 +CONFIG_DIR = PROJECT_ROOT / "app" / "config" + +# DBN 配置目录 +DBN_CONFIG_DIR = CONFIG_DIR / "dbn" + + +def get_logger(name: str = "algorithm"): + """ + 获取日志记录器的便捷函数 + + Args: + name: 日志名称 + + Returns: + logging.Logger 实例 + """ + from app.utils.logger import get_logger as _get_logger + return _get_logger(name, str(LOG_DIR)) diff --git a/app/models/dbn/__init__.py b/app/models/dbn/__init__.py new file mode 100644 index 0000000..21c5d63 --- /dev/null +++ b/app/models/dbn/__init__.py @@ -0,0 +1,3 @@ +""" +DBN 模型模块 +""" diff --git a/app/models/dbn/earthquake/__init__.py b/app/models/dbn/earthquake/__init__.py new file mode 100644 index 0000000..99d1c1d --- /dev/null +++ b/app/models/dbn/earthquake/__init__.py @@ -0,0 +1,8 @@ +""" +地震灾害链DBN模型模块 +""" +from .earthquake_dbn import EarthquakeDBN, earthquake_dbn + +__all__ = [ + 'EarthquakeDBN', 'earthquake_dbn', +] diff --git a/app/models/dbn/earthquake/earthquake_dbn.py b/app/models/dbn/earthquake/earthquake_dbn.py new file mode 100644 index 0000000..caedaaa --- /dev/null +++ b/app/models/dbn/earthquake/earthquake_dbn.py @@ -0,0 +1,395 @@ +""" +地震灾害链DBN模型 +实现贝叶斯网络推理,预测地震触发的3类地质灾害概率: +滑坡(landslide)、泥石流(debris_flow)、崩塌(collapse) +""" +import os +import math +import yaml +from typing import Optional, List, Dict, Any + +from app.utils.discretizer import discretizer +from app.repositories.dbn_repository import DbnRepository +from app.config.paths import DBN_CONFIG_DIR, get_logger + +logger = get_logger("earthquake_dbn") + + +class EarthquakeDBN: + """地震灾害链DBN模型""" + + # 灾害概率→离散等级的阈值映射 + HAZARD_LEVEL_THRESHOLDS = [ + (0.6, 'very_high'), + (0.4, 'high'), + (0.2, 'medium'), + (0.05, 'low'), + (0.0, 'none'), + ] + + def _probability_to_level(self, prob: float) -> str: + """将连续概率映射到离散等级""" + for threshold, level in self.HAZARD_LEVEL_THRESHOLDS: + if prob >= threshold: + return level + return 'none' + + def __init__(self, config_dir: Optional[str] = None): + """ + 初始化地震DBN模型 + + Args: + config_dir: 配置文件目录,默认为 app/config/dbn + """ + if config_dir is None: + config_dir = str(DBN_CONFIG_DIR) + + self.config_dir = config_dir + self.graph_config = self._load_graph_config() + self.cpt_config = self._load_cpt_config() + + self._build_network() + + def _load_graph_config(self) -> Dict[str, Any]: + """加载地震图结构配置""" + config_path = os.path.join(self.config_dir, 'earthquake_dbn_graph.yaml') + + if not os.path.exists(config_path): + logger.error(f"地震图结构配置文件不存在: {config_path}") + return {} + + with open(config_path, 'r', encoding='utf-8') as f: + return yaml.safe_load(f) + + def _load_cpt_config(self) -> Dict[str, Any]: + """加载地震CPT配置""" + config_path = os.path.join(self.config_dir, 'earthquake_cpt_params.yaml') + + if not os.path.exists(config_path): + logger.error(f"地震CPT配置文件不存在: {config_path}") + return {} + + with open(config_path, 'r', encoding='utf-8') as f: + return yaml.safe_load(f) + + def _build_network(self): + """构建贝叶斯网络结构""" + self.trigger_nodes = self.graph_config.get('layers', {}).get('trigger', []) + self.environment_nodes = self.graph_config.get('layers', {}).get('environment', []) + self.hazard_nodes = self.graph_config.get('layers', {}).get('hazard', []) + + self.all_nodes = self.trigger_nodes + self.environment_nodes + self.hazard_nodes + self.edges = self.graph_config.get('edges', []) + self.node_states = self.graph_config.get('node_states', {}) + + self.children = {node: [] for node in self.all_nodes} + self.parents = {node: [] for node in self.all_nodes} + + for parent, child in self.edges: + if parent in self.all_nodes and child in self.all_nodes: + self.children[parent].append(child) + self.parents[child].append(parent) + + self._build_cpt_tables() + + def _build_cpt_tables(self): + """构建条件概率表""" + self.cpt_tables = {} + + for node in self.all_nodes: + if node in self.cpt_config: + self.cpt_tables[node] = self.cpt_config[node] + else: + states = self.node_states.get(node, ['no', 'yes']) + if len(states) == 2: + self.cpt_tables[node] = { + 'type': 'prior', + 'probabilities': [0.5, 0.5] + } + else: + prob = 1.0 / len(states) + self.cpt_tables[node] = { + 'type': 'prior', + 'probabilities': [prob] * len(states) + } + + @staticmethod + def estimate_seismic_intensity(magnitude: float, epicenter_distance_km: float) -> float: + """ + 根据震级和震中距估算地震烈度 + 使用中国地震烈度衰减关系 + + I = 0.923 + 1.621*M - 3.494*ln(R+10) + + 参考:GB 18306-2015 中国地震动参数区划图 + + Args: + magnitude: 震级(Richter) + epicenter_distance_km: 震中距(km) + + Returns: + 估算的地震烈度(中国烈度表数值) + """ + if epicenter_distance_km < 0: + epicenter_distance_km = 0 + + intensity = 0.923 + 1.621 * magnitude - 3.494 * math.log(epicenter_distance_km + 10) + + # 限制在合理范围内 + return max(1.0, min(12.0, intensity)) + + def _get_node_probability(self, node: str, evidence: Dict[str, str]) -> List[float]: + """获取节点的概率分布""" + cpt = self.cpt_tables.get(node) + if not cpt: + states = self.node_states.get(node, ['no', 'yes']) + return [1.0 / len(states)] * len(states) + + if cpt.get('type') == 'prior': + return cpt.get('probabilities', [0.5, 0.5]) + + if cpt.get('type') == 'conditional': + return self._evaluate_conditional_probability(node, cpt, evidence) + + return [0.5, 0.5] + + def _evaluate_conditional_probability(self, node: str, cpt: Dict[str, Any], + evidence: Dict[str, str]) -> List[float]: + """评估条件概率""" + states = self.node_states.get(node, ['no', 'yes']) + default_prob = cpt.get('default_probability', 0.02) + + rules = cpt.get('rules', []) + for rule in rules: + condition = rule.get('condition', {}) + probability = rule.get('probability', default_prob) + + if self._check_condition(condition, evidence): + return [1.0 - probability, probability] + + return [1.0 - default_prob, default_prob] + + def _check_condition(self, condition: Dict[str, Any], evidence: Dict[str, str]) -> bool: + """检查条件是否满足""" + for node, required_states in condition.items(): + if node not in evidence: + return False + + evidence_state = evidence[node] + + if isinstance(required_states, list): + if evidence_state not in required_states: + return False + else: + if evidence_state != required_states: + return False + + return True + + def predict_single_point(self, point: Dict[str, Any], + magnitude: float, + epicenter_distance: Optional[float] = None, + seismic_intensity: Optional[float] = None, + epicenter_lon: Optional[float] = None, + epicenter_lat: Optional[float] = None) -> Dict[str, Any]: + """ + 对单个点进行地震灾害预测 + + Args: + point: 点信息(包含 static_factors 字段,来自 xian_risk_factors 表) + magnitude: 地震震级(Richter) + epicenter_distance: 震中距(km),若未提供则通过震中坐标计算 + seismic_intensity: 地震烈度(中国烈度表),若未提供则自动估算 + epicenter_lon: 震中经度(可选,用于计算震中距) + epicenter_lat: 震中纬度(可选,用于计算震中距) + + Returns: + 预测结果 + """ + point_id = point.get('id') + lon = point.get('lon') + lat = point.get('lat') + source_type = point.get('source_type') + + logger.info(f"地震预测点 ID={point_id}, source_type={source_type}") + + # 计算震中距(如果未直接提供) + if epicenter_distance is None: + if epicenter_lon is not None and epicenter_lat is not None: + epicenter_distance = self._haversine_distance( + lon, lat, epicenter_lon, epicenter_lat + ) + logger.info(f"计算震中距: {epicenter_distance:.1f} km") + else: + logger.warning("未提供震中距或震中坐标,使用默认值 100km") + epicenter_distance = 100.0 + + # 估算地震烈度(如果未直接提供) + if seismic_intensity is None: + seismic_intensity = self.estimate_seismic_intensity(magnitude, epicenter_distance) + logger.info(f"估算地震烈度: {seismic_intensity:.1f}") + + # 获取静态因子数据 + raw_factors = point.get('static_factors', {}) + static_factors = { + 'elevation': raw_factors.get('dem_value'), + 'slope': raw_factors.get('slope_value'), + 'aspect': raw_factors.get('aspect_value'), + 'soil_type': raw_factors.get('soil_type'), + 'lithology': raw_factors.get('lithology'), + 'landuse': raw_factors.get('landuse'), + 'terrain': raw_factors.get('landform'), + 'ndvi': raw_factors.get('vegetation_index'), + 'sand_content': raw_factors.get('soil_sand'), + 'ph': raw_factors.get('soil_ph'), + 'soil_moisture': raw_factors.get('soil_moisture'), + 'organic_carbon': raw_factors.get('organic_carbon'), + 'dist_to_river': raw_factors.get('river_distance'), + 'dist_to_fault': raw_factors.get('fault_distance'), + } + + # 合并地震触发因子和静态因子 + all_factors = { + 'magnitude': magnitude, + 'epicenter_distance': epicenter_distance, + 'seismic_intensity': seismic_intensity, + **static_factors + } + + # 离散化 + evidence = discretizer.discretize_all_factors(all_factors) + + # 运行推理 + hazard_results = self._run_inference(evidence) + + # 构造输出 + result = { + 'point_id': point_id, + 'source_type': source_type, + 'lon': lon, + 'lat': lat, + 'earthquake_params': { + 'magnitude': magnitude, + 'epicenter_distance': round(epicenter_distance, 1), + 'seismic_intensity': round(seismic_intensity, 1), + }, + 'disaster_probabilities': { + h: r['probability'] for h, r in hazard_results.items() + }, + 'disaster_levels': { + h: r['level'] for h, r in hazard_results.items() + } + } + + return result + + def _haversine_distance(self, lon1: float, lat1: float, + lon2: float, lat2: float) -> float: + """ + 使用Haversine公式计算两点间距离 + + Args: + lon1, lat1: 点1的经纬度 + lon2, lat2: 点2的经纬度 + + Returns: + 距离(km) + """ + R = 6371.0 # 地球半径(km) + + lat1_rad = math.radians(lat1) + lat2_rad = math.radians(lat2) + dlat = math.radians(lat2 - lat1) + dlon = math.radians(lon2 - lon1) + + a = math.sin(dlat / 2) ** 2 + \ + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2) ** 2 + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + + return R * c + + def _run_inference(self, evidence: Dict[str, str]) -> Dict[str, Any]: + """运行贝叶斯推理""" + hazard_probabilities = {} + + for hazard_node in self.hazard_nodes: + prob_dist = self._get_node_probability(hazard_node, evidence) + + if len(prob_dist) >= 2: + prob = prob_dist[1] + else: + prob = 0.0 + + hazard_probabilities[hazard_node] = { + 'probability': round(prob, 4), + 'level': self._probability_to_level(prob) + } + + return hazard_probabilities + + def predict(self, region_code: Optional[str] = None, + magnitude: float = 6.0, + epicenter_distance: Optional[float] = None, + seismic_intensity: Optional[float] = None, + epicenter_lon: Optional[float] = None, + epicenter_lat: Optional[float] = None) -> List[Dict[str, Any]]: + """ + 对所有点进行地震灾害预测 + + Args: + region_code: 行政区划代码(可选) + magnitude: 地震震级(默认6.0) + epicenter_distance: 震中距(km,可选) + seismic_intensity: 地震烈度(可选) + epicenter_lon: 震中经度(可选) + epicenter_lat: 震中纬度(可选) + + Returns: + 预测结果列表 + """ + points = DbnRepository.get_all_points(region_code) + + if not points: + logger.warning(f"没有找到点数据,region_code={region_code}") + return [] + + logger.info(f"地震灾害预测:共 {len(points)} 个点,震级 M{magnitude}") + + results = [] + for point in points: + try: + result = self.predict_single_point( + point, + magnitude=magnitude, + epicenter_distance=epicenter_distance, + seismic_intensity=seismic_intensity, + epicenter_lon=epicenter_lon, + epicenter_lat=epicenter_lat + ) + results.append(result) + except Exception as e: + logger.error(f"预测点 {point.get('id')} 失败: {e}") + results.append({ + 'point_id': point.get('id'), + 'source_type': point.get('source_type'), + 'lon': point.get('lon'), + 'lat': point.get('lat'), + 'error': str(e) + }) + + return results + + def get_model_info(self) -> Dict[str, Any]: + """获取模型信息""" + return { + 'model_type': 'earthquake', + 'trigger_nodes': self.trigger_nodes, + 'environment_nodes': self.environment_nodes, + 'hazard_nodes': self.hazard_nodes, + 'edges': self.edges, + 'node_states': self.node_states + } + + +# 创建全局实例 +earthquake_dbn = EarthquakeDBN() diff --git a/app/models/dbn/rainfall/__init__.py b/app/models/dbn/rainfall/__init__.py new file mode 100644 index 0000000..fcc35ca --- /dev/null +++ b/app/models/dbn/rainfall/__init__.py @@ -0,0 +1,11 @@ +""" +暴雨灾害链DBN模型模块 +数据库查询统一由 app.repositories.dbn_repository 提供 +离散化工具由 app.utils.discretizer 提供 +空间计算由 app.utils.spatial_utils 提供 +""" +from .rainfall_dbn import RainfallDBN, rainfall_dbn + +__all__ = [ + 'RainfallDBN', 'rainfall_dbn', +] diff --git a/app/models/dbn/rainfall/rainfall_dbn.py b/app/models/dbn/rainfall/rainfall_dbn.py new file mode 100644 index 0000000..aaa048a --- /dev/null +++ b/app/models/dbn/rainfall/rainfall_dbn.py @@ -0,0 +1,394 @@ +""" +暴雨灾害链DBN模型 +实现贝叶斯网络推理,预测5类灾害概率 +""" +import os +import yaml +from typing import Optional, List, Dict, Any +from datetime import datetime + +from app.utils.discretizer import discretizer +from app.repositories.dbn_repository import DbnRepository +from app.config.paths import DBN_CONFIG_DIR, get_logger + +logger = get_logger("dbn") + + +class RainfallDBN: + """暴雨灾害链DBN模型""" + + # 灾害概率→离散等级的阈值映射 + HAZARD_LEVEL_THRESHOLDS = [ + (0.6, 'very_high'), + (0.4, 'high'), + (0.2, 'medium'), + (0.05, 'low'), + (0.0, 'none'), + ] + + def _probability_to_level(self, prob: float) -> str: + """将连续概率映射到离散等级""" + for threshold, level in self.HAZARD_LEVEL_THRESHOLDS: + if prob >= threshold: + return level + return 'none' + + def __init__(self, config_dir: Optional[str] = None): + """ + 初始化DBN模型 + + Args: + config_dir: 配置文件目录 + """ + if config_dir is None: + config_dir = str(DBN_CONFIG_DIR) + + self.config_dir = config_dir + self.graph_config = self._load_graph_config() + self.cpt_config = self._load_cpt_config() + + # 构建贝叶斯网络结构 + self._build_network() + + def _load_graph_config(self) -> Dict[str, Any]: + """加载图结构配置""" + config_path = os.path.join(self.config_dir, 'rainfall_dbn_graph.yaml') + + if not os.path.exists(config_path): + logger.error(f"图结构配置文件不存在: {config_path}") + return {} + + with open(config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + + return config + + def _load_cpt_config(self) -> Dict[str, Any]: + """加载CPT配置""" + config_path = os.path.join(self.config_dir, 'rainfall_cpt_params.yaml') + + if not os.path.exists(config_path): + logger.error(f"CPT配置文件不存在: {config_path}") + return {} + + with open(config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + + return config + + def _build_network(self): + """构建贝叶斯网络结构""" + # 获取节点列表 + self.trigger_nodes = self.graph_config.get('layers', {}).get('trigger', []) + self.environment_nodes = self.graph_config.get('layers', {}).get('environment', []) + self.hazard_nodes = self.graph_config.get('layers', {}).get('hazard', []) + + # 获取所有节点 + self.all_nodes = self.trigger_nodes + self.environment_nodes + self.hazard_nodes + + # 获取边关系 + self.edges = self.graph_config.get('edges', []) + + # 获取节点状态 + self.node_states = self.graph_config.get('node_states', {}) + + # 构建父子关系 + self.children = {node: [] for node in self.all_nodes} + self.parents = {node: [] for node in self.all_nodes} + + for parent, child in self.edges: + if parent in self.all_nodes and child in self.all_nodes: + self.children[parent].append(child) + self.parents[child].append(parent) + + # 构建CPT表 + self._build_cpt_tables() + + def _build_cpt_tables(self): + """构建条件概率表""" + self.cpt_tables = {} + + for node in self.all_nodes: + if node in self.cpt_config: + self.cpt_tables[node] = self.cpt_config[node] + else: + # 如果没有配置,使用均匀分布 + states = self.node_states.get(node, ['no', 'yes']) + if len(states) == 2: + # 二值节点 + self.cpt_tables[node] = { + 'type': 'prior', + 'probabilities': [0.5, 0.5] + } + else: + # 多值节点 + prob = 1.0 / len(states) + self.cpt_tables[node] = { + 'type': 'prior', + 'probabilities': [prob] * len(states) + } + + def _get_node_probability(self, node: str, evidence: Dict[str, str]) -> List[float]: + """ + 获取节点的概率分布 + + Args: + node: 节点名称 + evidence: 证据字典 + + Returns: + 概率分布列表 + """ + cpt = self.cpt_tables.get(node) + if not cpt: + states = self.node_states.get(node, ['no', 'yes']) + return [1.0 / len(states)] * len(states) + + # 如果是先验概率 + if cpt.get('type') == 'prior': + return cpt.get('probabilities', [0.5, 0.5]) + + # 如果是条件概率 + if cpt.get('type') == 'conditional': + return self._evaluate_conditional_probability(node, cpt, evidence) + + return [0.5, 0.5] + + def _evaluate_conditional_probability(self, node: str, cpt: Dict[str, Any], + evidence: Dict[str, str]) -> List[float]: + """ + 评估条件概率 + + Args: + node: 节点名称 + cpt: CPT配置 + evidence: 证据字典 + + Returns: + 概率分布列表 + """ + states = self.node_states.get(node, ['no', 'yes']) + default_prob = cpt.get('default_probability', 0.05) + + # 检查规则 + rules = cpt.get('rules', []) + for rule in rules: + condition = rule.get('condition', {}) + probability = rule.get('probability', default_prob) + + # 检查是否满足条件 + if self._check_condition(condition, evidence): + # 返回 [P(no), P(yes)] + return [1.0 - probability, probability] + + # 如果没有匹配的规则,返回默认概率 + return [1.0 - default_prob, default_prob] + + def _check_condition(self, condition: Dict[str, Any], evidence: Dict[str, str]) -> bool: + """ + 检查条件是否满足 + + Args: + condition: 条件字典 + evidence: 证据字典 + + Returns: + 是否满足 + """ + for node, required_states in condition.items(): + if node not in evidence: + return False + + evidence_state = evidence[node] + + # 如果required_states是列表,检查是否在列表中 + if isinstance(required_states, list): + if evidence_state not in required_states: + return False + else: + # 如果是单个值,检查是否相等 + if evidence_state != required_states: + return False + + return True + + def predict_single_point(self, point: Dict[str, Any], + rainfall: Optional[float] = None, + duration: Optional[float] = None, + query_time: Optional[datetime] = None) -> Dict[str, Any]: + """ + 对单个点进行预测 + + Args: + point: 点信息(包含 static_factors 字段) + rainfall: 累计降雨量(可选) + duration: 持续时间(可选) + query_time: 查询时间(可选) + + Returns: + 预测结果 + """ + point_id = point.get('id') + lon = point.get('lon') + lat = point.get('lat') + source_type = point.get('source_type') + + logger.info(f"预测点 ID={point_id}, source_type={source_type}") + + # 获取降雨数据 + if rainfall is not None and duration is not None: + rain_intensity = rainfall / duration if duration > 0 else 0.0 + rainfall_data = { + 'accum_rain': rainfall, + 'duration_hours': duration, + 'rain_intensity': rain_intensity + } + else: + rainfall_data = DbnRepository.get_rainfall_data_with_duration(lon, lat, query_time) + + # 获取静态因子数据(从 point 的 static_factors 字段) + raw_factors = point.get('static_factors', {}) + static_factors = { + 'elevation': raw_factors.get('dem_value'), + 'slope': raw_factors.get('slope_value'), + 'aspect': raw_factors.get('aspect_value'), + 'soil_type': raw_factors.get('soil_type'), + 'lithology': raw_factors.get('lithology'), + 'landuse': raw_factors.get('landuse'), + 'terrain': raw_factors.get('landform'), + 'impervious': raw_factors.get('impervious_surface'), + 'ndvi': raw_factors.get('vegetation_index'), + 'sand_content': raw_factors.get('soil_sand'), + 'ph': raw_factors.get('soil_ph'), + 'soil_moisture': raw_factors.get('soil_moisture'), + 'organic_carbon': raw_factors.get('organic_carbon'), + 'dist_to_river': raw_factors.get('river_distance'), + 'dist_to_fault': raw_factors.get('fault_distance'), + 'pipe_density': raw_factors.get('pipe_density') + } + + # 合并所有因子 + all_factors = { + 'rain_intensity': rainfall_data.get('rain_intensity', 0.0), + 'duration': rainfall_data.get('duration_hours', 0), + 'accum_rain': rainfall_data.get('accum_rain', 0.0), + **static_factors + } + + # 离散化 + evidence = discretizer.discretize_all_factors(all_factors) + + # 运行推理 + hazard_results = self._run_inference(evidence) + + # 构造输出 + result = { + 'point_id': point_id, + 'source_type': source_type, + 'lon': lon, + 'lat': lat, + 'disaster_probabilities': { + h: r['probability'] for h, r in hazard_results.items() + }, + 'disaster_levels': { + h: r['level'] for h, r in hazard_results.items() + } + } + + return result + + def _run_inference(self, evidence: Dict[str, str]) -> Dict[str, Any]: + """ + 运行贝叶斯推理 + + Args: + evidence: 证据字典 + + Returns: + 灾害概率字典,每个值包含 probability 和 level + """ + hazard_probabilities = {} + + for hazard_node in self.hazard_nodes: + # 获取灾害节点的概率 + prob_dist = self._get_node_probability(hazard_node, evidence) + + # 取发生概率(第二个状态) + if len(prob_dist) >= 2: + prob = prob_dist[1] + else: + prob = 0.0 + + hazard_probabilities[hazard_node] = { + 'probability': round(prob, 4), + 'level': self._probability_to_level(prob) + } + + return hazard_probabilities + + def predict(self, region_code: Optional[str] = None, + rainfall: Optional[float] = None, + duration: Optional[float] = None, + timestamp: Optional[datetime] = None) -> List[Dict[str, Any]]: + """ + 预测灾害概率 + + Args: + region_code: 行政区划代码(可选) + rainfall: 累计降雨量(可选,全局值) + duration: 持续时间(可选,全局值) + timestamp: 时间(可选) + + Returns: + 预测结果列表 + """ + # 1. 获取点列表 + points = DbnRepository.get_all_points(region_code) + + if not points: + logger.warning(f"没有找到点数据,region_code={region_code}") + return [] + + logger.info(f"共找到 {len(points)} 个点") + + # 2. 对每个点进行预测 + results = [] + for point in points: + try: + result = self.predict_single_point( + point, + rainfall=rainfall, + duration=duration, + query_time=timestamp + ) + results.append(result) + except Exception as e: + logger.error(f"预测点 {point.get('id')} 失败: {e}") + results.append({ + 'point_id': point.get('id'), + 'source_type': point.get('source_type'), + 'lon': point.get('lon'), + 'lat': point.get('lat'), + 'error': str(e) + }) + + return results + + def get_model_info(self) -> Dict[str, Any]: + """ + 获取模型信息 + + Returns: + 模型信息字典 + """ + return { + 'trigger_nodes': self.trigger_nodes, + 'environment_nodes': self.environment_nodes, + 'hazard_nodes': self.hazard_nodes, + 'edges': self.edges, + 'node_states': self.node_states + } + + +# 创建全局实例 +rainfall_dbn = RainfallDBN() diff --git a/app/repositories/dbn_repository.py b/app/repositories/dbn_repository.py new file mode 100644 index 0000000..cd7e02b --- /dev/null +++ b/app/repositories/dbn_repository.py @@ -0,0 +1,515 @@ +""" +数据库查询模块 +负责从 xian_risk_factors、xian_meteorology 等表获取数据 +""" +import math +from typing import Optional, List, Dict, Any +from datetime import datetime +from app.utils.db_helper import db_helper +from app.config.paths import get_logger + +logger = get_logger("dbn") + + +class DbnRepository: + """数据库查询类 - 所有DBN相关查询统一管理""" + + # ==================== 风险因子查询 ==================== + + @staticmethod + def get_all_points(region_code: Optional[str] = None) -> List[Dict[str, Any]]: + """ + 获取所有隐患点和风险点(从 xian_risk_factors 表) + + Args: + region_code: 行政区划代码(区县名称),可选 + + Returns: + 点列表,每个元素包含:id, source_id, source_type, lon, lat, static_factors + """ + sql = """ + SELECT + id, + source_id, + source_type, + lon, + lat, + static_factors + FROM xian_risk_factors + WHERE is_delete = 0 + """ + params = (region_code,) if region_code else None + + if region_code: + sql += " AND county = %s" + + results = db_helper.execute_query(sql, params) + + points = [] + for row in results: + points.append({ + 'id': row['id'], + 'source_id': row['source_id'], + 'source_type': row['source_type'], + 'lon': float(row['lon']) if row['lon'] else None, + 'lat': float(row['lat']) if row['lat'] else None, + 'static_factors': row.get('static_factors') or {} + }) + + return points + + @staticmethod + def get_point_by_id(point_id: int) -> Optional[Dict[str, Any]]: + """ + 根据ID获取单个点信息 + + Args: + point_id: xian_risk_factors 表的 ID + + Returns: + 点信息 + """ + sql = """ + SELECT + rf.id, + rf.source_id, + rf.source_type, + rf.lon, + rf.lat, + rf.static_factors + FROM xian_risk_factors rf + WHERE rf.id = %s AND rf.is_delete = 0 + """ + result = db_helper.execute_query_one(sql, (point_id,)) + + if not result: + return None + + return { + 'id': result['id'], + 'source_id': result['source_id'], + 'source_type': result['source_type'], + 'lon': float(result['lon']) if result['lon'] else None, + 'lat': float(result['lat']) if result['lat'] else None, + 'static_factors': result.get('static_factors') or {} + } + + @staticmethod + def get_static_factors(point_id: int) -> Dict[str, Any]: + """ + 获取点的静态因子数据 + + Args: + point_id: xian_risk_factors 表的 ID + + Returns: + 静态因子数据 + """ + sql = """ + SELECT static_factors + FROM xian_risk_factors + WHERE id = %s AND is_delete = 0 + """ + result = db_helper.execute_query_one(sql, (point_id,)) + + if result and result.get('static_factors'): + return result['static_factors'] + return {} + + # ==================== 降雨数据查询 ==================== + + @staticmethod + def get_nearest_station_rainfall(lon: float, lat: float, + query_time: Optional[datetime] = None) -> Dict[str, Any]: + """ + 获取最近雨量站的降雨数据 + + Args: + lon: 经度 + lat: 纬度 + query_time: 查询时间,若未提供则取当前时间 + + Returns: + 降雨数据 + """ + if query_time is None: + query_time = datetime.now() + + # noinspection SqlNoDataSourceInspection + sql = """ + WITH station_data AS ( + SELECT + lon, + lat, + SUM(CAST(rainfall_1h AS DOUBLE PRECISION)) as total_rainfall, + COUNT(*) as record_count + FROM xian_meteorology + WHERE datetime BETWEEN + CAST(EXTRACT(EPOCH FROM (%s::timestamp - INTERVAL '24 hours')) AS BIGINT) + AND CAST(EXTRACT(EPOCH FROM %s::timestamp) AS BIGINT) + AND rainfall_1h IS NOT NULL + AND CAST(rainfall_1h AS DOUBLE PRECISION) > 0 + GROUP BY lon, lat + ) + SELECT + lon, + lat, + total_rainfall, + record_count, + ST_DistanceSphere( + ST_SetSRID(ST_MakePoint(lon, lat), 4326), + ST_SetSRID(ST_MakePoint(%s, %s), 4326) + ) as distance + FROM station_data + ORDER BY distance + LIMIT 1 + """ + result = db_helper.execute_query_one(sql, (query_time, query_time, lon, lat)) + + if result: + return { + 'rainfall': float(result['total_rainfall']) if result['total_rainfall'] else 0.0, + 'record_count': int(result['record_count']) if result['record_count'] else 0, + 'distance': float(result['distance']) if result['distance'] else 0.0, + 'station_lon': float(result['lon']) if result['lon'] else None, + 'station_lat': float(result['lat']) if result['lat'] else None + } + else: + return { + 'rainfall': 0.0, + 'record_count': 0, + 'distance': 0.0, + 'station_lon': None, + 'station_lat': None + } + + @staticmethod + def get_rainfall_data_with_duration(lon: float, lat: float, + query_time: Optional[datetime] = None) -> Dict[str, Any]: + """ + 获取降雨数据,包括累计降雨量和持续时间 + + Args: + lon: 经度 + lat: 纬度 + query_time: 查询时间,若未提供则取当前时间 + + Returns: + 降雨数据:{accum_rain, duration_hours, rain_intensity} + """ + if query_time is None: + query_time = datetime.now() + + # 查找最近的雨量站 + # noinspection SqlNoDataSourceInspection + sql = """ + SELECT lon, lat, dist + FROM ( + SELECT DISTINCT lon, lat, + ST_DistanceSphere( + ST_SetSRID(ST_MakePoint(lon, lat), 4326), + ST_SetSRID(ST_MakePoint(%s, %s), 4326) + ) as dist + FROM xian_meteorology + ) t + WHERE dist < 50000 + ORDER BY dist + LIMIT 1 + """ + station = db_helper.execute_query_one(sql, (lon, lat)) + + if not station: + return {'accum_rain': 0.0, 'duration_hours': 0, 'rain_intensity': 0.0} + + station_lon = station['lon'] + station_lat = station['lat'] + + # 查询该站点的降雨时序数据 + # noinspection SqlNoDataSourceInspection + sql = """ + SELECT + datetime, + CAST(rainfall_1h AS DOUBLE PRECISION) as rainfall + FROM xian_meteorology + WHERE lon = %s AND lat = %s + AND datetime BETWEEN + CAST(EXTRACT(EPOCH FROM (%s::timestamp - INTERVAL '72 hours')) AS BIGINT) + AND CAST(EXTRACT(EPOCH FROM %s::timestamp) AS BIGINT) + ORDER BY datetime DESC + """ + results = db_helper.execute_query(sql, (station_lon, station_lat, query_time, query_time)) + + if not results: + return {'accum_rain': 0.0, 'duration_hours': 0, 'rain_intensity': 0.0} + + # 计算累计降雨量和持续时间 + accum_rain = 0.0 + duration_hours = 0 + consecutive_no_rain = 0 + + for row in results: + rainfall = float(row['rainfall']) if row['rainfall'] else 0.0 + if rainfall > 0: + accum_rain += rainfall + duration_hours += 1 + consecutive_no_rain = 0 + else: + consecutive_no_rain += 1 + if consecutive_no_rain >= 3: + break + if accum_rain > 0: + duration_hours += 1 + + rain_intensity = accum_rain / duration_hours if duration_hours > 0 else 0.0 + + return { + 'accum_rain': accum_rain, + 'duration_hours': duration_hours, + 'rain_intensity': rain_intensity + } + + # ==================== 空间查询 ==================== + + @staticmethod + def get_nearest_station(lon: float, lat: float, + station_type: str = 'meteorology') -> Optional[Dict[str, Any]]: + """ + 获取最近的气象站点 + + Args: + lon: 经度 + lat: 纬度 + station_type: 站点类型 + + Returns: + 最近站点信息 + """ + sql = """ + SELECT DISTINCT ON (lon, lat) + lon, + lat, + ST_DistanceSphere( + ST_SetSRID(ST_MakePoint(lon, lat), 4326), + ST_SetSRID(ST_MakePoint(%s, %s), 4326) + ) as distance + FROM xian_meteorology + WHERE is_delete = 0 + ORDER BY lon, lat, distance + LIMIT 1 + """ + + result = db_helper.execute_query_one(sql, (lon, lat)) + + if result: + return { + 'lon': float(result['lon']), + 'lat': float(result['lat']), + 'distance': float(result['distance']) + } + return None + + @staticmethod + def get_distance_to_river(lon: float, lat: float) -> float: + """ + 计算点到最近河流的距离 + + Args: + lon: 经度 + lat: 纬度 + + Returns: + 距离(米) + """ + sql = """ + SELECT + MIN(ST_DistanceSphere( + ST_SetSRID(ST_MakePoint(%s, %s), 4326), + geom + )) as min_distance + FROM xian_rivers + WHERE is_delete = 0 + """ + + result = db_helper.execute_query_one(sql, (lon, lat)) + + if result and result['min_distance']: + return float(result['min_distance']) + return 0.0 + + @staticmethod + def get_distance_to_fault(lon: float, lat: float) -> float: + """ + 计算点到最近断裂带的距离 + + Args: + lon: 经度 + lat: 纬度 + + Returns: + 距离(米) + """ + sql = """ + SELECT + MIN(ST_DistanceSphere( + ST_SetSRID(ST_MakePoint(%s, %s), 4326), + geom + )) as min_distance + FROM xian_fault_lines + WHERE is_delete = 0 + """ + + result = db_helper.execute_query_one(sql, (lon, lat)) + + if result and result['min_distance']: + return float(result['min_distance']) + return 0.0 + + @staticmethod + def get_pipe_density(lon: float, lat: float, buffer_radius: float = 500.0) -> float: + """ + 计算点周围缓冲区内的管网密度 + + Args: + lon: 经度 + lat: 纬度 + buffer_radius: 缓冲区半径(米) + + Returns: + 管网密度(m/m²) + """ + sql = """ + SELECT + COALESCE(SUM(ST_Length(wp.geom::geography)), 0) as total_length + FROM xian_water_pipe wp + WHERE wp.is_delete = 0 + AND ST_DWithin( + ST_SetSRID(ST_MakePoint(%s, %s), 4326)::geography, + wp.geom::geography, + %s + ) + """ + + result = db_helper.execute_query_one(sql, (lon, lat, buffer_radius)) + + if result and result['total_length']: + total_length = float(result['total_length']) + buffer_area = math.pi * buffer_radius ** 2 + density = total_length / buffer_area + return density + return 0.0 + + @staticmethod + def get_river_density(lon: float, lat: float, buffer_radius: float = 1000.0) -> float: + """ + 计算点周围缓冲区内的河流密度 + + Args: + lon: 经度 + lat: 纬度 + buffer_radius: 缓冲区半径(米) + + Returns: + 河流密度(m/m²) + """ + sql = """ + SELECT + COALESCE(SUM(ST_Length(r.geom::geography)), 0) as total_length + FROM xian_rivers r + WHERE r.is_delete = 0 + AND ST_DWithin( + ST_SetSRID(ST_MakePoint(%s, %s), 4326)::geography, + r.geom::geography, + %s + ) + """ + + result = db_helper.execute_query_one(sql, (lon, lat, buffer_radius)) + + if result and result['total_length']: + total_length = float(result['total_length']) + buffer_area = math.pi * buffer_radius ** 2 + density = total_length / buffer_area + return density + return 0.0 + + @staticmethod + def get_point_elevation(lon: float, lat: float) -> Optional[float]: + """ + 获取点的高程值 + + Args: + lon: 经度 + lat: 纬度 + + Returns: + 高程值(米) + """ + sql = """ + SELECT + ST_Value(rast, ST_SetSRID(ST_MakePoint(%s, %s), 4326)) as elevation + FROM xian_dem + WHERE ST_Intersects(rast, ST_SetSRID(ST_MakePoint(%s, %s), 4326)) + LIMIT 1 + """ + + result = db_helper.execute_query_one(sql, (lon, lat, lon, lat)) + + if result and result['elevation']: + return float(result['elevation']) + return None + + @staticmethod + def get_point_slope(lon: float, lat: float) -> Optional[float]: + """ + 获取点的坡度值 + + Args: + lon: 经度 + lat: 纬度 + + Returns: + 坡度值(度) + """ + sql = """ + SELECT + ST_Value(rast, ST_SetSRID(ST_MakePoint(%s, %s), 4326)) as slope + FROM xian_slope + WHERE ST_Intersects(rast, ST_SetSRID(ST_MakePoint(%s, %s), 4326)) + LIMIT 1 + """ + + result = db_helper.execute_query_one(sql, (lon, lat, lon, lat)) + + if result and result['slope']: + return float(result['slope']) + return None + + @staticmethod + def get_point_aspect(lon: float, lat: float) -> Optional[float]: + """ + 获取点的坡向值 + + Args: + lon: 经度 + lat: 纬度 + + Returns: + 坡向值(度) + """ + sql = """ + SELECT + ST_Value(rast, ST_SetSRID(ST_MakePoint(%s, %s), 4326)) as aspect + FROM xian_aspect + WHERE ST_Intersects(rast, ST_SetSRID(ST_MakePoint(%s, %s), 4326)) + LIMIT 1 + """ + + result = db_helper.execute_query_one(sql, (lon, lat, lon, lat)) + + if result and result['aspect']: + return float(result['aspect']) + return None + + +# 创建全局实例 +dbn_repository = DbnRepository() diff --git a/app/utils/discretizer.py b/app/utils/discretizer.py new file mode 100644 index 0000000..8738d3e --- /dev/null +++ b/app/utils/discretizer.py @@ -0,0 +1,471 @@ +""" +离散化模块 +负责将连续值转换为离散状态 +""" +import os +import yaml +from typing import Optional, List, Dict, Any, Tuple +from app.config.paths import DBN_CONFIG_DIR, get_logger + +logger = get_logger("dbn") + + +class Discretizer: + """离散化工具类""" + + def __init__(self, config_dir: Optional[str] = None): + """ + 初始化离散化器 + + Args: + config_dir: 配置文件目录,默认为 app/config/dbn + """ + if config_dir is None: + config_dir = str(DBN_CONFIG_DIR) + + self.config_dir = config_dir + self.config = self._load_config() + + def _load_config(self) -> Dict[str, Any]: + """加载离散化配置""" + config_path = os.path.join(self.config_dir, 'discretization.yaml') + + if not os.path.exists(config_path): + logger.warning(f"离散化配置文件不存在: {config_path}") + return {} + + with open(config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + + return config + + def discretize(self, factor_name: str, value: float, + region_code: Optional[str] = None) -> str: + """ + 将连续值离散化 + + Args: + factor_name: 因子名称 + value: 连续值 + region_code: 行政区划代码,用于区域覆盖 + + Returns: + 离散状态标签 + """ + if factor_name not in self.config: + logger.warning(f"因子 {factor_name} 没有离散化配置") + return "unknown" + + factor_config = self.config[factor_name] + + # 检查是否是分类变量(有mapping字段) + if 'mapping' in factor_config: + return self._discretize_categorical(factor_config, value) + + # 检查是否有区域覆盖 + if region_code and 'region_overrides' in factor_config: + if region_code in factor_config['region_overrides']: + override_config = factor_config['region_overrides'][region_code] + return self._discretize_continuous(override_config, value) + + # 使用默认配置 + if 'default' in factor_config: + return self._discretize_continuous(factor_config['default'], value) + elif 'bins' in factor_config: + return self._discretize_continuous(factor_config, value) + + logger.warning(f"因子 {factor_name} 的配置格式不正确") + return "unknown" + + def _discretize_categorical(self, config: Dict[str, Any], value: float) -> str: + """ + 离散化分类变量 + + Args: + config: 配置 + value: 值 + + Returns: + 离散状态标签 + """ + mapping = config.get('mapping', {}) + default = config.get('default', 'unknown') + + # 将值转换为整数 + int_value = int(value) + + return mapping.get(int_value, default) + + def _discretize_continuous(self, config: Dict[str, Any], value: float) -> str: + """ + 离散化连续变量 + + Args: + config: 配置 + value: 值 + + Returns: + 离散状态标签 + """ + bins = config.get('bins', []) + labels = config.get('labels', []) + + if not bins or not labels: + logger.warning("离散化配置缺少bins或labels") + return "unknown" + + # 确保bins和labels长度匹配 + if len(bins) != len(labels) + 1: + logger.warning(f"bins长度({len(bins)})应该比labels长度({len(labels)})多1") + return "unknown" + + # 进行分箱 + for i in range(len(bins) - 1): + if bins[i] <= value < bins[i + 1]: + return labels[i] + + # 如果值超出范围,返回最后一个标签 + if value >= bins[-1]: + return labels[-1] + + return labels[0] + + def discretize_rain_intensity(self, rainfall_mm_h: float) -> str: + """ + 离散化降雨强度 + + Args: + rainfall_mm_h: 降雨强度(mm/h) + + Returns: + 离散状态标签 + """ + return self.discretize('rain_intensity', rainfall_mm_h) + + def discretize_duration(self, duration_hours: float) -> str: + """ + 离散化持续时间 + + Args: + duration_hours: 持续时间(小时) + + Returns: + 离散状态标签 + """ + return self.discretize('duration', duration_hours) + + def discretize_accum_rain(self, accum_rain_mm: float) -> str: + """ + 离散化累计降雨量 + + Args: + accum_rain_mm: 累计降雨量(mm) + + Returns: + 离散状态标签 + """ + return self.discretize('accum_rain', accum_rain_mm) + + # ---- 地震触发层离散化 ---- + + def discretize_magnitude(self, magnitude: float) -> str: + """ + 离散化地震震级 + + Args: + magnitude: 震级(Richter) + + Returns: + 离散状态标签 + """ + return self.discretize('magnitude', magnitude) + + def discretize_epicenter_distance(self, distance_km: float) -> str: + """ + 离散化震中距 + + Args: + distance_km: 震中距(km) + + Returns: + 离散状态标签 + """ + return self.discretize('epicenter_distance', distance_km) + + def discretize_seismic_intensity(self, intensity: float) -> str: + """ + 离散化地震烈度 + + Args: + intensity: 地震烈度(中国烈度表数值) + + Returns: + 离散状态标签 + """ + return self.discretize('seismic_intensity', intensity) + + def discretize_elevation(self, elevation_m: float, + region_code: Optional[str] = None) -> str: + """ + 离散化高程 + + Args: + elevation_m: 高程(米) + region_code: 行政区划代码 + + Returns: + 离散状态标签 + """ + return self.discretize('elevation', elevation_m, region_code) + + def discretize_slope(self, slope_deg: float) -> str: + """ + 离散化坡度 + + Args: + slope_deg: 坡度(度) + + Returns: + 离散状态标签 + """ + return self.discretize('slope', slope_deg) + + def discretize_aspect(self, aspect_deg: float) -> str: + """ + 离散化坡向 + + Args: + aspect_deg: 坡向(度) + + Returns: + 离散状态标签 + """ + return self.discretize('aspect', aspect_deg) + + def discretize_soil_type(self, soil_type_code: int) -> str: + """ + 离散化土壤类型 + + Args: + soil_type_code: 土壤类型代码 + + Returns: + 离散状态标签 + """ + return self.discretize('soil_type', soil_type_code) + + def discretize_lithology(self, lithology_code: int) -> str: + """ + 离散化岩性 + + Args: + lithology_code: 岩性代码 + + Returns: + 离散状态标签 + """ + return self.discretize('lithology', lithology_code) + + def discretize_landuse(self, landuse_code: int) -> str: + """ + 离散化土地利用类型 + + Args: + landuse_code: 土地利用类型代码 + + Returns: + 离散状态标签 + """ + return self.discretize('landuse', landuse_code) + + def discretize_terrain(self, terrain_code: int) -> str: + """ + 离散化地形分类 + + Args: + terrain_code: 地形分类代码 + + Returns: + 离散状态标签 + """ + return self.discretize('terrain', terrain_code) + + def discretize_impervious(self, impervious_ratio: float) -> str: + """ + 离散化不透水面 + + Args: + impervious_ratio: 不透水面比例 + + Returns: + 离散状态标签 + """ + return self.discretize('impervious', impervious_ratio) + + def discretize_ndvi(self, ndvi_value: float) -> str: + """ + 离散化植被指数 + + Args: + ndvi_value: NDVI值 + + Returns: + 离散状态标签 + """ + return self.discretize('ndvi', ndvi_value) + + def discretize_sand_content(self, sand_percent: float) -> str: + """ + 离散化土壤含沙量 + + Args: + sand_percent: 含沙量百分比 + + Returns: + 离散状态标签 + """ + return self.discretize('sand_content', sand_percent) + + def discretize_ph(self, ph_value: float) -> str: + """ + 离散化土壤PH值 + + Args: + ph_value: PH值 + + Returns: + 离散状态标签 + """ + return self.discretize('ph', ph_value) + + def discretize_soil_moisture(self, moisture_percent: float) -> str: + """ + 离散化土壤湿度 + + Args: + moisture_percent: 湿度百分比 + + Returns: + 离散状态标签 + """ + return self.discretize('soil_moisture', moisture_percent) + + def discretize_organic_carbon(self, carbon_percent: float) -> str: + """ + 离散化有机碳 + + Args: + carbon_percent: 有机碳百分比 + + Returns: + 离散状态标签 + """ + return self.discretize('organic_carbon', carbon_percent) + + def discretize_dist_to_river(self, distance_m: float) -> str: + """ + 离散化距离河道距离 + + Args: + distance_m: 距离(米) + + Returns: + 离散状态标签 + """ + return self.discretize('dist_to_river', distance_m) + + def discretize_dist_to_fault(self, distance_m: float) -> str: + """ + 离散化距离断裂带距离 + + Args: + distance_m: 距离(米) + + Returns: + 离散状态标签 + """ + return self.discretize('dist_to_fault', distance_m) + + def discretize_pipe_density(self, density: float, + region_code: Optional[str] = None) -> str: + """ + 离散化供水管网密度 + + Args: + density: 管网密度(m/m²) + region_code: 行政区划代码 + + Returns: + 离散状态标签 + """ + return self.discretize('pipe_density', density, region_code) + + def discretize_all_factors(self, factors: Dict[str, Any], + region_code: Optional[str] = None) -> Dict[str, str]: + """ + 离散化所有因子 + + Args: + factors: 因子字典,key为因子名称,value为连续值 + region_code: 行政区划代码 + + Returns: + 离散化后的因子字典 + """ + result = {} + + # 暴雨触发层 + if 'rain_intensity' in factors: + result['rain_intensity'] = self.discretize_rain_intensity(factors['rain_intensity']) + if 'duration' in factors: + result['duration'] = self.discretize_duration(factors['duration']) + if 'accum_rain' in factors: + result['accum_rain'] = self.discretize_accum_rain(factors['accum_rain']) + + # 地震触发层 + if 'magnitude' in factors: + result['magnitude'] = self.discretize_magnitude(factors['magnitude']) + if 'epicenter_distance' in factors: + result['epicenter_distance'] = self.discretize_epicenter_distance(factors['epicenter_distance']) + if 'seismic_intensity' in factors: + result['seismic_intensity'] = self.discretize_seismic_intensity(factors['seismic_intensity']) + + # 环境层 + if 'elevation' in factors: + result['elevation'] = self.discretize_elevation(factors['elevation'], region_code) + if 'slope' in factors: + result['slope'] = self.discretize_slope(factors['slope']) + if 'aspect' in factors: + result['aspect'] = self.discretize_aspect(factors['aspect']) + if 'soil_type' in factors: + result['soil_type'] = self.discretize_soil_type(factors['soil_type']) + if 'lithology' in factors: + result['lithology'] = self.discretize_lithology(factors['lithology']) + if 'landuse' in factors: + result['landuse'] = self.discretize_landuse(factors['landuse']) + if 'terrain' in factors: + result['terrain'] = self.discretize_terrain(factors['terrain']) + if 'impervious' in factors: + result['impervious'] = self.discretize_impervious(factors['impervious']) + if 'ndvi' in factors: + result['ndvi'] = self.discretize_ndvi(factors['ndvi']) + if 'sand_content' in factors: + result['sand_content'] = self.discretize_sand_content(factors['sand_content']) + if 'ph' in factors: + result['ph'] = self.discretize_ph(factors['ph']) + if 'soil_moisture' in factors: + result['soil_moisture'] = self.discretize_soil_moisture(factors['soil_moisture']) + if 'organic_carbon' in factors: + result['organic_carbon'] = self.discretize_organic_carbon(factors['organic_carbon']) + if 'dist_to_river' in factors: + result['dist_to_river'] = self.discretize_dist_to_river(factors['dist_to_river']) + if 'dist_to_fault' in factors: + result['dist_to_fault'] = self.discretize_dist_to_fault(factors['dist_to_fault']) + if 'pipe_density' in factors: + result['pipe_density'] = self.discretize_pipe_density(factors['pipe_density'], region_code) + + return result + + +# 创建全局实例 +discretizer = Discretizer() diff --git a/app/utils/spatial_utils.py b/app/utils/spatial_utils.py new file mode 100644 index 0000000..18c22ff --- /dev/null +++ b/app/utils/spatial_utils.py @@ -0,0 +1,69 @@ +""" +空间计算工具模块 +提供纯数学计算功能(不涉及数据库查询) +""" +import math +from typing import Tuple + + +class SpatialUtils: + """空间计算工具类 - 纯数学计算""" + + EARTH_RADIUS = 6371000 # 地球半径(米) + + @staticmethod + def haversine_distance(lon1: float, lat1: float, lon2: float, lat2: float) -> float: + """ + 使用Haversine公式计算两点间的球面距离 + + Args: + lon1: 点1经度 + lat1: 点1纬度 + lon2: 点2经度 + lat2: 点2纬度 + + Returns: + 距离(米) + """ + lon1, lat1, lon2, lat2 = map(math.radians, [lon1, lat1, lon2, lat2]) + + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2 + c = 2 * math.asin(math.sqrt(a)) + distance = SpatialUtils.EARTH_RADIUS * c + + return distance + + @staticmethod + def calculate_buffer_area(radius: float) -> float: + """ + 计算缓冲区面积 + + Args: + radius: 半径(米) + + Returns: + 面积(平方米) + """ + return math.pi * radius ** 2 + + @staticmethod + def calculate_density(total_length: float, area: float) -> float: + """ + 计算密度(长度/面积) + + Args: + total_length: 总长度(米) + area: 面积(平方米) + + Returns: + 密度(m/m²) + """ + if area <= 0: + return 0.0 + return total_length / area + + +# 创建全局实例 +spatial_utils = SpatialUtils()