初始化
This commit is contained in:
6
tools/__init__.py
Normal file
6
tools/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
ArcGIS Pro工具集
|
||||
包含各种地图处理和栅格处理工具
|
||||
"""
|
||||
|
||||
__version__ = '1.1.0'
|
||||
54
tools/config/arcgis_field_cal_code.py
Normal file
54
tools/config/arcgis_field_cal_code.py
Normal file
@@ -0,0 +1,54 @@
|
||||
'''
|
||||
ArcGis 字段计算器 模块代码
|
||||
'''
|
||||
codeblock_dltb_yjdl = """
|
||||
def calculate_yjdl(dlbm):
|
||||
if str(dlbm).startswith('01'):
|
||||
return '耕地'
|
||||
elif str(dlbm).startswith('02'):
|
||||
return '园地'
|
||||
elif str(dlbm).startswith('03'):
|
||||
return '林地'
|
||||
elif str(dlbm).startswith('04'):
|
||||
return '草地'
|
||||
else:
|
||||
return '其他'"""
|
||||
|
||||
codeblock_dltb_ejdl = """
|
||||
def calculate_ejdl(dlbm,dlmc):
|
||||
if str(dlbm).startswith('03'):
|
||||
return '林地'
|
||||
elif str(dlbm).startswith('04'):
|
||||
return '草地'
|
||||
elif str(dlbm).startswith('12'):
|
||||
return '其他'
|
||||
elif str(dlbm).startswith('0101'):
|
||||
return '水田'
|
||||
elif str(dlbm).startswith('0102'):
|
||||
return '水浇地'
|
||||
elif str(dlbm).startswith('0103'):
|
||||
return '旱地'
|
||||
elif str(dlbm).startswith('0201'):
|
||||
return '果园'
|
||||
elif str(dlbm).startswith('0202'):
|
||||
return '茶园'
|
||||
elif str(dlbm).startswith('0203'):
|
||||
return '橡胶园'
|
||||
elif str(dlbm).startswith('0204'):
|
||||
return '其他园地'
|
||||
else:
|
||||
return dlmc"""
|
||||
|
||||
codeblock_cal_shfj = """
|
||||
def calculate_shfj(girdcode):
|
||||
if int(girdcode) == 1:
|
||||
return "重度酸化"
|
||||
elif int(girdcode) == 2:
|
||||
return "中度酸化"
|
||||
elif int(girdcode) == 3:
|
||||
return "轻度酸化"
|
||||
elif int(girdcode) == 4:
|
||||
return "弱酸化"
|
||||
else: # dPH
|
||||
return "其他"
|
||||
"""
|
||||
49
tools/config/common_config.py
Normal file
49
tools/config/common_config.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
公共配置
|
||||
- 地区分组
|
||||
- 土壤性质字典
|
||||
- 土壤性质分级字典
|
||||
- 土壤性质分级标准字典
|
||||
'''
|
||||
|
||||
# 地区分组
|
||||
guangxi_region = ['广西壮族自治区', '北海市', '海城区', '银海区', '铁山港区', '苍梧县', '容县', '靖西市', '兴宁区', '邕宁区', '武鸣区', '天峨县', '平南县', '港南区', '来宾市']
|
||||
yunnan_region = ['云南省', '西畴县', '马关县', '澜沧县', '双江县', '永德县', '寻甸县', '罗平县', '丘北县', '永仁县', '南华县', '双柏县', '武定县', '祥云县', '楚雄彝族自治州']
|
||||
|
||||
# 土壤性质字典
|
||||
soil_prop_dict = {
|
||||
"AB": "有效硼",
|
||||
"ACU": "有效铜",
|
||||
"AMN": "有效锰",
|
||||
"AMO": "有效钼",
|
||||
"AS1": "有效硫",
|
||||
"AZN": "有效锌",
|
||||
"CEC": "阳离子交换量",
|
||||
"ECA": "交换性钙",
|
||||
"EMG": "交换性镁",
|
||||
"TSE": "全硒",
|
||||
"TN": "全氮",
|
||||
"TP": "全磷",
|
||||
"TK": "全钾",
|
||||
"AFE": "有效铁",
|
||||
"AK": "速效钾",
|
||||
"AP": "有效磷",
|
||||
"TRRZ": "土壤容重",
|
||||
"OM": "有机质",
|
||||
"FL": "粉粒含量",
|
||||
"NL": "黏粒含量",
|
||||
"SL": "砂粒含量",
|
||||
"PH": "土壤 pH",
|
||||
"YXTCHD": "有效土层厚度",
|
||||
"GZCHD": "耕作层厚度",
|
||||
"TRZD": "土壤质地",
|
||||
"TRZD12": "土壤质地",
|
||||
"LSFD": "砾石丰度",
|
||||
"三普PH": "三普PH",
|
||||
"二普PH": "二普PH",
|
||||
"测土PH": "测土PH",
|
||||
"二普-三普": "二普-三普",
|
||||
"测土-三普": "测土-三普",
|
||||
"二普-测土": "二普-测土"
|
||||
}
|
||||
125
tools/config/custom_sort.py
Normal file
125
tools/config/custom_sort.py
Normal file
@@ -0,0 +1,125 @@
|
||||
'''
|
||||
自定义排序:yl_order-亚类排序,ts_order-土属排序
|
||||
'''
|
||||
|
||||
yl_order = [
|
||||
"典型砖红壤",
|
||||
"典型赤红壤",
|
||||
"典型红壤",
|
||||
"黄红壤",
|
||||
"红壤性土",
|
||||
"典型黄壤",
|
||||
"漂洗黄壤",
|
||||
"表潜黄壤",
|
||||
"黄壤性土",
|
||||
"暗黄棕壤",
|
||||
"典型新积土",
|
||||
"滨海风沙土",
|
||||
"红色石灰土",
|
||||
"黑色石灰土",
|
||||
"棕色石灰土",
|
||||
"黄色石灰土",
|
||||
"酸性紫色土",
|
||||
"中性紫色土",
|
||||
"石灰性紫色土",
|
||||
"硅质岩粗骨土",
|
||||
"典型潮土",
|
||||
"灰潮土",
|
||||
"山地灌丛草甸土",
|
||||
"泥炭沼泽土",
|
||||
"盐化沼泽土",
|
||||
"滨海潮滩盐土",
|
||||
"含盐酸性硫酸盐土",
|
||||
"潴育水稻土",
|
||||
"淹育水稻土",
|
||||
"渗育水稻土",
|
||||
"潜育水稻土",
|
||||
"漂洗水稻土",
|
||||
"咸酸水稻土",
|
||||
]
|
||||
ts_order = [
|
||||
"涂砂质砖红壤",
|
||||
"暗泥质砖红壤",
|
||||
"麻砂质砖红壤",
|
||||
"砂泥质砖红壤",
|
||||
"红泥质赤红壤",
|
||||
"暗泥质赤红壤",
|
||||
"麻砂质赤红壤",
|
||||
"硅质赤红壤",
|
||||
"砂泥质赤红壤",
|
||||
"泥质赤红壤",
|
||||
"灰泥质赤红壤",
|
||||
"红泥质红壤",
|
||||
"麻砂质红壤",
|
||||
"砂泥质红壤",
|
||||
"泥质红壤",
|
||||
"麻砂质黄红壤",
|
||||
"硅质黄红壤",
|
||||
"砂泥质黄红壤",
|
||||
"灰泥质黄红壤",
|
||||
"泥砂质红壤性土",
|
||||
"麻砂质黄壤",
|
||||
"砂泥质黄壤",
|
||||
"麻砂质漂洗黄壤",
|
||||
"砂泥质漂洗黄壤",
|
||||
"砂泥质表潜黄壤",
|
||||
"砂泥质黄壤性土",
|
||||
"灰泥质黄壤性土",
|
||||
"麻砂质暗黄棕壤",
|
||||
"砂泥质暗黄棕壤",
|
||||
"山洪土",
|
||||
"滨海固定风沙土",
|
||||
"红色石灰土",
|
||||
"黑色石灰土",
|
||||
"棕色石灰土",
|
||||
"黄色石灰土",
|
||||
"酸紫砂土",
|
||||
"酸紫壤土",
|
||||
"酸紫黏土",
|
||||
"紫泥土",
|
||||
"灰紫壤土",
|
||||
"灰紫泥土",
|
||||
"白粉土",
|
||||
"潮壤土",
|
||||
"石灰性灰潮壤土",
|
||||
"灰潮砂土",
|
||||
"灰潮壤土",
|
||||
"山地灌丛草甸砂土",
|
||||
"山地灌丛草甸壤土",
|
||||
"泥炭沼泽土",
|
||||
"盐化沼泽土",
|
||||
"涂砂盐土",
|
||||
"涂泥盐土",
|
||||
"含盐酸性硫酸盐土",
|
||||
"潮泥田",
|
||||
"潮泥砂田",
|
||||
"涂泥田",
|
||||
"麻砂泥田",
|
||||
"砂泥田",
|
||||
"鳝泥田",
|
||||
"灰泥田",
|
||||
"紫泥田",
|
||||
"红泥田",
|
||||
"白粉泥田",
|
||||
"暗泥田",
|
||||
"浅潮泥田",
|
||||
"浅潮泥砂田",
|
||||
"浅暗泥田",
|
||||
"浅麻砂泥田",
|
||||
"浅砂泥田",
|
||||
"浅鳝泥田",
|
||||
"浅灰泥田",
|
||||
"浅紫泥田",
|
||||
"浅白粉泥田",
|
||||
"浅红泥田",
|
||||
"渗潮泥田",
|
||||
"渗砂泥田",
|
||||
"渗紫泥田",
|
||||
"青潮泥田",
|
||||
"青灰泥田",
|
||||
"青红泥田",
|
||||
"烂泥田",
|
||||
"泥炭土田",
|
||||
"漂红泥田",
|
||||
"咸酸田",
|
||||
]
|
||||
141
tools/config/pandas_field_cal_func.py
Normal file
141
tools/config/pandas_field_cal_func.py
Normal file
@@ -0,0 +1,141 @@
|
||||
'''
|
||||
用于pandas dataframe中字段计算
|
||||
'''
|
||||
# 计算一级土地利用类型
|
||||
def calculate_yjdl(dlbm):
|
||||
if str(dlbm).startswith('01'):
|
||||
return '耕地'
|
||||
elif str(dlbm).startswith('02'):
|
||||
return '园地'
|
||||
elif str(dlbm).startswith('03'):
|
||||
return '林地'
|
||||
elif str(dlbm).startswith('04'):
|
||||
return '草地'
|
||||
else:
|
||||
return '其他'
|
||||
|
||||
# 计算二级土地利用类型
|
||||
def calculate_ejdl(dlbm):
|
||||
dlbm_str = str(dlbm).strip()
|
||||
|
||||
if dlbm_str.startswith('0101'):
|
||||
return '水田'
|
||||
elif dlbm_str.startswith('0102'):
|
||||
return '水浇地'
|
||||
elif dlbm_str.startswith('0103'):
|
||||
return '旱地'
|
||||
elif dlbm_str.startswith('0201'):
|
||||
return '果园'
|
||||
elif dlbm_str.startswith('0202'):
|
||||
return '茶园'
|
||||
elif dlbm_str.startswith('0203'):
|
||||
return '橡胶园'
|
||||
elif dlbm_str.startswith('0204'):
|
||||
return '其他园地'
|
||||
elif dlbm_str.startswith('03'):
|
||||
return '林地'
|
||||
elif dlbm_str.startswith('04'):
|
||||
return '草地'
|
||||
elif dlbm_str.startswith('12'):
|
||||
return '其他'
|
||||
else:
|
||||
return '未分类'
|
||||
|
||||
# 计算母岩母质
|
||||
def calculate_muyan(soil_name):
|
||||
soil_name = str(soil_name) # 确保为字符串
|
||||
# 将"紫"放在最前面
|
||||
if "紫" in soil_name:
|
||||
return "紫色砂页岩"
|
||||
elif "红砂" in soil_name:
|
||||
return "第三纪红砂岩"
|
||||
elif "麻砂" in soil_name:
|
||||
return "花岗岩或花岗片麻岩"
|
||||
elif "涂砂" in soil_name:
|
||||
return "砂质浅海沉积物"
|
||||
elif "暗泥" in soil_name:
|
||||
return "玄武岩、火山灰(渣)"
|
||||
elif "砂泥" in soil_name:
|
||||
return "砂页岩、砂岩、砂砾岩"
|
||||
elif "硅" in soil_name or "白粉泥" in soil_name:
|
||||
return "石英砂岩、石英岩、硅质岩"
|
||||
elif "灰泥" in soil_name or "石灰" in soil_name:
|
||||
return "石灰岩、白云岩、大理岩"
|
||||
elif "磷灰" in soil_name:
|
||||
return "磷灰岩"
|
||||
elif "红泥" in soil_name:
|
||||
return "第四纪红色黏土"
|
||||
elif "红土" in soil_name:
|
||||
return "第三纪红色黏土"
|
||||
elif "风砂" in soil_name:
|
||||
return "风积砂"
|
||||
elif "潮泥砂" in soil_name or "泥砂" in soil_name or "新积土" in soil_name:
|
||||
return "洪积物"
|
||||
elif "淡涂泥" in soil_name:
|
||||
return "河口相沉积物"
|
||||
elif "涂泥" in soil_name:
|
||||
return "海相沉积物"
|
||||
elif "黄泥" in soil_name:
|
||||
return "古老洪冲积物"
|
||||
|
||||
# 将容易误匹配的放在最后
|
||||
elif "潮泥" in soil_name or "潮砂" in soil_name or "潮土" in soil_name:
|
||||
return "冲积物"
|
||||
elif "泥" in soil_name or "鳝泥" in soil_name:
|
||||
return "片岩、板岩、千枚岩、页岩、泥岩"
|
||||
else:
|
||||
return "其他" # 或者返回空值 ""
|
||||
|
||||
|
||||
def calculate_muzhi(value):
|
||||
if value in ["第三纪红砂岩", "花岗岩或花岗片麻岩", "玄武岩、火山灰(渣)",
|
||||
"砂页岩、砂岩、砂砾岩", "片岩、板岩、千枚岩、页岩、泥岩", "石英砂岩、石英岩、硅质岩",
|
||||
"石灰岩、白云岩、大理岩", "磷灰岩", "紫色砂页岩", "第三纪红色黏土", "风积砂"]:
|
||||
return "残坡积物"
|
||||
elif value in ["冲积物", "第四纪红色黏土", "洪积物", "海相沉积物", "古老洪冲积物", "河口相沉积物", "砂质浅海沉积物"]:
|
||||
return "第四纪松散沉积物"
|
||||
else:
|
||||
return "未知"
|
||||
|
||||
"""
|
||||
"粉(砂)质黏壤土": "1",
|
||||
"粉(砂)质黏土": "2",
|
||||
"粉(砂)质壤土": "3",
|
||||
"黏壤土": "4",
|
||||
"黏土": "5",
|
||||
"壤土": "6",
|
||||
"壤质黏土": "7",
|
||||
"砂土及壤质砂土": "8",
|
||||
"砂质黏壤土": "9",
|
||||
"砂质黏土": "10",
|
||||
"砂质壤土": "11",
|
||||
"重黏土": "12"
|
||||
"""
|
||||
|
||||
def cal_trzd(value):
|
||||
if value == "砂土及壤质砂土":
|
||||
return 1
|
||||
elif value == "砂质壤土":
|
||||
return 2
|
||||
elif value == "壤土":
|
||||
return 3
|
||||
elif value == "粉砂质壤土":
|
||||
return 4
|
||||
elif value == "砂质黏壤土":
|
||||
return 5
|
||||
elif value == "黏壤土":
|
||||
return 6
|
||||
elif value == "粉砂质黏壤土":
|
||||
return 7
|
||||
elif value == "砂质黏土":
|
||||
return 8
|
||||
elif value == "壤质黏土":
|
||||
return 9
|
||||
elif value == "粉砂质黏土":
|
||||
return 10
|
||||
elif value == "黏土":
|
||||
return 11
|
||||
elif value == "重黏土":
|
||||
return 12
|
||||
else:
|
||||
return 0
|
||||
315
tools/config_json/raster_test_config.json
Normal file
315
tools/config_json/raster_test_config.json
Normal file
@@ -0,0 +1,315 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.80",
|
||||
"等级二": "1.00~1.80",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "2.00~3.00",
|
||||
"等级三": "1.00~2.00",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1800",
|
||||
"等级二": "1300~1800",
|
||||
"等级三": "800~1300",
|
||||
"等级四": "300~800",
|
||||
"等级五": "≤300"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">250",
|
||||
"等级二": "150~250",
|
||||
"等级三": "70~150",
|
||||
"等级四": "30~70",
|
||||
"等级五": "≤30"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.50",
|
||||
"等级二": "1.00~1.50",
|
||||
"等级三": "0.60~1.00",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "8.0~10.0",
|
||||
"等级四": "3.0~8.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "100~150",
|
||||
"等级三": "75~100",
|
||||
"等级四": "50~75",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "20.0~40.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "30.0~35.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤20",
|
||||
"等级二": "20~40",
|
||||
"等级三": "40~60",
|
||||
"等级四": "60~80",
|
||||
"等级五": ">80"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "土壤pH\n级别",
|
||||
"分级标准": "分级标准/\npH值",
|
||||
"标准等级": {
|
||||
"等级一": "6.0~7.0",
|
||||
"等级二": "7.0~7.5,\n5.5~6.0",
|
||||
"等级三": "7.5~8.0,\n5.0~5.5",
|
||||
"等级四": "8.0~8.5,\n4.5~5.0",
|
||||
"等级五": ">8.5,\n≤4.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层\n厚度级别",
|
||||
"分级标准": "分级标准/\ncm",
|
||||
"标准等级": {
|
||||
"等级一": ">80",
|
||||
"等级二": "70~80",
|
||||
"等级三": "60~70",
|
||||
"等级四": "50~60",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层\n厚度级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "1.00~1.20",
|
||||
"等级二": "1.20~1.30",
|
||||
"等级三": "1.30~1.40,\n0.90~1.0",
|
||||
"等级四": "1.40~1.50",
|
||||
"等级五": ">1.50,\n≤0.90"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
328
tools/config_json/云南_华南_config.json
Normal file
328
tools/config_json/云南_华南_config.json
Normal file
@@ -0,0 +1,328 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.80",
|
||||
"等级二": "1.00~1.80",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "2.00~3.00",
|
||||
"等级三": "1.00~2.00",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1000",
|
||||
"等级二": "500~1000",
|
||||
"等级三": "200~500",
|
||||
"等级四": "50~200",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">200",
|
||||
"等级二": "100~200",
|
||||
"等级三": "50~100",
|
||||
"等级四": "25~50",
|
||||
"等级五": "≤25"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.50",
|
||||
"等级二": "1.00~1.50",
|
||||
"等级三": "0.60~1.00",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "8.0~10.0",
|
||||
"等级四": "3.0~8.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "100~150",
|
||||
"等级三": "75~100",
|
||||
"等级四": "50~75",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "20.0~40.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "30.0~35.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤20",
|
||||
"等级二": "20~40",
|
||||
"等级三": "40~60",
|
||||
"等级四": "60~80",
|
||||
"等级五": ">80"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "6.0~7.0",
|
||||
"等级二": "7.0~7.5,\n5.5~6.0",
|
||||
"等级三": "7.5~8.0,\n5.0~5.5",
|
||||
"等级四": "8.0~8.5,\n4.5~5.0",
|
||||
"等级五": ">8.5,\n≤4.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">80",
|
||||
"等级二": "70~80",
|
||||
"等级三": "60~70",
|
||||
"等级四": "50~60",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "1.00~1.20",
|
||||
"等级二": "1.20~1.30",
|
||||
"等级三": "1.30~1.40,\n0.90~1.0",
|
||||
"等级四": "1.40~1.50",
|
||||
"等级五": ">1.50,\n≤0.90"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"黏壤质": "1",
|
||||
"黏质": "2",
|
||||
"壤质": "3",
|
||||
"砂壤质": "4",
|
||||
"砂质": "5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
328
tools/config_json/云南_华南_地块_config.json
Normal file
328
tools/config_json/云南_华南_地块_config.json
Normal file
@@ -0,0 +1,328 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图(地块)",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.80",
|
||||
"等级二": "1.00~1.80",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图(地块)",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图(地块)",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "2.00~3.00",
|
||||
"等级三": "1.00~2.00",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图(地块)",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1000",
|
||||
"等级二": "500~1000",
|
||||
"等级三": "200~500",
|
||||
"等级四": "50~200",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">200",
|
||||
"等级二": "100~200",
|
||||
"等级三": "50~100",
|
||||
"等级四": "25~50",
|
||||
"等级五": "≤25"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图(地块)",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.50",
|
||||
"等级二": "1.00~1.50",
|
||||
"等级三": "0.60~1.00",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "8.0~10.0",
|
||||
"等级四": "3.0~8.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图(地块)",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "100~150",
|
||||
"等级三": "75~100",
|
||||
"等级四": "50~75",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图(地块)",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "20.0~40.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图(地块)",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "30.0~35.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤20",
|
||||
"等级二": "20~40",
|
||||
"等级三": "40~60",
|
||||
"等级四": "60~80",
|
||||
"等级五": ">80"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "6.0~7.0",
|
||||
"等级二": "7.0~7.5,\n5.5~6.0",
|
||||
"等级三": "7.5~8.0,\n5.0~5.5",
|
||||
"等级四": "8.0~8.5,\n4.5~5.0",
|
||||
"等级五": ">8.5,\n≤4.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">80",
|
||||
"等级二": "70~80",
|
||||
"等级三": "60~70",
|
||||
"等级四": "50~60",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图(地块)",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "1.00~1.20",
|
||||
"等级二": "1.20~1.30",
|
||||
"等级三": "1.30~1.40,\n0.90~1.0",
|
||||
"等级四": "1.40~1.50",
|
||||
"等级五": ">1.50,\n≤0.90"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "1",
|
||||
"等级二": "2",
|
||||
"等级三": "3",
|
||||
"等级四": "4",
|
||||
"等级五": "5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
328
tools/config_json/云南_西南_config.json
Normal file
328
tools/config_json/云南_西南_config.json
Normal file
@@ -0,0 +1,328 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.00",
|
||||
"等级二": "0.80~1.00",
|
||||
"等级三": "0.50~0.80",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "15.0~30.0",
|
||||
"等级三": "5.0~15.0",
|
||||
"等级四": "1.0~5.0",
|
||||
"等级五": "≤1.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "1.00~3.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.30~0.50",
|
||||
"等级五": "≤0.30"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1500",
|
||||
"等级二": "1000~1500",
|
||||
"等级三": "500~1000",
|
||||
"等级四": "200~500",
|
||||
"等级五": "≤200"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">200",
|
||||
"等级二": "150~200",
|
||||
"等级三": "100~150",
|
||||
"等级四": "50~100",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.00",
|
||||
"等级二": "0.80~1.00",
|
||||
"等级三": "0.60~0.80",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "5.0~10.0",
|
||||
"等级四": "3.0~5.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "100~150",
|
||||
"等级三": "75~100",
|
||||
"等级四": "50~75",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "25.0~40.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "5.0~15.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "25.0~35.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤20",
|
||||
"等级二": "20~40",
|
||||
"等级三": "40~60",
|
||||
"等级四": "60~80",
|
||||
"等级五": ">80"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "6.0~7.0",
|
||||
"等级二": "7.0~7.5,\n5.5~6.0",
|
||||
"等级三": "7.5~8.0,\n5.0~5.5",
|
||||
"等级四": "8.0~8.5,\n4.5~5.0",
|
||||
"等级五": ">8.5,\n≤4.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">80",
|
||||
"等级二": "70~80",
|
||||
"等级三": "60~70",
|
||||
"等级四": "50~60",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "1.10~1.25",
|
||||
"等级二": "1.25~1.35,\n1.00~1.10",
|
||||
"等级三": "1.35~1.45",
|
||||
"等级四": "1.45~1.55,\n0.90~1.00",
|
||||
"等级五": ">1.55,\n≤0.90"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"黏壤质": "1",
|
||||
"黏质": "2",
|
||||
"壤质": "3",
|
||||
"砂壤质": "4",
|
||||
"砂质": "5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
tools/config_json/华南分级.json
Normal file
30
tools/config_json/华南分级.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"AB": ">2.00;1.00~2.00;0.50~1.00;0.20~0.50;≤0.20",
|
||||
"ACU": ">1.80;1.00~1.80;0.50~1.00;0.20~0.50;≤0.20",
|
||||
"AMN": ">30.0;20.0~30.0;10.0~20.0;5.0~10.0;≤5.0",
|
||||
"AMO": ">0.20;0.15~0.20;0.10~0.15;0.05~0.10;≤0.05",
|
||||
"AS1": ">40.0;30.0~40.0;20.0~30.0;10.0~20.0;≤10.0",
|
||||
"AZN": ">3.00;2.00~3.00;1.00~2.00;0.50~1.00;≤0.50",
|
||||
"CEC": ">20.0;15.0~20.0;10.0~15.0;5.0~10.0;≤5.0",
|
||||
"ECA": ">4.99;2.50~4.99;1.00~2.50;0.25~1.00;≤0.25",
|
||||
"EMG": ">1.64;0.82~1.64;0.41~0.82;0.21~0.41;≤0.21",
|
||||
"TSE": ">3.00;0.40~3.00;0.17~0.40;≤0.17",
|
||||
"TN": ">2.00;1.50~2.00;1.00~1.50;0.50~1.00;≤0.50",
|
||||
"TP": ">1.50;1.00~1.50;0.60~1.00;0.40~0.60;≤0.40",
|
||||
"TK": ">20.0;15.0~20.0;10.0~15.0;5.0~10.0;≤5.0",
|
||||
"AFE": ">20.0;10.0~20.0;8.0~10.0;3.0~8.0;≤3.0",
|
||||
"AK": ">150;100~150;75~100;50~75;≤50",
|
||||
"AP": ">40.0;20.0~40.0;10.0~20.0;5.0~10.0;≤5.0",
|
||||
"OM": ">35.0;30.0~35.0;20.0~30.0;10.0~20.0;≤10.0",
|
||||
"FL": "≤15;15~30;30~45;45~75;>75",
|
||||
"NL": "≤15;15~25;25~45;45~65;>65",
|
||||
"SL": "≤30;30~40;40~55;55~85;>85",
|
||||
"PH": "6.0~7.0;7.0~7.5,5.5~6.0;7.5~8.0,5.0~5.5;8.0~8.5,4.5~5.0;>8.5,≤4.5",
|
||||
"YXTCHD": ">100;80~100;60~80;40~60;≤40",
|
||||
"GZCHD": ">25.0;20.0~25.0;15.0~20.0;10.0~15.0;≤10.0",
|
||||
"TRRZ": "1.00~1.20;1.20~1.30;1.30~1.40,0.90~1.0;1.40~1.50;>1.50,≤0.90",
|
||||
"TRZD": "1;2;3;4;5",
|
||||
"二普PH": ">6.5;5.5~6.5;4.5~5.5;≤4.5",
|
||||
"三普PH": ">6.5;5.5~6.5;4.5~5.5;≤4.5",
|
||||
"酸化PH": ">1.0;0.5~1.0;0.3~0.5;-0.3~0.3;-10~-0.3"
|
||||
}
|
||||
468
tools/config_json/广西_华南_config.json
Normal file
468
tools/config_json/广西_华南_config.json
Normal file
@@ -0,0 +1,468 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.80",
|
||||
"等级二": "1.00~1.80",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "2.00~3.00",
|
||||
"等级三": "1.00~2.00",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Ca²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">4.99",
|
||||
"等级二": "2.50~4.99",
|
||||
"等级三": "1.00~2.50",
|
||||
"等级四": "0.25~1.00",
|
||||
"等级五": "≤0.25"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Mg²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">1.64",
|
||||
"等级二": "0.82~1.64",
|
||||
"等级三": "0.41~0.82",
|
||||
"等级四": "0.21~0.41",
|
||||
"等级五": "≤0.21"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.50",
|
||||
"等级二": "1.00~1.50",
|
||||
"等级三": "0.60~1.00",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "8.0~10.0",
|
||||
"等级四": "3.0~8.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "100~150",
|
||||
"等级三": "75~100",
|
||||
"等级四": "50~75",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "20.0~40.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "30.0~35.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~30",
|
||||
"等级三": "30~45",
|
||||
"等级四": "45~75",
|
||||
"等级五": ">75"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤30",
|
||||
"等级二": "30~40",
|
||||
"等级三": "40~55",
|
||||
"等级四": "55~85",
|
||||
"等级五": ">85"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "≤4.5",
|
||||
"等级二": "4.5~5.0",
|
||||
"等级三": "5.0~5.5",
|
||||
"等级四": "5.5~6.0",
|
||||
"等级五": "6.0~7.0",
|
||||
"等级六": "7.0~7.5",
|
||||
"等级七": "7.5~8.0",
|
||||
"等级八": "8.0~8.5",
|
||||
"等级九": ">8.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">100",
|
||||
"等级二": "80~100",
|
||||
"等级三": "60~80",
|
||||
"等级四": "40~60",
|
||||
"等级五": "≤40"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "≤0.90",
|
||||
"等级二": "0.90~1.0",
|
||||
"等级三": "1.00~1.20",
|
||||
"等级四": "1.20~1.30",
|
||||
"等级五": "1.30~1.40",
|
||||
"等级六": "1.40~1.50",
|
||||
"等级七": ">1.50"
|
||||
}
|
||||
},
|
||||
"LSFD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砾石丰度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "砾石丰度\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤0",
|
||||
"等级二": "0~5",
|
||||
"等级三": "5~15",
|
||||
"等级四": "15~50",
|
||||
"等级五": ">50"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"砂质": "1",
|
||||
"砂壤质": "2",
|
||||
"壤质": "3",
|
||||
"黏壤质": "4",
|
||||
"黏质": "5"
|
||||
}
|
||||
},
|
||||
"TRZD12": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"砂土及壤质砂土": "1",
|
||||
"砂质壤土": "2",
|
||||
"壤土": "3",
|
||||
"粉(砂)质壤土": "4",
|
||||
"砂质黏壤土": "5",
|
||||
"黏壤土": "6",
|
||||
"粉(砂)质黏壤土": "7",
|
||||
"砂质黏土": "8",
|
||||
"壤质黏土": "9",
|
||||
"粉(砂)质黏土": "10",
|
||||
"黏土": "11",
|
||||
"重黏土": "12"
|
||||
}
|
||||
},
|
||||
"二普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"测土PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"三普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}三普土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"酸化PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"弱酸化": "0.1~0.3",
|
||||
"碱化": "-10~0.1"
|
||||
}
|
||||
},
|
||||
"二普-三普_PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"弱酸化": "0.1~0.3",
|
||||
"碱化": "-10~0.1"
|
||||
}
|
||||
},
|
||||
"测土-三普_PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"弱酸化": "0.1~0.3",
|
||||
"碱化": "-10~0.1"
|
||||
}
|
||||
},
|
||||
"二普-测土_PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"弱酸化": "0.1~0.3",
|
||||
"碱化": "-10~0.1"
|
||||
}
|
||||
},
|
||||
"专题图OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": "≤10.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "30.0~40.0",
|
||||
"等级五": ">40.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
404
tools/config_json/广西_华南_地块_config.json
Normal file
404
tools/config_json/广西_华南_地块_config.json
Normal file
@@ -0,0 +1,404 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图(地块)",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.80",
|
||||
"等级二": "1.00~1.80",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图(地块)",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图(地块)",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "2.00~3.00",
|
||||
"等级三": "1.00~2.00",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图(地块)",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Ca²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">4.99",
|
||||
"等级二": "2.50~4.99",
|
||||
"等级三": "1.00~2.50",
|
||||
"等级四": "0.25~1.00",
|
||||
"等级五": "≤0.25"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Mg²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">1.64",
|
||||
"等级二": "0.82~1.64",
|
||||
"等级三": "0.41~0.82",
|
||||
"等级四": "0.21~0.41",
|
||||
"等级五": "≤0.21"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图(地块)",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.50",
|
||||
"等级二": "1.00~1.50",
|
||||
"等级三": "0.60~1.00",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "8.0~10.0",
|
||||
"等级四": "3.0~8.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图(地块)",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "100~150",
|
||||
"等级三": "75~100",
|
||||
"等级四": "50~75",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图(地块)",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "20.0~40.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图(地块)",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "30.0~35.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~30",
|
||||
"等级三": "30~45",
|
||||
"等级四": "45~75",
|
||||
"等级五": ">75"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤30",
|
||||
"等级二": "30~40",
|
||||
"等级三": "40~55",
|
||||
"等级四": "55~85",
|
||||
"等级五": ">85"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "≤4.5",
|
||||
"等级二": "4.5~5.0",
|
||||
"等级三": "5.0~5.5",
|
||||
"等级四": "5.5~6.0",
|
||||
"等级五": "6.0~7.0",
|
||||
"等级六": "7.0~7.5",
|
||||
"等级七": "7.5~8.0",
|
||||
"等级八": "8.0~8.5",
|
||||
"等级九": ">8.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">100",
|
||||
"等级二": "80~100",
|
||||
"等级三": "60~80",
|
||||
"等级四": "40~60",
|
||||
"等级五": "≤40"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图(地块)",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "≤0.90",
|
||||
"等级二": "0.90~1.0",
|
||||
"等级三": "1.00~1.20",
|
||||
"等级四": "1.20~1.30",
|
||||
"等级五": "1.30~1.40",
|
||||
"等级六": "1.40~1.50",
|
||||
"等级七": ">1.50"
|
||||
}
|
||||
},
|
||||
"LSFD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砾石丰度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "砾石丰度\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤0",
|
||||
"等级二": "0~5",
|
||||
"等级三": "5~15",
|
||||
"等级四": "15~50",
|
||||
"等级五": ">50"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"砂质": "1",
|
||||
"砂壤质": "2",
|
||||
"壤质": "3",
|
||||
"黏壤质": "4",
|
||||
"黏质": "5"
|
||||
}
|
||||
},
|
||||
"TRZD12": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"砂土及壤质砂土": "1",
|
||||
"砂质壤土": "2",
|
||||
"壤土": "3",
|
||||
"粉(砂)质壤土": "4",
|
||||
"砂质黏壤土": "5",
|
||||
"黏壤土": "6",
|
||||
"粉(砂)质黏壤土": "7",
|
||||
"砂质黏土": "8",
|
||||
"壤质黏土": "9",
|
||||
"粉(砂)质黏土": "10",
|
||||
"黏土": "11",
|
||||
"重黏土": "12"
|
||||
}
|
||||
},
|
||||
"二普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"三普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}三普土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"酸化PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"未酸化": "-0.3~0.3",
|
||||
"碱化": "-10~-0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
418
tools/config_json/广西_西南_config.json
Normal file
418
tools/config_json/广西_西南_config.json
Normal file
@@ -0,0 +1,418 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.00",
|
||||
"等级二": "0.8~1.00",
|
||||
"等级三": "0.50~0.80",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "15.0~30.0",
|
||||
"等级三": "5.0~15.0",
|
||||
"等级四": "1.0~5.0",
|
||||
"等级五": "≤1.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "1.00~3.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Ca²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">7.49",
|
||||
"等级二": "4.99~7.49",
|
||||
"等级三": "2.50~4.99",
|
||||
"等级四": "1.00~2.50",
|
||||
"等级五": "≤1.00"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Mg²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">1.64",
|
||||
"等级二": "1.23~1.64",
|
||||
"等级三": "0.82~1.23",
|
||||
"等级四": "0.41~0.82",
|
||||
"等级五": "≤0.41"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.00",
|
||||
"等级二": "0.80~1.00",
|
||||
"等级三": "0.60~0.80",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "5.0~10.0",
|
||||
"等级四": "3.0~5.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "100~150",
|
||||
"等级三": "75~100",
|
||||
"等级四": "50~75",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "25.0~40.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "5.0~15.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "25.0~35.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~30",
|
||||
"等级三": "30~45",
|
||||
"等级四": "45~75",
|
||||
"等级五": ">75"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤30",
|
||||
"等级二": "30~40",
|
||||
"等级三": "40~55",
|
||||
"等级四": "55~85",
|
||||
"等级五": ">85"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "≤4.5",
|
||||
"等级二": "4.5~5.0",
|
||||
"等级三": "5.0~5.5",
|
||||
"等级四": "5.5~6.0",
|
||||
"等级五": "6.0~7.0",
|
||||
"等级六": "7.0~7.5",
|
||||
"等级七": "7.5~8.0",
|
||||
"等级八": "8.0~8.5",
|
||||
"等级九": ">8.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">100",
|
||||
"等级二": "80~100",
|
||||
"等级三": "60~80",
|
||||
"等级四": "40~60",
|
||||
"等级五": "≤40"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "≤0.90",
|
||||
"等级二": "0.90~1.00",
|
||||
"等级三": "1.00~1.10",
|
||||
"等级四": "1.10~1.25",
|
||||
"等级五": "1.25~1.35",
|
||||
"等级六": "1.35~1.45",
|
||||
"等级七": "1.45~1.55",
|
||||
"等级八": ">1.55"
|
||||
}
|
||||
},
|
||||
"LSFD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砾石丰度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "砾石丰度\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤0",
|
||||
"等级二": "0~5",
|
||||
"等级三": "5~15",
|
||||
"等级四": "15~50",
|
||||
"等级五": ">50"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分类分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n分类",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"砂质": "1",
|
||||
"砂壤质": "2",
|
||||
"壤质": "3",
|
||||
"黏壤质": "4",
|
||||
"黏质": "5"
|
||||
}
|
||||
},
|
||||
"TRZD12": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"砂土及壤质砂土": "1",
|
||||
"砂质壤土": "2",
|
||||
"壤土": "3",
|
||||
"粉(砂)质壤土": "4",
|
||||
"砂质黏壤土": "5",
|
||||
"黏壤土": "6",
|
||||
"粉(砂)质黏壤土": "7",
|
||||
"砂质黏土": "8",
|
||||
"壤质黏土": "9",
|
||||
"粉(砂)质黏土": "10",
|
||||
"黏土": "11",
|
||||
"重黏土": "12"
|
||||
}
|
||||
},
|
||||
"二普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"三普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}三普土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"酸化PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"未酸化": "-0.3~0.3",
|
||||
"碱化": "-10~-0.3"
|
||||
}
|
||||
},
|
||||
"专题图OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": "≤10.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "30.0~40.0",
|
||||
"等级五": ">40.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
405
tools/config_json/广西_西南_地块_config.json
Normal file
405
tools/config_json/广西_西南_地块_config.json
Normal file
@@ -0,0 +1,405 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图(地块)",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.00",
|
||||
"等级二": "0.8~1.00",
|
||||
"等级三": "0.50~0.80",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "15.0~30.0",
|
||||
"等级三": "5.0~15.0",
|
||||
"等级四": "1.0~5.0",
|
||||
"等级五": "≤1.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图(地块)",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图(地块)",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "1.00~3.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.20~0.50",
|
||||
"等级五": "≤0.20"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图(地块)",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Ca²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">7.49",
|
||||
"等级二": "4.99~7.49",
|
||||
"等级三": "2.50~4.99",
|
||||
"等级四": "1.00~2.50",
|
||||
"等级五": "≤1.00"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Mg²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">1.64",
|
||||
"等级二": "1.23~1.64",
|
||||
"等级三": "0.82~1.23",
|
||||
"等级四": "0.41~0.82",
|
||||
"等级五": "≤0.41"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图(地块)",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.00",
|
||||
"等级二": "0.80~1.00",
|
||||
"等级三": "0.60~0.80",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "15.0~20.0",
|
||||
"等级三": "10.0~15.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "5.0~10.0",
|
||||
"等级四": "3.0~5.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图(地块)",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "100~150",
|
||||
"等级三": "75~100",
|
||||
"等级四": "50~75",
|
||||
"等级五": "≤50"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图(地块)",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "25.0~40.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "5.0~15.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图(地块)",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "25.0~35.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~30",
|
||||
"等级三": "30~45",
|
||||
"等级四": "45~75",
|
||||
"等级五": ">75"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤30",
|
||||
"等级二": "30~40",
|
||||
"等级三": "40~55",
|
||||
"等级四": "55~85",
|
||||
"等级五": ">85"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "≤4.5",
|
||||
"等级二": "4.5~5.0",
|
||||
"等级三": "5.0~5.5",
|
||||
"等级四": "5.5~6.0",
|
||||
"等级五": "6.0~7.0",
|
||||
"等级六": "7.0~7.5",
|
||||
"等级七": "7.5~8.0",
|
||||
"等级八": "8.0~8.5",
|
||||
"等级九": ">8.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">100",
|
||||
"等级二": "80~100",
|
||||
"等级三": "60~80",
|
||||
"等级四": "40~60",
|
||||
"等级五": "≤40"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图(地块)",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "≤0.90",
|
||||
"等级二": "0.90~1.00",
|
||||
"等级三": "1.00~1.10",
|
||||
"等级四": "1.10~1.25",
|
||||
"等级五": "1.25~1.35",
|
||||
"等级六": "1.35~1.45",
|
||||
"等级七": "1.45~1.55",
|
||||
"等级八": ">1.55"
|
||||
}
|
||||
},
|
||||
"LSFD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砾石丰度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "砾石丰度\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤0",
|
||||
"等级二": "0~5",
|
||||
"等级三": "5~15",
|
||||
"等级四": "15~50",
|
||||
"等级五": ">50"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分类分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n分类",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"砂质": "1",
|
||||
"砂壤质": "2",
|
||||
"壤质": "3",
|
||||
"黏壤质": "4",
|
||||
"黏质": "5"
|
||||
}
|
||||
},
|
||||
"TRZD12": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"砂土及壤质砂土": "1",
|
||||
"砂质壤土": "2",
|
||||
"壤土": "3",
|
||||
"粉(砂)质壤土": "4",
|
||||
"砂质黏壤土": "5",
|
||||
"黏壤土": "6",
|
||||
"粉(砂)质黏壤土": "7",
|
||||
"砂质黏土": "8",
|
||||
"壤质黏土": "9",
|
||||
"粉(砂)质黏土": "10",
|
||||
"黏土": "11",
|
||||
"重黏土": "12"
|
||||
}
|
||||
},
|
||||
"二普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"三普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}三普土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"酸化PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"未酸化": "-0.3~0.3",
|
||||
"碱化": "-10~-0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
365
tools/config_json/广西_长江中下游_config.json
Normal file
365
tools/config_json/广西_长江中下游_config.json
Normal file
@@ -0,0 +1,365 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.25~0.50",
|
||||
"等级五": "≤0.25"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.80",
|
||||
"等级二": "1.00~1.80",
|
||||
"等级三": "0.20~1.00",
|
||||
"等级四": "0.10~0.20",
|
||||
"等级五": "≤0.10"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">50.0",
|
||||
"等级二": "15.0~50.0",
|
||||
"等级三": "7.0~15.0",
|
||||
"等级四": "3.0~7.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "2.00~3.00",
|
||||
"等级三": "1.00~2.00",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Ca²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">5.99",
|
||||
"等级二": "3.99~5.99",
|
||||
"等级三": "2.50~3.99",
|
||||
"等级四": "1.00~2.50",
|
||||
"等级五": "≤1.00"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Mg²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">2.47",
|
||||
"等级二": "1.64~2.47",
|
||||
"等级三": "0.82~1.64",
|
||||
"等级四": "0.41~0.82",
|
||||
"等级五": "≤0.41"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.75~1.00",
|
||||
"等级五": "≤0.75"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.00",
|
||||
"等级二": "0.80~1.00",
|
||||
"等级三": "0.60~0.80",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "4.5~10.0",
|
||||
"等级四": "2.5~4.5",
|
||||
"等级五": "≤2.5"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "125~150",
|
||||
"等级三": "100~125",
|
||||
"等级四": "75~100",
|
||||
"等级五": "≤75"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "25.0~35.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "25.0~35.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~30",
|
||||
"等级三": "30~45",
|
||||
"等级四": "45~75",
|
||||
"等级五": ">75"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤30",
|
||||
"等级二": "30~40",
|
||||
"等级三": "40~55",
|
||||
"等级四": "55~85",
|
||||
"等级五": ">85"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "6.5~7.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "7.5~8.5",
|
||||
"等级四": "4.5~5.5",
|
||||
"等级五": ">8.5,\n≤4.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">100",
|
||||
"等级二": "80~100",
|
||||
"等级三": "60~80",
|
||||
"等级四": "40~60",
|
||||
"等级五": "≤40"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "16.0~20.0",
|
||||
"等级三": "12.0~16.0",
|
||||
"等级四": "8.0~12.0",
|
||||
"等级五": "≤8.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "1.00~1.20",
|
||||
"等级二": "1.20~1.30,\n0.90~1.00",
|
||||
"等级三": "1.30~1.40",
|
||||
"等级四": "1.40~1.50",
|
||||
"等级五": ">1.50,\n≤0.90"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"黏壤质": "1",
|
||||
"黏质": "2",
|
||||
"壤质": "3",
|
||||
"砂壤质": "4",
|
||||
"砂质": "5"
|
||||
}
|
||||
},
|
||||
"二普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"三普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}三普土壤pH分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"酸化PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"未酸化": "-0.3~0.3",
|
||||
"碱化": "-10~-0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
365
tools/config_json/广西_长江中下游_地块_config.json
Normal file
365
tools/config_json/广西_长江中下游_地块_config.json
Normal file
@@ -0,0 +1,365 @@
|
||||
{
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硼含量分布图(地块)",
|
||||
"分析方法": "分析方法:沸水提取-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.00~2.00",
|
||||
"等级三": "0.50~1.00",
|
||||
"等级四": "0.25~0.50",
|
||||
"等级五": "≤0.25"
|
||||
}
|
||||
},
|
||||
"ACU": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铜含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铜\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.80",
|
||||
"等级二": "1.00~1.80",
|
||||
"等级三": "0.20~1.00",
|
||||
"等级四": "0.10~0.20",
|
||||
"等级五": "≤0.10"
|
||||
}
|
||||
},
|
||||
"AMN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锰含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锰\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">50.0",
|
||||
"等级二": "15.0~50.0",
|
||||
"等级三": "7.0~15.0",
|
||||
"等级四": "3.0~7.0",
|
||||
"等级五": "≤3.0"
|
||||
}
|
||||
},
|
||||
"AMO": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效钼含量分布图(地块)",
|
||||
"分析方法": "分析方法:草酸-草酸铵浸提-电感耦合等离子体质谱法",
|
||||
"项目分级": "有效钼\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">0.20",
|
||||
"等级二": "0.15~0.20",
|
||||
"等级三": "0.10~0.15",
|
||||
"等级四": "0.05~0.10",
|
||||
"等级五": "≤0.05"
|
||||
}
|
||||
},
|
||||
"AS1": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效硫含量分布图(地块)",
|
||||
"分析方法": "分析方法:电感耦合等离子体发射光谱法",
|
||||
"项目分级": "有效硫\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">40.0",
|
||||
"等级二": "30.0~40.0",
|
||||
"等级三": "20.0~30.0",
|
||||
"等级四": "10.0~20.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AZN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效锌含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效锌\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "2.00~3.00",
|
||||
"等级三": "1.00~2.00",
|
||||
"等级四": "0.50~1.00",
|
||||
"等级五": "≤0.50"
|
||||
}
|
||||
},
|
||||
"CEC": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤阳离子交换量分布图(地块)",
|
||||
"分析方法": "分析方法:EDTA-乙酸铵交换法",
|
||||
"项目分级": "阳离子\n交换量级别",
|
||||
"分级标准": "分级标准/\n(cmol/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">30.0",
|
||||
"等级二": "20.0~30.0",
|
||||
"等级三": "10.0~20.0",
|
||||
"等级四": "5.0~10.0",
|
||||
"等级五": "≤5.0"
|
||||
}
|
||||
},
|
||||
"ECA": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性钙含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性钙\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Ca²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">5.99",
|
||||
"等级二": "3.99~5.99",
|
||||
"等级三": "2.50~3.99",
|
||||
"等级四": "1.00~2.50",
|
||||
"等级五": "≤1.00"
|
||||
}
|
||||
},
|
||||
"EMG": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤交换性镁含量分布图(地块)",
|
||||
"分析方法": "分析方法:原子吸收分光光度法",
|
||||
"项目分级": "交换性镁\n含量级别",
|
||||
"分级标准": "分级标准/\ncmol(½Mg²⁺)/kg",
|
||||
"标准等级": {
|
||||
"等级一": ">2.47",
|
||||
"等级二": "1.64~2.47",
|
||||
"等级三": "0.82~1.64",
|
||||
"等级四": "0.41~0.82",
|
||||
"等级五": "≤0.41"
|
||||
}
|
||||
},
|
||||
"TSE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全硒含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸溶-氢化物发生-原子荧光光谱法",
|
||||
"项目分级": "全硒\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">3.00",
|
||||
"等级二": "0.40~3.00",
|
||||
"等级三": "0.17~0.40",
|
||||
"等级四": "≤0.17"
|
||||
}
|
||||
},
|
||||
"TN": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全氮含量分布图(地块)",
|
||||
"分析方法": "分析方法:自动定氮仪法",
|
||||
"项目分级": "全氮\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">2.00",
|
||||
"等级二": "1.50~2.00",
|
||||
"等级三": "1.00~1.50",
|
||||
"等级四": "0.75~1.00",
|
||||
"等级五": "≤0.75"
|
||||
}
|
||||
},
|
||||
"TP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全磷含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">1.00",
|
||||
"等级二": "0.80~1.00",
|
||||
"等级三": "0.60~0.80",
|
||||
"等级四": "0.40~0.60",
|
||||
"等级五": "≤0.40"
|
||||
}
|
||||
},
|
||||
"TK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤全钾含量分布图(地块)",
|
||||
"分析方法": "分析方法:酸消解-电感耦合等离子体发射光谱法",
|
||||
"项目分级": "全钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">25.0",
|
||||
"等级二": "20.0~25.0",
|
||||
"等级三": "15.0~20.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"AFE": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效铁含量分布图(地块)",
|
||||
"分析方法": "分析方法:二乙三胺五乙酸(DTPA)浸提法",
|
||||
"项目分级": "有效铁\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "10.0~20.0",
|
||||
"等级三": "4.5~10.0",
|
||||
"等级四": "2.5~4.5",
|
||||
"等级五": "≤2.5"
|
||||
}
|
||||
},
|
||||
"AK": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤速效钾含量分布图(地块)",
|
||||
"分析方法": "分析方法:乙酸钠浸提-火焰光度法",
|
||||
"项目分级": "速效钾\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">150",
|
||||
"等级二": "125~150",
|
||||
"等级三": "100~125",
|
||||
"等级四": "75~100",
|
||||
"等级五": "≤75"
|
||||
}
|
||||
},
|
||||
"AP": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效磷含量分布图(地块)",
|
||||
"分析方法": "分析方法:氟化铵-盐酸溶液浸提-钼锑抗比色法",
|
||||
"项目分级": "有效磷\n含量级别",
|
||||
"分级标准": "分级标准/\n(mg/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "25.0~35.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"OM": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有机质含量分布图(地块)",
|
||||
"分析方法": "分析方法:重铬酸钾氧化-容量法",
|
||||
"项目分级": "有机质\n含量级别",
|
||||
"分级标准": "分级标准/\n(g/kg)",
|
||||
"标准等级": {
|
||||
"等级一": ">35.0",
|
||||
"等级二": "25.0~35.0",
|
||||
"等级三": "15.0~25.0",
|
||||
"等级四": "10.0~15.0",
|
||||
"等级五": "≤10.0"
|
||||
}
|
||||
},
|
||||
"FL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤粉粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "粉粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~30",
|
||||
"等级三": "30~45",
|
||||
"等级四": "45~75",
|
||||
"等级五": ">75"
|
||||
}
|
||||
},
|
||||
"NL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤黏粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "黏粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤15",
|
||||
"等级二": "15~25",
|
||||
"等级三": "25~45",
|
||||
"等级四": "45~65",
|
||||
"等级五": ">65"
|
||||
}
|
||||
},
|
||||
"SL": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤砂粒含量分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "砂粒含量\n级别",
|
||||
"分级标准": "分级标准/\n(%)",
|
||||
"标准等级": {
|
||||
"等级一": "≤30",
|
||||
"等级二": "30~40",
|
||||
"等级三": "40~55",
|
||||
"等级四": "55~85",
|
||||
"等级五": ">85"
|
||||
}
|
||||
},
|
||||
"PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "6.5~7.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "7.5~8.5",
|
||||
"等级四": "4.5~5.5",
|
||||
"等级五": ">8.5,\n≤4.5"
|
||||
}
|
||||
},
|
||||
"YXTCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤有效土层厚度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "有效土层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">100",
|
||||
"等级二": "80~100",
|
||||
"等级三": "60~80",
|
||||
"等级四": "40~60",
|
||||
"等级五": "≤40"
|
||||
}
|
||||
},
|
||||
"GZCHD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤耕作层厚度分布图(地块)",
|
||||
"分析方法": "分析方法:剖面法",
|
||||
"项目分级": "耕作层厚度\n级别",
|
||||
"分级标准": "分级标准/\n(cm)",
|
||||
"标准等级": {
|
||||
"等级一": ">20.0",
|
||||
"等级二": "16.0~20.0",
|
||||
"等级三": "12.0~16.0",
|
||||
"等级四": "8.0~12.0",
|
||||
"等级五": "≤8.0"
|
||||
}
|
||||
},
|
||||
"TRRZ": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤容重分布图(地块)",
|
||||
"分析方法": "分析方法:热鼓风加热环刀法",
|
||||
"项目分级": "土壤容重\n级别",
|
||||
"分级标准": "分级标准/\n(g/cm³)",
|
||||
"标准等级": {
|
||||
"等级一": "1.00~1.20",
|
||||
"等级二": "1.20~1.30,\n0.90~1.00",
|
||||
"等级三": "1.30~1.40",
|
||||
"等级四": "1.40~1.50",
|
||||
"等级五": ">1.50,\n≤0.90"
|
||||
}
|
||||
},
|
||||
"TRZD": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤质地分布图(地块)",
|
||||
"分析方法": "分析方法:吸管法",
|
||||
"项目分级": "土壤质地\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"黏壤质": "1",
|
||||
"黏质": "2",
|
||||
"壤质": "3",
|
||||
"砂壤质": "4",
|
||||
"砂质": "5"
|
||||
}
|
||||
},
|
||||
"二普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"三普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}三普土壤pH分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": ">6.5",
|
||||
"等级二": "5.5~6.5",
|
||||
"等级三": "4.5~5.5",
|
||||
"等级四": "≤4.5"
|
||||
}
|
||||
},
|
||||
"酸化PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}二普土壤酸化分布图(地块)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"重度酸化": ">1.0",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"未酸化": "-0.3~0.3",
|
||||
"碱化": "-10~-0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
tools/config_json/西南分级.json
Normal file
30
tools/config_json/西南分级.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"AB": ">1.00;0.8~1.00;0.50~0.80;0.20~0.50;≤0.20",
|
||||
"ACU": ">2.00;1.00~2.00;0.50~1.00;0.20~0.50;≤0.20",
|
||||
"AMN": ">30.0;15.0~30.0;5.0~15.0;1.0~5.0;≤1.0",
|
||||
"AMO": ">0.20;0.15~0.20;0.10~0.15;0.05~0.10;≤0.05",
|
||||
"AS1": ">40.0;30.0~40.0;20.0~30.0;10.0~20.0;≤10.0",
|
||||
"AZN": ">3.00;1.00~3.00;0.50~1.00;0.20~0.50;≤0.20",
|
||||
"CEC": ">30.0;20.0~30.0;15.0~20.0;10.0~15.0;≤10.0",
|
||||
"ECA": ">7.49;4.99~7.49;2.50~4.99;1.00~2.50;≤1.00",
|
||||
"EMG": ">1.64;1.23~1.64;0.82~1.23;0.41~0.82;≤0.41",
|
||||
"TSE": ">3.00;0.40~3.00;0.17~0.40;≤0.17",
|
||||
"TN": ">2.00;1.50~2.00;1.00~1.50;0.50~1.00;≤0.50",
|
||||
"TP": ">1.00;0.80~1.00;0.60~0.80;0.40~0.60;≤0.40",
|
||||
"TK": ">20.0;15.0~20.0;10.0~15.0;5.0~10.0;≤5.0",
|
||||
"AFE": ">20.0;10.0~20.0;5.0~10.0;3.0~5.0;≤3.0",
|
||||
"AK": ">150;100~150;75~100;50~75;≤50",
|
||||
"AP": ">40.0;25.0~40.0;15.0~25.0;5.0~15.0;≤5.0",
|
||||
"OM": ">35.0;25.0~35.0;15.0~25.0;10.0~15.0;≤10.0",
|
||||
"FL": "≤15;15~30;30~45;45~75;>75",
|
||||
"NL": "≤15;15~25;25~45;45~65;>65",
|
||||
"SL": "≤30;30~40;40~55;55~85;>85",
|
||||
"PH": "6.0~7.0;7.0~7.5,5.5~6.0;7.5~8.0,5.0~5.5;8.0~8.5,4.5~5.0;>8.5,≤4.5",
|
||||
"YXTCHD": ">100;80~100;60~80;40~60;≤40",
|
||||
"GZCHD": ">25.0;20.0~25.0;15.0~20.0;10.0~15.0;≤10.0",
|
||||
"TRRZ": "1.10~1.25;1.25~1.35,1.00~1.10;1.35~1.45;1.45~1.55,0.90~1.00;>1.55,≤0.90",
|
||||
"TRZD": "1;2;3;4;5",
|
||||
"二普 PH": ">6.5;5.5~6.5;4.5~5.5;≤4.5",
|
||||
"三普 PH": ">6.5;5.5~6.5;4.5~5.5;≤4.5",
|
||||
"酸化 PH": ">1.0;0.5~1.0;0.3~0.5;-0.3~0.3;-10~-0.3"
|
||||
}
|
||||
91
tools/config_json/酸化专题配置_config.json
Normal file
91
tools/config_json/酸化专题配置_config.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"export_config": {
|
||||
"三普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤三普pH值分级分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "≤4.5",
|
||||
"等级二": "4.5~5.0",
|
||||
"等级三": "5.0~5.5",
|
||||
"等级四": "5.5~6.5",
|
||||
"等级五": "6.5~7.5",
|
||||
"等级六": "7.5~8.5",
|
||||
"等级七": "8.5~9.0",
|
||||
"等级八": ">9.0"
|
||||
}
|
||||
},
|
||||
"二普PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤二普pH值分级分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "≤4.5",
|
||||
"等级二": "4.5~5.0",
|
||||
"等级三": "5.0~5.5",
|
||||
"等级四": "5.5~6.5",
|
||||
"等级五": "6.5~7.5",
|
||||
"等级六": "7.5~8.5",
|
||||
"等级七": "8.5~9.0",
|
||||
"等级八": ">9.0"
|
||||
}
|
||||
},
|
||||
"测土PH": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}测土配方施肥pH值分级分布图",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "pH\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"等级一": "≤4.5",
|
||||
"等级二": "4.5~5.0",
|
||||
"等级三": "5.0~5.5",
|
||||
"等级四": "5.5~6.5",
|
||||
"等级五": "6.5~7.5",
|
||||
"等级六": "7.5~8.5",
|
||||
"等级七": "8.5~9.0",
|
||||
"等级八": ">9.0"
|
||||
}
|
||||
},
|
||||
"二普-三普": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤酸化等级分布图(土壤二普-土壤三普)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"弱酸化": "0.1~0.3",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"重度酸化": ">1.0",
|
||||
"其它": "-10~0.1"
|
||||
}
|
||||
},
|
||||
"二普-测土": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤酸化等级分布图(土壤二普-测土配方施肥)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"弱酸化": "0.1~0.3",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"重度酸化": ">1.0",
|
||||
"其它": "-10~0.1"
|
||||
}
|
||||
},
|
||||
"测土-三普": {
|
||||
"项目名称": "第三次全国土壤普查成果\n{区县占位符}土壤酸化等级分布图(测土配方施肥-土壤三普)",
|
||||
"分析方法": "分析方法:电位法",
|
||||
"项目分级": "酸化\n级别",
|
||||
"分级标准": "分级标准/\n ",
|
||||
"标准等级": {
|
||||
"弱酸化": "0.1~0.3",
|
||||
"轻度酸化": "0.3~0.5",
|
||||
"中度酸化": "0.5~1.0",
|
||||
"重度酸化": ">1.0",
|
||||
"其它": "-10~0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
tools/core/__init__.py
Normal file
4
tools/core/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
核心功能模块
|
||||
包含独立的功能脚本,可以在独立进程中运行
|
||||
"""
|
||||
0
tools/core/acid_stats/__init__.py
Normal file
0
tools/core/acid_stats/__init__.py
Normal file
610
tools/core/acid_stats/土地利用类型酸化统计表.py
Normal file
610
tools/core/acid_stats/土地利用类型酸化统计表.py
Normal file
@@ -0,0 +1,610 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from collections import OrderedDict
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
from tools.config.arcgis_field_cal_code import codeblock_cal_shfj, codeblock_dltb_ejdl, codeblock_dltb_yjdl
|
||||
from tools.core.utils.excel_utils import ExcelStyleUtils
|
||||
|
||||
|
||||
yjdl_order = ["耕地", "园地", "林地", "草地", "其他"]
|
||||
ejdl_order = ["水田", "旱地", "水浇地", "果园", "茶园", "橡胶园", "其他园地"]
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
# 等级计算
|
||||
def get_acidification_degree(delta_ph):
|
||||
"""根据ΔpH值判断酸化程度"""
|
||||
if pd.isna(delta_ph) or delta_ph == 0:
|
||||
return "-"
|
||||
# 请根据您的实际分级标准调整这里的阈值
|
||||
if delta_ph > 1.0:
|
||||
return "重度酸化"
|
||||
elif 0.5 < delta_ph <= 1.0:
|
||||
return "中度酸化"
|
||||
elif 0.3 < delta_ph <= 0.5:
|
||||
return "轻度酸化"
|
||||
elif -0.3 <= delta_ph <= 0.3:
|
||||
return "未酸化"
|
||||
else: # dPH < -0.3
|
||||
return "碱化"
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table5_3(gdb_path, mean_table_name, sample_table_name):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("【最终版 v2】开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
# ... (此函数不变)
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# --- a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
sample_table_path = os.path.join(gdb_path, sample_table_name)
|
||||
sample_fields = ['YJDL', 'EJDL', 'dPH']
|
||||
df_samples = pd.DataFrame(arcpy.da.TableToNumPyArray(sample_table_path, sample_fields,'dPH>0.3', skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, ['YJDL', 'EJDL'])
|
||||
|
||||
# 按 YJDL, EJDL 分组,计算 dPH 的均值
|
||||
df_sample_means = df_samples.groupby(['YJDL', 'EJDL'])['dPH'].mean().reset_index()
|
||||
df_sample_means.rename(columns={'dPH': '样点均值'}, inplace=True)
|
||||
print("样点均值计算完成。")
|
||||
|
||||
# --- b. 处理制图数据,获取“制图均值”和“制图样点数” ---
|
||||
print("--> 步骤2: 获取制图均值和样点数...")
|
||||
mean_table_path = os.path.join(gdb_path, mean_table_name)
|
||||
# **【核心修改】: 增加读取 COUNT 字段**
|
||||
mean_fields = ['YJDL', 'EJDL', 'MEAN', 'COUNT']
|
||||
df_map_data = pd.DataFrame(arcpy.da.TableToNumPyArray(mean_table_path, mean_fields, skip_nulls=False))
|
||||
df_map_data = clean_df(df_map_data, ['YJDL', 'EJDL'])
|
||||
df_map_data.rename(columns={'MEAN': '制图均值', 'COUNT': '制图样点数'}, inplace=True)
|
||||
print("制图数据获取完成。")
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['YJDL', 'EJDL']],
|
||||
df_map_data[['YJDL', 'EJDL']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['YJDL', 'EJDL'], how='left')
|
||||
# **【核心修改】: 合并整个 df_map_data,而不仅仅是均值列**
|
||||
df_final = pd.merge(df_final, df_map_data, on=['YJDL', 'EJDL'], how='left')
|
||||
|
||||
# --- d. 计算酸化程度 ---
|
||||
print("--> 步骤4: 计算酸化程度...")
|
||||
# **【核心修改】: 在计算酸化程度之前,先过滤掉不展示的行**
|
||||
# 我们只对 dPH 在酸化范围内 ( > 0.3) 的数据感兴趣
|
||||
# 但为了计算合计,我们需要保留所有数据,所以这一步只计算,不删除
|
||||
df_final['酸化程度_样本'] = df_final['样点均值'].apply(get_acidification_degree)
|
||||
df_final['酸化程度_制图'] = df_final['制图均值'].apply(get_acidification_degree)
|
||||
|
||||
# (可选) 按“一级地类”和“二级地类”排序
|
||||
in_ejdl_order = ejdl_order + [x for x in df_final['EJDL'].unique() if x not in ejdl_order]
|
||||
df_final["YJDL"] = pd.Categorical(df_final['YJDL'], categories=yjdl_order, ordered=True)
|
||||
df_final["EJDL"] = pd.Categorical(df_final['EJDL'], categories=in_ejdl_order, ordered=True)
|
||||
df_final.sort_values(['YJDL', 'EJDL'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
|
||||
|
||||
# --- 4. Excel 制表 均值---
|
||||
def write_to_excel_table5_3(df, output_path):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同土地利用类型pH变化统计"
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土地利用类型'
|
||||
ws.merge_cells('C1:F1'); ws['C1'] = 'ΔpH'
|
||||
|
||||
ws['A2'] = '一级'
|
||||
ws['B2'] = '二级'
|
||||
ws['C2'] = '样点均值'
|
||||
ws['D2'] = '酸化程度'
|
||||
ws['E2'] = '制图均值'
|
||||
ws['F2'] = '酸化程度'
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
# **【核心修改】: 先对整个DataFrame进行过滤,只保留需要展示的行**
|
||||
# 只有当“样点酸化程度”或“制图酸化程度”不为“未酸化”、“碱化”或“-”时,才展示该行
|
||||
acid_levels_to_show = ["轻度酸化", "中度酸化", "重度酸化"]
|
||||
df_to_write = df[
|
||||
df['酸化程度_样本'].isin(acid_levels_to_show) |
|
||||
df['酸化程度_制图'].isin(acid_levels_to_show)
|
||||
].copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('YJDL', sort=False, observed=False):
|
||||
|
||||
print(f"正在写入一级地类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 遍历该一级地类下的所有“二级地类”
|
||||
for _, row_data in group_yl_df.iterrows():
|
||||
ws.cell(row=current_row, column=2).value = row_data['EJDL']
|
||||
|
||||
# 填充样点数据
|
||||
sample_mean = row_data.get('样点均值')
|
||||
if pd.notna(sample_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{sample_mean:.2f}" if sample_mean > 0.3 else "-"
|
||||
ws.cell(row=current_row, column=4).value = row_data.get('酸化程度_样本', '-') if sample_mean > 0.3 else "-"
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
|
||||
# 填充制图数据
|
||||
map_mean = row_data.get('制图均值')
|
||||
if pd.notna(map_mean):
|
||||
ws.cell(row=current_row, column=5).value = f"{map_mean:.2f}" if map_mean > 0.3 else "-"
|
||||
ws.cell(row=current_row, column=6).value = row_data.get('酸化程度_制图', '-') if map_mean > 0.3 else "-"
|
||||
else:
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 计算并写入“合计”行
|
||||
if ws.cell(row=current_row-1, column=2).value in ["林地", "草地", "其他"]:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=yl_start_row, end_column=2)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
continue
|
||||
|
||||
ws.cell(row=current_row, column=2).value = '合计'
|
||||
|
||||
# 计算合计行的均值 (均值的均值)
|
||||
total_sample_mean = group_yl_df['样点均值'].mean()
|
||||
|
||||
if pd.notna(total_sample_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{total_sample_mean:.2f}"
|
||||
ws.cell(row=current_row, column=4).value = get_acidification_degree(total_sample_mean)
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
|
||||
# b. **【核心修正】: 计算合计行的“制图均值”(加权平均)**
|
||||
# 准备加权平均的分子和分母
|
||||
weighted_sum = 0
|
||||
total_count = 0
|
||||
|
||||
# 遍历当前一级地类分组中的每一行
|
||||
for _, row in group_yl_df.iterrows():
|
||||
mean_val = row.get('制图均值')
|
||||
count_val = row.get('制图样点数')
|
||||
|
||||
# 只有当均值和样点数都存在且有效时,才参与计算
|
||||
if pd.notna(mean_val) and pd.notna(count_val) and count_val > 0:
|
||||
weighted_sum += mean_val * count_val # Σ (mean * count)
|
||||
total_count += count_val # Σ (count)
|
||||
|
||||
# 计算加权平均值
|
||||
weighted_avg = (weighted_sum / total_count) if total_count > 0 else 0
|
||||
|
||||
if weighted_avg > 0:
|
||||
ws.cell(row=current_row, column=5).value = f"{weighted_avg:.2f}"
|
||||
ws.cell(row=current_row, column=6).value = get_acidification_degree(weighted_avg)
|
||||
else:
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
|
||||
# 合并“一级地类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
current_row += 1
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}{current_row-1}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
# 自动调整列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
# --- 2. 数据处理与分析 (使用 Pandas) ---
|
||||
def process_data_for_table5_4(gdb_path, area_table_name, sample_table_name, target_area_dict):
|
||||
"""
|
||||
【最终修正版 v2】: 先建立统一的层级结构,再分别合并统计结果。
|
||||
"""
|
||||
print("【最终修正版 v2】开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
# ... (此函数不变)
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# --- a. 从两个表中提取并建立唯一的 (YJDL, EJDL) 层级结构 "骨架" ---
|
||||
print("--> 步骤1: 建立统一的层级结构...")
|
||||
sample_table_path = os.path.join(gdb_path, sample_table_name)
|
||||
area_table_path = os.path.join(gdb_path, area_table_name)
|
||||
|
||||
df_samples_raw = pd.DataFrame(arcpy.da.TableToNumPyArray(sample_table_path, ['YJDL', 'EJDL'], skip_nulls=False))
|
||||
df_area_raw = pd.DataFrame(arcpy.da.TableToNumPyArray(area_table_path, ['YJDL', 'EJDL'], skip_nulls=False))
|
||||
|
||||
# 清理并合并两个表中的 (YJDL, EJDL) 组合
|
||||
df_samples_raw = clean_df(df_samples_raw, ['YJDL', 'EJDL'])
|
||||
df_area_raw = clean_df(df_area_raw, ['YJDL', 'EJDL'])
|
||||
|
||||
# 使用 concat 连接两个DataFrame,然后用 drop_duplicates 去除重复的组合
|
||||
df_skeleton = pd.concat([df_samples_raw, df_area_raw]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
if df_skeleton.empty:
|
||||
print("警告: 无法从源数据中建立任何有效的 (YJDL, EJDL) 层级结构。")
|
||||
return pd.DataFrame(), {}
|
||||
print(f"已建立包含 {len(df_skeleton)} 个唯一土壤类型的层级结构。")
|
||||
|
||||
# --- b. 独立统计样点数据 ---
|
||||
print("--> 步骤2: 独立统计样点数据...")
|
||||
df_samples = pd.DataFrame(arcpy.da.TableToNumPyArray(sample_table_path, ['EJDL', 'YJDL', 'dPH'], skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, ['YJDL', 'EJDL'])
|
||||
|
||||
if not df_samples.empty:
|
||||
# ... (统计逻辑不变)
|
||||
bins = [-np.inf, -0.3, 0.3, 0.5, 1.0, np.inf]
|
||||
labels = ["碱化", "未酸化", "轻度酸化", "中度酸化", "重度酸化"]
|
||||
df_samples['SHFJ'] = pd.cut(df_samples['dPH'], bins=bins, labels=labels, right=True)
|
||||
sample_counts = df_samples.groupby(['YJDL', 'EJDL', 'SHFJ'], observed=False).size().reset_index(name='样点数')
|
||||
ts_total_samples = sample_counts.groupby(['YJDL', 'EJDL'])['样点数'].transform('sum')
|
||||
sample_counts['样点占比'] = (sample_counts['样点数'] / ts_total_samples) * 100
|
||||
df_sample_stats = sample_counts.pivot_table(
|
||||
index=['YJDL', 'EJDL'], columns='SHFJ', values=['样点数', '样点占比'], fill_value=0, observed=False
|
||||
).reset_index()
|
||||
df_sample_stats.columns = [f'{col[0]}_{col[1]}'.strip('_') if col[1] else col[0] for col in df_sample_stats.columns]
|
||||
|
||||
# 将样点统计结果合并到骨架上
|
||||
df_final = pd.merge(df_skeleton, df_sample_stats, on=['YJDL', 'EJDL'], how='left')
|
||||
else:
|
||||
df_final = df_skeleton.copy()
|
||||
|
||||
# --- c. 独立统计面积数据 ---
|
||||
print("--> 步骤3: 独立统计面积数据...")
|
||||
df_area = pd.DataFrame(arcpy.da.TableToNumPyArray(area_table_path, ['EJDL', 'YJDL', 'SHFJ', 'AREA'], skip_nulls=False))
|
||||
df_area = clean_df(df_area, ['YJDL', 'EJDL'])
|
||||
|
||||
if not df_area.empty:
|
||||
# 计算平差系数
|
||||
landuse_types = {'耕地':'01', '园地':'02', '林地':'03', '草地':'04', '其他':'12'}
|
||||
|
||||
df_area['AREA_MU'] = df_area['AREA'] * 0.0015
|
||||
yjdl_area = df_area.groupby(['YJDL'])['AREA_MU'].sum().reset_index()
|
||||
yjdl_area.columns = ['YJDL', 'ORIGINAL_TOTAL_MU']
|
||||
|
||||
adjustment_factors = []
|
||||
for _, row in yjdl_area.iterrows():
|
||||
yjdl = row['YJDL']
|
||||
original_total = row['ORIGINAL_TOTAL_MU']
|
||||
target_total = target_area_dict.get(landuse_types[yjdl], original_total) # 如果没有指定,就用原始面积
|
||||
|
||||
adjustment_factor = target_total / original_total
|
||||
|
||||
adjustment_factors.append({
|
||||
'YJDL': yjdl,
|
||||
'原始总面积_亩': original_total,
|
||||
'目标总面积_亩': target_total,
|
||||
'平差系数': adjustment_factor
|
||||
})
|
||||
|
||||
factor_df = pd.DataFrame(adjustment_factors)
|
||||
|
||||
# 4. 对每个二级地类应用平差系数
|
||||
# 合并原始数据和平差系数
|
||||
df_with_factors = df_area.merge(factor_df[['YJDL', '平差系数']], on='YJDL')
|
||||
|
||||
df_with_factors['制图面积_亩'] = df_with_factors['AREA_MU'] * df_with_factors['平差系数']
|
||||
ts_total_area = df_with_factors.groupby(['YJDL', 'EJDL'])['制图面积_亩'].transform('sum')
|
||||
df_with_factors['面积占比'] = (df_with_factors['制图面积_亩'] / ts_total_area) * 100
|
||||
df_area_stats = df_with_factors.pivot_table(
|
||||
index=['YJDL', 'EJDL'], columns='SHFJ', values=['制图面积_亩', '面积占比'], fill_value=0
|
||||
).reset_index()
|
||||
df_area_stats.columns = [f'{col[0]}_{col[1]}'.strip('_') if col[1] else col[0] for col in df_area_stats.columns]
|
||||
|
||||
# 将面积统计结果合并到 df_final 上
|
||||
# 注意,这里我们合并到已经包含样点数据的 df_final 上
|
||||
df_final = pd.merge(df_final, df_area_stats, on=['YJDL', 'EJDL'], how='left')
|
||||
|
||||
# --- d. 最后清理和构建映射 ---
|
||||
df_final.fillna(0, inplace=True)
|
||||
|
||||
print("--> 步骤4: 自动构建层级结构...")
|
||||
dynamic_soil_mapping = df_final.groupby('YJDL')['EJDL'].unique().apply(list).to_dict()
|
||||
dynamic_soil_mapping = OrderedDict(sorted(dynamic_soil_mapping.items(),key=lambda item: yjdl_order.index(item[0])))
|
||||
in_ejdl_order = ejdl_order + [x for x in df_final['EJDL'].unique() if x not in ejdl_order]
|
||||
for yl in dynamic_soil_mapping:
|
||||
# dynamic_soil_mapping[yl].sort()
|
||||
dynamic_soil_mapping[yl] = sorted( dynamic_soil_mapping[yl], key=lambda x: in_ejdl_order.index(x))
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final, dynamic_soil_mapping
|
||||
|
||||
|
||||
# --- 3. Excel 制表 面积---
|
||||
def write_to_excel_table5_4(df, soil_mapping, output_path):
|
||||
"""
|
||||
【最终修正版】: 将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel,将创建一个空的报告。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同类型土壤酸化程度统计"
|
||||
|
||||
# --- b. 绘制表头 (不变) ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土地利用类型'
|
||||
ws['A2'] = '一级'
|
||||
ws['B2'] = '二级'
|
||||
|
||||
acid_levels = ['轻度酸化', '中度酸化', '重度酸化']
|
||||
all_possible_levels = ['碱化', '未酸化', '轻度酸化', '中度酸化', '重度酸化']
|
||||
acid_level_headers = ['轻度酸化(0.3<ΔpH≤0.5)', '中度酸化(0.5<ΔpH≤1.0)', '重度酸化(ΔpH>1.0)']
|
||||
|
||||
col_start = 3
|
||||
for header in acid_level_headers:
|
||||
ws.merge_cells(start_row=1, start_column=col_start, end_row=1, end_column=col_start + 3)
|
||||
ws.cell(row=1, column=col_start).value = header
|
||||
ws.cell(row=2, column=col_start).value = '样点数/个'
|
||||
ws.cell(row=2, column=col_start + 1).value = '占比/%'
|
||||
ws.cell(row=2, column=col_start + 2).value = '制图面积/亩'
|
||||
ws.cell(row=2, column=col_start + 3).value = '占比/%'
|
||||
col_start += 4
|
||||
|
||||
# --- c. 填充数据 (完全重构的逻辑) ---
|
||||
current_row = 3
|
||||
|
||||
# 使用 .groupby('YJDL', sort=False) 来保证我们之前设置的排序顺序
|
||||
for yl, ts_list in soil_mapping.items():
|
||||
|
||||
# **【关键】** group_yl 是一个只包含当前一级地类数据的子DataFrame
|
||||
# 我们可以安全地在这个子DataFrame上进行迭代和计算
|
||||
|
||||
print(f"正在写入一级地类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 筛选出当前一级地类的所有数据
|
||||
group_yl_df = df[df['YJDL'] == yl]
|
||||
|
||||
# 1. 遍历该一级地类下的所有“二级地类”并写入数据
|
||||
for ts in ts_list:
|
||||
ws.cell(row=current_row, column=2).value = ts
|
||||
|
||||
# 在子集中查找当前二级地类的数据行
|
||||
row_data = group_yl_df[group_yl_df['EJDL'] == ts]
|
||||
|
||||
# --- 填充单元格的逻辑开始 ---
|
||||
|
||||
col_start = 3 # 从第 C 列开始填充
|
||||
|
||||
# 检查是否找到了该土属的数据
|
||||
if not row_data.empty:
|
||||
# 如果找到了数据 (row_data 不为空),我们就获取这一行的数据
|
||||
# .iloc[0] 获取第一行(也是唯一一行)的数据,作为一个 Series 对象
|
||||
data_series = row_data.iloc[0]
|
||||
|
||||
# 遍历每一个酸化等级,填充对应的四列数据
|
||||
for level in acid_levels:
|
||||
# 1. 构建要从 data_series 中查找的列名
|
||||
sample_col = f'样点数_{level}'
|
||||
sample_pct_col = f'样点占比_{level}'
|
||||
area_col = f'制图面积_亩_{level}'
|
||||
area_pct_col = f'面积占比_{level}'
|
||||
|
||||
# 2. 从 data_series 中安全地获取值
|
||||
# 使用 .get(key, default_value) 的好处是,如果列名不存在,它会返回默认值(0),而不会报错
|
||||
sample_val = data_series.get(sample_col, 0)
|
||||
sample_pct_val = data_series.get(sample_pct_col, 0)
|
||||
area_val = data_series.get(area_col, 0)
|
||||
area_pct_val = data_series.get(area_pct_col, 0)
|
||||
|
||||
# 3. 将获取到的值填入单元格
|
||||
# - 对于数值,我们判断它是否大于0。如果是,就填入数值;否则,填入 "-"
|
||||
# - 对于样点数,我们将其转为整数
|
||||
# - 对于占比和面积,我们保留两位小数
|
||||
|
||||
# 样点数/个
|
||||
ws.cell(row=current_row, column=col_start).value = int(sample_val) if sample_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 1).value = f"{sample_pct_val:.2f}%" if sample_val > 0 else "-"
|
||||
# 制图面积/万亩
|
||||
ws.cell(row=current_row, column=col_start + 2).value = f"{area_val:.0f}" if area_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 3).value = f"{area_pct_val:.2f}%" if area_val > 0 else "-"
|
||||
|
||||
# 移动到下一个酸化等级的起始列
|
||||
col_start += 4
|
||||
else:
|
||||
# 如果没有找到该土属的数据 (row_data 为空)
|
||||
# 这意味着该土属在源数据中不存在任何样点或面积信息
|
||||
# 我们将整行所有统计单元格都填充为 "-"
|
||||
|
||||
# acid_levels 列表包含3个等级,每个等级4列,总共12列
|
||||
for _ in range(len(acid_levels) * 4):
|
||||
ws.cell(row=current_row, column=col_start).value = "-"
|
||||
col_start += 1
|
||||
|
||||
# --- 填充单元格的逻辑结束 ---
|
||||
|
||||
# 完成一行填充后,行号加1,为下一行做准备
|
||||
current_row += 1
|
||||
|
||||
# 2. 计算并写入这个一级地类的“合计”行
|
||||
if ws.cell(row=current_row-1, column=2).value in ["林地","草地", "其他"]:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=yl_start_row, end_column=2)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
continue
|
||||
|
||||
ws.cell(row=current_row, column=2).value = '合计'
|
||||
|
||||
# 计算总样点数和总面积,仅针对当前 group_yl
|
||||
yl_grand_total_samples = 0
|
||||
for lvl in all_possible_levels:
|
||||
if f'样点数_{lvl}' in group_yl_df:
|
||||
yl_grand_total_samples += group_yl_df[f'样点数_{lvl}'].sum()
|
||||
|
||||
yl_grand_total_area = 0
|
||||
for lvl in all_possible_levels:
|
||||
if f'制图面积_亩_{lvl}' in group_yl_df:
|
||||
yl_grand_total_area += group_yl_df[f'制图面积_亩_{lvl}'].sum()
|
||||
|
||||
col_start = 3
|
||||
for level in acid_levels:
|
||||
sample_sum = group_yl_df.get(f'样点数_{level}', 0).sum()
|
||||
col_name = f'制图面积_亩_{level}'
|
||||
area_sum = group_yl_df[col_name].sum() if col_name in group_yl_df else 0
|
||||
# area_sum = group_yl_df.get(f'制图面积_亩_{level}', 0).sum()
|
||||
|
||||
sample_perc = (sample_sum / yl_grand_total_samples * 100) if yl_grand_total_samples > 0 else 0
|
||||
area_perc = (area_sum / yl_grand_total_area * 100) if yl_grand_total_area > 0 else 0
|
||||
|
||||
ws.cell(row=current_row, column=col_start).value = int(sample_sum) if sample_sum > 0 else "-"
|
||||
ws.cell(row=current_row, column=col_start + 1).value = f"{sample_perc:.2f}%" if sample_sum > 0 else "-"
|
||||
ws.cell(row=current_row, column=col_start + 2).value = f"{area_sum:.0f}" if area_sum > 0 else "-"
|
||||
ws.cell(row=current_row, column=col_start + 3).value = f"{area_perc:.2f}%" if area_sum > 0 else "-"
|
||||
col_start += 4
|
||||
|
||||
# 3. 合并“一级地类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
current_row += 1
|
||||
|
||||
# --- a. 定义样式 (不变) ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
|
||||
# --- d. 应用样式和调整列宽 (最终健壮版) ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}{current_row-1}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
# 调整列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
def main(gdb_path:str, ph_features:str,dltb_class_feature:str, shph_tif:str, output_path:str,target_areas_dict:dict):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
output_excel_path = os.path.join(output_path, "土地利用类型酸化统计表.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
sample_table_name = "历史样点PH信息_Table" # 图2: 样点信息表名
|
||||
in_zone_feature = dltb_class_feature # 地类图斑
|
||||
in_class_feature = ph_features # 已重分类好的酸化PH图层
|
||||
in_value_raster = shph_tif # 赋值栅格,酸化PH栅格
|
||||
out_table_area = r"土地利用类型_酸化面积表" # 输出的面积统计表名
|
||||
out_table_mean = r"土地利用类型_酸化均值表" # 输出的均值表名
|
||||
|
||||
print("开始处理数据...")
|
||||
|
||||
if not arcpy.Exists(out_table_area):
|
||||
# 判断输入表是否存在SHFJ字段
|
||||
try:
|
||||
if not arcpy.ListFields(in_zone_feature, "EJDL"):
|
||||
arcpy.management.CalculateField(in_zone_feature, "EJDL", "calculate_ejdl(!DLBM!,!DLMC!)", "PYTHON3", codeblock_dltb_ejdl)
|
||||
arcpy.management.CalculateField(in_zone_feature, "YJDL", "calculate_yjdl(!DLBM!)", "PYTHON3", codeblock_dltb_yjdl)
|
||||
if not arcpy.ListFields(in_class_feature, "SHFJ"):
|
||||
arcpy.management.CalculateField(in_class_feature, "SHFJ", "calculate_shfj(!gridcode!)", "PYTHON3", codeblock_cal_shfj)
|
||||
except Exception as e:
|
||||
print(f"计算SHFJ字段时发生错误: {e}")
|
||||
|
||||
# 拿到地类图斑的坐标系
|
||||
desc = arcpy.Describe(in_zone_feature)
|
||||
spatial_ref = desc.spatialReference
|
||||
|
||||
# 1.用arcpy.analysis.TabulateIntersection进行交集制表,面积使用地类图斑投影坐标系下面积
|
||||
with arcpy.EnvManager(outputCoordinateSystem=spatial_ref):
|
||||
arcpy.analysis.TabulateIntersection(
|
||||
in_zone_feature,
|
||||
["YJDL", "EJDL"],
|
||||
in_class_feature,
|
||||
out_table_area,
|
||||
"SHFJ",
|
||||
out_units="SQUARE_METERS",
|
||||
)
|
||||
|
||||
if not arcpy.Exists(out_table_mean):
|
||||
# 判断输入表是否存在YJDL_EJDL字段
|
||||
if not arcpy.ListFields(in_zone_feature, "YJDL_EJDL"):
|
||||
# 如果不存在,则添加该字段
|
||||
arcpy.management.AddField(in_zone_feature, "YJDL_EJDL", "TEXT")
|
||||
# 计算YJDL_EJDL字段的值
|
||||
arcpy.management.CalculateField(in_zone_feature,"YJDL_EJDL","!YJDL! + '_' + !EJDL!","PYTHON3")
|
||||
|
||||
# 2.用arcpy.sa.ZonalStatisticsAsTable进行区域统计
|
||||
mean_table = arcpy.sa.ZonalStatisticsAsTable(
|
||||
in_zone_feature, "YJDL_EJDL", in_value_raster, out_table_mean, "DATA", "MEAN"
|
||||
)
|
||||
# 2.1 添加土壤类型字段并计算
|
||||
arcpy.management.AddFields(
|
||||
out_table_mean,
|
||||
[["YJDL", "TEXT"],["EJDL", "TEXT"]],
|
||||
)
|
||||
arcpy.management.CalculateField(mean_table, "YJDL", "!YJDL_EJDL!.split('_')[0]", "PYTHON3")
|
||||
arcpy.management.CalculateField(mean_table, "EJDL", "!YJDL_EJDL!.split('_')[1]", "PYTHON3")
|
||||
|
||||
# 生成表5.4的面积统计Excel报告
|
||||
final_dataframe, soil_structure = process_data_for_table5_4(gdb_path, out_table_area, sample_table_name,target_areas_dict)
|
||||
write_to_excel_table5_4(final_dataframe, soil_structure, output_excel_path)
|
||||
|
||||
# 生成表5.3的均值统计Excel报告
|
||||
final_mean_dataframe = process_data_for_table5_3(gdb_path, out_table_mean, sample_table_name)
|
||||
write_to_excel_table5_3(final_mean_dataframe, output_excel_path.replace(".xlsx", "_mean.xlsx"))
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
629
tools/core/acid_stats/土壤类型图酸化统计表.py
Normal file
629
tools/core/acid_stats/土壤类型图酸化统计表.py
Normal file
@@ -0,0 +1,629 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
from tools.config.arcgis_field_cal_code import codeblock_cal_shfj
|
||||
from tools.core.utils.excel_utils import ExcelStyleUtils
|
||||
from tools.config.custom_sort import yl_order, ts_order
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
# 获取要素类各酸化等级面积
|
||||
def get_acid_area_by_group(target_area_df):
|
||||
try:
|
||||
# 转为numpy数组供pandas统计使用
|
||||
df = target_area_df.copy()
|
||||
area_by_group = df.groupby("SHFJ")["AREA_MU"].sum()
|
||||
|
||||
for key in area_by_group.keys():
|
||||
area_by_group[key] = area_by_group[key]
|
||||
|
||||
return area_by_group.to_dict()
|
||||
|
||||
except Exception as e:
|
||||
print(f"计算面积时出错: {str(e)}")
|
||||
return None
|
||||
|
||||
def apply_adjustment_by_each_level(df, target_area_dict):
|
||||
"""
|
||||
对DataFrame中的面积数据按每一个酸化等级独立进行平差。
|
||||
|
||||
参数:
|
||||
df (pd.DataFrame): 包含面积统计的DataFrame。
|
||||
target_area_dict (dict): 每个酸化等级的目标总面积字典。
|
||||
例如: {'轻度酸化': 10000.0, '中度酸化': 8000.0, ...}
|
||||
"""
|
||||
print("\n开始按每个酸化等级独立进行平差...")
|
||||
df_adjusted = df.copy()
|
||||
|
||||
for level, target_area in target_area_dict.items():
|
||||
col_name = f'制图面积_亩_{level}'
|
||||
adjusted_col_name = f'平差后面积_亩_{level}'
|
||||
|
||||
if col_name not in df.columns:
|
||||
print(f"警告: 未找到列 '{col_name}',跳过该等级平差。")
|
||||
if adjusted_col_name not in df_adjusted.columns:
|
||||
df_adjusted[adjusted_col_name] = 0 # 创建一个空列
|
||||
continue
|
||||
|
||||
# a. 计算该等级的实际总面积
|
||||
actual_area = df_adjusted[col_name].sum()
|
||||
|
||||
if actual_area > 0:
|
||||
# b. 计算误差
|
||||
error = target_area - actual_area
|
||||
print(f"等级 '{level}': 目标面积={target_area:.2f}, 实际面积={actual_area:.2f}, 误差={error:.2f}")
|
||||
|
||||
# c. 按比例分配误差
|
||||
adjustment = error * (df_adjusted[col_name] / actual_area)
|
||||
df_adjusted[adjusted_col_name] = df_adjusted[col_name] + adjustment
|
||||
df_adjusted[adjusted_col_name] = df_adjusted[adjusted_col_name].clip(lower=0)
|
||||
else:
|
||||
df_adjusted[adjusted_col_name] = df_adjusted[col_name]
|
||||
|
||||
print("按每个酸化等级独立平差完成。")
|
||||
return df_adjusted
|
||||
|
||||
# 获取酸化程度
|
||||
def get_acidification_degree(delta_ph):
|
||||
"""根据ΔpH值判断酸化程度"""
|
||||
if pd.isna(delta_ph) or delta_ph == 0:
|
||||
return "-"
|
||||
# 请根据您的实际分级标准调整这里的阈值
|
||||
if delta_ph > 1.0:
|
||||
return "重度酸化"
|
||||
elif 0.5 < delta_ph <= 1.0:
|
||||
return "中度酸化"
|
||||
elif 0.3 < delta_ph <= 0.5:
|
||||
return "轻度酸化"
|
||||
elif -0.3 <= delta_ph <= 0.3:
|
||||
return "未酸化"
|
||||
else: # dPH < -0.3
|
||||
return "碱化"
|
||||
|
||||
# --- 3. 数据处理与分析 均值表---
|
||||
def process_data_for_table5_5(gdb_path, mean_table_name, sample_table_name):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("【最终版 v2】开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# --- a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
sample_table_path = os.path.join(gdb_path, sample_table_name)
|
||||
sample_fields = ['YL', 'TS', 'dPH']
|
||||
df_samples = pd.DataFrame(arcpy.da.TableToNumPyArray(sample_table_path, sample_fields, 'dPH>0.3', skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, ['YL', 'TS'])
|
||||
|
||||
# 按 YL, TS 分组,计算 dPH 的均值
|
||||
df_sample_means = df_samples.groupby(['YL', 'TS'])['dPH'].mean().reset_index()
|
||||
df_sample_means.rename(columns={'dPH': '样点均值'}, inplace=True)
|
||||
print("样点均值计算完成。")
|
||||
|
||||
# --- b. 处理制图数据,获取“制图均值”和“制图样点数” ---
|
||||
print("--> 步骤2: 获取制图均值和样点数...")
|
||||
mean_table_path = os.path.join(gdb_path, mean_table_name)
|
||||
mean_fields = ['YL', 'TS', 'MEAN', 'COUNT']
|
||||
df_map_data = pd.DataFrame(arcpy.da.TableToNumPyArray(mean_table_path, mean_fields, skip_nulls=False))
|
||||
df_map_data = clean_df(df_map_data, ['YL', 'TS'])
|
||||
df_map_data.rename(columns={'MEAN': '制图均值', 'COUNT': '制图样点数'}, inplace=True)
|
||||
print("制图数据获取完成。")
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['YL', 'TS']],
|
||||
df_map_data[['YL', 'TS']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['YL', 'TS'], how='left')
|
||||
# **【核心修改】: 合并整个 df_map_data,而不仅仅是均值列**
|
||||
df_final = pd.merge(df_final, df_map_data, on=['YL', 'TS'], how='left')
|
||||
|
||||
# --- d. 计算酸化程度 ---
|
||||
print("--> 步骤4: 计算酸化程度...")
|
||||
# **【核心修改】: 在计算酸化程度之前,先过滤掉不展示的行**
|
||||
# 我们只对 dPH 在酸化范围内 ( > 0.3) 的数据感兴趣
|
||||
# 但为了计算合计,我们需要保留所有数据,所以这一步只计算,不删除
|
||||
df_final['酸化程度_样本'] = df_final['样点均值'].apply(get_acidification_degree)
|
||||
df_final['酸化程度_制图'] = df_final['制图均值'].apply(get_acidification_degree)
|
||||
|
||||
# (可选) 按“亚类”和“土属”排序
|
||||
in_yl_order = yl_order + [x for x in df_final['YL'].unique() if x not in yl_order]
|
||||
in_ts_order = ts_order + [x for x in df_final['TS'].unique() if x not in ts_order]
|
||||
df_final["YL"] = pd.Categorical(df_final['YL'], categories=in_yl_order, ordered=True)
|
||||
df_final["TS"] = pd.Categorical(df_final['TS'], categories=in_ts_order, ordered=True)
|
||||
df_final.sort_values(['YL', 'TS'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
|
||||
|
||||
# --- 4. Excel 制表 均值表---
|
||||
def write_to_excel_table5_5(df, output_path):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同类型土壤pH变化统计"
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:A2'); ws['A1'] = '亚类'
|
||||
ws.merge_cells('B1:B2'); ws['B1'] = '土属'
|
||||
ws.merge_cells('C1:F1'); ws['C1'] = 'ΔpH'
|
||||
|
||||
ws['C2'] = '样点均值'
|
||||
ws['D2'] = '酸化程度'
|
||||
ws['E2'] = '制图均值'
|
||||
ws['F2'] = '酸化程度'
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
# **【核心修改】: 先对整个DataFrame进行过滤,只保留需要展示的行**
|
||||
# 只有当“样点酸化程度”或“制图酸化程度”不为“未酸化”、“碱化”或“-”时,才展示该行
|
||||
acid_levels_to_show = ["轻度酸化", "中度酸化", "重度酸化"]
|
||||
df_to_write = df[
|
||||
df['酸化程度_样本'].isin(acid_levels_to_show) |
|
||||
df['酸化程度_制图'].isin(acid_levels_to_show)
|
||||
].copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('YL', observed=True, sort=False):
|
||||
|
||||
print(f"正在写入亚类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 遍历该亚类下的所有“土属”
|
||||
for _, row_data in group_yl_df.iterrows():
|
||||
ws.cell(row=current_row, column=2).value = row_data['TS']
|
||||
|
||||
# 填充样点数据
|
||||
sample_mean = row_data.get('样点均值')
|
||||
if pd.notna(sample_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{sample_mean:.2f}" if sample_mean > 0.3 else "-"
|
||||
ws.cell(row=current_row, column=4).value = row_data.get('酸化程度_样本', '-') if sample_mean > 0.3 else "-"
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
|
||||
# 填充制图数据
|
||||
map_mean = row_data.get('制图均值')
|
||||
if pd.notna(map_mean):
|
||||
ws.cell(row=current_row, column=5).value = f"{map_mean:.2f}" if map_mean > 0.3 else "-"
|
||||
ws.cell(row=current_row, column=6).value = row_data.get('酸化程度_制图', '-') if map_mean > 0.3 else "-"
|
||||
else:
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 计算并写入“合计”行
|
||||
ws.cell(row=current_row, column=2).value = '合计'
|
||||
|
||||
# 计算合计行的均值 (均值的均值)
|
||||
total_sample_mean = group_yl_df['样点均值'].mean()
|
||||
|
||||
if pd.notna(total_sample_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{total_sample_mean:.2f}"
|
||||
ws.cell(row=current_row, column=4).value = get_acidification_degree(total_sample_mean)
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
|
||||
# b. **【核心修正】: 计算合计行的“制图均值”(加权平均)**
|
||||
# 准备加权平均的分子和分母
|
||||
weighted_sum = 0
|
||||
total_count = 0
|
||||
|
||||
# 遍历当前亚类分组中的每一行
|
||||
for _, row in group_yl_df.iterrows():
|
||||
mean_val = row.get('制图均值')
|
||||
count_val = row.get('制图样点数')
|
||||
|
||||
# 只有当均值和样点数都存在且有效时,才参与计算
|
||||
if pd.notna(mean_val) and pd.notna(count_val) and count_val > 0:
|
||||
weighted_sum += mean_val * count_val # Σ (mean * count)
|
||||
total_count += count_val # Σ (count)
|
||||
|
||||
# 计算加权平均值
|
||||
weighted_avg = (weighted_sum / total_count) if total_count > 0 else 0
|
||||
|
||||
if weighted_avg > 0:
|
||||
ws.cell(row=current_row, column=5).value = f"{weighted_avg:.2f}"
|
||||
ws.cell(row=current_row, column=6).value = get_acidification_degree(weighted_avg)
|
||||
else:
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
|
||||
# 合并“亚类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
current_row += 1
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
|
||||
# --- d. 应用样式和调整列宽 (最终健壮版) ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}{current_row-1}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
# 自动调整列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
# --- 2. 数据处理与分析 (面积统计表) ---
|
||||
def process_data_final(gdb_path, area_table_name, sample_table_name):
|
||||
"""
|
||||
【最终修正版 v2】: 先建立统一的层级结构,再分别合并统计结果。
|
||||
"""
|
||||
print("【最终修正版 v2】开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
# ... (此函数不变)
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# --- a. 从两个表中提取并建立唯一的 (YL, TS) 层级结构 "骨架" ---
|
||||
print("--> 步骤1: 建立统一的层级结构...")
|
||||
sample_table_path = os.path.join(gdb_path, sample_table_name)
|
||||
area_table_path = os.path.join(gdb_path, area_table_name)
|
||||
|
||||
df_samples_raw = pd.DataFrame(arcpy.da.TableToNumPyArray(sample_table_path, ['YL', 'TS'], skip_nulls=False))
|
||||
df_area_raw = pd.DataFrame(arcpy.da.TableToNumPyArray(area_table_path, ['YL', 'TS'], skip_nulls=False))
|
||||
|
||||
# 清理并合并两个表中的 (YL, TS) 组合
|
||||
df_samples_raw = clean_df(df_samples_raw, ['YL', 'TS'])
|
||||
df_area_raw = clean_df(df_area_raw, ['YL', 'TS'])
|
||||
|
||||
# 使用 concat 连接两个DataFrame,然后用 drop_duplicates 去除重复的组合
|
||||
df_skeleton = pd.concat([df_samples_raw, df_area_raw]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
if df_skeleton.empty:
|
||||
print("警告: 无法从源数据中建立任何有效的 (YL, TS) 层级结构。")
|
||||
return pd.DataFrame(), {}
|
||||
print(f"已建立包含 {len(df_skeleton)} 个唯一土壤类型的层级结构。")
|
||||
|
||||
# --- b. 独立统计样点数据 ---
|
||||
print("--> 步骤2: 独立统计样点数据...")
|
||||
df_samples = pd.DataFrame(arcpy.da.TableToNumPyArray(sample_table_path, ['TS', 'YL', 'dPH'], skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, ['YL', 'TS'])
|
||||
|
||||
if not df_samples.empty:
|
||||
bins = [-np.inf, -0.3, 0.3, 0.5, 1.0, np.inf]
|
||||
labels = ["碱化", "未酸化", "轻度酸化", "中度酸化", "重度酸化"]
|
||||
df_samples['SHFJ'] = pd.cut(df_samples['dPH'], bins=bins, labels=labels, right=True)
|
||||
sample_counts = df_samples.groupby(['YL', 'TS', 'SHFJ'], observed=False).size().reset_index(name='样点数')
|
||||
ts_total_samples = sample_counts.groupby(['YL', 'TS'])['样点数'].transform('sum')
|
||||
sample_counts['样点占比'] = (sample_counts['样点数'] / ts_total_samples) * 100
|
||||
df_sample_stats = sample_counts.pivot_table(
|
||||
index=['YL', 'TS'], columns='SHFJ', values=['样点数', '样点占比'], fill_value=0, observed=False
|
||||
).reset_index()
|
||||
df_sample_stats.columns = [f'{col[0]}_{col[1]}'.strip('_') if col[1] else col[0] for col in df_sample_stats.columns]
|
||||
|
||||
# 将样点统计结果合并到骨架上
|
||||
df_final = pd.merge(df_skeleton, df_sample_stats, on=['YL', 'TS'], how='left')
|
||||
else:
|
||||
df_final = df_skeleton.copy()
|
||||
|
||||
# --- c. 独立统计面积数据 ---
|
||||
print("--> 步骤3: 独立统计面积数据...")
|
||||
df_area = pd.DataFrame(arcpy.da.TableToNumPyArray(area_table_path, ['TS', 'YL', 'SHFJ', 'AREA'], skip_nulls=False))
|
||||
df_area = clean_df(df_area, ['YL', 'TS'])
|
||||
|
||||
if not df_area.empty:
|
||||
df_area['制图面积_亩'] = df_area['AREA'] * 0.0015
|
||||
ts_total_area = df_area.groupby(['YL', 'TS'])['制图面积_亩'].transform('sum')
|
||||
df_area['面积占比'] = (df_area['制图面积_亩'] / ts_total_area) * 100
|
||||
df_area_stats = df_area.pivot_table(
|
||||
index=['YL', 'TS'], columns='SHFJ', values=['制图面积_亩', '面积占比'], fill_value=0
|
||||
).reset_index()
|
||||
df_area_stats.columns = [f'{col[0]}_{col[1]}'.strip('_') if col[1] else col[0] for col in df_area_stats.columns]
|
||||
|
||||
# 将面积统计结果合并到 df_final 上
|
||||
# 注意,这里我们合并到已经包含样点数据的 df_final 上
|
||||
df_final = pd.merge(df_final, df_area_stats, on=['YL', 'TS'], how='left')
|
||||
|
||||
# --- d. 最后清理和构建映射 ---
|
||||
df_final.fillna(0, inplace=True)
|
||||
|
||||
print("--> 步骤4: 自动构建层级结构...")
|
||||
in_yl_order = yl_order + [x for x in df_final['YL'].unique() if x not in yl_order]
|
||||
in_ts_order = ts_order + [x for x in df_final['TS'].unique() if x not in ts_order]
|
||||
df_final["YL"] = pd.Categorical(df_final['YL'], categories=in_yl_order, ordered=True)
|
||||
df_final["TS"] = pd.Categorical(df_final['TS'], categories=in_ts_order, ordered=True)
|
||||
df_final.sort_values(['YL', 'TS'], inplace=True)
|
||||
dynamic_soil_mapping = df_final.groupby('YL', observed=True)['TS'].unique().apply(list).to_dict()
|
||||
# for yl in dynamic_soil_mapping:
|
||||
# dynamic_soil_mapping[yl].sort()
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final, dynamic_soil_mapping
|
||||
|
||||
|
||||
# --- 3. Excel 制表 面积统计表 ---
|
||||
def write_to_excel(df, soil_mapping, output_path):
|
||||
"""
|
||||
【最终修正版】: 将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel,将创建一个空的报告。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同类型土壤酸化程度统计"
|
||||
|
||||
# --- b. 绘制表头 (不变) ---
|
||||
ws.merge_cells('A1:A2'); ws['A1'] = '亚类'
|
||||
ws.merge_cells('B1:B2'); ws['B1'] = '土属'
|
||||
|
||||
acid_levels = ['轻度酸化', '中度酸化', '重度酸化']
|
||||
all_possible_levels = ['碱化', '未酸化', '轻度酸化', '中度酸化', '重度酸化']
|
||||
acid_level_headers = ['轻度酸化(0.3<ΔpH≤0.5)', '中度酸化(0.5<ΔpH≤1.0)', '重度酸化(ΔpH>1.0)']
|
||||
|
||||
col_start = 3
|
||||
for header in acid_level_headers:
|
||||
ws.merge_cells(start_row=1, start_column=col_start, end_row=1, end_column=col_start + 3)
|
||||
ws.cell(row=1, column=col_start).value = header
|
||||
ws.cell(row=2, column=col_start).value = '样点数/个'
|
||||
ws.cell(row=2, column=col_start + 1).value = '占比/%'
|
||||
ws.cell(row=2, column=col_start + 2).value = '制图面积/亩'
|
||||
ws.cell(row=2, column=col_start + 3).value = '占比/%'
|
||||
col_start += 4
|
||||
|
||||
# --- c. 填充数据 (完全重构的逻辑) ---
|
||||
current_row = 3
|
||||
|
||||
# 使用 .groupby('YL', sort=False) 来保证我们之前设置的排序顺序
|
||||
for yl, ts_list in soil_mapping.items():
|
||||
|
||||
# **【关键】** group_yl 是一个只包含当前亚类数据的子DataFrame
|
||||
# 我们可以安全地在这个子DataFrame上进行迭代和计算
|
||||
|
||||
print(f"正在写入亚类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 筛选出当前亚类的所有数据
|
||||
group_yl_df = df[df['YL'] == yl]
|
||||
|
||||
# 1. 遍历该亚类下的所有“土属”并写入数据
|
||||
for ts in ts_list:
|
||||
ws.cell(row=current_row, column=2).value = ts
|
||||
|
||||
# 在子集中查找当前土属的数据行
|
||||
row_data = group_yl_df[group_yl_df['TS'] == ts]
|
||||
|
||||
# --- 填充单元格的逻辑开始 ---
|
||||
|
||||
col_start = 3 # 从第 C 列开始填充
|
||||
|
||||
# 检查是否找到了该土属的数据
|
||||
if not row_data.empty:
|
||||
# 如果找到了数据 (row_data 不为空),我们就获取这一行的数据
|
||||
# .iloc[0] 获取第一行(也是唯一一行)的数据,作为一个 Series 对象
|
||||
data_series = row_data.iloc[0]
|
||||
|
||||
# 遍历每一个酸化等级,填充对应的四列数据
|
||||
for level in acid_levels:
|
||||
# 1. 构建要从 data_series 中查找的列名
|
||||
sample_col = f'样点数_{level}'
|
||||
sample_pct_col = f'样点占比_{level}'
|
||||
area_col = f'平差后面积_亩_{level}'
|
||||
area_pct_col = f'面积占比_{level}'
|
||||
|
||||
# 2. 从 data_series 中安全地获取值
|
||||
# 使用 .get(key, default_value) 的好处是,如果列名不存在,它会返回默认值(0),而不会报错
|
||||
sample_val = data_series.get(sample_col, 0)
|
||||
sample_pct_val = data_series.get(sample_pct_col, 0)
|
||||
area_val = data_series.get(area_col, 0)
|
||||
area_pct_val = data_series.get(area_pct_col, 0)
|
||||
|
||||
# 3. 将获取到的值填入单元格
|
||||
# - 对于数值,我们判断它是否大于0。如果是,就填入数值;否则,填入 "-"
|
||||
# - 对于样点数,我们将其转为整数
|
||||
# - 对于占比和面积,我们保留两位小数
|
||||
|
||||
# 样点数/个
|
||||
ws.cell(row=current_row, column=col_start).value = int(sample_val) if sample_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 1).value = f"{sample_pct_val:.2f}%" if sample_val > 0 else "-"
|
||||
# 制图面积/亩
|
||||
ws.cell(row=current_row, column=col_start + 2).number_format = "0.00"
|
||||
ws.cell(row=current_row, column=col_start + 2).value = f"{area_val:.0f}" if area_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 3).value = f"{area_pct_val:.2f}%" if area_val > 0 else "-"
|
||||
|
||||
# 移动到下一个酸化等级的起始列
|
||||
col_start += 4
|
||||
else:
|
||||
# 如果没有找到该土属的数据 (row_data 为空)
|
||||
# 这意味着该土属在源数据中不存在任何样点或面积信息
|
||||
# 我们将整行所有统计单元格都填充为 "-"
|
||||
|
||||
# acid_levels 列表包含3个等级,每个等级4列,总共12列
|
||||
for _ in range(len(acid_levels) * 4):
|
||||
ws.cell(row=current_row, column=col_start).value = "-"
|
||||
col_start += 1
|
||||
|
||||
# --- 填充单元格的逻辑结束 ---
|
||||
|
||||
# 完成一行填充后,行号加1,为下一行做准备
|
||||
current_row += 1
|
||||
|
||||
# 2. 计算并写入这个亚类的“合计”行
|
||||
ws.cell(row=current_row, column=2).value = '合计'
|
||||
|
||||
# 计算总样点数和总面积,仅针对当前 group_yl
|
||||
yl_grand_total_samples = 0
|
||||
for lvl in all_possible_levels:
|
||||
if f'样点数_{lvl}' in group_yl_df:
|
||||
yl_grand_total_samples += group_yl_df[f'样点数_{lvl}'].sum()
|
||||
|
||||
yl_grand_total_area = 0
|
||||
for lvl in all_possible_levels:
|
||||
if f'制图面积_亩_{lvl}' in group_yl_df:
|
||||
yl_grand_total_area += group_yl_df[f'制图面积_亩_{lvl}'].sum()
|
||||
|
||||
col_start = 3
|
||||
for level in acid_levels:
|
||||
sample_sum = group_yl_df.get(f'样点数_{level}', 0).sum()
|
||||
col_name = f'制图面积_亩_{level}'
|
||||
area_sum = group_yl_df[col_name].sum() if col_name in group_yl_df else 0
|
||||
# area_sum = group_yl_df.get(f'平差后面积_亩_{level}', 0).sum()
|
||||
|
||||
sample_perc = (sample_sum / yl_grand_total_samples * 100) if yl_grand_total_samples > 0 else 0
|
||||
area_perc = (area_sum / yl_grand_total_area * 100) if yl_grand_total_area > 0 else 0
|
||||
|
||||
ws.cell(row=current_row, column=col_start).value = int(sample_sum) if sample_sum > 0 else "-"
|
||||
ws.cell(row=current_row, column=col_start + 1).value = f"{sample_perc:.2f}%" if sample_sum > 0 else "-"
|
||||
ws.cell(row=current_row, column=col_start + 2).value = f"{area_sum:.0f}" if area_sum > 0 else "-"
|
||||
ws.cell(row=current_row, column=col_start + 3).value = f"{area_perc:.2f}%" if area_sum > 0 else "-"
|
||||
col_start += 4
|
||||
|
||||
# 3. 合并“亚类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
current_row += 1
|
||||
|
||||
# --- a. 定义样式 (不变) ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
|
||||
# d. 应用样式和调整列宽
|
||||
max_col = 2 + len(acid_levels) * 4
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{get_column_letter(max_col)}{current_row-1}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{get_column_letter(max_col)}2', header_font)
|
||||
|
||||
# 调整列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
def main(gdb_path, trlx_polygon, sh_ph_polygon, ph_raster, output_path, target_areas_df):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
sample_table_name = "历史样点PH信息_Table" # 图2: 样点信息表名
|
||||
|
||||
# 输出配置
|
||||
output_excel_path = os.path.join(output_path, "土壤类型酸化统计表.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
in_zone_feature = trlx_polygon # 土壤类型图
|
||||
# in_class_feature = sh_ph_polygon # 已重分类好的酸化PH图层
|
||||
in_class_feature = "最小面积统计单元"
|
||||
in_value_raster = ph_raster # 酸化PH栅格
|
||||
dltb_ph_statstable = "土地利用类型_酸化面积表" # 土壤类型_酸化面积表(gdb table)
|
||||
out_table_area = r"土壤类型_酸化面积表" # 输出的交集表名
|
||||
out_table_mean = r"土壤类型_酸化均值表" # 输出的均值表名
|
||||
|
||||
print("开始处理数据...")
|
||||
|
||||
if not arcpy.Exists(out_table_area):
|
||||
# 判断输入表是否存在SHFJ字段
|
||||
try:
|
||||
arcpy.management.CalculateField(in_class_feature, "SHFJ", "calculate_shfj(!gridcode!)", "PYTHON3", codeblock_cal_shfj)
|
||||
except Exception as e:
|
||||
print(f"计算SHFJ字段时发生错误: {e}")
|
||||
|
||||
# 1.用arcpy.analysis.TabulateIntersection进行交集制表
|
||||
arcpy.analysis.TabulateIntersection(
|
||||
in_zone_feature,
|
||||
["TS", "YL"],
|
||||
in_class_feature,
|
||||
out_table_area,
|
||||
"SHFJ",
|
||||
out_units="SQUARE_METERS",
|
||||
)
|
||||
|
||||
if not arcpy.Exists(out_table_mean):
|
||||
# 判断输入表是否存在YL_TS字段
|
||||
if not arcpy.ListFields(in_zone_feature, "YL_TS"):
|
||||
# 如果不存在,则添加该字段
|
||||
arcpy.management.AddField(in_zone_feature, "YL_TS", "TEXT")
|
||||
# 计算YL_TS字段的值
|
||||
arcpy.management.CalculateField(in_zone_feature,"YL_TS","!YL! + '_' + !TS!","PYTHON3")
|
||||
|
||||
# 2.用arcpy.sa.ZonalStatisticsAsTable进行区域统计
|
||||
mean_table = arcpy.sa.ZonalStatisticsAsTable(
|
||||
in_zone_feature, "YL_TS", in_value_raster, out_table_mean, "DATA", "MEAN"
|
||||
)
|
||||
# 2.1 添加土壤类型字段并计算
|
||||
arcpy.management.AddFields(
|
||||
out_table_mean,
|
||||
[["YL", "TEXT"],["TS", "TEXT"]],
|
||||
)
|
||||
arcpy.management.CalculateField(mean_table, "YL", "!YL_TS!.split('_')[0]", "PYTHON3")
|
||||
arcpy.management.CalculateField(mean_table, "TS", "!YL_TS!.split('_')[1]", "PYTHON3")
|
||||
|
||||
|
||||
# 生成表5.4的面积统计Excel报告
|
||||
final_dataframe, soil_structure = process_data_final(gdb_path, out_table_area, sample_table_name)
|
||||
|
||||
# 统计地类图斑酸化总面积亩
|
||||
each_acid_area = get_acid_area_by_group(target_areas_df)
|
||||
print(f"容县土壤类型图斑总 acid 总面积(亩):{each_acid_area}")
|
||||
# 执行平差计算
|
||||
if each_acid_area:
|
||||
adjusted_dataframe = apply_adjustment_by_each_level(final_dataframe, each_acid_area)
|
||||
print("使用平差值进行修正!")
|
||||
write_to_excel(adjusted_dataframe, soil_structure, output_excel_path)
|
||||
else:
|
||||
print("未使用平差值进行修正!")
|
||||
write_to_excel(final_dataframe, soil_structure, output_excel_path)
|
||||
|
||||
# 生成表5.4的均值统计Excel报告
|
||||
final_mean_dataframe = process_data_for_table5_5(gdb_path, out_table_mean, sample_table_name)
|
||||
write_to_excel_table5_5(final_mean_dataframe, output_excel_path.replace(".xlsx", "_mean.xlsx"))
|
||||
# adjusted_dataframe.to_csv(output_excel_path.replace(".xlsx", "_adjusted.csv"), index=False)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
167
tools/core/acid_stats/空间连接.py
Normal file
167
tools/core/acid_stats/空间连接.py
Normal file
@@ -0,0 +1,167 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import arcpy
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.append(str(Path(__file__).parent))
|
||||
from tools.config.arcgis_field_cal_code import codeblock_dltb_ejdl, codeblock_dltb_yjdl
|
||||
|
||||
def export_to_points(ph_points, dltb_features, trlx_features, xzq_features, assign_raster, workspace):
|
||||
# --- 1. 设置工作空间和变量 ---
|
||||
# 请根据您的实际情况修改以下路径
|
||||
arcpy.env.workspace = workspace
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
# 输入的要素类
|
||||
input_features = ph_points # 历史样点PH数据
|
||||
join_features_list = [trlx_features,xzq_features,dltb_features] # 连接图层 (规划分区)
|
||||
|
||||
# 输出的要素类
|
||||
final_output_fc = "历史样点PH信息_Table"
|
||||
|
||||
# --- 3. 主处理逻辑 ---
|
||||
try:
|
||||
print("开始处理赋值样点PH信息...")
|
||||
target_features = f"in_memory/temp_sample_raster"
|
||||
# 将栅格数据提取至历史PH样点
|
||||
arcpy.sa.ExtractValuesToPoints(
|
||||
in_point_features=input_features,
|
||||
in_raster=assign_raster,
|
||||
out_point_features=target_features,
|
||||
interpolate_values="NONE",
|
||||
add_attributes="VALUE_ONLY"
|
||||
)
|
||||
|
||||
print("开始计算地类一二级类别...")
|
||||
# 计算地类图斑一级、二级类别
|
||||
try:
|
||||
arcpy.management.CalculateField(dltb_features, "EJDL", "calculate_ejdl(!DLBM!,!DLMC!)", "PYTHON3", codeblock_dltb_ejdl)
|
||||
arcpy.management.CalculateField(dltb_features, "YJDL", "calculate_yjdl(!DLBM!)", "PYTHON3", codeblock_dltb_yjdl)
|
||||
arcpy.management.CalculateField(dltb_features, "YJDLBM", "!DLBM![:2]", "PYTHON3")
|
||||
|
||||
raster_path = Path(assign_raster)
|
||||
# if "二普" in raster_path.stem or "测土" in raster_path.stem:
|
||||
arcpy.management.CalculateField(target_features, "dPH", "!RASTERVALU!-!PH!", "PYTHON3", field_type="DOUBLE")
|
||||
# else:
|
||||
# arcpy.management.CalculateField(target_features, "dPH", "!PH!-!RASTERVALU!", "PYTHON3", field_type="DOUBLE")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
# --- 2. 定义要保留的字段 ---
|
||||
# 这是一个非常清晰的配置方式:指定每个图层要保留的字段列表
|
||||
fields_to_keep = {
|
||||
target_features: ["PH", "RASTERVALU", "dPH"],
|
||||
trlx_features: ["YL", "TS"],
|
||||
xzq_features: ["XZQMC"],
|
||||
dltb_features: ["YJDL", "EJDL"]
|
||||
}
|
||||
|
||||
print("开始配置字段映射...")
|
||||
|
||||
# 初始化当前的目标图层,最开始是原始的目标图层
|
||||
current_target = target_features
|
||||
|
||||
# 存储所有中间生成的临时文件,以便最后清理
|
||||
temp_outputs = []
|
||||
|
||||
temp_outputs.append(target_features)
|
||||
|
||||
# 获取目标图层的所有字段,以便在后续迭代中保留
|
||||
retained_fields = fields_to_keep.get(target_features, [])
|
||||
|
||||
|
||||
# 迭代处理每一个连接图层
|
||||
for i, join_features in enumerate(join_features_list):
|
||||
print(f"\n--- 开始处理第 {i+1} 个连接图层: {join_features} ---")
|
||||
|
||||
# 检查连接图层是否存在
|
||||
if not arcpy.Exists(join_features):
|
||||
print(f"警告: 连接图层 '{join_features}' 不存在,将跳过此连接。")
|
||||
continue
|
||||
|
||||
# --- 配置 FieldMappings ---
|
||||
field_mappings = arcpy.FieldMappings()
|
||||
|
||||
# a. 保留已经存在于 current_target 中的字段
|
||||
# 这些字段是在之前的迭代中保留下来的
|
||||
for field_name in retained_fields:
|
||||
try:
|
||||
field_map = arcpy.FieldMap()
|
||||
field_map.addInputField(current_target, field_name)
|
||||
field_mappings.addFieldMap(field_map)
|
||||
except Exception:
|
||||
# 如果字段在之前的某个步骤中未能成功添加,这里会捕获异常
|
||||
print(f"注意: 在图层 '{current_target}' 中未找到字段 '{field_name}',可能在之前的步骤中已被跳过。")
|
||||
|
||||
|
||||
# b. 从当前的 join_features 中添加新字段
|
||||
fields_from_current_join = fields_to_keep.get(join_features, [])
|
||||
for field_name in fields_from_current_join:
|
||||
try:
|
||||
field_map = arcpy.FieldMap()
|
||||
field_map.addInputField(join_features, field_name)
|
||||
field_map.mergeRule = "First" # 对所有连接字段使用 "First" 规则
|
||||
field_mappings.addFieldMap(field_map)
|
||||
except Exception as e:
|
||||
print(f"警告: 添加字段 '{field_name}' (来自 '{join_features}') 时出错,将跳过。错误信息: {e}")
|
||||
|
||||
# 如果本次迭代没有有效的字段映射,则跳过
|
||||
if field_mappings.fieldCount == 0:
|
||||
print(f"警告: 对于连接图层 '{join_features}' 没有有效的字段可以添加,跳过此连接。")
|
||||
continue
|
||||
|
||||
# 定义本次连接的临时输出名
|
||||
# 使用 in_memory 工作空间可以提高性能
|
||||
temp_output = f"in_memory/temp_join_{i}"
|
||||
temp_outputs.append(temp_output)
|
||||
|
||||
print(f"执行空间连接: '{current_target}' + '{join_features}' -> '{temp_output}'")
|
||||
|
||||
# 执行空间连接
|
||||
arcpy.analysis.SpatialJoin(
|
||||
target_features=current_target,
|
||||
join_features=join_features,
|
||||
out_feature_class=temp_output,
|
||||
join_operation="JOIN_ONE_TO_ONE",
|
||||
join_type="KEEP_ALL",
|
||||
field_mapping=field_mappings,
|
||||
match_option="INTERSECT"
|
||||
)
|
||||
|
||||
# 更新 current_target 为本次操作的输出,以便下一次迭代使用
|
||||
current_target = temp_output
|
||||
|
||||
# 更新已保留字段列表,为下一次迭代做准备
|
||||
retained_fields.extend(fields_from_current_join)
|
||||
print(f"连接成功。目前已保留的字段: {retained_fields}")
|
||||
|
||||
# --- 4. 保存最终结果并清理 ---
|
||||
|
||||
# 将最后一个临时输出复制或重命名为最终结果
|
||||
if arcpy.Exists(current_target):
|
||||
print(f"\n所有连接完成。将最终结果 '{current_target}' 保存为 '{final_output_fc}'...")
|
||||
# arcpy.management.CopyFeatures(current_target, final_output_fc)
|
||||
arcpy.conversion.ExportTable(current_target, final_output_fc)
|
||||
print("最终结果已保存。")
|
||||
|
||||
# 验证输出字段
|
||||
output_fields = [f.name for f in arcpy.ListFields(final_output_fc)]
|
||||
print(f"最终输出的字段为: {output_fields}")
|
||||
else:
|
||||
print("警告: 没有任何连接操作成功执行,未生成最终输出。")
|
||||
|
||||
except arcpy.ExecuteError:
|
||||
print("\n--- ArcPy 执行错误 ---")
|
||||
print(arcpy.GetMessages(2))
|
||||
except Exception as e:
|
||||
print(f"\n--- 发生未预料的错误 ---")
|
||||
print(e)
|
||||
finally:
|
||||
# 清理所有中间生成的临时文件
|
||||
print("\n开始清理临时文件...")
|
||||
for temp_file in temp_outputs:
|
||||
if arcpy.Exists(temp_file):
|
||||
arcpy.management.Delete(temp_file)
|
||||
print(f"已删除临时文件: {temp_file}")
|
||||
print("清理完成。")
|
||||
641
tools/core/acid_stats/行政区划酸化统计表.py
Normal file
641
tools/core/acid_stats/行政区划酸化统计表.py
Normal file
@@ -0,0 +1,641 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
from tools.config.arcgis_field_cal_code import codeblock_cal_shfj
|
||||
from tools.core.utils.excel_utils import ExcelStyleUtils
|
||||
|
||||
|
||||
|
||||
yjdl_order = ["耕地", "园地", "林地", "草地", "其他"]
|
||||
ejdl_order = ["水田", "旱地", "水浇地", "果园", "茶园", "橡胶园", "其他园地"]
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
# 等级计算
|
||||
def get_acidification_degree(delta_ph):
|
||||
"""根据ΔpH值判断酸化程度"""
|
||||
if pd.isna(delta_ph) or delta_ph == 0:
|
||||
return "-"
|
||||
# 请根据您的实际分级标准调整这里的阈值
|
||||
if delta_ph > 1.0:
|
||||
return "重度酸化"
|
||||
elif 0.5 < delta_ph <= 1.0:
|
||||
return "中度酸化"
|
||||
elif 0.3 < delta_ph <= 0.5:
|
||||
return "轻度酸化"
|
||||
elif 0.1 < delta_ph <= 0.3:
|
||||
return "弱酸化"
|
||||
else: # dPH < -0.3
|
||||
return "其他"
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table5_7(gdb_path, mean_table_name, sample_table_name):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# --- a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
sample_table_path = os.path.join(gdb_path, sample_table_name)
|
||||
sample_fields = ['XZQMC','YJDL','EJDL', 'dPH']
|
||||
df_samples = pd.DataFrame(arcpy.da.TableToNumPyArray(sample_table_path, sample_fields, 'dPH>0.3', skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, ['XZQMC','YJDL', 'EJDL'])
|
||||
|
||||
# 按 YJDL, EJDL 分组,计算 dPH 的均值
|
||||
df_sample_means = df_samples.groupby(['XZQMC'])['dPH'].mean().reset_index()
|
||||
df_sample_means.rename(columns={'dPH': '样点均值'}, inplace=True)
|
||||
print("样点均值计算完成。")
|
||||
|
||||
# --- b. 处理制图数据,获取“制图均值”和“制图样点数” ---
|
||||
print("--> 步骤2: 获取制图均值和样点数...")
|
||||
mean_table_path = os.path.join(gdb_path, mean_table_name)
|
||||
mean_fields = ['XZQMC', 'MEAN', 'COUNT']
|
||||
df_map_data = pd.DataFrame(arcpy.da.TableToNumPyArray(mean_table_path, mean_fields, skip_nulls=False))
|
||||
df_map_data = clean_df(df_map_data, ['XZQMC'])
|
||||
df_map_data.rename(columns={'MEAN': '制图均值', 'COUNT': '制图样点数'}, inplace=True)
|
||||
print("制图数据获取完成。")
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['XZQMC']],
|
||||
df_map_data[['XZQMC']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['XZQMC'], how='left')
|
||||
# **【核心修改】: 合并整个 df_map_data,而不仅仅是均值列**
|
||||
df_final = pd.merge(df_final, df_map_data, on=['XZQMC'], how='left')
|
||||
|
||||
# --- d. 计算酸化程度 ---
|
||||
print("--> 步骤4: 计算酸化程度...")
|
||||
# **【核心修改】: 在计算酸化程度之前,先过滤掉不展示的行**
|
||||
# 我们只对 dPH 在酸化范围内 ( > 0.3) 的数据感兴趣
|
||||
# 但为了计算合计,我们需要保留所有数据,所以这一步只计算,不删除
|
||||
df_final['酸化程度_样本'] = df_final['样点均值'].apply(get_acidification_degree)
|
||||
df_final['酸化程度_制图'] = df_final['制图均值'].apply(get_acidification_degree)
|
||||
|
||||
df_final.sort_values(['XZQMC'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
|
||||
|
||||
# --- 4. Excel 制表 均值---
|
||||
def write_to_excel_table5_7(df, output_path):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同土地利用类型pH变化统计"
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:A2'); ws['A1'] = '乡镇/街道'
|
||||
ws.merge_cells('B1:E1'); ws['B1'] = 'ΔpH'
|
||||
|
||||
ws['B2'] = '样点均值'
|
||||
ws['C2'] = '酸化程度'
|
||||
ws['D2'] = '制图均值'
|
||||
ws['E2'] = '酸化程度'
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
# **【核心修改】: 先对整个DataFrame进行过滤,只保留需要展示的行**
|
||||
acid_levels_to_show = ["弱酸化", "轻度酸化", "中度酸化", "重度酸化", "其他"]
|
||||
df_to_write = df[
|
||||
df['酸化程度_样本'].isin(acid_levels_to_show) | df['酸化程度_制图'].isin(acid_levels_to_show)
|
||||
].copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for _, row_data in df_to_write.iterrows():
|
||||
|
||||
print(f"正在写入一级地类...")
|
||||
|
||||
# 写入数据”
|
||||
ws.cell(row=current_row, column=1).value = row_data['XZQMC']
|
||||
|
||||
# 填充样点数据
|
||||
sample_mean = row_data.get('样点均值')
|
||||
if pd.notna(sample_mean):
|
||||
ws.cell(row=current_row, column=2).value = f"{sample_mean:.2f}" if sample_mean > 0.3 else "-"
|
||||
ws.cell(row=current_row, column=3).value = row_data.get('酸化程度_样本', '-') if sample_mean > 0.3 else "-"
|
||||
else:
|
||||
ws.cell(row=current_row, column=2).value = "-"
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
|
||||
# 填充制图数据
|
||||
map_mean = row_data.get('制图均值')
|
||||
if pd.notna(map_mean):
|
||||
ws.cell(row=current_row, column=4).value = f"{map_mean:.2f}" if map_mean > 0.3 else "-"
|
||||
ws.cell(row=current_row, column=5).value = row_data.get('酸化程度_制图', '-') if map_mean > 0.3 else "-"
|
||||
else:
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
|
||||
current_row += 1
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}{current_row-1}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
# 设置列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
# --- 2. 数据处理与分析 面积 各乡镇---
|
||||
def process_data_for_table5_4(gdb_path, area_table_name, target_area_dict):
|
||||
"""
|
||||
【最终修正版 v2】: 先建立统一的层级结构,再分别合并统计结果。
|
||||
"""
|
||||
print("【最终修正版 v2】开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# --- a. 独立统计面积数据 ---
|
||||
print("--> 步骤1: 独立统计面积数据...")
|
||||
area_table_path = os.path.join(gdb_path, area_table_name)
|
||||
df_area = pd.DataFrame(arcpy.da.TableToNumPyArray(area_table_path, ['XZQMC', 'SHFJ', 'AREA'], skip_nulls=False))
|
||||
df_area = clean_df(df_area, ['XZQMC'])
|
||||
|
||||
df_final = pd.DataFrame()
|
||||
if not df_area.empty:
|
||||
# 计算平差系数
|
||||
target_shfj_areas = target_area_dict.groupby(['SHFJ'])['AREA_MU'].sum().reset_index()
|
||||
original_shfj_areas = df_area.groupby(['SHFJ'])['AREA'].sum().reset_index()
|
||||
original_shfj_areas['AREA_MU'] = original_shfj_areas['AREA'] * 0.0015
|
||||
|
||||
adjustment_factors = []
|
||||
for index, row in original_shfj_areas.iterrows():
|
||||
shfj = row['SHFJ']
|
||||
area_mu = row['AREA_MU']
|
||||
adjustment_factor = target_shfj_areas[target_shfj_areas['SHFJ'] == shfj]['AREA_MU'].values[0] / area_mu
|
||||
adjustment_factors.append({
|
||||
'SHFJ': shfj,
|
||||
'平差系数':adjustment_factor
|
||||
})
|
||||
|
||||
|
||||
factor_df = pd.DataFrame(adjustment_factors)
|
||||
|
||||
df_sh_area = df_area.merge(factor_df[['SHFJ', '平差系数']], on='SHFJ')
|
||||
|
||||
df_sh_area['制图面积_亩'] = df_sh_area['AREA'] * 0.0015 * df_sh_area['平差系数']
|
||||
ts_total_area = df_sh_area.groupby(['XZQMC'])['制图面积_亩'].transform('sum')
|
||||
df_sh_area['面积占比'] = (df_sh_area['制图面积_亩'] / ts_total_area) * 100
|
||||
df_area_stats = df_sh_area.pivot_table(
|
||||
index=['XZQMC'], columns='SHFJ', values=['制图面积_亩', '面积占比'], fill_value=0
|
||||
).reset_index()
|
||||
df_area_stats.columns = [f'{col[0]}_{col[1]}'.strip('_') if col[1] else col[0] for col in df_area_stats.columns]
|
||||
|
||||
df_final = df_area_stats
|
||||
|
||||
print("--> 步骤2: 计算酸化面积合计...")
|
||||
|
||||
# 定义属于酸化类别的面积列
|
||||
acidic_area_cols = [
|
||||
'制图面积_亩_轻度酸化',
|
||||
'制图面积_亩_中度酸化',
|
||||
'制图面积_亩_重度酸化'
|
||||
]
|
||||
|
||||
# 确保这些列存在于DataFrame中,不存在的列用0代替
|
||||
for col in acidic_area_cols:
|
||||
if col not in df_final.columns:
|
||||
df_final[col] = 0
|
||||
|
||||
# 将这三列相加,得到合计值
|
||||
df_final['酸化面积合计_亩'] = df_final[acidic_area_cols].sum(axis=1)
|
||||
|
||||
# --- d. 最后清理和构建映射 ---
|
||||
df_final.fillna(0, inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
|
||||
# --- 3. Excel 制表 面积---
|
||||
def write_to_excel_table5_4(df, output_path):
|
||||
"""
|
||||
【最终修正版】: 将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel,将创建一个空的报告。")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同乡镇酸化面积统计"
|
||||
ws['A1'] = "没有有效的统计数据。"
|
||||
wb.save(output_path)
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同乡镇酸化面积统计"
|
||||
|
||||
# --- b. 绘制表头 (不变) ---
|
||||
ws.merge_cells('A1:A2'); ws['A1'] = '乡镇/街道'
|
||||
|
||||
acid_levels = ['弱酸化', '轻度酸化', '中度酸化', '重度酸化', '其他']
|
||||
# acid_level_headers = ['0.1<ΔpH≤0.3', '0.3<ΔpH≤0.5', '0.5<ΔpH≤1.0', 'ΔpH>1.0', '其他']
|
||||
# all_possible_levels = ['碱化', '未酸化', '轻度酸化', '中度酸化', '重度酸化']
|
||||
acid_level_headers = ['弱酸化(0.1<ΔpH≤0.3)','轻度酸化(0.3<ΔpH≤0.5)', '中度酸化(0.5<ΔpH≤1.0)', '重度酸化(ΔpH>1.0)', '其他(未酸化)']
|
||||
|
||||
col_start = 2
|
||||
for header in acid_level_headers:
|
||||
ws.merge_cells(start_row=1, start_column=col_start, end_row=1, end_column=col_start + 1)
|
||||
ws.cell(row=1, column=col_start).value = header
|
||||
ws.cell(row=2, column=col_start).value = '面积/亩'
|
||||
ws.cell(row=2, column=col_start + 1).value = '占比/%'
|
||||
col_start += 2
|
||||
|
||||
# 增加合计列的表头**
|
||||
total_col = col_start # 记录合计列的列号
|
||||
ws.merge_cells(start_row=1, start_column=total_col, end_row=2, end_column=total_col)
|
||||
ws.cell(row=1, column=total_col).value = '酸化面积合计'
|
||||
|
||||
# --- c. 填充数据 (完全重构的逻辑) ---
|
||||
current_row = 3
|
||||
|
||||
# **【核心修改】: 不再需要 group_yl_df,直接遍历整个 df**
|
||||
# 假设 df 已经按 XZQMC 排序(如果需要的话)
|
||||
df_sorted = df.sort_values('XZQMC').reset_index(drop=True)
|
||||
|
||||
for index, row_data in df_sorted.iterrows():
|
||||
ws.cell(row=current_row, column=1).value = row_data['XZQMC']
|
||||
|
||||
col_start = 2
|
||||
for level in acid_levels:
|
||||
area_col = f'制图面积_亩_{level}'
|
||||
area_pct_col = f'面积占比_{level}'
|
||||
area_val = row_data.get(area_col, 0)
|
||||
area_pct_val = row_data.get(area_pct_col, 0)
|
||||
|
||||
ws.cell(row=current_row, column=col_start).value = f"{area_val:.0f}" if area_val > 0 else "-"
|
||||
ws.cell(row=current_row, column=col_start + 1).value = f"{area_pct_val:.2f}%" if area_val > 0 else "-"
|
||||
col_start += 2
|
||||
|
||||
# **【核心修改】: 填充酸化面积合计列的值**
|
||||
total_area_val = row_data.get('酸化面积合计_亩', 0)
|
||||
ws.cell(row=current_row, column=total_col).value = f"{total_area_val:.0f}" if total_area_val > 0 else "-"
|
||||
|
||||
current_row += 1
|
||||
|
||||
# **(可选) 增加一个所有乡镇的“总合计”行**
|
||||
# print("--> 计算并写入总合计行...")
|
||||
# ws.cell(row=current_row, column=1).value = '总合计'
|
||||
|
||||
# col_start = 2
|
||||
# for level in acid_levels:
|
||||
# area_col = f'制图面积_亩_{level}'
|
||||
# area_sum = df_sorted.get(area_col, 0).sum()
|
||||
# # 总合计行的占比是相对于所有乡镇的总面积
|
||||
# grand_total_area = df_sorted[[f'制图面积_亩_{lvl}' for lvl in all_possible_levels if f'制图面积_亩_{lvl}' in df_sorted]].sum().sum()
|
||||
# area_perc = (area_sum / grand_total_area * 100) if grand_total_area > 0 else 0
|
||||
|
||||
# ws.cell(row=current_row, column=col_start).value = f"{area_sum:.2f}" if area_sum > 0 else "-"
|
||||
# ws.cell(row=current_row, column=col_start + 1).value = f"{area_perc:.2f}" if area_sum > 0 else "-"
|
||||
# col_start += 2
|
||||
|
||||
# grand_total_acidic_area = df_sorted['酸化面积合计_亩'].sum()
|
||||
# ws.cell(row=current_row, column=total_col).value = f"{grand_total_acidic_area:.2f}" if grand_total_acidic_area > 0 else "-"
|
||||
# current_row += 1
|
||||
|
||||
# --- a. 定义样式 (不变) ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
|
||||
# --- d. 应用样式和调整列宽 (最终健壮版) ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}{current_row-1}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
# 设置列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
# 步骤5.3: 生成表5.3 - 总表数据处理
|
||||
def process_data_for_table5_2(gdb_path, area_table_name, sample_table_name, target_area_dict:pd.DataFrame):
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# --- a. 从两个表中提取并建立唯一的 (YJDL, EJDL) 层级结构 "骨架" ---
|
||||
print("--> 步骤1: 建立统一的层级结构...")
|
||||
sample_table_path = os.path.join(gdb_path, sample_table_name)
|
||||
area_table_path = os.path.join(gdb_path, area_table_name)
|
||||
|
||||
|
||||
# --- b. 独立统计样点数据 ---
|
||||
print("--> 步骤2: 独立统计样点数据...")
|
||||
df_samples = pd.DataFrame(arcpy.da.TableToNumPyArray(sample_table_path, ['XZQMC', 'dPH'], skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, ['XZQMC'])
|
||||
|
||||
if not df_samples.empty:
|
||||
bins = [-np.inf, 0.1, 0.3, 0.5, 1.0, np.inf]
|
||||
labels = ["其他", "弱酸化", "轻度酸化", "中度酸化", "重度酸化"]
|
||||
df_samples['SHFJ'] = pd.cut(df_samples['dPH'], bins=bins, labels=labels, right=True)
|
||||
sample_counts = df_samples.groupby(['SHFJ'], observed=False).size().reset_index(name='样点数')
|
||||
sample_counts = sample_counts.merge(df_samples.groupby(['SHFJ'], observed=False)['dPH'].mean(), on='SHFJ')
|
||||
ts_total_samples = sample_counts['样点数'].sum()
|
||||
sample_counts['样点占比'] = (sample_counts['样点数'] / ts_total_samples) * 100
|
||||
# print(sample_counts)
|
||||
|
||||
# --- c. 独立统计面积数据 ---
|
||||
print("--> 步骤3: 独立统计面积数据...")
|
||||
df_area = pd.DataFrame(arcpy.da.TableToNumPyArray(area_table_path, ['XZQMC', 'SHFJ', 'AREA'], skip_nulls=False))
|
||||
df_area = clean_df(df_area, ['XZQMC'])
|
||||
|
||||
if not df_area.empty:
|
||||
# 计算平差系数
|
||||
target_shfj_areas = target_area_dict.groupby(['SHFJ'])['AREA_MU'].sum().reset_index()
|
||||
original_shfj_areas = df_area.groupby(['SHFJ'])['AREA'].sum().reset_index()
|
||||
original_shfj_areas['AREA_MU'] = original_shfj_areas['AREA'] * 0.0015
|
||||
|
||||
adjustment_factors = []
|
||||
for index, row in original_shfj_areas.iterrows():
|
||||
shfj = row['SHFJ']
|
||||
area_mu = row['AREA_MU']
|
||||
adjustment_factor = target_shfj_areas[target_shfj_areas['SHFJ'] == shfj]['AREA_MU'].values[0] / area_mu
|
||||
adjustment_factors.append({
|
||||
'SHFJ': shfj,
|
||||
'平差系数':adjustment_factor
|
||||
})
|
||||
|
||||
|
||||
factor_df = pd.DataFrame(adjustment_factors)
|
||||
|
||||
df_sh_area = df_area.merge(factor_df[['SHFJ', '平差系数']], on='SHFJ')
|
||||
|
||||
df_sh_area['制图面积_亩'] = df_sh_area['AREA'] * 0.0015 * df_sh_area['平差系数']
|
||||
df_area_counts = df_sh_area.groupby(['SHFJ'], observed=False)[['制图面积_亩']].sum()
|
||||
ts_total_area = df_area_counts['制图面积_亩'].sum()
|
||||
df_area_counts['面积占比'] = (df_area_counts['制图面积_亩'] / ts_total_area) * 100
|
||||
|
||||
df_final = pd.merge(sample_counts, df_area_counts, on=['SHFJ'], how='left')
|
||||
|
||||
# # --- d. 最后清理和构建映射 ---
|
||||
df_final.fillna(0, inplace=True)
|
||||
|
||||
return df_final
|
||||
|
||||
# --- 3. Excel 制表 总表---
|
||||
def write_to_excel_table5_2(df, df_mean, output_path):
|
||||
"""
|
||||
【最终修正版】: 将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel,将创建一个空的报告。")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws['A1'] = "没有有效的统计数据。"
|
||||
wb.save(output_path)
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "行政区酸化程度等级分布及占比"
|
||||
|
||||
# --- b. 绘制表头 (不变) ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '酸化程度'
|
||||
ws.merge_cells('C1:D1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('E1:F1'); ws['E1'] = '制图统计'
|
||||
ws.merge_cells('A8:B8'); ws['A8'] = '总计'
|
||||
ws.merge_cells('A9:B9'); ws['A9'] = '全县酸化样点ΔpH 均值'
|
||||
ws.merge_cells('A10:B10'); ws['A10'] = '全县酸化制图ΔpH 均值'
|
||||
|
||||
ws['A2'] = '分级'; ws['B2'] = '值域'
|
||||
ws['C2'] = '数量/个'; ws['D2'] = '占比'
|
||||
ws['E2'] = '面积/亩'; ws['F2'] = '占比'
|
||||
|
||||
acid_levels = ['弱酸化', '轻度酸化', '中度酸化', '重度酸化', '其他']
|
||||
acid_level_headers = ['0.1<ΔpH≤0.3', '0.3<ΔpH≤0.5', '0.5<ΔpH≤1.0', 'ΔpH>1.0', '未酸化']
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
# 1. 遍历该一级地类下的所有“二级地类”并写入数据
|
||||
for index,level in enumerate(acid_levels):
|
||||
ws.cell(row=current_row, column=1).value = level
|
||||
ws.cell(row=current_row, column=2).value = acid_level_headers[index]
|
||||
|
||||
# 在子集中查找当前二级地类的数据行
|
||||
row_data = df[df['SHFJ'] == level]
|
||||
|
||||
# --- 填充单元格的逻辑开始 ---
|
||||
col_start = 3 # 从第 C 列开始填充
|
||||
|
||||
# 检查是否找到了该土属的数据
|
||||
if not row_data.empty:
|
||||
data_series = row_data.iloc[0]
|
||||
|
||||
# 1. 构建要从 data_series 中查找的列名
|
||||
sample_col = f'样点数'
|
||||
sample_pct_col = f'样点占比'
|
||||
area_col = f'制图面积_亩'
|
||||
area_pct_col = f'面积占比'
|
||||
|
||||
# 2. 从 data_series 中安全地获取值
|
||||
sample_val = data_series.get(sample_col, 0)
|
||||
sample_pct_val = data_series.get(sample_pct_col, 0)
|
||||
area_val = data_series.get(area_col, 0)
|
||||
area_pct_val = data_series.get(area_pct_col, 0)
|
||||
|
||||
|
||||
# 3. 将获取到的值填入单元格
|
||||
ws.cell(row=current_row, column=col_start).value = f"{sample_val:.0f}" if sample_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 1).value = f"{sample_pct_val:.2f}%" if sample_val > 0 else "-"
|
||||
# 制图面积/亩
|
||||
ws.cell(row=current_row, column=col_start + 2).value = f"{area_val:.0f}" if area_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 3).value = f"{area_pct_val:.2f}%" if area_val > 0 else "-"
|
||||
|
||||
# 移动到下一个酸化等级的起始列
|
||||
col_start += 2
|
||||
else:
|
||||
for _ in range(4):
|
||||
ws.cell(row=current_row, column=col_start).value = "-"
|
||||
col_start += 1
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合计单元格填充
|
||||
mask = df["SHFJ"].isin(acid_levels)
|
||||
df_acid = df[mask]
|
||||
weighted_avg = (df_acid["dPH"] * df_acid["样点数"]).sum() / df_acid["样点数"].sum()
|
||||
|
||||
mean_msk = df_mean["酸化程度_制图"].isin(acid_levels)
|
||||
df_mean_acid = df_mean[mean_msk]
|
||||
weighted_mean = (df_mean_acid["制图均值"] * df_mean_acid["制图样点数"]).sum() / df_mean_acid["制图样点数"].sum()
|
||||
|
||||
ws.merge_cells('C9:F9')
|
||||
ws.merge_cells('C10:F10')
|
||||
ws['C8'] = df[df['SHFJ'].isin(acid_levels)]['样点数'].sum()
|
||||
ws['D8'] = f"{df[df['SHFJ'].isin(acid_levels)]['样点占比'].sum():.2f}%"
|
||||
ws['E8'] = f"{df[df['SHFJ'].isin(acid_levels)]['制图面积_亩'].sum():.0f}"
|
||||
ws['F8'] = f"{df[df['SHFJ'].isin(acid_levels)]['面积占比'].sum():.2f}%"
|
||||
ws['C9'] = f"{weighted_avg:.2f}" # type: ignore
|
||||
ws['C10'] = f"{weighted_mean:.2f}"
|
||||
|
||||
# --- a. 定义样式 (不变) ---
|
||||
header_font = Font(name='宋体', size=11)
|
||||
|
||||
# --- d. 应用样式和调整列宽 (最终健壮版) ---
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:F10')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:F2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
# 设置列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
def main(gdb_path, xzq_features, ph_features, dltb_features, sh_ph_tif, output_path,target_areas_dict:dict):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
output_excel_path = os.path.join(output_path,"乡镇街道酸化统计表.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
sample_table_name = "历史样点PH信息_Table" # 图2: 样点信息表名
|
||||
in_zone_feature = xzq_features # 规划分区图层
|
||||
in_class_feature = ph_features # 已重分类好的酸化PH图层
|
||||
dltb_class_feature = dltb_features
|
||||
in_value_raster = sh_ph_tif # 赋值栅格
|
||||
out_feature_class = "最小面积统计单元"
|
||||
out_table_area = r"行政区划_酸化面积表" # 输出的交集表名
|
||||
out_table_mean = r"行政区划_酸化均值表" # 输出的均值表名
|
||||
|
||||
print("开始处理数据...")
|
||||
|
||||
if not arcpy.Exists(out_feature_class):
|
||||
# 判断输入表是否存在SHFJ字段
|
||||
try:
|
||||
arcpy.management.CalculateField(in_class_feature, "SHFJ", "calculate_shfj(!gridcode!)", "PYTHON3", codeblock_cal_shfj)
|
||||
except Exception as e:
|
||||
print(f"计算SHFJ字段时发生错误: {e}")
|
||||
|
||||
arcpy.analysis.Intersect(
|
||||
in_features=[dltb_class_feature, in_class_feature],
|
||||
out_feature_class=out_feature_class,
|
||||
join_attributes="ALL",
|
||||
output_type="INPUT"
|
||||
)
|
||||
|
||||
if not arcpy.Exists(out_table_area):
|
||||
# 1.用arcpy.analysis.TabulateIntersection进行交集制表
|
||||
arcpy.analysis.TabulateIntersection(
|
||||
in_zone_feature,
|
||||
["XZQMC"],
|
||||
out_feature_class,
|
||||
out_table_area,
|
||||
"SHFJ",
|
||||
out_units="SQUARE_METERS",
|
||||
)
|
||||
|
||||
if not arcpy.Exists(out_table_mean):
|
||||
# 2.用arcpy.sa.ZonalStatisticsAsTable进行区域统计
|
||||
arcpy.sa.ZonalStatisticsAsTable(
|
||||
in_zone_feature, "XZQMC", in_value_raster, out_table_mean, "DATA", "MEAN"
|
||||
)
|
||||
|
||||
# 计算按地类平差后的各酸化等级面积
|
||||
if arcpy.Exists(out_feature_class):
|
||||
df = pd.DataFrame(arcpy.da.TableToNumPyArray(out_feature_class, ["YJDL", "SHFJ", "Shape_Area"]))
|
||||
df_area = df.groupby(["YJDL", "SHFJ"]).agg({"Shape_Area": "sum"}).reset_index()
|
||||
|
||||
yjdl_area = df_area.groupby(['YJDL'])['Shape_Area'].sum().reset_index()
|
||||
|
||||
landuse_types = {'耕地':'01', '园地':'02', '林地':'03', '草地':'04', '其他':'12'}
|
||||
adjustment_factors = []
|
||||
for _, row in yjdl_area.iterrows():
|
||||
yjdl = row['YJDL']
|
||||
original_total = row['Shape_Area'] * 0.0015
|
||||
target_total = target_areas_dict.get(landuse_types[yjdl], original_total)
|
||||
adjustment_factor = target_total / original_total
|
||||
|
||||
adjustment_factors.append({
|
||||
'YJDL': yjdl,
|
||||
'平差系数': adjustment_factor
|
||||
})
|
||||
|
||||
factor_df = pd.DataFrame(adjustment_factors)
|
||||
|
||||
df_with_factors = df_area.merge(factor_df[['YJDL', '平差系数']], on='YJDL')
|
||||
df_with_factors['AREA_MU'] = df_with_factors['Shape_Area'] * df_with_factors['平差系数'] * 0.0015
|
||||
|
||||
# print(df_with_factors)
|
||||
|
||||
# 生成表5.4的面积统计Excel报告
|
||||
final_area_dataframe = process_data_for_table5_4(gdb_path, out_table_area, df_with_factors)
|
||||
write_to_excel_table5_4(final_area_dataframe, output_excel_path)
|
||||
|
||||
# 生成表5.3的均值统计Excel报告
|
||||
final_mean_dataframe = process_data_for_table5_7(gdb_path, out_table_mean, sample_table_name)
|
||||
write_to_excel_table5_7(final_mean_dataframe, output_excel_path.replace(".xlsx", "_mean.xlsx"))
|
||||
|
||||
# 生成总表5.2的统计Excel报告
|
||||
final_dataframe = process_data_for_table5_2(gdb_path, out_table_area, sample_table_name, df_with_factors)
|
||||
write_to_excel_table5_2(final_dataframe, final_mean_dataframe, output_excel_path.replace(".xlsx", "_total.xlsx"))
|
||||
|
||||
return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
167
tools/core/export_excel_to_jpg_v1.py
Normal file
167
tools/core/export_excel_to_jpg_v1.py
Normal file
@@ -0,0 +1,167 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
输入重分类后栅格转面要素类、乡镇边界面要素类、地类图斑要素类;
|
||||
按一级地类统计土壤属性面积 和 按乡镇统计土壤属性面积;
|
||||
将统计结果导出为Excel表格;
|
||||
将Excel表格转换为jpg图片;
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import traceback
|
||||
import argparse
|
||||
import win32com.client as win32
|
||||
import pythoncom
|
||||
import time
|
||||
|
||||
sys.path.append(str(Path(__file__).parent))
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description="将Excel表格转换为jpg图片")
|
||||
parser.add_argument("--settings_path", required=True, help="配置文件路径")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.settings_path:
|
||||
with open(args.settings_path, 'r', encoding="utf-8") as settings_file:
|
||||
settings = json.load(settings_file)
|
||||
area_stat_settings = settings.get("area_stat_settings", {})
|
||||
else:
|
||||
print_status("错误: 未找到有效配置文件")
|
||||
sys.exit(1)
|
||||
|
||||
return area_stat_settings
|
||||
def print_status(message):
|
||||
"""
|
||||
输出状态信息到标准输出,用于 GUI 实时显示
|
||||
格式: STATUS: <message>
|
||||
"""
|
||||
print(f"STATUS:{message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def print_result(success, output_path="", error_message=""):
|
||||
"""
|
||||
输出最终结果到标准输出,用于 GUI 判断任务状态和获取结果
|
||||
格式: RESULT:True|<output_path>|
|
||||
格式: RESULT:False||<error_message>
|
||||
"""
|
||||
if success:
|
||||
print(f"RESULT:True|{output_path}|")
|
||||
else:
|
||||
# 在错误信息中替换换行符,避免干扰解析
|
||||
cleaned_error_message = error_message.replace('\n', ' ').replace('\r', '')
|
||||
print(f"RESULT:False||{cleaned_error_message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def export_excel_to_image(excel_path, sheet_name, output_path, range_address=None):
|
||||
|
||||
# 检查 Excel 文件是否存在
|
||||
if not os.path.exists(excel_path):
|
||||
print(f"错误: Excel 文件 '{excel_path}' 不存在。请检查路径。")
|
||||
return
|
||||
|
||||
# 确保输出目录存在
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
# 初始化 COM 库
|
||||
pythoncom.CoInitialize()
|
||||
|
||||
try:
|
||||
# 1. 获取 Excel 应用程序对象
|
||||
excel = win32.Dispatch("Excel.Application")
|
||||
excel.Visible = False # 不显示 Excel 窗口
|
||||
excel.DisplayAlerts = False # 不显示任何警告或提示框
|
||||
|
||||
# 2. 打开工作簿
|
||||
workbook_obj = excel.Workbooks.Open(excel_path)
|
||||
|
||||
# 3. 选择工作表
|
||||
try:
|
||||
sheet = workbook_obj.Sheets(sheet_name)
|
||||
except Exception:
|
||||
print(f"错误: 工作簿 '{os.path.basename(excel_path)}' 中找不到工作表 '{sheet_name}'。")
|
||||
# 尝试选择第一个工作表作为备用
|
||||
if workbook_obj.Sheets.Count > 0:
|
||||
sheet = workbook_obj.Sheets(1)
|
||||
print(f"改为导出第一个工作表 '{sheet.Name}'。")
|
||||
else:
|
||||
raise ValueError("工作簿中没有可用的工作表。")
|
||||
|
||||
# 4. 选择要复制的区域
|
||||
if range_address:
|
||||
try:
|
||||
range_obj = sheet.Range(range_address)
|
||||
except Exception:
|
||||
print(f"警告: 指定的导出范围 '{range_address}' 无效或不存在,将导出 UsedRange。")
|
||||
range_obj = sheet.UsedRange
|
||||
else:
|
||||
range_obj = sheet.UsedRange
|
||||
|
||||
# 选中区域(确保焦点)
|
||||
range_obj.Select()
|
||||
|
||||
# 5. 将选定区域复制为图片
|
||||
range_obj.CopyPicture(Format=1, Appearance=2) # xlBitmap = 1, xlScreen = 2
|
||||
|
||||
# 6. 临时创建ChartObject在当前工作表
|
||||
chart_width = range_obj.Width * (300/72) # 将点转换为厘米
|
||||
chart_height = range_obj.Height * (300/65) # 将点转换为厘米
|
||||
temp_chart_obj = sheet.ChartObjects().Add(0, 0, chart_width, chart_height).Chart
|
||||
|
||||
temp_chart_obj.Paste()
|
||||
|
||||
# 7. 导出图表为图片文件
|
||||
temp_chart_obj.Export(output_path, FilterName="JPG")
|
||||
print(f"图片已成功导出到 '{output_path}'。")
|
||||
|
||||
# 8. 删除临时图表对象
|
||||
sheet.ChartObjects(sheet.ChartObjects().Count).Delete()
|
||||
|
||||
# 9. 关闭工作簿,不保存更改
|
||||
workbook_obj.Close(False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理 Excel 时发生错误: {e}")
|
||||
print("请确保已安装 Microsoft Excel 应用程序,并且 Excel 文件路径、工作表名称正确。")
|
||||
finally:
|
||||
# 确保 Excel 应用程序被关闭
|
||||
if excel:
|
||||
try:
|
||||
excel.Quit()
|
||||
except Exception as e:
|
||||
print(f"关闭 Excel 应用程序时发生错误: {e}")
|
||||
|
||||
# 释放 COM 对象
|
||||
pythoncom.CoUninitialize()
|
||||
|
||||
def main():
|
||||
|
||||
params = None
|
||||
try:
|
||||
# 1. 解析参数
|
||||
params = parse_arguments()
|
||||
|
||||
output_path = params["batch_output_folder"]
|
||||
|
||||
for excel_file in os.listdir(output_path):
|
||||
time.sleep(1.5)
|
||||
if excel_file.endswith(".xlsx"):
|
||||
excel_file_path = os.path.join(output_path, excel_file)
|
||||
output_jpg_path = os.path.join(output_path, excel_file.replace(".xlsx", ".jpg"))
|
||||
export_excel_to_image(excel_file_path, "综合统计表", output_jpg_path)
|
||||
print_status(f"已处理文件: {excel_file}")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"主函数错误: {str(e)}\n{traceback.format_exc()}"
|
||||
print_status(error_msg)
|
||||
print_result(False, error_message=error_msg)
|
||||
finally:
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print_status("开始执行")
|
||||
main()
|
||||
310
tools/core/export_layout.py
Normal file
310
tools/core/export_layout.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
导出布局脚本
|
||||
此脚本可以独立运行,用于导出布局,不依赖于PyQt6线程
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import arcpy
|
||||
import argparse
|
||||
|
||||
|
||||
def log(message):
|
||||
"""日志输出函数"""
|
||||
print(message)
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description='导出布局')
|
||||
parser.add_argument('--mode', choices=['single', 'batch'], default='single', help='导出模式')
|
||||
parser.add_argument('--aprx_file_list', help='ArcGIS Pro工程文件路径')
|
||||
parser.add_argument('--input_aprx_folder', help='批量模式下的工程文件夹路径')
|
||||
parser.add_argument('--output_image_path', required=True, help='输出路径')
|
||||
parser.add_argument('--export_format', default='PDF', help='导出格式')
|
||||
parser.add_argument('--resolution', type=int, default=300, help='分辨率(DPI)')
|
||||
parser.add_argument('--use_multiprocessing', action='store_true', help='是否使用多进程')
|
||||
parser.add_argument('--process_count', type=int, default=2, help='进程数')
|
||||
parser.add_argument('--image_force_regenerate', help='输出文件名')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 处理图层列表参数(从JSON字符串转换为列表)
|
||||
try:
|
||||
# 尝试将字符串解析为JSON
|
||||
if args.aprx_file_list.startswith('[') and args.aprx_file_list.endswith(']'):
|
||||
args.aprx_file_list = json.loads(args.aprx_file_list)
|
||||
else:
|
||||
# 如果不是JSON格式,则假定是单个图层或逗号分隔的列表
|
||||
if ',' in args.aprx_file_list:
|
||||
cleaned = args.aprx_file_list.strip("[]")
|
||||
args.aprx_file_list = [aprx_file_list.strip() for aprx_file_list in cleaned.split(',')]
|
||||
else:
|
||||
args.aprx_file_list = [args.aprx_file_list]
|
||||
except json.JSONDecodeError:
|
||||
args.aprx_file_list = [args.aprx_file_list]
|
||||
|
||||
return args
|
||||
|
||||
def get_file_extension(format_name):
|
||||
"""根据格式名称获取文件扩展名"""
|
||||
format_dict = {
|
||||
"PDF": ".pdf",
|
||||
"PNG": ".png",
|
||||
"JPG": ".jpg",
|
||||
"JPEG": ".jpg",
|
||||
"TIFF": ".tif",
|
||||
"EPS": ".eps",
|
||||
"SVG": ".svg",
|
||||
"AI": ".ai"
|
||||
}
|
||||
|
||||
return format_dict.get(format_name.upper(), ".pdf")
|
||||
|
||||
|
||||
def export_layout(params):
|
||||
"""导出布局"""
|
||||
aprx = None
|
||||
try:
|
||||
# 获取参数
|
||||
log(f"开始导出布局...")
|
||||
aprx_path = params['aprx_path']
|
||||
output_folder = params['output_path']
|
||||
export_format = params.get('export_format', 'PDF')
|
||||
resolution = params.get('resolution', 300)
|
||||
output_name = params.get('output_name', '')
|
||||
|
||||
# 确保输出文件夹存在
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
|
||||
# 打开地图文档
|
||||
try:
|
||||
log(f"打开地图文档: {aprx_path}")
|
||||
aprx = arcpy.mp.ArcGISProject(aprx_path) # type: ignore
|
||||
except Exception as e:
|
||||
raise Exception(f"无法打开地图文档: {str(e)}")
|
||||
|
||||
# 获取布局
|
||||
layouts = aprx.listLayouts()
|
||||
if not layouts:
|
||||
raise Exception("地图文档中没有布局")
|
||||
|
||||
# 如果未指定输出名称,则使用地图文档名称
|
||||
if not output_name:
|
||||
output_name = os.path.splitext(os.path.basename(aprx_path))[0]
|
||||
|
||||
# 获取文件扩展名
|
||||
file_ext = get_file_extension(export_format)
|
||||
|
||||
# 导出每个布局
|
||||
exported_files = []
|
||||
for layout in layouts:
|
||||
layout_name = layout.name
|
||||
output_file = os.path.join(output_folder, f"{output_name}{file_ext}")
|
||||
log(f"导出布局 {layout_name} 到 {output_file}")
|
||||
|
||||
try:
|
||||
if export_format.upper() == "PDF":
|
||||
# 导出PDF
|
||||
layout.exportToPDF(output_file, resolution=resolution)
|
||||
elif export_format.upper() in ["PNG", "JPG", "JPEG", "TIFF"]:
|
||||
# 导出栅格图像
|
||||
layout.exportToJPEG(output_file, resolution=resolution,jpeg_quality=85) if export_format.upper() in ["JPG", "JPEG"] else None
|
||||
layout.exportToPNG(output_file, resolution=resolution) if export_format.upper() == "PNG" else None
|
||||
layout.exportToTIFF(output_file, resolution=resolution) if export_format.upper() == "TIFF" else None
|
||||
elif export_format.upper() in ["EPS", "SVG"]:
|
||||
# 导出矢量图像
|
||||
layout.exportToEPS(output_file, resolution=resolution) if export_format.upper() == "EPS" else None
|
||||
layout.exportToSVG(output_file, resolution=resolution) if export_format.upper() == "SVG" else None
|
||||
else:
|
||||
# 默认导出PDF
|
||||
layout.exportToPDF(output_file, resolution=resolution)
|
||||
|
||||
exported_files.append(output_file)
|
||||
log(f"成功导出布局 {layout_name} 到 {output_file}")
|
||||
except Exception as e:
|
||||
log(f"导出布局 {layout_name} 失败: {str(e)}")
|
||||
|
||||
return {
|
||||
'exported_files': exported_files,
|
||||
'count': len(exported_files)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
log(f"导出布局失败: {str(e)}")
|
||||
raise
|
||||
finally:
|
||||
# 释放资源
|
||||
if aprx:
|
||||
del aprx
|
||||
arcpy.management.ClearWorkspaceCache()
|
||||
|
||||
|
||||
def batch_export_layout(params):
|
||||
"""批量导出布局"""
|
||||
try:
|
||||
# 获取参数
|
||||
log(f"开始批量导出布局...")
|
||||
aprx_files = params['aprx_files']
|
||||
output_folder = params['output_path']
|
||||
export_format = params.get('export_format', 'PDF')
|
||||
resolution = params.get('resolution', 300)
|
||||
|
||||
# 确保输出文件夹存在
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
|
||||
# 记录导出结果
|
||||
all_exported_files = []
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
|
||||
# 逐个处理aprx文件
|
||||
for aprx_path in aprx_files:
|
||||
try:
|
||||
file_name = os.path.splitext(os.path.basename(aprx_path))[0]
|
||||
log(f"\n处理文件: {file_name}")
|
||||
|
||||
# 准备参数
|
||||
export_params = {
|
||||
'aprx_path': aprx_path,
|
||||
'output_path': output_folder,
|
||||
'export_format': export_format,
|
||||
'resolution': resolution,
|
||||
'output_name': file_name
|
||||
}
|
||||
|
||||
# 调用导出布局函数
|
||||
result = export_layout(export_params)
|
||||
all_exported_files.extend(result['exported_files'])
|
||||
success_count += result['count']
|
||||
|
||||
log(f"文件 {file_name} 处理完成")
|
||||
except Exception as e:
|
||||
log(f"处理文件 {os.path.basename(aprx_path)} 失败: {str(e)}")
|
||||
failed_count += 1
|
||||
|
||||
# 返回结果
|
||||
log(f"\n批量导出完成")
|
||||
log(f"成功: {success_count} 个布局")
|
||||
log(f"失败: {failed_count} 个文件")
|
||||
|
||||
return {
|
||||
'exported_files': all_exported_files,
|
||||
'count': success_count,
|
||||
'success_count': success_count,
|
||||
'failed_count': failed_count
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
log(f"批量导出布局失败: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
def export_worker(aprx_path, output_path, export_format, resolution):
|
||||
"""子进程专用工作函数(保持最小化参数)"""
|
||||
try:
|
||||
# 每个子进程独立初始化ArcPy环境
|
||||
import arcpy
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
result = export_layout({
|
||||
'aprx_path': aprx_path,
|
||||
'output_path': output_path,
|
||||
'export_format': export_format,
|
||||
'resolution': resolution
|
||||
})
|
||||
return (True, aprx_path, result)
|
||||
except Exception as e:
|
||||
return (False, aprx_path, str(e))
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
|
||||
try:
|
||||
args = parse_arguments()
|
||||
|
||||
if len(args.aprx_file_list) == 1:
|
||||
aprx_file = args.aprx_file_list[0]
|
||||
if not os.path.exists(aprx_file):
|
||||
log(f"所选文件{aprx_file}不存在,请确认")
|
||||
return 1
|
||||
|
||||
# 准备参数
|
||||
params = {
|
||||
'aprx_path': aprx_file,
|
||||
'output_path': args.output_image_path,
|
||||
'export_format': args.export_format,
|
||||
'resolution': args.resolution
|
||||
}
|
||||
|
||||
# 调用导出函数
|
||||
result = export_layout(params)
|
||||
log(f"导出完成,成功导出 {result['count']} 个布局")
|
||||
|
||||
elif len(args.aprx_file_list) >1:
|
||||
if not args.input_aprx_folder:
|
||||
log("批量导出模式需要指定aprx_folder参数")
|
||||
return 1
|
||||
|
||||
# 查找所有aprx文件
|
||||
aprx_files = []
|
||||
valied_files = []
|
||||
failed_files = []
|
||||
|
||||
for file in args.aprx_file_list:
|
||||
if not os.path.exists(file) and file.lower().endswith('.aprx'):
|
||||
failed_files.append(os.path.basename(file))
|
||||
continue
|
||||
aprx_files.append(file)
|
||||
valied_files.append(os.path.basename(file))
|
||||
|
||||
if not aprx_files:
|
||||
log(f"在指定文件夹中未找到aprx文件: {args.input_aprx_folder}")
|
||||
return 1
|
||||
|
||||
log(f"找到 {len(valied_files)} 个有效aprx文件: {', '.join(valied_files)}\n")
|
||||
log(f"{len(failed_files)} 个无效文件: {', '.join(failed_files)}")
|
||||
|
||||
if args.use_multiprocessing and args.process_count > 1 and len(aprx_files)>1:
|
||||
from multiprocessing import Pool
|
||||
tasks = [(aprx_file, args.output_image_path, args.export_format, args.resolution) for aprx_file in aprx_files]
|
||||
with Pool(min(int(args.process_count), len(tasks))) as p:
|
||||
results = p.starmap(export_worker, tasks)
|
||||
|
||||
for success, aprx_path, result in results:
|
||||
if success:
|
||||
log(f"成功导出布局 {aprx_path} 到 {result['exported_files']}") # type: ignore
|
||||
else:
|
||||
log(f"导出布局 {aprx_path} 失败: {result}")
|
||||
return 0
|
||||
|
||||
else:
|
||||
# 准备参数
|
||||
params = {
|
||||
'aprx_files': aprx_files,
|
||||
'output_path': args.output_image_path,
|
||||
'export_format': args.export_format,
|
||||
'resolution': args.resolution
|
||||
}
|
||||
|
||||
# 调用批量导出函数
|
||||
result = batch_export_layout(params)
|
||||
log(f"批量导出完成,成功导出 {result['count']} 个布局")
|
||||
return 0
|
||||
else:
|
||||
log("请选择要处理的aprx文件")
|
||||
return 0
|
||||
except Exception as e:
|
||||
log(f"导出失败: {str(e)}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
572
tools/core/export_map_v1.py
Normal file
572
tools/core/export_map_v1.py
Normal file
@@ -0,0 +1,572 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
导出地图脚本
|
||||
此脚本可以独立运行,用于导出地图,支持批量导出
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import arcpy
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
sys.path.append(str(Path(__file__).parent))
|
||||
from utils import common_utils
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description="导出地图工具")
|
||||
parser.add_argument("--config_file", required=True, help="配置文件路径")
|
||||
parser.add_argument("--county_name", required=True, help="区县名称")
|
||||
parser.add_argument("--polygon_list", required=True, help="要导出的图层列表,JSON格式字符串")
|
||||
parser.add_argument("--template_aprx_file", required=True, help="模板文件路径")
|
||||
parser.add_argument("--output_path", required=True, help="输出路径")
|
||||
parser.add_argument("--data_source_path", required=True, help="数据源路径")
|
||||
parser.add_argument("--symbol_path", required=True, help="符号文件路径")
|
||||
parser.add_argument("--force_regenerate", action="store_true", help="强制重新生成工程文件")
|
||||
parser.add_argument("--pic_path", required=True, help="图片输入路径")
|
||||
|
||||
# 解析参数
|
||||
args = parser.parse_args()
|
||||
|
||||
# 处理图层列表参数(从JSON字符串转换为列表)
|
||||
try:
|
||||
# 尝试将字符串解析为JSON
|
||||
if args.polygon_list.startswith('[') and args.polygon_list.endswith(']'):
|
||||
args.polygon_list = json.loads(args.polygon_list)
|
||||
else:
|
||||
# 如果不是JSON格式,则假定是单个图层或逗号分隔的列表
|
||||
if ',' in args.polygon_list:
|
||||
cleaned = args.polygon_list.strip("[]")
|
||||
args.polygon_list = [layer.strip() for layer in cleaned.split(',')]
|
||||
else:
|
||||
args.polygon_list = [args.polygon_list]
|
||||
except json.JSONDecodeError:
|
||||
args.polygon_list = [args.polygon_list]
|
||||
|
||||
return args
|
||||
|
||||
def print_status(message):
|
||||
"""
|
||||
输出状态信息到标准输出,用于 GUI 实时显示
|
||||
格式: STATUS: <message>
|
||||
"""
|
||||
print(f"STATUS:{message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def print_result(success, output_path="", error_message=""):
|
||||
"""
|
||||
输出最终结果到标准输出,用于 GUI 判断任务状态和获取结果
|
||||
格式: RESULT:True|<output_path>|
|
||||
格式: RESULT:False||<error_message>
|
||||
"""
|
||||
if success:
|
||||
print(f"RESULT:True|{output_path}|")
|
||||
else:
|
||||
# 在错误信息中替换换行符,避免干扰解析
|
||||
cleaned_error_message = error_message.replace('\n', ' ').replace('\r', '')
|
||||
print(f"RESULT:False||{cleaned_error_message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def log_arcpy_message(message):
|
||||
"""输出 ArcPy 产生的 geoprocessing 消息"""
|
||||
# 可以在这里进一步处理或过滤 ArcPy 消息
|
||||
if message.type == 'Message':
|
||||
print_status(f"ArcPy消息: {message.message}")
|
||||
elif message.type == 'Warning':
|
||||
print_status(f"ArcPy警告: {message.message}")
|
||||
elif message.type == 'Error':
|
||||
# 对于错误,也可以记录到标准错误
|
||||
print_status(f"ArcPy错误: {message.message}")
|
||||
sys.stderr.write(f"ArcPyError:{message.message}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
def area_statistics_by_field(layer, field_name="GRIDCODE", area_unit="HECTARES"):
|
||||
"""计算要素的面积统计信息"""
|
||||
area_stats = defaultdict(float, {1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00})
|
||||
try:
|
||||
# 检查图层是否有效
|
||||
if not layer or not layer.isFeatureLayer:
|
||||
raise ValueError("输入图层无效或不是要素图层")
|
||||
|
||||
# 检查字段是否存在
|
||||
if field_name not in [f.name for f in arcpy.ListFields(layer)]:
|
||||
raise ValueError(f"字段 '{field_name}' 在图层中不存在")
|
||||
|
||||
# 判断坐标系类型
|
||||
desc = arcpy.Describe(layer.dataSource)
|
||||
is_geographic = desc.spatialReference.type == "Geographic"
|
||||
if is_geographic:
|
||||
print_status("图层坐标系为地理坐标系,计算面积可能不准确")
|
||||
|
||||
# 创建游标遍历要素
|
||||
with arcpy.da.SearchCursor(layer, ["SHAPE@", field_name]) as cursor:
|
||||
for row in cursor:
|
||||
geometry = row[0]
|
||||
value = row[1]
|
||||
|
||||
# 计算面积
|
||||
area = geometry.getArea("GEODESIC" if is_geographic else "PLANAR", area_unit)
|
||||
|
||||
# 根据分类进行统计
|
||||
if value in area_stats:
|
||||
area_stats[value] += area
|
||||
else:
|
||||
area_stats[value] = area
|
||||
|
||||
return area_stats
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"计算面积统计信息失败: {str(e)}")
|
||||
|
||||
def update_text_elements(layout, config_data, county_name):
|
||||
"""更新布局中的文本元素"""
|
||||
|
||||
# 更新标题和其他文本元素
|
||||
for element_name, element_content in config_data.items():
|
||||
if element_name == "项目名称":
|
||||
new_text = element_content.replace('{区县占位符}', county_name)
|
||||
text_element = layout.listElements("TEXT_ELEMENT", element_name)[0]
|
||||
text_element.text = new_text
|
||||
if element_name == "分析方法":
|
||||
text_element = layout.listElements("TEXT_ELEMENT", element_name)[0]
|
||||
text_element.text = element_content
|
||||
|
||||
print_status(f"文本元素更新成功")
|
||||
|
||||
def update_data_source(layer, layer_name, new_data_source):
|
||||
"""更新图层数据源"""
|
||||
try:
|
||||
# 确保路径分隔符正确
|
||||
new_data_source = os.path.normpath(new_data_source)
|
||||
layer_path = os.path.join(new_data_source, layer_name)
|
||||
|
||||
# 检查数据源是否存在
|
||||
if not arcpy.Exists(layer_path):
|
||||
raise ValueError(f"数据源不存在: {layer_path}")
|
||||
|
||||
# 更新数据源
|
||||
cp = layer.connectionProperties
|
||||
cp["connection_info"]["database"] = new_data_source
|
||||
cp["dataset"] = layer_name
|
||||
cp["workspace_factory"] = "File Geodatabase"
|
||||
|
||||
layer.updateConnectionProperties(layer.connectionProperties, cp)
|
||||
|
||||
print_status(f"数据源更新成功")
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"更新数据源失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def update_symbol_system(layer, layer_name, symbol_path):
|
||||
"""
|
||||
更新符号系统
|
||||
1.ApplySymbologyFromLayer循环更新不起作用,还没找到原因
|
||||
2.替代方案: lf = arcpy.mp.LayerFile(symbol_file)
|
||||
layer.symbology = lf.listLayers()[0].symbology
|
||||
"""
|
||||
try:
|
||||
# 获取符号文件
|
||||
if os.path.isfile(symbol_path) and symbol_path.endswith('.lyr'):
|
||||
symbol_file = symbol_path
|
||||
|
||||
elif os.path.isdir(symbol_path):
|
||||
symbol_file = None
|
||||
for file in os.listdir(symbol_path):
|
||||
if (file.endswith('.lyr') or file.endswith('.lyrx')) and layer_name in file:
|
||||
symbol_file = os.path.join(symbol_path, file)
|
||||
break
|
||||
|
||||
if not symbol_file:
|
||||
raise FileNotFoundError(f"符号系统中未找到匹配 {layer_name} 的.lyr文件")
|
||||
else:
|
||||
raise FileNotFoundError("符号系统路径必须是有效的.lyr文件或文件夹")
|
||||
|
||||
# 更新符号系统
|
||||
lf = arcpy.mp.LayerFile(symbol_file) # type: ignore
|
||||
layer.symbology = lf.listLayers()[0].symbology
|
||||
# arcpy.management.ApplySymbologyFromLayer(layer, symbol_file, update_symbology="MAINTAIN")
|
||||
print_status(f"符号系统更新成功")
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"更新符号系统失败: {str(e)}")
|
||||
|
||||
# 添加注记
|
||||
def add_annotation(map_scale, map_obj, label_layer,layer_name, new_data_source):
|
||||
"""
|
||||
标注转注记
|
||||
标注存在缓存机制和layer.name绑定,需修改layer.name
|
||||
"""
|
||||
try:
|
||||
# 检查图层是否为空
|
||||
if label_layer is None:
|
||||
print_status(f"图层对象为空,无法进行标注转注记")
|
||||
return
|
||||
|
||||
# 转换为注记
|
||||
anno_layer_name = f"{layer_name}_GDBAnno"
|
||||
output_anno = os.path.join(new_data_source, anno_layer_name)
|
||||
|
||||
try:
|
||||
# 1. 从地图中移除所有相关图层
|
||||
for lyr in map_obj.listLayers("*GDBAnno"):
|
||||
map_obj.removeLayer(lyr)
|
||||
|
||||
label_layer.showLabels = False
|
||||
|
||||
# 将新生成的注记图层添加到地图中
|
||||
if arcpy.Exists(output_anno):
|
||||
new_anno_layer = map_obj.addDataFromPath(output_anno)
|
||||
map_obj.moveLayer(label_layer, new_anno_layer)
|
||||
print_status(f" 成功添加注记图层: {new_anno_layer.name}")
|
||||
else:
|
||||
raise FileNotFoundError(f"注记图层不存在: {output_anno}")
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"执行标注转注记工具时出错: {str(e)}")
|
||||
raise Exception(f"执行标注转注记工具时出错: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"标注转注记失败: {str(e)}")
|
||||
# 打印详细错误信息
|
||||
import traceback
|
||||
print_status(traceback.format_exc())
|
||||
|
||||
def label_to_annotation(map_scale, map_obj, label_layer,layer_name, new_data_source):
|
||||
"""
|
||||
标注转注记
|
||||
标注存在缓存机制和layer.name绑定,需修改layer.name
|
||||
"""
|
||||
try:
|
||||
# 检查图层是否为空
|
||||
if label_layer is None:
|
||||
print_status(f"图层对象为空,无法进行标注转注记")
|
||||
return
|
||||
|
||||
# 尝试开启标注
|
||||
if label_layer.supports("SHOWLABELS"):
|
||||
try:
|
||||
label_layer.showLabels = True
|
||||
except Exception as e:
|
||||
print_status(f"启用标注失败: {str(e)}")
|
||||
|
||||
# 转换为注记
|
||||
anno_layer_name = f"{layer_name}_GDBAnno"
|
||||
output_anno = os.path.join(new_data_source, anno_layer_name)
|
||||
|
||||
try:
|
||||
# 1. 从地图中移除所有相关图层
|
||||
for lyr in map_obj.listLayers("*GDBAnno"):
|
||||
map_obj.removeLayer(lyr)
|
||||
if arcpy.Exists(output_anno):
|
||||
# 2. 强制释放工作空间锁
|
||||
arcpy.management.ClearWorkspaceCache(new_data_source)
|
||||
|
||||
# 3. 带重试机制的要素类删除
|
||||
max_retries = 5
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
arcpy.management.Delete(output_anno)
|
||||
except arcpy.ExecuteError as e:
|
||||
if "000464" in str(e) and attempt < max_retries - 1:
|
||||
time.sleep((attempt + 1) * 3)
|
||||
arcpy.management.ClearWorkspaceCache(new_data_source)
|
||||
continue
|
||||
raise
|
||||
|
||||
# 4. 最终存在性检查
|
||||
if arcpy.Exists(output_anno):
|
||||
raise RuntimeError("无法彻底删除旧注记要素类")
|
||||
except Exception as e:
|
||||
raise Exception(f"清除旧注记失败,{str(e)}")
|
||||
|
||||
try:
|
||||
# 使用ConvertLabelsToAnnotation工具
|
||||
arcpy.cartography.ConvertLabelsToAnnotation(
|
||||
input_map=map_obj,
|
||||
conversion_scale=map_scale,
|
||||
output_geodatabase=new_data_source,
|
||||
anno_suffix="_GDBAnno",
|
||||
extent="DEFAULT",
|
||||
output_group_layer=f"{layer_name}GDBAnno",
|
||||
which_layers="SINGLE_LAYER",
|
||||
single_layer=label_layer
|
||||
)
|
||||
print_status(f"标注转注记成功")
|
||||
# 关闭原始标注显示(可选)
|
||||
label_layer.showLabels = False
|
||||
|
||||
# 将新生成的注记图层添加到地图中
|
||||
if arcpy.Exists(output_anno):
|
||||
new_anno_layer = map_obj.addDataFromPath(output_anno)
|
||||
map_obj.moveLayer(label_layer, new_anno_layer)
|
||||
print_status(f" 成功添加注记图层: {new_anno_layer.name}")
|
||||
else:
|
||||
raise FileNotFoundError(f"注记图层不存在: {output_anno}")
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"执行标注转注记工具时出错: {str(e)}")
|
||||
raise Exception(f"执行标注转注记工具时出错: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"标注转注记失败: {str(e)}")
|
||||
# 打印详细错误信息
|
||||
import traceback
|
||||
print_status(traceback.format_exc())
|
||||
|
||||
def export_map(params):
|
||||
"""导出地图"""
|
||||
start_time = time.time()
|
||||
aprx = None
|
||||
try:
|
||||
# 获取参数
|
||||
export_config = params['export_config']
|
||||
county_name = params['county_name']
|
||||
template_aprx_file = params['template_aprx_file']
|
||||
output_path = params['output_path']
|
||||
data_source_path = params['data_source_path']
|
||||
symbol_path = params['symbol_path']
|
||||
force_regenerate = params.get('force_regenerate', False) # 是否强制重新生成工程文件
|
||||
polygon_list = params['polygon_list']
|
||||
pic_path = params.get('pic_path', None)
|
||||
|
||||
# 结果记录
|
||||
success_count = 0
|
||||
|
||||
# 确保输出文件夹存在
|
||||
if not os.path.exists(output_path):
|
||||
os.makedirs(output_path)
|
||||
|
||||
# 创建工作空间
|
||||
workspace_path = os.path.join(output_path, f"{county_name}_工作空间")
|
||||
if not os.path.exists(workspace_path) or force_regenerate:
|
||||
if os.path.exists(workspace_path):
|
||||
print_status(f"强制重新生成,删除现有工作空间:{workspace_path}")
|
||||
# 删除旧工作空间(可能需要arcpy函数)
|
||||
|
||||
os.makedirs(workspace_path, exist_ok=True)
|
||||
print_status(f"创建工作空间:{workspace_path}")
|
||||
|
||||
# 工程模板不存在则返回
|
||||
if not arcpy.Exists(template_aprx_file):
|
||||
raise Exception("模板文件不存在")
|
||||
|
||||
# 设置工作空间
|
||||
orginal_workspace = arcpy.env.workspace
|
||||
arcpy.env.workspace = workspace_path
|
||||
|
||||
aprx = arcpy.mp.ArcGISProject(template_aprx_file) # type: ignore
|
||||
|
||||
# 获取指定布局
|
||||
target_layout = None
|
||||
layout_name = "属性图模板"
|
||||
target_layout = aprx.listLayouts(layout_name)[0]
|
||||
if not target_layout:
|
||||
raise Exception(f"未找到布局: {layout_name}")
|
||||
|
||||
# 获取当前地图比例尺
|
||||
map_frame = target_layout.listElements("MAPFRAME_ELEMENT", "地图框")[0]
|
||||
# if isinstance(map_frame, arcpy.mp.MapFrame):
|
||||
map_scale = map_frame.camera.scale
|
||||
# else:
|
||||
# raise Exception("地图框元素不存在")
|
||||
|
||||
# 获取指定地图
|
||||
target_map = None
|
||||
map_name = "土壤属性图层"
|
||||
target_map = aprx.listMaps(map_name)[0]
|
||||
if not target_map:
|
||||
raise Exception(f"未找到地图: {map_name}")
|
||||
|
||||
# 获取指定图层
|
||||
target_layer = None
|
||||
target_layer_name = "属性图"
|
||||
try:
|
||||
target_layer = target_map.listLayers(target_layer_name)[0]
|
||||
if not target_layer:
|
||||
raise Exception(f"未找到图层: {target_layer_name}")
|
||||
except Exception as e:
|
||||
raise Exception(f"错误信息: {str(e)}")
|
||||
|
||||
# 循环处理每个图层
|
||||
for layer_name in polygon_list:
|
||||
print_status(f"===== 开始处理要素: {layer_name} =====")
|
||||
|
||||
config_key = common_utils.get_config_key(layer_name)
|
||||
|
||||
try:
|
||||
# 更新图层名
|
||||
target_layer.name = config_key
|
||||
|
||||
# 获取图层配置
|
||||
single_export_config = export_config.get(config_key, {})
|
||||
if not single_export_config:
|
||||
print_status(f"警告: 未找到 {layer_name} 的配置信息, 将处理下一个")
|
||||
continue
|
||||
|
||||
# 生成输出文件名和路径
|
||||
temp_file_name = single_export_config['项目名称'].split('\n')[1]
|
||||
file_name = temp_file_name.replace('{区县占位符}', county_name)
|
||||
output_path = os.path.join(workspace_path, f"{file_name}.aprx")
|
||||
|
||||
# 检查工程文件是否已存在
|
||||
if os.path.exists(output_path) and not force_regenerate:
|
||||
print_status(f"工程文件已存在: {file_name}.aprx, 将直接使用")
|
||||
success_count += 1
|
||||
continue
|
||||
|
||||
# 检查数据源是否存在
|
||||
data_layer_path = os.path.join(data_source_path, layer_name)
|
||||
if not arcpy.Exists(data_layer_path):
|
||||
print_status(f"警告: 数据源不存在: {layer_name}要素,跳过此图层")
|
||||
continue
|
||||
|
||||
# 更新数据源
|
||||
if data_source_path:
|
||||
try:
|
||||
update_data_source(target_layer, layer_name, data_source_path)
|
||||
except Exception as e:
|
||||
print_status(f"更新数据源时出错: {str(e)},跳过此图层")
|
||||
continue
|
||||
|
||||
# 更新符号系统
|
||||
if os.path.exists(symbol_path):
|
||||
try:
|
||||
update_symbol_system(target_layer, config_key, symbol_path)
|
||||
except Exception as e:
|
||||
print_status(f"更新符号系统时出错: {str(e)},但将继续处理")
|
||||
|
||||
# 更新文本元素
|
||||
if target_layout:
|
||||
try:
|
||||
update_text_elements(target_layout, single_export_config, county_name)
|
||||
except Exception as e:
|
||||
print_status(f"更新文本元素时出错: {str(e)},但将继续处理")
|
||||
|
||||
# 替换图片
|
||||
try:
|
||||
for pic in os.listdir(pic_path):
|
||||
if pic.endswith(".jpg") and pic.startswith(config_key):
|
||||
pic_file = os.path.join(pic_path, pic)
|
||||
break
|
||||
pic_element = target_layout.listElements("PICTURE_ELEMENT", "统计图片")[0]
|
||||
# if isinstance(pic_element, arcpy.mp.PictureElement):
|
||||
pic_element.sourceImage = pic_file
|
||||
print_status(f"图片替换成功,{pic_file}")
|
||||
# else:
|
||||
# print_status(f"未找到统计图片元素,无法替换图片")
|
||||
except Exception as e:
|
||||
print_status(f"替换图片时出错: {str(e)},但将继续处理")
|
||||
|
||||
# 如果存在注记 直接添加;如果不存在,则尝试转注记
|
||||
try:
|
||||
add_annotation(map_scale, target_map, target_layer, config_key, data_source_path)
|
||||
except Exception as e:
|
||||
print_status(f"添加注记失败: {str(e)},但将继续处理")
|
||||
|
||||
# 标注转注记
|
||||
# try:
|
||||
# label_to_annotation(map_scale, target_map, target_layer, config_key, data_source_path)
|
||||
# except Exception as e:
|
||||
# print_status(f"标注转注记失败: {str(e)},但将继续处理")
|
||||
|
||||
# 保存工程文件
|
||||
aprx.saveACopy(output_path)
|
||||
print_status(f"成功保存工程文件: {output_path}")
|
||||
|
||||
# 记录结果
|
||||
success_count += 1
|
||||
|
||||
arcpy.management.ClearWorkspaceCache()
|
||||
except Exception as e:
|
||||
print_status(f"处理图层 {config_key} 时出错: {str(e)}")
|
||||
continue
|
||||
|
||||
print_status(f"===== 导出处理完成 =====")
|
||||
# 结束时间
|
||||
end_time = time.time()
|
||||
elapsed_time = end_time - start_time
|
||||
print_status(f"共处理 {len(polygon_list)} 个图层,成功 {success_count} 个,耗时:{elapsed_time:.2f}秒")
|
||||
|
||||
return success_count
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"导出过程中出错:{str(e)}")
|
||||
import traceback
|
||||
print_status(traceback.format_exc())
|
||||
return 0
|
||||
finally:
|
||||
# 确保释放资源
|
||||
arcpy.management.ClearWorkspaceCache()
|
||||
arcpy.env.workspace = orginal_workspace
|
||||
if 'target_layer' in locals():
|
||||
del target_layer
|
||||
if 'target_map' in locals():
|
||||
del target_map
|
||||
if 'target_layout' in locals():
|
||||
del target_layout
|
||||
if 'aprx' in locals():
|
||||
del aprx
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
try:
|
||||
# args1 = {
|
||||
# 'config_file': 'D:/arcpystudy/ArcGisPro/tools/ui/raster_test_config.json',
|
||||
# 'county_name': '澜沧拉祜族自治县',
|
||||
# 'polygon_list': ['AB_processed.shp'],
|
||||
# 'template_aprx_file': r'D:/工作/ArcGisPro/澜沧县模板/澜沧县模板/澜沧县模板.aprx',
|
||||
# 'output_path': 'D:/工作/三普成果编制/澜沧2/成果图',
|
||||
# 'data_source_path': 'D:/工作/三普成果编制/澜沧2/@矢量数据',
|
||||
# 'symbol_path': 'D:/工作/ArcGisPro/澜沧县模板/2.配色/华南配色',
|
||||
# 'force_regenerate': True,
|
||||
# 'pic_path': 'D:/工作/三普成果编制/澜沧2/@基础数据/面积统计表格'
|
||||
# }
|
||||
# # 将字典转为对象
|
||||
# args = argparse.Namespace(**args1)
|
||||
|
||||
|
||||
# 解析命令行参数
|
||||
args = parse_arguments()
|
||||
|
||||
# 执行导出
|
||||
with open(args.config_file,'r',encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
params = {
|
||||
"export_config": config["export_config"],
|
||||
"county_name": args.county_name,
|
||||
"polygon_list":args.polygon_list,
|
||||
"template_aprx_file": args.template_aprx_file,
|
||||
"output_path": args.output_path,
|
||||
"data_source_path": args.data_source_path,
|
||||
"symbol_path": args.symbol_path,
|
||||
"force_regenerate":args.force_regenerate,
|
||||
"pic_path":args.pic_path
|
||||
}
|
||||
|
||||
success_count = export_map(params)
|
||||
|
||||
# 返回结果
|
||||
if success_count > 0:
|
||||
return 0
|
||||
else:
|
||||
print_status("没有导出任何图层")
|
||||
return 1
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"错误:{str(e)}")
|
||||
import traceback
|
||||
print_status(traceback.format_exc())
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
475
tools/core/raster_to_polygon.py
Normal file
475
tools/core/raster_to_polygon.py
Normal file
@@ -0,0 +1,475 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
栅格处理模块: 提供栅格重分类、栅格转矢量和小面积图斑消除功能
|
||||
|
||||
设计用于通过 QProcess 调用,接收命令行参数,并通过标准输出返回结果和状态。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import arcpy
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
try:
|
||||
from utils import common_utils
|
||||
except ImportError:
|
||||
print("错误: 未找到 utils 模块。请确保 utils.py 文件存在或已添加到 PYTHONPATH。")
|
||||
sys.exit(1)
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description='处理栅格数据:重分类、转矢量、消除小图斑')
|
||||
parser.add_argument('--input_raster', required=True, help='输入栅格文件路径')
|
||||
parser.add_argument('--settings_path', required=True, help='配置文件路径')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.settings_path:
|
||||
with open(args.settings_path, 'r', encoding="utf-8") as settings_file:
|
||||
settings = json.load(settings_file)
|
||||
raster_settings = settings.get("raster_settings", {})
|
||||
|
||||
if raster_settings:
|
||||
standards_dict_path = raster_settings.get("config_file_path", "")
|
||||
|
||||
with open(standards_dict_path, 'r', encoding="utf-8") as standards_file:
|
||||
standards_dict = json.load(standards_file)
|
||||
raster_name = Path(args.input_raster).stem
|
||||
remap_table = common_utils.create_remap_table(standards_dict['export_config'][raster_name]["标准等级"])
|
||||
raster_settings["remap_table"] = remap_table
|
||||
raster_settings["input_raster"] = args.input_raster
|
||||
else:
|
||||
print("错误: 未找到有效配置文件")
|
||||
sys.exit(1)
|
||||
|
||||
return raster_settings
|
||||
|
||||
def print_status(message):
|
||||
"""
|
||||
输出状态信息到标准输出,用于 GUI 实时显示
|
||||
格式: STATUS: <message>
|
||||
"""
|
||||
print(f"STATUS:{message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def print_result(success, output_path="", error_message=""):
|
||||
"""
|
||||
输出最终结果到标准输出,用于 GUI 判断任务状态和获取结果
|
||||
格式: RESULT:True|<output_path>|
|
||||
格式: RESULT:False||<error_message>
|
||||
"""
|
||||
if success:
|
||||
print(f"RESULT:True|{output_path}|")
|
||||
else:
|
||||
# 在错误信息中替换换行符,避免干扰解析
|
||||
cleaned_error_message = error_message.replace('\n', ' ').replace('\r', '')
|
||||
print(f"RESULT:False||{cleaned_error_message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def log_arcpy_message(message):
|
||||
"""输出 ArcPy 产生的 geoprocessing 消息"""
|
||||
# 可以在这里进一步处理或过滤 ArcPy 消息
|
||||
if message.type == 'Message':
|
||||
print_status(f"ArcPy消息: {message.message}")
|
||||
elif message.type == 'Warning':
|
||||
print_status(f"ArcPy警告: {message.message}")
|
||||
elif message.type == 'Error':
|
||||
# 对于错误,也可以记录到标准错误
|
||||
print_status(f"ArcPy错误: {message.message}")
|
||||
sys.stderr.write(f"ArcPyError:{message.message}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
# --- 核心处理函数 ---
|
||||
|
||||
def reclassify_raster(input_raster, remap_table, temp_files_to_clean):
|
||||
"""
|
||||
根据重分类映射表重分类栅格数据,结果存储在内存中。
|
||||
|
||||
参数:
|
||||
input_raster (str): 输入栅格路径
|
||||
remap_table (list): 重分类映射表,格式为 [[from, to, new_value], ...]
|
||||
temp_files_to_clean (list): 用于收集临时文件路径的列表
|
||||
|
||||
返回:
|
||||
str: 内存栅格路径
|
||||
"""
|
||||
print_status(f"开始重分类栅格: {input_raster}")
|
||||
try:
|
||||
# 确保remap_table中的new_value是整数,并确保格式正确
|
||||
corrected_remap_table = []
|
||||
for item in remap_table:
|
||||
try:
|
||||
if len(item) == 3:
|
||||
from_value, to_value, new_value = item
|
||||
# 尝试转换为浮点数以处理范围值
|
||||
try:
|
||||
from_value = float(from_value)
|
||||
except (ValueError, TypeError):
|
||||
print_status(f"警告: 跳过无效的重分类项起始值: {item[0]}")
|
||||
continue
|
||||
|
||||
if isinstance(to_value, (int, float)) and to_value == float('inf'):
|
||||
to_value = 10000 # 用一个很大的数值代替
|
||||
elif isinstance(to_value, (int, float)) and to_value == float('-inf'):
|
||||
to_value = -10000 # 用一个很小的数值代替
|
||||
else:
|
||||
try:
|
||||
to_value = float(to_value)
|
||||
except (ValueError, TypeError):
|
||||
print_status(f"警告: 跳过无效的重分类项结束值: {item[1]}")
|
||||
continue
|
||||
|
||||
try:
|
||||
new_value = int(new_value)
|
||||
except (ValueError, TypeError):
|
||||
print_status(f"警告: 跳过无效的重分类项新值: {item[2]}")
|
||||
continue
|
||||
|
||||
# 验证范围的有效性
|
||||
if from_value > to_value:
|
||||
print_status(f"警告: 跳过无效范围: {from_value} > {to_value}")
|
||||
continue
|
||||
|
||||
corrected_remap_table.append([from_value, to_value, new_value])
|
||||
else:
|
||||
print_status(f"警告: 跳过无效的重分类项格式: {item}")
|
||||
except Exception as e: # 捕获更广泛的异常
|
||||
print_status(f"处理重分类项 {item} 时出错: {e}")
|
||||
|
||||
if not corrected_remap_table:
|
||||
raise ValueError("重分类映射表为空或无效,无法执行重分类。")
|
||||
|
||||
# 创建重分类对象
|
||||
remap = arcpy.sa.RemapRange(corrected_remap_table)
|
||||
|
||||
# 执行重分类到内存
|
||||
# 检查输入栅格是否存在
|
||||
if not arcpy.Exists(input_raster):
|
||||
raise FileNotFoundError(f"输入栅格不存在: {input_raster}")
|
||||
|
||||
# 设置Snap Raster和Mask环境,如果需要的话
|
||||
# arcpy.env.snapRaster = input_raster
|
||||
# arcpy.env.mask = input_raster # 如果需要按栅格范围裁剪
|
||||
|
||||
out_raster = arcpy.sa.Reclassify(input_raster, "VALUE", remap, "DATA")
|
||||
|
||||
# 保存到内存工作空间
|
||||
temp_reclass_raster_mem = f"in_memory/reclass_int_{uuid.uuid4().hex[:8]}"
|
||||
# 在返回之前保存到内存,并添加到清理列表
|
||||
out_raster.save(temp_reclass_raster_mem)
|
||||
temp_files_to_clean.append(temp_reclass_raster_mem)
|
||||
print_status(f"重分类已完成: {temp_reclass_raster_mem}")
|
||||
|
||||
return temp_reclass_raster_mem
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"重分类过程出错: {str(e)}")
|
||||
# 记录ArcPy消息
|
||||
for msg in arcpy.GetMessages(2).split('\n'):
|
||||
if msg:
|
||||
print_status(f"ArcPy重分类错误详情: {msg}")
|
||||
raise # 重新抛出异常
|
||||
|
||||
def eliminate_small_polygons(input_polygon, output_polygon, min_area, area_unit, temp_files_to_clean, current_iter=1, max_iterations=8, start_area=1000):
|
||||
"""
|
||||
递归消除小于指定面积的多边形,直至没有小图斑或达到最大迭代次数。
|
||||
|
||||
参数:
|
||||
input_polygon (str): 输入多边形要素类路径
|
||||
output_polygon (str): 最终输出多边形要素类路径
|
||||
min_area (float): 最小面积阈值
|
||||
area_unit (str): 面积单位
|
||||
max_iterations (int): 最大递归次数
|
||||
current_iter (int): 当前迭代次数
|
||||
temp_files_to_clean (list): 用于收集临时文件路径的列表
|
||||
|
||||
返回:
|
||||
str: 最终输出多边形要素类路径
|
||||
"""
|
||||
# print_status(f"开始第 {current_iter} 次消除小图斑...")
|
||||
|
||||
# 检查输入多边形是否存在
|
||||
if not arcpy.Exists(input_polygon):
|
||||
raise FileNotFoundError(f"输入多边形不存在: {input_polygon}")
|
||||
|
||||
try:
|
||||
# 如果是第一次迭代,检查输出文件是否已存在,并删除
|
||||
if current_iter == 1 and arcpy.Exists(output_polygon):
|
||||
try:
|
||||
arcpy.management.Delete(output_polygon)
|
||||
print_status(f"已删除现有输出文件: {output_polygon}")
|
||||
except Exception as delete_err:
|
||||
print_status(f"警告: 无法删除现有输出文件 {output_polygon}: {str(delete_err)}")
|
||||
|
||||
#==================增加逐面积消除================
|
||||
# 计算当前迭代的面积阈值
|
||||
current_threshold = start_area * (2 ** (current_iter - 1))
|
||||
|
||||
# 如果当前阈值超过最终阈值,使用最终阈值
|
||||
if current_threshold > min_area:
|
||||
current_threshold = min_area
|
||||
#==================增加逐面积消除================
|
||||
|
||||
# 创建临时内存图层以便选择
|
||||
temp_layer = f"input_lyr_{uuid.uuid4().hex[:8]}"
|
||||
arcpy.management.MakeFeatureLayer(input_polygon, temp_layer)[0]
|
||||
# print_status(f"已创建临时图层: {temp_layer_name}")
|
||||
|
||||
|
||||
# 添加或检查面积字段
|
||||
area_field_name = "TEMP_AREA"
|
||||
fields = [f.name for f in arcpy.ListFields(temp_layer)]
|
||||
if area_field_name not in fields:
|
||||
arcpy.management.AddField(temp_layer, area_field_name, "DOUBLE")
|
||||
# print_status(f"已添加临时面积字段: {area_field_name}")
|
||||
|
||||
# 计算面积
|
||||
arcpy.management.CalculateGeometryAttributes(
|
||||
temp_layer,
|
||||
[[area_field_name, "AREA"]],
|
||||
None,
|
||||
area_unit,
|
||||
None,
|
||||
"SAME_AS_INPUT"
|
||||
)
|
||||
# print_status("面积计算完成.")
|
||||
|
||||
|
||||
# 选择小于阈值的多边形
|
||||
selection_query = f"{arcpy.AddFieldDelimiters(temp_layer, area_field_name)} < {current_threshold}"
|
||||
# print_status(f"选择查询: {selection_query}")
|
||||
arcpy.management.SelectLayerByAttribute(temp_layer, "NEW_SELECTION", selection_query)
|
||||
|
||||
# 检查选中的要素数量
|
||||
count = int(arcpy.management.GetCount(temp_layer).getOutput(0))
|
||||
# print_status(f"发现 {count} 个小于 {min_area} {area_unit} 的小图斑.")
|
||||
|
||||
# 判断是否停止迭代
|
||||
if count == 0 or current_iter >= max_iterations:
|
||||
# print_status(f"复制最终结果到: {output_polygon}")
|
||||
arcpy.management.CopyFeatures(input_polygon, output_polygon)
|
||||
|
||||
# 删除临时面积字段 (可选,如果需要保持输出干净)
|
||||
# if area_field_name in fields: # 仅在字段是我们添加的情况下删除
|
||||
# arcpy.management.DeleteField(output_polygon, area_field_name)
|
||||
|
||||
return output_polygon
|
||||
|
||||
else:
|
||||
# print_status(f"执行消除操作...")
|
||||
temp_eliminate_output = f"in_memory/temp_eliminate_{uuid.uuid4().hex[:8]}"
|
||||
temp_files_to_clean.append(temp_eliminate_output) # 添加到临时文件列表
|
||||
|
||||
# 执行消除操作
|
||||
arcpy.Eliminate_management(temp_layer, temp_eliminate_output, "LENGTH")
|
||||
|
||||
# print_status(f"执行融合操作...")
|
||||
temp_dissolve_output = f"in_memory/dissolve_polygons_{uuid.uuid4().hex[:8]}"
|
||||
temp_files_to_clean.append(temp_dissolve_output)
|
||||
|
||||
# 添加融合字段
|
||||
dissolve_fields = ["gridcode"]
|
||||
|
||||
arcpy.management.Dissolve(temp_eliminate_output, temp_dissolve_output, dissolve_fields, multi_part="SINGLE_PART")
|
||||
# print_status(f"融合结果已保存到内存: {temp_dissolve_output}")
|
||||
|
||||
|
||||
# 递归调用,使用融合后的结果作为下一次迭代的输入
|
||||
return eliminate_small_polygons(
|
||||
temp_dissolve_output, # 下一次迭代使用临时融合输出作为输入
|
||||
output_polygon,
|
||||
min_area,
|
||||
area_unit,
|
||||
temp_files_to_clean,
|
||||
current_iter + 1,
|
||||
max_iterations,
|
||||
start_area
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print_status(f"消除小面积多边形过程出错 (迭代 {current_iter}): {str(e)}")
|
||||
# 记录ArcPy消息
|
||||
for msg in arcpy.GetMessages(2).split('\n'):
|
||||
if msg:
|
||||
print_status(f"ArcPy消除错误详情: {msg}")
|
||||
raise # 重新抛出异常
|
||||
finally:
|
||||
# 确保删除临时图层,即使出错
|
||||
if 'temp_layer' in locals() and arcpy.Exists(temp_layer):
|
||||
try:
|
||||
arcpy.management.Delete(temp_layer)
|
||||
# print_status(f"已删除临时图层: {temp_layer.name}")
|
||||
except Exception as delete_layer_err:
|
||||
print_status(f"警告: 无法删除临时图层 {temp_layer}: {str(delete_layer_err)}")
|
||||
|
||||
|
||||
# --- 主处理逻辑 ---
|
||||
|
||||
def main():
|
||||
"""主函数:解析参数,执行处理流程,输出结果和状态"""
|
||||
|
||||
|
||||
params = None
|
||||
temp_files_to_clean = []
|
||||
original_workspace = None
|
||||
|
||||
try:
|
||||
# 1. 解析参数
|
||||
params = parse_arguments()
|
||||
|
||||
input_raster = params["input_raster"]
|
||||
raster_name = Path(input_raster).stem
|
||||
input_folder = params["input_folder"]
|
||||
output_folder = params["batch_output_folder"]
|
||||
clip_features = params["clip_features"]
|
||||
clip_enabled = params["clip_enabled"]
|
||||
remap_table = params["remap_table"]
|
||||
min_area = params["min_area"]
|
||||
area_unit = params["area_unit"]
|
||||
simplify = params["simplify"]
|
||||
|
||||
# print_status(f"解析参数完成: {params}")
|
||||
|
||||
# 2. 设置工作空间和环境
|
||||
original_workspace = arcpy.env.workspace
|
||||
arcpy.env.workspace = input_folder
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print_status(f"ArcPy 工作空间设置为: {arcpy.env.workspace}")
|
||||
|
||||
# 3. 校验输入/输出路径
|
||||
if not arcpy.Exists(input_raster):
|
||||
raise FileNotFoundError(f"输入栅格文件不存在: {input_raster}")
|
||||
|
||||
# 创建输出文件夹
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
print_status(f"已创建输出文件夹: {output_folder}")
|
||||
|
||||
# 创建面积统计用文件夹
|
||||
disk_output_path = os.path.join(output_folder, "面积统计用栅格面")
|
||||
time.sleep(random.random())
|
||||
if not os.path.exists(disk_output_path):
|
||||
os.makedirs(disk_output_path)
|
||||
print_status(f"已创建面积统计文件夹: {disk_output_path}")
|
||||
|
||||
if clip_enabled and not arcpy.Exists(clip_features):
|
||||
raise FileNotFoundError(f"裁剪要素类不存在: {clip_features}")
|
||||
|
||||
# 4. 定义中间和最终输出路径
|
||||
# 根据输出文件夹类型确定文件扩展名和命名方式
|
||||
output_is_workspace = common_utils.get_data_type(output_folder) in ["Workspace", "FeatureDataset", "Geodatabase"]
|
||||
|
||||
if output_is_workspace:
|
||||
# 输出到地理数据库或要素数据集
|
||||
final_output_path = os.path.join(os.path.dirname(output_folder), f"{raster_name}_eliminate.shp")
|
||||
# final_output_path = f"{raster_name}_processed"
|
||||
|
||||
else:
|
||||
# 输出到文件夹 (例如 Shapefile)
|
||||
final_output_path = os.path.join(output_folder, f"{raster_name}_eliminate.shp")
|
||||
# final_output_path = f"{raster_name}_processed.shp"
|
||||
|
||||
|
||||
# 5. 执行重分类 (如果 remap_table 存在)
|
||||
if remap_table:
|
||||
reclassed_raster = reclassify_raster(input_raster, remap_table, temp_files_to_clean)
|
||||
arcpy.Raster(reclassed_raster).save(os.path.join(output_folder,f"{Path(input_raster).stem}分级后.tif"))
|
||||
|
||||
|
||||
# 6. 栅格转多边形
|
||||
print_status(f"开始栅格转多边形: {reclassed_raster}")
|
||||
temp_polygon_output = os.path.join("in_memory", f"raster_to_polygon_{uuid.uuid4().hex[:8]}")
|
||||
temp_files_to_clean.append(temp_polygon_output)
|
||||
|
||||
simplify_value = "SIMPLIFY" if simplify else "NO_SIMPLIFY"
|
||||
arcpy.conversion.RasterToPolygon(reclassed_raster, temp_polygon_output, simplify_value, "VALUE")
|
||||
print_status(f"栅格转多边形完成,结果在内存: {temp_polygon_output}")
|
||||
|
||||
# 将内存中的要素类保存到硬盘
|
||||
disk_output_polygon = os.path.join(disk_output_path, f"{raster_name}_reclassed_polygon.shp")
|
||||
arcpy.CopyFeatures_management(temp_polygon_output, disk_output_polygon)
|
||||
print_status(f"已将重分类转面结果保存到硬盘: {disk_output_polygon}")
|
||||
|
||||
|
||||
# 7. 裁剪 (如果 clip_features 存在)
|
||||
current_polygon_source = temp_polygon_output
|
||||
if clip_enabled:
|
||||
print_status(f"开始裁剪要素类: {current_polygon_source} using {clip_features}")
|
||||
temp_cliped_polygon_output = os.path.join("in_memory", f"cliped_{uuid.uuid4().hex[:8]}")
|
||||
temp_files_to_clean.append(temp_cliped_polygon_output)
|
||||
arcpy.analysis.Clip(current_polygon_source, clip_features, temp_cliped_polygon_output)
|
||||
current_polygon_source = temp_cliped_polygon_output
|
||||
print_status(f"裁剪完成,结果在内存: {current_polygon_source}")
|
||||
|
||||
# 多部件至单部件 (通常在裁剪后进行,确保每个要素都是独立的单部件)
|
||||
print_status(f"开始多部件至单部件转换: {current_polygon_source}")
|
||||
temp_multi_to_single_output = os.path.join("in_memory", f"single_{uuid.uuid4().hex[:8]}")
|
||||
temp_files_to_clean.append(temp_multi_to_single_output)
|
||||
arcpy.management.MultipartToSinglepart(current_polygon_source, temp_multi_to_single_output)
|
||||
current_polygon_source = temp_multi_to_single_output
|
||||
print_status(f"多部件至单部件转换完成,结果在内存: {current_polygon_source}")
|
||||
|
||||
|
||||
# 8. 消除小面积图斑
|
||||
print_status(f"开始消除小面积图斑: {current_polygon_source} (阈值: {min_area} {area_unit})")
|
||||
eliminate_small_polygons(
|
||||
current_polygon_source,
|
||||
final_output_path, # 直接传递最终输出路径
|
||||
min_area,
|
||||
area_unit,
|
||||
temp_files_to_clean # 传递临时文件列表
|
||||
)
|
||||
print_status("消除小面积图斑完成.")
|
||||
|
||||
# 9. 最终清理和输出结果
|
||||
print_status("处理流程全部完成.")
|
||||
# 清理在内存或临时位置生成的中间文件
|
||||
temp_files_processor.clean_up_temp_files(temp_files=temp_files_to_clean, workspace=original_workspace)
|
||||
|
||||
# 验证最终输出文件是否存在
|
||||
if arcpy.Exists(final_output_path):
|
||||
print_result(True, final_output_path, "")
|
||||
else:
|
||||
raise Exception(f"处理完成,但最终输出文件 {final_output_path} 不存在。")
|
||||
|
||||
|
||||
except FileNotFoundError as fnf_e:
|
||||
error_msg = f"文件不存在错误: {str(fnf_e)}"
|
||||
print_status(error_msg)
|
||||
print_result(False, "", error_msg)
|
||||
except ValueError as ve:
|
||||
error_msg = f"参数错误或数据校验失败: {str(ve)}"
|
||||
print_status(error_msg)
|
||||
print_result(False, "", error_msg)
|
||||
except arcpy.ExecuteError:
|
||||
# 捕获 ArcPy 执行错误
|
||||
error_msg = f"ArcPy 执行错误: {arcpy.GetMessages(2)}"
|
||||
print_status(error_msg)
|
||||
sys.stderr.write(f"ArcPyExecuteError:{arcpy.GetMessages(2)}\n") # 记录到标准错误
|
||||
print_result(False, "", error_msg)
|
||||
except Exception as e:
|
||||
# 捕获其他未预料的错误
|
||||
error_msg = f"发生未预料的错误: {str(e)}\n{traceback.format_exc()}"
|
||||
print_status(error_msg)
|
||||
sys.stderr.write(f"UnexpectedError:{error_msg}\n") # 记录到标准错误
|
||||
print_result(False, "", error_msg)
|
||||
|
||||
finally:
|
||||
# 确保在任何情况下都尝试清理(尽管在 except 块中也调用了)
|
||||
# 这里的调用是最后的保障,如果 except 块中的清理失败了
|
||||
print_status("脚本结束,执行最终清理...")
|
||||
temp_files_processor.clean_up_temp_files(temp_files=temp_files_to_clean, workspace=original_workspace)
|
||||
print_status("最终清理完成.")
|
||||
sys.exit(0) # 正常退出脚本
|
||||
|
||||
if __name__ == "__main__":
|
||||
print_status("脚本开始执行...")
|
||||
main()
|
||||
392
tools/core/soil_prop_stats/B1_TRZD12土壤属性分级分布.py
Normal file
392
tools/core/soil_prop_stats/B1_TRZD12土壤属性分级分布.py
Normal file
@@ -0,0 +1,392 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Border, Side, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from tools.core.utils import arcgis_utils, common_utils
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
|
||||
trzd5_order = ["砂质", "砂壤质", "壤质", "黏壤质", "黏质"]
|
||||
trzd12_order = ["砂土及壤质砂土", "砂质壤土", "壤土", "粉砂质壤土", "砂质黏壤土", "黏壤土", "粉砂质黏壤土", "砂质黏土", "壤质黏土", "粉砂质黏土", "黏土", "重黏土"]
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
# 判断单元格类型
|
||||
def get_merge_type(merged_range):
|
||||
"""
|
||||
判断合并类型
|
||||
返回: 'row'(行合并), 'column'(列合并), 'both'(行列合并)或 None(不是合并单元格)
|
||||
"""
|
||||
if not merged_range:
|
||||
return None
|
||||
|
||||
min_row, max_row = merged_range.min_row, merged_range.max_row
|
||||
min_col, max_col = merged_range.min_col, merged_range.max_col
|
||||
|
||||
if max_row > min_row and max_col > min_col:
|
||||
return 'both' # 同时跨行和跨列
|
||||
elif max_row > min_row:
|
||||
return 'row' # 行合并(垂直合并)
|
||||
elif max_col > min_col:
|
||||
return 'column' # 列合并(水平合并)
|
||||
else:
|
||||
return None # 实际上不是合并单元格
|
||||
|
||||
# 计算属性等级
|
||||
def get_prop_level(prop_level):
|
||||
"""根据输入值判断 返回等级"""
|
||||
if pd.isna(prop_level) or str(prop_level) == "0":
|
||||
return "-"
|
||||
# 请根据您的实际分级标准调整这里的阈值
|
||||
if str(prop_level) == "8" or prop_level == '砂土及壤质砂土':
|
||||
return "砂质"
|
||||
elif str(prop_level) == "11" or prop_level == '砂质壤土':
|
||||
return "砂壤质"
|
||||
elif str(prop_level) in ["6","3"] or prop_level in ['粉砂质壤土', '壤土']:
|
||||
return "壤质"
|
||||
elif str(prop_level) in ["1","4","9"] or prop_level in ['粉砂质年壤土', '黏壤土', '砂质黏壤土']:
|
||||
return "黏壤质"
|
||||
elif str(prop_level) in ["2","5","7","10","12"] or prop_level in ['粉砂质黏土', '黏土', '壤质黏土', '砂质黏土', '重黏土']:
|
||||
return "黏质"
|
||||
else:
|
||||
return "-"
|
||||
|
||||
# 等级计算
|
||||
def process_soil_dataframe(df:pd.DataFrame, level_config, target_prop):
|
||||
"""
|
||||
处理土壤数据DataFrame,添加分级列
|
||||
"""
|
||||
result_df = df.copy()
|
||||
|
||||
if level_config and target_prop in df.columns:
|
||||
grade_standards = level_config["标准等级"]
|
||||
grade_column = "GRIDCODE"
|
||||
|
||||
# 使用向量化方法(性能更好)
|
||||
result_df[grade_column] = common_utils.vectorized_grade_assignment(
|
||||
df[target_prop].values, grade_standards
|
||||
)
|
||||
|
||||
# 统计分级结果
|
||||
result_df['YJDL'] = result_df['TDLYLX'].str[:2]
|
||||
|
||||
return result_df
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table1(gdb_path, soil_prop_feature_name, df_origin_area, target_areas_dict,xzqmc,is_by_xzq, prop_config=None):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_feature_name
|
||||
sample_table_path = os.path.join(gdb_path, soil_prop_feature_name)
|
||||
sample_fields = ['TDLYLX', field_name]
|
||||
df_samples = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(sample_table_path, sample_fields, skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, [field_name])
|
||||
|
||||
processed_df = process_soil_dataframe(df_samples, prop_config, field_name) # 返回具有属性分级的列
|
||||
processed_df['GRIDCODE'] = processed_df['GRIDCODE'].astype('int')
|
||||
processed_df['属性分级'] = processed_df['GRIDCODE'].apply(get_prop_level)
|
||||
|
||||
# 计算全部样点均值、中位值、范围
|
||||
processed_df[field_name] = processed_df[field_name].astype('float')
|
||||
|
||||
# ===处理样点数据,计算 各分级样点数
|
||||
df_sample_means = processed_df.groupby(['属性分级','GRIDCODE']).size().reset_index(name='样点数')
|
||||
df_sample_means['样点数占比'] = df_sample_means['样点数'] / df_sample_means['样点数'].sum() * 100
|
||||
print("样点数计算完成。")
|
||||
|
||||
|
||||
# ==处理制图数据,获各等级制图面积
|
||||
# print(df_origin_area)
|
||||
df_origin_area['YJDL'] = df_origin_area['YJDL_EJDL'].str.split('_').str[0]
|
||||
df_map_data = df_origin_area.groupby(["XZQMC","YJDL", "GRIDCODE"]).agg({"temp_area": "sum"}).reset_index()
|
||||
# print(df_map_data)
|
||||
|
||||
try:
|
||||
if is_by_xzq:
|
||||
df_map_data['adjusted_area'] = df_map_data['temp_area']
|
||||
df_map_data['adjustment_factor'] = 1.0
|
||||
|
||||
# 获取所有存在的行政区和地类
|
||||
existing_districts = df_map_data['XZQMC'].unique()
|
||||
|
||||
# 检查目标字典中的行政区是否存在
|
||||
missing_districts = []
|
||||
tt = [td for td in target_areas_dict.keys()]
|
||||
for ed in existing_districts:
|
||||
if ed not in tt:
|
||||
missing_districts.append(ed)
|
||||
|
||||
# 如果有行政区不存在,返回原始数据并提示
|
||||
if missing_districts:
|
||||
print(f"警告:平差数据中不存在行政区: {missing_districts},未进行平差")
|
||||
|
||||
# 计算每个行政区每个地类的原始总面积
|
||||
original_totals = df_map_data.groupby(['XZQMC', 'YJDL'])['temp_area'].sum()
|
||||
|
||||
# 对每个行政区的每个地类进行平差
|
||||
for xzqmc, landuse_targets in target_areas_dict.items():
|
||||
for yjdl, target_area in landuse_targets.items():
|
||||
# 检查该行政区是否有此地类数据
|
||||
if (xzqmc, yjdl) in original_totals.index and original_totals[(xzqmc, yjdl)] > 0:
|
||||
adjustment_factor = target_area / original_totals[(xzqmc, yjdl)]
|
||||
|
||||
# 应用平差系数
|
||||
mask = (df_map_data['XZQMC'] == xzqmc) & (df_map_data['YJDL'] == yjdl)
|
||||
df_map_data.loc[mask, 'temp_area'] = df_map_data.loc[mask, 'temp_area'] * adjustment_factor
|
||||
df_map_data.loc[mask, 'adjustment_factor'] = adjustment_factor
|
||||
|
||||
# print(f"{xzqmc} - 地类 {yjdl}: 平差系数 = {adjustment_factor:.6f}")
|
||||
else:
|
||||
# 用df_target_area按YJDL进行平差计算
|
||||
original_totals = df_map_data.groupby('YJDL')['temp_area'].sum().to_dict()
|
||||
# 对每个地类进行平差
|
||||
target_area_dict = target_areas_dict.get(xzqmc,"")
|
||||
# print(target_areas_dict)
|
||||
for yjdl, target_area in target_area_dict.items():
|
||||
if (yjdl in original_totals and original_totals[yjdl] > 0) or target_area > 0:
|
||||
adjustment_factor = target_area / original_totals[yjdl]
|
||||
|
||||
# 应用平差系数
|
||||
mask = df_map_data['YJDL'] == yjdl
|
||||
df_map_data.loc[mask, 'temp_area'] = df_map_data.loc[mask, 'temp_area'] * adjustment_factor
|
||||
df_map_data.loc[mask, 'adjustment_factor'] = adjustment_factor
|
||||
|
||||
# print(f"地类 {yjdl}: 平差系数 = {adjustment_factor:.6f}")
|
||||
except Exception as e:
|
||||
print(f"平差处理失败: {e}")
|
||||
|
||||
# print(df_map_data)
|
||||
df_map_data['面积_亩'] = df_map_data['temp_area']
|
||||
|
||||
df_map_data['属性分级'] = df_map_data['GRIDCODE'].apply(get_prop_level)
|
||||
df_map_areas = df_map_data.groupby(['属性分级','GRIDCODE'])['面积_亩'].sum().reset_index(name='制图面积')
|
||||
# 面积平差
|
||||
df_map_areas['制图面积_平差后'] = df_map_areas['制图面积']
|
||||
# ===计算面积占比
|
||||
df_map_areas['面积占比'] = df_map_areas['制图面积_平差后'] / df_map_areas['制图面积_平差后'].sum() * 100
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['属性分级','GRIDCODE']],
|
||||
df_map_areas[['属性分级','GRIDCODE']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['属性分级','GRIDCODE'], how='left')
|
||||
df_final = pd.merge(df_final, df_map_areas, on=['属性分级','GRIDCODE'], how='left')
|
||||
# print(df_final)
|
||||
# (可选) 按“一级地类”和“二级地类”排序
|
||||
df_final["属性分级"] = pd.Categorical(df_final['属性分级'], categories=trzd5_order, ordered=True)
|
||||
# df_final["EJDL"] = pd.Categorical(df_final['EJDL'], categories=in_ejdl_order, ordered=True)
|
||||
|
||||
df_final.sort_values(['属性分级','GRIDCODE'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
# print(df_final)
|
||||
return df_final
|
||||
|
||||
# --- 3. Excel 制表 总表---
|
||||
def write_to_excel_table1(df:pd.DataFrame, output_path, prop_config):
|
||||
"""
|
||||
【最终修正版】: 将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel,将创建一个空的报告。")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws['A1'] = "没有有效的统计数据。"
|
||||
wb.save(output_path)
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "行政区酸化程度等级分布及占比"
|
||||
|
||||
# --- a. 定义样式 (不变) ---
|
||||
header_font = Font(name='宋体', size=11)
|
||||
cell_font = Font(name='宋体', size=11)
|
||||
center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
|
||||
top=Side(style='thin'), bottom=Side(style='thin'))
|
||||
|
||||
def apply_style(cell_range, font, alignment=None, border=None):
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
if alignment: cell.alignment = alignment
|
||||
if border: cell.border = border
|
||||
|
||||
# --- b. 绘制表头 (不变) ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土壤三普分类'
|
||||
ws.merge_cells('C1:D1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('E1:F1'); ws['E1'] = '制图统计'
|
||||
|
||||
ws['A2'] = '类别'; ws['B2'] = '名称'
|
||||
ws['C2'] = '数量/个'; ws['D2'] = '占比%'
|
||||
ws['E2'] = '面积/亩'; ws['F2'] = '占比%'
|
||||
|
||||
level_dict = prop_config['标准等级']
|
||||
# 创建两个列表来分别存储上段和下段范围
|
||||
upper_ranges = {value: key for key, value in level_dict.items()}
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('属性分级', sort=False, observed=False):
|
||||
|
||||
yl_start_row = current_row
|
||||
|
||||
# 1. 遍历该一级地类下的所有“二级地类”并写入数据
|
||||
for _, row_data in group_yl_df.iterrows():
|
||||
ws.cell(row=current_row, column=2).value = upper_ranges.get(str(row_data['GRIDCODE']), '-')
|
||||
|
||||
# --- 填充单元格的逻辑开始 ---
|
||||
col_start = 3 # 从第 C 列开始填充
|
||||
|
||||
# 检查是否找到了该土属的数据
|
||||
if not row_data.empty:
|
||||
|
||||
# 1. 构建要从 data_series 中查找的列名
|
||||
sample_col = f'样点数'
|
||||
sample_pct_col = f'样点数占比'
|
||||
area_col = f'制图面积_平差后'
|
||||
area_pct_col = f'面积占比'
|
||||
|
||||
# 2. 从 data_series 中安全地获取值
|
||||
sample_val = row_data.get(sample_col, 0)
|
||||
sample_pct_val = row_data.get(sample_pct_col, 0)
|
||||
area_val = row_data.get(area_col, 0)
|
||||
area_pct_val = row_data.get(area_pct_col, 0)
|
||||
|
||||
# 3. 将获取到的值填入单元格
|
||||
ws.cell(row=current_row, column=col_start).value = f"{sample_val:.0f}" if sample_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 1).value = f"{sample_pct_val:.1f}" if sample_val > 0 else "-"
|
||||
# 制图面积/亩
|
||||
ws.cell(row=current_row, column=col_start + 2).value = f"{area_val:.0f}" if area_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 3).value = f"{area_pct_val:.1f}" if area_val > 0 else "-"
|
||||
|
||||
# 移动到下一个酸化等级的起始列
|
||||
col_start += 2
|
||||
else:
|
||||
for _ in range(4):
|
||||
ws.cell(row=current_row, column=col_start).value = "-"
|
||||
col_start += 1
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并“一级地类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row-1, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
# 2. 填充总计行
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=2)
|
||||
ws.cell(row=current_row, column=1).value = '全区'
|
||||
ws.cell(row=current_row, column=3).value = df['样点数'].sum()
|
||||
ws.cell(row=current_row, column=4).value = '100'
|
||||
ws.cell(row=current_row, column=5).value = f"{df['制图面积_平差后'].sum():.0f}"
|
||||
ws.cell(row=current_row, column=6).value = '100'
|
||||
|
||||
# --- d. 应用样式和调整列宽 (最终健壮版) ---
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
apply_style(f'A1:F{current_row}', cell_font, center_align, thin_border)
|
||||
apply_style(f'A1:F2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
dims = {}
|
||||
for row in ws.rows:
|
||||
for cell in row:
|
||||
if cell.value:
|
||||
merged_range = next((range for range in ws.merged_cells.ranges if cell.coordinate in range), None)
|
||||
if get_merge_type(merged_range) == 'column':
|
||||
continue
|
||||
cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value))
|
||||
dims[cell.column] = max(dims.get(cell.column, 0), cell_len)
|
||||
# 设置列宽
|
||||
for col, value in dims.items():
|
||||
ws.column_dimensions[get_column_letter(int(col))].width = value + 5
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, reclassed_features_path, dltb_features, output_path, target_area_dict,xzqmc, prop_config):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path, f"{soil_prop_name}土壤分级分布.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
is_by_xzq = False if xzqmc not in ["北海市","来宾市","楚雄自治州"] else True
|
||||
|
||||
# out_table_mean = r"in_memory/out_table_mean"
|
||||
temp_out_feature_class = r"in_memory/temp_out_feature_class"
|
||||
temp_out_tables_area = r"in_memory/temp_out_tables_area"
|
||||
# temp_files.append(out_table_mean)
|
||||
temp_files.append(temp_out_tables_area)
|
||||
|
||||
# 求地类图斑和重分类栅格面的交集
|
||||
arcpy.analysis.Intersect(
|
||||
in_features=[dltb_features,reclassed_features_path],
|
||||
out_feature_class=temp_out_feature_class,
|
||||
join_attributes="ALL",
|
||||
output_type="INPUT"
|
||||
)
|
||||
# 行政区划和相交结果进行交集制表
|
||||
arcpy.analysis.TabulateIntersection(
|
||||
in_zone_features="行政区划", # 乡镇边界
|
||||
zone_fields="XZQMC",
|
||||
in_class_features=temp_out_feature_class,
|
||||
out_table=temp_out_tables_area,
|
||||
class_fields="gridcode;YJDL_EJDL",
|
||||
out_units="SQUARE_METERS"
|
||||
)
|
||||
clipped_table_df = arcgis_utils.read_arcgis_table(temp_out_tables_area)
|
||||
|
||||
# 生成表1 土壤属性分级分布 的统计Excel报告
|
||||
final_dataframe = process_data_for_table1(gdb_path, soil_prop_name, clipped_table_df, target_area_dict,xzqmc,is_by_xzq, prop_config)
|
||||
|
||||
write_to_excel_table1(final_dataframe, output_excel_path, prop_config)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
360
tools/core/soil_prop_stats/B1_TRZD土壤属性分级分布.py
Normal file
360
tools/core/soil_prop_stats/B1_TRZD土壤属性分级分布.py
Normal file
@@ -0,0 +1,360 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Border, Side, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from tools.core.utils import arcgis_utils, common_utils
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
# 判断单元格类型
|
||||
def get_merge_type(merged_range):
|
||||
"""
|
||||
判断合并类型
|
||||
返回: 'row'(行合并), 'column'(列合并), 'both'(行列合并)或 None(不是合并单元格)
|
||||
"""
|
||||
if not merged_range:
|
||||
return None
|
||||
|
||||
min_row, max_row = merged_range.min_row, merged_range.max_row
|
||||
min_col, max_col = merged_range.min_col, merged_range.max_col
|
||||
|
||||
if max_row > min_row and max_col > min_col:
|
||||
return 'both' # 同时跨行和跨列
|
||||
elif max_row > min_row:
|
||||
return 'row' # 行合并(垂直合并)
|
||||
elif max_col > min_col:
|
||||
return 'column' # 列合并(水平合并)
|
||||
else:
|
||||
return None # 实际上不是合并单元格
|
||||
|
||||
# 计算属性等级
|
||||
def get_prop_level(prop_level):
|
||||
"""根据输入值判断 返回等级"""
|
||||
if pd.isna(prop_level) or prop_level == 0:
|
||||
return "-"
|
||||
# 请根据您的实际分级标准调整这里的阈值
|
||||
if int(prop_level) == 5 or prop_level == "砂质":
|
||||
return "砂质"
|
||||
elif int(prop_level) == 4 or prop_level == "砂壤质":
|
||||
return "砂壤质"
|
||||
elif int(prop_level) == 3 or prop_level == "壤质":
|
||||
return "壤质"
|
||||
elif int(prop_level) == 1 or prop_level == "黏壤质":
|
||||
return "黏壤质"
|
||||
elif int(prop_level) == 2 or prop_level == "黏质":
|
||||
return "黏质"
|
||||
else:
|
||||
return "-"
|
||||
|
||||
# 等级计算
|
||||
def process_soil_dataframe(df:pd.DataFrame, level_config, target_prop):
|
||||
"""
|
||||
处理土壤数据DataFrame,添加分级列
|
||||
"""
|
||||
result_df = df.copy()
|
||||
|
||||
if level_config and target_prop in df.columns:
|
||||
grade_standards = level_config["标准等级"]
|
||||
grade_column = "GRIDCODE"
|
||||
|
||||
# 使用向量化方法(性能更好)
|
||||
result_df[grade_column] = common_utils.vectorized_grade_assignment(
|
||||
df[target_prop].values, grade_standards
|
||||
)
|
||||
|
||||
# 统计分级结果
|
||||
result_df['YJDL'] = result_df['TDLYLX'].str[:2]
|
||||
|
||||
return result_df
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table1(gdb_path, soil_prop_feature_name, df_origin_area, target_areas_dict,xzqmc,is_by_xzq, prop_config=None):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_feature_name
|
||||
sample_table_path = os.path.join(gdb_path, soil_prop_feature_name)
|
||||
sample_fields = ['TDLYLX', field_name]
|
||||
df_samples = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(sample_table_path, sample_fields, skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, [field_name])
|
||||
|
||||
processed_df = df_samples.copy()
|
||||
processed_df['属性分级'] = processed_df[field_name]
|
||||
|
||||
# ===处理样点数据,计算 各分级样点数
|
||||
df_sample_means = processed_df.groupby(['属性分级']).size().reset_index(name='样点数')
|
||||
df_sample_means['样点数占比'] = df_sample_means['样点数'] / df_sample_means['样点数'].sum() * 100
|
||||
print("样点数计算完成。")
|
||||
|
||||
|
||||
# ==处理制图数据,获各等级制图面积
|
||||
# print(df_origin_area)
|
||||
df_origin_area['YJDL'] = df_origin_area['YJDL_EJDL'].str.split('_').str[0]
|
||||
df_map_data = df_origin_area.groupby(["XZQMC","YJDL", "GRIDCODE"]).agg({"temp_area": "sum"}).reset_index()
|
||||
# print(df_map_data)
|
||||
|
||||
try:
|
||||
if is_by_xzq:
|
||||
df_map_data['adjusted_area'] = df_map_data['temp_area']
|
||||
df_map_data['adjustment_factor'] = 1.0
|
||||
|
||||
# 获取所有存在的行政区和地类
|
||||
existing_districts = df_map_data['XZQMC'].unique()
|
||||
|
||||
# 检查目标字典中的行政区是否存在
|
||||
missing_districts = []
|
||||
tt = [td for td in target_areas_dict.keys()]
|
||||
for ed in existing_districts:
|
||||
if ed not in tt:
|
||||
missing_districts.append(ed)
|
||||
|
||||
# 如果有行政区不存在,返回原始数据并提示
|
||||
if missing_districts:
|
||||
print(f"警告:平差数据中不存在行政区: {missing_districts},未进行平差")
|
||||
|
||||
# 计算每个行政区每个地类的原始总面积
|
||||
original_totals = df_map_data.groupby(['XZQMC', 'YJDL'])['temp_area'].sum()
|
||||
|
||||
# 对每个行政区的每个地类进行平差
|
||||
for xzqmc, landuse_targets in target_areas_dict.items():
|
||||
for yjdl, target_area in landuse_targets.items():
|
||||
# 检查该行政区是否有此地类数据
|
||||
if (xzqmc, yjdl) in original_totals.index and original_totals[(xzqmc, yjdl)] > 0:
|
||||
adjustment_factor = target_area / original_totals[(xzqmc, yjdl)]
|
||||
|
||||
# 应用平差系数
|
||||
mask = (df_map_data['XZQMC'] == xzqmc) & (df_map_data['YJDL'] == yjdl)
|
||||
df_map_data.loc[mask, 'temp_area'] = df_map_data.loc[mask, 'temp_area'] * adjustment_factor
|
||||
df_map_data.loc[mask, 'adjustment_factor'] = adjustment_factor
|
||||
|
||||
# print(f"{xzqmc} - 地类 {yjdl}: 平差系数 = {adjustment_factor:.6f}")
|
||||
else:
|
||||
# 用df_target_area按YJDL进行平差计算
|
||||
original_totals = df_map_data.groupby('YJDL')['temp_area'].sum().to_dict()
|
||||
# 对每个地类进行平差
|
||||
target_area_dict = target_areas_dict.get(xzqmc,"")
|
||||
# print(target_areas_dict)
|
||||
for yjdl, target_area in target_area_dict.items():
|
||||
if (yjdl in original_totals and original_totals[yjdl] > 0) or target_area > 0:
|
||||
adjustment_factor = target_area / original_totals[yjdl]
|
||||
|
||||
# 应用平差系数
|
||||
mask = df_map_data['YJDL'] == yjdl
|
||||
df_map_data.loc[mask, 'temp_area'] = df_map_data.loc[mask, 'temp_area'] * adjustment_factor
|
||||
df_map_data.loc[mask, 'adjustment_factor'] = adjustment_factor
|
||||
|
||||
# print(f"地类 {yjdl}: 平差系数 = {adjustment_factor:.6f}")
|
||||
except Exception as e:
|
||||
print(f"平差处理失败: {e}")
|
||||
|
||||
# print(df_map_data)
|
||||
df_map_data['面积_亩'] = df_map_data['temp_area']
|
||||
|
||||
df_map_data['属性分级'] = df_map_data['GRIDCODE'].apply(get_prop_level)
|
||||
df_map_areas = df_map_data.groupby(['属性分级'])['面积_亩'].sum().reset_index(name='制图面积')
|
||||
# 面积平差
|
||||
df_map_areas['制图面积_平差后'] = df_map_areas['制图面积']
|
||||
# ===计算面积占比
|
||||
df_map_areas['面积占比'] = df_map_areas['制图面积_平差后'] / df_map_areas['制图面积_平差后'].sum() * 100
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['属性分级']],
|
||||
df_map_areas[['属性分级']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['属性分级'], how='left')
|
||||
df_final = pd.merge(df_final, df_map_areas, on=['属性分级'], how='left')
|
||||
# print(df_final)
|
||||
df_final.sort_values(['属性分级'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
# print(df_final)
|
||||
return df_final
|
||||
|
||||
# --- 3. Excel 制表 总表---
|
||||
def write_to_excel_table1(df:pd.DataFrame, output_path, prop_config):
|
||||
"""
|
||||
【最终修正版】: 将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel,将创建一个空的报告。")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws['A1'] = "没有有效的统计数据。"
|
||||
wb.save(output_path)
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "土壤质地分类分布"
|
||||
|
||||
# --- a. 定义样式 (不变) ---
|
||||
header_font = Font(name='宋体', size=11)
|
||||
cell_font = Font(name='宋体', size=11)
|
||||
center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
|
||||
top=Side(style='thin'), bottom=Side(style='thin'))
|
||||
|
||||
def apply_style(cell_range, font, alignment=None, border=None):
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
if alignment: cell.alignment = alignment
|
||||
if border: cell.border = border
|
||||
|
||||
# --- b. 绘制表头 (不变) ---
|
||||
ws.merge_cells('A1:A2'); ws['A1'] = '土壤质地类别'
|
||||
ws.merge_cells('B1:C1'); ws['B1'] = '样点统计'
|
||||
ws.merge_cells('D1:E1'); ws['D1'] = '制图统计'
|
||||
|
||||
ws['B2'] = '数量/个'; ws['C2'] = '占比%'
|
||||
ws['D2'] = '面积/亩'; ws['E2'] = '占比%'
|
||||
|
||||
level_dict = prop_config['标准等级']
|
||||
# 创建两个列表来分别存储上段和下段范围
|
||||
upper_ranges = {value: key for key, value in level_dict.items()}
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for index, row_data in df_to_write.iterrows():
|
||||
|
||||
# 检查是否找到了该土属的数据
|
||||
if not row_data.empty:
|
||||
|
||||
# 1. 构建要从 data_series 中查找的列名
|
||||
sample_col = f'样点数'
|
||||
sample_pct_col = f'样点数占比'
|
||||
area_col = f'制图面积_平差后'
|
||||
area_pct_col = f'面积占比'
|
||||
|
||||
# 2. 从 data_series 中安全地获取值
|
||||
row_name = row_data.get('属性分级', "")
|
||||
sample_val = row_data.get(sample_col, 0)
|
||||
sample_pct_val = row_data.get(sample_pct_col, 0)
|
||||
area_val = row_data.get(area_col, 0)
|
||||
area_pct_val = row_data.get(area_pct_col, 0)
|
||||
|
||||
ws.cell(row=current_row, column=1).value = f"{row_name}" if row_name else "-"
|
||||
# 3. 将获取到的值填入单元格
|
||||
ws.cell(row=current_row, column=2).value = f"{sample_val:.0f}" if sample_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=3).value = f"{sample_pct_val:.1f}" if sample_val > 0 else "-"
|
||||
# 制图面积/亩
|
||||
ws.cell(row=current_row, column=4).value = f"{area_val:.0f}" if area_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=5).value = f"{area_pct_val:.1f}" if area_val > 0 else "-"
|
||||
|
||||
current_row += 1
|
||||
|
||||
|
||||
# 2. 填充总计行
|
||||
ws.cell(row=current_row, column=1).value = '全区'
|
||||
ws.cell(row=current_row, column=2).value = df['样点数'].sum()
|
||||
ws.cell(row=current_row, column=3).value = '100'
|
||||
ws.cell(row=current_row, column=4).value = f"{df['制图面积_平差后'].sum():.0f}"
|
||||
ws.cell(row=current_row, column=5).value = '100'
|
||||
|
||||
# --- d. 应用样式和调整列宽 (最终健壮版) ---
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
apply_style(f'A1:E{current_row}', cell_font, center_align, thin_border)
|
||||
apply_style(f'A1:E2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
dims = {}
|
||||
for row in ws.rows:
|
||||
for cell in row:
|
||||
if cell.value:
|
||||
merged_range = next((range for range in ws.merged_cells.ranges if cell.coordinate in range), None)
|
||||
if get_merge_type(merged_range) == 'column':
|
||||
continue
|
||||
cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value))
|
||||
dims[cell.column] = max(dims.get(cell.column, 0), cell_len)
|
||||
# 设置列宽
|
||||
for col, value in dims.items():
|
||||
ws.column_dimensions[get_column_letter(int(col))].width = value + 5
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, reclassed_features_path, dltb_features, output_path, target_area_dict,xzqmc, prop_config):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path, f"{soil_prop_name}土壤分级分布.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
is_by_xzq = False if xzqmc not in ["北海市","来宾市","楚雄自治州"] else True
|
||||
|
||||
# out_table_mean = r"in_memory/out_table_mean"
|
||||
temp_out_feature_class = r"in_memory/temp_out_feature_class"
|
||||
temp_out_tables_area = r"in_memory/temp_out_tables_area"
|
||||
# temp_files.append(out_table_mean)
|
||||
temp_files.append(temp_out_tables_area)
|
||||
|
||||
# 求地类图斑和重分类栅格面的交集
|
||||
arcpy.analysis.Intersect(
|
||||
in_features=[dltb_features,reclassed_features_path],
|
||||
out_feature_class=temp_out_feature_class,
|
||||
join_attributes="ALL",
|
||||
output_type="INPUT"
|
||||
)
|
||||
# 行政区划和相交结果进行交集制表
|
||||
arcpy.analysis.TabulateIntersection(
|
||||
in_zone_features="行政区划", # 乡镇边界
|
||||
zone_fields="XZQMC",
|
||||
in_class_features=temp_out_feature_class,
|
||||
out_table=temp_out_tables_area,
|
||||
class_fields="gridcode;YJDL_EJDL",
|
||||
out_units="SQUARE_METERS"
|
||||
)
|
||||
clipped_table_df = arcgis_utils.read_arcgis_table(temp_out_tables_area)
|
||||
|
||||
# 生成表1 土壤属性分级分布 的统计Excel报告
|
||||
final_dataframe = process_data_for_table1(gdb_path, soil_prop_name, clipped_table_df, target_area_dict,xzqmc,is_by_xzq, prop_config)
|
||||
|
||||
write_to_excel_table1(final_dataframe, output_excel_path, prop_config)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
513
tools/core/soil_prop_stats/B1土壤属性分级分布.py
Normal file
513
tools/core/soil_prop_stats/B1土壤属性分级分布.py
Normal file
@@ -0,0 +1,513 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
|
||||
from tools.core.utils import arcgis_utils, common_utils
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
from tools.core.utils.excel_utils import ExcelStyleUtils
|
||||
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
xn_region = ['天峨县', '寻甸县', '罗平县', '丘北县', '永仁县', '南华县', '双柏县', '武定县', '祥云县', '楚雄彝族自治州']
|
||||
hn_region = ['北海市', '海城区', '银海区', '铁山港区', '港南区', '容县', '平南县', '兴宁区', '武鸣区', '邕宁区', '苍梧县', '靖西市', '西畴县', '马关县', '澜沧县', '双江县', '永德县']
|
||||
# 计算属性等级
|
||||
def get_prop_level(prop_level):
|
||||
"""根据输入值判断 返回等级"""
|
||||
if pd.isna(prop_level) or prop_level == 0:
|
||||
return "-"
|
||||
# 请根据您的实际分级标准调整这里的阈值
|
||||
if int(prop_level) == 1 or int(prop_level) == 6 or prop_level == '等级一':
|
||||
return "Ⅰ级"
|
||||
elif int(prop_level) == 2 or int(prop_level) == 7 or prop_level == '等级二':
|
||||
return "Ⅱ级"
|
||||
elif int(prop_level) == 3 or int(prop_level) == 8 or prop_level == '等级三':
|
||||
return "Ⅲ级"
|
||||
elif int(prop_level) == 4 or int(prop_level) == 9 or prop_level == '等级四':
|
||||
return "Ⅳ级"
|
||||
elif int(prop_level) == 5 or int(prop_level) == 10 or prop_level == '等级五':
|
||||
return "Ⅴ级"
|
||||
else:
|
||||
return "-"
|
||||
def get_prop_level_for_pH(prop_level):
|
||||
if pd.isna(prop_level) or prop_level == 0:
|
||||
return "-"
|
||||
if int(prop_level) == 5 or prop_level == "等级五":
|
||||
return "Ⅰ级"
|
||||
elif int(prop_level) in [4, 6] or prop_level in ["等级四", "等级六"]:
|
||||
return "Ⅱ级"
|
||||
elif int(prop_level) in [3, 7] or prop_level in ["等级三", "等级七"]:
|
||||
return "Ⅲ级"
|
||||
elif int(prop_level) in [2, 8] or prop_level in ["等级二", "等级八"]:
|
||||
return "Ⅳ级"
|
||||
elif int(prop_level) in [1, 9] or prop_level in ["等级一", "等级九"]:
|
||||
return "Ⅴ级"
|
||||
else:
|
||||
return "-"
|
||||
|
||||
def get_prop_level_for_hn_TRRZ(prop_level):
|
||||
if pd.isna(prop_level) or prop_level == 0:
|
||||
return "-"
|
||||
if int(prop_level) == 3 or prop_level == "等级三":
|
||||
return "Ⅰ级"
|
||||
elif int(prop_level) == 4 or prop_level == "等级四":
|
||||
return "Ⅱ级"
|
||||
elif int(prop_level) in [2, 5] or prop_level in ["等级二", "等级五"]:
|
||||
return "Ⅲ级"
|
||||
elif int(prop_level) == 6 or prop_level == "等级六":
|
||||
return "Ⅳ级"
|
||||
elif int(prop_level) in [1, 7] or prop_level in ["等级一", "等级七"]:
|
||||
return "Ⅴ级"
|
||||
else:
|
||||
return "-"
|
||||
|
||||
def get_prop_level_for_xn_TRRZ(prop_level):
|
||||
if pd.isna(prop_level) or prop_level == 0:
|
||||
return "-"
|
||||
if int(prop_level) == 4 or prop_level == "等级四":
|
||||
return "Ⅰ级"
|
||||
elif int(prop_level) in [3,5] or prop_level in ["等级三", "等级五"]:
|
||||
return "Ⅱ级"
|
||||
elif int(prop_level) == 6 or prop_level == "等级六":
|
||||
return "Ⅲ级"
|
||||
elif int(prop_level) in [2, 7] or prop_level in ["等级二", "等级七"]:
|
||||
return "Ⅳ级"
|
||||
elif int(prop_level) in [1, 8] or prop_level in ["等级一", "等级八"]:
|
||||
return "Ⅴ级"
|
||||
else:
|
||||
return "-"
|
||||
|
||||
|
||||
# 等级计算
|
||||
def process_soil_dataframe(df:pd.DataFrame, level_config, target_prop):
|
||||
"""
|
||||
处理土壤数据DataFrame,添加分级列
|
||||
"""
|
||||
result_df = df.copy()
|
||||
|
||||
if level_config and target_prop in df.columns:
|
||||
grade_standards = level_config["标准等级"]
|
||||
grade_column = "GRIDCODE"
|
||||
|
||||
# 使用向量化方法(性能更好)
|
||||
result_df[grade_column] = common_utils.vectorized_grade_assignment(
|
||||
df[target_prop].values, grade_standards
|
||||
)
|
||||
|
||||
# 统计分级结果
|
||||
result_df['YJDL'] = result_df['TDLYLX'].str[:2]
|
||||
|
||||
return result_df
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table1(gdb_path, soil_prop_feature_name, df_origin_area, target_areas_dict,xzqmc,is_by_xzq, prop_config=None):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_feature_name
|
||||
sample_table_path = os.path.join(gdb_path, soil_prop_feature_name)
|
||||
sample_fields = ['TDLYLX', field_name]
|
||||
df_samples = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(sample_table_path, sample_fields, skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, [field_name])
|
||||
|
||||
processed_df = process_soil_dataframe(df_samples, prop_config, field_name) # 返回具有属性分级的列
|
||||
processed_df['GRIDCODE'] = processed_df['GRIDCODE'].astype('int')
|
||||
if soil_prop_feature_name == 'PH':
|
||||
processed_df['属性分级'] = processed_df['GRIDCODE'].apply(get_prop_level_for_pH)
|
||||
elif soil_prop_feature_name == 'TRRZ' and xzqmc in hn_region:
|
||||
processed_df['属性分级'] = processed_df['GRIDCODE'].apply(get_prop_level_for_hn_TRRZ)
|
||||
elif soil_prop_feature_name == 'TRRZ' and xzqmc in xn_region:
|
||||
processed_df['属性分级'] = processed_df['GRIDCODE'].apply(get_prop_level_for_xn_TRRZ)
|
||||
else:
|
||||
processed_df['属性分级'] = processed_df['GRIDCODE'].apply(get_prop_level)
|
||||
|
||||
# 计算全部样点均值、中位值、范围
|
||||
processed_df[field_name] = processed_df[field_name].astype('float')
|
||||
stat_sample = {
|
||||
'min': processed_df[field_name].min(),
|
||||
'max': processed_df[field_name].max(),
|
||||
'mean':processed_df[field_name].mean(),
|
||||
'median': processed_df[field_name].median(),
|
||||
}
|
||||
|
||||
# ===处理样点数据,计算 各分级样点数
|
||||
df_sample_means = processed_df.groupby(['属性分级','GRIDCODE']).size().reset_index(name='样点数')
|
||||
df_sample_means['样点数占比'] = df_sample_means['样点数'] / df_sample_means['样点数'].sum() * 100
|
||||
print("样点数计算完成。")
|
||||
|
||||
|
||||
# ==处理制图数据,获各等级制图面积
|
||||
# print(df_origin_area)
|
||||
df_origin_area['YJDL'] = df_origin_area['YJDL_EJDL'].str.split('_').str[0]
|
||||
|
||||
# 定义需要过滤地类的属性列表
|
||||
filtered_props = ['ECA', 'EMG', 'ACU', 'AZN', 'AFE', 'AMN', 'AMO', 'AB', 'AS1', 'TSE']
|
||||
|
||||
# 如果当前属性在列表中,则只统计耕地和园地
|
||||
if soil_prop_feature_name in filtered_props:
|
||||
farmland_yjdl = ['耕地', '园地'] # 01: 耕地, 02: 园地
|
||||
df_origin_area = df_origin_area[df_origin_area['YJDL'].isin(farmland_yjdl)]
|
||||
print(f"过滤制图数据:仅统计耕地和园地(YJDL in {farmland_yjdl})")
|
||||
# 如果土壤属性为GZCHD,则只需要耕地的面积统计
|
||||
if soil_prop_feature_name in ['GZCHD']:
|
||||
df_origin_area = df_origin_area[df_origin_area['YJDL'] == '耕地']
|
||||
print(f"过滤制图数据:GZCHD仅统计耕地")
|
||||
df_map_data = df_origin_area.groupby(["XZQMC","YJDL", "GRIDCODE"]).agg({"temp_area": "sum"}).reset_index()
|
||||
# print(df_map_data)
|
||||
|
||||
try:
|
||||
if is_by_xzq:
|
||||
df_map_data['adjusted_area'] = df_map_data['temp_area']
|
||||
df_map_data['adjustment_factor'] = 1.0
|
||||
|
||||
# 获取所有存在的行政区和地类
|
||||
existing_districts = df_map_data['XZQMC'].unique()
|
||||
|
||||
# 检查目标字典中的行政区是否存在
|
||||
missing_districts = []
|
||||
tt = [td for td in target_areas_dict.keys()]
|
||||
for ed in existing_districts:
|
||||
if ed not in tt:
|
||||
missing_districts.append(ed)
|
||||
|
||||
# 如果有行政区不存在,返回原始数据并提示
|
||||
if missing_districts:
|
||||
print(f"警告:平差数据中不存在行政区: {missing_districts},未进行平差")
|
||||
|
||||
# 计算每个行政区每个地类的原始总面积
|
||||
original_totals = df_map_data.groupby(['XZQMC', 'YJDL'])['temp_area'].sum()
|
||||
|
||||
# 对每个行政区的每个地类进行平差
|
||||
for xzqmc, landuse_targets in target_areas_dict.items():
|
||||
for yjdl, target_area in landuse_targets.items():
|
||||
# 检查该行政区是否有此地类数据
|
||||
if (xzqmc, yjdl) in original_totals.index and original_totals[(xzqmc, yjdl)] > 0:
|
||||
adjustment_factor = target_area / original_totals[(xzqmc, yjdl)]
|
||||
|
||||
# 应用平差系数
|
||||
mask = (df_map_data['XZQMC'] == xzqmc) & (df_map_data['YJDL'] == yjdl)
|
||||
df_map_data.loc[mask, 'temp_area'] = df_map_data.loc[mask, 'temp_area'] * adjustment_factor
|
||||
df_map_data.loc[mask, 'adjustment_factor'] = adjustment_factor
|
||||
|
||||
# print(f"{xzqmc} - 地类 {yjdl}: 平差系数 = {adjustment_factor:.6f}")
|
||||
else:
|
||||
# 用df_target_area按YJDL进行平差计算
|
||||
original_totals = df_map_data.groupby('YJDL')['temp_area'].sum().to_dict()
|
||||
# 对每个地类进行平差
|
||||
target_area_dict = target_areas_dict.get(xzqmc,"")
|
||||
# print(target_areas_dict)
|
||||
for yjdl, target_area in target_area_dict.items():
|
||||
if (yjdl in original_totals and original_totals[yjdl] > 0) or target_area > 0:
|
||||
adjustment_factor = target_area / original_totals[yjdl]
|
||||
|
||||
# 应用平差系数
|
||||
mask = df_map_data['YJDL'] == yjdl
|
||||
df_map_data.loc[mask, 'temp_area'] = df_map_data.loc[mask, 'temp_area'] * adjustment_factor
|
||||
df_map_data.loc[mask, 'adjustment_factor'] = adjustment_factor
|
||||
|
||||
# print(f"地类 {yjdl}: 平差系数 = {adjustment_factor:.6f}")
|
||||
except Exception as e:
|
||||
print(f"平差处理失败: {e}")
|
||||
|
||||
# print(df_map_data)
|
||||
df_map_data['面积_亩'] = df_map_data['temp_area']
|
||||
|
||||
if soil_prop_feature_name == 'PH':
|
||||
df_map_data['属性分级'] = df_map_data['GRIDCODE'].apply(get_prop_level_for_pH)
|
||||
elif soil_prop_feature_name == 'TRRZ' and xzqmc in hn_region:
|
||||
df_map_data['属性分级'] = df_map_data['GRIDCODE'].apply(get_prop_level_for_hn_TRRZ)
|
||||
elif soil_prop_feature_name == 'TRRZ' and xzqmc in xn_region:
|
||||
df_map_data['属性分级'] = df_map_data['GRIDCODE'].apply(get_prop_level_for_xn_TRRZ)
|
||||
else:
|
||||
df_map_data['属性分级'] = df_map_data['GRIDCODE'].apply(get_prop_level)
|
||||
|
||||
df_map_areas = df_map_data.groupby(['属性分级','GRIDCODE'])['面积_亩'].sum().reset_index(name='制图面积')
|
||||
# 面积平差
|
||||
df_map_areas['制图面积_平差后'] = df_map_areas['制图面积']
|
||||
# ===计算面积占比
|
||||
df_map_areas['面积占比'] = df_map_areas['制图面积_平差后'] / df_map_areas['制图面积_平差后'].sum() * 100
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['属性分级','GRIDCODE']],
|
||||
df_map_areas[['属性分级','GRIDCODE']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['属性分级','GRIDCODE'], how='left')
|
||||
df_final = pd.merge(df_final, df_map_areas, on=['属性分级','GRIDCODE'], how='left')
|
||||
# print(df_final)
|
||||
df_final.sort_values(['属性分级'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
# print(df_final)
|
||||
return df_final, stat_sample
|
||||
|
||||
# --- 3. Excel 制表 总表---
|
||||
def write_to_excel_table1(df:pd.DataFrame, output_path, prop_config, soil_prop_tif, stat_sample):
|
||||
"""
|
||||
【最终修正版】: 将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel,将创建一个空的报告。")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws['A1'] = "没有有效的统计数据。"
|
||||
wb.save(output_path)
|
||||
return
|
||||
|
||||
# 全区制图统计
|
||||
"""
|
||||
try:
|
||||
raster = arcpy.Raster(soil_prop_tif)
|
||||
|
||||
# 转换为numpy数组进行计算
|
||||
array = arcpy.RasterToNumPyArray(raster,nodata_to_value=9999)
|
||||
|
||||
# 过滤掉NoData值
|
||||
# 过滤NoData值和9999值
|
||||
array = array[~np.isnan(array)] # 过滤NoData
|
||||
array = array[array != 9999] # 过滤9999
|
||||
array = array.astype(np.float64)
|
||||
|
||||
stats = {
|
||||
'min': round(np.min(array),2),
|
||||
'max': round(np.max(array),2),
|
||||
'mean': round(np.mean(array),2),
|
||||
'median': round(np.median(array),2),
|
||||
'std': round(np.std(array),2)
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"错误: {e}")
|
||||
"""
|
||||
# 全区样点统计
|
||||
stats = stat_sample
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "行政区酸化程度等级分布及占比"
|
||||
|
||||
# 获取属性单位
|
||||
special_prop = ['耕作层厚度','阳离子','有机质','pH','有效磷','速效钾','交换性钙','交换性镁','有效硫','有效铁','有效锰','有效硅']
|
||||
fsn_props = ['砂粒含量','粉粒含量','黏粒含量','有效土层厚度']
|
||||
prop_name_str = prop_config.get('项目分级','')
|
||||
if prop_name_str:
|
||||
split_name = prop_name_str.split('\n')[0].strip()
|
||||
if split_name in special_prop:
|
||||
prop_name = '1f'
|
||||
elif split_name in fsn_props:
|
||||
prop_name = '0f'
|
||||
else:
|
||||
prop_name = '2f'
|
||||
else:
|
||||
prop_name = '1f'
|
||||
# print(prop_name_str, prop_name)
|
||||
|
||||
prop_unit_str = prop_config.get('分级标准', '')
|
||||
if prop_unit_str:
|
||||
prop_unit = prop_unit_str.split('\n')[1].strip()
|
||||
else:
|
||||
prop_unit = ''
|
||||
|
||||
# --- b. 绘制表头 (不变) ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土壤三普分级'
|
||||
ws.merge_cells('C1:D1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('E1:F1'); ws['E1'] = '制图统计'
|
||||
|
||||
ws['A2'] = '分级'; ws['B2'] = '值域/' + prop_unit if prop_unit else '值域'
|
||||
ws['C2'] = '数量/个'; ws['D2'] = '占比%'
|
||||
ws['E2'] = '面积/亩'; ws['F2'] = '占比%'
|
||||
|
||||
acid_levels = ['Ⅰ级','Ⅱ级', 'Ⅲ级', 'Ⅳ级', 'Ⅴ级']
|
||||
level_dict = prop_config['标准等级']
|
||||
# 创建两个列表来分别存储上段和下段范围
|
||||
upper_ranges = {}
|
||||
lower_ranges = {}
|
||||
|
||||
# 遍历排序后的等级
|
||||
for i, (level, ranges) in enumerate(sorted(level_dict.items(), key=lambda x: list(level_dict.keys()).index(x[0])), 1):
|
||||
# 分割范围字符串
|
||||
range_list = [r.strip() for r in ranges.split(',')]
|
||||
|
||||
if len(range_list) >= 1:
|
||||
upper_ranges[i] = range_list[0]
|
||||
|
||||
if len(range_list) >= 2:
|
||||
# 计算下段范围的索引(原始索引 + 等级总数)
|
||||
lower_index = i + len(level_dict)
|
||||
lower_ranges[lower_index] = range_list[1]
|
||||
|
||||
# 合并结果
|
||||
upper_ranges.update(lower_ranges)
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('属性分级', sort=False, observed=False):
|
||||
|
||||
yl_start_row = current_row
|
||||
|
||||
# 1. 遍历该一级地类下的所有“二级地类”并写入数据
|
||||
for _, row_data in group_yl_df.iterrows():
|
||||
ws.cell(row=current_row, column=2).value = upper_ranges.get(row_data['GRIDCODE'], '-')
|
||||
|
||||
# --- 填充单元格的逻辑开始 ---
|
||||
col_start = 3 # 从第 C 列开始填充
|
||||
|
||||
# 检查是否找到了该土属的数据
|
||||
if not row_data.empty:
|
||||
|
||||
# 1. 构建要从 data_series 中查找的列名
|
||||
sample_col = f'样点数'
|
||||
sample_pct_col = f'样点数占比'
|
||||
area_col = f'制图面积_平差后'
|
||||
area_pct_col = f'面积占比'
|
||||
|
||||
# 2. 从 data_series 中安全地获取值
|
||||
sample_val = row_data.get(sample_col, 0)
|
||||
sample_pct_val = row_data.get(sample_pct_col, 0)
|
||||
area_val = row_data.get(area_col, 0)
|
||||
area_pct_val = row_data.get(area_pct_col, 0)
|
||||
|
||||
# 3. 将获取到的值填入单元格
|
||||
ws.cell(row=current_row, column=col_start).value = f"{sample_val:.0f}" if sample_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 1).value = f"{sample_pct_val:.1f}" if sample_val > 0 else "-"
|
||||
# 制图面积/亩
|
||||
ws.cell(row=current_row, column=col_start + 2).value = f"{area_val:.0f}" if area_val > 0 else "-"
|
||||
# 占比/%
|
||||
ws.cell(row=current_row, column=col_start + 3).value = f"{area_pct_val:.1f}" if area_val > 0 else "-"
|
||||
|
||||
# 移动到下一个酸化等级的起始列
|
||||
col_start += 2
|
||||
else:
|
||||
for _ in range(4):
|
||||
ws.cell(row=current_row, column=col_start).value = "-"
|
||||
col_start += 1
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并“一级地类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row-1, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
# 2. 填充总计行
|
||||
ws.cell(row=current_row, column=1).value = '全区'
|
||||
ws.cell(row=current_row, column=2).value = '-'
|
||||
ws.cell(row=current_row, column=3).value = df['样点数'].sum()
|
||||
ws.cell(row=current_row, column=4).value = '100'
|
||||
ws.cell(row=current_row, column=5).value = f"{df['制图面积_平差后'].sum():.0f}"
|
||||
ws.cell(row=current_row, column=6).value = '100'
|
||||
|
||||
# 3. 合计单元格填充
|
||||
ws.merge_cells(f'B{current_row + 1}:F{current_row + 1}')
|
||||
ws.cell(row=current_row + 1, column=1).value = '全区均值'
|
||||
ws.cell(row=current_row + 1, column=2).value = f'{stats["mean"]:.{prop_name}}'
|
||||
|
||||
ws.merge_cells(f'B{current_row + 2}:F{current_row + 2}')
|
||||
ws.cell(row=current_row + 2, column=1).value = '全区中位值'
|
||||
ws.cell(row=current_row + 2, column=2).value = f'{stats["median"]:.{prop_name}}'
|
||||
|
||||
ws.merge_cells(f'B{current_row + 3}:F{current_row + 3}')
|
||||
ws.cell(row=current_row + 3, column=1).value = '全区范围'
|
||||
ws.cell(row=current_row + 3, column=2).value = f'{stats["min"]:.{prop_name}} ~ {stats["max"]:.{prop_name}}'
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='宋体', size=11, bold=True)
|
||||
|
||||
# --- d. 应用样式和调整列宽 (最终健壮版) ---
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:F{current_row+3}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:F2', header_font)
|
||||
|
||||
# 调整列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, reclassed_features_path, dltb_features, soil_prop_tif, output_path, target_area_dict,xzqmc, prop_config):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path, f"{soil_prop_name}土壤分级分布.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
is_by_xzq = False if xzqmc not in ["北海市","来宾市","楚雄自治州"] else True
|
||||
|
||||
# out_table_mean = r"in_memory/out_table_mean"
|
||||
temp_out_feature_class = r"in_memory/temp_out_feature_class"
|
||||
temp_out_tables_area = r"in_memory/temp_out_tables_area"
|
||||
# temp_files.append(out_table_mean)
|
||||
temp_files.append(temp_out_tables_area)
|
||||
|
||||
# if not arcpy.Exists(out_table_mean):
|
||||
# # 2.用arcpy.sa.ZonalStatisticsAsTable 以表格进行分区统计
|
||||
# arcpy.sa.ZonalStatisticsAsTable(
|
||||
# dltb_features, "YJDL_EJDL", soil_prop_tif, out_table_mean, "DATA", "MEAN"
|
||||
# )
|
||||
# arcpy.management.CalculateField(out_table_mean, "YJDL", "!YJDL_EJDL!.split('_')[0]", "PYTHON3")
|
||||
# arcpy.management.CalculateField(out_table_mean, "EJDL", "!YJDL_EJDL!.split('_')[1]", "PYTHON3")
|
||||
|
||||
# 求地类图斑和重分类栅格面的交集
|
||||
arcpy.analysis.Intersect(
|
||||
in_features=[dltb_features,reclassed_features_path],
|
||||
out_feature_class=temp_out_feature_class,
|
||||
join_attributes="ALL",
|
||||
output_type="INPUT"
|
||||
)
|
||||
# 行政区划和相交结果进行交集制表
|
||||
arcpy.analysis.TabulateIntersection(
|
||||
in_zone_features="行政区划", # 乡镇边界
|
||||
zone_fields="XZQMC",
|
||||
in_class_features=temp_out_feature_class,
|
||||
out_table=temp_out_tables_area,
|
||||
class_fields="gridcode;YJDL_EJDL",
|
||||
out_units="SQUARE_METERS"
|
||||
)
|
||||
clipped_table_df = arcgis_utils.read_arcgis_table(temp_out_tables_area)
|
||||
|
||||
# 生成表1 土壤属性分级分布 的统计Excel报告
|
||||
final_dataframe,stat = process_data_for_table1(gdb_path, soil_prop_name, clipped_table_df, target_area_dict,xzqmc,is_by_xzq, prop_config)
|
||||
|
||||
write_to_excel_table1(final_dataframe, output_excel_path, prop_config, soil_prop_tif, stat)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
315
tools/core/soil_prop_stats/B2_TRZD12土地利用类型土壤属性.py
Normal file
315
tools/core/soil_prop_stats/B2_TRZD12土地利用类型土壤属性.py
Normal file
@@ -0,0 +1,315 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Border, Side, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from tools.config.pandas_field_cal_func import calculate_ejdl, calculate_yjdl
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
|
||||
yjdl_order = ["耕地", "园地", "林地", "草地", "其他"]
|
||||
ejdl_order = ["水田", "旱地", "水浇地", "果园", "茶园", "橡胶园", "其他园地"]
|
||||
# 土壤12级地质类别
|
||||
trzd_order = ['砂土及壤质砂土', '砂质壤土','壤土','粉(砂)质壤土','砂质黏壤土','黏壤土','粉(砂)质黏壤土','砂质黏土','壤质黏土','粉(砂)质黏土','黏土','重黏土']
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
# 判断单元格类型
|
||||
def get_merge_type(merged_range):
|
||||
"""
|
||||
判断合并类型
|
||||
返回: 'row'(行合并), 'column'(列合并), 'both'(行列合并)或 None(不是合并单元格)
|
||||
"""
|
||||
if not merged_range:
|
||||
return None
|
||||
|
||||
min_row, max_row = merged_range.min_row, merged_range.max_row
|
||||
min_col, max_col = merged_range.min_col, merged_range.max_col
|
||||
|
||||
if max_row > min_row and max_col > min_col:
|
||||
return 'both' # 同时跨行和跨列
|
||||
elif max_row > min_row:
|
||||
return 'row' # 行合并(垂直合并)
|
||||
elif max_col > min_col:
|
||||
return 'column' # 列合并(水平合并)
|
||||
else:
|
||||
return None # 实际上不是合并单元格
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table2(gdb_path, soil_prop_feature_name, df_dltb, target_areas_df):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算样点数 ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_feature_name
|
||||
sample_table_path = os.path.join(gdb_path, soil_prop_feature_name)
|
||||
sample_fields = ['TDLYLX', field_name]
|
||||
df_samples = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(sample_table_path, sample_fields, skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, [field_name])
|
||||
|
||||
df_samples["YJDL"] = df_samples['TDLYLX'].apply(calculate_yjdl)
|
||||
df_samples["EJDL"] = df_samples['TDLYLX'].apply(calculate_ejdl)
|
||||
df_samples["GRIDCODE"] = df_samples[field_name].astype(int)
|
||||
|
||||
# 按 YJDL, EJDL 分组,计算 属性 的均值
|
||||
df_sample_means = df_samples.groupby(['YJDL', 'EJDL', 'GRIDCODE']).size().reset_index(name="样点数")
|
||||
total_sample_count = df_sample_means['样点数'].sum()
|
||||
df_sample_means['样点数占比'] = df_sample_means['样点数'] / total_sample_count
|
||||
|
||||
# ==b. 处理制图数据,获各等级制图面积
|
||||
df_dltb["YJDL"] = df_dltb['YJDL_EJDL'].apply(lambda x: x.split('_')[0])
|
||||
df_dltb["EJDL"] = df_dltb["YJDL_EJDL"].apply(lambda x: x.split('_')[1])
|
||||
df_dltb.columns = df_dltb.columns.str.upper()
|
||||
df_dltb = clean_df(df_dltb, ['YJDL', 'EJDL'])
|
||||
|
||||
df_map_data = df_dltb.groupby(["YJDL","EJDL", "GRIDCODE"]).agg({"AREA": "sum"}).reset_index()
|
||||
df_map_data['制图面积_原始'] = df_map_data['AREA'] * 0.0015 # 单位:亩
|
||||
# df_map_data['面积占比'] = df_map_data['制图面积'] / df_map_data['制图面积'].sum()
|
||||
|
||||
# 第二步:整理目标面积表(确保字段名统一)
|
||||
target_areas_df = target_areas_df.copy()
|
||||
target_areas_df.columns = target_areas_df.columns.str.strip() # 去除字段名空格
|
||||
# 重置索引,确保EJDL是列而不是索引
|
||||
if 'EJDL' not in target_areas_df.columns:
|
||||
target_areas_df = target_areas_df.reset_index()
|
||||
target_areas_df.rename(columns={'index': 'EJDL'}, inplace=True)
|
||||
# 确保面积字段为数值型
|
||||
target_areas_df['面积'] = pd.to_numeric(target_areas_df['面积'], errors='coerce').fillna(0)
|
||||
|
||||
# 第三步:按二级地类分组计算平差系数
|
||||
# 先计算每个二级地类的原始合计面积
|
||||
ejdl_original_sum = df_map_data.groupby('EJDL')['制图面积_原始'].sum().reset_index()
|
||||
ejdl_original_sum.rename(columns={'制图面积_原始': '原始合计面积'}, inplace=True)
|
||||
# 合并目标面积
|
||||
ejdl_adj = pd.merge(ejdl_original_sum, target_areas_df, on='EJDL', how='left')
|
||||
ejdl_adj.rename(columns={'面积': '目标合计面积'}, inplace=True)
|
||||
# 填充无目标面积的二级地类(目标面积=原始面积,平差系数=1)
|
||||
ejdl_adj['目标合计面积'] = ejdl_adj['目标合计面积'].fillna(ejdl_adj['原始合计面积'])
|
||||
# 计算平差系数(目标面积 / 原始面积,避免除以0)
|
||||
ejdl_adj['平差系数'] = ejdl_adj['目标合计面积'] / ejdl_adj['原始合计面积'].replace(0, 1)
|
||||
ejdl_adj['平差系数'] = ejdl_adj['平差系数'].fillna(1) # 极端情况填充1
|
||||
|
||||
# 第四步:应用平差系数到每个质地级别的制图面积
|
||||
df_map_data = pd.merge(df_map_data, ejdl_adj[['EJDL', '平差系数']], on='EJDL', how='left')
|
||||
df_map_data['平差系数'] = df_map_data['平差系数'].fillna(1) # 未匹配到的二级地类系数=1
|
||||
# 计算平差后的制图面积
|
||||
df_map_data['制图面积'] = df_map_data['制图面积_原始'] * df_map_data['平差系数']
|
||||
# 重新计算面积占比(基于平差后的面积)
|
||||
total_adjusted_area = df_map_data['制图面积'].sum()
|
||||
df_map_data['面积占比'] = df_map_data['制图面积'] / total_adjusted_area
|
||||
df_map_data = clean_df(df_map_data, ['YJDL', 'EJDL'])
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['YJDL', 'EJDL', 'GRIDCODE']],
|
||||
df_map_data[['YJDL', 'EJDL', 'GRIDCODE']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['YJDL', 'EJDL', 'GRIDCODE'], how='left')
|
||||
df_final = pd.merge(df_final, df_map_data, on=['YJDL', 'EJDL', 'GRIDCODE'], how='left')
|
||||
|
||||
# (可选) 按“一级地类”和“二级地类”排序
|
||||
in_ejdl_order = ejdl_order + [x for x in df_final['EJDL'].unique() if x not in ejdl_order]
|
||||
df_final["YJDL"] = pd.Categorical(df_final['YJDL'], categories=yjdl_order, ordered=True)
|
||||
df_final["EJDL"] = pd.Categorical(df_final['EJDL'], categories=in_ejdl_order, ordered=True)
|
||||
df_final["GRIDCODE"] = pd.Categorical(df_final['GRIDCODE'], categories=sorted(df_final['GRIDCODE'].unique()), ordered=True)
|
||||
df_final.sort_values(['YJDL', 'EJDL', 'GRIDCODE'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
|
||||
# 写入EXCEL 表2
|
||||
def write_to_excel_table2(df, output_path, prop_config):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同土地利用类型属性变化统计"
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
cell_font = Font(name='等线', size=11)
|
||||
center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
|
||||
top=Side(style='thin'), bottom=Side(style='thin'))
|
||||
|
||||
def apply_style(cell_range, font, alignment=None, border=None):
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
if alignment: cell.alignment = alignment
|
||||
if border: cell.border = border
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土地利用类型'
|
||||
ws.merge_cells('C1:E1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('F1:G1'); ws['F1'] = '制图统计'
|
||||
|
||||
ws['A2'] = '一级'
|
||||
ws['B2'] = '二级'
|
||||
ws['C2'] = '质地类型'
|
||||
ws['D2'] = '数量/个'
|
||||
ws['E2'] = '占比%'
|
||||
ws['F2'] = '面积/亩'
|
||||
ws['G2'] = '占比%'
|
||||
|
||||
level_dict = prop_config['标准等级']
|
||||
# 创建两个列表来分别存储上段和下段范围
|
||||
upper_ranges = {value: key for key, value in level_dict.items()}
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('YJDL', sort=False, observed=False):
|
||||
if group_yl_df.empty:
|
||||
continue
|
||||
|
||||
print(f"正在写入一级地类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 按二级地类分组
|
||||
for ej, group_ej_df in group_yl_df.groupby('EJDL', sort=False, observed=False):
|
||||
if group_ej_df.empty:
|
||||
continue
|
||||
|
||||
print(f"正在写入二级地类: {ej}...")
|
||||
ej_start_row = current_row
|
||||
|
||||
# 按“土壤质地分级”分组
|
||||
for idx, row_data in group_ej_df.iterrows():
|
||||
# 填充土壤质地分类
|
||||
ws.cell(row=current_row, column=3).value = upper_ranges.get(str(row_data['GRIDCODE']), '-')
|
||||
|
||||
# 填充样点数据
|
||||
ws.cell(row=current_row, column=4).value = row_data['样点数'] if not np.isnan(row_data['样点数']) else '-'
|
||||
ws.cell(row=current_row, column=5).value = round(row_data['样点数占比']*100, 2) if not np.isnan(row_data['样点数占比']) else '-'
|
||||
# 填充制图数据
|
||||
ws.cell(row=current_row, column=6).value = round(row_data['制图面积'], 0) if not np.isnan(row_data['制图面积']) else '-'
|
||||
ws.cell(row=current_row, column=7).value = round(row_data['面积占比']*100, 2) if not np.isnan(row_data['面积占比']) else '-'
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并二级地类单元格
|
||||
if ej_start_row <= current_row:
|
||||
ws.merge_cells(start_row=ej_start_row, start_column=2, end_row=current_row-1, end_column=2)
|
||||
ws.cell(row=ej_start_row, column=2).value = ej
|
||||
|
||||
# 一级地类合计行
|
||||
ws.merge_cells(start_row=current_row, start_column=2, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=2).value = '合计'
|
||||
ws.cell(row=current_row, column=4).value = round(group_yl_df['样点数'].sum(), 0) if not np.isnan(group_yl_df['样点数'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=5).value = round(group_yl_df['样点数占比'].sum()*100, 2) if not np.isnan(group_yl_df['样点数占比'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=6).value = round(group_yl_df['制图面积'].sum(), 0) if not np.isnan(group_yl_df['制图面积'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=7).value = round(group_yl_df['面积占比'].sum()*100, 2) if not np.isnan(group_yl_df['面积占比'].sum()) else '-'
|
||||
|
||||
# 合并一级地类单元格(修正合并范围)
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
current_row += 1
|
||||
|
||||
# --- 5. 全区汇总行 ---
|
||||
ws.cell(row=current_row, column=1).value = '全区汇总'
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=4).value = round(df_to_write['样点数'].sum(), 0) if not np.isnan(df_to_write['样点数'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=5).value = round(df_to_write['样点数占比'].sum()*100, 2) if not np.isnan(df_to_write['样点数占比'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=6).value = round(df_to_write['制图面积'].sum(), 0) if not np.isnan(df_to_write['制图面积'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=7).value = round(df_to_write['面积占比'].sum()*100, 2) if not np.isnan(df_to_write['面积占比'].sum()) else '-'
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
apply_style(f'A1:{max_col_letter}{current_row}', cell_font, center_align, thin_border)
|
||||
apply_style(f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
dims = {}
|
||||
for row in ws.rows:
|
||||
for cell in row:
|
||||
if cell.value:
|
||||
merged_range = next((range for range in ws.merged_cells.ranges if cell.coordinate in range), None)
|
||||
if get_merge_type(merged_range) == 'column':
|
||||
continue
|
||||
cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value))
|
||||
dims[cell.column] = max(dims.get(cell.column, 0), cell_len)
|
||||
# 设置列宽
|
||||
for col, value in dims.items():
|
||||
ws.column_dimensions[get_column_letter(int(col))].width = value + 5
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
# def main(gdb_path, soil_prop_name, dltb_features, reclassed_feature, output_path,target_areas_df, prop_config):
|
||||
# print(target_areas_df)
|
||||
# df = pd.read_csv(r"D:\ProgramData\ArcGis_Py\测试数据.csv")
|
||||
# output_path = r"E:\@三普属性图出图\测试\AAA.xlsx"
|
||||
# write_to_excel_table2(df,output_path,prop_config)
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, dltb_features, reclassed_feature, output_path,target_areas_df, prop_config):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path, f"{soil_prop_name}土地利用类型土壤.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
|
||||
out_table_mean = r"in_memory/out_table_mean"
|
||||
temp_files.append(out_table_mean)
|
||||
if not arcpy.Exists(out_table_mean):
|
||||
# 2.使用交集制表计算每个TRZD的面积
|
||||
arcpy.analysis.TabulateIntersection(dltb_features, "YJDL_EJDL", reclassed_feature, out_table_mean, "gridcode", out_units="SQUARE_METERS")
|
||||
|
||||
dltb_df = pd.DataFrame(arcpy.da.TableToNumPyArray(out_table_mean, ["YJDL_EJDL", "gridcode", "AREA"]))
|
||||
|
||||
|
||||
# 生成表1 土壤属性分级分布 的统计Excel报告
|
||||
final_dataframe = process_data_for_table2(gdb_path, soil_prop_name, dltb_df, target_areas_df)
|
||||
|
||||
write_to_excel_table2(final_dataframe, output_excel_path, prop_config)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# df = pd.read_csv(r"D:\ProgramData\ArcGis_Py\测试数据.csv")
|
||||
# output_path = r"E:\@三普属性图出图\测试\AAA.xlsx"
|
||||
# write_to_excel_table2(df,output_path)
|
||||
336
tools/core/soil_prop_stats/B2_TRZD土地利用类型土壤属性.py
Normal file
336
tools/core/soil_prop_stats/B2_TRZD土地利用类型土壤属性.py
Normal file
@@ -0,0 +1,336 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
|
||||
from matplotlib.artist import get
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Border, Side, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from tools.config.pandas_field_cal_func import calculate_ejdl, calculate_yjdl
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
|
||||
yjdl_order = ["耕地", "园地", "林地", "草地", "其他"]
|
||||
ejdl_order = ["水田", "旱地", "水浇地", "果园", "茶园", "橡胶园", "其他园地"]
|
||||
# 土壤12级地质类别
|
||||
trzd_order = ['黏壤质','黏质','壤质','砂壤质','砂质']
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
def get_prop_level(prop_level):
|
||||
"""根据输入值判断 返回等级"""
|
||||
if pd.isna(prop_level) or prop_level == 0:
|
||||
return "-"
|
||||
# 请根据您的实际分级标准调整这里的阈值
|
||||
if int(prop_level) == 5 or prop_level == "砂质":
|
||||
return "砂质"
|
||||
elif int(prop_level) == 4 or prop_level == "砂壤质":
|
||||
return "砂壤质"
|
||||
elif int(prop_level) == 3 or prop_level == "壤质":
|
||||
return "壤质"
|
||||
elif int(prop_level) == 1 or prop_level == "黏壤质":
|
||||
return "黏壤质"
|
||||
elif int(prop_level) == 2 or prop_level == "黏质":
|
||||
return "黏质"
|
||||
else:
|
||||
return "-"
|
||||
|
||||
# 判断单元格类型
|
||||
def get_merge_type(merged_range):
|
||||
"""
|
||||
判断合并类型
|
||||
返回: 'row'(行合并), 'column'(列合并), 'both'(行列合并)或 None(不是合并单元格)
|
||||
"""
|
||||
if not merged_range:
|
||||
return None
|
||||
|
||||
min_row, max_row = merged_range.min_row, merged_range.max_row
|
||||
min_col, max_col = merged_range.min_col, merged_range.max_col
|
||||
|
||||
if max_row > min_row and max_col > min_col:
|
||||
return 'both' # 同时跨行和跨列
|
||||
elif max_row > min_row:
|
||||
return 'row' # 行合并(垂直合并)
|
||||
elif max_col > min_col:
|
||||
return 'column' # 列合并(水平合并)
|
||||
else:
|
||||
return None # 实际上不是合并单元格
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table2(gdb_path, soil_prop_feature_name, df_dltb, target_areas_df):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算样点数 ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_feature_name
|
||||
sample_table_path = os.path.join(gdb_path, soil_prop_feature_name)
|
||||
sample_fields = ['TDLYLX', field_name]
|
||||
df_samples = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(sample_table_path, sample_fields, skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, [field_name])
|
||||
|
||||
df_samples["YJDL"] = df_samples['TDLYLX'].apply(calculate_yjdl)
|
||||
df_samples["EJDL"] = df_samples['TDLYLX'].apply(calculate_ejdl)
|
||||
df_samples["GRIDCODE"] = df_samples[field_name]
|
||||
|
||||
# 按 YJDL, EJDL 分组,计算 属性 的均值
|
||||
df_sample_means = df_samples.groupby(['YJDL', 'EJDL', 'GRIDCODE']).size().reset_index(name="样点数")
|
||||
total_sample_count = df_sample_means['样点数'].sum()
|
||||
df_sample_means['样点数占比'] = df_sample_means['样点数'] / total_sample_count
|
||||
|
||||
# ==b. 处理制图数据,获各等级制图面积
|
||||
df_dltb["YJDL"] = df_dltb['YJDL_EJDL'].apply(lambda x: x.split('_')[0])
|
||||
df_dltb["EJDL"] = df_dltb["YJDL_EJDL"].apply(lambda x: x.split('_')[1])
|
||||
df_dltb.columns = df_dltb.columns.str.upper()
|
||||
df_dltb = clean_df(df_dltb, ['YJDL', 'EJDL'])
|
||||
df_dltb['GRIDCODE'] = df_dltb['GRIDCODE'].apply(get_prop_level)
|
||||
|
||||
df_map_data = df_dltb.groupby(["YJDL","EJDL", "GRIDCODE"]).agg({"AREA": "sum"}).reset_index()
|
||||
df_map_data['制图面积_原始'] = df_map_data['AREA'] * 0.0015 # 单位:亩
|
||||
# df_map_data['面积占比'] = df_map_data['制图面积'] / df_map_data['制图面积'].sum()
|
||||
|
||||
# 第二步:整理目标面积表(确保字段名统一)
|
||||
target_areas_df = target_areas_df.copy()
|
||||
target_areas_df.columns = target_areas_df.columns.str.strip() # 去除字段名空格
|
||||
# 重置索引,确保EJDL是列而不是索引
|
||||
if 'EJDL' not in target_areas_df.columns:
|
||||
target_areas_df = target_areas_df.reset_index()
|
||||
target_areas_df.rename(columns={'index': 'EJDL'}, inplace=True)
|
||||
# 确保面积字段为数值型
|
||||
target_areas_df['面积'] = pd.to_numeric(target_areas_df['面积'], errors='coerce').fillna(0)
|
||||
|
||||
# 第三步:按二级地类分组计算平差系数
|
||||
# 先计算每个二级地类的原始合计面积
|
||||
ejdl_original_sum = df_map_data.groupby('EJDL')['制图面积_原始'].sum().reset_index()
|
||||
ejdl_original_sum.rename(columns={'制图面积_原始': '原始合计面积'}, inplace=True)
|
||||
# 合并目标面积
|
||||
ejdl_adj = pd.merge(ejdl_original_sum, target_areas_df, on='EJDL', how='left')
|
||||
ejdl_adj.rename(columns={'面积': '目标合计面积'}, inplace=True)
|
||||
# 填充无目标面积的二级地类(目标面积=原始面积,平差系数=1)
|
||||
ejdl_adj['目标合计面积'] = ejdl_adj['目标合计面积'].fillna(ejdl_adj['原始合计面积'])
|
||||
# 计算平差系数(目标面积 / 原始面积,避免除以0)
|
||||
ejdl_adj['平差系数'] = ejdl_adj['目标合计面积'] / ejdl_adj['原始合计面积'].replace(0, 1)
|
||||
ejdl_adj['平差系数'] = ejdl_adj['平差系数'].fillna(1) # 极端情况填充1
|
||||
|
||||
# 第四步:应用平差系数到每个质地级别的制图面积
|
||||
df_map_data = pd.merge(df_map_data, ejdl_adj[['EJDL', '平差系数']], on='EJDL', how='left')
|
||||
df_map_data['平差系数'] = df_map_data['平差系数'].fillna(1) # 未匹配到的二级地类系数=1
|
||||
# 计算平差后的制图面积
|
||||
df_map_data['制图面积'] = df_map_data['制图面积_原始'] * df_map_data['平差系数']
|
||||
# 重新计算面积占比(基于平差后的面积)
|
||||
total_adjusted_area = df_map_data['制图面积'].sum()
|
||||
df_map_data['面积占比'] = df_map_data['制图面积'] / total_adjusted_area
|
||||
df_map_data = clean_df(df_map_data, ['YJDL', 'EJDL'])
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['YJDL', 'EJDL', 'GRIDCODE']],
|
||||
df_map_data[['YJDL', 'EJDL', 'GRIDCODE']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['YJDL', 'EJDL', 'GRIDCODE'], how='left')
|
||||
df_final = pd.merge(df_final, df_map_data, on=['YJDL', 'EJDL', 'GRIDCODE'], how='left')
|
||||
|
||||
# (可选) 按“一级地类”和“二级地类”排序
|
||||
in_ejdl_order = ejdl_order + [x for x in df_final['EJDL'].unique() if x not in ejdl_order]
|
||||
df_final["YJDL"] = pd.Categorical(df_final['YJDL'], categories=yjdl_order, ordered=True)
|
||||
df_final["EJDL"] = pd.Categorical(df_final['EJDL'], categories=in_ejdl_order, ordered=True)
|
||||
df_final["GRIDCODE"] = pd.Categorical(df_final['GRIDCODE'], categories=sorted(df_final['GRIDCODE'].unique()), ordered=True)
|
||||
df_final.sort_values(['YJDL', 'EJDL', 'GRIDCODE'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
|
||||
# 写入EXCEL 表2
|
||||
def write_to_excel_table2(df, output_path, prop_config):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同土地利用类型属性变化统计"
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
cell_font = Font(name='等线', size=11)
|
||||
center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
|
||||
top=Side(style='thin'), bottom=Side(style='thin'))
|
||||
|
||||
def apply_style(cell_range, font, alignment=None, border=None):
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
if alignment: cell.alignment = alignment
|
||||
if border: cell.border = border
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土地利用类型'
|
||||
ws.merge_cells('C1:E1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('F1:G1'); ws['F1'] = '制图统计'
|
||||
|
||||
ws['A2'] = '一级'
|
||||
ws['B2'] = '二级'
|
||||
ws['C2'] = '质地类型'
|
||||
ws['D2'] = '数量/个'
|
||||
ws['E2'] = '占比%'
|
||||
ws['F2'] = '面积/亩'
|
||||
ws['G2'] = '占比%'
|
||||
|
||||
level_dict = prop_config['标准等级']
|
||||
# 创建两个列表来分别存储上段和下段范围
|
||||
upper_ranges = {value: key for key, value in level_dict.items()}
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('YJDL', sort=False, observed=False):
|
||||
if group_yl_df.empty:
|
||||
continue
|
||||
|
||||
print(f"正在写入一级地类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 按二级地类分组
|
||||
for ej, group_ej_df in group_yl_df.groupby('EJDL', sort=False, observed=False):
|
||||
if group_ej_df.empty:
|
||||
continue
|
||||
|
||||
print(f"正在写入二级地类: {ej}...")
|
||||
ej_start_row = current_row
|
||||
|
||||
# 按“土壤质地分级”分组
|
||||
for idx, row_data in group_ej_df.iterrows():
|
||||
# 填充土壤质地分类
|
||||
ws.cell(row=current_row, column=3).value = str(row_data['GRIDCODE'])
|
||||
|
||||
# 填充样点数据
|
||||
ws.cell(row=current_row, column=4).value = row_data['样点数'] if not np.isnan(row_data['样点数']) else '-'
|
||||
ws.cell(row=current_row, column=5).value = round(row_data['样点数占比']*100, 2) if not np.isnan(row_data['样点数占比']) else '-'
|
||||
# 填充制图数据
|
||||
ws.cell(row=current_row, column=6).value = round(row_data['制图面积'], 0) if not np.isnan(row_data['制图面积']) else '-'
|
||||
ws.cell(row=current_row, column=7).value = round(row_data['面积占比']*100, 2) if not np.isnan(row_data['面积占比']) else '-'
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并二级地类单元格
|
||||
if ej_start_row <= current_row:
|
||||
ws.merge_cells(start_row=ej_start_row, start_column=2, end_row=current_row-1, end_column=2)
|
||||
ws.cell(row=ej_start_row, column=2).value = ej
|
||||
|
||||
# 一级地类合计行
|
||||
ws.merge_cells(start_row=current_row, start_column=2, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=2).value = '合计'
|
||||
ws.cell(row=current_row, column=4).value = round(group_yl_df['样点数'].sum(), 0) if not np.isnan(group_yl_df['样点数'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=5).value = round(group_yl_df['样点数占比'].sum()*100, 2) if not np.isnan(group_yl_df['样点数占比'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=6).value = round(group_yl_df['制图面积'].sum(), 0) if not np.isnan(group_yl_df['制图面积'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=7).value = round(group_yl_df['面积占比'].sum()*100, 2) if not np.isnan(group_yl_df['面积占比'].sum()) else '-'
|
||||
|
||||
# 合并一级地类单元格(修正合并范围)
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
current_row += 1
|
||||
|
||||
# --- 5. 全区汇总行 ---
|
||||
ws.cell(row=current_row, column=1).value = '全区汇总'
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=4).value = round(df_to_write['样点数'].sum(), 0) if not np.isnan(df_to_write['样点数'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=5).value = round(df_to_write['样点数占比'].sum()*100, 2) if not np.isnan(df_to_write['样点数占比'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=6).value = round(df_to_write['制图面积'].sum(), 0) if not np.isnan(df_to_write['制图面积'].sum()) else '-'
|
||||
ws.cell(row=current_row, column=7).value = round(df_to_write['面积占比'].sum()*100, 2) if not np.isnan(df_to_write['面积占比'].sum()) else '-'
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
apply_style(f'A1:{max_col_letter}{current_row}', cell_font, center_align, thin_border)
|
||||
apply_style(f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
dims = {}
|
||||
for row in ws.rows:
|
||||
for cell in row:
|
||||
if cell.value:
|
||||
merged_range = next((range for range in ws.merged_cells.ranges if cell.coordinate in range), None)
|
||||
if get_merge_type(merged_range) == 'column':
|
||||
continue
|
||||
cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value))
|
||||
dims[cell.column] = max(dims.get(cell.column, 0), cell_len)
|
||||
# 设置列宽
|
||||
for col, value in dims.items():
|
||||
ws.column_dimensions[get_column_letter(int(col))].width = value + 5
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
# def main(gdb_path, soil_prop_name, dltb_features, reclassed_feature, output_path,target_areas_df, prop_config):
|
||||
# print(target_areas_df)
|
||||
# df = pd.read_csv(r"D:\ProgramData\ArcGis_Py\测试数据.csv")
|
||||
# output_path = r"E:\@三普属性图出图\测试\AAA.xlsx"
|
||||
# write_to_excel_table2(df,output_path,prop_config)
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, dltb_features, reclassed_feature, output_path,target_areas_df, prop_config):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path, f"{soil_prop_name}土地利用类型土壤.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
|
||||
out_table_mean = r"in_memory/out_table_mean"
|
||||
temp_files.append(out_table_mean)
|
||||
if not arcpy.Exists(out_table_mean):
|
||||
# 2.使用交集制表计算每个TRZD的面积
|
||||
arcpy.analysis.TabulateIntersection(dltb_features, "YJDL_EJDL", reclassed_feature, out_table_mean, "gridcode", out_units="SQUARE_METERS")
|
||||
|
||||
dltb_df = pd.DataFrame(arcpy.da.TableToNumPyArray(out_table_mean, ["YJDL_EJDL", "gridcode", "AREA"]))
|
||||
|
||||
|
||||
# 生成表1 土壤属性分级分布 的统计Excel报告
|
||||
final_dataframe = process_data_for_table2(gdb_path, soil_prop_name, dltb_df, target_areas_df)
|
||||
|
||||
write_to_excel_table2(final_dataframe, output_excel_path, prop_config)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# df = pd.read_csv(r"D:\ProgramData\ArcGis_Py\测试数据.csv")
|
||||
# output_path = r"E:\@三普属性图出图\测试\AAA.xlsx"
|
||||
# write_to_excel_table2(df,output_path)
|
||||
328
tools/core/soil_prop_stats/B2土地利用类型土壤属性.py
Normal file
328
tools/core/soil_prop_stats/B2土地利用类型土壤属性.py
Normal file
@@ -0,0 +1,328 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from tools.config.pandas_field_cal_func import calculate_ejdl, calculate_yjdl
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
from tools.core.utils.excel_utils import ExcelStyleUtils
|
||||
|
||||
|
||||
yjdl_order = ["耕地", "园地", "林地", "草地", "其他"]
|
||||
ejdl_order = ["水田", "旱地", "水浇地", "果园", "茶园", "橡胶园", "其他园地"]
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table2(gdb_path, soil_prop_feature_name, df_dltb, target_areas_df):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns):
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_feature_name
|
||||
sample_table_path = os.path.join(gdb_path, soil_prop_feature_name)
|
||||
sample_fields = ['TDLYLX', field_name]
|
||||
df_samples = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(sample_table_path, sample_fields, skip_nulls=False))
|
||||
df_samples = clean_df(df_samples, [field_name])
|
||||
|
||||
df_samples["YJDL"] = df_samples['TDLYLX'].apply(calculate_yjdl)
|
||||
df_samples["EJDL"] = df_samples['TDLYLX'].apply(calculate_ejdl)
|
||||
df_samples[field_name] = df_samples[field_name].astype(float)
|
||||
|
||||
# 按 YJDL, EJDL 分组,计算 属性 的均值
|
||||
df_sample_means = df_samples.groupby(['YJDL', 'EJDL'])[field_name].agg(['count', 'max', 'min', 'mean']).reset_index()
|
||||
|
||||
# ==b. 处理制图数据,获各等级制图面积
|
||||
df_dltb["YJDL"] = df_dltb['YJDL_EJDL'].apply(lambda x: x.split('_')[0])
|
||||
df_dltb["EJDL"] = df_dltb["YJDL_EJDL"].apply(lambda x: x.split('_')[1])
|
||||
df_dltb = clean_df(df_dltb, ['YJDL', 'EJDL'])
|
||||
df_dltb.rename(columns={'MEAN': '制图均值', 'COUNT': '制图样点数'}, inplace=True)
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['YJDL', 'EJDL']],
|
||||
df_dltb[['YJDL', 'EJDL']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['YJDL', 'EJDL'], how='left')
|
||||
df_final = pd.merge(df_final, df_dltb, on=['YJDL', 'EJDL'], how='left')
|
||||
df_final = pd.merge(df_final, target_areas_df, on=['EJDL'], how='left')
|
||||
|
||||
# (可选) 按“一级地类”和“二级地类”排序
|
||||
in_ejdl_order = ejdl_order + [x for x in df_final['EJDL'].unique() if x not in ejdl_order]
|
||||
df_final["YJDL"] = pd.Categorical(df_final['YJDL'], categories=yjdl_order, ordered=True)
|
||||
df_final["EJDL"] = pd.Categorical(df_final['EJDL'], categories=in_ejdl_order, ordered=True)
|
||||
df_final.sort_values(['YJDL', 'EJDL'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
|
||||
# 写入EXCEL 表2
|
||||
def write_to_excel_table2(df, output_path, prop_config:dict, soil_prop_name: str = ''):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同土地利用类型属性变化统计"
|
||||
|
||||
# 获取属性单位
|
||||
special_prop = ['耕作层厚度','阳离子','有机质','pH','有效磷','速效钾','交换性钙','交换性镁','有效硫','有效铁','有效锰','有效硅','全钾']
|
||||
fsn_props = ['砂粒含量','粉粒含量','黏粒含量','有效土层厚度']
|
||||
prop_name_str = prop_config.get('项目分级','')
|
||||
if prop_name_str:
|
||||
split_name = prop_name_str.split('\n')[0].strip()
|
||||
if split_name in special_prop:
|
||||
prop_name = '1f'
|
||||
elif split_name in fsn_props:
|
||||
prop_name = '0f'
|
||||
else:
|
||||
prop_name = '2f'
|
||||
else:
|
||||
prop_name = '1f'
|
||||
|
||||
prop_unit_str = prop_config.get('分级标准', '')
|
||||
if prop_unit_str:
|
||||
prop_unit = prop_unit_str.split('\n')[1].strip()
|
||||
else:
|
||||
prop_unit = ''
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土地利用类型'
|
||||
ws.merge_cells('C1:E1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('F1:G1'); ws['F1'] = '制图统计'
|
||||
|
||||
ws['A2'] = '一级'
|
||||
ws['B2'] = '二级'
|
||||
ws['C2'] = '均值/' + prop_unit
|
||||
ws['D2'] = '范围/' + prop_unit
|
||||
ws['E2'] = '数量/个'
|
||||
ws['F2'] = '均值/' + prop_unit
|
||||
ws['G2'] = '面积/亩'
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
filtered_props = ['ECA', 'EMG', 'ACU', 'AZN', 'AFE', 'AMN', 'AMO', 'AB', 'AS1', 'TSE']
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('YJDL', sort=False, observed=False):
|
||||
|
||||
print(f"正在写入一级地类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 遍历该一级地类下的所有“二级地类”
|
||||
for _, row_data in group_yl_df.iterrows():
|
||||
ws.cell(row=current_row, column=2).value = row_data['EJDL']
|
||||
|
||||
# 填充样点数据
|
||||
sample_mean = row_data.get('mean')
|
||||
if pd.notna(sample_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{sample_mean:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=4).value = f"{row_data.get('min', '-'):.{prop_name}}~{row_data.get('max', '-'):.{prop_name}}"
|
||||
ws.cell(row=current_row, column=5).value = row_data.get('count', '-')
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
|
||||
# 填充制图数据
|
||||
map_mean = row_data.get('制图均值')
|
||||
if pd.notna(map_mean):
|
||||
ws.cell(row=current_row, column=6).value = f"{map_mean:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=7).value = f"{row_data.get('面积', '-'):.0f}"
|
||||
else:
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
ws.cell(row=current_row, column=7).value = "-"
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 计算并写入“合计”行
|
||||
if ws.cell(row=current_row-1, column=2).value in ["林地", "草地", "其他"]:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=yl_start_row, end_column=2)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
if soil_prop_name in filtered_props:
|
||||
ws.cell(row=yl_start_row, column=6).value = "-"
|
||||
ws.cell(row=yl_start_row, column=7).value = "-"
|
||||
|
||||
continue
|
||||
|
||||
ws.cell(row=current_row, column=2).value = '合计'
|
||||
|
||||
# 计算合计行的均值 (均值的均值)
|
||||
total_count = group_yl_df['count'].sum()
|
||||
weighted_sum = group_yl_df['mean']*group_yl_df['count']
|
||||
if not weighted_sum.empty and total_count != 0:
|
||||
total_sample_mean = weighted_sum.sum()/group_yl_df['count'].sum()
|
||||
else:
|
||||
total_sample_mean = None
|
||||
min_min, max_max = group_yl_df['min'].min(), group_yl_df['max'].max()
|
||||
|
||||
if pd.notna(total_sample_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{total_sample_mean:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=4).value = f"{min_min:.{prop_name}}~{max_max:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=5).value = f"{total_count:.0f}"
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
|
||||
# b. **【核心修正】: 计算合计行的“制图均值”(加权平均)**
|
||||
# 准备加权平均的分子和分母
|
||||
weighted_sum = 0
|
||||
total_count = 0
|
||||
|
||||
# 遍历当前一级地类分组中的每一行
|
||||
for _, row in group_yl_df.iterrows():
|
||||
mean_val = row.get('制图均值')
|
||||
count_val = row.get('制图样点数')
|
||||
|
||||
# 只有当均值和样点数都存在且有效时,才参与计算
|
||||
if pd.notna(mean_val) and pd.notna(count_val) and count_val > 0:
|
||||
weighted_sum += mean_val * count_val # Σ (mean * count)
|
||||
total_count += count_val # Σ (count)
|
||||
|
||||
# 计算加权平均值
|
||||
weighted_avg = (weighted_sum / total_count) if total_count > 0 else 0
|
||||
total_area = group_yl_df['面积'].sum()
|
||||
|
||||
if weighted_avg > 0:
|
||||
ws.cell(row=current_row, column=6).value = f"{weighted_avg:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=7).value = f"{total_area:.0f}"
|
||||
else:
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
ws.cell(row=current_row, column=7).value = "-"
|
||||
|
||||
# 合并“一级地类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 计算全区的均值、范围、数量
|
||||
if soil_prop_name in filtered_props:
|
||||
# 只基于耕地和园地计算全区统计
|
||||
df_for_total = df_to_write[df_to_write['YJDL'].isin(['耕地', '园地'])].copy()
|
||||
print(f"全区统计过滤:仅基于耕地和园地(YJDL in ['耕地', '园地'])")
|
||||
else:
|
||||
df_for_total = df_to_write.copy()
|
||||
|
||||
# 使用 df_for_total 进行后续计算
|
||||
total_weighted_sum = df_for_total['mean'] * df_for_total['count']
|
||||
total_counts = df_for_total['count'].sum()
|
||||
if total_counts > 0:
|
||||
total_mean = total_weighted_sum.sum() / total_counts
|
||||
else:
|
||||
total_mean = None
|
||||
|
||||
if not df_for_total.empty:
|
||||
total_range = f"{df_for_total['min'].min():.{prop_name}}~{df_for_total['max'].max():.{prop_name}}"
|
||||
total_zhitu_weighted_sum = df_for_total['制图均值']*df_for_total['面积']
|
||||
total_areas = df_for_total['面积'].sum()
|
||||
if total_areas > 0:
|
||||
total_zhitu_mean = total_zhitu_weighted_sum.sum() / total_areas
|
||||
else:
|
||||
total_zhitu_mean = None
|
||||
else:
|
||||
total_range = "-"
|
||||
total_zhitu_mean = None
|
||||
total_areas = 0
|
||||
|
||||
# 填充全区统计行
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=2)
|
||||
ws.cell(row=current_row, column=1).value = '全区'
|
||||
if pd.notna(total_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{total_mean:.{prop_name}}"
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = total_range
|
||||
ws.cell(row=current_row, column=5).value = f"{total_counts:.0f}" if total_counts > 0 else "-"
|
||||
if pd.notna(total_zhitu_mean):
|
||||
ws.cell(row=current_row, column=6).value = f"{total_zhitu_mean:.{prop_name}}"
|
||||
else:
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
ws.cell(row=current_row, column=7).value = f"{total_areas:.0f}" if total_areas > 0 else "-"
|
||||
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws,f'A1:{max_col_letter}{current_row}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
# 设置列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, dltb_features, soil_prop_tif, output_path,target_areas_df, prop_config):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path, f"{soil_prop_name}土地利用类型土壤.xlsx") # 生成的Excel报告文件路径
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
|
||||
out_table_mean = r"in_memory/out_table_mean"
|
||||
temp_files.append(out_table_mean)
|
||||
if not arcpy.Exists(out_table_mean):
|
||||
# 2.用arcpy.sa.ZonalStatisticsAsTable 以表格进行分区统计
|
||||
arcpy.sa.ZonalStatisticsAsTable(dltb_features, "YJDL_EJDL", soil_prop_tif, out_table_mean, "DATA", "MEAN")
|
||||
|
||||
dltb_df = pd.DataFrame(arcpy.da.TableToNumPyArray(out_table_mean, ["YJDL_EJDL", "MEAN", "COUNT"]))
|
||||
|
||||
|
||||
# 生成表1 土壤属性分级分布 的统计Excel报告
|
||||
final_dataframe = process_data_for_table2(gdb_path, soil_prop_name, dltb_df, target_areas_df)
|
||||
|
||||
# final_dataframe = process_data_for_table5_2(gdb_path, out_table_area, sample_table_name, df_with_factors)
|
||||
write_to_excel_table2(final_dataframe, output_excel_path, prop_config, soil_prop_name)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
446
tools/core/soil_prop_stats/B3_TRZD12不同土壤类型土壤属性.py
Normal file
446
tools/core/soil_prop_stats/B3_TRZD12不同土壤类型土壤属性.py
Normal file
@@ -0,0 +1,446 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Border, Side, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from tools.config.pandas_field_cal_func import calculate_muyan, calculate_muzhi
|
||||
|
||||
from tools.config.custom_sort import yl_order, ts_order
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
# 判断单元格类型
|
||||
def get_merge_type(merged_range):
|
||||
"""
|
||||
判断合并类型
|
||||
返回: 'row'(行合并), 'column'(列合并), 'both'(行列合并)或 None(不是合并单元格)
|
||||
"""
|
||||
if not merged_range:
|
||||
return None
|
||||
|
||||
min_row, max_row = merged_range.min_row, merged_range.max_row
|
||||
min_col, max_col = merged_range.min_col, merged_range.max_col
|
||||
|
||||
if max_row > min_row and max_col > min_col:
|
||||
return 'both' # 同时跨行和跨列
|
||||
elif max_row > min_row:
|
||||
return 'row' # 行合并(垂直合并)
|
||||
elif max_col > min_col:
|
||||
return 'column' # 列合并(水平合并)
|
||||
else:
|
||||
return None # 实际上不是合并单元格
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table3(soil_prop_name, df_trlx_sample, df_trlx_zhitu, df_trlx, target_areas_df):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns) -> pd.DataFrame:
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_name
|
||||
sample_fields = ['YL', 'TS', field_name]
|
||||
df_samples = clean_df(df_trlx_sample, sample_fields)
|
||||
df_samples["GRIDCODE"] = df_samples[field_name].astype(int)
|
||||
|
||||
# 通过土属计算母岩母质
|
||||
df_samples['母岩'] = df_samples['TS'].apply(calculate_muyan)
|
||||
df_samples['母质'] = df_samples['母岩'].apply(calculate_muzhi)
|
||||
# 按 YJDL, EJDL 分组
|
||||
df_sample_means = df_samples.groupby(['YL', 'TS', 'GRIDCODE']).size().reset_index(name="样点数")
|
||||
total_sample_count = df_sample_means['样点数'].sum()
|
||||
df_sample_means['样点数占比'] = df_sample_means['样点数'] / total_sample_count
|
||||
# df_sample_mymz = df_samples.groupby(['母质', '母岩', 'TZ'])[field_name].agg(['count', 'mean', 'median']).reset_index()
|
||||
# print(df_sample_mymz)
|
||||
|
||||
# ==b. 处理制图数据,获各等级制图面积
|
||||
df_trlx_zhitu["YL"] = df_trlx_zhitu['YL_TS'].apply(lambda x: x.split('_')[0])
|
||||
df_trlx_zhitu["TS"] = df_trlx_zhitu["YL_TS"].apply(lambda x: x.split('_')[1])
|
||||
df_trlx_zhitu.columns = df_trlx_zhitu.columns.str.upper()
|
||||
df_trlx_zhitu = clean_df(df_trlx_zhitu, ['YL', 'TS'])
|
||||
|
||||
df_map_data = df_trlx_zhitu.groupby(["YL","TS", "GRIDCODE"]).agg({"AREA": "sum"}).reset_index()
|
||||
df_map_data['制图面积_原始'] = df_map_data['AREA'] * 0.0015 # 单位:亩
|
||||
|
||||
|
||||
# ==c. 处理制图数据,获各等级制图面积
|
||||
df_trlx = clean_df(df_trlx, ['YL', 'TS'])
|
||||
df_trlx["面积"] = df_trlx["Shape@Area"] * 0.0015
|
||||
|
||||
# 拿到目标df总面积,计算比例进行平差
|
||||
target_areas = target_areas_df['面积'].sum()
|
||||
original_area = df_trlx['面积'].sum()
|
||||
adjusted_area_yz = target_areas / original_area
|
||||
df_trlx["面积"] = df_trlx["面积"] * adjusted_area_yz
|
||||
df_trlx_area = df_trlx.groupby(['YL', 'TS'])['面积'].sum().reset_index()
|
||||
df_trlx_area['面积'] = pd.to_numeric(df_trlx_area['面积'], errors='coerce').fillna(0)
|
||||
# ==========================
|
||||
# 第三步:按二级地类分组计算平差系数
|
||||
# 先计算每个二级地类的原始合计面积
|
||||
ts_original_sum = df_map_data.groupby('TS')['制图面积_原始'].sum().reset_index()
|
||||
ts_original_sum.rename(columns={'制图面积_原始': '原始合计面积'}, inplace=True)
|
||||
# 合并目标面积
|
||||
ts_adj = pd.merge(ts_original_sum, df_trlx_area, on='TS', how='left')
|
||||
ts_adj.rename(columns={'面积': '目标合计面积'}, inplace=True)
|
||||
# 填充无目标面积的二级地类(目标面积=原始面积,平差系数=1)
|
||||
ts_adj['目标合计面积'] = ts_adj['目标合计面积'].fillna(ts_adj['原始合计面积'])
|
||||
# 计算平差系数(目标面积 / 原始面积,避免除以0)
|
||||
ts_adj['平差系数'] = ts_adj['目标合计面积'] / ts_adj['原始合计面积'].replace(0, 1)
|
||||
ts_adj['平差系数'] = ts_adj['平差系数'].fillna(1) # 极端情况填充1
|
||||
|
||||
# 第四步:应用平差系数到每个质地级别的制图面积
|
||||
df_map_data = pd.merge(df_map_data, ts_adj[['TS', '平差系数']], on='TS', how='left')
|
||||
df_map_data['平差系数'] = df_map_data['平差系数'].fillna(1) # 未匹配到的二级地类系数=1
|
||||
# 计算平差后的制图面积
|
||||
df_map_data['制图面积'] = df_map_data['制图面积_原始'] * df_map_data['平差系数']
|
||||
# 重新计算面积占比(基于平差后的面积)
|
||||
total_adjusted_area = df_map_data['制图面积'].sum()
|
||||
df_map_data['面积占比'] = df_map_data['制图面积'] / total_adjusted_area
|
||||
df_map_data = clean_df(df_map_data, ['YL', 'TS'])
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['YL', 'TS', 'GRIDCODE']],
|
||||
df_map_data[['YL', 'TS', 'GRIDCODE']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['YL', 'TS', 'GRIDCODE'], how='left')
|
||||
df_final = pd.merge(df_final, df_map_data, on=['YL', 'TS', 'GRIDCODE'], how='left')
|
||||
|
||||
# (可选) 按“亚类”和“土属”排序
|
||||
in_yl_order = yl_order + [x for x in df_final['YL'].unique() if x not in yl_order]
|
||||
in_ts_order = ts_order + [x for x in df_final['TS'].unique() if x not in ts_order]
|
||||
df_final["YL"] = pd.Categorical(df_final['YL'], categories=in_yl_order, ordered=True)
|
||||
df_final["TS"] = pd.Categorical(df_final['TS'], categories=in_ts_order, ordered=True)
|
||||
df_final["GRIDCODE"] = pd.Categorical(df_final['GRIDCODE'], categories=sorted(df_final['GRIDCODE'].unique()), ordered=True)
|
||||
df_final.sort_values(['YL', 'TS', 'GRIDCODE'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
# return df_final, df_sample_mymz
|
||||
|
||||
# 写入EXCEL 表2
|
||||
def write_to_excel_table3(df, output_path, prop_config:dict):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同土壤类型属性变化统计"
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
cell_font = Font(name='等线', size=11)
|
||||
center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
|
||||
top=Side(style='thin'), bottom=Side(style='thin'))
|
||||
|
||||
def apply_style(cell_range, font, alignment=None, border=None):
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
if alignment: cell.alignment = alignment
|
||||
if border: cell.border = border
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土壤类型'
|
||||
ws.merge_cells('C1:E1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('F1:G1'); ws['F1'] = '制图统计'
|
||||
|
||||
ws['A2'] = '亚类'
|
||||
ws['B2'] = '土属'
|
||||
ws['C2'] = '质地类型'
|
||||
ws['D2'] = '数量/个'
|
||||
ws['E2'] = '占比%'
|
||||
ws['F2'] = '面积/亩'
|
||||
ws['G2'] = '占比%'
|
||||
|
||||
level_dict = prop_config['标准等级']
|
||||
# 创建两个列表来分别存储上段和下段范围
|
||||
upper_ranges = {value: key for key, value in level_dict.items()}
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('YL', sort=False, observed=True):
|
||||
if group_yl_df.empty:
|
||||
continue
|
||||
|
||||
print(f"正在写入亚类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 按二级地类分组
|
||||
for ts, group_ts_df in group_yl_df.groupby('TS', sort=False, observed=False):
|
||||
if group_ts_df.empty:
|
||||
continue
|
||||
|
||||
print(f"正在写入二级地类: {ts}...")
|
||||
ts_start_row = current_row
|
||||
|
||||
# 遍历该亚类下下的所有“土属”
|
||||
for _, row_data in group_ts_df.iterrows():
|
||||
ws.cell(row=current_row, column=3).value = upper_ranges.get(str(row_data['GRIDCODE']), '-')
|
||||
|
||||
# 填充样点数据
|
||||
ws.cell(row=current_row, column=4).value = row_data['样点数'] if not np.isnan(row_data['样点数']) else '-'
|
||||
ws.cell(row=current_row, column=5).value = round(row_data['样点数占比']*100, 2) if not np.isnan(row_data['样点数占比']) else '-'
|
||||
# 填充制图数据
|
||||
ws.cell(row=current_row, column=6).value = round(row_data['制图面积'], 0) if not np.isnan(row_data['制图面积']) else '-'
|
||||
ws.cell(row=current_row, column=7).value = round(row_data['面积占比']*100, 2) if not np.isnan(row_data['面积占比']) else '-'
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并二级地类单元格
|
||||
if ts_start_row <= current_row:
|
||||
ws.merge_cells(start_row=ts_start_row, start_column=2, end_row=current_row-1, end_column=2)
|
||||
ws.cell(row=ts_start_row, column=2).value = ts
|
||||
|
||||
# 合并“一级地类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row-1, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
# 计算全区的均值、范围、数量
|
||||
total_areas = df_to_write['制图面积'].sum()
|
||||
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=1).value = '全区'
|
||||
ws.cell(row=current_row, column=4).value = df_to_write['样点数'].sum()
|
||||
ws.cell(row=current_row, column=5).value = round(df_to_write['样点数占比'].sum()*100, 2)
|
||||
ws.cell(row=current_row, column=6).value = round(total_areas, 0)
|
||||
ws.cell(row=current_row, column=7).value = round(df_to_write['面积占比'].sum()*100, 2)
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
apply_style(f'A1:{max_col_letter}{current_row}', cell_font, center_align, thin_border)
|
||||
apply_style(f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
dims = {}
|
||||
for row in ws.rows:
|
||||
for cell in row:
|
||||
if cell.value:
|
||||
merged_range = next((range for range in ws.merged_cells.ranges if cell.coordinate in range), None)
|
||||
if get_merge_type(merged_range) == 'column':
|
||||
continue
|
||||
cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value))
|
||||
dims[cell.column] = max(dims.get(cell.column, 0), cell_len)
|
||||
# 设置列宽
|
||||
for col, value in dims.items():
|
||||
ws.column_dimensions[get_column_letter(int(col))].width = value + 5
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
# 母岩母质表
|
||||
def write_to_excel_table4(df:pd.DataFrame, output_path, prop_config):
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "母岩母质土壤属性统计"
|
||||
|
||||
# 获取属性单位
|
||||
special_prop = ['耕作层厚度','阳离子','有机质','pH','有效磷','速效钾','交换性钙','交换性镁','有效硫','有效铁','有效锰','有效硅','全钾','沙粒','粉粒','粘粒']
|
||||
prop_name_str = prop_config.get('项目分级','')
|
||||
if prop_name_str:
|
||||
prop_name = prop_name_str.split('\n')[0].strip() in special_prop
|
||||
else:
|
||||
prop_name = False
|
||||
|
||||
prop_unit_str = prop_config.get('分级标准', '')
|
||||
if prop_unit_str:
|
||||
prop_unit = prop_unit_str.split('\n')[1].strip()
|
||||
else:
|
||||
prop_unit = ''
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
cell_font = Font(name='等线', size=11)
|
||||
center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
|
||||
top=Side(style='thin'), bottom=Side(style='thin'))
|
||||
|
||||
def apply_style(cell_range, font, alignment=None, border=None):
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
if alignment: cell.alignment = alignment
|
||||
if border: cell.border = border
|
||||
|
||||
# 写入表头
|
||||
headers = ['母岩母质','', '土种类型', '样点统计', '']
|
||||
ws.append(headers)
|
||||
ws.append(['', '', '', f'均值/{prop_unit}', '数量/个'])
|
||||
|
||||
# 合并表头单元格
|
||||
ws.merge_cells('A1:B2') # 母岩母质
|
||||
ws.merge_cells('C1:C2') # 土种类型
|
||||
ws.merge_cells('D1:E1') # 样点统计
|
||||
|
||||
current_row = 3
|
||||
|
||||
# 按母质和母岩进行分组
|
||||
grouped = df.groupby(['母质', '母岩']).agg({
|
||||
'TZ': lambda x: ','.join(x), # 将土种名称用逗号连接
|
||||
'mean': 'mean', # 计算均值
|
||||
'count': 'sum' # 计算总数
|
||||
}).reset_index()
|
||||
|
||||
parent_materials = grouped['母质'].unique()
|
||||
|
||||
for parent_material in parent_materials:
|
||||
|
||||
parent_material_row = current_row
|
||||
|
||||
if parent_material == '未知':
|
||||
continue
|
||||
|
||||
material_group = grouped[grouped['母质'] == parent_material]
|
||||
# 写入母岩母质分组(只在第一行显示)
|
||||
first_row_in_group = True
|
||||
|
||||
for _, row_data in material_group.iterrows():
|
||||
if first_row_in_group:
|
||||
# 第一行显示母岩母质名称
|
||||
ws.cell(row=current_row, column=1, value=parent_material)
|
||||
first_row_in_group = False
|
||||
else:
|
||||
# 后续行留空
|
||||
ws.cell(row=current_row, column=1, value='')
|
||||
|
||||
# 写入母岩类型
|
||||
ws.cell(row=current_row, column=2, value=row_data['母岩'])
|
||||
|
||||
# 写入土种类型(所有土种用逗号连接)
|
||||
ws.cell(row=current_row, column=3, value=row_data['TZ'])
|
||||
|
||||
# 写入统计数据
|
||||
ws.cell(row=current_row, column=4, value=round(row_data['mean'], 1))
|
||||
ws.cell(row=current_row, column=5, value=row_data['count'])
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并母岩母质分组
|
||||
if parent_material_row < current_row:
|
||||
ws.merge_cells(start_row=parent_material_row, start_column=1, end_row=current_row - 1, end_column=1)
|
||||
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=1, value='全区')
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
apply_style(f'A1:{max_col_letter}{current_row}', cell_font, center_align, thin_border)
|
||||
apply_style(f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
# 设置列宽
|
||||
ws.column_dimensions["A"].width = 20
|
||||
ws.column_dimensions["B"].width = 20
|
||||
ws.column_dimensions["C"].width = 30
|
||||
ws.column_dimensions["D"].width = 20
|
||||
ws.column_dimensions["E"].width = 20
|
||||
|
||||
# 保存文件
|
||||
wb.save(output_path)
|
||||
print(f"数据已成功写入到 {output_path}")
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, trlx_features, reclassed_feature, output_path,target_areas_df, prop_config):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path,f"{soil_prop_name}不同土壤类型土壤.xlsx") # 生成的Excel报告文件路径
|
||||
# output_excel4_path = os.path.join(output_path,f"{soil_prop_name}不同母岩母质土壤属性.xlsx")
|
||||
soil_prop_features = os.path.join(gdb_path,soil_prop_name)
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
|
||||
temp_out_features = r"in_memory/temp_out_type_features"
|
||||
out_table_mean = r"in_memory/out_table_type_mean"
|
||||
temp_files.append(temp_out_features)
|
||||
temp_files.append(out_table_mean)
|
||||
|
||||
# 2. 用样点进行空间连接到土壤类型图斑
|
||||
fields_to_keep = {
|
||||
soil_prop_features: [soil_prop_name],
|
||||
trlx_features: ["YL", "TS", "TZ"],
|
||||
}
|
||||
|
||||
field_mappings = arcpy.FieldMappings()
|
||||
|
||||
for join_features in fields_to_keep.keys():
|
||||
for field_name in fields_to_keep[join_features]:
|
||||
try:
|
||||
field_map = arcpy.FieldMap()
|
||||
field_map.addInputField(join_features, field_name)
|
||||
field_map.mergeRule = "First" # 对所有连接字段使用 "First" 规则
|
||||
field_mappings.addFieldMap(field_map)
|
||||
except Exception as e:
|
||||
print(f"警告: 添加字段 '{field_name}' (来自 '{join_features}') 时出错,将跳过。错误信息: {e}")
|
||||
# 空间连接
|
||||
arcpy.analysis.SpatialJoin(soil_prop_features, trlx_features, temp_out_features, "JOIN_ONE_TO_ONE", "KEEP_ALL",field_mappings, "INTERSECT")
|
||||
|
||||
# 3. 交集制表计算每个TRZD的面积
|
||||
arcpy.analysis.TabulateIntersection(trlx_features, "YL_TS", reclassed_feature, out_table_mean, "gridcode", out_units="SQUARE_METERS")
|
||||
|
||||
trlx_zhitu_df = pd.DataFrame(arcpy.da.TableToNumPyArray(out_table_mean, ["YL_TS", "gridcode", "AREA"]))
|
||||
trlx_sample_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(temp_out_features, ["YL", "TS", "TZ", soil_prop_name]))
|
||||
|
||||
# 获取土壤类型图斑面积
|
||||
trlx_area_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(trlx_features, ["YL", "TS", "Shape@Area"]))
|
||||
|
||||
# 处理表3数据
|
||||
final_dataframe = process_data_for_table3(soil_prop_name,trlx_sample_df, trlx_zhitu_df, trlx_area_df, target_areas_df)
|
||||
# print(final_dataframe)
|
||||
|
||||
# 生成表3
|
||||
write_to_excel_table3(final_dataframe, output_excel_path, prop_config)
|
||||
# 母岩母质表
|
||||
# write_to_excel_table4(df_mymz, output_excel4_path, prop_config)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
465
tools/core/soil_prop_stats/B3_TRZD不同土壤类型土壤属性.py
Normal file
465
tools/core/soil_prop_stats/B3_TRZD不同土壤类型土壤属性.py
Normal file
@@ -0,0 +1,465 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Border, Side, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from tools.config.pandas_field_cal_func import calculate_muyan, calculate_muzhi
|
||||
|
||||
from tools.config.custom_sort import yl_order, ts_order
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
|
||||
# --- 2. 辅助函数 ---
|
||||
def get_prop_level(prop_level):
|
||||
"""根据输入值判断 返回等级"""
|
||||
if pd.isna(prop_level) or prop_level == 0:
|
||||
return "-"
|
||||
# 请根据您的实际分级标准调整这里的阈值
|
||||
if int(prop_level) == 5 or prop_level == "砂质":
|
||||
return "砂质"
|
||||
elif int(prop_level) == 4 or prop_level == "砂壤质":
|
||||
return "砂壤质"
|
||||
elif int(prop_level) == 3 or prop_level == "壤质":
|
||||
return "壤质"
|
||||
elif int(prop_level) == 1 or prop_level == "黏壤质":
|
||||
return "黏壤质"
|
||||
elif int(prop_level) == 2 or prop_level == "黏质":
|
||||
return "黏质"
|
||||
else:
|
||||
return "-"
|
||||
|
||||
# 判断单元格类型
|
||||
def get_merge_type(merged_range):
|
||||
"""
|
||||
判断合并类型
|
||||
返回: 'row'(行合并), 'column'(列合并), 'both'(行列合并)或 None(不是合并单元格)
|
||||
"""
|
||||
if not merged_range:
|
||||
return None
|
||||
|
||||
min_row, max_row = merged_range.min_row, merged_range.max_row
|
||||
min_col, max_col = merged_range.min_col, merged_range.max_col
|
||||
|
||||
if max_row > min_row and max_col > min_col:
|
||||
return 'both' # 同时跨行和跨列
|
||||
elif max_row > min_row:
|
||||
return 'row' # 行合并(垂直合并)
|
||||
elif max_col > min_col:
|
||||
return 'column' # 列合并(水平合并)
|
||||
else:
|
||||
return None # 实际上不是合并单元格
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table3(soil_prop_name, df_trlx_sample, df_trlx_zhitu, df_trlx, target_areas_df):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns) -> pd.DataFrame:
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_name
|
||||
sample_fields = ['YL', 'TS', field_name]
|
||||
df_samples = clean_df(df_trlx_sample, sample_fields)
|
||||
df_samples["GRIDCODE"] = df_samples[field_name]
|
||||
|
||||
# 通过土属计算母岩母质
|
||||
df_samples['母岩'] = df_samples['TS'].apply(calculate_muyan)
|
||||
df_samples['母质'] = df_samples['母岩'].apply(calculate_muzhi)
|
||||
# 按 YJDL, EJDL 分组
|
||||
df_sample_means = df_samples.groupby(['YL', 'TS', 'GRIDCODE']).size().reset_index(name="样点数")
|
||||
total_sample_count = df_sample_means['样点数'].sum()
|
||||
df_sample_means['样点数占比'] = df_sample_means['样点数'] / total_sample_count
|
||||
# df_sample_mymz = df_samples.groupby(['母质', '母岩', 'TZ'])[field_name].agg(['count', 'mean', 'median']).reset_index()
|
||||
# print(df_sample_mymz)
|
||||
|
||||
# ==b. 处理制图数据,获各等级制图面积
|
||||
df_trlx_zhitu["YL"] = df_trlx_zhitu['YL_TS'].apply(lambda x: x.split('_')[0])
|
||||
df_trlx_zhitu["TS"] = df_trlx_zhitu["YL_TS"].apply(lambda x: x.split('_')[1])
|
||||
df_trlx_zhitu.columns = df_trlx_zhitu.columns.str.upper()
|
||||
df_trlx_zhitu = clean_df(df_trlx_zhitu, ['YL', 'TS'])
|
||||
df_trlx_zhitu['GRIDCODE'] = df_trlx_zhitu['GRIDCODE'].apply(get_prop_level)
|
||||
|
||||
df_map_data = df_trlx_zhitu.groupby(["YL","TS", "GRIDCODE"]).agg({"AREA": "sum"}).reset_index()
|
||||
df_map_data['制图面积_原始'] = df_map_data['AREA'] * 0.0015 # 单位:亩
|
||||
|
||||
|
||||
# ==c. 处理制图数据,获各等级制图面积
|
||||
df_trlx = clean_df(df_trlx, ['YL', 'TS'])
|
||||
df_trlx["面积"] = df_trlx["Shape@Area"] * 0.0015
|
||||
|
||||
# 拿到目标df总面积,计算比例进行平差
|
||||
target_areas = target_areas_df['面积'].sum()
|
||||
original_area = df_trlx['面积'].sum()
|
||||
adjusted_area_yz = target_areas / original_area
|
||||
df_trlx["面积"] = df_trlx["面积"] * adjusted_area_yz
|
||||
df_trlx_area = df_trlx.groupby(['YL', 'TS'])['面积'].sum().reset_index()
|
||||
df_trlx_area['面积'] = pd.to_numeric(df_trlx_area['面积'], errors='coerce').fillna(0)
|
||||
# ==========================
|
||||
# 第三步:按二级地类分组计算平差系数
|
||||
# 先计算每个二级地类的原始合计面积
|
||||
ts_original_sum = df_map_data.groupby('TS')['制图面积_原始'].sum().reset_index()
|
||||
ts_original_sum.rename(columns={'制图面积_原始': '原始合计面积'}, inplace=True)
|
||||
# 合并目标面积
|
||||
ts_adj = pd.merge(ts_original_sum, df_trlx_area, on='TS', how='left')
|
||||
ts_adj.rename(columns={'面积': '目标合计面积'}, inplace=True)
|
||||
# 填充无目标面积的二级地类(目标面积=原始面积,平差系数=1)
|
||||
ts_adj['目标合计面积'] = ts_adj['目标合计面积'].fillna(ts_adj['原始合计面积'])
|
||||
# 计算平差系数(目标面积 / 原始面积,避免除以0)
|
||||
ts_adj['平差系数'] = ts_adj['目标合计面积'] / ts_adj['原始合计面积'].replace(0, 1)
|
||||
ts_adj['平差系数'] = ts_adj['平差系数'].fillna(1) # 极端情况填充1
|
||||
|
||||
# 第四步:应用平差系数到每个质地级别的制图面积
|
||||
df_map_data = pd.merge(df_map_data, ts_adj[['TS', '平差系数']], on='TS', how='left')
|
||||
df_map_data['平差系数'] = df_map_data['平差系数'].fillna(1) # 未匹配到的二级地类系数=1
|
||||
# 计算平差后的制图面积
|
||||
df_map_data['制图面积'] = df_map_data['制图面积_原始'] * df_map_data['平差系数']
|
||||
# 重新计算面积占比(基于平差后的面积)
|
||||
total_adjusted_area = df_map_data['制图面积'].sum()
|
||||
df_map_data['面积占比'] = df_map_data['制图面积'] / total_adjusted_area
|
||||
df_map_data = clean_df(df_map_data, ['YL', 'TS'])
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['YL', 'TS', 'GRIDCODE']],
|
||||
df_map_data[['YL', 'TS', 'GRIDCODE']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['YL', 'TS', 'GRIDCODE'], how='left')
|
||||
df_final = pd.merge(df_final, df_map_data, on=['YL', 'TS', 'GRIDCODE'], how='left')
|
||||
|
||||
# (可选) 按“亚类”和“土属”排序
|
||||
in_yl_order = yl_order + [x for x in df_final['YL'].unique() if x not in yl_order]
|
||||
in_ts_order = ts_order + [x for x in df_final['TS'].unique() if x not in ts_order]
|
||||
df_final["YL"] = pd.Categorical(df_final['YL'], categories=in_yl_order, ordered=True)
|
||||
df_final["TS"] = pd.Categorical(df_final['TS'], categories=in_ts_order, ordered=True)
|
||||
df_final["GRIDCODE"] = pd.Categorical(df_final['GRIDCODE'], categories=sorted(df_final['GRIDCODE'].unique()), ordered=True)
|
||||
df_final.sort_values(['YL', 'TS', 'GRIDCODE'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final
|
||||
# return df_final, df_sample_mymz
|
||||
|
||||
# 写入EXCEL 表2
|
||||
def write_to_excel_table3(df, output_path, prop_config:dict):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同土壤类型属性变化统计"
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
cell_font = Font(name='等线', size=11)
|
||||
center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
|
||||
top=Side(style='thin'), bottom=Side(style='thin'))
|
||||
|
||||
def apply_style(cell_range, font, alignment=None, border=None):
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
if alignment: cell.alignment = alignment
|
||||
if border: cell.border = border
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土壤类型'
|
||||
ws.merge_cells('C1:E1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('F1:G1'); ws['F1'] = '制图统计'
|
||||
|
||||
ws['A2'] = '亚类'
|
||||
ws['B2'] = '土属'
|
||||
ws['C2'] = '质地类型'
|
||||
ws['D2'] = '数量/个'
|
||||
ws['E2'] = '占比%'
|
||||
ws['F2'] = '面积/亩'
|
||||
ws['G2'] = '占比%'
|
||||
|
||||
level_dict = prop_config['标准等级']
|
||||
# 创建两个列表来分别存储上段和下段范围
|
||||
upper_ranges = {value: key for key, value in level_dict.items()}
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('YL', sort=False, observed=True):
|
||||
if group_yl_df.empty:
|
||||
continue
|
||||
|
||||
print(f"正在写入亚类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 按二级地类分组
|
||||
for ts, group_ts_df in group_yl_df.groupby('TS', sort=False, observed=False):
|
||||
if group_ts_df.empty:
|
||||
continue
|
||||
|
||||
print(f"正在写入二级地类: {ts}...")
|
||||
ts_start_row = current_row
|
||||
|
||||
# 遍历该亚类下下的所有“土属”
|
||||
for _, row_data in group_ts_df.iterrows():
|
||||
ws.cell(row=current_row, column=3).value = row_data['GRIDCODE']
|
||||
|
||||
# 填充样点数据
|
||||
ws.cell(row=current_row, column=4).value = row_data['样点数'] if not np.isnan(row_data['样点数']) else '-'
|
||||
ws.cell(row=current_row, column=5).value = round(row_data['样点数占比']*100, 2) if not np.isnan(row_data['样点数占比']) else '-'
|
||||
# 填充制图数据
|
||||
ws.cell(row=current_row, column=6).value = round(row_data['制图面积'], 0) if not np.isnan(row_data['制图面积']) else '-'
|
||||
ws.cell(row=current_row, column=7).value = round(row_data['面积占比']*100, 2) if not np.isnan(row_data['面积占比']) else '-'
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并二级地类单元格
|
||||
if ts_start_row <= current_row:
|
||||
ws.merge_cells(start_row=ts_start_row, start_column=2, end_row=current_row-1, end_column=2)
|
||||
ws.cell(row=ts_start_row, column=2).value = ts
|
||||
|
||||
# 合并“一级地类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row-1, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
# 计算全区的均值、范围、数量
|
||||
total_areas = df_to_write['制图面积'].sum()
|
||||
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=1).value = '全区'
|
||||
ws.cell(row=current_row, column=4).value = df_to_write['样点数'].sum()
|
||||
ws.cell(row=current_row, column=5).value = round(df_to_write['样点数占比'].sum()*100, 2)
|
||||
ws.cell(row=current_row, column=6).value = round(total_areas, 0)
|
||||
ws.cell(row=current_row, column=7).value = round(df_to_write['面积占比'].sum()*100, 2)
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
apply_style(f'A1:{max_col_letter}{current_row}', cell_font, center_align, thin_border)
|
||||
apply_style(f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
dims = {}
|
||||
for row in ws.rows:
|
||||
for cell in row:
|
||||
if cell.value:
|
||||
merged_range = next((range for range in ws.merged_cells.ranges if cell.coordinate in range), None)
|
||||
if get_merge_type(merged_range) == 'column':
|
||||
continue
|
||||
cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value))
|
||||
dims[cell.column] = max(dims.get(cell.column, 0), cell_len)
|
||||
# 设置列宽
|
||||
for col, value in dims.items():
|
||||
ws.column_dimensions[get_column_letter(int(col))].width = value + 5
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
# 母岩母质表
|
||||
def write_to_excel_table4(df:pd.DataFrame, output_path, prop_config):
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "母岩母质土壤属性统计"
|
||||
|
||||
# 获取属性单位
|
||||
special_prop = ['耕作层厚度','阳离子','有机质','pH','有效磷','速效钾','交换性钙','交换性镁','有效硫','有效铁','有效锰','有效硅','全钾','沙粒','粉粒','粘粒']
|
||||
prop_name_str = prop_config.get('项目分级','')
|
||||
if prop_name_str:
|
||||
prop_name = prop_name_str.split('\n')[0].strip() in special_prop
|
||||
else:
|
||||
prop_name = False
|
||||
|
||||
prop_unit_str = prop_config.get('分级标准', '')
|
||||
if prop_unit_str:
|
||||
prop_unit = prop_unit_str.split('\n')[1].strip()
|
||||
else:
|
||||
prop_unit = ''
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
cell_font = Font(name='等线', size=11)
|
||||
center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
|
||||
top=Side(style='thin'), bottom=Side(style='thin'))
|
||||
|
||||
def apply_style(cell_range, font, alignment=None, border=None):
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
if alignment: cell.alignment = alignment
|
||||
if border: cell.border = border
|
||||
|
||||
# 写入表头
|
||||
headers = ['母岩母质','', '土种类型', '样点统计', '']
|
||||
ws.append(headers)
|
||||
ws.append(['', '', '', f'均值/{prop_unit}', '数量/个'])
|
||||
|
||||
# 合并表头单元格
|
||||
ws.merge_cells('A1:B2') # 母岩母质
|
||||
ws.merge_cells('C1:C2') # 土种类型
|
||||
ws.merge_cells('D1:E1') # 样点统计
|
||||
|
||||
current_row = 3
|
||||
|
||||
# 按母质和母岩进行分组
|
||||
grouped = df.groupby(['母质', '母岩']).agg({
|
||||
'TZ': lambda x: ','.join(x), # 将土种名称用逗号连接
|
||||
'mean': 'mean', # 计算均值
|
||||
'count': 'sum' # 计算总数
|
||||
}).reset_index()
|
||||
|
||||
parent_materials = grouped['母质'].unique()
|
||||
|
||||
for parent_material in parent_materials:
|
||||
|
||||
parent_material_row = current_row
|
||||
|
||||
if parent_material == '未知':
|
||||
continue
|
||||
|
||||
material_group = grouped[grouped['母质'] == parent_material]
|
||||
# 写入母岩母质分组(只在第一行显示)
|
||||
first_row_in_group = True
|
||||
|
||||
for _, row_data in material_group.iterrows():
|
||||
if first_row_in_group:
|
||||
# 第一行显示母岩母质名称
|
||||
ws.cell(row=current_row, column=1, value=parent_material)
|
||||
first_row_in_group = False
|
||||
else:
|
||||
# 后续行留空
|
||||
ws.cell(row=current_row, column=1, value='')
|
||||
|
||||
# 写入母岩类型
|
||||
ws.cell(row=current_row, column=2, value=row_data['母岩'])
|
||||
|
||||
# 写入土种类型(所有土种用逗号连接)
|
||||
ws.cell(row=current_row, column=3, value=row_data['TZ'])
|
||||
|
||||
# 写入统计数据
|
||||
ws.cell(row=current_row, column=4, value=round(row_data['mean'], 1))
|
||||
ws.cell(row=current_row, column=5, value=row_data['count'])
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并母岩母质分组
|
||||
if parent_material_row < current_row:
|
||||
ws.merge_cells(start_row=parent_material_row, start_column=1, end_row=current_row - 1, end_column=1)
|
||||
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=1, value='全区')
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
apply_style(f'A1:{max_col_letter}{current_row}', cell_font, center_align, thin_border)
|
||||
apply_style(f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
# 设置列宽
|
||||
ws.column_dimensions["A"].width = 20
|
||||
ws.column_dimensions["B"].width = 20
|
||||
ws.column_dimensions["C"].width = 30
|
||||
ws.column_dimensions["D"].width = 20
|
||||
ws.column_dimensions["E"].width = 20
|
||||
|
||||
# 保存文件
|
||||
wb.save(output_path)
|
||||
print(f"数据已成功写入到 {output_path}")
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, trlx_features, reclassed_feature, output_path,target_areas_df, prop_config):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path,f"{soil_prop_name}不同土壤类型土壤.xlsx") # 生成的Excel报告文件路径
|
||||
# output_excel4_path = os.path.join(output_path,f"{soil_prop_name}不同母岩母质土壤属性.xlsx")
|
||||
soil_prop_features = os.path.join(gdb_path,soil_prop_name)
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
|
||||
temp_out_features = r"in_memory/temp_out_type_features"
|
||||
out_table_mean = r"in_memory/out_table_type_mean"
|
||||
temp_files.append(temp_out_features)
|
||||
temp_files.append(out_table_mean)
|
||||
|
||||
# 2. 用样点进行空间连接到土壤类型图斑
|
||||
fields_to_keep = {
|
||||
soil_prop_features: [soil_prop_name],
|
||||
trlx_features: ["YL", "TS", "TZ"],
|
||||
}
|
||||
|
||||
field_mappings = arcpy.FieldMappings()
|
||||
|
||||
for join_features in fields_to_keep.keys():
|
||||
for field_name in fields_to_keep[join_features]:
|
||||
try:
|
||||
field_map = arcpy.FieldMap()
|
||||
field_map.addInputField(join_features, field_name)
|
||||
field_map.mergeRule = "First" # 对所有连接字段使用 "First" 规则
|
||||
field_mappings.addFieldMap(field_map)
|
||||
except Exception as e:
|
||||
print(f"警告: 添加字段 '{field_name}' (来自 '{join_features}') 时出错,将跳过。错误信息: {e}")
|
||||
# 空间连接
|
||||
arcpy.analysis.SpatialJoin(soil_prop_features, trlx_features, temp_out_features, "JOIN_ONE_TO_ONE", "KEEP_ALL",field_mappings, "INTERSECT")
|
||||
|
||||
# 3. 交集制表计算每个TRZD的面积
|
||||
arcpy.analysis.TabulateIntersection(trlx_features, "YL_TS", reclassed_feature, out_table_mean, "gridcode", out_units="SQUARE_METERS")
|
||||
|
||||
trlx_zhitu_df = pd.DataFrame(arcpy.da.TableToNumPyArray(out_table_mean, ["YL_TS", "gridcode", "AREA"]))
|
||||
trlx_sample_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(temp_out_features, ["YL", "TS", "TZ", soil_prop_name]))
|
||||
|
||||
# 获取土壤类型图斑面积
|
||||
trlx_area_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(trlx_features, ["YL", "TS", "Shape@Area"]))
|
||||
|
||||
# 处理表3数据
|
||||
final_dataframe = process_data_for_table3(soil_prop_name,trlx_sample_df, trlx_zhitu_df, trlx_area_df, target_areas_df)
|
||||
# print(final_dataframe)
|
||||
|
||||
# 生成表3
|
||||
write_to_excel_table3(final_dataframe, output_excel_path, prop_config)
|
||||
# 母岩母质表
|
||||
# write_to_excel_table4(df_mymz, output_excel4_path, prop_config)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
512
tools/core/soil_prop_stats/B3不同土壤类型土壤属性.py
Normal file
512
tools/core/soil_prop_stats/B3不同土壤类型土壤属性.py
Normal file
@@ -0,0 +1,512 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from tools.config.pandas_field_cal_func import calculate_muyan, calculate_muzhi
|
||||
from tools.config.custom_sort import yl_order, ts_order
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
from tools.core.utils.excel_utils import ExcelStyleUtils
|
||||
|
||||
|
||||
# --- 3. 数据处理与分析 均值---
|
||||
def process_data_for_table3(soil_prop_name, df_trlx_sample, df_trlx_zhitu, df_trlx, target_areas_df):
|
||||
"""
|
||||
【最终版 v2】: 增加对制图样点数的处理,以支持加权平均计算。
|
||||
"""
|
||||
print("开始处理数据...")
|
||||
|
||||
def clean_df(df, columns) -> pd.DataFrame:
|
||||
for col in columns:
|
||||
df[col] = df[col].astype(str).str.strip()
|
||||
df.replace(['<Null>', 'None', '', '<空>'], np.nan, inplace=True)
|
||||
df.dropna(subset=columns, inplace=True)
|
||||
return df
|
||||
|
||||
# ==a. 处理样点数据,计算“样点均值” ---
|
||||
print("--> 步骤1: 计算样点均值...")
|
||||
field_name = soil_prop_name
|
||||
sample_fields = ['YL', 'TS', field_name]
|
||||
df_samples = clean_df(df_trlx_sample, sample_fields)
|
||||
df_samples[field_name] = df_samples[field_name].astype(float)
|
||||
|
||||
# 通过土属计算母岩母质
|
||||
df_samples['母岩'] = df_samples['TS'].apply(calculate_muyan)
|
||||
df_samples['母质'] = df_samples['母岩'].apply(calculate_muzhi)
|
||||
# 按 YJDL, EJDL 分组,计算 dPH 的均值
|
||||
df_sample_means = df_samples.groupby(['YL', 'TS'])[field_name].agg(['count', 'max', 'min', 'mean', 'median']).reset_index()
|
||||
df_sample_mymz = df_samples.groupby(['母质', '母岩', 'TZ'])[field_name].agg(['count', 'mean', 'median']).reset_index()
|
||||
# print(df_sample_mymz)
|
||||
|
||||
# ==b. 处理制图数据,获各等级制图面积
|
||||
df_trlx_zhitu["YL"] = df_trlx_zhitu['YL_TS'].apply(lambda x: x.split('_')[0])
|
||||
df_trlx_zhitu["TS"] = df_trlx_zhitu["YL_TS"].apply(lambda x: x.split('_')[1])
|
||||
df_trlx_zhitu = clean_df(df_trlx_zhitu, ['YL', 'TS'])
|
||||
df_trlx_zhitu.rename(columns={'MEAN': '制图均值', 'COUNT': '制图样点数'}, inplace=True)
|
||||
|
||||
# ==c. 处理制图数据,获各等级制图面积
|
||||
df_trlx = clean_df(df_trlx, ['YL', 'TS'])
|
||||
df_trlx["面积_亩"] = df_trlx["Shape@Area"] * 0.0015
|
||||
|
||||
filtered_props = ['ECA', 'EMG', 'ACU', 'AZN', 'AFE', 'AMN', 'AMO', 'AB', 'AS1', 'TSE']
|
||||
|
||||
# 拿到目标df总面积,计算比例进行平差
|
||||
print(target_areas_df)
|
||||
if soil_prop_name == "GZCHD":
|
||||
target_areas = target_areas_df[target_areas_df['EJDL'] == '耕地']['面积'].values[0]
|
||||
elif soil_prop_name in filtered_props:
|
||||
target_areas = target_areas_df[target_areas_df['EJDL'].isin(['耕地', '园地'])]['面积'].sum()
|
||||
else:
|
||||
target_areas = target_areas_df['面积'].sum()
|
||||
original_area = df_trlx['面积_亩'].sum()
|
||||
adjusted_area_yz = target_areas / original_area
|
||||
|
||||
df_trlx["面积_亩"] = df_trlx["面积_亩"] * adjusted_area_yz
|
||||
df_trlx_area = df_trlx.groupby(['YL', 'TS'])['面积_亩'].sum().reset_index()
|
||||
|
||||
|
||||
# --- c. 合并数据 ---
|
||||
print("--> 步骤3: 合并数据...")
|
||||
df_skeleton = pd.concat([
|
||||
df_sample_means[['YL', 'TS']],
|
||||
df_trlx_zhitu[['YL', 'TS']]
|
||||
]).drop_duplicates().reset_index(drop=True)
|
||||
|
||||
df_final = pd.merge(df_skeleton, df_sample_means, on=['YL', 'TS'], how='left')
|
||||
df_final = pd.merge(df_final, df_trlx_zhitu, on=['YL', 'TS'], how='left')
|
||||
df_final = pd.merge(df_final, df_trlx_area, on=['YL', 'TS'], how='left')
|
||||
|
||||
# (可选) 按“亚类”和“土属”排序
|
||||
in_yl_order = yl_order + [x for x in df_final['YL'].unique() if x not in yl_order]
|
||||
in_ts_order = ts_order + [x for x in df_final['TS'].unique() if x not in ts_order]
|
||||
df_final["YL"] = pd.Categorical(df_final['YL'], categories=in_yl_order, ordered=True)
|
||||
df_final["TS"] = pd.Categorical(df_final['TS'], categories=in_ts_order, ordered=True)
|
||||
df_final.sort_values(['YL', 'TS'], inplace=True)
|
||||
|
||||
print("数据处理流程完成!")
|
||||
return df_final, df_sample_mymz
|
||||
|
||||
# 写入EXCEL 表2
|
||||
def write_to_excel_table3(df, output_path, prop_config:dict, stats):
|
||||
"""
|
||||
将处理好的数据写入格式化的 Excel 文件。
|
||||
"""
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
print(f"开始生成 Excel 报告到 '{output_path}'...")
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "不同土壤类型属性变化统计"
|
||||
|
||||
# 获取属性单位
|
||||
special_prop = ['耕作层厚度','阳离子','有机质','pH','有效磷','速效钾','交换性钙','交换性镁','有效硫','有效铁','有效锰','有效硅','全钾']
|
||||
fsn_props = ['砂粒含量','粉粒含量','黏粒含量']
|
||||
prop_name_str = prop_config.get('项目分级','')
|
||||
if prop_name_str:
|
||||
split_name = prop_name_str.split('\n')[0].strip()
|
||||
if split_name in special_prop:
|
||||
prop_name = '1f'
|
||||
elif split_name in fsn_props:
|
||||
prop_name = '0f'
|
||||
else:
|
||||
prop_name = '2f'
|
||||
else:
|
||||
prop_name = '1f'
|
||||
|
||||
prop_unit_str = prop_config.get('分级标准', '')
|
||||
if prop_unit_str:
|
||||
prop_unit = prop_unit_str.split('\n')[1].strip()
|
||||
else:
|
||||
prop_unit = ''
|
||||
|
||||
# --- b. 绘制表头 ---
|
||||
ws.merge_cells('A1:B1'); ws['A1'] = '土壤类型'
|
||||
ws.merge_cells('C1:F1'); ws['C1'] = '样点统计'
|
||||
ws.merge_cells('G1:H1'); ws['G1'] = '制图统计'
|
||||
|
||||
ws['A2'] = '亚类'
|
||||
ws['B2'] = '土属'
|
||||
ws['C2'] = '均值/' + prop_unit
|
||||
ws['D2'] = '中位值/' + prop_unit
|
||||
ws['E2'] = '范围/' + prop_unit
|
||||
ws['F2'] = '数量/个'
|
||||
ws['G2'] = '均值/' + prop_unit
|
||||
ws['H2'] = '面积/亩'
|
||||
|
||||
# --- c. 填充数据 ---
|
||||
current_row = 3
|
||||
|
||||
df_to_write = df.copy() # 使用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
for yl, group_yl_df in df_to_write.groupby('YL', sort=False, observed=True):
|
||||
|
||||
print(f"正在写入亚类: {yl}...")
|
||||
yl_start_row = current_row
|
||||
|
||||
# 遍历该亚类下下的所有“土属”
|
||||
for _, row_data in group_yl_df.iterrows():
|
||||
ws.cell(row=current_row, column=2).value = row_data['TS']
|
||||
|
||||
# 填充样点数据
|
||||
sample_mean = row_data.get('mean')
|
||||
if pd.notna(sample_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{sample_mean:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=4).value = f"{row_data.get('median', '-'):.{prop_name}}"
|
||||
ws.cell(row=current_row, column=5).value = f"{row_data.get('min', '-'):.{prop_name}}~{row_data.get('max', '-'):.{prop_name}}"
|
||||
ws.cell(row=current_row, column=6).value = row_data.get('count', '-')
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
|
||||
# 填充制图数据
|
||||
map_mean = row_data.get('制图均值')
|
||||
if pd.notna(map_mean):
|
||||
ws.cell(row=current_row, column=7).value = f"{map_mean:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=8).value = f"{row_data.get('面积_亩', '-'):.0f}"
|
||||
else:
|
||||
ws.cell(row=current_row, column=7).value = "-"
|
||||
ws.cell(row=current_row, column=8).value = "-"
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 计算并写入“合计”行
|
||||
if ws.cell(row=current_row-1, column=2).value in ["林地", "草地", "其他"]:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=yl_start_row, end_column=2)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
continue
|
||||
|
||||
ws.cell(row=current_row, column=2).value = '合计'
|
||||
|
||||
# 计算合计行的均值 (均值的均值)
|
||||
total_count = group_yl_df['count'].sum()
|
||||
weighted_sum = group_yl_df['mean'] * group_yl_df['count']
|
||||
if not weighted_sum.empty and total_count != 0:
|
||||
total_sample_mean = weighted_sum.sum() / total_count
|
||||
else:
|
||||
total_sample_mean = None
|
||||
total_median = group_yl_df['median'].mean()
|
||||
min_min, max_max = group_yl_df['min'].min(), group_yl_df['max'].max()
|
||||
|
||||
|
||||
if pd.notna(total_sample_mean):
|
||||
ws.cell(row=current_row, column=3).value = f"{total_sample_mean:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=4).value = f"{total_median:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=5).value = f"{min_min:.{prop_name}}~{max_max:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=6).value = f"{total_count:.0f}"
|
||||
else:
|
||||
ws.cell(row=current_row, column=3).value = "-"
|
||||
ws.cell(row=current_row, column=4).value = "-"
|
||||
ws.cell(row=current_row, column=5).value = "-"
|
||||
ws.cell(row=current_row, column=6).value = "-"
|
||||
|
||||
# b. **【核心修正】: 计算合计行的“制图均值”(加权平均)**
|
||||
# 准备加权平均的分子和分母
|
||||
weighted_sum = 0
|
||||
total_count = 0
|
||||
|
||||
# 遍历当前一级地类分组中的每一行
|
||||
for _, row in group_yl_df.iterrows():
|
||||
mean_val = row.get('制图均值')
|
||||
count_val = row.get('制图样点数')
|
||||
|
||||
# 只有当均值和样点数都存在且有效时,才参与计算
|
||||
if pd.notna(mean_val) and pd.notna(count_val) and count_val > 0:
|
||||
weighted_sum += mean_val * count_val # Σ (mean * count)
|
||||
total_count += count_val # Σ (count)
|
||||
|
||||
# 计算加权平均值
|
||||
weighted_avg = (weighted_sum / total_count) if total_count > 0 else 0
|
||||
total_area = group_yl_df['面积_亩'].sum()
|
||||
|
||||
if weighted_avg > 0:
|
||||
ws.cell(row=current_row, column=7).value = f"{weighted_avg:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=8).value = f"{total_area:.0f}"
|
||||
else:
|
||||
ws.cell(row=current_row, column=7).value = "-"
|
||||
ws.cell(row=current_row, column=8).value = "-"
|
||||
|
||||
# 合并“一级地类”单元格
|
||||
if yl_start_row <= current_row:
|
||||
ws.merge_cells(start_row=yl_start_row, start_column=1, end_row=current_row, end_column=1)
|
||||
ws.cell(row=yl_start_row, column=1).value = yl
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 计算全区的均值、范围、数量
|
||||
# total_counts = df_to_write['count'].sum()
|
||||
# total_weighted_sum = df_to_write['mean'] * df_to_write['count']
|
||||
# total_mean = total_weighted_sum.sum() / total_counts
|
||||
# total_median = df_to_write['median'].mean()
|
||||
total_range = f"{df_to_write['min'].min():.{prop_name}}~{df_to_write['max'].max():.{prop_name}}"
|
||||
total_zhitu_weighted_sum = df_to_write['制图均值'] * df_to_write['面积_亩']
|
||||
total_areas = df_to_write['面积_亩'].sum()
|
||||
total_zhitu_mean = total_zhitu_weighted_sum.sum() / total_areas
|
||||
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=2)
|
||||
ws.cell(row=current_row, column=1).value = '全区'
|
||||
ws.cell(row=current_row, column=3).value = f"{stats['mean']:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=4).value = f"{stats['median']:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=5).value = total_range
|
||||
ws.cell(row=current_row, column=6).value = f"{stats['count']:.0f}"
|
||||
ws.cell(row=current_row, column=7).value = f"{total_zhitu_mean:.{prop_name}}"
|
||||
ws.cell(row=current_row, column=8).value = f"{total_areas:.0f}"
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}{current_row}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
print("正在自动调整列宽...")
|
||||
|
||||
# 设置列宽
|
||||
ExcelStyleUtils.auto_adjust_column_width(ws)
|
||||
|
||||
# --- e. 保存文件 ---
|
||||
wb.save(output_path)
|
||||
print("Excel 报告生成成功!")
|
||||
|
||||
# 母岩母质表
|
||||
def write_to_excel_table4(df:pd.DataFrame, output_path, prop_config, stats):
|
||||
if df.empty:
|
||||
print("警告: 没有数据可以写入 Excel。")
|
||||
return
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.create_sheet("Mysheet", 0)
|
||||
ws.title = "母岩母质土壤属性统计"
|
||||
|
||||
# 获取属性单位
|
||||
special_prop = ['耕作层厚度','阳离子','有机质','pH','有效磷','速效钾','交换性钙','交换性镁','有效硫','有效铁','有效锰','有效硅','全钾']
|
||||
fsn_props = ['砂粒含量','粉粒含量','黏粒含量','有效土层厚度']
|
||||
prop_name_str = prop_config.get('项目分级','')
|
||||
if prop_name_str:
|
||||
split_name = prop_name_str.split('\n')[0].strip()
|
||||
if split_name in special_prop:
|
||||
prop_name = '1f'
|
||||
elif split_name in fsn_props:
|
||||
prop_name = '0f'
|
||||
else:
|
||||
prop_name = '2f'
|
||||
else:
|
||||
prop_name = '1f'
|
||||
|
||||
prop_unit_str = prop_config.get('分级标准', '')
|
||||
if prop_unit_str:
|
||||
prop_unit = prop_unit_str.split('\n')[1].strip()
|
||||
else:
|
||||
prop_unit = ''
|
||||
|
||||
# 写入表头
|
||||
headers = ['母岩母质','', '土种类型', '样点统计', '']
|
||||
ws.append(headers)
|
||||
ws.append(['', '', '', f'均值/{prop_unit}', '数量/个'])
|
||||
|
||||
# 合并表头单元格
|
||||
ws.merge_cells('A1:B2') # 母岩母质
|
||||
ws.merge_cells('C1:C2') # 土种类型
|
||||
ws.merge_cells('D1:E1') # 样点统计
|
||||
|
||||
current_row = 3
|
||||
|
||||
# 按母质和母岩进行分组
|
||||
grouped = df.groupby(['母质', '母岩']).agg({
|
||||
'TZ': lambda x: ','.join(x), # 将土种名称用逗号连接
|
||||
'mean': 'mean', # 计算均值
|
||||
'count': 'sum' # 计算总数
|
||||
}).reset_index()
|
||||
|
||||
parent_materials = grouped['母质'].unique()
|
||||
|
||||
for parent_material in parent_materials:
|
||||
|
||||
parent_material_row = current_row
|
||||
|
||||
if parent_material == '未知':
|
||||
continue
|
||||
|
||||
material_group = grouped[grouped['母质'] == parent_material]
|
||||
# 写入母岩母质分组(只在第一行显示)
|
||||
first_row_in_group = True
|
||||
|
||||
for _, row_data in material_group.iterrows():
|
||||
if first_row_in_group:
|
||||
# 第一行显示母岩母质名称
|
||||
ws.cell(row=current_row, column=1, value=parent_material)
|
||||
first_row_in_group = False
|
||||
else:
|
||||
# 后续行留空
|
||||
ws.cell(row=current_row, column=1, value='')
|
||||
|
||||
# 写入母岩类型
|
||||
ws.cell(row=current_row, column=2, value=row_data['母岩'])
|
||||
|
||||
# 写入土种类型(所有土种用逗号连接)
|
||||
ws.cell(row=current_row, column=3, value=row_data['TZ'])
|
||||
|
||||
# 写入统计数据
|
||||
ws.cell(row=current_row, column=4, value=round(row_data['mean'], 1))
|
||||
ws.cell(row=current_row, column=5, value=row_data['count'])
|
||||
|
||||
current_row += 1
|
||||
|
||||
# 合并母岩母质分组
|
||||
if parent_material_row < current_row:
|
||||
ws.merge_cells(start_row=parent_material_row, start_column=1, end_row=current_row - 1, end_column=1)
|
||||
|
||||
# 计算合计值并写入
|
||||
# total_mean = 0
|
||||
# total_count = df['count'].sum()
|
||||
# total_sum = df['mean'] * df['count']
|
||||
# if total_count and total_count!=0:
|
||||
# total_mean = total_sum.sum() / total_count
|
||||
|
||||
ws.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=3)
|
||||
ws.cell(row=current_row, column=1, value='全区')
|
||||
ws.cell(row=current_row, column=4, value=f"{stats['mean']:.{prop_name}}")
|
||||
ws.cell(row=current_row, column=5, value=f"{stats['count']:.0f}")
|
||||
|
||||
# --- a. 定义样式 ---
|
||||
header_font = Font(name='等线', size=11, bold=True)
|
||||
|
||||
# --- d. 应用样式和调整列宽 ---
|
||||
max_col_letter = get_column_letter(ws.max_column)
|
||||
if current_row > 1: # 确保有数据才应用样式
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}{current_row}')
|
||||
ExcelStyleUtils.set_style(ws, f'A1:{max_col_letter}2', header_font)
|
||||
|
||||
# 设置列宽
|
||||
ws.column_dimensions["A"].width = 20
|
||||
ws.column_dimensions["B"].width = 20
|
||||
ws.column_dimensions["C"].width = 30
|
||||
ws.column_dimensions["D"].width = 20
|
||||
ws.column_dimensions["E"].width = 20
|
||||
|
||||
# 保存文件
|
||||
wb.save(output_path)
|
||||
print(f"数据已成功写入到 {output_path}")
|
||||
|
||||
|
||||
def main(gdb_path, soil_prop_name, trlx_features, soil_prop_tif, output_path,target_areas_df, prop_config, dltb_features):
|
||||
try:
|
||||
# --- 1. 用户配置 ---
|
||||
# 输出配置
|
||||
temp_files = []
|
||||
output_excel_path = os.path.join(output_path,f"{soil_prop_name}不同土壤类型土壤.xlsx") # 生成的Excel报告文件路径
|
||||
output_excel4_path = os.path.join(output_path,f"{soil_prop_name}不同母岩母质土壤属性.xlsx")
|
||||
soil_prop_features = os.path.join(gdb_path,soil_prop_name)
|
||||
|
||||
# 设置工作空间和变量
|
||||
arcpy.env.workspace = gdb_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
print("开始处理数据...")
|
||||
if soil_prop_name == "GZCHD":
|
||||
temp_gdtb_trlx_out = r"in_memory/temp_gdtb_trlx_out"
|
||||
temp_gdtb_trlx = r"in_memory/temp_gdtb_trlx"
|
||||
temp_files.append(temp_gdtb_trlx)
|
||||
|
||||
temp_out_features = r"in_memory/temp_out_type_features"
|
||||
out_table_mean = r"in_memory/out_table_type_mean"
|
||||
temp_files.append(temp_out_features)
|
||||
temp_files.append(out_table_mean)
|
||||
|
||||
# 2. 用样点进行空间连接到土壤类型图斑
|
||||
fields_to_keep = {
|
||||
soil_prop_features: [soil_prop_name],
|
||||
trlx_features: ["YL", "TS", "TZ"],
|
||||
}
|
||||
|
||||
field_mappings = arcpy.FieldMappings()
|
||||
|
||||
for join_features in fields_to_keep.keys():
|
||||
for field_name in fields_to_keep[join_features]:
|
||||
try:
|
||||
field_map = arcpy.FieldMap()
|
||||
field_map.addInputField(join_features, field_name)
|
||||
field_map.mergeRule = "First" # 对所有连接字段使用 "First" 规则
|
||||
field_mappings.addFieldMap(field_map)
|
||||
except Exception as e:
|
||||
print(f"警告: 添加字段 '{field_name}' (来自 '{join_features}') 时出错,将跳过。错误信息: {e}")
|
||||
|
||||
# 定义需要过滤地类的属性列表
|
||||
filtered_props = ['ECA', 'EMG', 'ACU', 'AZN', 'AFE', 'AMN', 'AMO', 'AB', 'AS1', 'TSE']
|
||||
|
||||
# 空间连接
|
||||
arcpy.analysis.SpatialJoin(soil_prop_features, trlx_features, temp_out_features, "JOIN_ONE_TO_ONE", "KEEP_ALL",field_mappings, "INTERSECT")
|
||||
|
||||
if soil_prop_name == "GZCHD":
|
||||
arcpy.analysis.Intersect([trlx_features, dltb_features], temp_gdtb_trlx, 'NO_FID')
|
||||
arcpy.conversion.ExportFeatures(temp_gdtb_trlx,temp_gdtb_trlx_out,"DLBM LIKE '01%'")
|
||||
|
||||
# 3. 以表格显示分区统计 计算均值
|
||||
arcpy.sa.ZonalStatisticsAsTable(temp_gdtb_trlx_out, "YL_TS", soil_prop_tif, out_table_mean, "DATA", "MEAN")
|
||||
trlx_area_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(temp_gdtb_trlx_out, ["YL", "TS", "Shape@Area"]))
|
||||
# 如果当前属性在列表中,则只统计耕地和园地
|
||||
elif soil_prop_name in filtered_props:
|
||||
temp_gdtb_trlx_filtered = r"in_memory/temp_gdtb_trlx_filtered"
|
||||
temp_gdtb_trlx_out_filtered = r"in_memory/temp_gdtb_trlx_out_filtered"
|
||||
temp_files.append(temp_gdtb_trlx_filtered)
|
||||
temp_files.append(temp_gdtb_trlx_out_filtered)
|
||||
|
||||
# 交集土壤类型与土地利用图斑
|
||||
arcpy.analysis.Intersect([trlx_features, dltb_features], temp_gdtb_trlx_filtered, 'NO_FID')
|
||||
# 导出耕地和园地(DLBM LIKE '01%' OR DLBM LIKE '02%')
|
||||
arcpy.conversion.ExportFeatures(temp_gdtb_trlx_filtered, temp_gdtb_trlx_out_filtered, "DLBM LIKE '01%' OR DLBM LIKE '02%'")
|
||||
|
||||
# 使用过滤后的图斑进行分区统计(制图均值)
|
||||
arcpy.sa.ZonalStatisticsAsTable(temp_gdtb_trlx_out_filtered, "YL_TS", soil_prop_tif, out_table_mean, "DATA", "MEAN")
|
||||
# 获取过滤后的面积
|
||||
trlx_area_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(temp_gdtb_trlx_out_filtered, ["YL", "TS", "Shape@Area"]))
|
||||
|
||||
print(f"过滤制图数据:仅统计耕地和园地(DLBM LIKE '01%' OR '02%')")
|
||||
else:
|
||||
# 3. 以表格显示分区统计 计算均值
|
||||
arcpy.sa.ZonalStatisticsAsTable(trlx_features, "YL_TS", soil_prop_tif, out_table_mean, "DATA", "MEAN")
|
||||
# 获取土壤类型图斑面积
|
||||
trlx_area_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(trlx_features, ["YL", "TS", "Shape@Area"]))
|
||||
|
||||
trlx_zhitu_df = pd.DataFrame(arcpy.da.TableToNumPyArray(out_table_mean, ["YL_TS", "MEAN", "COUNT"]))
|
||||
trlx_sample_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(temp_out_features, ["YL", "TS", "TZ", soil_prop_name]))
|
||||
|
||||
stat_sample = {
|
||||
'min': trlx_sample_df[soil_prop_name].min(),
|
||||
'max': trlx_sample_df[soil_prop_name].max(),
|
||||
'mean':trlx_sample_df[soil_prop_name].mean(),
|
||||
'median': trlx_sample_df[soil_prop_name].median(),
|
||||
'count': trlx_sample_df[soil_prop_name].count()
|
||||
}
|
||||
|
||||
|
||||
# 处理表3数据
|
||||
final_dataframe, df_mymz = process_data_for_table3(soil_prop_name,trlx_sample_df, trlx_zhitu_df, trlx_area_df, target_areas_df)
|
||||
# print(final_dataframe)
|
||||
|
||||
# 生成表3
|
||||
write_to_excel_table3(final_dataframe, output_excel_path, prop_config, stat_sample)
|
||||
# 母岩母质表
|
||||
write_to_excel_table4(df_mymz, output_excel4_path, prop_config,stat_sample)
|
||||
|
||||
# return df_with_factors
|
||||
except Exception as e:
|
||||
print(f"\n处理过程中发生严重错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files)
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
# --- 4. 主程序入口 ---
|
||||
# if __name__ == "__main__":
|
||||
# main()
|
||||
1269
tools/core/soil_prop_stats/E1土壤属性历史变化.py
Normal file
1269
tools/core/soil_prop_stats/E1土壤属性历史变化.py
Normal file
File diff suppressed because it is too large
Load Diff
0
tools/core/soil_prop_stats/__init__.py
Normal file
0
tools/core/soil_prop_stats/__init__.py
Normal file
660
tools/core/stats_area_to_excel.py
Normal file
660
tools/core/stats_area_to_excel.py
Normal file
@@ -0,0 +1,660 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
输入重分类后栅格转面要素类、乡镇边界面要素类、地类图斑要素类;
|
||||
按一级地类统计土壤属性面积 和 按乡镇统计土壤属性面积;
|
||||
将统计结果导出为Excel表格;
|
||||
将Excel表格转换为jpg图片;
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import traceback
|
||||
import uuid
|
||||
import arcpy
|
||||
import argparse
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from openpyxl.styles import Font, Alignment, Border, Side, numbers
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
sys.path.append(str(Path(__file__).parent))
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
from tools.config.common_config import guangxi_region, yunnan_region
|
||||
from utils import common_utils, 平差工具
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description="将ArcGIS表格转换为Excel")
|
||||
parser.add_argument("--reclassed_polygon", required=True, help="重分类栅格的面要素")
|
||||
parser.add_argument("--settings_path", required=True, help="配置文件路径")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.settings_path:
|
||||
with open(args.settings_path, 'r', encoding="utf-8") as settings_file:
|
||||
settings = json.load(settings_file)
|
||||
area_stat_settings = settings.get("area_stat_settings", {})
|
||||
|
||||
if area_stat_settings:
|
||||
standards_dict_path = area_stat_settings.get("config_file_path", "")
|
||||
|
||||
with open(standards_dict_path, 'r', encoding="utf-8") as standards_file:
|
||||
standards_dict = json.load(standards_file)
|
||||
config_key = common_utils.get_config_key(Path(args.reclassed_polygon).stem)
|
||||
output_settings = standards_dict['export_config'][config_key]
|
||||
area_stat_settings["output_settings"] = output_settings
|
||||
area_stat_settings["reclassed_polygon"] = args.reclassed_polygon
|
||||
area_stat_settings["soil_property"] = config_key
|
||||
else:
|
||||
print_status("错误: 未找到有效配置文件")
|
||||
sys.exit(1)
|
||||
|
||||
return area_stat_settings
|
||||
|
||||
|
||||
def print_status(message):
|
||||
"""
|
||||
输出状态信息到标准输出,用于 GUI 实时显示
|
||||
格式: STATUS: <message>
|
||||
"""
|
||||
print(f"STATUS:{message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
|
||||
def print_result(success, output_path="", error_message=""):
|
||||
"""
|
||||
输出最终结果到标准输出,用于 GUI 判断任务状态和获取结果
|
||||
格式: RESULT:True|<output_path>|
|
||||
格式: RESULT:False||<error_message>
|
||||
"""
|
||||
if success:
|
||||
print(f"RESULT:True|{output_path}|")
|
||||
else:
|
||||
# 在错误信息中替换换行符,避免干扰解析
|
||||
cleaned_error_message = error_message.replace('\n', ' ').replace('\r', '')
|
||||
print(f"RESULT:False||{cleaned_error_message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
|
||||
def log_arcpy_message(message):
|
||||
"""输出 ArcPy 产生的 geoprocessing 消息"""
|
||||
# 可以在这里进一步处理或过滤 ArcPy 消息
|
||||
if message.type == 'Message':
|
||||
print_status(f"ArcPy消息: {message.message}")
|
||||
elif message.type == 'Warning':
|
||||
print_status(f"ArcPy警告: {message.message}")
|
||||
elif message.type == 'Error':
|
||||
# 对于错误,也可以记录到标准错误
|
||||
print_status(f"ArcPy错误: {message.message}")
|
||||
sys.stderr.write(f"ArcPyError:{message.message}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
def get_specail_map(original_dict):
|
||||
grade_map = {}
|
||||
dict_len = len(original_dict)
|
||||
order = 1
|
||||
for grade_key, range_str in original_dict.items():
|
||||
ranges = [r.strip() for r in range_str.replace('\n', ',').split(',') if r.strip()]
|
||||
s_order = 0
|
||||
for r in ranges:
|
||||
grade_map[str(order + s_order*dict_len)] = r
|
||||
s_order += 1
|
||||
order += 1
|
||||
|
||||
return grade_map
|
||||
|
||||
|
||||
class SoilQualityReporter:
|
||||
def __init__(self, output_path, grade_map: dict, landuse_map, output_settings):
|
||||
"""
|
||||
初始化土壤质量报告生成器
|
||||
|
||||
参数:
|
||||
output_path: 输出Excel文件路径
|
||||
"""
|
||||
self.output_path = output_path
|
||||
self.grade_map = grade_map
|
||||
self.landuse_map = landuse_map
|
||||
self.all_grades = [int(key) for key in self.grade_map.keys()]
|
||||
self.xiangmu_name = output_settings['项目分级'].split('\n')[0]
|
||||
self.xiangmu_jibie =self.xiangmu_name + "分级" + output_settings['分级标准'].split('\n')[1]
|
||||
|
||||
def prepare_data(self, stats):
|
||||
"""
|
||||
准备乡镇统计和地类统计两个表格
|
||||
|
||||
参数:
|
||||
stats: 包含原始统计数据的DataFrame
|
||||
需要包含列: XZQMC(乡镇名称), YJDLBM(地类编码), GRIDCODE(土壤等级), temp_area(面积)
|
||||
|
||||
返回:
|
||||
df_town: 乡镇统计表
|
||||
df_landuse: 地类统计表
|
||||
"""
|
||||
# 表格1:各乡镇耕地土壤有机质分级面积统计
|
||||
df_town = self._create_town_table(stats)
|
||||
|
||||
# 表格2:各地类土壤有机质分级面积统计
|
||||
df_landuse = self._create_landuse_table(stats)
|
||||
|
||||
return df_town, df_landuse
|
||||
|
||||
# 先创建GRIDCODE到分组的映射
|
||||
# def _map_grade(self, code):
|
||||
# code = int(code)
|
||||
# if code in (1,6):
|
||||
# return 1
|
||||
# elif code in (2, 7):
|
||||
# return 2
|
||||
# elif code in (3, 8):
|
||||
# return 3
|
||||
# elif code in (4, 9):
|
||||
# return 4
|
||||
# elif code in (5,10):
|
||||
# return 5
|
||||
|
||||
# return code
|
||||
|
||||
def _create_town_table(self, stats:pd.DataFrame):
|
||||
"""生成乡镇统计表"""
|
||||
|
||||
# 复制数据并添加分组列
|
||||
df_stats = stats.copy()
|
||||
# 如果存在YJDLBM列,确保其值为字符串
|
||||
if "YJDLBM" not in df_stats.columns:
|
||||
# 取YNDLBM列的前两位作为YJDLBM
|
||||
df_stats["YJDLBM"] = df_stats["YNDLBM"].str[:2]
|
||||
|
||||
# df_stats["GRID_GROUP"] = df_stats["GRIDCODE"].apply(self._map_grade)
|
||||
|
||||
# 使用分组列进行透视
|
||||
df = df_stats[df_stats["YJDLBM"] == "01"].pivot_table(
|
||||
index="XZQMC",
|
||||
columns="GRIDCODE",
|
||||
values="adjusted_area",
|
||||
aggfunc="sum",
|
||||
fill_value=0,
|
||||
observed=False
|
||||
)
|
||||
|
||||
# 确保所有等级列都存在
|
||||
for grade in self.all_grades:
|
||||
if grade not in df.columns:
|
||||
df[grade] = 0
|
||||
|
||||
# 按等级排序并添加总计
|
||||
df = df[self.all_grades]
|
||||
df["总计"] = df.sum(axis=1)
|
||||
df.loc["总计"] = df.sum(axis=0)
|
||||
|
||||
# 重命名列
|
||||
df.columns = [self.grade_map.get(str(col), str(col)) for col in df.columns]
|
||||
|
||||
return df
|
||||
|
||||
def _create_landuse_table(self, stats):
|
||||
"""生成地类统计表"""
|
||||
# 复制数据并添加分组列
|
||||
df_stats = stats.copy()
|
||||
# df_stats["GRID_GROUP"] = df_stats["GRIDCODE"].apply(self._map_grade)
|
||||
if "YJDLBM" not in df_stats.columns:
|
||||
df = df_stats.pivot_table(
|
||||
index="YNDLBM",
|
||||
columns="GRIDCODE",
|
||||
values="adjusted_area",
|
||||
aggfunc="sum",
|
||||
fill_value=0,
|
||||
observed=False
|
||||
)
|
||||
else:
|
||||
df = df_stats.pivot_table(
|
||||
index="YJDLBM",
|
||||
columns="GRIDCODE",
|
||||
values="adjusted_area",
|
||||
aggfunc="sum",
|
||||
fill_value=0,
|
||||
observed=False
|
||||
)
|
||||
|
||||
# 确保所有等级列都存在
|
||||
for grade in self.all_grades:
|
||||
if grade not in df.columns:
|
||||
df[grade] = 0
|
||||
|
||||
# 按等级排序并添加总计
|
||||
df = df[self.all_grades]
|
||||
df["总计"] = df.sum(axis=1)
|
||||
df.loc["总计"] = df.sum(axis=0)
|
||||
|
||||
# 重命名索引和列
|
||||
df = df.rename(index=self.landuse_map)
|
||||
df.columns = [self.grade_map.get(str(col), str(col)) for col in df.columns]
|
||||
|
||||
return df
|
||||
|
||||
def generate_report(self, stats):
|
||||
"""
|
||||
生成完整报告
|
||||
|
||||
参数:
|
||||
stats: 包含原始统计数据的DataFrame
|
||||
"""
|
||||
self.is_yunnan = True if 'YNDLBM' in stats.columns else False
|
||||
|
||||
# 准备数据
|
||||
df_town, df_landuse = self.prepare_data(stats)
|
||||
|
||||
# 导出Excel
|
||||
self._export_to_excel(df_town, df_landuse)
|
||||
|
||||
def _export_to_excel(self, df_town, df_landuse):
|
||||
"""导出数据到Excel"""
|
||||
with pd.ExcelWriter(self.output_path, engine='openpyxl') as writer:
|
||||
workbook = writer.book
|
||||
sheet = workbook.create_sheet("综合统计表")
|
||||
|
||||
# 写入乡镇统计表
|
||||
self._write_town_table(sheet, df_town)
|
||||
|
||||
# 写入地类统计表
|
||||
start_row_landuse = len(df_town) + 5
|
||||
self._write_landuse_table(sheet, df_landuse, start_row_landuse)
|
||||
|
||||
# 应用通用格式
|
||||
self._apply_common_format(sheet, start_row_landuse)
|
||||
|
||||
# 删除默认空工作表
|
||||
if 'Sheet' in workbook.sheetnames:
|
||||
workbook.remove(workbook['Sheet'])
|
||||
|
||||
def _write_town_table(self, sheet, df):
|
||||
"""写入乡镇统计表"""
|
||||
# 动态计算列数
|
||||
last_col = len(df.columns) + 2 # 最后一列的索引
|
||||
last_col_letter = get_column_letter(last_col) # 转为字母
|
||||
second_last_col_letter = get_column_letter(last_col - 1)
|
||||
|
||||
# 表头
|
||||
sheet.merge_cells(f"A1:{last_col_letter}1")
|
||||
sheet["A1"] = f"各乡镇耕地土壤{self.xiangmu_name}分级面积统计表"
|
||||
sheet["A1"].font = Font(size=18)
|
||||
sheet["A1"].alignment = Alignment(horizontal='center')
|
||||
sheet.row_dimensions[1].height = 34
|
||||
|
||||
# 单位行
|
||||
sheet[f"{last_col_letter}2"] = "单位:亩"
|
||||
sheet[f"{last_col_letter}2"].font = Font(size=14)
|
||||
sheet[f"{last_col_letter}2"].alignment = Alignment(horizontal='center')
|
||||
|
||||
# 列标题
|
||||
sheet.merge_cells("A3:B4")
|
||||
sheet["A3"] = "乡镇"
|
||||
|
||||
# 总计
|
||||
sheet.merge_cells(f"{last_col_letter}3:{last_col_letter}4")
|
||||
sheet[f"{last_col_letter}3"] = "总计"
|
||||
|
||||
sheet.merge_cells(f"C3:{second_last_col_letter}3")
|
||||
sheet["C3"] = self.xiangmu_jibie
|
||||
sheet.row_dimensions[3].height = 25
|
||||
|
||||
# 写入分级列名
|
||||
for col_num, col_name in enumerate(df.columns[:-1], start=2):
|
||||
# print(col_num, col_name)
|
||||
sheet.cell(row=4, column=col_num+1, value=col_name)
|
||||
|
||||
# 写入数据
|
||||
for r_idx, (index, row) in enumerate(df.iterrows(), start=5):
|
||||
sheet.merge_cells(f"A{r_idx}:B{r_idx}")
|
||||
sheet.cell(row=r_idx, column=1, value=index)
|
||||
for c_idx, value in enumerate(row, start=2):
|
||||
sheet.cell(row=r_idx, column=c_idx+1, value=value)
|
||||
|
||||
def _write_landuse_table(self, sheet, df, start_row):
|
||||
"""写入地类统计表"""
|
||||
# 动态计算列数
|
||||
last_col = len(df.columns) + 2 # 最后一列的索引
|
||||
last_col_letter = get_column_letter(last_col) # 转为字母
|
||||
second_last_col_letter = get_column_letter(last_col - 1)
|
||||
|
||||
# 表头
|
||||
sheet.merge_cells(f"A{start_row}:{last_col_letter}{start_row}")
|
||||
sheet[f"A{start_row}"] = f"各地类土壤{self.xiangmu_name}分级面积统计表"
|
||||
sheet.row_dimensions[start_row].height = 34
|
||||
|
||||
# 单位行
|
||||
sheet[f"{last_col_letter}{start_row+1}"] = "单位:亩"
|
||||
|
||||
# 列标题
|
||||
if self.is_yunnan:
|
||||
sheet.merge_cells(f"A{start_row+2}:B{start_row+2}")
|
||||
sheet[f"A{start_row+2}"] = "土地利用类型"
|
||||
sheet[f"A{start_row+3}"] = "一级"
|
||||
sheet[f"B{start_row+3}"] = "二级"
|
||||
else:
|
||||
sheet.merge_cells(f"A{start_row+2}:B{start_row+3}")
|
||||
sheet[f"A{start_row+2}"] = "土地利用\n类型"
|
||||
|
||||
sheet.merge_cells(f"{last_col_letter}{start_row+2}:{last_col_letter}{start_row+3}")
|
||||
sheet[f"{last_col_letter}{start_row+2}"] = "总计"
|
||||
|
||||
sheet.merge_cells(f"C{start_row+2}:{second_last_col_letter}{start_row+2}")
|
||||
sheet[f"C{start_row+2}"] = self.xiangmu_jibie
|
||||
sheet.row_dimensions[start_row+2].height = 25
|
||||
|
||||
# 写入分级列名
|
||||
for col_num, col_name in enumerate(df.columns[:-1], start=2):
|
||||
sheet.cell(row=start_row+3, column=col_num+1, value=col_name)
|
||||
|
||||
# 写入数据
|
||||
for r_idx, (index, row) in enumerate(df.iterrows(), start=start_row+4):
|
||||
sheet.merge_cells(f"A{r_idx}:B{r_idx}")
|
||||
sheet.cell(row=r_idx, column=1, value=index)
|
||||
for c_idx, value in enumerate(row, start=2):
|
||||
sheet.cell(row=r_idx, column=c_idx+1, value=value)
|
||||
|
||||
def _apply_common_format(self, sheet, landuse_start_row):
|
||||
"""应用通用格式"""
|
||||
# 设置列宽
|
||||
for col in range(1, sheet.max_column + 1):
|
||||
col_letter = chr(64 + col)
|
||||
sheet.column_dimensions[col_letter].width = 14
|
||||
|
||||
for row in range(5, sheet.max_row+1):
|
||||
if row not in [landuse_start_row,landuse_start_row+1, landuse_start_row+2,landuse_start_row+3]:
|
||||
sheet.row_dimensions[row].height = 23
|
||||
|
||||
# 定义边框样式
|
||||
thin_border = Border(
|
||||
left=Side(style='thin'),
|
||||
right=Side(style='thin'),
|
||||
top=Side(style='thin'),
|
||||
bottom=Side(style='thin')
|
||||
)
|
||||
|
||||
# 应用样式到所有单元格
|
||||
for row in sheet.iter_rows(min_row=3, max_row=sheet.max_row, min_col=1, max_col=sheet.max_column):
|
||||
for cell in row:
|
||||
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
cell.font = Font(bold=True, size=14)
|
||||
|
||||
# 特殊格式
|
||||
if cell.column == 1 and cell.row > 3 and cell.row not in (landuse_start_row, landuse_start_row+2): # 列A
|
||||
cell.font = Font(bold=False, size=14)
|
||||
|
||||
if cell.column == sheet.max_column and cell.row == landuse_start_row+1:
|
||||
cell.font = Font(bold=False, size=14)
|
||||
|
||||
#地类统计表头
|
||||
if cell.row == landuse_start_row:
|
||||
cell.font = Font(bold=False, size=18)
|
||||
cell.alignment = Alignment(vertical='bottom', horizontal='center')
|
||||
|
||||
# 地类统计表列标题
|
||||
if (cell.row == 3 and cell.column == 2) or (cell.row == landuse_start_row+2 and cell.column == 2):
|
||||
cell.font = Font(bold=False, size=14)
|
||||
|
||||
# 数字格式
|
||||
if isinstance(cell.value, (int, float)):
|
||||
cell.number_format = numbers.FORMAT_NUMBER
|
||||
if round(cell.value,0) == 0.0:
|
||||
cell.value = "-"
|
||||
|
||||
# 边框
|
||||
if cell.row >1 and cell.row not in (landuse_start_row, landuse_start_row+1):
|
||||
cell.border = thin_border
|
||||
|
||||
|
||||
def read_arcgis_table(table_path):
|
||||
"""将ArcGIS表格转换为Pandas DataFrame"""
|
||||
array = arcpy.da.TableToNumPyArray(table_path, "*")
|
||||
|
||||
df = pd.DataFrame(array)
|
||||
# df.to_csv(r"D:\工作\三普成果编制\出图数据\广西海城区\过程数据\酸化面积统计表\temp.csv")
|
||||
|
||||
df.columns = df.columns.str.upper()
|
||||
df["temp_area"] = df["AREA"] * 0.0015
|
||||
df["temp_area"] = df["temp_area"].round(4)
|
||||
|
||||
# 删除可能存在的OID字段(如果不需要)
|
||||
if 'OID@' in df.columns:
|
||||
df = df.drop('OID@', axis=1)
|
||||
|
||||
return df
|
||||
|
||||
# 获取每个一级地类面积,主要是12类
|
||||
def get_area_by_group(dltb_class_feature, excel_target_path, xzqmc, is_by_xzq=False):
|
||||
try:
|
||||
# 读取目标面积Excel文件
|
||||
if xzqmc in yunnan_region:
|
||||
target_df = pd.read_excel(excel_target_path, sheet_name="Sheet2")
|
||||
landuse_types = {'0101':'水田', '0102':'水浇地', '0103':'旱地', '02':'园地', '03':'林地', '04':'草地', '12':'其他'}
|
||||
elif xzqmc in guangxi_region:
|
||||
target_df = pd.read_excel(excel_target_path, sheet_name="Sheet1")
|
||||
landuse_types = {'01':'耕地', '02':'园地', '03':'林地', '04':'草地', '12':'其他'}
|
||||
else:
|
||||
target_df = pd.read_excel(excel_target_path, sheet_name="Sheet1")
|
||||
landuse_types = {'01':'耕地', '02':'园地', '03':'林地', '04':'草地', '12':'其他'}
|
||||
|
||||
# 确保列名匹配
|
||||
target_df.columns = target_df.columns.str.strip()
|
||||
|
||||
if is_by_xzq:
|
||||
# 地类编码映射字典
|
||||
land_type_mapping = {
|
||||
'耕地': '01',
|
||||
'园地': '02',
|
||||
'林地': '03',
|
||||
'草地': '04',
|
||||
'其他': '12'
|
||||
}
|
||||
|
||||
# 方法1:重命名列后转换为字典
|
||||
df_encoded = target_df.rename(columns=land_type_mapping)
|
||||
result_dict = df_encoded.set_index('行政单位').to_dict('index')
|
||||
|
||||
return result_dict
|
||||
|
||||
# 检查要素类是否存在
|
||||
if not arcpy.Exists(dltb_class_feature):
|
||||
print(f"警告:输入要素类不存在: {dltb_class_feature}")
|
||||
else:
|
||||
if xzqmc in yunnan_region:
|
||||
dlbm = 'YNDLBM'
|
||||
elif xzqmc in guangxi_region:
|
||||
dlbm = 'YJDLBM'
|
||||
else:
|
||||
dlbm = 'YJDLBM'
|
||||
# 转为numpy数组供pandas统计使用
|
||||
df = pd.DataFrame(arcpy.da.TableToNumPyArray(dltb_class_feature, [dlbm, "TBDLMJ"], skip_nulls=False, null_value=np.nan))
|
||||
qtdl_df = df[df[dlbm] == '12']
|
||||
if qtdl_df['TBDLMJ'].isnull().any() or qtdl_df['TBDLMJ'].eq(0).any():
|
||||
print("警告:其他地类TBDLMJ字段 存在空值或无效的记录,将不平差其他地类")
|
||||
target_areas = {}
|
||||
else:
|
||||
area_by_group = df.groupby(dlbm)["TBDLMJ"].sum()
|
||||
|
||||
for key in area_by_group.keys():
|
||||
area_by_group[key] = area_by_group[key] * 0.0015
|
||||
|
||||
target_areas = area_by_group.to_dict()
|
||||
|
||||
# 获取目标面积
|
||||
gangnan_target = target_df[target_df['行政单位'] == xzqmc]
|
||||
|
||||
if gangnan_target.empty:
|
||||
print(f"警告:未找到{xzqmc}的目标面积数据,将使用TBDLMJ数据进行平差")
|
||||
return target_areas
|
||||
|
||||
for dlbm, dlmc in landuse_types.items():
|
||||
if dlmc in gangnan_target.columns:
|
||||
if gangnan_target[dlmc].values[0]:
|
||||
target_areas[dlbm] = gangnan_target[dlmc].values[0]
|
||||
|
||||
return target_areas
|
||||
|
||||
except Exception as e:
|
||||
print(f"计算面积时出错: {str(e)}")
|
||||
return None
|
||||
|
||||
def main():
|
||||
|
||||
params = None
|
||||
temp_files_to_clean = []
|
||||
original_workspace = None
|
||||
try:
|
||||
# 1. 解析参数
|
||||
params = parse_arguments()
|
||||
|
||||
reclassed_polygon = params["reclassed_polygon"]
|
||||
soil_property = params["soil_property"]
|
||||
xzq_polygon = params["xzq_features"]
|
||||
dltb_polygon = params["dltb_features"]
|
||||
output_path = params["batch_output_folder"]
|
||||
input_path = params["input_folder"]
|
||||
output_settings = params["output_settings"]
|
||||
xzqmc = params["xzqmc"]
|
||||
is_by_xzq = params["is_by_xzq"]
|
||||
|
||||
original_workspace = arcpy.env.workspace
|
||||
arcpy.env.workspace = input_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
if not arcpy.Exists(reclassed_polygon):
|
||||
raise FileNotFoundError(f"输入文件不存在: {reclassed_polygon}")
|
||||
if not arcpy.Exists(xzq_polygon):
|
||||
raise FileNotFoundError(f"输入文件不存在: {xzq_polygon}")
|
||||
if not arcpy.Exists(dltb_polygon):
|
||||
raise FileNotFoundError(f"输入文件不存在: {dltb_polygon}")
|
||||
|
||||
if not os.path.exists(output_path):
|
||||
os.makedirs(output_path)
|
||||
output_origin_path = os.path.join(output_path, "原始结果")
|
||||
if not os.path.exists(output_origin_path):
|
||||
os.makedirs(output_origin_path)
|
||||
|
||||
output_xlsx_path = os.path.join(output_origin_path, f"{soil_property}_原始面积统计表.xlsx")
|
||||
output_adjust_xlsx_path = os.path.join(output_path, f"{soil_property}_面积统计表.xlsx")
|
||||
output_jpg_path = os.path.join(output_path, f"{soil_property}_area_stats.jpg")
|
||||
|
||||
out_feature_class = fr"in_memory\out_feature_class_{uuid.uuid4().hex[:8]}"
|
||||
out_dbf_table = fr"in_memory\out_dbf_table_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
temp_files_to_clean.append([out_feature_class, out_dbf_table])
|
||||
|
||||
# 求地类图斑和重分类栅格面的交集
|
||||
print_status(f"求地类图斑和重分类栅格面的交集...")
|
||||
in_features = [dltb_polygon, reclassed_polygon]
|
||||
arcpy.analysis.Intersect(
|
||||
in_features=in_features,
|
||||
out_feature_class=out_feature_class,
|
||||
join_attributes="ALL",
|
||||
output_type="INPUT"
|
||||
)
|
||||
|
||||
if xzqmc in yunnan_region:
|
||||
print_status(f"开始执行交集制表...")
|
||||
arcpy.analysis.TabulateIntersection(
|
||||
in_zone_features=xzq_polygon, # 乡镇边界
|
||||
zone_fields="XZQMC",
|
||||
in_class_features=out_feature_class,
|
||||
out_table=out_dbf_table,
|
||||
class_fields="gridcode;YNDLBM",
|
||||
out_units="SQUARE_METERS"
|
||||
)
|
||||
elif xzqmc in guangxi_region:
|
||||
# 交集制表
|
||||
print_status(f"开始执行交集制表...")
|
||||
arcpy.analysis.TabulateIntersection(
|
||||
in_zone_features=xzq_polygon, # 乡镇边界
|
||||
zone_fields="XZQMC",
|
||||
in_class_features=out_feature_class,
|
||||
out_table=out_dbf_table,
|
||||
class_fields="gridcode;YJDLBM",
|
||||
out_units="SQUARE_METERS"
|
||||
)
|
||||
else:
|
||||
print_status(f"未找到{xzqmc}的区域配置")
|
||||
raise ValueError(f"未找到{xzqmc}的区域配置")
|
||||
|
||||
# 读取DBF表格到Pandas DataFrame
|
||||
clipped_gdf = read_arcgis_table(out_dbf_table)
|
||||
|
||||
# 准备参数
|
||||
try:
|
||||
if xzqmc in yunnan_region:
|
||||
stats = (
|
||||
clipped_gdf.groupby(["XZQMC", "YNDLBM", "GRIDCODE"])
|
||||
.agg({"temp_area": "sum"})
|
||||
.reset_index()
|
||||
)
|
||||
elif xzqmc in guangxi_region:
|
||||
stats = (
|
||||
clipped_gdf.groupby(["XZQMC", "YJDLBM", "GRIDCODE"])
|
||||
.agg({"temp_area": "sum"})
|
||||
.reset_index()
|
||||
)
|
||||
else:
|
||||
print_status(f"未找到{xzqmc}的区域配置")
|
||||
raise ValueError(f"未找到{xzqmc}的区域配置")
|
||||
except Exception as e:
|
||||
stats = (
|
||||
clipped_gdf.groupby(["XZQMC", "YJDLBM", "GRIDCODE"])
|
||||
.agg({"temp_area": "sum"})
|
||||
.reset_index()
|
||||
)
|
||||
stats["adjusted_area"] = stats["temp_area"]
|
||||
# stats.to_csv("area_stats.csv", index=True)
|
||||
|
||||
# 重命名列(按实际土壤分级字段调整)
|
||||
if soil_property == "PH" or soil_property == "TRRZ":
|
||||
grade_map = get_specail_map(output_settings["标准等级"])
|
||||
elif "1" in output_settings["标准等级"].values(): # 土壤质地
|
||||
grade_map = {str(i+1): str(val) for i,val in enumerate(output_settings["标准等级"].keys())}
|
||||
elif "-10~-0.3" in output_settings["标准等级"].values(): # 酸化pH
|
||||
grade_map = {str(i+1): str(val) for i,val in enumerate(output_settings["标准等级"].keys())}
|
||||
elif "-10~0.1" in output_settings["标准等级"].values(): # 二普-三普变化pH
|
||||
grade_map = {str(i + 1): str(val) for i, val in enumerate(output_settings["标准等级"].keys())}
|
||||
else:
|
||||
grade_map = {str(i+1): str(val) for i,val in enumerate(output_settings["标准等级"].values())}
|
||||
|
||||
if xzqmc in guangxi_region:
|
||||
landuse_map = {"01": "耕地", "02": "园地", "03": "林地", "04": "草地", "12": "其他"}
|
||||
else:
|
||||
landuse_map = {"0101": "水田", "0102": "水浇地", "0103": "旱地", "02": "园地", "03": "林地", "04": "草地", "12": "其他"}
|
||||
|
||||
# 平差处理
|
||||
excel_target_path = Path("tools/config_json/公布的变更调查平差面积.xlsx") # 您的目标面积Excel文件路径
|
||||
each_dl_target = get_area_by_group(dltb_polygon, excel_target_path, xzqmc, is_by_xzq) # 获取每个地类目标面积
|
||||
# if soil_property == "GZCHD":
|
||||
# print(each_dl_target)
|
||||
# each_dl_target = {"01":each_dl_target["01"]}
|
||||
|
||||
if is_by_xzq:
|
||||
adjusted_stats = 平差工具.adjust_by_district_landuse(stats, each_dl_target)
|
||||
else:
|
||||
adjusted_stats = 平差工具.adjust_area_statistics(stats, each_dl_target)
|
||||
# print(adjusted_stats)
|
||||
|
||||
# 2. 生成XLSX报告
|
||||
reporter = SoilQualityReporter(output_xlsx_path, grade_map, landuse_map, output_settings)
|
||||
reporter.generate_report(stats)
|
||||
|
||||
reporter_adjust = SoilQualityReporter(output_adjust_xlsx_path, grade_map, landuse_map, output_settings)
|
||||
reporter_adjust.generate_report(adjusted_stats)
|
||||
|
||||
print_result(True, output_jpg_path, "")
|
||||
except Exception as e:
|
||||
error_msg = f"主函数错误: {str(e)}\n{traceback.format_exc()}"
|
||||
print_status(error_msg)
|
||||
print_result(False, error_message=error_msg)
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files_to_clean, workspace=original_workspace)
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print_status("开始执行")
|
||||
main()
|
||||
143
tools/core/stats_sh_to_excel.py
Normal file
143
tools/core/stats_sh_to_excel.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
输入重分类后栅格转面要素类、乡镇边界面要素类、地类图斑要素类;
|
||||
按一级地类统计土壤属性面积 和 按乡镇统计土壤属性面积;
|
||||
酸化情况统计表生成,第一按样点数量统计,第二按制图面积统计(分土壤类型、乡镇、土地利用类型等进行统计)
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
import arcpy
|
||||
import argparse
|
||||
|
||||
from tools.core.utils import 平差工具
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
sys.path.append(str(Path(__file__).parent))
|
||||
from acid_stats import 空间连接, 行政区划酸化统计表, 土地利用类型酸化统计表, 土壤类型图酸化统计表
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description="将ArcGIS表格转换为Excel")
|
||||
parser.add_argument("--settings_path", required=True, help="配置文件路径")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.settings_path:
|
||||
with open(args.settings_path, 'r', encoding="utf-8") as settings_file:
|
||||
settings = json.load(settings_file)
|
||||
area_stat_settings = settings.get("acid_stat_settings", {})
|
||||
else:
|
||||
print_status("错误: 未找到有效配置文件")
|
||||
sys.exit(1)
|
||||
|
||||
return area_stat_settings
|
||||
|
||||
def print_status(message):
|
||||
"""
|
||||
输出状态信息到标准输出,用于 GUI 实时显示
|
||||
格式: STATUS: <message>
|
||||
"""
|
||||
print(f"STATUS:{message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def print_result(success, output_path="", error_message=""):
|
||||
"""
|
||||
输出最终结果到标准输出,用于 GUI 判断任务状态和获取结果
|
||||
格式: RESULT:True|<output_path>|
|
||||
格式: RESULT:False||<error_message>
|
||||
"""
|
||||
if success:
|
||||
print(f"RESULT:True|{output_path}|")
|
||||
else:
|
||||
# 在错误信息中替换换行符,避免干扰解析
|
||||
cleaned_error_message = error_message.replace('\n', ' ').replace('\r', '')
|
||||
print(f"RESULT:False||{cleaned_error_message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def log_arcpy_message(message):
|
||||
"""输出 ArcPy 产生的 geoprocessing 消息"""
|
||||
# 可以在这里进一步处理或过滤 ArcPy 消息
|
||||
if message.type == 'Message':
|
||||
print_status(f"ArcPy消息: {message.message}")
|
||||
elif message.type == 'Warning':
|
||||
print_status(f"ArcPy警告: {message.message}")
|
||||
elif message.type == 'Error':
|
||||
# 对于错误,也可以记录到标准错误
|
||||
print_status(f"ArcPy错误: {message.message}")
|
||||
sys.stderr.write(f"ArcPyError:{message.message}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
def main():
|
||||
|
||||
params = None
|
||||
temp_files_to_clean = []
|
||||
original_workspace = None
|
||||
try:
|
||||
# 1. 解析参数
|
||||
params = parse_arguments()
|
||||
|
||||
xzq_polygon = params["xzq_features"]
|
||||
dltb_polygon = params["dltb_features"]
|
||||
output_path = params["batch_output_folder"]
|
||||
workspace_path = params["workspace_path"]
|
||||
trlx_features = params["soil_type_features"] # 土壤类型图
|
||||
assign_raster = params["assign_raster"] # 三普或者二普栅格
|
||||
ph_sample_feature = params["ph_samples"] # PH样点
|
||||
sh_ph_tif_temp = params["acid_raster"] # 酸化PH栅格
|
||||
ph_classed_polygon = params["acid_ph_features"] # 酸化PH重分类后要素
|
||||
xzqmc = params["xzqmc"]
|
||||
ph_sample_table = "历史样点PH信息_Table"
|
||||
|
||||
|
||||
original_workspace = arcpy.env.workspace
|
||||
arcpy.env.workspace = workspace_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
# sh_ph_tif = f"in_memory/temp_ph_raster"
|
||||
# temp_files_to_clean.append(sh_ph_tif)
|
||||
|
||||
input_ph_raster = arcpy.Raster(sh_ph_tif_temp)
|
||||
filtered_raster = arcpy.sa.Con(input_ph_raster > 0.3, input_ph_raster)
|
||||
# filtered_raster.save(sh_ph_tif)
|
||||
sh_ph_tif = arcpy.Raster(filtered_raster)
|
||||
temp_files_to_clean.append(sh_ph_tif)
|
||||
|
||||
# 1. 进行空间连接及赋值PH样点
|
||||
if not arcpy.Exists(ph_sample_table):
|
||||
print_status("样点空间连接...")
|
||||
空间连接.export_to_points(ph_sample_feature, dltb_polygon, trlx_features, xzq_polygon, assign_raster, workspace_path)
|
||||
time.sleep(4)
|
||||
|
||||
excel_target_path = Path("tools/config_json/公布的变更调查平差面积.xlsx") # 您的目标面积Excel文件路径
|
||||
target_area_dict = 平差工具.get_area_by_group(dltb_polygon, excel_target_path, xzqmc) # 获取每个地类目标面积
|
||||
|
||||
# 2. 制作统计表
|
||||
print_status("生成行政区划表...")
|
||||
df_with_factor = 行政区划酸化统计表.main(workspace_path, xzq_polygon, ph_classed_polygon, dltb_polygon, sh_ph_tif, output_path, target_area_dict)
|
||||
time.sleep(4)
|
||||
|
||||
print_status("生成土地利用类型表...")
|
||||
土地利用类型酸化统计表.main(workspace_path, ph_classed_polygon,dltb_polygon, sh_ph_tif, output_path,target_area_dict)
|
||||
time.sleep(4)
|
||||
|
||||
print_status("生成土壤类型酸化表...")
|
||||
土壤类型图酸化统计表.main(workspace_path, trlx_features, ph_classed_polygon, sh_ph_tif, output_path, df_with_factor)
|
||||
time.sleep(4)
|
||||
|
||||
print_result(True, output_path, "")
|
||||
except Exception as e:
|
||||
error_msg = f"主函数错误: {str(e)}\n{traceback.format_exc()}"
|
||||
print_status(error_msg)
|
||||
print_result(False, error_message=error_msg)
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files_to_clean, workspace=original_workspace)
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print_status("开始执行")
|
||||
main()
|
||||
235
tools/core/stats_soil_prop_to_excel.py
Normal file
235
tools/core/stats_soil_prop_to_excel.py
Normal file
@@ -0,0 +1,235 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
输入重分类后栅格转面要素类、乡镇边界面要素类、地类图斑要素类;
|
||||
按一级地类统计土壤属性面积 和 按乡镇统计土壤属性面积;
|
||||
土壤属性统计表生成,第一按样点数量统计,第二按制图面积统计(分土壤类型、乡镇、土地利用类型等进行统计)
|
||||
"""
|
||||
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
import arcpy
|
||||
import argparse
|
||||
|
||||
from tools.core.utils import 平差工具, common_utils
|
||||
|
||||
sys.path.append(str(Path(__file__).parent))
|
||||
from soil_prop_stats import B1土壤属性分级分布, B2土地利用类型土壤属性, B3不同土壤类型土壤属性, E1土壤属性历史变化, B3_TRZD不同土壤类型土壤属性, B1_TRZD土壤属性分级分布, B2_TRZD土地利用类型土壤属性
|
||||
from soil_prop_stats import B3_TRZD12不同土壤类型土壤属性, B1_TRZD12土壤属性分级分布, B2_TRZD12土地利用类型土壤属性
|
||||
from tools.config.arcgis_field_cal_code import codeblock_dltb_yjdl, codeblock_dltb_ejdl
|
||||
from tools.core.utils.os_utils import temp_files_processor
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description="将ArcGIS表格转换为Excel")
|
||||
parser.add_argument("--settings_path", required=True, help="配置文件路径")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.settings_path:
|
||||
with open(args.settings_path, 'r', encoding="utf-8") as settings_file:
|
||||
settings = json.load(settings_file)
|
||||
area_stat_settings = settings.get("soil_prop_stat_settings", {})
|
||||
else:
|
||||
print_status("错误: 未找到有效配置文件")
|
||||
sys.exit(1)
|
||||
|
||||
return area_stat_settings
|
||||
|
||||
def print_status(message):
|
||||
"""
|
||||
输出状态信息到标准输出,用于 GUI 实时显示
|
||||
格式: STATUS: <message>
|
||||
"""
|
||||
print(f"STATUS:{message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def print_result(success, output_path="", error_message=""):
|
||||
"""
|
||||
输出最终结果到标准输出,用于 GUI 判断任务状态和获取结果
|
||||
格式: RESULT:True|<output_path>|
|
||||
格式: RESULT:False||<error_message>
|
||||
"""
|
||||
if success:
|
||||
print(f"RESULT:True|{output_path}|")
|
||||
else:
|
||||
# 在错误信息中替换换行符,避免干扰解析
|
||||
cleaned_error_message = error_message.replace('\n', ' ').replace('\r', '')
|
||||
print(f"RESULT:False||{cleaned_error_message}")
|
||||
sys.stdout.flush() # 确保立即输出
|
||||
|
||||
def log_arcpy_message(message):
|
||||
"""输出 ArcPy 产生的 geoprocessing 消息"""
|
||||
# 可以在这里进一步处理或过滤 ArcPy 消息
|
||||
if message.type == 'Message':
|
||||
print_status(f"ArcPy消息: {message.message}")
|
||||
elif message.type == 'Warning':
|
||||
print_status(f"ArcPy警告: {message.message}")
|
||||
elif message.type == 'Error':
|
||||
# 对于错误,也可以记录到标准错误
|
||||
print_status(f"ArcPy错误: {message.message}")
|
||||
sys.stderr.write(f"ArcPyError:{message.message}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
def process_soil_property(args):
|
||||
"""处理单个土壤属性的函数,用于多进程"""
|
||||
soil_prop_name, config, data_source_path, history_samples_path, reclassed_features_path, history_reclassed_features_path, sanpu_prop_tif_path, history_raster_path, dltb_features, trlx_features, output_path, target_area_df1, target_area_df2, target_area_all, xzqmc = args
|
||||
|
||||
try:
|
||||
prop_config = config['export_config'][soil_prop_name]
|
||||
reclassed_features = os.path.join(reclassed_features_path, f"{soil_prop_name}_reclassed_polygon.shp")
|
||||
history_reclassed_features = os.path.join(history_reclassed_features_path, f"{soil_prop_name}_reclassed_polygon.shp")
|
||||
soil_prop_tif = os.path.join(sanpu_prop_tif_path, f"{soil_prop_name}.tif")
|
||||
history_raster = os.path.join(history_raster_path, f"{soil_prop_name}.tif")
|
||||
|
||||
if not arcpy.Exists(reclassed_features) or not arcpy.Exists(soil_prop_tif):
|
||||
print(f"缺少{soil_prop_name}的栅格或重分类要素,请检查输入文件路径是否正确!")
|
||||
return False
|
||||
|
||||
print(f"生成{soil_prop_name}的表1...")
|
||||
if soil_prop_name == "TRZD12":
|
||||
B1_TRZD12土壤属性分级分布.main(data_source_path, soil_prop_name, reclassed_features, dltb_features, output_path, target_area_all,xzqmc, prop_config)
|
||||
elif soil_prop_name == "TRZD":
|
||||
B1_TRZD土壤属性分级分布.main(data_source_path, soil_prop_name, reclassed_features, dltb_features, output_path, target_area_all,xzqmc, prop_config)
|
||||
else:
|
||||
B1土壤属性分级分布.main(data_source_path, soil_prop_name, reclassed_features, dltb_features, soil_prop_tif, output_path, target_area_all,xzqmc, prop_config)
|
||||
time.sleep(2)
|
||||
|
||||
# print(f"生成{soil_prop_name}的表2...")
|
||||
# if soil_prop_name == "TRZD12":
|
||||
# B2_TRZD12土地利用类型土壤属性.main(data_source_path, soil_prop_name, dltb_features, reclassed_features, output_path, target_area_df2, prop_config)
|
||||
# elif soil_prop_name == "TRZD":
|
||||
# B2_TRZD土地利用类型土壤属性.main(data_source_path, soil_prop_name, dltb_features, reclassed_features, output_path, target_area_df2, prop_config)
|
||||
# else:
|
||||
# B2土地利用类型土壤属性.main(data_source_path, soil_prop_name, dltb_features, soil_prop_tif, output_path, target_area_df2, prop_config)
|
||||
# time.sleep(2)
|
||||
|
||||
# print(f"生成{soil_prop_name}的表3...")
|
||||
# if soil_prop_name == "TRZD12":
|
||||
# B3_TRZD12不同土壤类型土壤属性.main(data_source_path,soil_prop_name,trlx_features,reclassed_features,output_path,target_area_df1,prop_config)
|
||||
# elif soil_prop_name == "TRZD":
|
||||
# B3_TRZD不同土壤类型土壤属性.main(data_source_path, soil_prop_name, trlx_features, reclassed_features, output_path, target_area_df1, prop_config)
|
||||
# else:
|
||||
# B3不同土壤类型土壤属性.main(data_source_path, soil_prop_name, trlx_features, soil_prop_tif, output_path, target_area_df1, prop_config, dltb_features)
|
||||
# time.sleep(2)
|
||||
|
||||
print(f"生成{soil_prop_name}的历史对比...")
|
||||
# if arcpy.Exists(history_reclassed_features) and arcpy.Exists(history_raster) and arcpy.Exists(history_samples_path):
|
||||
# E1土壤属性历史变化.main(xzqmc,data_source_path,history_samples_path,reclassed_features,history_reclassed_features, soil_prop_name, dltb_features, soil_prop_tif,history_raster, output_path, target_area_all, prop_config)
|
||||
# time.sleep(2)
|
||||
# else:
|
||||
# print_status(f"警告:缺少{soil_prop_name}的历史栅格或重分类要素,请检查输入文件路径是否正确!")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"处理{soil_prop_name}时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
|
||||
params = None
|
||||
temp_files_to_clean = []
|
||||
original_workspace = None
|
||||
try:
|
||||
# 1. 解析参数
|
||||
params = parse_arguments()
|
||||
|
||||
xzqmc = params["xzqmc"]
|
||||
config_file = params["config_file"]
|
||||
output_path = params["output_folder"]
|
||||
data_source_path = params["data_source_path"]
|
||||
sanpu_prop_tif_path = params["sanpu_prop_tif_folder"]
|
||||
reclassed_features_path = params["reclassed_feature_folder"]
|
||||
# history_reclassed_features_path = params["history_reclassed_feature_folder"]
|
||||
soil_prop_name_list = params["sample_list"]
|
||||
# history_samples_path = params["history_samples_folder"]
|
||||
# history_raster_path = params["history_raster_folder"]
|
||||
history_samples_path = r"E:\@三普属性图出图\广西武鸣区\@基础数据\测土配方样点数据\测土配方样点.gdb"
|
||||
history_raster_path = r"E:\@三普属性图出图\广西武鸣区\@基础数据\测土配方栅格\投影后"
|
||||
history_reclassed_features_path = r"E:\@三普属性图出图\广西武鸣区\过程数据\测土配方重分类\面积统计用栅格面"
|
||||
|
||||
|
||||
dltb_features = os.path.join(data_source_path, "地类图斑")
|
||||
trlx_features = os.path.join(data_source_path, "土壤类型图")
|
||||
|
||||
original_workspace = arcpy.env.workspace
|
||||
arcpy.env.workspace = data_source_path
|
||||
arcpy.env.overwriteOutput = True
|
||||
|
||||
excel_target = Path("tools/config_json/公布的变更调查平差面积.xlsx") # 您的目标面积Excel文件路径
|
||||
excel_target_path = str(excel_target.resolve())
|
||||
target_area_df2 = 平差工具.get_target_areas(excel_target_path,"Sheet2", xzqmc) # 获取每个二级地类目标面积
|
||||
target_area_df1 = 平差工具.get_target_areas(excel_target_path,"Sheet1", xzqmc) # 获取每个一级地类目标面积
|
||||
target_area_all = 平差工具.get_target_areas_by_group(excel_target_path)
|
||||
|
||||
# 2. 制作统计表
|
||||
# 计算土地利用类型图斑的地类
|
||||
if arcpy.Exists(dltb_features):
|
||||
try:
|
||||
check_fields = ["YJDL", "EJDL", "YJDLBM", "YJDL_EJDL"]
|
||||
if not common_utils.check_fields_exist_describe(dltb_features, check_fields):
|
||||
arcpy.management.CalculateField(dltb_features, "EJDL", "calculate_ejdl(!DLBM!,!DLMC!)", "PYTHON3", codeblock_dltb_ejdl)
|
||||
arcpy.management.CalculateField(dltb_features, "YJDL", "calculate_yjdl(!DLBM!)", "PYTHON3", codeblock_dltb_yjdl)
|
||||
arcpy.management.CalculateField(dltb_features, "YJDLBM", "!DLBM![:2]", "PYTHON3")
|
||||
arcpy.management.CalculateField(dltb_features,"YJDL_EJDL","!YJDL! + '_' + !EJDL!","PYTHON3")
|
||||
except Exception as e:
|
||||
print(f'报什么错:{e}')
|
||||
|
||||
# 计算土壤类型图斑的字段用于后续交集制表
|
||||
if arcpy.Exists(trlx_features):
|
||||
try:
|
||||
check_fields = ["YL_TS"]
|
||||
if not common_utils.check_fields_exist_describe(trlx_features, check_fields):
|
||||
# 计算YL_TS字段的值
|
||||
arcpy.management.CalculateField(trlx_features, "YL_TS", "!YL! + '_' + !TS!", "PYTHON3")
|
||||
except Exception as e:
|
||||
print(f'报什么错:{e}')
|
||||
|
||||
# 获取土壤属性配置文件
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# 准备多进程参数
|
||||
process_args = []
|
||||
for soil_prop_name in soil_prop_name_list:
|
||||
args = (soil_prop_name, config, data_source_path, history_samples_path, reclassed_features_path, history_reclassed_features_path,
|
||||
sanpu_prop_tif_path, history_raster_path, dltb_features, trlx_features, output_path,
|
||||
target_area_df1, target_area_df2, target_area_all,xzqmc)
|
||||
process_args.append(args)
|
||||
|
||||
# 使用多进程处理
|
||||
cpu_count = multiprocessing.cpu_count()
|
||||
# num_processes = min(int(cpu_count if cpu_count<2 else cpu_count/2), len(soil_prop_name_list))
|
||||
num_processes = 3
|
||||
|
||||
print(f"使用 {num_processes} 个进程并行处理 {len(soil_prop_name_list)} 个土壤属性...")
|
||||
|
||||
with multiprocessing.Pool(processes=num_processes) as pool:
|
||||
results = pool.map(process_soil_property, process_args)
|
||||
|
||||
# 检查所有任务是否成功完成
|
||||
if all(results):
|
||||
print_result(True, output_path, "")
|
||||
else:
|
||||
failed_count = results.count(False)
|
||||
error_msg = f"{failed_count} 个土壤属性处理失败"
|
||||
print_result(False, error_message=error_msg)
|
||||
|
||||
print_result(True, output_path, "")
|
||||
except Exception as e:
|
||||
error_msg = f"主函数错误: {str(e)}\n{traceback.format_exc()}"
|
||||
print_status(error_msg)
|
||||
print_result(False, error_message=error_msg)
|
||||
finally:
|
||||
temp_files_processor.clean_up_temp_files(temp_files_to_clean, workspace=original_workspace)
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print_status("开始执行")
|
||||
multiprocessing.freeze_support()
|
||||
main()
|
||||
249
tools/core/test_script.py
Normal file
249
tools/core/test_script.py
Normal file
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
测试脚本
|
||||
用于测试ArcGIS Pro图层属性和标注功能
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import traceback
|
||||
import arcpy
|
||||
|
||||
|
||||
def log(message):
|
||||
"""日志输出函数"""
|
||||
print(message)
|
||||
|
||||
|
||||
def test_arcgis_environment():
|
||||
"""测试ArcGIS Pro环境"""
|
||||
log("=== ArcGIS Pro环境信息 ===")
|
||||
log(f"Python版本: {sys.version}")
|
||||
log(f"Python路径: {sys.executable}")
|
||||
log(f"当前工作目录: {os.getcwd()}")
|
||||
log(f"ArcPy版本: {arcpy.GetInstallInfo()['Version']}")
|
||||
log(f"ArcPy产品: {arcpy.GetInstallInfo()['ProductName']}")
|
||||
|
||||
# 当前环境设置
|
||||
log("\n=== ArcGIS环境设置 ===")
|
||||
log(f"工作空间: {arcpy.env.workspace}")
|
||||
log(f"输出坐标系统: {arcpy.env.outputCoordinateSystem}")
|
||||
log(f"覆盖输出: {arcpy.env.overwriteOutput}")
|
||||
|
||||
|
||||
def test_layer_properties(layer_path):
|
||||
"""测试图层属性"""
|
||||
log(f"\n=== 图层属性测试: {layer_path} ===")
|
||||
|
||||
if not arcpy.Exists(layer_path):
|
||||
log(f"错误: 图层不存在 - {layer_path}")
|
||||
return
|
||||
|
||||
try:
|
||||
# 创建图层对象
|
||||
log("尝试创建图层对象...")
|
||||
layer = arcpy.mp.Layer(layer_path)
|
||||
log(f"成功创建图层: {layer.name}")
|
||||
|
||||
# 图层基本属性
|
||||
log("\n图层基本属性:")
|
||||
log(f"名称: {layer.name}")
|
||||
log(f"数据源类型: {type(layer.dataSource).__name__ if hasattr(layer, 'dataSource') else 'N/A'}")
|
||||
log(f"长名称: {layer.longName if hasattr(layer, 'longName') else 'N/A'}")
|
||||
|
||||
# 图层类型
|
||||
log("\n图层类型判断:")
|
||||
log(f"是要素图层: {layer.isFeatureLayer if hasattr(layer, 'isFeatureLayer') else 'N/A'}")
|
||||
log(f"是栅格图层: {layer.isRasterLayer if hasattr(layer, 'isRasterLayer') else 'N/A'}")
|
||||
log(f"是图形图层: {layer.isGroupLayer if hasattr(layer, 'isGroupLayer') else 'N/A'}")
|
||||
|
||||
# 支持的属性
|
||||
log("\n支持的属性:")
|
||||
properties = [
|
||||
'LABELCLASSES', 'SHOWLABELS', 'NAME', 'DATASOURCE', 'DEFINITIONQUERY',
|
||||
'VISIBLE', 'TRANSPARENCY', 'BRIGHTNESS', 'CONTRAST', 'SYMBOLOGY'
|
||||
]
|
||||
|
||||
for prop in properties:
|
||||
try:
|
||||
support = hasattr(layer, prop.lower()) or (hasattr(layer, 'supports') and layer.supports(prop))
|
||||
log(f"{prop}: {'支持' if support else '不支持'}")
|
||||
except Exception as e:
|
||||
log(f"{prop}: 检查失败 - {str(e)}")
|
||||
|
||||
# 尝试获取标注类
|
||||
log("\n标注类测试:")
|
||||
try:
|
||||
if hasattr(layer, 'listLabelClasses'):
|
||||
label_classes = layer.listLabelClasses()
|
||||
log(f"找到 {len(label_classes)} 个标注类")
|
||||
|
||||
# 显示每个标注类的信息
|
||||
for i, lc in enumerate(label_classes):
|
||||
log(f"标注类 #{i+1}: {lc.name if hasattr(lc, 'name') else 'N/A'}")
|
||||
log(f" 表达式: {lc.expression if hasattr(lc, 'expression') else 'N/A'}")
|
||||
log(f" SQL查询: {lc.SQLQuery if hasattr(lc, 'SQLQuery') else 'N/A'}")
|
||||
log(f" 可见: {lc.showClassLabels if hasattr(lc, 'showClassLabels') else 'N/A'}")
|
||||
else:
|
||||
log("图层不支持listLabelClasses方法")
|
||||
|
||||
# 检查标注状态
|
||||
if hasattr(layer, 'showLabels'):
|
||||
log(f"标注显示状态: {layer.showLabels}")
|
||||
else:
|
||||
log("图层没有showLabels属性")
|
||||
except Exception as e:
|
||||
log(f"获取标注类时出错: {str(e)}")
|
||||
|
||||
# 使用arcpy.da.Describe获取详细信息
|
||||
log("\narcpy.da.Describe描述信息:")
|
||||
try:
|
||||
desc = arcpy.da.Describe(layer)
|
||||
for key in desc:
|
||||
# 跳过复杂对象
|
||||
if isinstance(desc[key], (dict, list, tuple)):
|
||||
log(f"{key}: [复杂类型]")
|
||||
else:
|
||||
log(f"{key}: {desc[key]}")
|
||||
except Exception as e:
|
||||
log(f"获取Describe信息时出错: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
log(f"测试图层属性时出错: {str(e)}")
|
||||
log(traceback.format_exc())
|
||||
|
||||
|
||||
def test_annotation_conversion(map_path, layer_name, output_folder):
|
||||
"""测试标注转注记功能"""
|
||||
log(f"\n=== 标注转注记测试: {map_path}, 图层: {layer_name} ===")
|
||||
|
||||
if not arcpy.Exists(map_path):
|
||||
log(f"错误: 地图文档不存在 - {map_path}")
|
||||
return
|
||||
|
||||
# 确保输出文件夹存在
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
|
||||
try:
|
||||
# 打开地图文档
|
||||
log(f"打开地图文档...")
|
||||
aprx = arcpy.mp.ArcGISProject(map_path)
|
||||
|
||||
# 获取活动地图
|
||||
log(f"获取地图...")
|
||||
maps = aprx.listMaps()
|
||||
if not maps:
|
||||
log("错误: 地图文档中没有地图")
|
||||
return
|
||||
|
||||
target_map = maps[0] # 默认使用第一个地图
|
||||
log(f"使用地图: {target_map.name}")
|
||||
|
||||
# 查找指定图层
|
||||
log(f"查找图层: {layer_name}")
|
||||
layers = target_map.listLayers(layer_name)
|
||||
if not layers:
|
||||
log(f"错误: 找不到图层 {layer_name}")
|
||||
return
|
||||
|
||||
label_layer = layers[0]
|
||||
log(f"找到图层: {label_layer.name}")
|
||||
|
||||
# 测试图层属性
|
||||
test_layer_properties(label_layer)
|
||||
|
||||
# 尝试开启标注
|
||||
try:
|
||||
if hasattr(label_layer, 'showLabels'):
|
||||
log(f"当前标注状态: {label_layer.showLabels}")
|
||||
label_layer.showLabels = True
|
||||
log(f"已开启标注,新状态: {label_layer.showLabels}")
|
||||
except Exception as e:
|
||||
log(f"设置标注状态时出错: {str(e)}")
|
||||
|
||||
# 尝试执行标注转注记
|
||||
log("\n执行标注转注记...")
|
||||
try:
|
||||
anno_name = f"{label_layer.name}_Anno"
|
||||
output_anno = os.path.join(output_folder, anno_name)
|
||||
|
||||
# 检查是否已存在
|
||||
if arcpy.Exists(output_anno):
|
||||
log(f"注记已存在: {output_anno},将尝试删除")
|
||||
arcpy.management.Delete(output_anno)
|
||||
|
||||
# 执行转换
|
||||
log("使用ConvertLabelsToAnnotation...")
|
||||
arcpy.cartography.ConvertLabelsToAnnotation(
|
||||
target_map,
|
||||
[label_layer],
|
||||
output_folder,
|
||||
"FEATURE_LINKED",
|
||||
"STANDARD",
|
||||
None,
|
||||
None,
|
||||
None
|
||||
)
|
||||
log("ConvertLabelsToAnnotation执行成功")
|
||||
|
||||
except Exception as e:
|
||||
log(f"执行标注转注记时出错: {str(e)}")
|
||||
log(traceback.format_exc())
|
||||
|
||||
# 尝试替代方案
|
||||
try:
|
||||
log("\n尝试使用LabelFeatures替代方案...")
|
||||
arcpy.cartography.LabelFeatures(
|
||||
in_features=label_layer,
|
||||
out_geodatabase=output_folder
|
||||
)
|
||||
log("LabelFeatures执行成功")
|
||||
except Exception as e2:
|
||||
log(f"LabelFeatures也失败: {str(e2)}")
|
||||
|
||||
except Exception as e:
|
||||
log(f"测试标注转注记时出错: {str(e)}")
|
||||
log(traceback.format_exc())
|
||||
finally:
|
||||
if 'aprx' in locals():
|
||||
del aprx
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
parser = argparse.ArgumentParser(description='ArcGIS Pro图层和标注功能测试')
|
||||
parser.add_argument('--message', default='测试消息', help='测试消息')
|
||||
parser.add_argument('--count', type=int, default=1, help='重复次数')
|
||||
parser.add_argument('--map', help='地图文档路径(.aprx)')
|
||||
parser.add_argument('--layer', help='图层名称')
|
||||
parser.add_argument('--layer_path', help='图层文件路径(.lyrx)')
|
||||
parser.add_argument('--output', help='输出文件夹')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 输出系统信息
|
||||
test_arcgis_environment()
|
||||
|
||||
# 输出测试消息
|
||||
for i in range(args.count):
|
||||
log(f"\n{i+1}. {args.message}")
|
||||
|
||||
# 如果提供了图层文件路径,测试图层属性
|
||||
if args.layer_path:
|
||||
test_layer_properties(args.layer_path)
|
||||
|
||||
# 如果提供了地图文档、图层名称和输出文件夹,测试标注转注记
|
||||
if args.map and args.layer and args.output:
|
||||
test_annotation_conversion(args.map, args.layer, args.output)
|
||||
|
||||
# 退出前打印完成消息
|
||||
log("\n测试脚本执行完成")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
0
tools/core/utils/__init__.py
Normal file
0
tools/core/utils/__init__.py
Normal file
26
tools/core/utils/arcgis_utils.py
Normal file
26
tools/core/utils/arcgis_utils.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import arcpy
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def read_arcgis_table(table_path):
|
||||
"""
|
||||
将ArcGIS表格转换为Pandas DataFrame
|
||||
:param table_path: ArcGIS表格路径
|
||||
:return: Pandas DataFrame
|
||||
表格字段全部转换为大写
|
||||
面积字段AREA转换为亩,保留4位小数,存储在temp_area字段中
|
||||
"""
|
||||
array = arcpy.da.TableToNumPyArray(table_path, "*")
|
||||
|
||||
df = pd.DataFrame(array)
|
||||
# df.to_csv(r"D:\工作\三普成果编制\出图数据\广西海城区\过程数据\酸化面积统计表\temp.csv")
|
||||
|
||||
df.columns = df.columns.str.upper()
|
||||
df["temp_area"] = df["AREA"] * 0.0015
|
||||
df["temp_area"] = df["temp_area"].round(4)
|
||||
|
||||
# 删除可能存在的OID字段(如果不需要)
|
||||
if 'OID@' in df.columns:
|
||||
df = df.drop('OID@', axis=1)
|
||||
|
||||
return df
|
||||
291
tools/core/utils/common_utils.py
Normal file
291
tools/core/utils/common_utils.py
Normal file
@@ -0,0 +1,291 @@
|
||||
import arcpy
|
||||
import numpy as np
|
||||
|
||||
|
||||
def get_data_type(data_path):
|
||||
"""获取数据类型
|
||||
|
||||
:param data_path: 数据路径
|
||||
:return: 数据类型
|
||||
"""
|
||||
if arcpy.Exists(data_path):
|
||||
try:
|
||||
desc = arcpy.Describe(data_path)
|
||||
return desc.dataType
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_config_key(every_string: str) -> str:
|
||||
config_dict = {
|
||||
"AB": "有效硼","ACU": "有效铜","AMN": "有效锰","AMO": "有效钼","AS1": "有效硫","AZN": "有效锌","CEC": "阳离子交换量","ECA": "交换性钙",
|
||||
"EMG": "交换性镁","TSE": "全硒","TN": "全氮","TP": "全磷","TK": "全钾","AFE": "有效铁","AK": "速效钾","AP": "有效磷", "TRRZ": "土壤容重","LSFD":"砾石丰度",
|
||||
"OM": "有机质","FL": "粉粒含量","NL": "黏粒含量","SL": "砂粒含量","PH": "土壤 pH","YXTCHD": "有效土层厚度","GZCHD": "耕作层厚度","TRZD": "土壤质地","TRZD12": "土壤质地",
|
||||
"三普PH": "三普PH","二普PH": "二普PH","测土PH": "测土PH","二普-三普": "二普-三普","测土-三普": "测土-三普","二普-测土": "二普-测土"
|
||||
}
|
||||
try:
|
||||
for key in config_dict.keys():
|
||||
in_key = every_string.split("_")[0]
|
||||
if key == in_key:
|
||||
return key
|
||||
|
||||
return ""
|
||||
|
||||
except Exception as e:
|
||||
return ""
|
||||
|
||||
def parse_raster_standard(standard_str):
|
||||
"""解析重分类标准字符串,返回数值范围
|
||||
|
||||
例如:
|
||||
">2.00" -> (2.0, float('inf'))
|
||||
"1.00~2.00" -> (1.0, 2.0)
|
||||
"≤0.20" -> (0, 0.2)
|
||||
"""
|
||||
if "," in standard_str:
|
||||
temp = []
|
||||
parts = standard_str.split(",\n")
|
||||
for part in parts:
|
||||
temp_part = parse_raster_standard(part)
|
||||
temp.append(temp_part)
|
||||
return temp
|
||||
if ">" in standard_str:
|
||||
value = float(standard_str.replace(">", ""))
|
||||
return (value, float('inf'))
|
||||
elif "~" in standard_str:
|
||||
parts = standard_str.split("~")
|
||||
return (float(parts[0]), float(parts[1]))
|
||||
elif "≤" in standard_str:
|
||||
value = float(standard_str.replace("≤", ""))
|
||||
return (0, value)
|
||||
else:
|
||||
# 尝试直接解析为数值
|
||||
try:
|
||||
value = float(standard_str)
|
||||
return (value, value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def create_remap_table(standards_dict):
|
||||
"""根据标准配置创建重分类映射表
|
||||
|
||||
参数:
|
||||
standards_config -- 标准配置,格式为:
|
||||
{"标准1":5-6, "标准2":7-8, ...}
|
||||
remap_values -- 重分类值数组,默认为从1开始的整数序列
|
||||
|
||||
返回:
|
||||
重分类映射表,格式为 [[old_min, old_max, new_value], ...]
|
||||
"""
|
||||
|
||||
# 确保我们有一个有效的标准列表
|
||||
if not standards_dict or not isinstance(standards_dict, dict):
|
||||
print("警告: 没有有效的标准数据")
|
||||
return []
|
||||
|
||||
# 设置重分类值
|
||||
standards_length = len(standards_dict)
|
||||
remap_values = list(range(1, 2*standards_length + 1))
|
||||
|
||||
remap_table = []
|
||||
for i, (key, value) in enumerate(standards_dict.items()):
|
||||
range_tuple = parse_raster_standard(value)
|
||||
|
||||
if range_tuple:
|
||||
if type(range_tuple) is list:
|
||||
m = 0
|
||||
for range_tuple_item in range_tuple:
|
||||
j = m * standards_length + i
|
||||
remap_table.append([range_tuple_item[0], range_tuple_item[1], remap_values[j]])
|
||||
m = m + 1
|
||||
else:
|
||||
remap_table.append([range_tuple[0], range_tuple[1], remap_values[i]])
|
||||
|
||||
return remap_table
|
||||
|
||||
|
||||
def check_fields_exist_describe(feature_class, field_names):
|
||||
"""
|
||||
使用Describe函数检查要素类中字段是否存在
|
||||
"""
|
||||
try:
|
||||
desc = arcpy.Describe(feature_class)
|
||||
existing_fields = [field.name for field in desc.fields]
|
||||
|
||||
for field_name in field_names:
|
||||
if field_name not in existing_fields:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"检查字段时出错: {e}")
|
||||
return None
|
||||
|
||||
def get_grade_by_standard(value, grade_standards):
|
||||
"""
|
||||
通用的等级判断函数
|
||||
value: 数值
|
||||
grade_standards: 分级标准字典,如 {"等级一": ">2.00", "等级二": "1.00~2.00"}
|
||||
"""
|
||||
if value is None:
|
||||
return "无数据"
|
||||
|
||||
# 按等级顺序检查(从高到低)
|
||||
sorted_grades = sorted(grade_standards.items(),
|
||||
key=lambda x: list(grade_standards.keys()).index(x[0]))
|
||||
|
||||
for grade_name, grade_standard in sorted_grades:
|
||||
if is_value_in_grade(value, grade_standard):
|
||||
return grade_name
|
||||
|
||||
return "超出范围"
|
||||
|
||||
def is_value_in_grade(value, grade_standard):
|
||||
"""
|
||||
判断数值是否在分级标准范围内
|
||||
"""
|
||||
# 处理特殊字符
|
||||
grade_standard = grade_standard.replace('>', '>').replace('≤', '<=').replace('~', '~')
|
||||
|
||||
# 处理多范围情况(如pH值)
|
||||
if ',' in grade_standard:
|
||||
ranges = grade_standard.split(',')
|
||||
for range_str in ranges:
|
||||
if is_value_in_single_range(value, range_str.strip()):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return is_value_in_single_range(value, grade_standard)
|
||||
|
||||
def is_value_in_single_range(value, range_str):
|
||||
"""
|
||||
判断数值是否在单个范围内
|
||||
"""
|
||||
import re
|
||||
|
||||
# 提取数值
|
||||
numbers = re.findall(r'[-+]?\d*\.\d+|\d+', range_str)
|
||||
numbers = [float(num) for num in numbers]
|
||||
|
||||
if '>' in range_str and '~' in range_str:
|
||||
# 格式:>下限~上限
|
||||
return numbers[0] < value <= numbers[1]
|
||||
elif '>' in range_str:
|
||||
# 格式:>数值
|
||||
return value > numbers[0]
|
||||
elif '<=' in range_str:
|
||||
# 格式:<=数值
|
||||
return value <= numbers[0]
|
||||
elif '~' in range_str:
|
||||
# 格式:下限~上限
|
||||
return numbers[0] < value <= numbers[1]
|
||||
else:
|
||||
# 无法解析,使用字符串匹配
|
||||
return str(value) == range_str
|
||||
|
||||
def vectorized_grade_assignment(values, grade_standards):
|
||||
"""
|
||||
向量化的等级分配(性能更好)
|
||||
"""
|
||||
# 确保输入值是数值类型,如果是字符串则转换为浮点数
|
||||
if isinstance(values, np.ndarray) and values.dtype.kind in 'OUS': # 字符串类型
|
||||
values = values.astype(float)
|
||||
elif hasattr(values, 'dtype') and values.dtype == object: # 对象类型,可能包含字符串
|
||||
values = values.astype(float)
|
||||
|
||||
conditions = []
|
||||
choices = []
|
||||
|
||||
# 按等级顺序构建条件
|
||||
# 创建两个列表来分别存储上段和下段范围
|
||||
upper_ranges = []
|
||||
lower_ranges = []
|
||||
|
||||
# 遍历排序后的等级
|
||||
for i, (level, ranges) in enumerate(sorted(grade_standards.items(), key=lambda x: list(grade_standards.keys()).index(x[0])), 1):
|
||||
# 分割范围字符串
|
||||
range_list = [r.strip() for r in ranges.split(',')]
|
||||
|
||||
if len(range_list) >= 1:
|
||||
upper_ranges.append((i, range_list[0]))
|
||||
|
||||
if len(range_list) >= 2:
|
||||
# 计算下段范围的索引(原始索引 + 等级总数)
|
||||
lower_index = i + len(grade_standards)
|
||||
lower_ranges.append((lower_index, range_list[1]))
|
||||
|
||||
# 合并结果
|
||||
sorted_grades = upper_ranges + lower_ranges
|
||||
# sorted_grades = sorted(grade_standards.items(), key=lambda x: list(grade_standards.keys()).index(x[0]))
|
||||
|
||||
for grade_name, grade_standard in sorted_grades:
|
||||
condition = create_condition(values, grade_standard)
|
||||
conditions.append(condition)
|
||||
choices.append(grade_name)
|
||||
|
||||
# 使用np.select进行向量化操作
|
||||
result = np.select(conditions, choices, default="超出范围")
|
||||
return result
|
||||
|
||||
def create_condition(values, grade_standard):
|
||||
"""
|
||||
创建numpy条件
|
||||
"""
|
||||
# 清理字符串:替换特殊字符并移除换行符和空格
|
||||
grade_standard = (grade_standard.replace('>', '>')
|
||||
.replace('≤', '<=')
|
||||
.replace('~', '~')
|
||||
.replace('\n', '') # 移除换行符
|
||||
.replace(' ', '')) # 移除空格
|
||||
|
||||
if ',' in grade_standard:
|
||||
# 多范围处理
|
||||
ranges = grade_standard.split(',')
|
||||
condition = None
|
||||
for range_str in ranges:
|
||||
if range_str: # 确保不是空字符串
|
||||
range_condition = create_single_condition(values, range_str.strip())
|
||||
if condition is None:
|
||||
condition = range_condition
|
||||
else:
|
||||
condition = condition | range_condition
|
||||
return condition
|
||||
else:
|
||||
return create_single_condition(values, grade_standard)
|
||||
|
||||
def create_single_condition(values, range_str):
|
||||
"""
|
||||
创建单个范围的条件
|
||||
"""
|
||||
import re
|
||||
|
||||
# 调试输出,帮助排查问题
|
||||
# print(f"处理范围字符串: '{range_str}'")
|
||||
|
||||
# 提取数字
|
||||
numbers = re.findall(r'[-+]?\d*\.\d+|\d+', range_str)
|
||||
numbers = [float(num) for num in numbers]
|
||||
|
||||
if not numbers:
|
||||
raise ValueError(f"无法从字符串 '{range_str}' 中提取数字")
|
||||
|
||||
# 根据范围符号创建条件
|
||||
if '>' in range_str and '<=' in range_str:
|
||||
# 处理 >x<=y 的情况(虽然不常见)
|
||||
return (values > numbers[0]) & (values <= numbers[1])
|
||||
elif '>' in range_str and '~' in range_str:
|
||||
return (values > numbers[0]) & (values <= numbers[1])
|
||||
elif '>' in range_str:
|
||||
return values > numbers[0]
|
||||
elif '<=' in range_str:
|
||||
return values <= numbers[0]
|
||||
elif '~' in range_str:
|
||||
return (values > numbers[0]) & (values <= numbers[1])
|
||||
else:
|
||||
# 如果是单个数字
|
||||
try:
|
||||
return values == float(range_str)
|
||||
except ValueError:
|
||||
raise ValueError(f"无法解析的范围字符串: '{range_str}'")
|
||||
59
tools/core/utils/excel_utils.py
Normal file
59
tools/core/utils/excel_utils.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# utils/excel_utils.py
|
||||
import re
|
||||
from openpyxl.styles import Font, Alignment, Border, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
|
||||
class ExcelStyleUtils:
|
||||
"""Excel样式工具类"""
|
||||
@staticmethod
|
||||
def set_style(
|
||||
ws: Worksheet,
|
||||
cell_range: str,
|
||||
font: Font=Font(name='宋体', size=11),
|
||||
align: Alignment=Alignment(horizontal='center', vertical='center', wrap_text=True),
|
||||
border: Border=Border(left=Side(style='thin'), right=Side(style='thin'),top=Side(style='thin'), bottom=Side(style='thin'))):
|
||||
"""设置单元格样式"""
|
||||
if cell_range:
|
||||
for row in ws[cell_range]:
|
||||
for cell in row:
|
||||
cell.font = font
|
||||
cell.alignment = align
|
||||
cell.border = border
|
||||
|
||||
@staticmethod
|
||||
def auto_adjust_column_width(ws: Worksheet):
|
||||
"""自动调整列宽"""
|
||||
dims = {}
|
||||
for row in ws.rows:
|
||||
for cell in row:
|
||||
if cell.value:
|
||||
merged_range = next((range for range in ws.merged_cells.ranges if cell.coordinate in range), None)
|
||||
if get_merge_type(merged_range) == 'column':
|
||||
continue
|
||||
cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value))
|
||||
dims[cell.column] = max(dims.get(cell.column, 0), cell_len)
|
||||
# 设置列宽
|
||||
for col, value in dims.items():
|
||||
ws.column_dimensions[get_column_letter(int(col))].width = value + 5
|
||||
|
||||
# 判断单元格类型
|
||||
def get_merge_type(merged_range):
|
||||
"""
|
||||
判断合并类型
|
||||
返回: 'row'(行合并), 'column'(列合并), 'both'(行列合并)或 None(不是合并单元格)
|
||||
"""
|
||||
if not merged_range:
|
||||
return None
|
||||
|
||||
min_row, max_row = merged_range.min_row, merged_range.max_row
|
||||
min_col, max_col = merged_range.min_col, merged_range.max_col
|
||||
|
||||
if max_row > min_row and max_col > min_col:
|
||||
return 'both' # 同时跨行和跨列
|
||||
elif max_row > min_row:
|
||||
return 'row' # 行合并(垂直合并)
|
||||
elif max_col > min_col:
|
||||
return 'column' # 列合并(水平合并)
|
||||
else:
|
||||
return None # 实际上不是合并单元格
|
||||
147
tools/core/utils/math_utils.py
Normal file
147
tools/core/utils/math_utils.py
Normal file
@@ -0,0 +1,147 @@
|
||||
from typing import List, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
# 解决百分比相加不为100%
|
||||
def fix_percentages(values: List[float], total: float) -> List[float]:
|
||||
"""
|
||||
修正百分比相加不为100%的问题。
|
||||
|
||||
Args:
|
||||
values (list[float]): 百分比列表,元素个数与总和相同
|
||||
total (float): 总和
|
||||
|
||||
Returns:
|
||||
list[float]: 修正后的百分比列表
|
||||
|
||||
Examples:
|
||||
>>> values = [0.2, 0.3, 0.5]
|
||||
>>> total = 1
|
||||
>>> fix_percentages(values, total)
|
||||
[20.0, 30.0, 50.0]
|
||||
|
||||
>>> values = [0.2, 0.3, 0.5]
|
||||
>>> total = 0.8
|
||||
>>> fix_percentages(values, total)
|
||||
[25.0, 37.5, 62.5]
|
||||
"""
|
||||
exact = [v / total * 100 for v in values]
|
||||
floor = [np.floor(p * 100) / 100 for p in exact] # 向下取整到2位小数
|
||||
remainders = [exact[i] - floor[i] for i in range(len(exact))]
|
||||
|
||||
# 需要分配的百分点数(以0.01%为单位)
|
||||
to_distribute = int(round(10000 - sum(floor) * 100))
|
||||
|
||||
# 按余数大小分配
|
||||
indices = sorted(range(len(remainders)), key=lambda i: remainders[i], reverse=True)
|
||||
|
||||
fixed = floor.copy()
|
||||
for i in range(to_distribute):
|
||||
fixed[indices[i]] += 0.01
|
||||
|
||||
return [round(p, 2) for p in fixed]
|
||||
|
||||
|
||||
# === 误差矫正 ===
|
||||
def correct_rounding_error(target_total:Union[int,float], adjusted_areas:List[float], original_areas:List[float]) -> List[int]:
|
||||
"""
|
||||
健壮的数值舍入误差矫正函数:将浮点型面积值四舍五入后,调整至目标总和。
|
||||
核心逻辑:基于原始数值的小数部分优先级,逐次增减1来抵消舍入误差,确保最终总和匹配目标值,
|
||||
同时避免调整后数值出现负数,防止无限循环。
|
||||
|
||||
Args:
|
||||
target_total (int/float): 目标总和(最终舍入后数值的合计值),函数内部会转为整型
|
||||
adjusted_areas (list[float]): 经过比例调整后的浮点型面积列表(待舍入的原始数据)
|
||||
original_areas (list[float]): 调整前的原始浮点型面积列表(用于计算小数部分优先级)
|
||||
|
||||
Returns:
|
||||
list[int]: 矫正后的整型面积列表,总和尽可能接近/等于target_total;
|
||||
若无法完全矫正,返回尽可能接近的结果并打印警告
|
||||
|
||||
Raises:
|
||||
无显式抛出异常,所有异常会被捕获并打印错误信息,返回保底的四舍五入结果
|
||||
|
||||
Notes:
|
||||
1. 误差矫正规则:
|
||||
- 误差>0(当前总和 < 目标总和):优先给小数部分大的数值+1
|
||||
- 误差<0(当前总和 > 目标总和):优先给小数部分小的数值-1
|
||||
2. 边界限制:调整时确保数值≥0(避免出现负数面积)
|
||||
3. 防无限循环:最大迭代次数为 len(adjusted_areas) * 10,超出则终止并提示剩余误差
|
||||
|
||||
Examples:
|
||||
>>> target = 10
|
||||
>>> adjusted = [3.2, 2.8, 4.1] # 四舍五入后总和=3+3+4=10,无误差
|
||||
>>> original = [3.2, 2.8, 4.1]
|
||||
>>> correct_rounding_error(target, adjusted, original)
|
||||
[3, 3, 4]
|
||||
|
||||
>>> target = 10
|
||||
>>> adjusted = [3.1, 2.1, 4.1] # 四舍五入后总和=3+2+4=9,误差+1
|
||||
>>> original = [3.1, 2.1, 4.1]
|
||||
>>> correct_rounding_error(target, adjusted, original)
|
||||
[3, 2, 5] # 优先给小数部分最大的4.1+1
|
||||
|
||||
>>> target = 8
|
||||
>>> adjusted = [3.9, 2.9, 1.9] # 四舍五入后总和=4+3+2=9,误差-1
|
||||
>>> original = [3.9, 2.9, 1.9]
|
||||
>>> correct_rounding_error(target, adjusted, original)
|
||||
[3, 3, 2] # 优先给小数部分最小的1.9-1(实际小数1.9>2.9>3.9,故调整3.9)
|
||||
"""
|
||||
try:
|
||||
target_total = int(target_total)
|
||||
rounded_areas = [int(round(area)) for area in adjusted_areas]
|
||||
current_total = sum(rounded_areas)
|
||||
error = target_total - current_total
|
||||
|
||||
if error == 0 or len(adjusted_areas) == 0:
|
||||
return rounded_areas
|
||||
|
||||
# 使用循环分配直到误差为0或无法再分配
|
||||
remaining_error = error
|
||||
max_iterations = len(adjusted_areas) * 10 # 防止无限循环
|
||||
|
||||
for _ in range(max_iterations):
|
||||
if remaining_error == 0:
|
||||
break
|
||||
|
||||
# 每次迭代重新计算小数部分和排序
|
||||
decimal_parts = [float(area - int(area)) for area in original_areas]
|
||||
indices = list(range(len(adjusted_areas)))
|
||||
|
||||
if remaining_error > 0:
|
||||
indices.sort(key=lambda i: decimal_parts[i], reverse=True)
|
||||
adjustment = 1
|
||||
else:
|
||||
indices.sort(key=lambda i: decimal_parts[i])
|
||||
adjustment = -1
|
||||
|
||||
# 尝试分配一次调整
|
||||
adjusted = False
|
||||
for idx in indices:
|
||||
if (adjustment == 1 and rounded_areas[idx] >= 0) or (adjustment == -1 and rounded_areas[idx] > 0):
|
||||
rounded_areas[idx] += adjustment
|
||||
remaining_error -= adjustment
|
||||
adjusted = True
|
||||
break
|
||||
|
||||
if not adjusted: # 无法再调整
|
||||
break
|
||||
|
||||
if remaining_error != 0:
|
||||
print(f"警告:无法完全矫正误差,剩余: {remaining_error}")
|
||||
|
||||
return rounded_areas
|
||||
|
||||
except Exception as e:
|
||||
print(f"误差矫正出错: {e}")
|
||||
# 返回原始四舍五入结果作为保底
|
||||
return [int(round(area)) for area in adjusted_areas]
|
||||
|
||||
if __name__ == '__main__':
|
||||
target = 10
|
||||
adjusted = [3.3, 3.9, 4.2] # 四舍五入后总和=3+3+4=10,无误差
|
||||
original = [3.25, 2.85, 4.15]
|
||||
print(correct_rounding_error(target, adjusted, original))
|
||||
|
||||
|
||||
47
tools/core/utils/os_utils/temp_files_processor.py
Normal file
47
tools/core/utils/os_utils/temp_files_processor.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import gc
|
||||
import sys
|
||||
import arcpy
|
||||
|
||||
# 临时文件清理
|
||||
def clean_up_temp_files(temp_files, workspace=None):
|
||||
"""安全清理临时文件和内存工作空间"""
|
||||
try:
|
||||
if temp_files:
|
||||
for temp_file in temp_files:
|
||||
if arcpy.Exists(temp_file):
|
||||
try:
|
||||
arcpy.management.Delete(temp_file)
|
||||
# print_status(f"已删除临时文件: {temp_file}")
|
||||
except Exception as delete_err:
|
||||
sys.stderr.write(f"CleanupError:无法删除临时文件 {temp_file}: {str(delete_err)}\n")
|
||||
|
||||
# 清理内存工作空间 (确保在 in_memory 工作空间中操作,而不是删除其他地方的同名项)
|
||||
try:
|
||||
# 切换到内存工作空间进行清理
|
||||
if arcpy.Exists("in_memory"):
|
||||
arcpy.env.workspace = "in_memory"
|
||||
# 删除内存工作空间中的所有内容
|
||||
for item in arcpy.ListDatasets() + arcpy.ListFeatureClasses() + arcpy.ListRasters():
|
||||
try:
|
||||
arcpy.management.Delete(item)
|
||||
# print_status(f"已清理内存项: in_memory/{item}")
|
||||
except Exception as delete_mem_item_err:
|
||||
sys.stderr.write(f"CleanupError:无法清理内存项 in_memory/{item}: {str(delete_mem_item_err)}\n")
|
||||
|
||||
except Exception as delete_in_memory_err:
|
||||
sys.stderr.write(f"CleanupError:清理 in_memory 工作空间时发生错误: {str(delete_in_memory_err)}\n")
|
||||
|
||||
# 恢复原始工作空间
|
||||
if workspace and arcpy.Exists(workspace):
|
||||
try:
|
||||
arcpy.env.workspace = workspace
|
||||
arcpy.management.ClearWorkspaceCache()
|
||||
except Exception as restore_ws_err:
|
||||
sys.stderr.write(f"CleanupError:无法恢复原始工作空间 {workspace}: {str(restore_ws_err)}\n")
|
||||
|
||||
except Exception as cleanup_err:
|
||||
# 外层异常捕获
|
||||
sys.stderr.write(f"CleanupError:清理临时文件过程中发生未预料的错误: {str(cleanup_err)}\n")
|
||||
|
||||
# 强制垃圾回收
|
||||
gc.collect()
|
||||
201
tools/core/utils/平差工具.py
Normal file
201
tools/core/utils/平差工具.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# 获取每个一级地类面积,主要是12类
|
||||
import arcpy
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from .math_utils import correct_rounding_error
|
||||
|
||||
|
||||
# 获取目标面积
|
||||
def get_area_by_group(dltb_class_feature, excel_target_path, xzqmc, is_by_xzq=False):
|
||||
try:
|
||||
# 读取目标面积Excel文件
|
||||
target_df = pd.read_excel(excel_target_path)
|
||||
|
||||
# 确保列名匹配
|
||||
target_df.columns = target_df.columns.str.strip()
|
||||
|
||||
if is_by_xzq:
|
||||
# 地类编码映射字典
|
||||
land_type_mapping = {
|
||||
'耕地': '01',
|
||||
'园地': '02',
|
||||
'林地': '03',
|
||||
'草地': '04',
|
||||
'其他地类': '12'
|
||||
}
|
||||
|
||||
# 方法1:重命名列后转换为字典
|
||||
df_encoded = target_df.rename(columns=land_type_mapping)
|
||||
result_dict = df_encoded.set_index('行政单位').to_dict('index')
|
||||
|
||||
return result_dict
|
||||
|
||||
# 检查要素类是否存在
|
||||
if not arcpy.Exists(dltb_class_feature):
|
||||
print(f"警告:输入要素类不存在: {dltb_class_feature}")
|
||||
else:
|
||||
# 转为numpy数组供pandas统计使用
|
||||
df = pd.DataFrame(arcpy.da.TableToNumPyArray(dltb_class_feature, ["YJDLBM", "TBDLMJ"], skip_nulls=False, null_value=np.nan))
|
||||
qtdl_df = df[df['YJDLBM'] == '12']
|
||||
if qtdl_df['TBDLMJ'].isnull().any() or qtdl_df['TBDLMJ'].eq(0).any():
|
||||
print("警告:其他地类TBDLMJ字段 存在空值或无效的记录,将不平差其他地类")
|
||||
target_areas = {}
|
||||
else:
|
||||
area_by_group = df.groupby("YJDLBM")["TBDLMJ"].sum()
|
||||
|
||||
for key in area_by_group.keys():
|
||||
area_by_group[key] = area_by_group[key] * 0.0015
|
||||
|
||||
target_areas = area_by_group.to_dict()
|
||||
|
||||
# 获取铁山港区的目标面积
|
||||
gangnan_target = target_df[target_df['行政单位'] == xzqmc]
|
||||
|
||||
if gangnan_target.empty:
|
||||
print(f"警告:未找到{xzqmc}的目标面积数据,将使用TBDLMJ数据进行平差")
|
||||
return target_areas
|
||||
|
||||
# 提取各土地利用类型的目标面积
|
||||
landuse_types = {'01':'耕地', '02':'园地', '03':'林地', '04':'草地', '12':'其他地类'}
|
||||
|
||||
for dlbm, dlmc in landuse_types.items():
|
||||
if dlmc in gangnan_target.columns:
|
||||
if gangnan_target[dlmc].values[0] and not np.isnan(gangnan_target[dlmc].values[0]):
|
||||
target_areas[dlbm] = gangnan_target[dlmc].values[0]
|
||||
|
||||
return target_areas
|
||||
|
||||
except Exception as e:
|
||||
print(f"计算面积时出错: {str(e)}")
|
||||
return {}
|
||||
|
||||
# 按地类平差(全区统一平差)
|
||||
def adjust_area_statistics(stats_df, target_areas):
|
||||
"""
|
||||
根据Excel中的目标面积对统计数据进行平差处理
|
||||
|
||||
Parameters:
|
||||
stats_df: 原始统计数据DataFrame
|
||||
excel_target_path: 包含目标面积的Excel文件路径
|
||||
|
||||
Returns:
|
||||
adjusted_df: 平差后的DataFrame
|
||||
"""
|
||||
try:
|
||||
if target_areas is None:
|
||||
print("警告:目标面积数据为空,不进行平差")
|
||||
return stats_df
|
||||
|
||||
# 准备平差数据
|
||||
adjusted_df = stats_df.copy()
|
||||
if "YJDLBM" not in adjusted_df.columns:
|
||||
dlbm = "YNDLBM"
|
||||
else:
|
||||
dlbm = "YJDLBM"
|
||||
|
||||
adjusted_df['adjusted_area'] = adjusted_df['temp_area']
|
||||
adjusted_df['adjustment_factor'] = 1.0
|
||||
|
||||
# 计算每个地类的原始总面积
|
||||
original_totals = stats_df.groupby(dlbm)['temp_area'].sum().to_dict()
|
||||
|
||||
# 对每个地类进行平差
|
||||
for yjdl, target_area in target_areas.items():
|
||||
if (yjdl in original_totals and original_totals[yjdl] > 0) or target_area > 0:
|
||||
adjustment_factor = target_area / original_totals[yjdl]
|
||||
|
||||
# 应用平差系数
|
||||
mask = adjusted_df[dlbm] == yjdl
|
||||
adjusted_df.loc[mask, 'adjusted_area'] = adjusted_df.loc[mask, 'temp_area'] * adjustment_factor
|
||||
adjusted_df.loc[mask, 'adjustment_factor'] = adjustment_factor
|
||||
|
||||
# 应用误差矫正,确保总和等于目标值
|
||||
adjusted_areas = adjusted_df.loc[mask, 'adjusted_area'].tolist()
|
||||
original_areas = stats_df.loc[mask, 'temp_area'].tolist()
|
||||
corrected_areas = correct_rounding_error(target_area, adjusted_areas, original_areas)
|
||||
adjusted_df.loc[mask, 'adjusted_area'] = corrected_areas
|
||||
|
||||
print(f"地类 {yjdl}: 平差系数 = {adjustment_factor:.6f}, 目标面积 = {target_area}, 矫正后总面积 = {sum(corrected_areas)}")
|
||||
|
||||
return adjusted_df
|
||||
|
||||
except Exception as e:
|
||||
print(f"平差处理失败: {e}")
|
||||
return stats_df
|
||||
|
||||
# 按行政区+地类进行平差
|
||||
def adjust_by_district_landuse(stats_df:pd.DataFrame, target_areas_dict:dict):
|
||||
"""
|
||||
按行政区+地类进行平差
|
||||
|
||||
Parameters:
|
||||
stats_df: 原始统计数据DataFrame
|
||||
target_areas_dict: 目标面积字典,格式:{'行政区': {'地类': 目标面积}}
|
||||
|
||||
Returns:
|
||||
adjusted_df: 平差后的DataFrame
|
||||
"""
|
||||
# 复制原始数据
|
||||
adjusted_df = stats_df.copy()
|
||||
adjusted_df['adjusted_area'] = adjusted_df['temp_area']
|
||||
adjusted_df['adjustment_factor'] = 1.0
|
||||
|
||||
# 获取所有存在的行政区和地类
|
||||
existing_districts = adjusted_df['XZQMC'].unique()
|
||||
|
||||
# 检查目标字典中的行政区是否存在
|
||||
missing_districts = []
|
||||
tt = [td for td in target_areas_dict.keys()]
|
||||
for ed in existing_districts:
|
||||
if ed not in tt:
|
||||
missing_districts.append(ed)
|
||||
|
||||
# 如果有行政区不存在,返回原始数据并提示
|
||||
if missing_districts:
|
||||
print(f"警告:平差数据中不存在行政区: {missing_districts},未进行平差")
|
||||
return stats_df
|
||||
|
||||
# 计算每个行政区每个地类的原始总面积
|
||||
original_totals = stats_df.groupby(['XZQMC', 'YJDLBM'])['temp_area'].sum()
|
||||
|
||||
# 对每个行政区的每个地类进行平差
|
||||
for xzqmc, landuse_targets in target_areas_dict.items():
|
||||
for yjdl, target_area in landuse_targets.items():
|
||||
# 检查该行政区是否有此地类数据
|
||||
if (xzqmc, yjdl) in original_totals.index and original_totals.at[(xzqmc, yjdl)] > 0:
|
||||
adjustment_factor = target_area / original_totals[(xzqmc, yjdl)]
|
||||
|
||||
# 应用平差系数
|
||||
mask = (adjusted_df['XZQMC'] == xzqmc) & (adjusted_df['YJDLBM'] == yjdl)
|
||||
adjusted_df.loc[mask, 'adjusted_area'] = adjusted_df.loc[mask, 'temp_area'] * adjustment_factor
|
||||
adjusted_df.loc[mask, 'adjustment_factor'] = adjustment_factor
|
||||
|
||||
# 应用误差矫正,确保总和等于目标值
|
||||
adjusted_areas = adjusted_df.loc[mask, 'adjusted_area'].tolist()
|
||||
original_areas = stats_df.loc[mask, 'temp_area'].tolist()
|
||||
corrected_areas = correct_rounding_error(target_area, adjusted_areas, original_areas)
|
||||
adjusted_df.loc[mask, 'adjusted_area'] = corrected_areas
|
||||
|
||||
print(f"{xzqmc} - 地类 {yjdl}: 平差系数 = {adjustment_factor:.6f}, 目标面积 = {target_area}, 矫正后总面积 = {sum(corrected_areas)}")
|
||||
|
||||
return adjusted_df
|
||||
|
||||
|
||||
def get_target_areas(excel_path:str, sheet_name:str, xzqmc:str) -> pd.DataFrame:
|
||||
df_excel = pd.read_excel(excel_path, sheet_name)
|
||||
target_df = df_excel[df_excel['行政单位'] == xzqmc]
|
||||
|
||||
df_area_for_merge = target_df.set_index('行政单位').iloc[0].reset_index(name='面积').rename(columns={'index': 'EJDL'})
|
||||
|
||||
return df_area_for_merge
|
||||
|
||||
def get_target_areas_by_group(excel_target_path):
|
||||
# 读取目标面积Excel文件
|
||||
target_df = pd.read_excel(excel_target_path,"Sheet1")
|
||||
|
||||
# 确保列名匹配
|
||||
target_df.columns = target_df.columns.str.strip()
|
||||
|
||||
result_dict = target_df.set_index('行政单位').to_dict('index')
|
||||
|
||||
return result_dict
|
||||
1
tools/ui/__init__.py
Normal file
1
tools/ui/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# UI界面模块
|
||||
1
tools/ui/components/__init__.py
Normal file
1
tools/ui/components/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# UI界面模块
|
||||
67
tools/ui/components/file_list_group.py
Normal file
67
tools/ui/components/file_list_group.py
Normal file
@@ -0,0 +1,67 @@
|
||||
'''
|
||||
pyqt6 文件列表分组
|
||||
'''
|
||||
from PyQt6.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QPushButton, QListWidget, QGroupBox
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
|
||||
|
||||
class FileListGroup(QGroupBox):
|
||||
|
||||
load_files = pyqtSignal()
|
||||
def __init__(self, parent=None, title="文件列表"):
|
||||
super().__init__(title, parent)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# 文件列表
|
||||
self.file_list = QListWidget()
|
||||
self.file_list.setSelectionMode(QListWidget.SelectionMode.MultiSelection)
|
||||
layout.addWidget(self.file_list)
|
||||
|
||||
# 文件列表操作按钮
|
||||
file_list_btn_layout = QHBoxLayout()
|
||||
|
||||
# 加载文件按钮
|
||||
self.load_file_btn = QPushButton("加载文件")
|
||||
self.load_file_btn.clicked.connect(self.on_load_files)
|
||||
|
||||
# 全选/全不选按钮
|
||||
select_all_btn = QPushButton("全选")
|
||||
select_all_btn.clicked.connect(self.on_select_all_files)
|
||||
select_none_btn = QPushButton("全不选")
|
||||
select_none_btn.clicked.connect(self.on_select_none_files)
|
||||
|
||||
# 添加按钮到布局
|
||||
file_list_btn_layout.addWidget(self.load_file_btn)
|
||||
file_list_btn_layout.addWidget(select_all_btn)
|
||||
file_list_btn_layout.addWidget(select_none_btn)
|
||||
|
||||
layout.addLayout(file_list_btn_layout)
|
||||
|
||||
def on_load_files(self):
|
||||
# 通过信号槽机制,添加文件列表
|
||||
self.load_files.emit()
|
||||
|
||||
def on_select_all_files(self):
|
||||
# 全选文件
|
||||
for i in range(self.file_list.count()):
|
||||
item = self.file_list.item(i)
|
||||
if item:
|
||||
item.setSelected(True)
|
||||
|
||||
def on_select_none_files(self):
|
||||
# 全不选文件
|
||||
for i in range(self.file_list.count()):
|
||||
item = self.file_list.item(i)
|
||||
if item:
|
||||
item.setSelected(False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication([])
|
||||
app.setStyle("Fusion")
|
||||
window = FileListGroup()
|
||||
window.show()
|
||||
app.exec()
|
||||
64
tools/ui/components/input_group.py
Normal file
64
tools/ui/components/input_group.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from PyQt6.QtWidgets import (QGroupBox, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QLineEdit, QPushButton, QFileDialog)
|
||||
|
||||
class InputGroup(QGroupBox):
|
||||
"""输入设置组件"""
|
||||
def __init__(self, title="输入设置", parent=None):
|
||||
super().__init__(title, parent)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# 模板文件选择
|
||||
template_layout = QHBoxLayout()
|
||||
template_layout.addWidget(QLabel("配置文件:"))
|
||||
self.template_path = QLineEdit()
|
||||
template_layout.addWidget(self.template_path)
|
||||
template_btn = QPushButton("浏览...")
|
||||
template_btn.clicked.connect(self.browse_template)
|
||||
template_layout.addWidget(template_btn)
|
||||
layout.addLayout(template_layout)
|
||||
|
||||
# 数据源选择
|
||||
data_layout = QHBoxLayout()
|
||||
data_layout.addWidget(QLabel("数据源:"))
|
||||
self.data_source = QLineEdit()
|
||||
data_layout.addWidget(self.data_source)
|
||||
data_btn = QPushButton("浏览...")
|
||||
data_btn.clicked.connect(self.browse_data_source)
|
||||
data_layout.addWidget(data_btn)
|
||||
layout.addLayout(data_layout)
|
||||
|
||||
def browse_template(self):
|
||||
"""浏览选择模板文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"选择模板文件",
|
||||
"",
|
||||
"ArcGIS Pro Project (*.aprx);;All Files (*.*)"
|
||||
)
|
||||
if file_path:
|
||||
self.template_path.setText(file_path)
|
||||
|
||||
def browse_data_source(self):
|
||||
"""浏览选择数据源"""
|
||||
dir_path = QFileDialog.getExistingDirectory(
|
||||
self,
|
||||
"选择数据源目录"
|
||||
)
|
||||
if dir_path:
|
||||
self.data_source.setText(dir_path)
|
||||
|
||||
def get_template_path(self):
|
||||
"""获取模板文件路径"""
|
||||
return self.template_path.text()
|
||||
|
||||
def get_data_source(self):
|
||||
"""获取数据源路径"""
|
||||
return self.data_source.text()
|
||||
|
||||
def clear(self):
|
||||
"""清除输入"""
|
||||
self.template_path.clear()
|
||||
self.data_source.clear()
|
||||
0
tools/ui/config/__init__.py
Normal file
0
tools/ui/config/__init__.py
Normal file
60
tools/ui/config/settings.json
Normal file
60
tools/ui/config/settings.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"export_map_settings": {
|
||||
"config_file": "D:/ProgramData/ArcGis_Py/tools/config_json/广西_华南_地块_config.json",
|
||||
"county_name": "武鸣区",
|
||||
"template_aprx_file": "E:/@三普属性图出图/广西武鸣区/武鸣区模板/广西武鸣区中微量元素(地块).aprx",
|
||||
"output_path": "E:/@三普属性图出图/广西武鸣区/成果图/20260411/中微量元素",
|
||||
"data_source_path": "E:/@三普属性图出图/广西武鸣区/过程数据/三普重分类/按地块出图数据.gdb",
|
||||
"symbol_path": "E:/@三普属性图出图/@通用数据/符号系统/广西_华南配色",
|
||||
"pic_path": "E:/@三普属性图出图/广西武鸣区/过程数据/三普重分类/新建文件夹"
|
||||
},
|
||||
"export_image_settings": {
|
||||
"input_aprx_path": "E:\\@三普属性图出图\\广西武鸣区\\成果图\\20260411\\中微量元素\\武鸣区_工作空间",
|
||||
"output_image_path": "E:\\@三普属性图出图\\广西武鸣区\\成果图\\20260411\\中微量元素",
|
||||
"default_format": "JPG",
|
||||
"resolution": 300,
|
||||
"force_regenerate": false,
|
||||
"use_multiprocessing": true,
|
||||
"process_count": 7
|
||||
},
|
||||
"raster_settings": {
|
||||
"input_folder": "E:/@三普属性图出图/广西武鸣区/@基础数据/三普栅格/投影后",
|
||||
"batch_output_folder": "E:/@三普属性图出图/广西武鸣区/过程数据/三普重分类/新建文件夹",
|
||||
"config_file_path": "D:/ProgramData/ArcGis_Py/tools/config_json/广西_华南_地块_config.json",
|
||||
"simplify": false,
|
||||
"min_area": 10000.0,
|
||||
"area_unit": "SQUARE_METERS",
|
||||
"clip_features": "",
|
||||
"clip_enabled": false
|
||||
},
|
||||
"area_stat_settings": {
|
||||
"input_folder": "E:/@三普属性图出图/广西银海区/过程数据/面积统计用栅格面",
|
||||
"batch_output_folder": "E:/@三普属性图出图/测试",
|
||||
"config_file_path": "D:/ProgramData/ArcGis_Py/tools/config_json/广西_华南_地块_config.json",
|
||||
"xzq_features": "E:\\@三普属性图出图\\广西银海区\\银海区土壤属性制表\\土壤属性制表数据.gdb\\行政区划",
|
||||
"dltb_features": "E:\\@三普属性图出图\\广西银海区\\银海区土壤属性制表\\土壤属性制表数据.gdb\\地类图斑",
|
||||
"xzqmc": "西畴县",
|
||||
"is_by_xzq": false
|
||||
},
|
||||
"acid_stat_settings": {
|
||||
"workspace_path": "E:/@三普属性图出图/广西银海区/酸化专题图/酸化专题数据库.gdb",
|
||||
"batch_output_folder": "E:/@三普属性图出图/广西银海区/酸化专题图",
|
||||
"xzq_features": "行政区划",
|
||||
"dltb_features": "地类图斑",
|
||||
"ph_samples": "三普PH",
|
||||
"acid_ph_features": "二普至三普PH",
|
||||
"assign_raster": "E:/@三普属性图出图/广西北海市/@基础数据/二普栅格/投影后/PH.tif",
|
||||
"acid_raster": "E:/@三普属性图出图/广西北海市/北海市土壤属性制表/历史变化栅格/新建文件夹/二普-三普_PH.tif",
|
||||
"soil_type_features": "土壤类型图",
|
||||
"xzqmc": ""
|
||||
},
|
||||
"soil_prop_stat_settings": {
|
||||
"xzqmc": "",
|
||||
"config_file": "D:/ProgramData/ArcGis_Py/tools/config_json/广西_华南_地块_config.json",
|
||||
"data_source_path": "E:/@三普属性图出图/广西武鸣区/武鸣区报告图表/制表数据.gdb",
|
||||
"sanpu_prop_tif_folder": "E:/@三普属性图出图/广西武鸣区/@基础数据/三普栅格/投影后",
|
||||
"reclassed_feature_folder": "E:/@三普属性图出图/广西武鸣区/过程数据/三普重分类/面积统计用栅格面",
|
||||
"output_folder": "E:/@三普属性图出图/广西武鸣区/武鸣区报告图表",
|
||||
"sample_list": []
|
||||
}
|
||||
}
|
||||
291
tools/ui/main_window.py
Normal file
291
tools/ui/main_window.py
Normal file
@@ -0,0 +1,291 @@
|
||||
import traceback
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QMainWindow,
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QTabWidget,
|
||||
QTextEdit,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QHBoxLayout,
|
||||
)
|
||||
from PyQt6.QtCore import pyqtSignal, QThreadPool
|
||||
from PyQt6.QtGui import QTextCursor
|
||||
|
||||
from .tabs.export_layout_tab import ExportImageTab
|
||||
from .tabs.raster_tab import RasterTab
|
||||
from .tabs.export_map_tab import ExportMapTab
|
||||
from .tabs.area_stat_tab import XlsxToJpgTab
|
||||
from .tabs.acid_stat_tab import AcidStatsTab # 酸化专题图表格统计
|
||||
from .tabs.soil_prop_stat_tab import SoilPropStatsTab
|
||||
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
log_signal = pyqtSignal(str) # 定义日志信号
|
||||
|
||||
"""主窗口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("ArcGIS工具集")
|
||||
self.resize(600, 800)
|
||||
self.settings_file = os.path.join(
|
||||
os.path.dirname(__file__), "config", "settings.json"
|
||||
)
|
||||
print(self.settings_file)
|
||||
self.ensure_settings_file()
|
||||
self.settings = self.load_settings()
|
||||
self.init_ui()
|
||||
# 在创建UI后加载设置
|
||||
self.load_ui_settings()
|
||||
|
||||
def init_ui(self):
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
|
||||
# 添加选项卡
|
||||
tabs = QTabWidget()
|
||||
|
||||
self.export_map_tab = ExportMapTab(self)
|
||||
self.export_map_tab.log_signal.connect(self.log_signal) # 连接日志信号
|
||||
self.export_image_tab = ExportImageTab(self)
|
||||
self.raster_tab = RasterTab(self)
|
||||
self.area_stat_tab = XlsxToJpgTab(self)
|
||||
self.acid_stat_tab = AcidStatsTab(self)
|
||||
self.soil_prop_stat_tab = SoilPropStatsTab(self)
|
||||
|
||||
tabs.addTab(self.raster_tab, "栅格重分类")
|
||||
tabs.addTab(self.area_stat_tab, "面积统计表制作")
|
||||
tabs.addTab(self.export_map_tab, "导出工程文件")
|
||||
tabs.addTab(self.export_image_tab, "导出成果图")
|
||||
tabs.addTab(self.acid_stat_tab, "酸化专题图表格统计")
|
||||
tabs.addTab(self.soil_prop_stat_tab, "土壤属性表制作")
|
||||
|
||||
main_layout.addWidget(tabs)
|
||||
|
||||
# 添加日志区域
|
||||
self.log_area = QTextEdit()
|
||||
self.log_area.setReadOnly(True)
|
||||
self.log_area.setFixedHeight(120)
|
||||
self.log_signal.connect(self.append_log) # 日志信号连接
|
||||
|
||||
main_layout.addWidget(QLabel("日志输出"))
|
||||
main_layout.addWidget(self.log_area)
|
||||
|
||||
# 添加清除日志按钮
|
||||
log_control_layout = QHBoxLayout()
|
||||
clear_log_btn = QPushButton("清空日志")
|
||||
clear_log_btn.clicked.connect(self.clear_log)
|
||||
log_control_layout.addStretch()
|
||||
log_control_layout.addWidget(clear_log_btn)
|
||||
main_layout.addLayout(log_control_layout)
|
||||
|
||||
# 连接信号与槽
|
||||
self.raster_tab.value_changed.connect(self.export_map_tab.update_config_file)
|
||||
self.raster_tab.value_changed.connect(self.area_stat_tab.update_config_file)
|
||||
self.raster_tab.value_changed.connect(self.soil_prop_stat_tab.update_config_file)
|
||||
|
||||
def ensure_settings_file(self):
|
||||
"""确保配置文件存在,如不存在则创建"""
|
||||
if not os.path.exists(self.settings_file):
|
||||
default_settings = self.get_default_settings()
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self.settings_file), exist_ok=True)
|
||||
with open(self.settings_file, "w", encoding="utf-8") as f:
|
||||
json.dump(default_settings, f, indent=4, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"创建配置文件失败: {str(e)}")
|
||||
|
||||
def load_settings(self):
|
||||
"""加载设置"""
|
||||
try:
|
||||
if os.path.exists(self.settings_file):
|
||||
with open(self.settings_file, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
return self.get_default_settings()
|
||||
except Exception as e:
|
||||
print(f"加载设置失败: {str(e)}")
|
||||
return self.get_default_settings()
|
||||
|
||||
def save_settings(self):
|
||||
"""保存设置"""
|
||||
try:
|
||||
config_dir = os.path.dirname(self.settings_file)
|
||||
if not os.path.exists(config_dir):
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
|
||||
with open(self.settings_file, "w", encoding="utf-8") as f:
|
||||
json.dump(self.settings, f, indent=4, ensure_ascii=False)
|
||||
print("配置已更新")
|
||||
except Exception as e:
|
||||
print(f"保存设置失败: {str(e)}")
|
||||
|
||||
def get_default_settings(self):
|
||||
"""获取默认设置"""
|
||||
return {
|
||||
"export_map_settings": {
|
||||
"template_aprx_file": "D:/工作/ArcGisPro/模板/工程模板.aprx",
|
||||
"output_path": "D:/工作/ArcGisPro/输出文件夹",
|
||||
"data_source_path": "D:/工作/ArcGisPro/数据源/土壤属性图矢量数据.gdb",
|
||||
"config_file": "D:/arcpystudy/ArcGisPro/tools/ui/raster_test_config.json",
|
||||
"county_name": "港南区",
|
||||
"symbol_path": "D:/工作/ArcGisPro/模板/符号系统",
|
||||
},
|
||||
"export_image_settings": {
|
||||
"input_aprx_path": "D:/工作/ArcGisPro/输出文件夹/港南区_工作空间",
|
||||
"output_image_path": "D:/工作/ArcGisPro/输出文件夹",
|
||||
"default_format": "PDF",
|
||||
"resolution": 300,
|
||||
'force_regenerate': True,
|
||||
'use_multiprocessing': True,
|
||||
'process_count': 3
|
||||
},
|
||||
"raster_settings": {
|
||||
"input_folder": "D:/工作/三普成果编制/港南区土壤属性图成果最终",
|
||||
"batch_output_folder": "D:/工作/ArcGisPro/输出文件夹/港南区_工作空间",
|
||||
"config_file_path": "D:/arcpystudy/ArcGisPro/tools/ui/raster_test_config.json",
|
||||
"simplify": True,
|
||||
"min_area": 10000,
|
||||
"area_unit": "平方米",
|
||||
},
|
||||
"area_stat_settings": {
|
||||
"input_folder": "D:/工作/三普成果编制/港南区土壤属性图成果最终",
|
||||
"batch_output_folder": "D:/工作/ArcGisPro/输出文件夹/港南区_工作空间",
|
||||
"config_file_path": "D:/arcpystudy/ArcGisPro/tools/ui/raster_test_config.json",
|
||||
"dltb_polygon": "D:/工作/三普成果编制/港南区土壤属性图成果最终/港南区_地类图_2021-09-01.shp",
|
||||
"xzq_polygon": "D:/工作/三普成果编制/港南区土壤属性图成果最终/港南区_区县图_2021-09-01.shp",
|
||||
},
|
||||
}
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""窗口关闭处理"""
|
||||
thread_pool = QThreadPool.globalInstance()
|
||||
if thread_pool is not None:
|
||||
thread_pool.waitForDone(2000)
|
||||
self.update_settings()
|
||||
self.save_settings()
|
||||
super().closeEvent(event)
|
||||
|
||||
def update_settings(self):
|
||||
"""更新设置"""
|
||||
try:
|
||||
# 更新导出工程设置
|
||||
if hasattr(self.export_map_tab, "get_settings"):
|
||||
export_map_settings = self.export_map_tab.get_settings()
|
||||
if export_map_settings:
|
||||
self.settings["export_map_settings"] = export_map_settings
|
||||
# 更新导出成果图设置
|
||||
if hasattr(self.export_image_tab, "get_layout_settings"):
|
||||
raster_settings = self.export_image_tab.get_layout_settings()
|
||||
if raster_settings:
|
||||
self.settings["export_image_settings"]= raster_settings
|
||||
|
||||
# 更新栅格处理设置
|
||||
if hasattr(self.raster_tab, "get_raster_settings"):
|
||||
raster_settings = self.raster_tab.get_raster_settings()
|
||||
if raster_settings:
|
||||
self.settings["raster_settings"]= raster_settings
|
||||
|
||||
# 更新面积统计设置
|
||||
if hasattr(self.area_stat_tab, "get_area_stat_settings"):
|
||||
area_stat_settings = self.area_stat_tab.get_area_stat_settings()
|
||||
if area_stat_settings:
|
||||
self.settings["area_stat_settings"]= area_stat_settings
|
||||
|
||||
# 更新酸化统计设置
|
||||
if hasattr(self.acid_stat_tab, "get_acid_stat_settings"):
|
||||
acid_stat_settings = self.acid_stat_tab.get_acid_stat_settings()
|
||||
if acid_stat_settings:
|
||||
self.settings["acid_stat_settings"]= acid_stat_settings
|
||||
|
||||
# 更新土壤属性统计设置
|
||||
if hasattr(self.soil_prop_stat_tab, "get_soil_prop_stat_settings"):
|
||||
soil_prop_stat_settings = self.soil_prop_stat_tab.get_soil_prop_stat_settings()
|
||||
if soil_prop_stat_settings:
|
||||
self.settings["soil_prop_stat_settings"]= soil_prop_stat_settings
|
||||
|
||||
except Exception as e:
|
||||
print(f"更新设置失败: {str(e)}")
|
||||
# 回溯
|
||||
traceback.print_exc()
|
||||
|
||||
def load_ui_settings(self):
|
||||
"""加载UI设置到各个组件"""
|
||||
try:
|
||||
# 加载成果图导出标签页设置
|
||||
if hasattr(self.export_image_tab, "load_settings"):
|
||||
self.export_image_tab.load_settings()
|
||||
# 加载栅格处理标签页设置
|
||||
if hasattr(self.raster_tab, "load_settings"):
|
||||
self.raster_tab.load_settings(self.settings["raster_settings"])
|
||||
# 加载面积统计标签页设置
|
||||
if hasattr(self.area_stat_tab, "load_settings"):
|
||||
self.area_stat_tab.load_settings(self.settings["area_stat_settings"])
|
||||
# 加载酸化专题统计标签页设置
|
||||
if hasattr(self.acid_stat_tab, "load_settings"):
|
||||
self.acid_stat_tab.load_settings(self.settings["acid_stat_settings"])
|
||||
# 加载土壤属性统计标签页设置
|
||||
if hasattr(self.soil_prop_stat_tab, "load_settings"):
|
||||
self.soil_prop_stat_tab.load_settings(self.settings["soil_prop_stat_settings"])
|
||||
except Exception as e:
|
||||
print(f"加载UI设置失败: {str(e)}")
|
||||
|
||||
def append_log(self, message):
|
||||
"""在日志区域追加信息"""
|
||||
try:
|
||||
from datetime import datetime
|
||||
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
# 如果传入字符串中已存在时间戳,则不添加时间戳
|
||||
if "[" in message and "]" in message:
|
||||
formatted_message = message
|
||||
else:
|
||||
formatted_message = f"[{timestamp}] {message}"
|
||||
|
||||
# 根据消息类型设置不同颜色
|
||||
if "错误" in message or "失败" in message or "出错" in message:
|
||||
formatted_message = f'<span style="color: #e74c3c;">{formatted_message}</span>'
|
||||
elif "警告" in message:
|
||||
formatted_message = f'<span style="color: #f39c12;">{formatted_message}</span>'
|
||||
elif "成功" in message or "完成" in message:
|
||||
formatted_message = f'<span style="color: #27ae60;">{formatted_message}</span>'
|
||||
else:
|
||||
formatted_message = f'<span style="color: #34495e;">{formatted_message}</span>'
|
||||
|
||||
self.log_area.append(formatted_message)
|
||||
self.log_area.verticalScrollBar().setValue(
|
||||
self.log_area.verticalScrollBar().maximum()
|
||||
)
|
||||
|
||||
# 如果消息太多,清除旧消息
|
||||
if self.log_area.document().lineCount() > 500:
|
||||
cursor = self.log_area.textCursor()
|
||||
cursor.movePosition(QTextCursor.MoveOperation.Start)
|
||||
cursor.movePosition(
|
||||
QTextCursor.MoveOperation.Down, QTextCursor.MoveMode.KeepAnchor, 100
|
||||
)
|
||||
cursor.removeSelectedText()
|
||||
|
||||
except Exception as e:
|
||||
print(f"追加日志失败: {str(e)}")
|
||||
|
||||
def clear_log(self):
|
||||
"""清空日志区域"""
|
||||
self.log_area.clear()
|
||||
|
||||
|
||||
# 如果直接运行该模块
|
||||
if __name__ == "__main__":
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
0
tools/ui/runners/__init__.py
Normal file
0
tools/ui/runners/__init__.py
Normal file
475
tools/ui/runners/script_runner.py
Normal file
475
tools/ui/runners/script_runner.py
Normal file
@@ -0,0 +1,475 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
脚本运行器 (支持多进程)
|
||||
用于从PyQt6界面调用独立脚本并管理并行执行
|
||||
整合了多进程功能。
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import traceback
|
||||
import uuid
|
||||
from PyQt6.QtCore import (QObject, pyqtSignal, QProcess, QProcessEnvironment)
|
||||
|
||||
from pathlib import Path # 导入Path
|
||||
import functools # 导入 functools 用于偏函数
|
||||
|
||||
|
||||
class ScriptRunner(QObject):
|
||||
"""脚本运行器类,用于调用独立脚本处理任务并管理并行执行"""
|
||||
|
||||
# 定义信号 (包含任务ID)
|
||||
# 将原始的 started, finished, error, log 信号修改为包含任务ID
|
||||
task_started = pyqtSignal(str, str) # 任务ID, 任务描述/消息
|
||||
task_finished = pyqtSignal(str, bool, str) # 任务ID, 是否成功, 消息 (包含结果或错误)
|
||||
task_error = pyqtSignal(str, str) # 任务ID, 错误信息
|
||||
task_log = pyqtSignal(str, str) # 任务ID, 日志信息
|
||||
manager_log = pyqtSignal(str) # Runner 管理器自身的日志
|
||||
|
||||
def __init__(self, parent=None, max_concurrent=3):
|
||||
super().__init__(parent)
|
||||
self.max_concurrent = max_concurrent # 控制最大并行进程数
|
||||
self.running_processes = {} # {task_id: QProcess实例}
|
||||
self.pending_tasks = ([]) # [(task_id, script_path, args_dict, task_description, working_dir), ...]
|
||||
self._next_task_id_counter = 0 # 用于生成简单的任务ID (uuid 更推荐)
|
||||
|
||||
# 获取项目根目录
|
||||
self.base_dir = self._get_base_dir()
|
||||
self.manager_log.emit(f"项目根目录: {self.base_dir}")
|
||||
self.manager_log.emit(f"最大并行任务数: {self.max_concurrent}")
|
||||
|
||||
def _get_base_dir(self):
|
||||
"""获取项目根目录"""
|
||||
# 根据你提供的项目结构图调整这里的逻辑
|
||||
# 假设 script_runner.py 位于 ui/runners 目录下
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# 向上两级找到项目根目录 (runners <- ui <- project_root)
|
||||
# 调整为向上两级,因为 ui 和 tools 在同一级
|
||||
return os.path.dirname(os.path.dirname(current_dir)) # 假设 runners 在 ui 目录下
|
||||
|
||||
def _find_script(self, script_name):
|
||||
"""查找脚本文件"""
|
||||
# 根据你提供的项目结构图调整可能的脚本路径
|
||||
base_dir = Path(self.base_dir)
|
||||
possible_paths = [
|
||||
base_dir / "tools" / "core" / script_name,
|
||||
Path(__file__).resolve().parents[2] / "core" / script_name,
|
||||
Path.cwd() / "tools" / "core" / script_name
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
path = os.path.normpath(path)
|
||||
if os.path.exists(path):
|
||||
self.manager_log.emit(f"找到脚本: {path}")
|
||||
return path
|
||||
|
||||
self.manager_log.emit(f"警告: 无法找到脚本 '{script_name}', 尝试过以下路径:")
|
||||
for path in possible_paths:
|
||||
self.manager_log.emit(f" - {path} (存在: {os.path.exists(path)})")
|
||||
|
||||
# 返回None表示找不到
|
||||
return None
|
||||
|
||||
def _get_arcgis_python(self):
|
||||
"""获取ArcGIS Pro的Python解释器路径"""
|
||||
# 这里的逻辑与之前相同
|
||||
try:
|
||||
arcgis_python_paths = [
|
||||
r"C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\python.exe",
|
||||
r"D:\ProgramData\ArcGis_Py\.env\arcgispro-py3-clone\python.exe",
|
||||
]
|
||||
# 检查环境变量 ARCGISPRO_PYTHON 或 CONDA_PREFIX (如果使用Conda环境)
|
||||
if "ARCGISPRO_PYTHON" in os.environ:
|
||||
arcgis_python_paths.insert(0, os.environ["ARCGISPRO_PYTHON"])
|
||||
|
||||
for path in arcgis_python_paths:
|
||||
path = os.path.normpath(path)
|
||||
if os.path.exists(path):
|
||||
self.manager_log.emit(f"使用ArcGIS Python解释器: {path}")
|
||||
return path
|
||||
|
||||
self.manager_log.emit(
|
||||
"警告: 无法自动找到ArcGIS Pro Python解释器,尝试使用当前Python解释器."
|
||||
)
|
||||
return sys.executable
|
||||
|
||||
except Exception as e:
|
||||
self.manager_log.emit(f"获取ArcGIS Python路径失败: {str(e)}")
|
||||
return sys.executable
|
||||
|
||||
# 修正:将添加任务到队列并尝试启动的逻辑命名为 run_script
|
||||
def run_script(self, script_name, args_dict, task_description=None, working_dir=None):
|
||||
"""
|
||||
将一个脚本任务添加到队列中进行管理和并行执行。
|
||||
|
||||
Args:
|
||||
script_name (str): 要执行的脚本文件名。
|
||||
args_dict (dict): 传递给脚本的参数字典。
|
||||
task_description (str, optional): 任务的描述。
|
||||
working_dir (str, optional): 脚本执行的工作目录。
|
||||
|
||||
返回:
|
||||
str: 分配给该任务的唯一任务ID 或 None 如果脚本不存在。
|
||||
"""
|
||||
script_path = self._find_script(script_name)
|
||||
if not script_path or not os.path.exists(script_path):
|
||||
self.manager_log.emit(f"错误: 无法添加任务,脚本 '{script_name}' 不存在.")
|
||||
return None
|
||||
|
||||
# 生成唯一的任务ID
|
||||
task_id = str(uuid.uuid4().hex[:8])
|
||||
|
||||
if task_description is None:
|
||||
task_description = os.path.basename(script_name)
|
||||
|
||||
# 将任务信息添加到待处理队列
|
||||
self.pending_tasks.append((task_id, script_path, args_dict, task_description, working_dir))
|
||||
self.manager_log.emit(f"已添加任务{task_id} - {task_description}到队列...")
|
||||
|
||||
# 尝试启动下一个任务
|
||||
self._start_next_task()
|
||||
|
||||
return task_id
|
||||
|
||||
# 新增方法:启动下一个可用的任务
|
||||
def _start_next_task(self):
|
||||
"""检查队列和正在运行的进程数量,启动下一个可用的任务"""
|
||||
while len(self.running_processes) < self.max_concurrent and self.pending_tasks:
|
||||
task_id, script_path, args_dict, task_description, working_dir = (self.pending_tasks.pop(0))
|
||||
|
||||
self.manager_log.emit(f"正在启动任务: {task_id} - {task_description}")
|
||||
self.task_started.emit(task_id, task_description)
|
||||
|
||||
# 调用内部方法实际启动单个进程
|
||||
self._start_single_process(task_id, script_path, args_dict, task_description, working_dir)
|
||||
|
||||
# 新增方法:实际启动单个 QProcess 进程
|
||||
def _start_single_process(self, task_id, script_path, args_dict, task_description, working_dir):
|
||||
"""
|
||||
启动一个 QProcess 进程来执行指定的脚本任务。
|
||||
这是从原始 run_script 中提取的启动逻辑。
|
||||
"""
|
||||
python_executable = self._get_arcgis_python()
|
||||
if not python_executable or not os.path.exists(python_executable):
|
||||
error_msg = f"无法找到有效的 ArcGIS Pro Python 解释器: {python_executable or '未找到'}"
|
||||
self.task_error.emit(task_id, error_msg)
|
||||
self._on_process_finished(task_id, None, -1, QProcess.ExitStatus.NormalExit) # 模拟失败完成
|
||||
return
|
||||
|
||||
# 构建环境变量
|
||||
env = QProcessEnvironment.systemEnvironment()
|
||||
env.insert("PATH", os.environ["PATH"]) # 继承系统PATH
|
||||
env.insert("PYTHONPATH", ";".join(sys.path)) # 继承当前Python路径
|
||||
|
||||
cmd = [python_executable, "-u", script_path] # 使用 -u 参数禁用缓冲
|
||||
|
||||
# 将参数字典转换为命令行参数
|
||||
for key, value in args_dict.items():
|
||||
if not isinstance(key, str) or not key:
|
||||
self.task_error.emit(task_id, f"警告: 跳过无效的参数键: {key}")
|
||||
continue
|
||||
param_key = f"--{key}"
|
||||
if isinstance(value, bool):
|
||||
if value:
|
||||
cmd.append(param_key)
|
||||
elif value is not None and value != "NONE":
|
||||
cmd.append(param_key)
|
||||
if isinstance(value, (list, dict)):
|
||||
try:
|
||||
cmd.append(json.dumps(value))
|
||||
except Exception as e:
|
||||
self.task_error.emit(task_id, f"警告: 无法将参数 '{key}' 序列化为 JSON: {str(e)}")
|
||||
cmd.append(str(value))
|
||||
else:
|
||||
cmd.append(str(value))
|
||||
|
||||
self.task_log.emit(task_id, f"即将执行命令: {subprocess.list2cmdline(cmd)}")
|
||||
|
||||
proc = QProcess()
|
||||
proc.setProcessEnvironment(env) # 关键设置
|
||||
|
||||
# 连接信号到处理函数,使用 functools.partial 传递 task_id 和 process 对象
|
||||
# 注意:_handle_stdout/_handle_stderr 信号本身不传递 process,需要通过 task_id 从 running_processes 获取
|
||||
# _on_process_finished 信号传递 exit_code, exit_status,通过 partial 传递 task_id 和 proc
|
||||
proc.readyReadStandardOutput.connect(
|
||||
functools.partial(self._handle_stdout, task_id=task_id)
|
||||
)
|
||||
proc.readyReadStandardError.connect(
|
||||
functools.partial(self._handle_stderr, task_id=task_id)
|
||||
)
|
||||
proc.finished.connect(
|
||||
functools.partial(self._on_process_finished, task_id=task_id, proc=proc)
|
||||
)
|
||||
|
||||
# 设置工作目录
|
||||
if working_dir:
|
||||
if os.path.exists(working_dir):
|
||||
proc.setWorkingDirectory(working_dir)
|
||||
self.task_log.emit(task_id, f"工作目录设置为: {working_dir}")
|
||||
else:
|
||||
self.task_error.emit(task_id, f"警告: 工作目录不存在: {working_dir}")
|
||||
|
||||
# 启动进程
|
||||
try:
|
||||
proc.start(cmd[0], cmd[1:])
|
||||
self.running_processes[task_id] = proc # 将进程实例添加到正在运行列表
|
||||
self.task_log.emit(task_id, f"进程已启动,PID: {proc.processId()}")
|
||||
# 可以将任务描述等信息附加到 QProcess 对象,方便在槽函数中使用
|
||||
proc.setProperty("task_description", task_description)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"无法启动进程: {str(e)}\n命令: {subprocess.list2cmdline(cmd)}"
|
||||
self.task_error.emit(task_id, error_msg)
|
||||
# 在启动失败时,也需要调用 finished 槽来清理并触发下一个任务
|
||||
self._on_process_finished(task_id, proc, -1, QProcess.ExitStatus.NormalExit) # 模拟失败完成
|
||||
|
||||
# 修改处理槽函数以接受 task_id 并从 running_processes 获取进程
|
||||
def _handle_stdout(self, task_id):
|
||||
"""处理指定任务的标准输出"""
|
||||
proc = self.running_processes.get(task_id)
|
||||
if not proc:
|
||||
return # 如果进程已不在运行列表中,忽略信号
|
||||
|
||||
try:
|
||||
data = proc.readAllStandardOutput().data()
|
||||
# 尝试多种解码方式,确保能处理中文
|
||||
try:
|
||||
text = data.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
text = data.decode("gbk") # Windows 常用
|
||||
except UnicodeDecodeError:
|
||||
text = data.decode("utf-8", errors="replace") # 保底
|
||||
|
||||
if text:
|
||||
for line in text.splitlines():
|
||||
if line.strip():
|
||||
# 根据行的前缀进一步解析,例如 STATUS: 或 RESULT:
|
||||
if line.startswith("STATUS:"):
|
||||
status_message = line[len("STATUS:") :].strip()
|
||||
self.task_log.emit(task_id, f"状态: {status_message}")
|
||||
elif line.startswith("RESULT:"):
|
||||
self.task_log.emit(task_id, f"原始结果行: {line.strip()}")
|
||||
proc.setProperty("last_result_line", line.strip()) # 将结果行附加到 QProcess 对象
|
||||
else:
|
||||
self.task_log.emit(task_id, line.strip()) # 其他普通输出
|
||||
|
||||
except Exception as e:
|
||||
self.task_error.emit(task_id, f"处理标准输出时出错: {str(e)}")
|
||||
self.task_log.emit(task_id, traceback.format_exc()) # 记录详细错误
|
||||
|
||||
def _handle_stderr(self, task_id):
|
||||
"""处理指定任务的错误输出"""
|
||||
proc = self.running_processes.get(task_id)
|
||||
if not proc:
|
||||
return # 如果进程已不在运行列表中,忽略信号
|
||||
|
||||
try:
|
||||
data = proc.readAllStandardError().data()
|
||||
# 尝试多种解码方式
|
||||
try:
|
||||
text = data.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
text = data.decode("gbk") # Windows 常用
|
||||
except UnicodeDecodeError:
|
||||
text = data.decode("utf-8", errors="replace") # 保底
|
||||
|
||||
if text:
|
||||
for line in text.splitlines():
|
||||
if line.strip():
|
||||
# 错误信息直接作为错误日志发送
|
||||
self.task_error.emit(task_id, f"脚本错误: {line.strip()}")
|
||||
# 可以在这里将错误输出附加到 QProcess 对象,以便在 finished 中汇总
|
||||
current_stderr = proc.property("accumulated_stderr") or ""
|
||||
proc.setProperty("accumulated_stderr", current_stderr + line.strip() + "\n")
|
||||
|
||||
except Exception as e:
|
||||
self.task_error.emit(task_id, f"处理错误输出时出错: {str(e)}")
|
||||
self.task_log.emit(task_id, traceback.format_exc()) # 记录详细错误
|
||||
|
||||
# 修改处理槽函数以接受 task_id 和 proc 并进行清理和调度
|
||||
def _on_process_finished(self, exit_code, exit_status, task_id, proc):
|
||||
"""处理指定任务的进程完成事件"""
|
||||
self.task_log.emit(task_id, f"进程完成. 退出码: {exit_code}, 退出状态: {exit_status}")
|
||||
|
||||
# 在处理完成信号时,立即断开输出信号连接
|
||||
if proc:
|
||||
try:
|
||||
proc.readyReadStandardOutput.disconnect()
|
||||
except TypeError: # 有时信号可能已经断开,捕获 TypeError
|
||||
pass
|
||||
try:
|
||||
proc.readyReadStandardError.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
# 也可以断开 finished 信号本身,但通常不需要,因为它只触发一次
|
||||
# try:
|
||||
# proc.finished.disconnect()
|
||||
# except TypeError:
|
||||
# pass
|
||||
|
||||
success = False
|
||||
message = "未知错误或脚本未返回明确结果" # 默认消息
|
||||
|
||||
# 从运行列表中移除进程
|
||||
if task_id in self.running_processes:
|
||||
# 确保删除的是正确的进程实例
|
||||
if self.running_processes[task_id] is proc:
|
||||
del self.running_processes[task_id]
|
||||
else:
|
||||
self.manager_log.emit(f"警告: 任务ID {task_id} 在 running_processes 中对应进程不匹配完成信号的进程实例.")
|
||||
# 这种情况比较异常,可能需要更深入的调试
|
||||
else:
|
||||
# 如果任务ID不在运行列表中,可能是重复的 finished 信号或逻辑错误
|
||||
# 或者是在 _start_single_process 中模拟失败完成的情况
|
||||
if (proc and proc.processId() != 0): # 检查 proc 是否是有效的 QProcess 实例且已启动
|
||||
self.manager_log.emit(f"警告: 收到未知任务ID {task_id} 的完成信号,但 PID {proc.processId()} 已知。")
|
||||
else:
|
||||
self.manager_log.emit(f"警告: 收到未知任务ID {task_id} 的完成信号.")
|
||||
|
||||
# 尝试从 QProcess 对象属性中获取 RESULT 行
|
||||
# 在 _start_single_process 中模拟失败完成时,proc 可能为 None
|
||||
result_line = proc.property("last_result_line") if proc else None
|
||||
accumulated_stderr = proc.property("accumulated_stderr") or "" if proc else ""
|
||||
|
||||
if result_line:
|
||||
try:
|
||||
# 解析 RESULT 行
|
||||
parts = result_line[len("RESULT:") :].split("|", 2) # 分割最多两次
|
||||
if len(parts) >= 3:
|
||||
success_from_script = parts[0] == "True"
|
||||
output_path = parts[1]
|
||||
error_msg_from_script = parts[2]
|
||||
|
||||
if success_from_script:
|
||||
success = True
|
||||
message = f"成功: {output_path}"
|
||||
else:
|
||||
# 如果脚本返回失败,使用脚本提供的错误信息
|
||||
success = False
|
||||
message = f"脚本返回失败: {error_msg_from_script}"
|
||||
|
||||
else:
|
||||
# 如果 RESULT 行格式不正确
|
||||
success = False
|
||||
message = f"脚本返回结果格式错误: {result_line}"
|
||||
|
||||
except Exception as e:
|
||||
success = False
|
||||
message = f"解析脚本返回结果时出错: {str(e)}\n原始结果行: {result_line}"
|
||||
else:
|
||||
# 如果没有找到 RESULT 行,根据退出码判断
|
||||
if exit_status == QProcess.ExitStatus.NormalExit and exit_code == 0:
|
||||
success = True
|
||||
message = "脚本正常退出,但未找到 RESULT 行。"
|
||||
self.task_log.emit(task_id, message) # 记录为日志而不是错误
|
||||
else:
|
||||
success = False
|
||||
message = f"脚本异常退出 (退出码: {exit_code})."
|
||||
# 如果有累积的错误输出,也添加到消息中
|
||||
if accumulated_stderr:
|
||||
message += f"\n错误输出:\n{accumulated_stderr.strip()}"
|
||||
self.task_error.emit(task_id, message) # 记录为错误
|
||||
|
||||
# 清理 QProcess 实例 (仅当 proc 对象有效时)
|
||||
if proc:
|
||||
proc.close()
|
||||
proc.deleteLater() # 延迟删除对象,确保槽函数执行完毕
|
||||
|
||||
# 发射任务完成信号
|
||||
if len(self.running_processes) == 0:
|
||||
self.task_finished.emit(task_id, success, message)
|
||||
|
||||
# 尝试启动下一个任务
|
||||
self._start_next_task()
|
||||
|
||||
def stop_all_tasks(self):
|
||||
"""尝试停止所有正在运行的任务"""
|
||||
self.manager_log.emit("正在尝试停止所有任务...")
|
||||
# 复制字典的键,避免在迭代时修改
|
||||
tasks_to_stop = list(self.running_processes.keys())
|
||||
for task_id in tasks_to_stop:
|
||||
proc = self.running_processes.get(task_id)
|
||||
if proc and proc.state() == QProcess.ProcessState.Running:
|
||||
self.task_log.emit(task_id, "尝试终止进程...")
|
||||
proc.terminate() # 尝试优雅终止 (发送SIGTERM)
|
||||
# proc.kill() # 强制杀死进程 (发送SIGKILL) - 更可靠,但可能导致数据损坏
|
||||
|
||||
|
||||
# --- 保留原有 run_... 方法,它们现在会调用 run_script 来添加任务 ---
|
||||
def run_export_map(self, params):
|
||||
script_name = "export_map_v1.py"
|
||||
task_desc = f"导出地图: {params.get('county_name', '未知区县')}"
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
def run_export_layout(self, params):
|
||||
script_name = "export_layout.py"
|
||||
task_desc = f"导出布局: {os.path.basename(params.get('input_aprx_folder', '未知文件夹'))}"
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
def run_batch_export_layout(self, params):
|
||||
script_name = "batch_export_layout.py"
|
||||
task_desc = f"批量导出布局: {os.path.basename(params.get('input_aprx_folder', '未知文件夹'))}"
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
def run_process_raster(self, params):
|
||||
script_name = ("raster_to_polygon.py")
|
||||
task_desc = (f"处理栅格: {os.path.basename(params.get('input_raster', '未知栅格'))}")
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
def run_area_stat(self, params):
|
||||
script_name = "stats_area_to_excel.py"
|
||||
task_desc = f"统计面积: {os.path.basename(params.get('reclassed_polygon', '未知面要素'))}"
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
def run_suanhua_stat(self, params):
|
||||
script_name = "stats_sh_to_excel.py"
|
||||
task_desc = f"统计酸化: {os.path.basename(params.get('reclassed_polygon', '未知面要素'))}"
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
def run_soil_prop_stat(self, params):
|
||||
script_name = "stats_soil_prop_to_excel.py"
|
||||
task_desc = f"统计酸化: {os.path.basename(params.get('reclassed_polygon', '未知面要素'))}"
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
def run_excel_to_jpg(self, params):
|
||||
script_name = "export_excel_to_jpg_v1.py"
|
||||
task_desc = f"导出Excel: {os.path.basename(params.get('excel_path', '未知文件'))}"
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
def run_test_script(self, params):
|
||||
script_name = "test_script.py"
|
||||
task_desc = f"测试脚本: {params.get('message', '无消息')}"
|
||||
return self.run_script(script_name, params, task_description=task_desc)
|
||||
|
||||
# 可以添加其他通用的任务管理方法,例如:
|
||||
def get_pending_tasks(self):
|
||||
"""获取当前待处理任务列表"""
|
||||
# 返回任务ID和描述的列表
|
||||
return [(tid, desc) for tid, sp, args, desc, wd in self.pending_tasks]
|
||||
|
||||
def get_running_tasks(self):
|
||||
"""获取当前正在运行任务的 task_id 和描述的列表"""
|
||||
# 需要从 QProcess 对象中获取任务描述 (如果在启动时设置了属性)
|
||||
running_list = []
|
||||
for tid, proc in self.running_processes.items():
|
||||
desc = proc.property("task_description") or "未知任务"
|
||||
running_list.append((tid, desc))
|
||||
return running_list
|
||||
|
||||
def get_max_concurrent(self):
|
||||
"""获取最大并行任务数设置"""
|
||||
return self.max_concurrent
|
||||
|
||||
def set_max_concurrent(self, count):
|
||||
"""设置最大并行任务数,并尝试启动更多任务"""
|
||||
if count > 0:
|
||||
self.max_concurrent = count
|
||||
self.manager_log.emit(f"最大并行任务数已更改为: {self.max_concurrent}")
|
||||
self._start_next_task() # 尝试启动更多任务
|
||||
else:
|
||||
self.manager_log.emit("警告: 最大并行任务数必须大于 0。")
|
||||
655
tools/ui/runners/script_runner.txt
Normal file
655
tools/ui/runners/script_runner.txt
Normal file
@@ -0,0 +1,655 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
脚本运行器
|
||||
用于从PyQt6界面调用独立脚本
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import traceback
|
||||
from PyQt6.QtCore import QObject, pyqtSignal, QProcess, QThread, QRunnable, QThreadPool
|
||||
|
||||
|
||||
class LambdaTask(QRunnable):
|
||||
"""
|
||||
用于在QThreadPool中执行任意函数的任务类
|
||||
"""
|
||||
def __init__(self, fn, *args, **kwargs):
|
||||
super().__init__()
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def run(self):
|
||||
"""执行函数"""
|
||||
try:
|
||||
self.fn(*self.args, **self.kwargs)
|
||||
except Exception as e:
|
||||
print(f"任务执行错误: {str(e)}")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
class ScriptRunner(QObject):
|
||||
"""脚本运行器类,用于调用独立脚本处理任务"""
|
||||
|
||||
# 定义信号
|
||||
started = pyqtSignal(str)
|
||||
finished = pyqtSignal(object)
|
||||
error = pyqtSignal(str)
|
||||
log = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
# self.parent_ref = weakref.ref(parent) if parent else None
|
||||
# 获取项目根目录
|
||||
self.base_dir = self._get_base_dir()
|
||||
self.log.emit(f"项目根目录: {self.base_dir}")
|
||||
|
||||
def _get_base_dir(self):
|
||||
"""获取项目根目录"""
|
||||
# 当前文件的目录
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# 向上两级找到项目根目录 (ui -> tools -> project_root)
|
||||
return os.path.dirname(os.path.dirname(current_dir))
|
||||
|
||||
def _find_script(self, script_name):
|
||||
"""查找脚本文件"""
|
||||
# 可能的脚本路径
|
||||
possible_paths = [
|
||||
os.path.join(self.base_dir, 'tools', 'core', script_name), # 从项目根目录查找
|
||||
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'core', script_name), # 从tools目录查找
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'core', script_name)), # 相对于ui目录
|
||||
os.path.join(os.getcwd(), 'tools', 'core', script_name) # 从当前工作目录查找
|
||||
]
|
||||
|
||||
# 尝试每个可能的路径
|
||||
for path in possible_paths:
|
||||
path = os.path.normpath(path) # 规范化路径
|
||||
if os.path.exists(path):
|
||||
self.log.emit(f"找到脚本: {path}")
|
||||
return path
|
||||
|
||||
# 如果找不到脚本,记录所有尝试的路径
|
||||
self.log.emit("无法找到脚本,尝试过以下路径:")
|
||||
for path in possible_paths:
|
||||
path = os.path.normpath(path)
|
||||
self.log.emit(f" - {path} (存在: {os.path.exists(path)})")
|
||||
|
||||
# 返回第一个路径(即使它不存在)
|
||||
return possible_paths[0]
|
||||
|
||||
def run_script_external(self, script_path, args, working_dir=None):
|
||||
"""
|
||||
使用外部Python窗口运行脚本,显示实时输出
|
||||
|
||||
Args:
|
||||
script_path: 脚本路径
|
||||
args: 命令行参数字典
|
||||
working_dir: 工作目录
|
||||
|
||||
Returns:
|
||||
返回启动是否成功
|
||||
"""
|
||||
try:
|
||||
# 更详细的日志
|
||||
self.log.emit(f"尝试在外部窗口运行脚本: {script_path}")
|
||||
|
||||
# 检查脚本是否存在
|
||||
if not os.path.exists(script_path):
|
||||
self.error.emit(f"脚本不存在: {script_path}")
|
||||
self.log.emit(f"当前工作目录: {os.getcwd()}")
|
||||
self.log.emit(f"尝试查找脚本...")
|
||||
|
||||
# 从脚本名尝试查找脚本
|
||||
script_name = os.path.basename(script_path)
|
||||
actual_path = self._find_script(script_name)
|
||||
|
||||
if not os.path.exists(actual_path):
|
||||
self.error.emit(f"无法找到脚本: {script_name}")
|
||||
return False
|
||||
|
||||
script_path = actual_path
|
||||
|
||||
self.log.emit(f"使用脚本路径: {script_path}")
|
||||
|
||||
# 获取ArcGIS Pro的Python解释器路径
|
||||
python_exe = self._get_arcgis_python() or sys.executable
|
||||
self.log.emit(f"使用Python解释器: {python_exe}")
|
||||
|
||||
# 构建命令行参数
|
||||
cmd = [python_exe, script_path]
|
||||
|
||||
# 添加参数
|
||||
for key, value in args.items():
|
||||
if key.startswith('--'):
|
||||
param_key = key
|
||||
else:
|
||||
param_key = f"--{key}"
|
||||
|
||||
if isinstance(value, bool):
|
||||
# 布尔参数
|
||||
if value:
|
||||
cmd.append(param_key)
|
||||
elif isinstance(value, list):
|
||||
# 列表参数,通过JSON序列化传递
|
||||
cmd.append(param_key)
|
||||
cmd.append(json.dumps(value))
|
||||
elif value is not None:
|
||||
# 其他参数
|
||||
cmd.append(param_key)
|
||||
cmd.append(str(value))
|
||||
|
||||
# 通知开始
|
||||
self.started.emit(f"开始执行脚本: {os.path.basename(script_path)}")
|
||||
|
||||
# 创建临时批处理文件,为了能自动关闭窗口
|
||||
batch_file_path = os.path.join(os.environ.get('TEMP', '.'), f"run_script_{os.getpid()}.bat")
|
||||
|
||||
# 构建批处理文件内容
|
||||
batch_content = f"""@echo off
|
||||
chcp 65001 > nul
|
||||
echo 正在执行脚本: {os.path.basename(script_path)}...
|
||||
echo 命令: {' '.join(cmd)}
|
||||
echo.
|
||||
"""
|
||||
|
||||
# 设置工作目录
|
||||
if working_dir:
|
||||
batch_content += f'cd /d "{working_dir}"\n'
|
||||
|
||||
# 添加执行命令
|
||||
batch_content += f'"{python_exe}" "{script_path}"'
|
||||
|
||||
# 添加命令行参数
|
||||
for i in range(2, len(cmd)):
|
||||
# 检查参数是否需要引号
|
||||
param = cmd[i]
|
||||
if ' ' in param or ',' in param or ';' in param or '&' in param or '[' in param or ']' in param:
|
||||
batch_content += f' "{param}"'
|
||||
else:
|
||||
batch_content += f' {param}'
|
||||
|
||||
# 添加执行完成后的提示和暂停
|
||||
batch_content += "\necho.\necho 脚本执行完成,窗口将在3秒后自动关闭...\ntimeout /t 3 >nul\n"
|
||||
|
||||
# 写入批处理文件
|
||||
with open(batch_file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(batch_content)
|
||||
|
||||
self.log.emit(f"创建批处理文件: {batch_file_path}")
|
||||
|
||||
# 使用subprocess.Popen启动外部窗口
|
||||
# 使用start命令在新窗口中运行批处理文件
|
||||
start_cmd = f'start "执行脚本 - {os.path.basename(script_path)}" "{batch_file_path}"'
|
||||
subprocess.Popen(start_cmd, shell=True)
|
||||
|
||||
# 模拟成功启动
|
||||
self.log.emit("已在外部窗口启动脚本")
|
||||
# 注意:由于是在外部窗口运行,我们无法获知脚本的实际结果
|
||||
# 假设脚本已成功启动,但实际执行结果需要用户观察外部窗口
|
||||
QThread.msleep(1000) # 等待1秒
|
||||
self.finished.emit(True)
|
||||
|
||||
# 预定批处理文件的删除
|
||||
def delete_batch_file():
|
||||
try:
|
||||
if os.path.exists(batch_file_path):
|
||||
os.remove(batch_file_path)
|
||||
self.log.emit(f"已删除临时批处理文件: {batch_file_path}")
|
||||
except Exception as e:
|
||||
self.log.emit(f"删除临时批处理文件失败: {str(e)}")
|
||||
|
||||
# 创建延迟删除任务
|
||||
QThread.sleep(10) # 确保批处理文件有足够时间执行
|
||||
delete_task = LambdaTask(delete_batch_file)
|
||||
thread_pool = QThreadPool.globalInstance()
|
||||
if thread_pool is not None:
|
||||
thread_pool.start(delete_task)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"启动外部脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def run_script(self, script_path, args, working_dir=None):
|
||||
"""使用非阻塞方式运行脚本"""
|
||||
try:
|
||||
# 检查脚本是否存在
|
||||
if not os.path.exists(script_path):
|
||||
self.error.emit(f"脚本不存在: {script_path}")
|
||||
self.log.emit(f"当前工作目录: {os.getcwd()}")
|
||||
self.log.emit(f"尝试查找脚本...")
|
||||
|
||||
# 从脚本名尝试查找脚本
|
||||
script_name = os.path.basename(script_path)
|
||||
actual_path = self._find_script(script_name)
|
||||
|
||||
if not os.path.exists(actual_path):
|
||||
self.error.emit(f"无法找到脚本: {script_name}")
|
||||
return False
|
||||
|
||||
script_path = actual_path
|
||||
|
||||
# 获取ArcGIS Pro的Python解释器路径
|
||||
python_exe = self._get_arcgis_python() or sys.executable
|
||||
self.log.emit(f"使用Python解释器: {python_exe}")
|
||||
|
||||
# 构建命令行参数
|
||||
cmd = [python_exe, "-u", script_path]
|
||||
|
||||
# 添加参数
|
||||
for key, value in args.items():
|
||||
if key.startswith('--'):
|
||||
param_key = key
|
||||
else:
|
||||
param_key = f"--{key}"
|
||||
|
||||
if isinstance(value, bool):
|
||||
# 布尔参数
|
||||
if value:
|
||||
cmd.append(param_key)
|
||||
elif value is not None:
|
||||
# 其他参数
|
||||
cmd.append(param_key)
|
||||
cmd.append(str(value))
|
||||
|
||||
# 创建QProcess而不是subprocess.Popen
|
||||
process = QProcess()
|
||||
|
||||
# 连接QProcess的信号
|
||||
process.readyReadStandardOutput.connect(
|
||||
lambda: self._handle_stdout(process)
|
||||
)
|
||||
process.readyReadStandardError.connect(
|
||||
lambda: self._handle_stderr(process)
|
||||
)
|
||||
process.finished.connect(
|
||||
lambda exit_code, exit_status: self._handle_finished(
|
||||
exit_code, exit_status, process
|
||||
)
|
||||
)
|
||||
|
||||
# 设置工作目录
|
||||
if working_dir:
|
||||
process.setWorkingDirectory(working_dir)
|
||||
|
||||
# 通知开始
|
||||
self.started.emit(f"开始执行脚本: {os.path.basename(script_path)}")
|
||||
|
||||
# 非阻塞启动进程
|
||||
process.start(cmd[0], cmd[1:])
|
||||
|
||||
# 将进程对象保存在类变量中,防止被垃圾回收
|
||||
self.current_process = process
|
||||
|
||||
# 返回True表示进程已启动(结果将通过信号异步通知)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"执行脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def _handle_stdout(self, process):
|
||||
"""处理标准输出"""
|
||||
try:
|
||||
# 首先尝试使用utf-8解码
|
||||
data = process.readAllStandardOutput().data()
|
||||
try:
|
||||
text = data.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
# utf-8解码失败,尝试使用系统默认编码或GBK(中文Windows常用)
|
||||
try:
|
||||
text = data.decode('gbk')
|
||||
except UnicodeDecodeError:
|
||||
# 如果还是失败,使用errors='replace'选项替换无法解码的字符
|
||||
text = data.decode('utf-8', errors='replace')
|
||||
|
||||
if text:
|
||||
for line in text.splitlines():
|
||||
if line.strip():
|
||||
self.log.emit(line.strip())
|
||||
except Exception as e:
|
||||
self.log.emit(f"处理标准输出时出错: {str(e)}")
|
||||
|
||||
def _handle_stderr(self, process):
|
||||
"""处理错误输出"""
|
||||
try:
|
||||
# 首先尝试使用utf-8解码
|
||||
data = process.readAllStandardError().data()
|
||||
try:
|
||||
text = data.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
# utf-8解码失败,尝试使用系统默认编码或GBK(中文Windows常用)
|
||||
try:
|
||||
text = data.decode('gbk')
|
||||
except UnicodeDecodeError:
|
||||
# 如果还是失败,使用errors='replace'选项替换无法解码的字符
|
||||
text = data.decode('utf-8', errors='replace')
|
||||
|
||||
if text:
|
||||
for line in text.splitlines():
|
||||
if line.strip():
|
||||
self.log.emit(f"错误: {line.strip()}")
|
||||
except Exception as e:
|
||||
self.log.emit(f"处理错误输出时出错: {str(e)}")
|
||||
|
||||
def _handle_finished(self, exit_code, exit_status, process):
|
||||
"""处理进程结束"""
|
||||
if exit_code == 0:
|
||||
self.log.emit(f"脚本执行成功,返回代码: {exit_code}")
|
||||
self.finished.emit(True)
|
||||
else:
|
||||
self.error.emit(f"脚本执行失败,返回代码: {exit_code}")
|
||||
self.finished.emit(False)
|
||||
|
||||
# 清理进程
|
||||
process.close()
|
||||
if hasattr(self, 'current_process') and self.current_process == process:
|
||||
self.current_process = None
|
||||
|
||||
def _get_arcgis_python(self):
|
||||
"""获取ArcGIS Pro的Python解释器路径"""
|
||||
try:
|
||||
# 常见的ArcGIS Pro Python路径
|
||||
arcgis_python_paths = [
|
||||
r"C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\python.exe",
|
||||
r"C:\Program Files\ArcGIS\Pro\bin\Python\python.exe"
|
||||
]
|
||||
|
||||
# 检查环境变量
|
||||
if 'ARCGISPRO_PYTHON' in os.environ:
|
||||
arcgis_python_paths.insert(0, os.environ['ARCGISPRO_PYTHON'])
|
||||
|
||||
# 尝试每个可能的路径
|
||||
for path in arcgis_python_paths:
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
|
||||
return None # 如果找不到,返回None
|
||||
except Exception as e:
|
||||
self.log.emit(f"获取ArcGIS Python路径失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def run_export_map(self, params):
|
||||
"""
|
||||
运行导出地图脚本
|
||||
|
||||
Args:
|
||||
params: 参数字典,包含:
|
||||
- config_file: 配置文件路径
|
||||
- county_name: 区县名称
|
||||
- polygon_list: 要导出的图层列表
|
||||
- template_aprx_file: 模板文件路径
|
||||
- output_path: 输出路径
|
||||
- data_source_path: 数据源路径
|
||||
- symbol_path: 符号文件路径
|
||||
- force_regenerate: 是否强制重新生成
|
||||
|
||||
Returns:
|
||||
返回脚本执行结果
|
||||
"""
|
||||
try:
|
||||
# 检查必要参数是否存在
|
||||
required_params = ['config_file', 'county_name', 'polygon_list',
|
||||
'template_aprx_file', 'output_path',
|
||||
'data_source_path', 'symbol_path']
|
||||
|
||||
for param in required_params:
|
||||
if param not in params:
|
||||
self.error.emit(f"缺少必要参数: {param}")
|
||||
return False
|
||||
|
||||
# 查找脚本路径
|
||||
script_path = self._find_script('export_map.py')
|
||||
|
||||
# 构建命令行参数
|
||||
args = {
|
||||
'config_file': params['config_file'],
|
||||
'county_name': params['county_name'],
|
||||
'polygon_list': json.dumps(params['polygon_list']), # 将图层列表序列化为JSON字符串
|
||||
'template_aprx_file': params['template_aprx_file'],
|
||||
'output_path': params['output_path'],
|
||||
'data_source_path': params['data_source_path'],
|
||||
'symbol_path': params['symbol_path']
|
||||
}
|
||||
|
||||
# 添加可选参数
|
||||
if 'force_regenerate' in params and params['force_regenerate']:
|
||||
args['force_regenerate'] = True
|
||||
|
||||
# 使用外部窗口执行脚本
|
||||
return self.run_script(script_path, args)
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"执行导出地图脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def run_batch_export_map(self, params):
|
||||
"""运行批量导出地图脚本 """
|
||||
try:
|
||||
# 获取参数
|
||||
if 'export_config' not in params:
|
||||
self.error.emit(f"缺少必要参数: export_config")
|
||||
return False
|
||||
|
||||
export_config = params['export_config']
|
||||
county_name = params['county_name']
|
||||
template_aprx_file = params['template_aprx_file']
|
||||
output_folder = params['output_path']
|
||||
data_source_path = params['data_source_path']
|
||||
symbol_path = params['symbol_path']
|
||||
polygon_list = params.get('polygon_list', []) # 如果指定了图层列表,则只导出这些图层
|
||||
force_regenerate = params.get('force_regenerate', False) # 是否强制重新生成工程文件
|
||||
|
||||
# 如果没有指定图层列表,则使用配置文件中的所有图层
|
||||
if not polygon_list:
|
||||
polygon_list = list(export_config.keys())
|
||||
|
||||
self.log.emit(f"开始批量导出 {len(polygon_list)} 个图层")
|
||||
|
||||
# 准备脚本路径
|
||||
script_path = self._find_script('export_map.py')
|
||||
|
||||
# 记录成功和失败的图层
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
|
||||
# 循环处理每个图层
|
||||
for layer_name in polygon_list:
|
||||
try:
|
||||
self.log.emit(f"\n===== 开始处理图层: {layer_name} =====")
|
||||
|
||||
# 构建命令行参数
|
||||
args = {
|
||||
'config': params['config_file'],
|
||||
'county': county_name,
|
||||
'layer': layer_name,
|
||||
'template': template_aprx_file,
|
||||
'output': output_folder,
|
||||
'data_source': data_source_path,
|
||||
'symbol': symbol_path,
|
||||
'force': force_regenerate
|
||||
}
|
||||
|
||||
# 执行脚本
|
||||
if self.run_script(script_path, args):
|
||||
success_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
except Exception as e:
|
||||
self.log.emit(f"处理图层 {layer_name} 时出错: {str(e)}")
|
||||
failed_count += 1
|
||||
|
||||
# 通知完成
|
||||
self.log.emit(f"\n===== 批量处理完成 =====")
|
||||
self.log.emit(f"总计图层: {len(polygon_list)}")
|
||||
self.log.emit(f"成功: {success_count} 个")
|
||||
self.log.emit(f"失败: {failed_count} 个")
|
||||
|
||||
result = {
|
||||
'total': len(polygon_list),
|
||||
'success': success_count,
|
||||
'failed': failed_count
|
||||
}
|
||||
|
||||
self.finished.emit(result)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"执行批量导出地图脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def run_export_layout(self, params):
|
||||
"""
|
||||
运行导出布局脚本
|
||||
|
||||
Args:
|
||||
params: 参数字典
|
||||
|
||||
Returns:
|
||||
返回脚本执行结果
|
||||
"""
|
||||
try:
|
||||
# 准备参数
|
||||
script_path = self._find_script('export_layout.py')
|
||||
|
||||
# 构建命令行参数
|
||||
args = {
|
||||
'mode': 'single',
|
||||
'aprx': params['aprx_path'],
|
||||
'output': params['output_path'],
|
||||
'format': params.get('export_format', 'PDF'),
|
||||
'resolution': params.get('resolution', 300),
|
||||
'name': params.get('output_name', '')
|
||||
}
|
||||
|
||||
# 执行脚本
|
||||
# return self.run_script(script_path, args)
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"执行导出布局脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def run_batch_export_layout(self, params):
|
||||
"""
|
||||
运行批量导出布局脚本
|
||||
|
||||
Args:
|
||||
params: 参数字典
|
||||
|
||||
Returns:
|
||||
返回脚本执行结果
|
||||
"""
|
||||
try:
|
||||
# 准备参数
|
||||
script_path = self._find_script('export_layout.py')
|
||||
|
||||
# 构建命令行参数
|
||||
args = {
|
||||
'aprx_file_list':json.dumps(params['aprx_file_list']),
|
||||
'input_aprx_folder': params['input_aprx_folder'],
|
||||
'output': params['output_image_path'],
|
||||
'format': params.get('export_format', 'PDF'),
|
||||
'resolution': params.get('resolution', 300),
|
||||
'use_multiprocessing': params.get('use_multiprocessing', False),
|
||||
'process_count': params.get('process_count', 2)
|
||||
}
|
||||
|
||||
# 执行脚本
|
||||
return self.run_script(script_path, args)
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"执行批量导出布局脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def run_process_raster(self, params):
|
||||
"""
|
||||
运行栅格处理脚本
|
||||
|
||||
Args:
|
||||
params: 参数字典
|
||||
|
||||
Returns:
|
||||
返回脚本执行结果
|
||||
"""
|
||||
try:
|
||||
# 准备参数
|
||||
script_path = self._find_script('raster_to_vector.py')
|
||||
|
||||
# 构建命令行参数
|
||||
args = {
|
||||
'command': 'process',
|
||||
'input': params['input_raster'],
|
||||
'output': params['output_raster'],
|
||||
'format': params.get('output_format', 'TIFF'),
|
||||
'compression': params.get('compression', 'LZW')
|
||||
}
|
||||
|
||||
# 执行脚本
|
||||
return self.run_script(script_path, args)
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"执行栅格处理脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def run_raster_batch_process(self, params):
|
||||
"""
|
||||
运行批量栅格处理脚本
|
||||
|
||||
Args:
|
||||
params: 参数字典
|
||||
|
||||
Returns:
|
||||
返回脚本执行结果
|
||||
"""
|
||||
try:
|
||||
# 准备参数
|
||||
script_path = self._find_script('raster_handler.py')
|
||||
params["selected_files"] = json.dumps(params.get("selected_files"))
|
||||
# 执行脚本
|
||||
return self.run_script(script_path, params)
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"执行批量栅格处理脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def run_test_script(self, params):
|
||||
try:
|
||||
# 检查必要参数是否存在
|
||||
required_params = ['message', 'count']
|
||||
for param in required_params:
|
||||
if param not in params:
|
||||
self.error.emit(f"缺少必要参数: {param}")
|
||||
return False
|
||||
|
||||
# 查找脚本路径
|
||||
script_path = self._find_script('test_script.py')
|
||||
|
||||
# 构建命令行参数
|
||||
args = {
|
||||
'message': params['message'],
|
||||
'count': params['count']
|
||||
}
|
||||
|
||||
# 执行脚本
|
||||
return self.run_script(script_path, args)
|
||||
|
||||
except Exception as e:
|
||||
self.error.emit(f"执行导出地图脚本时出错: {str(e)}")
|
||||
self.log.emit(traceback.format_exc())
|
||||
return False
|
||||
|
||||
9
tools/ui/tabs/__init__.py
Normal file
9
tools/ui/tabs/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# UI界面模块
|
||||
from .export_layout_tab import ExportImageTab
|
||||
from .raster_tab import RasterTab
|
||||
from .export_map_tab import ExportMapTab
|
||||
|
||||
__all__ = ['ExportImageTab', 'RasterTab', 'ExportMapTab']
|
||||
373
tools/ui/tabs/acid_stat_tab.py
Normal file
373
tools/ui/tabs/acid_stat_tab.py
Normal file
@@ -0,0 +1,373 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
栅格处理界面: 提供栅格重分类、栅格转矢量和小面积图斑消除的界面操作
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import arcpy
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QFileDialog, QGridLayout,
|
||||
QGroupBox, QMessageBox)
|
||||
from tools.ui.runners.script_runner import ScriptRunner
|
||||
|
||||
|
||||
class AcidStatsTab(QWidget):
|
||||
"""栅格处理窗口部件类"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(AcidStatsTab, self).__init__(parent)
|
||||
self.main_window = parent
|
||||
# 创建的导出线程
|
||||
self.script_runner = ScriptRunner()
|
||||
self.connect_signals()
|
||||
self.init_ui()
|
||||
self.setWindowTitle("酸化状况统计")
|
||||
|
||||
def connect_signals(self):
|
||||
# 连接ScriptRunner的信号
|
||||
self.script_runner.task_started.connect(self.on_script_started)
|
||||
self.script_runner.task_finished.connect(self.on_script_finished)
|
||||
self.script_runner.task_error.connect(self.on_script_error)
|
||||
self.script_runner.task_log.connect(self.on_script_log)
|
||||
self.script_runner.manager_log.connect(self.log_message)
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化用户界面"""
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# 批量处理区域
|
||||
self.batch_mode_group = QGroupBox("批量处理设置")
|
||||
batch_layout = QGridLayout()
|
||||
|
||||
# 输入行政区名称
|
||||
batch_layout.addWidget(QLabel("行政区名称:"), 0, 0)
|
||||
self.input_xzqmc_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.input_xzqmc_edit, 0, 1)
|
||||
|
||||
# 设置工作空间
|
||||
batch_layout.addWidget(QLabel("工作空间:"), 1, 0)
|
||||
self.input_workspace_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.input_workspace_edit, 1, 1)
|
||||
self.browse_input_workspace_btn = QPushButton("选择GDB")
|
||||
self.browse_input_workspace_btn.clicked.connect(self.browse_input_workspace)
|
||||
batch_layout.addWidget(self.browse_input_workspace_btn, 1, 2)
|
||||
|
||||
# 批量输出文件夹
|
||||
batch_layout.addWidget(QLabel("输出文件夹:"), 2, 0)
|
||||
self.batch_output_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.batch_output_folder_edit, 2, 1)
|
||||
self.browse_batch_output_btn = QPushButton("选择文件夹")
|
||||
self.browse_batch_output_btn.clicked.connect(self.browse_batch_output_folder)
|
||||
batch_layout.addWidget(self.browse_batch_output_btn, 2, 2)
|
||||
|
||||
# 选择乡镇界线
|
||||
batch_layout.addWidget(QLabel("乡镇界线:"), 3, 0)
|
||||
self.xzq_features_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.xzq_features_edit, 3, 1)
|
||||
batch_layout.addWidget(QLabel("GDB中的要素类名"), 3, 2)
|
||||
|
||||
# 选择地类图斑
|
||||
batch_layout.addWidget(QLabel("地类图斑:"), 4, 0)
|
||||
self.dltb_features_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.dltb_features_edit, 4, 1)
|
||||
batch_layout.addWidget(QLabel("GDB中的要素类名"), 4, 2)
|
||||
|
||||
# 选择历史PH样点图斑
|
||||
batch_layout.addWidget(QLabel("历史PH样点:"), 5, 0)
|
||||
self.ph_samples_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.ph_samples_edit, 5, 1)
|
||||
batch_layout.addWidget(QLabel("GDB中的要素类名"), 5, 2)
|
||||
|
||||
# 选择重分类后的PH面要素
|
||||
batch_layout.addWidget(QLabel("分类后酸化PH面:"), 6, 0)
|
||||
self.acid_ph_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.acid_ph_edit, 6, 1)
|
||||
batch_layout.addWidget(QLabel("GDB中的要素类名"), 6, 2)
|
||||
|
||||
# 选择土壤类型图斑
|
||||
batch_layout.addWidget(QLabel("土壤类型图斑:"), 7, 0)
|
||||
self.soil_type_features_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.soil_type_features_edit, 7, 1)
|
||||
batch_layout.addWidget(QLabel("GDB中的要素类名"), 7, 2)
|
||||
|
||||
# 选择三普PH赋值栅格
|
||||
batch_layout.addWidget(QLabel("三普PH栅格:"), 8, 0)
|
||||
self.assign_raster_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.assign_raster_edit, 8, 1)
|
||||
self.assign_raster_btn = QPushButton("选择TIF")
|
||||
self.assign_raster_btn.clicked.connect(self.browse_assign_raster)
|
||||
batch_layout.addWidget(self.assign_raster_btn, 8, 2)
|
||||
|
||||
# 选择酸化PH赋值栅格
|
||||
batch_layout.addWidget(QLabel("酸化PH栅格:"), 9, 0)
|
||||
self.acid_raster_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.acid_raster_edit, 9, 1)
|
||||
self.acid_raster_btn = QPushButton("选择TIF")
|
||||
self.acid_raster_btn.clicked.connect(self.browse_acid_raster)
|
||||
batch_layout.addWidget(self.acid_raster_btn, 9, 2)
|
||||
|
||||
self.batch_mode_group.setLayout(batch_layout)
|
||||
|
||||
# 操作按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
|
||||
# 导出酸化统计表
|
||||
self.generate_sh_stat_btn = QPushButton("生成酸化统计表")
|
||||
self.generate_sh_stat_btn.clicked.connect(self.on_generate_area_stat)
|
||||
|
||||
self.cancel_btn = QPushButton("取消")
|
||||
# self.cancel_btn.clicked.connect(self.close)
|
||||
|
||||
btn_layout.addWidget(self.generate_sh_stat_btn)
|
||||
btn_layout.addWidget(self.cancel_btn)
|
||||
|
||||
# 添加所有组件到主布局
|
||||
main_layout.addWidget(self.batch_mode_group)
|
||||
main_layout.addLayout(btn_layout)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
|
||||
def browse_input_workspace(self):
|
||||
"""浏览选择输入GDB"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择GDB数据库")
|
||||
if folder_path:
|
||||
self.input_workspace_edit.setText(folder_path)
|
||||
|
||||
def browse_batch_output_folder(self):
|
||||
"""浏览选择表格输出文件夹"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择表格输出文件夹")
|
||||
if folder_path:
|
||||
self.batch_output_folder_edit.setText(folder_path)
|
||||
|
||||
def browse_assign_raster(self):
|
||||
"""浏览选择赋值栅格 (支持栅格数据)"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择赋值栅格", "",
|
||||
"栅格文件 (*.tif *.img *.jpg);;所有文件 (*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
if file_path.lower().endswith(('.tif', '.img', '.jpg')):
|
||||
self.assign_raster_edit.setText(file_path)
|
||||
self.log_message(f"已选择栅格文件: {file_path}")
|
||||
|
||||
else:
|
||||
QMessageBox.warning(self, "选择错误", "不支持的文件类型。请选择有效的栅格文件。")
|
||||
self.assign_raster_edit.clear()
|
||||
|
||||
def browse_acid_raster(self):
|
||||
"""浏览选择赋值栅格 (支持栅格数据)"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择赋值栅格", "",
|
||||
"栅格文件 (*.tif *.img *.jpg);;所有文件 (*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
if file_path.lower().endswith(('.tif', '.img', '.jpg')):
|
||||
self.acid_raster_edit.setText(file_path)
|
||||
self.log_message(f"已选择栅格文件: {file_path}")
|
||||
|
||||
else:
|
||||
QMessageBox.warning(self, "选择错误", "不支持的文件类型。请选择有效的栅格文件。")
|
||||
self.acid_raster_edit.clear()
|
||||
|
||||
def validate_inputs(self):
|
||||
"""验证输入参数"""
|
||||
# 验证工作空间路径
|
||||
if not self.input_workspace_edit.text() or not arcpy.Exists(self.input_workspace_edit.text()):
|
||||
QMessageBox.warning(self, "输入错误", "请选择有效的工作空间")
|
||||
return False
|
||||
|
||||
if not self.batch_output_folder_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请选择批量处理输出文件夹")
|
||||
return False
|
||||
|
||||
# 行政界线验证
|
||||
xzq_path = os.path.join(self.input_workspace_edit.text(),self.xzq_features_edit.text())
|
||||
if not xzq_path or not arcpy.Exists(xzq_path):
|
||||
QMessageBox.warning(self, "输入错误", "行政界线要素路径无效或不存在")
|
||||
return False
|
||||
|
||||
try:
|
||||
desc = arcpy.Describe(xzq_path)
|
||||
if desc.dataType != 'FeatureClass':
|
||||
QMessageBox.warning(self, "输入错误", f"行政界线不是一个要素类:\n{xzq_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"未知要素,请检查是否为有效要素类:\n{xzq_path}\n错误: {e}")
|
||||
return False
|
||||
|
||||
# 地类图斑验证
|
||||
dltb_path = os.path.join(self.input_workspace_edit.text(),self.dltb_features_edit.text())
|
||||
if not dltb_path or not arcpy.Exists(dltb_path):
|
||||
QMessageBox.warning(self, "输入错误", "地类图斑要素路径无效或不存在")
|
||||
return False
|
||||
|
||||
try:
|
||||
desc = arcpy.Describe(dltb_path)
|
||||
if desc.dataType != 'FeatureClass':
|
||||
QMessageBox.warning(self, "输入错误", f"地类图斑不是一个要素类:\n{dltb_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"未知要素,请检查是否为有效要素类:\n{dltb_path}\n错误: {e}")
|
||||
return False
|
||||
|
||||
# 验证PH样点
|
||||
samples_path = os.path.join(self.input_workspace_edit.text(), self.ph_samples_edit.text())
|
||||
if not samples_path or not arcpy.Exists(samples_path):
|
||||
QMessageBox.warning(self, "输入错误", "PH样点要素路径无效或不存在")
|
||||
return False
|
||||
try:
|
||||
desc = arcpy.Describe(samples_path)
|
||||
if desc.dataType != 'FeatureClass':
|
||||
QMessageBox.warning(self, "输入错误", f"PH样点不是一个要素类:\n{samples_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"未知要素,请检查是否为有效要素类:\n{samples_path}\n错误: {e}")
|
||||
return False
|
||||
|
||||
# 重分类后酸化面要素验证
|
||||
acid_ph_path = os.path.join(self.input_workspace_edit.text(), self.acid_ph_edit.text())
|
||||
if not acid_ph_path or not arcpy.Exists(acid_ph_path):
|
||||
QMessageBox.warning(self, "输入错误", "酸化PH要素路径无效或不存在")
|
||||
return False
|
||||
try:
|
||||
desc = arcpy.Describe(acid_ph_path)
|
||||
if desc.dataType != 'FeatureClass':
|
||||
QMessageBox.warning(self, "输入错误", f"酸化PH不是一个要素类:\n{acid_ph_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"未知要素,请检查是否为有效要素类:\n{acid_ph_path}\n错误: {e}")
|
||||
return False
|
||||
|
||||
# 土壤类型验证
|
||||
trlx_path = os.path.join(self.input_workspace_edit.text(), self.soil_type_features_edit.text())
|
||||
if not self.soil_type_features_edit.text() or not arcpy.Exists(trlx_path):
|
||||
QMessageBox.warning(self, "输入错误", "土壤类型要素路径无效或不存在")
|
||||
return False
|
||||
try:
|
||||
desc = arcpy.Describe(trlx_path)
|
||||
if desc.dataType != 'FeatureClass':
|
||||
QMessageBox.warning(self, "输入错误", f"土壤类型不是一个要素类:\n{trlx_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"未知要素,请检查是否为有效要素类:\n{trlx_path}\n错误: {e}")
|
||||
return False
|
||||
|
||||
# 验证输入是否为栅格
|
||||
if not self.acid_raster_edit.text() or not self.assign_raster_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请选择输入栅格")
|
||||
return False
|
||||
elif not arcpy.Exists(self.acid_raster_edit.text()) or not arcpy.Exists(self.assign_raster_edit.text()):
|
||||
QMessageBox.warning(self, "输入错误", "输入栅格不存在")
|
||||
return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
# 执行生成酸化统计表
|
||||
def on_generate_area_stat(self):
|
||||
"""执行生成面积统计表"""
|
||||
if not self.validate_inputs():
|
||||
return
|
||||
|
||||
try:
|
||||
if self.main_window and hasattr(self.main_window, 'save_settings'):
|
||||
self.main_window.update_settings()
|
||||
self.main_window.save_settings()
|
||||
settings_file = self.main_window.settings_file
|
||||
|
||||
params = {
|
||||
"settings_path": settings_file,
|
||||
}
|
||||
|
||||
task_id = self.script_runner.run_suanhua_stat(params)
|
||||
|
||||
if task_id:
|
||||
self.log_message(f"[GUI] 面积统计任务 {task_id} 已添加到列队")
|
||||
except Exception as e:
|
||||
error_msg = f"处理过程中出错: {str(e)}"
|
||||
QMessageBox.critical(self, "处理错误", error_msg)
|
||||
self.log_message(error_msg)
|
||||
|
||||
def get_acid_stat_settings(self):
|
||||
"""保存当前配置到JSON文件"""
|
||||
config = {
|
||||
"workspace_path": self.input_workspace_edit.text(),
|
||||
"batch_output_folder": self.batch_output_folder_edit.text(),
|
||||
"xzq_features": self.xzq_features_edit.text(),
|
||||
"dltb_features": self.dltb_features_edit.text(),
|
||||
"ph_samples": self.ph_samples_edit.text(),
|
||||
"acid_ph_features": self.acid_ph_edit.text(),
|
||||
"assign_raster": self.assign_raster_edit.text(),
|
||||
"acid_raster": self.acid_raster_edit.text(),
|
||||
"soil_type_features": self.soil_type_features_edit.text(),
|
||||
"xzqmc": self.input_xzqmc_edit.text()
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
def load_settings(self, settings):
|
||||
"""从字典加载配置"""
|
||||
try:
|
||||
# 批处理参数
|
||||
self.input_workspace_edit.setText(settings.get("workspace_path", ""))
|
||||
self.batch_output_folder_edit.setText(settings.get("batch_output_folder", ""))
|
||||
self.xzq_features_edit.setText(settings.get("xzq_features", ""))
|
||||
self.dltb_features_edit.setText(settings.get("dltb_features", ""))
|
||||
self.ph_samples_edit.setText(settings.get("ph_samples", ""))
|
||||
self.acid_ph_edit.setText(settings.get("acid_ph_features", ""))
|
||||
self.assign_raster_edit.setText(settings.get("assign_raster", ""))
|
||||
self.acid_raster_edit.setText(settings.get("acid_raster", ""))
|
||||
self.soil_type_features_edit.setText(settings.get("soil_type_features", ""))
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "加载配置错误", f"加载配置时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def set_buttons_enabled(self, enabled):
|
||||
"""设置按钮是否可用"""
|
||||
self.generate_sh_stat_btn.setEnabled(enabled)
|
||||
|
||||
def on_script_started(self, task_id, task_description):
|
||||
"""脚本开始执行"""
|
||||
self.set_buttons_enabled(False)
|
||||
self.log_message(f"{task_id}: 正在运行 - {task_description}")
|
||||
|
||||
def on_script_finished(self, task_id:str, success:bool, message:str):
|
||||
"""脚本执行完成"""
|
||||
self.set_buttons_enabled(True)
|
||||
status = "完成" if success else "失败"
|
||||
self.log_message(f"[{task_id}] 脚本执行{status}: {message}")
|
||||
|
||||
def on_script_error(self,task_id, error_msg):
|
||||
"""脚本执行出错"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message(f"错误:{task_id}-{error_msg}")
|
||||
# QMessageBox.critical(self, "错误", error_msg)
|
||||
|
||||
def on_script_log(self, task_id, message):
|
||||
"""脚本输出日志"""
|
||||
self.log_message(f"{task_id}: {message}")
|
||||
|
||||
def log_message(self, message):
|
||||
"""日志输出"""
|
||||
if self.main_window and hasattr(self.main_window, 'log_signal'):
|
||||
self.main_window.log_signal.emit(message)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
import sys
|
||||
app = QApplication(sys.argv)
|
||||
window = AcidStatsTab()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
564
tools/ui/tabs/area_stat_tab.py
Normal file
564
tools/ui/tabs/area_stat_tab.py
Normal file
@@ -0,0 +1,564 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
栅格处理界面: 提供栅格重分类、栅格转矢量和小面积图斑消除的界面操作
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import arcpy
|
||||
import traceback
|
||||
import multiprocessing
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QFileDialog, QGridLayout, QCheckBox,
|
||||
QGroupBox, QMessageBox, QSpinBox)
|
||||
|
||||
from tools.core.utils import common_utils
|
||||
from tools.ui.runners.script_runner import ScriptRunner
|
||||
from .config_editor_dialog import ConfigEditorDialogVisual
|
||||
from ..components.file_list_group import FileListGroup
|
||||
|
||||
yunnan_dlbm = """
|
||||
def yunnan_dlbm(dlbm):
|
||||
if dlbm.startswith("0101"):
|
||||
return "0101"
|
||||
elif dlbm.startswith("0102"):
|
||||
return "0102"
|
||||
elif dlbm.startswith("0103"):
|
||||
return "0103"
|
||||
else:
|
||||
return dlbm[:2]
|
||||
"""
|
||||
|
||||
class XlsxToJpgTab(QWidget):
|
||||
"""栅格处理窗口部件类"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(XlsxToJpgTab, self).__init__(parent)
|
||||
self.main_window = parent
|
||||
# 创建的导出线程
|
||||
self.script_runner = ScriptRunner()
|
||||
self.connect_signals()
|
||||
self.init_ui()
|
||||
self.setWindowTitle("面积统计")
|
||||
|
||||
def connect_signals(self):
|
||||
# 连接ScriptRunner的信号
|
||||
self.script_runner.task_started.connect(self.on_script_started)
|
||||
self.script_runner.task_finished.connect(self.on_script_finished)
|
||||
self.script_runner.task_error.connect(self.on_script_error)
|
||||
self.script_runner.task_log.connect(self.on_script_log)
|
||||
self.script_runner.manager_log.connect(self.log_message)
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化用户界面"""
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# 批量处理区域
|
||||
self.batch_mode_group = QGroupBox("批量处理设置")
|
||||
batch_layout = QGridLayout()
|
||||
|
||||
# 配置文件选择 (公用)
|
||||
batch_layout.addWidget(QLabel("配置文件:"), 0, 0)
|
||||
self.config_file_edit = QLineEdit()
|
||||
self.config_file_edit.setEnabled(False)
|
||||
batch_layout.addWidget(self.config_file_edit, 0, 1,1,2)
|
||||
# self.browse_config_btn = QPushButton("浏览...")
|
||||
# self.browse_config_btn.clicked.connect(self.browse_config_file)
|
||||
# self.browse_config_btn.setEnabled(False)
|
||||
# batch_layout.addWidget(self.browse_config_btn, 0, 2)
|
||||
|
||||
# # 添加编辑配置文件的按钮 <--- Add this button
|
||||
# self.edit_config_btn = QPushButton("编辑配置内容...")
|
||||
# self.edit_config_btn.setEnabled(False)
|
||||
# self.edit_config_btn.clicked.connect(self.open_config_editor)
|
||||
# batch_layout.addWidget(self.edit_config_btn, 0, 3)
|
||||
|
||||
# 输入文件夹
|
||||
batch_layout.addWidget(QLabel("重分类面要素:"), 1, 0)
|
||||
self.input_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.input_folder_edit, 1, 1)
|
||||
self.browse_input_folder_btn = QPushButton("浏览...")
|
||||
self.browse_input_folder_btn.clicked.connect(self.browse_input_folder)
|
||||
batch_layout.addWidget(self.browse_input_folder_btn, 1, 2)
|
||||
|
||||
# 批量输出文件夹
|
||||
batch_layout.addWidget(QLabel("输出文件夹:"), 2, 0)
|
||||
self.batch_output_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.batch_output_folder_edit, 2, 1)
|
||||
self.browse_batch_output_btn = QPushButton("浏览...")
|
||||
self.browse_batch_output_btn.clicked.connect(self.browse_batch_output_folder)
|
||||
batch_layout.addWidget(self.browse_batch_output_btn, 2, 2)
|
||||
|
||||
# 选择乡镇界线
|
||||
batch_layout.addWidget(QLabel("乡镇界线:"), 3, 0)
|
||||
self.xzq_features_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.xzq_features_edit, 3, 1)
|
||||
self.xzq_features_btn = QPushButton("浏览")
|
||||
self.xzq_features_btn.clicked.connect(self.browse_xzq_features)
|
||||
batch_layout.addWidget(self.xzq_features_btn, 3, 2)
|
||||
|
||||
# 选择地类图斑
|
||||
batch_layout.addWidget(QLabel("地类图斑:"), 4, 0)
|
||||
self.dltb_features_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.dltb_features_edit, 4, 1)
|
||||
self.dltb_features_btn = QPushButton("浏览")
|
||||
self.dltb_features_btn.clicked.connect(self.browse_dltb_features)
|
||||
batch_layout.addWidget(self.dltb_features_btn, 4, 2)
|
||||
|
||||
# 输入行政区名称
|
||||
batch_layout.addWidget(QLabel("行政区名称:"), 5, 0)
|
||||
self.xzqmc_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.xzqmc_edit, 5, 1, 1, 2)
|
||||
|
||||
# 单选框
|
||||
ext_layout = QHBoxLayout()
|
||||
ext_layout.addWidget(QLabel("是否同时对行政区和地类进行平差(需要xlsx文件有其他地类的平差面积):"))
|
||||
self.is_adjust_xzq_and_landuse_checkbox = QCheckBox()
|
||||
ext_layout.addWidget(self.is_adjust_xzq_and_landuse_checkbox)
|
||||
ext_layout.addStretch()
|
||||
batch_layout.addLayout(ext_layout, 6, 0, 1, 2)
|
||||
|
||||
self.batch_mode_group.setLayout(batch_layout)
|
||||
|
||||
# 文件列表显示组
|
||||
self.file_list_group = FileListGroup(self, "选择重分类后面要素")
|
||||
self.file_list_group.load_files.connect(self.on_load_raster)
|
||||
|
||||
# --- 处理参数设置 ---
|
||||
param_group = QGroupBox("处理参数")
|
||||
param_layout = QVBoxLayout(param_group)
|
||||
|
||||
# 多进程设置
|
||||
export_layout = QHBoxLayout()
|
||||
mutiprocess_layout = QHBoxLayout()
|
||||
mutiprocess_layout.addWidget(QLabel("使用多进程导出:"))
|
||||
self.mutiprocess_check = QCheckBox()
|
||||
self.mutiprocess_check.setChecked(True)
|
||||
mutiprocess_layout.addWidget(self.mutiprocess_check)
|
||||
mutiprocess_layout.addStretch()
|
||||
export_layout.addLayout(mutiprocess_layout)
|
||||
|
||||
# 进程数量设置
|
||||
process_count_layout = QHBoxLayout()
|
||||
process_count_layout.addWidget(QLabel("进程数量:"))
|
||||
self.process_count = QSpinBox()
|
||||
self.process_count.setRange(1, multiprocessing.cpu_count())
|
||||
self.process_count.setValue(max(1, multiprocessing.cpu_count() - 1)) # 默认使用CPU核心数-1
|
||||
self.process_count.setFixedWidth(180)
|
||||
process_count_layout.addWidget(self.process_count)
|
||||
|
||||
export_layout.addLayout(process_count_layout)
|
||||
param_layout.addLayout(export_layout)
|
||||
|
||||
# 操作按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
self.process_btn = QPushButton("导出到Excel")
|
||||
self.process_btn.clicked.connect(self.on_start_processing)
|
||||
|
||||
self.excel_to_jpg_btn = QPushButton("Eexcel导出到JPG")
|
||||
self.excel_to_jpg_btn.clicked.connect(self.on_export_excel_to_jpg)
|
||||
|
||||
self.cancel_btn = QPushButton("取消")
|
||||
# self.cancel_btn.clicked.connect(self.close)
|
||||
|
||||
btn_layout.addWidget(self.process_btn)
|
||||
btn_layout.addWidget(self.excel_to_jpg_btn)
|
||||
btn_layout.addWidget(self.cancel_btn)
|
||||
|
||||
# 添加所有组件到主布局
|
||||
main_layout.addWidget(self.batch_mode_group)
|
||||
main_layout.addWidget(self.file_list_group)
|
||||
main_layout.addWidget(param_group)
|
||||
main_layout.addLayout(btn_layout)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def browse_input_folder(self):
|
||||
"""浏览选择输入文件夹"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择重分类后面要素文件夹")
|
||||
if folder_path:
|
||||
self.input_folder_edit.setText(folder_path)
|
||||
self.on_load_raster()
|
||||
|
||||
def browse_batch_output_folder(self):
|
||||
"""浏览选理输出文件夹"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择输出文件夹")
|
||||
if folder_path:
|
||||
self.batch_output_folder_edit.setText(folder_path)
|
||||
|
||||
def browse_config_file(self):
|
||||
"""浏览选择配置文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择配置文件", "",
|
||||
"JSON 文件 (*.json);;所有文件 (*)"
|
||||
)
|
||||
if file_path:
|
||||
self.config_file_edit.setText(file_path)
|
||||
# self.validate_config_file(file_path)
|
||||
# self.edit_config_btn.setEnabled(True)
|
||||
def browse_xzq_features(self):
|
||||
"""浏览选择乡镇界线要素 (支持 shapefile)"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择乡镇界线要素 (Shapefile)", "",
|
||||
"Shapefile 文件 (*.shp);;所有文件 (*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
if file_path.lower().endswith(".shp"):
|
||||
self.xzq_features_edit.setText(file_path)
|
||||
self.log_message(f"已选择 Shapefile: {file_path}")
|
||||
|
||||
else:
|
||||
QMessageBox.warning(self, "选择错误", "不支持的文件类型。请选择 .shp 文件。")
|
||||
self.xzq_features_edit.clear()
|
||||
|
||||
def browse_dltb_features(self):
|
||||
"""浏览选择地类图斑要素 (支持 shapefile)"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择地类图斑要素 (Shapefile)", "",
|
||||
"Shapefile 文件 (*.shp);;所有文件 (*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
if file_path.lower().endswith(".shp"):
|
||||
self.dltb_features_edit.setText(file_path)
|
||||
self.log_message(f"已选择 Shapefile: {file_path}")
|
||||
|
||||
else:
|
||||
QMessageBox.warning(self, "选择错误", "不支持的文件类型。请选择 .shp 文件。")
|
||||
self.dltb_features_edit.clear()
|
||||
|
||||
def validate_config_file(self, config_file_path):
|
||||
"""对文件进行快速检查,以查看它是否类似于预期的配置结构。"""
|
||||
if not os.path.exists(config_file_path):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(config_file_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
if "export_config" in config and isinstance(config["export_config"], dict):
|
||||
if config["export_config"]:
|
||||
first_item_value = next(iter(config["export_config"].values()))
|
||||
if isinstance(first_item_value, dict) and all(k in first_item_value for k in ["项目名称", "标准等级"]):
|
||||
self.log_message(f"配置文件 '{os.path.basename(config_file_path)}' 加载成功。")
|
||||
return True
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 内容结构可能不匹配预期。")
|
||||
return False
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 中的 'export_config' 部分为空。")
|
||||
return False
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 缺少 'export_config' 键或格式不正确。")
|
||||
return False
|
||||
|
||||
except json.JSONDecodeError:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 不是有效的 JSON 格式。")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.log_message(f"警告: 检查配置文件 '{os.path.basename(config_file_path)}' 时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def open_config_editor(self):
|
||||
"""打开配置文件编辑器对话框"""
|
||||
config_path = self.config_file_edit.text()
|
||||
|
||||
if not config_path:
|
||||
reply = QMessageBox.question(self, "编辑配置", "没有选择配置文件,是否创建一个新配置文件?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||
if reply == QMessageBox.StandardButton.No:
|
||||
return
|
||||
|
||||
config_path = ""
|
||||
|
||||
# 使用配置编辑对话框
|
||||
dialog = ConfigEditorDialogVisual(config_path, self)
|
||||
|
||||
if dialog.exec() == ConfigEditorDialogVisual.DialogCode.Accepted:
|
||||
new_path = dialog.get_new_config_path()
|
||||
if new_path and new_path != self.config_file_edit.text():
|
||||
self.config_file_edit.setText(new_path)
|
||||
self.validate_config_file(new_path)
|
||||
|
||||
def on_load_raster(self):
|
||||
"""加载图层列表"""
|
||||
try:
|
||||
data_source_paths = self.input_folder_edit.text()
|
||||
|
||||
self.file_list_group.load_file_btn.setEnabled(False)
|
||||
|
||||
# 清空列表
|
||||
self.file_list_group.file_list.clear()
|
||||
|
||||
if not data_source_paths or not arcpy.Exists(data_source_paths):
|
||||
self.log_message(f"输入路径不存在或不是有效的ArcGIS工作空间/文件夹: {data_source_paths}")
|
||||
return
|
||||
|
||||
arcpy.env.workspace = data_source_paths
|
||||
|
||||
# 获取所有SHP 文件
|
||||
shp_files = arcpy.ListFeatureClasses("*.shp")
|
||||
if shp_files:
|
||||
for raster in shp_files:
|
||||
self.file_list_group.file_list.addItem(raster)
|
||||
|
||||
self.log_message(f"已加载 {self.file_list_group.file_list.count()} 个图层")
|
||||
|
||||
else:
|
||||
self.log_message(f"文件夹 '{os.path.basename(data_source_paths)}' 中没有找到重分类后面要素文件。")
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"加载图层列表失败: {str(e)}")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
self.file_list_group.load_file_btn.setEnabled(True)
|
||||
|
||||
def validate_inputs(self):
|
||||
"""验证输入参数"""
|
||||
# 验证配置文件路径
|
||||
config_path = self.config_file_edit.text()
|
||||
if not config_path or not os.path.exists(config_path):
|
||||
QMessageBox.warning(self, "输入错误", "请选择有效的配置文件")
|
||||
return False
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config_data = json.load(f)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"无法加载或解析配置文件: {e}")
|
||||
return False
|
||||
|
||||
if "export_config" not in config_data or not isinstance(config_data["export_config"], dict):
|
||||
QMessageBox.warning(self, "输入错误", "配置文件的内容无效或缺少 'export_config' 部分。")
|
||||
return False
|
||||
|
||||
standards_dict = config_data["export_config"]
|
||||
|
||||
if not standards_dict:
|
||||
QMessageBox.warning(self, "输入错误", "配置文件中的 'export_config' 部分为空,没有定义任何处理参数。")
|
||||
return False
|
||||
|
||||
# 验证'标准等级'用于配置重分类表
|
||||
error_keys_remap = []
|
||||
for key, value in standards_dict.items():
|
||||
try:
|
||||
levels_data = value.get("标准等级", {})
|
||||
if not isinstance(levels_data, dict):
|
||||
error_keys_remap.append(f"{key} ('标准等级' 格式不正确)")
|
||||
continue
|
||||
if not common_utils.create_remap_table(levels_data):
|
||||
error_keys_remap.append(f"{key} ('标准等级' 内容无效)")
|
||||
except Exception:
|
||||
error_keys_remap.append(f"{key} (验证出错)")
|
||||
|
||||
if error_keys_remap:
|
||||
QMessageBox.warning(self, "输入错误", "配置文件中部分参数的 '标准等级' 数据无效:\n" + "\n".join(error_keys_remap))
|
||||
return False
|
||||
|
||||
# 验证输入、输出文件夹
|
||||
if not self.input_folder_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请选择输入重分类后面要素文件夹")
|
||||
return False
|
||||
|
||||
if not self.batch_output_folder_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请选择批量处理输出文件夹")
|
||||
return False
|
||||
|
||||
if not self.xzqmc_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请输入县区名称")
|
||||
return False
|
||||
|
||||
# 验证地类图斑文件
|
||||
dltb_path = self.dltb_features_edit.text()
|
||||
if not dltb_path and not arcpy.Exists(dltb_path):
|
||||
QMessageBox.warning(self, "输入错误", "请选择有效的地类图斑文件")
|
||||
return False
|
||||
|
||||
# 3. 验证乡镇界线文件
|
||||
xzjx_path = self.xzq_features_edit.text()
|
||||
if not xzjx_path or not arcpy.Exists(xzjx_path):
|
||||
QMessageBox.warning(self, "输入错误", "请选择有效的乡镇界线文件。")
|
||||
return False
|
||||
|
||||
# Optional: Check if the path points to a Feature Class (SHP or inside GDB)
|
||||
try:
|
||||
desc = arcpy.Describe(xzjx_path)
|
||||
if desc.dataType != 'FeatureClass' and desc.dataType != 'ShapeFile':
|
||||
QMessageBox.warning(self, "输入错误", f"选择的乡镇界线文件不是一个要素类:\n{xzjx_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"无法描述乡镇界线文件,请检查是否为有效要素类:\n{xzjx_path}\n错误: {e}")
|
||||
return False
|
||||
|
||||
# 验证是否选择文件
|
||||
selected_files = [file.text() for file in self.file_list_group.file_list.selectedItems()]
|
||||
if not selected_files:
|
||||
QMessageBox.warning(self, "输入错误", "请在栅格列表中勾选要处理的文件。")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def on_start_processing(self):
|
||||
"""开始处理栅格数据"""
|
||||
if not self.validate_inputs():
|
||||
return
|
||||
|
||||
try:
|
||||
if self.main_window and hasattr(self.main_window, 'save_settings'):
|
||||
self.main_window.update_settings()
|
||||
self.main_window.save_settings()
|
||||
settings_file = self.main_window.settings_file
|
||||
|
||||
|
||||
# 获取公共参数 (从 VectorParamsWidget 获取)
|
||||
selected_files = [file.text() for file in self.file_list_group.file_list.selectedItems()]
|
||||
|
||||
if self.mutiprocess_check.isChecked():
|
||||
self.script_runner.set_max_concurrent(self.process_count.value())
|
||||
else:
|
||||
self.script_runner.set_max_concurrent(1)
|
||||
|
||||
# 计算DLTB字段
|
||||
try:
|
||||
dltb_polygon = self.dltb_features_edit.text()
|
||||
if arcpy.Exists(dltb_polygon) and not arcpy.ListFields(dltb_polygon,"YJDLBM"):
|
||||
arcpy.management.CalculateField(dltb_polygon, "YJDLBM", "!DLBM![:2]", "PYTHON3")
|
||||
if arcpy.Exists(dltb_polygon) and not arcpy.ListFields(dltb_polygon,"YNDLBM"):
|
||||
arcpy.management.CalculateField(dltb_polygon, "YNDLBM", "yunnan_dlbm(!DLBM!)", "PYTHON3", yunnan_dlbm)
|
||||
# else:
|
||||
# QMessageBox.critical(self, "处理错误", "地类图斑文件不存在")
|
||||
# return
|
||||
except Exception as e:
|
||||
print(f"计算SHFJ字段时发生错误: {e}")
|
||||
return
|
||||
|
||||
# 调用批量处理脚本
|
||||
for raster_file in selected_files:
|
||||
params = {
|
||||
"settings_path": settings_file,
|
||||
"reclassed_polygon": raster_file,
|
||||
}
|
||||
|
||||
# 统计面积
|
||||
task_id = self.script_runner.run_area_stat(params)
|
||||
# if task_id:
|
||||
# self.log_message(f"[GUI] 任务 {task_id} 处理 {raster_file} 已添加到列队")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"处理过程中出错: {str(e)}"
|
||||
QMessageBox.critical(self, "处理错误", error_msg)
|
||||
self.log_message(error_msg)
|
||||
|
||||
def on_export_excel_to_jpg(self):
|
||||
|
||||
self.log_message("[GUI] 开始处理")
|
||||
if not self.validate_inputs():
|
||||
return
|
||||
|
||||
try:
|
||||
if self.main_window and hasattr(self.main_window, 'save_settings'):
|
||||
self.main_window.update_settings()
|
||||
self.main_window.save_settings()
|
||||
settings_file = self.main_window.settings_file
|
||||
|
||||
params = {
|
||||
"settings_path":settings_file
|
||||
}
|
||||
|
||||
task_id = self.script_runner.run_excel_to_jpg(params)
|
||||
|
||||
if task_id:
|
||||
self.log_message(f"[GUI] 批量处理任务 {task_id} 已添加到列队")
|
||||
except Exception as e:
|
||||
error_msg = f"处理过程中出错: {str(e)}"
|
||||
QMessageBox.critical(self, "处理错误", error_msg)
|
||||
self.log_message(error_msg)
|
||||
|
||||
def get_area_stat_settings(self):
|
||||
"""保存当前配置到JSON文件"""
|
||||
config = {
|
||||
"input_folder": self.input_folder_edit.text(),
|
||||
"batch_output_folder": self.batch_output_folder_edit.text(),
|
||||
"config_file_path": self.config_file_edit.text(),
|
||||
"xzq_features": self.xzq_features_edit.text(),
|
||||
"dltb_features": self.dltb_features_edit.text(),
|
||||
"xzqmc": self.xzqmc_edit.text(),
|
||||
"is_by_xzq": self.is_adjust_xzq_and_landuse_checkbox.isChecked(),
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
def load_settings(self, settings):
|
||||
"""从字典加载配置"""
|
||||
try:
|
||||
# 批处理参数
|
||||
self.input_folder_edit.setText(settings.get("input_folder", ""))
|
||||
self.batch_output_folder_edit.setText(settings.get("batch_output_folder", ""))
|
||||
|
||||
# 配置文件路径和加载
|
||||
config_file_path = settings.get("config_file_path", "")
|
||||
self.config_file_edit.setText(config_file_path)
|
||||
# self.edit_config_btn.setEnabled(bool(config_file_path) and os.path.exists(config_file_path))
|
||||
|
||||
if config_file_path:
|
||||
self.validate_config_file(config_file_path)
|
||||
|
||||
self.xzq_features_edit.setText(settings.get("xzq_features", ""))
|
||||
self.dltb_features_edit.setText(settings.get("dltb_features", ""))
|
||||
|
||||
if self.input_folder_edit.text():
|
||||
self.on_load_raster()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "加载配置错误", f"加载配置时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def set_buttons_enabled(self, enabled):
|
||||
"""设置按钮是否可用"""
|
||||
self.process_btn.setEnabled(enabled)
|
||||
self.excel_to_jpg_btn.setEnabled(enabled)
|
||||
|
||||
def update_config_file(self, config_file_path):
|
||||
"""更新配置文件路径"""
|
||||
self.config_file_edit.setText(config_file_path)
|
||||
|
||||
def on_script_started(self, task_id, task_description):
|
||||
"""脚本开始执行"""
|
||||
self.set_buttons_enabled(False)
|
||||
self.log_message(f"{task_id}: 正在运行 - {task_description}")
|
||||
|
||||
def on_script_finished(self, task_id:str, success:bool, message:str):
|
||||
"""脚本执行完成"""
|
||||
self.set_buttons_enabled(True)
|
||||
status = "完成" if success else "失败"
|
||||
self.log_message(f"[{task_id}] 脚本执行{status}: {message}")
|
||||
|
||||
def on_script_error(self,task_id, error_msg):
|
||||
"""脚本执行出错"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message(f"错误:{task_id}-{error_msg}")
|
||||
# QMessageBox.critical(self, "错误", error_msg)
|
||||
|
||||
def on_script_log(self, task_id, message):
|
||||
"""脚本输出日志"""
|
||||
self.log_message(f"{task_id}: {message}")
|
||||
|
||||
def log_message(self, message):
|
||||
"""日志输出"""
|
||||
if self.main_window and hasattr(self.main_window, 'log_signal'):
|
||||
self.main_window.log_signal.emit(message)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
import sys
|
||||
app = QApplication(sys.argv)
|
||||
window = XlsxToJpgTab()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
597
tools/ui/tabs/config_editor_dialog.py
Normal file
597
tools/ui/tabs/config_editor_dialog.py
Normal file
@@ -0,0 +1,597 @@
|
||||
# --- START OF FILE config_editor_dialog_visual.py ---
|
||||
import json
|
||||
import os
|
||||
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QTextEdit,
|
||||
QPushButton, QFileDialog, QMessageBox, QLabel,
|
||||
QListWidget, QListWidgetItem, QStackedWidget,
|
||||
QWidget, QTableWidget, QTableWidgetItem,
|
||||
QHeaderView, QSizePolicy, QSplitter, QGroupBox, QGridLayout,
|
||||
QLineEdit, QInputDialog) # Added QInputDialog
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
|
||||
class ConfigEditorDialogVisual(QDialog):
|
||||
"""
|
||||
用于可视化编辑JSON配置文件的模态对话框
|
||||
"""
|
||||
def __init__(self, config_file_path, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("编辑配置文件 (可视化)")
|
||||
self.resize(800, 600)
|
||||
|
||||
self.config_file_path = config_file_path
|
||||
self._config_data = {} # Internal dictionary to hold the parsed JSON
|
||||
self.original_content_hash = None # To track changes
|
||||
|
||||
self.init_ui()
|
||||
self.load_config_data()
|
||||
|
||||
# Connect signals after UI is initialized and data is loaded
|
||||
self.parameter_list_widget.currentItemChanged.connect(self._load_parameter_details)
|
||||
self._load_parameter_details(self.parameter_list_widget.currentItem(), None) # Load initial item details if any
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化界面"""
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# Splitter for parameter list on the left and details on the right
|
||||
splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||
main_layout.addWidget(splitter)
|
||||
|
||||
# Left Panel: Parameter List
|
||||
left_widget = QWidget()
|
||||
left_layout = QVBoxLayout(left_widget)
|
||||
left_layout.addWidget(QLabel("参数列表:"))
|
||||
self.parameter_list_widget = QListWidget()
|
||||
left_layout.addWidget(self.parameter_list_widget)
|
||||
|
||||
# Buttons for adding/removing parameters
|
||||
param_btn_layout = QHBoxLayout()
|
||||
self.add_param_btn = QPushButton("添加参数")
|
||||
self.remove_param_btn = QPushButton("移除参数")
|
||||
param_btn_layout.addWidget(self.add_param_btn)
|
||||
param_btn_layout.addWidget(self.remove_param_btn)
|
||||
left_layout.addLayout(param_btn_layout)
|
||||
|
||||
self.add_param_btn.clicked.connect(self._add_parameter)
|
||||
self.remove_param_btn.clicked.connect(self._remove_parameter)
|
||||
|
||||
|
||||
splitter.addWidget(left_widget)
|
||||
|
||||
# Right Panel: Parameter Details (StackedWidget to potentially show empty state)
|
||||
self.details_stack = QStackedWidget()
|
||||
self.empty_details_widget = QLabel("请从左侧选择一个参数或添加新参数")
|
||||
self.empty_details_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.details_stack.addWidget(self.empty_details_widget) # Index 0
|
||||
|
||||
self.parameter_details_widget = QWidget()
|
||||
self._setup_parameter_details_ui(self.parameter_details_widget)
|
||||
self.details_stack.addWidget(self.parameter_details_widget)
|
||||
|
||||
|
||||
splitter.addWidget(self.details_stack)
|
||||
|
||||
# Set initial sizes for splitter panes
|
||||
splitter.setSizes([200, 600])
|
||||
|
||||
# Bottom Buttons
|
||||
button_layout = QHBoxLayout()
|
||||
self.save_btn = QPushButton("保存")
|
||||
self.save_as_btn = QPushButton("另存为...")
|
||||
self.cancel_btn = QPushButton("取消")
|
||||
|
||||
self.save_btn.clicked.connect(self.save_config)
|
||||
self.save_as_btn.clicked.connect(self.save_config_as)
|
||||
self.cancel_btn.clicked.connect(self.reject)
|
||||
|
||||
button_layout.addWidget(self.save_btn)
|
||||
button_layout.addWidget(self.save_as_btn)
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(self.cancel_btn)
|
||||
|
||||
main_layout.addLayout(button_layout)
|
||||
|
||||
# Initial state: Disable save buttons until something is loaded/changed
|
||||
self.save_btn.setEnabled(False)
|
||||
self.save_as_btn.setEnabled(False)
|
||||
self.remove_param_btn.setEnabled(False)
|
||||
|
||||
|
||||
def _setup_parameter_details_ui(self, parent_widget):
|
||||
"""Setup the UI for the parameter details panel"""
|
||||
layout = QVBoxLayout(parent_widget)
|
||||
|
||||
# Fixed fields
|
||||
fixed_fields_group = QGroupBox("基本信息")
|
||||
fixed_fields_layout = QGridLayout(fixed_fields_group)
|
||||
|
||||
self.name_edit = QTextEdit()
|
||||
self.name_edit.setFixedHeight(40) # Allow multi-line but not too tall
|
||||
self.method_edit = QTextEdit()
|
||||
self.method_edit.setFixedHeight(40)
|
||||
self.param_level_edit = QTextEdit()
|
||||
self.param_level_edit.setFixedHeight(40)
|
||||
self.standard_edit = QTextEdit()
|
||||
self.standard_edit.setFixedHeight(40)
|
||||
|
||||
fixed_fields_layout.addWidget(QLabel("项目名称:"), 0, 0)
|
||||
fixed_fields_layout.addWidget(self.name_edit, 0, 1)
|
||||
fixed_fields_layout.addWidget(QLabel("分析方法:"), 1, 0)
|
||||
fixed_fields_layout.addWidget(self.method_edit, 1, 1)
|
||||
fixed_fields_layout.addWidget(QLabel("项目分级:"), 2, 0)
|
||||
fixed_fields_layout.addWidget(self.param_level_edit, 2, 1)
|
||||
fixed_fields_layout.addWidget(QLabel("分级标准名称:"), 3, 0) # Renamed for clarity
|
||||
fixed_fields_layout.addWidget(self.standard_edit, 3, 1)
|
||||
|
||||
layout.addWidget(fixed_fields_group)
|
||||
|
||||
# Standard Levels Table
|
||||
levels_group = QGroupBox("标准等级")
|
||||
levels_layout = QVBoxLayout(levels_group)
|
||||
|
||||
self.levels_table = QTableWidget(0, 2)
|
||||
self.levels_table.setHorizontalHeaderLabels(["等级名称 (Key)", "标准值 (Value)"])
|
||||
self.levels_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
self.levels_table.verticalHeader().setVisible(False)
|
||||
levels_layout.addWidget(self.levels_table)
|
||||
|
||||
# Buttons for adding/removing levels
|
||||
level_btn_layout = QHBoxLayout()
|
||||
self.add_level_btn = QPushButton("添加等级")
|
||||
self.remove_level_btn = QPushButton("移除等级")
|
||||
level_btn_layout.addWidget(self.add_level_btn)
|
||||
level_btn_layout.addWidget(self.remove_level_btn)
|
||||
levels_layout.addLayout(level_btn_layout)
|
||||
|
||||
self.add_level_btn.clicked.connect(self._add_standard_level)
|
||||
self.remove_level_btn.clicked.connect(self._remove_standard_level)
|
||||
|
||||
|
||||
layout.addWidget(levels_group)
|
||||
|
||||
layout.addStretch() # Push everything to the top
|
||||
|
||||
# Connect signals for tracking changes (set dirty flag)
|
||||
self.name_edit.textChanged.connect(self._set_dirty)
|
||||
self.method_edit.textChanged.connect(self._set_dirty)
|
||||
self.param_level_edit.textChanged.connect(self._set_dirty)
|
||||
self.standard_edit.textChanged.connect(self._set_dirty)
|
||||
self.levels_table.cellChanged.connect(self._set_dirty)
|
||||
# Adding/removing items/levels also makes it dirty
|
||||
self.add_param_btn.clicked.connect(self._set_dirty)
|
||||
self.remove_param_btn.clicked.connect(self._set_dirty)
|
||||
self.add_level_btn.clicked.connect(self._set_dirty)
|
||||
self.remove_level_btn.clicked.connect(self._set_dirty)
|
||||
|
||||
|
||||
def load_config_data(self):
|
||||
"""加载文件内容到内部字典并填充UI"""
|
||||
self._config_data = {} # Clear previous data
|
||||
self.parameter_list_widget.clear()
|
||||
|
||||
if not self.config_file_path or not os.path.exists(self.config_file_path):
|
||||
QMessageBox.warning(self, "加载错误", f"配置文件不存在或路径无效:\n{self.config_file_path}")
|
||||
self.details_stack.setCurrentIndex(0) # Show empty message
|
||||
return
|
||||
|
||||
try:
|
||||
with open(self.config_file_path, 'r', encoding='utf-8') as f:
|
||||
self._config_data = json.load(f)
|
||||
# Store a hash or string representation for change detection
|
||||
# Use json.dumps to handle ordering consistency
|
||||
self.original_content_hash = hash(json.dumps(self._config_data, sort_keys=True, ensure_ascii=False))
|
||||
|
||||
# Populate the list widget from the loaded data
|
||||
if "export_config" in self._config_data and isinstance(self._config_data["export_config"], dict):
|
||||
for key in sorted(self._config_data["export_config"].keys()):
|
||||
self.parameter_list_widget.addItem(key)
|
||||
|
||||
if self.parameter_list_widget.count() > 0:
|
||||
self.parameter_list_widget.setCurrentRow(0)
|
||||
self.details_stack.setCurrentIndex(1)
|
||||
else:
|
||||
self.details_stack.setCurrentIndex(0)
|
||||
|
||||
else:
|
||||
QMessageBox.warning(self, "加载错误", "配置文件缺少 'export_config' 键或格式不正确。")
|
||||
self.details_stack.setCurrentIndex(0)
|
||||
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
QMessageBox.critical(self, "加载错误", f"配置文件JSON格式无效:\n{e}")
|
||||
self._config_data = {}
|
||||
self.details_stack.setCurrentIndex(0)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "加载错误", f"无法读取或解析配置文件:\n{str(e)}")
|
||||
self._config_data = {}
|
||||
self.details_stack.setCurrentIndex(0)
|
||||
|
||||
|
||||
def _load_parameter_details(self, current_item, previous_item):
|
||||
"""Load details of the newly selected parameter"""
|
||||
# Before loading new details, save changes from the previous item
|
||||
if previous_item:
|
||||
self._save_parameter_details(previous_item.text())
|
||||
|
||||
# Clear the details UI
|
||||
self.name_edit.clear()
|
||||
self.method_edit.clear()
|
||||
self.param_level_edit.clear()
|
||||
self.standard_edit.clear()
|
||||
self.levels_table.setRowCount(0) # Clear table
|
||||
|
||||
if current_item:
|
||||
key = current_item.text()
|
||||
param_data = self._config_data.get("export_config", {}).get(key, {})
|
||||
|
||||
self.name_edit.setPlainText(param_data.get("项目名称", ""))
|
||||
self.method_edit.setPlainText(param_data.get("分析方法", ""))
|
||||
self.param_level_edit.setPlainText(param_data.get("项目分级", ""))
|
||||
self.standard_edit.setPlainText(param_data.get("分级标准", ""))
|
||||
|
||||
# Load standard levels into the table
|
||||
levels = param_data.get("标准等级", {})
|
||||
if isinstance(levels, dict):
|
||||
self.levels_table.setRowCount(len(levels))
|
||||
# Sort levels by key (等级一, 等级二, etc.) for consistency
|
||||
# for i, (level_key, level_value) in enumerate(sort(levels.items())):
|
||||
for i, (level_key, level_value) in enumerate(levels.items()):
|
||||
self.levels_table.setItem(i, 0, QTableWidgetItem(str(level_key)))
|
||||
self.levels_table.setItem(i, 1, QTableWidgetItem(str(level_value).replace("\n", "\\n")))
|
||||
else:
|
||||
print(f"警告: 参数 '{key}' 的 '标准等级' 不是一个有效的字典。")
|
||||
|
||||
|
||||
self.details_stack.setCurrentIndex(1)
|
||||
self.remove_param_btn.setEnabled(True)
|
||||
self._set_dirty(False)
|
||||
|
||||
else:
|
||||
self.details_stack.setCurrentIndex(0)
|
||||
self.remove_param_btn.setEnabled(False)
|
||||
self._set_dirty(False)
|
||||
|
||||
|
||||
def _save_parameter_details(self, key):
|
||||
"""Save details from the UI widgets back to the internal dictionary for the given key"""
|
||||
if not key or "export_config" not in self._config_data or key not in self._config_data["export_config"]:
|
||||
return # Nothing to save if key is invalid or not in data
|
||||
|
||||
param_data = self._config_data["export_config"][key]
|
||||
|
||||
param_data["项目名称"] = self.name_edit.toPlainText()
|
||||
param_data["分析方法"] = self.method_edit.toPlainText()
|
||||
param_data["项目分级"] = self.param_level_edit.toPlainText()
|
||||
param_data["分级标准"] = self.standard_edit.toPlainText()
|
||||
|
||||
# Save standard levels from the table
|
||||
levels = {}
|
||||
for row in range(self.levels_table.rowCount()):
|
||||
key_item = self.levels_table.item(row, 0)
|
||||
value_item = self.levels_table.item(row, 1)
|
||||
if key_item and value_item:
|
||||
level_key = key_item.text().strip()
|
||||
level_value = value_item.text().strip().replace("\\n", "\n")
|
||||
if level_key: # Only save if level key is not empty
|
||||
levels[level_key] = level_value
|
||||
else:
|
||||
print(f"警告: 参数 '{key}' 中存在空的等级名称,该行将被忽略。")
|
||||
|
||||
param_data["标准等级"] = levels
|
||||
# print(f"Saved details for parameter: {key}") # Debugging
|
||||
|
||||
|
||||
def _add_parameter(self):
|
||||
"""Add a new parameter item"""
|
||||
key, ok = QInputDialog.getText(self, "添加新参数", "输入新的参数键 (例如: NEW):")
|
||||
if ok and key:
|
||||
key = key.strip()
|
||||
if not key:
|
||||
QMessageBox.warning(self, "输入错误", "参数键不能为空。")
|
||||
return
|
||||
if "export_config" not in self._config_data:
|
||||
self._config_data["export_config"] = {}
|
||||
|
||||
if key in self._config_data["export_config"]:
|
||||
QMessageBox.warning(self, "输入错误", f"参数键 '{key}' 已存在。")
|
||||
return
|
||||
|
||||
# Add a default structure for the new parameter
|
||||
self._config_data["export_config"][key] = {
|
||||
"项目名称": "新项目名称",
|
||||
"分析方法": "新分析方法",
|
||||
"项目分级": "新项目分级",
|
||||
"分级标准": "新分级标准",
|
||||
"标准等级": {}
|
||||
}
|
||||
|
||||
# Add to list widget and select it
|
||||
self.parameter_list_widget.addItem(key)
|
||||
# Re-sort the list
|
||||
self.parameter_list_widget.sortItems()
|
||||
# Find and select the new item
|
||||
items = self.parameter_list_widget.findItems(key, Qt.MatchFlag.MatchExactly)
|
||||
if items:
|
||||
self.parameter_list_widget.setCurrentItem(items[0])
|
||||
|
||||
self._set_dirty()
|
||||
|
||||
|
||||
def _remove_parameter(self):
|
||||
"""Remove the currently selected parameter item"""
|
||||
current_item = self.parameter_list_widget.currentItem()
|
||||
if not current_item:
|
||||
return # Nothing selected
|
||||
|
||||
key_to_remove = current_item.text()
|
||||
reply = QMessageBox.question(
|
||||
self, "移除参数", f"确定要移除参数 '{key_to_remove}' 吗?\n此操作不可撤销。",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||
)
|
||||
|
||||
if reply == QMessageBox.StandardButton.Yes:
|
||||
# Save details of the item being removed *before* removing it from data
|
||||
# (This handles case where user edits an item then immediately removes it)
|
||||
self._save_parameter_details(key_to_remove)
|
||||
|
||||
# Remove from internal data
|
||||
if "export_config" in self._config_data and key_to_remove in self._config_data["export_config"]:
|
||||
del self._config_data["export_config"][key_to_remove]
|
||||
|
||||
# Remove from list widget
|
||||
row = self.parameter_list_widget.row(current_item)
|
||||
self.parameter_list_widget.takeItem(row)
|
||||
|
||||
# Clear details panel and maybe select next item or show empty
|
||||
self._load_parameter_details(self.parameter_list_widget.currentItem(), current_item)
|
||||
self._set_dirty()
|
||||
|
||||
|
||||
def _add_standard_level(self):
|
||||
"""Add a new row to the standard levels table"""
|
||||
current_item = self.parameter_list_widget.currentItem()
|
||||
if not current_item:
|
||||
QMessageBox.warning(self, "添加等级", "请先选择一个参数。")
|
||||
return
|
||||
|
||||
row_count = self.levels_table.rowCount()
|
||||
self.levels_table.insertRow(row_count)
|
||||
# Add default items (editable)
|
||||
self.levels_table.setItem(row_count, 0, QTableWidgetItem("新等级"))
|
||||
self.levels_table.setItem(row_count, 1, QTableWidgetItem(""))
|
||||
|
||||
self._set_dirty()
|
||||
|
||||
|
||||
def _remove_standard_level(self):
|
||||
"""Remove selected rows from the standard levels table"""
|
||||
# selected_rows = sorted(list(set(index.row() for index in self.levels_table.selectedIndexes())), reverse=True)
|
||||
selected_rows = list(set(index.row() for index in self.levels_table.selectedIndexes()))
|
||||
if not selected_rows:
|
||||
QMessageBox.warning(self, "移除等级", "请选择要移除的等级行。")
|
||||
return
|
||||
|
||||
for row in selected_rows:
|
||||
self.levels_table.removeRow(row)
|
||||
|
||||
self._set_dirty()
|
||||
|
||||
|
||||
def _set_dirty(self, dirty=True):
|
||||
"""Set or clear the dirty flag and update button states"""
|
||||
# This slot might receive a bool from cellChanged, or be called with True/False
|
||||
# Ensure it's treated as a boolean
|
||||
self._is_dirty = bool(dirty)
|
||||
|
||||
# Enable save buttons if dirty
|
||||
self.save_btn.setEnabled(self._is_dirty)
|
||||
self.save_as_btn.setEnabled(True)
|
||||
|
||||
# print(f"Dirty state: {self._is_dirty}") # Debugging
|
||||
|
||||
|
||||
def is_dirty(self):
|
||||
"""Check if the configuration has been modified"""
|
||||
if not self._is_dirty:
|
||||
return False
|
||||
|
||||
# Perform a more thorough check by saving current state to a temp dict
|
||||
# and comparing its hash with the original hash
|
||||
try:
|
||||
# Save details of the currently selected item first
|
||||
current_key = self.parameter_list_widget.currentItem()
|
||||
if current_key:
|
||||
self._save_parameter_details(current_key.text())
|
||||
|
||||
current_hash = hash(json.dumps(self._config_data, sort_keys=True, ensure_ascii=False))
|
||||
return current_hash != self.original_content_hash
|
||||
except Exception as e:
|
||||
print(f"Error checking dirty state: {e}")
|
||||
# If hashing fails, assume it's dirty to be safe
|
||||
return True
|
||||
|
||||
|
||||
def validate_config(self):
|
||||
"""Validate the structure of the internal config data before saving"""
|
||||
if "export_config" not in self._config_data or not isinstance(self._config_data["export_config"], dict):
|
||||
QMessageBox.warning(self, "验证失败", "配置缺少主键 'export_config' 或其格式不正确。")
|
||||
return False
|
||||
|
||||
standards_dict = self._config_data["export_config"]
|
||||
error_keys = []
|
||||
for key, value in standards_dict.items():
|
||||
# Check for required fields and types
|
||||
if not isinstance(value, dict):
|
||||
error_keys.append(f"{key}: 结构不是字典")
|
||||
continue
|
||||
required_keys = ["项目名称", "分析方法", "项目分级", "分级标准", "标准等级"]
|
||||
if not all(k in value for k in required_keys):
|
||||
error_keys.append(f"{key}: 缺少必要字段")
|
||||
continue
|
||||
if not isinstance(value.get("标准等级"), dict):
|
||||
error_keys.append(f"{key}: '标准等级' 不是字典")
|
||||
continue
|
||||
|
||||
if error_keys:
|
||||
QMessageBox.warning(self, "验证失败", "配置文件结构或内容存在问题:\n" + "\n".join(error_keys))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def save_config(self):
|
||||
"""保存配置到当前文件路径"""
|
||||
# Save details of the currently selected item before saving the whole config
|
||||
current_item = self.parameter_list_widget.currentItem()
|
||||
if current_item:
|
||||
self._save_parameter_details(current_item.text())
|
||||
|
||||
if not self.validate_config():
|
||||
return False
|
||||
|
||||
if not self.config_file_path:
|
||||
# If no path is set (e.g., started with no file and added items), force Save As
|
||||
return self.save_config_as()
|
||||
|
||||
return self._perform_save(self.config_file_path)
|
||||
|
||||
|
||||
def save_config_as(self):
|
||||
"""另存配置"""
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
self, "另存配置文件", self.config_file_path if self.config_file_path else "",
|
||||
"JSON 文件 (*.json);;所有文件 (*)"
|
||||
)
|
||||
if file_path:
|
||||
# Save details of the currently selected item before saving the whole config
|
||||
current_item = self.parameter_list_widget.currentItem()
|
||||
if current_item:
|
||||
self._save_parameter_details(current_item.text())
|
||||
|
||||
if not self.validate_config():
|
||||
return False
|
||||
|
||||
if self._perform_save(file_path):
|
||||
# Update the current file path if Save As was successful
|
||||
self.config_file_path = file_path
|
||||
# Reload from the new path to reset original_content_hash and dirty state
|
||||
self.load_config_data()
|
||||
QMessageBox.information(self, "另存成功", f"配置文件已另存为:\n{self.config_file_path}")
|
||||
self.accept()
|
||||
return True
|
||||
return False
|
||||
return False
|
||||
def _perform_save(self, path):
|
||||
"""Execute the saving of the config data to a file"""
|
||||
try:
|
||||
# Ensure directory exists
|
||||
dir_name = os.path.dirname(path)
|
||||
if dir_name and not os.path.exists(dir_name):
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
# Use indent=4 for readability and ensure_ascii=False for Chinese chars
|
||||
json.dump(self._config_data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
# Update the original content hash after saving
|
||||
self.original_content_hash = hash(json.dumps(self._config_data, sort_keys=True, ensure_ascii=False))
|
||||
self._set_dirty(False) # Clear dirty flag
|
||||
|
||||
# For standard save, don't close the dialog, just inform
|
||||
# QMessageBox.information(self, "保存成功", f"配置文件已保存:\n{path}") # Removed msgbox for standard save
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "保存失败", f"保存文件时出错:\n{str(e)}")
|
||||
return False
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Handle close event, ask to save if dirty"""
|
||||
if self.is_dirty():
|
||||
reply = QMessageBox.question(
|
||||
self, "保存更改",
|
||||
"配置文件已修改,是否保存?",
|
||||
QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.Cancel,
|
||||
QMessageBox.StandardButton.Save
|
||||
)
|
||||
|
||||
if reply == QMessageBox.StandardButton.Save:
|
||||
if self.save_config():
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
elif reply == QMessageBox.StandardButton.Discard:
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
else:
|
||||
event.accept()
|
||||
# Method to retrieve the potentially new file path after saving (e.g., Save As)
|
||||
def get_new_config_path(self):
|
||||
return self.config_file_path
|
||||
|
||||
|
||||
# Example running (for testing this dialog itself)
|
||||
if __name__ == '__main__':
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
import sys
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# Create a dummy config file for testing if it doesn't exist
|
||||
test_dir = "temp_config_editor_test"
|
||||
os.makedirs(test_dir, exist_ok=True)
|
||||
test_config_path = os.path.join(test_dir, "test_config.json")
|
||||
|
||||
dummy_data = {
|
||||
"export_config": {
|
||||
"AB": {
|
||||
"项目名称": "测试硼",
|
||||
"分析方法": "测试方法",
|
||||
"项目分级": "硼级别",
|
||||
"分级标准": "硼标准",
|
||||
"标准等级": {
|
||||
"等级1": ">2.00",
|
||||
"等级2": "1.00~2.00"
|
||||
}
|
||||
},
|
||||
"ZN": {
|
||||
"项目名称": "测试锌",
|
||||
"分析方法": "锌方法",
|
||||
"项目分级": "锌级别",
|
||||
"分级标准": "锌标准",
|
||||
"标准等级": {
|
||||
"A级": ">3.00",
|
||||
"B级": "2.00~3.00",
|
||||
"C级": "<2.00"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if not os.path.exists(test_config_path):
|
||||
try:
|
||||
with open(test_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(dummy_data, f, indent=4, ensure_ascii=False)
|
||||
print(f"Created dummy config file: {test_config_path}")
|
||||
except Exception as e:
|
||||
print(f"Error creating dummy file: {e}")
|
||||
test_config_path = "" # Clear path if creation fails
|
||||
|
||||
|
||||
dialog = ConfigEditorDialogVisual(test_config_path)
|
||||
result = dialog.exec()
|
||||
|
||||
if result == QDialog.DialogCode.Accepted:
|
||||
final_path = dialog.get_new_config_path()
|
||||
print(f"Dialog accepted. Final config path: {final_path}")
|
||||
else:
|
||||
print("Dialog rejected or cancelled.")
|
||||
|
||||
# Optional: Clean up test files/directory
|
||||
# import shutil
|
||||
# if os.path.exists(test_dir):
|
||||
# shutil.rmtree(test_dir)
|
||||
|
||||
sys.exit(app.exec())
|
||||
291
tools/ui/tabs/export_layout_tab.py
Normal file
291
tools/ui/tabs/export_layout_tab.py
Normal file
@@ -0,0 +1,291 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QLineEdit, QFileDialog, QMessageBox, QListWidget,
|
||||
QComboBox, QSpinBox, QGroupBox, QFormLayout, QCheckBox)
|
||||
from ..runners.script_runner import ScriptRunner
|
||||
from ..components.file_list_group import FileListGroup
|
||||
|
||||
|
||||
class ExportImageTab(QWidget):
|
||||
"""导出成果图标签页"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.main_window = parent
|
||||
self.runner = ScriptRunner(self)
|
||||
self.connect_signals()
|
||||
self.init_ui()
|
||||
self.load_settings()
|
||||
self.on_load_files()
|
||||
|
||||
def connect_signals(self):
|
||||
"""连接脚本运行器信号"""
|
||||
# 连接ScriptRunner的信号
|
||||
self.runner.task_started.connect(self.on_script_started)
|
||||
self.runner.task_finished.connect(self.on_script_finished)
|
||||
self.runner.task_error.connect(self.on_script_error)
|
||||
self.runner.task_log.connect(self.on_script_log)
|
||||
|
||||
def init_ui(self):
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# 输入输出设置组
|
||||
io_group = QGroupBox("输入输出设置")
|
||||
io_layout = QFormLayout()
|
||||
|
||||
# 地图文档文件夹选择
|
||||
data_layout = QHBoxLayout()
|
||||
self.input_aprx_path_edit = QLineEdit()
|
||||
data_layout.addWidget(self.input_aprx_path_edit)
|
||||
browse_data_btn = QPushButton("浏览...")
|
||||
browse_data_btn.clicked.connect(self.browse_data_source)
|
||||
data_layout.addWidget(browse_data_btn)
|
||||
io_layout.addRow("工程文件夹:", data_layout)
|
||||
|
||||
# 输出路径选择
|
||||
output_layout = QHBoxLayout()
|
||||
self.output_image_path_edit = QLineEdit()
|
||||
output_layout.addWidget(self.output_image_path_edit)
|
||||
browse_output_btn = QPushButton("浏览...")
|
||||
browse_output_btn.clicked.connect(self.browse_output)
|
||||
output_layout.addWidget(browse_output_btn)
|
||||
io_layout.addRow("输出文件夹:", output_layout)
|
||||
|
||||
io_group.setLayout(io_layout)
|
||||
main_layout.addWidget(io_group)
|
||||
|
||||
# 添加文件列表布局
|
||||
self.file_list_group = FileListGroup(self, "选择要导出的文件")
|
||||
self.file_list_group.load_files.connect(self.on_load_files)
|
||||
main_layout.addWidget(self.file_list_group)
|
||||
|
||||
# 导出设置组
|
||||
export_group = QGroupBox("导出设置")
|
||||
export_layout = QFormLayout()
|
||||
|
||||
# 格式选择
|
||||
self.format_combo = QComboBox()
|
||||
self.format_combo.addItems(["PDF", "PNG", "JPG", "TIFF", "EPS", "SVG", "AI"])
|
||||
export_layout.addRow("导出格式:", self.format_combo)
|
||||
|
||||
# 分辨率设置
|
||||
self.resolution_spinbox = QSpinBox()
|
||||
self.resolution_spinbox.setRange(72, 1200)
|
||||
self.resolution_spinbox.setSingleStep(12)
|
||||
self.resolution_spinbox.setValue(300)
|
||||
self.resolution_spinbox.setSuffix(" DPI")
|
||||
export_layout.addRow("分辨率:", self.resolution_spinbox)
|
||||
|
||||
# 强制重新生成选项
|
||||
self.image_force_regenerate_check = QCheckBox("强制重新生成工程文件")
|
||||
self.image_force_regenerate_check.setChecked(False)
|
||||
self.image_force_regenerate_check.setToolTip("勾选后将忽略已存在的工程文件,重新生成")
|
||||
export_layout.addRow("处理选项:", self.image_force_regenerate_check)
|
||||
|
||||
# 多进程设置
|
||||
self.use_multiprocessing = QCheckBox("使用多进程导出")
|
||||
self.use_multiprocessing.setChecked(True)
|
||||
export_layout.addRow("批量模式:", self.use_multiprocessing)
|
||||
|
||||
# 进程数量设置
|
||||
self.process_count = QSpinBox()
|
||||
self.process_count.setRange(1, multiprocessing.cpu_count())
|
||||
self.process_count.setValue(max(1, multiprocessing.cpu_count() - 1)) # 默认使用CPU核心数-1
|
||||
export_layout.addRow("进程数量:", self.process_count)
|
||||
|
||||
export_group.setLayout(export_layout)
|
||||
main_layout.addWidget(export_group)
|
||||
|
||||
# 操作按钮
|
||||
self.export_existing_btn = QPushButton("导出成果图")
|
||||
self.export_existing_btn.setToolTip("仅导出输出文件夹中已存在的工程文件")
|
||||
self.export_existing_btn.clicked.connect(self.on_export_existing)
|
||||
|
||||
main_layout.addWidget(self.export_existing_btn)
|
||||
# main_layout.addStretch()
|
||||
def browse_data_source(self):
|
||||
"""浏览工程目录"""
|
||||
initial_dir = ""
|
||||
if os.path.exists(self.input_aprx_path_edit.text()):
|
||||
initial_dir = os.path.dirname(self.input_aprx_path_edit.text())
|
||||
|
||||
dir_path = QFileDialog.getExistingDirectory(self, "选择工程目录", initial_dir)
|
||||
if dir_path:
|
||||
self.input_aprx_path_edit.setText(dir_path)
|
||||
self.on_load_files()
|
||||
|
||||
def browse_output(self):
|
||||
"""选择输出路径"""
|
||||
initial_dir = ""
|
||||
if os.path.exists(self.output_image_path_edit.text()):
|
||||
initial_dir = os.path.dirname(self.output_image_path_edit.text())
|
||||
|
||||
dir_path = QFileDialog.getExistingDirectory(self, "选择输出路径", initial_dir)
|
||||
if dir_path:
|
||||
self.output_image_path_edit.setText(dir_path)
|
||||
|
||||
def on_load_files(self):
|
||||
"""加载图层列表"""
|
||||
try:
|
||||
input_aprx_path = self.input_aprx_path_edit.text()
|
||||
if not os.path.exists(input_aprx_path):
|
||||
QMessageBox.warning(self, "错误", "请先选择输入文件夹")
|
||||
return
|
||||
|
||||
# 清空列表
|
||||
self.file_list_group.file_list.clear()
|
||||
|
||||
# 添加文件
|
||||
for file_name in os.listdir(input_aprx_path):
|
||||
file_path = os.path.join(input_aprx_path, file_name)
|
||||
if file_name.endswith(".aprx") and os.path.isfile(file_path):
|
||||
self.file_list_group.file_list.addItem(file_path)
|
||||
|
||||
self.log_message(f"已加载 {self.file_list_group.file_list.count()} 个文件")
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"加载文件失败: {str(e)}")
|
||||
|
||||
def on_script_started(self, task_id, task_description):
|
||||
"""脚本开始执行回调"""
|
||||
self.set_buttons_enabled(False)
|
||||
self.log_message(f"{task_id}: 正在运行 - {task_description}")
|
||||
|
||||
def on_script_finished(self, task_id:str, success:bool, message:str):
|
||||
"""脚本执行完成回调"""
|
||||
self.set_buttons_enabled(True)
|
||||
if success:
|
||||
QMessageBox.information(self, "成功", "成果图导出完成!")
|
||||
else:
|
||||
QMessageBox.warning(self, "失败", "导出过程中出现错,请查看日志详情")
|
||||
|
||||
def on_script_error(self, error_msg):
|
||||
"""脚本执行错误回调"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message(f"错误: {error_msg}")
|
||||
QMessageBox.critical(self, "错误", error_msg)
|
||||
|
||||
def on_script_log(self, task_id, message):
|
||||
"""脚本输出日志"""
|
||||
self.log_message(f"{task_id}: {message}")
|
||||
def log_message(self, message):
|
||||
"""日志输出"""
|
||||
if self.main_window and hasattr(self.main_window, 'log_signal'):
|
||||
self.main_window.log_signal.emit(message)
|
||||
else:
|
||||
print(message)
|
||||
def set_buttons_enabled(self, enabled):
|
||||
"""设置按钮启用状态"""
|
||||
self.export_existing_btn.setEnabled(enabled)
|
||||
|
||||
def validate_and_params(self):
|
||||
"""验证输入参数并返回参数"""
|
||||
# 验证工程文件
|
||||
aprx_files = self.file_list_group.file_list.selectedItems()
|
||||
if not aprx_files or len(aprx_files) == 0:
|
||||
QMessageBox.warning(self, "警告", "请选择要导出的文件")
|
||||
return None
|
||||
|
||||
# 验证工程目录
|
||||
input_aprx_folder = self.input_aprx_path_edit.text()
|
||||
if not os.path.exists(input_aprx_folder):
|
||||
QMessageBox.warning(self, "警告", "请选择有效的工程文件目录")
|
||||
return None
|
||||
|
||||
# 验证输出路径
|
||||
output_image_path = self.output_image_path_edit.text()
|
||||
if not os.path.exists(output_image_path):
|
||||
QMessageBox.warning(self, "警告", "请选择输出路径")
|
||||
return None
|
||||
|
||||
aprx_file_list = [file.text() for file in aprx_files]
|
||||
output_image_path = os.path.join(output_image_path, self.format_combo.currentText().lower())
|
||||
|
||||
return {
|
||||
'input_aprx_folder': input_aprx_folder,
|
||||
'aprx_file_list': aprx_file_list,
|
||||
'output_image_path': output_image_path
|
||||
}
|
||||
|
||||
def get_layout_settings(self):
|
||||
""" 获取布局设置 """
|
||||
config = {
|
||||
'input_aprx_path': self.input_aprx_path_edit.text(),
|
||||
'output_image_path': self.output_image_path_edit.text(),
|
||||
'default_format': self.format_combo.currentText(),
|
||||
'resolution': self.resolution_spinbox.value(),
|
||||
'force_regenerate': self.image_force_regenerate_check.isChecked(),
|
||||
'use_multiprocessing': self.use_multiprocessing.isChecked(),
|
||||
'process_count': self.process_count.value()
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
def load_settings(self):
|
||||
"""加载设置"""
|
||||
if not self.main_window:
|
||||
return
|
||||
|
||||
try:
|
||||
settings = self.main_window.settings
|
||||
export_image_settings = settings.get('export_image_settings', {})
|
||||
|
||||
self.input_aprx_path_edit.setText(export_image_settings.get('input_aprx_path', ''))
|
||||
self.output_image_path_edit.setText(export_image_settings.get('output_image_path', ''))
|
||||
# 设置导出格式
|
||||
format_index = self.format_combo.findText(export_image_settings.get('default_format', 'PDF'))
|
||||
if format_index >= 0:
|
||||
self.format_combo.setCurrentIndex(format_index)
|
||||
|
||||
# 设置分辨率
|
||||
self.resolution_spinbox.setValue(export_image_settings.get('resolution', 300))
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"加载设置失败: {str(e)}")
|
||||
|
||||
def on_export_layout(self):
|
||||
"""仅导出布局按钮点击事件"""
|
||||
# 验证并获取参数
|
||||
layout_params = self.validate_and_params()
|
||||
if not layout_params:
|
||||
return
|
||||
|
||||
# 准备导出布局参数
|
||||
|
||||
layout_params['export_format'] = self.format_combo.currentText()
|
||||
layout_params['resolution'] = self.resolution_spinbox.value()
|
||||
layout_params['image_force_regenerate'] = self.image_force_regenerate_check.isChecked()
|
||||
print(layout_params)
|
||||
|
||||
# 调用导出布局脚本
|
||||
# self.runner.run_export_layout(layout_params)
|
||||
|
||||
def on_export_existing(self):
|
||||
"""导出已有工程按钮点击事件"""
|
||||
# 验证并获取参数
|
||||
layout_params = self.validate_and_params()
|
||||
if not layout_params:
|
||||
return
|
||||
|
||||
# 确认是否导出所有工程文件
|
||||
result = QMessageBox.question(
|
||||
self,
|
||||
"确认",
|
||||
f"将导出 {len(layout_params['aprx_file_list'])} 个工程文件的布局,是否继续?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No
|
||||
)
|
||||
|
||||
if result == QMessageBox.StandardButton.No:
|
||||
return
|
||||
|
||||
# 准备批量导出布局参数
|
||||
layout_params['export_format'] = self.format_combo.currentText()
|
||||
layout_params['resolution'] = self.resolution_spinbox.value()
|
||||
layout_params['image_force_regenerate'] = self.image_force_regenerate_check.isChecked()
|
||||
layout_params['use_multiprocessing'] = self.use_multiprocessing.isChecked()
|
||||
layout_params['process_count'] = self.process_count.value()
|
||||
|
||||
# 调用批量导出布局脚本
|
||||
# print(layout_params)
|
||||
self.runner.run_export_layout(layout_params)
|
||||
385
tools/ui/tabs/export_map_tab.py
Normal file
385
tools/ui/tabs/export_map_tab.py
Normal file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
导出地图选项卡模块
|
||||
"""
|
||||
|
||||
import os
|
||||
import arcpy
|
||||
import traceback
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QLabel,
|
||||
QLineEdit, QPushButton, QCheckBox, QFileDialog, QMessageBox)
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from ..components.file_list_group import FileListGroup
|
||||
from ..runners.script_runner import ScriptRunner
|
||||
|
||||
|
||||
class ExportMapTab(QWidget):
|
||||
"""导出地图选项卡"""
|
||||
|
||||
# 定义日志信号,用于将日志消息传递给主窗口
|
||||
log_signal = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.main_window = parent
|
||||
self.setupUi()
|
||||
self.load_settings()
|
||||
|
||||
def setupUi(self):
|
||||
"""设置界面"""
|
||||
# 主布局
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
|
||||
# 参数区域
|
||||
self.form_layout = QFormLayout()
|
||||
|
||||
# 区县名称
|
||||
self.county_name_edit = QLineEdit()
|
||||
self.form_layout.addRow("区县名称:", self.county_name_edit)
|
||||
|
||||
# 配置文件
|
||||
config_layout = QHBoxLayout()
|
||||
|
||||
self.config_file_edit = QLineEdit()
|
||||
self.config_file_edit.setEnabled(False)
|
||||
config_layout.addWidget(self.config_file_edit)
|
||||
|
||||
# self.config_file_browse = QPushButton("浏览...")
|
||||
# self.config_file_browse.clicked.connect(self.on_browse_config_file)
|
||||
# config_layout.addWidget(self.config_file_browse)
|
||||
|
||||
self.form_layout.addRow("配置文件:", config_layout)
|
||||
|
||||
# 模板文件
|
||||
self.template_aprx_file_edit = QLineEdit()
|
||||
self.template_path_browse = QPushButton("浏览...")
|
||||
self.template_path_browse.clicked.connect(self.on_browse_template_path)
|
||||
|
||||
template_layout = QHBoxLayout()
|
||||
template_layout.addWidget(self.template_aprx_file_edit)
|
||||
template_layout.addWidget(self.template_path_browse)
|
||||
self.form_layout.addRow("模板文件:", template_layout)
|
||||
|
||||
# 数据源路径
|
||||
self.data_source_path_edit = QLineEdit()
|
||||
self.data_source_path_browse = QPushButton("浏览...")
|
||||
self.data_source_path_browse.clicked.connect(self.on_browse_data_source_path)
|
||||
|
||||
data_source_layout = QHBoxLayout()
|
||||
data_source_layout.addWidget(self.data_source_path_edit)
|
||||
data_source_layout.addWidget(self.data_source_path_browse)
|
||||
self.form_layout.addRow("数据源路径:", data_source_layout)
|
||||
|
||||
# 符号系统路径
|
||||
self.symbol_path_edit = QLineEdit()
|
||||
self.symbol_path_browse = QPushButton("浏览...")
|
||||
self.symbol_path_browse.clicked.connect(self.on_browse_symbol_path)
|
||||
|
||||
symbol_layout = QHBoxLayout()
|
||||
symbol_layout.addWidget(self.symbol_path_edit)
|
||||
symbol_layout.addWidget(self.symbol_path_browse)
|
||||
self.form_layout.addRow("符号系统路径:", symbol_layout)
|
||||
|
||||
# 统计表格图片路径
|
||||
self.pic_path_edit = QLineEdit()
|
||||
self.pic_path_browse = QPushButton("浏览...")
|
||||
self.pic_path_browse.clicked.connect(self.on_browse_pic_path)
|
||||
|
||||
pic_layout = QHBoxLayout()
|
||||
pic_layout.addWidget(self.pic_path_edit)
|
||||
pic_layout.addWidget(self.pic_path_browse)
|
||||
self.form_layout.addRow("面积统计图片路径:", pic_layout)
|
||||
|
||||
# 输出路径
|
||||
self.output_path_edit = QLineEdit()
|
||||
self.output_path_browse = QPushButton("浏览...")
|
||||
self.output_path_browse.clicked.connect(self.on_browse_output_path)
|
||||
|
||||
output_layout = QHBoxLayout()
|
||||
output_layout.addWidget(self.output_path_edit)
|
||||
output_layout.addWidget(self.output_path_browse)
|
||||
self.form_layout.addRow("输出路径:", output_layout)
|
||||
|
||||
# 文件列表布局
|
||||
self.file_list_group = FileListGroup(self, "选择要导出的要素类:")
|
||||
self.file_list_group.load_files.connect(self.on_load_polygon)
|
||||
|
||||
# 选项区域
|
||||
options_layout = QVBoxLayout()
|
||||
|
||||
# 强制重新生成选项
|
||||
self.force_regenerate_check = QCheckBox("强制重新生成工程文件")
|
||||
options_layout.addWidget(self.force_regenerate_check)
|
||||
|
||||
# 添加到主布局
|
||||
self.main_layout.addLayout(self.form_layout)
|
||||
self.main_layout.addWidget(self.file_list_group)
|
||||
self.main_layout.addLayout(options_layout)
|
||||
|
||||
# 导出按钮
|
||||
self.export_button = QPushButton("导出地图")
|
||||
self.export_button.clicked.connect(self.on_export_map)
|
||||
self.main_layout.addWidget(self.export_button)
|
||||
|
||||
# 状态标签
|
||||
self.status_label = QLabel("状态: 就绪")
|
||||
self.main_layout.addWidget(self.status_label)
|
||||
|
||||
def on_browse_config_file(self):
|
||||
"""浏览配置文件"""
|
||||
initial_dir = ""
|
||||
if os.path.exists(self.config_file_edit.text()):
|
||||
initial_dir = self.config_file_edit.text()
|
||||
|
||||
file_path, _ = QFileDialog.getOpenFileName(self, "选择配置文件", initial_dir, "JSON文件 (*.json);;所有文件 (*.*)")
|
||||
|
||||
if file_path:
|
||||
self.config_file_edit.setText(file_path)
|
||||
|
||||
def on_browse_template_path(self):
|
||||
"""浏览模板文件"""
|
||||
initial_dir = ""
|
||||
if os.path.exists(self.template_aprx_file_edit.text()):
|
||||
initial_dir = self.template_aprx_file_edit.text()
|
||||
|
||||
file_path, _ = QFileDialog.getOpenFileName(self, "选择模板文件", initial_dir, "ArcGIS Pro工程文件 (*.aprx)")
|
||||
|
||||
if file_path:
|
||||
self.template_aprx_file_edit.setText(file_path)
|
||||
|
||||
def on_browse_data_source_path(self):
|
||||
"""浏览数据源路径"""
|
||||
initial_dir = ""
|
||||
if os.path.exists(self.data_source_path_edit.text()):
|
||||
initial_dir = os.path.dirname(self.data_source_path_edit.text())
|
||||
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择数据源文件夹", initial_dir)
|
||||
if folder_path and folder_path.endswith(".gdb"):
|
||||
self.data_source_path_edit.setText(folder_path)
|
||||
# 自动加载图层列表
|
||||
self.on_load_polygon()
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "请选择GDB的数据源。")
|
||||
|
||||
def on_browse_symbol_path(self):
|
||||
"""浏览符号系统路径"""
|
||||
initial_dir = ""
|
||||
if os.path.exists(self.symbol_path_edit.text()):
|
||||
initial_dir = os.path.dirname(self.symbol_path_edit.text())
|
||||
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择符号系统文件夹", initial_dir)
|
||||
if folder_path:
|
||||
self.symbol_path_edit.setText(folder_path)
|
||||
|
||||
def on_browse_pic_path(self):
|
||||
"""浏览符号系统路径"""
|
||||
initial_dir = ""
|
||||
if os.path.exists(self.pic_path_edit.text()):
|
||||
initial_dir = os.path.dirname(self.pic_path_edit.text())
|
||||
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择符号系统文件夹", initial_dir)
|
||||
if folder_path:
|
||||
self.pic_path_edit.setText(folder_path)
|
||||
|
||||
def on_browse_output_path(self):
|
||||
"""浏览输出路径"""
|
||||
initial_dir = ""
|
||||
if os.path.exists(self.output_path_edit.text()):
|
||||
initial_dir = os.path.dirname(self.output_path_edit.text())
|
||||
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择输出文件夹", initial_dir)
|
||||
if folder_path.endswith(".gdb"):
|
||||
QMessageBox.warning(self, "警告", "请选择有效的输出路径")
|
||||
return
|
||||
|
||||
self.output_path_edit.setText(folder_path)
|
||||
|
||||
def on_load_polygon(self):
|
||||
"""加载图层列表"""
|
||||
try:
|
||||
data_source_paths = self.data_source_path_edit.text()
|
||||
|
||||
# 清空列表
|
||||
self.file_list_group.file_list.clear()
|
||||
|
||||
# 添加图层
|
||||
if arcpy.Exists(data_source_paths):
|
||||
arcpy.env.workspace = data_source_paths
|
||||
for polygon in arcpy.ListFeatureClasses(feature_type="Polygon"):
|
||||
self.file_list_group.file_list.addItem(polygon)
|
||||
|
||||
self.log_message(f"已加载 {self.file_list_group.file_list.count()} 个图层")
|
||||
|
||||
except Exception as e:
|
||||
self.log_error(f"加载图层列表失败: {str(e)}")
|
||||
traceback.print_exc()
|
||||
|
||||
def get_selected_polygon(self):
|
||||
"""获取选中的图层列表"""
|
||||
selected_items = self.file_list_group.file_list.selectedItems()
|
||||
return [item.text() for item in selected_items]
|
||||
|
||||
def on_export_map(self):
|
||||
"""导出地图按钮点击事件"""
|
||||
try:
|
||||
# 验证并获取参数
|
||||
params = self.validate_and_params()
|
||||
if not params:
|
||||
return
|
||||
|
||||
polygon_list = params.get('polygon_list')
|
||||
|
||||
# 清空日志
|
||||
self.log_message("开始导出工程文件...")
|
||||
self.log_message(f"选中的图层: {polygon_list}")
|
||||
|
||||
# 创建导出线程
|
||||
self.runner = ScriptRunner()
|
||||
self.runner.task_log.connect(self.on_script_log)
|
||||
self.runner.task_error.connect(self.log_error)
|
||||
self.runner.task_finished.connect(self.on_script_finished)
|
||||
|
||||
# 禁用导出按钮
|
||||
self.export_button.setEnabled(False)
|
||||
self.status_label.setText("状态: 正在导出...")
|
||||
|
||||
# 运行导出脚本(使用外部窗口)
|
||||
# print(params)
|
||||
self.runner.run_export_map(params)
|
||||
|
||||
except Exception as e:
|
||||
self.log_error(f"导出准备过程出错: {str(e)}")
|
||||
traceback.print_exc()
|
||||
|
||||
def log_message(self, message):
|
||||
"""添加日志消息"""
|
||||
# 如果父窗口存在,使用父窗口的日志功能
|
||||
if self.main_window and hasattr(self.main_window, 'log_signal'):
|
||||
self.main_window.log_signal.emit(message)
|
||||
else:
|
||||
# 否则发射自己的信号
|
||||
self.log_signal.emit(message)
|
||||
|
||||
def log_error(self, message):
|
||||
"""添加错误日志"""
|
||||
self.log_message(f"错误: {message}")
|
||||
|
||||
def on_script_log(self, task_id, message):
|
||||
"""脚本输出日志"""
|
||||
self.log_message(f"{task_id}: {message}")
|
||||
|
||||
def on_script_finished(self, task_id:str, success:bool, message:str):
|
||||
"""导出完成事件处理"""
|
||||
# 恢复导出按钮
|
||||
self.export_button.setEnabled(True)
|
||||
|
||||
if success:
|
||||
self.status_label.setText("状态: 导出完成")
|
||||
QMessageBox.information(self, "成功", "地图导出完成!")
|
||||
else:
|
||||
self.status_label.setText("状态: 导出失败")
|
||||
QMessageBox.warning(self, "失败", "地图导出过程中出现错误,请查看日志详情")
|
||||
|
||||
def validate_and_params(self):
|
||||
# 获取输入参数
|
||||
county_name = self.county_name_edit.text().strip()
|
||||
if not county_name:
|
||||
QMessageBox.warning(self, "错误", "请输入区县名称")
|
||||
return
|
||||
|
||||
# 获取面要素列表
|
||||
polygon_list = self.get_selected_polygon()
|
||||
if not polygon_list:
|
||||
QMessageBox.warning(self, "错误", "请选择至少一个要素")
|
||||
return
|
||||
|
||||
# 获取配置文件
|
||||
config_file = self.config_file_edit.text()
|
||||
if not os.path.exists(config_file):
|
||||
QMessageBox.warning(self, "错误", "配置文件不存在")
|
||||
return
|
||||
|
||||
# 获取模板文件
|
||||
template_aprx_file = self.template_aprx_file_edit.text()
|
||||
if not os.path.exists(template_aprx_file):
|
||||
QMessageBox.warning(self, "错误", "模板文件不存在")
|
||||
return
|
||||
|
||||
# 获取输出路径
|
||||
output_path = self.output_path_edit.text()
|
||||
if not output_path:
|
||||
QMessageBox.warning(self, "错误", "请选择输出路径")
|
||||
return
|
||||
|
||||
# 获取数据源路径
|
||||
data_source_path = self.data_source_path_edit.text()
|
||||
if not os.path.exists(data_source_path) and not data_source_path.endswith(".gdb"):
|
||||
QMessageBox.warning(self, "错误", "请确认是否是有效数据源")
|
||||
return
|
||||
|
||||
# 获取符号系统路径
|
||||
symbol_path = self.symbol_path_edit.text()
|
||||
if not os.path.exists(symbol_path):
|
||||
QMessageBox.warning(self, "错误", "符号系统路径不存在")
|
||||
return
|
||||
|
||||
# 获取图片源路径
|
||||
pic_path = self.pic_path_edit.text()
|
||||
if not os.path.exists(pic_path):
|
||||
QMessageBox.warning(self, "错误", "符号系统路径不存在")
|
||||
return
|
||||
|
||||
# 是否强制重新生成
|
||||
force_regenerate = self.force_regenerate_check.isChecked()
|
||||
|
||||
# 准备参数
|
||||
return {
|
||||
'config_file': config_file,
|
||||
'county_name': county_name,
|
||||
'polygon_list': polygon_list,
|
||||
'template_aprx_file': template_aprx_file,
|
||||
'output_path': output_path,
|
||||
'data_source_path': data_source_path,
|
||||
'symbol_path': symbol_path,
|
||||
'force_regenerate': force_regenerate,
|
||||
'pic_path': pic_path
|
||||
}
|
||||
|
||||
def load_settings(self):
|
||||
"""从主窗口加载设置"""
|
||||
try:
|
||||
if not self.main_window or not hasattr(self.main_window, 'settings'):
|
||||
return
|
||||
|
||||
settings = self.main_window.settings
|
||||
if 'export_map_settings' in settings:
|
||||
paths = settings['export_map_settings']
|
||||
|
||||
# 直接使用get方法设置默认值
|
||||
self.config_file_edit.setText(paths.get("config_file", ""))
|
||||
self.county_name_edit.setText(paths.get("county_name", ""))
|
||||
self.template_aprx_file_edit.setText(paths.get("template_aprx_file", ""))
|
||||
self.output_path_edit.setText(paths.get("output_path", ""))
|
||||
self.data_source_path_edit.setText(paths.get("data_source_path", ""))
|
||||
self.symbol_path_edit.setText(paths.get("symbol_path", ""))
|
||||
self.pic_path_edit.setText(paths.get("pic_path", ""))
|
||||
|
||||
except Exception as e:
|
||||
self.log_error(f"加载设置失败: {str(e)}")
|
||||
|
||||
def get_settings(self):
|
||||
"""获取当前设置,供主窗口保存使用"""
|
||||
return {
|
||||
'config_file': self.config_file_edit.text(),
|
||||
'county_name': self.county_name_edit.text(),
|
||||
'template_aprx_file': self.template_aprx_file_edit.text(),
|
||||
'output_path': self.output_path_edit.text(),
|
||||
'data_source_path': self.data_source_path_edit.text(),
|
||||
'symbol_path': self.symbol_path_edit.text(),
|
||||
'pic_path': self.pic_path_edit.text()
|
||||
}
|
||||
|
||||
def update_config_file(self, config_file):
|
||||
self.config_file_edit.setText(config_file)
|
||||
244
tools/ui/tabs/raster_processing_common.py
Normal file
244
tools/ui/tabs/raster_processing_common.py
Normal file
@@ -0,0 +1,244 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
栅格处理共享组件: 提供栅格重分类、栅格转矢量和小面积图斑消除的共享UI组件
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QCheckBox,
|
||||
QGroupBox, QTableWidget, QTableWidgetItem, QHeaderView,
|
||||
QPushButton, QSpinBox, QDoubleSpinBox, QComboBox
|
||||
)
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtGui import QDoubleValidator
|
||||
|
||||
class ReclassificationWidget(QWidget):
|
||||
"""重分类参数组件"""
|
||||
|
||||
paramsChanged = pyqtSignal() # 参数变化信号
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ReclassificationWidget, self).__init__(parent)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化UI"""
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
group_box = QGroupBox("重分类映射表")
|
||||
group_layout = QVBoxLayout(group_box)
|
||||
|
||||
self.reclass_table = QTableWidget()
|
||||
self.reclass_table.setColumnCount(3)
|
||||
self.reclass_table.setHorizontalHeaderLabels(["从", "到", "新值"])
|
||||
# PyQt6 中,horizontalHeader().setSectionResizeMode 接受 QHeaderView.ResizeMode 枚举
|
||||
self.reclass_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # type: ignore
|
||||
# PyQt6 中,setSelectionBehavior 接受 QAbstractItemView.SelectionBehavior 枚举
|
||||
from PyQt6.QtWidgets import QAbstractItemView # 需要导入 QAbstractItemView
|
||||
self.reclass_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) # 整行选择
|
||||
|
||||
# 连接单元格变化信号
|
||||
self.reclass_table.itemChanged.connect(self.on_item_changed)
|
||||
|
||||
# 添加默认行
|
||||
self.add_row()
|
||||
|
||||
group_layout.addWidget(self.reclass_table)
|
||||
|
||||
# 添加/删除行按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
add_row_btn = QPushButton("添加行")
|
||||
del_row_btn = QPushButton("删除行")
|
||||
|
||||
# PyQt6 信号槽连接
|
||||
add_row_btn.clicked.connect(self.add_row)
|
||||
del_row_btn.clicked.connect(self.del_row)
|
||||
|
||||
btn_layout.addWidget(add_row_btn)
|
||||
btn_layout.addWidget(del_row_btn)
|
||||
|
||||
group_layout.addLayout(btn_layout)
|
||||
layout.addWidget(group_box)
|
||||
|
||||
def add_row(self, from_value=None, to_value=None, new_value=None):
|
||||
"""添加一行重分类规则"""
|
||||
row_count = self.reclass_table.rowCount()
|
||||
self.reclass_table.insertRow(row_count)
|
||||
|
||||
# 设置默认值或传入的值
|
||||
from_item = QTableWidgetItem(str(from_value) if from_value is not None else "")
|
||||
to_item = QTableWidgetItem(str(to_value) if to_value is not None else "")
|
||||
new_value_item = QTableWidgetItem(str(new_value) if new_value is not None else "")
|
||||
|
||||
# 设置数值验证器 (QTableWidgetItem 本身没有 setValidator 方法)
|
||||
# 如果需要验证,可以考虑使用代理 (Delegate)
|
||||
|
||||
self.reclass_table.setItem(row_count, 0, from_item)
|
||||
self.reclass_table.setItem(row_count, 1, to_item)
|
||||
self.reclass_table.setItem(row_count, 2, new_value_item)
|
||||
|
||||
def del_row(self):
|
||||
"""删除选中的行"""
|
||||
selected_rows = self.reclass_table.selectedIndexes()
|
||||
if not selected_rows:
|
||||
return
|
||||
|
||||
# 从最后一个开始删除,以避免索引变化问题
|
||||
rows_to_delete = sorted(list(set([index.row() for index in selected_rows])), reverse=True)
|
||||
for row in rows_to_delete:
|
||||
self.reclass_table.removeRow(row)
|
||||
|
||||
# 确保至少有一行
|
||||
if self.reclass_table.rowCount() == 0:
|
||||
self.add_row()
|
||||
|
||||
self.paramsChanged.emit() # 参数变化
|
||||
|
||||
def on_item_changed(self, item):
|
||||
"""当表格单元格内容变化时触发"""
|
||||
# 在这里可以添加一些简单的验证,例如检查是否为数字
|
||||
try:
|
||||
text = item.text()
|
||||
if text:
|
||||
if item.column() in [0, 1]: # "从" 和 "到" 列
|
||||
float(text) # 尝试转换为浮点数
|
||||
elif item.column() == 2: # "新值" 列
|
||||
int(text) # 尝试转换为整数
|
||||
except ValueError:
|
||||
# 如果转换失败,可以给用户提示或清空单元格
|
||||
# item.setText("") # 或者其他处理方式
|
||||
pass # 暂时不做处理,依赖后期的validate_inputs
|
||||
|
||||
self.paramsChanged.emit() # 参数变化
|
||||
|
||||
def get_remap_table(self):
|
||||
"""从表格获取重分类映射表"""
|
||||
remap_table = []
|
||||
for row in range(self.reclass_table.rowCount()):
|
||||
try:
|
||||
from_item = self.reclass_table.item(row, 0)
|
||||
to_item = self.reclass_table.item(row, 1)
|
||||
new_value_item = self.reclass_table.item(row, 2)
|
||||
|
||||
# 检查是否为空或无效
|
||||
if not from_item or not from_item.text() or \
|
||||
not to_item or not to_item.text() or \
|
||||
not new_value_item or not new_value_item.text():
|
||||
continue # 跳过空行
|
||||
|
||||
from_value_str = from_item.text()
|
||||
to_value_str = to_item.text()
|
||||
new_value_str = new_value_item.text()
|
||||
|
||||
# 处理特殊值,例如 "inf" 表示无穷大
|
||||
from_value = float(from_value_str) if from_value_str.lower() not in ('inf', '-inf') else (float('-inf') if from_value_str.lower() == '-inf' else float('inf'))
|
||||
to_value = float(to_value_str) if to_value_str.lower() not in ('inf', '-inf') else (float('-inf') if to_value_str.lower() == '-inf' else float('inf'))
|
||||
new_value = int(new_value_str)
|
||||
|
||||
# 验证范围的有效性
|
||||
if from_value > to_value:
|
||||
print(f"警告: 无效范围 {from_value} > {to_value},跳过此行")
|
||||
continue # 跳过无效范围的行
|
||||
|
||||
remap_table.append([from_value, to_value, new_value])
|
||||
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"警告: 读取重分类表格时遇到无效数值或类型,跳过此行. 错误: {e}")
|
||||
continue
|
||||
|
||||
return remap_table
|
||||
|
||||
def set_table_data(self, data):
|
||||
"""设置重分类表格数据"""
|
||||
self.reclass_table.clearContents()
|
||||
self.reclass_table.setRowCount(0)
|
||||
|
||||
if not data:
|
||||
self.add_row() # 添加一行默认行
|
||||
return
|
||||
|
||||
for row_data in data:
|
||||
if len(row_data) == 3:
|
||||
from_value, to_value, new_value = row_data
|
||||
self.add_row(from_value, to_value, new_value)
|
||||
|
||||
class VectorParamsWidget(QWidget):
|
||||
"""矢量化参数组件"""
|
||||
|
||||
paramsChanged = pyqtSignal() # 参数变化信号
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(VectorParamsWidget, self).__init__(parent)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化UI"""
|
||||
layout = QHBoxLayout(self)
|
||||
|
||||
# 矢量化参数
|
||||
# 简化多边形边界
|
||||
simplify_layout = QHBoxLayout()
|
||||
simplify_layout.addWidget(QLabel("简化多边形边界:"))
|
||||
self.simplify_check = QCheckBox()
|
||||
self.simplify_check.setChecked(False) # 默认不选中
|
||||
# PyQt6 信号槽连接
|
||||
self.simplify_check.stateChanged.connect(self.on_simplify_changed)
|
||||
simplify_layout.addWidget(self.simplify_check)
|
||||
simplify_layout.addStretch()
|
||||
layout.addLayout(simplify_layout)
|
||||
|
||||
# 最小面积阈值
|
||||
min_area_layout = QHBoxLayout()
|
||||
min_area_layout.addWidget(QLabel("最小面积:"))
|
||||
self.min_area_spin = QDoubleSpinBox()
|
||||
self.min_area_spin.setRange(0.1, 1000000)
|
||||
self.min_area_spin.setValue(100)
|
||||
self.min_area_spin.setSingleStep(10)
|
||||
# PyQt6 信号槽连接
|
||||
self.min_area_spin.valueChanged.connect(lambda: self.paramsChanged.emit())
|
||||
min_area_layout.addWidget(self.min_area_spin)
|
||||
|
||||
# 面积单位
|
||||
self.area_unit_combo = QComboBox()
|
||||
self.area_unit_combo.addItems(["平方米", "公顷", "平方公里"])
|
||||
self.area_unit_map = {
|
||||
"平方米": "SQUARE_METERS",
|
||||
"公顷": "HECTARES",
|
||||
"平方公里": "SQUARE_KILOMETERS"
|
||||
}
|
||||
# PyQt6 信号槽连接
|
||||
self.area_unit_combo.currentIndexChanged.connect(lambda: self.paramsChanged.emit())
|
||||
min_area_layout.addWidget(self.area_unit_combo)
|
||||
|
||||
layout.addLayout(min_area_layout)
|
||||
|
||||
def on_simplify_changed(self, state):
|
||||
"""当简化选项状态变化时触发"""
|
||||
# PyQt6 中 Qt.Checked 仍然可用
|
||||
is_checked = state == Qt.CheckState.Checked
|
||||
self.paramsChanged.emit()
|
||||
|
||||
def get_params(self):
|
||||
"""获取矢量化参数"""
|
||||
is_simplify = self.simplify_check.isChecked()
|
||||
return {
|
||||
"simplify": is_simplify,
|
||||
"min_area": self.min_area_spin.value(),
|
||||
"area_unit": self.area_unit_map[self.area_unit_combo.currentText()]
|
||||
}
|
||||
|
||||
def set_params(self, params):
|
||||
"""设置矢量化参数"""
|
||||
if "simplify" in params:
|
||||
simplify_value = params["simplify"]
|
||||
self.simplify_check.setChecked(simplify_value)
|
||||
|
||||
if "min_area" in params:
|
||||
self.min_area_spin.setValue(params["min_area"])
|
||||
|
||||
if "area_unit" in params:
|
||||
for text, value in self.area_unit_map.items():
|
||||
if value == params["area_unit"]:
|
||||
index = self.area_unit_combo.findText(text)
|
||||
if index >= 0:
|
||||
self.area_unit_combo.setCurrentIndex(index)
|
||||
break
|
||||
519
tools/ui/tabs/raster_tab.py
Normal file
519
tools/ui/tabs/raster_tab.py
Normal file
@@ -0,0 +1,519 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
栅格处理界面: 提供栅格重分类、栅格转矢量和小面积图斑消除的界面操作
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import arcpy
|
||||
import traceback
|
||||
import multiprocessing
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QFileDialog, QGridLayout, QCheckBox,
|
||||
QGroupBox, QMessageBox, QSpinBox)
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from tools.core.utils import common_utils
|
||||
from tools.ui.runners.script_runner import ScriptRunner
|
||||
from .config_editor_dialog import ConfigEditorDialogVisual
|
||||
from .raster_processing_common import VectorParamsWidget
|
||||
from ..components.file_list_group import FileListGroup
|
||||
|
||||
|
||||
class RasterTab(QWidget):
|
||||
value_changed = pyqtSignal(str)
|
||||
"""栅格处理窗口部件类"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(RasterTab, self).__init__(parent)
|
||||
self.main_window = parent
|
||||
self.init_ui()
|
||||
self.setWindowTitle("栅格处理")
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化用户界面"""
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# 批量处理区域
|
||||
self.batch_mode_group = QGroupBox("重分类并消除小面积图斑")
|
||||
batch_layout = QGridLayout()
|
||||
|
||||
# 配置文件选择 (公用)
|
||||
batch_layout.addWidget(QLabel("配置文件:"), 0, 0)
|
||||
self.config_file_edit = QLineEdit()
|
||||
self.config_file_edit.textChanged.connect(self.on_config_file_changed)
|
||||
batch_layout.addWidget(self.config_file_edit, 0, 1)
|
||||
self.browse_config_btn = QPushButton("浏览...")
|
||||
self.browse_config_btn.clicked.connect(self.browse_config_file)
|
||||
batch_layout.addWidget(self.browse_config_btn, 0, 2)
|
||||
|
||||
# 添加编辑配置文件的按钮 <--- Add this button
|
||||
self.edit_config_btn = QPushButton("编辑配置内容...")
|
||||
self.edit_config_btn.setEnabled(False)
|
||||
self.edit_config_btn.clicked.connect(self.open_config_editor)
|
||||
batch_layout.addWidget(self.edit_config_btn, 0, 3)
|
||||
|
||||
# 输入文件夹
|
||||
batch_layout.addWidget(QLabel("栅格文件夹:"), 1, 0)
|
||||
self.input_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.input_folder_edit, 1, 1, 1, 2)
|
||||
self.browse_input_folder_btn = QPushButton("浏览...")
|
||||
self.browse_input_folder_btn.clicked.connect(self.browse_input_folder)
|
||||
batch_layout.addWidget(self.browse_input_folder_btn, 1, 3)
|
||||
|
||||
# 批量输出文件夹
|
||||
batch_layout.addWidget(QLabel("输出文件夹:"), 2, 0)
|
||||
self.batch_output_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.batch_output_folder_edit, 2, 1, 1, 2)
|
||||
self.browse_batch_output_btn = QPushButton("浏览...")
|
||||
self.browse_batch_output_btn.clicked.connect(self.browse_batch_output_folder)
|
||||
batch_layout.addWidget(self.browse_batch_output_btn, 2, 3)
|
||||
|
||||
# 选择裁剪面checkbox
|
||||
self.clip_checkbox = QCheckBox("启用裁剪")
|
||||
self.clip_checkbox.setChecked(False)
|
||||
self.clip_checkbox.stateChanged.connect(self.on_clip_checkbox_changed)
|
||||
batch_layout.addWidget(self.clip_checkbox, 3, 0)
|
||||
self.clip_features_edit = QLineEdit()
|
||||
self.clip_features_edit.setVisible(False)
|
||||
batch_layout.addWidget(self.clip_features_edit, 3, 1, 1, 2)
|
||||
self.clip_features_btn = QPushButton("选择裁剪面")
|
||||
self.clip_features_btn.clicked.connect(self.browse_clip_features)
|
||||
self.clip_features_btn.setVisible(False)
|
||||
batch_layout.addWidget(self.clip_features_btn, 3, 3)
|
||||
|
||||
self.batch_mode_group.setLayout(batch_layout)
|
||||
|
||||
# 文件列表显示组
|
||||
self.file_list_group = FileListGroup(self, "选择要处理的栅格")
|
||||
self.file_list_group.load_files.connect(self.on_load_raster)
|
||||
|
||||
# --- 处理参数设置 ---
|
||||
param_group = QGroupBox("处理参数")
|
||||
param_layout = QVBoxLayout(param_group)
|
||||
|
||||
# 矢量化参数组件 (包含简化和最小面积阈值)
|
||||
self.vector_params = VectorParamsWidget()
|
||||
param_layout.addWidget(self.vector_params)
|
||||
|
||||
# 多进程设置
|
||||
export_layout = QHBoxLayout()
|
||||
mutiprocess_layout = QHBoxLayout()
|
||||
mutiprocess_layout.addWidget(QLabel("使用多进程导出:"))
|
||||
self.mutiprocess_check = QCheckBox()
|
||||
self.mutiprocess_check.setChecked(True)
|
||||
mutiprocess_layout.addWidget(self.mutiprocess_check)
|
||||
mutiprocess_layout.addStretch()
|
||||
export_layout.addLayout(mutiprocess_layout)
|
||||
|
||||
# 进程数量设置
|
||||
process_count_layout = QHBoxLayout()
|
||||
process_count_layout.addWidget(QLabel("进程数量:"))
|
||||
self.process_count = QSpinBox()
|
||||
self.process_count.setRange(1, multiprocessing.cpu_count())
|
||||
self.process_count.setValue(max(1, multiprocessing.cpu_count() - 1)) # 默认使用CPU核心数-1
|
||||
self.process_count.setFixedWidth(180)
|
||||
process_count_layout.addWidget(self.process_count)
|
||||
|
||||
export_layout.addLayout(process_count_layout)
|
||||
param_layout.addLayout(export_layout)
|
||||
|
||||
# 操作按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
self.process_btn = QPushButton("开始处理")
|
||||
self.process_btn.clicked.connect(self.on_start_processing)
|
||||
|
||||
self.cancel_btn = QPushButton("取消")
|
||||
# self.cancel_btn.clicked.connect(self.close)
|
||||
|
||||
btn_layout.addWidget(self.process_btn)
|
||||
btn_layout.addWidget(self.cancel_btn)
|
||||
|
||||
# 添加所有组件到主布局
|
||||
main_layout.addWidget(self.batch_mode_group)
|
||||
main_layout.addWidget(self.file_list_group)
|
||||
main_layout.addWidget(param_group)
|
||||
main_layout.addLayout(btn_layout)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def browse_input_folder(self):
|
||||
"""浏览选择输入文件夹"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择栅格文件夹")
|
||||
if folder_path:
|
||||
self.input_folder_edit.setText(folder_path)
|
||||
self.on_load_raster()
|
||||
|
||||
def browse_batch_output_folder(self):
|
||||
"""浏览选择批量处理输出文件夹"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择批量处理输出文件夹")
|
||||
if folder_path:
|
||||
if folder_path.endswith(".gdb"):
|
||||
QMessageBox.warning(self, "错误", "请选择一个文件夹,而不是一个数据库文件")
|
||||
return
|
||||
self.batch_output_folder_edit.setText(folder_path)
|
||||
|
||||
def browse_config_file(self):
|
||||
"""浏览选择配置文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择配置文件", "",
|
||||
"JSON 文件 (*.json);;所有文件 (*)"
|
||||
)
|
||||
if file_path:
|
||||
self.config_file_edit.setText(file_path)
|
||||
self.validate_config_file(file_path)
|
||||
self.edit_config_btn.setEnabled(True)
|
||||
def browse_clip_features(self):
|
||||
"""浏览选择裁剪面 (支持 shapefile)"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择裁剪要素 (Shapefile)", "",
|
||||
"Shapefile 文件 (*.shp);;所有文件 (*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
if file_path.lower().endswith(".shp"):
|
||||
self.clip_features_edit.setText(file_path)
|
||||
self.log_message(f"已选择 Shapefile: {file_path}")
|
||||
|
||||
else:
|
||||
QMessageBox.warning(self, "选择错误", "不支持的文件类型。请选择 .shp 文件。")
|
||||
self.clip_features_edit.clear()
|
||||
|
||||
def validate_config_file(self, config_file_path):
|
||||
"""对文件进行快速检查,以查看它是否类似于预期的配置结构。"""
|
||||
if not os.path.exists(config_file_path):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(config_file_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
if "export_config" in config and isinstance(config["export_config"], dict):
|
||||
if config["export_config"]:
|
||||
first_item_value = next(iter(config["export_config"].values()))
|
||||
if isinstance(first_item_value, dict) and all(k in first_item_value for k in ["项目名称", "标准等级"]):
|
||||
self.log_message(f"配置文件 '{os.path.basename(config_file_path)}' 加载成功。")
|
||||
return True
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 内容结构可能不匹配预期。")
|
||||
return False
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 中的 'export_config' 部分为空。")
|
||||
return False
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 缺少 'export_config' 键或格式不正确。")
|
||||
return False
|
||||
|
||||
except json.JSONDecodeError:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 不是有效的 JSON 格式。")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.log_message(f"警告: 检查配置文件 '{os.path.basename(config_file_path)}' 时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def open_config_editor(self):
|
||||
"""打开配置文件编辑器对话框"""
|
||||
config_path = self.config_file_edit.text()
|
||||
|
||||
if not config_path:
|
||||
reply = QMessageBox.question(self, "编辑配置", "没有选择配置文件,是否创建一个新配置文件?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||
if reply == QMessageBox.StandardButton.No:
|
||||
return
|
||||
|
||||
config_path = ""
|
||||
|
||||
# 使用配置编辑对话框
|
||||
dialog = ConfigEditorDialogVisual(config_path, self)
|
||||
|
||||
if dialog.exec() == ConfigEditorDialogVisual.DialogCode.Accepted:
|
||||
new_path = dialog.get_new_config_path()
|
||||
if new_path and new_path != self.config_file_edit.text():
|
||||
self.config_file_edit.setText(new_path)
|
||||
self.validate_config_file(new_path)
|
||||
|
||||
def on_config_file_changed(self):
|
||||
text = self.config_file_edit.text()
|
||||
self.value_changed.emit(text)
|
||||
def on_clip_checkbox_changed(self):
|
||||
clip_checkbox_state = self.clip_checkbox.isChecked()
|
||||
self.clip_features_edit.setVisible(clip_checkbox_state)
|
||||
self.clip_features_btn.setVisible(clip_checkbox_state)
|
||||
|
||||
def on_load_raster(self):
|
||||
"""加载图层列表"""
|
||||
try:
|
||||
data_source_paths = self.input_folder_edit.text()
|
||||
|
||||
self.file_list_group.load_file_btn.setEnabled(False)
|
||||
|
||||
# 清空列表
|
||||
self.file_list_group.file_list.clear()
|
||||
|
||||
if not data_source_paths or not arcpy.Exists(data_source_paths):
|
||||
self.log_message(f"输入路径不存在或不是有效的ArcGIS工作空间/文件夹: {data_source_paths}")
|
||||
return
|
||||
|
||||
arcpy.env.workspace = data_source_paths
|
||||
|
||||
rasters = arcpy.ListRasters()
|
||||
if rasters:
|
||||
for raster in rasters:
|
||||
self.file_list_group.file_list.addItem(raster)
|
||||
|
||||
self.log_message(f"已加载 {self.file_list_group.file_list.count()} 个图层")
|
||||
|
||||
else:
|
||||
self.log_message(f"文件夹 '{os.path.basename(data_source_paths)}' 中没有找到栅格图层。")
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"加载图层列表失败: {str(e)}")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
self.file_list_group.load_file_btn.setEnabled(True)
|
||||
|
||||
def validate_inputs(self):
|
||||
"""验证输入参数"""
|
||||
# 验证配置文件路径
|
||||
config_path = self.config_file_edit.text()
|
||||
if not config_path or not os.path.exists(config_path):
|
||||
QMessageBox.warning(self, "输入错误", "请选择有效的配置文件")
|
||||
return False
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config_data = json.load(f)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"无法加载或解析配置文件: {e}")
|
||||
return False
|
||||
|
||||
if "export_config" not in config_data or not isinstance(config_data["export_config"], dict):
|
||||
QMessageBox.warning(self, "输入错误", "配置文件的内容无效或缺少 'export_config' 部分。")
|
||||
return False
|
||||
|
||||
standards_dict = config_data["export_config"]
|
||||
|
||||
if not standards_dict:
|
||||
QMessageBox.warning(self, "输入错误", "配置文件中的 'export_config' 部分为空,没有定义任何处理参数。")
|
||||
return False
|
||||
|
||||
# 验证'标准等级'用于配置重分类表
|
||||
error_keys_remap = []
|
||||
for key, value in standards_dict.items():
|
||||
try:
|
||||
levels_data = value.get("标准等级", {})
|
||||
if not isinstance(levels_data, dict):
|
||||
error_keys_remap.append(f"{key} ('标准等级' 格式不正确)")
|
||||
continue
|
||||
if not common_utils.create_remap_table(levels_data):
|
||||
error_keys_remap.append(f"{key} ('标准等级' 内容无效)")
|
||||
except Exception:
|
||||
error_keys_remap.append(f"{key} (验证出错)")
|
||||
|
||||
if error_keys_remap:
|
||||
QMessageBox.warning(self, "输入错误", "配置文件中部分参数的 '标准等级' 数据无效:\n" + "\n".join(error_keys_remap))
|
||||
return False
|
||||
|
||||
# 验证输入、输出文件夹
|
||||
if not self.input_folder_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请选择输入栅格文件夹")
|
||||
return False
|
||||
|
||||
if not self.batch_output_folder_edit.text() or self.batch_output_folder_edit.text().endswith('.gdb'):
|
||||
QMessageBox.warning(self, "输入错误", "请选择批量处理输出文件夹")
|
||||
return False
|
||||
|
||||
if self.clip_checkbox.isChecked():
|
||||
clip_path = self.clip_features_edit.text()
|
||||
if not clip_path or not arcpy.Exists(clip_path):
|
||||
QMessageBox.warning(self, "输入错误", "已启用裁剪,但裁剪要素路径无效或不存在")
|
||||
return False
|
||||
|
||||
# 3. 验证裁剪要素是否可用
|
||||
if self.clip_checkbox.isChecked():
|
||||
clip_path = self.clip_features_edit.text()
|
||||
if not clip_path:
|
||||
QMessageBox.warning(self, "输入错误", "已启用裁剪,但裁剪要素路径为空。")
|
||||
return False
|
||||
|
||||
# Use arcpy.Exists and Describe to validate the path regardless of whether it's SHP or GDB/FeatureClass
|
||||
if not arcpy.Exists(clip_path):
|
||||
QMessageBox.warning(self, "输入错误", f"已启用裁剪,但裁剪要素路径不存在:\n{clip_path}")
|
||||
return False
|
||||
|
||||
# Optional: Check if the path points to a Feature Class (SHP or inside GDB)
|
||||
try:
|
||||
desc = arcpy.Describe(clip_path)
|
||||
if desc.dataType != 'ShapeFile':
|
||||
QMessageBox.warning(self, "输入错误", f"已启用裁剪,但选择的路径不是一个要素类:\n{clip_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"无法描述裁剪要素路径,请检查是否为有效要素类:\n{clip_path}\n错误: {e}")
|
||||
return False
|
||||
|
||||
# 验证是否选择文件
|
||||
selected_files = [file.text() for file in self.file_list_group.file_list.selectedItems()]
|
||||
if not selected_files:
|
||||
QMessageBox.warning(self, "输入错误", "请在栅格列表中勾选要处理的文件。")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def on_start_processing(self):
|
||||
"""开始处理栅格数据"""
|
||||
if not self.validate_inputs():
|
||||
return
|
||||
|
||||
try:
|
||||
if self.main_window and hasattr(self.main_window, 'save_settings'):
|
||||
self.main_window.update_settings()
|
||||
self.main_window.save_settings()
|
||||
settings_file = self.main_window.settings_file
|
||||
|
||||
# 创建的导出线程
|
||||
self.script_runner = ScriptRunner()
|
||||
|
||||
# 连接ScriptRunner的信号
|
||||
self.script_runner.task_started.connect(self.on_script_started)
|
||||
self.script_runner.task_finished.connect(self.on_script_finished)
|
||||
self.script_runner.task_error.connect(self.on_script_error)
|
||||
self.script_runner.task_log.connect(self.on_script_log)
|
||||
self.script_runner.manager_log.connect(self.log_message)
|
||||
|
||||
# 获取公共参数 (从 VectorParamsWidget 获取)
|
||||
selected_files = [file.text() for file in self.file_list_group.file_list.selectedItems()]
|
||||
|
||||
if self.mutiprocess_check.isChecked():
|
||||
self.script_runner.set_max_concurrent(self.process_count.value())
|
||||
else:
|
||||
self.script_runner.set_max_concurrent(1)
|
||||
|
||||
# 调用批量处理脚本
|
||||
for raster_file in selected_files:
|
||||
params = {
|
||||
"settings_path": settings_file,
|
||||
"input_raster": raster_file,
|
||||
}
|
||||
|
||||
task_id = self.script_runner.run_process_raster(params)
|
||||
# if task_id:
|
||||
# self.log_message(f"[GUI] 任务 {task_id} 处理 {raster_file} 已添加到列队")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"处理过程中出错: {str(e)}"
|
||||
QMessageBox.critical(self, "处理错误", error_msg)
|
||||
self.log_message(error_msg)
|
||||
|
||||
def get_raster_settings(self):
|
||||
"""保存当前配置到JSON文件"""
|
||||
# 从 VectorParamsWidget 获取参数
|
||||
vector_params = self.vector_params.get_params()
|
||||
|
||||
config = {
|
||||
"input_folder": self.input_folder_edit.text(),
|
||||
"batch_output_folder": self.batch_output_folder_edit.text(),
|
||||
"config_file_path": self.config_file_edit.text(),
|
||||
"simplify": vector_params["simplify"], # 从 VectorParamsWidget 获取
|
||||
"min_area": vector_params["min_area"], # 从 VectorParamsWidget 获取
|
||||
"area_unit": vector_params["area_unit"], # 从 VectorParamsWidget 获取
|
||||
"clip_features": self.clip_features_edit.text() if self.clip_checkbox.isChecked() else "",
|
||||
"clip_enabled": self.clip_checkbox.isChecked()
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
def load_settings(self, settings):
|
||||
"""从字典加载配置"""
|
||||
try:
|
||||
# 批处理参数
|
||||
self.input_folder_edit.setText(settings.get("input_folder", ""))
|
||||
self.batch_output_folder_edit.setText(settings.get("batch_output_folder", ""))
|
||||
|
||||
# 配置文件路径和加载
|
||||
config_file_path = settings.get("config_file_path", "")
|
||||
self.config_file_edit.setText(config_file_path)
|
||||
self.edit_config_btn.setEnabled(bool(config_file_path) and os.path.exists(config_file_path))
|
||||
|
||||
if config_file_path:
|
||||
self.validate_config_file(config_file_path)
|
||||
|
||||
clip_enabled = settings.get("clip_enabled", False)
|
||||
self.clip_checkbox.setChecked(clip_enabled)
|
||||
self.clip_features_edit.setText(settings.get("clip_features", ""))
|
||||
self.on_clip_checkbox_changed()
|
||||
|
||||
# 矢量化参数 (使用 VectorParamsWidget 的 set_params 方法)
|
||||
vector_params_config = {
|
||||
"simplify": settings.get("simplify", False), # 注意默认值需要与 VectorParamsWidget 匹配
|
||||
"min_area": settings.get("min_area", 1000),
|
||||
"area_unit": settings.get("area_unit", "平方米")
|
||||
}
|
||||
self.vector_params.set_params(vector_params_config)
|
||||
|
||||
if self.input_folder_edit.text():
|
||||
self.on_load_raster()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "加载配置错误", f"加载配置时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def set_buttons_enabled(self, enabled):
|
||||
"""设置按钮是否可用"""
|
||||
self.process_btn.setEnabled(enabled)
|
||||
|
||||
def on_script_started(self, task_id, task_description):
|
||||
"""脚本开始执行"""
|
||||
self.set_buttons_enabled(False)
|
||||
self.log_message(f"{task_id}: 正在运行 - {task_description}")
|
||||
|
||||
def on_script_finished(self, task_id:str, success:bool, message:str):
|
||||
"""脚本执行完成"""
|
||||
|
||||
try:
|
||||
# 将shp文件导入到GDB数据库
|
||||
if self.script_runner.get_running_tasks() == []:
|
||||
output_path = self.batch_output_folder_edit.text()
|
||||
shape_files = []
|
||||
for file in os.listdir(output_path):
|
||||
if file.endswith("eliminate.shp"):
|
||||
shape_files.append(os.path.join(output_path, file))
|
||||
if shape_files:
|
||||
try:
|
||||
path_result = arcpy.management.CreateFileGDB(output_path, "过程数据(消除小图斑).gdb", "CURRENT")
|
||||
# 批量导入数据
|
||||
arcpy.conversion.FeatureClassToGeodatabase(shape_files, path_result)
|
||||
arcpy.management.Delete(shape_files)
|
||||
QMessageBox.information(self, "成功", f"数据已保存到{path_result}")
|
||||
self.set_buttons_enabled(True)
|
||||
except Exception as e:
|
||||
self.log_message(f"批量导入数据出错: {str(e)}")
|
||||
except Exception as e:
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message(f"批量导入数据出错: {str(e)}")
|
||||
|
||||
def on_script_error(self, error_msg):
|
||||
"""脚本执行出错"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message(f"错误: {error_msg}")
|
||||
QMessageBox.critical(self, "错误", error_msg)
|
||||
|
||||
def on_script_log(self, task_id, message):
|
||||
"""脚本输出日志"""
|
||||
self.log_message(f"{task_id}: {message}")
|
||||
|
||||
def log_message(self, message):
|
||||
"""日志输出"""
|
||||
if self.main_window and hasattr(self.main_window, 'log_signal'):
|
||||
self.main_window.log_signal.emit(message)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
import sys
|
||||
app = QApplication(sys.argv)
|
||||
window = RasterTab()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
353
tools/ui/tabs/soil_prop_stat_tab.py
Normal file
353
tools/ui/tabs/soil_prop_stat_tab.py
Normal file
@@ -0,0 +1,353 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
栅格处理界面: 提供栅格重分类、栅格转矢量和小面积图斑消除的界面操作
|
||||
"""
|
||||
|
||||
import os
|
||||
import traceback
|
||||
import arcpy
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QFileDialog, QGridLayout,
|
||||
QGroupBox, QMessageBox)
|
||||
from tools.ui.components.file_list_group import FileListGroup
|
||||
from tools.ui.runners.script_runner import ScriptRunner
|
||||
from tools.ui.tabs.config_editor_dialog import ConfigEditorDialogVisual
|
||||
|
||||
|
||||
class SoilPropStatsTab(QWidget):
|
||||
"""栅格处理窗口部件类"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(SoilPropStatsTab, self).__init__(parent)
|
||||
self.main_window = parent
|
||||
# 创建的导出线程
|
||||
self.script_runner = ScriptRunner()
|
||||
self.connect_signals()
|
||||
self.init_ui()
|
||||
self.setWindowTitle("土壤属性统计")
|
||||
|
||||
def connect_signals(self):
|
||||
# 连接ScriptRunner的信号
|
||||
self.script_runner.task_started.connect(self.on_script_started)
|
||||
self.script_runner.task_finished.connect(self.on_script_finished)
|
||||
self.script_runner.task_error.connect(self.on_script_error)
|
||||
self.script_runner.task_log.connect(self.on_script_log)
|
||||
self.script_runner.manager_log.connect(self.log_message)
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化用户界面"""
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# 批量处理区域
|
||||
self.batch_mode_group = QGroupBox("批量处理设置")
|
||||
batch_layout = QGridLayout()
|
||||
|
||||
# 配置文件选择 (公用)
|
||||
batch_layout.addWidget(QLabel("配置文件:"), 0, 0)
|
||||
self.config_file_edit = QLineEdit()
|
||||
self.config_file_edit.setEnabled(False)
|
||||
batch_layout.addWidget(self.config_file_edit, 0, 1,1,2)
|
||||
# self.browse_config_btn = QPushButton("浏览...")
|
||||
# self.browse_config_btn.clicked.connect(self.browse_config_file)
|
||||
# self.browse_config_btn.setEnabled(False)
|
||||
# batch_layout.addWidget(self.browse_config_btn, 0, 2)
|
||||
|
||||
# # 添加编辑配置文件的按钮 <--- Add this button
|
||||
# self.edit_config_btn = QPushButton("编辑配置内容...")
|
||||
# self.edit_config_btn.setEnabled(False)
|
||||
# self.edit_config_btn.clicked.connect(self.open_config_editor)
|
||||
# batch_layout.addWidget(self.edit_config_btn, 0, 3)
|
||||
|
||||
# 输入行政区名称
|
||||
batch_layout.addWidget(QLabel("行政区名称:"), 1, 0)
|
||||
self.input_xzqmc_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.input_xzqmc_edit, 1, 1)
|
||||
|
||||
# 工作空间路径 - 使用水平布局容器
|
||||
label_container = QWidget()
|
||||
label_layout = QHBoxLayout(label_container)
|
||||
label_layout.setContentsMargins(0, 0, 0, 0) # 去掉边距
|
||||
|
||||
label = QLabel("❔")
|
||||
label.setToolTip("""<font color='blue'>各属性样点<br>地类图斑<br>土壤类型图<br>母岩母质</font>""")
|
||||
text_label = QLabel("数据源路径:")
|
||||
|
||||
label_layout.addWidget(label)
|
||||
label_layout.addWidget(text_label)
|
||||
|
||||
batch_layout.addWidget(label_container, 2, 0) # 将整个容器放在第2行第0列
|
||||
self.data_source_path_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.data_source_path_edit, 2, 1)
|
||||
self.browse_input_workspace_btn = QPushButton("选择GDB")
|
||||
self.browse_input_workspace_btn.clicked.connect(self.browse_input_workspace)
|
||||
batch_layout.addWidget(self.browse_input_workspace_btn, 2, 2)
|
||||
|
||||
# 选择三普属性栅格文件夹
|
||||
batch_layout.addWidget(QLabel("三普属性栅格:"), 3, 0)
|
||||
self.input_sanpu_prop_tif_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.input_sanpu_prop_tif_edit, 3, 1)
|
||||
self.browse_input_sanpu_prop_tif_btn = QPushButton("选择文件夹")
|
||||
self.browse_input_sanpu_prop_tif_btn.clicked.connect(self.browse_input_sanpu_prop_tif)
|
||||
batch_layout.addWidget(self.browse_input_sanpu_prop_tif_btn, 3, 2)
|
||||
|
||||
# 选择三普土壤属性重分类后的面要素
|
||||
batch_layout.addWidget(QLabel("属性重分类面:"), 4, 0)
|
||||
self.input_reclassed_feature_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.input_reclassed_feature_folder_edit, 4, 1)
|
||||
self.browse_input_reclassed_feature_folder_btn = QPushButton("选择文件夹")
|
||||
self.browse_input_reclassed_feature_folder_btn.clicked.connect(self.browse_input_reclassed_feature_folder)
|
||||
batch_layout.addWidget(self.browse_input_reclassed_feature_folder_btn, 4, 2)
|
||||
|
||||
# 批量输出文件夹
|
||||
batch_layout.addWidget(QLabel("输出文件夹:"), 5, 0)
|
||||
self.batch_output_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.batch_output_folder_edit, 5, 1)
|
||||
self.browse_batch_output_btn = QPushButton("选择文件夹")
|
||||
self.browse_batch_output_btn.clicked.connect(self.browse_batch_output_folder)
|
||||
batch_layout.addWidget(self.browse_batch_output_btn, 5, 2)
|
||||
|
||||
self.batch_mode_group.setLayout(batch_layout)
|
||||
|
||||
# 文件列表布局
|
||||
self.file_list_group = FileListGroup(self, "选择要导出的三普属性样点:")
|
||||
self.file_list_group.load_files.connect(self.on_load_polygon)
|
||||
|
||||
# 操作按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
|
||||
# 导出酸化统计表
|
||||
self.generate_sh_stat_btn = QPushButton("生成土壤属性统计表")
|
||||
self.generate_sh_stat_btn.clicked.connect(self.on_generate_area_stat)
|
||||
|
||||
self.cancel_btn = QPushButton("取消")
|
||||
# self.cancel_btn.clicked.connect(self.close)
|
||||
|
||||
btn_layout.addWidget(self.generate_sh_stat_btn)
|
||||
btn_layout.addWidget(self.cancel_btn)
|
||||
|
||||
# 添加所有组件到主布局
|
||||
main_layout.addWidget(self.batch_mode_group)
|
||||
main_layout.addWidget(self.file_list_group)
|
||||
main_layout.addLayout(btn_layout)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
|
||||
def browse_config_file(self):
|
||||
"""浏览选择配置文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择配置文件", "",
|
||||
"JSON 文件 (*.json);;所有文件 (*)"
|
||||
)
|
||||
if file_path:
|
||||
self.config_file_edit.setText(file_path)
|
||||
# self.validate_config_file(file_path)
|
||||
# self.edit_config_btn.setEnabled(True)
|
||||
def on_load_polygon(self):
|
||||
"""加载图层列表"""
|
||||
try:
|
||||
data_source_paths = self.data_source_path_edit.text()
|
||||
|
||||
# 清空列表
|
||||
self.file_list_group.file_list.clear()
|
||||
|
||||
# 添加图层
|
||||
if arcpy.Exists(data_source_paths):
|
||||
arcpy.env.workspace = data_source_paths
|
||||
for polygon in arcpy.ListFeatureClasses(feature_type="Point"):
|
||||
self.file_list_group.file_list.addItem(polygon)
|
||||
|
||||
self.log_message(f"已加载 {self.file_list_group.file_list.count()} 个图层")
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"加载图层列表失败: {str(e)}")
|
||||
traceback.print_exc()
|
||||
def browse_input_workspace(self):
|
||||
"""浏览选择输入GDB"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择GDB数据库")
|
||||
if folder_path:
|
||||
self.data_source_path_edit.setText(folder_path)
|
||||
|
||||
def browse_input_sanpu_prop_tif(self):
|
||||
"""浏览选择输入GDB"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹啊")
|
||||
if folder_path:
|
||||
self.input_sanpu_prop_tif_edit.setText(folder_path)
|
||||
|
||||
def browse_input_reclassed_feature_folder(self):
|
||||
"""浏览选择输入GDB"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择GDB数据库")
|
||||
if folder_path:
|
||||
self.input_reclassed_feature_folder_edit.setText(folder_path)
|
||||
|
||||
def browse_batch_output_folder(self):
|
||||
"""浏览选择表格输出文件夹"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择表格输出文件夹")
|
||||
if folder_path:
|
||||
self.batch_output_folder_edit.setText(folder_path)
|
||||
|
||||
def validate_inputs(self):
|
||||
"""验证输入参数"""
|
||||
# 验证行政区名称
|
||||
if not self.input_xzqmc_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请输入行政区名称")
|
||||
return False
|
||||
|
||||
# 验证工作空间路径
|
||||
if not self.data_source_path_edit.text() or not arcpy.Exists(self.data_source_path_edit.text()):
|
||||
QMessageBox.warning(self, "输入错误", "请选择有效的工作空间")
|
||||
return False
|
||||
|
||||
if not self.batch_output_folder_edit.text() or not arcpy.Exists(self.batch_output_folder_edit.text()):
|
||||
QMessageBox.warning(self, "输入错误", "请选择批量处理输出文件夹")
|
||||
return False
|
||||
|
||||
if not self.input_sanpu_prop_tif_edit.text() or not arcpy.Exists(self.input_sanpu_prop_tif_edit.text()):
|
||||
QMessageBox.warning(self, "输入错误", "请选择有效的三普属性栅格文件夹")
|
||||
return False
|
||||
|
||||
# 验证选择的要素类是否有对应的属性栅格
|
||||
missing_items = []
|
||||
for item in self.file_list_group.file_list.selectedItems():
|
||||
if not arcpy.Exists(os.path.join(self.input_sanpu_prop_tif_edit.text(), f"{item.text()}.tif")):
|
||||
missing_items.append(item.text())
|
||||
if missing_items:
|
||||
QMessageBox.warning(self, "输入错误", f"选择的样点中无相应的栅格文件: {missing_items}")
|
||||
return False
|
||||
|
||||
# 验证数据源中是否存在 地类图斑、土壤类型图斑、母岩母质图斑 等要素
|
||||
if not arcpy.Exists(os.path.join(self.data_source_path_edit.text(), "地类图斑")):
|
||||
QMessageBox.warning(self, "输入错误", "工作空间中不存在 地类图斑.shp")
|
||||
return False
|
||||
if not arcpy.Exists(os.path.join(self.data_source_path_edit.text(), "土壤类型图")):
|
||||
QMessageBox.warning(self, "输入错误", "工作空间中不存在土壤类型图.shp")
|
||||
return False
|
||||
# if not arcpy.Exists(os.path.join(self.data_source_path_edit.text(), "母岩母质图斑")):
|
||||
# QMessageBox.warning(self, "输入错误", "工作空间中不存在母岩母质图斑.shp")
|
||||
# return False
|
||||
|
||||
# 验证文件列表中是否已选中项目
|
||||
if not self.file_list_group.file_list.selectedItems():
|
||||
QMessageBox.warning(self, "输入错误", "请至少选择一个属性")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def open_config_editor(self):
|
||||
"""打开配置文件编辑器对话框"""
|
||||
config_path = self.config_file_edit.text()
|
||||
|
||||
if not config_path:
|
||||
reply = QMessageBox.question(self, "编辑配置", "没有选择配置文件,是否创建一个新配置文件?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||
if reply == QMessageBox.StandardButton.No:
|
||||
return
|
||||
|
||||
config_path = ""
|
||||
|
||||
# 使用配置编辑对话框
|
||||
dialog = ConfigEditorDialogVisual(config_path, self)
|
||||
|
||||
if dialog.exec() == ConfigEditorDialogVisual.DialogCode.Accepted:
|
||||
new_path = dialog.get_new_config_path()
|
||||
if new_path and new_path != self.config_file_edit.text():
|
||||
self.config_file_edit.setText(new_path)
|
||||
# self.validate_config_file(new_path)
|
||||
|
||||
# 执行生成酸化统计表
|
||||
def on_generate_area_stat(self):
|
||||
"""执行生成面积统计表"""
|
||||
if not self.validate_inputs():
|
||||
return
|
||||
|
||||
try:
|
||||
if self.main_window and hasattr(self.main_window, 'save_settings'):
|
||||
self.main_window.update_settings()
|
||||
self.main_window.save_settings()
|
||||
settings_file = self.main_window.settings_file
|
||||
|
||||
params = {
|
||||
"settings_path": settings_file,
|
||||
}
|
||||
|
||||
task_id = self.script_runner.run_soil_prop_stat(params)
|
||||
|
||||
if task_id:
|
||||
self.log_message(f"[GUI] 属性表格生成任务 {task_id} 已添加到列队")
|
||||
except Exception as e:
|
||||
error_msg = f"处理过程中出错: {str(e)}"
|
||||
QMessageBox.critical(self, "处理错误", error_msg)
|
||||
self.log_message(error_msg)
|
||||
|
||||
def get_soil_prop_stat_settings(self):
|
||||
"""保存当前配置到JSON文件"""
|
||||
config = {
|
||||
"xzqmc": self.input_xzqmc_edit.text(),
|
||||
"config_file": self.config_file_edit.text(),
|
||||
"data_source_path": self.data_source_path_edit.text(),
|
||||
"sanpu_prop_tif_folder": self.input_sanpu_prop_tif_edit.text(),
|
||||
"reclassed_feature_folder": self.input_reclassed_feature_folder_edit.text(),
|
||||
"output_folder": self.batch_output_folder_edit.text(),
|
||||
"sample_list": [item.text() for item in self.file_list_group.file_list.selectedItems()]
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
def load_settings(self, settings):
|
||||
"""从字典加载配置"""
|
||||
try:
|
||||
# 批处理参数
|
||||
self.data_source_path_edit.setText(settings.get("data_source_path", ""))
|
||||
self.input_sanpu_prop_tif_edit.setText(settings.get("sanpu_prop_tif_folder", ""))
|
||||
self.batch_output_folder_edit.setText(settings.get("output_folder", ""))
|
||||
self.input_reclassed_feature_folder_edit.setText(settings.get("reclassed_feature_folder", ""))
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "加载配置错误", f"加载配置时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def set_buttons_enabled(self, enabled):
|
||||
"""设置按钮是否可用"""
|
||||
self.generate_sh_stat_btn.setEnabled(enabled)
|
||||
|
||||
def update_config_file(self, config_file_path):
|
||||
"""更新配置文件路径"""
|
||||
self.config_file_edit.setText(config_file_path)
|
||||
|
||||
def on_script_started(self, task_id, task_description):
|
||||
"""脚本开始执行"""
|
||||
self.set_buttons_enabled(False)
|
||||
self.log_message(f"{task_id}: 正在运行 - {task_description}")
|
||||
|
||||
def on_script_finished(self, task_id:str, success:bool, message:str):
|
||||
"""脚本执行完成"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message("脚本执行完成")
|
||||
|
||||
def on_script_error(self,task_id, error_msg):
|
||||
"""脚本执行出错"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message(f"错误:{task_id}-{error_msg}")
|
||||
# QMessageBox.critical(self, "错误", error_msg)
|
||||
|
||||
def on_script_log(self, task_id, message):
|
||||
"""脚本输出日志"""
|
||||
self.log_message(f"{task_id}: {message}")
|
||||
|
||||
def log_message(self, message):
|
||||
"""日志输出"""
|
||||
if self.main_window and hasattr(self.main_window, 'log_signal'):
|
||||
self.main_window.log_signal.emit(message)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
import sys
|
||||
app = QApplication(sys.argv)
|
||||
window = SoilPropStatsTab()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
569
tools/ui/tabs/test_tab.py
Normal file
569
tools/ui/tabs/test_tab.py
Normal file
@@ -0,0 +1,569 @@
|
||||
import json
|
||||
import os
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QLabel, QLineEdit, QFileDialog, QMessageBox,
|
||||
QComboBox, QSpinBox, QGroupBox, QFormLayout,
|
||||
QTableWidget, QHeaderView, QSizePolicy, QTableWidgetItem,
|
||||
QCheckBox)
|
||||
import arcpy
|
||||
from ..runners.script_runner import ScriptRunner
|
||||
|
||||
|
||||
class TestTab(QWidget):
|
||||
"""导出成果图标签页"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.main_window = parent
|
||||
self.script_runner = ScriptRunner(self)
|
||||
self.connect_signals()
|
||||
self.init_ui()
|
||||
self.load_settings()
|
||||
|
||||
def connect_signals(self):
|
||||
"""连接脚本运行器信号"""
|
||||
# 连接ScriptRunner的信号
|
||||
self.script_runner.started.connect(self.on_script_started)
|
||||
self.script_runner.finished.connect(self.on_script_finished)
|
||||
self.script_runner.error.connect(self.on_script_error)
|
||||
self.script_runner.log.connect(self.log_message)
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# 配置文件设置组
|
||||
config_group = QGroupBox("配置设置")
|
||||
config_layout = QVBoxLayout(config_group)
|
||||
|
||||
# 配置文件选择
|
||||
config_file_layout = QHBoxLayout()
|
||||
config_file_layout.addWidget(QLabel("配置文件:"))
|
||||
self.config_file_path = QLineEdit()
|
||||
config_file_layout.addWidget(self.config_file_path)
|
||||
config_file_btn = QPushButton("浏览...")
|
||||
config_file_btn.clicked.connect(self.browse_config_file)
|
||||
config_file_layout.addWidget(config_file_btn)
|
||||
config_layout.addLayout(config_file_layout)
|
||||
|
||||
# 区县名字设置
|
||||
county_name_layout = QHBoxLayout()
|
||||
county_name_layout.addWidget(QLabel("区县名称:"))
|
||||
self.county_name = QLineEdit()
|
||||
self.county_name.setPlaceholderText("请输入区县名称")
|
||||
county_name_layout.addWidget(self.county_name)
|
||||
config_layout.addLayout(county_name_layout)
|
||||
|
||||
# 图层名称选择
|
||||
layer_select_layout = QHBoxLayout()
|
||||
layer_select_layout.addWidget(QLabel("选择图层:"))
|
||||
self.layer_name_combo = QComboBox()
|
||||
self.layer_name_combo.setMinimumWidth(200)
|
||||
self.layer_name_combo.currentIndexChanged.connect(self.on_layer_name_changed)
|
||||
layer_select_layout.addWidget(self.layer_name_combo)
|
||||
layer_select_layout.addStretch()
|
||||
config_layout.addLayout(layer_select_layout)
|
||||
|
||||
# 配置文件设置显示区域
|
||||
export_layout = QVBoxLayout()
|
||||
export_layout.addWidget(QLabel("配置文件设置:"))
|
||||
export_layout.minimumHeightForWidth(200)
|
||||
self.export_config_display = QTableWidget(0, 2)
|
||||
self.export_config_display.setHorizontalHeaderLabels(["元素名称", "元素内容"])
|
||||
header = self.export_config_display.horizontalHeader()
|
||||
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Interactive)
|
||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
||||
header.setDefaultSectionSize(150)
|
||||
self.export_config_display.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
|
||||
export_layout.addWidget(self.export_config_display)
|
||||
config_layout.addLayout(export_layout)
|
||||
|
||||
layout.addWidget(config_group)
|
||||
|
||||
# 输入输出设置组
|
||||
io_group = QGroupBox("输入输出设置")
|
||||
io_layout = QVBoxLayout()
|
||||
|
||||
# 地图文档选择
|
||||
doc_layout = QHBoxLayout()
|
||||
doc_layout.addWidget(QLabel("模板文件:"))
|
||||
self.template_aprx_file = QLineEdit()
|
||||
doc_layout.addWidget(self.template_aprx_file)
|
||||
browse_doc_btn = QPushButton("浏览...")
|
||||
browse_doc_btn.clicked.connect(self.browse_doc)
|
||||
doc_layout.addWidget(browse_doc_btn)
|
||||
io_layout.addLayout(doc_layout)
|
||||
|
||||
# 数据源选择
|
||||
data_layout = QHBoxLayout()
|
||||
data_layout.addWidget(QLabel("数据源:"))
|
||||
self.data_source_path = QLineEdit()
|
||||
data_layout.addWidget(self.data_source_path)
|
||||
browse_data_btn = QPushButton("浏览...")
|
||||
browse_data_btn.clicked.connect(self.browse_data_source)
|
||||
data_layout.addWidget(browse_data_btn)
|
||||
io_layout.addLayout(data_layout)
|
||||
|
||||
# 符号系统文件夹选择
|
||||
symbol_layout = QHBoxLayout()
|
||||
symbol_layout.addWidget(QLabel("符号系统:"))
|
||||
self.symbol_path = QLineEdit()
|
||||
symbol_layout.addWidget(self.symbol_path)
|
||||
browse_symbol_btn = QPushButton("浏览...")
|
||||
browse_symbol_btn.clicked.connect(self.browse_symbol_file)
|
||||
symbol_layout.addWidget(browse_symbol_btn)
|
||||
io_layout.addLayout(symbol_layout)
|
||||
|
||||
# 输出路径选择
|
||||
output_layout = QHBoxLayout()
|
||||
output_layout.addWidget(QLabel("输出路径:"))
|
||||
self.output_path = QLineEdit()
|
||||
output_layout.addWidget(self.output_path)
|
||||
browse_output_btn = QPushButton("浏览...")
|
||||
browse_output_btn.clicked.connect(self.browse_output)
|
||||
output_layout.addWidget(browse_output_btn)
|
||||
io_layout.addLayout(output_layout)
|
||||
|
||||
io_group.setLayout(io_layout)
|
||||
layout.addWidget(io_group)
|
||||
|
||||
# 导出设置组
|
||||
export_group = QGroupBox("导出设置")
|
||||
export_layout = QFormLayout()
|
||||
|
||||
# 格式选择
|
||||
self.format_combo = QComboBox()
|
||||
self.format_combo.addItems(["PDF", "PNG", "JPG", "TIFF", "EPS", "SVG", "AI"])
|
||||
export_layout.addRow("导出格式:", self.format_combo)
|
||||
|
||||
# 分辨率设置
|
||||
self.resolution_spinbox = QSpinBox()
|
||||
self.resolution_spinbox.setRange(72, 1200)
|
||||
self.resolution_spinbox.setSingleStep(12)
|
||||
self.resolution_spinbox.setValue(300)
|
||||
self.resolution_spinbox.setSuffix(" DPI")
|
||||
export_layout.addRow("分辨率:", self.resolution_spinbox)
|
||||
|
||||
# 强制重新生成选项
|
||||
self.force_regenerate = QCheckBox("强制重新生成工程文件")
|
||||
self.force_regenerate.setChecked(False)
|
||||
self.force_regenerate.setToolTip("勾选后将忽略已存在的工程文件,重新生成")
|
||||
export_layout.addRow("处理选项:", self.force_regenerate)
|
||||
|
||||
export_group.setLayout(export_layout)
|
||||
layout.addWidget(export_group)
|
||||
|
||||
# 操作按钮
|
||||
button_layout = QHBoxLayout()
|
||||
self.export_layout_btn = QPushButton("仅导出布局")
|
||||
self.export_layout_btn.clicked.connect(self.on_export_layout)
|
||||
self.export_btn = QPushButton("导出")
|
||||
self.export_btn.clicked.connect(self.on_export)
|
||||
self.batch_export_btn = QPushButton("批量导出")
|
||||
self.batch_export_btn.clicked.connect(self.on_batch_export)
|
||||
self.export_existing_btn = QPushButton("导出已有工程")
|
||||
self.export_existing_btn.setToolTip("仅导出输出文件夹中已存在的工程文件")
|
||||
self.export_existing_btn.clicked.connect(self.on_export_existing)
|
||||
self.test_btn = QPushButton("测试功能")
|
||||
self.test_btn.setToolTip("测试功能")
|
||||
self.test_btn.clicked.connect(self.on_test_functions)
|
||||
button_layout.addStretch()
|
||||
|
||||
button_layout.addWidget(self.export_layout_btn)
|
||||
button_layout.addWidget(self.export_btn)
|
||||
button_layout.addWidget(self.batch_export_btn)
|
||||
button_layout.addWidget(self.export_existing_btn)
|
||||
button_layout.addWidget(self.test_btn)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
def browse_config_file(self):
|
||||
"""浏览配置文件"""
|
||||
initial_path = os.getcwd()
|
||||
file_path, _ = QFileDialog.getOpenFileName(self, "选择配置文件", initial_path, "JSON Files (*.json);;All Files (*.*)")
|
||||
if file_path:
|
||||
self.config_file_path.setText(file_path)
|
||||
self.load_export_config(file_path)
|
||||
|
||||
def browse_doc(self):
|
||||
"""浏览地图文档"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"选择地图文档",
|
||||
"",
|
||||
"ArcGIS Pro Project (*.aprx);;All Files (*.*)"
|
||||
)
|
||||
if file_path:
|
||||
self.template_aprx_file.setText(file_path)
|
||||
|
||||
def browse_data_source(self):
|
||||
"""浏览数据源"""
|
||||
dir_path = QFileDialog.getExistingDirectory(
|
||||
self,
|
||||
"选择数据源目录"
|
||||
)
|
||||
if dir_path:
|
||||
self.data_source_path.setText(dir_path)
|
||||
|
||||
def browse_symbol_file(self):
|
||||
"""浏览符号系统文件夹"""
|
||||
dir_path = QFileDialog.getExistingDirectory(
|
||||
self,
|
||||
"选择符号系统文件夹"
|
||||
)
|
||||
if dir_path:
|
||||
self.symbol_path.setText(dir_path)
|
||||
|
||||
def browse_output(self):
|
||||
"""选择输出路径"""
|
||||
dir_path = QFileDialog.getExistingDirectory(
|
||||
self,
|
||||
"选择输出路径"
|
||||
)
|
||||
if dir_path:
|
||||
self.output_path.setText(dir_path)
|
||||
|
||||
def load_export_config(self, file_path):
|
||||
"""加载导出配置文件"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
export_config = config.get('export_config', {})
|
||||
|
||||
if not export_config:
|
||||
QMessageBox.warning(self, "警告", "配置文件中未找到导出设置")
|
||||
return
|
||||
|
||||
# 保存配置到实例变量
|
||||
self._config = config
|
||||
self._export_config = export_config
|
||||
|
||||
# 清空并添加图层名称到下拉框
|
||||
self.layer_name_combo.clear()
|
||||
self.layer_name_combo.addItems(export_config.keys())
|
||||
|
||||
# 如果有图层,自动选择第一个
|
||||
if self.layer_name_combo.count() > 0:
|
||||
self.layer_name_combo.setCurrentIndex(0)
|
||||
|
||||
self.log_message(f"已加载配置文件: {file_path}")
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"加载配置文件失败: {str(e)}")
|
||||
|
||||
def on_layer_name_changed(self, index):
|
||||
"""图层名称变更时更新配置显示"""
|
||||
if index < 0 or not hasattr(self, '_export_config'):
|
||||
return
|
||||
|
||||
layer_name = self.layer_name_combo.currentText()
|
||||
layer_config = self._export_config.get(layer_name, {})
|
||||
|
||||
# 清空并重新添加行
|
||||
self.export_config_display.setRowCount(0)
|
||||
|
||||
for i, (key, value) in enumerate(layer_config.items()):
|
||||
self.export_config_display.insertRow(i)
|
||||
self.export_config_display.setItem(i, 0, QTableWidgetItem(key))
|
||||
self.export_config_display.setItem(i, 1, QTableWidgetItem(str(value)))
|
||||
|
||||
def on_script_started(self, message):
|
||||
"""脚本开始执行回调"""
|
||||
self.set_buttons_enabled(False)
|
||||
self.log_message(message)
|
||||
|
||||
def on_script_finished(self, task_id:str, success:bool, message:str):
|
||||
"""脚本执行完成回调"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message("脚本执行完成")
|
||||
|
||||
def on_script_error(self, error_msg):
|
||||
"""脚本执行错误回调"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message(f"错误: {error_msg}")
|
||||
QMessageBox.critical(self, "错误", error_msg)
|
||||
|
||||
def set_buttons_enabled(self, enabled):
|
||||
"""设置按钮启用状态"""
|
||||
self.export_btn.setEnabled(enabled)
|
||||
self.batch_export_btn.setEnabled(enabled)
|
||||
self.export_layout_btn.setEnabled(enabled)
|
||||
self.export_existing_btn.setEnabled(enabled)
|
||||
self.test_btn.setEnabled(enabled)
|
||||
|
||||
def on_export(self):
|
||||
"""导出按钮点击事件"""
|
||||
# 验证输入
|
||||
if not self.validate_inputs():
|
||||
return
|
||||
|
||||
# 获取参数
|
||||
params = self.get_export_params()
|
||||
if not params:
|
||||
return
|
||||
|
||||
# 添加配置文件参数
|
||||
params['config_file'] = self.config_file_path.text()
|
||||
|
||||
# 调用导出地图脚本
|
||||
self.script_runner.run_export_map(params)
|
||||
|
||||
def on_batch_export(self):
|
||||
"""批量导出按钮点击事件"""
|
||||
# 验证输入
|
||||
if not self.validate_inputs(check_layer=False):
|
||||
return
|
||||
|
||||
# 获取参数
|
||||
params = self.get_export_params(include_layer=False)
|
||||
if not params:
|
||||
return
|
||||
|
||||
# 检查配置是否包含图层
|
||||
if not hasattr(self, '_export_config') or not self._export_config:
|
||||
QMessageBox.warning(self, "警告", "请先加载配置文件")
|
||||
return
|
||||
|
||||
# 添加配置文件路径参数
|
||||
params['config_file'] = self.config_file_path.text()
|
||||
params['export_config'] = self._export_config
|
||||
|
||||
# 检查可用图层
|
||||
polygon_list = list(self._export_config.keys())
|
||||
available_layers = []
|
||||
for layer_name in polygon_list:
|
||||
layer_path = os.path.join(params.get("data_source_path"), layer_name)
|
||||
if arcpy.Exists(layer_path):
|
||||
available_layers.append(layer_name)
|
||||
|
||||
params['polygon_list'] = available_layers
|
||||
|
||||
# 确认是否批量导出所有图层
|
||||
layer_count = len(available_layers)
|
||||
result = QMessageBox.question(
|
||||
self,
|
||||
"确认",
|
||||
f"数据源可用图层 {layer_count} 个,是否继续?",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No
|
||||
)
|
||||
|
||||
if result == QMessageBox.No:
|
||||
return
|
||||
|
||||
# 调用批量导出脚本
|
||||
self.script_runner.run_batch_export_map(params)
|
||||
|
||||
def validate_inputs(self, check_layer=True):
|
||||
"""验证输入参数"""
|
||||
if check_layer and self.layer_name_combo.currentIndex() < 0:
|
||||
QMessageBox.warning(self, "警告", "请选择一个图层")
|
||||
return False
|
||||
|
||||
if not self.county_name.text():
|
||||
QMessageBox.warning(self, "警告", "请输入区县名称")
|
||||
return False
|
||||
|
||||
if not self.template_aprx_file.text() or not os.path.exists(self.template_aprx_file.text()):
|
||||
QMessageBox.warning(self, "警告", "请选择有效的模板文件")
|
||||
return False
|
||||
|
||||
if not self.data_source_path.text() or not os.path.exists(self.data_source_path.text()):
|
||||
QMessageBox.warning(self, "警告", "请选择有效的数据源")
|
||||
return False
|
||||
|
||||
if not self.output_path.text():
|
||||
QMessageBox.warning(self, "警告", "请选择输出路径")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_export_params(self, include_layer=True):
|
||||
"""获取导出参数"""
|
||||
try:
|
||||
params = {
|
||||
'county_name': self.county_name.text(),
|
||||
'template_aprx_file': self.template_aprx_file.text(),
|
||||
'data_source_path': self.data_source_path.text(),
|
||||
'symbol_path': self.symbol_path.text(),
|
||||
'output_path': self.output_path.text(),
|
||||
'force_regenerate': self.force_regenerate.isChecked()
|
||||
}
|
||||
|
||||
if include_layer:
|
||||
params['layer_name'] = self.layer_name_combo.currentText()
|
||||
|
||||
return params
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"获取参数失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def load_settings(self):
|
||||
"""加载设置"""
|
||||
if not self.main_window:
|
||||
return
|
||||
|
||||
try:
|
||||
settings = self.main_window.settings
|
||||
last_paths = settings.get('last_paths', {})
|
||||
|
||||
if last_paths.get('config_file'):
|
||||
self.config_file_path.setText(last_paths.get('config_file', ''))
|
||||
if os.path.exists(last_paths.get('config_file', '')):
|
||||
self.load_export_config(last_paths.get('config_file', ''))
|
||||
|
||||
self.county_name.setText(last_paths.get('county_name', ''))
|
||||
self.template_aprx_file.setText(last_paths.get('template_aprx_file', ''))
|
||||
self.data_source_path.setText(last_paths.get('data_source_path', ''))
|
||||
self.symbol_path.setText(last_paths.get('symbol_path', ''))
|
||||
self.output_path.setText(last_paths.get('output_path', ''))
|
||||
|
||||
export_settings = settings.get('export_settings', {})
|
||||
|
||||
# 设置导出格式
|
||||
format_index = self.format_combo.findText(export_settings.get('default_format', 'PDF'))
|
||||
if format_index >= 0:
|
||||
self.format_combo.setCurrentIndex(format_index)
|
||||
|
||||
# 设置分辨率
|
||||
self.resolution_spinbox.setValue(export_settings.get('resolution', 300))
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"加载设置失败: {str(e)}")
|
||||
|
||||
def on_export_layout(self):
|
||||
"""仅导出布局按钮点击事件"""
|
||||
# 验证输入
|
||||
if not self.validate_inputs():
|
||||
return
|
||||
|
||||
# 获取参数
|
||||
export_params = self.get_export_params()
|
||||
if not export_params:
|
||||
return
|
||||
|
||||
# 检查导出配置和图层名称
|
||||
layer_name = self.layer_name_combo.currentText()
|
||||
if not hasattr(self, '_export_config') or layer_name not in self._export_config:
|
||||
QMessageBox.warning(self, "警告", "请先加载配置文件并选择图层")
|
||||
return
|
||||
|
||||
# 准备工程文件路径
|
||||
single_export_config = self._export_config.get(layer_name, {})
|
||||
temp_file_name = single_export_config['项目名称'].split('\n')[1]
|
||||
file_name = temp_file_name.replace('{区县占位符}', export_params['county_name'])
|
||||
aprx_path = os.path.join(export_params['output_path'], f"{file_name}.aprx")
|
||||
|
||||
# 检查工程文件是否存在
|
||||
if not os.path.exists(aprx_path):
|
||||
result = QMessageBox.question(
|
||||
self,
|
||||
"确认",
|
||||
f"工程文件不存在: {aprx_path}\n是否先生成工程文件?",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.Yes
|
||||
)
|
||||
|
||||
if result == QMessageBox.Yes:
|
||||
# 先生成工程文件
|
||||
# 添加配置文件路径参数
|
||||
export_params['config_file'] = self.config_file_path.text()
|
||||
# 执行生成工程文件
|
||||
self.script_runner.run_export_map(export_params)
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
# 准备导出布局参数
|
||||
layout_params = {
|
||||
'aprx_path': aprx_path,
|
||||
'output_path': export_params['output_path'],
|
||||
'export_format': self.format_combo.currentText(),
|
||||
'resolution': self.resolution_spinbox.value(),
|
||||
'output_name': file_name
|
||||
}
|
||||
|
||||
# 调用导出布局脚本
|
||||
self.script_runner.run_export_layout(layout_params)
|
||||
|
||||
def on_export_existing(self):
|
||||
"""导出已有工程按钮点击事件"""
|
||||
# 验证输出路径
|
||||
if not self.output_path.text() or not os.path.exists(self.output_path.text()):
|
||||
QMessageBox.warning(self, "警告", "请选择有效的输出路径")
|
||||
return
|
||||
|
||||
output_path = self.output_path.text()
|
||||
|
||||
# 查找所有aprx文件
|
||||
aprx_files = []
|
||||
for file in os.listdir(output_path):
|
||||
if file.lower().endswith('.aprx'):
|
||||
aprx_files.append(os.path.join(output_path, file))
|
||||
|
||||
if not aprx_files:
|
||||
QMessageBox.warning(self, "警告", f"在指定目录中未找到aprx文件: {output_path}")
|
||||
return
|
||||
|
||||
# 确认是否导出所有工程文件
|
||||
result = QMessageBox.question(
|
||||
self,
|
||||
"确认",
|
||||
f"将导出 {len(aprx_files)} 个工程文件的布局,是否继续?",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No
|
||||
)
|
||||
|
||||
if result == QMessageBox.No:
|
||||
return
|
||||
|
||||
# 准备批量导出布局参数
|
||||
batch_params = {
|
||||
'aprx_folder': output_path,
|
||||
'output_path': os.path.join(output_path, self.format_combo.currentText().lower()),
|
||||
'export_format': self.format_combo.currentText(),
|
||||
'resolution': self.resolution_spinbox.value()
|
||||
}
|
||||
|
||||
# 调用批量导出布局脚本
|
||||
self.script_runner.run_batch_export_layout(batch_params)
|
||||
|
||||
def log_message(self, message):
|
||||
"""日志输出"""
|
||||
if self.main_window and hasattr(self.main_window, 'log_signal'):
|
||||
self.main_window.log_signal.emit(message)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
def on_test_functions(self):
|
||||
"""测试功能按钮点击事件"""
|
||||
self.log_message("正在执行测试功能...")
|
||||
|
||||
try:
|
||||
# 调用测试脚本
|
||||
test_params = {
|
||||
'message': "测试成功!这是来自脚本的消息。",
|
||||
'count': 1
|
||||
}
|
||||
|
||||
# 使用script_runner的run_test_script方法
|
||||
if hasattr(self.script_runner, 'run_test_script'):
|
||||
self.script_runner.run_test_script(test_params)
|
||||
self.log_message("测试脚本调用已启动,请查看日志")
|
||||
else:
|
||||
# 如果没有run_test_script方法,尝试调用test_script.py
|
||||
script_path = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'core', 'test_script.py'))
|
||||
if os.path.exists(script_path):
|
||||
self.log_message(f"找到测试脚本: {script_path}")
|
||||
args = {
|
||||
'message': test_params['message'],
|
||||
'count': test_params['count']
|
||||
}
|
||||
self.script_runner.run_script(script_path, args)
|
||||
else:
|
||||
QMessageBox.information(self, "测试", "找不到测试脚本: test_script.py")
|
||||
self.log_message(f"找不到测试脚本: {script_path}")
|
||||
except Exception as e:
|
||||
self.log_message(f"测试功能调用失败: {str(e)}")
|
||||
QMessageBox.critical(self, "错误", f"测试功能调用失败: {str(e)}")
|
||||
496
tools/ui/tabs/xlsx_jpg_tab.py
Normal file
496
tools/ui/tabs/xlsx_jpg_tab.py
Normal file
@@ -0,0 +1,496 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
栅格处理界面: 提供栅格重分类、栅格转矢量和小面积图斑消除的界面操作
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import arcpy
|
||||
import traceback
|
||||
import multiprocessing
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QFileDialog, QGridLayout, QCheckBox,
|
||||
QGroupBox, QMessageBox, QSpinBox)
|
||||
|
||||
from tools.core.utils import common_utils
|
||||
from tools.ui.runners.script_runner import ScriptRunner
|
||||
from .config_editor_dialog import ConfigEditorDialogVisual
|
||||
from ..components.file_list_group import FileListGroup
|
||||
|
||||
|
||||
class XlsxToJpgTab(QWidget):
|
||||
"""栅格处理窗口部件类"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(XlsxToJpgTab, self).__init__(parent)
|
||||
self.main_window = parent
|
||||
self.init_ui()
|
||||
self.setWindowTitle("面积统计")
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化用户界面"""
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# 批量处理区域
|
||||
self.batch_mode_group = QGroupBox("批量处理设置")
|
||||
batch_layout = QGridLayout()
|
||||
|
||||
# 配置文件选择 (公用)
|
||||
batch_layout.addWidget(QLabel("配置文件:"), 0, 0)
|
||||
self.config_file_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.config_file_edit, 0, 1)
|
||||
self.browse_config_btn = QPushButton("浏览...")
|
||||
self.browse_config_btn.clicked.connect(self.browse_config_file)
|
||||
batch_layout.addWidget(self.browse_config_btn, 0, 2)
|
||||
|
||||
# 添加编辑配置文件的按钮 <--- Add this button
|
||||
self.edit_config_btn = QPushButton("编辑配置内容...")
|
||||
self.edit_config_btn.setEnabled(False)
|
||||
self.edit_config_btn.clicked.connect(self.open_config_editor)
|
||||
batch_layout.addWidget(self.edit_config_btn, 0, 3)
|
||||
|
||||
# 输入文件夹
|
||||
batch_layout.addWidget(QLabel("重分类面要素:"), 1, 0)
|
||||
self.input_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.input_folder_edit, 1, 1, 1, 2)
|
||||
self.browse_input_folder_btn = QPushButton("浏览...")
|
||||
self.browse_input_folder_btn.clicked.connect(self.browse_input_folder)
|
||||
batch_layout.addWidget(self.browse_input_folder_btn, 1, 3)
|
||||
|
||||
# 批量输出文件夹
|
||||
batch_layout.addWidget(QLabel("输出文件夹:"), 2, 0)
|
||||
self.batch_output_folder_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.batch_output_folder_edit, 2, 1, 1, 2)
|
||||
self.browse_batch_output_btn = QPushButton("浏览...")
|
||||
self.browse_batch_output_btn.clicked.connect(self.browse_batch_output_folder)
|
||||
batch_layout.addWidget(self.browse_batch_output_btn, 2, 3)
|
||||
|
||||
# 选择乡镇界线
|
||||
batch_layout.addWidget(QLabel("乡镇界线:"), 3, 0)
|
||||
self.xzq_features_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.xzq_features_edit, 3, 1, 1, 2)
|
||||
self.xzq_features_btn = QPushButton("浏览")
|
||||
self.xzq_features_btn.clicked.connect(self.browse_xzq_features)
|
||||
batch_layout.addWidget(self.xzq_features_btn, 3, 3)
|
||||
|
||||
# 选择地类图斑
|
||||
batch_layout.addWidget(QLabel("地类图斑:"), 4, 0)
|
||||
self.dltb_features_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.dltb_features_edit, 4, 1, 1, 2)
|
||||
self.dltb_features_btn = QPushButton("浏览")
|
||||
self.dltb_features_btn.clicked.connect(self.browse_dltb_features)
|
||||
batch_layout.addWidget(self.dltb_features_btn, 4, 3)
|
||||
|
||||
# 输入行政区名称
|
||||
batch_layout.addWidget(QLabel("行政区名称:"), 5, 0)
|
||||
self.xzqmc_edit = QLineEdit()
|
||||
batch_layout.addWidget(self.xzqmc_edit, 5, 1, 1, 2)
|
||||
|
||||
# 单选框
|
||||
batch_layout.addWidget(QLabel("是否同时平差行政区和地类:"), 6, 0)
|
||||
self.is_adjust_xzq_and_landuse_checkbox = QCheckBox()
|
||||
batch_layout.addWidget(self.is_adjust_xzq_and_landuse_checkbox, 6, 1)
|
||||
|
||||
self.batch_mode_group.setLayout(batch_layout)
|
||||
|
||||
# 文件列表显示组
|
||||
self.file_list_group = FileListGroup(self, "选择要处理的栅格")
|
||||
self.file_list_group.load_files.connect(self.on_load_raster)
|
||||
|
||||
# --- 处理参数设置 ---
|
||||
param_group = QGroupBox("处理参数")
|
||||
param_layout = QVBoxLayout(param_group)
|
||||
|
||||
# 多进程设置
|
||||
export_layout = QHBoxLayout()
|
||||
mutiprocess_layout = QHBoxLayout()
|
||||
mutiprocess_layout.addWidget(QLabel("使用多进程导出:"))
|
||||
self.mutiprocess_check = QCheckBox()
|
||||
self.mutiprocess_check.setChecked(True)
|
||||
mutiprocess_layout.addWidget(self.mutiprocess_check)
|
||||
mutiprocess_layout.addStretch()
|
||||
export_layout.addLayout(mutiprocess_layout)
|
||||
|
||||
# 进程数量设置
|
||||
process_count_layout = QHBoxLayout()
|
||||
process_count_layout.addWidget(QLabel("进程数量:"))
|
||||
self.process_count = QSpinBox()
|
||||
self.process_count.setRange(1, multiprocessing.cpu_count())
|
||||
self.process_count.setValue(max(1, multiprocessing.cpu_count() - 1)) # 默认使用CPU核心数-1
|
||||
self.process_count.setFixedWidth(180)
|
||||
process_count_layout.addWidget(self.process_count)
|
||||
|
||||
export_layout.addLayout(process_count_layout)
|
||||
param_layout.addLayout(export_layout)
|
||||
|
||||
# 操作按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
self.process_btn = QPushButton("开始处理")
|
||||
self.process_btn.clicked.connect(self.on_start_processing)
|
||||
|
||||
self.cancel_btn = QPushButton("取消")
|
||||
# self.cancel_btn.clicked.connect(self.close)
|
||||
|
||||
btn_layout.addWidget(self.process_btn)
|
||||
btn_layout.addWidget(self.cancel_btn)
|
||||
|
||||
# 添加所有组件到主布局
|
||||
main_layout.addWidget(self.batch_mode_group)
|
||||
main_layout.addWidget(self.file_list_group)
|
||||
main_layout.addWidget(param_group)
|
||||
main_layout.addLayout(btn_layout)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def browse_input_folder(self):
|
||||
"""浏览选择输入文件夹"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择栅格文件夹")
|
||||
if folder_path:
|
||||
self.input_folder_edit.setText(folder_path)
|
||||
self.on_load_raster()
|
||||
|
||||
def browse_batch_output_folder(self):
|
||||
"""浏览选择批量处理输出文件夹"""
|
||||
folder_path = QFileDialog.getExistingDirectory(self, "选择批量处理输出文件夹")
|
||||
if folder_path:
|
||||
self.batch_output_folder_edit.setText(folder_path)
|
||||
|
||||
def browse_config_file(self):
|
||||
"""浏览选择配置文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择配置文件", "",
|
||||
"JSON 文件 (*.json);;所有文件 (*)"
|
||||
)
|
||||
if file_path:
|
||||
self.config_file_edit.setText(file_path)
|
||||
self.validate_config_file(file_path)
|
||||
self.edit_config_btn.setEnabled(True)
|
||||
def browse_xzq_features(self):
|
||||
"""浏览选择裁剪面 (支持 shapefile)"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择裁剪要素 (Shapefile)", "",
|
||||
"Shapefile 文件 (*.shp);;所有文件 (*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
if file_path.lower().endswith(".shp"):
|
||||
self.xzq_features_edit.setText(file_path)
|
||||
self.log_message(f"已选择 Shapefile: {file_path}")
|
||||
|
||||
else:
|
||||
QMessageBox.warning(self, "选择错误", "不支持的文件类型。请选择 .shp 文件。")
|
||||
self.xzq_features_edit.clear()
|
||||
|
||||
def browse_dltb_features(self):
|
||||
"""浏览选择裁剪面 (支持 shapefile)"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择裁剪要素 (Shapefile)", "",
|
||||
"Shapefile 文件 (*.shp);;所有文件 (*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
if file_path.lower().endswith(".shp"):
|
||||
self.dltb_features_edit.setText(file_path)
|
||||
self.log_message(f"已选择 Shapefile: {file_path}")
|
||||
|
||||
else:
|
||||
QMessageBox.warning(self, "选择错误", "不支持的文件类型。请选择 .shp 文件。")
|
||||
self.dltb_features_edit.clear()
|
||||
|
||||
def validate_config_file(self, config_file_path):
|
||||
"""对文件进行快速检查,以查看它是否类似于预期的配置结构。"""
|
||||
if not os.path.exists(config_file_path):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(config_file_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
if "export_config" in config and isinstance(config["export_config"], dict):
|
||||
if config["export_config"]:
|
||||
first_item_value = next(iter(config["export_config"].values()))
|
||||
if isinstance(first_item_value, dict) and all(k in first_item_value for k in ["项目名称", "标准等级"]):
|
||||
self.log_message(f"配置文件 '{os.path.basename(config_file_path)}' 加载成功。")
|
||||
return True
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 内容结构可能不匹配预期。")
|
||||
return False
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 中的 'export_config' 部分为空。")
|
||||
return False
|
||||
else:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 缺少 'export_config' 键或格式不正确。")
|
||||
return False
|
||||
|
||||
except json.JSONDecodeError:
|
||||
self.log_message(f"警告: 配置文件 '{os.path.basename(config_file_path)}' 不是有效的 JSON 格式。")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.log_message(f"警告: 检查配置文件 '{os.path.basename(config_file_path)}' 时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def open_config_editor(self):
|
||||
"""打开配置文件编辑器对话框"""
|
||||
config_path = self.config_file_edit.text()
|
||||
|
||||
if not config_path:
|
||||
reply = QMessageBox.question(self, "编辑配置", "没有选择配置文件,是否创建一个新配置文件?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||
if reply == QMessageBox.StandardButton.No:
|
||||
return
|
||||
|
||||
config_path = ""
|
||||
|
||||
# 使用配置编辑对话框
|
||||
dialog = ConfigEditorDialogVisual(config_path, self)
|
||||
|
||||
if dialog.exec() == ConfigEditorDialogVisual.DialogCode.Accepted:
|
||||
new_path = dialog.get_new_config_path()
|
||||
if new_path and new_path != self.config_file_edit.text():
|
||||
self.config_file_edit.setText(new_path)
|
||||
self.validate_config_file(new_path)
|
||||
|
||||
def on_load_raster(self):
|
||||
"""加载图层列表"""
|
||||
try:
|
||||
data_source_paths = self.input_folder_edit.text()
|
||||
|
||||
self.file_list_group.load_file_btn.setEnabled(False)
|
||||
|
||||
# 清空列表
|
||||
self.file_list_group.file_list.clear()
|
||||
|
||||
if not data_source_paths or not arcpy.Exists(data_source_paths):
|
||||
self.log_message(f"输入路径不存在或不是有效的ArcGIS工作空间/文件夹: {data_source_paths}")
|
||||
return
|
||||
|
||||
arcpy.env.workspace = data_source_paths
|
||||
|
||||
# 获取所有SHP 文件
|
||||
shp_files = arcpy.ListFeatureClasses("*.shp")
|
||||
if shp_files:
|
||||
for raster in shp_files:
|
||||
self.file_list_group.file_list.addItem(raster)
|
||||
|
||||
self.log_message(f"已加载 {self.file_list_group.file_list.count()} 个图层")
|
||||
|
||||
else:
|
||||
self.log_message(f"文件夹 '{os.path.basename(data_source_paths)}' 中没有找到栅格图层。")
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"加载图层列表失败: {str(e)}")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
self.file_list_group.load_file_btn.setEnabled(True)
|
||||
|
||||
def validate_inputs(self):
|
||||
"""验证输入参数"""
|
||||
# 验证配置文件路径
|
||||
config_path = self.config_file_edit.text()
|
||||
if not config_path or not os.path.exists(config_path):
|
||||
QMessageBox.warning(self, "输入错误", "请选择有效的配置文件")
|
||||
return False
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config_data = json.load(f)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"无法加载或解析配置文件: {e}")
|
||||
return False
|
||||
|
||||
if "export_config" not in config_data or not isinstance(config_data["export_config"], dict):
|
||||
QMessageBox.warning(self, "输入错误", "配置文件的内容无效或缺少 'export_config' 部分。")
|
||||
return False
|
||||
|
||||
standards_dict = config_data["export_config"]
|
||||
|
||||
if not standards_dict:
|
||||
QMessageBox.warning(self, "输入错误", "配置文件中的 'export_config' 部分为空,没有定义任何处理参数。")
|
||||
return False
|
||||
|
||||
# 验证'标准等级'用于配置重分类表
|
||||
error_keys_remap = []
|
||||
for key, value in standards_dict.items():
|
||||
try:
|
||||
levels_data = value.get("标准等级", {})
|
||||
if not isinstance(levels_data, dict):
|
||||
error_keys_remap.append(f"{key} ('标准等级' 格式不正确)")
|
||||
continue
|
||||
if not common_utils.create_remap_table(levels_data):
|
||||
error_keys_remap.append(f"{key} ('标准等级' 内容无效)")
|
||||
except Exception:
|
||||
error_keys_remap.append(f"{key} (验证出错)")
|
||||
|
||||
if error_keys_remap:
|
||||
QMessageBox.warning(self, "输入错误", "配置文件中部分参数的 '标准等级' 数据无效:\n" + "\n".join(error_keys_remap))
|
||||
return False
|
||||
|
||||
# 验证输入、输出文件夹
|
||||
if not self.input_folder_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请选择输入栅格文件夹")
|
||||
return False
|
||||
|
||||
if not self.batch_output_folder_edit.text():
|
||||
QMessageBox.warning(self, "输入错误", "请选择批量处理输出文件夹")
|
||||
return False
|
||||
|
||||
clip_path = self.xzq_features_edit.text()
|
||||
if not clip_path or not arcpy.Exists(clip_path):
|
||||
QMessageBox.warning(self, "输入错误", "已启用裁剪,但裁剪要素路径无效或不存在")
|
||||
return False
|
||||
|
||||
# 3. 验证乡镇界限
|
||||
clip_path = self.xzq_features_edit.text()
|
||||
if not clip_path:
|
||||
QMessageBox.warning(self, "输入错误", "已启用裁剪,但裁剪要素路径为空。")
|
||||
return False
|
||||
|
||||
# Use arcpy.Exists and Describe to validate the path regardless of whether it's SHP or GDB/FeatureClass
|
||||
if not arcpy.Exists(clip_path):
|
||||
QMessageBox.warning(self, "输入错误", f"已启用裁剪,但裁剪要素路径不存在:\n{clip_path}")
|
||||
return False
|
||||
|
||||
# Optional: Check if the path points to a Feature Class (SHP or inside GDB)
|
||||
try:
|
||||
desc = arcpy.Describe(clip_path)
|
||||
if desc.dataType != 'FeatureClass' and desc.dataType != 'ShapeFile':
|
||||
QMessageBox.warning(self, "输入错误", f"已启用裁剪,但选择的路径不是一个要素类:\n{clip_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "输入错误", f"无法描述裁剪要素路径,请检查是否为有效要素类:\n{clip_path}\n错误: {e}")
|
||||
return False
|
||||
|
||||
# 验证是否选择文件
|
||||
selected_files = [file.text() for file in self.file_list_group.file_list.selectedItems()]
|
||||
if not selected_files:
|
||||
QMessageBox.warning(self, "输入错误", "请在栅格列表中勾选要处理的文件。")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def on_start_processing(self):
|
||||
"""开始处理栅格数据"""
|
||||
if not self.validate_inputs():
|
||||
return
|
||||
|
||||
try:
|
||||
if self.main_window and hasattr(self.main_window, 'save_settings'):
|
||||
self.main_window.update_settings()
|
||||
self.main_window.save_settings()
|
||||
settings_file = self.main_window.settings_file
|
||||
|
||||
# 创建的导出线程
|
||||
self.script_runner = ScriptRunner()
|
||||
|
||||
# 连接ScriptRunner的信号
|
||||
self.script_runner.task_started.connect(self.on_script_started)
|
||||
self.script_runner.task_finished.connect(self.on_script_finished)
|
||||
self.script_runner.task_error.connect(self.on_script_error)
|
||||
self.script_runner.task_log.connect(self.on_script_log)
|
||||
self.script_runner.manager_log.connect(self.log_message)
|
||||
|
||||
# 获取公共参数 (从 VectorParamsWidget 获取)
|
||||
selected_files = [file.text() for file in self.file_list_group.file_list.selectedItems()]
|
||||
|
||||
if self.mutiprocess_check.isChecked():
|
||||
self.script_runner.set_max_concurrent(self.process_count.value())
|
||||
else:
|
||||
self.script_runner.set_max_concurrent(1)
|
||||
|
||||
# 调用批量处理脚本
|
||||
for raster_file in selected_files:
|
||||
params = {
|
||||
"settings_path": settings_file,
|
||||
"reclassed_polygon": raster_file,
|
||||
}
|
||||
|
||||
# 统计面积
|
||||
task_id = self.script_runner.run_area_stat(params)
|
||||
# if task_id:
|
||||
# self.log_message(f"[GUI] 任务 {task_id} 处理 {raster_file} 已添加到列队")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"处理过程中出错: {str(e)}"
|
||||
QMessageBox.critical(self, "处理错误", error_msg)
|
||||
self.log_message(error_msg)
|
||||
|
||||
def get_area_stat_settings(self):
|
||||
"""保存当前配置到JSON文件"""
|
||||
|
||||
config = {
|
||||
"input_folder": self.input_folder_edit.text(),
|
||||
"batch_output_folder": self.batch_output_folder_edit.text(),
|
||||
"config_file_path": self.config_file_edit.text(),
|
||||
"xzq_features": self.xzq_features_edit.text(),
|
||||
"dltb_features": self.dltb_features_edit.text(),
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
def load_settings(self, settings):
|
||||
"""从字典加载配置"""
|
||||
try:
|
||||
# 批处理参数
|
||||
self.input_folder_edit.setText(settings.get("input_folder", ""))
|
||||
self.batch_output_folder_edit.setText(settings.get("batch_output_folder", ""))
|
||||
|
||||
# 配置文件路径和加载
|
||||
config_file_path = settings.get("config_file_path", "")
|
||||
self.config_file_edit.setText(config_file_path)
|
||||
self.edit_config_btn.setEnabled(bool(config_file_path) and os.path.exists(config_file_path))
|
||||
|
||||
if config_file_path:
|
||||
self.validate_config_file(config_file_path)
|
||||
|
||||
self.xzq_features_edit.setText(settings.get("xzq_features", ""))
|
||||
self.dltb_features_edit.setText(settings.get("dltb_features", ""))
|
||||
|
||||
if self.input_folder_edit.text():
|
||||
self.on_load_raster()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "加载配置错误", f"加载配置时出错: {str(e)}")
|
||||
return False
|
||||
|
||||
def set_buttons_enabled(self, enabled):
|
||||
"""设置按钮是否可用"""
|
||||
self.process_btn.setEnabled(enabled)
|
||||
|
||||
def on_script_started(self, task_id, task_description):
|
||||
"""脚本开始执行"""
|
||||
self.set_buttons_enabled(False)
|
||||
self.log_message(f"{task_id}: 正在运行 - {task_description}")
|
||||
|
||||
def on_script_finished(self, task_id:str, success:bool, message:str):
|
||||
"""脚本执行完成"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message("脚本执行完成")
|
||||
|
||||
def on_script_error(self,task_id, error_msg):
|
||||
"""脚本执行出错"""
|
||||
self.set_buttons_enabled(True)
|
||||
self.log_message(f"错误:{task_id}-{error_msg}")
|
||||
# QMessageBox.critical(self, "错误", error_msg)
|
||||
|
||||
def on_script_log(self, task_id, message):
|
||||
"""脚本输出日志"""
|
||||
self.log_message(f"{task_id}: {message}")
|
||||
|
||||
def log_message(self, message):
|
||||
"""日志输出"""
|
||||
if self.main_window and hasattr(self.main_window, 'log_signal'):
|
||||
self.main_window.log_signal.emit(message)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
import sys
|
||||
app = QApplication(sys.argv)
|
||||
window = XlsxToJpgTab()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
Reference in New Issue
Block a user