可视化与交互增强
EnerOS v0.46.0 大幅提升可视化与交互能力,新增 3D 场景 glTF 2.0 导出、自然语言查询引擎(8 意图识别)与 PDF/Excel/Word 报表导出。运维人员无需学习复杂查询语言,即可通过自然语言与电网数据交互。可视化能力由 eneros-viz crate 提供。
可视化架构
┌──────────────────────────────────────────────────────────────┐
│ 交互层 │
│ 自然语言查询 / 仪表盘 / Web 界面 / 移动端 │
└────────┬─────────────────────────────────────────────────────┘
│
┌────────▼─────────────────────────────────────────────────────┐
│ 渲染层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 3D 场景 │ │ 拓扑图 │ │ 图表 │ │ 报表 │ │
│ │ glTF 2.0 │ │ SVG/Canvas│ │ ECharts │ │ PDF/XLSX│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└────────┬─────────────────────────────────────────────────────┘
│
┌────────▼─────────────────────────────────────────────────────┐
│ 数据层 │
│ 时序数据库 / 拓扑模型 / 告警 / Agent 状态 │
└──────────────────────────────────────────────────────────────┘
| 模块 | 输出格式 | 适用 |
|---|
| 3D 场景 | glTF 2.0 / GLB | 站内巡视、设备巡视 |
| 自然语言查询 | JSON / 文本 | 运维交互 |
| 报表导出 | PDF / Excel / Word | 报告归档 |
| 仪表盘 | HTML / WebSocket | 实时监控 |
| 拓扑图 | SVG / Canvas | 电网结构 |
| 图表 | PNG / SVG | 趋势分析 |
3D 场景
将电网拓扑与设备状态渲染为 3D 场景,支持 glTF 2.0 标准导出,可在 Three.js / Babylon.js / Cesium 等渲染引擎中加载:
use eneros_viz::scene::{Scene, SceneBuilder, GltfExporter, SubstationMesh, LineMesh, ColorScheme};
let mut scene = SceneBuilder::new()
.scale(Scale::kilometers())
.terrain(Terrain::from_dem("dem/srtm.tif"));
// 添加变电站
scene.add_substation(SubstationMesh::new("sub_a")
.position(31.23, 121.47, 0.0)
.voltage_level(220.0)
.equipment(EquipmentMesh::all()));
scene.add_substation(SubstationMesh::new("sub_b")
.position(31.25, 121.50, 0.0)
.voltage_level(220.0));
// 添加线路
scene.add_line(LineMesh::between("sub_a", "sub_b")
.voltage(220.0)
.load_ratio(0.65)
.conductor(ConductorType::Lgj400)
.towers(TowerPlacement::every(500.0)));
// 添加设备状态
scene.add_device(DeviceMesh::transformer("t1")
.position_in("sub_a")
.load(0.78)
.temperature(65.0));
// 状态着色
scene.color_by(ColorScheme::VoltageBand); // 按电压着色
// 添加标注
scene.annotation(Annotation::label("sub_a", "220kV 变电站 A"));
// 导出 glTF
let gltf = GltfExporter::export(&scene.build())
.embed_textures(true)
.draco_compression(true)
.version(GltfVersion::V2_0)?;
std::fs::write("grid.gltf", gltf)?;
场景层级
Scene
├── Substations
│ ├── Bays
│ │ ├── Breakers
│ │ ├── Disconnectors
│ │ └── Transformers
│ └── Control House
├── Lines
│ ├── Towers
│ └── Conductors
├── Generators
├── Loads
└── Annotations
着色方案
| 方案 | 说明 | 颜色映射 |
|---|
| VoltageBand | 按电压等级 | 红 (>1.05) / 绿 (0.95-1.05) / 蓝 (<0.95) |
| LoadRatio | 按负载率 | 红 (>90%) / 黄 (70-90%) / 绿 (<70%) |
| DeviceStatus | 按设备状态 | 红 (故障) / 黄 (告警) / 绿 (正常) |
| OutageArea | 停电区域 | 灰 (失电) / 彩色 (正常) |
| RealtimeFlow | 实时潮流 | 流向动画 |
自然语言查询
8 类意图识别,无需学习查询语法,支持中文自然语言:
use eneros_viz::nl::{NlQuery, Intent, NlResponse};
let response = NlQuery::ask("昨天上午 10 点 5 号母线电压是多少?").await?;
match response.intent {
Intent::PointInTimeQuery => {
println!("5 号母线 10:00 电压: {:.2} kV", response.value);
println!("SQL 等价: {}", response.translated_sql);
}
Intent::RangeQuery => {
println!("数据点数: {}", response.data_points.len());
}
_ => {}
}
// 其他示例查询
let queries = vec![
"过去 24 小时哪些线路过载?", // → AnomalySearch
"比较 1 号和 2 号变压器今天的负荷", // → Comparison
"5 分钟内可能跳闸的设备", // → Prediction
"生成上月运行报告", // → Report
"为什么 5 号母线电压偏低", // → RootCause
"给 3 号发电机加 10MW", // → Action
];
for q in queries {
let resp = NlQuery::ask(q).await?;
println!("'{}' → 意图: {:?}", q, resp.intent);
}
意图矩阵
| 意图 | 说明 | 示例 | 输出 |
|---|
| PointInTimeQuery | 时间点查询 | 昨天上午 10 点 5 号母线电压 | 单值 |
| RangeQuery | 范围查询 | 过去 1 小时的频率曲线 | 时序数据 |
| AnomalySearch | 异常搜索 | 哪些设备过载 | 异常列表 |
| Comparison | 对比分析 | 比较 A 和 B 变电站 | 对比表 |
| Prediction | 预测查询 | 下小时负荷预测 | 预测曲线 |
| Report | 报表生成 | 生成运行报告 | 文档 |
| RootCause | 根因分析 | 为什么电压偏低 | 因果链 |
| Action | 操作执行 | 给 3 号发电机加 10MW | 命令确认 |
多轮对话
use eneros_viz::nl::{NlConversation, Context};
let mut conversation = NlConversation::new()
.context(Context::new()
.user("alice")
.role("dispatcher"));
// 第一轮
let r1 = conversation.ask("5 号母线电压是多少?").await?;
// r1: 5 号母线当前电压 1.02 pu
// 第二轮(上下文延续)
let r2 = conversation.ask("过去 1 小时呢?").await?;
// r2: 5 号母线过去 1 小时电压曲线 + 统计
// 第三轮
let r3 = conversation.ask("为什么偏低?").await?;
// r3: 根因分析 + 建议动作
报表导出
支持 PDF、Excel、Word 三种格式,内置电力行业模板:
use eneros_viz::report::{Report, ReportFormat, Template, Chart, Range};
let report = Report::new()
.template(Template::daily_operation())
.period(Range::last(Duration::from_secs(86400)))
.data(daily_data)
.charts(vec![
Chart::load_curve()
.width(800).height(400),
Chart::voltage_profile()
.width(800).height(300),
Chart::frequency_deviation()
.width(800).height(300),
Chart::generation_mix()
.ty(ChartType::Pie),
])
.tables(vec![
Table::outage_statistics(),
Table::equipment_status(),
])
.metadata(Metadata::new()
.author("EnerOS")
.title("2026-07-06 日报")
.confidentiality(Confidentiality::Internal));
// 导出为不同格式
report.export(ReportFormat::Pdf, "daily.pdf").await?;
report.export(ReportFormat::Excel, "daily.xlsx").await?;
report.export(ReportFormat::Word, "daily.docx").await?;
report.export(ReportFormat::Html, "daily.html").await?;
报表模板
| 模板 | 内容 | 适用 |
|---|
| daily_operation | 日运行报告 | 日常运维 |
| weekly_summary | 周汇总 | 周报 |
| monthly_review | 月度回顾 | 月报 |
| incident_analysis | 事故分析 | 事后复盘 |
| equipment_health | 设备健康度 | 检修计划 |
| economic_dispatch | 经济调度 | 调度优化 |
| compliance_audit | 合规审计 | 监管报送 |
定时报表
use eneros_viz::report::{ReportScheduler, Schedule, Day};
let scheduler = ReportScheduler::new()
.schedule("daily", Template::daily_operation(),
Schedule::daily(8, 0))
.schedule("weekly", Template::weekly_summary(),
Schedule::weekly(Day::Monday, 8, 0))
.schedule("monthly", Template::monthly_review(),
Schedule::monthly(1, 8, 0))
.recipients(vec!["ops@grid.com", "manager@grid.com"])
.format(ReportFormat::Pdf)
.subject("EnerOS {{template}} - {{date}}")
.on_failure(|err| async move {
notify_admin("报表生成失败").await
});
scheduler.start().await?;
实时仪表盘
use eneros_viz::dashboard::{Dashboard, Widget, Range, MapLayer, AlertFilter};
let dashboard = Dashboard::new("实时监控")
.layout(Layout::grid(3, 3))
.widget(Widget::single_line("频率", "grid.frequency",
Range::last(Duration::from_secs(60))))
.widget(Widget::gauge("总负荷", "grid.total_load", 0.0, 1000.0)
.thresholds(vec![
Threshold::warn(800.0),
Threshold::critical(950.0),
]))
.widget(Widget::map("电网地图", MapLayer::substations()
.zoom(8)
.center(31.23, 121.47)))
.widget(Widget::alert_list("告警", AlertFilter::active()
.severity_gte(Severity::Warn)))
.widget(Widget::bar_chart("分区负荷", "feeder.load"))
.widget(Widget::heatmap("设备温度", "device.temperature"))
.widget(Widget::topology("拓扑图", TopologyConfig::auto()))
.widget(Widget::status_grid("设备状态", DeviceFilter::all()))
.refresh(Duration::from_secs(1));
dashboard.serve("0.0.0.0:8080").await?;
| Widget | 数据类型 | 更新频率 |
|---|
| single_line | 时序 | 1s |
| gauge | 实时值 | 1s |
| bar_chart | 离散值 | 5s |
| pie_chart | 占比 | 30s |
| map | 地理 | 5s |
| topology | 拓扑 | 事件 |
| heatmap | 矩阵 | 5s |
| alert_list | 告警 | 实时 |
| status_grid | 状态 | 5s |
| table | 表格 | 30s |
| 3d_scene | 3D | 按需 |
数据可视化类型
| 类型 | 用途 | 交互 |
|---|
| 单值卡片 | KPI 监控 | 钻取 |
| 曲线图 | 时序数据 | 缩放 / 框选 |
| 柱状图 | 对比分析 | 钻取 |
| 饼图 | 占比分布 | 钻取 |
| 地图 | 地理分布 | 点击 |
| 拓扑图 | 电网结构 | 拖拽 / 钻取 |
| 热力图 | 负载分布 | 框选 |
| 3D 场景 | 站内巡视 | 旋转 / 漫游 |
| 散点图 | 关联分析 | 框选 |
| 桑基图 | 能量流 | 钻取 |
性能指标
| 操作 | 延迟 | 备注 |
|---|
| glTF 场景生成(1000 设备) | < 500ms | 含 Draco 压缩 |
| glTF 场景生成(10000 设备) | < 3s | 含 Draco 压缩 |
| 自然语言意图识别 | < 200ms | LLM 调用 |
| 自然语言查询执行 | < 1s | 含 SQL 翻译 |
| PDF 报表生成(含图表) | < 2s | A4 10 页 |
| Excel 报表生成(10k 行) | < 1s | 含格式化 |
| Word 报表生成 | < 3s | 含图表 |
| 仪表盘刷新(10 widgets) | < 100ms | WebSocket 推送 |
| 拓扑图渲染(1000 节点) | < 500ms | SVG |
| 多轮对话响应 | < 2s | 含上下文 |
与其他能力的关系
相关文档