geopro/docs/superpowers/plans/2026-06-07-m1-phase2-data.md

11 KiB
Raw Permalink Blame History

M1 Phase 2数据层Repository + 解析器)实现计划

For agentic workers: REQUIRED SUB-SKILL: superpowers:subagent-driven-development。Steps 用 - [ ]

Goal: 把真实样本文件解析成 core 领域模型,经 Repository 抽象喂给工作台;最终实现「左侧对象树 → 选中数据集 → 中央 QVTK 渲染该数据集」的真实联动(在本地样本上)。

Architecture: data/ 层。纯解析器(无 Qt,nlohmann-json,gtest 可测)+ LocalSampleRepository(用 QFile 读真实中文路径文件 → 调解析器,并合成一个小项目结构树)。app 的左/右占位面板替换为真实对象树/数据集列表,选中驱动渲染。

Tech Stack: C++17 / nlohmann-json / Qt(QFile,data 层可依赖 Qt;core 仍不依赖)/ gtest。

前置: Phase 1 core 完成(LocalFrame/模型/ColorScale/IDW/CrsTransform,12/12 绿)。构建经 external/dev.bat,统一 Release。样本 ASCII 拷贝在 D:/dev/spike_data/(grid.json/colorbar.json);真实中文文件在 docs/剖面网格数据的色阶数据2等文件/

已知样本结构(spec §6.1,已核实):

  • 剖面原数据N.txt:data:{min,max,projectXList,projectYList,vlist,xlist,ylist,hlist}(2597 点)
  • 剖面网格数据N.txt:data:{x[100],y[22],v[22][100],z[22][100],elevation[100],lat[100],lon[100],vmin,vmax,sectionType}
  • 剖面网格数据的色阶数据N.txt:data:{properties:{colorBar:[[值,rgba]],lineConfig,labelConfig,lvlMinMax}}(网格色阶 rgba alpha=0-255)
  • 异常.txt:data:[{exceptionName,exceptionMarkType(1点/2线/3面),legend{...},location:{coordinate:[{x,y}]},...}](3 条,markType=2)

范围说明: Phase 2 用同步接口(本地小数据,简单可测);async/分页/取消留到 ApiRepository(M1.5,spec §6 已记)。eastCoord/northCoord 字段名值颠倒,一律按 projectX/projectY 取(spec §5)。


Task 1core 数据模型补全(Anomaly + 扩展 Grid/ScatterField)

Files: src/core/model/Field.hpp(扩展)、src/core/model/Anomaly.hpp(新建)、tests/core/test_model_data.cpp(新建)、tests/CMakeLists.txt

  • Step 1: 失败测试 tests/core/test_model_data.cpp:
#include <gtest/gtest.h>
#include "model/Field.hpp"
#include "model/Anomaly.hpp"
using namespace geopro::core;

TEST(DataModel, GridHasMetaFields) {
    Grid g(2, 3);
    g.z.assign(6, 0.0); g.elevation = {1,2}; g.lat = {22.5,22.5}; g.lon = {114.1,114.1};
    g.vmin = -10; g.vmax = 800;
    EXPECT_EQ(g.z.size(), 6u);
    EXPECT_DOUBLE_EQ(g.vmax, 800);
}

TEST(DataModel, AnomalyHolds) {
    Anomaly a;
    a.name = "ERT1-54"; a.markType = AnomalyMarkType::Polyline;
    a.localPts = {{1.0, 2.0}, {3.0, 4.0}};
    EXPECT_EQ(a.markType, AnomalyMarkType::Polyline);
    EXPECT_EQ(a.localPts.size(), 2u);
}

tests/CMakeLists.txt(spike 之前)加 target_sources(geopro_tests PRIVATE core/test_model_data.cpp)

  • Step 2: 配置+编译,确认失败。

  • Step 3: 扩展 Field.hppGrid,新增成员(在 std::vector<double> x, y; 旁):

    std::vector<double> z;          // [nx*ny] 褶皱面抬升(可空=平面)
    std::vector<double> elevation;  // [nx] 电极/地表高程
    std::vector<double> lat, lon;   // [nx] 经纬度
    double vmin = 0.0, vmax = 0.0;

ScatterFieldstd::vector<double> projX, projY;(GIS 平面坐标)。 新建 src/core/model/Anomaly.hpp:

