From 52f7a7d5e8fad845ba012e4e50b1a64f3a10cb33 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 17 Jun 2026 12:10:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(vtk):=20=E5=BA=95=E5=9B=BE=E6=8C=89?= =?UTF-8?q?=E7=9B=B8=E6=9C=BA=E5=8F=AF=E8=A7=86=E8=8C=83=E5=9B=B4=E8=A6=86?= =?UTF-8?q?=E7=9B=96(=E8=A7=86=E9=94=A5=E4=BA=A4z=3D0,=E6=B2=BB=E6=97=8B?= =?UTF-8?q?=E8=BD=AC=E9=BB=91=E8=BE=B9,=E5=B8=A6=E4=B8=8A=E9=99=90+?= =?UTF-8?q?=E5=9B=9E=E9=80=80)+=E5=BD=B1=E5=83=8F/DEM=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E8=B7=A8=E9=9A=90=E8=97=8F-=E9=87=8D=E9=80=89=E5=A4=8D?= =?UTF-8?q?=E7=94=A8(=E6=B2=BB=E9=87=8D=E9=80=89=E6=85=A2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/TileBasemap.cpp | 97 ++++++++++++++++++++++++++++++++++++++--- src/app/TileBasemap.hpp | 5 ++- 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/src/app/TileBasemap.cpp b/src/app/TileBasemap.cpp index ac2029c..c1bb4a5 100644 --- a/src/app/TileBasemap.cpp +++ b/src/app/TileBasemap.cpp @@ -39,7 +39,8 @@ namespace geopro::app { namespace { // 天地图 WMTS 令牌(与轨迹图 trajectory_map.html 同源)。 const char* kTk = "aca91d8c9f59a4f779f39061b8a07737"; -constexpr int kRadius = 4; // 中心瓦片 ±4 → 最多 9x9=81 块,留旋转/平移余量(治近距旋转黑边) +constexpr int kRadius = 4; // 回退用:算不出可视范围时中心 ±4 +constexpr int kMaxTilesPerSide = 13; // 单边瓦片上限(防倾斜时可视范围过大拉爆请求) constexpr int kMinZoom = 3; constexpr int kMaxZoom = 18; constexpr double kGroundZ = 0.0; // 底图置于 z=0 地面参考(剖面深度向下为负,落其下) @@ -137,8 +138,8 @@ void TileBasemap::show(Kind kind) { for (auto& kv : placed_) scene_.renderer()->RemoveViewProp(kv.second); placed_.clear(); inFlight_.clear(); - demCache_.clear(); desired_.clear(); + // demCache_/texCache_ 跨隐藏-重选保留 → 重选地图秒出,不重拉。 terrainProbed_ = false; kind_ = kind; if (kind == Hidden) { @@ -177,6 +178,47 @@ bool TileBasemap::computeView(double& centerLat, double& centerLon, int& zoom) c return true; } +bool TileBasemap::visibleGroundBox(double& west, double& south, double& east, double& north) const { + auto* ren = scene_.renderer(); + if (!ren) return false; + const int* sz = ren->GetSize(); + if (!sz || sz[0] <= 0 || sz[1] <= 0) return false; + const double W = sz[0], H = sz[1]; + const double corners[4][2] = {{0, 0}, {W, 0}, {0, H}, {W, H}}; + + double minX = 1e30, minY = 1e30, maxX = -1e30, maxY = -1e30; + int valid = 0; + for (auto& c : corners) { + double nearP[4], farP[4]; + ren->SetDisplayPoint(c[0], c[1], 0.0); + ren->DisplayToWorld(); + ren->GetWorldPoint(nearP); + ren->SetDisplayPoint(c[0], c[1], 1.0); + ren->DisplayToWorld(); + ren->GetWorldPoint(farP); + if (nearP[3] == 0.0 || farP[3] == 0.0) continue; + for (int k = 0; k < 3; ++k) { + nearP[k] /= nearP[3]; + farP[k] /= farP[3]; + } + const double dz = farP[2] - nearP[2]; + if (std::abs(dz) < 1e-9) continue; + const double t = (kGroundZ - nearP[2]) / dz; // 射线交 z=0 地面 + if (t < 0.0 || t > 1.5) continue; // 该角看向地平线之外/反向 → 跳过 + const double gx = nearP[0] + t * (farP[0] - nearP[0]); + const double gy = nearP[1] + t * (farP[1] - nearP[1]); + minX = std::min(minX, gx); maxX = std::max(maxX, gx); + minY = std::min(minY, gy); maxY = std::max(maxY, gy); + ++valid; + } + if (valid < 2) return false; // 看不到地面(地平线视角) → 让调用方回退 + + const auto sw = frame_->toLatLon(minX, minY); // 局部米 → 经纬 + const auto ne = frame_->toLatLon(maxX, maxY); + west = sw.lon; south = sw.lat; east = ne.lon; north = ne.lat; + return true; +} + void TileBasemap::refresh() { if (kind_ == Hidden || refreshing_) return; refreshing_ = true; @@ -188,16 +230,39 @@ void TileBasemap::refresh() { return; } - const geopro::render::TileXY center = geopro::render::lonLatToTile(clon, clat, zoom); const int n = 1 << zoom; + const geopro::render::TileXY center = geopro::render::lonLatToTile(clon, clat, zoom); + int x0 = center.x - kRadius, x1 = center.x + kRadius; + int y0 = center.y - kRadius, y1 = center.y + kRadius; + + // 优先按相机可视范围覆盖(治倾斜/旋转黑边);算不出则用上面的中心±半径回退。 + double w, s, e, nn; + if (visibleGroundBox(w, s, e, nn)) { + const geopro::render::TileXY tnw = geopro::render::lonLatToTile(w, nn, zoom); // 西北 + const geopro::render::TileXY tse = geopro::render::lonLatToTile(e, s, zoom); // 东南 + x0 = std::min(tnw.x, tse.x) - 1; // 各留 1 圈余量 + x1 = std::max(tnw.x, tse.x) + 1; + y0 = std::min(tnw.y, tse.y) - 1; + y1 = std::max(tnw.y, tse.y) + 1; + // 上限保护:单边超 kMaxTilesPerSide 则以中心截断(倾斜看地平线时范围会爆)。 + if (x1 - x0 + 1 > kMaxTilesPerSide) { + const int cx = (x0 + x1) / 2; + x0 = cx - kMaxTilesPerSide / 2; + x1 = cx + kMaxTilesPerSide / 2; + } + if (y1 - y0 + 1 > kMaxTilesPerSide) { + const int cy = (y0 + y1) / 2; + y0 = cy - kMaxTilesPerSide / 2; + y1 = cy + kMaxTilesPerSide / 2; + } + } + desired_.clear(); - for (int dy = -kRadius; dy <= kRadius; ++dy) { - for (int dx = -kRadius; dx <= kRadius; ++dx) { - const int tx = center.x + dx, ty = center.y + dy; + for (int ty = y0; ty <= y1; ++ty) + for (int tx = x0; tx <= x1; ++tx) { if (tx < 0 || ty < 0 || tx >= n || ty >= n) continue; desired_.insert(tileKey(zoom, tx, ty)); } - } // 拉取缺失瓦片(旧层暂不删,留作回退;新层落地后由 purgeStale 清理)。 for (long long key : desired_) { @@ -230,6 +295,22 @@ void TileBasemap::purgeStale() { } void TileBasemap::fetchTile(int z, int x, int y, long long key) { + // 命中影像缓存 → 不走网络,直接落地(DEM 多半也已缓存)。重选地图/缩放回看即秒出。 + auto cit = texCache_.find(key); + if (cit != texCache_.end()) { + inFlight_.insert(key); + auto tex = cit->second; + if (kind_ == Satellite) { + fetchTerrain(z, x, y, key, tex); + } else { + placeActor(key, buildFlat(z, x, y, tex)); + inFlight_.erase(key); + purgeStale(); + if (rw_) rw_->Render(); + } + return; + } + const QString layerDir = (kind_ == Satellite) ? QStringLiteral("img_w") : QStringLiteral("vec_w"); const QString layer = (kind_ == Satellite) ? QStringLiteral("img") : QStringLiteral("vec"); const int sub = (x + y) % 8; // 子域负载分担 t0-t7 @@ -262,6 +343,8 @@ void TileBasemap::fetchTile(int z, int x, int y, long long key) { return; } auto tex = makeTexture(img); + if (texCache_.size() > 1200) texCache_.clear(); // 兜底内存;在用纹理由 actor 自身保活 + texCache_[key] = tex; // 缓存供重选/缩放回看复用 if (kind_ == Satellite) { fetchTerrain(z, x, y, key, tex); // 拉 DEM 后直接落地起伏块(inFlight 续到那时) } else { diff --git a/src/app/TileBasemap.hpp b/src/app/TileBasemap.hpp index 7beeb11..22bc6eb 100644 --- a/src/app/TileBasemap.hpp +++ b/src/app/TileBasemap.hpp @@ -40,6 +40,8 @@ private: void ensureObserver(); // 首次显示时挂到交互样式的 EndInteractionEvent void purgeStale(); // 本轮请求全部落地后再删旧层瓦片,避免缩放空白闪烁 bool computeView(double& centerLat, double& centerLon, int& zoom) const; + // 相机视锥 ∩ z=0 地面 → 可视经纬范围(治倾斜/旋转黑边);算不出(看向地平线)返回 false。 + bool visibleGroundBox(double& west, double& south, double& east, double& north) const; void fetchTile(int z, int x, int y, long long key); void fetchTerrain(int z, int x, int y, long long key, vtkSmartPointer tex); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)后落地 @@ -60,7 +62,8 @@ private: std::map> placed_; // 已贴瓦片:key→actor std::set desired_; // 当前视野应显示的瓦片 key std::set inFlight_; // 在途瓦片(续到起伏/平面最终落地) - std::map demCache_; // DEM 块缓存(key=DEMz/x/y),避免重复拉 + std::map demCache_; // DEM 块缓存(key=DEMz/x/y),跨隐藏/重选复用 + std::map> texCache_; // 影像纹理缓存,重选/缩放回看免重拉 double baseline_ = 0.0; // 基准高程(首块中心),地形绕 z=0 起伏 bool haveBaseline_ = false; bool terrainProbed_ = false; // 首次 fetchTerrain 打一行诊断日志