初始化
This commit is contained in:
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