#pragma once
#include <string>
#include <vector>
namespace geopro::core {

enum class AnomalyMarkType { Point = 1, Polyline = 2, Polygon = 3 };

struct Vec2 { double x, y; };

struct Anomaly {
    std::string name;
    std::string typeName;                 // exceptionTypeName
    AnomalyMarkType markType = AnomalyMarkType::Polyline;
    std::vector<Vec2> localPts;           // location.coordinate局部坐标
    // 样式legend后续渲染用先留最常用字段
    std::string lineColor = "#000000";
    double lineWidth = 1.0;
    bool dashed = true;
};

}  // namespace geopro::core

(均 header-only,无需改 core CMakeLists。)

  • Step 4: 编译+ctest -R "DataModel" → 2 PASS。
  • Step 5: 提交 feat(core): 补全数据模型(Anomaly + Grid/ScatterField 元字段)

Task 2JSON 解析器(纯函数,无 Qt,真实样本 TDD)

Files: src/core/io/SampleParsers.hpp/.cpp(放 core?否——解析依赖 nlohmann,core 目前不依赖。放 data 层:src/data/parse/SampleParsers.{hpp,cpp})、src/data/CMakeLists.txt(新建)、src/CMakeLists.txt(加 add_subdirectory(data))、tests/data/test_parsers.cpptests/CMakeLists.txt

解析器输入 const std::string& jsonText(纯,便于测试;文件读取在 Task 3 的 Repository 用 QFile 做)。

  • Step 1:src/data/CMakeLists.txt:
