geopro/tests/data/test_measurement_dto.cpp

314 lines
13 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include <algorithm>
#include <QJsonDocument>
#include <QJsonObject>
#include "dto/MeasurementDto.hpp"
using namespace geopro::data::dto;
static QJsonObject obj(const char* json) {
return QJsonDocument::fromJson(json).object();
}
// scatter/graph 的 dataelectrodeList + scatterGraphData.rows17 列定位数组,取自真实夹具前 3 行)。
static const char* kScatterData = R"({
"electrodeList": [
{"electrodeNo":1,"horizontalDistance":0,"slopeDistance":0,"pseudoDepth":0},
{"electrodeNo":2,"horizontalDistance":1.08,"slopeDistance":1.2459494894830485,"pseudoDepth":0},
{"electrodeNo":3,"horizontalDistance":3.15,"slopeDistance":3.3194843662393243,"pseudoDepth":0}
],
"scatterGraphData": {
"hasCoordinate": true,
"rows": [
[2.115013167852418,2.2827169278611863,null,-1.2,242.952988,2.385,4.116,0,"1453611521843200",24.288,1,4,2,3,12.5664,1,242.952988],
[5.124875148343908,5.300065674086864,null,-2.4,-1066.592407,1.873,13.377,0,"1453611521843201",23.12,1,7,3,5,25.1327,2,-1066.592407],
[7.994418521491841,8.175900836015376,null,-3.6,100.239388,2.001,9.976,0,"1453611521843202",21.987,1,10,4,7,37.6991,3,100.239388]
],
"__rowsTotal": 576
}
})";
// colorGradation/getDetail{type:3} 的 data与反演同构混合格式 colorBarhex + rgba alpha 01
static const char* kColorBarData = R"json({
"properties": {
"lvlSchemeType": "normal",
"colorBar": [
["0.00", "#00008B"],
["1.51", "rgba(0, 0, 170, 1)"],
["208.40", "#FF0000"],
["1323.20", "rgba(48, 0, 48, 1)"]
]
}
})json";
// measurement/rows 的 datafiledList列定义+ rowList含 vmap 摊平),取自真实夹具前 2 行。
static const char* kRowsData = R"({
"filedList": [
{"fieldCode":"a","name":"A"},
{"fieldCode":"b","name":"B"},
{"fieldCode":"k","name":"K"},
{"fieldCode":"R","name":"电阻"},
{"fieldCode":"R0","name":"视电阻率"}
],
"rowList": [
{"id":"1453611521843200","rowNo":1,"displayStatus":0,"a":1,"b":4,"k":12.5664,
"vmap":{"R":19.333542,"V":10223.44531,"I":528.793213,"R0_RD":0,"SP":127.640175,"R0":242.952988}},
{"id":"1453611521843201","rowNo":2,"displayStatus":0,"a":1,"b":7,"k":25.1327,
"vmap":{"R":2.974368,"V":1822.526123,"I":612.743958,"R0_RD":0,"SP":92.380089,"R0":74.753899}}
],
"__rowListTotal": 576
})";
TEST(MeasurementDto, ParsesScatterPositionalRows) {
auto p = parseMeasurementScatter(obj(kScatterData), obj(kColorBarData));
const auto& s = p.scatter;
// 3 个散点连续上色cauto与反演原数据同路径已无 discreteColor 字段)
// + 图例与反演统一为底部横条。
EXPECT_FALSE(p.verticalLegend); // measurement 图例改用底部横条(与反演一致)
ASSERT_EQ(s.x.size(), 3u);
ASSERT_EQ(s.y.size(), 3u);
ASSERT_EQ(s.v.size(), 3u);
// x = col1 斜距y = col3 伪深度(负);色值 = col16。
EXPECT_DOUBLE_EQ(s.x[0], 2.2827169278611863);
EXPECT_DOUBLE_EQ(s.y[0], -1.2);
EXPECT_LT(s.y[0], 0.0); // 伪深度向下为负
EXPECT_DOUBLE_EQ(s.v[0], 242.952988);
EXPECT_DOUBLE_EQ(s.x[2], 8.175900836015376);
EXPECT_DOUBLE_EQ(s.y[2], -3.6);
// cauto 关键事实负异常值col16原样保留——视图 setDataRange 据此得 cmin<0
// 使中段视电阻率≈25250归一化到色阶中部深品红/紫),与原版 Plotly 一致。
EXPECT_DOUBLE_EQ(s.v[1], -1066.592407);
EXPECT_LT(s.v[1], 0.0); // 负异常值未被过滤
const double vMin = std::min({s.v[0], s.v[1], s.v[2]});
EXPECT_DOUBLE_EQ(vMin, -1066.592407); // 数据范围下界为负 → cmin<0
// A/B/M/N = col10-13。
ASSERT_EQ(s.a.size(), 3u);
EXPECT_DOUBLE_EQ(s.a[0], 1.0);
EXPECT_DOUBLE_EQ(s.b[0], 4.0);
EXPECT_DOUBLE_EQ(s.m[0], 2.0);
EXPECT_DOUBLE_EQ(s.n[0], 3.0);
// 电极 X = electrodeList.slopeDistance。
ASSERT_EQ(s.electrodeX.size(), 3u);
EXPECT_DOUBLE_EQ(s.electrodeX[0], 0.0);
EXPECT_DOUBLE_EQ(s.electrodeX[1], 1.2459494894830485);
// 电极编号 = electrodeList.electrodeNo1-based与 electrodeX 一一对应(供 hover num
ASSERT_EQ(s.electrodeNo.size(), 3u);
EXPECT_DOUBLE_EQ(s.electrodeNo[0], 1.0);
EXPECT_DOUBLE_EQ(s.electrodeNo[1], 2.0);
EXPECT_DOUBLE_EQ(s.electrodeNo[2], 3.0);
}
TEST(MeasurementDto, ParsesScatterPointMetadata) {
// [i]信息 / 显隐持久化用元数据id(col8) / displayStatus(col7) / row(col15) / pseu(col16)。
auto p = parseMeasurementScatter(obj(kScatterData), obj(kColorBarData));
const auto& s = p.scatter;
ASSERT_EQ(s.id.size(), 3u);
EXPECT_EQ(s.id[0], "1453611521843200");
EXPECT_EQ(s.id[1], "1453611521843201");
ASSERT_EQ(s.displayStatus.size(), 3u);
EXPECT_EQ(s.displayStatus[0], 0); // col7=0 → 可见
ASSERT_EQ(s.row.size(), 3u);
EXPECT_DOUBLE_EQ(s.row[0], 1.0); // DataRow = col15
EXPECT_DOUBLE_EQ(s.row[2], 3.0);
ASSERT_EQ(s.pseu.size(), 3u);
EXPECT_DOUBLE_EQ(s.pseu[0], 242.952988); // Pseu_Resis = col16
}
// scatter/graph 含 scatterGraphConf 时:工具条下拉项 + 默认选中 + x/y 本地重绘备选列。
static const char* kScatterDataWithConf = R"({
"electrodeList": [
{"electrodeNo":1,"horizontalDistance":0,"slopeDistance":0,"pseudoDepth":0}
],
"scatterGraphConf": {
"x": [
{"fieldCode":"horizontalDistance","name":"平距"},
{"fieldCode":"slopeDistance","name":"斜距"}
],
"y": [
{"fieldCode":"Layer No","name":"层数"},
{"fieldCode":"pseudoDepth","name":"伪深度"},
{"fieldCode":"elevationPseudoDepth","name":"伪深度+高程"}
],
"v": [
{"fieldCode":"I","name":"电流"},
{"fieldCode":"R0","name":"视电阻率"},
{"fieldCode":"SP","name":"自然电位"}
],
"vcalculationMethod": [
{"fieldCode":null,"name":"线性"},
{"fieldCode":null,"name":"对数"},
{"fieldCode":null,"name":"导数"}
]
},
"scatterGraphData": {
"rows": [
[2.115013167852418,2.2827169278611863,null,-1.2,242.952988,2.385,4.116,0,"id0",24.288,1,4,2,3,12.5664,1,242.952988]
]
}
})";
TEST(MeasurementDto, ParsesScatterToolbarConfAndDefaults) {
auto p = parseMeasurementScatter(obj(kScatterDataWithConf), obj(kColorBarData));
const auto& tb = p.toolbar;
EXPECT_FALSE(tb.empty());
ASSERT_EQ(tb.x.size(), 2u);
ASSERT_EQ(tb.y.size(), 3u);
ASSERT_EQ(tb.v.size(), 3u);
ASSERT_EQ(tb.method.size(), 3u);
// 选项名/码(顺序保留)。
EXPECT_EQ(tb.x[0].code.toStdString(), "horizontalDistance");
EXPECT_EQ(tb.x[0].name.toStdString(), std::string("平距"));
EXPECT_EQ(tb.x[1].code.toStdString(), "slopeDistance");
EXPECT_EQ(tb.method[0].name.toStdString(), std::string("线性"));
EXPECT_TRUE(tb.method[0].code.isEmpty()); // method 的 fieldCode 为 null
// 默认选中:斜距 / 伪深度 / 视电阻率 / 线性。
EXPECT_EQ(tb.defaultX.toStdString(), "slopeDistance");
EXPECT_EQ(tb.defaultY.toStdString(), "pseudoDepth");
EXPECT_EQ(tb.defaultV.toStdString(), "R0");
EXPECT_EQ(tb.defaultMethod.toStdString(), std::string("线性"));
}
TEST(MeasurementDto, CarriesAltColumnsForLocalReplot) {
auto p = parseMeasurementScatter(obj(kScatterDataWithConf), obj(kColorBarData));
// x 备选:平距(col0) / 斜距(col1)y 备选:伪深度(col3) / 伪深度+高程(col9)。
ASSERT_EQ(p.altXHorizontal.size(), 1u);
ASSERT_EQ(p.altXSlope.size(), 1u);
ASSERT_EQ(p.altYPseudo.size(), 1u);
ASSERT_EQ(p.altYElevationPseudo.size(), 1u);
EXPECT_DOUBLE_EQ(p.altXHorizontal[0], 2.115013167852418);
EXPECT_DOUBLE_EQ(p.altXSlope[0], 2.2827169278611863);
EXPECT_DOUBLE_EQ(p.altYPseudo[0], -1.2);
EXPECT_DOUBLE_EQ(p.altYElevationPseudo[0], 24.288);
}
TEST(MeasurementDto, ToolbarEmptyWhenNoConf) {
// 无 scatterGraphConf反演原数据形状工具条空 → 视图渲染反演工具条。
auto p = parseMeasurementScatter(obj(kScatterData), obj(kColorBarData));
EXPECT_TRUE(p.toolbar.empty());
}
TEST(MeasurementDto, ParsesScatterColorBarOpaque) {
auto p = parseMeasurementScatter(obj(kScatterData), obj(kColorBarData));
auto stops = p.scale.stops();
ASSERT_EQ(stops.size(), 4u);
// 首/末值。
EXPECT_DOUBLE_EQ(stops.front().first, 0.0);
EXPECT_DOUBLE_EQ(stops.back().first, 1323.20);
// 首色 #00008B深蓝末色 rgba(48,0,48,1)alpha=1 → 255不透明
EXPECT_EQ(stops.front().second.r, 0);
EXPECT_EQ(stops.front().second.g, 0);
EXPECT_EQ(stops.front().second.b, 0x8B);
EXPECT_EQ(stops.front().second.a, 255);
EXPECT_EQ(stops.back().second.r, 48);
EXPECT_EQ(stops.back().second.g, 0);
EXPECT_EQ(stops.back().second.b, 48);
EXPECT_EQ(stops.back().second.a, 255); // 回归alpha=1 须映射为 255 不透明
}
TEST(MeasurementDto, CapturesColorBarTemplateId) {
// 色阶 getDetail 顶层 templateId → ScatterPayload.templateId保存色阶回带对照原版
auto withTpl = parseMeasurementScatter(
obj(kScatterData),
obj(R"json({"templateId":"tpl-123","properties":{"colorBar":[["0.00","#00008B"]]}})json"));
EXPECT_EQ(withTpl.templateId.toStdString(), "tpl-123");
// 缺省 templateId → 空串(原版亦可空)。
auto noTpl = parseMeasurementScatter(obj(kScatterData), obj(kColorBarData));
EXPECT_TRUE(noTpl.templateId.isEmpty());
}
TEST(MeasurementDto, ParsesTableColumnsAndVmapFlattened) {
auto t = parseMeasurementTable(obj(kRowsData));
// 列来自 filedList5 列)+ 末尾追加“隐藏/显示”开关列 = 6 列。
ASSERT_EQ(t.columns.size(), 6u);
EXPECT_EQ(t.columns[0].code.toStdString(), "a");
EXPECT_EQ(t.columns[0].title.toStdString(), "A");
EXPECT_EQ(t.columns[4].code.toStdString(), "R0");
EXPECT_EQ(t.columns[4].title.toStdString(), std::string("视电阻率"));
// 2 行total 来自 __rowListTotal。
ASSERT_EQ(t.rows.size(), 2u);
EXPECT_EQ(t.total, 576);
// 顶层列a/b/k从行对象取vmap 列R/R0从 vmap 摊平。
ASSERT_EQ(t.rows[0].size(), 6u);
EXPECT_EQ(t.rows[0][0].toStdString(), "1"); // a整数无小数点
EXPECT_EQ(t.rows[0][1].toStdString(), "4"); // b
EXPECT_EQ(t.rows[0][3].toStdString(), "19.333542"); // R from vmap
EXPECT_EQ(t.rows[0][4].toStdString(), "242.952988"); // R0 from vmap
EXPECT_EQ(t.rows[1][4].toStdString(), "74.753899");
}
TEST(MeasurementDto, AppendsHideShowToggleColumn) {
auto t = parseMeasurementTable(obj(kRowsData));
// 末列为“隐藏/显示”kind=Togglecode=displayStatus。
ASSERT_EQ(t.columns.size(), 6u);
const auto& toggle = t.columns.back();
EXPECT_EQ(toggle.title.toStdString(), std::string("隐藏/显示"));
EXPECT_EQ(toggle.code.toStdString(), "displayStatus");
EXPECT_EQ(toggle.kind, geopro::core::TableColumnKind::Toggle);
// displayStatus==0 ⇒ ON/可见("1"),全部 ON。
ASSERT_EQ(t.rows.size(), 2u);
EXPECT_EQ(t.rows[0].back().toStdString(), "1");
EXPECT_EQ(t.rows[1].back().toStdString(), "1");
}
TEST(MeasurementDto, ToggleOffWhenDisplayStatusNonZero) {
// displayStatus!=0 ⇒ OFF"0")。
auto t = parseMeasurementTable(obj(R"({
"filedList": [{"fieldCode":"a","name":"A"}],
"rowList": [
{"id":"x","a":1,"displayStatus":0},
{"id":"y","a":2,"displayStatus":1}
]
})"));
ASSERT_EQ(t.columns.size(), 2u);
EXPECT_EQ(t.columns.back().kind, geopro::core::TableColumnKind::Toggle);
ASSERT_EQ(t.rows.size(), 2u);
EXPECT_EQ(t.rows[0].back().toStdString(), "1"); // displayStatus 0 → ON
EXPECT_EQ(t.rows[1].back().toStdString(), "0"); // displayStatus 1 → OFF
}
TEST(MeasurementDto, MeasurementListIsToggleInteractiveWithRowIds) {
// M2measurement 列表filedList 驱动)→ toggleInteractive=true + 每行点 id。
auto t = parseMeasurementTable(obj(R"({
"filedList": [{"fieldCode":"a","name":"A"}],
"rowList": [
{"id":"x","a":1,"displayStatus":0},
{"id":1453611521843201,"a":2,"displayStatus":1}
]
})"));
EXPECT_TRUE(t.toggleInteractive);
ASSERT_EQ(t.rowIds.size(), 2u);
EXPECT_EQ(t.rowIds[0].toStdString(), "x");
EXPECT_EQ(t.rowIds[1].toStdString(), "1453611521843201"); // 数字 id 转字符串
}
TEST(MeasurementDto, GridListNotToggleInteractive) {
// grid/trajectory 列表gridHeaderDisplay 驱动)→ 无 Toggle 列、不可交互、无 rowIds。
auto t = parseMeasurementTable(obj(R"({
"gridHeaderDisplay": [{"fieldCode":"a","name":"A"}],
"rowList": [ {"a":1}, {"a":2} ]
})"));
EXPECT_FALSE(t.toggleInteractive);
EXPECT_TRUE(t.rowIds.empty());
for (const auto& c : t.columns)
EXPECT_NE(c.kind, geopro::core::TableColumnKind::Toggle);
}