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 102 additions and 14 deletions
Showing only changes of commit b5bab42825 - Show all commits

View File

@ -41,6 +41,8 @@ namespace {
const char* kTk = "aca91d8c9f59a4f779f39061b8a07737"; const char* kTk = "aca91d8c9f59a4f779f39061b8a07737";
constexpr int kRadius = 4; // 回退用:算不出可视范围时中心 ±4 constexpr int kRadius = 4; // 回退用:算不出可视范围时中心 ±4
constexpr int kMaxTilesPerSide = 13; // 单边瓦片上限(防倾斜时可视范围过大拉爆请求) constexpr int kMaxTilesPerSide = 13; // 单边瓦片上限(防倾斜时可视范围过大拉爆请求)
constexpr int kBaseZoom = 13; // 远处粗底层级(单块~4.9km)
constexpr int kBaseRadius = 3; // 粗底半径 ±3 → 7x7 覆盖~34km填到天边
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,15 +139,20 @@ void TileBasemap::show(Kind kind) {
++generation_; // 旧回包(含换源前的层)按 generation 丢弃 ++generation_; // 旧回包(含换源前的层)按 generation 丢弃
for (auto& kv : placed_) scene_.renderer()->RemoveViewProp(kv.second); for (auto& kv : placed_) scene_.renderer()->RemoveViewProp(kv.second);
placed_.clear(); placed_.clear();
for (auto& a : baseTiles_) scene_.renderer()->RemoveViewProp(a);
baseTiles_.clear();
baseLoaded_ = false;
inFlight_.clear(); inFlight_.clear();
desired_.clear(); desired_.clear();
// demCache_/texCache_ 跨隐藏-重选保留 → 重选地图秒出,不重拉。 // demCache_/texCache_ 跨隐藏-重选保留 → 重选地图秒出,不重拉。
haveBaseline_ = false; // 数据可能已换位置 → 重算基准高程
terrainProbed_ = false; terrainProbed_ = false;
kind_ = kind; kind_ = kind;
if (kind == Hidden) { if (kind == Hidden) {
if (rw_) rw_->Render(); if (rw_) rw_->Render();
return; return;
} }
ensureBaseLayer(); // 先铺远处粗底(填到天边),再叠近处精细
refresh(); refresh();
} }
@ -362,6 +369,94 @@ void TileBasemap::placeActor(long long key, vtkSmartPointer<vtkActor> actor) {
placed_[key] = 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<vtkTexture> 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<vtkActor> 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<vtkActor> TileBasemap::buildFlat(int z, int x, int y, vtkSmartPointer<vtkActor> TileBasemap::buildFlat(int z, int x, int y,
vtkSmartPointer<vtkTexture> tex) { vtkSmartPointer<vtkTexture> tex) {
const geopro::render::LonLatBox b = geopro::render::tileBounds(z, x, y); 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/清理。 // 落地一块瓦片DEM 有效→起伏,否则→平面兜底;并推进 inFlight/清理。
auto place = [this, key, z, x, y, dz, dx, dy, tex](const QImage* dem) { auto place = [this, key, z, x, y, dz, dx, dy, tex](const QImage* dem) {
if (dem && !dem->isNull()) { if (dem && !dem->isNull()) {
if (!haveBaseline_) { // 基准高程取首块中心 → 地形整体绕 z=0 起伏 ensureBaseline(*dem); // 首块定基准(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";
}
placeActor(key, buildWarped(z, x, y, dz, dx, dy, tex, *dem)); placeActor(key, buildWarped(z, x, y, dz, dx, dy, tex, *dem));
} else { } else {
placeActor(key, buildFlat(z, x, y, tex)); // DEM 拉不到 → 平面兜底 placeActor(key, buildFlat(z, x, y, tex)); // DEM 拉不到 → 平面兜底

View File

@ -7,6 +7,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <set> #include <set>
#include <vector>
#include <vtkSmartPointer.h> #include <vtkSmartPointer.h>
@ -46,6 +47,9 @@ private:
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 取祖先块)后落地
void placeActor(long long key, vtkSmartPointer<vtkActor> actor); void placeActor(long long key, vtkSmartPointer<vtkActor> actor);
void ensureBaseline(const QImage& dem); // 首块 DEM 定基准高程(base/detail 共用→地形连续)
void ensureBaseLayer(); // 远处粗底图层(填到天边,治倾斜露黑边)
void fetchBaseTile(int z, int x, int y); // 单块粗底(sat+DEM→warp,持久不purge)
vtkSmartPointer<vtkActor> buildFlat(int z, int x, int y, vtkSmartPointer<vtkActor> buildFlat(int z, int x, int y,
vtkSmartPointer<vtkTexture> tex); // 平面瓦片(DEM 兜底) vtkSmartPointer<vtkTexture> tex); // 平面瓦片(DEM 兜底)
vtkSmartPointer<vtkActor> buildWarped(int sz, int sx, int sy, int dz, int dx, int dy, vtkSmartPointer<vtkActor> buildWarped(int sz, int sx, int sy, int dz, int dx, int dy,
@ -64,6 +68,8 @@ private:
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_; // 影像纹理缓存,重选/缩放回看免重拉 std::map<long long, vtkSmartPointer<vtkTexture>> texCache_; // 影像纹理缓存,重选/缩放回看免重拉
std::vector<vtkSmartPointer<vtkActor>> baseTiles_; // 远处粗底层(持久,不随相机purge)
bool baseLoaded_ = false;
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 打一行诊断日志