geopro/src/app/main.cpp

405 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// M1 工作台(视图重构 Task B正确产品模型。
// - 左 对象树GS→TM→DS复选框。勾选 dd_section → 在中央当前视图显示该数据集,可多条共存。
// - 中央「二维地图 / 三维视图」:两个互斥视图(内容不同,不是同一物体换相机)。
// 二维地图 = 对每个勾选数据集 buildSurveyLinelat/lon 红线俯视z=0+ applyTop2D浅底背景
// 三维视图 = 对每个勾选数据集 buildCurtain竖直断面墙actor SetScale(1,1,3) 纵向夸张 + applyFree3D白底
// 切视图 / 勾选变化 → 按当前勾选集重建对应内容。
// - 下方「数据详情」:独立 QVTK 小视图 + 工具条「反演剖面 / 原数据」切换。单击某 DS → 显示该数据集:
// 反演剖面 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。
// 原数据 = #17 彩色散点buildScatterx=距离/y=深度,按散点自带色阶上色)。
// 两者皆平躺俯视正交 + 属性。
// - 右 属性:选中数据集属性文本。
// 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame全项目共享保证多视图配准
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <QActionGroup>
#include <QApplication>
#include <QDialog>
#include <QLabel>
#include <QMainWindow>
#include <QSurfaceFormat>
#include <QToolBar>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <QWidget>
#include <DockManager.h>
#include <DockWidget.h>
#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 <algorithm>
#include <vector>
#include <QVTKOpenGLStereoWidget.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkSmartPointer.h>
namespace {
// 角色DS 项的 dd 类型存在 UserRole+1dsId 存在 UserRole
constexpr int kRoleDsId = Qt::UserRole;
constexpr int kRoleDdType = Qt::UserRole + 1;
// 从对象结构树构建 QTreeWidgetGS → TM → DS 三层。
// DS 项可勾选复选框勾选驱动该测线竖直帘面在中央场景显示UserRole 存 dsId、UserRole+1 存 ddType。
// 网格剖面dd_section默认勾选启动即显示帘面。
void populateTree(QTreeWidget* tree, const std::vector<geopro::data::GsNode>& 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<double> 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<geopro::core::GeoLocalFrame>(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<vtkGenericOpenGLRenderWindow> renderWindow;
vtkWidget->setRenderWindow(renderWindow);
renderWindow->AddRenderer(scene->renderer());
vtkRenderer* rendererPtr = scene->renderer();
vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get();
// 当前视图模式(全局共享,切视图/勾选时据此重建内容)。默认二维地图。
auto viewMode = std::make_shared<ViewMode>(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<vtkGenericOpenGLRenderWindow> detailRenderWindow;
vtkNew<vtkRenderer> 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<QTreeWidgetItem*> 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<QString>();
auto detailMode = std::make_shared<DetailMode>(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 + 登录编排 AuthServiceRSA 公钥从 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();
}