You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxGeoRefDlg.cpp

518 lines
24 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmWxGeoRefDlg.h"
#include "iUnitGroup.h"
#include "iUnitHelper.h"
#include "ZxBaseUtil.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QtCore/qnumeric.h>
#include <cmath>
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<int>(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<int>(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<QLabel*> listLabels = findChildren<QLabel*>();
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<CoordinateField>(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<CoordinateField>(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<CoordinateField>(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();
}