feat/vtk-3d-view #7
|
|
@ -136,9 +136,12 @@ QWidget* ExceptionDetailDialog::buildCoordTab() {
|
||||||
topRow->addWidget(new QLabel(QStringLiteral("坐标系:"), tab));
|
topRow->addWidget(new QLabel(QStringLiteral("坐标系:"), tab));
|
||||||
coordSysCombo_ = new QComboBox(tab);
|
coordSysCombo_ = new QComboBox(tab);
|
||||||
coordSysCombo_->addItem(QStringLiteral("图形坐标"), QStringLiteral("jb"));
|
coordSysCombo_->addItem(QStringLiteral("图形坐标"), QStringLiteral("jb"));
|
||||||
// 经纬度/投影:客户端暂无换算数据(DTO 只解析图形坐标),先占位、切换给提示,不静默。
|
// 条件显示(对照原版 drawerExceptionInfo:latLon.length===0 → 仅图形坐标;否则三项)。
|
||||||
|
// 纯展示响应坐标,不做客户端换算;响应未携带经纬度时退化为仅图形坐标,与原版一致。
|
||||||
|
if (!anomaly_.lonLatPts.empty()) {
|
||||||
coordSysCombo_->addItem(QStringLiteral("经纬度坐标"), QStringLiteral("lonlat"));
|
coordSysCombo_->addItem(QStringLiteral("经纬度坐标"), QStringLiteral("lonlat"));
|
||||||
coordSysCombo_->addItem(QStringLiteral("投影坐标"), QStringLiteral("projection"));
|
coordSysCombo_->addItem(QStringLiteral("投影坐标"), QStringLiteral("projection"));
|
||||||
|
}
|
||||||
topRow->addWidget(coordSysCombo_);
|
topRow->addWidget(coordSysCombo_);
|
||||||
topRow->addStretch();
|
topRow->addStretch();
|
||||||
vertexCountLabel_ =
|
vertexCountLabel_ =
|
||||||
|
|
@ -163,31 +166,32 @@ QWidget* ExceptionDetailDialog::buildCoordTab() {
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<geopro::core::Vec2>& ExceptionDetailDialog::activeCoords() const {
|
||||||
|
// 按当前坐标系返回对应点集(对照原版 handleCoordChange:jb=图形 / lonlat=经纬度 / projection=投影)。
|
||||||
|
const QString sys = coordSysCombo_ ? coordSysCombo_->currentData().toString() : QString();
|
||||||
|
if (sys == QStringLiteral("lonlat")) return anomaly_.lonLatPts; // x=经度 y=纬度
|
||||||
|
if (sys == QStringLiteral("projection")) return anomaly_.eastNorthPts; // x=northCoord y=eastCoord
|
||||||
|
return anomaly_.localPts; // 图形坐标
|
||||||
|
}
|
||||||
|
|
||||||
void ExceptionDetailDialog::onCoordSystemChanged() {
|
void ExceptionDetailDialog::onCoordSystemChanged() {
|
||||||
if (!coordTable_) return;
|
if (!coordTable_) return;
|
||||||
const QString sys = coordSysCombo_->currentData().toString();
|
// 纯展示响应坐标(不做客户端换算):按当前坐标系填表(对照原版 showCoord 重填)。
|
||||||
// 仅图形坐标有数据;经纬度/投影暂无换算能力 → 清表 + 提示(不静默吞)。
|
const std::vector<geopro::core::Vec2>& pts = activeCoords();
|
||||||
if (sys != QStringLiteral("jb")) {
|
const int n = static_cast<int>(pts.size());
|
||||||
coordTable_->setRowCount(0);
|
|
||||||
QMessageBox::information(this, windowTitle(),
|
|
||||||
QStringLiteral("经纬度/投影坐标换算暂未在客户端提供,仅支持图形坐标。"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const int n = static_cast<int>(anomaly_.localPts.size());
|
|
||||||
coordTable_->setRowCount(n);
|
coordTable_->setRowCount(n);
|
||||||
for (int r = 0; r < n; ++r) {
|
for (int r = 0; r < n; ++r) {
|
||||||
coordTable_->setItem(r, 0, new QTableWidgetItem(QString::number(r + 1)));
|
coordTable_->setItem(r, 0, new QTableWidgetItem(QString::number(r + 1)));
|
||||||
coordTable_->setItem(
|
coordTable_->setItem(r, 1, new QTableWidgetItem(QString::number(pts[r].x, 'f', 7)));
|
||||||
r, 1, new QTableWidgetItem(QString::number(anomaly_.localPts[r].x, 'f', 7)));
|
coordTable_->setItem(r, 2, new QTableWidgetItem(QString::number(pts[r].y, 'f', 7)));
|
||||||
coordTable_->setItem(
|
|
||||||
r, 2, new QTableWidgetItem(QString::number(anomaly_.localPts[r].y, 'f', 7)));
|
|
||||||
coordTable_->setItem(r, 3, new QTableWidgetItem(QString())); // Z 空(对照原版)
|
coordTable_->setItem(r, 3, new QTableWidgetItem(QString())); // Z 空(对照原版)
|
||||||
}
|
}
|
||||||
|
if (vertexCountLabel_) vertexCountLabel_->setText(QStringLiteral("顶点数:%1").arg(n));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExceptionDetailDialog::exportCoords() {
|
void ExceptionDetailDialog::exportCoords() {
|
||||||
if (coordSysCombo_->currentData().toString() != QStringLiteral("jb") ||
|
const std::vector<geopro::core::Vec2>& pts = activeCoords();
|
||||||
anomaly_.localPts.empty()) {
|
if (pts.empty()) {
|
||||||
QMessageBox::information(this, windowTitle(), QStringLiteral("当前坐标系无可导出的坐标。"));
|
QMessageBox::information(this, windowTitle(), QStringLiteral("当前坐标系无可导出的坐标。"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -205,9 +209,9 @@ void ExceptionDetailDialog::exportCoords() {
|
||||||
QTextStream ts(&f);
|
QTextStream ts(&f);
|
||||||
// 对照原版:TSV「序号\tX\tY\tZ」,X/Y 7位小数,Z 空。
|
// 对照原版:TSV「序号\tX\tY\tZ」,X/Y 7位小数,Z 空。
|
||||||
ts << QStringLiteral("序号\tX\tY\tZ\n");
|
ts << QStringLiteral("序号\tX\tY\tZ\n");
|
||||||
for (int i = 0; i < static_cast<int>(anomaly_.localPts.size()); ++i) {
|
for (int i = 0; i < static_cast<int>(pts.size()); ++i) {
|
||||||
ts << (i + 1) << '\t' << QString::number(anomaly_.localPts[i].x, 'f', 7) << '\t'
|
ts << (i + 1) << '\t' << QString::number(pts[i].x, 'f', 7) << '\t'
|
||||||
<< QString::number(anomaly_.localPts[i].y, 'f', 7) << "\t\n";
|
<< QString::number(pts[i].y, 'f', 7) << "\t\n";
|
||||||
}
|
}
|
||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,10 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onConfirm();
|
void onConfirm();
|
||||||
void onCoordSystemChanged(); // 切换坐标系 → 重填坐标表(图形有数据,经纬度/投影暂无)
|
void onCoordSystemChanged(); // 切换坐标系 → 按对应点集重填坐标表(图形/经纬度/投影)
|
||||||
void exportCoords(); // 导出当前坐标系坐标为 txt(7位小数)
|
void exportCoords(); // 导出当前坐标系坐标为 txt(7位小数)
|
||||||
|
// 当前坐标系对应的点集(jb=图形 / lonlat=经纬度 / projection=投影;纯展示响应数据,不换算)。
|
||||||
|
const std::vector<geopro::core::Vec2>& activeCoords() const;
|
||||||
QWidget* buildLegendTab(); // 图例信息 Tab(只读样式)
|
QWidget* buildLegendTab(); // 图例信息 Tab(只读样式)
|
||||||
QWidget* buildCoordTab(); // 坐标信息 Tab
|
QWidget* buildCoordTab(); // 坐标信息 Tab
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
|
@ -66,14 +67,7 @@ ExceptionDialog::ExceptionDialog(geopro::data::IDatasetCommandRepository* repo,
|
||||||
typeRowLay->addWidget(exceptionTypeCombo_, 1);
|
typeRowLay->addWidget(exceptionTypeCombo_, 1);
|
||||||
typeRowLay->addWidget(addTypeBtn_);
|
typeRowLay->addWidget(addTypeBtn_);
|
||||||
form->addRow(formkit::editLabel(QStringLiteral("异常类型")), typeRow);
|
form->addRow(formkit::editLabel(QStringLiteral("异常类型")), typeRow);
|
||||||
connect(addTypeBtn_, &QPushButton::clicked, this, [this]() {
|
connect(addTypeBtn_, &QPushButton::clicked, this, &ExceptionDialog::onAddType);
|
||||||
// 原版点开「标注类型」新增弹窗(异常属性+名称,含完整 legend),提交 addExceptionType。
|
|
||||||
// 该子流程含整套 legend 编辑,后端 addExceptionType 端点客户端尚未接入;此处先提示,
|
|
||||||
// 不静默吞操作。用户可在 web 端新增类型后回到客户端选用。
|
|
||||||
QMessageBox::information(
|
|
||||||
this, windowTitle(),
|
|
||||||
QStringLiteral("新增异常类型请在 Web 端完成,新增后此处下拉会同步可选。"));
|
|
||||||
});
|
|
||||||
|
|
||||||
nameEdit_ = new QLineEdit(this);
|
nameEdit_ = new QLineEdit(this);
|
||||||
nameEdit_->setPlaceholderText(QStringLiteral("数据名称+异常类型代号+序号"));
|
nameEdit_->setPlaceholderText(QStringLiteral("数据名称+异常类型代号+序号"));
|
||||||
|
|
@ -165,6 +159,33 @@ void ExceptionDialog::onTypeChanged() {
|
||||||
loadExceptionTypes();
|
loadExceptionTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExceptionDialog::onAddType() {
|
||||||
|
if (!repo_) return;
|
||||||
|
// 最小可用复刻:小弹窗输类型名 → newCustomExceptionType → 成功后刷新下拉并选中新建项。
|
||||||
|
// (原版按钮打开「标注属性+名称」完整 legend 子弹窗走 addExceptionType;此处仅类型名,差距已记录。)
|
||||||
|
bool ok = false;
|
||||||
|
const QString name =
|
||||||
|
QInputDialog::getText(this, QStringLiteral("新增异常类型"), QStringLiteral("类型名称:"),
|
||||||
|
QLineEdit::Normal, QString(), &ok)
|
||||||
|
.trimmed();
|
||||||
|
if (!ok || name.isEmpty()) return;
|
||||||
|
|
||||||
|
addTypeBtn_->setEnabled(false);
|
||||||
|
QPointer<ExceptionDialog> self(this);
|
||||||
|
repo_->newCustomExceptionType(projectId_, name, [self, name](bool success, QString msg) {
|
||||||
|
if (!self) return;
|
||||||
|
self->updateAddTypeEnabled(); // 恢复按钮可用性(按当前标注类型)
|
||||||
|
if (!success) {
|
||||||
|
QMessageBox::warning(self, self->windowTitle(),
|
||||||
|
msg.isEmpty() ? QStringLiteral("新增异常类型失败") : msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 成功 → 记录待选中类型名,重拉列表后按名称匹配选中(对照原版刷新下拉)。
|
||||||
|
self->pendingSelectTypeName_ = name;
|
||||||
|
self->loadExceptionTypes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void ExceptionDialog::updateAddTypeEnabled() {
|
void ExceptionDialog::updateAddTypeEnabled() {
|
||||||
if (!addTypeBtn_) return;
|
if (!addTypeBtn_) return;
|
||||||
// 原版:文字类型(4) 或 未选标注类型时禁用「新增异常类型」。
|
// 原版:文字类型(4) 或 未选标注类型时禁用「新增异常类型」。
|
||||||
|
|
@ -188,6 +209,12 @@ void ExceptionDialog::loadExceptionTypes() {
|
||||||
if (id.isEmpty()) id = o.value(QStringLiteral("id")).toString();
|
if (id.isEmpty()) id = o.value(QStringLiteral("id")).toString();
|
||||||
if (!id.isEmpty()) self->exceptionTypeCombo_->addItem(label, id);
|
if (!id.isEmpty()) self->exceptionTypeCombo_->addItem(label, id);
|
||||||
}
|
}
|
||||||
|
// 若刚新建了类型 → 按名称匹配选中(找不到则保持默认首项,不报错)。
|
||||||
|
if (!self->pendingSelectTypeName_.isEmpty()) {
|
||||||
|
const int idx = self->exceptionTypeCombo_->findText(self->pendingSelectTypeName_);
|
||||||
|
if (idx >= 0) self->exceptionTypeCombo_->setCurrentIndex(idx);
|
||||||
|
self->pendingSelectTypeName_.clear();
|
||||||
|
}
|
||||||
if (self->exceptionTypeCombo_->count() > 0) self->suggestName();
|
if (self->exceptionTypeCombo_->count() > 0) self->suggestName();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onTypeChanged(); // 标注类型变 → 清名称 + 重拉异常类型列表 + 刷新「新增类型」可用性
|
void onTypeChanged(); // 标注类型变 → 清名称 + 重拉异常类型列表 + 刷新「新增类型」可用性
|
||||||
|
void onAddType(); // 「新增异常类型」:小弹窗输类型名 → newCustomExceptionType → 刷新+选中
|
||||||
void loadExceptionTypes(); // listExceptionTypes(projectId, remarkSourceType)
|
void loadExceptionTypes(); // listExceptionTypes(projectId, remarkSourceType)
|
||||||
void suggestName(); // getExceptionName(exceptionTypeId, remarkSourceId) → 名称回填
|
void suggestName(); // getExceptionName(exceptionTypeId, remarkSourceId) → 名称回填
|
||||||
void onConfirm(); // 校验 → 有手填坐标则直接 newException,否则 accept() 交给绘形
|
void onConfirm(); // 校验 → 有手填坐标则直接 newException,否则 accept() 交给绘形
|
||||||
|
|
@ -52,6 +53,7 @@ private:
|
||||||
QComboBox* markTypeCombo_ = nullptr; // userData = "1".."4"
|
QComboBox* markTypeCombo_ = nullptr; // userData = "1".."4"
|
||||||
QComboBox* exceptionTypeCombo_ = nullptr; // userData = 异常类型 id
|
QComboBox* exceptionTypeCombo_ = nullptr; // userData = 异常类型 id
|
||||||
QPushButton* addTypeBtn_ = nullptr; // 新增异常类型(对照原版,文字/未选类型禁用)
|
QPushButton* addTypeBtn_ = nullptr; // 新增异常类型(对照原版,文字/未选类型禁用)
|
||||||
|
QString pendingSelectTypeName_; // 新建类型后待选中的类型名(下一次列表刷新时匹配选中)
|
||||||
QLineEdit* nameEdit_ = nullptr;
|
QLineEdit* nameEdit_ = nullptr;
|
||||||
QPlainTextEdit* remarkEdit_ = nullptr;
|
QPlainTextEdit* remarkEdit_ = nullptr;
|
||||||
QTableWidget* coordTable_ = nullptr;
|
QTableWidget* coordTable_ = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,7 @@ void GridDataChartView::setPayload(const QVariant& payload) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto p = payload.value<geopro::core::ContourPayload>();
|
const auto p = payload.value<geopro::core::ContourPayload>();
|
||||||
|
lvlTemplateId_ = p.templateId; // 色阶模板 id(保存/覆盖回带,对照原版 lvlTemplateId)
|
||||||
setGridData(p.grid, p.scale, p.anomalies);
|
setGridData(p.grid, p.scale, p.anomalies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,9 +319,9 @@ void GridDataChartView::openColorScaleEditor() {
|
||||||
|
|
||||||
// projectId 在打开时取一次(随项目切换生效);无 getter 退化为空 → 后端按钮禁用。
|
// projectId 在打开时取一次(随项目切换生效);无 getter 退化为空 → 后端按钮禁用。
|
||||||
const QString projectId = projectIdGetter_ ? projectIdGetter_() : QString();
|
const QString projectId = projectIdGetter_ ? projectIdGetter_() : QString();
|
||||||
// 网格剖面载荷(ContourPayload)无 templateId 字段 → lvlTemplateId 传空(覆盖复选框禁用)。
|
// 传入网格色阶模板 id(getDetail type2 顶层 templateId)→ 「另存为覆盖」可用(对照原版 lvlTemplateId)。
|
||||||
ColorScaleConfigDialog dlg(gridScale_, grid_.vmin, grid_.vmax, std::move(samples), lineCfg_,
|
ColorScaleConfigDialog dlg(gridScale_, grid_.vmin, grid_.vmax, std::move(samples), lineCfg_,
|
||||||
tplRepo_, projectId, QString(), this);
|
tplRepo_, projectId, lvlTemplateId_, this);
|
||||||
if (dlg.exec() != QDialog::Accepted) return;
|
if (dlg.exec() != QDialog::Accepted) return;
|
||||||
|
|
||||||
gridScale_ = dlg.colorScale();
|
gridScale_ = dlg.colorScale();
|
||||||
|
|
@ -369,6 +370,7 @@ void GridDataChartView::reloadGrid() {
|
||||||
msg.isEmpty() ? QStringLiteral("重载失败") : msg);
|
msg.isEmpty() ? QStringLiteral("重载失败") : msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self->lvlTemplateId_ = p.templateId; // 重载后同步模板 id(色阶覆盖回带)
|
||||||
self->setGridData(p.grid, p.scale, p.anomalies);
|
self->setGridData(p.grid, p.scale, p.anomalies);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ private:
|
||||||
geopro::core::Grid grid_{1, 1};
|
geopro::core::Grid grid_{1, 1};
|
||||||
geopro::core::ColorScale gridScale_;
|
geopro::core::ColorScale gridScale_;
|
||||||
std::vector<geopro::core::Anomaly> anoms_;
|
std::vector<geopro::core::Anomaly> anoms_;
|
||||||
|
QString lvlTemplateId_; // 网格色阶模板 id(getDetail type2 顶层 templateId);色阶「另存为覆盖」用
|
||||||
bool hasGrid_ = false;
|
bool hasGrid_ = false;
|
||||||
|
|
||||||
// 工具条显隐开关
|
// 工具条显隐开关
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ struct Anomaly {
|
||||||
std::string createTime; // 创建时间(异常列表展示用,只读)
|
std::string createTime; // 创建时间(异常列表展示用,只读)
|
||||||
AnomalyMarkType markType = AnomalyMarkType::Polyline;
|
AnomalyMarkType markType = AnomalyMarkType::Polyline;
|
||||||
std::vector<Vec2> localPts; // 2D 局部坐标(剖面详情:x=距离, y=深度)
|
std::vector<Vec2> localPts; // 2D 局部坐标(剖面详情:x=距离, y=深度)
|
||||||
|
// 经纬度 / 投影坐标(详情坐标系切换用,纯展示,不做客户端换算;对照原版 drawerExceptionInfo)。
|
||||||
|
// 来源响应字段:latitudeLongitude.latLon[].{longitude,latitude}(→lonLatPts: x=经度 y=纬度)、
|
||||||
|
// geographicalCoordinates.coordinates[].{northCoord,eastCoord}(→eastNorthPts: x=northCoord y=eastCoord)。
|
||||||
|
// 空 = 响应未携带 → 坐标系下拉退化为仅「图形坐标」(与原版 latLon.length===0 一致)。
|
||||||
|
std::vector<Vec2> lonLatPts; // 经纬度坐标:x=经度(longitude), y=纬度(latitude)
|
||||||
|
std::vector<Vec2> eastNorthPts; // 投影坐标:x=northCoord, y=eastCoord(对照原版映射)
|
||||||
// VTK 三维:异常多边形/折线/点的世界 3D 坐标(落在所在切片平面上)+ 平面(法向/一点),
|
// VTK 三维:异常多边形/折线/点的世界 3D 坐标(落在所在切片平面上)+ 平面(法向/一点),
|
||||||
// 用于 3D 渲染与重定位/正视;与切片生命周期解耦(切片可删,异常按 worldPts/plane 仍可显示)。
|
// 用于 3D 渲染与重定位/正视;与切片生命周期解耦(切片可删,异常按 worldPts/plane 仍可显示)。
|
||||||
std::vector<Vec3> worldPts;
|
std::vector<Vec3> worldPts;
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,9 @@ struct ContourPayload {
|
||||||
geopro::core::Grid grid{1, 1};
|
geopro::core::Grid grid{1, 1};
|
||||||
geopro::core::ColorScale scale;
|
geopro::core::ColorScale scale;
|
||||||
std::vector<geopro::core::Anomaly> anomalies;
|
std::vector<geopro::core::Anomaly> anomalies;
|
||||||
|
// 色阶模板 id(来自 lvl/colorGradation/getDetail type2 的顶层 templateId):保存/覆盖色阶时回带
|
||||||
|
// (对照原版 contourPage lvlTemplateId = lvlConfig?.templateId;可空)。
|
||||||
|
QString templateId;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 列渲染种类:Text=预格式化文本(默认);Toggle=每行开关(蓝色药丸开关,ON=可见)。
|
// 列渲染种类:Text=预格式化文本(默认);Toggle=每行开关(蓝色药丸开关,ON=可见)。
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,9 @@ void ApiDatasetCommandRepository::loadInversionGrid(
|
||||||
dto::parseColorBar(r[1].data),
|
dto::parseColorBar(r[1].data),
|
||||||
dto::parseDatasetAnomalies(
|
dto::parseDatasetAnomalies(
|
||||||
r[2].data.value(QStringLiteral("value")).toArray())};
|
r[2].data.value(QStringLiteral("value")).toArray())};
|
||||||
|
// 顶层 templateId(对照原版 lvlTemplateId;色阶保存/覆盖回带)。
|
||||||
|
p.templateId =
|
||||||
|
r[1].data.value(QStringLiteral("templateId")).toVariant().toString();
|
||||||
cb(true, p, {});
|
cb(true, p, {});
|
||||||
});
|
});
|
||||||
QObject::connect(batch, &net::ApiBatch::failed, batch,
|
QObject::connect(batch, &net::ApiBatch::failed, batch,
|
||||||
|
|
@ -399,6 +402,16 @@ void ApiDatasetCommandRepository::newException(const QJsonObject& body,
|
||||||
wireStatus(api_.postJsonAsync(QStringLiteral("/business/exception"), body), std::move(cb));
|
wireStatus(api_.postJsonAsync(QStringLiteral("/business/exception"), body), std::move(cb));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiDatasetCommandRepository::newCustomExceptionType(
|
||||||
|
const QString& projectId, const QString& name, std::function<void(bool, QString)> cb) {
|
||||||
|
// 对照原版 newCustomExceptionType(datasetInfo/index.js:160):POST /business/customExceptionType
|
||||||
|
// body {projectId, exceptionTypeName},projectId 必填。
|
||||||
|
QJsonObject body{{QStringLiteral("projectId"), projectId},
|
||||||
|
{QStringLiteral("exceptionTypeName"), name}};
|
||||||
|
wireStatus(api_.postJsonAsync(QStringLiteral("/business/customExceptionType"), body),
|
||||||
|
std::move(cb));
|
||||||
|
}
|
||||||
|
|
||||||
void ApiDatasetCommandRepository::deleteException(const QString& id,
|
void ApiDatasetCommandRepository::deleteException(const QString& id,
|
||||||
std::function<void(bool, QString)> cb) {
|
std::function<void(bool, QString)> cb) {
|
||||||
wireStatus(api_.deleteAsync(QStringLiteral("/business/exception/%1").arg(enc(id))),
|
wireStatus(api_.deleteAsync(QStringLiteral("/business/exception/%1").arg(enc(id))),
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,8 @@ public:
|
||||||
std::function<void(bool ok, QString name, QString msg)> cb) override;
|
std::function<void(bool ok, QString name, QString msg)> cb) override;
|
||||||
void newException(const QJsonObject& body,
|
void newException(const QJsonObject& body,
|
||||||
std::function<void(bool ok, QString msg)> cb) override;
|
std::function<void(bool ok, QString msg)> cb) override;
|
||||||
|
void newCustomExceptionType(const QString& projectId, const QString& name,
|
||||||
|
std::function<void(bool ok, QString msg)> cb) override;
|
||||||
void deleteException(const QString& id,
|
void deleteException(const QString& id,
|
||||||
std::function<void(bool ok, QString msg)> cb) override;
|
std::function<void(bool ok, QString msg)> cb) override;
|
||||||
void updateException(const QJsonObject& body,
|
void updateException(const QJsonObject& body,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ struct GridParts {
|
||||||
geopro::core::Grid grid{1, 1}; // Grid 无默认构造;占位
|
geopro::core::Grid grid{1, 1}; // Grid 无默认构造;占位
|
||||||
geopro::core::ColorScale gridScale;
|
geopro::core::ColorScale gridScale;
|
||||||
std::vector<geopro::core::Anomaly> anomalies;
|
std::vector<geopro::core::Anomaly> anomalies;
|
||||||
|
QString templateId; // 网格色阶模板 id(保存/覆盖回带,对照原版 lvlTemplateId)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 失败判定(原 must() 口径):业务码 != 200 或传输错误。
|
// 失败判定(原 must() 口径):业务码 != 200 或传输错误。
|
||||||
|
|
@ -78,6 +79,8 @@ GridParts parseGridParts(const QList<net::ApiResponse>& r) {
|
||||||
p.grid = dto::parseInversionGrid(r[0].data);
|
p.grid = dto::parseInversionGrid(r[0].data);
|
||||||
p.gridScale = dto::parseColorBar(r[1].data);
|
p.gridScale = dto::parseColorBar(r[1].data);
|
||||||
p.anomalies = dto::parseDatasetAnomalies(r[2].data.value(QStringLiteral("value")).toArray());
|
p.anomalies = dto::parseDatasetAnomalies(r[2].data.value(QStringLiteral("value")).toArray());
|
||||||
|
// 顶层 templateId(对照原版 lvlConfig?.templateId,与散点 parseScatterParts 同范式)。
|
||||||
|
p.templateId = r[1].data.value(QStringLiteral("templateId")).toVariant().toString();
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +182,9 @@ DetailLoad* ApiDatasetRepository::makeInversionScatter(const std::string& dsId)
|
||||||
DetailLoad* ApiDatasetRepository::makeInversionGrid(const std::string& dsId) {
|
DetailLoad* ApiDatasetRepository::makeInversionGrid(const std::string& dsId) {
|
||||||
return new ApiDetailLoad(inversionGridBatch(api_, dsId), [](const QList<net::ApiResponse>& r) {
|
return new ApiDetailLoad(inversionGridBatch(api_, dsId), [](const QList<net::ApiResponse>& r) {
|
||||||
GridParts p = parseGridParts(r);
|
GridParts p = parseGridParts(r);
|
||||||
return QVariant::fromValue(core::ContourPayload{p.grid, p.gridScale, p.anomalies});
|
core::ContourPayload payload{p.grid, p.gridScale, p.anomalies};
|
||||||
|
payload.templateId = p.templateId; // 色阶保存/覆盖回带(对照原版 lvlTemplateId)
|
||||||
|
return QVariant::fromValue(payload);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,18 @@ std::vector<Anomaly> parseDatasetAnomalies(const QJsonArray& arr) {
|
||||||
const QJsonObject p = c.toObject();
|
const QJsonObject p = c.toObject();
|
||||||
a.localPts.push_back(Vec2{p.value("x").toDouble(), p.value("y").toDouble()});
|
a.localPts.push_back(Vec2{p.value("x").toDouble(), p.value("y").toDouble()});
|
||||||
}
|
}
|
||||||
|
// 经纬度坐标(对照原版:latitudeLongitude.latLon[].{longitude,latitude})。
|
||||||
|
// 纯展示,不换算;空/缺失 → 详情坐标系下拉只给「图形坐标」(与原版 latLon.length===0 一致)。
|
||||||
|
for (auto c : o.value("latitudeLongitude").toObject().value("latLon").toArray()) {
|
||||||
|
const QJsonObject p = c.toObject();
|
||||||
|
a.lonLatPts.push_back(Vec2{p.value("longitude").toDouble(), p.value("latitude").toDouble()});
|
||||||
|
}
|
||||||
|
// 投影坐标(对照原版:geographicalCoordinates.coordinates[].{northCoord,eastCoord},
|
||||||
|
// 原版映射 x=northCoord、y=eastCoord)。
|
||||||
|
for (auto c : o.value("geographicalCoordinates").toObject().value("coordinates").toArray()) {
|
||||||
|
const QJsonObject p = c.toObject();
|
||||||
|
a.eastNorthPts.push_back(Vec2{p.value("northCoord").toDouble(), p.value("eastCoord").toDouble()});
|
||||||
|
}
|
||||||
out.push_back(std::move(a));
|
out.push_back(std::move(a));
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,14 @@ public:
|
||||||
virtual void newException(const QJsonObject& body,
|
virtual void newException(const QJsonObject& body,
|
||||||
std::function<void(bool ok, QString msg)> cb) = 0;
|
std::function<void(bool ok, QString msg)> cb) = 0;
|
||||||
|
|
||||||
|
// 新增自定义异常类型:POST /business/customExceptionType
|
||||||
|
// body {projectId, exceptionTypeName}(对应原版 newCustomExceptionType,datasetInfo/index.js:160)。
|
||||||
|
// 注:原版「新增异常类型」按钮实际走的是 addExceptionType(POST /business/exceptionType)
|
||||||
|
// 的完整 legend 编辑子流程;客户端此处复刻为「最小可用」——仅类型名,调用更简的
|
||||||
|
// customExceptionType 端点(见 IDialog 注释,差距已记录)。
|
||||||
|
virtual void newCustomExceptionType(const QString& projectId, const QString& name,
|
||||||
|
std::function<void(bool ok, QString msg)> cb) = 0;
|
||||||
|
|
||||||
// 删除异常:DELETE /business/exception/{id}(对应原版 deleteExceptionDataInProfileInversion)。
|
// 删除异常:DELETE /business/exception/{id}(对应原版 deleteExceptionDataInProfileInversion)。
|
||||||
virtual void deleteException(const QString& id,
|
virtual void deleteException(const QString& id,
|
||||||
std::function<void(bool ok, QString msg)> cb) = 0;
|
std::function<void(bool ok, QString msg)> cb) = 0;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,35 @@ TEST(DatasetChartDto, ParsesAnomalyPolyline) {
|
||||||
EXPECT_DOUBLE_EQ(v[0].localPts[1].x, 3.0);
|
EXPECT_DOUBLE_EQ(v[0].localPts[1].x, 3.0);
|
||||||
EXPECT_TRUE(v[0].dashed);
|
EXPECT_TRUE(v[0].dashed);
|
||||||
}
|
}
|
||||||
|
TEST(DatasetChartDto, ParsesAnomalyLatLonAndEastNorth) {
|
||||||
|
// 对照原版 drawerExceptionInfo:经纬度 latitudeLongitude.latLon[].{longitude,latitude}、
|
||||||
|
// 投影 geographicalCoordinates.coordinates[].{northCoord,eastCoord}(x=northCoord y=eastCoord)。
|
||||||
|
auto arr = QJsonDocument::fromJson(
|
||||||
|
R"([{"id":"exc-2","exceptionName":"A2","exceptionMarkType":2,
|
||||||
|
"location":{"coordinate":[{"x":1,"y":2}]},
|
||||||
|
"latitudeLongitude":{"latLon":[{"longitude":120.5,"latitude":30.25}]},
|
||||||
|
"geographicalCoordinates":{"coordinates":[{"northCoord":3350000.0,"eastCoord":500000.0}]}}])")
|
||||||
|
.array();
|
||||||
|
auto v = parseDatasetAnomalies(arr);
|
||||||
|
ASSERT_EQ(v.size(), 1u);
|
||||||
|
ASSERT_EQ(v[0].lonLatPts.size(), 1u);
|
||||||
|
EXPECT_DOUBLE_EQ(v[0].lonLatPts[0].x, 120.5); // 经度
|
||||||
|
EXPECT_DOUBLE_EQ(v[0].lonLatPts[0].y, 30.25); // 纬度
|
||||||
|
ASSERT_EQ(v[0].eastNorthPts.size(), 1u);
|
||||||
|
EXPECT_DOUBLE_EQ(v[0].eastNorthPts[0].x, 3350000.0); // northCoord → X
|
||||||
|
EXPECT_DOUBLE_EQ(v[0].eastNorthPts[0].y, 500000.0); // eastCoord → Y
|
||||||
|
}
|
||||||
|
TEST(DatasetChartDto, AnomalyWithoutGeoCoordsLeavesVectorsEmpty) {
|
||||||
|
// 响应未携带经纬度/投影 → 两向量为空(详情坐标系下拉退化为仅「图形坐标」,与原版一致)。
|
||||||
|
auto arr = QJsonDocument::fromJson(
|
||||||
|
R"([{"id":"exc-3","exceptionName":"A3","exceptionMarkType":2,
|
||||||
|
"location":{"coordinate":[{"x":1,"y":2}]}}])")
|
||||||
|
.array();
|
||||||
|
auto v = parseDatasetAnomalies(arr);
|
||||||
|
ASSERT_EQ(v.size(), 1u);
|
||||||
|
EXPECT_TRUE(v[0].lonLatPts.empty());
|
||||||
|
EXPECT_TRUE(v[0].eastNorthPts.empty());
|
||||||
|
}
|
||||||
TEST(DatasetChartDto, ParsesAnomalyIdentityFields) {
|
TEST(DatasetChartDto, ParsesAnomalyIdentityFields) {
|
||||||
// I10/I11/I12 需要 id(删除/更新/定位)、备注、异常类型 id、创建时间。
|
// I10/I11/I12 需要 id(删除/更新/定位)、备注、异常类型 id、创建时间。
|
||||||
auto arr = QJsonDocument::fromJson(
|
auto arr = QJsonDocument::fromJson(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue