From b5bab42825cab537d911cfb87fbdd4d87675b1af Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 17 Jun 2026 12:20:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(vtk):=20=E5=8A=A0=E8=BF=9C=E5=A4=84?= =?UTF-8?q?=E7=B2=97=E5=BA=95=E5=9B=BE=E5=B1=82(z13~34km,=E9=9A=8F?= =?UTF-8?q?=E5=9C=B0=E5=BD=A2=E8=B5=B7=E4=BC=8F,=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E4=B8=8Dpurge)=E5=A1=AB=E5=88=B0=E5=A4=A9=E8=BE=B9=E6=B2=BB?= =?UTF-8?q?=E5=80=BE=E6=96=9C=E9=9C=B2=E9=BB=91=E8=BE=B9;=E5=9F=BA?= =?UTF-8?q?=E5=87=86=E9=AB=98=E7=A8=8Bbase/detail=E5=85=B1=E7=94=A8?= =?UTF-8?q?=E4=BF=9D=E8=BF=9E=E7=BB=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/TileBasemap.cpp | 110 +++++++++++++++++++++++++++++++++++----- src/app/TileBasemap.hpp | 6 +++ 2 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/app/TileBasemap.cpp b/src/app/TileBasemap.cpp index c1bb4a5..df73150 100644 --- a/src/app/TileBasemap.cpp +++ b/src/app/TileBasemap.cpp @@ -41,6 +41,8 @@ namespace { const char* kTk = "aca91d8c9f59a4f779f39061b8a07737"; constexpr int kRadius = 4; // 回退用:算不出可视范围时中心 ±4 constexpr int kMaxTilesPerSide = 13; // 单边瓦片上限(防倾斜时可视范围过大拉爆请求) +constexpr int kBaseZoom = 13; // 远处粗底层级(单块~4.9km) +constexpr int kBaseRadius = 3; // 粗底半径 ±3 → 7x7 覆盖~34km,填到天边 constexpr int kMinZoom = 3; constexpr int kMaxZoom = 18; constexpr double kGroundZ = 0.0; // 底图置于 z=0 地面参考(剖面深度向下为负,落其下) @@ -137,15 +139,20 @@ void TileBasemap::show(Kind kind) { ++generation_; // 旧回包(含换源前的层)按 generation 丢弃 for (auto& kv : placed_) scene_.renderer()->RemoveViewProp(kv.second); placed_.clear(); + for (auto& a : baseTiles_) scene_.renderer()->RemoveViewProp(a); + baseTiles_.clear(); + baseLoaded_ = false; inFlight_.clear(); desired_.clear(); // demCache_/texCache_ 跨隐藏-重选保留 → 重选地图秒出,不重拉。 + haveBaseline_ = false; // 数据可能已换位置 → 重算基准高程 terrainProbed_ = false; kind_ = kind; if (kind == Hidden) { if (rw_) rw_->Render(); return; } + ensureBaseLayer(); // 先铺远处粗底(填到天边),再叠近处精细 refresh(); } @@ -362,6 +369,94 @@ 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); + 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"; +} + +void TileBasemap::ensureBaseLayer() { + if (baseLoaded_ || kind_ != Satellite) return; + baseLoaded_ = true; + const auto c = frame_->toLatLon(0.0, 0.0); // 数据中心(show 在重锚后调 → 已对准数据) + const geopro::render::TileXY ct = geopro::render::lonLatToTile(c.lon, c.lat, kBaseZoom); + const int n = 1 << kBaseZoom; + for (int dy = -kBaseRadius; dy <= kBaseRadius; ++dy) + for (int dx = -kBaseRadius; dx <= kBaseRadius; ++dx) { + const int tx = ct.x + dx, ty = ct.y + dy; + if (tx < 0 || ty < 0 || tx >= n || ty >= n) continue; + fetchBaseTile(kBaseZoom, tx, ty); + } +} + +void TileBasemap::fetchBaseTile(int z, int x, int y) { + const int gen = generation_; + const long long key = tileKey(z, x, y); + + // 有了卫星纹理后 → 取 DEM(base zoom ≤ kDemMaxZoom, 同级) → warp 成持久粗底块。 + auto onTex = [this, z, x, y, gen](vtkSmartPointer tex) { + const long long demKey = tileKey(z, x, y); + auto placeBase = [this, z, x, y, gen, tex](const QImage* dem) { + if (gen != generation_ || kind_ != Satellite) return; // 已隐藏/换源 → 丢弃 + vtkSmartPointer a; + if (dem && !dem->isNull()) { + ensureBaseline(*dem); + a = buildWarped(z, x, y, z, x, y, tex, *dem); + } else { + a = buildFlat(z, x, y, tex); + } + a->SetUseBounds(false); // 粗底不参与包围盒/取景 + scene_.addActor(a); + baseTiles_.push_back(a); + if (rw_) rw_->Render(); + }; + auto dc = demCache_.find(demKey); + if (dc != demCache_.end()) { placeBase(&dc->second); return; } + const QString durl = + QStringLiteral("https://api.mapbox.com/v4/mapbox.terrain-rgb/%1/%2/%3.pngraw?access_token=%4") + .arg(z).arg(x).arg(y).arg(QString::fromLatin1(kMapboxToken)); + QNetworkReply* dr = nam_.get(QNetworkRequest(QUrl(durl))); + connect(dr, &QNetworkReply::finished, this, [this, dr, demKey, gen, placeBase]() { + dr->deleteLater(); + if (gen != generation_) return; + QImage dem; + if (dr->error() == QNetworkReply::NoError && dem.loadFromData(dr->readAll())) { + demCache_[demKey] = dem; + placeBase(&dem); + } else { + placeBase(nullptr); + } + }); + }; + + auto tc = texCache_.find(key); + if (tc != texCache_.end()) { onTex(tc->second); return; } + const int sub = (x + y) % 8; + const QString url = + QStringLiteral("http://t%1.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile" + "&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix=%2&TileRow=%3" + "&TileCol=%4&style=default&format=tiles&tk=%5") + .arg(sub).arg(z).arg(y).arg(x).arg(QString::fromLatin1(kTk)); + QNetworkReply* reply = nam_.get(QNetworkRequest(QUrl(url))); + connect(reply, &QNetworkReply::finished, this, [this, reply, key, gen, onTex]() { + reply->deleteLater(); + if (gen != generation_ || kind_ != Satellite) return; + QImage img; + if (reply->error() != QNetworkReply::NoError || !img.loadFromData(reply->readAll())) return; + auto tex = makeTexture(img); + texCache_[key] = tex; + onTex(tex); + }); +} + vtkSmartPointer TileBasemap::buildFlat(int z, int x, int y, vtkSmartPointer tex) { const geopro::render::LonLatBox b = geopro::render::tileBounds(z, x, y); @@ -395,20 +490,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()) { - if (!haveBaseline_) { // 基准高程取首块中心 → 地形整体绕 z=0 起伏 - baseline_ = demElev(*dem, 0.5, 0.5); - 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"; - } + ensureBaseline(*dem); // 首块定基准(base/detail 共用)→ 地形连续 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 22bc6eb..4e29391 100644 --- a/src/app/TileBasemap.hpp +++ b/src/app/TileBasemap.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -46,6 +47,9 @@ 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 定基准高程(base/detail 共用→地形连续) + void ensureBaseLayer(); // 远处粗底图层(填到天边,治倾斜露黑边) + void fetchBaseTile(int z, int x, int y); // 单块粗底(sat+DEM→warp,持久不purge) 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, @@ -64,6 +68,8 @@ private: std::set inFlight_; // 在途瓦片(续到起伏/平面最终落地) std::map demCache_; // DEM 块缓存(key=DEMz/x/y),跨隐藏/重选复用 std::map> texCache_; // 影像纹理缓存,重选/缩放回看免重拉 + std::vector> baseTiles_; // 远处粗底层(持久,不随相机purge) + bool baseLoaded_ = false; double baseline_ = 0.0; // 基准高程(首块中心),地形绕 z=0 起伏 bool haveBaseline_ = false; bool terrainProbed_ = false; // 首次 fetchTerrain 打一行诊断日志