feat/vtk-3d-view #7
|
|
@ -14,11 +14,15 @@
|
||||||
#include <vtkCallbackCommand.h>
|
#include <vtkCallbackCommand.h>
|
||||||
#include <vtkCamera.h>
|
#include <vtkCamera.h>
|
||||||
#include <vtkCommand.h>
|
#include <vtkCommand.h>
|
||||||
|
#include <vtkDataArray.h>
|
||||||
#include <vtkImageData.h>
|
#include <vtkImageData.h>
|
||||||
#include <vtkInteractorObserver.h>
|
#include <vtkInteractorObserver.h>
|
||||||
#include <vtkNew.h>
|
#include <vtkNew.h>
|
||||||
#include <vtkPlaneSource.h>
|
#include <vtkPlaneSource.h>
|
||||||
|
#include <vtkPointData.h>
|
||||||
|
#include <vtkPolyData.h>
|
||||||
#include <vtkPolyDataMapper.h>
|
#include <vtkPolyDataMapper.h>
|
||||||
|
#include <vtkPoints.h>
|
||||||
#include <vtkProperty.h>
|
#include <vtkProperty.h>
|
||||||
#include <vtkRenderWindow.h>
|
#include <vtkRenderWindow.h>
|
||||||
#include <vtkRenderWindowInteractor.h>
|
#include <vtkRenderWindowInteractor.h>
|
||||||
|
|
@ -44,12 +48,28 @@ constexpr double kPi = 3.14159265358979323846;
|
||||||
constexpr double kEarthCirc = 40075016.686; // 赤道周长(米) = z0 单瓦片地面尺寸
|
constexpr double kEarthCirc = 40075016.686; // 赤道周长(米) = z0 单瓦片地面尺寸
|
||||||
constexpr double kTilesAcross = 4.0; // 视野跨度目标覆盖瓦片数(决定层级)
|
constexpr double kTilesAcross = 4.0; // 视野跨度目标覆盖瓦片数(决定层级)
|
||||||
|
|
||||||
|
// 地面起伏:AWS Terrarium DEM 瓦片(免费/无 token,XYZ Web-Mercator 与卫星瓦片对齐)。
|
||||||
|
// elev(米) = R*256 + G + B/256 - 32768。数据到约 z15,更高层级无 DEM → 保持平面。
|
||||||
|
constexpr int kDemMaxZoom = 15;
|
||||||
|
constexpr int kTerrainGrid = 32; // 每瓦片网格分辨率(33x33 顶点)
|
||||||
|
constexpr double kTerrainExag = 1.0; // 地形垂向夸张(1=真实高程)
|
||||||
|
|
||||||
// key 打包:z<<44 | x<<22 | y(z≤18, x/y<2^18 < 2^22)。
|
// key 打包:z<<44 | x<<22 | y(z≤18, x/y<2^18 < 2^22)。
|
||||||
void unpackKey(long long key, int& z, int& x, int& y) {
|
void unpackKey(long long key, int& z, int& x, int& y) {
|
||||||
z = static_cast<int>(key >> 44);
|
z = static_cast<int>(key >> 44);
|
||||||
x = static_cast<int>((key >> 22) & 0x3FFFFF);
|
x = static_cast<int>((key >> 22) & 0x3FFFFF);
|
||||||
y = static_cast<int>(key & 0x3FFFFF);
|
y = static_cast<int>(key & 0x3FFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Terrarium 像素解码高程:(fx,fy)∈[0,1],fy=0 北/顶行。
|
||||||
|
double demElev(const QImage& dem, double fx, double fy) {
|
||||||
|
const int w = dem.width(), h = dem.height();
|
||||||
|
if (w <= 0 || h <= 0) return 0.0;
|
||||||
|
const int px = std::clamp(static_cast<int>(std::lround(fx * (w - 1))), 0, w - 1);
|
||||||
|
const int py = std::clamp(static_cast<int>(std::lround(fy * (h - 1))), 0, h - 1);
|
||||||
|
const QRgb c = dem.pixel(px, py);
|
||||||
|
return qRed(c) * 256.0 + qGreen(c) + qBlue(c) / 256.0 - 32768.0;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
long long TileBasemap::tileKey(int z, int x, int y) {
|
long long TileBasemap::tileKey(int z, int x, int y) {
|
||||||
|
|
@ -91,6 +111,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();
|
||||||
desired_.clear();
|
desired_.clear();
|
||||||
kind_ = kind;
|
kind_ = kind;
|
||||||
if (kind == Hidden) {
|
if (kind == Hidden) {
|
||||||
|
|
@ -209,6 +230,7 @@ void TileBasemap::fetchTile(int z, int x, int y, long long key) {
|
||||||
if (!placed_.count(key) && reply->error() == QNetworkReply::NoError &&
|
if (!placed_.count(key) && reply->error() == QNetworkReply::NoError &&
|
||||||
img.loadFromData(reply->readAll())) {
|
img.loadFromData(reply->readAll())) {
|
||||||
placeTile(key, z, x, y, img); // 仅新瓦片落地;其余(失败/已存在)只推动清理
|
placeTile(key, z, x, y, img); // 仅新瓦片落地;其余(失败/已存在)只推动清理
|
||||||
|
if (kind_ == Satellite) fetchTerrain(z, x, y, key); // 平面就位后拉 DEM 换起伏
|
||||||
}
|
}
|
||||||
purgeStale(); // 本块已解决 → 若整轮落地则清理被顶替的旧层
|
purgeStale(); // 本块已解决 → 若整轮落地则清理被顶替的旧层
|
||||||
});
|
});
|
||||||
|
|
@ -256,4 +278,86 @@ void TileBasemap::placeTile(long long key, int z, int x, int y, const QImage& im
|
||||||
if (rw_) rw_->Render();
|
if (rw_) rw_->Render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TileBasemap::fetchTerrain(int z, int x, int y, long long key) {
|
||||||
|
if (z > kDemMaxZoom) return; // 高层级无 DEM → 维持平面
|
||||||
|
if (terrainInFlight_.count(key)) return;
|
||||||
|
const QString url =
|
||||||
|
QStringLiteral("http://s3.amazonaws.com/elevation-tiles-prod/terrarium/%1/%2/%3.png")
|
||||||
|
.arg(z)
|
||||||
|
.arg(x)
|
||||||
|
.arg(y);
|
||||||
|
const int gen = generation_;
|
||||||
|
terrainInFlight_.insert(key);
|
||||||
|
QNetworkReply* reply = nam_.get(QNetworkRequest(QUrl(url)));
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply, key, z, x, y, gen]() {
|
||||||
|
reply->deleteLater();
|
||||||
|
terrainInFlight_.erase(key);
|
||||||
|
if (gen != generation_ || kind_ != Satellite) return;
|
||||||
|
if (placed_.find(key) == placed_.end()) return; // 平面块已被清理
|
||||||
|
if (reply->error() != QNetworkReply::NoError) return;
|
||||||
|
QImage dem;
|
||||||
|
if (!dem.loadFromData(reply->readAll())) return;
|
||||||
|
applyTerrain(key, z, x, y, dem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileBasemap::applyTerrain(long long key, int z, int x, int y, 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;
|
||||||
|
}
|
||||||
|
auto warped = buildWarped(z, x, y, tex, dem);
|
||||||
|
scene_.renderer()->RemoveViewProp(it->second);
|
||||||
|
scene_.addActor(warped);
|
||||||
|
it->second = warped;
|
||||||
|
if (rw_) rw_->Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkActor> TileBasemap::buildWarped(int z, int x, int y,
|
||||||
|
vtkSmartPointer<vtkTexture> tex,
|
||||||
|
const QImage& dem) {
|
||||||
|
const geopro::render::LonLatBox b = geopro::render::tileBounds(z, x, y);
|
||||||
|
const auto sw = frame_->toLocal(b.south, b.west);
|
||||||
|
const auto se = frame_->toLocal(b.south, b.east);
|
||||||
|
const auto nw = frame_->toLocal(b.north, b.west);
|
||||||
|
const double base = kGroundZ + (z - kMinZoom) * kZEps;
|
||||||
|
|
||||||
|
// PlaneSource(等距圆柱下平面插值即正确 x/y) + 自动 tcoord;再按高程位移每点 Z。
|
||||||
|
vtkNew<vtkPlaneSource> plane;
|
||||||
|
plane->SetOrigin(sw.x, sw.y, base);
|
||||||
|
plane->SetPoint1(se.x, se.y, base);
|
||||||
|
plane->SetPoint2(nw.x, nw.y, base);
|
||||||
|
plane->SetResolution(kTerrainGrid, kTerrainGrid);
|
||||||
|
plane->Update();
|
||||||
|
|
||||||
|
auto warped = vtkSmartPointer<vtkPolyData>::New();
|
||||||
|
warped->DeepCopy(plane->GetOutput());
|
||||||
|
vtkDataArray* tc = warped->GetPointData()->GetTCoords();
|
||||||
|
vtkPoints* pts = warped->GetPoints();
|
||||||
|
const vtkIdType n = pts->GetNumberOfPoints();
|
||||||
|
for (vtkIdType id = 0; id < n; ++id) {
|
||||||
|
double t[2];
|
||||||
|
tc->GetTuple(id, t);
|
||||||
|
const double fx = t[0], fy = 1.0 - t[1]; // tcoord v: 南0北1 → fy: 北0南1(对齐 DEM 顶行=北)
|
||||||
|
const double elev = demElev(dem, fx, fy);
|
||||||
|
double p[3];
|
||||||
|
pts->GetPoint(id, p);
|
||||||
|
p[2] = base + (elev - baseline_) * kTerrainExag;
|
||||||
|
pts->SetPoint(id, p);
|
||||||
|
}
|
||||||
|
pts->Modified();
|
||||||
|
|
||||||
|
vtkNew<vtkPolyDataMapper> mapper;
|
||||||
|
mapper->SetInputData(warped);
|
||||||
|
auto actor = vtkSmartPointer<vtkActor>::New();
|
||||||
|
actor->SetMapper(mapper);
|
||||||
|
actor->SetTexture(tex);
|
||||||
|
actor->GetProperty()->LightingOff();
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace geopro::app
|
} // namespace geopro::app
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
class vtkActor;
|
class vtkActor;
|
||||||
class vtkObject;
|
class vtkObject;
|
||||||
|
class vtkTexture;
|
||||||
class vtkRenderWindow;
|
class vtkRenderWindow;
|
||||||
class vtkInteractorObserver;
|
class vtkInteractorObserver;
|
||||||
class vtkCallbackCommand;
|
class vtkCallbackCommand;
|
||||||
|
|
@ -41,6 +42,10 @@ private:
|
||||||
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 placeTile(long long key, int z, int x, int y, const QImage& img);
|
||||||
|
void fetchTerrain(int z, int x, int y, long long key); // 拉对应 DEM 瓦片(卫星模式)
|
||||||
|
void applyTerrain(long long key, int z, int x, int y, const QImage& dem); // 平面块换起伏块
|
||||||
|
vtkSmartPointer<vtkActor> buildWarped(int z, int x, int y, vtkSmartPointer<vtkTexture> tex,
|
||||||
|
const QImage& dem); // DEM 位移网格 + 卫星贴图
|
||||||
static void onInteractionEnd(vtkObject*, unsigned long, void* clientData, void*);
|
static void onInteractionEnd(vtkObject*, unsigned long, void* clientData, void*);
|
||||||
|
|
||||||
geopro::render::Scene& scene_;
|
geopro::render::Scene& scene_;
|
||||||
|
|
@ -51,7 +56,10 @@ 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 瓦片在途请求
|
||||||
|
double baseline_ = 0.0; // 基准高程(首块中心),地形绕 z=0 起伏
|
||||||
|
bool haveBaseline_ = false;
|
||||||
vtkSmartPointer<vtkInteractorObserver> styleObs_; // 持引用保证回调期有效
|
vtkSmartPointer<vtkInteractorObserver> styleObs_; // 持引用保证回调期有效
|
||||||
vtkSmartPointer<vtkCallbackCommand> observer_;
|
vtkSmartPointer<vtkCallbackCommand> observer_;
|
||||||
bool refreshing_ = false;
|
bool refreshing_ = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue