fix(vtk): 消除地形先平面后逐块形变-改DEM到位直接铺起伏块(拉不到才降级平面)+DEM缓存(同祖先块复用)减少逐块卡顿
This commit is contained in:
parent
33e9949623
commit
d99e5c61f4
|
|
@ -62,6 +62,25 @@ void unpackKey(long long key, int& z, int& x, int& y) {
|
||||||
y = static_cast<int>(key & 0x3FFFFF);
|
y = static_cast<int>(key & 0x3FFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QImage → vtkTexture:转 RGBA + 垂直翻转,使纹理 v=0 对应瓦片南边(与 PlaneSource tcoord 一致)。
|
||||||
|
vtkSmartPointer<vtkTexture> makeTexture(const QImage& img) {
|
||||||
|
const QImage rgba = img.convertToFormat(QImage::Format_RGBA8888);
|
||||||
|
const int w = rgba.width(), h = rgba.height();
|
||||||
|
if (w <= 0 || h <= 0) return nullptr;
|
||||||
|
vtkNew<vtkImageData> vimg;
|
||||||
|
vimg->SetDimensions(w, h, 1);
|
||||||
|
vimg->AllocateScalars(VTK_UNSIGNED_CHAR, 4);
|
||||||
|
for (int row = 0; row < h; ++row) {
|
||||||
|
const uchar* src = rgba.scanLine(h - 1 - row);
|
||||||
|
auto* dst = static_cast<uchar*>(vimg->GetScalarPointer(0, row, 0));
|
||||||
|
std::memcpy(dst, src, static_cast<size_t>(w) * 4);
|
||||||
|
}
|
||||||
|
auto tex = vtkSmartPointer<vtkTexture>::New();
|
||||||
|
tex->SetInputData(vimg);
|
||||||
|
tex->InterpolateOn();
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
// Terrarium 像素解码高程:(fx,fy)∈[0,1],fy=0 北/顶行。
|
// Terrarium 像素解码高程:(fx,fy)∈[0,1],fy=0 北/顶行。
|
||||||
double demElev(const QImage& dem, double fx, double fy) {
|
double demElev(const QImage& dem, double fx, double fy) {
|
||||||
const int w = dem.width(), h = dem.height();
|
const int w = dem.width(), h = dem.height();
|
||||||
|
|
@ -112,7 +131,7 @@ 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();
|
||||||
terrainInFlight_.clear();
|
demCache_.clear();
|
||||||
desired_.clear();
|
desired_.clear();
|
||||||
terrainProbed_ = false;
|
terrainProbed_ = false;
|
||||||
kind_ = kind;
|
kind_ = kind;
|
||||||
|
|
@ -224,68 +243,98 @@ void TileBasemap::fetchTile(int z, int x, int y, long long key) {
|
||||||
QNetworkReply* reply = nam_.get(QNetworkRequest(QUrl(url)));
|
QNetworkReply* reply = nam_.get(QNetworkRequest(QUrl(url)));
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply, key, z, x, y, gen]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply, key, z, x, y, gen]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
inFlight_.erase(key);
|
// inFlight 保持到瓦片最终落地(起伏/平面),使旧层在新块就位前不被清理 → 无空白闪烁。
|
||||||
if (gen != generation_) return; // 换源/隐藏后丢弃
|
|
||||||
if (kind_ == Hidden) return;
|
|
||||||
if (desired_.find(key) == desired_.end()) return; // 已移出视野
|
|
||||||
QImage img;
|
QImage img;
|
||||||
if (!placed_.count(key) && reply->error() == QNetworkReply::NoError &&
|
const bool stale = (gen != generation_) || kind_ == Hidden ||
|
||||||
img.loadFromData(reply->readAll())) {
|
desired_.find(key) == desired_.end() || placed_.count(key);
|
||||||
placeTile(key, z, x, y, img); // 仅新瓦片落地;其余(失败/已存在)只推动清理
|
const bool ok = !stale && reply->error() == QNetworkReply::NoError &&
|
||||||
if (kind_ == Satellite) fetchTerrain(z, x, y, key); // 平面就位后拉 DEM 换起伏
|
img.loadFromData(reply->readAll());
|
||||||
|
if (!ok) {
|
||||||
|
inFlight_.erase(key);
|
||||||
|
purgeStale();
|
||||||
|
if (rw_) rw_->Render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto tex = makeTexture(img);
|
||||||
|
if (kind_ == Satellite) {
|
||||||
|
fetchTerrain(z, x, y, key, tex); // 拉 DEM 后直接落地起伏块(inFlight 续到那时)
|
||||||
|
} else {
|
||||||
|
placeActor(key, buildFlat(z, x, y, tex)); // 街道图无地形 → 直接平面
|
||||||
|
inFlight_.erase(key);
|
||||||
|
purgeStale();
|
||||||
|
if (rw_) rw_->Render();
|
||||||
}
|
}
|
||||||
purgeStale(); // 本块已解决 → 若整轮落地则清理被顶替的旧层
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileBasemap::placeTile(long long key, int z, int x, int y, const QImage& img) {
|
void TileBasemap::placeActor(long long key, vtkSmartPointer<vtkActor> actor) {
|
||||||
|
if (!actor) return;
|
||||||
|
scene_.addActor(actor);
|
||||||
|
placed_[key] = actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkActor> TileBasemap::buildFlat(int z, int x, int y,
|
||||||
|
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);
|
||||||
const auto sw = frame_->toLocal(b.south, b.west);
|
const auto sw = frame_->toLocal(b.south, b.west);
|
||||||
const auto se = frame_->toLocal(b.south, b.east);
|
const auto se = frame_->toLocal(b.south, b.east);
|
||||||
const auto nw = frame_->toLocal(b.north, b.west);
|
const auto nw = frame_->toLocal(b.north, b.west);
|
||||||
const double gz = kGroundZ + (z - kMinZoom) * kZEps; // 高层级略抬高,压在旧层之上防共面闪烁
|
const double gz = kGroundZ + (z - kMinZoom) * kZEps; // 高层级略抬高,压在旧层之上防共面闪烁
|
||||||
|
|
||||||
// QImage → vtkImageData(RGBA),垂直翻转:使纹理 v=0 对应瓦片南边(与下方 PlaneSource tcoord 一致)。
|
// PlaneSource 自动 tcoord:origin=SW→u 西0东1、v 南0北1(与翻转后纹理对齐)。
|
||||||
const QImage rgba = img.convertToFormat(QImage::Format_RGBA8888);
|
|
||||||
const int w = rgba.width(), h = rgba.height();
|
|
||||||
if (w <= 0 || h <= 0) return;
|
|
||||||
vtkNew<vtkImageData> vimg;
|
|
||||||
vimg->SetDimensions(w, h, 1);
|
|
||||||
vimg->AllocateScalars(VTK_UNSIGNED_CHAR, 4);
|
|
||||||
for (int row = 0; row < h; ++row) {
|
|
||||||
const uchar* src = rgba.scanLine(h - 1 - row);
|
|
||||||
auto* dst = static_cast<uchar*>(vimg->GetScalarPointer(0, row, 0));
|
|
||||||
std::memcpy(dst, src, static_cast<size_t>(w) * 4);
|
|
||||||
}
|
|
||||||
vtkNew<vtkTexture> tex;
|
|
||||||
tex->SetInputData(vimg);
|
|
||||||
tex->InterpolateOn();
|
|
||||||
|
|
||||||
// PlaneSource 自动 tcoord:origin→(0,0)、point1→(1,0)、point2→(0,1)。
|
|
||||||
// origin=SW(西南)、point1=SE(东) → u 西→东;point2=NW(北) → v 南→北。与翻转后纹理对齐。
|
|
||||||
vtkNew<vtkPlaneSource> plane;
|
vtkNew<vtkPlaneSource> plane;
|
||||||
plane->SetOrigin(sw.x, sw.y, gz);
|
plane->SetOrigin(sw.x, sw.y, gz);
|
||||||
plane->SetPoint1(se.x, se.y, gz);
|
plane->SetPoint1(se.x, se.y, gz);
|
||||||
plane->SetPoint2(nw.x, nw.y, gz);
|
plane->SetPoint2(nw.x, nw.y, gz);
|
||||||
|
|
||||||
vtkNew<vtkPolyDataMapper> mapper;
|
vtkNew<vtkPolyDataMapper> mapper;
|
||||||
mapper->SetInputConnection(plane->GetOutputPort());
|
mapper->SetInputConnection(plane->GetOutputPort());
|
||||||
auto actor = vtkSmartPointer<vtkActor>::New();
|
auto actor = vtkSmartPointer<vtkActor>::New();
|
||||||
actor->SetMapper(mapper);
|
actor->SetMapper(mapper);
|
||||||
actor->SetTexture(tex);
|
actor->SetTexture(tex);
|
||||||
actor->GetProperty()->LightingOff(); // 底图不受场景光照
|
actor->GetProperty()->LightingOff(); // 底图不受场景光照
|
||||||
|
return actor;
|
||||||
scene_.addActor(actor);
|
|
||||||
placed_[key] = actor;
|
|
||||||
if (rw_) rw_->Render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileBasemap::fetchTerrain(int z, int x, int y, long long key) {
|
void TileBasemap::fetchTerrain(int z, int x, int y, long long key, vtkSmartPointer<vtkTexture> tex) {
|
||||||
if (terrainInFlight_.count(key)) return;
|
|
||||||
// Terrarium 数据约到 z15;更高层级取覆盖本块的祖先 DEM 瓦片,按经纬采样其子区域。
|
// Terrarium 数据约到 z15;更高层级取覆盖本块的祖先 DEM 瓦片,按经纬采样其子区域。
|
||||||
const int dz = std::min(z, kDemMaxZoom);
|
const int dz = std::min(z, kDemMaxZoom);
|
||||||
const int shift = z - dz;
|
const int shift = z - dz;
|
||||||
const int dx = x >> shift, dy = y >> shift;
|
const int dx = x >> shift, dy = y >> shift;
|
||||||
|
const long long demKey = tileKey(dz, dx, dy);
|
||||||
|
|
||||||
|
// 落地一块瓦片: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";
|
||||||
|
}
|
||||||
|
placeActor(key, buildWarped(z, x, y, dz, dx, dy, tex, *dem));
|
||||||
|
} else {
|
||||||
|
placeActor(key, buildFlat(z, x, y, tex)); // DEM 拉不到 → 平面兜底
|
||||||
|
}
|
||||||
|
inFlight_.erase(key);
|
||||||
|
purgeStale();
|
||||||
|
if (rw_) rw_->Render();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 命中缓存:同一祖先 DEM 块的多个瓦片瞬间起伏,免重复网络。
|
||||||
|
auto cached = demCache_.find(demKey);
|
||||||
|
if (cached != demCache_.end()) {
|
||||||
|
place(&cached->second);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!terrainProbed_) {
|
if (!terrainProbed_) {
|
||||||
terrainProbed_ = true;
|
terrainProbed_ = true;
|
||||||
qInfo() << "[basemap] 首次拉DEM 卫星z=" << z << " → DEMz=" << dz << "(" << dx << "," << dy
|
qInfo() << "[basemap] 首次拉DEM 卫星z=" << z << " → DEMz=" << dz << "(" << dx << "," << dy
|
||||||
|
|
@ -298,53 +347,30 @@ void TileBasemap::fetchTerrain(int z, int x, int y, long long key) {
|
||||||
.arg(dx)
|
.arg(dx)
|
||||||
.arg(dy);
|
.arg(dy);
|
||||||
const int gen = generation_;
|
const int gen = generation_;
|
||||||
terrainInFlight_.insert(key);
|
|
||||||
QNetworkReply* reply = nam_.get(QNetworkRequest(QUrl(url)));
|
QNetworkReply* reply = nam_.get(QNetworkRequest(QUrl(url)));
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply, key, z, x, y, dz, dx, dy, gen]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply, key, demKey, gen, place]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
terrainInFlight_.erase(key);
|
if (gen != generation_ || kind_ != Satellite ||
|
||||||
if (gen != generation_ || kind_ != Satellite) return;
|
desired_.find(key) == desired_.end() || placed_.count(key)) {
|
||||||
if (placed_.find(key) == placed_.end()) return; // 平面块已被清理
|
inFlight_.erase(key); // 过期/移出视野 → 不落地
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
purgeStale();
|
||||||
qWarning() << "[basemap] DEM 拉取失败" << reply->url().toString() << reply->errorString();
|
if (rw_) rw_->Render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QImage dem;
|
QImage dem;
|
||||||
if (!dem.loadFromData(reply->readAll())) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
qWarning() << "[basemap] DEM 解码失败" << reply->url().toString();
|
qWarning() << "[basemap] DEM 拉取失败(降级平面)" << reply->url().toString()
|
||||||
return;
|
<< reply->errorString();
|
||||||
|
} else if (!dem.loadFromData(reply->readAll())) {
|
||||||
|
qWarning() << "[basemap] DEM 解码失败(降级平面)" << reply->url().toString();
|
||||||
|
dem = QImage();
|
||||||
|
} else {
|
||||||
|
demCache_[demKey] = dem; // 缓存供同祖先块复用
|
||||||
}
|
}
|
||||||
applyTerrain(key, z, x, y, dz, dx, dy, dem);
|
place(dem.isNull() ? nullptr : &dem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileBasemap::applyTerrain(long long key, int sz, int sx, int sy, int dz, int dx, int dy,
|
|
||||||
const QImage& dem) {
|
|
||||||
auto it = placed_.find(key);
|
|
||||||
if (it == placed_.end()) return;
|
|
||||||
vtkSmartPointer<vtkTexture> tex = it->second->GetTexture(); // 复用已贴卫星纹理
|
|
||||||
if (!tex) return;
|
|
||||||
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 首块高程范围[" << mn << ","
|
|
||||||
<< mx << "]m 起伏=" << (mx - mn) << "m";
|
|
||||||
}
|
|
||||||
auto warped = buildWarped(sz, sx, sy, dz, dx, dy, tex, dem);
|
|
||||||
scene_.renderer()->RemoveViewProp(it->second);
|
|
||||||
scene_.addActor(warped);
|
|
||||||
it->second = warped;
|
|
||||||
if (rw_) rw_->Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
vtkSmartPointer<vtkActor> TileBasemap::buildWarped(int sz, int sx, int sy, int dz, int dx, int dy,
|
vtkSmartPointer<vtkActor> TileBasemap::buildWarped(int sz, int sx, int sy, int dz, int dx, int dy,
|
||||||
vtkSmartPointer<vtkTexture> tex,
|
vtkSmartPointer<vtkTexture> tex,
|
||||||
const QImage& dem) {
|
const QImage& dem) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
|
|
@ -15,7 +16,6 @@ class vtkTexture;
|
||||||
class vtkRenderWindow;
|
class vtkRenderWindow;
|
||||||
class vtkInteractorObserver;
|
class vtkInteractorObserver;
|
||||||
class vtkCallbackCommand;
|
class vtkCallbackCommand;
|
||||||
class QImage;
|
|
||||||
namespace geopro::render { class Scene; }
|
namespace geopro::render { class Scene; }
|
||||||
namespace geopro::core { class GeoLocalFrame; }
|
namespace geopro::core { class GeoLocalFrame; }
|
||||||
|
|
||||||
|
|
@ -41,10 +41,11 @@ private:
|
||||||
void purgeStale(); // 本轮请求全部落地后再删旧层瓦片,避免缩放空白闪烁
|
void purgeStale(); // 本轮请求全部落地后再删旧层瓦片,避免缩放空白闪烁
|
||||||
bool computeView(double& centerLat, double& centerLon, int& zoom) const;
|
bool computeView(double& centerLat, double& centerLon, int& zoom) const;
|
||||||
void fetchTile(int z, int x, int y, long long key);
|
void fetchTile(int z, int x, int y, long long key);
|
||||||
void placeTile(long long key, int z, int x, int y, const QImage& img);
|
void fetchTerrain(int z, int x, int y, long long key,
|
||||||
void fetchTerrain(int z, int x, int y, long long key); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)
|
vtkSmartPointer<vtkTexture> tex); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)后落地
|
||||||
void applyTerrain(long long key, int sz, int sx, int sy, int dz, int dx, int dy,
|
void placeActor(long long key, vtkSmartPointer<vtkActor> actor);
|
||||||
const QImage& dem); // 平面块换起伏块
|
vtkSmartPointer<vtkActor> buildFlat(int z, int x, int y,
|
||||||
|
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,
|
||||||
vtkSmartPointer<vtkTexture> tex,
|
vtkSmartPointer<vtkTexture> tex,
|
||||||
const QImage& dem); // DEM 位移网格 + 卫星贴图
|
const QImage& dem); // DEM 位移网格 + 卫星贴图
|
||||||
|
|
@ -58,8 +59,8 @@ private:
|
||||||
int generation_ = 0; // show/hide/换源 自增,丢弃过期回包
|
int generation_ = 0; // show/hide/换源 自增,丢弃过期回包
|
||||||
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::set<long long> terrainInFlight_; // DEM 瓦片在途请求
|
std::map<long long, QImage> demCache_; // DEM 块缓存(key=DEMz/x/y),避免重复拉
|
||||||
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 打一行诊断日志
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue