# -*- 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