diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index db6075c6..3463295b 100644
Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ
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();
}