1:洪水预报

2:多项式回归
master
yangzhe123 2025-11-19 15:48:56 +08:00
parent 59a67a3b61
commit 1b02203a6c
10 changed files with 492 additions and 335 deletions

View File

@ -107,7 +107,11 @@ public class SyncRainfallService implements SyncTableRService<JcskStPptnR, StPpt
if (existingTarget == null) {
StPptnR targetEntity = new StPptnR();
BeanUtils.copyProperties(sourceEntity, targetEntity);
targetEntity.setTm(LocalDateTimeConverter.toDate(sourceEntity.getTm()));
targetEntity.setChtm(new Date());
if(sourceEntity.getDrp() != null){
targetEntity.setDrp(sourceEntity.getDrp().toString());
}
targetMapper.insert(targetEntity);
}
count++;

View File

@ -105,6 +105,9 @@ public class SyncRsvrWaterLevelService implements SyncTableRService<JcskStRsvrR,
StRsvrR targetEntity = new StRsvrR();
BeanUtils.copyProperties(sourceEntity, targetEntity);
targetEntity.setTm(LocalDateTimeConverter.toDate(sourceEntity.getTm()));
if(sourceEntity.getRz() != null){
targetEntity.setRz(sourceEntity.getRz().toString());
}
targetMapper.insert(targetEntity);
}
count++;

View File

