From 69a81b2eaceedacdde689031fabde005dbc74f16 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 17 Jun 2026 14:53:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(vtk):=20=E7=BB=9F=E4=B8=80=E7=9B=B8?= =?UTF-8?q?=E6=9C=BA=E5=8F=96=E6=99=AF=E7=94=A8=E6=95=B0=E6=8D=AE=E5=8C=85?= =?UTF-8?q?=E5=9B=B4=E7=9B=92-=E9=A2=84=E8=AE=BE=E6=8C=89=E9=92=AE(?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E5=B7=A6=E5=8F=B3)=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E8=A2=AB=E5=BA=95=E5=9B=BE=E6=8E=A8=E8=BF=9C;=20=E5=9F=BA?= =?UTF-8?q?=E5=87=86=E9=AB=98=E7=A8=8B=E9=94=9A=E6=95=B0=E6=8D=AE=E4=B8=AD?= =?UTF-8?q?=E5=BF=83(=E7=A1=AE=E5=AE=9A=E6=80=A7)=E4=BF=AE=E5=9E=82?= =?UTF-8?q?=E7=9B=B4=E5=81=8F=E7=A7=BB;=20=E6=BB=9A=E8=BD=AE=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E9=98=B2=E6=8A=96140ms=E6=B2=BB=E7=BC=A9=E6=94=BE?= =?UTF-8?q?=E5=8D=A1=E9=A1=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/TileBasemap.cpp | 35 +++++++++++++++++++++-------------- src/app/TileBasemap.hpp | 4 +++- src/app/VtkSceneView.cpp | 6 +++++- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/app/TileBasemap.cpp b/src/app/TileBasemap.cpp index 6101ddc..cda36fb 100644 --- a/src/app/TileBasemap.cpp +++ b/src/app/TileBasemap.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -107,7 +108,11 @@ long long TileBasemap::tileKey(int z, int x, int y) { TileBasemap::TileBasemap(geopro::render::Scene& scene, vtkRenderWindow* rw, std::shared_ptr frame, QObject* parent) - : QObject(parent), scene_(scene), rw_(rw), frame_(std::move(frame)) {} + : QObject(parent), scene_(scene), rw_(rw), frame_(std::move(frame)) { + refreshTimer_ = new QTimer(this); + refreshTimer_->setSingleShot(true); + connect(refreshTimer_, &QTimer::timeout, this, [this]() { refresh(); }); +} TileBasemap::~TileBasemap() { if (styleObs_ && observer_) styleObs_->RemoveObserver(observer_); @@ -128,7 +133,8 @@ void TileBasemap::ensureObserver() { } void TileBasemap::onInteractionEnd(vtkObject*, unsigned long, void* clientData, void*) { - if (auto* self = static_cast(clientData)) self->refresh(); + // 防抖:交互(滚轮/旋转/平移)停止 ~140ms 后才刷新四叉树,避免每格事件都重算卡顿。 + if (auto* self = static_cast(clientData)) self->refreshTimer_->start(140); } void TileBasemap::enqueueGet(const QString& url, std::function onDone) { @@ -348,18 +354,19 @@ void TileBasemap::placeActor(long long key, vtkSmartPointer actor) { placed_[key] = actor; } -void TileBasemap::ensureBaseline(const QImage& dem) { - if (haveBaseline_) return; // 仅首块定基准 → base/detail 共用同一基准,地形连续无断层 - baseline_ = demElev(dem, 0.5, 0.5); +void TileBasemap::ensureBaseline(int z, int x, int y, const QImage& dem) { + if (haveBaseline_) return; + // 基准必须锚"数据中心"的地面高程(确定性):只有含数据中心的块来定,按中心点采样。 + // 否则四叉树里随便哪块先到都定基准 → 地形整体偏移 → 三维剖面相对地面忽上忽下。 + const auto c = frame_->toLatLon(0.0, 0.0); // 数据中心经纬 + const geopro::render::TileXY ct = geopro::render::lonLatToTile(c.lon, c.lat, z); + if (ct.x != x || ct.y != y) return; // 此块不含数据中心 → 不用它定基准 + const geopro::render::LonLatBox b = geopro::render::tileBounds(z, x, y); + const double fx = (c.lon - b.west) / (b.east - b.west); // 数据中心在块内的列比例 + const double fy = (b.north - c.lat) / (b.north - b.south); // 行比例(顶=北) + baseline_ = demElev(dem, fx, fy); haveBaseline_ = true; - double mn = 1e30, mx = -1e30; - for (int yy = 0; yy < dem.height(); yy += 16) - for (int xx = 0; xx < dem.width(); xx += 16) { - const double e = demElev(dem, xx / double(dem.width() - 1), yy / double(dem.height() - 1)); - mn = std::min(mn, e); - mx = std::max(mx, e); - } - qInfo() << "[basemap] 地形启用 baseline=" << baseline_ << "m 起伏=" << (mx - mn) << "m"; + qInfo() << "[basemap] 地形启用 baseline(数据中心)=" << baseline_ << "m z=" << z; } vtkSmartPointer TileBasemap::buildFlat(int z, int x, int y, @@ -396,7 +403,7 @@ void TileBasemap::fetchTerrain(int z, int x, int y, long long key, vtkSmartPoint // 落地一块瓦片:DEM 有效→起伏,否则→平面兜底;并推进 inFlight/清理。 auto place = [this, key, z, x, y, dz, dx, dy, tex](const QImage* dem) { if (dem && !dem->isNull()) { - ensureBaseline(*dem); // 首块定基准(base/detail 共用)→ 地形连续 + ensureBaseline(dz, dx, dy, *dem); // 用含数据中心的 DEM 块定基准(确定性) placeActor(key, buildWarped(z, x, y, dz, dx, dy, tex, *dem)); } else { placeActor(key, buildFlat(z, x, y, tex)); // DEM 拉不到 → 平面兜底 diff --git a/src/app/TileBasemap.hpp b/src/app/TileBasemap.hpp index 971c534..349b478 100644 --- a/src/app/TileBasemap.hpp +++ b/src/app/TileBasemap.hpp @@ -20,6 +20,7 @@ class vtkRenderWindow; class vtkInteractorObserver; class vtkCallbackCommand; class QNetworkReply; +class QTimer; namespace geopro::render { class Scene; } namespace geopro::core { class GeoLocalFrame; } @@ -49,7 +50,7 @@ private: void fetchTerrain(int z, int x, int y, long long key, vtkSmartPointer tex); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)后落地 void placeActor(long long key, vtkSmartPointer actor); - void ensureBaseline(const QImage& dem); // 首块 DEM 定基准高程(各层级共用→地形连续) + void ensureBaseline(int z, int x, int y, const QImage& dem); // 用含数据中心的块定基准(确定性) vtkSmartPointer buildFlat(int z, int x, int y, vtkSmartPointer tex); // 平面瓦片(DEM 兜底) vtkSmartPointer buildWarped(int sz, int sx, int sy, int dz, int dx, int dy, @@ -84,6 +85,7 @@ private: bool terrainProbed_ = false; // 首次 fetchTerrain 打一行诊断日志 vtkSmartPointer styleObs_; // 持引用保证回调期有效 vtkSmartPointer observer_; + QTimer* refreshTimer_ = nullptr; // 交互防抖:滚轮/旋转停止后才刷新四叉树(避免每格卡) bool refreshing_ = false; }; diff --git a/src/app/VtkSceneView.cpp b/src/app/VtkSceneView.cpp index 248ed28..5893001 100644 --- a/src/app/VtkSceneView.cpp +++ b/src/app/VtkSceneView.cpp @@ -182,7 +182,11 @@ void VtkSceneView::setAxes(geopro::controller::AxesMode mode, geopro::controller } void VtkSceneView::applyCameraView(geopro::controller::ViewDir dir) { - geopro::render::applyView(scene_.renderer(), toRenderViewDir(dir)); + geopro::render::applyView(scene_.renderer(), toRenderViewDir(dir)); // 设朝向(内部 ResetCamera 含底图) + double bounds[6]; + if (computeDataBounds(bounds)) + scene_.renderer()->ResetCamera(bounds); // 重新取景到数据(否则被~公里级底图推到超远) + scene_.renderer()->ResetCameraClippingRange(); // 裁剪面含底图 if (renderWindow_) renderWindow_->Render(); }