|
|
#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();
|
|
|
}
|