feat(render): 异常叠加(AnomalyActor) + 数据详情命名对齐原型

- AnomalyActor(buildAnomalies): 按 markType 点(vtkVertex)/线(开放polyline,dashed)
  /面(闭合polyline轮廓), 每异常一 actor 带自身 lineColor/width/dashed; 坐标(x,-y,0)
  与 #18 同空间。离屏 verify_section_anomaly.png 折线位置吻合 Python 真值 ref_18。
- 接入 app 数据详情: 「显示异常」开关(默认开)叠加在 #18/#17 上(同纵向夸张对齐)。
- 按原型(prototype.geomative.cn)重命名数据详情切换为「原数据/网格数据」并调顺序。
- 新增 test_anomaly 4 例(线/面闭合/点/空跳过/颜色/y取负); 全 35 测试绿。
- STATUS §6.10 记录原型权威布局与待对齐项(左下数据列表/右上异常列表/电极/底图)。
- 注: dashed 点画在 VTK OpenGL2 下偏弱(几何/颜色/位置正确), 纯观感项后续调。
This commit is contained in:
gaozheng 2026-06-08 07:56:25 +08:00
parent f51fe44533
commit d4b4a4bc64
9 changed files with 279 additions and 18 deletions

View File

@ -17,9 +17,9 @@
- **中央 二维地图 / 三维视图**(两个**真内容**,非相机切换): - **中央 二维地图 / 三维视图**(两个**真内容**,非相机切换):
- 二维地图 = `MapLineActor`:测线 `lat/lon` 轨迹**红线**俯视(浅底),像地图。 - 二维地图 = `MapLineActor`:测线 `lat/lon` 轨迹**红线**俯视(浅底),像地图。
- 三维视图 = `CurtainActor`:沿测线的**竖直断面墙**(分段色带,z 纵向夸张×3,沿弯曲测线弯)。 - 三维视图 = `CurtainActor`:沿测线的**竖直断面墙**(分段色带,z 纵向夸张×3,沿弯曲测线弯)。
- **下方 数据详情**:工具条「反演剖面/原数据」切换。单击数据集 → 反演剖面=`GridContourActor` 平面剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5);原数据=`ScatterActor` 彩色方块散点(#17,x=距离/y=深度取负,用散点自带色阶)。 - **下方 数据详情**:工具条「原数据/网格数据」切换 +「显示异常」开关(对齐原型命名)。单击数据集 → 网格数据=`GridContourActor` 平面剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5);原数据=`ScatterActor` 彩色方块散点(#17,x=距离/y=深度取负,用散点自带色阶);显示异常=`AnomalyActor` 在上图叠加异常 dashed 折线(同纵向夸张对齐)。
- **右 属性**:名称/网格 nx×ny/vmin·vmax。 - **右 属性**:名称/网格 nx×ny/vmin·vmax。
- 单元测试累计 **31 个全绿**(core/data/net/render;含 Scatter 2 个、修复了陈旧的 Curtain mapper 类型断言);离屏 `verify_section/map/curtain_3d/scatter.png` 均核对正确(scatter 与 Python 真值 ref_17 吻合)。 - 单元测试累计 **35 个全绿**(core/data/net/render;含 Scatter 2 + Anomaly 4、修复了陈旧的 Curtain mapper 类型断言);离屏 `verify_section/map/curtain_3d/scatter/section_anomaly.png` 均核对正确(scatter 吻合 ref_17、异常折线位置吻合 ref_18)。
## 2. 各 Phase 完成度 ## 2. 各 Phase 完成度
@ -29,7 +29,7 @@
| P1 | core(LocalFrame/模型/ColorScale/IDW/CrsTransform/GeoLocalFrame) | ✅ | | P1 | core(LocalFrame/模型/ColorScale/IDW/CrsTransform/GeoLocalFrame) | ✅ |
| P2 | data(解析器/LocalSampleRepository)+ 对象树 | ✅ | | P2 | data(解析器/LocalSampleRepository)+ 对象树 | ✅ |
| P3 | 登录(RsaEncryptor/ApiClient/AuthService/LoginWindow) | ✅(**Credential 记住免登录未做**) | | P3 | 登录(RsaEncryptor/ApiClient/AuthService/LoginWindow) | ✅(**Credential 记住免登录未做**) |
| P4 | 渲染:render 层 + 二维地图(线)+ 三维视图(帘面)+ 数据详情(#18/#17) | 🔶 **核心三视图 + 散点#17 已对**;**异常叠加 / DEM地形 / dd_voxel回归 / 底图瓦片 未做** | | P4 | 渲染:render 层 + 二维地图(线)+ 三维视图(帘面)+ 数据详情(#18/#17/异常) | 🔶 **核心三视图 + 散点#17 + 异常叠加 已对**;**DEM地形 / dd_voxel回归 / 底图瓦片 未做;布局对齐原型(左下数据列表/右上异常列表/电极/底图)未做** |
## 3. 构建约定(**机器本地** ## 3. 构建约定(**机器本地**
@ -61,7 +61,7 @@
## 6. 已知问题 / 待办P4 下次会话) ## 6. 已知问题 / 待办P4 下次会话)
1. ~~**散点 #17**:`ScatterActor`(剖面原数据 2597 点彩色散点),数据详情"原数据"视图~~**已完成**(2026-06-08,离屏 PNG 核对吻合 Python 真值,接入数据详情「反演剖面/原数据」切换;app 待人工登录肉眼复核交互)。 1. ~~**散点 #17**:`ScatterActor`(剖面原数据 2597 点彩色散点),数据详情"原数据"视图~~**已完成**(2026-06-08,离屏 PNG 核对吻合 Python 真值,接入数据详情「反演剖面/原数据」切换;app 待人工登录肉眼复核交互)。
2. **异常叠加**:`AnomalyActor`(markType 点/线/面),叠加在剖面/帘面上(异常数据已能解析)。**下一项** 2. ~~**异常叠加**:`AnomalyActor`(markType 点/线/面)~~**已完成**(2026-06-08,叠加在数据详情 #18/#17 上,「显示异常」开关默认开;离屏 `verify_section_anomaly.png` 折线位置吻合 ref_18;样本 3 异常均 markType=2 dashed;app 待人工登录复核)。**注**:dashed 点画在 VTK OpenGL2 下偏弱(几乎实线),几何/颜色/位置正确,纯观感项可后续调
3. **DEM/影像地形**:加 vcpkg `gdal`;GDAL 读 dem.tif/image.tif;**影像 EPSG:3857 必须 PROJ 重投影到世界系**;`vtkWarpScalar` 地形面 + 纹理。 3. **DEM/影像地形**:加 vcpkg `gdal`;GDAL 读 dem.tif/image.tif;**影像 EPSG:3857 必须 PROJ 重投影到世界系**;`vtkWarpScalar` 地形面 + 纹理。
4. **dd_voxel 回归**:需先确认项目 CRS,使散点 projX/Y 能转到 lat/lon 世界系,与帘面配准;VoxelActor 已就绪。 4. **dd_voxel 回归**:需先确认项目 CRS,使散点 projX/Y 能转到 lat/lon 世界系,与帘面配准;VoxelActor 已就绪。
5. **底图瓦片**(二维地图,天地图/Mapbox):M1.5。 5. **底图瓦片**(二维地图,天地图/Mapbox):M1.5。
@ -69,6 +69,12 @@
7. 多测线:当前样本仅 1 条 dd_section(grid1);多条共存机制已就绪,加数据即叠加。 7. 多测线:当前样本仅 1 条 dd_section(grid1);多条共存机制已就绪,加数据即叠加。
8. 取景微调(数据详情/帘面的相机余量);纵向夸张倍数(剖面1.5/帘面3)可做成可调。 8. 取景微调(数据详情/帘面的相机余量);纵向夸张倍数(剖面1.5/帘面3)可做成可调。
9. render 仍部分内联在 main.cpp;可逐步抽到 view/controller。 9. render 仍部分内联在 main.cpp;可逐步抽到 view/controller。
10. **布局对齐原型**(权威参考 `http://prototype.geomative.cn/`,用户指定;截图存 `.playwright-mcp/`)。原型为**四区六面板**,当前 app 为简化版,缺:
- **左下「数据真实显示栏」**:选中 TM(测线)后列其采集批次(=DS 数据集),tab「数据/文件」。当前 app 把 DS 直接塞树里。
- **右上「异常列表」**:异常条目(名称/深度/尺寸/电阻率 + 颜色标记 + 眼睛显隐),与数据详情「显示异常」联动。
- **数据详情工具条**:原型还有 色阶配置/滤波处理/显示等值线标签/纵化容差滑块/显示电极/网格 等(当前仅原数据·网格数据·显示异常)。
- **电极标记**:剖面顶部倒三角▼电极位 + 数值标签;**底图影像**(3D 卫星底图 + 测线落地)= DEM/底图任务(CRS 阻塞)。
诚实记录:已对齐者=中央二维/三维切换、数据详情原数据/网格数据/显示异常、对象树、属性。
## 7. 渲染验证手段(务必用) ## 7. 渲染验证手段(务必用)
@ -84,5 +90,5 @@ cmd /c "...\external\dev.bat cmake --build build/release --target render_verify"
- 计划:`plans/` 下 phase0-4 + `2026-06-07-m1-view-redesign.md`(正确视图模型)+ spike-report - 计划:`plans/` 下 phase0-4 + `2026-06-07-m1-view-redesign.md`(正确视图模型)+ spike-report
- 环境:`../ENV_SETUP_Windows.md` - 环境:`../ENV_SETUP_Windows.md`
- 验证脚本:`tools/validate_samples.py`(#17/#18 真值)、`tools/validate_voxel.py`(voxel)、`tests/spike/render_verify.cpp`(app 渲染积木离屏核对) - 验证脚本:`tools/validate_samples.py`(#17/#18 真值)、`tools/validate_voxel.py`(voxel)、`tests/spike/render_verify.cpp`(app 渲染积木离屏核对)
- 渲染积木(render 层):`MapLineActor`(测线线)、`CurtainActor`(断面墙)、`GridContourActor`(#18 平面)、`ScatterActor`(#17 散点)、`VoxelActor`(体,未接 UI)、`ColorLutBuilder`、`CameraPreset`、`Scene`;`core::GeoLocalFrame`(经纬→局部米)。 - 渲染积木(render 层):`MapLineActor`(测线线)、`CurtainActor`(断面墙)、`GridContourActor`(#18 平面)、`ScatterActor`(#17 散点)、`AnomalyActor`(异常 点/线/面)、`VoxelActor`(体,未接 UI)、`ColorLutBuilder`、`CameraPreset`、`Scene`;`core::GeoLocalFrame`(经纬→局部米)。
- 散点 #17 专属色阶经 `LocalSampleRepository::loadScatterColorScale`(剖面原数据自带色阶,范围/分段与网格色阶不同)。 - 散点 #17 专属色阶经 `LocalSampleRepository::loadScatterColorScale`(剖面原数据自带色阶,范围/分段与网格色阶不同)。

View File

@ -5,7 +5,7 @@
> **⚠️ 状态(2026-06-07,务必先读 `2026-06-07-m1-view-redesign.md` + STATUS.md):** > **⚠️ 状态(2026-06-07,务必先读 `2026-06-07-m1-view-redesign.md` + STATUS.md):**
> - **Task 1(render 层 + 相机预设)**:✅ 已完成。但中央"单场景平躺剖面 + 2D/3D 仅换相机"的做法**已被 view-redesign 取代** —— 现在中央是 **二维地图(测线线)/ 三维视图(竖直帘面)两种内容**(详见 view-redesign 的"实施结果")。 > - **Task 1(render 层 + 相机预设)**:✅ 已完成。但中央"单场景平躺剖面 + 2D/3D 仅换相机"的做法**已被 view-redesign 取代** —— 现在中央是 **二维地图(测线线)/ 三维视图(竖直帘面)两种内容**(详见 view-redesign 的"实施结果")。
> - **Task 2(dd_voxel 体绘制+切片)**:🔶 VoxelActor 代码完成,但**已从 UI 移除/搁置**(散点 projX/Y 真实 CRS 未确认,无法与 lat/lon 帘面配准)。**CRS 确认后再回归**。 > - **Task 2(dd_voxel 体绘制+切片)**:🔶 VoxelActor 代码完成,但**已从 UI 移除/搁置**(散点 projX/Y 真实 CRS 未确认,无法与 lat/lon 帘面配准)。**CRS 确认后再回归**。
> - **Task 3(散点#17 + 异常叠加)**:🔶 **散点#17 ✅ 已完成**(2026-06-08,`ScatterActor` 离屏 PNG 核对吻合 Python 真值 ref_17,接入数据详情「反演剖面/原数据」切换,+2 单测,全 31 测试绿;app 待人工登录复核)。**异常叠加 ⬜ 未做(下一项)** > - **Task 3(散点#17 + 异常叠加)**:**已完成**(2026-06-08)。散点#17=`ScatterActor`(吻合 ref_17);异常叠加=`AnomalyActor`(点/线/面,叠在数据详情#18/#17,「显示异常」开关,吻合 ref_18)。数据详情切换按原型命名「原数据/网格数据」。+6 单测,全 35 测试绿;app 待人工登录复核。布局对齐原型其余项见 STATUS §6.10
> - **Task 4(DEM/影像地形)**:⬜ 未做(P4 下次,需先确认 CRS + 加 gdal)。 > - **Task 4(DEM/影像地形)**:⬜ 未做(P4 下次,需先确认 CRS + 加 gdal)。
> 渲染必须用 `tests/spike/render_verify.cpp` 离屏 PNG 核对(本会话教训)。 > 渲染必须用 `tests/spike/render_verify.cpp` 离屏 PNG 核对(本会话教训)。
@ -50,7 +50,7 @@
**Files:** `src/render/actors/ScatterActor.{hpp,cpp}`、`src/render/actors/AnomalyActor.{hpp,cpp}`、`main.cpp` **Files:** `src/render/actors/ScatterActor.{hpp,cpp}`、`src/render/actors/AnomalyActor.{hpp,cpp}`、`main.cpp`
- [x] `render::buildScatter(const core::ScatterField&, const core::ColorScale&, float pointSize)`→`vtkActor`:`vtkPolyData`(verts,点 x/-y/0,与 #18 同坐标系)+ 点标量 v + LUT(色阶范围优先 colorBar 真实分段值);方块点 SetPointSize。色阶用散点自带文件(`loadScatterColorScale`)。离屏 `verify_scatter.png` 核对吻合 ref_17。 - [x] `render::buildScatter(const core::ScatterField&, const core::ColorScale&, float pointSize)`→`vtkActor`:`vtkPolyData`(verts,点 x/-y/0,与 #18 同坐标系)+ 点标量 v + LUT(色阶范围优先 colorBar 真实分段值);方块点 SetPointSize。色阶用散点自带文件(`loadScatterColorScale`)。离屏 `verify_scatter.png` 核对吻合 ref_17。
- [ ] `AnomalyActor::build(const std::vector<core::Anomaly>&)`→`vtkActor`(或多 actor):按 markType——点(`vtkGlyph3D`)/线(polydata,dashed,lineColor/width)/面(`vtkPolygon`填充+边);坐标用 localPts。叠加在剖面上 - [x] `render::buildAnomalies(const std::vector<core::Anomaly>&)`→`std::vector<vtkActor>`(每异常一 actor,各自 lineColor/width/dashed):按 markType——点(vtkVertex+点大小)/线(开放 polyline,dashed stipple)/面(闭合 polyline 轮廓);坐标 (x,-y,0) 与 #18 同空间。叠加在数据详情剖面上(同纵向夸张)。dashed 在 OpenGL2 偏弱(几何/色/位正确)
- [ ] main.cpp:数据集详情可切换"网格/散点"显示;异常按需叠加。**人工验证**:散点图 ~#17;异常虚线叠在剖面上。提交 `feat(render): 散点(#17) + 异常叠加` - [ ] main.cpp:数据集详情可切换"网格/散点"显示;异常按需叠加。**人工验证**:散点图 ~#17;异常虚线叠在剖面上。提交 `feat(render): 散点(#17) + 异常叠加`
--- ---

View File

@ -4,9 +4,11 @@
// 二维地图 = 对每个勾选数据集 buildSurveyLinelat/lon 红线俯视z=0+ applyTop2D浅底背景 // 二维地图 = 对每个勾选数据集 buildSurveyLinelat/lon 红线俯视z=0+ applyTop2D浅底背景
// 三维视图 = 对每个勾选数据集 buildCurtain竖直断面墙actor SetScale(1,1,3) 纵向夸张 + applyFree3D白底 // 三维视图 = 对每个勾选数据集 buildCurtain竖直断面墙actor SetScale(1,1,3) 纵向夸张 + applyFree3D白底
// 切视图 / 勾选变化 → 按当前勾选集重建对应内容。 // 切视图 / 勾选变化 → 按当前勾选集重建对应内容。
// - 下方「数据详情」:独立 QVTK 小视图 + 工具条「反演剖面 / 原数据」切换。单击某 DS → 显示该数据集: // - 下方「数据详情」:独立 QVTK 小视图 + 工具条「原数据 / 网格数据」切换 +「显示异常」开关(对齐原型)。
// 反演剖面 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。 // 单击某 DS → 显示该数据集:
// 网格数据 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。
// 原数据 = #17 彩色散点buildScatterx=距离/y=深度,按散点自带色阶上色)。 // 原数据 = #17 彩色散点buildScatterx=距离/y=深度,按散点自带色阶上色)。
// 显示异常 = 在上图叠加异常圈定buildAnomaliesdashed 折线,同纵向夸张对齐)。
// 两者皆平躺俯视正交 + 属性。 // 两者皆平躺俯视正交 + 属性。
// - 右 属性:选中数据集属性文本。 // - 右 属性:选中数据集属性文本。
// 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame全项目共享保证多视图配准 // 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame全项目共享保证多视图配准
@ -42,6 +44,7 @@
#include "CameraPreset.hpp" #include "CameraPreset.hpp"
#include "Scene.hpp" #include "Scene.hpp"
#include "actors/AnomalyActor.hpp"
#include "actors/CurtainActor.hpp" #include "actors/CurtainActor.hpp"
#include "actors/GridContourActor.hpp" #include "actors/GridContourActor.hpp"
#include "actors/MapLineActor.hpp" #include "actors/MapLineActor.hpp"
@ -111,7 +114,7 @@ double median(std::vector<double> v)
// 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。 // 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。
enum class ViewMode { Map2D, View3D }; enum class ViewMode { Map2D, View3D };
// 数据详情显示内容(默认反演剖面)。反演剖面=#18 banded原数据=#17 散点 // 数据详情显示内容(默认网格数据)。网格数据=#18 banded原数据=#17 散点(对齐原型命名)
enum class DetailMode { Section18, Scatter17 }; enum class DetailMode { Section18, Scatter17 };
// #17 散点屏幕像素方块边长。 // #17 散点屏幕像素方块边长。
@ -191,16 +194,21 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
detailLayout->setContentsMargins(0, 0, 0, 0); detailLayout->setContentsMargins(0, 0, 0, 0);
detailLayout->setSpacing(0); detailLayout->setSpacing(0);
// 工具条对齐原型:「原数据 | 网格数据」互斥 +「显示异常」开关。
auto* detailToolBar = new QToolBar(); auto* detailToolBar = new QToolBar();
auto* detailGroup = new QActionGroup(detailToolBar); auto* detailGroup = new QActionGroup(detailToolBar);
detailGroup->setExclusive(true); detailGroup->setExclusive(true);
auto* actSection = detailToolBar->addAction(QStringLiteral("反演剖面"));
auto* actScatter = detailToolBar->addAction(QStringLiteral("原数据")); auto* actScatter = detailToolBar->addAction(QStringLiteral("原数据"));
actSection->setCheckable(true); auto* actSection = detailToolBar->addAction(QStringLiteral("网格数据"));
actScatter->setCheckable(true); actScatter->setCheckable(true);
detailGroup->addAction(actSection); actSection->setCheckable(true);
detailGroup->addAction(actScatter); detailGroup->addAction(actScatter);
actSection->setChecked(true); // 默认反演剖面 (#18) detailGroup->addAction(actSection);
actSection->setChecked(true); // 默认网格数据 (#18)
detailToolBar->addSeparator();
auto* actShowAnomaly = detailToolBar->addAction(QStringLiteral("显示异常"));
actShowAnomaly->setCheckable(true);
actShowAnomaly->setChecked(true); // 默认显示异常(对齐原型 ☑显示异常)
detailLayout->addWidget(detailToolBar); detailLayout->addWidget(detailToolBar);
detailLayout->addWidget(detailWidget, 1); detailLayout->addWidget(detailWidget, 1);
@ -284,10 +292,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 当前选中数据集 id空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。 // 当前选中数据集 id空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。
auto currentDsId = std::make_shared<QString>(); auto currentDsId = std::make_shared<QString>();
auto detailMode = std::make_shared<DetailMode>(DetailMode::Section18); auto detailMode = std::make_shared<DetailMode>(DetailMode::Section18);
auto showAnomalies = std::make_shared<bool>(true); // 默认显示异常(对齐原型)
// 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。 // 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。
auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, currentDsId, // 勾选「显示异常」时在 #18/#17 上叠加异常 dashed 折线(同纵向夸张对齐)。
detailMode]() { auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, currentDsId, detailMode,
showAnomalies]() {
detailRendererPtr->RemoveAllViewProps(); detailRendererPtr->RemoveAllViewProps();
if (currentDsId->isEmpty()) { // 未选数据集:清空即可 if (currentDsId->isEmpty()) { // 未选数据集:清空即可
detailRenderWindowPtr->Render(); detailRenderWindowPtr->Render();
@ -295,7 +305,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
} }
const std::string id = currentDsId->toStdString(); const std::string id = currentDsId->toStdString();
if (*detailMode == DetailMode::Section18) { if (*detailMode == DetailMode::Section18) {
// 反演剖面#18 banded 等值面 + 等值线,两 actor 纵向夸张 1.5x(沿 y // 网格数据#18 banded 等值面 + 等值线,两 actor 纵向夸张 1.5x(沿 y
const auto g = repo.loadGrid(id); const auto g = repo.loadGrid(id);
const auto cs = repo.loadColorScale(id); const auto cs = repo.loadColorScale(id);
const auto actors = geopro::render::buildGridContour(g, cs); const auto actors = geopro::render::buildGridContour(g, cs);
@ -317,6 +327,14 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
detailRendererPtr->AddViewProp(a); detailRendererPtr->AddViewProp(a);
} }
} }
// 异常叠加(与剖面同坐标系/同纵向夸张)。
if (*showAnomalies) {
const auto anomalies = repo.loadAnomalies(id);
for (auto& act : geopro::render::buildAnomalies(anomalies)) {
act->SetScale(1.0, kDetailYScale, 1.0);
detailRendererPtr->AddViewProp(act);
}
}
geopro::render::applyTop2D(detailRendererPtr); geopro::render::applyTop2D(detailRendererPtr);
detailRendererPtr->ResetCamera(); detailRendererPtr->ResetCamera();
detailRenderWindowPtr->Render(); detailRenderWindowPtr->Render();
@ -355,6 +373,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
rebuildDetail(); rebuildDetail();
}); });
// ──「显示异常」开关:切换异常叠加 → 重建数据详情 ──
QObject::connect(actShowAnomaly, &QAction::toggled, detailWidget,
[showAnomalies, rebuildDetail](bool on) {
*showAnomalies = on;
rebuildDetail();
});
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 按当前勾选集重建对应内容 ── // ── 工具条「二维地图/三维视图」:切换互斥视图 → 按当前勾选集重建对应内容 ──
QObject::connect(act2D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() { QObject::connect(act2D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() {
*viewMode = ViewMode::Map2D; *viewMode = ViewMode::Map2D;

View File

@ -1,6 +1,6 @@
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingCore RenderingOpenGL2 RenderingVolumeOpenGL2 InteractionStyle InteractionWidgets) find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingCore RenderingOpenGL2 RenderingVolumeOpenGL2 InteractionStyle InteractionWidgets)
add_library(geopro_render STATIC add_library(geopro_render STATIC
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.cpp) Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.cpp actors/AnomalyActor.cpp)
target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES}) target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES})
target_compile_features(geopro_render PUBLIC cxx_std_17) target_compile_features(geopro_render PUBLIC cxx_std_17)

