From a7d624cdcc76f6db0aa6eccf35e63472ca97a0d6 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Sun, 7 Jun 2026 20:21:24 +0800 Subject: [PATCH] =?UTF-8?q?plan:=20M1=20Phase=202=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=B1=82(Repository+=E8=A7=A3=E6=9E=90=E5=99=A8+=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E6=A0=91=E8=81=94=E5=8A=A8)=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plans/2026-06-07-m1-phase2-data.md | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-07-m1-phase2-data.md diff --git a/docs/superpowers/plans/2026-06-07-m1-phase2-data.md b/docs/superpowers/plans/2026-06-07-m1-phase2-data.md new file mode 100644 index 0000000..35bfd39 --- /dev/null +++ b/docs/superpowers/plans/2026-06-07-m1-phase2-data.md @@ -0,0 +1,203 @@ +# 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 1:core 数据模型补全(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`: +```cpp +#include +#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.hpp` 的 `Grid`,新增成员(在 `std::vector x, y;` 旁): +```cpp + std::vector z; // [nx*ny] 褶皱面抬升(可空=平面) + std::vector elevation; // [nx] 电极/地表高程 + std::vector lat, lon; // [nx] 经纬度 + double vmin = 0.0, vmax = 0.0; +``` +`ScatterField` 增 `std::vector projX, projY;`(GIS 平面坐标)。 +新建 `src/core/model/Anomaly.hpp`: +```cpp +#pragma once +#include +#include +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 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 2:JSON 解析器(纯函数,无 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.cpp`、`tests/CMakeLists.txt` + +> 解析器输入 `const std::string& jsonText`(纯,便于测试;文件读取在 Task 3 的 Repository 用 QFile 做)。 + +- [ ] **Step 1:** 建 `src/data/CMakeLists.txt`: +```cmake +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.txt` 加 `add_subdirectory(data)`(在 core 之后、app 之前)。 + +- [ ] **Step 2:** 失败测试 `tests/data/test_parsers.cpp`(读 ASCII 拷贝 `D:/dev/spike_data/grid.json`、`colorbar.json`;断言对齐已知事实): +```cpp +#include +#include +#include +#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< +#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 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/y`、`legend.polylineColor/polylineWidth/polylineShape=="dash"`)。完整实现这些字段。 + +- [ ] **Step 5:** 编译+ctest `-R Parsers` → 2+ PASS。 +- [ ] **Step 6:** 提交 `feat(data): 样本 JSON 解析器(grid/scatter/colorscale/anomaly)`。 + +--- + +## Task 3:Repository 接口 + LocalSampleRepository + +**Files:** `src/data/repo/IDatasetRepository.hpp`、`src/data/repo/IProjectRepository.hpp`、`src/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 loadStructure()`,`Grid loadGrid(const std::string& dsId)`,`ColorScale loadColorScale(dsId)`,`std::vector loadAnomalies(dsId)` 等;`GsNode`/`TmNode`/`DsNode` 轻量树结构放 `repo` 头)。 + - `LocalSampleRepository`:构造传样本目录;`loadStructure` 合成树;`loadGrid` 用 `QFile` 读 `剖面网格数据1.txt`→`QString`→`std::string`(UTF-8)→`parseGrid`。 + - `src/data/CMakeLists.txt` 加 `find_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.cpp` 用 `LocalSampleRepository` 取结构填 `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 锁文件)。