feat(vtk): 类目描述符目录 categoryCatalog(classify谓词+扩展契约)取代 categoryConfigs

splitByCategory 改遍历 catalog 用 classify 路由(首个 classify==true 入段);新增 5 段含 trajectory。旧 CategoryConfig/categoryConfigs 暂保留供 CategoryAnalysisTab/CategorySection。同步修正 test_dataset_category 旧断言(段数随 catalog=5)。
This commit is contained in:
gaozheng 2026-06-30 21:05:15 +08:00
parent 7d9f34d3ec
commit 8f7da3657a
7 changed files with 124 additions and 13 deletions

View File

@ -1,21 +1,15 @@
#include "DatasetCategory.hpp"
#include "repo/CategoryDescriptor.hpp"
namespace geopro::app {
CategoryBuckets splitByCategory(const std::vector<geopro::data::DsRow>& rows) {
const auto& cfg = categoryConfigs();
const auto& cat = geopro::data::categoryCatalog();
CategoryBuckets b;
b.segments.resize(cfg.size());
for (const auto& r : rows) {
int hit = -1;
// 先按 ddCode三维体/切片)——它们无 dsTypeCode来自 Api3dRepository mock 行)。
for (std::size_t i = 0; i < cfg.size() && hit < 0; ++i)
if (!cfg[i].ddCode.empty() && r.ddCode == cfg[i].ddCode) hit = static_cast<int>(i);
// 再按 dsTypeCode。
for (std::size_t i = 0; i < cfg.size() && hit < 0; ++i)
if (!cfg[i].dsTypeCode.empty() && r.dsTypeCode == cfg[i].dsTypeCode) hit = static_cast<int>(i);
if (hit >= 0) b.segments[static_cast<std::size_t>(hit)].push_back(r);
}
b.segments.resize(cat.size());
for (const auto& r : rows)
for (std::size_t i = 0; i < cat.size(); ++i)
if (cat[i].classify && cat[i].classify(r)) { b.segments[i].push_back(r); break; }
return b;
}

View File

@ -5,6 +5,7 @@ add_library(geopro_data STATIC
repo/LocalSampleRepository.cpp
repo/LocalSample3dRepository.cpp
repo/DatasetFieldDictionary.cpp
repo/CategoryDescriptor.cpp
dto/NavDto.cpp
dto/Vtk3dRequests.cpp
dto/DatasetChartDto.cpp

View File

@ -0,0 +1,47 @@
#include "repo/CategoryDescriptor.hpp"
#include <vector>
namespace geopro::data {
std::function<bool(const DsRow&)> byDdCode(std::initializer_list<std::string> codes) {
std::vector<std::string> cs(codes);
return [cs](const DsRow& r) {
for (const auto& c : cs) if (r.ddCode == c) return true;
return false;
};
}
std::function<bool(const DsRow&)> byDsTypeCode(std::initializer_list<std::string> codes) {
std::vector<std::string> cs(codes);
return [cs](const DsRow& r) {
for (const auto& c : cs) if (r.dsTypeCode == c) return true;
return false;
};
}
const std::vector<CategoryDescriptor>& categoryCatalog() {
static const std::vector<CategoryDescriptor> kCat = {
{"resistivity", "电阻率数据", SceneKind::Curtain3D,
byDsTypeCode({"ERT platform inversion data"}),
{FilterKind::DateRange, FilterKind::ArrayType},
{OpKind::GenerateVolume, OpKind::Filter}, "curtain"},
{"apparent", "视电阻率数据", SceneKind::Curtain3D,
byDsTypeCode({"visual resistivity data"}),
{FilterKind::DateRange, FilterKind::ArrayType},
{OpKind::GenerateVolume, OpKind::Filter}, "curtain"},
{"transient", "瞬变电磁数据", SceneKind::Curtain3D,
byDsTypeCode({"DD TRANSIENT ELECTROMAGNETIC INVERSION"}),
{FilterKind::DateRange},
{OpKind::GenerateVolume, OpKind::Filter}, "curtain"},
{"voxel", "三维体", SceneKind::Volume3D,
byDdCode({"dd_voxel"}),
{FilterKind::DateRange},
{OpKind::Filter}, "volume"},
{"trajectory", "轨迹数据", SceneKind::Plane2D,
byDdCode({"dd_trajectory_data"}),
{FilterKind::DateRange},
{OpKind::PlaneZ, OpKind::Filter, OpKind::Basemap}, "plane2d"},
};
return kCat;
}
} // namespace geopro::data

View File

@ -0,0 +1,30 @@
#pragma once
#include <functional>
#include <initializer_list>
#include <string>
#include <vector>
#include "repo/RepoTypes.hpp" // DsRow
namespace geopro::data {
enum class SceneKind { Volume3D, Curtain3D, Plane2D }; // 渲染语义/共存规则
enum class FilterKind { DateRange, ArrayType }; // 筛选器契约(可扩展)
enum class OpKind { GenerateVolume, Filter, PlaneZ, Basemap }; // 段操作契约(可扩展)
struct CategoryDescriptor {
std::string id;
std::string title;
SceneKind sceneKind;
std::function<bool(const DsRow&)> classify; // 轴1 数据来源/分类
std::vector<FilterKind> filters; // 轴2 筛选器
std::vector<OpKind> operations; // 轴3 段头图标操作
std::string renderStrategyId; // 轴4 渲染策略键
};
// classify 便捷构造器(常见按 ddCode / dsTypeCode 接入)
std::function<bool(const DsRow&)> byDdCode(std::initializer_list<std::string> codes);
std::function<bool(const DsRow&)> byDsTypeCode(std::initializer_list<std::string> codes);
const std::vector<CategoryDescriptor>& categoryCatalog();
} // namespace geopro::data

View File

@ -59,6 +59,8 @@ target_sources(geopro_tests PRIVATE data/test_nav_request.cpp)
# GprVolumeRepository线 GPR int16 量化体(BuiltI16) app float (VolumeGrid)
# + 全链(合成多通道 .iprb 走真 P1/P2) VolumeGrid
target_sources(geopro_tests PRIVATE data/test_gpr_volume_repository.cpp)
# CategoryDescriptor categoryCatalog(classify谓词+扩展契约) + splitByCategory
target_sources(geopro_tests PRIVATE data/test_category_descriptor.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_data)
# store ChunkedVolumeStoreGPR round-trip + +

View File

@ -1,5 +1,6 @@
#include <gtest/gtest.h>
#include "DatasetCategory.hpp"
#include "repo/CategoryDescriptor.hpp"
using geopro::data::DsRow;
using namespace geopro::app;
@ -23,7 +24,8 @@ TEST(SplitByCategory, RoutesByDsTypeCodeAndDdCode) {
row("x", "dd_ert_measurement_gr_data", "earth resistance"), // 接地电阻 → 丢弃
};
const CategoryBuckets b = splitByCategory(rows);
ASSERT_EQ(b.segments.size(), categoryConfigs().size());
// splitByCategory 现走 categoryCatalog()5 段,含 trajectory旧 categoryConfigs 暂保留供 UI。
ASSERT_EQ(b.segments.size(), geopro::data::categoryCatalog().size());
EXPECT_EQ(b.segments[0].size(), 1u); EXPECT_EQ(b.segments[0][0].id, "a");
EXPECT_EQ(b.segments[1].size(), 1u); EXPECT_EQ(b.segments[1][0].id, "b");
EXPECT_EQ(b.segments[2].size(), 1u); EXPECT_EQ(b.segments[2][0].id, "c");

View File

@ -0,0 +1,35 @@
#include <gtest/gtest.h>
#include "repo/CategoryDescriptor.hpp"
#include "DatasetCategory.hpp"
using namespace geopro::data;
TEST(CategoryCatalog, HasFiveSegmentsInOrder) {
const auto& cat = categoryCatalog();
ASSERT_EQ(cat.size(), 5u);
EXPECT_EQ(cat[0].id, "resistivity");
EXPECT_EQ(cat[3].id, "voxel");
EXPECT_EQ(cat[4].id, "trajectory");
EXPECT_EQ(cat[4].sceneKind, SceneKind::Plane2D);
EXPECT_EQ(cat[4].renderStrategyId, "plane2d");
}
TEST(CategoryCatalog, TrajectoryClassifiesByDdCode) {
const auto& cat = categoryCatalog();
DsRow traj; traj.id = "t1"; traj.ddCode = "dd_trajectory_data";
EXPECT_TRUE(cat[4].classify(traj));
DsRow vox; vox.ddCode = "dd_voxel";
EXPECT_FALSE(cat[4].classify(vox));
}
TEST(SplitByCategory, RoutesRowToFirstMatchingDescriptor) {
DsRow traj; traj.id = "t1"; traj.ddCode = "dd_trajectory_data";
DsRow ert; ert.id = "e1"; ert.dsTypeCode = "ERT platform inversion data";
auto b = geopro::app::splitByCategory({traj, ert});
const auto& cat = categoryCatalog();
ASSERT_EQ(b.segments.size(), cat.size());
EXPECT_EQ(b.segments[0].size(), 1u); // resistivity ← ert
EXPECT_EQ(b.segments[0][0].id, "e1");
EXPECT_EQ(b.segments[4].size(), 1u); // trajectory ← traj
EXPECT_EQ(b.segments[4][0].id, "t1");
}