diff --git a/docs/superpowers/STATUS.md b/docs/superpowers/STATUS.md index c6c6482..f6c19a3 100644 --- a/docs/superpowers/STATUS.md +++ b/docs/superpowers/STATUS.md @@ -17,9 +17,9 @@ - **中央 二维地图 / 三维视图**(两个**真内容**,非相机切换): - 二维地图 = `MapLineActor`:测线 `lat/lon` 轨迹**红线**俯视(浅底),像地图。 - 三维视图 = `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。 -- 单元测试累计 **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 完成度 @@ -29,7 +29,7 @@ | P1 | core(LocalFrame/模型/ColorScale/IDW/CrsTransform/GeoLocalFrame) | ✅ | | P2 | data(解析器/LocalSampleRepository)+ 对象树 | ✅ | | P3 | 登录(RsaEncryptor/ApiClient/AuthService/LoginWindow) | ✅(**Credential 记住免登录未做**) | -| P4 | 渲染:render 层 + 二维地图(线)+ 三维视图(帘面)+ 数据详情(#18/#17) | 🔶 **核心三视图 + 散点#17 已对**;**异常叠加 / DEM地形 / dd_voxel回归 / 底图瓦片 未做** | +| P4 | 渲染:render 层 + 二维地图(线)+ 三维视图(帘面)+ 数据详情(#18/#17/异常) | 🔶 **核心三视图 + 散点#17 + 异常叠加 已对**;**DEM地形 / dd_voxel回归 / 底图瓦片 未做;布局对齐原型(左下数据列表/右上异常列表/电极/底图)未做** | ## 3. 构建约定(**机器本地**) @@ -61,7 +61,7 @@ ## 6. 已知问题 / 待办(P4 下次会话) 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` 地形面 + 纹理。 4. **dd_voxel 回归**:需先确认项目 CRS,使散点 projX/Y 能转到 lat/lon 世界系,与帘面配准;VoxelActor 已就绪。 5. **底图瓦片**(二维地图,天地图/Mapbox):M1.5。 @@ -69,6 +69,12 @@ 7. 多测线:当前样本仅 1 条 dd_section(grid1);多条共存机制已就绪,加数据即叠加。 8. 取景微调(数据详情/帘面的相机余量);纵向夸张倍数(剖面1.5/帘面3)可做成可调。 9. render 仍部分内联在 main.cpp;可逐步抽到 view/controller。 +10. **布局对齐原型**(权威参考 `http://prototype.geomative.cn/`,用户指定;截图存 `.playwright-mcp/`)。原型为**四区六面板**,当前 app 为简化版,缺: + - **左下「数据真实显示栏」**:选中 TM(测线)后列其采集批次(=DS 数据集),tab「数据/文件」。当前 app 把 DS 直接塞树里。 + - **右上「异常列表」**:异常条目(名称/深度/尺寸/电阻率 + 颜色标记 + 眼睛显隐),与数据详情「显示异常」联动。 + - **数据详情工具条**:原型还有 色阶配置/滤波处理/显示等值线标签/纵化容差滑块/显示电极/网格 等(当前仅原数据·网格数据·显示异常)。 + - **电极标记**:剖面顶部倒三角▼电极位 + 数值标签;**底图影像**(3D 卫星底图 + 测线落地)= DEM/底图任务(CRS 阻塞)。 + 诚实记录:已对齐者=中央二维/三维切换、数据详情原数据/网格数据/显示异常、对象树、属性。 ## 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 - 环境:`../ENV_SETUP_Windows.md` - 验证脚本:`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`(剖面原数据自带色阶,范围/分段与网格色阶不同)。 diff --git a/docs/superpowers/plans/2026-06-07-m1-phase4-render.md b/docs/superpowers/plans/2026-06-07-m1-phase4-render.md index 8c7ee62..54cea83 100644 --- a/docs/superpowers/plans/2026-06-07-m1-phase4-render.md +++ b/docs/superpowers/plans/2026-06-07-m1-phase4-render.md @@ -5,7 +5,7 @@ > **⚠️ 状态(2026-06-07,务必先读 `2026-06-07-m1-view-redesign.md` + STATUS.md):** > - **Task 1(render 层 + 相机预设)**:✅ 已完成。但中央"单场景平躺剖面 + 2D/3D 仅换相机"的做法**已被 view-redesign 取代** —— 现在中央是 **二维地图(测线线)/ 三维视图(竖直帘面)两种内容**(详见 view-redesign 的"实施结果")。 > - **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)。 > 渲染必须用 `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` - [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&)`→`vtkActor`(或多 actor):按 markType——点(`vtkGlyph3D`)/线(polydata,dashed,lineColor/width)/面(`vtkPolygon`填充+边);坐标用 localPts。叠加在剖面上。 +- [x] `render::buildAnomalies(const std::vector&)`→`std::vector`(每异常一 actor,各自 lineColor/width/dashed):按 markType——点(vtkVertex+点大小)/线(开放 polyline,dashed stipple)/面(闭合 polyline 轮廓);坐标 (x,-y,0) 与 #18 同空间。叠加在数据详情剖面上(同纵向夸张)。dashed 在 OpenGL2 偏弱(几何/色/位正确)。 - [ ] main.cpp:数据集详情可切换"网格/散点"显示;异常按需叠加。**人工验证**:散点图 ~#17;异常虚线叠在剖面上。提交 `feat(render): 散点(#17) + 异常叠加`。 --- diff --git a/src/app/main.cpp b/src/app/main.cpp index 7fffc0e..5ba5a61 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -4,9 +4,11 @@ // 二维地图 = 对每个勾选数据集 buildSurveyLine(lat/lon 红线俯视,z=0)+ applyTop2D(浅底背景)。 // 三维视图 = 对每个勾选数据集 buildCurtain(竖直断面墙),actor SetScale(1,1,3) 纵向夸张 + applyFree3D(白底)。 // 切视图 / 勾选变化 → 按当前勾选集重建对应内容。 -// - 下方「数据详情」:独立 QVTK 小视图 + 工具条「反演剖面 / 原数据」切换。单击某 DS → 显示该数据集: -// 反演剖面 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。 +// - 下方「数据详情」:独立 QVTK 小视图 + 工具条「原数据 / 网格数据」切换 +「显示异常」开关(对齐原型)。 +// 单击某 DS → 显示该数据集: +// 网格数据 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。 // 原数据 = #17 彩色散点(buildScatter,x=距离/y=深度,按散点自带色阶上色)。 +// 显示异常 = 在上图叠加异常圈定(buildAnomalies,dashed 折线,同纵向夸张对齐)。 // 两者皆平躺俯视正交 + 属性。 // - 右 属性:选中数据集属性文本。 // 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame(全项目共享,保证多视图配准)。 @@ -42,6 +44,7 @@ #include "CameraPreset.hpp" #include "Scene.hpp" +#include "actors/AnomalyActor.hpp" #include "actors/CurtainActor.hpp" #include "actors/GridContourActor.hpp" #include "actors/MapLineActor.hpp" @@ -111,7 +114,7 @@ double median(std::vector v) // 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。 enum class ViewMode { Map2D, View3D }; -// 数据详情显示内容(默认反演剖面)。反演剖面=#18 banded;原数据=#17 散点。 +// 数据详情显示内容(默认网格数据)。网格数据=#18 banded;原数据=#17 散点(对齐原型命名)。 enum class DetailMode { Section18, Scatter17 }; // #17 散点屏幕像素方块边长。 @@ -191,16 +194,21 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re 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); + auto* actSection = detailToolBar->addAction(QStringLiteral("网格数据")); actScatter->setCheckable(true); - detailGroup->addAction(actSection); + actSection->setCheckable(true); 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(detailWidget, 1); @@ -284,10 +292,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re // 当前选中数据集 id(空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。 auto currentDsId = std::make_shared(); auto detailMode = std::make_shared(DetailMode::Section18); + auto showAnomalies = std::make_shared(true); // 默认显示异常(对齐原型) // 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。 - auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, currentDsId, - detailMode]() { + // 勾选「显示异常」时在 #18/#17 上叠加异常 dashed 折线(同纵向夸张对齐)。 + auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, currentDsId, detailMode, + showAnomalies]() { detailRendererPtr->RemoveAllViewProps(); if (currentDsId->isEmpty()) { // 未选数据集:清空即可 detailRenderWindowPtr->Render(); @@ -295,7 +305,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re } const std::string id = currentDsId->toStdString(); if (*detailMode == DetailMode::Section18) { - // 反演剖面:#18 banded 等值面 + 等值线,两 actor 纵向夸张 1.5x(沿 y)。 + // 网格数据:#18 banded 等值面 + 等值线,两 actor 纵向夸张 1.5x(沿 y)。 const auto g = repo.loadGrid(id); const auto cs = repo.loadColorScale(id); const auto actors = geopro::render::buildGridContour(g, cs); @@ -317,6 +327,14 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re 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); detailRendererPtr->ResetCamera(); detailRenderWindowPtr->Render(); @@ -355,6 +373,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re rebuildDetail(); }); + // ──「显示异常」开关:切换异常叠加 → 重建数据详情 ── + QObject::connect(actShowAnomaly, &QAction::toggled, detailWidget, + [showAnomalies, rebuildDetail](bool on) { + *showAnomalies = on; + rebuildDetail(); + }); + // ── 工具条「二维地图/三维视图」:切换互斥视图 → 按当前勾选集重建对应内容 ── QObject::connect(act2D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() { *viewMode = ViewMode::Map2D; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 7650917..84736e5 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingCore RenderingOpenGL2 RenderingVolumeOpenGL2 InteractionStyle InteractionWidgets) 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_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES}) target_compile_features(geopro_render PUBLIC cxx_std_17) diff --git a/src/render/actors/AnomalyActor.cpp b/src/render/actors/AnomalyActor.cpp new file mode 100644 index 0000000..4a43ea8 --- /dev/null +++ b/src/render/actors/AnomalyActor.cpp @@ -0,0 +1,100 @@ +#include "actors/AnomalyActor.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 灌入 points(x, -y, 0:深度取负,与 #18 同坐标系)。 +void fillPoints(vtkPoints* points, const geopro::core::Anomaly& a) +{ + points->SetNumberOfPoints(static_cast(a.localPts.size())); + for (std::size_t i = 0; i < a.localPts.size(); ++i) { + points->SetPoint(static_cast(i), a.localPts[i].x, -a.localPts[i].y, 0.0); + } +} + +} // namespace + +std::vector> buildAnomalies( + const std::vector& anomalies) +{ + std::vector> out; + out.reserve(anomalies.size()); + + for (const auto& a : anomalies) { + const std::size_t n = a.localPts.size(); + if (n == 0) continue; // 无几何,跳过 + + vtkNew points; + fillPoints(points, a); + + vtkNew poly; + poly->SetPoints(points); + + const bool asPoints = (a.markType == geopro::core::AnomalyMarkType::Point); + if (asPoints) { + // 点型:每点一个 vtkVertex。 + vtkNew verts; + for (std::size_t i = 0; i < n; ++i) { + const auto id = static_cast(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(n) + (closed ? 1 : 0); + vtkNew line; + line->GetPointIds()->SetNumberOfIds(count); + for (std::size_t i = 0; i < n; ++i) { + line->GetPointIds()->SetId(static_cast(i), static_cast(i)); + } + if (closed) line->GetPointIds()->SetId(static_cast(n), 0); // 回到起点 + vtkNew cells; + cells->InsertNextCell(line); + poly->SetLines(cells); + } + + vtkNew mapper; + mapper->SetInputData(poly); + mapper->ScalarVisibilityOff(); // 用 actor 单色,不用标量上色 + + auto actor = vtkSmartPointer::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 diff --git a/src/render/actors/AnomalyActor.hpp b/src/render/actors/AnomalyActor.hpp new file mode 100644 index 0000000..e685ab3 --- /dev/null +++ b/src/render/actors/AnomalyActor.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +#include +#include + +#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> buildAnomalies( + const std::vector& anomalies); + +} // namespace geopro::render diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c60e3f2..0f4ca68 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -66,6 +66,8 @@ target_sources(geopro_tests PRIVATE render/test_voxel_build.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) +# Anomaly:buildAnomalies(markType 点/线/面 -> vtkActor) 几何/闭合/颜色/y取负/空跳过。 +target_sources(geopro_tests PRIVATE render/test_anomaly.cpp) target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES}) vtk_module_autoinit(TARGETS geopro_tests MODULES ${VTK_LIBRARIES}) diff --git a/tests/render/test_anomaly.cpp b/tests/render/test_anomaly.cpp new file mode 100644 index 0000000..e7e08b2 --- /dev/null +++ b/tests/render/test_anomaly.cpp @@ -0,0 +1,87 @@ +#include + +#include +#include +#include +#include +#include + +#include "actors/AnomalyActor.hpp" +#include "model/Anomaly.hpp" + +using namespace geopro::core; + +namespace { +vtkPolyData* polyOf(const vtkSmartPointer& 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()); +} diff --git a/tests/spike/render_verify.cpp b/tests/spike/render_verify.cpp index 4e48f39..5af30e6 100644 --- a/tests/spike/render_verify.cpp +++ b/tests/spike/render_verify.cpp @@ -19,6 +19,7 @@ #include "ColorLutBuilder.hpp" #include "actors/CurtainActor.hpp" #include "actors/GridContourActor.hpp" +#include "actors/AnomalyActor.hpp" #include "actors/MapLineActor.hpp" #include "actors/ScatterActor.hpp" #include "geo/GeoLocalFrame.hpp" @@ -91,6 +92,25 @@ int main() { std::printf("SCATTER pts=%zu\n", s.v.size()); } + // 5) 异常叠加 — #18 平面剖面 + 异常 dashed 折线叠加(同纵向夸张 1.5x 对齐) + { + std::vector anomalies = + data::parseAnomalies(slurp((dir + "anomaly.json").c_str())); + auto a = render::buildGridContour(g, cs); + const double exag = 1.5; + vtkNew 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); return 0; }