#include "nmWxPropertyInterpolationDlg.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nmCalculationUtils.h" #include "nmDataOutline.h" #include "nmGuiPlot.h" #include "nmObjPoint.h" #include "nmObjPointWell.h" #include "nmWxPropertyInterpolationPreviewDlg.h" #include "iUnitGroup.h" #include "iUnitHelper.h" #include "tCurvePlotView.h" #include "ZxDataWell.h" #include "ZxObjText.h" #include "ZxPlot.h" namespace { // 表格第0行保留给单位,实际测点从第1行开始. const int POINT_UNIT_ROW = 0; const int FIRST_POINT_ROW = 1; // 坐标和厚度以m保存,渗透率以D保存但默认使用mD显示. const char* LENGTH_BASE_UNIT = "m"; const char* PERMEABILITY_BASE_UNIT = "D"; const char* PERMEABILITY_DISPLAY_UNIT = "mD"; // 使用独立翻译上下文,便于统一维护对话框文本. QString interpolationText(const char* sourceText) { return QCoreApplication::translate("nmWxPropertyInterpolationDlg", sourceText); } // 创建统一格式的非负数值输入框. QDoubleSpinBox* createNumberSpinBox(QWidget* parent, double value) { QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent); spinBox->setRange(0.0, 1.0e12); spinBox->setDecimals(6); spinBox->setValue(value); spinBox->setButtonSymbols(QAbstractSpinBox::NoButtons); return spinBox; } QString propertyBaseUnit(const QString& property) { // 孔隙度为无量纲量,返回空单位后界面会禁用对应单位下拉框. if(property == "k") { return PERMEABILITY_BASE_UNIT; } if(property == "h") { return LENGTH_BASE_UNIT; } return QString(); } QString defaultPropertyDisplayUnit(const QString& property) { // 渗透率采用工程中常用的mD显示,其余属性默认显示基准单位. if(property == "k") { return PERMEABILITY_DISPLAY_UNIT; } return propertyBaseUnit(property); } QString unitGroupLookupUnit(const QString& unit) { // D与时间单位d仅大小写不同,使用唯一的mD定位渗透率单位组. return unit == PERMEABILITY_BASE_UNIT ? QString(PERMEABILITY_DISPLAY_UNIT) : unit; } bool convertUnitValue(double sourceValue, const QString& sourceUnit, const QString& destinationUnit, double& destinationValue) { if(sourceUnit == destinationUnit || (sourceUnit.isEmpty() && destinationUnit.isEmpty())) { destinationValue = sourceValue; return true; } if(sourceUnit.isEmpty() || destinationUnit.isEmpty()) { return false; } // 复用框架单位表完成换算,不在插值模块中维护独立换算系数. iUnitGroup* unitGroup = iUnitHelper::getUnitGroupByUnit( unitGroupLookupUnit(sourceUnit)); if(unitGroup == NULL || unitGroup->indexOf(destinationUnit) < 0) { return false; } int digit = 6; return unitGroup->convert(sourceUnit, sourceValue, destinationUnit, destinationValue, digit); } double convertedUnitValue(double sourceValue, const QString& sourceUnit, const QString& destinationUnit) { double destinationValue = sourceValue; convertUnitValue(sourceValue, sourceUnit, destinationUnit, destinationValue); return destinationValue; } QString displayValueText(double value) { // 表格使用较高有效位数,减少多次显示和回写造成的精度损失. return QString::number(value, 'g', 15); } void populateUnitCombo(QComboBox* comboBox, const QString& baseUnit, const QString& selectedUnit) { if(comboBox == NULL) { return; } // 单位列表从全局单位表加载,屏蔽信号可避免初始化过程反向修改数据. bool oldBlock = comboBox->blockSignals(true); comboBox->clear(); if(baseUnit.isEmpty()) { comboBox->addItem(QString()); comboBox->setCurrentIndex(0); comboBox->setEnabled(false); comboBox->blockSignals(oldBlock); return; } const QString lookupUnit = unitGroupLookupUnit(baseUnit); QStringList units; iUnitGroup* unitGroup = iUnitHelper::getUnitGroupByUnit(lookupUnit); if(unitGroup != NULL) { units = unitGroup->getAllUnitNames(); } if(units.isEmpty()) { units.append(baseUnit); } comboBox->addItems(units); int selectedIndex = comboBox->findText(selectedUnit); if(selectedIndex < 0) { selectedIndex = comboBox->findText(lookupUnit); } if(selectedIndex < 0) { selectedIndex = comboBox->findText(baseUnit); } comboBox->setCurrentIndex(selectedIndex < 0 ? 0 : selectedIndex); comboBox->setEnabled(true); comboBox->blockSignals(oldBlock); } void setTableUnitComboStyle(QComboBox* comboBox) { if(comboBox == NULL) { return; } // 仅借用可编辑模式调整文字对齐,输入区域仍设置为只读. comboBox->setEditable(true); comboBox->lineEdit()->setAlignment(Qt::AlignCenter); comboBox->lineEdit()->setReadOnly(true); comboBox->setStyleSheet( "QComboBox { border: none; background: transparent; padding-left: 4px; }" "QComboBox::drop-down { border: none; }"); } // 插值测点使用独立的小方块标记,避免与井图元混淆. class PropertyInterpolationMarker : public nmObjPoint { public: PropertyInterpolationMarker(const QString& name, ZxSubAxisX* axisX, ZxSubAxisY* axisY) : nmObjPoint(name, axisX, axisY) { setDotRadius(1.0); } virtual bool drawWellPos(QPainter* painter, QPointF point) override { if(painter == NULL) { return false; } const QTransform transform = painter->combinedTransform(); qreal scaleX = qAbs(transform.m11()); qreal scaleY = qAbs(transform.m22()); if(scaleX < 0.0001) { scaleX = 1.0; } if(scaleY < 0.0001) { scaleY = 1.0; } const qreal halfPixelSize = 3.0; QRectF markerRect(point.x() - halfPixelSize / scaleX, point.y() - halfPixelSize / scaleY, halfPixelSize * 2.0 / scaleX, halfPixelSize * 2.0 / scaleY); painter->save(); QPen pen(QColor(0, 25, 100)); pen.setWidthF(1.0); pen.setCosmetic(true); painter->setPen(pen); painter->setBrush(QColor(0, 70, 200)); painter->drawRect(markerRect); painter->restore(); return true; } }; void setMarkerLabel(nmObjPoint* marker, const QString& text) { if(marker == NULL || marker->getChildren().isEmpty()) { return; } ZxObjText* label = dynamic_cast(marker->at(0)); if(label != NULL) { label->setText(text); label->setTextColor(QColor(30, 30, 30)); } } // 将储层边界转换成预览裁剪使用的多边形,并计算规则采样范围. bool createPreviewOutline(nmDataOutline* outline, QVector& outlinePoints, QRectF& bounds) { outlinePoints.clear(); bounds = QRectF(); if(outline == NULL) { return false; } if(outline->getOutlineType() == NM_Round_Outline_Type) { // 圆形边界离散成多边形,便于预览窗口统一裁剪. const QPointF center = outline->getCenter(); const double radius = outline->getRadius(); if(radius <= 0.0) { return false; } const int segmentCount = 72; const double fullCircle = 6.28318530717958647692; for(int i = 0; i < segmentCount; ++i) { const double angle = fullCircle * i / segmentCount; outlinePoints.append(QPointF(center.x() + radius * qCos(angle), center.y() + radius * qSin(angle))); } } else { outlinePoints = outline->getOutlinePoints(); if(outlinePoints.size() > 3 && outlinePoints.first() == outlinePoints.last()) { outlinePoints.remove(outlinePoints.size() - 1); } } if(outlinePoints.size() < 3) { return false; } bounds = QPolygonF(outlinePoints).boundingRect().normalized(); return bounds.width() > 0.0 && bounds.height() > 0.0; } } nmWxPropertyInterpolationDlg::nmWxPropertyInterpolationDlg( nmGuiPlot* plot, nmDataAnalyzeManager* dataManager, QWidget* parent) : iDlgBase(parent), m_pPlot(plot), m_pDataManager(dataManager), m_pDataSetCombo(NULL), m_pAddDataSetButton(NULL), m_pDeleteDataSetButton(NULL), m_nCurrentDataSetIndex(-1), m_nNextPointId(0), m_bUpdatingUi(true), m_pPropertyCombo(NULL), m_pNameEdit(NULL), m_pNewValueSpin(NULL), m_pNewValueUnitCombo(NULL), m_pMapPickButton(NULL), m_pPointTable(NULL), m_pXUnitCombo(NULL), m_pYUnitCombo(NULL), m_pValueUnitCombo(NULL), m_pDeletePointButton(NULL), m_pClearPointsButton(NULL), m_pShowPointsCheck(NULL), m_pShowLabelsCheck(NULL), m_pNuggetSpin(NULL), m_pSillSpin(NULL), m_pRangeSpin(NULL), m_pRangeUnitCombo(NULL), m_pModelCombo(NULL), m_pUseKCheck(NULL), m_pKCalculationDataSetCombo(NULL), m_pUsePhiCheck(NULL), m_pPhiCalculationDataSetCombo(NULL), m_pUseHCheck(NULL), m_pHCalculationDataSetCombo(NULL), m_pPreviewButton(NULL), m_pPreviewDialog(NULL), m_pPreviewRefreshTimer(NULL) { setWindowTitle(interpolationText("Property Interpolation")); setWindowModality(Qt::NonModal); setModal(false); setMinimumSize(600, 620); resize(640, 680); // 合并连续编辑触发的刷新,避免每次数值变化都立即重新插值. m_pPreviewRefreshTimer = new QTimer(this); m_pPreviewRefreshTimer->setSingleShot(true); m_pPreviewRefreshTimer->setInterval(200); connect(m_pPreviewRefreshTimer, SIGNAL(timeout()), this, SLOT(refreshPreview())); initInterpolationUI(); initializeDataSets(); if(qApp != NULL) { qApp->installEventFilter(this); } } nmWxPropertyInterpolationDlg::~nmWxPropertyInterpolationDlg() { if(qApp != NULL) { qApp->removeEventFilter(this); } onMapPickToggled(false); saveCurrentDataSet(); syncDataSetsToManager(false); removeAllMarkers(); } void nmWxPropertyInterpolationDlg::initInterpolationUI() { const int topControlWidth = 350; const int compactComboWidth = 200; QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(12, 10, 12, 10); mainLayout->setSpacing(8); // 数据组、名称和属性共用一个表单,保证三行标签及输入控件对齐. QFormLayout* dataSetLayout = new QFormLayout; dataSetLayout->setContentsMargins(0, 0, 0, 0); dataSetLayout->setHorizontalSpacing(8); dataSetLayout->setVerticalSpacing(6); dataSetLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); QWidget* dataSetSelectWidget = new QWidget(this); dataSetSelectWidget->setMaximumWidth(topControlWidth); QHBoxLayout* dataSetSelectLayout = new QHBoxLayout(dataSetSelectWidget); dataSetSelectLayout->setContentsMargins(0, 0, 0, 0); dataSetSelectLayout->setSpacing(6); m_pDataSetCombo = new QComboBox(dataSetSelectWidget); dataSetSelectLayout->addWidget(m_pDataSetCombo, 1); QString iconDir = QCoreApplication::applicationDirPath(); iconDir = iconDir.section('/', 0, -2) + "/Res/Icon/"; m_pAddDataSetButton = new QPushButton(dataSetSelectWidget); m_pAddDataSetButton->setIcon(QIcon(iconDir + "Add.png")); m_pAddDataSetButton->setIconSize(QSize(18, 18)); m_pAddDataSetButton->setFixedSize(28, 28); m_pAddDataSetButton->setToolTip(interpolationText("Add dataset")); dataSetSelectLayout->addWidget(m_pAddDataSetButton); m_pDeleteDataSetButton = new QPushButton(dataSetSelectWidget); m_pDeleteDataSetButton->setIcon(QIcon(iconDir + "NmDelete.png")); m_pDeleteDataSetButton->setIconSize(QSize(18, 18)); m_pDeleteDataSetButton->setFixedSize(28, 28); m_pDeleteDataSetButton->setToolTip(interpolationText("Delete dataset")); dataSetSelectLayout->addWidget(m_pDeleteDataSetButton); dataSetLayout->addRow(interpolationText("Dataset:"), dataSetSelectWidget); m_pNameEdit = new QLineEdit(this); m_pNameEdit->setMaximumWidth(topControlWidth); dataSetLayout->addRow(interpolationText("Name:"), m_pNameEdit); m_pPropertyCombo = new QComboBox(this); m_pPropertyCombo->setMaximumWidth(topControlWidth); m_pPropertyCombo->addItem(interpolationText("Permeability k"), QString("k")); m_pPropertyCombo->addItem(interpolationText("Porosity phi"), QString("phi")); m_pPropertyCombo->addItem(interpolationText("Reservoir thickness h"), QString("h")); dataSetLayout->addRow(interpolationText("Property:"), m_pPropertyCombo); mainLayout->addLayout(dataSetLayout); // 测点数据录入区域. QGroupBox* pointGroup = new QGroupBox(interpolationText("Measurement Points"), this); QVBoxLayout* pointLayout = new QVBoxLayout(pointGroup); pointLayout->setContentsMargins(10, 8, 10, 10); pointLayout->setSpacing(6); QHBoxLayout* newPointLayout = new QHBoxLayout; newPointLayout->setSpacing(6); newPointLayout->addWidget(new QLabel(interpolationText("New value:"), pointGroup)); m_pNewValueSpin = createNumberSpinBox(pointGroup, 0.0); newPointLayout->addWidget(m_pNewValueSpin, 1); m_pNewValueUnitCombo = new QComboBox(pointGroup); m_pNewValueUnitCombo->setFixedWidth(80); populateUnitCombo(m_pNewValueUnitCombo, PERMEABILITY_BASE_UNIT, PERMEABILITY_DISPLAY_UNIT); newPointLayout->addWidget(m_pNewValueUnitCombo); m_pMapPickButton = new QPushButton(interpolationText("Select on Map"), pointGroup); m_pMapPickButton->setIcon(QIcon(iconDir + "AddPoint.png")); m_pMapPickButton->setIconSize(QSize(16, 16)); m_pMapPickButton->setMinimumWidth(100); m_pMapPickButton->setCheckable(true); m_pMapPickButton->setEnabled(!m_pPlot.isNull() && m_pPlot->m_pPlotView != NULL && m_pPlot->m_pPlot != NULL); newPointLayout->addWidget(m_pMapPickButton); pointLayout->addLayout(newPointLayout); m_pPointTable = new QTableWidget(1, 3, pointGroup); QStringList headers; headers << interpolationText("X") << interpolationText("Y") << interpolationText("Value"); m_pPointTable->setHorizontalHeaderLabels(headers); m_pPointTable->verticalHeader()->setVisible(true); m_pPointTable->verticalHeader()->setFixedWidth(36); m_pPointTable->verticalHeader()->setDefaultAlignment(Qt::AlignCenter); // 第0行为单位选择,不占用测点序号. m_pPointTable->setVerticalHeaderItem( POINT_UNIT_ROW, new QTableWidgetItem(QString())); m_pPointTable->horizontalHeader()->setResizeMode(QHeaderView::Stretch); m_pPointTable->setSelectionBehavior(QAbstractItemView::SelectRows); m_pPointTable->setSelectionMode(QAbstractItemView::SingleSelection); m_pPointTable->setAlternatingRowColors(true); m_pPointTable->setMinimumHeight(170); m_pPointTable->setRowHeight(POINT_UNIT_ROW, 24); // 第0行固定显示各列单位,数据行从第1行开始. m_pXUnitCombo = new QComboBox(m_pPointTable); m_pYUnitCombo = new QComboBox(m_pPointTable); m_pValueUnitCombo = new QComboBox(m_pPointTable); setTableUnitComboStyle(m_pXUnitCombo); setTableUnitComboStyle(m_pYUnitCombo); setTableUnitComboStyle(m_pValueUnitCombo); populateUnitCombo(m_pXUnitCombo, LENGTH_BASE_UNIT, LENGTH_BASE_UNIT); populateUnitCombo(m_pYUnitCombo, LENGTH_BASE_UNIT, LENGTH_BASE_UNIT); populateUnitCombo(m_pValueUnitCombo, PERMEABILITY_BASE_UNIT, PERMEABILITY_DISPLAY_UNIT); m_pPointTable->setCellWidget(POINT_UNIT_ROW, 0, m_pXUnitCombo); m_pPointTable->setCellWidget(POINT_UNIT_ROW, 1, m_pYUnitCombo); m_pPointTable->setCellWidget(POINT_UNIT_ROW, 2, m_pValueUnitCombo); pointLayout->addWidget(m_pPointTable, 1); // 显示选项放在左侧,当前测点操作放在右侧,减少分散的按钮行. QHBoxLayout* pointButtonLayout = new QHBoxLayout; pointButtonLayout->setSpacing(8); m_pShowPointsCheck = new QCheckBox(interpolationText("Show measurement points"), pointGroup); m_pShowPointsCheck->setChecked(true); pointButtonLayout->addWidget(m_pShowPointsCheck); m_pShowLabelsCheck = new QCheckBox(interpolationText("Show labels"), pointGroup); m_pShowLabelsCheck->setChecked(true); pointButtonLayout->addWidget(m_pShowLabelsCheck); pointButtonLayout->addStretch(); m_pDeletePointButton = new QPushButton(interpolationText("Delete Selected"), pointGroup); m_pDeletePointButton->setMinimumWidth(84); m_pDeletePointButton->setEnabled(false); pointButtonLayout->addWidget(m_pDeletePointButton); m_pClearPointsButton = new QPushButton(interpolationText("Clear"), pointGroup); m_pClearPointsButton->setMinimumWidth(72); m_pClearPointsButton->setEnabled(false); pointButtonLayout->addWidget(m_pClearPointsButton); pointLayout->addLayout(pointButtonLayout); mainLayout->addWidget(pointGroup, 1); // Kriging参数采用两行双列布局,保持信息完整并压缩纵向空间. QGroupBox* parameterGroup = new QGroupBox(interpolationText("Kriging Parameters"), this); QGridLayout* parameterLayout = new QGridLayout(parameterGroup); parameterLayout->setContentsMargins(10, 8, 10, 10); parameterLayout->setHorizontalSpacing(8); parameterLayout->setVerticalSpacing(6); m_pNuggetSpin = createNumberSpinBox(parameterGroup, 0.01); parameterLayout->addWidget(new QLabel(interpolationText("Nugget:"), parameterGroup), 0, 0); parameterLayout->addWidget(m_pNuggetSpin, 0, 1); m_pSillSpin = createNumberSpinBox(parameterGroup, 100.0); parameterLayout->addWidget(new QLabel(interpolationText("Sill:"), parameterGroup), 0, 2); parameterLayout->addWidget(m_pSillSpin, 0, 3); QWidget* rangeWidget = new QWidget(parameterGroup); QHBoxLayout* rangeLayout = new QHBoxLayout(rangeWidget); rangeLayout->setContentsMargins(0, 0, 0, 0); rangeLayout->setSpacing(6); m_pRangeSpin = createNumberSpinBox(rangeWidget, 1000.0); rangeLayout->addWidget(m_pRangeSpin, 1); m_pRangeUnitCombo = new QComboBox(rangeWidget); m_pRangeUnitCombo->setFixedWidth(72); populateUnitCombo(m_pRangeUnitCombo, LENGTH_BASE_UNIT, LENGTH_BASE_UNIT); rangeLayout->addWidget(m_pRangeUnitCombo); parameterLayout->addWidget(new QLabel(interpolationText("Range:"), parameterGroup), 1, 0); parameterLayout->addWidget(rangeWidget, 1, 1); m_pModelCombo = new QComboBox(parameterGroup); m_pModelCombo->setFixedWidth(compactComboWidth); m_pModelCombo->addItem(interpolationText("Spherical (0)"), 0); m_pModelCombo->addItem(interpolationText("Exponential (1)"), 1); m_pModelCombo->addItem(interpolationText("Gaussian (2)"), 2); parameterLayout->addWidget(new QLabel(interpolationText("Model:"), parameterGroup), 1, 2); parameterLayout->addWidget(m_pModelCombo, 1, 3); parameterLayout->setColumnStretch(1, 1); parameterLayout->setColumnStretch(3, 1); mainLayout->addWidget(parameterGroup); // 分别指定正式求解时需要使用插值结果的属性和数据组. QGroupBox* calculationGroup = new QGroupBox( interpolationText("Use Interpolation in Solver"), this); QGridLayout* calculationLayout = new QGridLayout(calculationGroup); calculationLayout->setContentsMargins(10, 8, 10, 10); calculationLayout->setHorizontalSpacing(8); calculationLayout->setVerticalSpacing(6); m_pUseKCheck = new QCheckBox(interpolationText("Permeability k"), calculationGroup); m_pKCalculationDataSetCombo = new QComboBox(calculationGroup); m_pKCalculationDataSetCombo->setFixedWidth(compactComboWidth); calculationLayout->addWidget(m_pUseKCheck, 0, 0); calculationLayout->addWidget(m_pKCalculationDataSetCombo, 0, 1); m_pUsePhiCheck = new QCheckBox(interpolationText("Porosity phi"), calculationGroup); m_pPhiCalculationDataSetCombo = new QComboBox(calculationGroup); m_pPhiCalculationDataSetCombo->setFixedWidth(compactComboWidth); calculationLayout->addWidget(m_pUsePhiCheck, 1, 0); calculationLayout->addWidget(m_pPhiCalculationDataSetCombo, 1, 1); m_pUseHCheck = new QCheckBox(interpolationText("Reservoir thickness h"), calculationGroup); m_pHCalculationDataSetCombo = new QComboBox(calculationGroup); m_pHCalculationDataSetCombo->setFixedWidth(compactComboWidth); calculationLayout->addWidget(m_pUseHCheck, 2, 0); calculationLayout->addWidget(m_pHCalculationDataSetCombo, 2, 1); calculationLayout->setColumnMinimumWidth(1, compactComboWidth); calculationLayout->setColumnStretch(2, 1); mainLayout->addWidget(calculationGroup); // 底部操作按钮. QHBoxLayout* buttonLayout = new QHBoxLayout; m_pPreviewButton = new QPushButton(interpolationText("Preview"), this); m_pPreviewButton->setMinimumWidth(84); m_pPreviewButton->setEnabled(false); buttonLayout->addWidget(m_pPreviewButton); buttonLayout->addStretch(); QPushButton* closeButton = new QPushButton(interpolationText("Close"), this); closeButton->setMinimumWidth(84); buttonLayout->addWidget(closeButton); mainLayout->addLayout(buttonLayout); connect(m_pDataSetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onDataSetChanged(int))); connect(m_pAddDataSetButton, SIGNAL(clicked()), this, SLOT(onAddDataSet())); connect(m_pDeleteDataSetButton, SIGNAL(clicked()), this, SLOT(onDeleteDataSet())); connect(m_pNameEdit, SIGNAL(textChanged(QString)), this, SLOT(onDataSetNameChanged(QString))); connect(m_pPropertyCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentDataSetPropertyChanged())); connect(m_pNuggetSpin, SIGNAL(valueChanged(double)), this, SLOT(onCurrentDataSetEdited())); connect(m_pSillSpin, SIGNAL(valueChanged(double)), this, SLOT(onCurrentDataSetEdited())); connect(m_pRangeSpin, SIGNAL(valueChanged(double)), this, SLOT(onCurrentDataSetEdited())); connect(m_pXUnitCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(onXDisplayUnitChanged(const QString&))); connect(m_pYUnitCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(onYDisplayUnitChanged(const QString&))); connect(m_pNewValueUnitCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(onValueDisplayUnitChanged(const QString&))); connect(m_pValueUnitCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(onValueDisplayUnitChanged(const QString&))); connect(m_pRangeUnitCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(onRangeDisplayUnitChanged(const QString&))); connect(m_pModelCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentDataSetEdited())); connect(m_pMapPickButton, SIGNAL(toggled(bool)), this, SLOT(onMapPickToggled(bool))); connect(m_pPointTable, SIGNAL(itemSelectionChanged()), this, SLOT(onPointSelectionChanged())); connect(m_pPointTable, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(onPointItemChanged(QTableWidgetItem*))); connect(m_pDeletePointButton, SIGNAL(clicked()), this, SLOT(onDeleteSelectedPoint())); connect(m_pClearPointsButton, SIGNAL(clicked()), this, SLOT(onClearPoints())); connect(m_pShowPointsCheck, SIGNAL(toggled(bool)), this, SLOT(onShowPointsToggled(bool))); connect(m_pShowLabelsCheck, SIGNAL(toggled(bool)), this, SLOT(onShowLabelsToggled(bool))); connect(m_pUseKCheck, SIGNAL(toggled(bool)), this, SLOT(onCalculationSelectionChanged())); connect(m_pKCalculationDataSetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onCalculationSelectionChanged())); connect(m_pUsePhiCheck, SIGNAL(toggled(bool)), this, SLOT(onCalculationSelectionChanged())); connect(m_pPhiCalculationDataSetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onCalculationSelectionChanged())); connect(m_pUseHCheck, SIGNAL(toggled(bool)), this, SLOT(onCalculationSelectionChanged())); connect(m_pHCalculationDataSetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onCalculationSelectionChanged())); connect(m_pPreviewButton, SIGNAL(clicked()), this, SLOT(onPreview())); connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); } void nmWxPropertyInterpolationDlg::initializeDataSets() { m_bUpdatingUi = true; // 数据组允许为空,首次进入窗口时不自动创建默认数据组. if(!m_pDataManager.isNull()) { m_vecDataSets = m_pDataManager->getPropertyInterpolationDataSets(); } bool dataChanged = false; for(int i = 0; i < m_vecDataSets.size(); ++i) { if(m_vecDataSets[i].name.isEmpty()) { m_vecDataSets[i].name = nextDataSetName(); dataChanged = true; } } m_vecMarkerNames.resize(m_vecDataSets.size()); // 测点标记不参与持久化,打开对话框时根据已保存测点重新创建. createMarkersForAllDataSets(); bool oldBlock = m_pDataSetCombo->blockSignals(true); m_pDataSetCombo->clear(); for(int i = 0; i < m_vecDataSets.size(); ++i) { m_pDataSetCombo->addItem(m_vecDataSets[i].name); } const int initialIndex = m_vecDataSets.isEmpty() ? -1 : 0; m_pDataSetCombo->setCurrentIndex(initialIndex); m_pDataSetCombo->blockSignals(oldBlock); m_nCurrentDataSetIndex = initialIndex; loadDataSet(initialIndex); m_bUpdatingUi = false; updateDataSetButtons(); updateCalculationDataSetControls(); if(dataChanged) { syncDataSetsToManager(false); } } nmPropertyInterpolationDataSet nmWxPropertyInterpolationDlg::createDefaultDataSet() { nmPropertyInterpolationDataSet dataSet; dataSet.name = nextDataSetName(); dataSet.property = "k"; dataSet.showPoints = true; dataSet.showLabels = true; dataSet.nugget = 0.01; dataSet.sill = 100.0; dataSet.range = 1000.0; dataSet.model = 0; return dataSet; } QString nmWxPropertyInterpolationDlg::nextDataSetName() { for(int number = 1; ; ++number) { const QString name = interpolationText("Dataset #%1").arg(number); bool exists = false; for(int i = 0; i < m_vecDataSets.size(); ++i) { if(m_vecDataSets[i].name == name) { exists = true; break; } } if(!exists) { return name; } } } void nmWxPropertyInterpolationDlg::loadDataSet(int index) { if(index < 0 || index >= m_vecDataSets.size()) { clearCurrentDataSet(); return; } // 切换数据组时批量刷新控件,避免控件信号被误判为用户修改. bool wasUpdating = m_bUpdatingUi; m_bUpdatingUi = true; nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[index]; m_pNameEdit->setText(dataSet.name); int propertyIndex = m_pPropertyCombo->findData(dataSet.property); m_pPropertyCombo->setCurrentIndex(propertyIndex < 0 ? 0 : propertyIndex); updateUnitControls(dataSet); m_pNewValueSpin->setValue(0.0); m_pNuggetSpin->setValue(dataSet.nugget); m_pSillSpin->setValue(dataSet.sill); // Range在数据组中保存为m,加载时转换成当前选择的显示单位. m_pRangeSpin->setValue(convertedUnitValue( dataSet.range, LENGTH_BASE_UNIT, m_pRangeUnitCombo->currentText())); int modelIndex = m_pModelCombo->findData(dataSet.model); m_pModelCombo->setCurrentIndex(modelIndex < 0 ? 0 : modelIndex); m_pShowPointsCheck->setChecked(dataSet.showPoints); m_pShowLabelsCheck->setChecked(dataSet.showLabels); // 重建测点行时保留第0行单位控件,并阻止itemChanged反向修改数据. bool oldTableBlock = m_pPointTable->blockSignals(true); clearPointRows(); const QStringList markerNames = m_vecMarkerNames.value(index); for(int i = 0; i < dataSet.points.size(); ++i) { appendPointRow(dataSet.points[i], markerNames.value(i)); } m_pPointTable->blockSignals(oldTableBlock); updatePointDisplayValues(); clearTableSelection(); updatePointButtons(); updateCurrentMarkerVisibility(); m_bUpdatingUi = wasUpdating; updateDataSetButtons(); } void nmWxPropertyInterpolationDlg::clearCurrentDataSet() { bool wasUpdating = m_bUpdatingUi; m_bUpdatingUi = true; if(m_pMapPickButton->isChecked()) { m_pMapPickButton->setChecked(false); } m_pNameEdit->clear(); m_pPropertyCombo->setCurrentIndex(-1); m_pNewValueSpin->setValue(0.0); // 没有当前数据组时保留坐标基准单位,属性值单位置空并禁用. populateUnitCombo(m_pXUnitCombo, LENGTH_BASE_UNIT, LENGTH_BASE_UNIT); populateUnitCombo(m_pYUnitCombo, LENGTH_BASE_UNIT, LENGTH_BASE_UNIT); populateUnitCombo(m_pNewValueUnitCombo, QString(), QString()); populateUnitCombo(m_pValueUnitCombo, QString(), QString()); populateUnitCombo(m_pRangeUnitCombo, LENGTH_BASE_UNIT, LENGTH_BASE_UNIT); clearPointRows(); m_pShowPointsCheck->setChecked(false); m_pShowLabelsCheck->setChecked(false); m_pNuggetSpin->setValue(0.01); m_pSillSpin->setValue(100.0); m_pRangeSpin->setValue(1000.0); m_pModelCombo->setCurrentIndex(-1); clearTableSelection(); m_bUpdatingUi = wasUpdating; updateDataSetButtons(); } void nmWxPropertyInterpolationDlg::saveCurrentDataSet() { if(m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; dataSet.name = m_pNameEdit->text(); dataSet.property = m_pPropertyCombo->itemData(m_pPropertyCombo->currentIndex()).toString(); // 显示单位作为界面偏好保存,Range写回数据组前统一转换为米. dataSet.xDisplayUnit = m_pXUnitCombo->currentText(); dataSet.yDisplayUnit = m_pYUnitCombo->currentText(); dataSet.valueDisplayUnit = propertyBaseUnit(dataSet.property).isEmpty() ? QString() : m_pValueUnitCombo->currentText(); dataSet.rangeDisplayUnit = m_pRangeUnitCombo->currentText(); dataSet.nugget = m_pNuggetSpin->value(); dataSet.sill = m_pSillSpin->value(); dataSet.range = convertedUnitValue( m_pRangeSpin->value(), dataSet.rangeDisplayUnit, LENGTH_BASE_UNIT); dataSet.model = m_pModelCombo->itemData(m_pModelCombo->currentIndex()).toInt(); dataSet.showPoints = m_pShowPointsCheck->isChecked(); dataSet.showLabels = m_pShowLabelsCheck->isChecked(); } void nmWxPropertyInterpolationDlg::updateUnitControls( nmPropertyInterpolationDataSet& dataSet) { populateUnitCombo(m_pXUnitCombo, LENGTH_BASE_UNIT, dataSet.xDisplayUnit); populateUnitCombo(m_pYUnitCombo, LENGTH_BASE_UNIT, dataSet.yDisplayUnit); const QString valueBaseUnit = propertyBaseUnit(dataSet.property); // 未保存显示单位时按属性默认值初始化,孔隙度使用空单位. const QString valueDisplayUnit = dataSet.valueDisplayUnit.isEmpty() ? defaultPropertyDisplayUnit(dataSet.property) : dataSet.valueDisplayUnit; populateUnitCombo(m_pNewValueUnitCombo, valueBaseUnit, valueDisplayUnit); populateUnitCombo(m_pValueUnitCombo, valueBaseUnit, valueDisplayUnit); populateUnitCombo(m_pRangeUnitCombo, LENGTH_BASE_UNIT, dataSet.rangeDisplayUnit); dataSet.xDisplayUnit = m_pXUnitCombo->currentText(); dataSet.yDisplayUnit = m_pYUnitCombo->currentText(); dataSet.valueDisplayUnit = valueBaseUnit.isEmpty() ? QString() : m_pValueUnitCombo->currentText(); dataSet.rangeDisplayUnit = m_pRangeUnitCombo->currentText(); } void nmWxPropertyInterpolationDlg::updatePointDisplayValues() { if(m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } const nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; const QString valueBaseUnit = propertyBaseUnit(dataSet.property); // 测点保留基准值,仅换算表格和地图标签;屏蔽信号避免刷新时反向写回. bool oldTableBlock = m_pPointTable->blockSignals(true); for(int pointIndex = 0; pointIndex < dataSet.points.size(); ++pointIndex) { const int tableRow = pointIndex + FIRST_POINT_ROW; if(tableRow >= m_pPointTable->rowCount()) { break; } const nmPropertyInterpolationPointData& point = dataSet.points[pointIndex]; QTableWidgetItem* xItem = m_pPointTable->item(tableRow, 0); QTableWidgetItem* yItem = m_pPointTable->item(tableRow, 1); QTableWidgetItem* valueItem = m_pPointTable->item(tableRow, 2); if(xItem != NULL) { xItem->setText(displayValueText(convertedUnitValue( point.x, LENGTH_BASE_UNIT, dataSet.xDisplayUnit))); } if(yItem != NULL) { yItem->setText(displayValueText(convertedUnitValue( point.y, LENGTH_BASE_UNIT, dataSet.yDisplayUnit))); } const double displayValue = convertedUnitValue( point.value, valueBaseUnit, dataSet.valueDisplayUnit); const QString valueText = displayValueText(displayValue); if(valueItem != NULL) { valueItem->setText(valueText); } nmObjPoint* marker = markerAt(tableRow); if(marker != NULL) { setMarkerLabel(marker, valueText); marker->update(); } } m_pPointTable->blockSignals(oldTableBlock); } void nmWxPropertyInterpolationDlg::clearPointRows() { // 只删除测点数据行,第0行的单位下拉框始终保留. if(m_pPointTable != NULL && m_pPointTable->rowCount() > FIRST_POINT_ROW) { m_pPointTable->setRowCount(FIRST_POINT_ROW); } updatePointRowNumbers(); } void nmWxPropertyInterpolationDlg::updatePointRowNumbers() { if(m_pPointTable == NULL) { return; } // 单位行留空,测点数据行始终从1开始连续编号. for(int row = POINT_UNIT_ROW; row < m_pPointTable->rowCount(); ++row) { const QString number = row < FIRST_POINT_ROW ? QString() : QString::number(row - FIRST_POINT_ROW + 1); QTableWidgetItem* headerItem = m_pPointTable->verticalHeaderItem(row); if(headerItem == NULL) { m_pPointTable->setVerticalHeaderItem(row, new QTableWidgetItem(number)); } else { headerItem->setText(number); } } } void nmWxPropertyInterpolationDlg::syncDataSetsToManager(bool markModified) { if(!m_pDataManager.isNull()) { m_pDataManager->setPropertyInterpolationDataSets(m_vecDataSets); } if(markModified && !m_pPlot.isNull()) { m_pPlot->setModified(true); } } void nmWxPropertyInterpolationDlg::updateDataSetButtons() { const bool hasCurrent = m_nCurrentDataSetIndex >= 0 && m_nCurrentDataSetIndex < m_vecDataSets.size(); const bool canPickOnMap = hasCurrent && !m_pPlot.isNull() && m_pPlot->m_pPlotView != NULL && m_pPlot->m_pPlot != NULL; const bool hasValueUnit = hasCurrent && !propertyBaseUnit(m_vecDataSets[m_nCurrentDataSetIndex].property).isEmpty(); m_pDataSetCombo->setEnabled(hasCurrent); m_pDeleteDataSetButton->setEnabled(hasCurrent); m_pNameEdit->setEnabled(hasCurrent); m_pPropertyCombo->setEnabled(hasCurrent); m_pNewValueSpin->setEnabled(hasCurrent); m_pNewValueUnitCombo->setEnabled(hasValueUnit); m_pMapPickButton->setEnabled(canPickOnMap); m_pPointTable->setEnabled(hasCurrent); m_pXUnitCombo->setEnabled(hasCurrent); m_pYUnitCombo->setEnabled(hasCurrent); m_pValueUnitCombo->setEnabled(hasValueUnit); m_pShowPointsCheck->setEnabled(hasCurrent); m_pShowLabelsCheck->setEnabled(hasCurrent); m_pNuggetSpin->setEnabled(hasCurrent); m_pSillSpin->setEnabled(hasCurrent); m_pRangeSpin->setEnabled(hasCurrent); m_pRangeUnitCombo->setEnabled(hasCurrent); m_pModelCombo->setEnabled(hasCurrent); updatePointButtons(); } void nmWxPropertyInterpolationDlg::updateCalculationDataSetControls() { // 批量重建三个下拉框时阻止选择变化信号写回未完成的界面状态. bool wasUpdating = m_bUpdatingUi; m_bUpdatingUi = true; updateCalculationDataSetControl("k", m_pUseKCheck, m_pKCalculationDataSetCombo); updateCalculationDataSetControl("phi", m_pUsePhiCheck, m_pPhiCalculationDataSetCombo); updateCalculationDataSetControl("h", m_pUseHCheck, m_pHCalculationDataSetCombo); m_bUpdatingUi = wasUpdating; } void nmWxPropertyInterpolationDlg::updateCalculationDataSetControl( const QString& property, QCheckBox* checkBox, QComboBox* comboBox) { if(checkBox == NULL || comboBox == NULL) { return; } // 下拉项保存原数据组下标,并根据useForCalculation恢复当前选择. comboBox->clear(); int selectedComboIndex = -1; for(int dataSetIndex = 0; dataSetIndex < m_vecDataSets.size(); ++dataSetIndex) { const nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[dataSetIndex]; if(dataSet.property != property) { continue; } comboBox->addItem(dataSet.name, dataSetIndex); if(selectedComboIndex < 0 && dataSet.useForCalculation) { selectedComboIndex = comboBox->count() - 1; } } comboBox->setCurrentIndex(selectedComboIndex >= 0 ? selectedComboIndex : (comboBox->count() > 0 ? 0 : -1)); checkBox->setChecked(selectedComboIndex >= 0); checkBox->setEnabled(comboBox->count() > 0); comboBox->setEnabled(checkBox->isChecked() && comboBox->count() > 0); } void nmWxPropertyInterpolationDlg::saveCalculationSelection( const QString& property, QCheckBox* checkBox, QComboBox* comboBox) { // 先清除同属性的旧标记,保证每个属性最多启用一个插值数据组. for(int dataSetIndex = 0; dataSetIndex < m_vecDataSets.size(); ++dataSetIndex) { if(m_vecDataSets[dataSetIndex].property == property) { m_vecDataSets[dataSetIndex].useForCalculation = false; } } if(checkBox == NULL || comboBox == NULL || !checkBox->isChecked()) { return; } const int selectedDataSetIndex = comboBox->itemData( comboBox->currentIndex()).toInt(); if(selectedDataSetIndex >= 0 && selectedDataSetIndex < m_vecDataSets.size() && m_vecDataSets[selectedDataSetIndex].property == property) { m_vecDataSets[selectedDataSetIndex].useForCalculation = true; } } void nmWxPropertyInterpolationDlg::onDataSetChanged(int index) { if(m_bUpdatingUi || index < 0 || index >= m_vecDataSets.size() || index == m_nCurrentDataSetIndex) { return; } saveCurrentDataSet(); syncDataSetsToManager(false); m_nCurrentDataSetIndex = index; loadDataSet(index); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onAddDataSet() { saveCurrentDataSet(); m_vecDataSets.append(createDefaultDataSet()); m_vecMarkerNames.append(QStringList()); int newIndex = m_vecDataSets.size() - 1; bool oldBlock = m_pDataSetCombo->blockSignals(true); m_pDataSetCombo->addItem(m_vecDataSets[newIndex].name); m_pDataSetCombo->setCurrentIndex(newIndex); m_pDataSetCombo->blockSignals(oldBlock); m_nCurrentDataSetIndex = newIndex; loadDataSet(newIndex); updateDataSetButtons(); updateCalculationDataSetControls(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onDeleteDataSet() { if(m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } int removedIndex = m_nCurrentDataSetIndex; removeDataSetMarkers(removedIndex); m_vecDataSets.remove(removedIndex); m_vecMarkerNames.remove(removedIndex); const int newIndex = m_vecDataSets.isEmpty() ? -1 : qMin(removedIndex, m_vecDataSets.size() - 1); bool oldBlock = m_pDataSetCombo->blockSignals(true); m_pDataSetCombo->removeItem(removedIndex); m_pDataSetCombo->setCurrentIndex(newIndex); m_pDataSetCombo->blockSignals(oldBlock); m_nCurrentDataSetIndex = newIndex; loadDataSet(newIndex); updateDataSetButtons(); updateCalculationDataSetControls(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onDataSetNameChanged(const QString& name) { if(m_bUpdatingUi || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } m_vecDataSets[m_nCurrentDataSetIndex].name = name; m_pDataSetCombo->setItemText(m_nCurrentDataSetIndex, name); updateCalculationDataSetControls(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onCurrentDataSetPropertyChanged() { if(m_bUpdatingUi || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } // 数据组切换属性后取消原有求解选择,并切换到对应的基准显示单位. nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; dataSet.useForCalculation = false; dataSet.property = m_pPropertyCombo->itemData( m_pPropertyCombo->currentIndex()).toString(); dataSet.valueDisplayUnit = defaultPropertyDisplayUnit(dataSet.property); updateUnitControls(dataSet); m_pNewValueSpin->setValue(0.0); updatePointDisplayValues(); saveCurrentDataSet(); updateCalculationDataSetControls(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onCurrentDataSetEdited() { if(m_bUpdatingUi) { return; } saveCurrentDataSet(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onXDisplayUnitChanged(const QString& unit) { if(m_bUpdatingUi || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } // 坐标基准值保持为m,仅刷新X列的显示文本. m_vecDataSets[m_nCurrentDataSetIndex].xDisplayUnit = unit; updatePointDisplayValues(); syncDataSetsToManager(true); } void nmWxPropertyInterpolationDlg::onYDisplayUnitChanged(const QString& unit) { if(m_bUpdatingUi || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } // 坐标基准值保持为m,仅刷新Y列的显示文本. m_vecDataSets[m_nCurrentDataSetIndex].yDisplayUnit = unit; updatePointDisplayValues(); syncDataSetsToManager(true); } void nmWxPropertyInterpolationDlg::onValueDisplayUnitChanged(const QString& unit) { if(m_bUpdatingUi || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; const QString baseUnit = propertyBaseUnit(dataSet.property); if(baseUnit.isEmpty() || unit.isEmpty() || unit == dataSet.valueDisplayUnit) { return; } // 经基准单位中转,切换单位时不会改变待添加值表示的物理量. const double baseNewValue = convertedUnitValue( m_pNewValueSpin->value(), dataSet.valueDisplayUnit, baseUnit); dataSet.valueDisplayUnit = unit; // 新值和表头共用显示单位,联动时屏蔽信号以避免递归触发. bool oldBlock = m_pNewValueUnitCombo->blockSignals(true); m_pNewValueUnitCombo->setCurrentIndex( m_pNewValueUnitCombo->findText(unit)); m_pNewValueUnitCombo->blockSignals(oldBlock); oldBlock = m_pValueUnitCombo->blockSignals(true); m_pValueUnitCombo->setCurrentIndex(m_pValueUnitCombo->findText(unit)); m_pValueUnitCombo->blockSignals(oldBlock); oldBlock = m_pNewValueSpin->blockSignals(true); m_pNewValueSpin->setValue(convertedUnitValue( baseNewValue, baseUnit, unit)); m_pNewValueSpin->blockSignals(oldBlock); updatePointDisplayValues(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onRangeDisplayUnitChanged(const QString& unit) { if(m_bUpdatingUi || unit.isEmpty() || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; // 先按旧显示单位还原米制基准值,再使用新单位回显. dataSet.range = convertedUnitValue( m_pRangeSpin->value(), dataSet.rangeDisplayUnit, LENGTH_BASE_UNIT); dataSet.rangeDisplayUnit = unit; bool oldBlock = m_pRangeSpin->blockSignals(true); m_pRangeSpin->setValue(convertedUnitValue( dataSet.range, LENGTH_BASE_UNIT, unit)); m_pRangeSpin->blockSignals(oldBlock); syncDataSetsToManager(true); } void nmWxPropertyInterpolationDlg::onCalculationSelectionChanged() { if(m_bUpdatingUi) { return; } // 先保存当前编辑内容,再统一更新三个属性的唯一求解数据组. saveCurrentDataSet(); saveCalculationSelection("k", m_pUseKCheck, m_pKCalculationDataSetCombo); saveCalculationSelection("phi", m_pUsePhiCheck, m_pPhiCalculationDataSetCombo); saveCalculationSelection("h", m_pUseHCheck, m_pHCalculationDataSetCombo); m_pKCalculationDataSetCombo->setEnabled(m_pUseKCheck->isChecked()); m_pPhiCalculationDataSetCombo->setEnabled(m_pUsePhiCheck->isChecked()); m_pHCalculationDataSetCombo->setEnabled(m_pUseHCheck->isChecked()); syncDataSetsToManager(true); } bool nmWxPropertyInterpolationDlg::eventFilter(QObject* watched, QEvent* event) { // 点击表格空白处或其他控件时取消测点行的选中状态. if(event != NULL && event->type() == QEvent::MouseButtonPress && m_pPointTable != NULL) { QWidget* clickedWidget = qobject_cast(watched); if(clickedWidget != NULL) { bool isTableViewport = clickedWidget == m_pPointTable->viewport() || m_pPointTable->viewport()->isAncestorOf(clickedWidget); if(clickedWidget == m_pPointTable->viewport()) { QMouseEvent* mouseEvent = static_cast(event); if(m_pPointTable->itemAt(mouseEvent->pos()) == NULL) { clearTableSelection(); } } else if(!isTableViewport && clickedWidget != m_pDeletePointButton) { clearTableSelection(); } } } return iDlgBase::eventFilter(watched, event); } void nmWxPropertyInterpolationDlg::onMapPickToggled(bool checked) { if(m_pPlot.isNull() || m_pPlot->m_pPlotView == NULL) { return; } // 每次切换前先断开旧连接,防止一次地图点击被重复处理. disconnect(m_pPlot->m_pPlotView, SIGNAL(sigLeftClick(QPointF)), this, SLOT(onMapClicked(QPointF))); if(checked) { m_pPlot->cancelActiveTools(); connect(m_pPlot->m_pPlotView, SIGNAL(sigLeftClick(QPointF)), this, SLOT(onMapClicked(QPointF))); m_pPlot->m_pPlotView->setCursor(Qt::CrossCursor); } else { m_pPlot->m_pPlotView->restoreCursor(); } } void nmWxPropertyInterpolationDlg::onMapClicked(const QPointF& position) { if(!m_pMapPickButton->isChecked() || m_pPlot.isNull() || m_pPlot->m_pPlot == NULL || m_nCurrentDataSetIndex < 0) { return; } if(!m_pPlot->m_pPlot->getInnerRectF().contains(position)) { return; } // 点击位置为绘图区坐标,需要转换成实际数据坐标后保存. QPointF plotPosition = position; QPointF valuePoint = m_pPlot->m_pPlot->getValueForPos(plotPosition); appendPoint(valuePoint); } void nmWxPropertyInterpolationDlg::appendPoint(const QPointF& valuePoint) { if(m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; const QString valueBaseUnit = propertyBaseUnit(dataSet.property); const double displayPointValue = m_pNewValueSpin->value(); // 属性值按求解器基准单位保存,地图标签继续使用当前显示值. const double pointValue = convertedUnitValue( displayPointValue, dataSet.valueDisplayUnit, valueBaseUnit); nmPropertyInterpolationPointData point(valuePoint.x(), valuePoint.y(), pointValue); QString markerName = createMarker(valuePoint, displayPointValue, dataSet.showPoints, dataSet.showLabels); // 测点数据、地图标记和表格行按相同顺序同步追加. dataSet.points.append(point); m_vecMarkerNames[m_nCurrentDataSetIndex].append(markerName); bool oldBlock = m_pPointTable->blockSignals(true); appendPointRow(point, markerName); m_pPointTable->blockSignals(oldBlock); m_pPointTable->scrollToBottom(); updatePointButtons(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::appendPointRow( const nmPropertyInterpolationPointData& point, const QString& markerName) { // rowCount已包含单位行,新插入行天然与points下标保持FIRST_POINT_ROW偏移. int row = m_pPointTable->rowCount(); m_pPointTable->insertRow(row); m_pPointTable->setVerticalHeaderItem( row, new QTableWidgetItem(QString::number( row - FIRST_POINT_ROW + 1))); QString xUnit = LENGTH_BASE_UNIT; QString yUnit = LENGTH_BASE_UNIT; QString valueUnit; QString valueBaseUnit; if(m_nCurrentDataSetIndex >= 0 && m_nCurrentDataSetIndex < m_vecDataSets.size()) { const nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; xUnit = dataSet.xDisplayUnit; yUnit = dataSet.yDisplayUnit; valueUnit = dataSet.valueDisplayUnit; valueBaseUnit = propertyBaseUnit(dataSet.property); } QTableWidgetItem* xItem = new QTableWidgetItem(displayValueText( convertedUnitValue(point.x, LENGTH_BASE_UNIT, xUnit))); // 将地图标记名称绑定在X列,后续可由表格行直接定位对应标记. xItem->setData(Qt::UserRole, markerName); m_pPointTable->setItem(row, 0, xItem); QTableWidgetItem* yItem = new QTableWidgetItem(displayValueText( convertedUnitValue(point.y, LENGTH_BASE_UNIT, yUnit))); m_pPointTable->setItem(row, 1, yItem); m_pPointTable->setItem(row, 2, new QTableWidgetItem(displayValueText( convertedUnitValue(point.value, valueBaseUnit, valueUnit)))); } QString nmWxPropertyInterpolationDlg::createMarker(const QPointF& valuePosition, double value, bool showPoints, bool showLabels) { if(m_pPlot.isNull() || m_pPlot->m_pPlot == NULL) { return QString(); } QString markerName; do { markerName = QString("__PropertyInterpolationPoint_%1").arg(++m_nNextPointId); } while(m_pPlot->m_pPlot->getObjByName(markerName) != NULL); // 地图标记仅用于临时显示,不应单独改变工程修改状态. bool wasModified = m_pPlot->isModified(); PropertyInterpolationMarker* marker = new PropertyInterpolationMarker( markerName, m_pPlot->m_pPlot->getMainAxisX(), m_pPlot->m_pPlot->getMainAxisY()); m_pPlot->bindObjSignals(marker); if(!m_pPlot->m_pPlot->addOneObj(marker)) { delete marker; m_pPlot->setModified(wasModified); return QString(); } QVector values; values.append(valuePosition); marker->setAllValues(values); m_pPlot->slotObjCompleted(marker); setMarkerLabel(marker, QString::number(value, 'g', 10)); marker->setReadOnly(true); marker->setLockPos(true); marker->showSubObjs(showPoints && showLabels); marker->setVisible(showPoints); marker->deselect(true); marker->update(); m_pPlot->setModified(wasModified); return markerName; } void nmWxPropertyInterpolationDlg::createMarkersForAllDataSets() { if(m_pPlot.isNull() || m_pPlot->m_pPlot == NULL) { return; } // 地图标签使用显示单位,数据组中的测点基准值保持不变. for(int dataSetIndex = 0; dataSetIndex < m_vecDataSets.size(); ++dataSetIndex) { const nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[dataSetIndex]; QStringList markerNames; for(int pointIndex = 0; pointIndex < dataSet.points.size(); ++pointIndex) { const nmPropertyInterpolationPointData& point = dataSet.points[pointIndex]; QPointF valuePosition(point.x, point.y); const double displayValue = convertedUnitValue( point.value, propertyBaseUnit(dataSet.property), dataSet.valueDisplayUnit); markerNames.append(createMarker(valuePosition, displayValue, dataSet.showPoints, dataSet.showLabels)); } m_vecMarkerNames[dataSetIndex] = markerNames; } } void nmWxPropertyInterpolationDlg::removeDataSetMarkers(int dataSetIndex) { if(dataSetIndex < 0 || dataSetIndex >= m_vecMarkerNames.size()) { return; } const QStringList markerNames = m_vecMarkerNames[dataSetIndex]; for(int i = 0; i < markerNames.size(); ++i) { removeMarkerByName(markerNames[i]); } m_vecMarkerNames[dataSetIndex].clear(); } void nmWxPropertyInterpolationDlg::removeAllMarkers() { for(int i = 0; i < m_vecMarkerNames.size(); ++i) { removeDataSetMarkers(i); } m_vecMarkerNames.clear(); } QString nmWxPropertyInterpolationDlg::markerNameAt(int row) const { if(m_pPointTable == NULL || row < FIRST_POINT_ROW || row >= m_pPointTable->rowCount()) { return QString(); } QTableWidgetItem* item = m_pPointTable->item(row, 0); return item == NULL ? QString() : item->data(Qt::UserRole).toString(); } nmObjPoint* nmWxPropertyInterpolationDlg::markerByName(const QString& markerName) const { if(m_pPlot.isNull() || m_pPlot->m_pPlot == NULL || markerName.isEmpty()) { return NULL; } return dynamic_cast(m_pPlot->m_pPlot->getObjByName(markerName)); } nmObjPoint* nmWxPropertyInterpolationDlg::markerAt(int row) const { return markerByName(markerNameAt(row)); } void nmWxPropertyInterpolationDlg::removeMarkerByName(const QString& markerName) { if(m_pPlot.isNull() || m_pPlot->m_pPlot == NULL || markerName.isEmpty() || m_pPlot->m_pPlot->getObjByName(markerName) == NULL) { return; } bool wasModified = m_pPlot->isModified(); m_pPlot->m_pPlot->removeObjByName(markerName); m_pPlot->setModified(wasModified); } void nmWxPropertyInterpolationDlg::onPointSelectionChanged() { // 单位行只用于切换显示单位,不作为可删除的测点行. if(m_pPointTable->currentRow() == POINT_UNIT_ROW) { clearTableSelection(); return; } updatePointButtons(); } void nmWxPropertyInterpolationDlg::onPointItemChanged(QTableWidgetItem* item) { // 表格行减去单位行偏移后,才是数据组中的测点下标. const int pointIndex = item == NULL ? -1 : item->row() - FIRST_POINT_ROW; if(m_bUpdatingUi || item == NULL || item->column() < 0 || item->column() > 2 || pointIndex < 0 || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size() || pointIndex >= m_vecDataSets[m_nCurrentDataSetIndex].points.size()) { return; } nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; nmPropertyInterpolationPointData& point = dataSet.points[pointIndex]; // X、Y及属性值分别按当前显示单位换算回数据中心使用的基准单位. QString baseUnit; QString displayUnit; double oldBaseValue = 0.0; if(item->column() == 0) { baseUnit = LENGTH_BASE_UNIT; displayUnit = dataSet.xDisplayUnit; oldBaseValue = point.x; } else if(item->column() == 1) { baseUnit = LENGTH_BASE_UNIT; displayUnit = dataSet.yDisplayUnit; oldBaseValue = point.y; } else { baseUnit = propertyBaseUnit(dataSet.property); displayUnit = dataSet.valueDisplayUnit; oldBaseValue = point.value; } bool valueOk = false; const double displayValue = item->text().toDouble(&valueOk); if(!valueOk || (item->column() == 2 && displayValue < 0.0)) { // 坐标允许为负数;属性值仍要求非负,非法输入恢复为修改前的值. const double oldDisplayValue = convertedUnitValue( oldBaseValue, baseUnit, displayUnit); const bool oldBlock = m_pPointTable->blockSignals(true); item->setText(displayValueText(oldDisplayValue)); m_pPointTable->blockSignals(oldBlock); return; } const double baseValue = convertedUnitValue( displayValue, displayUnit, baseUnit); if(item->column() == 0) { point.x = baseValue; } else if(item->column() == 1) { point.y = baseValue; } else { point.value = baseValue; } const QString displayText = displayValueText(displayValue); if(item->text() != displayText) { const bool oldBlock = m_pPointTable->blockSignals(true); item->setText(displayText); m_pPointTable->blockSignals(oldBlock); } // 坐标修改后移动Map标记,属性值修改后刷新标记标签. nmObjPoint* marker = markerAt(item->row()); if(marker != NULL) { if(item->column() < 2) { QVector positions; positions.append(QPointF(point.x, point.y)); marker->setAllValues(positions); } else { setMarkerLabel(marker, displayText); } marker->update(); } syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onDeleteSelectedPoint() { int row = m_pPointTable->currentRow(); // 同时删除相同下标的数据、地图标记和表格行,保持三者一一对应. const int pointIndex = row - FIRST_POINT_ROW; if(pointIndex < 0 || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } removeMarkerByName(markerNameAt(row)); if(pointIndex < m_vecDataSets[m_nCurrentDataSetIndex].points.size()) { m_vecDataSets[m_nCurrentDataSetIndex].points.remove(pointIndex); } if(pointIndex < m_vecMarkerNames[m_nCurrentDataSetIndex].size()) { m_vecMarkerNames[m_nCurrentDataSetIndex].removeAt(pointIndex); } m_pPointTable->removeRow(row); updatePointRowNumbers(); clearTableSelection(); updatePointButtons(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onClearPoints() { if(m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } removeDataSetMarkers(m_nCurrentDataSetIndex); m_vecDataSets[m_nCurrentDataSetIndex].points.clear(); clearPointRows(); clearTableSelection(); updatePointButtons(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onShowPointsToggled(bool checked) { if(m_bUpdatingUi || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } m_vecDataSets[m_nCurrentDataSetIndex].showPoints = checked; updateCurrentMarkerVisibility(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onShowLabelsToggled(bool checked) { if(m_bUpdatingUi || m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size()) { return; } m_vecDataSets[m_nCurrentDataSetIndex].showLabels = checked; updateCurrentMarkerVisibility(); syncDataSetsToManager(true); schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::clearTableSelection() { m_pPointTable->clearSelection(); m_pPointTable->setCurrentCell(-1, -1); } void nmWxPropertyInterpolationDlg::updatePointButtons() { const bool hasCurrent = m_nCurrentDataSetIndex >= 0 && m_nCurrentDataSetIndex < m_vecDataSets.size(); const bool hasPoints = hasCurrent && !m_vecDataSets[m_nCurrentDataSetIndex].points.isEmpty(); // 已有测点的数值含义已经确定,需清空测点后才能切换数据组属性. m_pPropertyCombo->setEnabled(hasCurrent && !hasPoints); m_pPropertyCombo->setToolTip(hasPoints ? interpolationText("Clear measurement points before changing the property.") : QString()); m_pDeletePointButton->setEnabled( hasCurrent && m_pPointTable->currentRow() >= FIRST_POINT_ROW); m_pClearPointsButton->setEnabled( hasCurrent && m_pPointTable->rowCount() > FIRST_POINT_ROW); // Kriging预览至少需要两个已知测点. m_pPreviewButton->setEnabled(hasCurrent && m_vecDataSets[m_nCurrentDataSetIndex].points.size() >= 2); } void nmWxPropertyInterpolationDlg::onPreview() { if(m_pPreviewRefreshTimer != NULL) { m_pPreviewRefreshTimer->stop(); } updatePreviewWindow(true); } void nmWxPropertyInterpolationDlg::schedulePreviewRefresh() { if(m_pPreviewDialog.isNull() || !m_pPreviewDialog->isVisible() || m_pPreviewRefreshTimer == NULL) { return; } // 连续编辑参数时重置计时,仅对最终状态执行一次插值计算. m_pPreviewRefreshTimer->start(); } void nmWxPropertyInterpolationDlg::refreshPreview() { if(m_pPreviewDialog.isNull() || !m_pPreviewDialog->isVisible()) { return; } updatePreviewWindow(false); } void nmWxPropertyInterpolationDlg::showPreviewError( const QString& message, bool activateWindow) { const QString errorText = message.isEmpty() ? interpolationText("Preview Failed") : message; QString dataSetName; if(m_nCurrentDataSetIndex >= 0 && m_nCurrentDataSetIndex < m_vecDataSets.size()) { dataSetName = m_vecDataSets[m_nCurrentDataSetIndex].name; } if(!m_pPreviewDialog.isNull()) { m_pPreviewDialog->showMessage(dataSetName, errorText); if(activateWindow) { m_pPreviewDialog->show(); m_pPreviewDialog->raise(); m_pPreviewDialog->activateWindow(); } } else if(activateWindow) { QMessageBox::warning(this, interpolationText("Preview Failed"), errorText); } } void nmWxPropertyInterpolationDlg::updatePreviewWindow(bool activateWindow) { if(m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size() || m_pDataManager.isNull()) { showPreviewError(interpolationText("Preview Failed"), activateWindow); return; } saveCurrentDataSet(); const nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[m_nCurrentDataSetIndex]; // 预览范围直接使用储层边界,与地图中的数据坐标保持一致. QVector outlinePoints; QRectF previewBounds; if(!createPreviewOutline(m_pDataManager->getOutlineData(), outlinePoints, previewBounds)) { showPreviewError( interpolationText("Please create a valid reservoir boundary before previewing."), activateWindow); return; } QVector measurementPoints; QVector measurementValues; measurementPoints.reserve(dataSet.points.size()); measurementValues.reserve(dataSet.points.size()); for(int i = 0; i < dataSet.points.size(); ++i) { measurementPoints.append(QPointF(dataSet.points[i].x, dataSet.points[i].y)); measurementValues.append(dataSet.points[i].value); } // 按行生成规则待插值点,输出下标与预览网格下标保持一致; // 较疏采样可使分级色带边界更清晰. const int columnCount = 19; const int rowCount = 19; QVector targetPoints; targetPoints.reserve(columnCount * rowCount); for(int row = 0; row < rowCount; ++row) { const double y = previewBounds.top() + previewBounds.height() * row / (rowCount - 1); for(int column = 0; column < columnCount; ++column) { const double x = previewBounds.left() + previewBounds.width() * column / (columnCount - 1); targetPoints.append(QPointF(x, y)); } } QVector interpolationValues; QString errorMessage; QApplication::setOverrideCursor(Qt::WaitCursor); // 通过统一封装调用Kriging接口,返回值顺序与待插值点一致. const bool calculationSucceeded = nmCalculationUtils::calculateKriging( targetPoints, measurementPoints, measurementValues, dataSet.nugget, dataSet.sill, dataSet.range, dataSet.model, m_pDataManager->getLicensePath(), interpolationValues, &errorMessage); QApplication::restoreOverrideCursor(); if(!calculationSucceeded) { showPreviewError(errorMessage, activateWindow); return; } // Kriging使用内部基准值;渲染前统一转换表面和测点的显示单位. const QString valueBaseUnit = propertyBaseUnit(dataSet.property); QVector displayInterpolationValues; displayInterpolationValues.reserve(interpolationValues.size()); for(int i = 0; i < interpolationValues.size(); ++i) { displayInterpolationValues.append(convertedUnitValue( interpolationValues[i], valueBaseUnit, dataSet.valueDisplayUnit)); } QVector displayMeasurementValues; displayMeasurementValues.reserve(measurementValues.size()); for(int i = 0; i < measurementValues.size(); ++i) { displayMeasurementValues.append(convertedUnitValue( measurementValues[i], valueBaseUnit, dataSet.valueDisplayUnit)); } QString scalarTitle = m_pPropertyCombo->currentText(); if(!dataSet.valueDisplayUnit.isEmpty()) { scalarTitle += QString(" (%1)").arg(dataSet.valueDisplayUnit); } // 直接读取Map中实际显示的井,避免“参与计算井”尚未设置时预览中没有井位. QVector wellPoints; QStringList wellNames; const QVector wellPlots = m_pPlot.isNull() ? QVector() : m_pPlot->getWellPlots(); for(int i = 0; i < wellPlots.size(); ++i) { nmObjPointWell* wellPlot = wellPlots[i]; if(wellPlot == NULL || wellPlot->getWellData() == NULL) { continue; } const QVector wellInformation = wellPlot->getWellInformation(); const QString wellName = wellPlot->getWellData()->getName(); if(wellInformation.size() < 2 || wellName.isEmpty() || wellNames.contains(wellName)) { continue; } const QPointF wellPoint(wellInformation[0], wellInformation[1]); if(previewBounds.contains(wellPoint)) { wellPoints.append(wellPoint); wellNames.append(wellName); } } // 预览窗口只负责裁剪和渲染,不修改数据组及正式求解参数. if(m_pPreviewDialog.isNull()) { m_pPreviewDialog = new nmWxPropertyInterpolationPreviewDlg( dataSet.name, scalarTitle, previewBounds, columnCount, rowCount, displayInterpolationValues, measurementPoints, displayMeasurementValues, outlinePoints, wellPoints, wellNames, dataSet.showPoints, dataSet.showLabels, this); m_pPreviewDialog->setAttribute(Qt::WA_DeleteOnClose, true); } else { m_pPreviewDialog->updatePreview( dataSet.name, scalarTitle, previewBounds, columnCount, rowCount, displayInterpolationValues, measurementPoints, displayMeasurementValues, outlinePoints, wellPoints, wellNames, dataSet.showPoints, dataSet.showLabels); } if(activateWindow) { m_pPreviewDialog->show(); m_pPreviewDialog->raise(); m_pPreviewDialog->activateWindow(); } } void nmWxPropertyInterpolationDlg::updateCurrentMarkerVisibility() { // 地图上只显示当前数据组的测点,其他数据组统一隐藏. for(int dataSetIndex = 0; dataSetIndex < m_vecMarkerNames.size(); ++dataSetIndex) { const bool isCurrentDataSet = dataSetIndex == m_nCurrentDataSetIndex && dataSetIndex < m_vecDataSets.size(); const bool showPoints = isCurrentDataSet && m_vecDataSets[dataSetIndex].showPoints; const bool showLabels = showPoints && m_vecDataSets[dataSetIndex].showLabels; const QStringList markerNames = m_vecMarkerNames[dataSetIndex]; for(int markerIndex = 0; markerIndex < markerNames.size(); ++markerIndex) { nmObjPoint* marker = markerByName(markerNames[markerIndex]); if(marker != NULL) { marker->setVisible(showPoints); marker->showSubObjs(showLabels); marker->update(); } } } if(!m_pPlot.isNull() && m_pPlot->m_pPlotView != NULL) { m_pPlot->m_pPlotView->updateViewport(); } }