#include #include #include #include #include "dto/MeasurementDto.hpp" using namespace geopro::data::dto; static QJsonObject obj(const char* json) { return QJsonDocument::fromJson(json).object(); } // scatter/graph 的 data:electrodeList + scatterGraphData.rows(17 列定位数组,取自真实夹具前 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:与反演同构混合格式 colorBar(hex + rgba alpha 0–1)。 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 的 data:filedList(列定义)+ 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, // 使中段视电阻率(≈25–250)归一化到色阶中部(深品红/紫),与原版 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.electrodeNo(1-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, ParsesTableColumnsAndVmapFlattened) { auto t = parseMeasurementTable(obj(kRowsData)); // 列来自 filedList(5 列)+ 末尾追加“隐藏/显示”开关列 = 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=Toggle,code=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 }