fix(render): 地形高程按测线地表基准 rebase + 切片改左键拖动移动切面

- 反馈1 地形浮空/偏位: 诊断确认 DEM 是 WGS84 经纬度(26x10, 覆盖~700x330m), 测线仅~70m 在其南缘
  (横向"偏"实为地形覆盖远大于测线, 地理正确); 纵向浮空因地形用绝对高程(16-95m) vs 帘面深度。
  → buildTerrain 加 zOffset(从高程减基准), app 传测线地表高程中位数 refElev, 使地形落在测线附近。
  完整 Z 基准统一(与帘面/体素夸张一致)仍属 spec M-3 待办。
- 反馈2 切片交互: vtkImagePlaneWidget 默认左键=取值光标(十字), 不直观; 改 左键=移动切面
  (VTK_SLICE_MOTION_ACTION)、中键=取值。现在左键拖动直接滑动切面。
- 全 40 测试绿; app 构建干净。
This commit is contained in:
gaozheng 2026-06-08 11:38:14 +08:00
parent 7007619bf2
commit f57291a127
5 changed files with 18 additions and 7 deletions

View File

@ -167,6 +167,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
const double lat0 = median(baseGrid.lat); const double lat0 = median(baseGrid.lat);
const double lon0 = median(baseGrid.lon); const double lon0 = median(baseGrid.lon);
auto frame = std::make_shared<geopro::core::GeoLocalFrame>(lat0, lon0); auto frame = std::make_shared<geopro::core::GeoLocalFrame>(lat0, lon0);
// 测线地表高程基准(地形 z rebase 用,使地形落在测线附近而非按绝对高程浮空)。
const double refElev = baseGrid.elevation.empty() ? 0.0 : median(baseGrid.elevation);
// ── 中央 QVTK + Scene竖直帘面场景───────────────────────────────── // ── 中央 QVTK + Scene竖直帘面场景─────────────────────────────────
// Scene 非 QObject堆分配用 widget 销毁信号清理widget 随 window 销毁)。 // Scene 非 QObject堆分配用 widget 销毁信号清理widget 随 window 销毁)。
@ -348,7 +350,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// frame/structure 全局共享;切视图/勾选变化都调用此函数重建当前视图。 // frame/structure 全局共享;切视图/勾选变化都调用此函数重建当前视图。
auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, &repo, frame, tree, auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, &repo, frame, tree,
structure, showCurtain, showVoxel, showTerrain, showSlice, slicePlane, structure, showCurtain, showVoxel, showTerrain, showSlice, slicePlane,
crs]() { crs, refElev]() {
// 先拆除上次的切片 widget(独立于 scene actor须显式关闭),再按条件重建。 // 先拆除上次的切片 widget(独立于 scene actor须显式关闭),再按条件重建。
if (*slicePlane) { (*slicePlane)->Off(); *slicePlane = nullptr; } if (*slicePlane) { (*slicePlane)->Off(); *slicePlane = nullptr; }
scene->clear(); scene->clear();
@ -416,6 +418,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
plane->SetSliceIndex(dims[0] / 2); plane->SetSliceIndex(dims[0] / 2);
plane->SetLookupTable(lut); plane->SetLookupTable(lut);
plane->DisplayTextOn(); plane->DisplayTextOn();
// 左键拖动=移动切面(默认左键是取值光标十字,不直观);中键仍可取值。
plane->SetLeftButtonAction(vtkImagePlaneWidget::VTK_SLICE_MOTION_ACTION);
plane->SetMiddleButtonAction(vtkImagePlaneWidget::VTK_CURSOR_ACTION);
plane->On(); plane->On();
*slicePlane = plane; *slicePlane = plane;
} }
@ -424,7 +429,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 三维「地形」图层GDAL 读 DEM(高程)+影像(EPSG:3857)重投影到世界系warp 面 + 纹理。 // 三维「地形」图层GDAL 读 DEM(高程)+影像(EPSG:3857)重投影到世界系warp 面 + 纹理。
if (!is2D && *showTerrain && crs) { if (!is2D && *showTerrain && crs) {
auto terr = geopro::render::buildTerrain(repo.demPath(), repo.imagePath(), *frame, 1.0); // zOffset=refElev 使地形落在测线地表高程附近(不按绝对高程浮空);zScale=1 真实起伏。
auto terr = geopro::render::buildTerrain(repo.demPath(), repo.imagePath(), *frame,
refElev, 1.0);
if (terr) scene->addActor(terr); if (terr) scene->addActor(terr);
} }

View File

