diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 140921bd..37718f1e 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 a9bfb2f5..5d7f2533 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -7000,36 +7000,36 @@ Please check your input coordinates. 本地网格 - X: - + X + X - Y: - + Y + Y Coordinates 坐标 - Longitude: - 经度: + Longitude + 经度 - Latitude: - 纬度: + Latitude + 纬度 - UTM zone: - UTM 带: + UTM zone + UTM 带 - Easting: - 东向坐标: + Easting + 东向坐标 - Northing: - 北向坐标: + Northing + 北向坐标 Close diff --git a/Include/nmNum/nmSubWxs/nmWxPointerPosDlg.h b/Include/nmNum/nmSubWxs/nmWxPointerPosDlg.h index d8366f49..8756d787 100644 --- a/Include/nmNum/nmSubWxs/nmWxPointerPosDlg.h +++ b/Include/nmNum/nmSubWxs/nmWxPointerPosDlg.h @@ -4,45 +4,72 @@ #include "iDlgBase.h" #include "nmSubWxs_global.h" -#include "nmDataGeoRef.h" +#include -class QLineEdit; class QComboBox; -class QPushButton; -class nmGUIComponentLineEdit; -class nmDataAttribute; +class QGridLayout; +class QLineEdit; +class nmDataAnalyzeManager; +/** + * @brief 模型位置显示窗口,跟随所属画布的鼠标位置更新坐标,不修改分析数据。 + * @note 本地 X/Y 和东向、北向坐标以米接收,由框架单位绑定器负责显示换算。 + * 坐标均可选中复制;UTM 带只展示当前参考点的设置,不提供编辑入口。 + * 输入列与单位列按 8:5 横向伸展,纵向采用字体和主题的推荐高度。 + */ class NM_SUB_WXS_EXPORT nmWxPointerPosDlg : public iDlgBase { - Q_OBJECT + Q_OBJECT public: - explicit nmWxPointerPosDlg(QWidget* parent = 0); - - public slots: - void setPointerPos(double x, double y); + /** @brief 绑定父级 nmGuiPlot 的分析管理器,由 Qt 父子关系管理窗口生命周期。 */ + explicit nmWxPointerPosDlg(QWidget* parent = nullptr); +public slots: + /** @brief 接收画布的米制坐标,刷新显示并保留各字段已选择的单位。 */ + void setPointerPos(double x, double y); private: - void initUI(); - void loadGeoRefDataToUI(); - QString formatCoordinate(double coordinate, bool isLatitude); // ¾­γ¶Èת»» + /** @brief 四个长度字段分别保存单位选择,不混用本地坐标与地理坐标。 */ + enum LengthField + { + LocalX, + LocalY, + Easting, + Northing, + LengthFieldCount + }; + + /** @brief 创建本地网格、地理坐标分组及标准关闭按钮。 */ + void initUI() override; + /** @brief 添加只读长度字段,并通过初始单位 m 识别框架长度单位组。 */ + void createLengthField(QGridLayout* pGrid, int nRow, LengthField field, + const QString& sLabel, const QString& sObjectName); - nmGUIComponentLineEdit* m_pX; - nmGUIComponentLineEdit* m_pY; + /** @brief 回填米制值;无有效数据时清空显示并暂停单位切换,避免恢复旧值。 */ + void setLengthValue(LengthField field, double dValue, bool bValid); - nmDataAttribute* m_pAttrX; - nmDataAttribute* m_pAttrY; + /** @brief 未设置参考点或参考数据失效时,清空地理坐标,保留本地坐标显示。 */ + void clearGeographicValues(); + + /** @brief 将有限且在范围内的经纬度转换为度分秒,按百分之一秒四舍五入并进位。 */ + QString formatCoordinate(double dCoordinate, bool bLatitude) const; + +private: + // 借用所属分析的管理器,删除时自动置空,不使用全局当前分析。 + // 参考点可能在窗口打开后新增、清空或重建,每次刷新重新读取,不缓存其裸指针。 + QPointer m_pDataManager; - QLineEdit* m_pLonEdit; - QLineEdit* m_pLatEdit; - QComboBox* m_pUtmZoneCombo; - QLineEdit* m_pEastingEdit; - QLineEdit* m_pNorthingEdit; - QPushButton* m_pCloseButton; + // 长度控件均由 Qt 父子关系释放;绑定状态独立记录,禁止以错误单位展示原始值。 + QLineEdit* m_pLengthEdits[LengthFieldCount]; + QComboBox* m_pLengthUnits[LengthFieldCount]; + bool m_bUnitsBound[LengthFieldCount]; - nmDataGeoRef* m_pGeoRefData; + // 非长度字段使用普通只读文本框,经纬度文本本身包含度、分、秒符号。 + QLineEdit* m_pLonEdit; + QLineEdit* m_pLatEdit; + QLineEdit* m_pUtmZoneEdit; }; #endif // NMWXPOINTERPOSDLG_H diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index ee94e63a..75ee1667 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -1756,7 +1756,8 @@ QWidget* nmSubWndMain::showPointerPosDialog() { nmGuiPlot* plot = dynamic_cast(m_pWxPlot); - if(plot == nullptr) { + // 位置窗口只服务于已绑定分析的画布,避免回退到全局当前分析。 + if(plot == nullptr || plot->boundDataManager() == nullptr) { return nullptr; } diff --git a/Src/nmNum/nmSubWxs/nmWxPointerPosDlg.cpp b/Src/nmNum/nmSubWxs/nmWxPointerPosDlg.cpp index 27f1b423..e69d522e 100644 --- a/Src/nmNum/nmSubWxs/nmWxPointerPosDlg.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPointerPosDlg.cpp @@ -1,277 +1,287 @@ #include "nmWxPointerPosDlg.h" -#include "nmGUIComponentLineEdit.h" + +#include "iUnitWxBinder.h" #include "nmDataAnalyzeManager.h" -#include "nmDataAttribute.h" +#include "nmDataGeoRef.h" +#include "nmGuiPlot.h" +#include "ZxBaseUtil.h" -#include #include -#include +#include +#include #include -#include #include -#include #include +#include +#include +#include +#include #include -nmWxPointerPosDlg::nmWxPointerPosDlg(QWidget* parent) - : iDlgBase(parent) +namespace { - setWindowTitle(tr("Pointer position")); - - m_pAttrX = new nmDataAttribute(); - m_pAttrX->setName("X"); - m_pAttrX->setUnit("m"); - m_pAttrX->setListUnitSelections(QStringList() << "m" << "ft" << "cm"); - m_pAttrX->setValue(0.0); - - m_pAttrY = new nmDataAttribute(); - m_pAttrY->setName("Y"); - m_pAttrY->setUnit("m"); - m_pAttrY->setListUnitSelections(QStringList() << "m" << "ft" << "cm"); - m_pAttrY->setValue(0.0); + // 与比例尺、测量工具保持相同的三列布局,不固定窗口或控件的像素尺寸。 + QGridLayout* createCoordinateGrid(QGroupBox* pGroup) + { + pGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + QGridLayout* pGrid = new QGridLayout(pGroup); + pGrid->setContentsMargins(8, 15, 8, 10); + pGrid->setHorizontalSpacing(6); + pGrid->setVerticalSpacing(6); + pGrid->setColumnStretch(0, 0); + pGrid->setColumnStretch(1, 8); + pGrid->setColumnStretch(2, 5); + return pGrid; + } + + // 只读而非禁用,保留鼠标选择、键盘导航和复制能力;纯文本字段可跨两列显示。 + QLineEdit* createReadOnlyField(QGridLayout* pGrid, int nRow, + const QString& sLabel, const QString& sObjectName, + int nColumnSpan) + { + 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->setReadOnly(true); + pEdit->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + pEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + pGrid->addWidget(pEdit, nRow, 1, 1, nColumnSpan); + return pEdit; + } +} - m_pGeoRefData = nmDataAnalyzeManager::getCurrentInstance()->getGeoRefData(); +nmWxPointerPosDlg::nmWxPointerPosDlg(QWidget* parent) + : iDlgBase(parent), + m_pLonEdit(nullptr), + m_pLatEdit(nullptr), + m_pUtmZoneEdit(nullptr) +{ + for (int nField = 0; nField < LengthFieldCount; ++nField) + { + m_pLengthEdits[nField] = nullptr; + m_pLengthUnits[nField] = nullptr; + m_bUnitsBound[nField] = false; + } + + // 浮动窗口的坐标与参考点必须来自同一画布,不能随全局当前分析切换而串用。 + nmGuiPlot* pOwnerPlot = qobject_cast(parent); + if (pOwnerPlot != nullptr) + { + m_pDataManager = pOwnerPlot->boundDataManager(); + } initUI(); - loadGeoRefDataToUI(); - this->resize(320, 400); - this->setFixedSize(this->size()); + // 沿用原窗口的初始状态:尚未收到鼠标位置时,展示本地原点及对应参考点。 + setPointerPos(0.0, 0.0); + setWindowTitle(tr("Pointer position")); + setWindowFlags((windowFlags() | Qt::WindowStaysOnTopHint) & + ~Qt::WindowContextHelpButtonHint); + // 初始大小由布局、字体和主题决定,允许用户随后调整窗口。 + adjustSize(); } void nmWxPointerPosDlg::initUI() { - // Local grid group - QGroupBox* localGroup = new QGroupBox(tr("Local grid")); - - QFormLayout* localForm = new QFormLayout; - localForm->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); - localForm->setVerticalSpacing(10); - localForm->setHorizontalSpacing(15); - localForm->setContentsMargins(8, 8, 8, 8); - - m_pX = new nmGUIComponentLineEdit(m_pAttrX, false); - m_pY = new nmGUIComponentLineEdit(m_pAttrY, false); - QLineEdit* leX = m_pX->findChild(); - - if(leX) { - leX->setMinimumWidth(100); - } - - QLineEdit* leY = m_pY->findChild(); - - if(leY) { - leY->setMinimumWidth(100); - } - - QWidget* xWidget = new QWidget; - QHBoxLayout* xLay = new QHBoxLayout(xWidget); - xLay->setContentsMargins(0, 0, 0, 0); - xLay->addWidget(m_pX); - - QWidget* yWidget = new QWidget; - QHBoxLayout* yLay = new QHBoxLayout(yWidget); - yLay->setContentsMargins(0, 0, 0, 0); - yLay->addWidget(m_pY); - - localForm->addRow(tr("X:"), xWidget); - localForm->addRow(tr("Y:"), yWidget); - - QVBoxLayout* localWrapper = new QVBoxLayout; - localWrapper->addStretch(1); - localWrapper->addLayout(localForm); - localWrapper->addStretch(1); - localWrapper->setContentsMargins(0, 0, 0, 0); - localGroup->setLayout(localWrapper); - - // Coordinates group - QGroupBox* coordGroup = new QGroupBox(tr("Coordinates")); - coordGroup->setEnabled(true); - - QFormLayout* coordForm = new QFormLayout; - coordForm->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); - coordForm->setVerticalSpacing(10); - coordForm->setHorizontalSpacing(15); - coordForm->setContentsMargins(8, 8, 8, 8); - - m_pLonEdit = new QLineEdit; - m_pLatEdit = new QLineEdit; - m_pUtmZoneCombo = new QComboBox; - m_pEastingEdit = new QLineEdit; - m_pNorthingEdit = new QLineEdit; - - // 初始化UTM Zone下拉框选项 - 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) { - QString zoneStr = QString("%1%2").arg(zone, 2, 10, QLatin1Char('0')).arg(band); - m_pUtmZoneCombo->addItem(zoneStr); - } - } - - m_pLonEdit->setEnabled(false); - m_pLatEdit->setEnabled(false); - m_pUtmZoneCombo->setEnabled(false); // ComboBox禁用编辑 - m_pEastingEdit->setEnabled(false); - m_pNorthingEdit->setEnabled(false); - - coordForm->addRow(tr("Longitude:"), m_pLonEdit); - coordForm->addRow(tr("Latitude:"), m_pLatEdit); - coordForm->addItem(new QSpacerItem(0, 20, QSizePolicy::Minimum, QSizePolicy::Fixed)); - coordForm->addRow(tr("UTM zone:"), m_pUtmZoneCombo); - coordForm->addRow(tr("Easting:"), m_pEastingEdit); - coordForm->addRow(tr("Northing:"), m_pNorthingEdit); - - QVBoxLayout* coordWrapper = new QVBoxLayout; - coordWrapper->addStretch(1); - coordWrapper->addLayout(coordForm); - coordWrapper->addStretch(1); - coordWrapper->setContentsMargins(0, 0, 0, 0); - coordGroup->setLayout(coordWrapper); - - // Bottom button - m_pCloseButton = new QPushButton(tr("Close")); - connect(m_pCloseButton, SIGNAL(clicked()), this, SLOT(reject())); - QHBoxLayout* btnLay = new QHBoxLayout; - btnLay->addStretch(); - btnLay->addWidget(m_pCloseButton); - - // 整体布局 - QVBoxLayout* mainLay = new QVBoxLayout; - mainLay->setContentsMargins(12, 12, 12, 12); - mainLay->setSpacing(15); - mainLay->addWidget(localGroup); - mainLay->addWidget(coordGroup); - mainLay->addLayout(btnLay); - setLayout(mainLay); + QVBoxLayout* pMainLayout = new QVBoxLayout(this); + pMainLayout->setContentsMargins(12, 10, 12, 0); + pMainLayout->setSpacing(10); + + QGroupBox* pLocalGroup = new QGroupBox(tr("Local grid"), this); + QGridLayout* pLocalGrid = createCoordinateGrid(pLocalGroup); + createLengthField(pLocalGrid, 0, LocalX, tr("X"), "pointerX"); + createLengthField(pLocalGrid, 1, LocalY, tr("Y"), "pointerY"); + pMainLayout->addWidget(pLocalGroup); + + QGroupBox* pGeoGroup = new QGroupBox(tr("Coordinates"), this); + QGridLayout* pGeoGrid = createCoordinateGrid(pGeoGroup); + m_pLonEdit = createReadOnlyField(pGeoGrid, 0, tr("Longitude"), "pointerLongitude", 2); + m_pLatEdit = createReadOnlyField(pGeoGrid, 1, tr("Latitude"), "pointerLatitude", 2); + // UTM 带是参考点的只读属性,无需创建包含全部分带选项的禁用下拉框。 + m_pUtmZoneEdit = createReadOnlyField(pGeoGrid, 2, tr("UTM zone"), "pointerUtmZone", 2); + createLengthField(pGeoGrid, 3, Easting, tr("Easting"), "pointerEasting"); + createLengthField(pGeoGrid, 4, Northing, tr("Northing"), "pointerNorthing"); + pMainLayout->addWidget(pGeoGroup); + + // 两个分组按当前标签的推荐宽度对齐,不写死中文字宽或控件宽度。 + int nLabelWidth = 0; + const QList listLabels = findChildren(); + for (int nIndex = 0; nIndex < listLabels.size(); ++nIndex) + { + nLabelWidth = qMax(nLabelWidth, listLabels[nIndex]->sizeHint().width()); + } + pLocalGrid->setColumnMinimumWidth(0, nLabelWidth); + pGeoGrid->setColumnMinimumWidth(0, nLabelWidth); + // 纵向余量留在内容与按钮之间,输入框和分组不随窗口拉高而变高。 + pMainLayout->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::Close, Qt::Horizontal, pFooter); + QPushButton* pClose = pButtons->button(QDialogButtonBox::Close); + pClose->setText(tr("Close")); + pClose->setIcon(zxLoadIcon("Cancel")); + pClose->setAutoDefault(false); + pFooterLayout->addWidget(pButtons); + pMainLayout->addWidget(pFooter); + connect(pButtons, SIGNAL(rejected()), this, SLOT(reject())); } -void nmWxPointerPosDlg::setPointerPos(double x, double y) +void nmWxPointerPosDlg::createLengthField(QGridLayout* pGrid, int nRow, + LengthField field, const QString& sLabel, + const QString& sObjectName) { - // 更新X、Y坐标显示 - m_pX->setDisplayValue(x); - m_pY->setDisplayValue(y); - - // 检查是否有地理参考数据 - if (!m_pGeoRefData) { - return; // 提前返回,避免后续无效计算 - } - - // 验证地理参考数据有效性 - QString utmZone = m_pGeoRefData->getUtmZone().getValue().toString(); - if (utmZone.isEmpty()) { - return; // 提前返回,避免后续无效计算 - } - - // 获取基础地理参考坐标 - double baseEasting = m_pGeoRefData->getEasting().getValue().toDouble(); - double baseNorthing = m_pGeoRefData->getNorthing().getValue().toDouble(); - double baseLongitude = m_pGeoRefData->getLongitude().getValue().toDouble(); - double baseLatitude = m_pGeoRefData->getLatitude().getValue().toDouble(); - - // 计算并更新UTM坐标 - double newEasting = baseEasting + x; - double newNorthing = baseNorthing + y; - m_pEastingEdit->setText(QString::number(newEasting, 'f', 2)); - m_pNorthingEdit->setText(QString::number(newNorthing, 'f', 2)); - - // 计算并更新经纬度坐标 - const double METERS_PER_DEGREE_LAT = 111111.0; - const double PI = 3.14159265358979323846; - - double latitudeRadians = baseLatitude * PI / 180.0; - double metersPerDegreeLon = METERS_PER_DEGREE_LAT * cos(latitudeRadians); - - double newLatitude = baseLatitude + (y / METERS_PER_DEGREE_LAT); - double newLongitude = baseLongitude + (x / metersPerDegreeLon); - - // 转换为度分秒格式并更新UI - QString latStr = formatCoordinate(newLatitude, true); - QString lonStr = formatCoordinate(newLongitude, false); - m_pLatEdit->setText(latStr); - m_pLonEdit->setText(lonStr); + QLineEdit* pEdit = createReadOnlyField(pGrid, nRow, sLabel, sObjectName, 1); + QComboBox* pUnit = new QComboBox(pGrid->parentWidget()); + pUnit->setObjectName(sObjectName + "Unit"); + pUnit->setSizeAdjustPolicy(QComboBox::AdjustToContents); + pUnit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + pGrid->addWidget(pUnit, nRow, 2); + + // 空参数标识配合 m 识别长度单位组,避免借用井参数而附带不适用的数值范围。 + pUnit->addItem("m"); + m_pLengthEdits[field] = pEdit; + m_pLengthUnits[field] = pUnit; + m_bUnitsBound[field] = bindUnitPairWxs(pEdit, pUnit, QString(), false); + m_bUnitsBound[field] = m_bUnitsBound[field] && m_pUnitBinder != nullptr; + // 初次有效值到达前保持空白;绑定失败时也不显示未经换算的坐标。 + setLengthValue(field, 0.0, false); } -void nmWxPointerPosDlg::loadGeoRefDataToUI() +void nmWxPointerPosDlg::setLengthValue(LengthField field, double dValue, bool bValid) { - if (!m_pGeoRefData) { - m_pUtmZoneCombo->setCurrentIndex(-1); // 设置为空选项 - return; - } - - // 检查是否有有效的地理参考数据 - QString utmZone = m_pGeoRefData->getUtmZone().getValue().toString(); - bool hasValidData = !utmZone.isEmpty(); - - if (hasValidData) { - // 加载经纬度 - double longitude = m_pGeoRefData->getLongitude().getValue().toDouble(); - double latitude = m_pGeoRefData->getLatitude().getValue().toDouble(); - - // 分别转换经度和纬度,并设置到UI - QString lonStr = formatCoordinate(longitude, false); // false表示经度 - QString latStr = formatCoordinate(latitude, true); // true表示纬度 - - m_pLonEdit->setText(lonStr); // 显示转换后的经度 - m_pLatEdit->setText(latStr); // 显示转换后的纬度 - - // 加载UTM Zone - int index = m_pUtmZoneCombo->findText(utmZone); - if (index >= 0) { - m_pUtmZoneCombo->setCurrentIndex(index); - } - - // 加载UTM坐标 - double easting = m_pGeoRefData->getEasting().getValue().toDouble(); - double northing = m_pGeoRefData->getNorthing().getValue().toDouble(); - - m_pEastingEdit->setText(QString::number(easting, 'f', 2)); - m_pNorthingEdit->setText(QString::number(northing, 'f', 2)); - } + const bool bCanDisplay = bValid && qIsFinite(dValue) && m_bUnitsBound[field]; + m_pLengthUnits[field]->setEnabled(bCanDisplay); + if (m_bUnitsBound[field]) + { + // 只更新米制基准值,不重建单位列表,鼠标移动后仍保留用户的显示单位。 + // 无数据时同步清除绑定器的旧值,避免后续单位切换使过期坐标重新出现。 + m_pUnitBinder->setBaseValue(m_pLengthEdits[field], bCanDisplay ? dValue : 0.0); + } + if (!bCanDisplay) + { + m_pLengthEdits[field]->clear(); + } } -QString nmWxPointerPosDlg::formatCoordinate(double coordinate, bool isLatitude) +void nmWxPointerPosDlg::clearGeographicValues() { - // 判断正负,确定方向 - bool isPositive = coordinate >= 0; - double absCoordinate = qAbs(coordinate); - - // 提取度数 - int degrees = static_cast(absCoordinate); - - // 计算分数 - double minutesFloat = (absCoordinate - degrees) * 60.0; - int minutes = static_cast(minutesFloat); - - // 计算秒数 - double seconds = (minutesFloat - minutes) * 60.0; - - // 防止浮点精度问题导致秒数≥60 - if (seconds >= 60.0) { - seconds = 59.999; - } - - // 确定方向字符 - QString direction; - if (isLatitude) { - direction = isPositive ? "N" : "S"; // 纬度 - } else { - direction = isPositive ? "E" : "W"; // 经度 - } + m_pLonEdit->clear(); + m_pLatEdit->clear(); + m_pUtmZoneEdit->clear(); + setLengthValue(Easting, 0.0, false); + setLengthValue(Northing, 0.0, false); +} - // 分步构建字符串 - QString result; - result += QString("%1").arg(degrees, 2, 10, QChar('0')); // 度数 - result += QString(tr("deg")); // 度符号 - result += QString("%1").arg(minutes, 2, 10, QChar('0')); // 分数 - result += QString(tr("min")); // 分符号 - result += QString("%1").arg(static_cast(seconds), 2, 10, QChar('0')); // 秒数 - result += QString(tr("sec")); // 秒符号 - result += direction; // 方向 +void nmWxPointerPosDlg::setPointerPos(double x, double y) +{ + // 若所属分析已释放或画布已改绑,不把新画布的坐标套用到旧分析的参考点。 + nmGuiPlot* pOwnerPlot = qobject_cast(parentWidget()); + const bool bValidPoint = !m_pDataManager.isNull() && pOwnerPlot != nullptr && + pOwnerPlot->boundDataManager() == m_pDataManager.data() && + qIsFinite(x) && qIsFinite(y); + setLengthValue(LocalX, x, bValidPoint); + setLengthValue(LocalY, y, bValidPoint); + if (!bValidPoint) + { + clearGeographicValues(); + return; + } + + // 每次从所属分析取最新对象,覆盖打开窗口后才设置参考点、参考点重建等情况。 + nmDataGeoRef* pGeoRef = m_pDataManager->getGeoRefData(); + if (pGeoRef == nullptr) + { + clearGeographicValues(); + return; + } + + const QString sZone = pGeoRef->getUtmZone().getValue().toString().trimmed(); + bool bZoneOK = false; + const int nZone = sZone.left(2).toInt(&bZoneOK); + const QString sBands("CDEFGHJKLMNPQRSTUVWX"); + if (!bZoneOK || nZone < 1 || nZone > 60 || sZone.size() != 3 || + !sBands.contains(sZone.at(2))) + { + clearGeographicValues(); + return; + } + + // 空字段不能按 0 参与换算;无效数据只清空显示,不弹校验提示。 + bool bEastingOK = false; + bool bNorthingOK = false; + bool bLongitudeOK = false; + bool bLatitudeOK = false; + const double dBaseEasting = pGeoRef->getEasting().getValue().toDouble(&bEastingOK); + const double dBaseNorthing = pGeoRef->getNorthing().getValue().toDouble(&bNorthingOK); + const double dBaseLongitude = pGeoRef->getLongitude().getValue().toDouble(&bLongitudeOK); + const double dBaseLatitude = pGeoRef->getLatitude().getValue().toDouble(&bLatitudeOK); + if (!bEastingOK || !bNorthingOK || !bLongitudeOK || !bLatitudeOK || + !qIsFinite(dBaseEasting) || !qIsFinite(dBaseNorthing) || + !qIsFinite(dBaseLongitude) || !qIsFinite(dBaseLatitude) || + qAbs(dBaseLongitude) > 180.0 || qAbs(dBaseLatitude) > 90.0) + { + clearGeographicValues(); + return; + } + + // 模型坐标为相对参考点的米制偏移,显示单位不参与坐标计算。 + const double dEasting = dBaseEasting + x; + const double dNorthing = dBaseNorthing + y; + if (!qIsFinite(dEasting) || !qIsFinite(dNorthing)) + { + clearGeographicValues(); + return; + } + m_pUtmZoneEdit->setText(sZone); + setLengthValue(Easting, dEasting, true); + setLengthValue(Northing, dNorthing, true); + + // 沿用现有的小范围经纬度近似换算,并非严格的 UTM 反投影。 + // 极点附近经度换算分母趋近于零,此时保留平面坐标,清空无法可靠计算的经纬度。 + const double dMetersPerDegreeLat = 111111.0; + const double dPi = 3.14159265358979323846; + const double dMetersPerDegreeLon = dMetersPerDegreeLat * + std::cos(dBaseLatitude * dPi / 180.0); + if (qAbs(dMetersPerDegreeLon) < 1.0e-6) + { + m_pLonEdit->clear(); + m_pLatEdit->clear(); + return; + } + + const double dLatitude = dBaseLatitude + y / dMetersPerDegreeLat; + const double dLongitude = dBaseLongitude + x / dMetersPerDegreeLon; + m_pLonEdit->setText(formatCoordinate(dLongitude, false)); + m_pLatEdit->setText(formatCoordinate(dLatitude, true)); +} - return result; -} \ No newline at end of file +QString nmWxPointerPosDlg::formatCoordinate(double dCoordinate, bool bLatitude) const +{ + if (!qIsFinite(dCoordinate) || qAbs(dCoordinate) > (bLatitude ? 90.0 : 180.0)) + { + return QString(); + } + + // 先按百分之一秒整体取整再分解,自动处理 59.995 秒进位,避免出现 60 秒。 + // 经纬度范围已限定,最大值为 180 * 3600 * 100,不会超出 int 范围。 + const int nTotalHundredths = qRound(qAbs(dCoordinate) * 360000.0); + const int nDegrees = nTotalHundredths / 360000; + const int nMinutes = (nTotalHundredths / 6000) % 60; + const double dSeconds = (nTotalHundredths % 6000) / 100.0; + const QString sDirection = bLatitude ? + (dCoordinate >= 0.0 ? "N" : "S") : (dCoordinate >= 0.0 ? "E" : "W"); + return QString("%1%2%3%4%5%6%7") + .arg(nDegrees, 2, 10, QLatin1Char('0')).arg(tr("deg")) + .arg(nMinutes, 2, 10, QLatin1Char('0')).arg(tr("min")) + .arg(dSeconds, 5, 'f', 2, QLatin1Char('0')).arg(tr("sec")).arg(sDirection); +}