天气预报

master
wany 2024-07-09 11:28:36 +08:00
parent a4e2e494d2
commit dce7b97d93
11 changed files with 628 additions and 5 deletions

View File

@ -0,0 +1,45 @@
package com.gunshi.project.xyt.controller;
import com.gunshi.core.annotation.Post;
import com.gunshi.core.result.R;
import com.gunshi.project.xyt.entity.so.ShortWeatherSo;
import com.gunshi.project.xyt.entity.so.WeatherSo;
import com.gunshi.project.xyt.entity.vo.ForeRainVo;
import com.gunshi.project.xyt.service.ForecastService;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Description:
* Created by wanyan on 2024/3/11
*
* @author wanyan
* @version 1.0
*/
@RestController
@RequestMapping("/weather")
@Tag(name = "天气预报")
public class WeatherController {
@Resource
private ForecastService forecastService;
@Post(path = "/fore", summary = "24小时")
public R<List<ForeRainVo>> fore(@RequestBody WeatherSo weatherSo) {
return R.ok(forecastService.fore(weatherSo));
}
@Post(path = "/short/fore", summary = "短临预报")
public R<List<ForeRainVo>> shortFore(@RequestBody ShortWeatherSo weatherSo) {
return R.ok(forecastService.shortFore(weatherSo));
}
}

View File

@ -0,0 +1,18 @@
package com.gunshi.project.xyt.entity.so;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Data
public class ShortWeatherSo {
@Schema(description = "时间点(格式YYYYMMDDHHmm,mm只能取00和30)", example = "202402251100")
@NotEmpty(message = "时间点不能为空")
@Pattern(regexp = "^20[0-9]{6}[0-2][0-9]00|20[0-9]{6}[0-2][0-9]30", message = "时间格式应为YYYYMMDDHHmm其中mm只能取00和30")
private String tm;
}

View File

@ -0,0 +1,18 @@
package com.gunshi.project.xyt.entity.so;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Data
public class WeatherSo {
@Schema(description = "时间点(tm格式YYYYMMDDHH,HH只能取08和20)",example = "2023062908")
@NotEmpty(message = "时间点不能为空")
@Pattern(regexp = "^20[0-9]{6}08|20[0-9]{6}20", message = "时间格式应为YYYYMMDDHH其中HH只能为08或20")
private String tm;
}

View File

@ -0,0 +1,31 @@
package com.gunshi.project.xyt.entity.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.gunshi.core.dateformat.DateFormatString;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* -
* Created by wanyan on 2024/3/13
*
* @author wanyan
* @version 1.0
*/
@Data
@Builder
public class ForeRainTimeVo {
@Schema(description = "时间")
@JsonFormat(pattern = DateFormatString.YYYY_MM_DD_HH_MM_SS, timezone = "GMT+8")
private Date tm;
@Schema(description = "雨量")
private BigDecimal drp;
}

View File

@ -0,0 +1,40 @@
package com.gunshi.project.xyt.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class ForeRainVo {
private String stcd;
private String stnm;
private String lgtd;
private String lttd;
@Schema(description = "1h(mm)")
private BigDecimal h1;
@Schema(description = "3h(mm)")
private BigDecimal h3;
@Schema(description = "6h(mm)")
private BigDecimal h6;
@Schema(description = "12h(mm)")
private BigDecimal h12;
@Schema(description = "24h(mm)")
private Double h24;
@Schema(description = "2h(mm)")
private Double h2;
@Schema(description = "逐小时雨量")
private List<ForeRainTimeVo> timeRainVos;
}

View File