View File

@ -0,0 +1,100 @@
#include "actors/AnomalyActor.hpp"
#include <cstddef>
#include <vtkCellArray.h>
#include <vtkNew.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkPolyLine.h>
#include <vtkProperty.h>
#include "model/ColorScale.hpp"
namespace geopro::render {
namespace {
// 虚线点画图案(16 位)与重复因子dashed 异常用。
constexpr int kDashPattern = 0xF0F0;
constexpr int kDashRepeat = 1;
// Point 型异常的方块点像素边长。
constexpr float kPointSize = 8.0F;
// 把一个异常的 localPts 灌入 pointsx, -y, 0深度取负与 #18 同坐标系)。
void fillPoints(vtkPoints* points, const geopro::core::Anomaly& a)
{
points->SetNumberOfPoints(static_cast<vtkIdType>(a.localPts.size()));
for (std::size_t i = 0; i < a.localPts.size(); ++i) {
points->SetPoint(static_cast<vtkIdType>(i), a.localPts[i].x, -a.localPts[i].y, 0.0);
}
}
} // namespace
std::vector<vtkSmartPointer<vtkActor>> buildAnomalies(
const std::vector<geopro::core::Anomaly>& anomalies)
{
std::vector<vtkSmartPointer<vtkActor>> out;
out.reserve(anomalies.size());
for (const auto& a : anomalies) {
const std::size_t n = a.localPts.size();
if (n == 0) continue; // 无几何,跳过
vtkNew<vtkPoints> points;
fillPoints(points, a);
vtkNew<vtkPolyData> poly;
poly->SetPoints(points);
const bool asPoints = (a.markType == geopro::core::AnomalyMarkType::Point);
if (asPoints) {
// 点型:每点一个 vtkVertex。
vtkNew<vtkCellArray> verts;
for (std::size_t i = 0; i < n; ++i) {
const auto id = static_cast<vtkIdType>(i);
verts->InsertNextCell(1, &id);
}
poly->SetVerts(verts);
} else {
// 线/面型:经各点的折线;面(Polygon)闭合(首尾相连)成轮廓。
const bool closed = (a.markType == geopro::core::AnomalyMarkType::Polygon) && n >= 3;
const vtkIdType count = static_cast<vtkIdType>(n) + (closed ? 1 : 0);
vtkNew<vtkPolyLine> line;
line->GetPointIds()->SetNumberOfIds(count);
for (std::size_t i = 0; i < n; ++i) {
line->GetPointIds()->SetId(static_cast<vtkIdType>(i), static_cast<vtkIdType>(i));
}
if (closed) line->GetPointIds()->SetId(static_cast<vtkIdType>(n), 0); // 回到起点
vtkNew<vtkCellArray> cells;
cells->InsertNextCell(line);
poly->SetLines(cells);
}
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputData(poly);
mapper->ScalarVisibilityOff(); // 用 actor 单色,不用标量上色
auto actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
const auto c = geopro::core::parseColor(a.lineColor, geopro::core::AlphaScale::Bit255);
actor->GetProperty()->SetColor(c.r / 255.0, c.g / 255.0, c.b / 255.0);
if (asPoints) {
actor->GetProperty()->SetPointSize(kPointSize);
} else {
actor->GetProperty()->SetLineWidth(a.lineWidth > 0.0 ? a.lineWidth : 1.0);
if (a.dashed) {
actor->GetProperty()->SetLineStipplePattern(kDashPattern);
actor->GetProperty()->SetLineStippleRepeatFactor(kDashRepeat);
}
}
out.push_back(actor);
}
return out;
}
} // namespace geopro::render

