fix(vtk): 统一相机取景用数据包围盒-预设按钮(上下左右)不再被底图推远; 基准高程锚数据中心(确定性)修垂直偏移; 滚轮交互防抖140ms治缩放卡顿

This commit is contained in:
gaozheng 2026-06-17 14:53:33 +08:00
parent 67eaade7bd
commit 69a81b2eac
3 changed files with 29 additions and 16 deletions

View File

@ -8,6 +8,7 @@
#include <QDebug>
#include <QImage>
#include <QNetworkReply>
#include <QTimer>
#include <QNetworkRequest>
#include <QString>
#include <QUrl>
@ -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<geopro::core::GeoLocalFrame> 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<TileBasemap*>(clientData)) self->refresh();
// 防抖:交互(滚轮/旋转/平移)停止 ~140ms 后才刷新四叉树,避免每格事件都重算卡顿。
if (auto* self = static_cast<TileBasemap*>(clientData)) self->refreshTimer_->start(140);
}
void TileBasemap::enqueueGet(const QString& url, std::function<void(QNetworkReply*)> onDone) {
@ -348,18 +354,19 @@ void TileBasemap::placeActor(long long key, vtkSmartPointer<vtkActor> 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<vtkActor> 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 拉不到 → 平面兜底

View File

@ -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<vtkTexture> tex); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)后落地
void placeActor(long long key, vtkSmartPointer<vtkActor> actor);
void ensureBaseline(const QImage& dem); // 首块 DEM 定基准高程(各层级共用→地形连续)
void ensureBaseline(int z, int x, int y, const QImage& dem); // 用含数据中心的块定基准(确定性)
vtkSmartPointer<vtkActor> buildFlat(int z, int x, int y,
vtkSmartPointer<vtkTexture> tex); // 平面瓦片(DEM 兜底)
vtkSmartPointer<vtkActor> buildWarped(int sz, int sx, int sy, int dz, int dx, int dy,
@ -84,6 +85,7 @@ private:
bool terrainProbed_ = false; // 首次 fetchTerrain 打一行诊断日志
vtkSmartPointer<vtkInteractorObserver> styleObs_; // 持引用保证回调期有效
vtkSmartPointer<vtkCallbackCommand> observer_;
QTimer* refreshTimer_ = nullptr; // 交互防抖:滚轮/旋转停止后才刷新四叉树(避免每格卡)
bool refreshing_ = false;
};

View File

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