波形解析相关

This commit is contained in:
2026-04-16 08:18:22 +08:00
parent 47d103918f
commit 7fc8996bdf
21 changed files with 2330 additions and 0 deletions

View File

@@ -0,0 +1,266 @@
# parseComtrade API 文档
## 1. 接口概述
- 接口名称:解析 COMTRADE 波形文件
- Controller[WaveController.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/controller/WaveController.java)
- 方法:`parseComtrade`
- 请求路径:`POST /wave/parseComtrade`
- Content-Type`multipart/form-data`
- 返回类型:`HttpResult<WaveComtradeResultVO>`
用途说明:
- 上传一组 COMTRADE `cfg/dat` 文件
- 解析原始波形数据
- 按请求决定是否补充 RMS 数据、前端查看明细和特征值结果
## 2. 请求参数
### 2.1 文件参数
| 参数名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `cfgFile` | file | 是 | COMTRADE 配置文件 `.cfg` |
| `datFile` | file | 是 | COMTRADE 数据文件 `.dat` |
### 2.2 表单参数
参数定义来源:[WaveComtradeParseParam.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveComtradeParseParam.java)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `parseType` | integer | 否 | `1` | 解析类型:`0` 高级算法采样率 32-128`1` 普通展示,`2` App 抽点,`3` 原始波形 |
| `ptType` | integer | 否 | `0` | PT 接线方式:`0` 星形,`1` 三角,`2` 开口三角 |
| `pt` | number | 否 | `1` | PT 变比 |
| `ct` | number | 否 | `1` | CT 变比 |
| `monitorName` | string | 否 | `未命名测点` | 测点名称 |
| `calculateRms` | boolean | 否 | `true` | 是否计算 RMS |
| `buildDetails` | boolean | 否 | `true` | 是否构建前端查看明细 |
| `calculateEigenvalue` | boolean | 否 | `false` | 是否计算特征值 |
| `dynamicThreshold` | boolean | 否 | `true` | 特征值是否使用浮动门槛 |
## 3. 请求示例
```bash
curl -X POST "http://localhost:8080/wave/parseComtrade" \
-F "cfgFile=@D:/00-B7-8D-00-E4-09/1_20260321_201458_748.CFG" \
-F "datFile=@D:/00-B7-8D-00-E4-09/1_20260321_201458_748.DAT" \
-F "parseType=1" \
-F "ptType=0" \
-F "pt=1" \
-F "ct=1" \
-F "monitorName=监测点1" \
-F "calculateRms=true" \
-F "buildDetails=true" \
-F "calculateEigenvalue=true" \
-F "dynamicThreshold=true"
```
## 4. 响应结构
### 4.1 外层响应
Controller 返回的是 `HttpResult<WaveComtradeResultVO>`。当前仓库内未展开 `HttpResult` 类型源码,本接口文档只对业务 `data` 部分做精确定义。
业务数据类型来源:[WaveComtradeResultVO.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeResultVO.java)
### 4.2 data 字段定义
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| `waveData` | object | 波形基础数据 |
| `waveDataDetails` | array | 前端查看明细,`buildDetails=true` 时返回 |
| `eigenvalues` | array | 特征值结果,`calculateEigenvalue=true` 时返回 |
## 5. 业务对象说明
### 5.1 waveData
定义来源:[WaveDataDTO.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveDataDTO.java)
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| `comtradeCfgDTO` | object | CFG 解析结果 |
| `waveTitle` | array<string> | 波形标题,例如 `["Time","UA相","UB相"]` |
| `channelNames` | array<string> | 通道名称列表 |
| `listWaveData` | array<array<number>> | 原始波形数据,首列为时间,后续列为相电压/电流值 |
| `listRmsData` | array<array<number>> | RMS 波形数据,`calculateRms=true` 时可用 |
| `listRmsMinData` | array<array<number>> | RMS 最小值摘要 |
| `iPhasic` | integer | 相别数量 |
| `ptType` | integer | PT 接线方式 |
| `pt` | number | PT 变比 |
| `ct` | number | CT 变比 |
| `time` | string | 事件发生时刻 |
| `monitorName` | string | 测点名称 |
### 5.2 comtradeCfgDTO
定义来源:[ComtradeCfgDTO.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/ComtradeCfgDTO.java)
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| `nChannelNum` | integer | 通道总数 |
| `nPhasic` | integer | 相别数量 |
| `nAnalogNum` | integer | 模拟量通道数 |
| `nDigitalNum` | integer | 开关量通道数 |
| `timeStart` | string/date | 录波开始时间 |
| `timeTrige` | string/date | 触发时间 |
| `lstAnalogDTO` | array | 模拟量通道配置 |
| `lstDigitalDTO` | array | 开关量通道配置 |
| `nRates` | integer | 采样率分段数 |
| `lstRate` | array | 采样率分段配置 |
| `firstTime` | string/date | 首个触发时间对象 |
| `firstMs` | integer | 首个触发毫秒值 |
| `nPush` | integer | 触发前推点数 |
| `finalSampleRate` | integer | 最终采样率 |
| `nAllWaveNum` | integer | 总周波数 |
| `strBinType` | string | 文件编码类型,例如 `BINARY` |
### 5.3 waveDataDetails
定义来源:[WaveDataDetail.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/WaveDataDetail.java)
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| `instantData` | object | 瞬时波形数据 |
| `rmsData` | object | RMS 波形数据 |
| `a` | string | A 相名称 |
| `b` | string | B 相名称 |
| `c` | string | C 相名称 |
| `channelName` | string | 通道名称 |
| `unit` | string | 单位 |
| `isOpen` | boolean | 是否开口三角模式 |
| `title` | string | 当前图标题 |
| `colors` | array<string> | 曲线颜色 |
其中 `instantData``rmsData` 结构一致,定义分别来自:
- [InstantData.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/InstantData.java)
- [RmsData.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/RmsData.java)
公共字段:
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| `max` | number | 当前曲线最大值 |
| `min` | number | 当前曲线最小值 |
| `aValue` | array<array<number>> | A 相点位 |
| `bValue` | array<array<number>> | B 相点位 |
| `cValue` | array<array<number>> | C 相点位 |
### 5.4 eigenvalues
定义来源:[EigenvalueDTO.java](D:/Work/SourceCode/CN_Tool/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/EigenvalueDTO.java)
| 字段名 | 类型 | 说明 |
| --- | --- | --- |
| `amplitude` | number | 特征幅值百分比 |
| `residualVoltage` | number | 残余电压 |
| `ratedVoltage` | number | 额定电压 |
| `durationTime` | number | 持续时间 |
## 6. 成功响应示例
以下示例基于真实样本文件联测结果整理,长数组做了截断展示。
```json
{
"code": "SUCCESS",
"message": "成功",
"data": {
"waveData": {
"comtradeCfgDTO": {
"nChannelNum": 6,
"nPhasic": 3,
"nAnalogNum": 6,
"nDigitalNum": 0,
"timeStart": "2026-03-21 20:14:58.648",
"timeTrige": "2026-03-21 20:14:58.748",
"nRates": 1,
"firstMs": 748,
"nPush": 100,
"finalSampleRate": 512,
"nAllWaveNum": 30,
"strBinType": "BINARY"
},
"waveTitle": ["Time", "UA相", "UB相", "UC相", "IA相", "IB相", "IC相"],
"channelNames": ["/", "U1", "U2", "U3", "I1", "I2", "I3"],
"listWaveData": {
"count": 15616,
"first": [-100.0, -146.56, -76.9, -76.9, -0.13, 0.01, -0.2],
"last": [509.96, 148.02, 69.73, 69.75, 0.16, 0.01, 0.15]
},
"listRmsData": {
"count": 15616,
"first": [-100.0, 104.94, 104.22, 104.23, 0.27, 0.01, 0.28],
"last": [509.96, 105.6, 105.1, 105.12, 0.24, 0.01, 0.24]
},
"listRmsMinData": [
[40.74, 41.2],
[362.19, 0.01]
],
"iPhasic": 3,
"ptType": 0,
"pt": 1.0,
"ct": 1.0,
"time": "2026-03-21 20:14:58.748",
"monitorName": "监测点1"
},
"waveDataDetails": [
{
"channelName": "U1",
"unit": "kV",
"a": "A相",
"b": "B相",
"c": "C相",
"isOpen": false
},
{
"channelName": "I1",
"unit": "A",
"a": "A相",
"b": "B相",
"c": "C相",
"isOpen": false
}
],
"eigenvalues": [
{
"amplitude": 0.3926178,
"residualVoltage": 41.200005,
"ratedVoltage": 104.936676,
"durationTime": 48.632812
},
{
"amplitude": 0.4067544,
"residualVoltage": 42.390152,
"ratedVoltage": 104.21559,
"durationTime": 54.492188
},
{
"amplitude": 0.40674016,
"residualVoltage": 42.396355,
"ratedVoltage": 104.2345,
"durationTime": 54.492188
}
]
}
}
```
## 7. 失败场景
基于当前代码,常见失败场景包括:
| 场景 | 说明 |
| --- | --- |
| `cfgFile``datFile` 未上传 | 返回业务异常提示“cfg 或 dat 文件不能为空” |
| CFG 文件格式错误 | 返回 CFG 解析失败 |
| DAT 文件为空或格式错误 | 返回 DAT 解析失败 |
| COMTRADE 解析过程中出现异常 | 返回“COMTRADE 波形解析失败” |
## 8. 备注
- 当前接口已经移除图片生成相关参数,不再支持 `generateInstantImage``generateRmsImage` 等旧字段。
- 当前接口文档只覆盖 `parseComtrade`,其他波形文本解析接口请单独编写。

