package com.gunshi.project.hsz.service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.gunshi.project.hsz.entity.vo.RsvrComplexVo; import com.gunshi.project.hsz.mapper.StZqrlBMapper; import com.gunshi.project.hsz.model.StRsvrR; import com.gunshi.project.hsz.model.StZqrlB; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.Duration; import java.util.*; import java.util.stream.Collectors; /** * 描述: 水位流量关系曲线表 * author: xusan * date: 2024-07-08 17:30:38 */ @Service @Slf4j @Transactional(rollbackFor = Exception.class) public class StZqrlBService extends ServiceImpl { /** * 计算溢洪量 * * @param rz 计算溢洪的起始水位 * @param seconds 溢洪时间,是两条水位数据的时间差,单位秒 * @param zqrlList 水位流量关系曲线 * @return 溢洪量 * @author lyf */ public BigDecimal calculateSpilledVolume(BigDecimal rz, long seconds, List zqrlList) { return getQFromZqrl(rz, zqrlList).multiply(BigDecimal.valueOf(seconds)); } /** * 计算溢洪量 * * @param rz 计算溢洪的起始水位 * @param seconds 溢洪时间,是两条水位数据的时间差,单位秒 * @return 溢洪量 * @author lyf */ public BigDecimal calculateSpilledVolume(BigDecimal rz, long seconds) { return calculateSpilledVolume(rz, seconds, list()); } /** * 批量计算溢洪量 * * @param rsvrList 要计算的水位数据 * @param zqrlList 水位流量关系曲线 * @return 计算后的水位数据(时间倒序),含每条水位对应的流量和与上一条水位对应的溢洪量 * @author lyf */ public List calculateSpilledVolumeList(List rsvrList, List zqrlList) { rsvrList.sort(Comparator.comparing(StRsvrR::getTm).reversed()); List ret = new ArrayList<>(); for (int i = 0; i < rsvrList.size(); i++) { StRsvrR rsvr = rsvrList.get(i); BigDecimal rz = new BigDecimal(rsvr.getRz()); Date tm = rsvr.getTm(); RsvrComplexVo vo = RsvrComplexVo.of(rz, tm); BigDecimal q = getQFromZqrl(rz, zqrlList); //TODO 这个需要公式进行对水位->流量的转换 if (i == 0) { vo.setQ(getQFromZqrl(rz, zqrlList)); vo.setSv(BigDecimal.ZERO); } else { StRsvrR rsvrPrev = rsvrList.get(i - 1); Date tmPrev = rsvrPrev.getTm(); long seconds = tm.getTime() - tmPrev.getTime(); vo.setQ(getQFromZqrl(rz, zqrlList)); //TODO 这里有个函数调用,记得到时候也替换一下计算公式 vo.setSv(calculateSpilledVolume(rz, seconds)); } ret.add(vo); } return ret; } /** * 批量计算溢洪量 * * @param rsvrList 要计算的水位数据 * @return 计算后的水位数据(时间倒序),含每条水位对应的流量和与上一条水位对应的溢洪量 * @author lyf */ public List calculateSpilledVolumeList(List rsvrList) { return calculateSpilledVolumeList(rsvrList, list()); } /** * 根据水位查表得流量 * * @param rz 水位 * @return 查表流量 * @author lyf */ public BigDecimal getQFromZqrl(BigDecimal rz) { return getQFromZqrl(rz, list()); } /** * 根据水位查表得流量 * * @param rz 水位 * @param zqrlList 水位流量关系曲线 * @return 查表流量 * @author lyf */ public BigDecimal getQFromZqrl(BigDecimal rz, List zqrlList) { return toMap(zqrlList).getOrDefault(rz.setScale(3, RoundingMode.DOWN), BigDecimal.ZERO); } /** * 将水位流量曲线列表转为字典,水位作为键,注意BigDecimal做键的时候要匹配值和精度 * * @param zqrlList 水位流量曲线列表 * @return 水位流量曲线字典 * @author lyf */ private Map toMap(List zqrlList) { return zqrlList.stream() .map(item -> Map.entry(item.getZ().setScale(3, RoundingMode.DOWN), item.getQ())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } public BigDecimal getQByZqrl(List zqrlBList, BigDecimal rz) { // 1. 参数校验 if (zqrlBList == null || zqrlBList.isEmpty()) { throw new IllegalArgumentException("水位-流量关系列表不能为空"); } if (rz == null) { throw new IllegalArgumentException("水位值不能为空"); } // 2. 检查边界情况:如果目标水位低于最低水位或高于最高水位 BigDecimal minRz = zqrlBList.get(0).getZ(); BigDecimal maxRz = zqrlBList.get(zqrlBList.size() - 1).getZ(); if (rz.compareTo(minRz) < 0) { // 低于最低水位,返回0流量 return BigDecimal.ZERO; } if (rz.compareTo(maxRz) > 0) { // 高于最高水位,返回0流量 return BigDecimal.ZERO; } // 3. 二分查找水位区间 int l = 0; int r = zqrlBList.size() - 1; while (l <= r) { int mid = l + (r - l) / 2; BigDecimal midRz = zqrlBList.get(mid).getZ(); int compareResult = midRz.compareTo(rz); if (compareResult == 0) { // 找到完全匹配的水位,直接返回对应的流量 return zqrlBList.get(mid).getQ(); } else if (compareResult < 0) { l = mid + 1; } else { r = mid - 1; } } /** * 此时: * l 指向第一个大于rz的位置 * r 指向最后一个小于rz的位置 * 即:r为 (x1, y1) - 下限水位和流量 * l为 (x2, y2) - 上限水位和流量 */ if (l >= zqrlBList.size() || r < 0) { return BigDecimal.ZERO; } // 获取前后相邻的两个点 StZqrlB lowerPoint = zqrlBList.get(r); StZqrlB upperPoint = zqrlBList.get(l); // 使用线性插值计算流量 return linearInterpolation( lowerPoint.getZ(), lowerPoint.getQ(), upperPoint.getZ(), upperPoint.getQ(), rz ).setScale(3, RoundingMode.HALF_UP); } /** * 线性插值辅助方法(可以复用之前的) * @param x1 已知点1的水位 * @param y1 已知点1的流量 * @param x2 已知点2的水位 * @param y2 已知点2的流量 * @param x 目标水位 * @return 目标水位对应的流量 */ private BigDecimal linearInterpolation(BigDecimal x1, BigDecimal y1, BigDecimal x2, BigDecimal y2, BigDecimal x) { // 使用公式: y = y1 + ( (y2 - y1) / (x2 - x1) ) * (x - x1) // 计算斜率: (y2 - y1) / (x2 - x1) BigDecimal slope = y2.subtract(y1) .divide(x2.subtract(x1), 10, RoundingMode.HALF_UP); // 计算: slope * (x - x1) BigDecimal xDiff = x.subtract(x1); BigDecimal product = slope.multiply(xDiff); // 计算最终结果: y1 + product return y1.add(product); } /** * 计算累计溢洪量 * @param complexList * @return */ public BigDecimal getFlowQSum(List complexList) { BigDecimal sum = BigDecimal.ZERO; for (int i = 0; i < complexList.size(); i++) { RsvrComplexVo current = complexList.get(i); if (i == 0) { // 第一条数据,累计量为0 sum = BigDecimal.ZERO; } else { RsvrComplexVo previous = complexList.get(i - 1); // 计算时间间隔(秒) long timeDiffSeconds = Duration.between(previous.getTm().toInstant(), current.getTm().toInstant()).getSeconds(); if (timeDiffSeconds > 0) { // 使用梯形法则计算这段时间内的溢洪量:(上一个流量 + 当前流量) / 2 * 时间间隔 BigDecimal avgFlow = previous.getSv().add(current.getSv()) .divide(BigDecimal.valueOf(2), 10, RoundingMode.HALF_UP); BigDecimal increment = avgFlow.multiply(BigDecimal.valueOf(timeDiffSeconds)); sum = sum.add(increment); } } } return sum; } }