cjl
13 hours ago bcc076d63c5c2ff473adb5ac15ea242559086000
fix:雷达调整
11 files added
6 files modified
675 ■■■■■ changed files
screen-api/src/main/java/com/moral/api/service/impl/HistorySecondRadarServiceImpl.java 68 ●●●●● patch | view | raw | blame | history
screen-api/src/main/resources/mapper/HistorySecondRadarMapper.xml 5 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/controller/RadarProcessController.java 71 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/entity/HistorySecondRadar.java 66 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/entity/RadarHourlySummary.java 67 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/mapper/HistorySecondRadarMapper.java 24 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/mapper/RadarHourlySummaryMapper.java 24 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/service/HistorySecondRadarService.java 24 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/service/RadarHourlySummaryService.java 27 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/service/impl/HistoryHourlyServiceImpl.java 4 ●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/service/impl/HistorySecondCruiserServiceImpl.java 22 ●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/service/impl/HistorySecondRadarServiceImpl.java 28 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/service/impl/RadarHourlySummaryServiceImpl.java 190 ●●●●● patch | view | raw | blame | history
screen-job/src/main/java/com/moral/api/task/WeatherTask.java 15 ●●●●● patch | view | raw | blame | history
screen-job/src/main/resources/mapper/HistorySecondCruiserMapper.xml 2 ●●● patch | view | raw | blame | history
screen-job/src/main/resources/mapper/HistorySecondRadarMapper.xml 21 ●●●●● patch | view | raw | blame | history
screen-job/src/main/resources/mapper/RadarHourlySummaryMapper.xml 17 ●●●●● patch | view | raw | blame | history
screen-api/src/main/java/com/moral/api/service/impl/HistorySecondRadarServiceImpl.java
@@ -31,8 +31,23 @@
    public RadarListVo radarList(RadarListCond cond) {
        RadarListVo radarListVo = new RadarListVo();
        List<String> time = new ArrayList<>();
        List<HistorySecondRadarListVo> list = this.baseMapper.listRadar(cond);
        List<List<double[]>> pm25 = new ArrayList<>();
        List<List<double[]>> pm10 = new ArrayList<>();
        List<String> highList = buildHighList(cond.getHigh2());
        List<String> highList1 = buildHighList(cond.getHigh1());
        List<HistorySecondRadarListVo> list = this.baseMapper.listRadar(cond);
        for (HistorySecondRadarListVo row : list) {
            time.add(row.getTimeStr());
            double[] pm25Values = parseValues(row.getRadarData(), "a34004-Avg");
            double[] pm10Values = parseValues(row.getRadarData(), "a34002-Avg");
            pm25.add(buildPointList(pm25Values, highList.size(),highList1.size()));
            pm10.add(buildPointList(pm10Values, highList.size(),highList1.size()));
        }
        radarListVo.setPm25(pm25);
        radarListVo.setTime(time);
        radarListVo.setPm10(pm10);
       /* List<List<double[]>> pm25 = new ArrayList<>();
        List<List<double[]>> pm10 = new ArrayList<>();
        for(HistorySecondRadarListVo ot : list){
            pm10.add(convertSingleRecordToJson(ot.getRadarDataPm10(),cond.getHigh1(),cond.getHigh2()));
@@ -41,10 +56,59 @@
        }
        radarListVo.setPm25(pm25);
        radarListVo.setTime(time);
        radarListVo.setPm10(pm10);
        radarListVo.setPm10(pm10);*/
        return radarListVo;
    }
    private static List<double[]> buildPointList(double[] values, int pointCount, int minPointCount) {
        List<double[]> pointList = new ArrayList<double[]>(pointCount);
        int count = Math.min(pointCount, values.length);
        int minCount = Math.min(minPointCount, values.length);
        for (int i = minCount; i < count; i++) {
            pointList.add(new double[]{values[i]});
        }
        return pointList;
    }
    private static List<String> buildHighList(int MAX_HEIGHT) {
        List<String> highList = new ArrayList<String>();
        int count = (int) (MAX_HEIGHT / HEIGHT_INTERVAL) + 1;
        for (int i = 0; i < count; i++) {
            highList.add(formatHeight(i * HEIGHT_INTERVAL));
        }
        return highList;
    }
    private static double[] parseValues(String json, String key) {
        if (json == null) {
            return new double[0];
        }
        String keyToken = "\"" + key + "\":";
        int keyIndex = json.indexOf(keyToken);
        if (keyIndex < 0) {
            return new double[0];
        }
        int bracketStart = json.indexOf('[', keyIndex + keyToken.length());
        int bracketEnd = json.indexOf(']', bracketStart + 1);
        if (bracketStart < 0 || bracketEnd < 0 || bracketEnd - bracketStart <= 1) {
            return new double[0];
        }
        String body = json.substring(bracketStart + 1, bracketEnd).trim();
        if (body.isEmpty()) {
            return new double[0];
        }
        String[] parts = body.split(",");
        double[] values = new double[parts.length];
        for (int i = 0; i < parts.length; i++) {
            values[i] = Double.parseDouble(parts[i].trim());
        }
        return values;
    }
    private static String formatHeight(double height) {
        return BigDecimal.valueOf(height).stripTrailingZeros().toPlainString();
    }
    private List<double[]> convertSingleRecordToJson(String rawData,int high1,int high2){
        if (rawData == null || rawData.isEmpty()) {
            return new ArrayList<>();
screen-api/src/main/resources/mapper/HistorySecondRadarMapper.xml
@@ -15,9 +15,8 @@
        </resultMap>
    <select id="listRadar" resultType="com.moral.api.pojo.vo.radar.HistorySecondRadarListVo">
        select radar_time,pm25,high_pm25,pm10,high_pm10,boundary_layer,DATE_FORMAT(time, '%H:%i:%s') as timeStr,radar_data->>'$."a34002-Avg"' as radarDataPm10,
        radar_data->>'$."a34004-Avg"' as radarDataPm25
        from history_second_radar
        select DATE_FORMAT(time, '%H:%i:%s') as timeStr, radar_data
        from radar_hourly_summary
        where 1 =1
        <if test="cond.startTime != null and cond.startTime != '' ">
            and time <![CDATA[>=]]> #{cond.startTime}
screen-job/src/main/java/com/moral/api/controller/RadarProcessController.java
New file
@@ -0,0 +1,71 @@
package com.moral.api.controller;
import com.moral.api.service.RadarHourlySummaryService;
import com.moral.constant.ResultMessage;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * @ClassName RadarProcessController
 * @Description TODO
 * @Author @cjl
 * @Date 2026-09-08 14:12
 * @Version 1.0
 */
@Slf4j
@Api(tags = {"雷达"})
@RestController
@RequestMapping("/radar")
public class RadarProcessController {
    @Autowired
    private RadarHourlySummaryService hourlySummaryService;
    /**
     * 处理上一完整小时
     * GET /radar/process-last
     */
    @GetMapping("/process-last")
    public ResultMessage processLastHour() {
        hourlySummaryService.processLastHour();
        return new ResultMessage();
    }
    /**
     * 处理指定小时
     * GET /radar/process?hour=2026-09-08 09:00:00
     */
    @GetMapping("/process")
    public ResultMessage processHour() {
        String startStr = "2026-05-31 23:00:00";
        String endStr = "2026-09-08 14:00:00";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime startTime = LocalDateTime.parse(startStr, formatter);
        LocalDateTime endTime = LocalDateTime.parse(endStr, formatter);
        LocalDateTime current = startTime;
        while (!current.isAfter(endTime)) {
            // 将当前时间格式化为字符串并加入列表
            hourlySummaryService.processHour(current.format(formatter));
            // 时间增加 1 小时
            current = current.plusHours(1);
        }
        return new ResultMessage();
    }
    @GetMapping("/process1")
    public ResultMessage processHour1() {
        String startStr = "2026-09-01 00:00:00";
        hourlySummaryService.processHour(startStr);
        return new ResultMessage();
    }
}
screen-job/src/main/java/com/moral/api/entity/HistorySecondRadar.java
New file
@@ -0,0 +1,66 @@
package com.moral.api.entity;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * <p>
 *
 * </p>
 *
 * @author moral
 * @since 2026-09-08
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class HistorySecondRadar extends Model<HistorySecondRadar> {
    private static final long serialVersionUID = 1L;
    /**
     * 时间
     */
    private LocalDateTime radarTime;
    /**
     * 数据
     */
    private String radarData;
    /**
     * pm2.5最高值
     */
    private String pm25;
    /**
     * pm2.5最高高度
     */
    private String highPm25;
    /**
     * pm10最高
     */
    private String pm10;
    /**
     * pm10高度
     */
    private String highPm10;
    /**
     * 边界层
     */
    private String boundaryLayer;
    private LocalDateTime time;
    @Override
    protected Serializable pkVal() {
        return null;
    }
}
screen-job/src/main/java/com/moral/api/entity/RadarHourlySummary.java
New file
@@ -0,0 +1,67 @@
package com.moral.api.entity;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * <p>
 * 雷达小时汇总表
 * </p>
 *
 * @author moral
 * @since 2026-09-08
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class RadarHourlySummary extends Model<RadarHourlySummary> {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 小时时间,精确到小时
     */
    private LocalDateTime time;
    /**
     * 小时平均数据 {"a34004-Avg": [...], "a34002-Avg": [...]}
     */
    private String radarData;
    /**
     * a34004-Avg 最高值所在高度(m)
     */
    private BigDecimal a34004PeakHeight;
    /**
     * a34004-Avg 突变下降高度(m)(边界层)
     */
    private BigDecimal a34004DropHeight;
    /**
     * a34002-Avg 最高值所在高度(m)
     */
    private BigDecimal a34002PeakHeight;
    /**
     * a34002-Avg 突变下降高度(m)(边界层)
     */
    private BigDecimal a34002DropHeight;
    private LocalDateTime createTime;
    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}
screen-job/src/main/java/com/moral/api/mapper/HistorySecondRadarMapper.java
New file
@@ -0,0 +1,24 @@
package com.moral.api.mapper;
import com.moral.api.entity.HistorySecondRadar;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author moral
 * @since 2026-09-08
 */
public interface HistorySecondRadarMapper extends BaseMapper<HistorySecondRadar> {
    /**
     * 查询指定小时内的原始雷达数据
     */
    List<String> queryRawDataByHour(@Param("start") String start, @Param("end") String end);
}
screen-job/src/main/java/com/moral/api/mapper/RadarHourlySummaryMapper.java
New file
@@ -0,0 +1,24 @@
package com.moral.api.mapper;
import com.moral.api.entity.RadarHourlySummary;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * <p>
 * 雷达小时汇总表 Mapper 接口
 * </p>
 *
 * @author moral
 * @since 2026-09-08
 */
public interface RadarHourlySummaryMapper extends BaseMapper<RadarHourlySummary> {
    /**
     * 查询指定时间范围的汇总数据
     */
}
screen-job/src/main/java/com/moral/api/service/HistorySecondRadarService.java
New file
@@ -0,0 +1,24 @@
package com.moral.api.service;
import com.moral.api.entity.HistorySecondRadar;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * <p>
 *  服务类
 * </p>
 *
 * @author moral
 * @since 2026-09-08
 */
public interface HistorySecondRadarService extends IService<HistorySecondRadar> {
    /**
     * 查询指定小时内的原始雷达数据
     */
    List<String> queryRawDataByHour(String start,String end);
}
screen-job/src/main/java/com/moral/api/service/RadarHourlySummaryService.java
New file
@@ -0,0 +1,27 @@
package com.moral.api.service;
import com.moral.api.entity.RadarHourlySummary;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * 雷达小时汇总表 服务类
 * </p>
 *
 * @author moral
 * @since 2026-09-08
 */
public interface RadarHourlySummaryService extends IService<RadarHourlySummary> {
    /**
     * 处理指定小时的数据
     * @return 处理结果摘要
     */
    void processHour(String hourStart);
    /**
     * 处理上一完整小时(自动计算)
     */
    void processLastHour();
}
screen-job/src/main/java/com/moral/api/service/impl/HistoryHourlyServiceImpl.java
@@ -807,8 +807,8 @@
    }
    public static void main(String[] args) {
        String result = "QN=20231019000100001;ST=31;CN=2051;PW=123456;MN=P5DND7A0392082;Flag=4;CP=&&DataTime=20231019000100;a01001-Avg=25.0,a01001-Flag=N;a01002-Avg=61.0,a01002-Flag=N;a34004-Avg=19.3,a34004-Flag=N;a34002-Avg=38.5,a34002-Flag=N;a34001-Avg=41.2,a34001-Flag=N;a01007-Avg=0.00,a01007-Flag=N;a01008-Avg=137.53,a01008-Flag=N;a01006-Avg=4255.774,a01006-Flag=T;a00e13-Avg=39.2,a00e13-Flag=N&&";
        //String result = "QN=20231019000100001;ST=31;CN=2051;PW=123456;MN=P5DND7A0392082;Flag=4;CP=&&DataTime=20231019000100;a01001-Avg=25.0,a01001-Flag=N;a01002-Avg=61.0,a01002-Flag=N;a34004-Avg=19.3,a34004-Flag=N;a34002-Avg=38.5,a34002-Flag=N;a34001-Avg=41.2,a34001-Flag=N;a01007-Avg=0.00,a01007-Flag=N;a01008-Avg=137.53,a01008-Flag=N;a01006-Avg=4255.774,a01006-Flag=T;a00e13-Avg=39.2,a00e13-Flag=N&&";
        String result = "QN=20190708102334000;ST=91;CN=9014;PW=3546D0E65659D92744234D6D;MN=1100000120001;Flag=4;CP=&&ExeRtn=1&&";
        String s = Crc16Utils.padLeftTest(String.valueOf(result.length()),4,null);
        String s1 = Crc16Utils.crc16_2017(result, result.length());
        System.out.println("##"+s+result+s1+"\r\n");
screen-job/src/main/java/com/moral/api/service/impl/HistorySecondCruiserServiceImpl.java
@@ -63,25 +63,31 @@
        //
        ArrayList<SecondCruiserSort> list1 = new ArrayList<>();
        if (!ObjectUtils.isEmpty(result)){
            for (HistorySecondCruiser historySecondCruiser : result) {
                String mac = historySecondCruiser.getMac();
                Integer organizationId = historySecondCruiser.getOrganizationId();
                List<Integer> orgList = organizationMapper.orgIdSpecialDevList(organizationId, mac);
                if(CollectionUtils.isEmpty(orgList)){
                    return;
                    continue;
                }
                HashMap<String, Object> params = new HashMap<>();
                params.put("mac",mac);
                params.put("startTime",startTime);
                params.put("endTime",endTime);
                List<Map<String, Object>> dusts = historySecondCruiserMapper.getDusts(params);
                if (ObjectUtils.isEmpty(dusts)){
                    continue;
                }
                Map<String, List<DustldDTO>> collect = manageCoordinateDetailMapper.CompareTo(orgList.get(0)).stream().collect(Collectors.groupingBy(o -> o.getName()));
                if (ObjectUtils.isEmpty(dusts) || ObjectUtils.isEmpty(collect)){
                    return;
                if (ObjectUtils.isEmpty(collect)){
                    continue;
                }
                Set<String> strings = collect.keySet();
                int i = 1;
                for (String string : strings) {
                    log.info("当前记录>>>>>>>>>  "+i+"/"+strings.size()+"    >>>>>>>>    "+string+"      >>>>>>");
                    i++;
                    SecondCruiserSort secondCruiserSort = new SecondCruiserSort();
                    ArrayList<Double> doubleArrayList = new ArrayList<>();
                    List<DustldDTO> dustldDTOS = collect.get(string);
@@ -93,7 +99,9 @@
                        }
                        double latDouble1 = Double.parseDouble(flyLat);
                        double lonDouble1 = Double.parseDouble(flyLon);
                        for (Map<String, Object> dust : dusts) {
                        //for (Map<String, Object> dust : dusts) {
                       for( int ii=0;ii<dusts.size();ii++){
                           Map<String, Object> dust = dusts.get(ii);
                            String flyLat1 = Objects.nonNull(dust.get("flyLat")) ? dust.get("flyLat").toString() :"0";
                            String flyLon1 = Objects.nonNull(dust.get("flyLon")) ? dust.get("flyLon").toString() :"0";
                            double latDouble = Double.parseDouble(flyLat1);
@@ -103,6 +111,8 @@
                                Double dustld = Objects.nonNull(dust.get("dustld"))?Double.parseDouble(dust.get("dustld").toString()):0d;
                                doubleArrayList.add(dustld);
//                                break;
                                dusts.remove(ii);
                                ii--;
                            }
                        }
                    }
@@ -121,8 +131,8 @@
            }
        }
        if (!ObjectUtils.isEmpty(list1)){
//            log.info(list1.size()+"");
            secondCruiserSortMapper.insertAll(list1);
            log.info("新增路段排名>>>>>>>> "+list1.size()+" >>>>>>>>>条");
        }
    }
screen-job/src/main/java/com/moral/api/service/impl/HistorySecondRadarServiceImpl.java
New file
@@ -0,0 +1,28 @@
package com.moral.api.service.impl;
import com.moral.api.entity.HistorySecondRadar;
import com.moral.api.mapper.HistorySecondRadarMapper;
import com.moral.api.service.HistorySecondRadarService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author moral
 * @since 2026-09-08
 */
@Service
public class HistorySecondRadarServiceImpl extends ServiceImpl<HistorySecondRadarMapper, HistorySecondRadar> implements HistorySecondRadarService {
    @Override
    public List<String> queryRawDataByHour(String start, String end) {
        List<String>  list =  this.baseMapper.queryRawDataByHour(start, end);
        return list;
    }
}
screen-job/src/main/java/com/moral/api/service/impl/RadarHourlySummaryServiceImpl.java
New file
@@ -0,0 +1,190 @@
package com.moral.api.service.impl;
import com.moral.api.entity.RadarHourlySummary;
import com.moral.api.mapper.RadarHourlySummaryMapper;
import com.moral.api.service.HistorySecondRadarService;
import com.moral.api.service.RadarHourlySummaryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
 * <p>
 * 雷达小时汇总表 服务实现类
 * </p>
 *
 * @author moral
 * @since 2026-09-08
 */
@Service
public class RadarHourlySummaryServiceImpl extends ServiceImpl<RadarHourlySummaryMapper, RadarHourlySummary> implements RadarHourlySummaryService {
    @Autowired
    private HistorySecondRadarService radarService;
    private static final double HEIGHT_STEP = 7.5;
    private static final int PEAK_SEARCH_LIMIT = 200;
    private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00:00");
    @Override
    public void processLastHour() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime prev = now.minusHours(1).withMinute(0).withSecond(0).withNano(0);
        processHour(prev.format(FMT));
    }
    @Override
    public void processHour(String hourStart) {
        String hourEnd = nextHour(hourStart);
        List<String> rawList = radarService.queryRawDataByHour(hourStart, hourEnd);
        if (rawList.isEmpty()) {
            return ;
        }
        // 提取 a34004 / a34002
        List<String> a34004List = new ArrayList<>();
        List<String> a34002List = new ArrayList<>();
        for (String json : rawList) {
            a34004List.add(extractJsonField(json, "a34004-Avg"));
            a34002List.add(extractJsonField(json, "a34002-Avg"));
        }
        double[] avg04 = averageByHeight(a34004List);
        double[] avg02 = averageByHeight(a34002List);
        R r04 = analyze(avg04);
        R r02 = analyze(avg02);
        RadarHourlySummary entity = new RadarHourlySummary();
        entity.setTime(LocalDateTime.parse(hourStart, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        entity.setRadarData(buildRadarJson(avg04, avg02));
        entity.setA34004PeakHeight(BigDecimal.valueOf(r04.peak));
        entity.setA34004DropHeight(BigDecimal.valueOf(r04.drop));
        entity.setA34002PeakHeight(BigDecimal.valueOf(r02.peak));
        entity.setA34002DropHeight(BigDecimal.valueOf(r02.drop));
        this.baseMapper.insert(entity);
    }
// ======================== 算法 ========================
    private double[] averageByHeight(List<String> rawList) {
        List<double[]> rows = new ArrayList<>();
        int minLen = Integer.MAX_VALUE;
        for (String s : rawList) {
            double[] arr = parseToArray(s);
            rows.add(arr);
            if (arr.length < minLen) minLen = arr.length;
        }
        double[] avg = new double[minLen];
        int n = rows.size();
        for (int i = 0; i < minLen; i++) {
            double sum = 0;
            for (double[] row : rows) sum += row[i];
            BigDecimal bd = new BigDecimal(String.valueOf(sum / n));
            BigDecimal resultBd = bd.setScale(2, RoundingMode.HALF_UP);
            avg[i] =resultBd.doubleValue();
        }
        return avg;
    }
    private R analyze(double[] values) {
        int searchEnd = Math.min(PEAK_SEARCH_LIMIT, values.length);
        int peakIdx = 0;
        double peakVal = values[0];
        for (int i = 1; i < searchEnd; i++) {
            if (values[i] > peakVal) { peakVal = values[i]; peakIdx = i; }
        }
        double peakHeight = peakIdx * HEIGHT_STEP;
        System.out.println("最大值 》》》》》》》"+values[peakIdx]+"  》》》》最大高度>>>>>"+peakHeight+"  》》》》最大高度位置>>>>>"+peakIdx);
        int dropIdx = -1;
        if(PEAK_SEARCH_LIMIT-peakIdx == 1){
            searchEnd = searchEnd+60;
        }
        double base = findBaseValue(values, peakIdx, searchEnd);
        for (int i = peakIdx + 1; i < searchEnd; i++) {
            if (values[i] <= base * 1.05) { dropIdx = i; break; }
        }
        double dropHeight = dropIdx > 0 ? dropIdx * HEIGHT_STEP : 0;
        System.out.println("最小值 》》》》》》》"+values[dropIdx]+"  》》》》最小高度>>>>>"+dropHeight+"  米 》》》》最小高度位置>>>>>"+dropIdx);
        return new R(peakHeight, dropHeight);
    }
    private double findBaseValue(double[] values, int peakIdx, int end) {
        List<Double> tail = new ArrayList<>();
        for (int i = peakIdx + 1; i < end; i++) tail.add(values[i]);
        if (tail.isEmpty()) return 0;
        Collections.sort(tail);
        if (tail.size() >= 2) {
            double a = tail.get(0), b = tail.get(1);
            if (b - a > 2.0 || (a > 0 && b / a > 1.5)) return b;
        }
        return tail.get(0);
    }
    // ======================== 工具 ========================
    private String extractJsonField(String json, String field) {
        String key = "\"" + field + "\"";
        int s = json.indexOf(key);
        if (s == -1) return "";
        s = json.indexOf(":", s) + 1;
        while (s < json.length() && (json.charAt(s) == ' ' || json.charAt(s) == '"')) s++;
        int e = json.indexOf("\"", s);
        if (e == -1) e = json.indexOf(",", s);
        if (e == -1) e = json.indexOf("}", s);
        if (e == -1) return "";
        while (e > s && json.charAt(e - 1) == '"') e--;
        return json.substring(s, e);
    }
    private double[] parseToArray(String raw) {
        if (raw == null || raw.isEmpty()) return new double[0];
        String[] parts = raw.split("\\*");
        double[] arr = new double[parts.length];
        for (int i = 0; i < parts.length; i++) {
            try { arr[i] = Double.parseDouble(parts[i]); }
            catch (NumberFormatException e) { arr[i] = 0; }
        }
        return arr;
    }
    private String buildRadarJson(double[] a04, double[] a02) {
        StringBuilder sb = new StringBuilder("{\"a34004-Avg\":[");
        for (int i = 0; i < a04.length; i++) {
            if (i > 0) sb.append(",");
            sb.append(String.format("%.6f", a04[i]));
        }
        sb.append("],\"a34002-Avg\":[");
        for (int i = 0; i < a02.length; i++) {
            if (i > 0) sb.append(",");
            sb.append(String.format("%.6f", a02[i]));
        }
        sb.append("]}");
        return sb.toString();
    }
    private String nextHour(String timeStr) {
        LocalDateTime time = LocalDateTime.parse(timeStr, FMT);
        LocalDateTime nextTime = time.plusHours(1);
        return nextTime.format(FMT);
    }
    static class R {
        double peak, drop;
        R(double peak, double drop) { this.peak = peak; this.drop = drop; }
    }
}
screen-job/src/main/java/com/moral/api/task/WeatherTask.java
@@ -1,5 +1,6 @@
package com.moral.api.task;
import com.moral.api.service.RadarHourlySummaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -18,6 +19,9 @@
    @Autowired
    private CityWeatherForecastService cityWeatherForecastService;
    @Autowired
    private RadarHourlySummaryService hourlySummaryService;
    //城市逐小时气象实测数据insert
    @XxlJob("insertCityWeather")
    public ReturnT insertCityWeather() {
@@ -41,4 +45,15 @@
        }
        return ReturnT.SUCCESS;
    }
    @XxlJob("processLastHour")
    public ReturnT processLastHour() {
        try {
            hourlySummaryService.processLastHour();
        } catch (Exception e) {
            XxlJobHelper.log(e.getMessage());
            return new ReturnT(ReturnT.FAIL_CODE, e.getMessage());
        }
        return ReturnT.SUCCESS;
    }
}
screen-job/src/main/resources/mapper/HistorySecondCruiserMapper.xml
@@ -19,7 +19,7 @@
        FROM `history_second_cruiser`
        WHERE mac = #{mac}
          AND `time` <![CDATA[>=]]> #{startTime}
          AND `time` <![CDATA[<]]> #{endTime}
          AND `time` <![CDATA[<]]> #{endTime}   and  value ->>'$.flylat' is not null  and   value ->>'$.flylon' is not null
    </select>
    <select id="getResult" resultType="com.moral.api.entity.HistorySecondCruiser">
