From 7fc8996bdfd28ed09f32f1ea267faf87bb0a3f33 Mon Sep 17 00:00:00 2001 From: yexb <553699424@qq.com> Date: Thu, 16 Apr 2026 08:18:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A2=E5=BD=A2=E8=A7=A3=E6=9E=90=E7=9B=B8?= =?UTF-8?q?=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/wave-tool/PARSE_COMTRADE_API.md | 266 ++++ .../wave/component/WaveFileComponent.java | 1077 +++++++++++++++++ .../component/WaveFileComponentTestMain.java | 122 ++ .../gather/tool/wave/pojo/bo/InstantData.java | 28 + .../gather/tool/wave/pojo/bo/RmsData.java | 28 + .../tool/wave/pojo/bo/WaveDataDetail.java | 43 + .../gather/tool/wave/pojo/dto/AnalogDTO.java | 43 + .../tool/wave/pojo/dto/ComtradeCfgDTO.java | 51 + .../gather/tool/wave/pojo/dto/DigitalDTO.java | 27 + .../tool/wave/pojo/dto/EigenvalueDTO.java | 25 + .../tool/wave/pojo/dto/MutationDTO.java | 29 + .../gather/tool/wave/pojo/dto/RateDTO.java | 23 + .../tool/wave/pojo/dto/WaveDataDTO.java | 42 + .../wave/pojo/enums/WaveFileResponseEnum.java | 36 + .../pojo/param/WaveComtradeParseParam.java | 50 + .../tool/wave/pojo/param/WaveParseParam.java | 47 + .../wave/pojo/vo/WaveComtradeResultVO.java | 31 + .../tool/wave/pojo/vo/WaveParseResultVO.java | 60 + .../gather/tool/wave/pojo/vo/WavePointVO.java | 27 + .../gather/tool/wave/utils/BitConverter.java | 83 ++ .../njcn/gather/tool/wave/utils/WaveUtil.java | 192 +++ 21 files changed, 2330 insertions(+) create mode 100644 tools/wave-tool/PARSE_COMTRADE_API.md create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponent.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/InstantData.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/RmsData.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/WaveDataDetail.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/AnalogDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/ComtradeCfgDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/DigitalDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/EigenvalueDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/MutationDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/RateDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveDataDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/enums/WaveFileResponseEnum.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveComtradeParseParam.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveParseParam.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeResultVO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveParseResultVO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WavePointVO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/utils/BitConverter.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/utils/WaveUtil.java diff --git a/tools/wave-tool/PARSE_COMTRADE_API.md b/tools/wave-tool/PARSE_COMTRADE_API.md new file mode 100644 index 0000000..920b321 --- /dev/null +++ b/tools/wave-tool/PARSE_COMTRADE_API.md @@ -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` + +用途说明: + +- 上传一组 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`。当前仓库内未展开 `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 | 波形标题,例如 `["Time","UA相","UB相"]` | +| `channelNames` | array | 通道名称列表 | +| `listWaveData` | array> | 原始波形数据,首列为时间,后续列为相电压/电流值 | +| `listRmsData` | array> | RMS 波形数据,`calculateRms=true` 时可用 | +| `listRmsMinData` | array> | 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 | 曲线颜色 | + +其中 `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> | A 相点位 | +| `bValue` | array> | B 相点位 | +| `cValue` | array> | 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`,其他波形文本解析接口请单独编写。 diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponent.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponent.java new file mode 100644 index 0000000..bdc1c44 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponent.java @@ -0,0 +1,1077 @@ +package com.njcn.gather.tool.wave.component; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.gather.tool.wave.pojo.dto.*; +import com.njcn.gather.tool.wave.pojo.enums.WaveFileResponseEnum; +import com.njcn.gather.tool.wave.utils.BitConverter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.*; +import java.nio.file.Files; +import java.util.*; + + +/** + * 波形文件解析组件。 + */ +@Slf4j +@Component +public class WaveFileComponent { + + /** 90% 额定电压阈值。 */ + private static final float UN09 = 0.90f * 57.74f; + /** 2% 额定电压变化阈值。 */ + private static final float UN002 = 0.02f * 57.74f; + /** 110% 额定电压阈值。 */ + private static final float UN110 = 1.10f * 57.74f; + + + /** + * 解析 COMTRADE 配置与数据流,返回波形基础数据。 + */ + public WaveDataDTO getComtrade(InputStream cfgStream, InputStream datStream, int iType) { + WaveDataDTO waveDataDTO = new WaveDataDTO(); + ComtradeCfgDTO comtradeCfgDTO = getComtradeCfg(cfgStream); + if (comtradeCfgDTO == null || !"BINARY".equalsIgnoreCase(comtradeCfgDTO.getStrBinType())) { + throw new BusinessException(WaveFileResponseEnum.CFG_DATA_ERROR); + } + + + if (comtradeCfgDTO.getNAnalogNum() % 3 == 0) { + comtradeCfgDTO.setNPhasic(3); + } else { + comtradeCfgDTO.setNPhasic(1); + } + + waveDataDTO.setIPhasic(comtradeCfgDTO.getNPhasic()); + + getWaveTitle(waveDataDTO, comtradeCfgDTO); + + List> listWaveData = getComtradeDat(comtradeCfgDTO, datStream, iType); + + waveDataDTO.setComtradeCfgDTO(comtradeCfgDTO); + + waveDataDTO.setListWaveData(listWaveData); + + waveDataDTO.setTime(DateUtil.format(comtradeCfgDTO.getTimeTrige(), DatePattern.NORM_DATETIME_MS_PATTERN)); + + + return waveDataDTO; + } + + /** + * 基于瞬时值计算 RMS 数据。 + */ + @SuppressWarnings("unused") + public WaveDataDTO getValidData(WaveDataDTO waveDataDTO) { + ComtradeCfgDTO comtradeCfgDTO = waveDataDTO.getComtradeCfgDTO(); + List> lstWave = waveDataDTO.getListWaveData(); + List> listRms = new ArrayList<>(); + if (comtradeCfgDTO == null || comtradeCfgDTO.getFinalSampleRate() == null || comtradeCfgDTO.getFinalSampleRate() <= 0) { + throw new BusinessException(WaveFileResponseEnum.RMS_DATA_ERROR); + } + + int halfTs = comtradeCfgDTO.getFinalSampleRate().intValue(); + + double iWave; + int nPhasic; + List> listRmsMin = new ArrayList<>(); + if (lstWave.size() > 0) { + nPhasic = comtradeCfgDTO.getNPhasic(); + iWave = Math.floor((lstWave.get(0).size() - 1) / (double) nPhasic); + List tmpListRms; + List tmpListRmsMin; + double fMinTime = 0.0, fMinValue = 0.0; + for (int j = 0; j < iWave; j++) { + tmpListRmsMin = new ArrayList<>(); + double fSumA = 0.0, fSumB = 0.0, fSumC = 0.0; + double fValidA, fValidB, fValidC; + for (int i = 0; i < lstWave.size(); i++) { + List tmpListValue = lstWave.get(i); + if (j == 0) { + tmpListRms = new ArrayList<>(); + tmpListRms.add(tmpListValue.get(0)); + listRms.add(tmpListRms); + } else { + tmpListRms = listRms.get(i); + } + + if (tmpListValue.size() >= 2) { + switch (comtradeCfgDTO.getNPhasic()) { + case 1: + fSumA += Math.pow(tmpListValue.get(1 + nPhasic * j), 2); + if (i >= halfTs) { + List forwardListValue = lstWave.get(i - halfTs); + fSumA -= Math.pow(forwardListValue.get(1 + nPhasic * j), 2); + } + fValidA = Math.sqrt(fSumA / halfTs); + tmpListRms.add((float) (Math.round(fValidA * 100)) / 100); + listRms.set(i, tmpListRms); + if (i >= halfTs) { + if (i == halfTs) { + fMinValue = fValidA; + fMinTime = tmpListValue.get(0); + } else { + if (fValidA < fMinValue) { + fMinValue = fValidA; + fMinTime = tmpListValue.get(0); + } + } + } + break; + case 2: + fSumA += Math.pow(tmpListValue.get(1 + nPhasic * j), 2); + fSumB += Math.pow(tmpListValue.get(2 + nPhasic * j), 2); + if (i >= halfTs) { + List forwardListValue = lstWave.get(i - halfTs); + fSumA -= Math.pow(forwardListValue.get(1 + nPhasic * j), 2); + fSumB -= Math.pow(forwardListValue.get(2 + nPhasic * j), 2); + } + fValidA = Math.sqrt(fSumA / halfTs); + fValidB = Math.sqrt(fSumB / halfTs); + + tmpListRms.add((float) (Math.round(fValidA * 100)) / 100); + tmpListRms.add((float) (Math.round(fValidB * 100)) / 100); + listRms.set(i, tmpListRms); + + if (i >= halfTs) { + if (i == halfTs) { + fMinValue = fValidA; + fMinTime = tmpListValue.get(0); + } else { + if (fValidA < fMinValue) { + fMinValue = fValidA; + fMinTime = tmpListValue.get(0); + } + if (fValidB < fMinValue) { + fMinValue = fValidB; + fMinTime = tmpListValue.get(0); + } + } + } + break; + case 3: + fSumA += Math.pow(tmpListValue.get(1 + nPhasic * j), 2); + fSumB += Math.pow(tmpListValue.get(2 + nPhasic * j), 2); + fSumC += Math.pow(tmpListValue.get(3 + nPhasic * j), 2); + if (i >= halfTs) { + List forwardListValue = lstWave.get(i - halfTs); + fSumA -= Math.pow(forwardListValue.get(1 + nPhasic * j), 2); + fSumB -= Math.pow(forwardListValue.get(2 + nPhasic * j), 2); + fSumC -= Math.pow(forwardListValue.get(3 + nPhasic * j), 2); + } + fValidA = Math.sqrt(fSumA / halfTs); + fValidB = Math.sqrt(fSumB / halfTs); + fValidC = Math.sqrt(fSumC / halfTs); + + tmpListRms.add((float) (Math.round(fValidA * 100)) / 100); + tmpListRms.add((float) (Math.round(fValidB * 100)) / 100); + tmpListRms.add((float) (Math.round(fValidC * 100)) / 100); + listRms.set(i, tmpListRms); + + if (i >= halfTs) { + if (i == halfTs) { + fMinValue = fValidA; + fMinTime = tmpListValue.get(0); + } else { + if (fValidA < fMinValue) { + fMinValue = fValidA; + fMinTime = tmpListValue.get(0); + } + if (fValidB < fMinValue) { + fMinValue = fValidB; + fMinTime = tmpListValue.get(0); + } + if (fValidC < fMinValue) { + fMinValue = fValidC; + fMinTime = tmpListValue.get(0); + } + } + } + break; + default: + break; + } + } + } + tmpListRmsMin.add((float) fMinTime); + tmpListRmsMin.add((float) (Math.round(fMinValue * 100)) / 100); + listRmsMin.add(tmpListRmsMin); + } + + try { + for (int i = 0; i < halfTs; i++) { + List tmpNewListRms = new ArrayList<>(); + for (int j = 0; j < iWave; j++) { + if (j == 0) { + tmpNewListRms.add(listRms.get(i).get(0)); + } + switch (nPhasic) { + case 1: + tmpNewListRms.add(listRms.get(i + halfTs).get(1 + nPhasic * j)); + break; + case 2: + tmpNewListRms.add(listRms.get(i + halfTs).get(1 + nPhasic * j)); + tmpNewListRms.add(listRms.get(i + halfTs).get(2 + nPhasic * j)); + break; + case 3: + tmpNewListRms.add(listRms.get(i + halfTs).get(1 + nPhasic * j)); + tmpNewListRms.add(listRms.get(i + halfTs).get(2 + nPhasic * j)); + tmpNewListRms.add(listRms.get(i + halfTs).get(3 + nPhasic * j)); + break; + default: + break; + } + } + listRms.set(i, tmpNewListRms); + } + } catch (Exception e) { + throw new BusinessException(WaveFileResponseEnum.RMS_DATA_ERROR); + } + } + waveDataDTO.setListRmsData(listRms); + waveDataDTO.setListRmsMinData(listRmsMin); + + return waveDataDTO; + } + + /** + * 计算波形特征值结果。 + */ + public List getEigenvalue(WaveDataDTO waveDataDTO, boolean blType) { + ComtradeCfgDTO comtradeCfgDTO = waveDataDTO.getComtradeCfgDTO(); + List> lstWave = waveDataDTO.getListWaveData(); + if (comtradeCfgDTO == null || comtradeCfgDTO.getFinalSampleRate() == null || comtradeCfgDTO.getFinalSampleRate() <= 0) { + throw new BusinessException(WaveFileResponseEnum.CFG_DATA_ERROR); + } + int finalSampleRate = comtradeCfgDTO.getFinalSampleRate(); + List lstEigenvalueDTO = new ArrayList<>(); + + if (lstWave.size() > 0) { + MutationDTO mutationDTO = getMutationValue(lstWave, finalSampleRate); + if (mutationDTO.getListRms_Offline().size() > 0 && mutationDTO.getListTBL_Offline().size() > 0) { + lstEigenvalueDTO = getEventValue(lstWave, mutationDTO, comtradeCfgDTO, blType); + } + } else { + lstEigenvalueDTO = null; + } + return lstEigenvalueDTO; + } + + /** + * 按文件路径打开波形文件输入流。 + */ + public InputStream getFileInputStreamByFilePath(String filePath) { + File file = new File(filePath); + if (file.isFile() && file.exists()) { + InputStream inputStream; + try { + inputStream = Files.newInputStream(file.toPath()); + if (inputStream.available() < 1) { + throw new BusinessException(WaveFileResponseEnum.WAVE_DATA_INVALID); + } + return inputStream; + } catch (IOException e) { + throw new BusinessException(WaveFileResponseEnum.WAVE_DATA_INVALID); + } + } else { + throw new BusinessException(WaveFileResponseEnum.ANALYSE_WAVE_NOT_FOUND); + } + } + + /** + * 读取并解析 CFG 配置内容。 + */ + private ComtradeCfgDTO getComtradeCfg(InputStream cfgStream) { + ComtradeCfgDTO comtradeCfgDTO = new ComtradeCfgDTO(); + try (InputStreamReader read = new InputStreamReader(cfgStream, CharsetUtil.CHARSET_GBK); + BufferedReader bufferedReader = new BufferedReader(read)) { + String strFileLine = bufferedReader.readLine(); + if (StrUtil.isBlank(strFileLine)) { + throw new BusinessException(WaveFileResponseEnum.CFG_DATA_ERROR); + } + + strFileLine = bufferedReader.readLine(); + if (StrUtil.isBlank(strFileLine)) { + throw new BusinessException(WaveFileResponseEnum.CFG_DATA_ERROR); + } + String[] strTempArray = strFileLine.split(StrUtil.COMMA); + comtradeCfgDTO.setNChannelNum(Integer.parseInt(strTempArray[0])); + comtradeCfgDTO.setNAnalogNum(Integer.parseInt(strTempArray[1].substring(0, strTempArray[1].length() - 1))); + comtradeCfgDTO.setNDigitalNum(Integer.parseInt(strTempArray[2].substring(0, strTempArray[2].length() - 1))); + + List lstAnalogDTO = new ArrayList<>(); + comtradeCfgDTO.setLstAnalogDTO(lstAnalogDTO); + for (int i = 0; i < comtradeCfgDTO.getNAnalogNum(); i++) { + AnalogDTO analogDTO = new AnalogDTO(); + lstAnalogDTO.add(analogDTO); + strFileLine = bufferedReader.readLine(); + strTempArray = strFileLine.split(StrUtil.COMMA); + analogDTO.setNIndex(Integer.parseInt(strTempArray[0])); + analogDTO.setSzChannleName(strTempArray[1]); + analogDTO.setSzPhasicName(strTempArray[2]); + analogDTO.setSzMonitoredChannleName(strTempArray[3]); + analogDTO.setSzUnitName(strTempArray[4]); + analogDTO.setFCoefficent(Float.parseFloat(strTempArray[5])); + analogDTO.setFOffset(Float.parseFloat(strTempArray[6])); + analogDTO.setFTimeOffset(Float.parseFloat(strTempArray[7])); + analogDTO.setNMin(Integer.parseInt(strTempArray[8])); + analogDTO.setNMax(Integer.parseInt(strTempArray[9])); + analogDTO.setFPrimary(Float.parseFloat(strTempArray[10])); + analogDTO.setFSecondary(Float.parseFloat(strTempArray[11])); + analogDTO.setSzValueType(strTempArray[12]); + } + + List lstDigitalDTO = new ArrayList<>(); + comtradeCfgDTO.setLstDigitalDTO(lstDigitalDTO); + for (int i = 0; i < comtradeCfgDTO.getNDigitalNum(); i++) { + DigitalDTO digitalDTO = new DigitalDTO(); + lstDigitalDTO.add(digitalDTO); + strFileLine = bufferedReader.readLine(); + strTempArray = strFileLine.split(StrUtil.COMMA); + digitalDTO.setNIndex(Integer.parseInt(strTempArray[0])); + digitalDTO.setSzChannleName(strTempArray[1]); + digitalDTO.setSzPhasicName(strTempArray[2]); + digitalDTO.setSzMonitoredChannleName(strTempArray[3]); + digitalDTO.setInitial(Integer.parseInt(strTempArray[4])); + } + + String freqLine = bufferedReader.readLine(); + int nFreq; + try { + nFreq = (int) Math.round(Double.parseDouble(freqLine)); + } catch (NumberFormatException e) { + nFreq = Integer.parseInt(freqLine); + } + + strFileLine = bufferedReader.readLine(); + int nRates = Integer.parseInt(strFileLine); + comtradeCfgDTO.setNRates(nRates); + List lstRate = new ArrayList<>(); + int nOffset = 0; + for (int i = 0; i < nRates; i++) { + strFileLine = bufferedReader.readLine(); + strTempArray = strFileLine.split(StrUtil.COMMA); + RateDTO rateDTO = new RateDTO(); + double doubleValue = Double.parseDouble(strTempArray[0]); // 先按 double 解析采样率 + int result = (int) (doubleValue / nFreq); // 转换为单周波采样点数 + rateDTO.setNOneSample(result); + rateDTO.setNSampleNum((Integer.parseInt(strTempArray[1]) - nOffset)); + nOffset += rateDTO.getNSampleNum(); + lstRate.add(rateDTO); + } + comtradeCfgDTO.setLstRate(lstRate); + String timeFormat = "dd/MM/yyyy,HH:mm:ss.SSS"; + strFileLine = bufferedReader.readLine(); + strFileLine = strFileLine.substring(0, strFileLine.length() - 3); + comtradeCfgDTO.setTimeStart(DateUtil.parse(strFileLine, timeFormat)); + + strFileLine = bufferedReader.readLine(); + strFileLine = strFileLine.substring(0, strFileLine.length() - 3); + comtradeCfgDTO.setTimeTrige(DateUtil.parse(strFileLine, timeFormat)); + + Calendar calendar = DateUtil.calendar(comtradeCfgDTO.getTimeTrige()); + comtradeCfgDTO.setFirstMs(calendar.get(Calendar.MILLISECOND)); + comtradeCfgDTO.setFirstTime(calendar.getTime()); + + + long a = comtradeCfgDTO.getTimeStart().getTime(); + long b = comtradeCfgDTO.getTimeTrige().getTime(); + + int c = (int) (b - a); + if (c >= 90 && c <= 110) { + comtradeCfgDTO.setNPush(100); + } else if (c >= 190 && c <= 210) { + comtradeCfgDTO.setNPush(200); + } + comtradeCfgDTO.setStrBinType(bufferedReader.readLine().toUpperCase()); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("CFG 文件解析失败", e); + throw new BusinessException(WaveFileResponseEnum.CFG_DATA_ERROR); + } + return comtradeCfgDTO; + } + + /** + * 读取并按目标采样率转换 DAT 数据。 + */ + private List> getComtradeDat(ComtradeCfgDTO comtradeCfgDTO, InputStream datStream, int iType) { + List> listWaveData = new ArrayList<>(); + float xValueAll = 0; + boolean blxValue = false; + byte[] datArray; + int datLength = -1; + int nDigSize = -1; + int nBlockSize = -1; + int nBlockNum = -1; + try { + datArray = IoUtil.readBytes(datStream); + datLength = datArray == null ? -1 : datArray.length; + if (ArrayUtil.isEmpty(datArray)) { + log.warn("DAT 文件解析失败:文件内容为空,{}", buildDatParseContext(comtradeCfgDTO, 0, iType, nDigSize, nBlockSize, nBlockNum)); + throw new BusinessException(WaveFileResponseEnum.DAT_DATA_ERROR); + } + nDigSize = (comtradeCfgDTO.getNDigitalNum() % 16) > 0 ? (comtradeCfgDTO.getNDigitalNum() / 16 + 1) * 2 : comtradeCfgDTO.getNDigitalNum() / 16 * 2; + nBlockSize = 2 * Integer.SIZE / 8 + comtradeCfgDTO.getNAnalogNum() * 2 + nDigSize; + if (nBlockSize <= 0) { + log.error("DAT 文件解析失败:根据 CFG 计算出的块大小非法,{}", buildDatParseContext(comtradeCfgDTO, datArray.length, iType, nDigSize, nBlockSize, nBlockNum)); + throw new BusinessException(WaveFileResponseEnum.DAT_DATA_ERROR); + } + nBlockNum = (int) Math.floor((double) datArray.length / nBlockSize); + if (nBlockNum <= 0) { + log.warn("DAT 文件解析失败:DAT 长度小于单帧块大小,{}", buildDatParseContext(comtradeCfgDTO, datArray.length, iType, nDigSize, nBlockSize, nBlockNum)); + throw new BusinessException(WaveFileResponseEnum.DAT_DATA_ERROR); + } + if (datArray.length % nBlockSize != 0) { + log.warn("DAT 文件长度与块大小不整除,将按完整块解析,{}", buildDatParseContext(comtradeCfgDTO, datArray.length, iType, nDigSize, nBlockSize, nBlockNum)); + } + + int finalSampleRate = getFinalWaveSample(comtradeCfgDTO.getLstRate(), iType); + if (finalSampleRate <= 0) { + log.error("DAT 文件解析失败:最终采样率非法,{},采样率分段={}", + buildDatParseContext(comtradeCfgDTO, datArray.length, iType, nDigSize, nBlockSize, nBlockNum), + buildRateSummary(comtradeCfgDTO.getLstRate())); + throw new BusinessException(WaveFileResponseEnum.DAT_DATA_ERROR); + } + comtradeCfgDTO.setFinalSampleRate(finalSampleRate); + int nnInd = 0; + int nWaveNum; + List newLstRate = new ArrayList<>(); + for (int iRate = 0; iRate < comtradeCfgDTO.getNRates(); iRate++) { + nWaveNum = comtradeCfgDTO.getLstRate().get(iRate).getNSampleNum() / comtradeCfgDTO.getLstRate().get(iRate).getNOneSample(); + comtradeCfgDTO.setNAllWaveNum(comtradeCfgDTO.getNAllWaveNum() + nWaveNum); + RateDTO tmpRateDTO = new RateDTO(); + if (comtradeCfgDTO.getLstRate().get(iRate).getNOneSample() >= 32) { + //YXB 2025-08-27 + tmpRateDTO.bRMSFlag = false; + } else if (comtradeCfgDTO.getLstRate().get(iRate).getNOneSample() <= 2) { + //YXB 2025-08-27 + tmpRateDTO.bRMSFlag = true; + } + newLstRate.add(tmpRateDTO); + if (iType != 3) { + if (!Objects.equals(comtradeCfgDTO.getLstRate().get(iRate).getNOneSample(), comtradeCfgDTO.getFinalSampleRate())) { + newLstRate.get(nnInd).setNOneSample(comtradeCfgDTO.getFinalSampleRate()); + newLstRate.get(nnInd).setNSampleNum(comtradeCfgDTO.getFinalSampleRate() * nWaveNum); + } else { + newLstRate.get(nnInd).setNOneSample(comtradeCfgDTO.getLstRate().get(iRate).getNOneSample()); + newLstRate.get(nnInd).setNSampleNum(comtradeCfgDTO.getLstRate().get(iRate).getNOneSample() * nWaveNum); + } + } else { + newLstRate.get(nnInd).setNOneSample(comtradeCfgDTO.getLstRate().get(iRate).getNOneSample()); + newLstRate.get(nnInd).setNSampleNum(comtradeCfgDTO.getLstRate().get(iRate).getNOneSample() * nWaveNum); + } + + nnInd++; + } + long nOffSet = 0, nWaveSpan; + float fValue, dfValue; + int nIndex = 0; + RateDTO tmpRateDTO; + for (int i = 0; i < nBlockNum; i++) { + tmpRateDTO = comtradeCfgDTO.getLstRate().get(nIndex); + if (i == tmpRateDTO.getNSampleNum() + nOffSet) { + nOffSet += tmpRateDTO.getNSampleNum(); + nIndex++; + if (nIndex == nnInd) { + break; + } + } + tmpRateDTO = comtradeCfgDTO.getLstRate().get(nIndex); + if (newLstRate.get(nIndex).bRMSFlag == true) { + nWaveSpan = newLstRate.get(nIndex).getNOneSample() / tmpRateDTO.getNOneSample(); + } else { + nWaveSpan = tmpRateDTO.getNOneSample() / newLstRate.get(nIndex).getNOneSample(); + } + + dfValue = (float) 20 / tmpRateDTO.getNOneSample(); + if (i % nWaveSpan == 0) { + List tmpWaveData = new ArrayList<>(); + if (newLstRate.get(nIndex).bRMSFlag == true) { + long allWaveTemp = newLstRate.get(nIndex).getNSampleNum() / newLstRate.get(nIndex).getNOneSample(); + long allempSample = newLstRate.get(nIndex).getNOneSample(); + + int currentDataIndex = i; + for (int iWaveTemp = 0; iWaveTemp < allWaveTemp; iWaveTemp++) { + for (int mTempSample = 0; mTempSample < allempSample; mTempSample++) { + tmpWaveData = new ArrayList<>(); + for (int j = 0; j < comtradeCfgDTO.getNAnalogNum(); j++) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzPhasicName().equalsIgnoreCase("N")) { + break; + } + float fCoef = comtradeCfgDTO.getLstAnalogDTO().get(j).getFCoefficent(); + fValue = BitConverter.byte2ToUnsignedShort(datArray, currentDataIndex * nBlockSize + 2 * 4 + j * 2) * fCoef; + //WW 2019-11-14 + + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzValueType().equalsIgnoreCase("S")) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzUnitName().equalsIgnoreCase("KV")) { + fValue = fValue * 1000.0f; + } else { + fValue = fValue; + } + } else if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzValueType().equalsIgnoreCase("P")) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzUnitName().equalsIgnoreCase("V")) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary() != 0.0f) { + fValue = fValue * comtradeCfgDTO.getLstAnalogDTO().get(j).getFSecondary() / comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary(); + } else { + fValue = fValue; + } + } else if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzUnitName().equalsIgnoreCase("KV")) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary() != 0.0f) { + fValue = fValue * 1000.0f * comtradeCfgDTO.getLstAnalogDTO().get(j).getFSecondary() / comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary(); + } else { + fValue = fValue; + } + } else { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary() != 0.0f) { + fValue = fValue * comtradeCfgDTO.getLstAnalogDTO().get(j).getFSecondary() / comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary(); + } else { + fValue = fValue; + } + } + } + + if (!blxValue && j == 0) { + xValueAll = (float) (currentDataIndex * 20) / tmpRateDTO.getNOneSample() - comtradeCfgDTO.getNPush(); + blxValue = true; + tmpWaveData.add((float) (Math.round(xValueAll * 100)) / 100); + } else if (j == 0) { + xValueAll += (float) dfValue / nWaveSpan; + tmpWaveData.add((float) (Math.round(xValueAll * 100)) / 100); + } + + tmpWaveData.add((float) (Math.round(fValue * 100)) / 100); + } + listWaveData.add(tmpWaveData); + } + if (iWaveTemp < (allWaveTemp - 1)) { + currentDataIndex++; + } + } + } else { + for (int j = 0; j < comtradeCfgDTO.getNAnalogNum(); j++) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzPhasicName().equalsIgnoreCase("N")) { + break; + } + + float fCoef = comtradeCfgDTO.getLstAnalogDTO().get(j).getFCoefficent(); + fValue = BitConverter.byte2ToUnsignedShort(datArray, i * nBlockSize + 2 * 4 + j * 2) * fCoef; + + //WW 2019-11-14 + + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzValueType().equalsIgnoreCase("S")) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzUnitName().equalsIgnoreCase("KV")) { + fValue = fValue * 1000.0f; + } else { + fValue = fValue; + } + } else if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzValueType().equalsIgnoreCase("P")) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzUnitName().equalsIgnoreCase("V")) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary() != 0.0f) { + fValue = fValue * comtradeCfgDTO.getLstAnalogDTO().get(j).getFSecondary() / comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary(); + } else { + fValue = fValue; + } + } else if (comtradeCfgDTO.getLstAnalogDTO().get(j).getSzUnitName().equalsIgnoreCase("KV")) { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary() != 0.0f) { + fValue = fValue * 1000.0f * comtradeCfgDTO.getLstAnalogDTO().get(j).getFSecondary() / comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary(); + } else { + fValue = fValue; + } + } else { + if (comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary() != 0.0f) { + fValue = fValue * comtradeCfgDTO.getLstAnalogDTO().get(j).getFSecondary() / comtradeCfgDTO.getLstAnalogDTO().get(j).getFPrimary(); + } else { + fValue = fValue; + } + } + } + if (!blxValue && j == 0) { + xValueAll = (float) (i * 20) / tmpRateDTO.getNOneSample() - comtradeCfgDTO.getNPush(); + blxValue = true; + tmpWaveData.add((float) (Math.round(xValueAll * 100)) / 100); + } else if (j == 0) { + xValueAll += (float) nWaveSpan * dfValue; + tmpWaveData.add((float) (Math.round(xValueAll * 100)) / 100); + } + + tmpWaveData.add((float) (Math.round(fValue * 100)) / 100); + } + listWaveData.add(tmpWaveData); + } + } + } + } catch (BusinessException e) { + throw e; + } catch (ArithmeticException e) { + log.error("DAT 文件解析失败:采样率计算出现算术异常,{},采样率分段={}", + buildDatParseContext(comtradeCfgDTO, datLength, iType, nDigSize, nBlockSize, nBlockNum), + buildRateSummary(comtradeCfgDTO.getLstRate()), e); + throw new BusinessException(WaveFileResponseEnum.DAT_DATA_ERROR); + } catch (IndexOutOfBoundsException e) { + log.error("DAT 文件解析失败:按 CFG 读取 DAT 时发生越界,疑似 CFG 与 DAT 不匹配,{},采样率分段={}", + buildDatParseContext(comtradeCfgDTO, datLength, iType, nDigSize, nBlockSize, nBlockNum), + buildRateSummary(comtradeCfgDTO.getLstRate()), e); + throw new BusinessException(WaveFileResponseEnum.DAT_DATA_ERROR); + } catch (Exception e) { + log.error("DAT 文件解析失败:{},采样率分段={}", + buildDatParseContext(comtradeCfgDTO, datLength, iType, nDigSize, nBlockSize, nBlockNum), + buildRateSummary(comtradeCfgDTO.getLstRate()), e); + throw new BusinessException(WaveFileResponseEnum.DAT_DATA_ERROR); + } + + return listWaveData; + } + + /** + * 组装 DAT 解析上下文,便于日志直接定位 cfg/dat 是否匹配。 + */ + private String buildDatParseContext(ComtradeCfgDTO comtradeCfgDTO, int datLength, int iType, int nDigSize, int nBlockSize, int nBlockNum) { + if (comtradeCfgDTO == null) { + return StrUtil.format("parseType={}, datLength={}, nDigSize={}, nBlockSize={}, nBlockNum={}, cfg=空", + iType, datLength, nDigSize, nBlockSize, nBlockNum); + } + return StrUtil.format( + "parseType={}, datLength={}, nDigSize={}, nBlockSize={}, nBlockNum={}, analogNum={}, digitalNum={}, nRates={}, strBinType={}", + iType, + datLength, + nDigSize, + nBlockSize, + nBlockNum, + comtradeCfgDTO.getNAnalogNum(), + comtradeCfgDTO.getNDigitalNum(), + comtradeCfgDTO.getNRates(), + comtradeCfgDTO.getStrBinType() + ); + } + + /** + * 摘要输出采样率分段,便于判断最终采样率为何被算成非法值。 + */ + private String buildRateSummary(List lstRate) { + if (lstRate == null || lstRate.isEmpty()) { + return "[]"; + } + StringJoiner joiner = new StringJoiner("; ", "[", "]"); + for (int i = 0; i < lstRate.size(); i++) { + RateDTO rateDTO = lstRate.get(i); + if (rateDTO == null) { + joiner.add(StrUtil.format("#{}=null", i)); + continue; + } + joiner.add(StrUtil.format("#{}:nOneSample={},nSampleNum={},bRMSFlag={}", + i, + rateDTO.getNOneSample(), + rateDTO.getNSampleNum(), + rateDTO.bRMSFlag)); + } + return joiner.toString(); + } + + /** + * 根据解析类型确定最终采样点数。 + */ + private int getFinalWaveSample(List lstRate, int iType) { + int nFinalOneSample = -1; + int nMinOneSample = -1; + if (!lstRate.isEmpty()) { + nMinOneSample = lstRate.get(0).getNOneSample(); + int tmpOneSample; + for (RateDTO rateDTO : lstRate) { + tmpOneSample = rateDTO.getNOneSample(); + //YXB 2025-08-27 32->16 + if (tmpOneSample >= 16) { + if (nMinOneSample > tmpOneSample) { + nMinOneSample = tmpOneSample; + } + } + } + } + + switch (iType) { + case 0: + if (nMinOneSample < 32) { + nFinalOneSample = 32; + } else if (nMinOneSample > 128) { + nFinalOneSample = 128; + } else { + nFinalOneSample = nMinOneSample; + } + break; + case 2: + nFinalOneSample = 32; + break; + + default: + nFinalOneSample = nMinOneSample; + break; + + } + return nFinalOneSample; + } + + + /** + * 计算 RMS 与突变量基础数据。 + */ + private MutationDTO getMutationValue(List> lstWave, float finalSampleRate) { + MutationDTO mutationDTO = new MutationDTO(); + double fSumA = 0.0, fSumB = 0.0, fSumC = 0.0; + double fValidA, fValidB, fValidC; + double fValue = 0.0; + int HalfTs = (int) finalSampleRate; + List tmpRealValue, forwardRealValue, tblValue, forwardTblValue; + for (int i = 0; i < lstWave.size(); i++) { + tmpRealValue = lstWave.get(i); + if (tmpRealValue.size() >= 4) { + fSumA += Math.pow(tmpRealValue.get(1), 2); + fSumB += Math.pow(tmpRealValue.get(2), 2); + fSumC += Math.pow(tmpRealValue.get(3), 2); + List tmpRmsValue = new ArrayList<>(); + if (i >= finalSampleRate) { + forwardRealValue = lstWave.get(i - HalfTs); + fSumA -= Math.pow(forwardRealValue.get(1), 2); + fSumB -= Math.pow(forwardRealValue.get(2), 2); + fSumC -= Math.pow(forwardRealValue.get(3), 2); + } + List tmpTblValue = new ArrayList<>(); + tmpTblValue.add(tmpRealValue.get(0)); + if (i >= HalfTs) { + tblValue = lstWave.get(i); + forwardTblValue = lstWave.get(i - HalfTs); + tmpTblValue.add(tblValue.get(1) - forwardTblValue.get(1)); + tmpTblValue.add(tblValue.get(2) - forwardTblValue.get(2)); + tmpTblValue.add(tblValue.get(3) - forwardTblValue.get(3)); + } else { + tmpTblValue.add(0f); + tmpTblValue.add(0f); + tmpTblValue.add(0f); + } + fValidA = Math.sqrt(fSumA / HalfTs); + fValidB = Math.sqrt(fSumB / HalfTs); + fValidC = Math.sqrt(fSumC / HalfTs); + + if (i >= finalSampleRate) { + if (fValidA < mutationDTO.getFMinMagA()) { + mutationDTO.setFMinMagA(fValidA); + } + if (fValidB < mutationDTO.getFMinMagB()) { + mutationDTO.setFMinMagB(fValidB); + } + if (fValidC < mutationDTO.getFMinMagC()) { + mutationDTO.setFMinMagC(fValidC); + } + } + tmpRmsValue.add(tmpRealValue.get(0)); + tmpRmsValue.add((float) fValidA); + tmpRmsValue.add((float) fValidB); + tmpRmsValue.add((float) fValidC); + mutationDTO.getListRms_Offline().add(tmpRmsValue); + + mutationDTO.getListTBL_Offline().add(tmpTblValue); + } + } + return mutationDTO; + } + + /** + * 计算三相电压特征值。 + */ + private List getEventValue(List> lstWave, MutationDTO mutationDTO, ComtradeCfgDTO comtradeCfgDTO, boolean blType) { + List> tblWave = mutationDTO.getListTBL_Offline(); + List> rmstWave = mutationDTO.getListRms_Offline(); + float fBase = 57.74f; + List lstAnalogDTO = comtradeCfgDTO.getLstAnalogDTO(); + if (!lstAnalogDTO.isEmpty()) { + if (lstAnalogDTO.get(0).getFPrimary() / lstAnalogDTO.get(0).getFSecondary() <= 1) { + fBase = 220f; + } + } + int nSJ = comtradeCfgDTO.getFinalSampleRate().intValue(); + + List lstEigenvalueDTO = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + EigenvalueDTO eigenvalueDTO = new EigenvalueDTO(); + DisturbanceResult ddyResult = App_Disturb_DDY1(lstWave, tblWave, rmstWave, nSJ, i, blType); + DisturbanceResult finalResult = ddyResult; + if (ddyResult.disturbValue == 0 && ddyResult.disturbStart == 0) { + DisturbanceResult gdyResult = App_Disturb_GDY1(lstWave, tblWave, rmstWave, nSJ, i, blType); + finalResult = gdyResult; + if (gdyResult.tableIndex != 0) { + if (gdyResult.disturbValue != 0) { + if (blType) { + eigenvalueDTO.setAmplitude(gdyResult.disturbValue / rmstWave.get(nSJ + 2).get(i + 1)); + eigenvalueDTO.setResidualVoltage(gdyResult.disturbValue); + eigenvalueDTO.setRatedVoltage(rmstWave.get(nSJ + 2).get(i + 1)); + } else { + eigenvalueDTO.setAmplitude(gdyResult.disturbValue / 57.74f); + eigenvalueDTO.setResidualVoltage(gdyResult.disturbValue); + eigenvalueDTO.setRatedVoltage(57.74f); + } + } + } + else { + double rate = 0f; + double residualVoltage = 0.f; + switch (i) { + case 0: + residualVoltage = mutationDTO.getFMinMagA(); + break; + case 1: + residualVoltage = mutationDTO.getFMinMagB(); + break; + case 2: + residualVoltage = mutationDTO.getFMinMagC(); + break; + default: + break; + } + + if (residualVoltage != -1) { + rate = residualVoltage / fBase > 1 ? 1.0f : residualVoltage / fBase; + } + eigenvalueDTO.setAmplitude((float) rate); + eigenvalueDTO.setResidualVoltage((float) residualVoltage); + eigenvalueDTO.setRatedVoltage(fBase); + } + } else { + if (ddyResult.disturbValue != 0) { + if (blType) { + eigenvalueDTO.setAmplitude(ddyResult.disturbValue / rmstWave.get(nSJ + 2).get(i + 1)); + eigenvalueDTO.setResidualVoltage(ddyResult.disturbValue); + eigenvalueDTO.setRatedVoltage(rmstWave.get(nSJ + 2).get(i + 1)); + } else { + eigenvalueDTO.setAmplitude(ddyResult.disturbValue / 57.74f); + eigenvalueDTO.setResidualVoltage(ddyResult.disturbValue); + eigenvalueDTO.setRatedVoltage(57.74f); + } + } + } + eigenvalueDTO.setDurationTime(finalResult.disturbStart / nSJ * 20.0f); + lstEigenvalueDTO.add(eigenvalueDTO); + } + return lstEigenvalueDTO; + } + + /** + * 电压暂降判据,兼容暂降与短时中断。 + */ + private DisturbanceResult App_Disturb_DDY1(List> realWave, List> tblWave, List> rmstWave, + int nCirclePoint, int nType, boolean blFlag) { + DisturbanceResult result = new DisturbanceResult(); + double temp, ADC, Disturb_JS_Val = 0; + int iTbl = 0; + long Disturb_QD = 0; + int nSJ = nCirclePoint, nHalfSJ = nCirclePoint / 2; + float fUN09 = UN09, fUN002 = UN002; + if (blFlag) { + if (rmstWave.size() > nSJ + 2) { + fUN09 = rmstWave.get(nSJ + 2).get(nType + 1) * 0.9f; + fUN002 = rmstWave.get(nSJ + 2).get(nType + 1) * 0.02f; + } + } + + for (int i = nSJ; i < realWave.size(); i++) { + float rmsValue = rmstWave.get(i).get(nType + 1); + if (Disturb_QD == 0) { + if (rmsValue < fUN09) { + Disturb_QD = 0xff; + result.disturbValue = rmsValue; + Disturb_JS_Val = rmsValue; + for (int j = 0; j < nHalfSJ; j++) { + ADC = tblWave.get(i - nHalfSJ + j).get(nType + 1); + if (ADC < 0) { + ADC = 0 - ADC; + } + if (ADC > fUN002) { + result.disturbStart += (nHalfSJ - j); + iTbl = (i - nHalfSJ + j); + result.tableIndex = iTbl; + break; + } + } + } + } + else { + if (rmsValue < (fUN09 + fUN002)) { + result.disturbStart++; + if (result.disturbValue > rmsValue) { + result.disturbValue = rmsValue; + } + Disturb_JS_Val = rmsValue; + } else { + if (result.disturbStart >= (nSJ + nHalfSJ)) { + int iFlag = 0; + for (int j = 0; j < nHalfSJ; j++) { + iFlag = j; + ADC = tblWave.get(i - nHalfSJ + j).get(nType + 1); + if (ADC < 0) { + ADC = 0 - ADC; + } + if (ADC > fUN002) { + break; + } + } + result.disturbStart -= (nHalfSJ - iFlag); + result.tableIndex = iTbl; + return result; + } else { + result.disturbStart++; + ADC = realWave.get(i).get(nType + 1); + if (ADC < 0) { + ADC = 0 - ADC; + } + temp = rmsValue - Disturb_JS_Val; + if (temp < 0) { + temp = 0 - temp; + } + if ((ADC > 100) && (temp < 0.1)) { + result.disturbStart -= (nHalfSJ + 1); + result.tableIndex = iTbl; + return result; + } + } + Disturb_JS_Val = rmsValue; + } + } + } + result.tableIndex = iTbl; + return result; + } + + /** + * 电压过电压判据。 + */ + private DisturbanceResult App_Disturb_GDY1(List> realWave, List> tblWave, List> rmstWave, + int nCirclePoint, int nType, boolean blFlag) { + DisturbanceResult result = new DisturbanceResult(); + double temp, ADC, Disturb_JS_Val = 0; + int iTbl = 0; + long Disturb_QD = 0; + int nSJ = nCirclePoint, nHalfSJ = nCirclePoint / 2; + float fUN110 = UN110, fUN002 = UN002; + if (blFlag) { + if (rmstWave.size() > nSJ + 2) { + fUN110 = rmstWave.get(nSJ + 2).get(nType + 1) * 1.1f; + fUN002 = rmstWave.get(nSJ + 2).get(nType + 1) * 0.02f; + } + } + + for (int i = nSJ; i < realWave.size(); i++) { + float rmsValue = rmstWave.get(i).get(nType + 1); + if (Disturb_QD == 0) { + if (rmsValue > fUN110) { + Disturb_QD = 0xff; + result.disturbValue = rmsValue; + Disturb_JS_Val = rmsValue; + for (int j = 0; j < nHalfSJ; j++) { + ADC = tblWave.get(i - nHalfSJ + j).get(nType + 1); + if (ADC < 0) { + ADC = 0 - ADC; + } + if (ADC > fUN002) { + result.disturbStart += (nHalfSJ - j); + iTbl = (int) (i - nHalfSJ + j); + result.tableIndex = iTbl; + break; + } + } + } + } + else { + if (rmsValue > (fUN110 - fUN002)) { + result.disturbStart++; + if (result.disturbValue < rmsValue) { + result.disturbValue = rmsValue; + } + Disturb_JS_Val = rmsValue; + } else { + if (result.disturbStart >= (nSJ + nHalfSJ)) { + int iFlag = 0; + for (int j = 0; j < nHalfSJ; j++) { + iFlag = j; + ADC = tblWave.get(i - nHalfSJ + j).get(nType + 1); + if (ADC < 0) { + ADC = 0 - ADC; + } + if (ADC > fUN002) { + break; + } + } + result.disturbStart -= (nHalfSJ - iFlag); + result.tableIndex = iTbl; + return result; + } else { + result.disturbStart++; + ADC = realWave.get(i).get(nType + 1); + if (ADC < 0) { + ADC = 0 - ADC; + } + temp = rmsValue - Disturb_JS_Val; + if (temp < 0) { + temp = 0 - temp; + } + if ((ADC > 100) && (temp < 0.1)) { + result.disturbStart -= (nHalfSJ + 1); + result.tableIndex = iTbl; + return result; + } + } + Disturb_JS_Val = rmsValue; + } + } + } + result.tableIndex = iTbl; + return result; + } + + /** + * 组装波形标题与通道名称。 + */ + private void getWaveTitle(WaveDataDTO waveDataDTO, ComtradeCfgDTO comtradeCfgDTO) { + List tmpWaveTitle = new ArrayList<>(); + List channelName = new ArrayList<>(); + AnalogDTO analogDTO; + tmpWaveTitle.add("Time"); + channelName.add("/"); + String strUnit; + for (int j = 0; j < comtradeCfgDTO.getNAnalogNum(); j++) { + analogDTO = comtradeCfgDTO.getLstAnalogDTO().get(j); + if (!StrUtil.equals(analogDTO.getSzPhasicName().toUpperCase(), "N")) { + if ("A".equalsIgnoreCase(analogDTO.getSzUnitName())) { + strUnit = "I"; + } else { + strUnit = "U"; + } + tmpWaveTitle.add(strUnit + analogDTO.getSzPhasicName().toUpperCase() + "相"); + channelName.add(analogDTO.getSzChannleName()); + } + } + waveDataDTO.setWaveTitle(tmpWaveTitle); + waveDataDTO.setChannelNames(channelName); + } + + /** + * 单次扰动判据的中间结果。 + */ + private static final class DisturbanceResult { + /** 扰动幅值。 */ + private float disturbValue; + /** 扰动持续采样点数。 */ + private float disturbStart; + /** 扰动起始索引。 */ + private int tableIndex; + } + +} + + diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java new file mode 100644 index 0000000..02339c9 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java @@ -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 [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 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 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(); + } +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/InstantData.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/InstantData.java new file mode 100644 index 0000000..1e8697c --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/InstantData.java @@ -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> aValue = new ArrayList<>(); + + /** B 相点位。 */ + private List> bValue = new ArrayList<>(); + + /** C 相点位。 */ + private List> cValue = new ArrayList<>(); +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/RmsData.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/RmsData.java new file mode 100644 index 0000000..96ee013 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/RmsData.java @@ -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> aValue = new ArrayList<>(); + + /** B 相点位。 */ + private List> bValue = new ArrayList<>(); + + /** C 相点位。 */ + private List> cValue = new ArrayList<>(); +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/WaveDataDetail.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/WaveDataDetail.java new file mode 100644 index 0000000..826eb9c --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/bo/WaveDataDetail.java @@ -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 colors = new ArrayList<>(); +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/AnalogDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/AnalogDTO.java new file mode 100644 index 0000000..67316e5 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/AnalogDTO.java @@ -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; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/ComtradeCfgDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/ComtradeCfgDTO.java new file mode 100644 index 0000000..6db0bae --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/ComtradeCfgDTO.java @@ -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 lstAnalogDTO; + /** 开关量通道配置。 */ + private List lstDigitalDTO; + /** 采样率分段数。 */ + public Integer nRates; + /** 采样率分段配置。 */ + public List lstRate; + /** 首个触发时间对象。 */ + private Date firstTime; + /** 首个触发毫秒值。 */ + private Integer firstMs; + /** 触发前推点数。 */ + private Integer nPush = 0; + /** 最终采样率。 */ + private Integer finalSampleRate; + /** 总周波数。 */ + private Integer nAllWaveNum = 0; + /** 文件编码类型,例如 BINARY。 */ + private String strBinType; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/DigitalDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/DigitalDTO.java new file mode 100644 index 0000000..fc66a8f --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/DigitalDTO.java @@ -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; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/EigenvalueDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/EigenvalueDTO.java new file mode 100644 index 0000000..4737cdd --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/EigenvalueDTO.java @@ -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; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/MutationDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/MutationDTO.java new file mode 100644 index 0000000..e708ce5 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/MutationDTO.java @@ -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> listRms_Offline = new ArrayList<>(); + /** 离线突变量数据。 */ + private List> listTBL_Offline = new ArrayList<>(); + /** A 相最小幅值。 */ + private double fMinMagA = 99999d; + /** B 相最小幅值。 */ + private double fMinMagB = 99999d; + /** C 相最小幅值。 */ + private double fMinMagC = 99999d; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/RateDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/RateDTO.java new file mode 100644 index 0000000..315035f --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/RateDTO.java @@ -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; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveDataDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveDataDTO.java new file mode 100644 index 0000000..ac6b9f7 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveDataDTO.java @@ -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 waveTitle; + /** 通道名称列表。 */ + private List channelNames; + /** 原始波形数据。 */ + private List> listWaveData; + /** RMS 波形数据。 */ + private List> listRmsData; + /** RMS 最小值数据。 */ + private List> 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; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/enums/WaveFileResponseEnum.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/enums/WaveFileResponseEnum.java new file mode 100644 index 0000000..1be8252 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/enums/WaveFileResponseEnum.java @@ -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; + } +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveComtradeParseParam.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveComtradeParseParam.java new file mode 100644 index 0000000..d69c015 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveComtradeParseParam.java @@ -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-128,1 普通展示,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; + +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveParseParam.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveParseParam.java new file mode 100644 index 0000000..fd44a25 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/param/WaveParseParam.java @@ -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; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeResultVO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeResultVO.java new file mode 100644 index 0000000..6d263f8 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeResultVO.java @@ -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 waveDataDetails; + + /** 特征值结果。 */ + @ApiModelProperty("特征值结果") + private List eigenvalues; + +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveParseResultVO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveParseResultVO.java new file mode 100644 index 0000000..b84caf0 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveParseResultVO.java @@ -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 points; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WavePointVO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WavePointVO.java new file mode 100644 index 0000000..cf9f631 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WavePointVO.java @@ -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; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/utils/BitConverter.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/utils/BitConverter.java new file mode 100644 index 0000000..29ab2aa --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/utils/BitConverter.java @@ -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; + } +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/utils/WaveUtil.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/utils/WaveUtil.java new file mode 100644 index 0000000..6ea1328 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/utils/WaveUtil.java @@ -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 ONE_PHASE_COLORS = Collections.unmodifiableList(Arrays.asList("#DAA520", "#fff", "#fff")); + /** 两相展示颜色。 */ + private static final List TWO_PHASE_COLORS = Collections.unmodifiableList(Arrays.asList("#DAA520", "#2E8B57", "#fff")); + /** 三相展示颜色。 */ + private static final List THREE_PHASE_COLORS = Collections.unmodifiableList(Arrays.asList("#DAA520", "#2E8B57", "#A52a2a")); + + private WaveUtil() { + } + + /** + * 将原始波形结果整理成前端查看明细。 + * + * @param waveDataDTO 波形结果数据 + * @return 波形明细列表 + */ + public static List filterWaveData(WaveDataDTO waveDataDTO) { + List waveDataDetails = new ArrayList<>(); + List waveTitle = waveDataDTO.getWaveTitle(); + List 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 waveTitle, List 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> sourceData, List waveTitle, int phaseCount, int index, + float ratio, boolean openTri) { + List> aValues = new ArrayList<>(); + List> bValues = new ArrayList<>(); + List> cValues = new ArrayList<>(); + float max = Float.NEGATIVE_INFINITY; + float min = Float.POSITIVE_INFINITY; + + if (sourceData == null) { + sourceData = Collections.emptyList(); + } + + for (List 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 buildPoint(float x, float y) { + List point = new ArrayList<>(2); + point.add(x); + point.add(y); + return point; + } + + /** + * 基于瞬时数据构建明细对象。 + */ + private static InstantData buildInstantData(List> sourceData, List 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; + } +}