@ -7,14 +7,17 @@ import com.gunshi.project.hsz.entity.dto.ProjectSaveReportDto;
import com.gunshi.project.hsz.entity.dto.SyRegressionDataDto;
import com.gunshi.project.hsz.entity.vo.ForecastResultVo;
import com.gunshi.project.hsz.entity.vo.ProjectSafeCalculateVo;
import com.gunshi.project.hsz.entity.vo.ProjectSaveReportVo;
import com.gunshi.project.hsz.model.ForecastTask;
import com.gunshi.project.hsz.model.RegressionEquation;
import com.gunshi.project.hsz.model.SyRegressionData;
import com.gunshi.project.hsz.service.ForecastResultsService;
import com.gunshi.project.hsz.service.JcskSyRService;
import com.gunshi.project.hsz.service.SyRegressionDataService;
import com.gunshi.project.hsz.util.RegressionEquationFormatter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -46,9 +49,17 @@ public class ProjectSafeAnalyseController {
return R.ok(ans);
}
@Operation(summary = "多项式回归-导出")
@PostMapping("/caculate/export")
public void caculateExport(@RequestBody ProjectSafeCalculateDto dto, HttpServletResponse response){
ProjectSafeCalculateVo ans = jcskSyRService.calculate(dto);
syRegressionDataService.export(dto.getDvcd(),ans,response);
}
@Operation(summary = "预报")
@PostMapping("/report")
public R<List<OsmoticPressDetailVo>> report(@RequestBody ProjectSaveReportDto projectSaveReportDto){
public R<ProjectSaveReportVo> report(@RequestBody ProjectSaveReportDto projectSaveReportDto){
ProjectSaveReportVo res = new ProjectSaveReportVo();
ForecastTask forecastTask = projectSaveReportDto.getForecastTask();
List<ForecastResultVo> voList = forecastResultsService.getHumanForecastResult(forecastTask);
SyRegressionData syRegressionData = new SyRegressionData();
@ -59,10 +70,33 @@ public class ProjectSafeAnalyseController {
return R.ok(null);
}
//计算
List<OsmoticPressDetailVo> res = forecastResultsService.calculateY(queryReg.get(0),voList);
List<OsmoticPressDetailVo> datas = forecastResultsService.calculateY(queryReg.get(0),voList);
res.setDatas(datas);
String formatEquation = RegressionEquationFormatter.formatEquation(queryReg.get(0).getRegressionEquation());
res.setRegression(formatEquation);
return R.ok(res);
}
@Operation(summary = "预报-导出")
@PostMapping("/report/export")
public void reportExport(@RequestBody ProjectSaveReportDto projectSaveReportDto,HttpServletResponse response){
ForecastTask forecastTask = projectSaveReportDto.getForecastTask();
List<ForecastResultVo> voList = forecastResultsService.getHumanForecastResult(forecastTask);
SyRegressionData syRegressionData = new SyRegressionData();
syRegressionData.setDvcd(projectSaveReportDto.getDvcd());
syRegressionData.setOrder(projectSaveReportDto.getOrder());
List<SyRegressionData> queryReg = syRegressionDataService.queryData(syRegressionData);//获取方程组
if(queryReg.isEmpty()){
return;
}
//计算
List<OsmoticPressDetailVo> res = forecastResultsService.calculateY(queryReg.get(0),voList);
String formatEquation = RegressionEquationFormatter.formatEquation(queryReg.get(0).getRegressionEquation());
forecastResultsService.reportExport(res,formatEquation,projectSaveReportDto.getDvcd(),response);
}
@Operation(summary = "保存多线程回归方程")
@PostMapping("/save")
public R<Boolean> save(@RequestBody SyRegressionDataDto dto){

View File

@ -15,4 +15,6 @@ public class SyDataCheckRulePageSo {
private PageSo pageSo;
private String dvcd;
private String dm;
}

View File

@ -0,0 +1,17 @@
package com.gunshi.project.hsz.entity.vo;
import com.gunshi.project.hsz.common.model.vo.OsmoticPressDetailVo;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class ProjectSaveReportVo {
private String regression;
private List<OsmoticPressDetailVo> datas;
}

View File

@ -67,4 +67,8 @@ public class SyDataCheckRule implements Serializable {
@TableField(value = "is_available")
@Schema(description = "是否启用 0否 1是")
private Integer isAvailable;
@TableField(value = "dm")
@Schema(description = "断面编码")
private String dm;
}

View File

@ -19,13 +19,19 @@ import com.gunshi.project.hsz.grb.RainGrib2Layer;
import com.gunshi.project.hsz.mapper.ForecastResultsMapper;
import com.gunshi.project.hsz.model.*;
import com.gunshi.project.hsz.util.DataHandleUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
@ -55,6 +61,8 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
private static SimpleDateFormat sixteenSdf = new SimpleDateFormat("yyyy-MM-dd 16");
private static SimpleDateFormat startSdf = new SimpleDateFormat("yyyy-MM-dd HH");
@Autowired
private ForecastUseparamService forecastUseparamService;
@ -421,299 +429,6 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
}
private List<Map<String, Object>> excuteForecastDebug(ForecastTask forecastTask) throws Exception {
List<ForecastResultVo> voList = new ArrayList<>();
// 获取配置参数
List<ForecastUseparam> paramList = forecastUseparamService.list(new QueryWrapper<ForecastUseparam>().isNotNull("param_code").isNotNull("param_value"));
if (CollectionUtils.isEmpty(paramList)) {
throw new IllegalArgumentException("温馨提示:当前洪水预报所依赖的数据尚不完整,请检查入参是否缺失。");
}
// 获取系统当前的水库站编码、汛限水位
AttResBase attResBase = attResBaseService.getOne(new QueryWrapper<>());
// 多站点水库面雨量
// List<StStbprpB> stbs = stStbprpBService.list(new QueryWrapper<StStbprpB>().eq("res_code", attResBase.getResCode()));
// 单站点
List<StStbprpB> stbs = stStbprpBService.list(new QueryWrapper<StStbprpB>().eq("stcd", attResBase.getStcd()));
if (CollectionUtils.isEmpty(stbs)) {
return null;
}
Date nowHourTime = forecastTask.getNowTime();
Date startTime = forecastTask.getStartTime();
Date endTime = forecastTask.getEndTime();
// 获取整个时间线的降雨数据
List<StPptnR> pptnRAllList = new ArrayList<>();
List<StPptnR> pptnRFutureList = new ArrayList<>();
QueryWrapper<StPptnR> qwExisted = new QueryWrapper<>();
Boolean isHaveFuturePPtn = true;
// 檀树岗修改实测降雨查询表数据预测降雨使用geom参数。最后按时间算数平均
// for(StStbprpB b : stbs){
// String stcd = b.getStcd();
// // 如果结束时间在当前时间之前,降雨序列从历史降雨表获取
// if (endTime.compareTo(nowHourTime) <= 0) {
// qwExisted = new QueryWrapper<StPptnR>().eq("stcd", stcd).ge("tm", startTime).le("tm", endTime).orderBy(true, true, "tm");
// } else {
// qwExisted = new QueryWrapper<StPptnR>().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<StPptnR> pptnRExistedList = stPptnRService.list(qwExisted);
// pptnRAllList.addAll(pptnRExistedList);
// pptnRAllList.addAll(pptnRFutureList);
// }
if (endTime.compareTo(nowHourTime) <= 0) {
qwExisted = new QueryWrapper<StPptnR>().in("stcd", stbs.stream().map(StStbprpB::getStcd).toArray(String[]::new)).ge("tm", startTime).le("tm", endTime).orderBy(true, true, "tm");
} else {
qwExisted = new QueryWrapper<StPptnR>().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<StPptnR> pptnRExistedList = stPptnRService.list(qwExisted);
pptnRAllList.addAll(pptnRExistedList);
pptnRAllList.addAll(pptnRFutureList);
if (CollectionUtils.isEmpty(pptnRAllList)) {
return null;
}
// 多站点面雨量结果list
List<StPptnR> 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<String, String> 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<ForecastU> 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<ForecastPa> paList = forecastPaService.list(new QueryWrapper<ForecastPa>().eq("stcd", attResBase.getStcd()).ge("tm", sdfDay.format(cal.getTime())).le("tm", sdfDay.format(endTime)));
if (CollectionUtils.isEmpty(paList)) {
throw new IllegalArgumentException("温馨提示当前洪水预报所依赖的数据尚不完整请检查每日土壤含水量Pa是否缺失。");
}
Map<String, ForecastPa> 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<StRsvrR>().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<StZqrlB> stZqrlBList = stZqrlBService.list(new QueryWrapper<StZqrlB>().eq("stcd", attResBase.getStcd()).orderBy(true, true, "z"));
// 库容曲线
List<StZvarlB> zvarlBS = stZvarlBService.list(new QueryWrapper<StZvarlB>().eq("stcd", attResBase.getStcd()).orderBy(true, true, "rz"));
// 水位历史数据
List<StRsvrR> rsvrRRealList = stRsvrRService.list(new QueryWrapper<StRsvrR>().eq("stcd", attResBase.getStcd()).ge("tm", startTime).le("tm", endTime));
List<StRsvrR> resultList = reorganizeRsvrRData(rsvrRRealList, dt);
List<Date[]> periods = splitByDay8To8(startTime, endTime);
// v累计降雨
BigDecimal vValue = BigDecimal.ZERO;
BigDecimal vSum = BigDecimal.ZERO;
// Rsum累计径流深
BigDecimal Rsum = BigDecimal.ZERO;
BigDecimal Psum = BigDecimal.ZERO;
List<Map<String, Object>> 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<StPptnR> filterList = polyPptnRList.stream().filter(e -> e.getTm().compareTo(period[0]) >= 0).filter(e -> e.getTm().compareTo(period[1]) <= 0).collect(Collectors.toList());
Map<String, List<StPptnR>> 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<String> 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<String, String> 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<Double> 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<String,Object> 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<FloodAlgorithemVo> 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<StRsvrR> realRsvrList = resultList.stream().filter(item -> item.getTm().compareTo(period[0]) >= 0 && item.getTm().compareTo(period[1]) <= 0).collect(Collectors.toList());
Map<String, String> 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<BigDecimal, BigDecimal> stZvalMap = stZqrlBList.stream().collect(Collectors.toMap(StZqrlB::getZ, StZqrlB::getQ, (existing, replacement) -> existing));
List<BigDecimal> 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 线
@ -1122,23 +837,6 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
return jsonBuilder.toString();
}
public List<Map<String, Object>> getHumanForecastResultDebug(ForecastTask forecastTask) {
// 当前时间整点,作为获取雨量数据历史、预测分隔点
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date nowHourTime = calendar.getTime();
forecastTask.setNowTime(nowHourTime);
List<Map<String, Object>> maps;
try {
maps = excuteForecastDebug(forecastTask);
} catch (Exception e) {
e.printStackTrace();
throw new IllegalArgumentException(e.getMessage());
}
return maps;
}
public List<OsmoticPressDetailVo> calculateY(SyRegressionData syRegressionData, List<ForecastResultVo> voList) {
//过滤出预测时间的数据
@ -1188,7 +886,7 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
}
/**
* y
* y
* @param equation
* @param x x
* @return y
@ -1213,7 +911,7 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
} else {
result = calculateFirstOrder(cleanEquation, x);
}
// 四舍五入保留两位小数
// 只在最后结果保留两位小数,中间计算过程保持高精度
return result.setScale(2, RoundingMode.HALF_UP);
} catch (Exception e) {
// 计算异常时返回0
@ -1222,7 +920,7 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
}
/**
* 线 y = ax + b
* 线 y = ax + b
*/
private BigDecimal calculateFirstOrder(String equation, BigDecimal x) {
// 解析方程系数格式如0.0455x+94.2395
@ -1234,15 +932,15 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
BigDecimal a = parseCoefficient(parts[0]);
BigDecimal b = parseCoefficient(parts[1]);
return a.multiply(x).add(b);
// 高精度计算a * x + b
return a.multiply(x, MathContext.DECIMAL128).add(b, MathContext.DECIMAL128);
}
/**
* y = ax² + bx + c
* y = ax² + bx + c
*/
private BigDecimal calculateSecondOrder(String equation, BigDecimal x) {
// 解析方程系数,格式如:-68.4211x²+16312.8684x-972224.1397
// 支持两种格式x² 和 x^2
String normalizedEquation = equation.replace("x^2", "x²");
String[] parts = normalizedEquation.split("x");
if (parts.length < 3) {
@ -1253,16 +951,20 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
BigDecimal b = parseCoefficient(parts[1].replace("²", ""));
BigDecimal c = parseCoefficient(parts[2]);
BigDecimal x2 = x.multiply(x); // x²
return a.multiply(x2).add(b.multiply(x)).add(c);
// 高精度计算x² = x * x
BigDecimal x2 = x.multiply(x, MathContext.DECIMAL128);
// 高精度计算a * x² + b * x + c
BigDecimal term1 = a.multiply(x2, MathContext.DECIMAL128);
BigDecimal term2 = b.multiply(x, MathContext.DECIMAL128);
return term1.add(term2, MathContext.DECIMAL128).add(c, MathContext.DECIMAL128);
}
/**
* y = ax³ + bx² + cx + d
* y = ax³ + bx² + cx + d
*/
private BigDecimal calculateThirdOrder(String equation, BigDecimal x) {
// 解析方程系数,格式如:-2291.6667x³+819497.9167x²-97683901.8750x+3881297151.1650
// 支持两种格式x³ 和 x^3, x² 和 x^2
String normalizedEquation = equation.replace("x^3", "x³").replace("x^2", "x²");
String[] parts = normalizedEquation.split("x");
if (parts.length < 4) {
@ -1274,18 +976,22 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
BigDecimal c = parseCoefficient(parts[2].replace("²", ""));
BigDecimal d = parseCoefficient(parts[3]);
BigDecimal x2 = x.multiply(x); // x²
BigDecimal x3 = x2.multiply(x); // x³
// 高精度计算x² = x * x, x³ = x² * x
BigDecimal x2 = x.multiply(x, MathContext.DECIMAL128);
BigDecimal x3 = x2.multiply(x, MathContext.DECIMAL128);
return a.multiply(x3).add(b.multiply(x2)).add(c.multiply(x)).add(d);
// 高精度计算a * x³ + b * x² + c * x + d
BigDecimal term1 = a.multiply(x3, MathContext.DECIMAL128);
BigDecimal term2 = b.multiply(x2, MathContext.DECIMAL128);
BigDecimal term3 = c.multiply(x, MathContext.DECIMAL128);
return term1.add(term2, MathContext.DECIMAL128).add(term3, MathContext.DECIMAL128).add(d, MathContext.DECIMAL128);
}
/**
* y = ax + bx³ + cx² + dx + e
* y = ax + bx³ + cx² + dx + e
*/
private BigDecimal calculateFourthOrder(String equation, BigDecimal x) {
// 解析方程系数,格式如:-5.9039x⁴+523.5482x³+316095.2736x²-57676816.2672x+2688986002.6804
// 支持两种格式x⁴ 和 x^4, x³ 和 x^3, x² 和 x^2
String normalizedEquation = equation.replace("x^4", "x⁴").replace("x^3", "x³").replace("x^2", "x²");
String[] parts = normalizedEquation.split("x");
if (parts.length < 5) {
@ -1298,15 +1004,24 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
BigDecimal d = parseCoefficient(parts[3].replace("²", ""));
BigDecimal e = parseCoefficient(parts[4]);
BigDecimal x2 = x.multiply(x); // x²
BigDecimal x3 = x2.multiply(x); // x³
BigDecimal x4 = x3.multiply(x); // x⁴
// 高精度计算x² = x * x, x³ = x² * x, x⁴ = x³ * x
BigDecimal x2 = x.multiply(x, MathContext.DECIMAL128);
BigDecimal x3 = x2.multiply(x, MathContext.DECIMAL128);
BigDecimal x4 = x3.multiply(x, MathContext.DECIMAL128);
return a.multiply(x4).add(b.multiply(x3)).add(c.multiply(x2)).add(d.multiply(x)).add(e);
// 高精度计算a * x⁴ + b * x³ + c * x² + d * x + e
BigDecimal term1 = a.multiply(x4, MathContext.DECIMAL128);
BigDecimal term2 = b.multiply(x3, MathContext.DECIMAL128);
BigDecimal term3 = c.multiply(x2, MathContext.DECIMAL128);
BigDecimal term4 = d.multiply(x, MathContext.DECIMAL128);
return term1.add(term2, MathContext.DECIMAL128)
.add(term3, MathContext.DECIMAL128)
.add(term4, MathContext.DECIMAL128)
.add(e, MathContext.DECIMAL128);
}
/**
*
*
*/
private BigDecimal parseCoefficient(String coeffStr) {
if (coeffStr == null || coeffStr.isEmpty()) {
@ -1324,7 +1039,8 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
}
try {
return new BigDecimal(coeffStr);
// 使用BigDecimal的高精度解析
return new BigDecimal(coeffStr, MathContext.DECIMAL128);
} catch (NumberFormatException e) {
// 如果解析失败,尝试处理特殊情况
if (coeffStr.equals("+")) {
@ -1335,4 +1051,99 @@ public class ForecastResultsService extends ServiceImpl<ForecastResultsMapper, F
return BigDecimal.ZERO;
}
}
public void reportExport(List<OsmoticPressDetailVo> data, String regression, String dvcd, HttpServletResponse response) {
try {
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
String fileName = URLEncoder.encode("渗透压力预测报告", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
// 创建Excel工作簿
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("渗透压力预测报告");
// 创建单元格样式 - 支持换行
CellStyle wrapStyle = workbook.createCellStyle();
wrapStyle.setWrapText(true);
// 设置行号
int rowNum = 0;
// 第一行:测点编号(不合并)
Row row1 = sheet.createRow(rowNum++);
Cell label1 = row1.createCell(0);
label1.setCellValue("测点编号:");
Cell value1 = row1.createCell(1);
value1.setCellValue(dvcd);
// 第二行回归方程从D列开始合并10个单元格
Row row2 = sheet.createRow(rowNum++);
Cell label2 = row2.createCell(0);
label2.setCellValue("回归方程:");
Cell value2 = row2.createCell(3); // D列索引为3
value2.setCellValue(regression != null ? regression : "");
value2.setCellStyle(wrapStyle);
// 合并D列到M列共10个单元格从第3列到第12列
sheet.addMergedRegion(new CellRangeAddress(1, 1, 3, 12));
// 空一行
rowNum++;
// 表头行
Row headerRow = sheet.createRow(rowNum++);
String[] headers = {"预测时间", "预测值", "预测库水位"};
for (int i = 0; i < headers.length; i++) {
Cell headerCell = headerRow.createCell(i);
headerCell.setCellValue(headers[i]);
}
// 数据行
if (data != null && !data.isEmpty()) {
for (OsmoticPressDetailVo detail : data) {
Row dataRow = sheet.createRow(rowNum++);
// 预测时间
Cell timeCell = dataRow.createCell(0);
timeCell.setCellValue(detail.getTm() != null ? detail.getTm() : "");
// 预测值
Cell valueCell = dataRow.createCell(1);
if (detail.getValue() != null) {
valueCell.setCellValue(detail.getValue().doubleValue());
} else {
valueCell.setCellValue("");
}
// 预测库水位
Cell rzCell = dataRow.createCell(2);
if (detail.getRz() != null) {
rzCell.setCellValue(detail.getRz().doubleValue());
} else {
rzCell.setCellValue("");
}
}
}
// 设置列宽
sheet.setColumnWidth(0, 15 * 256); // A列标签列固定宽度
sheet.setColumnWidth(1, 12 * 256); // B列测点编号值固定宽度
sheet.setColumnWidth(2, 12 * 256); // C列空列固定宽度
sheet.setColumnWidth(3, 50 * 256); // D列方程列开始固定宽度
// 自动调整数据表格的列宽
sheet.autoSizeColumn(0); // 预测时间列
sheet.autoSizeColumn(1); // 预测值列
sheet.autoSizeColumn(2); // 预测库水位列
// 写入响应流
workbook.write(response.getOutputStream());
workbook.close();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("导出Excel失败");
}
}
}

View File

@ -25,6 +25,9 @@ public class SyDataCheckRuleService extends ServiceImpl<SyDataCheckRuleMapper, S
if(!StringUtils.isBlank(page.getDvcd())){
queryWrapper.eq(SyDataCheckRule::getDvcd,page.getDvcd());
}
if(!StringUtils.isBlank(page.getDm())){
queryWrapper.eq(SyDataCheckRule::getDm,page.getDm());
}
Page<SyDataCheckRule> syDataCheckRulePage = this.baseMapper.selectPage(page.getPageSo().toPage(), queryWrapper);
return syDataCheckRulePage;
}

View File

@ -3,15 +3,21 @@ package com.gunshi.project.hsz.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gunshi.project.hsz.mapper.StZvarlBMapper;
import com.gunshi.project.hsz.common.model.vo.OsmoticPressDetailVo;
import com.gunshi.project.hsz.entity.vo.ProjectSafeCalculateVo;
import com.gunshi.project.hsz.mapper.SyRegressionDataMapper;
import com.gunshi.project.hsz.model.StZvarlB;
import com.gunshi.project.hsz.model.SyRegressionData;
import com.gunshi.project.hsz.util.RegressionEquationFormatter;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.URLEncoder;
import java.util.List;
@Service
@ -33,4 +39,133 @@ public class SyRegressionDataService extends ServiceImpl<SyRegressionDataMapper,
List<SyRegressionData> syRegressionData = this.baseMapper.selectList(queryWrapper);
return syRegressionData;
}
public void export(String dvcd, ProjectSafeCalculateVo ans, HttpServletResponse response) {
try {
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
String fileName = URLEncoder.encode("渗透压力监测数据", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
// 创建Excel工作簿
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("渗透压力监测数据");
// 创建单元格样式 - 支持换行
CellStyle wrapStyle = workbook.createCellStyle();
wrapStyle.setWrapText(true); // 设置自动换行
// 设置行号
int rowNum = 0;
// 第一行:测点编号(不合并)
Row row1 = sheet.createRow(rowNum++);
Cell label1 = row1.createCell(0);
label1.setCellValue("测点编号:");
Cell value1 = row1.createCell(1);
value1.setCellValue(dvcd);
// 第二行一阶回归方程从D列开始合并10个单元格
Row row2 = sheet.createRow(rowNum++);
Cell label2 = row2.createCell(0);
label2.setCellValue("一阶回归方程:");
Cell value2 = row2.createCell(3); // D列索引为3
String one = RegressionEquationFormatter.formatEquation(ans.getOne());
value2.setCellValue(one != null ? one : "");
value2.setCellStyle(wrapStyle);
// 合并D列到M列共10个单元格从第3列到第12列
sheet.addMergedRegion(new CellRangeAddress(1, 1, 3, 12));
// 第三行二阶回归方程从D列开始合并10个单元格
Row row3 = sheet.createRow(rowNum++);
Cell label3 = row3.createCell(0);
label3.setCellValue("二阶回归方程:");
Cell value3 = row3.createCell(3); // D列索引为3
String tow = RegressionEquationFormatter.formatEquation(ans.getTwo());
value3.setCellValue(tow != null ? tow : "");
value3.setCellStyle(wrapStyle);
// 合并D列到M列共10个单元格从第3列到第12列
sheet.addMergedRegion(new CellRangeAddress(2, 2, 3, 12));
// 第四行三阶回归方程从D列开始合并10个单元格
Row row4 = sheet.createRow(rowNum++);
Cell label4 = row4.createCell(0);
label4.setCellValue("三阶回归方程:");
Cell value4 = row4.createCell(3); // D列索引为3
String three = RegressionEquationFormatter.formatEquation(ans.getThree());
value4.setCellValue(three != null ? three : "");
value4.setCellStyle(wrapStyle);
// 合并D列到M列共10个单元格从第3列到第12列
sheet.addMergedRegion(new CellRangeAddress(3, 3, 3, 12));
// 第五行四阶回归方程从D列开始合并10个单元格
Row row5 = sheet.createRow(rowNum++);
Cell label5 = row5.createCell(0);
label5.setCellValue("四阶回归方程:");
Cell value5 = row5.createCell(3); // D列索引为3
String four = RegressionEquationFormatter.formatEquation(ans.getFour());
value5.setCellValue(four != null ? four : "");
value5.setCellStyle(wrapStyle);
// 合并D列到M列共10个单元格从第3列到第12列
sheet.addMergedRegion(new CellRangeAddress(4, 4, 3, 12));
// 空一行
rowNum++;
// 表头行 - 从第0列开始正常显示
Row headerRow = sheet.createRow(rowNum++);
String[] headers = {"监测时间", "监测值", "库水位"};
for (int i = 0; i < headers.length; i++) {
Cell headerCell = headerRow.createCell(i);
headerCell.setCellValue(headers[i]);
}
// 数据行
if (ans.getDatas() != null && !ans.getDatas().isEmpty()) {
for (OsmoticPressDetailVo data : ans.getDatas()) {
Row dataRow = sheet.createRow(rowNum++);
// 监测时间 - 第0列
Cell timeCell = dataRow.createCell(0);
timeCell.setCellValue(data.getTm() != null ? data.getTm() : "");
// 监测值 - 第1列
Cell valueCell = dataRow.createCell(1);
if (data.getValue() != null) {
valueCell.setCellValue(data.getValue().doubleValue());
} else {
valueCell.setCellValue("");
}
// 库水位 - 第2列
Cell rzCell = dataRow.createCell(2);
if (data.getRz() != null) {
rzCell.setCellValue(data.getRz().doubleValue());
} else {
rzCell.setCellValue("");
}
}
}
// 设置列宽
sheet.setColumnWidth(0, 15 * 256); // A列标签列固定宽度
sheet.setColumnWidth(1, 12 * 256); // B列测点编号值固定宽度
sheet.setColumnWidth(2, 12 * 256); // C列空列固定宽度
sheet.setColumnWidth(3, 50 * 256); // D列方程列开始固定宽度
// 自动调整数据表格的列宽(只对数据区域有效)
sheet.autoSizeColumn(0); // 监测时间列
sheet.autoSizeColumn(1); // 监测值列
sheet.autoSizeColumn(2); // 库水位列
// 写入响应流
workbook.write(response.getOutputStream());
workbook.close();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("导出Excel失败");
}
}
}

View File

@ -0,0 +1,144 @@
package com.gunshi.project.hsz.util;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
*/
public class RegressionEquationFormatter {
/**
*
* @param equation
* @param decimalDigits
* @return
*/
public static String formatEquation(String equation, int decimalDigits) {
if (equation == null || equation.trim().isEmpty()) {
return equation;
}
// 使用正则表达式匹配数字
String regex = "-?\\d+\\.\\d+";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(equation);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String numberStr = matcher.group();
try {
// 截取小数点后指定位数,进行四舍五入
String formatted = roundDecimal(numberStr, decimalDigits);
matcher.appendReplacement(result, formatted);
} catch (Exception e) {
// 如果处理失败,保留原数字
matcher.appendReplacement(result, numberStr);
}
}
matcher.appendTail(result);
return result.toString();
}
/**
*
*/
private static String roundDecimal(String numberStr, int decimalDigits) {
boolean isNegative = numberStr.startsWith("-");
if (isNegative) {
numberStr = numberStr.substring(1);
}
// 使用BigDecimal进行精确的四舍五入
BigDecimal bd = new BigDecimal(numberStr);
bd = bd.setScale(decimalDigits, RoundingMode.HALF_UP);
// 转换为字符串
String result = bd.toPlainString();
// 确保显示所有小数位即使末尾是0
if (result.contains(".")) {
String[] parts = result.split("\\.");
String integerPart = parts[0];
String decimalPart = parts[1];
// 如果小数部分长度不足补0
while (decimalPart.length() < decimalDigits) {
decimalPart += "0";
}
result = integerPart + "." + decimalPart;
}
return isNegative ? "-" + result : result;
}
/**
* 4
* @param equation
* @return
*/
public static String formatEquation(String equation) {
return formatEquation(equation, 4);
}
/**
*
* @param number
* @param decimalDigits
* @return
*/
public static String formatNumber(double number, int decimalDigits) {
String numberStr = String.valueOf(number);
return roundDecimal(numberStr, decimalDigits);
}
/**
* 4
* @param number
* @return
*/
public static String formatNumber(double number) {
return formatNumber(number, 4);
}
/**
*
* @param number
* @param decimalDigits
* @return
*/
public static String formatNumber(BigDecimal number, int decimalDigits) {
if (number == null) {
return "0";
}
number = number.setScale(decimalDigits, RoundingMode.HALF_UP);
String result = number.toPlainString();
// 确保显示所有小数位即使末尾是0
if (result.contains(".")) {
String[] parts = result.split("\\.");
String integerPart = parts[0];
String decimalPart = parts[1];
// 如果小数部分长度不足补0
while (decimalPart.length() < decimalDigits) {
decimalPart += "0";
}
result = integerPart + "." + decimalPart;
}
return result;
}
/**
* 4
* @param number
* @return
*/
public static String formatNumber(BigDecimal number) {
return formatNumber(number, 4);
}
}