@ -0,0 +1,155 @@
package com.gunshi.project.xyt.grb;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
* Description:
* Created by wanyan on 2024/2/23
*
* @author wanyan
* @version 1.0
*/
@Data
public class RainGrib2Layer {
@Schema(description = "tmRange")
private TmRange tmRange;
@Schema(description = "data")
public BigDecimal[][] data;
@Schema(description = "图片base64数据")
private String base64;
@Schema(description = "x1")
private BigDecimal x1;
@Schema(description = "x2")
private BigDecimal x2;
@Schema(description = "y1")
private BigDecimal y1;
@Schema(description = "y2")
private BigDecimal y2;
@Schema(description = "dw")
private BigDecimal dw;
@Schema(description = "dh")
private BigDecimal dh;
@Schema(description = "nw")
private int nw;
@Schema(description = "nh")
private int nh;
// public RainGrib2Layer createSubset(Double bx1, Double by1, Double bx2, Double by2) {
// int ix1 = 0, ix2 = nw - 1, iy1 = 0, iy2 = nh - 1;
// if (bx1 != null && bx1 > x1) {
// ix1 = (int)((nw - 1) * (bx1 - x1) / (x2 - x1));
// }
// if (by1 != null && by1 > y1) {
// iy1 = (int)((nh - 1) * (by1 - y1) / (y2 - y1));
// }
// if (bx2 != null && bx2 < x2) {
// ix2 = (int)Math.ceil((nw - 1) * (bx2 - x1) / (x2 - x1));
// }
// if (by2 != null && by2 < y2) {
// iy2 = (int)Math.ceil((nh - 1) * (by2 - y1) / (y2 - y1));
// }
//
// RainGrib2Layer ret = new RainGrib2Layer();
//
// ret.tmRange = (TmRange) tmRange.clone();
// ret.x1 = x1 + ix1 * dw;
// ret.y1 = y1 + iy1 * dh;
// ret.x2 = x1 + ix2 * dw;
// ret.y2 = y1 + iy2 * dh;
// ret.nw = ix2 - ix1 + 1;
// ret.nh = iy2 - iy1 + 1;
// ret.dw = dw;
// ret.dh = dh;
// ret.data = new float[ret.nh][ret.nw];
// for (int iy = iy1; iy <= iy2; iy += 1) {
// for (int ix = ix1; ix <= ix2; ix += 1) {
// ret.data[iy - iy1][ix - ix1] = data[iy][ix];
// }
// }
//
// return ret;
// }
//
// boolean setContent(Grib2FrameParams frame, Grib2LayerParams layer, byte[] bin) {
// if (layer.calDataLen() != bin.length) {
// return false;
// }
//
// x1 = frame.x1;
// x2 = frame.x2;
// y1 = frame.y1;
// y2 = frame.y2;
// nw = frame.nw;
// nh = frame.nh;
// dw = frame.dw;
// dh = frame.dh;
// tmRange = (TmRange) layer.tmRange.clone();
// data = new float[nh][nw];
//
// float decimalScale = layer.getDecimalScale();
// float binaryScale = layer.getBinaryScale();
//
// boolean constValue = layer.numBits == 0;
// float RValue = layer.R;
//
// try {
// BitInputStream bis = new BitInputStream(new ByteArrayInputStream(bin),null);
// for (int iy = 0; iy < nh; iy++) {
// for (int ix = 0; ix < nw; ix++) {
// if (constValue) {
// data[iy][ix] = RValue;
// } else {
// long v = bis.readBits(layer.numBits);
// double value = decimalScale * (layer.R + v * binaryScale);
//
// data[iy][ix] = (float)value;
// }
// }
// }
// bis.close();
// return true;
// } catch (Exception e) {
// e.printStackTrace();
// return false;
// }
// }
//
// @Override
// public Object clone() {
// RainGrib2Layer ret = new RainGrib2Layer();
//
// ret.tmRange = (TmRange) tmRange.clone();
// ret.x1 = x1;
// ret.y1 = y1;
// ret.x2 = x2;
// ret.y2 = y2;
// ret.nw = nw;
// ret.nh = nh;
// ret.dw = dw;
// ret.dh = dh;
// ret.data = new float[nh][nw];
// for (int iy = 0; iy < nh; iy += 1) {
// for (int ix = 0; ix < nw; ix += 1) {
// ret.data[iy][ix] = data[iy][ix];
// }
// }
//
// return ret;
// }
}

View File

@ -0,0 +1,53 @@
package com.gunshi.project.xyt.grb;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/**
* Description:
* Created by wanyan on 2024/2/23
*
* @author wanyan
* @version 1.0
*/
@Data
public class TmRange {
public static final String TM_FORMAT = "yyyyMMddHH";
public static final String TM_FORMAT_RADAR = "yyyyMMddHHmm";
@Schema(description = "startTm")
private Date startTm;
@Schema(description = "offset")
private int offset;
@Schema(description = "interval")
private int interval;
@Schema(description = "tm1")
private Date tm1;
@Schema(description = "tm2")
private Date tm2;
@Schema(description = "key")
private String key;
@Override
public Object clone() {
TmRange ret = new TmRange();
ret.startTm = (Date)startTm.clone();
ret.offset = offset;
ret.tm1 = (Date)tm1.clone();
ret.tm2 = (Date)tm2.clone();
ret.interval = interval;
ret.key = key;
return ret;
}
}

View File