View File

@ -0,0 +1,21 @@
#pragma once
#include <vector>
#include <vtkActor.h>
#include <vtkSmartPointer.h>
#include "model/Anomaly.hpp"
namespace geopro::render {
// 异常圈定叠加(#18 剖面上):按 markType 渲染——
// Point(1) = 各点方块标记;
// Polyline(2)= 经各点的折线(dashed 时虚线);
// Polygon(3) = 闭合多边形轮廓(首尾相连)。
// 坐标与数据详情 #18 一致x=距离、y=深度取负(向下)、z=0。每个异常产一个 actor
// 带其自身 lineColor / lineWidth / dashed便于按需逐个加入/移除与叠加纵向夸张。
// 调用方应对返回的每个 actor 施加与剖面相同的 SetScale 以对齐。空输入返回空 vector。
std::vector<vtkSmartPointer<vtkActor>> buildAnomalies(
const std::vector<geopro::core::Anomaly>& anomalies);
} // namespace geopro::render

View File

@ -66,6 +66,8 @@ target_sources(geopro_tests PRIVATE render/test_voxel_build.cpp)
target_sources(geopro_tests PRIVATE render/test_curtain.cpp) target_sources(geopro_tests PRIVATE render/test_curtain.cpp)
# Scatter(#17)buildScatter(ScatterField+ColorScale->vtkPolyData 彩色散点) /verts//y # Scatter(#17)buildScatter(ScatterField+ColorScale->vtkPolyData 彩色散点) /verts//y
target_sources(geopro_tests PRIVATE render/test_scatter.cpp) target_sources(geopro_tests PRIVATE render/test_scatter.cpp)
# AnomalybuildAnomalies(markType 点/线/面 -> vtkActor) ///y/
target_sources(geopro_tests PRIVATE render/test_anomaly.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES}) target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES})
vtk_module_autoinit(TARGETS geopro_tests MODULES ${VTK_LIBRARIES}) vtk_module_autoinit(TARGETS geopro_tests MODULES ${VTK_LIBRARIES})

