# 生成完整的exactextract面积加权计算Python脚本 # -*- coding: utf-8 -*- """ 土壤属性栅格数据面积加权统计脚本 基于exactextract库实现多属性栅格的面积加权平均值计算 最终输出格式与土壤属性图斑数据表一致 """ import re import sys import exactextract as ee import geopandas as gpd import rasterio import pandas as pd import numpy as np from pathlib import Path import warnings from pyproj import CRS warnings.filterwarnings('ignore') project_root = Path(__file__).parent.parent.parent sys.path.insert(0, str(project_root)) import app def init_logger(): """初始化日志输出,便于跟踪处理过程""" import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) return logging.getLogger(__name__) def validate_data(vector_path, raster_files): """ 验证输入数据的有效性 :param vector_path: 矢量图斑文件路径 :param raster_files: 栅格文件字典 :return: 验证结果(布尔值) """ logger = init_logger() # 验证矢量文件是否存在 # if not Path(vector_path).exists(): # logger.error(f"矢量文件不存在:{vector_path}") # return False # 验证栅格文件是否存在 for attr_name, raster_path in raster_files.items(): if not Path(raster_path).exists(): logger.error(f"栅格文件不存在:{attr_name} -> {raster_path}") return False # 验证矢量文件格式 try: # gdf = gpd.read_file(vector_path) gdf = app.readers.read_vector(vector_path) if gdf.geometry.type.unique()[0] != 'Polygon' and gdf.geometry.type.unique()[0] != 'MultiPolygon': logger.error("矢量文件必须是Polygon类型(面要素)") return False except Exception as e: logger.error(f"矢量文件读取失败:{str(e)}") return False # 验证栅格文件格式 try: test_raster = next(iter(raster_files.values())) with rasterio.open(test_raster) as src: if src.count != 1: logger.error("每个栅格文件必须是单波段(每个属性单独一个栅格)") return False except Exception as e: logger.error(f"栅格文件读取失败:{str(e)}") return False logger.info("所有输入数据验证通过") return True def extract_epsg(crs_obj): """ 终极EPSG提取器:从正常方法到暴力正则 """ if crs_obj is None: return None # 1. 尝试常规方法 epsg = crs_obj.to_epsg() if epsg: return str(epsg) # 2. 降低匹配严格度尝试 (pyproj 内置的容错方法) epsg_loose = crs_obj.to_epsg(min_confidence=25) if epsg_loose: return str(epsg_loose) # 3. 终极必杀:正则匹配 WKT 文本 try: wkt_str = crs_obj.to_wkt() # 匹配文本中的 AUTHORITY["EPSG","4523"] 或 ID["EPSG", 4523] matches = re.findall(r'(?:AUTHORITY|ID)\["EPSG",\s*"?(\d+)"?\]', wkt_str) if matches: # WKT中会有多个EPSG(比如单位、椭球体),最后一个通常才是整个坐标系的EPSG return matches[-1] except Exception as e: pass return None def standardize_crs(gdf, raster_path): """ 标准化矢量与栅格的坐标参考系统(CRS) """ logger = init_logger() # 请确保你有 init_logger with rasterio.open(raster_path) as src: # 兼容旧版本 rasterio 的读取方式 raster_crs = CRS.from_user_input(src.crs) vector_crs = CRS.from_user_input(gdf.crs) # 使用终极提取器获取 EPSG v_epsg = extract_epsg(vector_crs) r_epsg = extract_epsg(raster_crs) print(f"当前矢量CRS EPSG:{v_epsg}") print(f"目标栅格CRS EPSG:{r_epsg}") # 判断逻辑:先看数学是否相等,再看强制提取的EPSG编号是否相等 is_same_crs = False if vector_crs.equals(raster_crs): is_same_crs = True elif v_epsg and r_epsg and v_epsg == r_epsg: is_same_crs = True # 执行转换(若真的不同才转) if not is_same_crs: logger.warning("矢量与栅格CRS不一致,正在进行转换...") gdf = gdf.to_crs(raster_crs) new_epsg = extract_epsg(CRS.from_user_input(gdf.crs)) logger.info(f"CRS转换完成,新矢量CRS EPSG:{new_epsg}") else: logger.info(f"矢量与栅格CRS一致 (均判定为 EPSG:{v_epsg}),无需转换。") return gdf def calculate_area_weighted_stats(vector_path, raster_files, output_path): """ 核心函数:计算面积加权统计值 :param vector_path: 矢量图斑文件路径 :param raster_files: 栅格文件字典(键:属性名,值:栅格路径) :param output_path: 结果输出路径(Excel文件) :return: 统计结果DataFrame """ logger = init_logger() logger.info("开始面积加权统计计算") # 1. 加载矢量数据 logger.info(f"加载矢量数据:{vector_path}") gdf = app.readers.read_vector(vector_path) # 2. 标准化CRS test_raster = next(iter(raster_files.values())) gdf = standardize_crs(gdf, test_raster) # 中断程序 # sys.exit(0) # 3. 初始化结果DataFrame(保留矢量中的关键属性) logger.info("初始化结果数据结构") # 基础字段列表(与土壤属性图斑表格式对齐) # TODO base_fields = ["BSM","YSDM","TBBH","DLBM", "DLMC","TL", "YL", "TS", "TZ","ZLDWDM","ZLDWMC","TBMJ","geometry"] # 检查矢量中是否包含必要字段,若不包含则创建空字段 result_gdf = gpd.GeoDataFrame() for field in base_fields: if field in gdf.columns: result_gdf[field] = gdf[field] else: result_gdf[field] = np.nan logger.warning(f"矢量中缺少'{field}'字段,将生成空值") # 设置几何列以保持GeoDataFrame result_gdf = gpd.GeoDataFrame(result_gdf, geometry=gdf.geometry) # 4. 计算图斑面积(转换为亩) # logger.info("计算图斑面积(单位:亩)") # # 计算平方米面积(根据CRS单位自动适应) # gdf["area_sqm"] = gdf.geometry.area # # 转换为亩(1亩 ≈ 666.6667平方米) # result_gdf["面积亩"] = gdf["area_sqm"] * 0.0015 # # 保留6位小数,与示例数据格式一致 # result_gdf["面积亩"] = result_gdf["面积亩"].round(6) # 5. 对每个土壤属性进行面积加权平均计算 logger.info("开始处理土壤属性栅格(面积加权平均)") for attr_idx, (attr_name, raster_path) in enumerate(raster_files.items(), 1): total_attrs = len(raster_files) logger.info(f"处理进度:{attr_idx}/{total_attrs} - 属性:{attr_name}") try: # 使用exactextract计算面积加权平均 # weights="area":按矢量与栅格的交集面积进行加权,如果是TRZD等分类属性,则不使用面积加权,直接计算众数 if attr_name in ["TRZD"]: stats = ee.exact_extract( raster_path, gdf, ["majority"], # 计算众数 output="pandas" # 输出为DataFrame格式 ) else: stats = ee.exact_extract( raster_path, gdf, ["mean"], # 计算平均值 output="pandas" # 输出为DataFrame格式 ) # 将统计结果添加到结果DataFrame if stats is None: logger.warning(f"{attr_name}计算结果为空,可能无交集区域") result_gdf[attr_name] = np.nan continue else: # 确保 stats 为 pandas.DataFrame,以便使用字符串索引 if not isinstance(stats, pd.DataFrame): stats = pd.DataFrame(stats) # 保留4位小数,确保数据精度 if attr_name in ["TRZD"]: result_gdf[attr_name] = stats["majority"] else: result_gdf[attr_name] = stats["mean"].round(2) # 处理可能的空值(无交集区域) if result_gdf[attr_name].isnull().sum() > 0: null_count = result_gdf[attr_name].isnull().sum() logger.warning(f"{attr_name}存在{null_count}个空值(图斑与栅格无交集)") # 用0填充空值(可根据业务需求调整) result_gdf[attr_name] = result_gdf[attr_name].fillna(0) except Exception as e: logger.error(f"{attr_name}处理失败:{str(e)}") # 失败时填充空值,避免整个程序崩溃 result_gdf[attr_name] = np.nan # 6. 添加属性分级字段(根据业务规则实现) logger.info("添加土壤属性分级字段") result_gdf = add_attribute_classification(result_gdf) # 7. 整理最终字段顺序(与示例表格完全对齐) logger.info("整理输出字段顺序") # TODO final_columns = [ "BSM","YSDM","TBBH","DLBM", "DLMC","TL", "YL", "TS", "TZ","ZLDWDM","ZLDWMC","TBMJ", "SL", "FL", "NL", "TRZD", "GZCHD", "TRRZ", "SWXDT", "DBLSFD", "PH", "CEC", "OM", "TN", "TP", "TK", "AP", "AK", "ECA", "EMG", "AS1", "AFE", "AMN", "ACU","AZN","AB","AMO", "SL_DJ", "FL_DJ", "NL_DJ", "TRZD_DJ", "GZCHD_DJ", "TRRZ_DJ", "SWXDT_DJ", "DBLSFD_DJ", "PH_DJ", "CEC_DJ", "OM_DJ", "TN_DJ", "TP_DJ", "TK_DJ", "AP_DJ", "AK_DJ", "ECA_DJ", "EMG_DJ", "AS1_DJ", "AFE_DJ", "AMN_DJ", "ACU_DJ","AZN_DJ","AB_DJ","AMO_DJ", "SL_DJZY", "FL_DJZY", "NL_DJZY", "TRZD_DJZY", "GZCHD_DJZY", "TRRZ_DJZY", "SWXDT_DJZY", "DBLSFD_DJZY", "PH_DJZY", "CEC_DJZY", "OM_DJZY", "TN_DJZY", "TP_DJZY", "TK_DJZY", "AP_DJZY", "AK_DJZY", "ECA_DJZY", "EMG_DJZY", "AS1_DJZY", "AFE_DJZY", "AMN_DJZY", "ACU_DJZY","AZN_DJZY","AB_DJZY","AMO_DJZY", "geometry" # 保留几何列以保持GeoDataFrame ] # 补充缺失的字段(如乡代码等) for col in final_columns: if col not in result_gdf.columns: result_gdf[col] = np.nan # 按最终顺序排列字段 result_gdf = result_gdf[final_columns] # 8. 导出结果到Excel logger.info(f"导出结果到:{output_path}") # 使用openpyxl引擎支持.xlsx格式 # result_gdf.to_excel(output_path, index=False, engine="openpyxl") result_gdf.to_file(output_path, index=False, driver="ESRI Shapefile") # 导出为GeoPackage格式,保留空间信息 logger.info(f"结果导出完成,共生成{len(result_gdf)}条记录") return result_gdf def add_attribute_classification(df): """ 添加土壤属性分级字段(根据常见土壤分类标准实现) 可根据实际业务需求调整分级阈值 :param df: 包含原始属性的DataFrame :return: 包含分级字段和分级值域的DataFrame """ # todo # 1. 酸碱度分级(pH值) - 按PH标准 def classify_ph(ph): """ 土壤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 """ if ph > 8.5 or ph <= 4.5: return 5 # 等级五: >8.5, ≤4.5 elif (8.0 < ph <= 8.5) or (4.5 < ph <= 5.0): return 4 # 等级四: 8.0~8.5, 4.5~5.0 elif (7.5 < ph <= 8.0) or (5.0 < ph <= 5.5): return 3 # 等级三: 7.5~8.0, 5.0~5.5 elif (7.0 < ph <= 7.5) or (5.5 < ph <= 6.0): return 2 # 等级二: 7.0~7.5, 5.5~6.0 elif 6.0 < ph <= 7.0: return 1 # 等级一: 6.0~7.0 else: return None # 异常值 # 2. 有机质分级(单位:g/kg) - 按OM标准 def classify_organic(organic): """ 土壤有机质分级(第三次全国土壤普查标准) 标准等级: 等级一: >35.0 等级二: 25.0~35.0 等级三: 15.0~25.0 等级四: 10.0~15.0 等级五: ≤10.0 """ if organic > 35.0: return 1 # 等级一: >35.0 elif 30.0 < organic <= 35.0: return 2 # 等级二: 25.0~35.0 elif 20.0 < organic <= 30.0: return 3 # 等级三: 15.0~25.0 elif 10.0 < organic <= 20.0: return 4 # 等级四: 10.0~15.0 elif organic <= 10.0: return 5 # 等级五: ≤10.0 else: return None # 异常值 # 3. 阳离子交换量分级(单位:cmol/kg) - 按CEC标准 def classify_cation(cation): """ 土壤阳离子交换量分级(第三次全国土壤普查标准) 标准等级: 等级一: >20.0 等级二: 15.0~20.0 等级三: 10.0~15.0 等级四: 5.0~10.0 等级五: ≤5.0 """ if cation > 20.0: return 1 # 等级一: >20.0 elif 15.0 < cation <= 20.0: return 2 # 等级二: 15.0~20.0 elif 10.0 < cation <= 15.0: return 3 # 等级三: 10.0~15.0 elif 5.0 < cation <= 10.0: return 4 # 等级四: 5.0~10.0 elif cation <= 5.0: return 5 # 等级五: ≤5.0 else: return None # 异常值 # 4. 有效磷分级(单位:mg/kg) - 按AP标准 def classify_available_p(p): """ 土壤有效磷分级(第三次全国土壤普查标准) 标准等级: 等级一: >40.0 等级二: 20.0~40.0 等级三: 10.0~20.0 等级四: 5.0~10.0 等级五: ≤5.0 """ if p > 40.0: return 1 # 等级一: >40.0 elif 20.0 < p <= 40.0: return 2 # 等级二: 25.0~40.0 elif 10.0 < p <= 20.0: return 3 # 等级三: 15.0~25.0 elif 5.0 < p <= 10.0: return 4 # 等级四: 5.0~15.0 elif p <= 5.0: return 5 # 等级五: ≤5.0 else: return None # 异常值 # 5. 速效钾分级(单位:mg/kg) - 按AK标准 def classify_available_k(k): """ 土壤速效钾分级(第三次全国土壤普查标准) 标准等级: 等级一: >150 等级二: 100~150 等级三: 75~100 等级四: 50~75 等级五: ≤50 """ if k > 150: return 1 # 等级一: >150 elif 100 < k <= 150: return 2 # 等级二: 100~150 elif 75 < k <= 100: return 3 # 等级三: 75~100 elif 50 < k <= 75: return 4 # 等级四: 50~75 elif k <= 50: return 5 # 等级五: ≤50 else: return None # 异常值 # 6. 耕层厚度分级(单位:cm) - 按GZCHD标准 def classify_soil_depth(depth): """ 土壤耕作层厚度分级(第三次全国土壤普查标准) 标准等级: 等级一: >25.0 等级二: 20.0~25.0 等级三: 15.0~20.0 等级四: 10.0~15.0 等级五: ≤10.0 """ if depth > 25.0: return 1 # 等级一: >25.0 elif 20.0 < depth <= 25.0: return 2 # 等级二: 20.0~25.0 elif 15.0 < depth <= 20.0: return 3 # 等级三: 15.0~20.0 elif 10.0 < depth <= 15.0: return 4 # 等级四: 10.0~15.0 elif depth <= 10.0: return 5 # 等级五: ≤10.0 else: return None # 异常值 # 7. 土壤容重分级(单位:g/cm³) - 按TRRZ标准 def classify_bulk_density(density): """ 土壤容重分级(第三次全国土壤普查标准) 标准等级: 等级一: 1.00~1.20 等级二: 1.20~1.30 等级三: 1.30~1.40, 0.90~1.00 等级四: 1.40~1.50 等级五: >1.50, ≤0.90 """ if 1.00 < density <= 1.20: return 1 # 等级一: 1.00~1.20 elif 1.20 < density <= 1.30: return 2 # 等级二: 1.20~1.30 elif (1.30 < density <= 1.40) or (0.90 < density <= 1.00): return 3 # 等级三: 1.30~1.40, 0.90~1.00 elif 1.40 < density <= 1.50: return 4 # 等级四: 1.40~1.50 elif density > 1.50 or density <= 0.90: return 5 # 等级五: >1.50, ≤0.90 else: return None # 异常值 # 8. 全氮分级(单位:g/kg) - 按TN标准 def classify_total_n(n): """ 土壤全氮分级(第三次全国土壤普查标准) 标准等级: 等级一: >2.00 等级二: 1.50~2.00 等级三: 1.00~1.50 等级四: 0.50~1.00 等级五: ≤0.50 """ if n > 2.00: return 1 # 等级一: >2.00 elif 1.50 < n <= 2.00: return 2 # 等级二: 1.50~2.00 elif 1.00 < n <= 1.50: return 3 # 等级三: 1.00~1.50 elif 0.50 < n <= 1.00: return 4 # 等级四: 0.50~1.00 elif n <= 0.50: return 5 # 等级五: ≤0.50 else: return None # 异常值 # 9. 全磷分级(单位:g/kg) - 按TP标准 def classify_total_p(p): """ 土壤全磷分级(第三次全国土壤普查标准) 标准等级: 等级一: >1.50 等级二: 1.00~1.50 等级三: 0.60~1.00 等级四: 0.40~0.60 等级五: ≤0.40 """ if p > 1.50: return 1 # 等级一: >1.50 elif 1.00 < p <= 1.50: return 2 # 等级二: 1.00~1.50 elif 0.60 < p <= 1.00: return 3 # 等级三: 0.60~1.00 elif 0.40 <= p <= 0.60: return 4 # 等级四: 0.40~0.60 elif p <= 0.40: return 5 # 等级五: ≤0.40 else: return None # 异常值 # 10. 全钾分级(单位:g/kg) - 按TK标准 def classify_total_k(k): """ 土壤全钾分级(第三次全国土壤普查标准) 标准等级: 等级一: >20.0 等级二: 15.0~20.0 等级三: 10.0~15.0 等级四: 5.0~10.0 等级五: ≤5.0 """ if k > 20.0: return 1 # 等级一: >20.0 elif 15.0 < k <= 20.0: return 2 # 等级二: 15.0~20.0 elif 10.0 < k <= 15.0: return 3 # 等级三: 10.0~15.0 elif 5.0 < k <= 10.0: return 4 # 等级四: 5.0~10.0 elif k <= 5.0: return 5 # 等级五: ≤5.0 else: return None # 异常值 # 11. 有效铁分级(单位:mg/kg) def classify_available_fe(fe): """ 土壤有效铁分级(第三次全国土壤普查标准) 参数: fe: 有效铁含量 (mg/kg) 返回: 分级等级 (1-5) """ if fe <= 3.0: return 5 # 等级五: ≤3.0 elif 3.0 < fe <= 8.0: return 4 # 等级四: 3.0~8.0 elif 8.0 < fe <= 10.0: return 3 # 等级三: 8.0~10.0 elif 10.0 < fe <= 20.0: return 2 # 等级二: 10.0~20.0 else: # fe > 20.0 return 1 # 等级一: >20.0 # 12. 有效锌分级(单位:mg/kg) def classify_available_zn(zn): """ 土壤有效锌分级(第三次全国土壤普查标准) 参数: zn: 有效锌含量 (mg/kg) 返回: 分级等级 (1-5) """ if zn <= 0.50: return 5 # 等级五: ≤0.50 elif 0.50 < zn <= 1.00: return 4 # 等级四: 0.50~1.00 elif 1.00 < zn <= 2.00: return 3 # 等级三: 1.00~2.00 elif 2.00 < zn <= 3.00: return 2 # 等级二: 2.00~3.00 else: # zn > 3.00 return 1 # 等级一: >3.00 # 13. 有效锰分级(单位:mg/kg) def classify_available_mn(mn): """ 土壤有效锰分级(第三次全国土壤普查标准) 参数: mn: 有效锰含量 (mg/kg) 返回: 分级等级 (1-5) """ if mn <= 5.0: return 5 # 等级五: ≤5.0 elif 5.0 < mn <= 10.0: return 4 # 等级四: 5.0~10.0 elif 10.0 < mn <= 20.0: return 3 # 等级三: 10.0~20.0 elif 20.0 < mn <= 30.0: return 2 # 等级二: 20.0~30.0 else: # mn > 30.0 return 1 # 等级一: >30.0 # 14. 有效铜分级(单位:mg/kg) def classify_available_cu(cu): """ 土壤有效铜分级(第三次全国土壤普查标准) 参数: cu: 有效铜含量 (mg/kg) 返回: 分级等级 (1-5) """ if cu <= 0.20: return 5 # 等级五: ≤0.20 elif 0.20 < cu <= 0.50: return 4 # 等级四: 0.20~0.50 elif 0.50 < cu <= 1.00: return 3 # 等级三: 0.50~1.00 elif 1.00 < cu <= 1.80: return 2 # 等级二: 1.00~1.80 else: # cu > 1.80 return 1 # 等级一: >1.80 # 15. 有效硼分级(单位:mg/kg) def classify_available_b(b): """ 土壤有效硼分级(第三次全国土壤普查标准) 参数: b: 有效硼含量 (mg/kg) 返回: 分级等级 (1-5) """ if b <= 0.20: return 5 # 等级五: ≤0.20 elif 0.20 < b <= 0.50: return 4 # 等级四: 0.20~0.50 elif 0.50 < b <= 1.00: return 3 # 等级三: 0.50~1.00 elif 1.00 < b <= 2.00: return 2 # 等级二: 1.00~2.00 else: # b > 2.00 return 1 # 等级一: >2.00 # 16. 有效钼分级(单位:mg/kg) def classify_available_mo(mo): """ 土壤有效钼分级(第三次全国土壤普查标准) 参数: mo: 有效钼含量 (mg/kg) 返回: 分级等级 (1-5) """ if mo <= 0.05: return 5 # 等级五: ≤0.05 elif 0.05 < mo <= 0.10: return 4 # 等级四: 0.05~0.10 elif 0.10 < mo <= 0.15: return 3 # 等级三: 0.10~0.15 elif 0.15 < mo <= 0.20: return 2 # 等级二: 0.15~0.20 else: # mo > 0.20 return 1 # 等级一: >0.20 # 17. 有效硫分级(单位:mg/kg) def classify_available_s(s): """ 土壤有效硫分级(第三次全国土壤普查标准) 参数: s: 有效硫含量 (mg/kg) 返回: 分级等级 (1-5) """ if s <= 10.0: return 5 # 等级五: ≤10.0 elif 10.0 < s <= 20.0: return 4 # 等级四: 10.0~20.0 elif 20.0 < s <= 30.0: return 3 # 等级三: 20.0~30.0 elif 30.0 < s <= 40.0: return 2 # 等级二: 30.0~40.0 else: # s > 40.0 return 1 # 等级一: >40.0 # 18. 交换性钙分级(单位:cmol(½Ca²⁺)/kg) def classify_exchangeable_ca(ca): """ 土壤交换性钙分级(第三次全国土壤普查标准) 参数: ca: 交换性钙含量 (cmol(½Ca²⁺)/kg) 返回: 分级等级 (1-5) """ if ca <= 0.25: return 5 # 等级五: ≤0.25 elif 0.25 < ca <= 1.00: return 4 # 等级四: 0.25~1.00 elif 1.00 < ca <= 2.50: return 3 # 等级三: 1.00~2.50 elif 2.50 < ca <= 4.99: return 2 # 等级二: 2.50~4.99 else: # ca > 4.99 return 1 # 等级一: >4.99 # 19. 交换性镁分级(单位:cmol(½Mg²⁺)/kg) def classify_exchangeable_mg(mg): """ 土壤交换性镁分级(第三次全国土壤普查标准) 参数: mg: 交换性镁含量 (cmol(½Mg²⁺)/kg) 返回: 分级等级 (1-5) """ if mg <= 0.21: return 5 # 等级五: ≤0.21 elif 0.21 < mg <= 0.41: return 4 # 等级四: 0.21~0.41 elif 0.41 < mg <= 0.82: return 3 # 等级三: 0.41~0.82 elif 0.82 < mg <= 1.64: return 2 # 等级二: 0.82~1.64 else: # mg > 1.64 return 1 # 等级一: >1.64 # 20. 全硒分级(单位:mg/kg) def classify_total_se(se): """ 土壤全硒分级(第三次全国土壤普查标准) 参数: se: 全硒含量 (mg/kg) 返回: 分级等级 (1-4) """ if se <= 0.17: return 4 # 等级四: ≤0.17 elif 0.17 < se <= 0.40: return 3 # 等级三: 0.17~0.40 elif 0.40 < se <= 3.00: return 2 # 等级二: 0.40~3.00 else: # se > 3.00 return 1 # 等级一: >3.00 # 21. 粉粒含量分级(单位:%) def classify_silt(silt): """ 土壤粉粒含量分级(第三次全国土壤普查标准) 参数: silt: 粉粒含量 (%) 返回: 分级等级 (1-5) """ if silt > 75: return 5 # 等级五: >75 elif 45 < silt <= 75: return 4 # 等级四: 45~75 elif 30 < silt <= 45: return 3 # 等级三: 30~45 elif 15 < silt <= 30: return 2 # 等级二: 15~30 else: # silt <= 15 return 1 # 等级一: ≤15 # 22. 黏粒含量分级(单位:%) def classify_clay(clay): """ 土壤黏粒含量分级(第三次全国土壤普查标准) 参数: clay: 黏粒含量 (%) 返回: 分级等级 (1-5) """ if clay > 65: return 5 # 等级五: >65 elif 45 < clay <= 65: return 4 # 等级四: 45~65 elif 25 < clay <= 45: return 3 # 等级三: 25~45 elif 15 < clay <= 25: return 2 # 等级二: 15~25 else: # clay <= 15 return 1 # 等级一: ≤15 # 23. 砂粒含量分级(单位:%) def classify_sand(sand): """ 土壤砂粒含量分级(第三次全国土壤普查标准) 参数: sand: 砂粒含量 (%) 返回: 分级等级 (1-5) """ if sand > 85: return 5 # 等级五: >85 elif 55 < sand <= 85: return 4 # 等级四: 55~85 elif 40 < sand <= 55: return 3 # 等级三: 40~55 elif 30 < sand <= 40: return 2 # 等级二: 30~40 else: # sand <= 30 return 1 # 等级一: ≤30 # 24. 有效土层厚度分级(单位:cm) def classify_yxtchd(depth): """ 土壤有效土层厚度分级(第三次全国土壤普查标准) 参数: depth: 有效土层厚度 (cm) 返回: 分级等级 (1-5) """ if depth <= 40: return 5 # 等级五: ≤40 elif 40 < depth <= 60: return 4 # 等级四: 40~60 elif 60 < depth <= 80: return 3 # 等级三: 60~80 elif 80 < depth <= 100: return 2 # 等级二: 80~100 else: # depth > 100 return 1 # 等级一: >100 # 25. 土壤质地 def classify_trzd(trzd): """ 土壤质地 参数: trzd: 土壤质地分类(1-5) 返回: 分级等级 (1-5) """ trzd = round(trzd, 0) return trzd # if trzd == 5: # return 5 # 等级五: ≤40 # elif trzd == 4: # return 4 # 等级四: 40~60 # elif trzd == 3: # return 3 # 等级三: 60~80 # elif trzd == 2: # return 2 # 等级二: 80~100 # else: # depth > 100 # return 1 # 等级一: >100 # 26. 地表砾石丰度分级(单位:%) def classify_lsfd(gravel): """ 土壤地表砾石丰度分级(第三次全国土壤普查标准) 参数: gravel: 地表砾石丰度 (%) 返回: 分级等级 (1-5) """ if gravel <= 0: return 1 # 等级一: ≤0 elif 0 < gravel <= 5: return 2 # 等级二: 0~5 elif 5 < gravel <= 15: return 3 # 等级三: 5~15 elif 15 < gravel <= 50: return 4 # 等级三: 15~50 else: # gravel > 50 return 5 # 等级五: >50 # 应用分级函数(只处理非空值) if "PH" in df.columns: df["PH_DJ"] = df["PH"].apply(lambda x: classify_ph(x) if pd.notna(x) else np.nan) df["PH_DJZY"] = "等级一: 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" if "OM" in df.columns: df["OM_DJ"] = df["OM"].apply(lambda x: classify_organic(x) if pd.notna(x) else np.nan) df["OM_DJZY"] = "等级一: >35,等级二: 30~35,等级三: 20~30,等级四: 10~20,等级五: ≤10" if "CEC" in df.columns: df["CEC_DJ"] = df["CEC"].apply(lambda x: classify_cation(x) if pd.notna(x) else np.nan) df["CEC_DJZY"] = "等级一: >20,等级二: 15~20,等级三: 10~15,等级四: 5~10,等级五: ≤5" if "AP" in df.columns: df["AP_DJ"] = df["AP"].apply(lambda x: classify_available_p(x) if pd.notna(x) else np.nan) df["AP_DJZY"] = "等级一: >40,等级二: 20~40,等级三: 10~20,等级四: 5~10,等级五: ≤5" if "AK" in df.columns: df["AK_DJ"] = df["AK"].apply(lambda x: classify_available_k(x) if pd.notna(x) else np.nan) df["AK_DJZY"] = "等级一: >150,等级二: 100~150,等级三: 75~100,等级四: 50~75,等级五: ≤50" if "GZCHD" in df.columns: df["GZCHD_DJ"] = df["GZCHD"].apply(lambda x: classify_soil_depth(x) if pd.notna(x) else np.nan) df["GZCHD_DJZY"] = "等级一: >25,等级二: 20~25,等级三: 15~20,等级四: 10~15,等级五: ≤10" if "TRRZ" in df.columns: df["TRRZ_DJ"] = df["TRRZ"].apply(lambda x: classify_bulk_density(x) if pd.notna(x) else np.nan) df["TRRZ_DJZY"] = "等级一: >1.00~1.20,等级二: 1.20~1.30,等级三: 1.30~1.40,0.90~1.0,等级四: 1.40~1.50,等级五: >1.50,≤0.90" if "TN" in df.columns: df["TN_DJ"] = df["TN"].apply(lambda x: classify_total_n(x) if pd.notna(x) else np.nan) df["TN_DJZY"] = "等级一: >2.0,等级二: 1.5~2.0,等级三: 1.0~1.5,等级四: 0.5~1.0,等级五: ≤0.5" if "TP" in df.columns: df["TP_DJ"] = df["TP"].apply(lambda x: classify_total_p(x) if pd.notna(x) else np.nan) df["TP_DJZY"] = "等级一: >1.5,等级二: 1.0~1.5,等级三: 0.6~1.0,等级四: 0.4~0.6,等级五: ≤0.4" if "TK" in df.columns: df["TK_DJ"] = df["TK"].apply(lambda x: classify_total_k(x) if pd.notna(x) else np.nan) df["TK_DJZY"] = "等级一: >20.0,等级二: 15.0~20.0,等级三: 10.0~15.0,等级四: 5.0~10.0,等级五: ≤5.0" if "AFE" in df.columns: df["AFE_DJ"] = df["AFE"].apply(lambda x: classify_available_fe(x) if pd.notna(x) else np.nan) df["AFE_DJZY"] = "等级一: >20,等级二: 10~20,等级三: 8~10,等级四: 3~8,等级五: ≤3" if "AZN" in df.columns: df["AZN_DJ"] = df["AZN"].apply(lambda x: classify_available_zn(x) if pd.notna(x) else np.nan) df["AZN_DJZY"] = "等级一: >3.0,等级二: 2.0~3.0,等级三: 1.0~2.0,等级四: 0.5~1.0,等级五: ≤0.5" if "AMN" in df.columns: df["AMN_DJ"] = df["AMN"].apply(lambda x: classify_available_mn(x) if pd.notna(x) else np.nan) df["AMN_DJZY"] = "等级一: >30.0,等级二: 20.0~30.0,等级三: 10.0~20.0,等级四: 5.0~10.0,等级五: ≤5.0" if "ACU" in df.columns: df["ACU_DJ"] = df["ACU"].apply(lambda x: classify_available_cu(x) if pd.notna(x) else np.nan) df["ACU_DJZY"] = "等级一: >1.80,等级二: 1.00~1.80,等级三: 0.50~1.00,等级四: 0.2~0.5,等级五: ≤0.2" if "AB" in df.columns: df["AB_DJ"] = df["AB"].apply(lambda x: classify_available_b(x) if pd.notna(x) else np.nan) df["AB_DJZY"] = "等级一: >2.0,等级二: 1.0~2.0,等级三: 0.5~1.0,等级四: 0.2~0.5,等级五: ≤0.2" if "AMO" in df.columns: df["AMO_DJ"] = df["AMO"].apply(lambda x: classify_available_mo(x) if pd.notna(x) else np.nan) df["AMO_DJZY"] = "等级一: >0.20,等级二: 0.15~0.20,等级三: 0.10~0.15,等级四: 0.05~0.10,等级五: ≤0.05" if "AS1" in df.columns: df["AS1_DJ"] = df["AS1"].apply(lambda x: classify_available_s(x) if pd.notna(x) else np.nan) df["AS1_DJZY"] = "等级一: >40.0,等级二: 30.0~40.0,等级三: 20.0~30.0,等级四: 10.0~20.0,等级五: ≤10.0" if "ECA" in df.columns: df["ECA_DJ"] = df["ECA"].apply(lambda x: classify_exchangeable_ca(x) if pd.notna(x) else np.nan) df["ECA_DJZY"] = "等级一: >20,等级二: 15~30.0,等级三: 10.0~15.0,等级四: 5~10.0,等级五: ≤5.0" if "EMG" in df.columns: df["EMG_DJ"] = df["EMG"].apply(lambda x: classify_exchangeable_mg(x) if pd.notna(x) else np.nan) df["EMG_DJZY"] = "等级一: >1.64,等级二: 0.82~1.64,等级三: 0.41~0.82,等级四: 0.21~0.41,等级五: ≤0.21" if "TSE" in df.columns: df["TSE_DJ"] = df["TSE"].apply(lambda x: classify_total_se(x) if pd.notna(x) else np.nan) df["TSE_DJZY"] = "等级一: >3.0,等级二: 0.4~3.0,等级三: 0.17~0.4,等级四:≤0.17" if "FL" in df.columns: df["FL_DJ"] = df["FL"].apply(lambda x: classify_silt(x) if pd.notna(x) else np.nan) df["FL_DJZY"] = "等级一: ≤15,等级二: 15~30,等级三: 30~45,等级四: 45~75,等级五: >75" if "NL" in df.columns: df["NL_DJ"] = df["NL"].apply(lambda x: classify_clay(x) if pd.notna(x) else np.nan) df["NL_DJZY"] = "等级一: ≤15,等级二: 15~25,等级三: 25~45,等级四: 45~65,等级五: >65" if "SL" in df.columns: df["SL_DJ"] = df["SL"].apply(lambda x: classify_sand(x) if pd.notna(x) else np.nan) df["SL_DJZY"] = "等级一: ≤30,等级二: 30~40,等级三: 40~55,等级四: 55~85,等级五: >85" if "YXTCHD" in df.columns: df["YXTCHD_DJ"] = df["YXTCHD"].apply(lambda x: classify_yxtchd(x) if pd.notna(x) else np.nan) df["YXTCHD_DJZY"] = "等级一: >100,等级二: 80~100,等级三: 60~80,等级四: 40~60,等级五: ≤40" if "TRZD" in df.columns: df["TRZD_DJ"] = df["TRZD"].apply(lambda x: classify_trzd(x) if pd.notna(x) else np.nan) df["TRZD_DJZY"] = "粉(砂)质黏壤土: 1,粉(砂)质黏土: 2,粉(砂)质壤土: 3,黏壤土: 4,黏土: 5,壤土: 6,壤质黏土: 7,砂土及壤质砂土: 8,砂质黏壤土: 9,砂质黏土: 10,砂质壤土: 11,重黏土: 12" if "DBLSFD" in df.columns: df["DBLSFD_DJ"] = df["DBLSFD"].apply(lambda x: classify_lsfd(x) if pd.notna(x) else np.nan) df["DBLSFD_DJZY"] = "等级一: =0,等级二: 0~5,等级三: 5.0~15.0,等级四: 15~50,等级五: ≥50" return df def main(): """ 主函数:程序入口 用户需根据实际情况修改以下参数 """ logger = init_logger() logger.info("="*50) logger.info("土壤属性栅格面积加权统计程序启动") logger.info("="*50) # -------------------------- # 用户配置区域(必须修改!) # -------------------------- # 1. 矢量图斑文件路径(支持Shapefile、GeoPackage等格式) # TODO VECTOR_PATH = r"F:\@user\missum\Documents\ArcGIS\Projects\MyProject\MyProject.gdb\西畴县耕园林草其他_SpatialJoin" # 示例:"D:/data/土壤图斑.shp" # 2. 土壤属性栅格文件配置(键:属性名称,值:栅格文件路径) # 注意:属性名称必须与最终表格列名一致 # TODO RASTER_FILES = { # "GZCHD": r"E:\@三普\云南丘北县\基础数据\三普栅格\GZCHD.tif", # "TRRZ": r"E:\@三普\云南丘北县\基础数据\三普栅格\TRRZ.tif", # "SL": r"E:\@三普\云南丘北县\基础数据\三普栅格\SL.tif", # "FL": r"E:\@三普\云南丘北县\基础数据\三普栅格\FL.tif", # "NL": r"E:\@三普\云南丘北县\基础数据\三普栅格\NL.tif", # "PH": r"E:\@三普\云南丘北县\基础数据\三普栅格\PH.tif", # "CEC": r"E:\@三普\云南丘北县\基础数据\三普栅格\CEC.tif", # "OM": r"E:\@三普\云南丘北县\基础数据\三普栅格\OM.tif", # "TN": r"E:\@三普\云南丘北县\基础数据\三普栅格\TN.tif", # "TP": r"E:\@三普\云南丘北县\基础数据\三普栅格\TP.tif", # "TK": r"E:\@三普\云南丘北县\基础数据\三普栅格\TK.tif", # "AP": r"E:\@三普\云南丘北县\基础数据\三普栅格\AP.tif", # "AK": r"E:\@三普\云南丘北县\基础数据\三普栅格\AK.tif", # "AFE": r"E:\@三普\云南丘北县\基础数据\三普栅格\AFE.tif", # "AZN": r"E:\@三普\云南丘北县\基础数据\三普栅格\AZN.tif", # "AMN": r"E:\@三普\云南丘北县\基础数据\三普栅格\AMN.tif", # "ACU": r"E:\@三普\云南丘北县\基础数据\三普栅格\ACU.tif", # "AB": r"E:\@三普\云南丘北县\基础数据\三普栅格\AB.tif", # "AMO": r"E:\@三普\云南丘北县\基础数据\三普栅格\AMO.tif", # "AS1": r"E:\@三普\云南丘北县\基础数据\三普栅格\AS1.tif", # "ECA": r"E:\@三普\云南丘北县\基础数据\三普栅格\ECA.tif", # "EMG": r"E:\@三普\云南丘北县\基础数据\三普栅格\EMG.tif", # "YXTCHD": r"E:\@三普\云南丘北县\基础数据\三普栅格\YXTCHD.tif", "TRZD": r"E:\@三普\云南西畴县\基础数据\三普栅格\投影后\TRZD.tif", # "DBLSFD": r"E:\@三普\云南丘北县\基础数据\三普栅格\LSFD.tif", # "TSE": r"E:\@三普\云南丘北县\基础数据\三普栅格\TSE.tif" } # 3. 结果输出路径(Excel文件) OUTPUT_PATH = r"E:\@三普\@临时文件夹\西畴县TRSX11.shp" # 示例:"D:/result/结果.shp" # -------------------------- # 程序执行流程(无需修改) # -------------------------- try: # 1. 数据验证 if not validate_data(VECTOR_PATH, RASTER_FILES): logger.error("数据验证失败,程序终止") return # 2. 执行面积加权统计 result_gdf = calculate_area_weighted_stats(VECTOR_PATH, RASTER_FILES, OUTPUT_PATH) # 3. 显示结果预览 # logger.info("\\n结果预览(前3行):") # print(result_gdf.head(3).to_string(index=False)) logger.info("\\n" + "="*50) logger.info("程序执行完成!") logger.info(f"结果文件:{OUTPUT_PATH}") logger.info("="*50) except Exception as e: logger.error(f"程序执行出错:{str(e)}", exc_info=True) logger.error("程序异常终止") if __name__ == "__main__": # 启动主程序 main()