feat/vtk-merged-dataset-column #10

Merged
gaozheng merged 40 commits from feat/vtk-merged-dataset-column into main 2026-07-01 14:48:38 +08:00
10 changed files with 53 additions and 30 deletions
Showing only changes of commit 94d0ac9c3b - Show all commits

View File

@ -218,6 +218,16 @@ void TileBasemap::setOpacity(double o) {
if (kind_ != Hidden) show(kind_); // 重建套用新透明度
}
void TileBasemap::setGroundZ(double z) {
// 直接平移平面高程:瓦片几何建于相对 z(仅含逐层级 z-fighting 偏移),平面高程 groundZ_ 经 actor position 施加。
// 拖 z 值滑块时只改所有已贴瓦片的 SetPosition无需重下载/重建;后续 refresh() 新瓦片经 placeActor 自动取新 groundZ_。
if (z == groundZ_) return;
groundZ_ = z;
for (auto& kv : placed_)
if (kv.second) kv.second->SetPosition(0.0, 0.0, groundZ_);
requestRender();
}
void TileBasemap::refineTile(int z, int x, int y, std::set<long long>& out, int& count) {
if (count >= kMaxLeaves) { out.insert(tileKey(z, x, y)); return; } // 安全上限:停止细分
const int n = 1 << z;
@ -439,6 +449,7 @@ void TileBasemap::fetchTile(int z, int x, int y, long long key) {
void TileBasemap::placeActor(long long key, vtkSmartPointer<vtkActor> actor) {
if (!actor) return;
actor->SetPosition(0.0, 0.0, groundZ_); // 平面高程经 position 施加:几何建于相对 z此处抬到当前 groundZ_
scene_.addActor(actor);
placed_[key] = actor;
}
@ -449,7 +460,7 @@ vtkSmartPointer<vtkActor> TileBasemap::buildFlat(int z, int x, int y,
const auto sw = frame_->toLocal(b.south, b.west);
const auto se = frame_->toLocal(b.south, b.east);
const auto nw = frame_->toLocal(b.north, b.west);
const double gz = groundZ_ + (z - kMinZoom) * kZEps; // 高层级略抬高,压在旧层之上防共面闪烁
const double gz = (z - kMinZoom) * kZEps; // 仅逐层级 z-fighting 偏移(相对 z);平面高程由 actor position 施加
// PlaneSource 自动 tcoordorigin=SW→u 西0东1、v 南0北1与翻转后纹理对齐
vtkNew<vtkPlaneSource> plane;
@ -538,7 +549,7 @@ vtkSmartPointer<vtkActor> TileBasemap::buildWarped(int sz, int sx, int sy, int d
const auto sw = frame_->toLocal(sb.south, sb.west);
const auto se = frame_->toLocal(sb.south, sb.east);
const auto nw = frame_->toLocal(sb.north, sb.west);
const double base = groundZ_ + (sz - kMinZoom) * kZEps;
const double base = (sz - kMinZoom) * kZEps; // 仅逐层级 z-fighting 偏移(相对 z);平面高程由 actor position 施加
// PlaneSource(等距圆柱下平面插值即正确 x/y) + 自动 tcoord再按各点真实经纬采 DEM 位移 Z。
vtkNew<vtkPlaneSource> plane;

View File

@ -42,6 +42,7 @@ public:
void refresh(); // 按当前相机重算层级+覆盖,增量更新瓦片(交互结束回调)
void setVerticalExaggeration(double ve); // 地形垂向夸张(须与剖面 VE 一致才对齐)
void setOpacity(double o); // 底图半透明度[0,1],供渲染工具栏底图弹窗调节
void setGroundZ(double z); // 直接平移底图平面 z(拖 z 值滑块):改所有已贴瓦片 actor 的 position无重铺
// 数据半径提供者:刷新时查询当前所有勾选剖面的合并范围(半径,米),据此动态定底图最大范围。
void setDataRadiusProvider(std::function<double()> fn) { dataRadiusProvider_ = std::move(fn); }

View File

@ -28,6 +28,7 @@ public:
}
void hide() override { bm_.hide(); }
void setOpacity(double o) override { bm_.setOpacity(o); }
void setGroundZ(double z) override { bm_.setGroundZ(z); }
private:
TileBasemap bm_;

View File

@ -253,14 +253,29 @@ void VtkSceneView::addMapLine(const std::string& dsId, const geopro::data::MapLi
// worldZ 已是最终世界高程(含摆放语义),不再施加 VE足迹是水平线非随深度的竖直图元
// 足迹可能是首个(且唯一)带经纬的数据 → 与帘面同样重锚原点,否则按样本默认原点投到数百公里外不可见。
anchorFrameIfNeeded(line.lat, line.lon, static_cast<int>(line.lat.size()));
auto actor = geopro::render::buildMapLine(line.lat, line.lon, worldZ, *frame_);
// 折线几何建于 Z=0平面高程 worldZ 经 actor SetPosition 施加 → 后续拖 z 值滑块只改 position 即直接平移,
// 无需移除+异步重载几何setMapLinesZ 走此)。首勾/后续勾选在当前平面 z 加入者立即摆到该 z。
auto actor = geopro::render::buildMapLine(line.lat, line.lon, 0.0, *frame_);
if (actor) {
actor->SetPosition(0.0, 0.0, worldZ);
scene_.addActor(actor);
dsProps_[dsId].push_back(actor);
mapLineDs_.insert(dsId); // 记录此 ds 为 2D 足迹(供足迹归属识别)
}
}
void VtkSceneView::setMapLinesZ(const std::vector<std::string>& dsIds, double z) {
// 直接平移足迹:仅对属于足迹的 dsId 改其 actor 的 SetPosition(0,0,z),即时渲染,无移除+重载。
for (const auto& dsId : dsIds) {
if (!mapLineDs_.count(dsId)) continue;
auto it = dsProps_.find(dsId);
if (it == dsProps_.end()) continue;
for (auto& prop : it->second)
if (auto* a = vtkActor::SafeDownCast(prop)) a->SetPosition(0.0, 0.0, z);
}
if (renderWindow_) renderWindow_->Render();
}
void VtkSceneView::addTerrain(const geopro::data::TerrainPaths& paths) {
auto terrain = geopro::render::buildTerrain(paths.demPath, paths.imagePath, *frame_, zRefElev_,
verticalExaggeration_);

View File

@ -46,6 +46,7 @@ public:
const geopro::core::ColorScale& cs) override;
void addMapLine(const std::string& dsId, const geopro::data::MapLine& line,
double worldZ) override;
void setMapLinesZ(const std::vector<std::string>& dsIds, double z) override;
void addTerrain(const geopro::data::TerrainPaths& paths) override;
void removeDataset(const std::string& dsId) override;
void addAnomaly(const geopro::core::Anomaly& a) override;

View File

@ -484,23 +484,12 @@ void CategorySection::showPlaneZPopup(QToolButton* host) {
sld->setToolTip(QStringLiteral("平面高程 z"));
auto syncLabel = [lab](int v) { lab->setText(QStringLiteral("平面 z%1 米").arg(v)); };
syncLabel(sld->value());
// 发射防抖valueChanged 在拖动期逐整数步触发,每步直发 planeZChanged 会引发数十次
// removeDataset+add2DDatasetAsync 抖动重摆(生产异步路径 loadingDs_ 守护会丢中间帧→足迹滞后于陈旧 z
// 故 label/lastPlaneZ_ 即时回显,但 planeZChanged 经单发 QTimer(150ms)合并——每次变更重启,停手后只发一次终值。
// 定时器 parent=this(CategorySection),存活于 modal popup(menu.exec)之外,即便 popup 已销毁也能安全发射终值。
if (!planeZTimer_) {
planeZTimer_ = new QTimer(this);
planeZTimer_->setSingleShot(true);
planeZTimer_->setInterval(150);
connect(planeZTimer_, &QTimer::timeout, this, [this]() {
emit planeZChanged(QString::fromStdString(desc_.id), pendingPlaneZ_);
});
}
// 直接平移平面/足迹/底图(改 actor position即时同步→ 无需防抖valueChanged 每步直发 planeZChanged
// 拖动即实时跟随、无移除+异步重载。lastPlaneZ_ 记住终值供重开 popup 回显。
connect(sld, &QSlider::valueChanged, this, [this, syncLabel](int v) {
lastPlaneZ_ = v;
syncLabel(v);
pendingPlaneZ_ = static_cast<double>(v);
planeZTimer_->start(); // 重启防抖窗口:覆盖拖动、键盘、点轨——停手后一次性发射
emit planeZChanged(QString::fromStdString(desc_.id), static_cast<double>(v));
});
lay->addWidget(lab);
lay->addWidget(sld);

View File

@ -93,12 +93,10 @@ private:
QTreeWidget* list_ = nullptr;
QTimer* spinTimer_ = nullptr; // 驱动 busy 行 spinner 旋转(有 busy 行时运行)
int spinAngle_ = 0; // 当前 spinner 角度(度)
double lastPlaneZ_ = 0.0; // 上次 z 值滑块设定的平面高程(重开 popup 时回显,无则 0
QTimer* planeZTimer_ = nullptr; // z 值滑块发射防抖:拖动/键盘/点轨期合并为停手后一次重摆owned by this存活于 popup 之外)
double pendingPlaneZ_ = 0.0; // 防抖待发的平面 z定时器到点时取此值发射 planeZChanged
double lastPlaneZ_ = 0.0; // 上次 z 值滑块设定的平面高程(重开 popup 时回显,无则 0直接平移故无防抖
int lastBasemapKind_ = 0; // 上次底图选择0=矢量平面/1=无),重开 popup 时回显
int lastBasemapOpacity_ = 50; // 上次底图透明度0100重开 popup 回显;默认 50
QTimer* basemapOpacityTimer_ = nullptr; // 底图透明度滑块发射防抖(同 planeZ停手后一次发射免抖动重铺瓦片
QTimer* basemapOpacityTimer_ = nullptr; // 底图透明度滑块发射防抖(透明度改动会触发瓦片重铺,仍需防抖
double pendingBasemapOpacity_ = 0.5; // 防抖待发的底图透明度[0,1](定时器到点发射 basemapOpacityChanged
};

View File

@ -1,5 +1,8 @@
#include "controller/DatasetRenderStrategy.hpp"
#include <string>
#include <vector>
#include "controller/VtkSceneController.hpp" // 完整类型 + 含 I3dSceneViewview_.removeDataset
namespace geopro::controller {
@ -52,15 +55,14 @@ void Plane2DRenderStrategy::onTypeDeactivated(const std::string& typeId) {
void Plane2DRenderStrategy::setPlaneZ(const std::string& typeId, double z) {
planeReg_.setPlaneZ(typeId, z); // 平面 z 真源更新(类型不存在则无操作)
// 重摆该类型全部已勾选足迹:移除旧 actor按既有 add 路径在新 z 重新加载摆放。
// add2DDatasetAsync 复用 rebuildGeneration_ + loadingDs_/is2DChecked 防迟到/防重复(与 add 同护栏)。
for (const auto& [dsId, t] : dsToType_) {
if (t != typeId) continue;
ctrl_.view_.removeDataset(dsId);
ctrl_.add2DDatasetAsync(dsId, ctrl_.rebuildGeneration_, z);
}
// 底图同步升降:销毁旧实例、按新平面 z 重建(复用当前 kind/opacity),简单可靠(无 setGroundZ 增量重铺)。
if (bms_.count(typeId)) createBasemap(typeId);
// 直接平移该类型全部已勾选足迹:只改足迹 actor 的 position无移除+异步重载)→ 拖滑块即时跟随、无闪烁。
std::vector<std::string> dsIds;
for (const auto& [dsId, t] : dsToType_)
if (t == typeId) dsIds.push_back(dsId);
if (!dsIds.empty()) ctrl_.view_.setMapLinesZ(dsIds, z);
// 底图同步平移:直接改瓦片 position无销毁+重建、无重下载)→ 底图与足迹一同实时跟随滑块。
auto it = bms_.find(typeId);
if (it != bms_.end()) it->second->setGroundZ(z);
ctrl_.view_.renderIncremental();
}

View File

@ -19,6 +19,7 @@ public:
virtual void show(int kind) = 0; // 0=矢量平面(Street)/其它=无(hide)
virtual void hide() = 0;
virtual void setOpacity(double o) = 0; // 半透明度[0,1]
virtual void setGroundZ(double z) = 0; // 直接平移平面高程 z(拖 z 值滑块):改瓦片 position无重铺
};
// 工厂:按平面 z 造一份平面底图(底图所需 scene/渲染窗/frame/数据半径规则由 app 闭包捕获)。
// 未注入(空)则不建底图——便于无 VTK 的纯逻辑单测。

View File

@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <vector>
#include "model/ColorScale.hpp"
#include "model/Field.hpp"
@ -51,6 +52,9 @@ public:
// 2D 足迹:把测线/轨迹经纬折线平铺进 3D 地图worldZ=摆放高程);按 dsId 跟踪以支持增量移除。
virtual void addMapLine(const std::string& dsId, const geopro::data::MapLine& line,
double worldZ) = 0;
// 直接平移一组 2D 足迹到新平面 z拖 z 值滑块用):改足迹 actor 的 SetPosition无移除+异步重载。
// 仅对属于足迹的 dsId 生效;即时渲染。默认空实现,测试 mock 无需覆盖。
virtual void setMapLinesZ(const std::vector<std::string>& /*dsIds*/, double /*z*/) {}
// 3DDEM 地形 + 影像纹理。
virtual void addTerrain(const geopro::data::TerrainPaths& paths) = 0;
// 增量移除某数据集的全部图元(取消勾选时调,不影响其余 ds 与底图)。