#include "nmWxGeoRefDlg.h" #include "iUnitGroup.h" #include "iUnitHelper.h" #include "ZxBaseUtil.h" #include #include #include #include #include #include #include #include #include #include #include namespace { // WGS84 投影计算只处理度和米,不访问控件、工程对象或显示单位。 const double s_dPi = 3.14159265358979323846; const double s_dSemiMajor = 6378137.0; // WGS84 长半轴,单位 m。 const double s_dFlattening = 1.0 / 298.257223563; const double s_dEccentricity2 = 2.0 * s_dFlattening - s_dFlattening * s_dFlattening; const double s_dScale = 0.9996; // UTM 中央经线比例因子。 const char s_szBands[] = "CDEFGHJKLMNPQRSTUVWX"; // 检查度制经纬度是否有限且位于 WGS84/UTM 支持范围内。 bool isValidGeographic(double dLatitude, double dLongitude) { // UTM 仅覆盖南纬 80 度至北纬 84 度;极区需要另一套投影,不能沿用 UTM 公式。 return qIsFinite(dLatitude) && qIsFinite(dLongitude) && dLatitude >= -80.0 && dLatitude <= 84.0 && dLongitude >= -180.0 && dLongitude <= 180.0; } // 检查米制投影坐标的基本范围;画布显示范围不参与参考点合法性判断。 bool isValidProjected(double dEasting, double dNorthing) { // 检查参考点本身,不随画布缩放范围改变允许输入的坐标范围。 return qIsFinite(dEasting) && qIsFinite(dNorthing) && dEasting >= 0.0 && dEasting <= 1000000.0 && dNorthing >= 0.0 && dNorthing <= 10000000.0; } // 解析两位经度带号和一位纬度带字母;失败时输出参数不可用于转换。 bool parseZone(const QString& sZone, int& nZone, char& cBand) { bool bNumber = false; nZone = sZone.left(2).toInt(&bNumber); if (sZone.size() != 3 || !bNumber || nZone < 1 || nZone > 60) return false; cBand = sZone.at(2).toLatin1(); if (!QString::fromLatin1(s_szBands).contains(QChar(cBand))) return false; // Svalbard 的 X 纬度带只有加宽后的奇数分带,32X/34X/36X 不存在。 return cBand != 'X' || (nZone != 32 && nZone != 34 && nZone != 36); } // 根据合法的度制纬度确定 C~X 纬度带,跳过 I、O。 char latitudeBand(double dLatitude) { // X 带覆盖 72~84 度,最后一个索引需截断,避免 84 度越界。 const int nBand = qBound(0, static_cast(floor((dLatitude + 80.0) / 8.0)), 19); return s_szBands[nBand]; } // 生成与旧工程一致的三字符分带标识,如 01C、50S。 QString zoneText(int nZone, char cBand) { return QString("%1%2").arg(nZone, 2, 10, QLatin1Char('0')).arg(QChar(cBand)); } // 按合法的度制经纬度选择自然 UTM 经度带,包含区域特例及日期变更线边界。 int longitudeZone(double dLatitude, double dLongitude) { // +180 度属于第 60 带,不能产生不存在的第 61 带。 int nZone = qBound(1, static_cast(floor((dLongitude + 180.0) / 6.0)) + 1, 60); // 按 UTM 约定处理 Norway 和 Svalbard 的特殊带宽。 if (dLatitude >= 56.0 && dLatitude < 64.0 && dLongitude >= 3.0 && dLongitude < 12.0) nZone = 32; if (dLatitude >= 72.0 && dLongitude >= 0.0 && dLongitude < 42.0) { if (dLongitude < 9.0) nZone = 31; else if (dLongitude < 21.0) nZone = 33; else if (dLongitude < 33.0) nZone = 35; else nZone = 37; } return nZone; } // WGS84 正投影:输入纬度/经度(度),成功后输出自然分带和东向/北向坐标(米)。 bool geographicToUtm(double dLatitude, double dLongitude, QString& sZone, double& dEasting, double& dNorthing) { if (!isValidGeographic(dLatitude, dLongitude)) return false; const int nZone = longitudeZone(dLatitude, dLongitude); const double dLat = dLatitude * s_dPi / 180.0; const double dDeltaLon = (dLongitude - (-183.0 + nZone * 6.0)) * s_dPi / 180.0; const double dE2 = s_dEccentricity2; const double dEp2 = dE2 / (1.0 - dE2); // 第二偏心率平方,用于横轴墨卡托级数系数。 const double dN = s_dSemiMajor / sqrt(1.0 - dE2 * sin(dLat) * sin(dLat)); const double dT = tan(dLat) * tan(dLat); const double dC = dEp2 * cos(dLat) * cos(dLat); const double dA = cos(dLat) * dDeltaLon; const double dA2 = dA * dA; const double dM = s_dSemiMajor * ( (1.0 - dE2 / 4.0 - 3.0 * dE2 * dE2 / 64.0 - 5.0 * dE2 * dE2 * dE2 / 256.0) * dLat - (3.0 * dE2 / 8.0 + 3.0 * dE2 * dE2 / 32.0 + 45.0 * dE2 * dE2 * dE2 / 1024.0) * sin(2.0 * dLat) + (15.0 * dE2 * dE2 / 256.0 + 45.0 * dE2 * dE2 * dE2 / 1024.0) * sin(4.0 * dLat) - 35.0 * dE2 * dE2 * dE2 / 3072.0 * sin(6.0 * dLat)); dEasting = 500000.0 + s_dScale * dN * (dA + (1.0 - dT + dC) * dA * dA2 / 6.0 + (5.0 - 18.0 * dT + dT * dT + 72.0 * dC - 58.0 * dEp2) * dA * dA2 * dA2 / 120.0); dNorthing = s_dScale * (dM + dN * tan(dLat) * (dA2 / 2.0 + (5.0 - dT + 9.0 * dC + 4.0 * dC * dC) * dA2 * dA2 / 24.0 + (61.0 - 58.0 * dT + dT * dT + 600.0 * dC - 330.0 * dEp2) * dA2 * dA2 * dA2 / 720.0)); if (dLatitude < 0.0) dNorthing += 10000000.0; // 南半球假北值。 sZone = zoneText(nZone, latitudeBand(dLatitude)); return isValidProjected(dEasting, dNorthing); } // WGS84 反投影:输入已校验的分带及米制坐标,成功后输出纬度/经度(度)。 bool utmToGeographic(int nZone, char cBand, double dEasting, double dNorthing, double& dLatitude, double& dLongitude) { if (!isValidProjected(dEasting, dNorthing)) return false; const double dE2 = s_dEccentricity2; const double dEp2 = dE2 / (1.0 - dE2); const double dE1 = (1.0 - sqrt(1.0 - dE2)) / (1.0 + sqrt(1.0 - dE2)); const double dY = dNorthing - (cBand < 'N' ? 10000000.0 : 0.0); const double dMu = dY / (s_dScale * s_dSemiMajor * (1.0 - dE2 / 4.0 - 3.0 * dE2 * dE2 / 64.0 - 5.0 * dE2 * dE2 * dE2 / 256.0)); const double dPhi = dMu + (3.0 * dE1 / 2.0 - 27.0 * pow(dE1, 3.0) / 32.0) * sin(2.0 * dMu) + (21.0 * dE1 * dE1 / 16.0 - 55.0 * pow(dE1, 4.0) / 32.0) * sin(4.0 * dMu) + 151.0 * pow(dE1, 3.0) / 96.0 * sin(6.0 * dMu) + 1097.0 * pow(dE1, 4.0) / 512.0 * sin(8.0 * dMu); if (!qIsFinite(dPhi) || qAbs(cos(dPhi)) < 1e-6) return false; const double dC = dEp2 * cos(dPhi) * cos(dPhi); const double dT = tan(dPhi) * tan(dPhi); const double dN = s_dSemiMajor / sqrt(1.0 - dE2 * sin(dPhi) * sin(dPhi)); const double dR = s_dSemiMajor * (1.0 - dE2) / pow(1.0 - dE2 * sin(dPhi) * sin(dPhi), 1.5); const double dD = (dEasting - 500000.0) / (dN * s_dScale); const double dD2 = dD * dD; dLatitude = (dPhi - dN * tan(dPhi) / dR * (dD2 / 2.0 - (5.0 + 3.0 * dT + 10.0 * dC - 4.0 * dC * dC - 9.0 * dEp2) * dD2 * dD2 / 24.0 + (61.0 + 90.0 * dT + 298.0 * dC + 45.0 * dT * dT - 252.0 * dEp2 - 3.0 * dC * dC) * dD2 * dD2 * dD2 / 720.0)) * 180.0 / s_dPi; dLongitude = -183.0 + nZone * 6.0 + (dD - (1.0 + 2.0 * dT + dC) * dD * dD2 / 6.0 + (5.0 - 2.0 * dC + 28.0 * dT - 3.0 * dC * dC + 8.0 * dEp2 + 24.0 * dT * dT) * dD * dD2 * dD2 / 120.0) / cos(dPhi) * 180.0 / s_dPi; if (!qIsFinite(dLatitude) || !qIsFinite(dLongitude)) return false; // 仅容忍投影级数在覆盖边界的微小数值误差,不把极区坐标截成合法纬度。 if (dLatitude < -80.0000001 || dLatitude > 84.0000001) return false; dLatitude = qBound(-80.0, dLatitude, 84.0); if ((cBand < 'N' && dLatitude >= 0.0) || (cBand >= 'N' && dLatitude < 0.0)) return false; if (dLongitude > 180.0) dLongitude -= 360.0; if (dLongitude < -180.0) dLongitude += 360.0; return isValidGeographic(dLatitude, dLongitude); } // 为已校验的分带求覆盖区域中心(度),供用户主动选带时初始化参考点。 void zoneCenter(int nZone, char cBand, double& dLatitude, double& dLongitude) { const int nBand = QString::fromLatin1(s_szBands).indexOf(QChar(cBand)); dLatitude = cBand == 'X' ? 78.0 : -76.0 + nBand * 8.0; dLongitude = -183.0 + nZone * 6.0; // 特殊加宽/缩窄带使用实际覆盖区域中心,确保初始化后仍属于用户选中的带。 if (cBand == 'V' && nZone == 31) dLongitude = 1.5; if (cBand == 'V' && nZone == 32) dLongitude = 7.5; if (cBand == 'X' && nZone == 31) dLongitude = 4.5; if (cBand == 'X' && nZone == 37) dLongitude = 37.5; } // 创建与模型位置、测量工具一致的三列坐标网格,布局所有权交给分组控件。 QGridLayout* createCoordinateGrid(QGroupBox* pGroup) { pGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QGridLayout* pGrid = new QGridLayout(pGroup); pGrid->setContentsMargins(8, 15, 8, 10); pGrid->setHorizontalSpacing(6); pGrid->setVerticalSpacing(6); pGrid->setColumnStretch(1, 8); pGrid->setColumnStretch(2, 5); return pGrid; } } nmWxGeoRefDlg::nmWxGeoRefDlg(const nmDataGeoRef& oData, QWidget* pParent) : iDlgBase(pParent), m_oGeoRefData(oData), m_pLengthUnitGroup(iUnitHelper::getUnitGroupByUnit("m")), m_eLastInput(NoInput), m_bHasCoordinates(false), m_bInputValid(false), m_bRefreshing(false), m_bChanged(false), m_pUtmZoneComboBox(nullptr), m_pOkButton(nullptr) { for (int nField = 0; nField < FieldCount; ++nField) { m_dBaseValues[nField] = 0.0; m_pCoordinateEdits[nField] = nullptr; m_pLengthUnits[nField] = nullptr; m_sDisplayUnits[nField] = nField < Easting ? "o" : "m"; } setWindowTitle(tr("Geo referencing")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); initUI(); loadDataToUI(); adjustSize(); // 初始尺寸来自字体、主题与布局,允许后续拉伸。 } void nmWxGeoRefDlg::initUI() { QVBoxLayout* pMain = new QVBoxLayout(this); pMain->setContentsMargins(12, 10, 12, 0); pMain->setSpacing(10); QGroupBox* pLocationGroup = new QGroupBox(tr("Location"), this); QGridLayout* pLocationGrid = createCoordinateGrid(pLocationGroup); const QString sDegree = QString(" (%1)").arg(QChar(0x00b0)); createCoordinateField(pLocationGrid, 0, Longitude, tr("Longitude") + sDegree, "geoLongitude"); createCoordinateField(pLocationGrid, 1, Latitude, tr("Latitude") + sDegree, "geoLatitude"); pMain->addWidget(pLocationGroup); QGroupBox* pProjectionGroup = new QGroupBox(tr("Zone"), this); QGridLayout* pProjectionGrid = createCoordinateGrid(pProjectionGroup); QLabel* pZoneLabel = new QLabel(tr("UTM zone"), pProjectionGroup); pZoneLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pProjectionGrid->addWidget(pZoneLabel, 0, 0); m_pUtmZoneComboBox = new QComboBox(pProjectionGroup); m_pUtmZoneComboBox->setObjectName("geoUtmZone"); m_pUtmZoneComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_pUtmZoneComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); // 分带属于坐标标识,不是长度单位;选项沿用数据模型,过滤不存在的特殊带。 const QStringList listZones = m_oGeoRefData.getUtmZone().getListValueSelections(); foreach (const QString& sZone, listZones) { int nZone = 0; char cBand = 0; if (parseZone(sZone, nZone, cBand)) m_pUtmZoneComboBox->addItem(sZone); } m_pUtmZoneComboBox->setCurrentIndex(-1); pProjectionGrid->addWidget(m_pUtmZoneComboBox, 0, 1, 1, 2); createCoordinateField(pProjectionGrid, 1, Easting, tr("Easting"), "geoEasting"); createCoordinateField(pProjectionGrid, 2, Northing, tr("Northing"), "geoNorthing"); pMain->addWidget(pProjectionGroup); // 两个分组按实际标签宽度对齐;数值与单位按 8:5 横向分配剩余宽度。 int nLabelWidth = 0; const QList listLabels = findChildren(); foreach (QLabel* pLabel, listLabels) nLabelWidth = qMax(nLabelWidth, pLabel->sizeHint().width()); pLocationGrid->setColumnMinimumWidth(0, nLabelWidth); pProjectionGrid->setColumnMinimumWidth(0, nLabelWidth); pMain->addStretch(1); // 纵向余量留在内容与按钮之间,字段本身不被拉高。 QWidget* pFooter = new QWidget(this); pFooter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); QHBoxLayout* pFooterLayout = new QHBoxLayout(pFooter); pFooterLayout->setContentsMargins(4, 5, 4, 5); pFooterLayout->setSpacing(8); pFooterLayout->addStretch(); QDialogButtonBox* pButtons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, pFooter); m_pOkButton = pButtons->button(QDialogButtonBox::Ok); m_pOkButton->setText(tr("OK")); m_pOkButton->setIcon(zxLoadIcon("OK")); m_pOkButton->setDefault(true); QPushButton* pCancel = pButtons->button(QDialogButtonBox::Cancel); pCancel->setText(tr("Cancel")); pCancel->setIcon(zxLoadIcon("Cancel")); pCancel->setAutoDefault(false); pFooterLayout->addWidget(pButtons); pMain->addWidget(pFooter); connect(pButtons, SIGNAL(accepted()), this, SLOT(accept())); connect(pButtons, SIGNAL(rejected()), this, SLOT(reject())); connect(m_pUtmZoneComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onUtmZoneChanged(int))); } void nmWxGeoRefDlg::createCoordinateField(QGridLayout* pGrid, int nRow, CoordinateField eField, const QString& sLabel, const QString& sObjectName) { QWidget* pParent = pGrid->parentWidget(); QLabel* pLabel = new QLabel(sLabel, pParent); pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); pGrid->addWidget(pLabel, nRow, 0); QLineEdit* pEdit = new QLineEdit(pParent); pEdit->setObjectName(sObjectName); pEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_pCoordinateEdits[eField] = pEdit; pGrid->addWidget(pEdit, nRow, 1, 1, eField < Easting ? 2 : 1); connect(pEdit, SIGNAL(textEdited(QString)), this, SLOT(onCoordinateEdited(QString))); if (eField < Easting) return; QComboBox* pUnit = new QComboBox(pParent); pUnit->setObjectName(sObjectName + "Unit"); pUnit->setSizeAdjustPolicy(QComboBox::AdjustToContents); pUnit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); if (m_pLengthUnitGroup) pUnit->addItems(m_pLengthUnitGroup->getAllUnitNames()); pUnit->setCurrentIndex(pUnit->findText("m")); m_pLengthUnits[eField] = pUnit; pGrid->addWidget(pUnit, nRow, 2); connect(pUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(onLengthUnitChanged(int))); } bool nmWxGeoRefDlg::unitsAvailable() const { return m_pLengthUnitGroup && m_pLengthUnits[Easting]->findText("m") >= 0 && m_pLengthUnits[Northing]->findText("m") >= 0; } QString nmWxGeoRefDlg::displayValue(CoordinateField eField, double dValue, const QString& sUnit) const { int nDigits = 6; // 经纬度保持六位小数;投影坐标使用框架目标单位的显示精度。 if (eField >= Easting && (!m_pLengthUnitGroup || !m_pLengthUnitGroup->convert("m", dValue, sUnit, dValue, nDigits))) return QString(); if (!qIsFinite(dValue)) return QString(); const QString sText = nDigits >= 0 ? QString::number(dValue, 'f', qMin(nDigits, 15)) : QString::number(dValue, 'g', 12); return dValue != 0.0 && sText.toDouble() == 0.0 ? QString::number(dValue, 'g', 12) : sText; } void nmWxGeoRefDlg::loadDataToUI() { m_bRefreshing = true; const QVariant values[FieldCount] = { m_oGeoRefData.getLongitude().getValue(), m_oGeoRefData.getLatitude().getValue(), m_oGeoRefData.getEasting().getValue(), m_oGeoRefData.getNorthing().getValue() }; bool bComplete = true; // 空值不能被 toDouble 默认转零后当成合法参考点。 for (int nField = 0; nField < FieldCount; ++nField) { bool bNumber = false; const double dValue = values[nField].toDouble(&bNumber); bNumber = bNumber && qIsFinite(dValue); bComplete = bComplete && bNumber; m_dBaseValues[nField] = bNumber ? dValue : 0.0; m_sShownTexts[nField] = bNumber ? displayValue(static_cast(nField), dValue, m_sDisplayUnits[nField]) : values[nField].toString(); m_pCoordinateEdits[nField]->setText(m_sShownTexts[nField]); } m_sUtmZone = m_oGeoRefData.getUtmZone().getValue().toString().trimmed(); m_pUtmZoneComboBox->setCurrentIndex(m_pUtmZoneComboBox->findText(m_sUtmZone)); int nZone = 0; char cBand = 0; m_bHasCoordinates = bComplete && parseZone(m_sUtmZone, nZone, cBand) && m_pUtmZoneComboBox->currentIndex() >= 0 && isValidGeographic(m_dBaseValues[Latitude], m_dBaseValues[Longitude]) && isValidProjected(m_dBaseValues[Easting], m_dBaseValues[Northing]); m_bInputValid = m_bHasCoordinates; // 加载时不重新投影或写回舍入值,避免仅打开旧工程就移动参考点。 m_bRefreshing = false; updateActions(); } bool nmWxGeoRefDlg::readField(CoordinateField eField, double& dValue) const { const QString sText = m_pCoordinateEdits[eField]->text(); if (m_bHasCoordinates && sText == m_sShownTexts[eField]) { dValue = m_dBaseValues[eField]; return qIsFinite(dValue); } bool bNumber = false; dValue = sText.trimmed().toDouble(&bNumber); if (!bNumber || !qIsFinite(dValue)) return false; if (eField < Easting) return true; int nDigits = -1; // 读取原始换算结果,不按显示小数位舍入。 return m_pLengthUnitGroup && m_pLengthUnitGroup->convert(m_sDisplayUnits[eField], dValue, "m", dValue, nDigits) && qIsFinite(dValue); } bool nmWxGeoRefDlg::updateCoordinates(InputSource eSource) { double dLongitude = 0.0, dLatitude = 0.0; // 待提交的地理坐标,单位为度。 double dEasting = 0.0, dNorthing = 0.0; // 待提交的投影坐标,单位为米。 QString sZone = m_pUtmZoneComboBox->currentText(); bool bValid = unitsAvailable(); if (eSource == GeographicInput) { bValid = bValid && readField(Longitude, dLongitude) && readField(Latitude, dLatitude) && geographicToUtm(dLatitude, dLongitude, sZone, dEasting, dNorthing); } else if (eSource == ProjectedInput) { int nZone = 0; char cBand = 0; bValid = bValid && parseZone(sZone, nZone, cBand) && readField(Easting, dEasting) && readField(Northing, dNorthing) && utmToGeographic(nZone, cBand, dEasting, dNorthing, dLatitude, dLongitude); if (bValid) { // 字母表示纬度带,不仅是南北半球;北向坐标跨带后同步更新字母。 sZone = zoneText(nZone, latitudeBand(dLatitude)); bValid = parseZone(sZone, nZone, cBand); } } else { return m_bInputValid; } bValid = bValid && m_pUtmZoneComboBox->findText(sZone) >= 0; m_bInputValid = bValid; if (bValid) applyCoordinates(dLongitude, dLatitude, dEasting, dNorthing, sZone, eSource); updateActions(); return bValid; } void nmWxGeoRefDlg::applyCoordinates(double dLongitude, double dLatitude, double dEasting, double dNorthing, const QString& sZone, InputSource eSource) { m_bRefreshing = true; m_dBaseValues[Longitude] = dLongitude; m_dBaseValues[Latitude] = dLatitude; m_dBaseValues[Easting] = dEasting; m_dBaseValues[Northing] = dNorthing; m_sUtmZone = sZone; m_pUtmZoneComboBox->setCurrentIndex(m_pUtmZoneComboBox->findText(sZone)); for (int nField = 0; nField < FieldCount; ++nField) { // 正在输入的一组保留原文和光标;另一组只展示成功转换后的坐标。 const bool bSourceField = (eSource == GeographicInput && nField < Easting) || (eSource == ProjectedInput && nField >= Easting); if (!bSourceField) m_pCoordinateEdits[nField]->setText(displayValue( static_cast(nField), m_dBaseValues[nField], m_sDisplayUnits[nField])); m_sShownTexts[nField] = m_pCoordinateEdits[nField]->text(); } m_bHasCoordinates = true; m_bInputValid = true; m_bChanged = true; m_bRefreshing = false; } void nmWxGeoRefDlg::onCoordinateEdited(const QString&) { if (m_bRefreshing) return; for (int nField = 0; nField < FieldCount; ++nField) { if (sender() != m_pCoordinateEdits[nField]) continue; m_eLastInput = nField < Easting ? GeographicInput : ProjectedInput; updateCoordinates(m_eLastInput); return; } } void nmWxGeoRefDlg::onLengthUnitChanged(int) { if (m_bRefreshing) return; for (int nField = Easting; nField < FieldCount; ++nField) { QComboBox* pUnit = m_pLengthUnits[nField]; if (sender() != pUnit) continue; const QString sUnit = pUnit->currentText(); const QString sText = displayValue(static_cast(nField), m_dBaseValues[nField], sUnit); m_bRefreshing = true; if (!m_bInputValid || sText.isEmpty()) { pUnit->setCurrentIndex(pUnit->findText(m_sDisplayUnits[nField])); } else { // 切换单位只刷新显示;不经过坐标反算,不改变工程值和修改标记。 m_sDisplayUnits[nField] = sUnit; m_sShownTexts[nField] = sText; m_pCoordinateEdits[nField]->setText(sText); } m_bRefreshing = false; return; } } void nmWxGeoRefDlg::onUtmZoneChanged(int nIndex) { if (m_bRefreshing || nIndex < 0) return; int nZone = 0; char cBand = 0; const QString sSelected = m_pUtmZoneComboBox->itemText(nIndex); double dLatitude = 0.0, dLongitude = 0.0, dEasting = 0.0, dNorthing = 0.0; QString sZone; if (!parseZone(sSelected, nZone, cBand)) return; zoneCenter(nZone, cBand, dLatitude, dLongitude); // 沿用选带后填入区域中心的交互;四个值由同一次正投影产生,避免默认坐标相互矛盾。 m_bInputValid = geographicToUtm(dLatitude, dLongitude, sZone, dEasting, dNorthing) && sZone == sSelected; if (m_bInputValid) { m_eLastInput = NoInput; applyCoordinates(dLongitude, dLatitude, dEasting, dNorthing, sZone, NoInput); } updateActions(); } void nmWxGeoRefDlg::updateActions() { const bool bUnits = unitsAvailable(); // 尚未设置参考点时可直接输入经纬度,也可以先选 UTM 带再修改投影坐标。 // 某一组输入不完整时,暂时锁住另一组,避免切换编辑来源时丢弃未修正的文本。 const bool bGeographic = bUnits && (m_bInputValid || m_eLastInput != ProjectedInput); const bool bProjected = bUnits && m_pUtmZoneComboBox->currentIndex() >= 0 && (m_bInputValid || m_eLastInput != GeographicInput); m_pCoordinateEdits[Longitude]->setEnabled(bGeographic); m_pCoordinateEdits[Latitude]->setEnabled(bGeographic); m_pCoordinateEdits[Easting]->setEnabled(bProjected); m_pCoordinateEdits[Northing]->setEnabled(bProjected); m_pLengthUnits[Easting]->setEnabled(bUnits && m_bInputValid); m_pLengthUnits[Northing]->setEnabled(bUnits && m_bInputValid); m_pUtmZoneComboBox->setEnabled(bUnits); m_pOkButton->setEnabled(bUnits && m_bInputValid); } const nmDataGeoRef& nmWxGeoRefDlg::geoRefData() const { return m_oGeoRefData; } bool nmWxGeoRefDlg::hasChanges() const { return m_bChanged; } void nmWxGeoRefDlg::accept() { // 文本编辑即时检查,提交仍再次兜底;回车也不能绕过无效输入和单位服务检查。 if (!unitsAvailable() || !updateCoordinates(m_eLastInput)) return; if (m_bChanged) { m_oGeoRefData.getLongitude().setValue(m_dBaseValues[Longitude]); m_oGeoRefData.getLatitude().setValue(m_dBaseValues[Latitude]); m_oGeoRefData.getEasting().setValue(m_dBaseValues[Easting]); m_oGeoRefData.getNorthing().setValue(m_dBaseValues[Northing]); m_oGeoRefData.getUtmZone().setValue(m_sUtmZone); // 持久化仍采用度和米;显示单位仅在本次窗口中使用,不改变旧工程的读取约定。 m_oGeoRefData.getLongitude().setUnit("o"); m_oGeoRefData.getLatitude().setUnit("o"); m_oGeoRefData.getEasting().setUnit("m"); m_oGeoRefData.getNorthing().setUnit("m"); } iDlgBase::accept(); }