View File

@ -0,0 +1,87 @@
#include <gtest/gtest.h>
#include <vtkActor.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include "actors/AnomalyActor.hpp"
#include "model/Anomaly.hpp"
using namespace geopro::core;
namespace {
vtkPolyData* polyOf(const vtkSmartPointer<vtkActor>& a) {
auto* m = vtkPolyDataMapper::SafeDownCast(a->GetMapper());
return m ? vtkPolyData::SafeDownCast(m->GetInput()) : nullptr;
}
} // namespace
// Polyline 异常开放折线n 点 → n 个点y 取负;颜色取 lineColor。
TEST(Anomaly, PolylineBuildsOpenLine) {
Anomaly a;
a.markType = AnomalyMarkType::Polyline;
a.localPts = {{0.0, 1.0}, {10.0, 2.0}, {20.0, 3.0}};
a.lineColor = "#FF0000";
a.lineWidth = 5.0;
a.dashed = true;
auto actors = geopro::render::buildAnomalies({a});
ASSERT_EQ(actors.size(), 1u);
auto* poly = polyOf(actors[0]);
ASSERT_NE(poly, nullptr);
EXPECT_EQ(poly->GetNumberOfPoints(), 3);
EXPECT_EQ(poly->GetNumberOfLines(), 1);
double p1[3];
poly->GetPoint(1, p1);
EXPECT_DOUBLE_EQ(p1[0], 10.0);
EXPECT_DOUBLE_EQ(p1[1], -2.0); // y 取负
double rgb[3];
actors[0]->GetProperty()->GetColor(rgb);
EXPECT_NEAR(rgb[0], 1.0, 1e-6);
EXPECT_NEAR(rgb[1], 0.0, 1e-6);
EXPECT_NEAR(rgb[2], 0.0, 1e-6);
}
// Polygon 异常:闭合轮廓 → 折线点数 = n+1首尾相连
TEST(Anomaly, PolygonClosesLoop) {
Anomaly a;
a.markType = AnomalyMarkType::Polygon;
a.localPts = {{0.0, 0.0}, {10.0, 0.0}, {10.0, 5.0}, {0.0, 5.0}};
auto actors = geopro::render::buildAnomalies({a});
ASSERT_EQ(actors.size(), 1u);
auto* poly = polyOf(actors[0]);
ASSERT_NE(poly, nullptr);
EXPECT_EQ(poly->GetNumberOfPoints(), 4);
EXPECT_EQ(poly->GetNumberOfLines(), 1);
// 闭合折线单元含 n+1 个 id。
vtkIdType npts = 0;
const vtkIdType* pts = nullptr;
poly->GetLines()->InitTraversal();
poly->GetLines()->GetNextCell(npts, pts);
EXPECT_EQ(npts, 5); // 4 + 回起点
}
// Point 异常:每点一个 vtkVertex。
TEST(Anomaly, PointBuildsVerts) {
Anomaly a;
a.markType = AnomalyMarkType::Point;
a.localPts = {{1.0, 1.0}, {2.0, 2.0}};
auto actors = geopro::render::buildAnomalies({a});
ASSERT_EQ(actors.size(), 1u);
auto* poly = polyOf(actors[0]);
ASSERT_NE(poly, nullptr);
EXPECT_EQ(poly->GetNumberOfVerts(), 2);
EXPECT_EQ(poly->GetNumberOfLines(), 0);
}
// 空几何异常被跳过。
TEST(Anomaly, EmptyPointsSkipped) {
Anomaly a;
a.markType = AnomalyMarkType::Polyline; // 无 localPts
auto actors = geopro::render::buildAnomalies({a});
EXPECT_TRUE(actors.empty());
}

