diff --git a/weather-service/src/main/java/com/htfp/weather/griddata/common/TableConfig.java b/weather-service/src/main/java/com/htfp/weather/griddata/common/TableConfig.java index e5e7991..6eea121 100644 --- a/weather-service/src/main/java/com/htfp/weather/griddata/common/TableConfig.java +++ b/weather-service/src/main/java/com/htfp/weather/griddata/common/TableConfig.java @@ -63,7 +63,7 @@ public class TableConfig { // private final String configPath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("config/tableConf.json")).getPath(); private final String configPath = System.getProperty("user.dir") +"/tableConf.json"; @PostConstruct - private void initTableConfig() { + public void initTableConfig() { readConfig(); initLonList(); initLatList(); diff --git a/weather-service/src/main/java/com/htfp/weather/utils/HttpClientUtils.java b/weather-service/src/main/java/com/htfp/weather/utils/HttpClientUtils.java index ca293d5..060cf82 100644 --- a/weather-service/src/main/java/com/htfp/weather/utils/HttpClientUtils.java +++ b/weather-service/src/main/java/com/htfp/weather/utils/HttpClientUtils.java @@ -58,7 +58,10 @@ public class HttpClientUtils { public static String sendGet(String url, Map params) throws IOException { StringBuilder stringBuilder = new StringBuilder(); if (!CollectionUtils.isEmpty(params)) { - params.keySet().forEach(res -> { + for (String res : params.keySet()) { + if (res == null || params.get(res) == null) { + continue; + } if (StringUtils.isNotBlank(stringBuilder)) { stringBuilder.append("&"); } else { @@ -69,7 +72,7 @@ public class HttpClientUtils { } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } - }); + } } Request request = new Builder() .url(url + stringBuilder) diff --git a/weather-service/src/main/java/com/htfp/weather/web/controller/SurfaceWeatherController.java b/weather-service/src/main/java/com/htfp/weather/web/controller/SurfaceWeatherController.java index 3ff8705..8fcaf76 100644 --- a/weather-service/src/main/java/com/htfp/weather/web/controller/SurfaceWeatherController.java +++ b/weather-service/src/main/java/com/htfp/weather/web/controller/SurfaceWeatherController.java @@ -7,6 +7,7 @@ import com.htfp.weather.web.pojo.response.Result; import com.htfp.weather.web.pojo.response.SurfaceWeatherWarning; import com.htfp.weather.web.pojo.response.TimeSeriesDataset; import com.htfp.weather.web.service.surfaceapi.CaiYunServiceImpl; +import com.htfp.weather.web.service.surfaceapi.CmaServiceImpl; import com.htfp.weather.web.service.surfaceapi.HeFengServiceImpl; import com.htfp.weather.web.service.IDataService; import com.htfp.weather.web.service.surfaceapi.ISurfaceDataService; @@ -29,6 +30,8 @@ public class SurfaceWeatherController { @Resource(name = "hefeng") ISurfaceDataService surfaceDataService; + @Resource + CmaServiceImpl cmaService; @PostMapping("/querySurfaceNowWeather") public Result queryNowWeather(@Validated @RequestBody Position2D position2D) throws Exception { double lat = position2D.getLatitude(); @@ -58,7 +61,8 @@ public class SurfaceWeatherController { double lat = position2D.getLatitude(); double lon = position2D.getLongitude(); log.info("[data-server] 地面气象预警信息查询 start: param={}", position2D); - List warning = surfaceDataService.getSurfaceWarning(lat, lon); + // List warning = surfaceDataService.getSurfaceWarning(lat, lon); + List warning = cmaService.getSurfaceWarning(lat, lon); log.info("[data-server] 地面气象预警信息查询 end"); return Result.success(warning); } diff --git a/weather-service/src/main/java/com/htfp/weather/web/exception/ErrorCode.java b/weather-service/src/main/java/com/htfp/weather/web/exception/ErrorCode.java index 21314de..2a12966 100644 --- a/weather-service/src/main/java/com/htfp/weather/web/exception/ErrorCode.java +++ b/weather-service/src/main/java/com/htfp/weather/web/exception/ErrorCode.java @@ -21,9 +21,12 @@ public enum ErrorCode { // 数据查询 HE_FENG_THIS_AREA_HAVE_NO_DATA(3001, "查询的数据或地区不存在"), HE_FENG_REQUEST_ERROR(3002, "查询请求错误"), - CAI_YUN_REQUEST_ERROR(4002, "查询请求错误"), - CAI_YUN_DATA_PARSE_ERROR(4003, "数据解析错误"), - + CAI_YUN_REQUEST_ERROR(3003, "查询请求错误"), + CAI_YUN_DATA_PARSE_ERROR(3004, "数据解析错误"), + CMA_REQUEST_ERROR(3005, "查询请求错误"), + CMA_DATA_PARSE_ERROR(3006, "数据解析错误"), + TIANDITU_REQUEST_ERROR(3007, "查询请求错误"), + TIANDITU_DATA_PARSE_ERROR(3008, "数据解析错误"), IMPORT_DATA_INITIAL_FAILED(5001, "导入数据初始化失败"), NO_NC_OR_GRIB_FILES(5002, "文件夹不存在或中没有对应的气象数据文件"), diff --git a/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/AddressComponent.java b/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/AddressComponent.java new file mode 100644 index 0000000..386524c --- /dev/null +++ b/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/AddressComponent.java @@ -0,0 +1,32 @@ +package com.htfp.weather.web.pojo.cma; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class AddressComponent { + + private String address; + private String city; + @JsonProperty("county_code") + private String countyCode; + private String nation; + @JsonProperty("poi_position") + private String poiPosition; + private String county; + @JsonProperty("city_code") + private String cityCode; + @JsonProperty("address_position") + private String addressPosition; + private String poi; + @JsonProperty("province_code") + private String provinceCode; + private String province; + private String road; + @JsonProperty("road_distance") + private int roadDistance; + @JsonProperty("poi_distance") + private int poiDistance; + @JsonProperty("address_distance") + private int addressDistance; +} diff --git a/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/AntiGeoCodeResponse.java b/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/AntiGeoCodeResponse.java new file mode 100644 index 0000000..fd3bbf1 --- /dev/null +++ b/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/AntiGeoCodeResponse.java @@ -0,0 +1,22 @@ +package com.htfp.weather.web.pojo.cma; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * @Author : shiyi + * @Date : 2024/7/2 16:13 + * @Description : 天地图逆编码响应结构 + */ +@Data +public class AntiGeoCodeResponse { + Result result; + String msg; + String status; + @Data @JsonIgnoreProperties(value = {"formattedAddress", "location"}, ignoreUnknown = true) + public static class Result { + AddressComponent addressComponent; + } +} + + diff --git a/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/CmaWarning.java b/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/CmaWarning.java new file mode 100644 index 0000000..1be30a3 --- /dev/null +++ b/weather-service/src/main/java/com/htfp/weather/web/pojo/cma/CmaWarning.java @@ -0,0 +1,32 @@ +package com.htfp.weather.web.pojo.cma; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * @Author : shiyi + * @Date : 2024/7/1 17:58 + * @Description : 中央气象台预警信息单元 + */ +@Data @JsonIgnoreProperties(value = {"warnDefine"}, ignoreUnknown = true) +public class CmaWarning { + private String expires; + private String procince; + private Integer leveltype; + private String signalypecode; + private Double lon; + private Double lat; + private Integer zoom; + private String issuecontent; // 发布内容 + private String signallevelcode; // 颜色等级: YELLOW, BLUE, ORANGE, RED + private String reference; + private String areaId; // 县级行政编码 + private String signallevel; // 预警等级:黄色,蓝色,橙色,红色 + private String dataid; // 预警数据ID + private String sender; // 数据发布单位 + private String codename; // 预警信号名称 + private String name; // 行政单位名称 + private String time; // 发布时间 + private String msgtype; // 消息类型:ALERT、UPDATE + private String procincecode; // 省级行政编码(前两位) +} diff --git a/weather-service/src/main/java/com/htfp/weather/web/pojo/response/SurfaceWeatherWarning.java b/weather-service/src/main/java/com/htfp/weather/web/pojo/response/SurfaceWeatherWarning.java index 846f0f7..3114fe1 100644 --- a/weather-service/src/main/java/com/htfp/weather/web/pojo/response/SurfaceWeatherWarning.java +++ b/weather-service/src/main/java/com/htfp/weather/web/pojo/response/SurfaceWeatherWarning.java @@ -1,5 +1,6 @@ package com.htfp.weather.web.pojo.response; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; /** @@ -10,10 +11,12 @@ import lombok.Data; @Data public class SurfaceWeatherWarning { String pubTime; - // String startTime; + String endTime; String title; String text; String typeName; String severityColor; String source; + @JsonIgnore + String dataid; } diff --git a/weather-service/src/main/java/com/htfp/weather/web/service/surfaceapi/CmaServiceImpl.java b/weather-service/src/main/java/com/htfp/weather/web/service/surfaceapi/CmaServiceImpl.java new file mode 100644 index 0000000..acc44dd --- /dev/null +++ b/weather-service/src/main/java/com/htfp/weather/web/service/surfaceapi/CmaServiceImpl.java @@ -0,0 +1,154 @@ +package com.htfp.weather.web.service.surfaceapi; + +import com.htfp.weather.info.Constant; +import com.htfp.weather.utils.DateTimeUtils; +import com.htfp.weather.utils.JSONUtils; +import com.htfp.weather.web.exception.AppException; +import com.htfp.weather.web.exception.ErrorCode; +import com.htfp.weather.web.pojo.cma.AntiGeoCodeResponse; +import com.htfp.weather.web.pojo.cma.CmaWarning; +import com.htfp.weather.web.pojo.response.NowWeatherStatus; +import com.htfp.weather.web.pojo.response.SurfaceWeatherWarning; +import com.htfp.weather.web.pojo.response.TimeSeriesDataset; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static com.htfp.weather.utils.HttpClientUtils.sendGet; + +/** + * @Author : shiyi + * @Date : 2024/7/2 14:02 + * @Description : 中央气象台相关数据 + */ +@Slf4j +@Service("cma") +public class CmaServiceImpl implements ISurfaceDataService{ + @Value("${tianditu.key}") @Setter + private String keyOfTianDiTu; + private Map> warningCache = new ConcurrentHashMap<>(); + + @Override + public NowWeatherStatus getNowSurfaceWeatherStatus(double lat, double lon) throws Exception { + return null; + } + + @Override + public TimeSeriesDataset getForecastSeries(double lat, double lon) throws Exception { + return null; + } + + @Override + public List getSurfaceWarning(double lat, double lon) throws Exception { + String countyId = getCountyIdByLatLon(lat, lon); + return getSurfaceWarningByCounty(countyId); + } + + public List getSurfaceWarningByCounty(String countyCode) throws Exception { + return warningCache.get(countyCode); + // else { + // // 如果缓存中没有,则先查一遍 + // updateCmaWarning(countyCode); + // return warningCache.get(countyCode); + // } + + } + + /**根据经纬度获取县级行政区代码*/ + public String getCountyIdByLatLon(double lat, double lon) throws Exception { + String url = "http://api.tianditu.gov.cn/geocoder"; + HashMap params = new HashMap<>(); + params.put("postStr", String.format("{'lon':%.6f,'lat':%.6f,'ver':1}", lon, lat)); + params.put("type", "geocode"); + params.put("tk", keyOfTianDiTu); + AntiGeoCodeResponse antiGeoCodeResponse = sendGet(url, params, AntiGeoCodeResponse.class); + if (!"ok".equals(antiGeoCodeResponse.getMsg())) { + throw new AppException(ErrorCode.TIANDITU_REQUEST_ERROR, "获取行政区划信息失败:" + antiGeoCodeResponse.getMsg()); + } + String countyCode = antiGeoCodeResponse.getResult().getAddressComponent().getCountyCode(); + if (StringUtils.isNotEmpty(countyCode)) { + return countyCode.substring(countyCode.length()-6); + } else { + throw new AppException(ErrorCode.TIANDITU_DATA_PARSE_ERROR, "获取行政区代码失败:" + antiGeoCodeResponse.getMsg()); + } + } + /**每半小时定时更新全国的气象预警数据; + * 存在数据更新不及时,例如地震龙卷飑线等短时强灾害 + * */ + @Scheduled(cron = "0 20,50 * * * ?") + @PostConstruct + public void updateCmaWarning() { + updateCmaWarning(null); // 更新全国预警数据 + } + + /**更新某县区所在省的全部预警数据 + * @param countyCode 县级行政区代码*/ + public void updateCmaWarning(String countyCode) { + String url = "https://data.cma.cn/dataGis/disasterWarning/getWarningDataByCnty"; + // 2小时前,24小时后 + OffsetDateTime startTime = DateTimeUtils.offsetDateTimeToSystemZone(OffsetDateTime.now()).minusHours(2); + OffsetDateTime endTime = startTime.plusHours(24); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd+HH:mm:ss"); + HashMap params = new HashMap<>(); + params.put("startTime", startTime.format(formatter)); + params.put("endTime", endTime.format(formatter)); + params.put("provinceCode", countyCode); + log.info("更新预警数据 start:{}", params); + try { + if (params.get("provinceCode") == null) { + warningCache.clear(); + } + String jsonStr = sendGet(url, params); + List cmaWarningList = JSONUtils.json2list(jsonStr, CmaWarning.class); + for (CmaWarning cmaWarning : cmaWarningList) { + String countyId = cmaWarning.getAreaId(); + String dataid = cmaWarning.getDataid(); + SurfaceWeatherWarning surfaceWeatherWarning = buildSurfaceWeatherWarning(cmaWarning); + if (! warningCache.containsKey(countyId)) { + warningCache.put(countyId, new ArrayList<>()); + } + List countyCacheList = warningCache.get(countyId); + if (countyCacheList.stream().noneMatch(w -> dataid.equals(w.getDataid()))) { + // 该数据未缓存过 + warningCache.get(countyId).add(surfaceWeatherWarning); + } + } + } catch (Exception e) { + log.error("[中央气象台] 预警信息请求结果处理错误, {}", e.getMessage()); + } + log.info("更新预警数据end"); + } + + private SurfaceWeatherWarning buildSurfaceWeatherWarning(CmaWarning cmaWarning) { + SurfaceWeatherWarning surfaceWeatherWarning = new SurfaceWeatherWarning(); + surfaceWeatherWarning.setSource(cmaWarning.getSender()); + surfaceWeatherWarning.setSeverityColor(cmaWarning.getSignallevelcode().toUpperCase()); + surfaceWeatherWarning.setText(cmaWarning.getIssuecontent()); + String startTime = cmaWarning.getTime(); // NOTE 2024/7/2: 中央气象台默认使用 GMT+8 北京时 + if (StringUtils.isNotEmpty(startTime)) { + surfaceWeatherWarning.setPubTime(OffsetDateTime.parse(startTime + "+08:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxxx")).format(Constant.API_TIME_FORMATTER) + ); + } + String endTime = cmaWarning.getExpires(); + if (StringUtils.isNotEmpty(endTime)) { + surfaceWeatherWarning.setEndTime(OffsetDateTime.parse(endTime + "+08:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxxx")).format(Constant.API_TIME_FORMATTER) + ); + } + surfaceWeatherWarning.setTypeName(cmaWarning.getCodename()); + surfaceWeatherWarning.setTitle(String.format("%s发布:%s%s预警", cmaWarning.getSender(), cmaWarning.getCodename(), cmaWarning.getSignallevel())); + surfaceWeatherWarning.setDataid(cmaWarning.getDataid()); + return surfaceWeatherWarning; + } +} diff --git a/weather-service/src/main/resources/application-weather.yml b/weather-service/src/main/resources/application-weather.yml index e409048..c3184e6 100644 --- a/weather-service/src/main/resources/application-weather.yml +++ b/weather-service/src/main/resources/application-weather.yml @@ -3,7 +3,8 @@ hefeng: key: 4df66bb82da04b5bb85624874209fc73 caiyun: key: Tc9tgOYr5jlPPlEw - +tianditu: + key: 31094f552e1133bc7a82cfea7f03d74a # tablestore tablestore: endpoint: https://gfs-test.cn-hangzhou.ots.aliyuncs.com diff --git a/weather-service/src/main/resources/application.yml b/weather-service/src/main/resources/application.yml index 4778567..6e127e4 100644 --- a/weather-service/src/main/resources/application.yml +++ b/weather-service/src/main/resources/application.yml @@ -17,7 +17,11 @@ spring: auth: true enable: true required: true + task: + scheduling: + pool: + size: 3 mail: send: from: shiyi@htsdfp.com - to: shiyi@htsdfp.com \ No newline at end of file + to: shiyi@htsdfp.com diff --git a/weather-service/src/test/java/com/htfp/weather/web/service/surfaceapi/CmaServiceImplTest.java b/weather-service/src/test/java/com/htfp/weather/web/service/surfaceapi/CmaServiceImplTest.java new file mode 100644 index 0000000..fbec7b6 --- /dev/null +++ b/weather-service/src/test/java/com/htfp/weather/web/service/surfaceapi/CmaServiceImplTest.java @@ -0,0 +1,46 @@ +package com.htfp.weather.web.service.surfaceapi; + +import com.htfp.weather.web.exception.AppException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @Author : shiyi + * @Date : 2024/7/2 16:43 + * @Description : + */ +@SpringBootTest +class CmaServiceImplTest { + @Resource + CmaServiceImpl cmaService; + @Test + void normalTest() throws Exception { + String countyIdByLatLon = cmaService.getCountyIdByLatLon(39.9042, 116.4073); + Assertions.assertEquals("110101", countyIdByLatLon); + } + @Test + void outOfRange() throws Exception { + cmaService.getCountyIdByLatLon(39.9042, 166.4073); + Assertions.assertThrows(AppException.class, () -> { + cmaService.getCountyIdByLatLon(39.9042, 166.4073); + }); + Assertions.assertThrows(AppException.class, () -> { + cmaService.getCountyIdByLatLon(139.9042, 166.4073); + }); + } + @Test + void updateCmaWarning() throws Exception { + cmaService.updateCmaWarning(); + System.out.println(cmaService.getSurfaceWarningByCounty("310000")); + } + + @Test + void getWarningByLocation() throws Exception { + System.out.println(cmaService.getSurfaceWarning(41.22, 123.07)); + } +} \ No newline at end of file diff --git a/weather-service/src/test/resources/application-weather.yml b/weather-service/src/test/resources/application-weather.yml index d73b258..c3184e6 100644 --- a/weather-service/src/test/resources/application-weather.yml +++ b/weather-service/src/test/resources/application-weather.yml @@ -3,10 +3,12 @@ hefeng: key: 4df66bb82da04b5bb85624874209fc73 caiyun: key: Tc9tgOYr5jlPPlEw - +tianditu: + key: 31094f552e1133bc7a82cfea7f03d74a # tablestore tablestore: endpoint: https://gfs-test.cn-hangzhou.ots.aliyuncs.com accessId: LTAI5tDphvXLfwKJEmVQqrVz accessKey: ksioVP1S36PI6plkIIRN4A2xLB94uc - instanceName: gfs-test \ No newline at end of file + instanceName: gfs-test +