feat/vtk-3d-view #7
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<vtkTexture> tex); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)后落地
|
||||
|
|
@ -60,7 +62,8 @@ private:
|
|||
std::map<long long, vtkSmartPointer<vtkActor>> placed_; // 已贴瓦片:key→actor
|
||||
std::set<long long> desired_; // 当前视野应显示的瓦片 key
|
||||
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 起伏
|
||||
bool haveBaseline_ = false;
|
||||
bool terrainProbed_ = false; // 首次 fetchTerrain 打一行诊断日志
|
||||
|
|
|
|||
Loading…
Reference in New Issue