feat(render): 散点#17(ScatterActor) — 数据详情「反演剖面/原数据」切换
- ScatterActor(buildScatter): ScatterField+ColorScale → vtkPolyData 彩色方块散点 (x=距离/y=深度取负, 与#18同坐标系; 点标量+LUT, 色阶范围优先colorBar真实分段值) - 离屏 verify_scatter.png 核对吻合 Python 真值 ref_17(三角拟断面/顶部深蓝/右侧紫) - 接入 app 数据详情: 工具条「反演剖面(#18)/原数据(#17)」互斥切换, rebuildDetail 统一重建 - LocalSampleRepository.loadScatterColorScale: 散点自带色阶(范围/分段与网格色阶不同) - 修复陈旧测试 test_curtain(断言改为 vtkPolyDataMapper+banded 非空 polydata) - 新增 test_scatter 2 例(点数/verts/上色/y取负/退化安全); 全 31 测试绿
This commit is contained in:
parent
95bc521f49
commit
f51fe44533
|
|
@ -17,9 +17,9 @@
|
||||||
- **中央 二维地图 / 三维视图**(两个**真内容**,非相机切换):
|
- **中央 二维地图 / 三维视图**(两个**真内容**,非相机切换):
|
||||||
- 二维地图 = `MapLineActor`:测线 `lat/lon` 轨迹**红线**俯视(浅底),像地图。
|
- 二维地图 = `MapLineActor`:测线 `lat/lon` 轨迹**红线**俯视(浅底),像地图。
|
||||||
- 三维视图 = `CurtainActor`:沿测线的**竖直断面墙**(分段色带,z 纵向夸张×3,沿弯曲测线弯)。
|
- 三维视图 = `CurtainActor`:沿测线的**竖直断面墙**(分段色带,z 纵向夸张×3,沿弯曲测线弯)。
|
||||||
- **下方 数据详情**:单击数据集 → `GridContourActor` 平面反演剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5)。
|
- **下方 数据详情**:工具条「反演剖面/原数据」切换。单击数据集 → 反演剖面=`GridContourActor` 平面剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5);原数据=`ScatterActor` 彩色方块散点(#17,x=距离/y=深度取负,用散点自带色阶)。
|
||||||
- **右 属性**:名称/网格 nx×ny/vmin·vmax。
|
- **右 属性**:名称/网格 nx×ny/vmin·vmax。
|
||||||
- 单元测试累计 ~28 个全绿(core/data/net/render);离屏 `verify_section/map/curtain_3d.png` 均核对正确。
|
- 单元测试累计 **31 个全绿**(core/data/net/render;含 Scatter 2 个、修复了陈旧的 Curtain mapper 类型断言);离屏 `verify_section/map/curtain_3d/scatter.png` 均核对正确(scatter 与 Python 真值 ref_17 吻合)。
|
||||||
|
|
||||||
## 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 / 异常叠加 / DEM地形 / dd_voxel回归 / 底图瓦片 未做** |
|
| P4 | 渲染:render 层 + 二维地图(线)+ 三维视图(帘面)+ 数据详情(#18/#17) | 🔶 **核心三视图 + 散点#17 已对**;**异常叠加 / DEM地形 / dd_voxel回归 / 底图瓦片 未做** |
|
||||||
|
|
||||||
## 3. 构建约定(**机器本地**)
|
## 3. 构建约定(**机器本地**)
|
||||||
|
|
||||||
|
|
@ -60,8 +60,8 @@
|
||||||
|
|
||||||
## 6. 已知问题 / 待办(P4 下次会话)
|
## 6. 已知问题 / 待办(P4 下次会话)
|
||||||
|
|
||||||
1. **散点 #17**:`ScatterActor`(剖面原数据 2597 点彩色散点),可作数据详情的"原数据"视图。
|
1. ~~**散点 #17**:`ScatterActor`(剖面原数据 2597 点彩色散点),数据详情"原数据"视图~~ ✅ **已完成**(2026-06-08,离屏 PNG 核对吻合 Python 真值,接入数据详情「反演剖面/原数据」切换;app 待人工登录肉眼复核交互)。
|
||||||
2. **异常叠加**:`AnomalyActor`(markType 点/线/面),叠加在剖面/帘面上(异常数据已能解析)。
|
2. **异常叠加**:`AnomalyActor`(markType 点/线/面),叠加在剖面/帘面上(异常数据已能解析)。**下一项**。
|
||||||
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。
|
||||||
|
|
@ -84,4 +84,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 平面)、`VoxelActor`(体,未接 UI)、`ColorLutBuilder`、`CameraPreset`、`Scene`;`core::GeoLocalFrame`(经纬→局部米)。
|
- 渲染积木(render 层):`MapLineActor`(测线线)、`CurtainActor`(断面墙)、`GridContourActor`(#18 平面)、`ScatterActor`(#17 散点)、`VoxelActor`(体,未接 UI)、`ColorLutBuilder`、`CameraPreset`、`Scene`;`core::GeoLocalFrame`(经纬→局部米)。
|
||||||
|
- 散点 #17 专属色阶经 `LocalSampleRepository::loadScatterColorScale`(剖面原数据自带色阶,范围/分段与网格色阶不同)。
|
||||||
|
|
|
||||||
|
|
@ -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 + 异常叠加)**:⬜ 未做(P4 下次)。
|
> - **Task 3(散点#17 + 异常叠加)**:🔶 **散点#17 ✅ 已完成**(2026-06-08,`ScatterActor` 离屏 PNG 核对吻合 Python 真值 ref_17,接入数据详情「反演剖面/原数据」切换,+2 单测,全 31 测试绿;app 待人工登录复核)。**异常叠加 ⬜ 未做(下一项)**。
|
||||||
> - **Task 4(DEM/影像地形)**:⬜ 未做(P4 下次,需先确认 CRS + 加 gdal)。
|
> - **Task 4(DEM/影像地形)**:⬜ 未做(P4 下次,需先确认 CRS + 加 gdal)。
|
||||||
> 渲染必须用 `tests/spike/render_verify.cpp` 离屏 PNG 核对(本会话教训)。
|
> 渲染必须用 `tests/spike/render_verify.cpp` 离屏 PNG 核对(本会话教训)。
|
||||||
|
|
||||||
|
|
@ -49,7 +49,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`
|
||||||
|
|
||||||
- [ ] `ScatterActor::build(const core::ScatterField&, const core::ColorScale&)`→`vtkActor`:`vtkPolyData`(verts,点用 x/y/(z))+ 标量 v + LUT;点大小适中。(#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。叠加在剖面上。
|
- [ ] `AnomalyActor::build(const std::vector<core::Anomaly>&)`→`vtkActor`(或多 actor):按 markType——点(`vtkGlyph3D`)/线(polydata,dashed,lineColor/width)/面(`vtkPolygon`填充+边);坐标用 localPts。叠加在剖面上。
|
||||||
- [ ] main.cpp:数据集详情可切换"网格/散点"显示;异常按需叠加。**人工验证**:散点图 ~#17;异常虚线叠在剖面上。提交 `feat(render): 散点(#17) + 异常叠加`。
|
- [ ] main.cpp:数据集详情可切换"网格/散点"显示;异常按需叠加。**人工验证**:散点图 ~#17;异常虚线叠在剖面上。提交 `feat(render): 散点(#17) + 异常叠加`。
|
||||||
|
|
||||||
|
|
|
||||||
102
src/app/main.cpp
102
src/app/main.cpp
|
|
@ -4,8 +4,10 @@
|
||||||
// 二维地图 = 对每个勾选数据集 buildSurveyLine(lat/lon 红线俯视,z=0)+ applyTop2D(浅底背景)。
|
// 二维地图 = 对每个勾选数据集 buildSurveyLine(lat/lon 红线俯视,z=0)+ applyTop2D(浅底背景)。
|
||||||
// 三维视图 = 对每个勾选数据集 buildCurtain(竖直断面墙),actor SetScale(1,1,3) 纵向夸张 + applyFree3D(白底)。
|
// 三维视图 = 对每个勾选数据集 buildCurtain(竖直断面墙),actor SetScale(1,1,3) 纵向夸张 + applyFree3D(白底)。
|
||||||
// 切视图 / 勾选变化 → 按当前勾选集重建对应内容。
|
// 切视图 / 勾选变化 → 按当前勾选集重建对应内容。
|
||||||
// - 下方「数据详情」:独立 QVTK 小视图。单击某 DS → 显示该数据集平面反演剖面(#18 banded 等值面+等值线,
|
// - 下方「数据详情」:独立 QVTK 小视图 + 工具条「反演剖面 / 原数据」切换。单击某 DS → 显示该数据集:
|
||||||
// 两 actor SetScale(1,1.5,1) 纵向夸张,平躺俯视正交)+ 属性。
|
// 反演剖面 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。
|
||||||
|
// 原数据 = #17 彩色散点(buildScatter,x=距离/y=深度,按散点自带色阶上色)。
|
||||||
|
// 两者皆平躺俯视正交 + 属性。
|
||||||
// - 右 属性:选中数据集属性文本。
|
// - 右 属性:选中数据集属性文本。
|
||||||
// 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame(全项目共享,保证多视图配准)。
|
// 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame(全项目共享,保证多视图配准)。
|
||||||
|
|
||||||
|
|
@ -43,6 +45,7 @@
|
||||||
#include "actors/CurtainActor.hpp"
|
#include "actors/CurtainActor.hpp"
|
||||||
#include "actors/GridContourActor.hpp"
|
#include "actors/GridContourActor.hpp"
|
||||||
#include "actors/MapLineActor.hpp"
|
#include "actors/MapLineActor.hpp"
|
||||||
|
#include "actors/ScatterActor.hpp"
|
||||||
|
|
||||||
#include "geo/GeoLocalFrame.hpp"
|
#include "geo/GeoLocalFrame.hpp"
|
||||||
|
|
||||||
|
|
@ -108,6 +111,12 @@ double median(std::vector<double> v)
|
||||||
// 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。
|
// 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。
|
||||||
enum class ViewMode { Map2D, View3D };
|
enum class ViewMode { Map2D, View3D };
|
||||||
|
|
||||||
|
// 数据详情显示内容(默认反演剖面)。反演剖面=#18 banded;原数据=#17 散点。
|
||||||
|
enum class DetailMode { Section18, Scatter17 };
|
||||||
|
|
||||||
|
// #17 散点屏幕像素方块边长。
|
||||||
|
constexpr float kScatterPointSize = 4.0F;
|
||||||
|
|
||||||
// 纵向夸张倍数:三维断面墙沿 z 拉伸成墙;数据详情 #18 沿 y 拉伸填面板。
|
// 纵向夸张倍数:三维断面墙沿 z 拉伸成墙;数据详情 #18 沿 y 拉伸填面板。
|
||||||
constexpr double kCurtainZScale = 3.0;
|
constexpr double kCurtainZScale = 3.0;
|
||||||
constexpr double kDetailYScale = 1.5;
|
constexpr double kDetailYScale = 1.5;
|
||||||
|
|
@ -176,8 +185,27 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
vtkRenderer* detailRendererPtr = detailRenderer.Get();
|
vtkRenderer* detailRendererPtr = detailRenderer.Get();
|
||||||
vtkGenericOpenGLRenderWindow* detailRenderWindowPtr = detailRenderWindow.Get();
|
vtkGenericOpenGLRenderWindow* detailRenderWindowPtr = detailRenderWindow.Get();
|
||||||
|
|
||||||
|
// 数据详情容器:顶部「反演剖面/原数据」工具条 + 下方 QVTK 小视图。
|
||||||
|
auto* detailContainer = new QWidget();
|
||||||
|
auto* detailLayout = new QVBoxLayout(detailContainer);
|
||||||
|
detailLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
detailLayout->setSpacing(0);
|
||||||
|
|
||||||
|
auto* detailToolBar = new QToolBar();
|
||||||
|
auto* detailGroup = new QActionGroup(detailToolBar);
|
||||||
|
detailGroup->setExclusive(true);
|
||||||
|
auto* actSection = detailToolBar->addAction(QStringLiteral("反演剖面"));
|
||||||
|
auto* actScatter = detailToolBar->addAction(QStringLiteral("原数据"));
|
||||||
|
actSection->setCheckable(true);
|
||||||
|
actScatter->setCheckable(true);
|
||||||
|
detailGroup->addAction(actSection);
|
||||||
|
detailGroup->addAction(actScatter);
|
||||||
|
actSection->setChecked(true); // 默认反演剖面 (#18)
|
||||||
|
detailLayout->addWidget(detailToolBar);
|
||||||
|
detailLayout->addWidget(detailWidget, 1);
|
||||||
|
|
||||||
auto* detailDock = new ads::CDockWidget(QStringLiteral("数据详情"));
|
auto* detailDock = new ads::CDockWidget(QStringLiteral("数据详情"));
|
||||||
detailDock->setWidget(detailWidget);
|
detailDock->setWidget(detailContainer);
|
||||||
// 放在中央视图下方。
|
// 放在中央视图下方。
|
||||||
dockManager->addDockWidget(ads::BottomDockWidgetArea, detailDock, centerDockArea);
|
dockManager->addDockWidget(ads::BottomDockWidgetArea, detailDock, centerDockArea);
|
||||||
|
|
||||||
|
|
@ -252,24 +280,25 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
rebuildCentral();
|
rebuildCentral();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 单击 DS → 下方数据详情显示平面反演剖面 + 右侧属性(与勾选区分;不改帘面可见性)──
|
// ── 数据详情共享状态 + 重建 ──────────────────────────────────────────
|
||||||
QObject::connect(
|
// 当前选中数据集 id(空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。
|
||||||
tree, &QTreeWidget::itemClicked, tree,
|
auto currentDsId = std::make_shared<QString>();
|
||||||
[&repo, propLabel, detailRendererPtr, detailRenderWindowPtr](QTreeWidgetItem* item, int) {
|
auto detailMode = std::make_shared<DetailMode>(DetailMode::Section18);
|
||||||
const QString dsId = item->data(0, kRoleDsId).toString();
|
|
||||||
if (dsId.isEmpty()) return; // GS/TM 节点无详情
|
|
||||||
const QString ddType = item->data(0, kRoleDdType).toString();
|
|
||||||
const QString name = item->text(0);
|
|
||||||
if (ddType != "dd_section") return;
|
|
||||||
|
|
||||||
const std::string id = dsId.toStdString();
|
// 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。
|
||||||
|
auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, currentDsId,
|
||||||
|
detailMode]() {
|
||||||
|
detailRendererPtr->RemoveAllViewProps();
|
||||||
|
if (currentDsId->isEmpty()) { // 未选数据集:清空即可
|
||||||
|
detailRenderWindowPtr->Render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string id = currentDsId->toStdString();
|
||||||
|
if (*detailMode == DetailMode::Section18) {
|
||||||
|
// 反演剖面:#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);
|
||||||
|
|
||||||
// 下方数据详情:平面反演剖面(#18 banded 等值面 + 等值线),平躺俯视正交。
|
|
||||||
// 两个 actor 都纵向夸张 1.5x(沿 y)填满面板。
|
|
||||||
const auto actors = geopro::render::buildGridContour(g, cs);
|
const auto actors = geopro::render::buildGridContour(g, cs);
|
||||||
detailRendererPtr->RemoveAllViewProps();
|
|
||||||
if (actors.bands) {
|
if (actors.bands) {
|
||||||
actors.bands->SetScale(1.0, kDetailYScale, 1.0);
|
actors.bands->SetScale(1.0, kDetailYScale, 1.0);
|
||||||
detailRendererPtr->AddViewProp(actors.bands);
|
detailRendererPtr->AddViewProp(actors.bands);
|
||||||
|
|
@ -278,17 +307,54 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
actors.edges->SetScale(1.0, kDetailYScale, 1.0);
|
actors.edges->SetScale(1.0, kDetailYScale, 1.0);
|
||||||
detailRendererPtr->AddViewProp(actors.edges);
|
detailRendererPtr->AddViewProp(actors.edges);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 原数据:#17 彩色散点,用散点自带色阶;纵向夸张同剖面以对齐观感。
|
||||||
|
const auto s = repo.loadScatter(id);
|
||||||
|
const auto scs = repo.loadScatterColorScale(id);
|
||||||
|
auto a = geopro::render::buildScatter(s, scs, kScatterPointSize);
|
||||||
|
if (a) {
|
||||||
|
a->SetScale(1.0, kDetailYScale, 1.0);
|
||||||
|
detailRendererPtr->AddViewProp(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
geopro::render::applyTop2D(detailRendererPtr);
|
geopro::render::applyTop2D(detailRendererPtr);
|
||||||
detailRendererPtr->ResetCamera();
|
detailRendererPtr->ResetCamera();
|
||||||
detailRenderWindowPtr->Render();
|
detailRenderWindowPtr->Render();
|
||||||
|
};
|
||||||
|
|
||||||
// 右侧属性。
|
// ── 单击 DS → 记选中 + 重建数据详情 + 右侧属性(与勾选区分;不改帘面可见性)──
|
||||||
|
QObject::connect(
|
||||||
|
tree, &QTreeWidget::itemClicked, tree,
|
||||||
|
[&repo, propLabel, currentDsId, rebuildDetail](QTreeWidgetItem* item, int) {
|
||||||
|
const QString dsId = item->data(0, kRoleDsId).toString();
|
||||||
|
if (dsId.isEmpty()) return; // GS/TM 节点无详情
|
||||||
|
const QString ddType = item->data(0, kRoleDdType).toString();
|
||||||
|
if (ddType != "dd_section") return;
|
||||||
|
const QString name = item->text(0);
|
||||||
|
|
||||||
|
*currentDsId = dsId;
|
||||||
|
rebuildDetail();
|
||||||
|
|
||||||
|
// 右侧属性(数据集级,与详情模式无关)。
|
||||||
|
const auto g = repo.loadGrid(dsId.toStdString());
|
||||||
propLabel->setText(
|
propLabel->setText(
|
||||||
QStringLiteral("数据集: %1\n类型: 剖面网格 (dd_section)\n网格: %2 x %3\n"
|
QStringLiteral("数据集: %1\n类型: 剖面网格 (dd_section)\n网格: %2 x %3\n"
|
||||||
"vmin / vmax: %4 / %5")
|
"vmin / vmax: %4 / %5")
|
||||||
.arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax));
|
.arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── 数据详情工具条「反演剖面/原数据」:切模式 → 重建数据详情 ──
|
||||||
|
QObject::connect(actSection, &QAction::triggered, detailWidget,
|
||||||
|
[detailMode, rebuildDetail]() {
|
||||||
|
*detailMode = DetailMode::Section18;
|
||||||
|
rebuildDetail();
|
||||||
|
});
|
||||||
|
QObject::connect(actScatter, &QAction::triggered, detailWidget,
|
||||||
|
[detailMode, rebuildDetail]() {
|
||||||
|
*detailMode = DetailMode::Scatter17;
|
||||||
|
rebuildDetail();
|
||||||
|
});
|
||||||
|
|
||||||
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 按当前勾选集重建对应内容 ──
|
// ── 工具条「二维地图/三维视图」:切换互斥视图 → 按当前勾选集重建对应内容 ──
|
||||||
QObject::connect(act2D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() {
|
QObject::connect(act2D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() {
|
||||||
*viewMode = ViewMode::Map2D;
|
*viewMode = ViewMode::Map2D;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ constexpr const char* kDsId = "grid1";
|
||||||
constexpr const char* kGridFile = u8"剖面网格数据1.txt";
|
constexpr const char* kGridFile = u8"剖面网格数据1.txt";
|
||||||
constexpr const char* kColorScaleFile = u8"剖面网格数据的色阶数据1.txt";
|
constexpr const char* kColorScaleFile = u8"剖面网格数据的色阶数据1.txt";
|
||||||
constexpr const char* kScatterFile = u8"剖面原数据1.txt";
|
constexpr const char* kScatterFile = u8"剖面原数据1.txt";
|
||||||
|
constexpr const char* kScatterColorScaleFile = u8"剖面原数据的色阶数据1.txt";
|
||||||
// dd_voxel:两条交叉剖面散点(约 78° 相交),合并为体素插值输入。
|
// dd_voxel:两条交叉剖面散点(约 78° 相交),合并为体素插值输入。
|
||||||
constexpr const char* kVoxelScatterFile1 = u8"剖面原数据1.txt";
|
constexpr const char* kVoxelScatterFile1 = u8"剖面原数据1.txt";
|
||||||
constexpr const char* kVoxelScatterFile2 = u8"剖面原数据2.txt";
|
constexpr const char* kVoxelScatterFile2 = u8"剖面原数据2.txt";
|
||||||
|
|
@ -93,6 +94,11 @@ std::vector<Anomaly> LocalSampleRepository::loadAnomalies(const std::string& dsI
|
||||||
return parseAnomalies(readFile(kAnomalyFile));
|
return parseAnomalies(readFile(kAnomalyFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColorScale LocalSampleRepository::loadScatterColorScale(const std::string& dsId) {
|
||||||
|
requireKnownDs(dsId);
|
||||||
|
return parseColorScale(readFile(kScatterColorScaleFile));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<ScatterField> LocalSampleRepository::loadVoxelScatters() {
|
std::vector<ScatterField> LocalSampleRepository::loadVoxelScatters() {
|
||||||
std::vector<ScatterField> out;
|
std::vector<ScatterField> out;
|
||||||
out.push_back(parseScatter(readFile(kVoxelScatterFile1)));
|
out.push_back(parseScatter(readFile(kVoxelScatterFile1)));
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ public:
|
||||||
geopro::core::ColorScale loadColorScale(const std::string& dsId) override;
|
geopro::core::ColorScale loadColorScale(const std::string& dsId) override;
|
||||||
std::vector<geopro::core::Anomaly> loadAnomalies(const std::string& dsId) override;
|
std::vector<geopro::core::Anomaly> loadAnomalies(const std::string& dsId) override;
|
||||||
|
|
||||||
|
// 散点原数据(#17)专属色阶:剖面原数据自带的色阶文件(与网格色阶不同:分段值范围更大)。
|
||||||
|
// 具体类专有方法(不进 IDatasetRepository 接口);散点点为不透明,alpha 量纲差异无影响。
|
||||||
|
geopro::core::ColorScale loadScatterColorScale(const std::string& dsId);
|
||||||
|
|
||||||
// dd_voxel 输入:读两条交叉剖面散点(剖面原数据1.txt + 剖面原数据2.txt),
|
// dd_voxel 输入:读两条交叉剖面散点(剖面原数据1.txt + 剖面原数据2.txt),
|
||||||
// 返回两者,供体素插值合并。具体类专有方法(不进 IDatasetRepository 接口)。
|
// 返回两者,供体素插值合并。具体类专有方法(不进 IDatasetRepository 接口)。
|
||||||
std::vector<geopro::core::ScatterField> loadVoxelScatters();
|
std::vector<geopro::core::ScatterField> loadVoxelScatters();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.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)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
#include "actors/ScatterActor.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <vtkCellArray.h>
|
||||||
|
#include <vtkDoubleArray.h>
|
||||||
|
#include <vtkNew.h>
|
||||||
|
#include <vtkPointData.h>
|
||||||
|
#include <vtkPoints.h>
|
||||||
|
#include <vtkPolyData.h>
|
||||||
|
#include <vtkPolyDataMapper.h>
|
||||||
|
#include <vtkProperty.h>
|
||||||
|
|
||||||
|
#include "ColorLutBuilder.hpp"
|
||||||
|
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// LUT 级数。
|
||||||
|
constexpr int kLutLevels = 256;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkActor> buildScatter(const geopro::core::ScatterField& s,
|
||||||
|
const geopro::core::ColorScale& cs,
|
||||||
|
float pointSize)
|
||||||
|
{
|
||||||
|
const std::size_t n = s.v.size();
|
||||||
|
|
||||||
|
// 退化输入:无值或 x/y 长度不足 → 空 actor(调用方仍可安全 add,mapper 无输入则不绘制)。
|
||||||
|
if (n == 0 || s.x.size() < n || s.y.size() < n) {
|
||||||
|
return vtkSmartPointer<vtkActor>::New();
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkNew<vtkPoints> points;
|
||||||
|
points->SetNumberOfPoints(static_cast<vtkIdType>(n));
|
||||||
|
|
||||||
|
vtkNew<vtkCellArray> verts; // 每点一个 vtkVertex(GL_POINTS),渲染为方块点
|
||||||
|
|
||||||
|
vtkNew<vtkDoubleArray> sc;
|
||||||
|
sc->SetName("v");
|
||||||
|
sc->SetNumberOfTuples(static_cast<vtkIdType>(n));
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < n; ++i) {
|
||||||
|
const auto id = static_cast<vtkIdType>(i);
|
||||||
|
// y=深度(越大越深)→取负,使深部在下、剖面不倒置(与 GridContourActor 一致)。
|
||||||
|
points->SetPoint(id, s.x[i], -s.y[i], 0.0);
|
||||||
|
verts->InsertNextCell(1, &id);
|
||||||
|
sc->SetValue(id, s.v[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkNew<vtkPolyData> poly;
|
||||||
|
poly->SetPoints(points);
|
||||||
|
poly->SetVerts(verts);
|
||||||
|
poly->GetPointData()->SetScalars(sc);
|
||||||
|
|
||||||
|
// 色阶范围:优先 colorBar 真实分段值(非均匀),否则回退到 v 实测范围。
|
||||||
|
const std::vector<double> stops = cs.stopValues();
|
||||||
|
double vmin, vmax;
|
||||||
|
if (stops.size() >= 2) {
|
||||||
|
vmin = stops.front();
|
||||||
|
vmax = stops.back();
|
||||||
|
} else {
|
||||||
|
vmin = s.v.front();
|
||||||
|
vmax = vmin;
|
||||||
|
for (double v : s.v) {
|
||||||
|
if (v < vmin) vmin = v;
|
||||||
|
if (v > vmax) vmax = v;
|
||||||
|
}
|
||||||
|
if (vmin >= vmax) vmax = vmin + 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lut = buildLut(cs, vmin, vmax, kLutLevels);
|
||||||
|
|
||||||
|
vtkNew<vtkPolyDataMapper> mapper;
|
||||||
|
mapper->SetInputData(poly);
|
||||||
|
mapper->SetScalarModeToUsePointData();
|
||||||
|
mapper->SetLookupTable(lut);
|
||||||
|
mapper->SetScalarRange(vmin, vmax);
|
||||||
|
|
||||||
|
auto actor = vtkSmartPointer<vtkActor>::New();
|
||||||
|
actor->SetMapper(mapper);
|
||||||
|
actor->GetProperty()->SetPointSize(pointSize);
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vtkActor.h>
|
||||||
|
#include <vtkSmartPointer.h>
|
||||||
|
|
||||||
|
#include "model/ColorScale.hpp"
|
||||||
|
#include "model/Field.hpp"
|
||||||
|
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
// 剖面原数据散点(#17):把 ScatterField 的每个采样点画成按 v 着色的方块点。
|
||||||
|
// 坐标与数据详情 #18 一致:x=xlist(距离)、y=-ylist(深度向下)、z=0(face-on 俯视读)。
|
||||||
|
// pointSize 为屏幕像素方块边长。色阶范围优先取 colorBar 真实分段值(非均匀),
|
||||||
|
// 否则回退到 v 的实际 min/max。退化输入(无点)返回空 actor,调用方可安全 add。
|
||||||
|
vtkSmartPointer<vtkActor> buildScatter(const geopro::core::ScatterField& s,
|
||||||
|
const geopro::core::ColorScale& cs,
|
||||||
|
float pointSize = 4.0F);
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -64,6 +64,8 @@ target_sources(geopro_tests PRIVATE render/test_color_lut.cpp)
|
||||||
target_sources(geopro_tests PRIVATE render/test_voxel_build.cpp)
|
target_sources(geopro_tests PRIVATE render/test_voxel_build.cpp)
|
||||||
# Curtain:buildCurtain(Grid+GeoLocalFrame->vtkStructuredGrid 帘面) 非空 actor + 点数=nx*ny。
|
# Curtain:buildCurtain(Grid+GeoLocalFrame->vtkStructuredGrid 帘面) 非空 actor + 点数=nx*ny。
|
||||||
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取负。
|
||||||
|
target_sources(geopro_tests PRIVATE render/test_scatter.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})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <vtkDataSet.h>
|
#include <vtkPolyData.h>
|
||||||
#include <vtkDataSetMapper.h>
|
#include <vtkPolyDataMapper.h>
|
||||||
#include <vtkMapper.h>
|
|
||||||
|
|
||||||
#include "actors/CurtainActor.hpp"
|
#include "actors/CurtainActor.hpp"
|
||||||
#include "geo/GeoLocalFrame.hpp"
|
#include "geo/GeoLocalFrame.hpp"
|
||||||
|
|
@ -11,7 +10,8 @@
|
||||||
|
|
||||||
using namespace geopro::core;
|
using namespace geopro::core;
|
||||||
|
|
||||||
// buildCurtain: 小 Grid(3,2) + lat/lon/y/values -> 非空 actor,mapper 输入点数=6。
|
// buildCurtain: 小 Grid(3,2) + lat/lon/y/values -> 非空 actor,经 banded contour 管线
|
||||||
|
// (vtkDataSetSurfaceFilter→vtkBandedPolyDataContourFilter→vtkPolyDataMapper) 产出非空 polydata。
|
||||||
TEST(Curtain, BuildsStructuredGridFromSmallGrid) {
|
TEST(Curtain, BuildsStructuredGridFromSmallGrid) {
|
||||||
Grid g(3, 2); // nx=3 (lat/lon 列), ny=2 (深度行)
|
Grid g(3, 2); // nx=3 (lat/lon 列), ny=2 (深度行)
|
||||||
g.lat = {22.50, 22.50, 22.50};
|
g.lat = {22.50, 22.50, 22.50};
|
||||||
|
|
@ -32,9 +32,14 @@ TEST(Curtain, BuildsStructuredGridFromSmallGrid) {
|
||||||
auto actor = geopro::render::buildCurtain(g, cs, frame);
|
auto actor = geopro::render::buildCurtain(g, cs, frame);
|
||||||
ASSERT_NE(actor.GetPointer(), nullptr);
|
ASSERT_NE(actor.GetPointer(), nullptr);
|
||||||
|
|
||||||
auto* mapper = vtkDataSetMapper::SafeDownCast(actor->GetMapper());
|
auto* mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper());
|
||||||
ASSERT_NE(mapper, nullptr);
|
ASSERT_NE(mapper, nullptr);
|
||||||
auto* input = mapper->GetInput();
|
ASSERT_NE(mapper->GetLookupTable(), nullptr);
|
||||||
ASSERT_NE(input, nullptr);
|
|
||||||
EXPECT_EQ(input->GetNumberOfPoints(), 6);
|
// 运行管线,断言 banded contour 产出非空 polydata(色带面)。
|
||||||
|
mapper->Update();
|
||||||
|
auto* out = mapper->GetInput();
|
||||||
|
ASSERT_NE(out, nullptr);
|
||||||
|
EXPECT_GT(out->GetNumberOfPoints(), 0);
|
||||||
|
EXPECT_GT(out->GetNumberOfCells(), 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <vtkPointData.h>
|
||||||
|
#include <vtkPolyData.h>
|
||||||
|
#include <vtkPolyDataMapper.h>
|
||||||
|
|
||||||
|
#include "actors/ScatterActor.hpp"
|
||||||
|
#include "model/ColorScale.hpp"
|
||||||
|
#include "model/Field.hpp"
|
||||||
|
|
||||||
|
using namespace geopro::core;
|
||||||
|
|
||||||
|
// buildScatter: 小散点场 -> 非空 actor,polydata 点数=点数、含 verts、按 v 上色(LUT)。
|
||||||
|
TEST(Scatter, BuildsColoredPointsFromScatterField) {
|
||||||
|
ScatterField s;
|
||||||
|
s.x = {0.0, 10.0, 20.0};
|
||||||
|
s.y = {1.0, 2.0, 3.0}; // 深度,渲染时取负
|
||||||
|
s.v = {0.0, 2.5, 5.0};
|
||||||
|
|
||||||
|
ColorScale cs;
|
||||||
|
cs.addStop(0.0, Rgba{0, 0, 255, 255});
|
||||||
|
cs.addStop(5.0, Rgba{255, 0, 0, 255});
|
||||||
|
|
||||||
|
auto actor = geopro::render::buildScatter(s, cs);
|
||||||
|
ASSERT_NE(actor.GetPointer(), nullptr);
|
||||||
|
|
||||||
|
auto* mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper());
|
||||||
|
ASSERT_NE(mapper, nullptr);
|
||||||
|
ASSERT_NE(mapper->GetLookupTable(), nullptr);
|
||||||
|
|
||||||
|
auto* poly = vtkPolyData::SafeDownCast(mapper->GetInput());
|
||||||
|
ASSERT_NE(poly, nullptr);
|
||||||
|
EXPECT_EQ(poly->GetNumberOfPoints(), 3);
|
||||||
|
EXPECT_EQ(poly->GetNumberOfVerts(), 3); // 每点一个 vtkVertex
|
||||||
|
ASSERT_NE(poly->GetPointData()->GetScalars(), nullptr);
|
||||||
|
|
||||||
|
// y 取负:深度向下(与剖面 #18 一致)。
|
||||||
|
double p1[3];
|
||||||
|
poly->GetPoint(1, p1);
|
||||||
|
EXPECT_DOUBLE_EQ(p1[0], 10.0);
|
||||||
|
EXPECT_DOUBLE_EQ(p1[1], -2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退化输入(无值)返回非空 actor 但无 polydata 输入(调用方可安全 add)。
|
||||||
|
TEST(Scatter, EmptyFieldYieldsSafeActor) {
|
||||||
|
ScatterField s; // 空
|
||||||
|
ColorScale cs;
|
||||||
|
cs.addStop(0.0, Rgba{0, 0, 255, 255});
|
||||||
|
auto actor = geopro::render::buildScatter(s, cs);
|
||||||
|
ASSERT_NE(actor.GetPointer(), nullptr);
|
||||||
|
EXPECT_EQ(actor->GetMapper(), nullptr); // 未设 mapper
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#include "actors/CurtainActor.hpp"
|
#include "actors/CurtainActor.hpp"
|
||||||
#include "actors/GridContourActor.hpp"
|
#include "actors/GridContourActor.hpp"
|
||||||
#include "actors/MapLineActor.hpp"
|
#include "actors/MapLineActor.hpp"
|
||||||
|
#include "actors/ScatterActor.hpp"
|
||||||
#include "geo/GeoLocalFrame.hpp"
|
#include "geo/GeoLocalFrame.hpp"
|
||||||
#include "parse/SampleParsers.hpp"
|
#include "parse/SampleParsers.hpp"
|
||||||
|
|
||||||
|
|
@ -78,6 +79,18 @@ int main() {
|
||||||
render::applyFree3D(ren);
|
render::applyFree3D(ren);
|
||||||
renderToPng(ren, (dir + "verify_curtain_3d.png").c_str(), 700, 500);
|
renderToPng(ren, (dir + "verify_curtain_3d.png").c_str(), 700, 500);
|
||||||
}
|
}
|
||||||
|
// 4) 散点 (#17) — 剖面原数据彩色方块散点; x=距离, y=深度(取负向下), face-on 俯视
|
||||||
|
{
|
||||||
|
core::ScatterField s = data::parseScatter(slurp((dir + "scatter.json").c_str()));
|
||||||
|
core::ColorScale scs = data::parseColorScale(slurp((dir + "scatter_colorbar.json").c_str()));
|
||||||
|
auto a = render::buildScatter(s, scs, 4.0F);
|
||||||
|
vtkNew<vtkRenderer> ren; ren->SetBackground(1, 1, 1);
|
||||||
|
ren->AddActor(a);
|
||||||
|
render::applyTop2D(ren);
|
||||||
|
renderToPng(ren, (dir + "verify_scatter.png").c_str(), 1100, 360);
|
||||||
|
std::printf("SCATTER pts=%zu\n", s.v.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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue