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 com.moral.api.vo.*;
import org.apache.commons.collections4.CollectionUtils;
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;
/**
*
* 雷达小时汇总表 服务实现类
*
*
* @author moral
* @since 2026-09-08
*/
@Service
public class RadarHourlySummaryServiceImpl extends ServiceImpl 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");
private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@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 ComparisonResponse getComparison(String location, String time) {
// 入参时间 → 前一个整点 = current 时刻
LocalDateTime inputTime = LocalDateTime.parse(time, DT_FMT);
LocalDateTime prevHour = inputTime.minusHours(1).withMinute(0).withSecond(0).withNano(0);
String currentTime = prevHour.format(DT_FMT);
// 30 天范围:currentTime(不含)往前推 30 天
String endStr = currentTime;
String startStr = prevHour.minusDays(30).format(DT_FMT);
ComparisonResponse resp = new ComparisonResponse();
resp.setLocation(location);
resp.setTime(time);
// current: 前一个整点的 a34004_drop_height
RadarHourlySummaryVO cur = this.baseMapper.getByTime(currentTime);
resp.setCurrent(cur != null && cur.getA34004DropHeight() != null ? cur.getA34004DropHeight().doubleValue() : 0.0);
// avg30: 720 小时平均
Double avg = this.baseMapper.getAvgDropHeightInRange(startStr, endStr);
resp.setAvg30(avg != null ? avg : 0.0);
// daily30: 按天分组,不含今天
List rows = this.baseMapper.getDailyAvgDropHeight(startStr, endStr);
if(CollectionUtils.isEmpty(rows)){
List daily = new ArrayList<>();
String todayStr = prevHour.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
DailyDropHeightVO dropHeightVO = new DailyDropHeightVO();
dropHeightVO.setDate(todayStr);
dropHeightVO.setDropHeight(0d);
daily.add(dropHeightVO);
resp.setDaily30(daily);
}else {
resp.setDaily30(rows);
}
return resp;
}
@Override
public HourlyResponse getHourly5Days(String location, String startTime, String endTime) {
HourlyResponse resp = new HourlyResponse();
resp.setLocation(location);
resp.setStartTime(startTime);
resp.setEndTime(endTime);
List list = this.baseMapper.queryHourlyByRange(startTime, endTime);
List items = new ArrayList<>();
for (RadarHourlySummaryVO s : list) {
HourlyItemVO item = new HourlyItemVO();
item.setTime(s.getTimeStr());
item.setA34004DropHeight(s.getA34004DropHeight().doubleValue());
items.add(item);
}
resp.setCount(items.size());
resp.setHourly(items);
return resp;
}
@Override
public void processHour(String hourStart) {
String hourEnd = nextHour(hourStart);
List rawList = radarService.queryRawDataByHour(hourStart, hourEnd);
if (rawList.isEmpty()) {
return ;
}
// 提取 a34004 / a34002
List a34004List = new ArrayList<>();
List 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 rawList) {
List 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 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; }
}
}