@ -1,5 +1,6 @@
package com.gunshi.project.xyt.mapper;
import com.gunshi.project.xyt.entity.vo.ForeRainVo;
import com.gunshi.project.xyt.entity.vo.RealRainListVo;
import com.gunshi.project.xyt.entity.vo.StPptnVo;
import com.gunshi.project.xyt.model.StPptnRD;
@ -119,4 +120,16 @@ public interface RealRainMapper {
</script>
""")
StPptnRReal queryPptnByStcd(@Param("stcd") String stcd);
@Select("""
<script>
select t1.stcd,
t1.stnm,
t1.lgtd,
t1.lttd
from st_stbprp_b t1
where t1.stcd in (select distinct(stcd) from st_stbprp_b_elem where elem ='drp')
</script>
""")
List<ForeRainVo> querySttpList();
}

View File

@ -15,6 +15,7 @@ import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@ -60,14 +61,14 @@ public class RescueTeamB implements Serializable, AbstractModelWithAttachService
*/
@TableField(value="lgtd")
@Schema(description="经度 (°)")
private String lgtd;
private BigDecimal lgtd;
/**
* (°)
*/
@TableField(value="lttd")
@Schema(description="纬度 (°)")
private String lttd;
private BigDecimal lttd;
/**
*
@ -98,7 +99,7 @@ public class RescueTeamB implements Serializable, AbstractModelWithAttachService
*/
@TableField(value="register_date")
@Schema(description="登记日期")
@JsonFormat(pattern = DateFormatString.YYYY_MM_DD_HH_MM_SS, timezone = "GMT+8")
@JsonFormat(pattern = DateFormatString.YYYY_MM_DD, timezone = "GMT+8")
private Date registerDate;
/**
@ -106,7 +107,7 @@ public class RescueTeamB implements Serializable, AbstractModelWithAttachService
*/
@TableField(value="valid_start_date")
@Schema(description="有效期开始时间")
@JsonFormat(pattern = DateFormatString.YYYY_MM_DD_HH_MM_SS, timezone = "GMT+8")
@JsonFormat(pattern = DateFormatString.YYYY_MM_DD, timezone = "GMT+8")
private Date validStartDate;
/**
@ -114,7 +115,7 @@ public class RescueTeamB implements Serializable, AbstractModelWithAttachService
*/
@TableField(value="valid_end_date")
@Schema(description="有效期结束时间")
@JsonFormat(pattern = DateFormatString.YYYY_MM_DD_HH_MM_SS, timezone = "GMT+8")
@JsonFormat(pattern = DateFormatString.YYYY_MM_DD, timezone = "GMT+8")
private Date validEndDate;
/**

View File

@ -0,0 +1,247 @@
package com.gunshi.project.xyt.service;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.gunshi.project.xyt.entity.so.ShortWeatherSo;
import com.gunshi.project.xyt.entity.so.WeatherSo;
import com.gunshi.project.xyt.entity.vo.ForeRainTimeVo;
import com.gunshi.project.xyt.entity.vo.ForeRainVo;
import com.gunshi.project.xyt.grb.RainGrib2Layer;
import com.gunshi.project.xyt.mapper.RealRainMapper;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
/**
* Description:
* Created by wanyan on 2024/3/13
*
* @author wanyan
* @version 1.0
*/
@Service
@Data
@Slf4j
public class ForecastService {
@Value("${shqxjsCloudowrCnPath}")
private String shqxjsCloudowrCnPath;//获取气象预报文件名接口 http://shqxjs.cloudowr.cn/service/
public static String grbgetName = "grb/get";
public static String radargetName = "radar/get";
public static String getData = "res/grblayerset";
public static String getRadarData = "res/radarlayer";
public static Double x1 = 108.62318959955861;
public static Double y1 = 29.328651167767397;
public static Double x2 = 109.33730092768361;
public static Double y2 = 30.04791233720099;
public static String totalLayers = "00.24";//只取24小时每个网格的总量
public static String detailLayers = "00.24,00.01,01.01,02.01,03.01,04.01,05.01,06.01,07.01,08.01,09.01,10.01,11.01,12.01,13.01,14.01,15.01,16.01,17.01,18.01,19.01,20.01,21.01,22.01,23.01,24.01";
@Resource
private RealRainMapper realRainMapper;
private List<ForeRainVo> calcDrp(List<RainGrib2Layer> gribList,List<ForeRainVo> rainLevelVos){
if(CollectionUtils.isEmpty(gribList)){
throw new IllegalArgumentException("该时间无预报数据");
}
RainGrib2Layer layer = gribList.get(0);
BigDecimal dh = layer.getDh();
BigDecimal dw = layer.getDw();
int nh = layer.getNh();//横向格子数
int nw = layer.getNw();//纵向格子数
//网格左下角经纬度
BigDecimal x11 = layer.getX1();
BigDecimal y11 = layer.getY1();
harry : for(ForeRainVo vo : rainLevelVos){
BigDecimal lgtd = new BigDecimal(vo.getLgtd());
BigDecimal lttd = new BigDecimal(vo.getLttd());
for(int i = 0;i < nh;i++){
for(int j=0;j< nw;j++){
BigDecimal xMin = x11.add(dh.multiply(BigDecimal.valueOf(i)));
BigDecimal xMax = x11.add(dh.multiply(BigDecimal.valueOf(i+1)));
BigDecimal yMin = y11.add(dw.multiply(BigDecimal.valueOf(j)));
BigDecimal yMax = y11.add(dw.multiply(BigDecimal.valueOf(j+1)));
if(lgtd.compareTo(xMin)>=0 && lgtd.compareTo(xMax)<0 && lttd.compareTo(yMin)>=0 && lttd.compareTo(yMax)<0){
vo.setH2(layer.data[j][i].doubleValue());
continue harry;
}
}
}
}
return rainLevelVos;
}
/**
* 24
* @param tm
* @return
*/
private List<RainGrib2Layer> getGribData(String tm,Boolean isOnlyQueryTotal){
List<RainGrib2Layer> list = new ArrayList<>();
String grbLatestUrl = shqxjsCloudowrCnPath + grbgetName + "?tm=" + tm;
HttpClient httpClient = HttpClientBuilder.create().build();
HttpGet httpGet = new HttpGet(grbLatestUrl);
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity entity = httpResponse.getEntity();
JSONObject jsonObject = JSONUtil.parseObj(EntityUtils.toString(entity));
JSONObject data = jsonObject.getJSONObject("data");
if (data == null) {
throw new IllegalArgumentException("该时间无预报数据");
};
String rainFile = data.getStr("rainFile");
String layers = detailLayers;
if(isOnlyQueryTotal){
layers = totalLayers;
}
String grblayersetUrl = shqxjsCloudowrCnPath + getData + "?filename="+rainFile + "&x1=" + x1+ "&y1=" + y1+ "&x2=" + x2+ "&y2=" + y2+"&layers="+layers;
HttpGet grbData = new HttpGet(grblayersetUrl);
HttpResponse responseForGrb = httpClient.execute(grbData);
HttpEntity entityForGrb = responseForGrb.getEntity();
JSONObject grbObject = JSONUtil.parseObj(EntityUtils.toString(entityForGrb));
JSONArray grbArrData = grbObject.getJSONArray("data");
if (grbArrData == null) return list;
list = grbArrData.toList(RainGrib2Layer.class);
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
/**
*
* @param tm
* @return
*/
private RainGrib2Layer getRadarData(String tm){
RainGrib2Layer rainGrib2Layer = new RainGrib2Layer();
String radarLatestUrl = shqxjsCloudowrCnPath + radargetName + "?tm=" + tm;
HttpClient httpClient = HttpClientBuilder.create().build();
HttpGet httpGet = new HttpGet(radarLatestUrl);
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity entity = httpResponse.getEntity();
JSONObject jsonObject = JSONUtil.parseObj(EntityUtils.toString(entity));
JSONObject data = jsonObject.getJSONObject("data");
if (data == null) {
throw new IllegalArgumentException("该时间无预报数据");
};
String rainFile = data.getStr("rainFile");
String radarUrl = shqxjsCloudowrCnPath + getRadarData + "?filename="+rainFile + "&x1=" + x1+ "&y1=" + y1+ "&x2=" + x2+ "&y2=" + y2;
HttpGet radarData = new HttpGet(radarUrl);
HttpResponse responseForRadar = httpClient.execute(radarData);
HttpEntity entityForRadar = responseForRadar.getEntity();
JSONObject radarObject = JSONUtil.parseObj(EntityUtils.toString(entityForRadar));
rainGrib2Layer = radarObject.get("data",RainGrib2Layer.class);
} catch (IOException e) {
e.printStackTrace();
}
return rainGrib2Layer;
}
public List<ForeRainVo> shortFore(ShortWeatherSo weatherSo) {
List<ForeRainVo> rainLevelVos = realRainMapper.querySttpList();
List<RainGrib2Layer> gribList = Arrays.asList(getRadarData(weatherSo.getTm()));
return calcDrp(gribList,rainLevelVos);
}
public List<ForeRainVo> fore(WeatherSo weatherSo) {
List<ForeRainVo> rainLevelVos = realRainMapper.querySttpList();
return commonDataHandle(weatherSo,rainLevelVos);
}
private List<ForeRainVo> commonDataHandle(WeatherSo weatherSo,List<ForeRainVo> list){
List<RainGrib2Layer> gribList = getGribData(weatherSo.getTm(),false);
if(CollectionUtils.isEmpty(list)){
return list;
}
//24小时每个网格的总量
List<RainGrib2Layer> total = gribList.stream().filter(o -> o.getTmRange().getInterval() == 24).collect(Collectors.toList());
//24小时每个网格的逐小时雨量
List<RainGrib2Layer> detail = gribList.stream().filter(o -> o.getTmRange().getInterval() == 1).collect(Collectors.toList());
for(ForeRainVo vo : list){
List<ForeRainTimeVo> resultList = getData(vo,total,detail);
vo.setTimeRainVos(resultList);
vo.setH1(resultList.get(0).getDrp());
List<ForeRainTimeVo> list3=resultList.subList(0, Math.min(3, list.size()));
vo.setH3(list3.stream().map(ForeRainTimeVo::getDrp).reduce(BigDecimal.ZERO, BigDecimal::add));
List<ForeRainTimeVo> list6=resultList.subList(0, Math.min(6, list.size()));
vo.setH6(list6.stream().map(ForeRainTimeVo::getDrp).reduce(BigDecimal.ZERO, BigDecimal::add));
List<ForeRainTimeVo> list12=resultList.subList(0, Math.min(12, list.size()));
vo.setH12(list12.stream().map(ForeRainTimeVo::getDrp).reduce(BigDecimal.ZERO, BigDecimal::add));
}
return list.stream().sorted(Comparator.comparing(ForeRainVo::getH24).reversed()).collect(Collectors.toList());
}
private List<ForeRainTimeVo> getData(ForeRainVo vo, List<RainGrib2Layer> total,List<RainGrib2Layer> detail) {
List<ForeRainTimeVo> result = new ArrayList<>();
RainGrib2Layer layer = total.get(0);
BigDecimal dh = layer.getDh();
BigDecimal dw = layer.getDw();
int nh = layer.getNh();//横向格子数
int nw = layer.getNw();//纵向格子数
//网格左下角经纬度
BigDecimal x11 = layer.getX1();
BigDecimal y11 = layer.getY1();
BigDecimal lgtd = new BigDecimal(vo.getLgtd());
BigDecimal lttd = new BigDecimal(vo.getLttd());
int rownum = 0;
int colnum = 0;
for(int i = 0;i < nh;i++){
for(int j=0;j< nw;j++){
BigDecimal xMin = x11.add(dh.multiply(BigDecimal.valueOf(i)));
BigDecimal xMax = x11.add(dh.multiply(BigDecimal.valueOf(i+1)));
BigDecimal yMin = y11.add(dw.multiply(BigDecimal.valueOf(j)));
BigDecimal yMax = y11.add(dw.multiply(BigDecimal.valueOf(j+1)));
if(lgtd.compareTo(xMin)>=0 && lgtd.compareTo(xMax)<0 && lttd.compareTo(yMin)>=0 && lttd.compareTo(yMax)<0){
rownum = j;
colnum = i;
vo.setH24(layer.data[rownum][colnum].doubleValue());
break;
}
}
}
for(RainGrib2Layer lay : detail){
result.add(ForeRainTimeVo.builder().tm(lay.getTmRange().getTm2()).drp(lay.data[rownum][colnum]).build());
}
Map<Date, BigDecimal> sumByGroup = result.stream()
.collect(Collectors.groupingBy(ForeRainTimeVo::getTm, Collectors.mapping(ForeRainTimeVo::getDrp, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
List<ForeRainTimeVo> resultList = sumByGroup.entrySet().stream()
.map(entry -> ForeRainTimeVo.builder().tm(entry.getKey()).drp(entry.getValue().divide(new BigDecimal(1),1, RoundingMode.UP)).build())
.sorted(Comparator.comparing(ForeRainTimeVo::getTm))
.collect(Collectors.toList());
return resultList;
}
}

View File

@ -25,3 +25,5 @@ gunshi:
publicBucket: test.by-lyf.tmp
loginBucket: test.by-lyf.tmp
privateBucket: test.by-lyf.tmp
shqxjsCloudowrCnPath: http://shqxjs.cloudowr.cn/service/