find_package(nlohmann_json CONFIG REQUIRED)
add_library(geopro_data STATIC parse/SampleParsers.cpp)
target_include_directories(geopro_data PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(geopro_data PUBLIC geopro_core PRIVATE nlohmann_json::nlohmann_json)
target_compile_features(geopro_data PUBLIC cxx_std_17)
set_target_properties(geopro_data PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)

src/CMakeLists.txtadd_subdirectory(data)(在 core 之后、app 之前)。

  • Step 2: 失败测试 tests/data/test_parsers.cpp(读 ASCII 拷贝 D:/dev/spike_data/grid.jsoncolorbar.json;断言对齐已知事实):
#include <gtest/gtest.h>
#include <fstream>
#include <sstream>
#include "parse/SampleParsers.hpp"
using namespace geopro::data;
using namespace geopro::core;

static std::string slurp(const char* p){std::ifstream f(p);std::stringstream s;s<<f.rdbuf();return s.str();}

TEST(Parsers, Grid) {
    Grid g = parseGrid(slurp("D:/dev/spike_data/grid.json"));
    EXPECT_EQ(g.nx(), 100); EXPECT_EQ(g.ny(), 22);
    EXPECT_EQ(g.values().size(), 100u*22u);
    EXPECT_NEAR(g.vmin, -57.09, 0.1); EXPECT_NEAR(g.vmax, 828.08, 0.1);
    EXPECT_EQ(g.lat.size(), 100u);
}

TEST(Parsers, ColorScale) {
    ColorScale cs = parseColorScale(slurp("D:/dev/spike_data/colorbar.json"));
    EXPECT_FALSE(cs.empty());
    // 17 段;首段值 -57.09 → rgba(0,0,170,255)
    auto c = cs.colorAt(-57.0);
    EXPECT_EQ(c.b, 170);
}

tests/CMakeLists.txt(spike 之前):

target_sources(geopro_tests PRIVATE data/test_parsers.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_data)
  • Step 3: 配置+编译,确认失败。

  • Step 4: 实现 src/data/parse/SampleParsers.hpp:

#pragma once
#include <string>
#include "model/Field.hpp"
#include "model/ColorScale.hpp"
#include "model/Anomaly.hpp"
namespace geopro::data {

geopro::core::Grid         parseGrid(const std::string& jsonText);
geopro::core::ScatterField parseScatter(const std::string& jsonText);
geopro::core::ColorScale   parseColorScale(const std::string& jsonText);
std::vector<geopro::core::Anomaly> parseAnomalies(const std::string& jsonText);

}  // namespace geopro::data

SampleParsers.cpp(用 nlohmann::json::parse;data 子对象;Grid 的 v 是 [j][i]→填 valueAt(i,j);ColorScale 用 core::parseColor(s, AlphaScale::Bit255) 逐段 addStop;异常按 exceptionMarkType 映射 enum、读 location.coordinate[].x/ylegend.polylineColor/polylineWidth/polylineShape=="dash")。完整实现这些字段。

  • Step 5: 编译+ctest -R Parsers → 2+ PASS。
  • Step 6: 提交 feat(data): 样本 JSON 解析器(grid/scatter/colorscale/anomaly)

Task 3Repository 接口 + LocalSampleRepository

Files: src/data/repo/IDatasetRepository.hppsrc/data/repo/IProjectRepository.hppsrc/data/repo/LocalSampleRepository.{hpp,cpp}src/data/CMakeLists.txt(链 Qt6::Core)、tests/data/test_local_repo.cpp

LocalSampleRepository 用 QFile(正确读中文路径)读 docs/剖面网格数据的色阶数据2等文件/ 真实文件 → 调解析器。并合成一个小项目结构:Project「香港 Volia」→ GS「测区」→ TM「ERT1」→ DS「剖面网格数据1」,供对象树展示。

  • Step 1: 失败测试 tests/data/test_local_repo.cpp(读真实中文路径文件,验证拿到 Grid 100×22 + 合成树非空)。完整断言:loadStructure() 返回 ≥1 个 GS、其下 ≥1 个 TM;loadGrid(dsId) 返回 100×22。
  • Step 2: 配置+编译失败。
  • Step 3: 实现:
    • IProjectRepository/IDatasetRepository 接口(同步:Project loadProject(),std::vector<GsNode> loadStructure(),Grid loadGrid(const std::string& dsId),ColorScale loadColorScale(dsId),std::vector<Anomaly> loadAnomalies(dsId) 等;GsNode/TmNode/DsNode 轻量树结构放 repo 头)。
    • LocalSampleRepository:构造传样本目录;loadStructure 合成树;loadGridQFile剖面网格数据1.txtQStringstd::string(UTF-8)→parseGrid
    • src/data/CMakeLists.txtfind_package(Qt6 COMPONENTS Core) + 链 Qt6::Core(data 层可用 Qt;但仍 AUTOMOC OFF,接口无 Q_OBJECT)。
  • Step 4: 编译+ctest -R "LocalRepo" → PASS。
  • Step 5: 提交 feat(data): Repository 接口 + LocalSampleRepository(QFile 读中文路径)

Task 4接入工作台 — 对象树 → 选中数据集 → 渲染(可见里程碑)

Files: src/app/main.cpp(或拆出 src/view/panels/ObjectTreePanel.* + 简单 controller)、src/app/CMakeLists.txt(链 geopro_data)

目标:左面板显示真实对象树(QTreeWidget,来自 LocalSampleRepository::loadStructure);双击/选中数据集 → 用 Repository 取 Grid+ColorScale → 复用 demo 的 banded contour 渲染到中央 QVTK。右面板显示该数据集属性(名称/通道/vmin-vmax)。

  • Step 1: app 链 geopro_data;main.cppLocalSampleRepository 取结构填 QTreeWidget(左 dock)。
  • Step 2: 把 demo 的 buildGridSection 重构成 renderGrid(renderer, Grid, ColorScale)(入参为 core 模型,不再直接读文件);选中数据集时调用之并 renderWindow->Render()
  • Step 3: 选中 → 右 dock 显示属性(QLabel/QFormLayout:数据集名、网格 nx×ny、vmin/vmax)。
  • Step 4: 构建 + 部署(windeployqt)+ 启动;人工验证:左树点数据集 → 中央出现 ERT 剖面、右侧出属性。(交互验证由用户确认)
  • Step 5: 提交 feat(app): 对象树→选中数据集→渲染剖面 联动(本地样本)

Self-Review 备注

  • 覆盖 spec §6(Repository/解析,同步版,async 留 M1.5)、§6.1(解析约定)、§9(对象树→渲染联动雏形)。
  • 类型一致:解析器产出 core::Grid/ScatterField/ColorScale/Anomaly;Repository 同。
  • 铁律:core 仍无 Qt/VTK;data 可用 Qt(QFile)但 AUTOMOC OFF、接口无 VTK。
  • 构建经 external/dev.bat,Release;改 CMake 后先重配置。app 启动前先杀残留 geopro_desktop.exe(否则 LNK1104 锁文件)。