@ -92,7 +92,8 @@ Raster readGeo(GDALDataset* ds) {
} // namespace } // namespace
vtkSmartPointer<vtkActor> buildTerrain(const std::string& demPath, const std::string& imagePath, vtkSmartPointer<vtkActor> buildTerrain(const std::string& demPath, const std::string& imagePath,
const geopro::core::GeoLocalFrame& frame, double zScale) const geopro::core::GeoLocalFrame& frame, double zOffset,
double zScale)
{ {
GDALAllRegister(); GDALAllRegister();
@ -152,7 +153,7 @@ vtkSmartPointer<vtkActor> buildTerrain(const std::string& demPath, const std::st
const vtkIdType id = static_cast<vtkIdType>(j) * dg.w + i; const vtkIdType id = static_cast<vtkIdType>(j) * dg.w + i;
float z = elev[static_cast<size_t>(j) * dg.w + i]; float z = elev[static_cast<size_t>(j) * dg.w + i];
if (hasNoData && z == static_cast<float>(noData)) z = vmin; if (hasNoData && z == static_cast<float>(noData)) z = vmin;
points->SetPoint(id, local.x, local.y, z * zScale); points->SetPoint(id, local.x, local.y, (z - zOffset) * zScale); // rebase 到测线高程
sc->SetValue(id, z); sc->SetValue(id, z);
if (hasImage) { if (hasImage) {
const auto m = llTo3857.forward(ll.x, ll.y); // (mercX, mercY) const auto m = llTo3857.forward(ll.x, ll.y); // (mercX, mercY)

View File

@ -13,10 +13,13 @@ namespace geopro::render {
// 高程作 z 起伏(vtkStructuredGrid 面)影像按经纬→3857→像素 算纹理坐标贴面。 // 高程作 z 起伏(vtkStructuredGrid 面)影像按经纬→3857→像素 算纹理坐标贴面。
// 影像加载失败则退化为按高程上色(无纹理)。读不到 DEM 返回空 actor。 // 影像加载失败则退化为按高程上色(无纹理)。读不到 DEM 返回空 actor。
// //
// 依赖 GDAL/PROJ调用方运行时须有 PROJ_DATA。zScale 为高程纵向夸张(地形通常 1.0~数倍)。 // 依赖 GDAL/PROJ调用方运行时须有 PROJ_DATA。
// zOffset从高程减去的基准(米)——传测线地表高程使地形落在测线附近(否则按绝对高程浮空,
// spec M-3 Z 基准)。世界 z = (elev - zOffset) * zScale。zScale 为纵向夸张。
vtkSmartPointer<vtkActor> buildTerrain(const std::string& demPath, vtkSmartPointer<vtkActor> buildTerrain(const std::string& demPath,
const std::string& imagePath, const std::string& imagePath,
const geopro::core::GeoLocalFrame& frame, const geopro::core::GeoLocalFrame& frame,
double zOffset = 0.0,
double zScale = 1.0); double zScale = 1.0);
} // namespace geopro::render } // namespace geopro::render

View File

@ -18,7 +18,7 @@ static const std::string kDir =
// (需 PROJ_DATA + GDAL 运行时; tests CMake 注入 PROJ_DATA。) // (需 PROJ_DATA + GDAL 运行时; tests CMake 注入 PROJ_DATA。)
TEST(Terrain, BuildsTexturedSurfaceFromSampleDemImage) { TEST(Terrain, BuildsTexturedSurfaceFromSampleDemImage) {
GeoLocalFrame frame(22.546, 114.164); // 测区附近(香港) GeoLocalFrame frame(22.546, 114.164); // 测区附近(香港)
auto actor = geopro::render::buildTerrain(kDir + "dem.tif", kDir + "image.tif", frame, 1.0); auto actor = geopro::render::buildTerrain(kDir + "dem.tif", kDir + "image.tif", frame);
ASSERT_NE(actor.GetPointer(), nullptr); ASSERT_NE(actor.GetPointer(), nullptr);
ASSERT_NE(actor->GetMapper(), nullptr); // 成功读 DEM → 有 mapper(空 actor 无 mapper) ASSERT_NE(actor->GetMapper(), nullptr); // 成功读 DEM → 有 mapper(空 actor 无 mapper)
} }

View File

@ -152,7 +152,7 @@ int main() {
// 7) DEM 地形 + 影像贴图 — GDAL 读 + 重投影到世界系 + warp 面 + 纹理 // 7) DEM 地形 + 影像贴图 — GDAL 读 + 重投影到世界系 + warp 面 + 纹理
{ {
auto terr = render::buildTerrain(dir + "dem.tif", dir + "image.tif", frame, 1.0); auto terr = render::buildTerrain(dir + "dem.tif", dir + "image.tif", frame, 0.0, 1.0);
vtkNew<vtkRenderer> ren; ren->SetBackground(0.50, 0.60, 0.72); vtkNew<vtkRenderer> ren; ren->SetBackground(0.50, 0.60, 0.72);
if (terr) ren->AddActor(terr); if (terr) ren->AddActor(terr);
render::applyFree3D(ren); render::applyFree3D(ren);