package com.ruoyi.system.service.impl;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.util.*;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONObject;
|
import com.ruoyi.common.config.TiandituMapConfig;
|
import com.ruoyi.common.core.redis.RedisCache;
|
import com.ruoyi.common.utils.http.HttpUtils;
|
import com.ruoyi.system.domain.VehicleGps;
|
import com.ruoyi.system.domain.VehicleGpsSegmentMileage;
|
import com.ruoyi.system.domain.SysTask;
|
import com.ruoyi.system.domain.VehicleInfo;
|
import com.ruoyi.system.mapper.VehicleGpsMapper;
|
import com.ruoyi.system.mapper.VehicleGpsSegmentMileageMapper;
|
import com.ruoyi.system.mapper.SysTaskMapper;
|
import com.ruoyi.system.mapper.VehicleInfoMapper;
|
import com.ruoyi.system.service.IVehicleGpsSegmentMileageService;
|
import com.ruoyi.system.service.IVehicleMileageStatsService;
|
import com.ruoyi.system.service.ISysConfigService;
|
|
/**
|
* 车辆GPS分段里程Service业务层处理
|
*/
|
@Service
|
public class VehicleGpsSegmentMileageServiceImpl implements IVehicleGpsSegmentMileageService {
|
|
private static final Logger logger = LoggerFactory.getLogger(VehicleGpsSegmentMileageServiceImpl.class);
|
|
/** 地球半径(公里) */
|
private static final double EARTH_RADIUS_KM = 6371.0;
|
|
/** 天地图批量路径规划API */
|
private static final String TIANDITU_ROUTE_API = "http://api.tianditu.gov.cn/drive";
|
|
@Autowired
|
private VehicleGpsSegmentMileageMapper segmentMileageMapper;
|
|
@Autowired
|
private VehicleGpsMapper vehicleGpsMapper;
|
|
@Autowired
|
private SysTaskMapper sysTaskMapper;
|
|
@Autowired
|
private TiandituMapConfig tiandituMapConfig;
|
|
@Autowired
|
private ISysConfigService configService;
|
|
@Autowired
|
private RedisCache redisCache;
|
|
@Autowired
|
private IVehicleMileageStatsService mileageStatsService;
|
|
@Autowired
|
private VehicleInfoMapper vehicleInfoMapper;
|
|
@Override
|
public VehicleGpsSegmentMileage selectVehicleGpsSegmentMileageById(Long segmentId) {
|
return segmentMileageMapper.selectVehicleGpsSegmentMileageById(segmentId);
|
}
|
|
@Override
|
public List<VehicleGpsSegmentMileage> selectVehicleGpsSegmentMileageList(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
|
return segmentMileageMapper.selectVehicleGpsSegmentMileageList(vehicleGpsSegmentMileage);
|
}
|
|
@Override
|
public int insertVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
|
return segmentMileageMapper.insertVehicleGpsSegmentMileage(vehicleGpsSegmentMileage);
|
}
|
|
@Override
|
public int updateVehicleGpsSegmentMileage(VehicleGpsSegmentMileage vehicleGpsSegmentMileage) {
|
return segmentMileageMapper.updateVehicleGpsSegmentMileage(vehicleGpsSegmentMileage);
|
}
|
|
@Override
|
public int deleteVehicleGpsSegmentMileageByIds(Long[] segmentIds) {
|
return segmentMileageMapper.deleteVehicleGpsSegmentMileageByIds(segmentIds);
|
}
|
|
@Override
|
public int deleteVehicleGpsSegmentMileageById(Long segmentId) {
|
return segmentMileageMapper.deleteVehicleGpsSegmentMileageById(segmentId);
|
}
|
|
@Override
|
public int batchCalculateSegmentMileage(Date startTime, Date endTime) {
|
try {
|
// logger.info("开始批量计算GPS分段里程 - 时间范围: {} 到 {}", startTime, endTime);
|
|
// 查询在指定时间范围内有GPS数据的所有车辆(添加慢SQL监控)
|
long startQueryTime = System.currentTimeMillis();
|
List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime);
|
long queryTime = System.currentTimeMillis() - startQueryTime;
|
|
// 慢查询警告(超过1秒)
|
if (queryTime > 1000) {
|
logger.warn("查询活跃车辆ID耗时过长: {}ms, 开始时间: {}, 建议检查 tb_vehicle_gps 表的索引(需要 vehicle_id 和 collect_time 组合索引)",
|
queryTime, startTime);
|
}
|
logger.info("查询到 {} 辆活跃车辆,查询耗时: {}ms", vehicleIds != null ? vehicleIds.size() : 0, queryTime);
|
|
if (vehicleIds == null || vehicleIds.isEmpty()) {
|
logger.info("没有找到活跃车辆");
|
return 0;
|
}
|
|
logger.info("找到 {} 辆活跃车辆,开始逐辆计算...", vehicleIds.size());
|
|
int successCount = 0;
|
int failedCount = 0;
|
|
// 逐辆计算,包含错误处理和重试机制
|
for (int i = 0; i < vehicleIds.size(); i++) {
|
Long vehicleId = vehicleIds.get(i);
|
try {
|
// logger.info("正在处理车辆 {} ({}/{})", vehicleId, i + 1, vehicleIds.size());
|
|
int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
|
if (segmentCount > 0) {
|
successCount++;
|
// logger.info("车辆 {} 计算成功,生成 {} 个分段记录", vehicleId, segmentCount);
|
} else {
|
// logger.debug("车辆 {} 无有新的GPS分段数据", vehicleId);
|
}
|
} catch (Exception e) {
|
failedCount++;
|
logger.error("计算车辆 {} 的分段里程失败 ({}/{})", vehicleId, i + 1, vehicleIds.size(), e);
|
|
// 不中断整个批处理,继续处理下一辆车
|
}
|
|
// 每处理10辆车输出一次进度
|
if ((i + 1) % 10 == 0) {
|
logger.info("批量计算进度: {}/{}, 成功: {}, 失败: {}",
|
i + 1, vehicleIds.size(), successCount, failedCount);
|
}
|
}
|
|
logger.info("批量分段里程计算完成 - 时间范围: {} 到 {}, 总车辆数: {}, 成功: {}, 失败: {}",
|
startTime, endTime, vehicleIds.size(), successCount, failedCount);
|
return successCount;
|
|
} catch (Exception e) {
|
logger.error("批量计算分段里程失败 - 时间范围: {} 到 {}", startTime, endTime, e);
|
throw new RuntimeException("批量计算失败: " + e.getMessage(), e);
|
}
|
}
|
|
@Override
|
public int compensateCalculation(int lookbackDays) {
|
try {
|
// 计算时间范围(回溯指定天数)
|
Calendar cal = Calendar.getInstance();
|
Date endTime = cal.getTime();
|
cal.add(Calendar.DAY_OF_MONTH, -lookbackDays);
|
Date startTime = cal.getTime();
|
|
logger.info("开始补偿计算 - 回溯天数: {}, 时间范围: {} 到 {}", lookbackDays, startTime, endTime);
|
|
// 查询所有活跃车辆
|
List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(startTime);
|
|
if (vehicleIds == null || vehicleIds.isEmpty()) {
|
logger.info("没有找到活跃车辆");
|
return 0;
|
}
|
|
int successCount = 0;
|
int totalUncalculated = 0;
|
|
for (Long vehicleId : vehicleIds) {
|
try {
|
// 查询该车辆未被计算的GPS数据
|
List<VehicleGps> uncalculatedGps = vehicleGpsMapper.selectUncalculatedGps(vehicleId, startTime, endTime);
|
|
if (uncalculatedGps == null || uncalculatedGps.isEmpty()) {
|
logger.debug("车辆 {} 没有未计算的GPS数据", vehicleId);
|
continue;
|
}
|
|
totalUncalculated += uncalculatedGps.size();
|
logger.info("车辆 {} 发现 {} 个未计算的GPS点,开始补偿计算...",
|
vehicleId, uncalculatedGps.size());
|
|
// 获取未计算GPS数据的时间范围
|
Date uncalculatedStartTime = parseDateTime(uncalculatedGps.get(0).getCollectTime());
|
Date uncalculatedEndTime = parseDateTime(uncalculatedGps.get(uncalculatedGps.size() - 1).getCollectTime());
|
|
// 查找该时间段之前最后一个已处理的GPS坐标ID
|
Long lastCalculatedGpsId = segmentMileageMapper.selectLastCalculatedGpsId(vehicleId, uncalculatedStartTime);
|
|
if (lastCalculatedGpsId != null) {
|
logger.info("车辆 {} 找到最后一个已处理的GPS点ID: {},将与未处理数据一起计算", vehicleId, lastCalculatedGpsId);
|
|
// 将最后一个已处理的GPS点加入列表前面,作为前置点
|
VehicleGps lastCalculatedGps = vehicleGpsMapper.selectVehicleGpsById(lastCalculatedGpsId);
|
if (lastCalculatedGps != null) {
|
uncalculatedGps.add(0, lastCalculatedGps); // 插入到列表最前面
|
logger.info("已将GPS点 {} 作为前置点加入计算列表", lastCalculatedGpsId);
|
}
|
} else {
|
logger.info("车辆 {} 没有找到已处理的前置 GPS点,从第一个未处理点开始计算", vehicleId);
|
}
|
|
// 重新计算该车辆在该时间范围的分段里程
|
// 注意:这里会重新计算整个时间范围,确保边缘节点被正确处理
|
int segmentCount = calculateVehicleSegmentMileageWithGpsList(
|
vehicleId, uncalculatedGps, uncalculatedStartTime, uncalculatedEndTime);
|
|
if (segmentCount > 0) {
|
successCount++;
|
logger.info("车辆 {} 补偿计算完成,生成 {} 个分段记录", vehicleId, segmentCount);
|
}
|
} catch (Exception e) {
|
logger.error("车辆 {} 补偿计算失败", vehicleId, e);
|
}
|
}
|
|
logger.info("补偿计算完成 - 总车辆数: {}, 未计算GPS点数: {}, 成功车辆数: {}",
|
vehicleIds.size(), totalUncalculated, successCount);
|
return successCount;
|
|
} catch (Exception e) {
|
logger.error("补偿计算失败", e);
|
throw new RuntimeException("补偿计算失败: " + e.getMessage());
|
}
|
}
|
|
@Override
|
public int calculateVehicleSegmentMileage(Long vehicleId, Date startTime, Date endTime) {
|
try {
|
// 查询车辆在时间范围内的GPS数据
|
List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, startTime, endTime);
|
|
if (gpsList == null || gpsList.isEmpty()) {
|
logger.debug("车辆ID: {} 在时间范围 {} 到 {} 内无GPS数据", vehicleId, startTime, endTime);
|
return 0;
|
}
|
|
logger.info("车辆ID: {} 查询到 {} 条GPS数据 startTime:{},endTime:{}", vehicleId, gpsList.size(),startTime,endTime);
|
|
return calculateVehicleSegmentMileageWithGpsList(vehicleId, gpsList, startTime, endTime);
|
|
} catch (Exception e) {
|
logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
|
throw new RuntimeException("计算分段里程失败: " + e.getMessage());
|
}
|
}
|
|
/**
|
* 根据提供的GPS列表计算车辆分段里程
|
* @param vehicleId 车辆ID
|
* @param gpsList GPS列表(已按时间排序)
|
* @param startTime 起始时间
|
* @param endTime 结束时间
|
* @return 生成的分段数量
|
*/
|
private int calculateVehicleSegmentMileageWithGpsList(Long vehicleId, List<VehicleGps> gpsList,
|
Date startTime, Date endTime) {
|
try {
|
// 验证输入数据
|
if (gpsList == null || gpsList.isEmpty()) {
|
logger.debug("车辆ID: {} 在时间范围 {} 到 {} 内无GPS数据", vehicleId, startTime, endTime);
|
return 0;
|
}
|
|
// 加载配置参数
|
MileageCalculationConfig config = loadMileageCalculationConfig();
|
|
// 按时间段分组GPS数据
|
Map<Date, List<VehicleGps>> segmentedData = segmentGpsDataByTime(gpsList, config.segmentMinutes);
|
|
// 处理每个时间段并计算里程
|
int savedCount = processSegmentedGpsData(vehicleId, segmentedData, config);
|
|
logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount);
|
|
// 自动触发每日统计汇总
|
if (savedCount > 0) {
|
triggerDailyMileageAggregation(vehicleId, segmentedData);
|
}
|
|
return savedCount;
|
|
} catch (Exception e) {
|
logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
|
throw new RuntimeException("计算分段里程失败: " + e.getMessage());
|
}
|
}
|
|
/**
|
* 加载里程计算配置参数
|
*/
|
private MileageCalculationConfig loadMileageCalculationConfig() {
|
MileageCalculationConfig config = new MileageCalculationConfig();
|
|
// 获取时间间隔配置(分钟)
|
config.segmentMinutes = configService.selectConfigByKey("gps.mileage.segment.minutes") != null
|
? Integer.parseInt(configService.selectConfigByKey("gps.mileage.segment.minutes"))
|
: 5;
|
|
// 获取计算方式配置
|
config.calculateMethod = configService.selectConfigByKey("gps.mileage.calculate.method");
|
if (config.calculateMethod == null || config.calculateMethod.isEmpty()) {
|
config.calculateMethod = "tianditu";
|
}
|
|
// 获取是否跳过已计算GPS点的配置
|
String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
|
config.skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
|
logger.info("控制跳过重复计算标识: {}", config.skipCalculated);
|
|
return config;
|
}
|
|
/**
|
* 处理分段后的GPS数据并计算里程
|
* @param vehicleId 车辆ID
|
* @param segmentedData 分段后的GPS数据
|
* @param config 计算配置
|
* @return 成功保存的分段数量
|
*/
|
private int processSegmentedGpsData(Long vehicleId, Map<Date, List<VehicleGps>> segmentedData,
|
MileageCalculationConfig config) {
|
int savedCount = 0;
|
VehicleGps previousSegmentLastPoint = null; // 记录上一个时间段的最后一个点
|
|
// 遍历每个时间段,计算里程
|
for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
|
Date segmentStartTime = entry.getKey();
|
List<VehicleGps> segmentGpsList = entry.getValue();
|
|
// 校验当前时间段数据
|
if (!isSegmentValidForCalculation(segmentGpsList, previousSegmentLastPoint, vehicleId, segmentStartTime)) {
|
// 保留当前点作为下一段的前置点(如果有的话)
|
if (!segmentGpsList.isEmpty()) {
|
previousSegmentLastPoint = segmentGpsList.get(0);
|
}
|
continue;
|
}
|
|
// 检查是否已存在该时间段的记录
|
if (isSegmentAlreadyCalculated(vehicleId, segmentStartTime, segmentGpsList)) {
|
previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
|
continue;
|
}
|
|
// 计算并保存分段里程
|
boolean success = calculateAndSaveSegment(vehicleId, segmentStartTime, segmentGpsList,
|
previousSegmentLastPoint, config);
|
if (success) {
|
savedCount++;
|
}
|
|
// 更新上一段最后一个点,供下一段使用
|
previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
|
}
|
|
return savedCount;
|
}
|
|
/**
|
* 校验时间段数据是否有效
|
*/
|
private boolean isSegmentValidForCalculation(List<VehicleGps> segmentGpsList,
|
VehicleGps previousSegmentLastPoint,
|
Long vehicleId, Date segmentStartTime) {
|
// 如果当前段没有GPS点,跳过
|
if (segmentGpsList == null || segmentGpsList.isEmpty()) {
|
return false;
|
}
|
|
// 如果本段只有1个点,且没有上一段的最后一个点,无法计算距离
|
if (segmentGpsList.size() == 1 && previousSegmentLastPoint == null) {
|
logger.debug("车辆 {} 时间段 {} 只有1个GPS点且无前置点,暂存待下一段计算", vehicleId, segmentStartTime);
|
return false;
|
}
|
|
return true;
|
}
|
|
/**
|
* 检查时间段是否已被计算
|
*/
|
private boolean isSegmentAlreadyCalculated(Long vehicleId, Date segmentStartTime, List<VehicleGps> segmentGpsList) {
|
VehicleGpsSegmentMileage existing = segmentMileageMapper.selectByVehicleIdAndTime(vehicleId, segmentStartTime);
|
if (existing != null) {
|
logger.debug("车辆 {} 时间段 {} 的分段里程已存在,跳过", vehicleId, segmentStartTime);
|
return true;
|
}
|
return false;
|
}
|
|
/**
|
* 计算并保存单个时间段的里程
|
*/
|
private boolean calculateAndSaveSegment(Long vehicleId, Date segmentStartTime,
|
List<VehicleGps> segmentGpsList,
|
VehicleGps previousSegmentLastPoint,
|
MileageCalculationConfig config) {
|
try {
|
// 计算时间段的结束时间
|
Date segmentEndTime = calculateSegmentEndTime(segmentStartTime, config.segmentMinutes);
|
|
// 计算该时间段的里程(包括跨段距离)
|
BigDecimal distance = calculateSegmentDistanceWithGap(segmentGpsList, config.calculateMethod, previousSegmentLastPoint);
|
|
// 收集GPS ID列表
|
List<Long> gpsIdList = collectGpsIds(segmentGpsList, previousSegmentLastPoint);
|
String gpsIds = gpsIdList.stream()
|
.map(String::valueOf)
|
.collect(java.util.stream.Collectors.joining(","));
|
|
// 创建分段里程记录
|
VehicleGpsSegmentMileage segment = buildSegmentMileageRecord(
|
vehicleId, segmentStartTime, segmentEndTime, segmentGpsList,
|
distance, gpsIdList, gpsIds, config.calculateMethod);
|
|
// 保存到数据库
|
logger.info("保存车辆分时段里程到数据库中,车辆ID: {}, 时间段: {} 到 {}", vehicleId, segmentStartTime, segmentEndTime);
|
segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
|
|
// 记录已计算的GPS点(如果开启了重复计算控制)
|
if (config.skipCalculated && segment.getSegmentId() != null) {
|
recordCalculatedGpsPoints(gpsIdList, segment.getSegmentId(), vehicleId);
|
}
|
|
logger.debug("车辆 {} 时间段 {} 到 {} 里程: {}km, GPS点数: {}, GPS IDs: {}",
|
vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(),
|
gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
|
|
return true;
|
|
} catch (Exception e) {
|
logger.error("保存车辆 {} 时间段 {} 的里程记录失败", vehicleId, segmentStartTime, e);
|
return false;
|
}
|
}
|
|
/**
|
* 计算时间段结束时间
|
*/
|
private Date calculateSegmentEndTime(Date segmentStartTime, int segmentMinutes) {
|
Calendar cal = Calendar.getInstance();
|
cal.setTime(segmentStartTime);
|
cal.add(Calendar.MINUTE, segmentMinutes);
|
return cal.getTime();
|
}
|
|
/**
|
* 收集GPS ID列表(包括前置点)
|
*/
|
private List<Long> collectGpsIds(List<VehicleGps> segmentGpsList, VehicleGps previousSegmentLastPoint) {
|
List<Long> gpsIdList = new ArrayList<>();
|
|
// 如果有上一段的最后一个点,先添加它的ID(用于计算跨段距离)
|
if (previousSegmentLastPoint != null && previousSegmentLastPoint.getGpsId() != null) {
|
gpsIdList.add(previousSegmentLastPoint.getGpsId());
|
}
|
|
// 再添加当前段的所有GPS点ID
|
for (VehicleGps gps : segmentGpsList) {
|
if (gps.getGpsId() != null) {
|
gpsIdList.add(gps.getGpsId());
|
}
|
}
|
|
return gpsIdList;
|
}
|
|
/**
|
* 构建分段里程记录对象
|
*/
|
private VehicleGpsSegmentMileage buildSegmentMileageRecord(Long vehicleId, Date segmentStartTime,
|
Date segmentEndTime, List<VehicleGps> segmentGpsList,
|
BigDecimal distance, List<Long> gpsIdList,
|
String gpsIds, String calculateMethod) {
|
VehicleGpsSegmentMileage segment = new VehicleGpsSegmentMileage();
|
segment.setVehicleId(vehicleId);
|
|
// 获取车牌号
|
String vehicleNo = getVehicleNo(vehicleId, segmentGpsList.get(0));
|
segment.setVehicleNo(vehicleNo);
|
|
// 设置时间范围
|
segment.setSegmentStartTime(segmentStartTime);
|
segment.setSegmentEndTime(segmentEndTime);
|
|
// 设置起点坐标
|
VehicleGps firstPoint = segmentGpsList.get(0);
|
segment.setStartLongitude(BigDecimal.valueOf(firstPoint.getLongitude()));
|
segment.setStartLatitude(BigDecimal.valueOf(firstPoint.getLatitude()));
|
|
// 设置终点坐标
|
VehicleGps lastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
|
segment.setEndLongitude(BigDecimal.valueOf(lastPoint.getLongitude()));
|
segment.setEndLatitude(BigDecimal.valueOf(lastPoint.getLatitude()));
|
|
// 设置里程数据
|
segment.setSegmentDistance(distance);
|
segment.setGpsPointCount(gpsIdList.size());
|
segment.setGpsIds(gpsIds);
|
segment.setCalculateMethod(calculateMethod);
|
|
// 查询并关联正在执行的任务
|
associateActiveTask(segment, vehicleId, segmentStartTime, segmentEndTime);
|
|
return segment;
|
}
|
|
/**
|
* 获取车牌号
|
*/
|
private String getVehicleNo(Long vehicleId, VehicleGps firstGps) {
|
String vehicleNo = firstGps.getVehicleNo();
|
if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
|
// GPS数据中没有车牌号,从车辆表查询
|
VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
|
if (vehicleInfo != null) {
|
vehicleNo = vehicleInfo.getVehicleNo();
|
}
|
}
|
return vehicleNo;
|
}
|
|
/**
|
* 记录已计算的GPS点到状态表
|
*/
|
private void recordCalculatedGpsPoints(List<Long> gpsIdList, Long segmentId, Long vehicleId) {
|
for (Long gpsId : gpsIdList) {
|
try {
|
segmentMileageMapper.insertGpsCalculated(gpsId, segmentId, vehicleId);
|
} catch (Exception e) {
|
// 忽略重复键异常,继续处理
|
logger.debug("记录GPS计算状态失败,可能已存在: gpsId={}", gpsId);
|
}
|
}
|
}
|
|
/**
|
* 触发每日里程统计汇总
|
*/
|
private void triggerDailyMileageAggregation(Long vehicleId, Map<Date, List<VehicleGps>> segmentedData) {
|
try {
|
// 获取涉及的日期范围
|
Set<Date> affectedDates = extractAffectedDates(segmentedData);
|
|
// 对每个涉及的日期,触发汇总
|
for (Date statDate : affectedDates) {
|
try {
|
mileageStatsService.aggregateFromSegmentMileage(vehicleId, statDate);
|
logger.info("车辆 {} 日期 {} 的统计数据已自动汇总生成", vehicleId, statDate);
|
} catch (Exception e) {
|
logger.error("车辆 {} 日期 {} 自动汇总统计失败", vehicleId, statDate, e);
|
}
|
}
|
} catch (Exception e) {
|
logger.error("触发自动汇总失败", e);
|
}
|
}
|
|
/**
|
* 提取受影响的日期列表(用于汇总统计)
|
*/
|
private Set<Date> extractAffectedDates(Map<Date, List<VehicleGps>> segmentedData) {
|
Set<Date> affectedDates = new HashSet<>();
|
Calendar cal = Calendar.getInstance();
|
|
for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
|
cal.setTime(entry.getKey());
|
cal.set(Calendar.HOUR_OF_DAY, 0);
|
cal.set(Calendar.MINUTE, 0);
|
cal.set(Calendar.SECOND, 0);
|
cal.set(Calendar.MILLISECOND, 0);
|
affectedDates.add(cal.getTime());
|
}
|
|
return affectedDates;
|
}
|
|
/**
|
* 里程计算配置类
|
*/
|
private static class MileageCalculationConfig {
|
int segmentMinutes; // 时间段间隔(分钟)
|
String calculateMethod; // 计算方式
|
boolean skipCalculated; // 是否跳过已计算的GPS点
|
}
|
|
/**
|
* 将GPS数据按时间段分组
|
*/
|
private Map<Date, List<VehicleGps>> segmentGpsDataByTime(List<VehicleGps> gpsList, int segmentMinutes) {
|
Map<Date, List<VehicleGps>> segmentedData = new LinkedHashMap<>();
|
|
for (VehicleGps gps : gpsList) {
|
// 解析GPS采集时间
|
Date collectTime = parseDateTime(gps.getCollectTime());
|
|
// 计算该GPS点所属的时间段起始时间(向下取整到最近的时间段)
|
Date segmentStart = getSegmentStartTime(collectTime, segmentMinutes);
|
|
// 添加到对应时间段
|
segmentedData.computeIfAbsent(segmentStart, k -> new ArrayList<>()).add(gps);
|
}
|
|
return segmentedData;
|
}
|
|
/**
|
* 获取时间段的起始时间(向下取整)
|
*/
|
private Date getSegmentStartTime(Date time, int segmentMinutes) {
|
Calendar cal = Calendar.getInstance();
|
cal.setTime(time);
|
|
// 将分钟数向下取整到最近的分段
|
int minute = cal.get(Calendar.MINUTE);
|
int segmentIndex = minute / segmentMinutes;
|
int alignedMinute = segmentIndex * segmentMinutes;
|
|
cal.set(Calendar.MINUTE, alignedMinute);
|
cal.set(Calendar.SECOND, 0);
|
cal.set(Calendar.MILLISECOND, 0);
|
|
return cal.getTime();
|
}
|
|
/**
|
* 计算一个时间段内的总里程(包括与上一段的间隙距离)
|
* @param gpsList 当前时间段的GPS点列表(至少1个点)
|
* @param calculateMethod 计算方式
|
* @param previousLastPoint 上一个时间段的最后一个点(可为null)
|
* @return 总里程(公里),保留3位小数
|
*/
|
private BigDecimal calculateSegmentDistanceWithGap(List<VehicleGps> gpsList, String calculateMethod, VehicleGps previousLastPoint) {
|
if (gpsList == null || gpsList.isEmpty()) {
|
return BigDecimal.ZERO;
|
}
|
|
BigDecimal totalDistance = BigDecimal.ZERO;
|
|
// 1. 先计算跨段间隙距离(上一段最后一个点 -> 当前段第一个点)
|
if (previousLastPoint != null) {
|
VehicleGps currentFirstPoint = gpsList.get(0);
|
double gapDistance = calculateHaversineDistance(
|
previousLastPoint.getLatitude().doubleValue(),
|
previousLastPoint.getLongitude().doubleValue(),
|
currentFirstPoint.getLatitude().doubleValue(),
|
currentFirstPoint.getLongitude().doubleValue()
|
);
|
totalDistance = totalDistance.add(BigDecimal.valueOf(gapDistance));
|
|
logger.debug("跨段间隙距离: {}km (上一段末点 -> 当前段首点)",
|
String.format("%.3f", gapDistance));
|
}
|
|
// 2. 再计算当前段内部的距离(如果有2个或以上GPS点)
|
if (gpsList.size() >= 2) {
|
BigDecimal segmentInternalDistance;
|
if ("tianditu".equalsIgnoreCase(calculateMethod)) {
|
segmentInternalDistance = calculateDistanceByTianditu(gpsList);
|
} else {
|
segmentInternalDistance = calculateDistanceByHaversine(gpsList);
|
}
|
totalDistance = totalDistance.add(segmentInternalDistance);
|
}
|
// 如果只有1个点,段内距离为0,只计算跨段距离
|
|
return totalDistance.setScale(3, RoundingMode.HALF_UP);
|
}
|
|
/**
|
* 计算一个时间段内的总里程(仅段内距离)
|
*/
|
private BigDecimal calculateSegmentDistance(List<VehicleGps> gpsList, String calculateMethod) {
|
if (gpsList == null || gpsList.size() < 2) {
|
return BigDecimal.ZERO;
|
}
|
|
BigDecimal totalDistance = BigDecimal.ZERO;
|
|
if ("tianditu".equalsIgnoreCase(calculateMethod)) {
|
// 使用天地图API计算(批量计算更精确)
|
totalDistance = calculateDistanceByTianditu(gpsList);
|
} else {
|
// 使用Haversine公式计算(直线距离,更快但不够精确)
|
totalDistance = calculateDistanceByHaversine(gpsList);
|
}
|
|
return totalDistance.setScale(3, RoundingMode.HALF_UP);
|
}
|
|
/**
|
* 使用天地图API计算距离
|
*/
|
private BigDecimal calculateDistanceByTianditu(List<VehicleGps> gpsList) {
|
try {
|
// 天地图路径规划API有点数限制,如果点太多需要分批处理
|
int maxPointsPerRequest = 50; // 天地图API建议不超过50个点
|
BigDecimal totalDistance = BigDecimal.ZERO;
|
|
// 如果GPS点数较少,直接使用Haversine公式(避免频繁调用API)
|
if (gpsList.size() <= 3) {
|
return calculateDistanceByHaversine(gpsList);
|
}
|
|
// 分批处理
|
for (int i = 0; i < gpsList.size() - 1; i += maxPointsPerRequest) {
|
int endIndex = Math.min(i + maxPointsPerRequest, gpsList.size());
|
List<VehicleGps> batchList = gpsList.subList(i, endIndex);
|
|
BigDecimal batchDistance = calculateBatchDistanceByTianditu(batchList);
|
totalDistance = totalDistance.add(batchDistance);
|
}
|
|
return totalDistance;
|
|
} catch (Exception e) {
|
logger.warn("天地图API计算距离失败,降级使用Haversine公式: {}", e.getMessage());
|
return calculateDistanceByHaversine(gpsList);
|
}
|
}
|
|
/**
|
* 使用天地图API计算一批GPS点的距离
|
*/
|
private BigDecimal calculateBatchDistanceByTianditu(List<VehicleGps> gpsList) {
|
try {
|
// 简化处理:计算相邻点之间的直线距离总和
|
// 注:天地图的路径规划API主要用于导航,这里用简化的距离计算
|
BigDecimal totalDistance = BigDecimal.ZERO;
|
|
for (int i = 0; i < gpsList.size() - 1; i++) {
|
VehicleGps p1 = gpsList.get(i);
|
VehicleGps p2 = gpsList.get(i + 1);
|
|
double distance = calculateHaversineDistance(
|
p1.getLatitude().doubleValue(),
|
p1.getLongitude().doubleValue(),
|
p2.getLatitude().doubleValue(),
|
p2.getLongitude().doubleValue()
|
);
|
|
totalDistance = totalDistance.add(BigDecimal.valueOf(distance));
|
}
|
|
return totalDistance;
|
|
} catch (Exception e) {
|
logger.error("天地图批量距离计算失败", e);
|
throw e;
|
}
|
}
|
|
/**
|
* 使用Haversine公式计算距离
|
*/
|
private BigDecimal calculateDistanceByHaversine(List<VehicleGps> gpsList) {
|
BigDecimal totalDistance = BigDecimal.ZERO;
|
|
for (int i = 0; i < gpsList.size() - 1; i++) {
|
VehicleGps p1 = gpsList.get(i);
|
VehicleGps p2 = gpsList.get(i + 1);
|
|
double distance = calculateHaversineDistance(
|
p1.getLatitude().doubleValue(),
|
p1.getLongitude().doubleValue(),
|
p2.getLatitude().doubleValue(),
|
p2.getLongitude().doubleValue()
|
);
|
|
totalDistance = totalDistance.add(BigDecimal.valueOf(distance));
|
}
|
|
return totalDistance;
|
}
|
|
/**
|
* 使用Haversine公式计算两点之间的距离(公里)
|
*/
|
private double calculateHaversineDistance(double lat1, double lon1, double lat2, double lon2) {
|
// 如果起点和终点经纬度相同,直接返回0,避免不必要的计算
|
if (lat1 == lat2 && lon1 == lon2) {
|
return 0.0;
|
}
|
|
// 将角度转换为弧度
|
double dLat = Math.toRadians(lat2 - lat1);
|
double dLon = Math.toRadians(lon2 - lon1);
|
double rLat1 = Math.toRadians(lat1);
|
double rLat2 = Math.toRadians(lat2);
|
|
// Haversine公式
|
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
Math.cos(rLat1) * Math.cos(rLat2) *
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
return EARTH_RADIUS_KM * c;
|
}
|
|
/**
|
* 解析日期时间字符串
|
*/
|
private Date parseDateTime(String dateTimeStr) {
|
if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
|
throw new RuntimeException("日期时间字符串不能为空");
|
}
|
|
try {
|
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
sdf.setLenient(false);
|
return sdf.parse(dateTimeStr.trim());
|
} catch (Exception e) {
|
throw new RuntimeException("日期时间格式错误: " + dateTimeStr + ", 应为 yyyy-MM-dd HH:mm:ss", e);
|
}
|
}
|
|
/**
|
* 查询并关联车辆正在执行的任务
|
* @param segment 分段里程记录
|
* @param vehicleId 车辆ID
|
* @param segmentStartTime 时间段开始时间
|
* @param segmentEndTime 时间段结束时间
|
*/
|
private void associateActiveTask(VehicleGpsSegmentMileage segment, Long vehicleId,
|
Date segmentStartTime, Date segmentEndTime) {
|
try {
|
// 查询该车辆正在执行的任务列表
|
List<SysTask> activeTasks = sysTaskMapper.selectTaskByVehicleIdAndDate(vehicleId,segmentStartTime,segmentEndTime);
|
|
if (activeTasks == null || activeTasks.isEmpty()) {
|
logger.debug("车辆 {} 在时间段 {} - {} 没有正在执行的任务", vehicleId, segmentStartTime, segmentEndTime);
|
return;
|
}
|
|
// 遍历任务,查找与当前时间段有重叠的任务
|
for (SysTask task : activeTasks) {
|
// 获取任务的实际执行时间,如果没有实际时间则使用计划时间
|
Date taskStart = task.getActualStartTime() != null ? task.getActualStartTime() : task.getPlannedStartTime();
|
Date taskEnd = task.getActualEndTime() != null ? task.getActualEndTime() : task.getPlannedEndTime();
|
|
// 判断时间段是否有重叠
|
if (isTimeOverlap(segmentStartTime, segmentEndTime, taskStart, taskEnd)) {
|
// 关联任务ID和任务编号
|
segment.setTaskId(task.getTaskId());
|
segment.setTaskCode(task.getTaskCode());
|
|
logger.debug("车辆 {} 时间段 {} - {} 关联任务: taskId={}, taskCode={}",
|
vehicleId, segmentStartTime, segmentEndTime, task.getTaskId(), task.getTaskCode());
|
break; // 找到一个匹配的任务即可
|
}
|
}
|
|
} catch (Exception e) {
|
// 关联任务失败不影响主流程,只记录日志
|
logger.warn("关联车辆 {} 的任务信息失败", vehicleId, e);
|
}
|
}
|
|
/**
|
* 判断两个时间段是否有重叠
|
* @param start1 时间段1开始
|
* @param end1 时间段1结束
|
* @param start2 时间段2开始
|
* @param end2 时间段2结束
|
* @return true-有重叠, false-无重叠
|
*/
|
private boolean isTimeOverlap(Date start1, Date end1, Date start2, Date end2) {
|
// 任何时间为null,返回false
|
if (start1 == null || end1 == null || start2 == null || end2 == null) {
|
return false;
|
}
|
|
// 两个时间段有重叠的条件:
|
// start1 < end2 && end1 > start2
|
return start1.before(end2) && end1.after(start2);
|
}
|
}
|