diff --git a/src/main/java/com/gunshi/project/hsz/controller/RiverWaterController.java b/src/main/java/com/gunshi/project/hsz/controller/RiverWaterController.java index 17092a4..4a5726d 100644 --- a/src/main/java/com/gunshi/project/hsz/controller/RiverWaterController.java +++ b/src/main/java/com/gunshi/project/hsz/controller/RiverWaterController.java @@ -46,7 +46,7 @@ public class RiverWaterController { return R.ok(riverWaterService.list()); } - @Get(path = "/zqrl", summary = "水位流量关系") + @Get(path = "/zqrl", summary = "某个站点的水位流量关系曲线") public R> zqrl(@Schema(name = "stcd") @RequestParam("stcd") String stcd) { return R.ok(riverWaterService.zqrl(stcd)); } diff --git a/src/main/java/com/gunshi/project/hsz/entity/vo/AttRiverNowDataVo.java b/src/main/java/com/gunshi/project/hsz/entity/vo/AttRiverNowDataVo.java index a30f35c..f6dd051 100644 --- a/src/main/java/com/gunshi/project/hsz/entity/vo/AttRiverNowDataVo.java +++ b/src/main/java/com/gunshi/project/hsz/entity/vo/AttRiverNowDataVo.java @@ -15,7 +15,7 @@ public class AttRiverNowDataVo extends StPptnRReal { private Date waterTm; @Schema(description = "监测水位") - private BigDecimal waterVal; + private BigDecimal rz; @Schema(description = "警戒水位") diff --git a/src/main/java/com/gunshi/project/hsz/entity/vo/ForecastResultVo.java b/src/main/java/com/gunshi/project/hsz/entity/vo/ForecastResultVo.java index b88894a..1157dd3 100644 --- a/src/main/java/com/gunshi/project/hsz/entity/vo/ForecastResultVo.java +++ b/src/main/java/com/gunshi/project/hsz/entity/vo/ForecastResultVo.java @@ -105,4 +105,16 @@ public class ForecastResultVo { */ @Schema(description = "水库当前库容") private BigDecimal nowCap; + + /** + * 累积R + */ + @Schema(description = "累积R") + private BigDecimal rSum; + + /** + * 累积P + */ + @Schema(description = "累积P") + private BigDecimal pSum = BigDecimal.ZERO; } diff --git a/src/main/java/com/gunshi/project/hsz/service/ForecastResultsService.java b/src/main/java/com/gunshi/project/hsz/service/ForecastResultsService.java index b08329d..3fd0dc7 100644 --- a/src/main/java/com/gunshi/project/hsz/service/ForecastResultsService.java +++ b/src/main/java/com/gunshi/project/hsz/service/ForecastResultsService.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.gunshi.algorithm.RrainfallForecast; import com.gunshi.algorithm.RunoffService; import com.gunshi.model.vo.FloodAlgorithemVo; +import com.gunshi.project.hsz.service.*; import com.gunshi.project.hsz.entity.vo.ForeRainStatVo; import com.gunshi.project.hsz.entity.vo.ForeRainTimeVo; import com.gunshi.project.hsz.entity.vo.ForecastResultVo; @@ -113,7 +114,7 @@ public class ForecastResultsService extends ServiceImpl + * @return: java.util.List * @auther: cxw * @date: 2024-07-31, 周三, 11:09:24 */ @@ -139,7 +140,7 @@ public class ForecastResultsService extends ServiceImpl + * @return: java.util.List * @auther: cxw * @date: 2024-08-05, 周一, 17:14:52 */ @@ -153,7 +154,9 @@ public class ForecastResultsService extends ServiceImpl()); // 多站点水库面雨量 - List stbs = stStbprpBService.list(new QueryWrapper().eq("res_code", attResBase.getResCode())); + // List stbs = stStbprpBService.list(new QueryWrapper().eq("res_code", attResBase.getResCode())); + // 单站点 + List stbs = stStbprpBService.list(new QueryWrapper().eq("stcd", attResBase.getStcd())); if (CollectionUtils.isEmpty(stbs)) { return voList; } @@ -193,7 +196,7 @@ public class ForecastResultsService extends ServiceImpl().in("stcd", stbs.stream().map(StStbprpB::getStcd).toArray(String[]::new)).ge("tm", startTime).le("tm", nowHourTime).orderBy(true, true, "tm"); // 获取预报数据 try { - pptnRFutureList = getForecastDrpData(nowHourTime, ""); + pptnRFutureList = getForecastDrpData(nowHourTime, attResBase.getStcd()); if(CollectionUtils.isEmpty(pptnRFutureList)) { isHaveFuturePPtn = false; log.error("该时间无预报数据"); @@ -226,7 +229,7 @@ public class ForecastResultsService extends ServiceImpl paramMap = paramList.stream().collect(Collectors.toMap(ForecastUseparam::getParamCode, ForecastUseparam::getParamValue)); + Map paramMap = paramList.stream().collect(Collectors.toMap(ForecastUseparam::getParamCode, ForecastUseparam::getParamValue, (existing, replacement) -> existing)); if (paramMap.get("dt").isEmpty()) { throw new IllegalArgumentException("温馨提示:当前洪水预报所依赖的数据尚不完整,请检查时间单元△T是否缺失。"); } @@ -237,6 +240,7 @@ public class ForecastResultsService extends ServiceImpl uList = forecastUService.list(); if (CollectionUtils.isEmpty(uList)) { @@ -253,7 +257,7 @@ public class ForecastResultsService extends ServiceImpl paMap = paList.stream().collect(Collectors.toMap(ForecastPa::getTm, entity -> entity)); + Map paMap = paList.stream().collect(Collectors.toMap(ForecastPa::getTm, entity -> entity, (existing, replacement) -> existing)); // 获取预测开始时间前的最后水库水位 double H1 = 0.0;// 初始水库水位,可以根据H1->V1,H1->q1得到初始的水库库容和下泄流量 StRsvrR rsvrR = stRsvrRService.getOne(new QueryWrapper().eq("stcd", attResBase.getStcd()).le("tm", startTime).orderBy(true, false, "tm").last("limit 1")); @@ -269,7 +273,15 @@ public class ForecastResultsService extends ServiceImpl rsvrRRealList = stRsvrRService.list(new QueryWrapper().eq("stcd", attResBase.getStcd()).ge("tm", startTime).le("tm", endTime)); List resultList = reorganizeRsvrRData(rsvrRRealList, dt); List periods = splitByDay8To8(startTime, endTime); - for (Date[] period : periods) { + // v:累计降雨 + BigDecimal vValue = BigDecimal.ZERO; + BigDecimal vSum = BigDecimal.ZERO; + // Rsum:累计径流深 + BigDecimal Rsum = BigDecimal.ZERO; + BigDecimal Psum = BigDecimal.ZERO; + // 存储最后u的长度-1个r值 + for (int k = 0; k < periods.size(); k++) { + Date[] period = periods.get(k); Calendar calNew = Calendar.getInstance(); calNew.setTime(period[0]); // 根据每段时间的开始时间,如果在08点前,则采用前一天的pa值计算 @@ -289,32 +301,58 @@ public class ForecastResultsService extends ServiceImpl pForecastList = retMap.get("listForForecast").stream().map(s -> s.getDrp()).collect(Collectors.toList()); - if(pForecastList.size() == 0){ + if (pForecastList.size() == 0) { continue; } - if(ObjectUtils.isEmpty(retMap.get("listForReal"))){ + if (ObjectUtils.isEmpty(retMap.get("listForReal"))) { continue; } - Map pRealMap = retMap.get("listForReal").stream().collect(Collectors.toMap(pptnR -> sdfMinute.format(pptnR.getTm()), StPptnR::getDrp)); + Map pRealMap = retMap.get("listForReal").stream().collect(Collectors.toMap(pptnR -> sdfMinute.format(pptnR.getTm()), StPptnR::getDrp, (existing, replacement) -> existing)); double[] PList = pForecastList.stream().mapToDouble(Double::parseDouble).toArray(); + vSum = vSum.add(vValue); + vValue = BigDecimal.ZERO; + for (double value : PList) { + BigDecimal bd = BigDecimal.valueOf(value); + // 累加到总和中 + vValue = vValue.add(bd); + } + + // 存储上一次的径流深r,最开始默认全部为0 + List lastRArr = new ArrayList<>(); + for (int i = 0; i < u.length - 1; i++) { + lastRArr.add(0.0); // 填充初始值 + } + // 从后往前遍历不定长的实体列表 + for (int i = voList.size() - 1; i >= 0; i--) { + int index = lastRArr.size() - 1 - (voList.size() - 1 - i); + lastRArr.set(index, voList.get(i).getR().doubleValue()); + if(index <= 0){ + break; + } + } + // 预测执行 List forecastVoList = RrainfallForecast.getData(sdf.format(period[0]), forecastPa.getK().doubleValue(), forecastPa.getPa0().doubleValue(), Wm, forecastPa.getPt0().doubleValue(), H1, dt, - forecastPa.getPa().doubleValue(), PList, u, attResBase.getStcd(), qOther); + forecastPa.getPa().doubleValue(), PList, u, attResBase.getStcd(), qOther, vSum.doubleValue(), Rsum.doubleValue(), lastRArr); if (CollectionUtils.isNotEmpty(forecastVoList)) { // 筛选同时段的真实水位数据 List realRsvrList = resultList.stream().filter(item -> item.getTm().compareTo(period[0]) >= 0 && item.getTm().compareTo(period[1]) <= 0).collect(Collectors.toList()); - Map realRsvrMap = realRsvrList.stream().collect(Collectors.toMap(rsvr -> sdfMinute.format(rsvr.getTm()), StRsvrR::getRz)); - forecastVoList = forecastVoList.subList(0, PList.length); + Map realRsvrMap = realRsvrList.stream().collect(Collectors.toMap(rsvr -> sdfMinute.format(rsvr.getTm()), StRsvrR::getRz, (existing, replacement) -> existing)); + forecastVoList = forecastVoList.subList(0, PList.length + 1); // 去除预测出来的最后一条与第二次实际的相同时间的数据 - if(voList.size() > 0 && voList.get(voList.size() - 1).getTm().equals(forecastVoList.get(0).getDateStr())){ + if (voList.size() > 0 && voList.get(voList.size() - 1).getTm().equals(forecastVoList.get(0).getDateStr())) { voList.remove(voList.size() - 1); } for (int j = 0; j < forecastVoList.size(); j++) { FloodAlgorithemVo floodAlgorithemVo = forecastVoList.get(j); + // dt不同,预测结果的条数不同(跟new_q参数有关) + if (floodAlgorithemVo.getDateStr().substring(0, 15).compareTo(sdfMinute.format(period[1])) > 0) { + break; + } String dateStr = floodAlgorithemVo.getDateStr(); ForecastResultVo resultVo = new ForecastResultVo(); resultVo.setTm(dateStr); @@ -338,7 +376,7 @@ public class ForecastResultsService extends ServiceImpl 0) { resultVo.setRealCkQValue(BigDecimal.ZERO);// 真实出库流量 } else { - Map stZvalMap = stZqrlBList.stream().collect(Collectors.toMap(StZqrlB::getZ, StZqrlB::getQ)); + Map stZvalMap = stZqrlBList.stream().collect(Collectors.toMap(StZqrlB::getZ, StZqrlB::getQ, (existing, replacement) -> existing)); List list = stZqrlBList.stream().map(StZqrlB::getZ).collect(Collectors.toList()); resultVo.setRealCkQValue(DataHandleUtil.calcData(realSwHValue, stZvalMap, list));// 真实出库流量 } @@ -347,15 +385,28 @@ public class ForecastResultsService extends ServiceImpl> excuteForecastDebug(ForecastTask forecastTask) throws Exception { + List voList = new ArrayList<>(); + // 获取配置参数 + List paramList = forecastUseparamService.list(new QueryWrapper().isNotNull("param_code").isNotNull("param_value")); + if (CollectionUtils.isEmpty(paramList)) { + throw new IllegalArgumentException("温馨提示:当前洪水预报所依赖的数据尚不完整,请检查入参是否缺失。"); + } + // 获取系统当前的水库站编码、汛限水位 + AttResBase attResBase = attResBaseService.getOne(new QueryWrapper<>()); + // 多站点水库面雨量 + // List stbs = stStbprpBService.list(new QueryWrapper().eq("res_code", attResBase.getResCode())); + // 单站点 + List stbs = stStbprpBService.list(new QueryWrapper().eq("stcd", attResBase.getStcd())); + if (CollectionUtils.isEmpty(stbs)) { + return null; + } + Date nowHourTime = forecastTask.getNowTime(); + Date startTime = forecastTask.getStartTime(); + Date endTime = forecastTask.getEndTime(); + // 获取整个时间线的降雨数据 + List pptnRAllList = new ArrayList<>(); + List pptnRFutureList = new ArrayList<>(); + QueryWrapper qwExisted = new QueryWrapper<>(); + Boolean isHaveFuturePPtn = true; + // 檀树岗修改:实测降雨查询表数据,预测降雨使用geom参数。最后按时间算数平均 +// for(StStbprpB b : stbs){ +// String stcd = b.getStcd(); +// // 如果结束时间在当前时间之前,降雨序列从历史降雨表获取 +// if (endTime.compareTo(nowHourTime) <= 0) { +// qwExisted = new QueryWrapper().eq("stcd", stcd).ge("tm", startTime).le("tm", endTime).orderBy(true, true, "tm"); +// } else { +// qwExisted = new QueryWrapper().eq("stcd", stcd).ge("tm", startTime).le("tm", nowHourTime).orderBy(true, true, "tm"); +// try { +// // 获取预报数据 +// pptnRFutureList = getForecastDrpData(nowHourTime, stcd); +// } catch (IllegalArgumentException e) { +// if(stcd.equals(attResBase.getStcd())){ +// isHaveFuturePPtn = false; +// } +// log.error("该时间无预报数据"); +// } +// } +// List pptnRExistedList = stPptnRService.list(qwExisted); +// pptnRAllList.addAll(pptnRExistedList); +// pptnRAllList.addAll(pptnRFutureList); +// } + if (endTime.compareTo(nowHourTime) <= 0) { + qwExisted = new QueryWrapper().in("stcd", stbs.stream().map(StStbprpB::getStcd).toArray(String[]::new)).ge("tm", startTime).le("tm", endTime).orderBy(true, true, "tm"); + } else { + qwExisted = new QueryWrapper().in("stcd", stbs.stream().map(StStbprpB::getStcd).toArray(String[]::new)).ge("tm", startTime).le("tm", nowHourTime).orderBy(true, true, "tm"); + // 获取预报数据 + try { + pptnRFutureList = getForecastDrpData(nowHourTime, attResBase.getStcd()); + if(CollectionUtils.isEmpty(pptnRFutureList)) { + isHaveFuturePPtn = false; + log.error("该时间无预报数据"); + } + } catch (IllegalArgumentException e) { + isHaveFuturePPtn = false; + log.error("该时间无预报数据"); + } + } + List pptnRExistedList = stPptnRService.list(qwExisted); + pptnRAllList.addAll(pptnRExistedList); + pptnRAllList.addAll(pptnRFutureList); + + if (CollectionUtils.isEmpty(pptnRAllList)) { + return null; + } + // 多站点面雨量结果list + List polyPptnRList = pptnRAllList.stream() + .collect(Collectors.groupingBy( + StPptnR::getTm, + Collectors.mapping( + pptn -> Double.parseDouble(pptn.getDrp()), // 将String转换为double + Collectors.averagingDouble(d -> d) // 计算平均值 + ) + )) + .entrySet().stream() + .map(entry -> new StPptnRAverage(entry.getKey(), String.valueOf(entry.getValue()))) + .sorted(Comparator.comparing(StPptnRAverage::getTm)) + .collect(Collectors.toList()); + double dt = 0.0; + double Wm = 0.0; + double qOther = 0.0; + Map paramMap = paramList.stream().collect(Collectors.toMap(ForecastUseparam::getParamCode, ForecastUseparam::getParamValue, (existing, replacement) -> existing)); + if (paramMap.get("dt").isEmpty()) { + throw new IllegalArgumentException("温馨提示:当前洪水预报所依赖的数据尚不完整,请检查时间单元△T是否缺失。"); + } + if (paramMap.get("Im").isEmpty()) { + throw new IllegalArgumentException("温馨提示:当前洪水预报所依赖的数据尚不完整,请检查最大初损值Im是否缺失。"); + } + if (!paramMap.get("qOther").isEmpty()) { + qOther = Double.parseDouble(paramMap.get("qOther")); + } + dt = Double.parseDouble(paramMap.get("dt")); + // dt = Double.parseDouble("1.0"); + Wm = Double.parseDouble(paramMap.get("Im")); + List uList = forecastUService.list(); + if (CollectionUtils.isEmpty(uList)) { + throw new IllegalArgumentException("温馨提示:当前洪水预报所依赖的数据尚不完整,请检查U(I)单位线是否缺失。"); + } + // 每小时的单位径流量,单位m³/s + double[] u = uList.stream().mapToDouble(forecastU -> forecastU.getUValue().doubleValue()).toArray(); + // 根据开始结束时间查询pa + Calendar cal = Calendar.getInstance(); + cal.setTime(startTime); + // 将日期往前推一天 + cal.add(Calendar.DATE, -1); + List paList = forecastPaService.list(new QueryWrapper().eq("stcd", attResBase.getStcd()).ge("tm", sdfDay.format(cal.getTime())).le("tm", sdfDay.format(endTime))); + if (CollectionUtils.isEmpty(paList)) { + throw new IllegalArgumentException("温馨提示:当前洪水预报所依赖的数据尚不完整,请检查每日土壤含水量Pa是否缺失。"); + } + Map paMap = paList.stream().collect(Collectors.toMap(ForecastPa::getTm, entity -> entity, (existing, replacement) -> existing)); + // 获取预测开始时间前的最后水库水位 + double H1 = 0.0;// 初始水库水位,可以根据H1->V1,H1->q1得到初始的水库库容和下泄流量 + StRsvrR rsvrR = stRsvrRService.getOne(new QueryWrapper().eq("stcd", attResBase.getStcd()).le("tm", startTime).orderBy(true, false, "tm").last("limit 1")); + if (ObjectUtils.isEmpty(rsvrR)) { + return null; + } + H1 = Double.parseDouble(rsvrR.getRz()); + // 泄流量 + List stZqrlBList = stZqrlBService.list(new QueryWrapper().eq("stcd", attResBase.getStcd()).orderBy(true, true, "z")); + // 库容曲线 + List zvarlBS = stZvarlBService.list(new QueryWrapper().eq("stcd", attResBase.getStcd()).orderBy(true, true, "rz")); + // 水位历史数据 + List rsvrRRealList = stRsvrRService.list(new QueryWrapper().eq("stcd", attResBase.getStcd()).ge("tm", startTime).le("tm", endTime)); + List resultList = reorganizeRsvrRData(rsvrRRealList, dt); + List periods = splitByDay8To8(startTime, endTime); + // v:累计降雨 + BigDecimal vValue = BigDecimal.ZERO; + BigDecimal vSum = BigDecimal.ZERO; + // Rsum:累计径流深 + BigDecimal Rsum = BigDecimal.ZERO; + BigDecimal Psum = BigDecimal.ZERO; + List> res = new ArrayList<>(); + // 存储最后u的长度-1个r值 + for (int k = 0; k < periods.size(); k++) { + Date[] period = periods.get(k); + Calendar calNew = Calendar.getInstance(); + calNew.setTime(period[0]); + // 根据每段时间的开始时间,如果在08点前,则采用前一天的pa值计算 + if (isBeforeEightAM(period[0])) { + // 将日期往前推一天 + calNew.add(Calendar.DATE, -1); + } + ForecastPa forecastPa = paMap.get(sdfDay.format(calNew.getTime())); + if (ObjectUtils.isEmpty(forecastPa)) { + continue; +// throw new IllegalArgumentException("温馨提示:当前洪水预报所依赖的数据尚不完整,请检查每日土壤含水量Pa、K值是否缺失。"); + } + // 根据降雨数据,按照△t的颗粒度,均分 + // 筛选时间段内的降雨数据。不包前但包后 + List filterList = polyPptnRList.stream().filter(e -> e.getTm().compareTo(period[0]) >= 0).filter(e -> e.getTm().compareTo(period[1]) <= 0).collect(Collectors.toList()); + Map> retMap = new HashMap<>(); + if (CollectionUtils.isNotEmpty(filterList)) { + retMap = reorganizePptnRData(filterList, dt, period[0], period[1], nowHourTime, isHaveFuturePPtn); + } + if (ObjectUtils.isEmpty(retMap.get("listForForecast"))) { + continue; + } + List pForecastList = retMap.get("listForForecast").stream().map(s -> s.getDrp()).collect(Collectors.toList()); + if (pForecastList.size() == 0) { + continue; + } + if (ObjectUtils.isEmpty(retMap.get("listForReal"))) { + continue; + } + Map pRealMap = retMap.get("listForReal").stream().collect(Collectors.toMap(pptnR -> sdfMinute.format(pptnR.getTm()), StPptnR::getDrp, (existing, replacement) -> existing)); + double[] PList = pForecastList.stream().mapToDouble(Double::parseDouble).toArray(); + vSum = vSum.add(vValue); + vValue = BigDecimal.ZERO; + for (double value : PList) { + BigDecimal bd = BigDecimal.valueOf(value); + // 累加到总和中 + vValue = vValue.add(bd); + } + + // 存储上一次的径流深r,最开始默认全部为0 + List lastRArr = new ArrayList<>(); + for (int i = 0; i < u.length - 1; i++) { + lastRArr.add(0.0); // 填充初始值 + } + // 从后往前遍历不定长的实体列表 + for (int i = voList.size() - 1; i >= 0; i--) { + int index = lastRArr.size() - 1 - (voList.size() - 1 - i); + lastRArr.set(index, voList.get(i).getR().doubleValue()); + if(index <= 0){ + break; + } + } + + // 预测执行 + Map map = new HashMap<>(); + map.put("in period" + k, sdf.format(period[0])); + map.put("in K" + k, forecastPa.getK().doubleValue()); + map.put("in pa0" + k, forecastPa.getPa0().doubleValue()); + map.put("in Wm" + k, Wm); + map.put("in pt0" + k, forecastPa.getPt0().doubleValue()); + map.put("in H1" + k, H1); + map.put("in dt" + k, dt); + map.put("in pa" + k, forecastPa.getPa().doubleValue()); + map.put("in PList" + k, PList); + map.put("in u" + k, u); + map.put("in stcd" + k, attResBase.getStcd()); + map.put("in qOther" + k, qOther); + map.put("in vSum" + k, vSum.doubleValue()); + map.put("in Rsum" + k, Rsum.doubleValue()); + map.put("in lastRArr" + k, lastRArr); + List forecastVoList = RrainfallForecast.getData(sdf.format(period[0]), forecastPa.getK().doubleValue(), forecastPa.getPa0().doubleValue(), Wm, forecastPa.getPt0().doubleValue(), H1, dt, + forecastPa.getPa().doubleValue(), PList, u, attResBase.getStcd(), qOther, vSum.doubleValue(), Rsum.doubleValue(), lastRArr); + map.put("out res" + k, forecastVoList); + res.add(map); + if (CollectionUtils.isNotEmpty(forecastVoList)) { + // 筛选同时段的真实水位数据 + List realRsvrList = resultList.stream().filter(item -> item.getTm().compareTo(period[0]) >= 0 && item.getTm().compareTo(period[1]) <= 0).collect(Collectors.toList()); + Map realRsvrMap = realRsvrList.stream().collect(Collectors.toMap(rsvr -> sdfMinute.format(rsvr.getTm()), StRsvrR::getRz, (existing, replacement) -> existing)); + forecastVoList = forecastVoList.subList(0, PList.length + 1); + // 去除预测出来的最后一条与第二次实际的相同时间的数据 + if (voList.size() > 0 && voList.get(voList.size() - 1).getTm().equals(forecastVoList.get(0).getDateStr())) { + voList.remove(voList.size() - 1); + } + for (int j = 0; j < forecastVoList.size(); j++) { + FloodAlgorithemVo floodAlgorithemVo = forecastVoList.get(j); + // dt不同,预测结果的条数不同(跟new_q参数有关) + if (floodAlgorithemVo.getDateStr().substring(0, 15).compareTo(sdfMinute.format(period[1])) > 0) { + break; + } + String dateStr = floodAlgorithemVo.getDateStr(); + ForecastResultVo resultVo = new ForecastResultVo(); + resultVo.setTm(dateStr); + resultVo.setYcRkQValue(floodAlgorithemVo.getRq());// 预测入库流量 +// resultVo.setRealRkQValue();// 暂无真实入库流量 + resultVo.setYcCkQValue(floodAlgorithemVo.getCq());// 预测出库流量 + resultVo.setYcSwHValue(floodAlgorithemVo.getKh());// 预测水库水位 + H1 = resultVo.getYcSwHValue().doubleValue();// 以预测水位作为下一次预测段起始值 + String dateMinuteStr = dateStr.substring(0, dateStr.length() - 3);// 年月日 时分 + if (realRsvrMap.containsKey(dateMinuteStr)) { + BigDecimal realSwHValue = new BigDecimal(realRsvrMap.get(dateMinuteStr));// 根据时间取更准确 + resultVo.setRealSwHValue(realSwHValue);// 真实水库水位 + calculateCap(zvarlBS, resultVo, realSwHValue);// 有真实水位就用真实水位算(作为实测库容) + resultVo.setSwHDValue(resultVo.getYcSwHValue().subtract(resultVo.getRealSwHValue()));// 预测与真实水位差 + // 注释:修复跨8点时的陡升陡降 +// H1 = realSwHValue.doubleValue();// 如果有真实水位,将最后一条的真实水位作为下一次预测段的初始水位 + // 真实出库流量=真实水库水位与泄流量曲线差值法 + if (realSwHValue != null && CollectionUtils.isNotEmpty(stZqrlBList)) { + BigDecimal maxZ = stZqrlBList.stream().max(Comparator.comparing(StZqrlB::getZ)).get().getZ(); + BigDecimal minZ = stZqrlBList.stream().min(Comparator.comparing(StZqrlB::getZ)).get().getZ(); + if (realSwHValue.compareTo(minZ) < 0 || realSwHValue.compareTo(maxZ) > 0) { + resultVo.setRealCkQValue(BigDecimal.ZERO);// 真实出库流量 + } else { + Map stZvalMap = stZqrlBList.stream().collect(Collectors.toMap(StZqrlB::getZ, StZqrlB::getQ, (existing, replacement) -> existing)); + List list = stZqrlBList.stream().map(StZqrlB::getZ).collect(Collectors.toList()); + resultVo.setRealCkQValue(DataHandleUtil.calcData(realSwHValue, stZvalMap, list));// 真实出库流量 + } + } + } else { + // 没有真实水位就用预测水位算(作为预测库容) + calculateCap(zvarlBS, resultVo, resultVo.getYcSwHValue()); + } + + String pRealDrp = "0.0"; + if (pRealMap.containsKey(dateMinuteStr)) { + pRealDrp = pRealMap.get(dateMinuteStr); + } + BigDecimal drp = new BigDecimal(pRealDrp);// 根据时间取更准确 + if (drp.compareTo(BigDecimal.ZERO) < 0) { + resultVo.setDrp(BigDecimal.ZERO); + } else { + resultVo.setDrp(drp); + } + Psum = Psum.add(resultVo.getDrp()); + resultVo.setPSum(Psum); + Date dateTm = sdf.parse(floodAlgorithemVo.getDateStr()); + resultVo.setIspreDrp(dateTm.compareTo(nowHourTime) <= 0 ? "0" : "1");// 0:真实 1:预测 + resultVo.setR(floodAlgorithemVo.getR()); + if (resultVo.getR() != null) { + Rsum = Rsum.add(resultVo.getR()); + } else { + continue; + } + resultVo.setRSum(Rsum); + resultVo.setFlLowLimLev(attResBase.getFlLowLimLev()); + resultVo.setCurrentYdgdyjz(MapUtil.get(paramMap, "ydgdyjz", BigDecimal.class, BigDecimal.ZERO)); + resultVo.setPa(floodAlgorithemVo.getPa()); + voList.add(resultVo); + } + } + } + return res; + } + /** * @description: 根据水位计算库容 * @param zvarlBS 库容曲线 @@ -382,7 +727,7 @@ public class ForecastResultsService extends ServiceImpl 0){ return; } - Map stZvalMap = zvarlBS.stream().collect(Collectors.toMap(StZvarlB::getRz, StZvarlB::getW)); + Map stZvalMap = zvarlBS.stream().collect(Collectors.toMap(StZvarlB::getRz, StZvarlB::getW, (existing, replacement) -> existing)); List list = zvarlBS.stream().map(StZvarlB::getRz).sorted().collect(Collectors.toList()); resultVo.setNowCap(DataHandleUtil.calcData(rz,stZvalMap,list)); } @@ -452,7 +797,7 @@ public class ForecastResultsService extends ServiceImpl + * @return: java.util.List * @auther: cxw */ private List reorganizeRsvrRData(List rsvrRRealList, double dt) { @@ -513,22 +858,23 @@ public class ForecastResultsService extends ServiceImpl + * @return: java.util.List * @auther: cxw */ private Map> reorganizePptnRData(List filterList, double dt, Date startTm, Date endTm, Date nowHourTime, Boolean isHaveFuturePPtn) { + // 目前dt适配0.5和1 // 只保留整小时的数据 filterList = filterList.stream().filter(entity -> { - Date date = entity.getTm(); - return date.getMinutes() == 0 && date.getSeconds() == 0; - }).collect(Collectors.toMap( + Date date = entity.getTm(); + return date.getMinutes() == 0 && date.getSeconds() == 0; + }).collect(Collectors.toMap( e -> e.getStcd() + "_" + e.getTm(), // 使用属性组合作为键 Function.identity(), (existing, replacement) -> existing // 如果有冲突,保留现有的 )).values().stream().collect(Collectors.toList()); filterList.sort(Comparator.comparing(StPptnR::getTm)); Map> retMap = new HashMap<>(); - Map tmDrpMap = filterList.stream().collect(Collectors.toMap(pptnR -> sdfMinute.format(pptnR.getTm()), StPptnR::getDrp)); + Map tmDrpMap = filterList.stream().collect(Collectors.toMap(pptnR -> sdfMinute.format(pptnR.getTm()), StPptnR::getDrp, (existing, replacement) -> existing)); List listForForecast = new ArrayList<>();// 传给预测程序使用,缺失的数据为0 List listForReal = new ArrayList<>();// 返回给前端数据使用,缺失的数据为null Calendar calendar = Calendar.getInstance(); @@ -549,17 +895,20 @@ public class ForecastResultsService extends ServiceImpl { } public BigDecimal getQByZqrl(List zqrlBList, BigDecimal rz) { - int l =0; - int r = zqrlBList.size() - 1; - if(rz.compareTo(zqrlBList.get(0).getZ()) < 0){ + // 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; } - //使用二分法,来获取 避免On的查询时间复杂度 - while(l < r){ + 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; - if(zqrlBList.get(mid).getZ().compareTo(rz) ==0){ + BigDecimal midRz = zqrlBList.get(mid).getZ(); + int compareResult = midRz.compareTo(rz); + + if (compareResult == 0) { + // 找到完全匹配的水位,直接返回对应的流量 return zqrlBList.get(mid).getQ(); - }else if(zqrlBList.get(mid).getZ().compareTo(rz) >0){ - r = mid - 1; - }else{ + } else if (compareResult < 0) { l = mid + 1; + } else { + r = mid - 1; } } - // 现在 l 是最接近的索引,但需要比较前后三个值 - int closestIndex = l; - - // 处理边界情况 - if (l == 0) { - // 只有第一个元素,或者比较第一个和第二个哪个更接近 - if (zqrlBList.size() > 1) { - BigDecimal diff1 = rz.subtract(zqrlBList.get(0).getZ()).abs(); - BigDecimal diff2 = rz.subtract(zqrlBList.get(1).getZ()).abs(); - closestIndex = diff1.compareTo(diff2) <= 0 ? 0 : 1; - } - //TODO 这里找到最接近的值后,仍然要通过公式,计算出真正的流量 - return zqrlBList.get(closestIndex).getQ(); + /** + * 此时: + * l 指向第一个大于rz的位置 + * r 指向最后一个小于rz的位置 + * 即:r为 (x1, y1) - 下限水位和流量 + * l为 (x2, y2) - 上限水位和流量 + */ + if (l >= zqrlBList.size() || r < 0) { + return BigDecimal.ZERO; } - if (l == zqrlBList.size() - 1) { - // 只有最后一个元素,或者比较最后两个哪个更接近 - if (zqrlBList.size() > 1) { - BigDecimal diff1 = rz.subtract(zqrlBList.get(l - 1).getZ()).abs(); - BigDecimal diff2 = rz.subtract(zqrlBList.get(l).getZ()).abs(); - closestIndex = diff1.compareTo(diff2) <= 0 ? l - 1 : l; - } - //TODO 这里找到最接近的值后,仍然要通过公式,计算出真正的流量 - return zqrlBList.get(closestIndex).getQ(); - } + // 获取前后相邻的两个点 + StZqrlB lowerPoint = zqrlBList.get(r); + StZqrlB upperPoint = zqrlBList.get(l); - // 正常情况:比较前一个、当前、后一个三个值,找到最接近的 - BigDecimal preVal = zqrlBList.get(l - 1).getZ(); - BigDecimal curVal = zqrlBList.get(l).getZ(); - BigDecimal nextVal = zqrlBList.get(l + 1).getZ(); + // 使用线性插值计算流量 + return linearInterpolation( + lowerPoint.getZ(), lowerPoint.getQ(), + upperPoint.getZ(), upperPoint.getQ(), + rz + ).setScale(3, RoundingMode.HALF_UP); + } - //计算他们与目标的插值 - BigDecimal diffPre = rz.subtract(preVal).abs(); - BigDecimal diffCur = rz.subtract(curVal).abs(); - BigDecimal diffNext = rz.subtract(nextVal).abs(); + /** + * 线性插值辅助方法(可以复用之前的) + * @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) - // 找到最小的差值 - if (diffPre.compareTo(diffCur) <= 0 && diffPre.compareTo(diffNext) <= 0) { - closestIndex = l - 1; - } else if (diffCur.compareTo(diffPre) <= 0 && diffCur.compareTo(diffNext) <= 0) { - closestIndex = l; - } else { - closestIndex = l + 1; - } + // 计算斜率: (y2 - y1) / (x2 - x1) + BigDecimal slope = y2.subtract(y1) + .divide(x2.subtract(x1), 10, RoundingMode.HALF_UP); - //TODO 这里找到最接近的值后,仍然要通过公式,计算出真正的流量 + // 计算: slope * (x - x1) + BigDecimal xDiff = x.subtract(x1); + BigDecimal product = slope.multiply(xDiff); - return zqrlBList.get(closestIndex).getQ(); + // 计算最终结果: y1 + product + return y1.add(product); } /**