View File

@@ -0,0 +1,122 @@
package com.njcn.gather.tool.wave.component;
import com.njcn.gather.tool.wave.pojo.dto.EigenvalueDTO;
import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* 本地调试 getComtrade 的命令行入口。
*/
public final class WaveFileComponentTestMain {
/** 默认测试 CFG 文件路径。 */
private static final String DEFAULT_CFG_PATH = "D:\\00-B7-8D-00-E4-09\\PQMonitor_PQM2_006970_20260320_175033_734.CFG";
/** 默认测试 DAT 文件路径。 */
private static final String DEFAULT_DAT_PATH = "D:\\00-B7-8D-00-E4-09\\PQMonitor_PQM2_006970_20260320_175033_734.DAT";
/** 默认解析类型1 表示普通展示。 */
private static final int DEFAULT_PARSE_TYPE = 1;
/** 默认使用浮动门槛计算特征值。 */
private static final boolean DEFAULT_DYNAMIC_THRESHOLD = true;
private WaveFileComponentTestMain() {
}
/**
* 用法:
* java com.njcn.gather.tool.wave.component.WaveFileComponentTestMain <cfgPath> <datPath> [parseType]
*/
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("未传入参数,使用默认测试文件路径");
System.out.println("cfgPath: " + DEFAULT_CFG_PATH);
System.out.println("datPath: " + DEFAULT_DAT_PATH);
}
Path cfgPath = Paths.get(args.length > 0 ? args[0] : DEFAULT_CFG_PATH);
Path datPath = Paths.get(args.length > 1 ? args[1] : DEFAULT_DAT_PATH);
int parseType = args.length > 2 ? Integer.parseInt(args[2]) : DEFAULT_PARSE_TYPE;
WaveFileComponent waveFileComponent = new WaveFileComponent();
try (InputStream cfgStream = Files.newInputStream(cfgPath);
InputStream datStream = Files.newInputStream(datPath)) {
WaveDataDTO waveDataDTO = waveFileComponent.getComtrade(cfgStream, datStream, parseType);
printComtradeResult(waveDataDTO);
WaveDataDTO validWaveDataDTO = waveFileComponent.getValidData(waveDataDTO);
printValidDataResult(validWaveDataDTO);
List<EigenvalueDTO> eigenvalues = waveFileComponent.getEigenvalue(validWaveDataDTO, DEFAULT_DYNAMIC_THRESHOLD);
printEigenvalueResult(eigenvalues);
}
}
/**
* 打印 getComtrade 的关键摘要,便于快速人工核对。
*/
private static void printComtradeResult(WaveDataDTO waveDataDTO) {
System.out.println("=== getComtrade 结果 ===");
System.out.println("事件时间: " + waveDataDTO.getTime());
System.out.println("相别数: " + waveDataDTO.getIPhasic());
System.out.println("标题数: " + sizeOf(waveDataDTO.getWaveTitle()));
System.out.println("通道数: " + sizeOf(waveDataDTO.getChannelNames()));
System.out.println("波形点数: " + sizeOf(waveDataDTO.getListWaveData()));
if (waveDataDTO.getComtradeCfgDTO() != null) {
System.out.println("模拟量通道数: " + waveDataDTO.getComtradeCfgDTO().getNAnalogNum());
System.out.println("开关量通道数: " + waveDataDTO.getComtradeCfgDTO().getNDigitalNum());
System.out.println("最终采样率: " + waveDataDTO.getComtradeCfgDTO().getFinalSampleRate());
}
if (waveDataDTO.getWaveTitle() != null && !waveDataDTO.getWaveTitle().isEmpty()) {
System.out.println("波形标题: " + waveDataDTO.getWaveTitle());
}
if (waveDataDTO.getListWaveData() != null && !waveDataDTO.getListWaveData().isEmpty()) {
System.out.println("首行数据: " + waveDataDTO.getListWaveData().get(0));
System.out.println("末行数据: " + waveDataDTO.getListWaveData().get(waveDataDTO.getListWaveData().size() - 1));
}
}
/**
* 打印 getValidData 的关键摘要。
*/
private static void printValidDataResult(WaveDataDTO waveDataDTO) {
System.out.println("=== getValidData 结果 ===");
System.out.println("RMS 点数: " + sizeOf(waveDataDTO.getListRmsData()));
System.out.println("RMS 最小值点数: " + sizeOf(waveDataDTO.getListRmsMinData()));
if (waveDataDTO.getListRmsData() != null && !waveDataDTO.getListRmsData().isEmpty()) {
System.out.println("RMS 首行数据: " + waveDataDTO.getListRmsData().get(0));
System.out.println("RMS 末行数据: " + waveDataDTO.getListRmsData().get(waveDataDTO.getListRmsData().size() - 1));
}
if (waveDataDTO.getListRmsMinData() != null && !waveDataDTO.getListRmsMinData().isEmpty()) {
System.out.println("RMS 最小值数据: " + waveDataDTO.getListRmsMinData());
}
}
/**
* 打印特征值结果摘要。
*/
private static void printEigenvalueResult(List<EigenvalueDTO> eigenvalues) {
System.out.println("=== getEigenvalue 结果 ===");
System.out.println("特征值数量: " + sizeOf(eigenvalues));
if (eigenvalues == null || eigenvalues.isEmpty()) {
return;
}
for (int i = 0; i < eigenvalues.size(); i++) {
EigenvalueDTO eigenvalueDTO = eigenvalues.get(i);
System.out.println("" + (i + 1) + "相: amplitude=" + eigenvalueDTO.getAmplitude()
+ ", residualVoltage=" + eigenvalueDTO.getResidualVoltage()
+ ", ratedVoltage=" + eigenvalueDTO.getRatedVoltage()
+ ", durationTime=" + eigenvalueDTO.getDurationTime());
}
}
/**
* 安全返回列表大小,避免空指针。
*/
private static int sizeOf(List<?> list) {
return list == null ? 0 : list.size();
}
}

