244 lines
9.4 KiB
Python
244 lines
9.4 KiB
Python
# -*- 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 |