| | |
| | | package com.ruoyi.system.service.impl; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import com.ruoyi.system.domain.VehicleGps; |
| | | import com.ruoyi.system.domain.VehicleMileageStats; |
| | | import com.ruoyi.system.domain.TaskTimeInterval; |
| | | import com.ruoyi.system.mapper.VehicleGpsMapper; |
| | | import com.ruoyi.system.mapper.VehicleMileageStatsMapper; |
| | | import com.ruoyi.system.service.IVehicleMileageStatsService; |
| | | |
| | | /** |
| | | * 车辆里程统计Service业务层处理 |
| | | */ |
| | | @Service |
| | | public class VehicleMileageStatsServiceImpl implements IVehicleMileageStatsService { |
| | | |
| | | private static final Logger logger = LoggerFactory.getLogger(VehicleMileageStatsServiceImpl.class); |
| | | |
| | | /** 地球半径(公里) */ |
| | | private static final double EARTH_RADIUS_KM = 6371.0; |
| | | |
| | | @Autowired |
| | | private VehicleMileageStatsMapper vehicleMileageStatsMapper; |
| | | |
| | | @Autowired |
| | | private VehicleGpsMapper vehicleGpsMapper; |
| | | |
| | | /** |
| | | * 查询车辆里程统计 |
| | | */ |
| | | @Override |
| | | public VehicleMileageStats selectVehicleMileageStatsById(Long statsId) { |
| | | return vehicleMileageStatsMapper.selectVehicleMileageStatsById(statsId); |
| | | } |
| | | |
| | | /** |
| | | * 查询车辆里程统计列表 |
| | | */ |
| | | @Override |
| | | public List<VehicleMileageStats> selectVehicleMileageStatsList(VehicleMileageStats vehicleMileageStats) { |
| | | return vehicleMileageStatsMapper.selectVehicleMileageStatsList(vehicleMileageStats); |
| | | } |
| | | |
| | | /** |
| | | * 新增车辆里程统计 |
| | | */ |
| | | @Override |
| | | public int insertVehicleMileageStats(VehicleMileageStats vehicleMileageStats) { |
| | | return vehicleMileageStatsMapper.insertVehicleMileageStats(vehicleMileageStats); |
| | | } |
| | | |
| | | /** |
| | | * 修改车辆里程统计 |
| | | */ |
| | | @Override |
| | | public int updateVehicleMileageStats(VehicleMileageStats vehicleMileageStats) { |
| | | return vehicleMileageStatsMapper.updateVehicleMileageStats(vehicleMileageStats); |
| | | } |
| | | |
| | | /** |
| | | * 批量删除车辆里程统计 |
| | | */ |
| | | @Override |
| | | public int deleteVehicleMileageStatsByIds(Long[] statsIds) { |
| | | return vehicleMileageStatsMapper.deleteVehicleMileageStatsByIds(statsIds); |
| | | } |
| | | |
| | | /** |
| | | * 删除车辆里程统计信息 |
| | | */ |
| | | @Override |
| | | public int deleteVehicleMileageStatsById(Long statsId) { |
| | | return vehicleMileageStatsMapper.deleteVehicleMileageStatsById(statsId); |
| | | } |
| | | |
| | | /** |
| | | * 计算并保存指定车辆指定日期的里程统计 |
| | | */ |
| | | @Override |
| | | public VehicleMileageStats calculateAndSaveMileageStats(Long vehicleId, Date statDate) { |
| | | try { |
| | | // 1. 获取统计日期的开始和结束时间 |
| | | Calendar calendar = Calendar.getInstance(); |
| | | calendar.setTime(statDate); |
| | | calendar.set(Calendar.HOUR_OF_DAY, 0); |
| | | calendar.set(Calendar.MINUTE, 0); |
| | | calendar.set(Calendar.SECOND, 0); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | Date dayStart = calendar.getTime(); |
| | | |
| | | calendar.add(Calendar.DAY_OF_MONTH, 1); |
| | | Date dayEnd = calendar.getTime(); |
| | | |
| | | // 2. 查询车辆在该日期的GPS数据(按时间排序) |
| | | List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, dayStart, dayEnd); |
| | | |
| | | if (gpsList == null || gpsList.isEmpty()) { |
| | | logger.info("车辆ID: {} 在日期: {} 无GPS数据", vehicleId, statDate); |
| | | return null; |
| | | } |
| | | |
| | | // 3. 查询车辆在该日期的任务时间区间 |
| | | List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); |
| | | |
| | | // 4. 计算里程 |
| | | MileageCalculation calculation = calculateMileage(gpsList, taskIntervals); |
| | | |
| | | // 5. 查询或创建统计记录 |
| | | VehicleMileageStats stats = vehicleMileageStatsMapper.selectByVehicleIdAndDate(vehicleId, statDate); |
| | | boolean isNew = (stats == null); |
| | | |
| | | if (isNew) { |
| | | stats = new VehicleMileageStats(); |
| | | stats.setVehicleId(vehicleId); |
| | | stats.setStatDate(statDate); |
| | | |
| | | // 获取车牌号 |
| | | if (!gpsList.isEmpty() && gpsList.get(0).getVehicleNo() != null) { |
| | | stats.setVehicleNo(gpsList.get(0).getVehicleNo()); |
| | | } |
| | | } |
| | | |
| | | // 6. 设置统计数据 |
| | | stats.setTotalMileage(calculation.totalMileage); |
| | | stats.setTaskMileage(calculation.taskMileage); |
| | | stats.setNonTaskMileage(calculation.nonTaskMileage); |
| | | stats.setTaskRatio(calculation.taskRatio); |
| | | stats.setGpsPointCount(gpsList.size()); |
| | | stats.setTaskCount(taskIntervals == null ? 0 : taskIntervals.size()); |
| | | |
| | | // 7. 保存到数据库 |
| | | if (isNew) { |
| | | vehicleMileageStatsMapper.insertVehicleMileageStats(stats); |
| | | } else { |
| | | vehicleMileageStatsMapper.updateVehicleMileageStats(stats); |
| | | } |
| | | |
| | | logger.info("车辆ID: {} 日期: {} 里程统计完成 - 总里程: {}km, 任务里程: {}km, 非任务里程: {}km, 占比: {}", |
| | | vehicleId, statDate, calculation.totalMileage, calculation.taskMileage, |
| | | calculation.nonTaskMileage, calculation.taskRatio); |
| | | |
| | | return stats; |
| | | |
| | | } catch (Exception e) { |
| | | logger.error("计算车辆里程统计失败 - 车辆ID: {}, 日期: {}", vehicleId, statDate, e); |
| | | throw new RuntimeException("计算里程统计失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 批量计算所有车辆指定日期的里程统计 |
| | | */ |
| | | @Override |
| | | public int batchCalculateMileageStats(Date statDate) { |
| | | try { |
| | | // 查询所有活跃车辆 |
| | | List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds(); |
| | | |
| | | if (vehicleIds == null || vehicleIds.isEmpty()) { |
| | | logger.info("没有找到活跃车辆"); |
| | | return 0; |
| | | } |
| | | |
| | | int successCount = 0; |
| | | for (Long vehicleId : vehicleIds) { |
| | | try { |
| | | calculateAndSaveMileageStats(vehicleId, statDate); |
| | | successCount++; |
| | | } catch (Exception e) { |
| | | logger.error("计算车辆 {} 的里程统计失败", vehicleId, e); |
| | | } |
| | | } |
| | | |
| | | logger.info("批量里程统计完成 - 日期: {}, 总车辆数: {}, 成功: {}", statDate, vehicleIds.size(), successCount); |
| | | return successCount; |
| | | |
| | | } catch (Exception e) { |
| | | logger.error("批量计算里程统计失败 - 日期: {}", statDate, e); |
| | | throw new RuntimeException("批量计算失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计算里程的内部方法 |
| | | */ |
| | | private MileageCalculation calculateMileage(List<VehicleGps> gpsList, List<TaskTimeInterval> taskIntervals) { |
| | | MileageCalculation result = new MileageCalculation(); |
| | | |
| | | // 遍历GPS点,计算相邻点之间的距离 |
| | | for (int i = 0; i < gpsList.size() - 1; i++) { |
| | | VehicleGps p1 = gpsList.get(i); |
| | | VehicleGps p2 = gpsList.get(i + 1); |
| | | |
| | | // 计算两点间距离(使用Haversine公式) |
| | | double distance = calculateDistance( |
| | | p1.getLatitude().doubleValue(), |
| | | p1.getLongitude().doubleValue(), |
| | | p2.getLatitude().doubleValue(), |
| | | p2.getLongitude().doubleValue() |
| | | ); |
| | | |
| | | // 获取这段距离的时间区间 |
| | | Date segmentStart = p1.getCollectTime(); |
| | | Date segmentEnd = p2.getCollectTime(); |
| | | |
| | | // 计算这段距离在任务时段的占比 |
| | | double taskRatio = calculateTaskOverlapRatio(segmentStart, segmentEnd, taskIntervals); |
| | | |
| | | // 分摊里程 |
| | | double taskDistance = distance * taskRatio; |
| | | double nonTaskDistance = distance * (1 - taskRatio); |
| | | |
| | | result.totalMileage = result.totalMileage.add(BigDecimal.valueOf(distance)); |
| | | result.taskMileage = result.taskMileage.add(BigDecimal.valueOf(taskDistance)); |
| | | result.nonTaskMileage = result.nonTaskMileage.add(BigDecimal.valueOf(nonTaskDistance)); |
| | | } |
| | | |
| | | // 计算任务里程占比 |
| | | if (result.totalMileage.compareTo(BigDecimal.ZERO) > 0) { |
| | | result.taskRatio = result.taskMileage.divide(result.totalMileage, 4, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | // 保留两位小数 |
| | | result.totalMileage = result.totalMileage.setScale(2, RoundingMode.HALF_UP); |
| | | result.taskMileage = result.taskMileage.setScale(2, RoundingMode.HALF_UP); |
| | | result.nonTaskMileage = result.nonTaskMileage.setScale(2, RoundingMode.HALF_UP); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 使用Haversine公式计算两个GPS坐标之间的距离(公里) |
| | | */ |
| | | private double calculateDistance(double lat1, double lon1, double lat2, double lon2) { |
| | | // 将角度转换为弧度 |
| | | 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 double calculateTaskOverlapRatio(Date segmentStart, Date segmentEnd, List<TaskTimeInterval> taskIntervals) { |
| | | if (taskIntervals == null || taskIntervals.isEmpty()) { |
| | | return 0.0; |
| | | } |
| | | |
| | | long segmentDuration = segmentEnd.getTime() - segmentStart.getTime(); |
| | | if (segmentDuration <= 0) { |
| | | return 0.0; |
| | | } |
| | | |
| | | long totalOverlap = 0; |
| | | |
| | | for (TaskTimeInterval task : taskIntervals) { |
| | | // 计算重叠时间 |
| | | long overlapStart = Math.max(segmentStart.getTime(), task.getStartTime().getTime()); |
| | | long overlapEnd = Math.min(segmentEnd.getTime(), task.getEndTime().getTime()); |
| | | |
| | | if (overlapEnd > overlapStart) { |
| | | totalOverlap += (overlapEnd - overlapStart); |
| | | } |
| | | } |
| | | |
| | | return (double) totalOverlap / segmentDuration; |
| | | } |
| | | |
| | | /** |
| | | * 里程计算结果内部类 |
| | | */ |
| | | private static class MileageCalculation { |
| | | BigDecimal totalMileage = BigDecimal.ZERO; |
| | | BigDecimal taskMileage = BigDecimal.ZERO; |
| | | BigDecimal nonTaskMileage = BigDecimal.ZERO; |
| | | BigDecimal taskRatio = BigDecimal.ZERO; |
| | | } |
| | | } |