// M1 工作台(视图重构 Task B):正确产品模型。 // - 左 对象树:GS→TM→DS(复选框)。勾选 dd_section → 在中央当前视图显示该数据集,可多条共存。 // - 中央「二维地图 / 三维视图」:两个互斥视图(内容不同,不是同一物体换相机)。 // 二维地图 = 对每个勾选数据集 buildSurveyLine(lat/lon 红线俯视,z=0)+ applyTop2D(浅底背景)。 // 三维视图 = 对每个勾选数据集 buildCurtain(竖直断面墙),actor SetScale(1,1,3) 纵向夸张 + applyFree3D(白底)。 // 切视图 / 勾选变化 → 按当前勾选集重建对应内容。 // - 下方「数据详情」:独立 QVTK 小视图 + 工具条「反演剖面 / 原数据」切换。单击某 DS → 显示该数据集: // 反演剖面 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。 // 原数据 = #17 彩色散点(buildScatter,x=距离/y=深度,按散点自带色阶上色)。 // 两者皆平躺俯视正交 + 属性。 // - 右 属性:选中数据集属性文本。 // 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame(全项目共享,保证多视图配准)。 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "model/ColorScale.hpp" #include "model/Field.hpp" #include "repo/LocalSampleRepository.hpp" #include "ApiClient.hpp" #include "AuthService.hpp" #include "login/LoginWindow.hpp" #include "CameraPreset.hpp" #include "Scene.hpp" #include "actors/CurtainActor.hpp" #include "actors/GridContourActor.hpp" #include "actors/MapLineActor.hpp" #include "actors/ScatterActor.hpp" #include "geo/GeoLocalFrame.hpp" #include #include #include #include #include #include namespace { // 角色:DS 项的 dd 类型存在 UserRole+1(dsId 存在 UserRole)。 constexpr int kRoleDsId = Qt::UserRole; constexpr int kRoleDdType = Qt::UserRole + 1; // 从对象结构树构建 QTreeWidget:GS → TM → DS 三层。 // DS 项可勾选(复选框):勾选驱动该测线竖直帘面在中央场景显示;UserRole 存 dsId、UserRole+1 存 ddType。 // 网格剖面(dd_section)默认勾选,启动即显示帘面。 void populateTree(QTreeWidget* tree, const std::vector& gss) { for (const auto& gs : gss) { auto* gsItem = new QTreeWidgetItem(tree); gsItem->setText(0, QString::fromStdString(gs.name)); for (const auto& tm : gs.tms) { auto* tmItem = new QTreeWidgetItem(gsItem); tmItem->setText(0, QString::fromStdString(tm.name)); for (const auto& ds : tm.dss) { auto* dsItem = new QTreeWidgetItem(tmItem); dsItem->setText(0, QString::fromStdString(ds.name)); dsItem->setData(0, kRoleDsId, QString::fromStdString(ds.id)); dsItem->setData(0, kRoleDdType, QString::fromStdString(ds.ddType)); dsItem->setFlags(dsItem->flags() | Qt::ItemIsUserCheckable); // 网格剖面默认勾选 → 启动即显帘面;其余默认不勾。 dsItem->setCheckState( 0, ds.ddType == "dd_section" ? Qt::Checked : Qt::Unchecked); } } } tree->expandAll(); } // 读取 RSA 公钥 PEM 全文(登录时密码加密用)。读不到返回空串,登录将报错。 std::string readPem(const std::string& path) { std::ifstream in(path, std::ios::binary); if (!in) return {}; std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } // 取 vector 中位数(用于由测线 lat/lon 推世界系原点)。空则返回 0。 double median(std::vector v) { if (v.empty()) return 0.0; std::sort(v.begin(), v.end()); const size_t n = v.size(); return n % 2 ? v[n / 2] : 0.5 * (v[n / 2 - 1] + v[n / 2]); } // 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。 enum class ViewMode { Map2D, View3D }; // 数据详情显示内容(默认反演剖面)。反演剖面=#18 banded;原数据=#17 散点。 enum class DetailMode { Section18, Scatter17 }; // #17 散点屏幕像素方块边长。 constexpr float kScatterPointSize = 4.0F; // 纵向夸张倍数:三维断面墙沿 z 拉伸成墙;数据详情 #18 沿 y 拉伸填面板。 constexpr double kCurtainZScale = 3.0; constexpr double kDetailYScale = 1.5; // 在给定 QMainWindow 上构建 M1 工作台。 // repo 生命周期须覆盖到事件循环结束(由调用方保证)。 void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo) { // ── 世界系:启动取一次 grid1 的 lat/lon,用中位数作 GeoLocalFrame 原点 ── // 全项目共享(shared_ptr 持有):所有帘面用同一 frame 投影,保证多条测线空间配准。 const auto baseGrid = repo.loadGrid("grid1"); const double lat0 = median(baseGrid.lat); const double lon0 = median(baseGrid.lon); auto frame = std::make_shared(lat0, lon0); // ── 中央 QVTK + Scene(竖直帘面场景)───────────────────────────────── // Scene 非 QObject:堆分配,用 widget 销毁信号清理(widget 随 window 销毁)。 auto* scene = new geopro::render::Scene(); auto* vtkWidget = new QVTKOpenGLStereoWidget(); QObject::connect(vtkWidget, &QObject::destroyed, [scene]() { delete scene; }); vtkNew renderWindow; vtkWidget->setRenderWindow(renderWindow); renderWindow->AddRenderer(scene->renderer()); vtkRenderer* rendererPtr = scene->renderer(); vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get(); // 当前视图模式(全局共享,切视图/勾选时据此重建内容)。默认二维地图。 auto viewMode = std::make_shared(ViewMode::Map2D); auto* dockManager = new ads::CDockManager(&window); window.setCentralWidget(dockManager); // 中央容器:顶部「二维地图/三维视图」工具条 + 下方 QVTK 视图。 auto* centerWidget = new QWidget(); auto* centerLayout = new QVBoxLayout(centerWidget); centerLayout->setContentsMargins(0, 0, 0, 0); centerLayout->setSpacing(0); // 工具条:「二维地图/三维视图」两个互斥可勾选 action。切换=按当前勾选集重建对应内容。默认二维地图。 auto* viewToolBar = new QToolBar(); auto* viewGroup = new QActionGroup(viewToolBar); viewGroup->setExclusive(true); auto* act2D = viewToolBar->addAction(QStringLiteral("二维地图")); auto* act3D = viewToolBar->addAction(QStringLiteral("三维视图")); act2D->setCheckable(true); act3D->setCheckable(true); viewGroup->addAction(act2D); viewGroup->addAction(act3D); act2D->setChecked(true); // 默认二维地图 centerLayout->addWidget(viewToolBar); centerLayout->addWidget(vtkWidget, 1); auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维地图/三维视图")); vtkDock->setWidget(centerWidget); auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock); // ── 下方「数据详情」dock:独立 QVTK 小视图(独立 renderer/renderWindow)── // 单击 DS → 显示该数据集平面反演剖面(#18 banded,平躺俯视正交)。 auto* detailWidget = new QVTKOpenGLStereoWidget(); vtkNew detailRenderWindow; vtkNew detailRenderer; detailRenderer->SetBackground(1.0, 1.0, 1.0); // 白底 detailWidget->setRenderWindow(detailRenderWindow); detailRenderWindow->AddRenderer(detailRenderer); vtkRenderer* detailRendererPtr = detailRenderer.Get(); vtkGenericOpenGLRenderWindow* detailRenderWindowPtr = detailRenderWindow.Get(); // 数据详情容器:顶部「反演剖面/原数据」工具条 + 下方 QVTK 小视图。 auto* detailContainer = new QWidget(); auto* detailLayout = new QVBoxLayout(detailContainer); detailLayout->setContentsMargins(0, 0, 0, 0); detailLayout->setSpacing(0); auto* detailToolBar = new QToolBar(); auto* detailGroup = new QActionGroup(detailToolBar); detailGroup->setExclusive(true); auto* actSection = detailToolBar->addAction(QStringLiteral("反演剖面")); auto* actScatter = detailToolBar->addAction(QStringLiteral("原数据")); actSection->setCheckable(true); actScatter->setCheckable(true); detailGroup->addAction(actSection); detailGroup->addAction(actScatter); actSection->setChecked(true); // 默认反演剖面 (#18) detailLayout->addWidget(detailToolBar); detailLayout->addWidget(detailWidget, 1); auto* detailDock = new ads::CDockWidget(QStringLiteral("数据详情")); detailDock->setWidget(detailContainer); // 放在中央视图下方。 dockManager->addDockWidget(ads::BottomDockWidgetArea, detailDock, centerDockArea); // 左 dock:对象树。 auto* tree = new QTreeWidget(); tree->setHeaderLabel(QStringLiteral("对象")); populateTree(tree, repo.loadStructure()); auto* leftDock = new ads::CDockWidget(QStringLiteral("对象列表")); leftDock->setWidget(tree); dockManager->addDockWidget(ads::LeftDockWidgetArea, leftDock); // 右 dock:属性。 auto* propLabel = new QLabel(QStringLiteral("(单击左侧数据集查看属性与平面剖面)")); propLabel->setWordWrap(true); propLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); propLabel->setMargin(8); auto* rightDock = new ads::CDockWidget(QStringLiteral("数据集 / 属性")); rightDock->setWidget(propLabel); dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock); // ── 中央视图重建(核心)───────────────────────────────────────────── // 两个互斥视图按当前勾选集整体重建:scene.clear() → 对每个勾选 dd_section 加对应 actor。 // 二维地图 = buildSurveyLine(红线俯视,浅底背景)+ applyTop2D。 // 三维视图 = buildCurtain(断面墙)SetScale(1,1,kCurtainZScale) + applyFree3D(白底)。 // frame 全局共享;切视图/勾选变化都调用此函数重建当前视图。 auto rebuildCentral = [scene, rendererPtr, renderWindowPtr, viewMode, &repo, frame, tree]() { scene->clear(); const bool is2D = (*viewMode == ViewMode::Map2D); rendererPtr->SetBackground(is2D ? 0.96 : 1.0, is2D ? 0.97 : 1.0, is2D ? 0.99 : 1.0); // 遍历对象树收集所有勾选的 dd_section,逐个加入当前视图内容。 QList stack; for (int i = 0; i < tree->topLevelItemCount(); ++i) stack.append(tree->topLevelItem(i)); while (!stack.isEmpty()) { QTreeWidgetItem* cur = stack.takeFirst(); for (int i = 0; i < cur->childCount(); ++i) stack.append(cur->child(i)); const QString dsId = cur->data(0, kRoleDsId).toString(); if (dsId.isEmpty()) continue; // GS/TM 节点忽略 if (cur->checkState(0) != Qt::Checked) continue; // 仅显示勾选的 const QString ddType = cur->data(0, kRoleDdType).toString(); if (ddType != "dd_section") continue; // 当前仅支持剖面网格 const std::string id = dsId.toStdString(); const auto g = repo.loadGrid(id); if (is2D) { auto line = geopro::render::buildSurveyLine(g, *frame); if (line) scene->addActor(line); } else { const auto cs = repo.loadColorScale(id); auto curtain = geopro::render::buildCurtain(g, cs, *frame); if (curtain) { curtain->SetScale(1.0, 1.0, kCurtainZScale); // 纵向夸张成墙 scene->addActor(curtain); } } } if (is2D) geopro::render::applyTop2D(rendererPtr); else geopro::render::applyFree3D(rendererPtr); rendererPtr->ResetCamera(); renderWindowPtr->Render(); }; // 勾选/取消某 dd_section → 重建当前视图内容(勾的才显示;可多条共存)。 QObject::connect(tree, &QTreeWidget::itemChanged, tree, [rebuildCentral](QTreeWidgetItem* item, int) { if (item->data(0, kRoleDsId).toString().isEmpty()) return; // GS/TM 忽略 rebuildCentral(); }); // ── 数据详情共享状态 + 重建 ────────────────────────────────────────── // 当前选中数据集 id(空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。 auto currentDsId = std::make_shared(); auto detailMode = std::make_shared(DetailMode::Section18); // 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。 auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, currentDsId, detailMode]() { detailRendererPtr->RemoveAllViewProps(); if (currentDsId->isEmpty()) { // 未选数据集:清空即可 detailRenderWindowPtr->Render(); return; } const std::string id = currentDsId->toStdString(); if (*detailMode == DetailMode::Section18) { // 反演剖面:#18 banded 等值面 + 等值线,两 actor 纵向夸张 1.5x(沿 y)。 const auto g = repo.loadGrid(id); const auto cs = repo.loadColorScale(id); const auto actors = geopro::render::buildGridContour(g, cs); if (actors.bands) { actors.bands->SetScale(1.0, kDetailYScale, 1.0); detailRendererPtr->AddViewProp(actors.bands); } if (actors.edges) { actors.edges->SetScale(1.0, kDetailYScale, 1.0); detailRendererPtr->AddViewProp(actors.edges); } } else { // 原数据:#17 彩色散点,用散点自带色阶;纵向夸张同剖面以对齐观感。 const auto s = repo.loadScatter(id); const auto scs = repo.loadScatterColorScale(id); auto a = geopro::render::buildScatter(s, scs, kScatterPointSize); if (a) { a->SetScale(1.0, kDetailYScale, 1.0); detailRendererPtr->AddViewProp(a); } } geopro::render::applyTop2D(detailRendererPtr); detailRendererPtr->ResetCamera(); detailRenderWindowPtr->Render(); }; // ── 单击 DS → 记选中 + 重建数据详情 + 右侧属性(与勾选区分;不改帘面可见性)── QObject::connect( tree, &QTreeWidget::itemClicked, tree, [&repo, propLabel, currentDsId, rebuildDetail](QTreeWidgetItem* item, int) { const QString dsId = item->data(0, kRoleDsId).toString(); if (dsId.isEmpty()) return; // GS/TM 节点无详情 const QString ddType = item->data(0, kRoleDdType).toString(); if (ddType != "dd_section") return; const QString name = item->text(0); *currentDsId = dsId; rebuildDetail(); // 右侧属性(数据集级,与详情模式无关)。 const auto g = repo.loadGrid(dsId.toStdString()); propLabel->setText( QStringLiteral("数据集: %1\n类型: 剖面网格 (dd_section)\n网格: %2 x %3\n" "vmin / vmax: %4 / %5") .arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax)); }); // ── 数据详情工具条「反演剖面/原数据」:切模式 → 重建数据详情 ── QObject::connect(actSection, &QAction::triggered, detailWidget, [detailMode, rebuildDetail]() { *detailMode = DetailMode::Section18; rebuildDetail(); }); QObject::connect(actScatter, &QAction::triggered, detailWidget, [detailMode, rebuildDetail]() { *detailMode = DetailMode::Scatter17; rebuildDetail(); }); // ── 工具条「二维地图/三维视图」:切换互斥视图 → 按当前勾选集重建对应内容 ── QObject::connect(act2D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() { *viewMode = ViewMode::Map2D; rebuildCentral(); }); QObject::connect(act3D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() { *viewMode = ViewMode::View3D; rebuildCentral(); }); // ── 启动默认:dd_section 已勾选,但 itemChanged 在 connect 之前触发故未渲染。 // 这里 connect 之后主动按默认视图(二维地图)重建一次中央内容。 rebuildCentral(); } } // namespace int main(int argc, char* argv[]) { // QVTK 默认 surface format 必须在 QApplication 之前设置(全局一次,两个 QVTK widget 共用)。 QSurfaceFormat::setDefaultFormat(QVTKOpenGLStereoWidget::defaultFormat()); QApplication app(argc, argv); // 网络层:共享会话 ApiClient + 登录编排 AuthService(RSA 公钥从 resources 读取)。 geopro::net::ApiClient api(QStringLiteral("http://tenant.geomative.cn/pop-api")); const std::string pem = readPem("D:/Git/lanbingtech/geopro/resources/rsa_public_key.pem"); geopro::net::AuthService auth(api, pem); // 先弹登录窗;用户取消/未登录则退出。 geopro::app::LoginWindow login(auth); if (login.exec() != QDialog::Accepted) return 0; api.setToken(login.token()); // 注入 token 供后续 API 使用 // 登录成功 → 构建并显示工作台。 geopro::data::LocalSampleRepository repo( "D:/Git/lanbingtech/geopro/docs/剖面网格数据的色阶数据2等文件/"); QMainWindow window; window.setWindowTitle(QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)")); window.resize(1280, 800); buildWorkbench(window, repo); window.show(); return app.exec(); }