|
|
#include "nmWxPointerPosDlg.h"
|
|
|
|
|
|
#include "iUnitWxBinder.h"
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
#include "nmDataGeoRef.h"
|
|
|
#include "nmGuiPlot.h"
|
|
|
#include "ZxBaseUtil.h"
|
|
|
|
|
|
#include <QComboBox>
|
|
|
#include <QDialogButtonBox>
|
|
|
#include <QGridLayout>
|
|
|
#include <QGroupBox>
|
|
|
#include <QHBoxLayout>
|
|
|
#include <QLabel>
|
|
|
#include <QLineEdit>
|
|
|
#include <QPushButton>
|
|
|
#include <QVBoxLayout>
|
|
|
#include <qnumeric.h>
|
|
|
#include <cmath>
|
|
|
|
|
|
namespace
|
|
|
{
|
|
|
// 与比例尺、测量工具保持相同的三列布局,不固定窗口或控件的像素尺寸。
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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<nmGuiPlot*>(parent);
|
|
|
if (pOwnerPlot != nullptr)
|
|
|
{
|
|
|
m_pDataManager = pOwnerPlot->boundDataManager();
|
|
|
}
|
|
|
|
|
|
initUI();
|
|
|
// 沿用原窗口的初始状态:尚未收到鼠标位置时,展示本地原点及对应参考点。
|
|
|
setPointerPos(0.0, 0.0);
|
|
|
setWindowTitle(tr("Pointer position"));
|
|
|
setWindowFlags((windowFlags() | Qt::WindowStaysOnTopHint) &
|
|
|
~Qt::WindowContextHelpButtonHint);
|
|
|
// 初始大小由布局、字体和主题决定,允许用户随后调整窗口。
|
|
|
adjustSize();
|
|
|
}
|
|
|
|
|
|
void nmWxPointerPosDlg::initUI()
|
|
|
{
|
|
|
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<QLabel*> listLabels = findChildren<QLabel*>();
|
|
|
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::createLengthField(QGridLayout* pGrid, int nRow,
|
|
|
LengthField field, const QString& sLabel,
|
|
|
const QString& sObjectName)
|
|
|
{
|
|
|
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::setLengthValue(LengthField field, double dValue, bool bValid)
|
|
|
{
|
|
|
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();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPointerPosDlg::clearGeographicValues()
|
|
|
{
|
|
|
m_pLonEdit->clear();
|
|
|
m_pLatEdit->clear();
|
|
|
m_pUtmZoneEdit->clear();
|
|
|
setLengthValue(Easting, 0.0, false);
|
|
|
setLengthValue(Northing, 0.0, false);
|
|
|
}
|
|
|
|
|
|
void nmWxPointerPosDlg::setPointerPos(double x, double y)
|
|
|
{
|
|
|
// 若所属分析已释放或画布已改绑,不把新画布的坐标套用到旧分析的参考点。
|
|
|
nmGuiPlot* pOwnerPlot = qobject_cast<nmGuiPlot*>(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));
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
}
|