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(); }