View File

@@ -0,0 +1,28 @@
package com.njcn.gather.tool.wave.pojo.bo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 瞬时波形数据。
*/
@Data
public class InstantData {
/** 当前曲线最大值。 */
private Float max;
/** 当前曲线最小值。 */
private Float min;
/** A 相点位。 */
private List<List<Float>> aValue = new ArrayList<>();
/** B 相点位。 */
private List<List<Float>> bValue = new ArrayList<>();
/** C 相点位。 */
private List<List<Float>> cValue = new ArrayList<>();
}

View File

@@ -0,0 +1,28 @@
package com.njcn.gather.tool.wave.pojo.bo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* RMS 波形数据。
*/
@Data
public class RmsData {
/** 当前曲线最大值。 */
private Float max;
/** 当前曲线最小值。 */
private Float min;
/** A 相点位。 */
private List<List<Float>> aValue = new ArrayList<>();
/** B 相点位。 */
private List<List<Float>> bValue = new ArrayList<>();
/** C 相点位。 */
private List<List<Float>> cValue = new ArrayList<>();
}

View File

@@ -0,0 +1,43 @@
package com.njcn.gather.tool.wave.pojo.bo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 前端查看使用的波形明细。
*/
@Data
public class WaveDataDetail {
/** 瞬时波形数据。 */
private InstantData instantData;
/** RMS 波形数据。 */
private RmsData rmsData;
/** A 相名称。 */
private String a = "A相";
/** B 相名称。 */
private String b = "B相";
/** C 相名称。 */
private String c = "C相";
/** 通道名称。 */
private String channelName;
/** 单位。 */
private String unit;
/** 是否为开口三角模式。 */
private Boolean isOpen = false;
/** 当前图标题。 */
private String title;
/** 线条颜色。 */
private List<String> colors = new ArrayList<>();
}

View File

@@ -0,0 +1,43 @@
package com.njcn.gather.tool.wave.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 模拟量通道信息。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AnalogDTO implements Serializable {
/** 通道序号。 */
private Integer nIndex;
/** 通道名称。 */
private String szChannleName;
/** 相位名称。 */
private String szPhasicName;
/** 监视通道名称。 */
private String szMonitoredChannleName;
/** 单位。 */
private String szUnitName;
/** 系数。 */
private Float fCoefficent;
/** 偏移量。 */
private Float fOffset;
/** 起始采样时间偏移量。 */
private Float fTimeOffset;
/** 采样值最小值。 */
private Integer nMin;
/** 采样值最大值。 */
private Integer nMax;
/** 一次变比。 */
private Float fPrimary;
/** 二次变比。 */
private Float fSecondary;
/** 值类型P 表示一次值S 表示二次值。 */
private String szValueType;
}

View File

@@ -0,0 +1,51 @@
package com.njcn.gather.tool.wave.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* COMTRADE CFG 配置数据。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ComtradeCfgDTO implements Serializable {
/** 通道总数。 */
private Integer nChannelNum;
/** 相别数量。 */
private Integer nPhasic;
/** 模拟量通道数。 */
private Integer nAnalogNum;
/** 开关量通道数。 */
private Integer nDigitalNum;
/** 录波开始时间。 */
private Date timeStart;
/** 触发时间。 */
private Date timeTrige;
/** 模拟量通道配置。 */
private List<AnalogDTO> lstAnalogDTO;
/** 开关量通道配置。 */
private List<DigitalDTO> lstDigitalDTO;
/** 采样率分段数。 */
public Integer nRates;
/** 采样率分段配置。 */
public List<RateDTO> lstRate;
/** 首个触发时间对象。 */
private Date firstTime;
/** 首个触发毫秒值。 */
private Integer firstMs;
/** 触发前推点数。 */
private Integer nPush = 0;
/** 最终采样率。 */
private Integer finalSampleRate;
/** 总周波数。 */
private Integer nAllWaveNum = 0;
/** 文件编码类型,例如 BINARY。 */
private String strBinType;
}

View File

@@ -0,0 +1,27 @@
package com.njcn.gather.tool.wave.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 开关量通道信息。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DigitalDTO implements Serializable {
/** 通道序号。 */
private Integer nIndex;
/** 通道名称。 */
private String szChannleName;
/** 相位名称。 */
private String szPhasicName;
/** 监视通道名称。 */
private String szMonitoredChannleName;
/** 初始值。 */
private Integer Initial;
}

View File

@@ -0,0 +1,25 @@
package com.njcn.gather.tool.wave.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 波形特征值结果。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EigenvalueDTO implements Serializable {
/** 特征幅值百分比。 */
private float amplitude;
/** 残余电压。 */
private float residualVoltage;
/** 额定电压。 */
private float ratedVoltage;
/** 持续时间。 */
private float durationTime;
}

View File

@@ -0,0 +1,29 @@
package com.njcn.gather.tool.wave.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 突变量计算结果。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MutationDTO implements Serializable {
/** 离线 RMS 数据。 */
private List<List<Float>> listRms_Offline = new ArrayList<>();
/** 离线突变量数据。 */
private List<List<Float>> listTBL_Offline = new ArrayList<>();
/** A 相最小幅值。 */
private double fMinMagA = 99999d;
/** B 相最小幅值。 */
private double fMinMagB = 99999d;
/** C 相最小幅值。 */
private double fMinMagC = 99999d;
}

View File

@@ -0,0 +1,23 @@
package com.njcn.gather.tool.wave.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 采样率分段配置。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RateDTO implements Serializable {
/** 单周波采样点数。 */
private Integer nOneSample;
/** 当前分段总采样点数。 */
private Integer nSampleNum;
/** 是否为需要补点的 RMS 数据。 */
public Boolean bRMSFlag;
}

View File

@@ -0,0 +1,42 @@
package com.njcn.gather.tool.wave.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 波形解析结果数据。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WaveDataDTO implements Serializable {
/** CFG 配置数据。 */
private ComtradeCfgDTO comtradeCfgDTO;
/** 波形标题。 */
private List<String> waveTitle;
/** 通道名称列表。 */
private List<String> channelNames;
/** 原始波形数据。 */
private List<List<Float>> listWaveData;
/** RMS 波形数据。 */
private List<List<Float>> listRmsData;
/** RMS 最小值数据。 */
private List<List<Float>> listRmsMinData;
/** 相别数量。 */
private Integer iPhasic;
/** PT 接线方式0 星形1 三角2 开口三角。 */
private Integer ptType;
/** PT 变比。 */
private Double pt;
/** CT 变比。 */
private Double ct;
/** 事件发生时刻。 */
private String time;
/** 测点名称。 */
private String monitorName;
}

View File

@@ -0,0 +1,36 @@
package com.njcn.gather.tool.wave.pojo.enums;
import lombok.Getter;
/**
* 波形处理相关响应码定义。
*/
@Getter
public enum WaveFileResponseEnum {
/** 暂降事件或监测点不存在。 */
EVENT_NOT_FOUND("A00651", "暂降事件或监测点不存在"),
/** 波形文件不存在。 */
ANALYSE_WAVE_NOT_FOUND("A00652", "波形文件不存在"),
/** CFG 文件解析失败。 */
CFG_DATA_ERROR("A00653", "CFG 文件解析失败"),
/** DAT 文件读取失败。 */
DAT_DATA_ERROR("A00654", "DAT 文件读取失败"),
/** RMS 数据计算失败。 */
RMS_DATA_ERROR("A00655", "RMS 数据计算失败"),
/** 波形文件数据缺失。 */
WAVE_DATA_INVALID("A00656", "波形文件数据缺失"),
/** 波形图片合成失败。 */
COMPOSE_PIC_ERROR("A00657", "波形图片合成失败");
/** 响应码。 */
private final String code;
/** 响应消息。 */
private final String message;
WaveFileResponseEnum(String code, String message) {
this.code = code;
this.message = message;
}
}

View File

@@ -0,0 +1,50 @@
package com.njcn.gather.tool.wave.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* COMTRADE 波形解析参数。
*/
@Data
@ApiModel("COMTRADE 波形解析参数")
public class WaveComtradeParseParam {
/** 解析类型。 */
@ApiModelProperty(value = "解析类型0 高级算法采样率 32-1281 普通展示2 App 抽点3 原始波形", example = "1")
private Integer parseType = 1;
/** PT 接线方式。 */
@ApiModelProperty(value = "PT 接线方式0 星形1 三角2 开口三角", example = "0")
private Integer ptType = 0;
/** PT 变比。 */
@ApiModelProperty(value = "PT 变比,默认 1")
private Double pt = 1D;
/** CT 变比。 */
@ApiModelProperty(value = "CT 变比,默认 1")
private Double ct = 1D;
/** 测点名称。 */
@ApiModelProperty(value = "测点名称")
private String monitorName = "未命名测点";
/** 是否计算 RMS。 */
@ApiModelProperty(value = "是否计算 RMS默认 true")
private Boolean calculateRms = true;
/** 是否构建查看明细。 */
@ApiModelProperty(value = "是否组装前端查看明细,默认 true")
private Boolean buildDetails = true;
/** 是否计算特征值。 */
@ApiModelProperty(value = "是否计算特征值,默认 false")
private Boolean calculateEigenvalue = false;
/** 是否使用浮动门槛。 */
@ApiModelProperty(value = "特征值计算方式true 浮动门槛false 固定门槛,默认 true")
private Boolean dynamicThreshold = true;
}

View File

@@ -0,0 +1,47 @@
package com.njcn.gather.tool.wave.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 文本波形解析参数。
*/
@Data
@ApiModel("波形解析参数")
public class WaveParseParam {
/** 波形文本内容。 */
@ApiModelProperty(value = "波形文本内容,支持单列幅值、单行多值或双列时间/幅值数据", required = true)
private String waveformText;
/** 分隔符。 */
@ApiModelProperty(value = "分隔符,默认 AUTO 自动识别,也支持 TAB、SPACE 或直接传入具体字符")
private String separator;
/** 是否显式包含 X 轴列。 */
@ApiModelProperty(value = "是否包含 X 轴列true 表示文本中显式传入时间列")
private Boolean containsXAxis;
/** X 轴列下标。 */
@ApiModelProperty(value = "X 轴列下标,默认 0")
private Integer xColumnIndex;
/** Y 轴列下标。 */
@ApiModelProperty(value = "Y 轴列下标,单列波形默认 0双列波形默认 1")
private Integer yColumnIndex;
/** 跳过的表头行数。 */
@ApiModelProperty(value = "跳过的表头行数,默认 0")
private Integer skipHeaderLines;
/** 单列波形采样间隔。 */
@ApiModelProperty(value = "单列波形的采样间隔,默认 1")
private BigDecimal samplingInterval;
/** 最大返回点数。 */
@ApiModelProperty(value = "返回的最大点位数,超过时自动下采样,默认 2000")
private Integer maxPointCount;
}

View File

@@ -0,0 +1,31 @@
package com.njcn.gather.tool.wave.pojo.vo;
import com.njcn.gather.tool.wave.pojo.bo.WaveDataDetail;
import com.njcn.gather.tool.wave.pojo.dto.EigenvalueDTO;
import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* COMTRADE 波形解析结果。
*/
@Data
@ApiModel("COMTRADE 波形解析结果")
public class WaveComtradeResultVO {
/** 波形基础数据。 */
@ApiModelProperty("波形基础数据")
private WaveDataDTO waveData;
/** 前端查看明细。 */
@ApiModelProperty("前端查看明细")
private List<WaveDataDetail> waveDataDetails;
/** 特征值结果。 */
@ApiModelProperty("特征值结果")
private List<EigenvalueDTO> eigenvalues;
}

View File

@@ -0,0 +1,60 @@
package com.njcn.gather.tool.wave.pojo.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* 波形解析结果。
*/
@Data
@ApiModel("波形解析结果")
public class WaveParseResultVO {
/** 是否包含显式 X 轴。 */
@ApiModelProperty("是否包含显式 X 轴")
private Boolean containsXAxis;
/** 原始有效点位数。 */
@ApiModelProperty("原始有效点位数")
private Integer sourcePointCount;
/** 返回的显示点位数。 */
@ApiModelProperty("返回的显示点位数")
private Integer displayPointCount;
/** 被忽略的无效行数。 */
@ApiModelProperty("被忽略的无效行数")
private Integer ignoredLineCount;
/** 是否发生下采样。 */
@ApiModelProperty("是否发生下采样")
private Boolean sampled;
/** X 轴最小值。 */
@ApiModelProperty("X 轴最小值")
private BigDecimal minX;
/** X 轴最大值。 */
@ApiModelProperty("X 轴最大值")
private BigDecimal maxX;
/** Y 轴最小值。 */
@ApiModelProperty("Y 轴最小值")
private BigDecimal minY;
/** Y 轴最大值。 */
@ApiModelProperty("Y 轴最大值")
private BigDecimal maxY;
/** Y 轴平均值。 */
@ApiModelProperty("Y 轴平均值")
private BigDecimal averageY;
/** 用于查看的波形点位。 */
@ApiModelProperty("用于查看的波形点位")
private List<WavePointVO> points;
}

View File

@@ -0,0 +1,27 @@
package com.njcn.gather.tool.wave.pojo.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* 波形点位。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("波形点位")
public class WavePointVO {
/** X 轴值。 */
@ApiModelProperty("X 轴值")
private BigDecimal x;
/** Y 轴值。 */
@ApiModelProperty("Y 轴值")
private BigDecimal y;
}

View File

@@ -0,0 +1,83 @@
package com.njcn.gather.tool.wave.utils;
/**
* 波形二进制数据转换工具。
*/
public final class BitConverter {
private BitConverter() {
}
/**
* 将两个字节转换为无符号 short。
*
* @param bytes 字节数组
* @param off 起始位置
* @return 转换结果
*/
public static short byte2ToUnsignedShort(byte[] bytes, int off) {
int low = bytes[off] & 0xFF;
int high = bytes[off + 1] & 0xFF;
return (short) (((high & 0x00FF) << 8) | (0x00FF & low));
}
/**
* 将四个字节转换为 float。
*
* @param bytes 字节数组
* @param index 起始位置
* @return 转换结果
*/
public static float byte4float(byte[] bytes, int index) {
int value = bytes[index] & 0xFF;
value |= ((long) bytes[index + 1] << 8);
value &= 0xFFFF;
value |= ((long) bytes[index + 2] << 16);
value &= 0xFFFFFF;
value |= ((long) bytes[index + 3] << 24);
return Float.intBitsToFloat(value);
}
/**
* 将四个字节转换为 int32。
*
* @param bytes 字节数组
* @param off 起始位置
* @return 转换结果
*/
public static int byte4ToInt(byte[] bytes, int off) {
int b0 = bytes[off] & 0xFF;
int b1 = bytes[off + 1] & 0xFF;
int b2 = bytes[off + 2] & 0xFF;
int b3 = bytes[off + 3] & 0xFF;
return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
}
/**
* 将两个字节转换为 int16。
*
* @param bytes 字节数组
* @param off 起始位置
* @return 转换结果
*/
public static int byte2ToInt(byte[] bytes, int off) {
int b0 = bytes[off] & 0xFF;
int b1 = bytes[off + 1] & 0xFF;
return (b1 << 8) | b0;
}
/**
* 将四个字节转换为 long。
*
* @param bytes 字节数组
* @param off 起始位置
* @return 转换结果
*/
public static long byte4ToLong(byte[] bytes, int off) {
long b0 = bytes[off] & 0xFF;
long b1 = bytes[off + 1] & 0xFF;
long b2 = bytes[off + 2] & 0xFF;
long b3 = bytes[off + 3] & 0xFF;
return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
}
}

View File

