From d2d0460c20beb198c26a5d7fdc8171d9cd90fe58 Mon Sep 17 00:00:00 2001 From: lh <2334563547@qq.com> Date: Fri, 13 Nov 2026 17:15:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(Nm):=20=E7=BB=9F=E4=B8=80=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=8F=82=E8=80=83=E7=82=B9=E7=95=8C=E9=9D=A2=E5=B9=B6=E4=BF=AE?= =?UTF-8?q?=E5=A4=8DUTM=E5=9D=90=E6=A0=87=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一对话框布局与按钮样式,接入框架长度单位换算 - 修复默认东向坐标、180°经线及特殊分带处理 - 完善坐标联动与输入校验,保留原值精度 - 绑定所属分析,确定后保存,取消不修改数据 - 补充中文注释,清理废弃翻译并同步语言包 --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 207491 -> 205614 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 82 +- Include/nmNum/nmSubWxs/nmWxGeoRefDlg.h | 114 ++- Src/nmNum/nmSubWnd/nmSubWndMain.cpp | 10 +- Src/nmNum/nmSubWxs/nmWxGeoRefDlg.cpp | 1148 +++++++++--------------- 5 files changed, 521 insertions(+), 833 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index db6075c65266753011f98a7c6a4e0c2bc7575baa..3463295bca883ec0bd5d0cf4dcd0ae77447f397f 100644 GIT binary patch delta 10100 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 XEVA2s;17E5r?#eVkKF4;x4-;9ns{Ge delta 12064 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 M9*+YU@k*ZlA4c1+5dZ)H diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index 5f468668..4daefb70 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -5822,32 +5822,28 @@ Please choose a smaller length. 位置 - Longitude: - 经度: + Longitude + 经度 - Latitude: - 纬度: + Latitude + 纬度 Zone 投影带 - UTM zone: - UTM 带: - - - Easting: - 东向坐标: + UTM zone + UTM 带 - Northing: - 北向坐标: + Easting + 东向坐标 - Google - Google + Northing + 北向坐标 OK @@ -5857,64 +5853,6 @@ Please choose a smaller length. Cancel 取消 - - Input Error - 输入错误 - - - Please enter valid numbers - 请输入有效的数字 - - - Latitude must be between - 90° and + 90° - 纬度必须在 -90° 到 +90° 之间 - - - Longitude must be between - 180° and + 180° - 经度必须在 -180° 到 +180° 之间 - - - Easting must be between 0 and 1,000,000 meters - 东向坐标必须在 0 到 1,000,000 米之间" - - - Northing must be between 0 and 10,000,000 meters - 北向坐标必须在 0 到 10,000,000 米之间 - - - Please select a valid UTM Zone - 请选择有效的UTM带 - - - Conversion Error - 转换错误 - - - UTM coordinate conversion result is out of valid range, please check input - UTM坐标转换结果超出有效范围,请检查输入 - - - Latitude must be between - 90o and + 90o - 纬度必须在-90°至90°之间 - - - Longitude must be between - 180o and + 180o - 经度必须在-180°至180°之间 - - - The UTM coordinates you entered correspond to an extreme polar region where coordinate conversion may be inaccurate. -Please try coordinates closer to the center of the UTM zone. - 您输入的UTM坐标对应极地区域,坐标转换可能不准确。 -请尝试使用更接近UTM区域中心的坐标。 - - - UTM coordinate conversion failed. -The coordinates may be outside the valid range for this UTM zone. -Please check your input coordinates. - UTM坐标转换失败。 -输入的坐标可能超出了该UTM区域的有效范围。 -请检查您的输入坐标。 - nmWxGeometryLayerDlg diff --git a/Include/nmNum/nmSubWxs/nmWxGeoRefDlg.h b/Include/nmNum/nmSubWxs/nmWxGeoRefDlg.h index 4241ca39..c168d734 100644 --- a/Include/nmNum/nmSubWxs/nmWxGeoRefDlg.h +++ b/Include/nmNum/nmSubWxs/nmWxGeoRefDlg.h @@ -2,70 +2,86 @@ #define NMWXGEOREFDLG_H #include "iDlgBase.h" -#include -#include -#include -#include - #include "nmSubWxs_global.h" #include "nmDataGeoRef.h" +class QComboBox; +class QGridLayout; +class QLineEdit; +class QPushButton; +class iUnitGroup; + +// 模型参考点编辑器:编辑本地原点对应的 WGS84 经纬度和 UTM 坐标。 +// 只持有数据副本,不读取全局当前分析;调用方在确定后负责写回所属分析,取消不改工程。 class NM_SUB_WXS_EXPORT nmWxGeoRefDlg : public iDlgBase { Q_OBJECT public: - explicit nmWxGeoRefDlg(QWidget* parent = 0); - void loadDataToUI(); // 从数据加载到界面 - void saveDataUI(); // 从界面保存到数据 - - // 坐标转换辅助函数 - void updateFromLatLon(); // 根据经纬度更新UTM坐标 - void updateFromUTM(); // 根据UTM坐标更新经纬度 - void latLonToUTM(double lat, double lon, int& zone, char& band, double& easting, double& northing); - bool utmToLatLon(int zone, char band, double easting, double northing, double& lat, double& lon); - int getUTMZoneFromLongitude(double longitude); - char getUTMBandFromLatitude(double latitude); - double calculateNorthing(double latitude, double longitude, int zone); - void restoreLastValidValues(); + // 复制传入的参考点并初始化界面;构造和加载均不修改调用方数据,不自动重算旧坐标。 + explicit nmWxGeoRefDlg(const nmDataGeoRef& oData, QWidget* pParent = 0); + // 返回窗口持有的结果副本,仅在 Accepted 后读取;引用有效期不超过窗口生命周期。 + const nmDataGeoRef& geoRefData() const; + // 判断是否发生坐标编辑;只打开窗口或切换显示单位返回 false,调用方可跳过写回。 + bool hasChanges() const; - // 输入验证函数 - bool isValidLatitude(double lat); - bool isValidLongitude(double lon); - bool isValidEasting(double easting); - bool isValidNorthing(double northing); +public slots: + // 确定按钮和回车共用提交入口;非法/不完整输入保留在界面中,不按零值保存。 + virtual void accept(); - private slots: - void onUtmZoneChanged(int index); - void onLongitudeChanged(); // 经度改变槽函数 - void onLatitudeChanged(); // 纬度改变槽函数 - void onEastingChanged(); // 东向坐标改变槽函数 - void onNorthingChanged(); // 北向坐标改变槽函数 - void okAccept(); - void cancelReject(); +private slots: + // 响应用户修改文本,按信号来源确定经纬度或投影坐标组,检查并更新另一组坐标。 + void onCoordinateEdited(const QString& sText); + // 响应长度单位选择,仅从米制原值刷新对应字段;无效输入或换算失败时恢复原单位。 + void onLengthUnitChanged(int nIndex); + // 响应 UTM 带选择,以该带覆盖区域中心初始化四个坐标;程序刷新期间不重复处理。 + void onUtmZoneChanged(int nIndex); private: - void initUI(); - - QLineEdit* m_pLongitudeEdit; - QLineEdit* m_pLatitudeEdit; + // 坐标字段及数组下标约定:前两项为度制经纬度,后两项为米制投影坐标。 + enum CoordinateField { Longitude = 0, Latitude, Easting, Northing, FieldCount }; + // 转换输入来源;NoInput 用于刚加载或选带初始化完成、不需要再次反算的状态。 + enum InputSource { NoInput = 0, GeographicInput, ProjectedInput }; - QComboBox* m_pUtmZoneCombo; - QLineEdit* m_pEastingEdit; - QLineEdit* m_pNorthingEdit; - - QPushButton* m_pOkButton; - QPushButton* m_pCancelButton; - - QString m_lastLongitude; - QString m_lastLatitude; - QString m_lastEasting; - QString m_lastNorthing; + // 创建两组坐标字段、框架长度单位下拉框及标准按钮栏,不固定窗口和字段宽度。 + void initUI(); + // 回显副本中的原始坐标并记录完整精度;空值保持为空,不因打开窗口触发投影或写回。 + void loadDataToUI(); + // 在指定网格行创建字段;经纬度跨两列,东向/北向字段附带框架长度单位选择。 + void createCoordinateField(QGridLayout* pGrid, int nRow, CoordinateField eField, + const QString& sLabel, const QString& sObjectName); + // 读取时优先保留未编辑字段的完整原值;长度文本通过框架单位组换算为米。 + bool readField(CoordinateField eField, double& dValue) const; + // 将度/米制原值格式化为显示文本;长度采用框架目标单位精度,换算失败返回空串。 + QString displayValue(CoordinateField eField, double dValue, const QString& sUnit) const; + // 经纬度/投影坐标成对转换,全部成功后才替换副本;失败保留输入,禁用确定。 + bool updateCoordinates(InputSource eSource); + // 一次性接收成功转换的完整坐标(度、米),保留输入组原文并刷新派生组和 UTM 带。 + void applyCoordinates(double dLongitude, double dLatitude, double dEasting, + double dNorthing, const QString& sZone, InputSource eSource); + // 按单位服务和输入有效性更新操作状态;无效输入阻止确定,并暂时锁住另一坐标组。 + void updateActions(); + // 检查框架长度单位组及米制入口是否可用;不可用时禁止坐标编辑和保存。 + bool unitsAvailable() const; - // 防止递归更新的标志 - bool m_bUpdating; +private: + nmDataGeoRef m_oGeoRefData; // 工程参考点的编辑副本,保留原属性及持久化格式。 + iUnitGroup* m_pLengthUnitGroup; // 借用框架长度单位组;单位选项和换算规则均由框架维护。 + double m_dBaseValues[FieldCount]; // 最近一组完整坐标:经纬度为度,东向/北向为米。 + QString m_sShownTexts[FieldCount]; // 对应原值的显示文本,用于避免舍入显示反写完整精度。 + QString m_sDisplayUnits[FieldCount]; // 当前显示单位;经纬度固定为度,长度允许分别切换。 + QString m_sUtmZone; // 最近一组完整坐标的分带标识,如 50S;字母表示纬度带。 + InputSource m_eLastInput; // 最近编辑的坐标组,确定时按该组再次检查转换结果。 + bool m_bHasCoordinates; // 是否已有一组完整坐标,可用于保留未编辑字段的原值。 + bool m_bInputValid; // 当前输入能否转换;无效时保留原文并阻止确定。 + bool m_bRefreshing; // 程序刷新控件期间抑制联动,避免递归转换。 + bool m_bChanged; // 只记录坐标编辑;打开窗口和切换单位不算修改。 - nmDataGeoRef m_geoRefData; + // 以下控件交给 Qt 父子关系释放;输入框和单位框横向伸展,高度采用主题建议值。 + QLineEdit* m_pCoordinateEdits[FieldCount];// 经纬度及投影坐标输入框,顺序对应 CoordinateField。 + QComboBox* m_pLengthUnits[FieldCount]; // 仅 Easting/Northing 使用,角度字段对应项为空。 + QComboBox* m_pUtmZoneComboBox; // UTM 带选择,沿用数据对象提供的分带选项。 + QPushButton* m_pOkButton; // 仅在单位服务和当前坐标均有效时允许提交。 }; #endif // NMWXGEOREFDLG_H diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 80e6ac40..bb2b8225 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -1646,8 +1646,14 @@ void nmSubWndMain::drawMeasure() void nmSubWndMain::drawGeoRef() { - nmWxGeoRefDlg dlg(nullptr); - dlg.exec(); + // 参考点属于当前地图绑定的分析;编辑器只收发副本,不访问全局当前分析。 + nmGuiPlot* pPlot = dynamic_cast(m_pWxPlot); + QPointer pManager = pPlot ? pPlot->boundDataManager() : nullptr; + if (!pManager) return; + nmWxGeoRefDlg dialog(pManager->getGeoRefDataCopy(), this); + if (dialog.exec() == QDialog::Accepted && pManager && dialog.hasChanges()) { + pManager->updateGeoRefData(dialog.geoRefData()); + } } void nmSubWndMain::drawPointerPos() diff --git a/Src/nmNum/nmSubWxs/nmWxGeoRefDlg.cpp b/Src/nmNum/nmSubWxs/nmWxGeoRefDlg.cpp index 73d16114..f5fce06f 100644 --- a/Src/nmNum/nmSubWxs/nmWxGeoRefDlg.cpp +++ b/Src/nmNum/nmSubWxs/nmWxGeoRefDlg.cpp @@ -1,789 +1,517 @@ -#define _USE_MATH_DEFINES - #include "nmWxGeoRefDlg.h" + +#include "iUnitGroup.h" +#include "iUnitHelper.h" +#include "ZxBaseUtil.h" + +#include +#include +#include #include -#include -#include -#include #include +#include +#include +#include +#include +#include #include -#include "nmDataAnalyzeManager.h" -#include "nmDataAxis.h" - -nmWxGeoRefDlg::nmWxGeoRefDlg(QWidget* parent) - : iDlgBase(parent), m_bUpdating(false) +namespace { - this->setWindowTitle(tr("Geo referencing")); - initUI(); - this->resize(370, 320); - this->setFixedSize(this->size()); - - m_geoRefData = nmDataAnalyzeManager::getCurrentInstance()->getGeoRefDataCopy(); - - loadDataToUI(); +// 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; } -void nmWxGeoRefDlg::initUI() +// 检查米制投影坐标的基本范围;画布显示范围不参与参考点合法性判断。 +bool isValidProjected(double dEasting, double dNorthing) { - // Location group - QGroupBox* locationGroup = new QGroupBox(tr("Location")); - - // form 布局 - QFormLayout* locForm = new QFormLayout; - locForm->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); - locForm->setVerticalSpacing(12); - locForm->setHorizontalSpacing(20); - locForm->setContentsMargins(8, 8, 8, 8); - - m_pLongitudeEdit = new QLineEdit; m_pLongitudeEdit->setFixedWidth(180); - m_pLatitudeEdit = new QLineEdit; m_pLatitudeEdit ->setFixedWidth(180); - locForm->addRow(tr("Longitude:"), m_pLongitudeEdit); - locForm->addRow(tr("Latitude:"), m_pLatitudeEdit); - - // 包一层 vbox,用 stretch 让 form 居中 - QVBoxLayout* locWrapper = new QVBoxLayout; - locWrapper->addStretch(1); - locWrapper->addLayout(locForm); - locWrapper->addStretch(1); - locWrapper->setContentsMargins(0, 0, 0, 0); // 根据需要调整 groupbox 内边距 - locationGroup->setLayout(locWrapper); - - // Zone group - QGroupBox* zoneGroup = new QGroupBox(tr("Zone")); - - QFormLayout* zoneForm = new QFormLayout; - zoneForm->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); - zoneForm->setVerticalSpacing(12); - zoneForm->setHorizontalSpacing(20); - zoneForm->setContentsMargins(8, 8, 8, 8); - - m_pUtmZoneCombo = new QComboBox; m_pUtmZoneCombo->setFixedWidth(180); - m_pEastingEdit = new QLineEdit; m_pEastingEdit ->setFixedWidth(180); - m_pNorthingEdit = new QLineEdit; m_pNorthingEdit->setFixedWidth(180); - zoneForm->addRow(tr("UTM zone:"), m_pUtmZoneCombo); - zoneForm->addRow(tr("Easting:"), m_pEastingEdit); - zoneForm->addRow(tr("Northing:"), m_pNorthingEdit); - - // 初始化 UTM Zone 下拉框(格式:01C~60X) - QStringList bands; - bands << "C" << "D" << "E" << "F" << "G" << "H" << "J" << "K" << "L" << "M" - << "N" << "P" << "Q" << "R" << "S" << "T" << "U" << "V" << "W" << "X"; // 有效纬度带 - - // 外层循环:Band;内层循环:Zone - for(int i = 0; i < bands.size(); ++i) { - QString band = bands.at(i); - - for(int zone = 1; zone <= 60; ++zone) { - // 格式化为两位数 Zone + 纬度带(例如 "01C", "02C") - QString zoneStr = QString("%1%2").arg(zone, 2, 10, QLatin1Char('0')).arg(band); - m_pUtmZoneCombo->addItem(zoneStr); - } - } - - m_pUtmZoneCombo->setCurrentIndex(-1); - - QVBoxLayout* zoneWrapper = new QVBoxLayout; - zoneWrapper->addStretch(1); - zoneWrapper->addLayout(zoneForm); - zoneWrapper->addStretch(1); - zoneWrapper->setContentsMargins(0, 0, 0, 0); - zoneGroup->setLayout(zoneWrapper); - - // Buttons - m_pOkButton = new QPushButton(tr("OK")); - m_pCancelButton = new QPushButton(tr("Cancel")); - m_pOkButton->setDefault(true); - connect(m_pOkButton, SIGNAL(clicked()), this, SLOT(okAccept())); - connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(cancelReject())); - - QHBoxLayout* btnLay = new QHBoxLayout; - btnLay->addStretch(); - btnLay->addWidget(m_pOkButton); - btnLay->addWidget(m_pCancelButton); - - // 主布局 - QVBoxLayout* mainLay = new QVBoxLayout; - mainLay->setContentsMargins(10, 10, 10, 10); - mainLay->setSpacing(15); - mainLay->addWidget(locationGroup); - mainLay->addWidget(zoneGroup); - mainLay->addLayout(btnLay); - setLayout(mainLay); - - // 初始都设成不可编辑 - m_pLongitudeEdit->setEnabled(false); - m_pLatitudeEdit ->setEnabled(false); - m_pUtmZoneCombo ->setEnabled(true); - m_pEastingEdit ->setEnabled(false); - m_pNorthingEdit ->setEnabled(false); - - connect(m_pUtmZoneCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onUtmZoneChanged(int))); - connect(m_pLongitudeEdit, SIGNAL(editingFinished()), this, SLOT(onLongitudeChanged())); - connect(m_pLatitudeEdit, SIGNAL(editingFinished()), this, SLOT(onLatitudeChanged())); - connect(m_pEastingEdit, SIGNAL(editingFinished()), this, SLOT(onEastingChanged())); - connect(m_pNorthingEdit, SIGNAL(editingFinished()), this, SLOT(onNorthingChanged())); + // 检查参考点本身,不随画布缩放范围改变允许输入的坐标范围。 + return qIsFinite(dEasting) && qIsFinite(dNorthing) + && dEasting >= 0.0 && dEasting <= 1000000.0 + && dNorthing >= 0.0 && dNorthing <= 10000000.0; } -// 根据经纬度和UTM zone计算Northing坐标的辅助函数 -double nmWxGeoRefDlg::calculateNorthing(double latitude, double longitude, int zone) +// 解析两位经度带号和一位纬度带字母;失败时输出参数不可用于转换。 +bool parseZone(const QString& sZone, int& nZone, char& cBand) { - // UTM投影的简化计算公式 - // 这里使用基本的UTM投影算法 - - const double a = 6378137.0; // WGS84长半轴 - const double e2 = 0.00669437999014; // WGS84第一偏心率的平方 - const double k0 = 0.9996; // UTM比例因子 - - // 转换为弧度 - double lat_rad = latitude * M_PI / 180.0; - double lon_rad = longitude * M_PI / 180.0; - - // 中央经线 - double lon0_rad = (-183.0 + zone * 6.0) * M_PI / 180.0; - double dlon = lon_rad - lon0_rad; - - // UTM投影计算 - double N = a / sqrt(1 - e2 * sin(lat_rad) * sin(lat_rad)); - double T = tan(lat_rad) * tan(lat_rad); - double C = e2 * cos(lat_rad) * cos(lat_rad) / (1 - e2); - double A = cos(lat_rad) * dlon; - - double M = a * ((1 - e2 / 4 - 3 * e2 * e2 / 64 - 5 * e2 * e2 * e2 / 256) * lat_rad - - (3 * e2 / 8 + 3 * e2 * e2 / 32 + 45 * e2 * e2 * e2 / 1024) * sin(2 * lat_rad) - + (15 * e2 * e2 / 256 + 45 * e2 * e2 * e2 / 1024) * sin(4 * lat_rad) - - (35 * e2 * e2 * e2 / 3072) * sin(6 * lat_rad)); - - double northing = k0 * (M + N * tan(lat_rad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24 - + (61 - 58 * T + T * T + 600 * C - 330 * e2) * A * A * A * A * A * A / 720)); - - // 南半球需要加上假北值 - if(latitude < 0) { - northing += 10000000.0; - } - - return northing; + 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); } -// 经度编辑完成时的处理 -void nmWxGeoRefDlg::onLongitudeChanged() +// 根据合法的度制纬度确定 C~X 纬度带,跳过 I、O。 +char latitudeBand(double dLatitude) { - if(m_bUpdating) return; // 防止递归更新 - - QString lonText = m_pLongitudeEdit->text(); - QString latText = m_pLatitudeEdit->text(); - - // 检查经度值是否真的发生了变化 - if(lonText == m_lastLongitude) { - return; // 值没有变化,不进行转换 - } - - if(!lonText.isEmpty() && !latText.isEmpty()) { - updateFromLatLon(); - } + // X 带覆盖 72~84 度,最后一个索引需截断,避免 84 度越界。 + const int nBand = qBound(0, static_cast(floor((dLatitude + 80.0) / 8.0)), 19); + return s_szBands[nBand]; } -// 纬度编辑完成时的处理 -void nmWxGeoRefDlg::onLatitudeChanged() +// 生成与旧工程一致的三字符分带标识,如 01C、50S。 +QString zoneText(int nZone, char cBand) { - if(m_bUpdating) return; // 防止递归更新 - - QString lonText = m_pLongitudeEdit->text(); - QString latText = m_pLatitudeEdit->text(); - - // 检查纬度值是否真的发生了变化 - if(latText == m_lastLatitude) { - return; // 值没有变化,不进行转换 - } - - if(!lonText.isEmpty() && !latText.isEmpty()) { - updateFromLatLon(); - } + return QString("%1%2").arg(nZone, 2, 10, QLatin1Char('0')).arg(QChar(cBand)); } -// 东向坐标编辑完成时的处理 -void nmWxGeoRefDlg::onEastingChanged() +// 按合法的度制经纬度选择自然 UTM 经度带,包含区域特例及日期变更线边界。 +int longitudeZone(double dLatitude, double dLongitude) { - if(m_bUpdating) return; // 防止递归更新 - - QString eastingText = m_pEastingEdit->text(); - QString northingText = m_pNorthingEdit->text(); - QString zoneText = m_pUtmZoneCombo->currentText(); - - // 检查东向坐标值是否真的发生了变化 - if(eastingText == m_lastEasting) { - return; // 值没有变化,不进行转换 - } - - if(!eastingText.isEmpty() && !northingText.isEmpty() && !zoneText.isEmpty()) { - updateFromUTM(); + // +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; } -// 北向坐标编辑完成时的处理 -void nmWxGeoRefDlg::onNorthingChanged() +// WGS84 正投影:输入纬度/经度(度),成功后输出自然分带和东向/北向坐标(米)。 +bool geographicToUtm(double dLatitude, double dLongitude, QString& sZone, + double& dEasting, double& dNorthing) { - if(m_bUpdating) return; // 防止递归更新 - - QString eastingText = m_pEastingEdit->text(); - QString northingText = m_pNorthingEdit->text(); - QString zoneText = m_pUtmZoneCombo->currentText(); - - // 检查北向坐标值是否真的发生了变化 - if(northingText == m_lastNorthing) { - return; // 值没有变化,不进行转换 - } - - if(!eastingText.isEmpty() && !northingText.isEmpty() && !zoneText.isEmpty()) { - updateFromUTM(); - } + 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); } -// 更新记录的值 -void nmWxGeoRefDlg::updateFromLatLon() +// WGS84 反投影:输入已校验的分带及米制坐标,成功后输出纬度/经度(度)。 +bool utmToGeographic(int nZone, char cBand, double dEasting, double dNorthing, + double& dLatitude, double& dLongitude) { - m_bUpdating = true; // 设置更新标志 - - bool ok1, ok2; - double latitude = m_pLatitudeEdit->text().toDouble(&ok1); - double longitude = m_pLongitudeEdit->text().toDouble(&ok2); - - if(!ok1 || !ok2) { - QMessageBox::warning(this, tr("Input Error"), tr("Please enter valid numbers")); - // 恢复之前的有效值 - m_pLongitudeEdit->setText(m_lastLongitude); - m_pLatitudeEdit->setText(m_lastLatitude); - m_bUpdating = false; - return; - } - - // 验证经纬度范围 - if(!isValidLatitude(latitude)) { - QMessageBox::warning(this, tr("Input Error"), tr("Latitude must be between - 90o and + 90o")); - // 恢复之前的有效值 - m_pLatitudeEdit->setText(m_lastLatitude); - m_bUpdating = false; - return; - } - - if(!isValidLongitude(longitude)) { - QMessageBox::warning(this, tr("Input Error"), tr("Longitude must be between - 180o and + 180o")); - // 恢复之前的有效值 - m_pLongitudeEdit->setText(m_lastLongitude); - m_bUpdating = false; - return; - } - - int zone; - char band; - double easting, northing; - - // 执行坐标转换 - latLonToUTM(latitude, longitude, zone, band, easting, northing); - - // 更新UTM Zone下拉框 - QString zoneStr = QString("%1%2").arg(zone, 2, 10, QLatin1Char('0')).arg(band); - int index = m_pUtmZoneCombo->findText(zoneStr); - - if(index >= 0) { - m_pUtmZoneCombo->setCurrentIndex(index); - } - - // 更新东向和北向坐标 - QString eastingStr = QString::number(easting, 'f', 2); - QString northingStr = QString::number(northing, 'f', 2); - - m_pEastingEdit->setText(eastingStr); - m_pNorthingEdit->setText(northingStr); - - // 转换成功后,更新所有记录的值 - m_lastLongitude = m_pLongitudeEdit->text(); // 使用当前输入框的值 - m_lastLatitude = m_pLatitudeEdit->text(); - m_lastEasting = eastingStr; - m_lastNorthing = northingStr; - - m_bUpdating = false; // 清除更新标志 + 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 nmWxGeoRefDlg::updateFromUTM() +// 为已校验的分带求覆盖区域中心(度),供用户主动选带时初始化参考点。 +void zoneCenter(int nZone, char cBand, double& dLatitude, double& dLongitude) { - m_bUpdating = true; // 设置更新标志 - - bool ok1, ok2; - double easting = m_pEastingEdit->text().toDouble(&ok1); - double northing = m_pNorthingEdit->text().toDouble(&ok2); - - QString zoneText = m_pUtmZoneCombo->currentText(); - - if(!ok1 || !ok2) { - QMessageBox::warning(this, tr("Input Error"), tr("Please enter valid numbers")); - restoreLastValidValues(); - return; - } - - // 验证UTM坐标范围 - if(!isValidEasting(easting)) { - QMessageBox::warning(this, tr("Input Error"), tr("Easting must be between 0 and 1,000,000 meters")); - restoreLastValidValues(); - return; - } - - if(!isValidNorthing(northing)) { - QMessageBox::warning(this, tr("Input Error"), tr("Northing must be between 0 and 10,000,000 meters")); - restoreLastValidValues(); - return; - } - - if(zoneText.isEmpty()) { - QMessageBox::warning(this, tr("Input Error"), tr("Please select a valid UTM Zone")); - m_bUpdating = false; - return; - } - - // 解析UTM Zone - int zone = zoneText.left(2).toInt(); - char band = zoneText.right(1).toLocal8Bit().at(0); - - double latitude, longitude; - - // 执行坐标转换,检查返回值 - bool conversionSuccess = utmToLatLon(zone, band, easting, northing, latitude, longitude); - - if (!conversionSuccess) { - // 根据具体失败原因给出不同的错误信息 - if (band == 'C' || band == 'X' || zone <= 2 || zone >= 59) { - QMessageBox::warning(this, tr("Conversion Error"), - tr("The UTM coordinates you entered correspond to an extreme polar region where coordinate conversion may be inaccurate.\n" - "Please try coordinates closer to the center of the UTM zone.")); - } else { - QMessageBox::warning(this, tr("Conversion Error"), - tr("UTM coordinate conversion failed.\n" - "The coordinates may be outside the valid range for this UTM zone.\n" - "Please check your input coordinates.")); - } - restoreLastValidValues(); - return; - } - - // 验证转换结果 - if(!isValidLatitude(latitude) || !isValidLongitude(longitude)) { - QMessageBox::warning(this, tr("Conversion Error"), tr("UTM coordinate conversion result is out of valid range, please check input")); - restoreLastValidValues(); - return; - } - - // 更新经纬度 - QString longitudeStr = QString::number(longitude, 'f', 6); - QString latitudeStr = QString::number(latitude, 'f', 6); - - m_pLongitudeEdit->setText(longitudeStr); - m_pLatitudeEdit->setText(latitudeStr); - - // 转换成功后,更新所有记录的值 - m_lastLongitude = longitudeStr; - m_lastLatitude = latitudeStr; - m_lastEasting = m_pEastingEdit->text(); // 使用当前输入框的值 - m_lastNorthing = m_pNorthingEdit->text(); - - m_bUpdating = false; // 清除更新标志 + 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; } -// 恢复上次有效值 -void nmWxGeoRefDlg::restoreLastValidValues() +// 创建与模型位置、测量工具一致的三列坐标网格,布局所有权交给分组控件。 +QGridLayout* createCoordinateGrid(QGroupBox* pGroup) { - m_pEastingEdit->setText(m_lastEasting); - m_pNorthingEdit->setText(m_lastNorthing); - m_bUpdating = false; + 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; +} } -// 更新记录的值 -void nmWxGeoRefDlg::onUtmZoneChanged(int index) +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) { - // 添加这行检查!如果正在更新过程中,直接返回 - if(m_bUpdating) return; - - // 设置更新标志,防止触发其他转换函数 - m_bUpdating = true; - - bool enabled = (index >= 0); // 选择了有效的 UTM Zone 时启用 - m_pLongitudeEdit->setEnabled(enabled); - m_pLatitudeEdit ->setEnabled(enabled); - m_pEastingEdit ->setEnabled(enabled); - m_pNorthingEdit ->setEnabled(enabled); - - if(enabled) { - // 获取选中的 UTM Zone - QString selectedZone = m_pUtmZoneCombo->itemText(index); - - // 从选中的 UTM Zone 提取 Zone 和 Band - int zone = selectedZone.left(2).toInt(); - QString band = selectedZone.right(1); - - // 计算经度(Longitude) - double longitude = -177.0 + (zone - 1) * 6.0; - QString longitudeStr = QString::number(longitude, 'f', 6); - m_pLongitudeEdit->setText(longitudeStr); - - // 计算纬度(Latitude) - QStringList bands; - bands << "C" << "D" << "E" << "F" << "G" << "H" << "J" << "K" << "L" << "M" - << "N" << "P" << "Q" << "R" << "S" << "T" << "U" << "V" << "W" << "X"; - - int bandIndex = bands.indexOf(band); - double latitude = -76.0; - - if(bandIndex >= 0) { - if(bandIndex < bands.indexOf("X")) { - // X之前的所有band,每个递增8度 - latitude += bandIndex * 8.0; - } else { - // X band:W到X跨度为10度 - latitude += (bandIndex - 1) * 8.0 + 10.0; - } - } - - QString latitudeStr = QString::number(latitude, 'f', 6); - m_pLatitudeEdit->setText(latitudeStr); - - // 设置东向坐标(Easting) - double easting = 50000.00; - QString eastingStr = QString::number(easting, 'f', 2); - m_pEastingEdit->setText(eastingStr); - - // 设置北向坐标(Northing) - double northing = calculateNorthing(latitude, longitude, zone); - QString northingStr = QString::number(northing, 'f', 2); - m_pNorthingEdit->setText(northingStr); - - // 更新记录的所有值 - m_lastLongitude = longitudeStr; - m_lastLatitude = latitudeStr; - m_lastEasting = eastingStr; - m_lastNorthing = northingStr; + 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"; } - - // 清除更新标志 - m_bUpdating = false; + setWindowTitle(tr("Geo referencing")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + initUI(); + loadDataToUI(); + adjustSize(); // 初始尺寸来自字体、主题与布局,允许后续拉伸。 } -// 经纬度转UTM坐标 -void nmWxGeoRefDlg::latLonToUTM(double lat, double lon, int& zone, char& band, double& easting, double& northing) +void nmWxGeoRefDlg::initUI() { - // 确定UTM Zone - zone = getUTMZoneFromLongitude(lon); - band = getUTMBandFromLatitude(lat); - - // WGS84椭球体参数 - const double a = 6378137.0; // 长半轴 - const double e2 = 0.00669437999014; // 第一偏心率的平方 - const double k0 = 0.9996; // 比例因子 - - // 转换为弧度 - double lat_rad = lat * M_PI / 180.0; - double lon_rad = lon * M_PI / 180.0; - - // 中央经线 - double lon0 = -183.0 + zone * 6.0; - double lon0_rad = lon0 * M_PI / 180.0; - double dlon = lon_rad - lon0_rad; - - // UTM投影计算 - double N = a / sqrt(1 - e2 * sin(lat_rad) * sin(lat_rad)); - double T = tan(lat_rad) * tan(lat_rad); - double C = e2 * cos(lat_rad) * cos(lat_rad) / (1 - e2); - double A = cos(lat_rad) * dlon; - - double M = a * ((1 - e2 / 4 - 3 * e2 * e2 / 64 - 5 * e2 * e2 * e2 / 256) * lat_rad - - (3 * e2 / 8 + 3 * e2 * e2 / 32 + 45 * e2 * e2 * e2 / 1024) * sin(2 * lat_rad) - + (15 * e2 * e2 / 256 + 45 * e2 * e2 * e2 / 1024) * sin(4 * lat_rad) - - (35 * e2 * e2 * e2 / 3072) * sin(6 * lat_rad)); - - // 计算东向坐标 - easting = k0 * N * (A + (1 - T + C) * A * A * A / 6 - + (5 - 18 * T + T * T + 72 * C - 58 * e2) * A * A * A * A * A / 120) + 500000.0; - - // 计算北向坐标 - northing = k0 * (M + N * tan(lat_rad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24 - + (61 - 58 * T + T * T + 600 * C - 330 * e2) * A * A * A * A * A * A / 720)); - - // 南半球需要加上假北值 - if(lat < 0) { - northing += 10000000.0; + 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))); } -// UTM坐标转经纬度 -bool nmWxGeoRefDlg::utmToLatLon(int zone, char band, double easting, double northing, double& lat, double& lon) +void nmWxGeoRefDlg::createCoordinateField(QGridLayout* pGrid, int nRow, CoordinateField eField, + const QString& sLabel, const QString& sObjectName) { - // WGS84椭球体参数 - const double a = 6378137.0; // 长半轴 (米) - const double f = 1.0 / 298.257223563; // 扁率 - const double e2 = 2 * f - f * f; // 第一偏心率的平方 - const double k0 = 0.9996; // UTM比例因子 - const double e1 = (1 - sqrt(1 - e2)) / (1 + sqrt(1 - e2)); - - // 中央经线经度 - double lon0 = -183.0 + zone * 6.0; - - // 调整北向坐标(南半球处理) - double y = northing; - if(band < 'N') { // 南半球 - y -= 10000000.0; - } - - // 调整东向坐标 - double x = easting - 500000.0; - - // 计算足点纬度 - double M = y / k0; - double mu = M / (a * (1 - e2 / 4 - 3 * e2 * e2 / 64 - 5 * e2 * e2 * e2 / 256)); - - // 使用级数展开计算足点纬度 - double phi1 = mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * sin(2 * mu) - + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * sin(4 * mu) - + (151 * e1 * e1 * e1 / 96) * sin(6 * mu) - + (1097 * e1 * e1 * e1 * e1 / 512) * sin(8 * mu); - - // cos(phi1)是否过小 - if (fabs(cos(phi1)) < 1e-6) { - // 设置默认值,让调用方知道转换失败 - lat = 0.0; - lon = 0.0; - return false; // 返回false表示转换失败 - } - - // 计算各种参数 - double C1 = e2 * cos(phi1) * cos(phi1) / (1 - e2); - double T1 = tan(phi1) * tan(phi1); - double N1 = a / sqrt(1 - e2 * sin(phi1) * sin(phi1)); - double R1 = a * (1 - e2) / pow(1 - e2 * sin(phi1) * sin(phi1), 1.5); - double D = x / (N1 * k0); - - // 计算纬度(弧度) - double lat_rad = phi1 - (N1 * tan(phi1) / R1) * - (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * e2) * D * D * D * D / 24 - + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * e2 - 3 * C1 * C1) * D * D * D * D * D * D / 720); - - // 计算经度(弧度) - double lon_rad = (D - (1 + 2 * T1 + C1) * D * D * D / 6 - + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * e2 + 24 * T1 * T1) * D * D * D * D * D / 120) / cos(phi1); - - // 转换为度并加上中央经线 - lat = lat_rad * 180.0 / M_PI; - lon = lon0 + lon_rad * 180.0 / M_PI; - - // 第二个检查:确保结果在合理范围内 - if (lat != lat || lon != lon || // 检查NaN - fabs(lat) > 90.0 || // 纬度必须在-90到90之间 - fabs(lon - lon0) > 180.0) { // 经度距离中央经线不应超过180度 - - // 设置默认值 - lat = 0.0; - lon = 0.0; - return false; // 返回false表示转换失败 - } - - // 确保经度在有效范围内 - while(lon > 180.0) lon -= 360.0; - while(lon < -180.0) lon += 360.0; - - return true; // 转换成功 + 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))); } -// 根据经度确定UTM Zone -int nmWxGeoRefDlg::getUTMZoneFromLongitude(double longitude) +bool nmWxGeoRefDlg::unitsAvailable() const { - return (int)floor((longitude + 180.0) / 6.0) + 1; + return m_pLengthUnitGroup && m_pLengthUnits[Easting]->findText("m") >= 0 + && m_pLengthUnits[Northing]->findText("m") >= 0; } -// 根据纬度确定UTM Band -char nmWxGeoRefDlg::getUTMBandFromLatitude(double latitude) +QString nmWxGeoRefDlg::displayValue(CoordinateField eField, double dValue, const QString& sUnit) const { - const char bands[] = "CDEFGHJKLMNPQRSTUVWX"; - int index = (int)floor((latitude + 80.0) / 8.0); - - if(index < 0) return 'C'; - - if(index >= 20) return 'X'; - - return bands[index]; + 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; } -bool nmWxGeoRefDlg::isValidLatitude(double lat) +void nmWxGeoRefDlg::loadDataToUI() { - return (lat >= -90.0 && lat <= 90.0); + 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::isValidLongitude(double lon) +bool nmWxGeoRefDlg::readField(CoordinateField eField, double& dValue) const { - return (lon >= -180.0 && lon <= 180.0); + 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::isValidEasting(double easting) +bool nmWxGeoRefDlg::updateCoordinates(InputSource eSource) { - // 基本UTM范围检查 - if (easting < 0.0 || easting > 1000000.0) { - return false; - } - - // 获取坐标轴数据,考虑画布范围 - nmDataAxis* axisData = nmDataAnalyzeManager::getCurrentInstance()->getAxisData(); - if (axisData) { - double xMin = axisData->getXMin().getValue().toDouble(); - double xMax = axisData->getXMax().getValue().toDouble(); - - // 计算基准坐标的安全范围 - double safeEastingMin = 160000.0 - xMin; // UTM东向最小值 - 画布最小X - double safeEastingMax = 840000.0 - xMax; // UTM东向最大值 - 画布最大X - - // 确保范围有效 - if (safeEastingMin < 0.0) safeEastingMin = 0.0; - if (safeEastingMax > 1000000.0) safeEastingMax = 1000000.0; - if (safeEastingMin >= safeEastingMax) { - safeEastingMin = 0.0; - safeEastingMax = 1000000.0; - } - - return (easting >= safeEastingMin && easting <= safeEastingMax); - } - - return true; // 如果没有坐标轴数据,使用默认验证 + 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; } -// 验证北向坐标范围 -bool nmWxGeoRefDlg::isValidNorthing(double northing) +void nmWxGeoRefDlg::applyCoordinates(double dLongitude, double dLatitude, double dEasting, + double dNorthing, const QString& sZone, InputSource eSource) { - // 基本UTM范围检查 - if (northing < 0.0 || northing > 10000000.0) { - return false; - } - - // 获取坐标轴数据,考虑画布范围 - nmDataAxis* axisData = nmDataAnalyzeManager::getCurrentInstance()->getAxisData(); - if (axisData) { - double yMin = axisData->getYMin().getValue().toDouble(); - double yMax = axisData->getYMax().getValue().toDouble(); - - // 计算基准坐标的安全范围 - double safeNorthingMin = 0.0 - yMin; // UTM北向最小值 - 画布最小Y - double safeNorthingMax = 9000000.0 - yMax; // UTM北向最大值 - 画布最大Y - - // 确保范围有效 - if (safeNorthingMin < 0.0) safeNorthingMin = 0.0; - if (safeNorthingMax > 10000000.0) safeNorthingMax = 10000000.0; - if (safeNorthingMin >= safeNorthingMax) { - safeNorthingMin = 0.0; - safeNorthingMax = 10000000.0; - } - - return (northing >= safeNorthingMin && northing <= safeNorthingMax); - } - - return true; // 如果没有坐标轴数据,使用默认验证 + 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::loadDataToUI() +void nmWxGeoRefDlg::onCoordinateEdited(const QString&) { - m_bUpdating = true; - - // 检查UTM Zone是否有效 - QString utmZone = m_geoRefData.getUtmZone().getValue().toString(); - bool hasValidData = !utmZone.isEmpty(); - - if(hasValidData) { - // 有有效数据时,加载所有数据 - QString lonStr = QString::number(m_geoRefData.getLongitude().getValue().toDouble(), 'f', 6); - m_pLongitudeEdit->setText(lonStr); - m_lastLongitude = lonStr; - - QString latStr = QString::number(m_geoRefData.getLatitude().getValue().toDouble(), 'f', 6); - m_pLatitudeEdit->setText(latStr); - m_lastLatitude = latStr; - - int index = m_pUtmZoneCombo->findText(utmZone); - if(index >= 0) { - m_pUtmZoneCombo->setCurrentIndex(index); - } - - QString eastingStr = QString::number(m_geoRefData.getEasting().getValue().toDouble(), 'f', 2); - m_pEastingEdit->setText(eastingStr); - m_lastEasting = eastingStr; - - QString northingStr = QString::number(m_geoRefData.getNorthing().getValue().toDouble(), 'f', 2); - m_pNorthingEdit->setText(northingStr); - m_lastNorthing = northingStr; - - // 启用所有控件 - m_pLongitudeEdit->setEnabled(true); - m_pLatitudeEdit->setEnabled(true); - m_pEastingEdit->setEnabled(true); - m_pNorthingEdit->setEnabled(true); - } else { - // 无有效数据时,清空所有文本框,初始化last值 - m_pLongitudeEdit->setText(""); - m_pLatitudeEdit->setText(""); - m_pEastingEdit->setText(""); - m_pNorthingEdit->setText(""); - - m_lastLongitude = ""; - m_lastLatitude = ""; - m_lastEasting = ""; - m_lastNorthing = ""; - } - - m_bUpdating = false; + 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::saveDataUI() +void nmWxGeoRefDlg::onLengthUnitChanged(int) { - // 保存经度 - nmDataAttribute longitude = m_geoRefData.getLongitude(); - double lonValue = m_pLongitudeEdit->text().toDouble(); - longitude.setValue(lonValue); - m_geoRefData.setLongitude(longitude); - - // 保存纬度 - nmDataAttribute latitude = m_geoRefData.getLatitude(); - double latValue = m_pLatitudeEdit->text().toDouble(); - latitude.setValue(latValue); - m_geoRefData.setLatitude(latitude); - - // 保存UTM Zone - nmDataAttribute utmZone = m_geoRefData.getUtmZone(); - utmZone.setValue(QVariant(m_pUtmZoneCombo->currentText())); - m_geoRefData.setUtmZone(utmZone); - - // 保存东向坐标 - nmDataAttribute easting = m_geoRefData.getEasting(); - double estValue = m_pEastingEdit->text().toDouble(); - easting.setValue(estValue); - m_geoRefData.setEasting(easting); - - // 保存北向坐标 - nmDataAttribute northing = m_geoRefData.getNorthing(); - double norValue = m_pNorthingEdit->text().toDouble(); - northing.setValue(norValue); - m_geoRefData.setNorthing(northing); + 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::okAccept() +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(); +} - saveDataUI(); +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); +} - // 更新到全局数据管理器 - nmDataAnalyzeManager::getCurrentInstance()->updateGeoRefData(m_geoRefData); +const nmDataGeoRef& nmWxGeoRefDlg::geoRefData() const +{ + return m_oGeoRefData; +} - // 调用基类的accept - iDlgBase::accept(); +bool nmWxGeoRefDlg::hasChanges() const +{ + return m_bChanged; } -void nmWxGeoRefDlg::cancelReject() +void nmWxGeoRefDlg::accept() { - // 调用基类的reject - iDlgBase::reject(); + // 文本编辑即时检查,提交仍再次兜底;回车也不能绕过无效输入和单位服务检查。 + 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(); }