diff --git a/das/src/main/java/com/das/common/constant/StatisticalAnalysisConstant.java b/das/src/main/java/com/das/common/constant/StatisticalAnalysisConstant.java index a1ba8fa7..cb3e8f27 100644 --- a/das/src/main/java/com/das/common/constant/StatisticalAnalysisConstant.java +++ b/das/src/main/java/com/das/common/constant/StatisticalAnalysisConstant.java @@ -6,9 +6,9 @@ package com.das.common.constant; public interface StatisticalAnalysisConstant { - String TREND_ANALYSE = "趋势分析"; + String TREND_ANALYSE = "单机分析"; String POWER_CURVE = "功率曲线"; - String TREND_CONTRAST = "趋势对比"; + String TREND_CONTRAST = "多机对比"; } diff --git a/das/src/main/java/com/das/common/constant/StatisticalAnalysisFunctionConstant.java b/das/src/main/java/com/das/common/constant/StatisticalAnalysisFunctionConstant.java new file mode 100644 index 00000000..2501bff8 --- /dev/null +++ b/das/src/main/java/com/das/common/constant/StatisticalAnalysisFunctionConstant.java @@ -0,0 +1,16 @@ +package com.das.common.constant; + +/** + * 统计分析 + */ +public interface StatisticalAnalysisFunctionConstant { + + + String INTERPOLATION = "interpolation"; + + String AVERAGE = "average"; + + String MAX = "max"; + + String min = "min"; +} diff --git a/das/src/main/java/com/das/modules/page/controller/StatisticalAnalysisController.java b/das/src/main/java/com/das/modules/page/controller/StatisticalAnalysisController.java index 26cc5aae..a3772034 100644 --- a/das/src/main/java/com/das/modules/page/controller/StatisticalAnalysisController.java +++ b/das/src/main/java/com/das/modules/page/controller/StatisticalAnalysisController.java @@ -26,7 +26,7 @@ public class StatisticalAnalysisController { /** - * 趋势分析Excel导出 + * 趋势分析 (单机分析)Excel导出 * @param param 查询条件 */ @PostMapping("/trendAnalyseExport") @@ -45,7 +45,7 @@ public class StatisticalAnalysisController { /** - * 趋势对比Excel导出 + * 趋势对比(多机对比) Excel导出 * @param param 查询条件 */ @PostMapping("/trendContrastExport") diff --git a/das/src/main/java/com/das/modules/page/domian/dto/TrendAnalyseDto.java b/das/src/main/java/com/das/modules/page/domian/dto/TrendAnalyseDto.java index 079fe844..dbfd59cd 100644 --- a/das/src/main/java/com/das/modules/page/domian/dto/TrendAnalyseDto.java +++ b/das/src/main/java/com/das/modules/page/domian/dto/TrendAnalyseDto.java @@ -38,6 +38,7 @@ public class TrendAnalyseDto private String timeName; + /** * 设备属性列表 */ @@ -52,4 +53,11 @@ public class TrendAnalyseDto * 模型 */ private String model; + + private String calFunction; + + private String windSource; + + //是否显示曲线,0不显示,1显示 + private int displayCurve; } diff --git a/das/src/main/java/com/das/modules/page/domian/dto/TrendContrastDto.java b/das/src/main/java/com/das/modules/page/domian/dto/TrendContrastDto.java index 9bf82c14..5ba7fb6f 100644 --- a/das/src/main/java/com/das/modules/page/domian/dto/TrendContrastDto.java +++ b/das/src/main/java/com/das/modules/page/domian/dto/TrendContrastDto.java @@ -33,11 +33,12 @@ public class TrendContrastDto - /** * 设备属性列表 */ private List devices; + private String calFunction; + } diff --git a/das/src/main/java/com/das/modules/page/service/impl/StatisticalAnalysisServiceImpl.java b/das/src/main/java/com/das/modules/page/service/impl/StatisticalAnalysisServiceImpl.java index e5a4123e..22f6c198 100644 --- a/das/src/main/java/com/das/modules/page/service/impl/StatisticalAnalysisServiceImpl.java +++ b/das/src/main/java/com/das/modules/page/service/impl/StatisticalAnalysisServiceImpl.java @@ -4,15 +4,16 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelWriter; -import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.das.common.constant.StatisticalAnalysisConstant; +import com.das.common.constant.StatisticalAnalysisFunctionConstant; import com.das.common.exceptions.ServiceException; import com.das.common.utils.BeanCopyUtils; import com.das.modules.curve.domain.entity.CurveItemEntity; import com.das.modules.curve.service.TheoreticalPowerCurveService; import com.das.modules.data.domain.SnapshotValueQueryParam; import com.das.modules.data.domain.TSValueQueryParam; +import com.das.modules.data.domain.WindowValueQueryParam; import com.das.modules.data.service.DataService; import com.das.modules.equipment.entity.SysIotModelField; import com.das.modules.equipment.mapper.SysIotModelFieldMapper; @@ -22,6 +23,7 @@ import com.das.modules.page.service.StatisticalAnalysisService; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.HorizontalAlignment; @@ -33,6 +35,7 @@ import org.apache.poi.xssf.usermodel.XSSFChart; import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.XSSFDrawing; import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.jetbrains.annotations.NotNull; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartUtils; import org.jfree.chart.JFreeChart; @@ -56,7 +59,9 @@ import java.text.SimpleDateFormat; import java.util.List; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.IntStream; +@Slf4j @Service public class StatisticalAnalysisServiceImpl implements StatisticalAnalysisService { @@ -81,10 +86,19 @@ public class StatisticalAnalysisServiceImpl implements StatisticalAnalysisServic //根据条件获取历史数据 List>>> mapsList = new ArrayList<>(); for (TrendAnalyseDto trendAnalyseDto : param) { - TSValueQueryParam tsValueQueryParam = new TSValueQueryParam(); - BeanCopyUtils.copy(trendAnalyseDto,tsValueQueryParam); - Map>> resultMap = dataService.queryTimeSeriesValues(tsValueQueryParam); - mapsList.add(resultMap); + //瞬时值 + if (trendAnalyseDto.getCalFunction().equals(StatisticalAnalysisFunctionConstant.INTERPOLATION)){ + TSValueQueryParam tsValueQueryParam = new TSValueQueryParam(); + BeanCopyUtils.copy(trendAnalyseDto,tsValueQueryParam); + Map>> resultMap = dataService.queryTimeSeriesValues(tsValueQueryParam); + mapsList.add(resultMap); + }else { + //其他 + WindowValueQueryParam windowValueQueryParam = new WindowValueQueryParam(); + BeanCopyUtils.copy(trendAnalyseDto,windowValueQueryParam); + Map>> stringMapMap = dataService.queryWindowsValues(windowValueQueryParam); + mapsList.add(stringMapMap); + } } //获取Excel的列 LinkedHashMap map = getTrendColumnName(param); @@ -119,23 +133,43 @@ public class StatisticalAnalysisServiceImpl implements StatisticalAnalysisServic TSValueQueryParam tsValueQueryParam = new TSValueQueryParam(); BeanCopyUtils.copy(param,tsValueQueryParam); Map>> resultMap = dataService.queryTimeSeriesValues(tsValueQueryParam); + //显示曲线,需要计算功率的平均值 + if (param.getDisplayCurve()==1){ + try { + List windSpeedList = new ArrayList<>(); + List powerList = new ArrayList<>(); + getPowerCurveValueList(resultMap, powerList, windSpeedList); + List>> maps = calculateAverages(windSpeedList, powerList); + setPowerCurveValueList(resultMap, maps); + } catch (Exception e) { + log.error("计算功率曲线平均值失败:"+e.getMessage()); + } + } List> dataList = new ArrayList<>(); //填充功率曲线,Excel的数据集 setPowerCurveExcelValue(resultMap, dataList, curveItemEntitieList); //获取功率曲线的列 - LinkedHashMap map = getPowerCurveColumnName(); + LinkedHashMap map = getPowerCurveColumnName(param); //获取图表类别集 List chartKey = getCharKey(map); ExcelWriter writer = ExcelUtil.getWriter(RandomUtil.randomInt(100, 1000) + "statistics" + ".xlsx"); //设置Excel样式 setExcelStyle(writer, map, dataList); //生成折线图 - addChartToExcel(writer,dataList,StatisticalAnalysisConstant.POWER_CURVE,chartKey); + if (param.getDisplayCurve()==1){ + addChartToExcel(writer,dataList,StatisticalAnalysisConstant.POWER_CURVE,chartKey); + }else { + //生成散点图 + addScattersChartToExcel(writer,dataList,StatisticalAnalysisConstant.POWER_CURVE,chartKey); + } //下载Excel downloadExcel(response, writer,StatisticalAnalysisConstant.POWER_CURVE); } + + + /** * 趋势对比Excel导出 * @param param 查询条件 @@ -143,10 +177,17 @@ public class StatisticalAnalysisServiceImpl implements StatisticalAnalysisServic */ @Override public void trendContrastExport(TrendContrastDto param, HttpServletRequest request, HttpServletResponse response) { - //根据条件获取历史数据 - TSValueQueryParam tsValueQueryParam = new TSValueQueryParam(); - BeanCopyUtils.copy(param,tsValueQueryParam); - Map>> maps = dataService.queryTimeSeriesValues(tsValueQueryParam); + Map>> maps = null; + //瞬时值,历史区间数据查询 + if (param.getCalFunction().equals(StatisticalAnalysisFunctionConstant.INTERPOLATION)){ + TSValueQueryParam tsValueQueryParam = new TSValueQueryParam(); + BeanCopyUtils.copy(param,tsValueQueryParam); + maps = dataService.queryTimeSeriesValues(tsValueQueryParam); + }else { + WindowValueQueryParam windowparam = new WindowValueQueryParam(); + BeanCopyUtils.copy(param,windowparam); + maps = dataService.queryWindowsValues(windowparam); + } //自定义别名 别名的key和实体类中的名称要对应上! LinkedHashMap map = gettrendContrastColumnName(param); List> dataList = new ArrayList<>(); @@ -374,11 +415,15 @@ public class StatisticalAnalysisServiceImpl implements StatisticalAnalysisServic * 获取功率曲线的列 * @return Excel的列 */ - private LinkedHashMap getPowerCurveColumnName() { + private LinkedHashMap getPowerCurveColumnName(TrendAnalyseDto param) { LinkedHashMap map = new LinkedHashMap<>(); map.put("time", "时间"); - map.put("iWindSpeed", "风速"); - map.put("iGenPower", "功率"); + if (param.getWindSource().equals("原始风速")){ + map.put("AvgWindSpeed_10min", "原始风速"); + }else { + map.put("AvgWindSpeedCal_10min", "处理后风速"); + } + map.put("AvgActivePower_10min", "功率"); map.put("theoryIWindSpeed", "理论风速"); map.put("theoryIGenPower", "理论功率"); return map; @@ -607,6 +652,74 @@ public class StatisticalAnalysisServiceImpl implements StatisticalAnalysisServic } } + /** + * 生成散点图,并插入到 Excel 中 + * @param writer Sheet 对象 + * @param data 测点数据集合 + * @param titleText 标题 + * @param chartKey 类别数据集 + */ + private static void addScattersChartToExcel(ExcelWriter writer, List> data, + String titleText, List chartKey) { + try { + if (CollectionUtils.isEmpty(data)) { + return; + } + // 获取 Sheet 对象 + XSSFSheet sheet = (XSSFSheet) writer.getSheet(); + // 创建绘图区域 + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + int columnCount = sheet.getRow(0).getPhysicalNumberOfCells() + 1; + // 图表位置,插入到最后一列+1 + ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, columnCount, 0, 15, 15); + // 创建图表 + XSSFChart xssfChart = drawing.createChart(anchor); + // 设置图表标题 + xssfChart.setTitleText(titleText); + // 创建数据系列 + XDDFChartLegend legend = xssfChart.getOrAddLegend(); + legend.setPosition(LegendPosition.TOP_RIGHT); + XDDFValueAxis bottomAxis = xssfChart.createValueAxis(AxisPosition.BOTTOM); + XDDFValueAxis leftAxis = xssfChart.createValueAxis(AxisPosition.LEFT); + leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN); + // 创建散点图数据 + XDDFScatterChartData chartData = (XDDFScatterChartData) xssfChart.createData(ChartTypes.SCATTER, bottomAxis, leftAxis); + // 遍历每个系列并添加数据 + setChartDataForScatter(data, titleText, chartKey, chartData, sheet); + // 确保散点图数据点之间不连线 + for (XDDFChartData.Series series : chartData.getSeries()) { + if (series instanceof XDDFScatterChartData.Series) { + XDDFScatterChartData.Series scatterSeries = (XDDFScatterChartData.Series) series; + scatterSeries.setSmooth(false); + scatterSeries.setMarkerStyle(MarkerStyle.CIRCLE); + } + } + xssfChart.plot(chartData); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 为散点图设置数据系列 + * @param data 测点数据集合 + * @param titleText 标题 + * @param chartKey 类别数据集 + * @param chartData 散点图数据对象 + * @param sheet Excel Sheet 对象 + */ + private static void setChartDataForScatter(List> data, String titleText, List chartKey, + XDDFScatterChartData chartData, XSSFSheet sheet) { + for (String key : chartKey) { + XDDFDataSource xAxisData = XDDFDataSourcesFactory.fromNumericCellRange(sheet, + new CellRangeAddress(1, data.size(), 0, 0)); + XDDFNumericalDataSource yAxisData = XDDFDataSourcesFactory.fromNumericCellRange(sheet, + new CellRangeAddress(1, data.size(), chartKey.indexOf(key) + 1, chartKey.indexOf(key) + 1)); + XDDFScatterChartData.Series series = (XDDFScatterChartData.Series) chartData.addSeries(xAxisData, yAxisData); + series.setTitle(key, null); + } + } + /** * 给每个类别,添加数据 * @param data 测点数据 @@ -661,5 +774,138 @@ public class StatisticalAnalysisServiceImpl implements StatisticalAnalysisServic CellRangeAddress ref = new CellRangeAddress(startRow, endRow, startCol, endCol); return XDDFDataSourcesFactory.fromNumericCellRange(sheet, ref); } + + + + /** + * 计算平均功率 + */ + public static List>> calculateAverages(List windSpeeds, List powers) { + // 找出最大的风速 + double maxWindSpeed = Double.MIN_VALUE; + for (double windSpeed : windSpeeds) { + if (windSpeed > maxWindSpeed) { + maxWindSpeed = windSpeed; + } + } + double interval = 0.5; + List>> result = new ArrayList<>(); + Map> dataMap = new HashMap<>(); + List windSpeedList = new ArrayList<>(); + List powerList = new ArrayList<>(); + // 从 0 开始,以 interval 为步长遍历风速 + for (double windSpeed = 0; windSpeed <= maxWindSpeed; windSpeed += interval) { + double sumPower = 0; + int count = 0; + + // 遍历输入列表,计算满足风速范围的功率总和和计数 + for (int i = 0; i < windSpeeds.size(); i++) { + double currentWindSpeed = windSpeeds.get(i); + double currentPower = powers.get(i); + + if (currentWindSpeed >= windSpeed && currentWindSpeed < windSpeed + interval) { + sumPower += currentPower; + count++; + } + } + + // 如果计数大于 0,计算平均值并添加到结果列表 + if (count > 0) { + double averagePower = sumPower / count; + windSpeedList.add(windSpeed + interval); + powerList.add(averagePower); + } + } + dataMap.put("windSpeed", windSpeedList); + dataMap.put("power", powerList); + result.add(dataMap); + return result; + } + + /** + * 根据size删除List value1的值 + */ + @NotNull + private static List delTimes(Map.Entry stringObjectEntry, List>> maps) { + int powerSize = maps.get(0).get("power").size(); + List value1 = (List) stringObjectEntry.getValue(); + List collect = IntStream.range(0, value1.size()) + .filter(i -> i < powerSize) + .mapToObj(value1::get) + .collect(Collectors.toList()); + return collect; + } + + private static void getPowerCurveValueList(Map>> resultMap, + List powerList, List windSpeedList) { + for (Map.Entry>> stringMapEntry : resultMap.entrySet()) { + for (Map.Entry> mapEntry : stringMapEntry.getValue().entrySet()) { + String key = mapEntry.getKey(); + Map value = mapEntry.getValue(); + //功率 + if (key.equals("AvgActivePower_10min")) { + for (Map.Entry stringObjectEntry : value.entrySet()) { + if (stringObjectEntry.getKey().equals("values")) { + List values = (List) stringObjectEntry.getValue(); + List doubleList = values.stream() + .map(Float::doubleValue) + .collect(Collectors.toList()); + powerList.addAll(doubleList); + } + } + } + if (key.equals("AvgWindSpeed_10min") || key.equals("AvgWindSpeedCal_10min")){ + for (Map.Entry stringObjectEntry : value.entrySet()) { + if (stringObjectEntry.getKey().equals("values")){ + List values = (List) stringObjectEntry.getValue(); + List doubleList = values.stream() + .map(Float::doubleValue) + .collect(Collectors.toList()); + windSpeedList.addAll(doubleList); + } + } + } + } + } + } + + private static void setPowerCurveValueList(Map>> resultMap, + List>> maps) { + if (CollectionUtils.isEmpty(maps)){ + return; + } + for (Map.Entry>> stringMapEntry : resultMap.entrySet()) { + for (Map.Entry> mapEntry : stringMapEntry.getValue().entrySet()) { + String key = mapEntry.getKey(); + Map value = mapEntry.getValue(); + //功率 + if (key.equals("AvgActivePower_10min")) { + for (Map.Entry stringObjectEntry : value.entrySet()) { + if (stringObjectEntry.getKey().equals("values")) { + stringObjectEntry.setValue(maps.get(0).get("power")); + } + if (stringObjectEntry.getKey().equals("times")){ + List collect = delTimes(stringObjectEntry, maps); + stringObjectEntry.setValue(collect); + } + } + } + if (key.equals("AvgWindSpeed_10min") || key.equals("AvgWindSpeedCal_10min")){ + for (Map.Entry stringObjectEntry : value.entrySet()) { + if (stringObjectEntry.getKey().equals("values")){ + stringObjectEntry.setValue(maps.get(0).get("windSpeed")); + } + if (stringObjectEntry.getKey().equals("times")){ + List collect = delTimes(stringObjectEntry, maps); + stringObjectEntry.setValue(collect); + } + } + } + } + } + } + + + }