@@ -0,0 +1,192 @@
package com.njcn.gather.tool.wave.utils;
import com.njcn.gather.tool.wave.pojo.bo.InstantData;
import com.njcn.gather.tool.wave.pojo.bo.RmsData;
import com.njcn.gather.tool.wave.pojo.bo.WaveDataDetail;
import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 波形明细组装工具。
*/
public final class WaveUtil {
/** 单相展示颜色。 */
private static final List<String> ONE_PHASE_COLORS = Collections.unmodifiableList(Arrays.asList("#DAA520", "#fff", "#fff"));
/** 两相展示颜色。 */
private static final List<String> TWO_PHASE_COLORS = Collections.unmodifiableList(Arrays.asList("#DAA520", "#2E8B57", "#fff"));
/** 三相展示颜色。 */
private static final List<String> THREE_PHASE_COLORS = Collections.unmodifiableList(Arrays.asList("#DAA520", "#2E8B57", "#A52a2a"));
private WaveUtil() {
}
/**
* 将原始波形结果整理成前端查看明细。
*
* @param waveDataDTO 波形结果数据
* @return 波形明细列表
*/
public static List<WaveDataDetail> filterWaveData(WaveDataDTO waveDataDTO) {
List<WaveDataDetail> waveDataDetails = new ArrayList<>();
List<String> waveTitle = waveDataDTO.getWaveTitle();
List<String> channelNames = waveDataDTO.getChannelNames();
int phaseCount = waveDataDTO.getIPhasic();
boolean openTri = waveDataDTO.getPtType() != null && waveDataDTO.getPtType() == 2;
int picCount = (waveTitle.size() - 1) / phaseCount;
for (int i = 0; i < picCount; i++) {
WaveDataDetail detail = buildBaseDetail(waveTitle, channelNames, phaseCount, i);
float ratio = resolveRatio(waveDataDTO, waveTitle.get(phaseCount * i + 1), detail);
detail.setInstantData(buildInstantData(waveDataDTO.getListWaveData(), waveTitle, phaseCount, i, ratio, openTri));
detail.setRmsData(buildRmsData(waveDataDTO.getListRmsData(), waveTitle, phaseCount, i, ratio, openTri));
detail.setIsOpen(openTri);
waveDataDetails.add(detail);
}
return waveDataDetails;
}
/**
* 构建单组波形的基础明细信息。
*/
private static WaveDataDetail buildBaseDetail(List<String> waveTitle, List<String> channelNames, int phaseCount, int index) {
WaveDataDetail detail = new WaveDataDetail();
switch (phaseCount) {
case 1:
detail.setA(waveTitle.get(index + 1).substring(1));
detail.setB("");
detail.setC("");
detail.setChannelName(channelNames.get(index + 1));
detail.setColors(new ArrayList<>(ONE_PHASE_COLORS));
break;
case 2:
detail.setA(waveTitle.get(index * 2 + 1).substring(1));
detail.setB(waveTitle.get(index * 2 + 2).substring(1));
detail.setC("");
detail.setChannelName(channelNames.get(index * 2 + 1));
detail.setColors(new ArrayList<>(TWO_PHASE_COLORS));
break;
case 3:
default:
detail.setA(waveTitle.get(index * 3 + 1).substring(1));
detail.setB(waveTitle.get(index * 3 + 2).substring(1));
detail.setC(waveTitle.get(index * 3 + 3).substring(1));
detail.setChannelName(channelNames.get(index * 3 + 1));
detail.setColors(new ArrayList<>(THREE_PHASE_COLORS));
break;
}
return detail;
}
/**
* 根据标题判断比例和单位。
*/
private static float resolveRatio(WaveDataDTO waveDataDTO, String title, WaveDataDetail detail) {
if (title.substring(0, 1).equalsIgnoreCase("U")) {
detail.setUnit("kV");
return waveDataDTO.getPt().floatValue() / 1000;
}
detail.setUnit("A");
return waveDataDTO.getCt().floatValue();
}
/**
* 构建 RMS 明细数据。
*/
private static RmsData buildRmsData(List<List<Float>> sourceData, List<String> waveTitle, int phaseCount, int index,
float ratio, boolean openTri) {
List<List<Float>> aValues = new ArrayList<>();
List<List<Float>> bValues = new ArrayList<>();
List<List<Float>> cValues = new ArrayList<>();
float max = Float.NEGATIVE_INFINITY;
float min = Float.POSITIVE_INFINITY;
if (sourceData == null) {
sourceData = Collections.emptyList();
}
for (List<Float> row : sourceData) {
if (row == null || row.isEmpty()) {
continue;
}
float x = row.get(0);
float valueA = 0f;
float valueB = 0f;
float valueC = 0f;
boolean existsA = false;
boolean existsB = false;
boolean existsC = false;
for (int m = 0; m < phaseCount; m++) {
String currentTitle = waveTitle.get(phaseCount * index + m + 1).substring(1);
float value = row.get(phaseCount * index + m + 1) * ratio;
if (currentTitle.contains("A")) {
valueA = value;
existsA = true;
aValues.add(buildPoint(x, value));
} else if (currentTitle.contains("B")) {
valueB = value;
existsB = true;
bValues.add(buildPoint(x, value));
} else if (currentTitle.contains("C")) {
valueC = value;
existsC = true;
cValues.add(buildPoint(x, value));
}
}
if (existsA) {
max = Math.max(max, valueA);
min = Math.min(min, valueA);
}
if (existsB) {
max = Math.max(max, valueB);
if (!openTri) {
min = Math.min(min, valueB);
}
}
if (existsC) {
max = Math.max(max, valueC);
min = Math.min(min, valueC);
}
}
RmsData data = new RmsData();
data.setAValue(aValues);
data.setBValue(bValues);
data.setCValue(cValues);
data.setMax(max == Float.NEGATIVE_INFINITY ? 0f : max);
data.setMin(min == Float.POSITIVE_INFINITY ? 0f : min);
return data;
}
/**
* 构建单个点位。
*/
private static List<Float> buildPoint(float x, float y) {
List<Float> point = new ArrayList<>(2);
point.add(x);
point.add(y);
return point;
}
/**
* 基于瞬时数据构建明细对象。
*/
private static InstantData buildInstantData(List<List<Float>> sourceData, List<String> waveTitle, int phaseCount, int index,
float ratio, boolean openTri) {
RmsData rmsData = buildRmsData(sourceData, waveTitle, phaseCount, index, ratio, openTri);
InstantData instantData = new InstantData();
instantData.setAValue(rmsData.getAValue());
instantData.setBValue(rmsData.getBValue());
instantData.setCValue(rmsData.getCValue());
instantData.setMax(rmsData.getMax());
instantData.setMin(rmsData.getMin());
return instantData;
}
}