From a588b651a6798d6b8939cc0024b007fead68219d Mon Sep 17 00:00:00 2001 From: gaozheng Date: Tue, 16 Jun 2026 22:01:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(vtk):=20=E5=A4=A9=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=E5=BA=95=E5=9B=BE=E7=93=A6=E7=89=87=E5=B1=82=20TileBasemap(?= =?UTF-8?q?=E2=91=A2:=E5=A4=8D=E7=94=A8WMTS=20token,=E7=BB=8F=E5=90=8C?= =?UTF-8?q?=E4=B8=80frame=E9=85=8D=E5=87=86,col2D=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=E5=BC=80=E5=85=B3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/CMakeLists.txt | 5 +- src/app/TileBasemap.cpp | 132 ++++++++++++++++++++++++++++++++++++++++ src/app/TileBasemap.hpp | 44 ++++++++++++++ src/app/main.cpp | 10 +++ 4 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 src/app/TileBasemap.cpp create mode 100644 src/app/TileBasemap.hpp diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index df7e512..b44e3f8 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 diff --git a/src/app/TileBasemap.cpp b/src/app/TileBasemap.cpp new file mode 100644 index 0000000..a679f9f --- /dev/null +++ b/src/app/TileBasemap.cpp @@ -0,0 +1,132 @@ +#include "TileBasemap.hpp" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 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(vimg->GetScalarPointer(0, row, 0)); + std::memcpy(dst, src, static_cast(w) * 4); + } + vtkNew 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 plane; + plane->SetOrigin(sw.x, sw.y, kGroundZ); + plane->SetPoint1(se.x, se.y, kGroundZ); + plane->SetPoint2(nw.x, nw.y, kGroundZ); + + vtkNew mapper; + mapper->SetInputConnection(plane->GetOutputPort()); + auto actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->SetTexture(tex); + actor->GetProperty()->LightingOff(); // 底图不受场景光照 + + scene_.addActor(actor); + tiles_.push_back(actor); + if (rw_) rw_->Render(); +} + +} // namespace geopro::app diff --git a/src/app/TileBasemap.hpp b/src/app/TileBasemap.hpp new file mode 100644 index 0000000..ab5bdd0 --- /dev/null +++ b/src/app/TileBasemap.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +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 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 frame_; + QNetworkAccessManager nam_; + std::vector> tiles_; + int generation_ = 0; // 每次 show/hide 自增;迟到的瓦片回包比对丢弃 +}; + +} // namespace geopro::app diff --git a/src/app/main.cpp b/src/app/main.cpp index 555b750..4e3fd0d 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -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 即可。