| | |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, dayStart, dayEnd); |
| | | |
| | | if (gpsList == null || gpsList.isEmpty()) { |
| | | logger.info("车辆ID: {} 在日期: {} 无GPS数据", vehicleId, statDate); |
| | | // logger.info("车辆ID: {} 在日期: {} 无GPS数据", vehicleId, statDate); |
| | | return null; |
| | | } |
| | | |
| | | // logger.info("车辆ID:{} GPS数据条数:{}", vehicleId, gpsList.size()); |
| | | // 3. 查询车辆在该日期的任务时间区间 |
| | | List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); |
| | | |
| | | // 4. 计算里程 |
| | | MileageCalculation calculation = calculateMileage(gpsList, taskIntervals); |
| | | |
| | | |
| | | |
| | | List<VehicleGpsSegmentMileage> mileages = this.getTaskDistanceMileage(vehicleId, dayStart, dayEnd); |
| | | int totalGpsPoints = mileages.stream() |
| | | .filter(segment -> segment.getGpsPointCount() != null) |
| | | .mapToInt(VehicleGpsSegmentMileage::getGpsPointCount) |
| | | .sum(); |
| | | BigDecimal taskDistance = getTaskDistance(taskIntervals,mileages); |
| | | |
| | | MileageCalculation calculation = calculateMileage(gpsList, taskDistance); |
| | | // logger.info("计算出车辆当天总里程,车辆ID:{},总里程:{},任务里程:{}",vehicleId,calculation.totalMileage,calculation.taskMileage); |
| | | // 5. 查询或创建统计记录 |
| | | VehicleMileageStats stats = vehicleMileageStatsMapper.selectByVehicleIdAndDate(vehicleId, statDate); |
| | | boolean isNew = (stats == null); |
| | |
| | | stats.setTaskRatio(calculation.taskRatio); |
| | | stats.setGpsPointCount(gpsList.size()); |
| | | stats.setTaskCount(taskIntervals == null ? 0 : taskIntervals.size()); |
| | | |
| | | // logger.info("车辆ID: {} 日期: {} 里程统计完成 - 总里程: {}km, 任务里程: {}km, 非任务里程: {}km, 占比: {}", |
| | | // vehicleId, statDate, calculation.totalMileage, taskDistance, calculation.nonTaskMileage, calculation.taskRatio); |
| | | |
| | | // 7. 保存到数据库 |
| | | if (isNew) { |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | private BigDecimal calculateTotalMileage(List<VehicleGpsSegmentMileage> mileages) { |
| | | return mileages.stream() |
| | | .map(mileage -> mileage.getSegmentDistance()) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | |
| | | } |
| | | /** |
| | | * 计算里程的内部方法 |
| | | */ |
| | | private MileageCalculation calculateMileage(List<VehicleGps> gpsList, List<TaskTimeInterval> taskIntervals) { |
| | | private MileageCalculation calculateMileage(List<VehicleGps> gpsList, BigDecimal taskDistance) { |
| | | MileageCalculation result = new MileageCalculation(); |
| | | |
| | | // 遍历GPS点,计算相邻点之间的距离 |
| | |
| | | p2.getLatitude().doubleValue(), |
| | | p2.getLongitude().doubleValue() |
| | | ); |
| | | |
| | | // 获取这段距离的时间区间 |
| | | Date segmentStart = parseDateTime(p1.getCollectTime()); |
| | | Date segmentEnd = parseDateTime(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)); |
| | | } |
| | | |
| | | |
| | | result.taskMileage=taskDistance; |
| | | result.nonTaskMileage=result.totalMileage.subtract(result.taskMileage); |
| | | // 计算任务里程占比 |
| | | if (result.totalMileage.compareTo(BigDecimal.ZERO) > 0) { |
| | | result.taskRatio = result.taskMileage.divide(result.totalMileage, 4, RoundingMode.HALF_UP); |
| | |
| | | return EARTH_RADIUS_KM * c; |
| | | } |
| | | |
| | | //计算任务时间段内的里程,应该拿到该任务在工作时间段里的分段距离然后相加 |
| | | private List<VehicleGpsSegmentMileage> getTaskDistanceMileage(Long vehicleId, Date segmentStart, Date segmentEnd) { |
| | | return segmentMileageMapper.selectSegmentsByDateRange(vehicleId, segmentStart, segmentEnd); |
| | | } |
| | | |
| | | /** |
| | | * 计算时间段与任务时段的重叠比例 |
| | | * 计算在任务时间段内的实际任务里程 |
| | | * 通过检查分段里程数据是否与任务时间段重叠,累加这些重叠分段的实际里程 |
| | | * |
| | | * @param taskTimeIntervals 任务时间段列表 |
| | | * @param segmentMileages 分段里程数据列表 |
| | | * @return 在任务时间段内的总里程 |
| | | */ |
| | | private double calculateTaskOverlapRatio(Date segmentStart, Date segmentEnd, List<TaskTimeInterval> taskIntervals) { |
| | | if (taskIntervals == null || taskIntervals.isEmpty()) { |
| | | return 0.0; |
| | | private BigDecimal getTaskDistance(List<TaskTimeInterval> taskTimeIntervals, List<VehicleGpsSegmentMileage> segmentMileages) { |
| | | if (taskTimeIntervals == null || taskTimeIntervals.isEmpty() || |
| | | segmentMileages == null || segmentMileages.isEmpty()) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | long segmentDuration = segmentEnd.getTime() - segmentStart.getTime(); |
| | | if (segmentDuration <= 0) { |
| | | return 0.0; |
| | | } |
| | | BigDecimal totalTaskDistance = BigDecimal.ZERO; |
| | | |
| | | 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()); |
| | | // 遍历所有分段里程数据 |
| | | for (VehicleGpsSegmentMileage segment : segmentMileages) { |
| | | // 只处理有关联任务ID且有距离数据的分段 |
| | | |
| | | |
| | | // 检查该分段是否与任何任务时间段重叠 |
| | | Date segmentStart = segment.getSegmentStartTime(); |
| | | Date segmentEnd = segment.getSegmentEndTime(); |
| | | |
| | | if (overlapEnd > overlapStart) { |
| | | totalOverlap += (overlapEnd - overlapStart); |
| | | boolean isInTaskPeriod = false; |
| | | for (TaskTimeInterval taskInterval : taskTimeIntervals) { |
| | | // 计算时间重叠 --任务时间段 |
| | | long overlapStart = Math.max(segmentStart.getTime(), taskInterval.getStartTime().getTime()); |
| | | long overlapEnd = Math.min(segmentEnd.getTime(), taskInterval.getEndTime().getTime()); |
| | | |
| | | // 如果有时间重叠,则该分段属于任务里程 |
| | | if (overlapEnd > overlapStart) { |
| | | isInTaskPeriod = true; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 如果分段在任务时间段内,则累加其里程 |
| | | if (isInTaskPeriod) { |
| | | totalTaskDistance = totalTaskDistance.add(segment.getSegmentDistance()); |
| | | } |
| | | } |
| | | |
| | | return (double) totalOverlap / segmentDuration; |
| | | return totalTaskDistance; |
| | | } |
| | | /** |
| | | * 计算指定时间段内的实际任务里程 |
| | | * 通过查找与该时间段重叠的任务,并累加这些任务在该时间段内的实际里程 |
| | | */ |
| | | private double calculateActualTaskMileage(Date segmentStart, Date segmentEnd, List<VehicleGpsSegmentMileage> segmentMileages) { |
| | | if (segmentMileages == null || segmentMileages.isEmpty()) { |
| | | return 0.0; |
| | | } |
| | | |
| | | double totalTaskMileage = 0.0; |
| | | |
| | | // 遍历所有分段里程数据,找出与指定时间段重叠且有关联任务的分段 |
| | | for (VehicleGpsSegmentMileage segment : segmentMileages) { |
| | | // 只处理有关联任务的分段 |
| | | if (segment.getTaskId() == null) { |
| | | continue; |
| | | } |
| | | |
| | | // 检查分段时间与指定时间段是否有重叠 |
| | | Date segStart = segment.getSegmentStartTime(); |
| | | Date segEnd = segment.getSegmentEndTime(); |
| | | |
| | | // 计算重叠时间 |
| | | long overlapStart = Math.max(segmentStart.getTime(), segStart.getTime()); |
| | | long overlapEnd = Math.min(segmentEnd.getTime(), segEnd.getTime()); |
| | | |
| | | // 如果有时间重叠,则将该分段的距离加入任务里程 |
| | | if (overlapEnd > overlapStart) { |
| | | if (segment.getSegmentDistance() != null) { |
| | | totalTaskMileage += segment.getSegmentDistance().doubleValue(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return totalTaskMileage; |
| | | } |
| | | |
| | | /** |
| | |
| | | @Override |
| | | public VehicleMileageStats aggregateFromSegmentMileage(Long vehicleId, Date statDate) { |
| | | try { |
| | | calculateAndSaveMileageStats(vehicleId, statDate); |
| | | //TODO |
| | | // 1. 获取统计日期的开始和结束时间 |
| | | Calendar calendar = Calendar.getInstance(); |
| | | calendar.setTime(statDate); |
| | |
| | | logger.info("车辆ID: {} 在日期: {} 无分段里程数据", vehicleId, statDate); |
| | | return null; |
| | | } |
| | | |
| | | |
| | | List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); |
| | | Integer taskCount = taskIntervals.size(); |
| | | |
| | | |
| | | List<VehicleGpsSegmentMileage> mileages = this.getTaskDistanceMileage(vehicleId, dayStart, dayEnd); |
| | | Integer totalGpsPoints = mileages.stream() |
| | | .filter(segment -> segment.getGpsPointCount() != null) |
| | | .mapToInt(VehicleGpsSegmentMileage::getGpsPointCount) |
| | | .sum(); |
| | | BigDecimal taskDistance = getTaskDistance(taskIntervals,mileages); |
| | | BigDecimal totalDistance = calculateTotalMileage(segments); |
| | | BigDecimal nonTaskDistance = totalDistance.subtract(taskDistance); |
| | | BigDecimal taskRatio = taskDistance.divide(totalDistance, 4, RoundingMode.HALF_UP); |
| | | // 3. 汇总里程数据 |
| | | BigDecimal totalMileage = BigDecimal.ZERO; |
| | | int totalGpsPoints = 0; |
| | | |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | if (segment.getSegmentDistance() != null) { |
| | | totalMileage = totalMileage.add(segment.getSegmentDistance()); |
| | | } |
| | | if (segment.getGpsPointCount() != null) { |
| | | totalGpsPoints += segment.getGpsPointCount(); |
| | | } |
| | | } |
| | | |
| | | // 4. 计算任务里程和非任务里程(优化:优先使用task_id直接聚合) |
| | | BigDecimal taskMileage = BigDecimal.ZERO; |
| | | BigDecimal nonTaskMileage = BigDecimal.ZERO; |
| | | int taskCount = 0; // 任务数量 |
| | | |
| | | // 4.1 统计有task_id的分段数量 |
| | | int segmentsWithTask = 0; |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | if (segment.getTaskId() != null) { |
| | | segmentsWithTask++; |
| | | } |
| | | } |
| | | |
| | | // 4.2 如果大部分分段都有task_id,使用优化方案(直接按task_id聚合) |
| | | if (segmentsWithTask > segments.size() * 0.8) { |
| | | logger.debug("车辆ID: {} 日期: {} 使用优化方案:直接按task_id聚合({}个分段有task_id,占比{}%)", |
| | | vehicleId, statDate, segmentsWithTask, (segmentsWithTask * 100.0 / segments.size())); |
| | | |
| | | // 使用Set统计去重的任务ID数量 |
| | | Set<Long> uniqueTaskIds = new HashSet<>(); |
| | | |
| | | // 直接按task_id分组聚合 |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | BigDecimal segDistance = segment.getSegmentDistance() != null ? segment.getSegmentDistance() : BigDecimal.ZERO; |
| | | |
| | | if (segment.getTaskId() != null) { |
| | | // 有任务ID,计入任务里程 |
| | | taskMileage = taskMileage.add(segDistance); |
| | | uniqueTaskIds.add(segment.getTaskId()); |
| | | } else { |
| | | // 没有任务ID,计入非任务里程 |
| | | nonTaskMileage = nonTaskMileage.add(segDistance); |
| | | } |
| | | } |
| | | |
| | | // 设置去重后的任务数量 |
| | | taskCount = uniqueTaskIds.size(); |
| | | |
| | | } else { |
| | | // 4.3 降级方案:使用原有的时间重叠计算方式 |
| | | logger.debug("车辆ID: {} 日期: {} 使用降级方案:时间重叠计算(只有{}个分段有task_id,占比{}%)", |
| | | vehicleId, statDate, segmentsWithTask, (segmentsWithTask * 100.0 / segments.size())); |
| | | |
| | | List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); |
| | | |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | Date segStart = segment.getSegmentStartTime(); |
| | | Date segEnd = segment.getSegmentEndTime(); |
| | | BigDecimal segDistance = segment.getSegmentDistance() != null ? segment.getSegmentDistance() : BigDecimal.ZERO; |
| | | |
| | | // 计算该分段与任务时段的重叠比例 |
| | | double taskRatio = calculateTaskOverlapRatio(segStart, segEnd, taskIntervals); |
| | | |
| | | // 分摊里程 |
| | | BigDecimal taskDist = segDistance.multiply(BigDecimal.valueOf(taskRatio)); |
| | | BigDecimal nonTaskDist = segDistance.multiply(BigDecimal.valueOf(1 - taskRatio)); |
| | | |
| | | taskMileage = taskMileage.add(taskDist); |
| | | nonTaskMileage = nonTaskMileage.add(nonTaskDist); |
| | | } |
| | | |
| | | // 设置任务数量 |
| | | taskCount = taskIntervals == null ? 0 : taskIntervals.size(); |
| | | } |
| | | |
| | | // 计算任务里程占比 |
| | | BigDecimal taskRatio = BigDecimal.ZERO; |
| | | if (totalMileage.compareTo(BigDecimal.ZERO) > 0) { |
| | | taskRatio = taskMileage.divide(totalMileage, 4, RoundingMode.HALF_UP); |
| | | |
| | | // 数据校验:占比应在0-1之间,如果超出说明数据异常 |
| | | if (taskRatio.compareTo(BigDecimal.ONE) > 0) { |
| | | logger.warn("车辆ID: {} 日期: {} 任务里程占比异常: {} (任务里程:{}, 总里程:{}), 强制设为1.0", |
| | | vehicleId, statDate, taskRatio, taskMileage, totalMileage); |
| | | taskRatio = BigDecimal.ONE; |
| | | } else if (taskRatio.compareTo(BigDecimal.ZERO) < 0) { |
| | | logger.warn("车辆ID: {} 日期: {} 任务里程占比为负: {}, 强制设为0", |
| | | vehicleId, statDate, taskRatio); |
| | | taskRatio = BigDecimal.ZERO; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | // 5. 查询或创建统计记录 |
| | | VehicleMileageStats stats = vehicleMileageStatsMapper.selectByVehicleIdAndDate(vehicleId, statDate); |
| | | boolean isNew = (stats == null); |
| | |
| | | } |
| | | |
| | | // 6. 设置统计数据 |
| | | stats.setTotalMileage(totalMileage.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setTaskMileage(taskMileage.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setNonTaskMileage(nonTaskMileage.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setTotalMileage(totalDistance.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setTaskMileage(taskDistance.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setNonTaskMileage(nonTaskDistance.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setTaskRatio(taskRatio); |
| | | stats.setGpsPointCount(totalGpsPoints); |
| | | stats.setTaskCount(taskCount); |
| | |
| | | vehicleMileageStatsMapper.updateVehicleMileageStats(stats); |
| | | } |
| | | |
| | | logger.info("车辆ID: {} 日期: {} 从分段汇总完成 - 总里程: {}km, 任务里程: {}km, 非任务里程: {}km, 分段数: {}", |
| | | vehicleId, statDate, totalMileage, taskMileage, nonTaskMileage, segments.size()); |
| | | // logger.info("车辆ID: {} 日期: {} 从分段汇总完成 - 总里程: {}km, 任务里程: {}km, 非任务里程: {}km, 分段数: {}", |
| | | // vehicleId, statDate, totalMileage, taskMileage, nonTaskMileage, segments.size()); |
| | | |
| | | return stats; |
| | | |