jinpengyong
2022-01-04 b09e39ad120086b67371e2d9ac03231cd6c5b398
臭氧预测
6 files added
4 files modified
405 ■■■■■ changed files
screen-api/src/main/java/com/moral/api/service/impl/HistoryDailyServiceImpl.java 8 ●●●● patch | view | raw | blame | history
screen-api/src/main/java/com/moral/api/service/impl/HistoryHourlyServiceImpl.java 8 ●●●● patch | view | raw | blame | history
screen-api/src/main/java/com/moral/api/service/impl/HistoryMonthlyServiceImpl.java 8 ●●●● patch | view | raw | blame | history
screen-common/src/main/java/com/moral/util/DateUtils.java 18 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/entity/Forecast.java 45 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/mapper/ForecastMapper.java 16 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/service/ForecastService.java 18 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/service/impl/ForecastServiceImpl.java 245 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/task/ForecastTask.java 27 ●●●●● patch | view | raw | blame | history
screen-job/src/main/resources/mapper/ForecastMapper.xml 12 ●●●●● patch | view | raw | blame | history
screen-api/src/main/java/com/moral/api/service/impl/HistoryDailyServiceImpl.java
@@ -366,13 +366,13 @@
            result = doubleStream.sum();
        } else {
            if ("min".equals(type)) {
                optionalDouble = doubleStream.average();
            } else if ("max".equals(type)) {
                optionalDouble = doubleStream.min();
            } else if ("avg".equals(type)) {
            } else if ("max".equals(type)) {
                optionalDouble = doubleStream.max();
            } else if ("avg".equals(type)) {
                optionalDouble = doubleStream.average();
            }
            if (optionalDouble.isPresent()) {
screen-api/src/main/java/com/moral/api/service/impl/HistoryHourlyServiceImpl.java
@@ -651,13 +651,13 @@
            result = doubleStream.sum();
        } else {
            if ("min".equals(type)) {
                optionalDouble = doubleStream.average();
            } else if ("max".equals(type)) {
                optionalDouble = doubleStream.min();
            } else if ("avg".equals(type)) {
            } else if ("max".equals(type)) {
                optionalDouble = doubleStream.max();
            } else if ("avg".equals(type)) {
                optionalDouble = doubleStream.average();
            }
            if (optionalDouble.isPresent()) {
screen-api/src/main/java/com/moral/api/service/impl/HistoryMonthlyServiceImpl.java
@@ -331,13 +331,13 @@
            result = doubleStream.sum();
        } else {
            if ("min".equals(type)) {
                optionalDouble = doubleStream.average();
            } else if ("max".equals(type)) {
                optionalDouble = doubleStream.min();
            } else if ("avg".equals(type)) {
            } else if ("max".equals(type)) {
                optionalDouble = doubleStream.max();
            } else if ("avg".equals(type)) {
                optionalDouble = doubleStream.average();
            }
            if (optionalDouble.isPresent()) {
screen-common/src/main/java/com/moral/util/DateUtils.java
@@ -1795,5 +1795,23 @@
    public static Date getFirstDayOfLastYear() {
        String lastYear = getDateAddYear(DateUtils.dateToDateString(getDate(), DateUtils.yyyy), -1);
        return DateUtils.getDate(lastYear, DateUtils.yyyy);
    }
    //获取一天中每个小时的前后一小时集合
    public static Map<Date, List<Integer>> getBeforeAndAfterHourDate(Date date) {
        String s = dateToDateString(date, yyyy_MM_dd_EN);
        List<String> timeLag = getTimeLag(s);
        Map<Date, List<Integer>> result = new HashMap<>();
        for (String s1 : timeLag) {
            List<Integer> objects = new ArrayList<>();
            Date current = getDate(s1, yyyy_MM_dd_HH_EN);
            Date before = addHours(current, -1);
            Date after = addHours(current, 1);
            objects.add(getHour(before));
            objects.add(getHour(current));
            objects.add(getHour(after));
            result.put(current, objects);
        }
        return result;
    }
}
screen-job/src/main/java/com/moral/api/entity/Forecast.java
New file
@@ -0,0 +1,45 @@
package com.moral.api.entity;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * <p>
 * 预测小时数据
 * </p>
 *
 * @author moral
 * @since 2021-12-31
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class Forecast extends Model<Forecast> {
    private static final long serialVersionUID = 1L;
    /**
     * 城市id
     */
    private Integer cityCode;
    /**
     * 时间
     */
    private Date time;
    /**
     * 数据
     */
    private String value;
    @Override
    protected Serializable pkVal() {
        return null;
    }
}
screen-job/src/main/java/com/moral/api/mapper/ForecastMapper.java
New file
@@ -0,0 +1,16 @@
package com.moral.api.mapper;
import com.moral.api.entity.Forecast;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * <p>
 * 预测小时数据 Mapper 接口
 * </p>
 *
 * @author moral
 * @since 2021-12-31
 */
public interface ForecastMapper extends BaseMapper<Forecast> {
}
screen-job/src/main/java/com/moral/api/service/ForecastService.java
New file
@@ -0,0 +1,18 @@
package com.moral.api.service;
import com.moral.api.entity.Forecast;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * 预测小时数据 服务类
 * </p>
 *
 * @author moral
 * @since 2021-12-31
 */
public interface ForecastService extends IService<Forecast> {
    void forecastO3();
}
screen-job/src/main/java/com/moral/api/service/impl/ForecastServiceImpl.java
New file
@@ -0,0 +1,245 @@
package com.moral.api.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.moral.api.entity.CityAqi;
import com.moral.api.entity.CityConfigWeatherForecast;
import com.moral.api.entity.CityWeather;
import com.moral.api.entity.CityWeatherForecast;
import com.moral.api.entity.Forecast;
import com.moral.api.mapper.ForecastMapper;
import com.moral.api.service.CityAqiService;
import com.moral.api.service.CityConfigWeatherForecastService;
import com.moral.api.service.CityWeatherForecastService;
import com.moral.api.service.CityWeatherService;
import com.moral.api.service.ForecastService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.moral.constant.Constants;
import com.moral.util.AmendUtils;
import com.moral.util.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.OptionalDouble;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.Stream;
/**
 * <p>
 * 预测小时数据 服务实现类
 * </p>
 *
 * @author moral
 * @since 2021-12-31
 */
@Service
public class ForecastServiceImpl extends ServiceImpl<ForecastMapper, Forecast> implements ForecastService {
    @Autowired
    private CityWeatherForecastService cityWeatherForecastService;
    @Autowired
    private CityConfigWeatherForecastService cityConfigWeatherForecastService;
    @Autowired
    private CityWeatherService cityWeatherService;
    @Autowired
    private CityAqiService cityAqiService;
    @Autowired
    private ForecastService forecastService;
    private static Map<String, Integer> weatherScore = new HashMap<>();
    static {
        weatherScore.put("晴", 100);
        weatherScore.put("雾", 90);
        weatherScore.put("多云", 80);
        weatherScore.put("霾", 70);
        weatherScore.put("阴", 60);
        weatherScore.put("扬沙", 60);
        weatherScore.put("浮尘", 60);
        weatherScore.put("阵雨", 45);
        weatherScore.put("雷阵雨", 40);
        weatherScore.put("雷阵雨转小雨", 30);
        weatherScore.put("阵雨转小雨", 30);
        weatherScore.put("小雨", 20);
        weatherScore.put("雨", 10);
        weatherScore.put("雷阵雨转中雨", 5);
        weatherScore.put("雷阵雨转大雨", 4);
        weatherScore.put("中雨", 0);
        weatherScore.put("大雨", 0);
        weatherScore.put("暴雨", 0);
        weatherScore.put("小雪", 0);
        weatherScore.put("中雪", 0);
        weatherScore.put("大雪", 0);
        weatherScore.put("暴雪", 0);
        weatherScore.put("雨夹雪", 0);
    }
    @Override
    public void forecastO3() {
        Date nextDay = DateUtils.addDays(new Date(), 1);
        String nextTime = DateUtils.dateToDateString(nextDay, DateUtils.yyyy_MM_dd_EN);
        //根据最近一个月历史数据来预测
        Date start = DateUtils.addMonths(DateUtils.getDate(nextTime), -1);
        //获取要预测的城市
        QueryWrapper<CityConfigWeatherForecast> cityConfigWeatherForecastQueryWrapper = new QueryWrapper<>();
        cityConfigWeatherForecastQueryWrapper.select("city_code")
                .eq("is_delete", Constants.NOT_DELETE);
        List<Object> cityCodes = cityConfigWeatherForecastService.listObjs(cityConfigWeatherForecastQueryWrapper);
        //获取预测日逐小时气象数据
        QueryWrapper<CityWeatherForecast> cityWeatherForecastQueryWrapper = new QueryWrapper<>();
        cityWeatherForecastQueryWrapper.likeRight("time", nextTime)
                .in("city_code", cityCodes);
        List<CityWeatherForecast> forecasts = cityWeatherForecastService.list(cityWeatherForecastQueryWrapper);
        Map<Integer, List<CityWeatherForecast>> cityForecast = forecasts.stream()
                .collect(Collectors.groupingBy(CityWeatherForecast::getCityCode));
        //获取近两个月历史气象数据
        QueryWrapper<CityWeather> cityWeatherQueryWrapper = new QueryWrapper<>();
        cityWeatherQueryWrapper.ge("time", start)
                .in("city_code", cityCodes);
        List<CityWeather> historyWeather = cityWeatherService.list(cityWeatherQueryWrapper);
        Map<Integer, List<CityWeather>> cityHistoryWeather = historyWeather.stream()
                .collect(Collectors.groupingBy(CityWeather::getCityCode));
        //获取近两个月历史aqi数据
        QueryWrapper<CityAqi> cityAqiQueryWrapper = new QueryWrapper<>();
        cityAqiQueryWrapper.ge("time", start)
                .in("city_code", cityCodes);
        List<CityAqi> historyAqi = cityAqiService.list(cityAqiQueryWrapper);
        Map<Integer, List<CityAqi>> cityHistoryAqi = historyAqi.stream()
                .collect(Collectors.groupingBy(CityAqi::getCityCode));
        //获取前后一小时map
        Map<Date, List<Integer>> hours = DateUtils.getBeforeAndAfterHourDate(nextDay);
        Forecast forecast = new Forecast();
        Map<String, Object> forecastMap = new HashMap<>();
        for (Object obj : cityCodes) {
            Integer cityCode = Integer.parseInt(obj.toString());
            forecast.setCityCode(cityCode);
            //预测
            List<CityWeatherForecast> cityWeatherForecasts = cityForecast.get(cityCode);
            //获取城市历史气象数据
            List<CityWeather> cityWeathers = cityHistoryWeather.get(cityCode);
            //获取城市历史aqi数据
            List<CityAqi> cityAqis = cityHistoryAqi.get(cityCode);
            Map<Date, List<CityAqi>> cityAqiMap = cityAqis.stream()
                    .collect(Collectors.groupingBy(CityAqi::getTime));
            Map<Date, List<Map<String, Object>>> cityHistoryMap = new HashMap<>();
            for (Map.Entry<Date, List<Integer>> dateListEntry : hours.entrySet()) {
                Date date = dateListEntry.getKey();
                List<Integer> hourList = dateListEntry.getValue();
                List<Map<String, Object>> hourWeatherData = new ArrayList<>();
                for (CityWeather cityWeather : cityWeathers) {
                    Date cityWeatherTime = cityWeather.getTime();
                    int dataHour = DateUtils.getHour(cityWeatherTime);
                    if (hourList.contains(dataHour)) {
                        Map<String, Object> valueMap = JSONObject.parseObject(cityWeather.getValue(), Map.class);
                        List<CityAqi> o = cityAqiMap.get(cityWeatherTime);
                        if (!ObjectUtils.isEmpty(o)) {
                            String value = o.get(0).getValue();
                            Map<String, Object> aqiMap = JSONObject.parseObject(value, Map.class);
                            valueMap.put("O3", aqiMap.get("O3"));
                            hourWeatherData.add(valueMap);
                        }
                    }
                }
                cityHistoryMap.put(date, hourWeatherData);
            }
            for (CityWeatherForecast cityWeatherForecast : cityWeatherForecasts) {
                Date time = cityWeatherForecast.getTime();
                Map<String, Object> value = JSONObject.parseObject(cityWeatherForecast.getValue(), Map.class);
                String weather = value.get("text").toString();
                Integer forecastScore = weatherScore.get(weather);
                int min;
                if (forecastScore >= 80) {
                    min = 80;
                } else if (forecastScore >= 40) {
                    min = 40;
                } else {
                    min = 0;
                }
                //气象
                List<Map<String, Object>> weatherMaps = cityHistoryMap.get(time);
                //根据天气分数筛选
                weatherMaps.removeIf(o -> {
                    Integer historyScore = weatherScore.get(o.get("text").toString());
                    return historyScore < min;
                });
                int size = weatherMaps.size();
                Double tempAvg = calculateAvg(weatherMaps, "temp");
                Double o3Avg = calculateAvg(weatherMaps, "O3");
                Double sum1 = calculateProduct(weatherMaps, "temp", "O3");
                Double sum2 = calculateProduct(weatherMaps, "temp", "temp");
                double b = (sum1 - size * tempAvg * o3Avg) / (sum2 - size * tempAvg * tempAvg);
                double a = o3Avg - b * tempAvg;
                double tempForecast = Double.parseDouble(value.get("temp").toString());
                double o3Forecast = b * tempForecast + a;
                forecast.setTime(cityWeatherForecast.getTime());
                if (!Double.isNaN(o3Forecast)) {
                    forecastMap.put("O3", AmendUtils.sciCal(o3Forecast, 0));
                    forecast.setValue(JSONObject.toJSONString(forecastMap));
                    forecastService.save(forecast);
                }
            }
        }
    }
    private Double calculateAvg(List<Map<String, Object>> list, String param) {
        Supplier<Stream<Map<String, Object>>> supplier = list::stream;
        DoubleStream doubleStream = supplier.get()
                .flatMapToDouble(value -> {
                    String sensorValue = value.get(param).toString();
                    double paramValue = Double.parseDouble(sensorValue);
                    return DoubleStream.of(paramValue);
                });
        Double result = null;
        OptionalDouble optionalDouble = doubleStream.average();
        if (optionalDouble.isPresent()) {
            result = optionalDouble.getAsDouble();
        }
        return result;
    }
    private Double calculateProduct(List<Map<String, Object>> list, String param1, String param2) {
        Supplier<Stream<Map<String, Object>>> supplier = list::stream;
        DoubleStream doubleStream = supplier.get()
                .flatMapToDouble(value -> {
                    String sensorValue1 = value.get(param1).toString();
                    String sensorValue2 = value.get(param2).toString();
                    double paramValue1 = Double.parseDouble(sensorValue1);
                    double paramValue2 = Double.parseDouble(sensorValue2);
                    return DoubleStream.of(paramValue1 * paramValue2);
                });
        return doubleStream.sum();
    }
}
screen-job/src/main/java/com/moral/api/task/ForecastTask.java
New file
@@ -0,0 +1,27 @@
package com.moral.api.task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.moral.api.service.ForecastService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
@Component
public class ForecastTask {
    @Autowired
    private ForecastService forecastService;
    @XxlJob("forecastO3")
    public ReturnT forecastO3() {
        try {
            forecastService.forecastO3();
        } catch (Exception e) {
            XxlJobHelper.log(e.getMessage());
            return new ReturnT(ReturnT.FAIL_CODE, e.getMessage());
        }
        return ReturnT.SUCCESS;
    }
}
screen-job/src/main/resources/mapper/ForecastMapper.xml
New file
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.moral.api.mapper.ForecastMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.moral.api.entity.Forecast">
        <result column="city_code" property="cityCode"/>
        <result column="time" property="time"/>
        <result column="value" property="value"/>
    </resultMap>
</mapper>