feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
2 changed files with 94 additions and 8 deletions
Showing only changes of commit 52f7a7d5e8 - Show all commits

View File

@ -39,7 +39,8 @@ namespace geopro::app {
namespace { namespace {
// 天地图 WMTS 令牌(与轨迹图 trajectory_map.html 同源)。 // 天地图 WMTS 令牌(与轨迹图 trajectory_map.html 同源)。
const char* kTk = "aca91d8c9f59a4f779f39061b8a07737"; 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 kMinZoom = 3;
constexpr int kMaxZoom = 18; constexpr int kMaxZoom = 18;
constexpr double kGroundZ = 0.0; // 底图置于 z=0 地面参考(剖面深度向下为负,落其下) 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); for (auto& kv : placed_) scene_.renderer()->RemoveViewProp(kv.second);
placed_.clear(); placed_.clear();
inFlight_.clear(); inFlight_.clear();
demCache_.clear();
desired_.clear(); desired_.clear();
// demCache_/texCache_ 跨隐藏-重选保留 → 重选地图秒出,不重拉。
terrainProbed_ = false; terrainProbed_ = false;
kind_ = kind; kind_ = kind;
if (kind == Hidden) { if (kind == Hidden) {
@ -177,6 +178,47 @@ bool TileBasemap::computeView(double& centerLat, double& centerLon, int& zoom) c
return true; 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() { void TileBasemap::refresh() {
if (kind_ == Hidden || refreshing_) return; if (kind_ == Hidden || refreshing_) return;
refreshing_ = true; refreshing_ = true;
@ -188,16 +230,39 @@ void TileBasemap::refresh() {
return; return;
} }
const geopro::render::TileXY center = geopro::render::lonLatToTile(clon, clat, zoom);
const int n = 1 << 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(); desired_.clear();
for (int dy = -kRadius; dy <= kRadius; ++dy) { for (int ty = y0; ty <= y1; ++ty)
for (int dx = -kRadius; dx <= kRadius; ++dx) { for (int tx = x0; tx <= x1; ++tx) {
const int tx = center.x + dx, ty = center.y + dy;
if (tx < 0 || ty < 0 || tx >= n || ty >= n) continue; if (tx < 0 || ty < 0 || tx >= n || ty >= n) continue;
desired_.insert(tileKey(zoom, tx, ty)); desired_.insert(tileKey(zoom, tx, ty));
} }
}
// 拉取缺失瓦片(旧层暂不删,留作回退;新层落地后由 purgeStale 清理)。 // 拉取缺失瓦片(旧层暂不删,留作回退;新层落地后由 purgeStale 清理)。
for (long long key : desired_) { for (long long key : desired_) {
@ -230,6 +295,22 @@ void TileBasemap::purgeStale() {
} }
void TileBasemap::fetchTile(int z, int x, int y, long long key) { 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 layerDir = (kind_ == Satellite) ? QStringLiteral("img_w") : QStringLiteral("vec_w");
const QString layer = (kind_ == Satellite) ? QStringLiteral("img") : QStringLiteral("vec"); const QString layer = (kind_ == Satellite) ? QStringLiteral("img") : QStringLiteral("vec");
const int sub = (x + y) % 8; // 子域负载分担 t0-t7 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; return;
} }
auto tex = makeTexture(img); auto tex = makeTexture(img);
if (texCache_.size() > 1200) texCache_.clear(); // 兜底内存;在用纹理由 actor 自身保活
texCache_[key] = tex; // 缓存供重选/缩放回看复用
if (kind_ == Satellite) { if (kind_ == Satellite) {
fetchTerrain(z, x, y, key, tex); // 拉 DEM 后直接落地起伏块(inFlight 续到那时) fetchTerrain(z, x, y, key, tex); // 拉 DEM 后直接落地起伏块(inFlight 续到那时)
} else { } else {

View File

@ -40,6 +40,8 @@ private:
void ensureObserver(); // 首次显示时挂到交互样式的 EndInteractionEvent void ensureObserver(); // 首次显示时挂到交互样式的 EndInteractionEvent
void purgeStale(); // 本轮请求全部落地后再删旧层瓦片,避免缩放空白闪烁 void purgeStale(); // 本轮请求全部落地后再删旧层瓦片,避免缩放空白闪烁
bool computeView(double& centerLat, double& centerLon, int& zoom) const; 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 fetchTile(int z, int x, int y, long long key);
void fetchTerrain(int z, int x, int y, long long key, void fetchTerrain(int z, int x, int y, long long key,
vtkSmartPointer<vtkTexture> tex); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)后落地 vtkSmartPointer<vtkTexture> tex); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)后落地
@ -60,7 +62,8 @@ private:
std::map<long long, vtkSmartPointer<vtkActor>> placed_; // 已贴瓦片key→actor std::map<long long, vtkSmartPointer<vtkActor>> placed_; // 已贴瓦片key→actor
std::set<long long> desired_; // 当前视野应显示的瓦片 key std::set<long long> desired_; // 当前视野应显示的瓦片 key
std::set<long long> inFlight_; // 在途瓦片(续到起伏/平面最终落地) std::set<long long> inFlight_; // 在途瓦片(续到起伏/平面最终落地)
std::map<long long, QImage> demCache_; // DEM 块缓存(key=DEMz/x/y),避免重复拉 std::map<long long, QImage> demCache_; // DEM 块缓存(key=DEMz/x/y),跨隐藏/重选复用
std::map<long long, vtkSmartPointer<vtkTexture>> texCache_; // 影像纹理缓存,重选/缩放回看免重拉
double baseline_ = 0.0; // 基准高程(首块中心),地形绕 z=0 起伏 double baseline_ = 0.0; // 基准高程(首块中心),地形绕 z=0 起伏
bool haveBaseline_ = false; bool haveBaseline_ = false;
bool terrainProbed_ = false; // 首次 fetchTerrain 打一行诊断日志 bool terrainProbed_ = false; // 首次 fetchTerrain 打一行诊断日志