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
4 changed files with 189 additions and 2 deletions
Showing only changes of commit a588b651a6 - Show all commits

View File

@ -65,14 +65,15 @@ add_executable(geopro_desktop WIN32
ExportDatasetDialog.cpp
SettingsDialog.cpp
Logging.cpp
DatasetDimension.cpp)
DatasetDimension.cpp
TileBasemap.cpp)
target_include_directories(geopro_desktop PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
# QtKeychain FetchContent target / export
target_include_directories(geopro_desktop PRIVATE ${qtkeychain_SOURCE_DIR} ${qtkeychain_BINARY_DIR})
target_link_libraries(geopro_desktop PRIVATE
Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Svg
Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Svg Qt6::Network
Qt6::WebEngineWidgets Qt6::WebEngineQuick
${VTK_LIBRARIES}
ads::qt6advanceddocking

132
src/app/TileBasemap.cpp Normal file
View File

@ -0,0 +1,132 @@
#include "TileBasemap.hpp"
#include <cstring>
#include <QImage>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QString>
#include <QUrl>
#include <vtkActor.h>
#include <vtkImageData.h>
#include <vtkNew.h>
#include <vtkPlaneSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkTexture.h>
#include "Scene.hpp"
#include "geo/GeoLocalFrame.hpp"
#include "ground/TileMath.hpp"
namespace geopro::app {
namespace {
// 天地图 WMTS 令牌(与轨迹图 trajectory_map.html 同源)。
const char* kTk = "aca91d8c9f59a4f779f39061b8a07737";
constexpr int kZoom = 16; // 固定层级(MVP):单瓦片 ~数百米,半径覆盖典型测线
constexpr int kRadius = 3; // 中心瓦片 ±3 → 7x7=49 块,约数公里
constexpr double kGroundZ = 0.0; // 底图置于 z=0 地面参考(剖面深度向下为负,落其下)
} // namespace
TileBasemap::TileBasemap(geopro::render::Scene& scene, vtkRenderWindow* rw,
std::shared_ptr<geopro::core::GeoLocalFrame> frame, QObject* parent)
: QObject(parent), scene_(scene), rw_(rw), frame_(std::move(frame)) {}
void TileBasemap::clearTiles() {
for (auto& a : tiles_) scene_.renderer()->RemoveViewProp(a);
tiles_.clear();
}
void TileBasemap::hide() {
++generation_;
clearTiles();
if (rw_) rw_->Render();
}
void TileBasemap::show(Kind kind) {
clearTiles();
if (kind == Hidden) {
++generation_;
if (rw_) rw_->Render();
return;
}
const int myGen = ++generation_;
const auto c = frame_->toLatLon(0.0, 0.0); // 数据原点经纬(已重锚到真实剖面中心)
const geopro::render::TileXY center = geopro::render::lonLatToTile(c.lon, c.lat, kZoom);
const QString layerDir = (kind == Satellite) ? QStringLiteral("img_w") : QStringLiteral("vec_w");
const QString layer = (kind == Satellite) ? QStringLiteral("img") : QStringLiteral("vec");
const int n = 1 << kZoom;
for (int dy = -kRadius; dy <= kRadius; ++dy) {
for (int dx = -kRadius; dx <= kRadius; ++dx) {
const int tx = center.x + dx, ty = center.y + dy;
if (tx < 0 || ty < 0 || tx >= n || ty >= n) continue;
const int sub = (tx + ty) % 8; // 子域负载分担 t0-t7
const QString url =
QStringLiteral("http://t%1.tianditu.gov.cn/%2/wmts?service=wmts&request=GetTile"
"&version=1.0.0&LAYER=%3&tileMatrixSet=w&TileMatrix=%4&TileRow=%5"
"&TileCol=%6&style=default&format=tiles&tk=%7")
.arg(sub)
.arg(layerDir, layer)
.arg(kZoom)
.arg(ty)
.arg(tx)
.arg(QString::fromLatin1(kTk));
QNetworkReply* reply = nam_.get(QNetworkRequest(QUrl(url)));
connect(reply, &QNetworkReply::finished, this, [this, reply, tx, ty, myGen]() {
reply->deleteLater();
if (myGen != generation_) return; // 已被新的 show/hide 取代 → 丢弃
if (reply->error() != QNetworkReply::NoError) return;
QImage img;
if (!img.loadFromData(reply->readAll())) return;
placeTile(kZoom, tx, ty, img);
});
}
}
}
void TileBasemap::placeTile(int z, int x, int y, const QImage& img) {
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);
// QImage → vtkImageData(RGBA),垂直翻转:使纹理 v=0 对应瓦片南边(与下方 PlaneSource tcoord 一致)。
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 自动 tcoordorigin→(0,0)、point1→(1,0)、point2→(0,1)。
// origin=SW(西南)、point1=SE(东) → u 西→东point2=NW(北) → v 南→北。与翻转后纹理对齐。
vtkNew<vtkPlaneSource> plane;
plane->SetOrigin(sw.x, sw.y, kGroundZ);
plane->SetPoint1(se.x, se.y, kGroundZ);
plane->SetPoint2(nw.x, nw.y, kGroundZ);
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(plane->GetOutputPort());
auto actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
actor->SetTexture(tex);
actor->GetProperty()->LightingOff(); // 底图不受场景光照
scene_.addActor(actor);
tiles_.push_back(actor);
if (rw_) rw_->Render();
}
} // namespace geopro::app

44
src/app/TileBasemap.hpp Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <QNetworkAccessManager>
#include <QObject>
#include <memory>
#include <vector>
#include <vtkSmartPointer.h>
class vtkActor;
class vtkRenderWindow;
class QImage;
namespace geopro::render { class Scene; }
namespace geopro::core { class GeoLocalFrame; }
namespace geopro::app {
// 天地图 WMTS 底图层:以共享 GeoLocalFrame 原点为中心,异步拉取覆盖瓦片贴成地面纹理面(z=0)。
// 复用轨迹图同款 token/WMTS。瓦片经同一 frame 配准 → 自动与帘面/轨迹对齐。
// 注:本期固定 zoom + 固定覆盖半径(MVP),相机驱动 LOD/数据范围自适应后续再加。
class TileBasemap : public QObject {
Q_OBJECT
public:
enum Kind { Street = 0, Satellite = 1, Hidden = 2 };
TileBasemap(geopro::render::Scene& scene, vtkRenderWindow* rw,
std::shared_ptr<geopro::core::GeoLocalFrame> frame, QObject* parent = nullptr);
void show(Kind kind); // 拉取并显示Hidden 等同 hide
void hide(); // 移除全部瓦片面
private:
void clearTiles();
void placeTile(int z, int x, int y, const QImage& img);
geopro::render::Scene& scene_;
vtkRenderWindow* rw_;
std::shared_ptr<geopro::core::GeoLocalFrame> frame_;
QNetworkAccessManager nam_;
std::vector<vtkSmartPointer<vtkActor>> tiles_;
int generation_ = 0; // 每次 show/hide 自增;迟到的瓦片回包比对丢弃
};
} // namespace geopro::app

View File

@ -118,6 +118,7 @@
#include "panels/ObjectAttrPanel.hpp"
#include "panels/DatasetAttrPanel.hpp"
#include "panels/ObjectExceptionPanel.hpp"
#include "TileBasemap.hpp"
#include "panels/columns/ColumnDrawer.hpp"
#include "panels/columns/Column3DDataset.hpp"
#include "panels/columns/Column2DDataset.hpp"
@ -394,6 +395,15 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
detailCtrl.openDataset(dsId, ddCode, name);
});
// ── 二维数据集栏:天地图底图开关(③,复用轨迹图 token经同一共享 GeoLocalFrame 配准)──
auto* basemap = new geopro::app::TileBasemap(*scene, renderWindowPtr, frame, &window);
QObject::connect(drawer->col2D(), &geopro::app::Column2DDataset::basemapChanged, basemap,
[basemap](int idx) {
// 地图下拉0 天地图(街道) / 1 Google(暂未实现→隐藏) / 2 隐藏。
basemap->show(idx == 0 ? geopro::app::TileBasemap::Street
: geopro::app::TileBasemap::Hidden);
});
// ── 中央“空状态”引导浮层:未接入真实 sections 时,引导首次使用者从左侧入手。──
// 透明背景 + 鼠标穿透(不挡 QVTK 交互CenterOverlay 随视口尺寸保持居中;
// 接入真实中央数据后改成依 sections 是否为空调 setVisible 即可。