View File

@ -19,6 +19,7 @@
#include "ColorLutBuilder.hpp" #include "ColorLutBuilder.hpp"
#include "actors/CurtainActor.hpp" #include "actors/CurtainActor.hpp"
#include "actors/GridContourActor.hpp" #include "actors/GridContourActor.hpp"
#include "actors/AnomalyActor.hpp"
#include "actors/MapLineActor.hpp" #include "actors/MapLineActor.hpp"
#include "actors/ScatterActor.hpp" #include "actors/ScatterActor.hpp"
#include "geo/GeoLocalFrame.hpp" #include "geo/GeoLocalFrame.hpp"
@ -91,6 +92,25 @@ int main() {
std::printf("SCATTER pts=%zu\n", s.v.size()); std::printf("SCATTER pts=%zu\n", s.v.size());
} }
// 5) 异常叠加 — #18 平面剖面 + 异常 dashed 折线叠加(同纵向夸张 1.5x 对齐)
{
std::vector<core::Anomaly> anomalies =
data::parseAnomalies(slurp((dir + "anomaly.json").c_str()));
auto a = render::buildGridContour(g, cs);
const double exag = 1.5;
vtkNew<vtkRenderer> ren; ren->SetBackground(1, 1, 1);
if (a.bands) { a.bands->SetScale(1.0, exag, 1.0); ren->AddActor(a.bands); }
if (a.edges) { a.edges->SetScale(1.0, exag, 1.0); ren->AddActor(a.edges); }
auto anomActors = render::buildAnomalies(anomalies);
for (auto& act : anomActors) {
act->SetScale(1.0, exag, 1.0);
ren->AddActor(act);
}
render::applyTop2D(ren);
renderToPng(ren, (dir + "verify_section_anomaly.png").c_str(), 1100, 360);
std::printf("ANOMALY n=%zu\n", anomalies.size());
}
std::printf("RENDER_VERIFY_DONE grid=%dx%d lat0=%.5f lon0=%.5f\n", g.nx(), g.ny(), lat0, lon0); std::printf("RENDER_VERIFY_DONE grid=%dx%d lat0=%.5f lon0=%.5f\n", g.nx(), g.ny(), lat0, lon0);
return 0; return 0;
} }