From 6313905356b971a89a3133465dfe631d09dbdd72 Mon Sep 17 00:00:00 2001 From: missum Date: Thu, 23 Apr 2026 17:30:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- .../B1_TRZD12土壤属性分级分布.py | 10 +- .../soil_prop_stats/B1土壤属性分级分布.py | 90 +++++++---- .../soil_prop_stats/B2土地利用类型土壤属性.py | 8 +- .../soil_prop_stats/B3不同土壤类型土壤属性.py | 57 +++---- tools/core/stats_area_to_excel.py | 144 ++++++++++++++++-- tools/core/stats_soil_prop_to_excel.py | 86 ++++++++--- tools/ui/config/settings.json | 18 ++- tools/ui/tabs/area_stat_tab.py | 8 + 8 files changed, 313 insertions(+), 108 deletions(-) diff --git a/tools/core/soil_prop_stats/B1_TRZD12土壤属性分级分布.py b/tools/core/soil_prop_stats/B1_TRZD12土壤属性分级分布.py index 61fed4b..617bf89 100644 --- a/tools/core/soil_prop_stats/B1_TRZD12土壤属性分级分布.py +++ b/tools/core/soil_prop_stats/B1_TRZD12土壤属性分级分布.py @@ -43,15 +43,15 @@ 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 == '砂土及壤质砂土': + if str(prop_level) == "1" or prop_level == '砂土及壤质砂土': return "砂质" - elif str(prop_level) == "11" or prop_level == '砂质壤土': + elif str(prop_level) == "2" or prop_level == '砂质壤土': return "砂壤质" - elif str(prop_level) in ["6","3"] or prop_level in ['粉砂质壤土', '壤土']: + elif str(prop_level) in ["3","4"] or prop_level in ['粉砂质壤土', '壤土']: return "壤质" - elif str(prop_level) in ["1","4","9"] or prop_level in ['粉砂质年壤土', '黏壤土', '砂质黏壤土']: + elif str(prop_level) in ["5","6","7"] or prop_level in ['粉砂质年壤土', '黏壤土', '砂质黏壤土']: return "黏壤质" - elif str(prop_level) in ["2","5","7","10","12"] or prop_level in ['粉砂质黏土', '黏土', '壤质黏土', '砂质黏土', '重黏土']: + elif str(prop_level) in ["8","9","10","11","12"] or prop_level in ['粉砂质黏土', '黏土', '壤质黏土', '砂质黏土', '重黏土']: return "黏质" else: return "-" diff --git a/tools/core/soil_prop_stats/B1土壤属性分级分布.py b/tools/core/soil_prop_stats/B1土壤属性分级分布.py index ae0abd9..84eb887 100644 --- a/tools/core/soil_prop_stats/B1土壤属性分级分布.py +++ b/tools/core/soil_prop_stats/B1土壤属性分级分布.py @@ -259,7 +259,7 @@ def process_data_for_table1(gdb_path, soil_prop_feature_name, df_origin_area, ta return df_final, stat_sample # --- 3. Excel 制表 总表--- -def write_to_excel_table1(df:pd.DataFrame, output_path, prop_config, soil_prop_tif, stat_sample): +def write_to_excel_table1(df:pd.DataFrame, output_path, prop_config, stats_map, stat_sample): """ 【最终修正版】: 将处理好的数据写入格式化的 Excel 文件。 """ @@ -271,30 +271,35 @@ def write_to_excel_table1(df:pd.DataFrame, output_path, prop_config, soil_prop_t wb.save(output_path) return - # 全区制图统计 - """ - try: - raster = arcpy.Raster(soil_prop_tif) + # # 全区制图统计 + # try: + # raster = arcpy.Raster(soil_prop_tif) - # 转换为numpy数组进行计算 - array = arcpy.RasterToNumPyArray(raster,nodata_to_value=9999) + # # 按行政区蒙膜区域统计 + # out_raster = arcpy.sa.ExtractByMask( + # in_raster=raster, + # in_mask_data=tif_mask, + # extraction_area="INSIDE" + # ) + # # 转换为numpy数组进行计算 + # array = arcpy.RasterToNumPyArray(out_raster,nodata_to_value=9999) - # 过滤掉NoData值 - # 过滤NoData值和9999值 - array = array[~np.isnan(array)] # 过滤NoData - array = array[array != 9999] # 过滤9999 - array = array.astype(np.float64) + # # 过滤掉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_map = { + # '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 @@ -419,17 +424,26 @@ def write_to_excel_table1(df:pd.DataFrame, output_path, prop_config, soil_prop_t ws.cell(row=current_row, column=6).value = '100' # 3. 合计单元格填充 - ws.merge_cells(f'B{current_row + 1}:F{current_row + 1}') + ws.merge_cells(f'A{current_row + 1}:B{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'C{current_row + 1}:D{current_row + 1}') + ws.cell(row=current_row + 1, column=3).value = f'{stats["mean"]:.{prop_name}}' + ws.merge_cells(f'E{current_row + 1}:F{current_row + 1}') + ws.cell(row=current_row + 1, column=5).value = f'{stats_map["mean"]:.{prop_name}}' - ws.merge_cells(f'B{current_row + 2}:F{current_row + 2}') + ws.merge_cells(f'A{current_row + 2}:B{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'C{current_row + 2}:D{current_row + 2}') + ws.cell(row=current_row + 2, column=3).value = f'{stats["median"]:.{prop_name}}' + ws.merge_cells(f'E{current_row + 2}:F{current_row + 2}') + ws.cell(row=current_row + 2, column=5).value = f'{stats_map["median"]:.{prop_name}}' - ws.merge_cells(f'B{current_row + 3}:F{current_row + 3}') + ws.merge_cells(f'A{current_row + 3}:B{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}}' + ws.merge_cells(f'C{current_row + 3}:D{current_row + 3}') + ws.cell(row=current_row + 3, column=3).value = f'{stats["min"]:.{prop_name}} ~ {stats["max"]:.{prop_name}}' + ws.merge_cells(f'E{current_row + 3}:F{current_row + 3}') + ws.cell(row=current_row + 3, column=5).value = f'{stats_map["min"]:.{prop_name}} ~ {stats_map["max"]:.{prop_name}}' # --- a. 定义样式 --- header_font = Font(name='宋体', size=11, bold=True) @@ -447,7 +461,7 @@ def write_to_excel_table1(df:pd.DataFrame, output_path, prop_config, soil_prop_t 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): +def main(gdb_path, soil_prop_name, reclassed_features_path, dltb_features, stats_map, output_path, target_area_dict,xzqmc, prop_config): try: # --- 1. 用户配置 --- # 输出配置 @@ -493,10 +507,24 @@ def main(gdb_path, soil_prop_name, reclassed_features_path, dltb_features, soil_ ) clipped_table_df = arcgis_utils.read_arcgis_table(temp_out_tables_area) + # 定义需要过滤地类的属性列表 + filtered_props = ['ECA', 'EMG', 'ACU', 'AZN', 'AFE', 'AMN', 'AMO', 'AB', 'AS1', 'TSE'] + dltb_features = os.path.join(gdb_path, "地类图斑") + # 生成表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) + + # 过滤地类图斑 + temp_mask = r"in_memory/temp_dltb_features_filtered" + temp_files.append(temp_mask) + if soil_prop_name == "GZCHD": + arcpy.conversion.ExportFeatures(dltb_features, temp_mask, "DLBM LIKE '01%'") + if soil_prop_name in filtered_props: + arcpy.conversion.ExportFeatures(dltb_features, temp_mask, "DLBM LIKE '01%' OR DLBM LIKE '02%'") + else: + temp_mask = dltb_features + + write_to_excel_table1(final_dataframe, output_excel_path, prop_config, stats_map, stat) # return df_with_factors except Exception as e: diff --git a/tools/core/soil_prop_stats/B2土地利用类型土壤属性.py b/tools/core/soil_prop_stats/B2土地利用类型土壤属性.py index 4a73f87..a74efc3 100644 --- a/tools/core/soil_prop_stats/B2土地利用类型土壤属性.py +++ b/tools/core/soil_prop_stats/B2土地利用类型土壤属性.py @@ -72,7 +72,7 @@ def process_data_for_table2(gdb_path, soil_prop_feature_name, df_dltb, target_ar return df_final # 写入EXCEL 表2 -def write_to_excel_table2(df, output_path, prop_config:dict, soil_prop_name: str = ''): +def write_to_excel_table2(df, output_path,stats_map, prop_config:dict, soil_prop_name: str = ''): """ 将处理好的数据写入格式化的 Excel 文件。 """ @@ -260,7 +260,7 @@ def write_to_excel_table2(df, output_path, prop_config:dict, soil_prop_name: str 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}}" + ws.cell(row=current_row, column=6).value = f"{stats_map['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 "-" @@ -285,7 +285,7 @@ def write_to_excel_table2(df, output_path, prop_config:dict, soil_prop_name: str print("Excel 报告生成成功!") -def main(gdb_path, soil_prop_name, dltb_features, soil_prop_tif, output_path,target_areas_df, prop_config): +def main(gdb_path, soil_prop_name, dltb_features, soil_prop_tif, output_path,target_areas_df, prop_config,stats_map): try: # --- 1. 用户配置 --- # 输出配置 @@ -311,7 +311,7 @@ def main(gdb_path, soil_prop_name, dltb_features, soil_prop_tif, output_path,tar 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) + write_to_excel_table2(final_dataframe, output_excel_path,stats_map, prop_config, soil_prop_name) # return df_with_factors except Exception as e: diff --git a/tools/core/soil_prop_stats/B3不同土壤类型土壤属性.py b/tools/core/soil_prop_stats/B3不同土壤类型土壤属性.py index c502a19..1c73000 100644 --- a/tools/core/soil_prop_stats/B3不同土壤类型土壤属性.py +++ b/tools/core/soil_prop_stats/B3不同土壤类型土壤属性.py @@ -55,7 +55,6 @@ def process_data_for_table3(soil_prop_name, df_trlx_sample, df_trlx_zhitu, df_tr 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: @@ -91,7 +90,7 @@ def process_data_for_table3(soil_prop_name, df_trlx_sample, df_trlx_zhitu, df_tr return df_final, df_sample_mymz # 写入EXCEL 表2 -def write_to_excel_table3(df, output_path, prop_config:dict, stats): +def write_to_excel_table3(df, output_path, prop_config:dict, stats,stats_map): """ 将处理好的数据写入格式化的 Excel 文件。 """ @@ -246,9 +245,9 @@ def write_to_excel_table3(df, output_path, prop_config:dict, stats): # 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_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 + # 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 = '全区' @@ -256,7 +255,7 @@ def write_to_excel_table3(df, output_path, prop_config:dict, stats): 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=7).value = f"{stats_map['mean']:.{prop_name}}" ws.cell(row=current_row, column=8).value = f"{total_areas:.0f}" # --- a. 定义样式 --- @@ -397,7 +396,7 @@ def write_to_excel_table4(df:pd.DataFrame, output_path, prop_config, stats): 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): +def main(gdb_path, soil_prop_name, trlx_features, soil_prop_tif, output_path,target_areas_df, prop_config, dltb_features,stats_map): try: # --- 1. 用户配置 --- # 输出配置 @@ -411,16 +410,6 @@ def main(gdb_path, soil_prop_name, trlx_features, soil_prop_tif, output_path,tar 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], @@ -442,25 +431,37 @@ def main(gdb_path, soil_prop_name, trlx_features, soil_prop_tif, output_path,tar # 定义需要过滤地类的属性列表 filtered_props = ['ECA', 'EMG', 'ACU', 'AZN', 'AFE', 'AMN', 'AMO', 'AB', 'AS1', 'TSE'] + if soil_prop_name == "GZCHD": + temp_gdtb_trlx_out = r"in_memory/temp_gdtb_trlx_out" + temp_files.append(temp_gdtb_trlx_out) + + # 2. 创建临时图斑用于过滤、土壤类型与地类图斑相交 + # 用于样点 + 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) + # 用于制图 + 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.SpatialJoin(soil_prop_features, trlx_features, temp_out_features, "JOIN_ONE_TO_ONE", "KEEP_ALL",field_mappings, "INTERSECT") + # 交集土壤类型与土地利用图斑 + arcpy.analysis.Intersect([trlx_features, dltb_features], temp_gdtb_trlx_filtered, 'NO_FID') + + # 3. 交集土壤类型与土地利用图斑 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%'") + arcpy.conversion.ExportFeatures(temp_gdtb_trlx_filtered,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%'") @@ -472,7 +473,7 @@ def main(gdb_path, soil_prop_name, trlx_features, soil_prop_tif, output_path,tar print(f"过滤制图数据:仅统计耕地和园地(DLBM LIKE '01%' OR '02%')") else: # 3. 以表格显示分区统计 计算均值 - arcpy.sa.ZonalStatisticsAsTable(trlx_features, "YL_TS", soil_prop_tif, out_table_mean, "DATA", "MEAN") + arcpy.sa.ZonalStatisticsAsTable(temp_gdtb_trlx_filtered, "YL_TS", soil_prop_tif, out_table_mean, "DATA", "MEAN") # 获取土壤类型图斑面积 trlx_area_df = pd.DataFrame(arcpy.da.FeatureClassToNumPyArray(trlx_features, ["YL", "TS", "Shape@Area"])) @@ -493,7 +494,7 @@ def main(gdb_path, soil_prop_name, trlx_features, soil_prop_tif, output_path,tar # print(final_dataframe) # 生成表3 - write_to_excel_table3(final_dataframe, output_excel_path, prop_config, stat_sample) + write_to_excel_table3(final_dataframe, output_excel_path, prop_config, stat_sample,stats_map) # 母岩母质表 write_to_excel_table4(df_mymz, output_excel4_path, prop_config,stat_sample) diff --git a/tools/core/stats_area_to_excel.py b/tools/core/stats_area_to_excel.py index 972ec28..f3b4f5a 100644 --- a/tools/core/stats_area_to_excel.py +++ b/tools/core/stats_area_to_excel.py @@ -10,6 +10,7 @@ import json import os from pathlib import Path +import re import sys import traceback import uuid @@ -92,6 +93,27 @@ def log_arcpy_message(message): sys.stderr.write(f"ArcPyError:{message.message}\n") sys.stderr.flush() +# 判断单元格类型 +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_specail_map(original_dict): grade_map = {} dict_len = len(original_dict) @@ -200,7 +222,8 @@ class SoilQualityReporter: # 复制数据并添加分组列 df_stats = stats.copy() # df_stats["GRID_GROUP"] = df_stats["GRIDCODE"].apply(self._map_grade) - if "YJDLBM" not in df_stats.columns: + if "YJDLBM" not in df_stats.columns and "YNDLBM" in df_stats.columns: + # 云南地区使用YNDLBM(二级地类) df = df_stats.pivot_table( index="YNDLBM", columns="GRIDCODE", @@ -210,6 +233,7 @@ class SoilQualityReporter: observed=False ) else: + # 其他地区使用YJDLBM(一级地类) df = df_stats.pivot_table( index="YJDLBM", columns="GRIDCODE", @@ -227,12 +251,47 @@ class SoilQualityReporter: # 按等级排序并添加总计 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] + # 添加总计行 + df.loc["总计"] = df.sum(axis=0) + # 对于云南地区,添加耕地、园地合计 + if self.is_yunnan: + # 定义排序顺序 + desired_order = ['水田', '水浇地', '旱地', '耕地合计', '果园', '茶园', '橡胶园', '其他园地', '园地合计', '林地', '草地', '其他', '总计'] + + # 检查是否存在水田、水浇地、旱地中的任意一个 + cultivated_types = ['水田', '水浇地', '旱地'] + existing_cultivated_types = [ctype for ctype in cultivated_types if ctype in df.index] + if existing_cultivated_types: + # 计算耕地合计 + cultivated_total = df.loc[existing_cultivated_types].sum() + # 添加耕地合计行 + df.loc["耕地合计"] = cultivated_total + + # 检查是否存在果园、茶园、橡胶园、其他园地中的任意一个 + other_garden_types = ['果园', '茶园', '橡胶园', '其他园地'] + existing_garden_types = [ctype for ctype in other_garden_types if ctype in df.index] + if existing_garden_types: + # 计算园地合计 + other_garden_total = df.loc[existing_garden_types].sum() + # 添加园地合计行 + df.loc["园地合计"] = other_garden_total + + # 过滤出实际存在的地类 + actual_order = [item for item in desired_order if item in df.index] + + # 添加未在排序列表中的其他地类 + for item in df.index: + if item not in actual_order and item != "总计": + actual_order.append(item) + + # 重新排序 + df = df.reindex(actual_order) + return df def generate_report(self, stats): @@ -344,27 +403,75 @@ class SoilQualityReporter: 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) + if self.is_yunnan and index in ['水田','水浇地','旱地', '果园', '茶园', '橡胶园', '其他园地', '耕地合计', '园地合计']: + if index in ['耕地合计', '园地合计']: + sheet.cell(row=r_idx, column=2, value="合计") + else: + sheet.cell(row=r_idx, column=2, value=index) + else: + 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) - + + # 对于云南地区,合并耕地行的A列 + if self.is_yunnan: + # 查找水田、水浇地、旱地的行索引 + cultivated_rows = [] + other_garden_rows = [] + for r_idx, (index, _) in enumerate(df.iterrows(), start=start_row+4): + if index in ['水田','水浇地','旱地']: + cultivated_rows.append(r_idx) + if index in ['果园', '茶园', '橡胶园', '其他园地']: + other_garden_rows.append(r_idx) + + # 合并耕地行的A列 + if cultivated_rows: + start = cultivated_rows[0] + end = cultivated_rows[-1] + sheet.merge_cells(f"A{start}:A{end+1}") + sheet[f"A{start}"] = "耕地" + # 合并其他园地行的A列 + if other_garden_rows: + start = other_garden_rows[0] + end = other_garden_rows[-1] + sheet.merge_cells(f"A{start}:A{end+1}") + sheet[f"A{start}"] = "园地" + + def _apply_common_format(self, sheet, landuse_start_row): """应用通用格式""" + print("正在自动调整列宽...") + dims = {} + for row in sheet.rows: + for cell in row: + if cell.value: + merged_range = next((range for range in sheet.merged_cells.ranges if cell.coordinate in range), None) + if get_merge_type(merged_range) == 'column' or get_merge_type(merged_range) == 'both': + continue + if r'\n' in str(cell.value): + continue + # 获取字体大小 + font_size = cell.font.size + cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value)) * font_size + # 计算列宽 + cell_len = cell_len / 8 + dims[cell.column] = max(dims.get(cell.column, 0), cell_len) # 设置列宽 - for col in range(1, sheet.max_column + 1): - col_letter = chr(64 + col) - sheet.column_dimensions[col_letter].width = 14 + for col, value in dims.items(): + len_val = max(value+4,14) + sheet.column_dimensions[get_column_letter(int(col))].width = len_val - 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]: + for row in range(4, sheet.max_row+1): + if row not in [landuse_start_row,landuse_start_row+1, landuse_start_row+2]: sheet.row_dimensions[row].height = 23 # 定义边框样式 @@ -378,12 +485,16 @@ class SoilQualityReporter: # 应用样式到所有单元格 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.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) + + # B列bold = False + if cell.column == 2 and cell.row > 3 and cell.row not in (landuse_start_row, landuse_start_row+2): + 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) @@ -407,7 +518,6 @@ class SoilQualityReporter: 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, "*") @@ -431,7 +541,7 @@ def get_area_by_group(dltb_class_feature, excel_target_path, xzqmc, is_by_xzq=Fa # 读取目标面积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':'其他'} + landuse_types = {'0101':'水田', '0102':'水浇地', '0103':'旱地', '0201':'果园', '0202':'茶园', '0203':'橡胶园', '0204':'其他园地', '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':'其他'} @@ -607,7 +717,9 @@ def main(): .reset_index() ) stats["adjusted_area"] = stats["temp_area"] + # stats.to_csv("area_stats.csv", index=True) + # 重命名列(按实际土壤分级字段调整) if soil_property == "PH" or soil_property == "TRRZ": @@ -624,7 +736,7 @@ def main(): if xzqmc in guangxi_region: landuse_map = {"01": "耕地", "02": "园地", "03": "林地", "04": "草地", "12": "其他"} else: - landuse_map = {"0101": "水田", "0102": "水浇地", "0103": "旱地", "02": "园地", "03": "林地", "04": "草地", "12": "其他"} + landuse_map = {'0101':'水田', '0102':'水浇地', '0103':'旱地', '0201':'果园', '0202':'茶园', '0203':'橡胶园', '0204':'其他园地', '03':'林地', '04':'草地', '12':'其他'} # 平差处理 excel_target_path = Path("tools/config_json/公布的变更调查平差面积.xlsx") # 您的目标面积Excel文件路径 @@ -657,4 +769,4 @@ def main(): if __name__ == '__main__': print_status("开始执行") - main() + main() \ No newline at end of file diff --git a/tools/core/stats_soil_prop_to_excel.py b/tools/core/stats_soil_prop_to_excel.py index 438891c..30b45e8 100644 --- a/tools/core/stats_soil_prop_to_excel.py +++ b/tools/core/stats_soil_prop_to_excel.py @@ -16,6 +16,8 @@ import time import arcpy import argparse +import numpy as np + from tools.core.utils import 平差工具, common_utils sys.path.append(str(Path(__file__).parent)) @@ -91,32 +93,77 @@ def process_soil_property(args): print(f"缺少{soil_prop_name}的栅格或重分类要素,请检查输入文件路径是否正确!") return False + # 输出配置 + temp_files = [] + + filtered_props = ['ECA', 'EMG', 'ACU', 'AZN', 'AFE', 'AMN', 'AMO', 'AB', 'AS1', 'TSE'] + dltb_features = os.path.join(data_source_path, "地类图斑") + + # 过滤地类图斑 + temp_mask = r"in_memory/temp_dltb_features_filtered" + temp_files.append(temp_mask) + if soil_prop_name == "GZCHD": + arcpy.conversion.ExportFeatures(dltb_features, temp_mask, "DLBM LIKE '01%'") + if soil_prop_name in filtered_props: + arcpy.conversion.ExportFeatures(dltb_features, temp_mask, "DLBM LIKE '01%' OR DLBM LIKE '02%'") + else: + temp_mask = dltb_features + # 全区制图统计 + try: + raster = arcpy.Raster(soil_prop_tif) + + # 按腌膜区域统计 + out_raster = arcpy.sa.ExtractByMask( + in_raster=raster, + in_mask_data=temp_mask, + extraction_area="INSIDE" + ) + # 转换为numpy数组进行计算 + array = arcpy.RasterToNumPyArray(out_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_map = { + '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}") + + # ================================================================= 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) + B1土壤属性分级分布.main(data_source_path, soil_prop_name, reclassed_features, dltb_features, stats_map, 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}的表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,stats_map) + 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}的表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,stats_map) + 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): @@ -129,6 +176,11 @@ def process_soil_property(args): except Exception as e: print(f"处理{soil_prop_name}时出错: {str(e)}") return False + finally: + # 清理临时文件 + temp_files_processor.clean_up_temp_files(temp_files) + import gc + gc.collect() def main(): diff --git a/tools/ui/config/settings.json b/tools/ui/config/settings.json index 4ae5feb..497fdbd 100644 --- a/tools/ui/config/settings.json +++ b/tools/ui/config/settings.json @@ -33,7 +33,7 @@ "config_file_path": "D:/ProgramData/ArcGis_Py/tools/config_json/广西_华南_地块_config.json", "xzq_features": "E:\\@三普属性图出图\\广西银海区\\银海区土壤属性制表\\土壤属性制表数据.gdb\\行政区划", "dltb_features": "E:\\@三普属性图出图\\广西银海区\\银海区土壤属性制表\\土壤属性制表数据.gdb\\地类图斑", - "xzqmc": "西畴县", + "xzqmc": "", "is_by_xzq": false }, "acid_stat_settings": { @@ -49,12 +49,16 @@ "xzqmc": "" }, "soil_prop_stat_settings": { - "xzqmc": "", + "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": [] + "data_source_path": "E:/@三普属性图出图/广西银海区/银海区土壤属性制表/土壤属性制表数据.gdb", + "sanpu_prop_tif_folder": "E:/@三普属性图出图/广西北海市/@基础数据/三普栅格/投影后", + "reclassed_feature_folder": "E:/@三普属性图出图/广西北海市/过程数据/面积统计用栅格面", + "output_folder": "E:/@三普属性图出图/测试", + "sample_list": [ + "CEC", + "AS1", + "AP" + ] } } \ No newline at end of file diff --git a/tools/ui/tabs/area_stat_tab.py b/tools/ui/tabs/area_stat_tab.py index 7da5c28..a7b2294 100644 --- a/tools/ui/tabs/area_stat_tab.py +++ b/tools/ui/tabs/area_stat_tab.py @@ -26,6 +26,14 @@ def yunnan_dlbm(dlbm): return "0102" elif dlbm.startswith("0103"): return "0103" + elif dlbm.startswith("0201"): + return "0201" + elif dlbm.startswith("0202"): + return "0202" + elif dlbm.startswith("0203"): + return "0203" + elif dlbm.startswith("0204"): + return "0204" else: return dlbm[:2] """