screen-job/src/main/resources/mapper/HistorySecondRadarMapper.xml
New file
@@ -0,0 +1,21 @@
<?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.HistorySecondRadarMapper">
        <!-- 通用查询映射结果 -->
        <resultMap id="BaseResultMap" type="com.moral.api.entity.HistorySecondRadar">
                    <result column="radar_time" property="radarTime" />
                    <result column="radar_data" property="radarData" />
                    <result column="pm25" property="pm25" />
                    <result column="high_pm25" property="highPm25" />
                    <result column="pm10" property="pm10" />
                    <result column="high_pm10" property="highPm10" />
                    <result column="boundary_layer" property="boundaryLayer" />
                    <result column="time" property="time" />
        </resultMap>
    <select id="queryRawDataByHour" resultType="java.lang.String">
        SELECT radar_data FROM history_second_radar
       WHERE time &gt;= #{start} AND time &lt; #{end} ORDER BY time ASC
    </select>
</mapper>
screen-job/src/main/resources/mapper/RadarHourlySummaryMapper.xml
New file
@@ -0,0 +1,17 @@
<?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.RadarHourlySummaryMapper">
        <!-- 通用查询映射结果 -->
        <resultMap id="BaseResultMap" type="com.moral.api.entity.RadarHourlySummary">
                    <id column="id" property="id" />
                    <result column="time" property="time" />
                    <result column="radar_data" property="radarData" />
                    <result column="a34004_peak_height" property="a34004PeakHeight" />
                    <result column="a34004_drop_height" property="a34004DropHeight" />
                    <result column="a34002_peak_height" property="a34002PeakHeight" />
                    <result column="a34002_drop_height" property="a34002DropHeight" />
                    <result column="create_time" property="createTime" />
        </resultMap>
</mapper>