wlzboy
2025-11-16 f67945d53b20f6a45ae50b27d74c966eb1355bb4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
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 {
            // 查询所有活跃车辆
            List<Long> vehicleIds = vehicleGpsMapper.selectActiveVehicleIds();
            
            if (vehicleIds == null || vehicleIds.isEmpty()) {
                logger.info("没有找到活跃车辆");
                return 0;
            }
            
            int successCount = 0;
            for (Long vehicleId : vehicleIds) {
                try {
                    int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
                    if (segmentCount > 0) {
                        successCount++;
                    }
                } catch (Exception e) {
                    logger.error("计算车辆 {} 的分段里程失败", vehicleId, e);
                }
            }
            
            logger.info("批量分段里程计算完成 - 时间范围: {} 到 {}, 总车辆数: {}, 成功: {}", 
                       startTime, endTime, vehicleIds.size(), successCount);
            return successCount;
            
        } catch (Exception e) {
            logger.error("批量计算分段里程失败", e);
            throw new RuntimeException("批量计算失败: " + e.getMessage());
        }
    }
 
    @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();
            
            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());
                    
                    // 重新计算该车辆在该时间范围的分段里程
                    // 注意:这里会重新计算整个时间范围,确保边缘节点被正确处理
                    int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
                    
                    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 {
            // 获取配置的时间间隔(分钟)
            int segmentMinutes = configService.selectConfigByKey("gps.mileage.segment.minutes") != null 
                ? Integer.parseInt(configService.selectConfigByKey("gps.mileage.segment.minutes")) 
                : 5;
            
            // 获取计算方式配置
            String calculateMethod = configService.selectConfigByKey("gps.mileage.calculate.method");
            if (calculateMethod == null || calculateMethod.isEmpty()) {
                calculateMethod = "tianditu";
            }
            
            // 获取是否跳过已计算GPS点的配置
            String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
            boolean skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
            
            // 查询车辆在时间范围内的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数据", vehicleId, gpsList.size());
            
            // 按时间段分组GPS数据
            Map<Date, List<VehicleGps>> segmentedData = segmentGpsDataByTime(gpsList, segmentMinutes);
            
            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 (segmentGpsList.size() < 2) {
                    // 如果本段只有1个点,但有上一段的最后一个点,仍可计算跨段距离
                    if (segmentGpsList.size() == 1 && previousSegmentLastPoint != null) {
                        // 保留当前点作为下一段的前置点,但不创建记录
                        previousSegmentLastPoint = segmentGpsList.get(0);
                    }
                    continue; // 至少需要2个点才能计算距离
                }
                
                // 检查是否已存在该时间段的记录
                VehicleGpsSegmentMileage existing = segmentMileageMapper.selectByVehicleIdAndTime(vehicleId, segmentStartTime);
                if (existing != null) {
                    logger.debug("车辆 {} 时间段 {} 的分段里程已存在,跳过", vehicleId, segmentStartTime);
                    // 更新上一段最后一个点
                    previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                    continue;
                }
                
                // 计算时间段的结束时间
                Calendar cal = Calendar.getInstance();
                cal.setTime(segmentStartTime);
                cal.add(Calendar.MINUTE, segmentMinutes);
                Date segmentEndTime = cal.getTime();
                
                // 计算该时间段的里程(包括跨段距离)
                BigDecimal distance = calculateSegmentDistanceWithGap(segmentGpsList, calculateMethod, previousSegmentLastPoint);
                
                // 收集GPS ID列表(包括上一段的最后一个点,因为跨段间隙距离也用到了它)
                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());
                    }
                }
                String gpsIds = gpsIdList.stream()
                    .map(String::valueOf)
                    .collect(java.util.stream.Collectors.joining(","));
                
                // 创建分段里程记录
                VehicleGpsSegmentMileage segment = new VehicleGpsSegmentMileage();
                segment.setVehicleId(vehicleId);
                
                // 从GPS数据或车辆表获取车牌号
                String vehicleNo = segmentGpsList.get(0).getVehicleNo();
                if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
                    // GPS数据中没有车牌号,从车辆表查询
                    VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
                    if (vehicleInfo != null) {
                        vehicleNo = vehicleInfo.getVehicleNo();
                    }
                }
                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()); // GPS点数:包括边缘点 + 当前段的点
                segment.setGpsIds(gpsIds); // 设置GPS ID列表
                segment.setCalculateMethod(calculateMethod);
                
                // 查询并关联正在执行的任务
                associateActiveTask(segment, vehicleId, segmentStartTime, segmentEndTime);
                
                // 保存到数据库
                segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
                
                // 更新上一段最后一个点,供下一段使用
                previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                
                // 记录已计算的GPS点到状态表(如果开启了重复计算控制)
                if (skipCalculated && segment.getSegmentId() != null) {
                    for (Long gpsId : gpsIdList) {
                        try {
                            segmentMileageMapper.insertGpsCalculated(gpsId, segment.getSegmentId(), vehicleId);
                        } catch (Exception e) {
                            // 忽略重复键异常,继续处理
                            logger.debug("记录GPS计算状态失败,可能已存在: gpsId={}", gpsId);
                        }
                    }
                }
                
                savedCount++;
                
                logger.debug("车辆 {} 时间段 {} 到 {} 里程: {}km, GPS点数: {}, GPS IDs: {}", 
                           vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(), 
                           gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
            }
            
            logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount);
            
            // 自动触发汇总生成每日统计(如果有数据被保存)
            if (savedCount > 0) {
                try {
                    // 获取涉及的日期范围,触发汇总
                    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());
                    }
                    
                    // 对每个涉及的日期,触发汇总
                    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);
                }
            }
            
            return savedCount;
            
        } catch (Exception e) {
            logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
            throw new RuntimeException("计算分段里程失败: " + e.getMessage());
        }
    }
 
    /**
     * 将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点列表
     * @param calculateMethod 计算方式
     * @param previousLastPoint 上一个时间段的最后一个点(可为null)
     */
    private BigDecimal calculateSegmentDistanceWithGap(List<VehicleGps> gpsList, String calculateMethod, VehicleGps previousLastPoint) {
        if (gpsList == null || gpsList.size() < 2) {
            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. 再计算当前段内部的距离
        BigDecimal segmentInternalDistance;
        if ("tianditu".equalsIgnoreCase(calculateMethod)) {
            segmentInternalDistance = calculateDistanceByTianditu(gpsList);
        } else {
            segmentInternalDistance = calculateDistanceByHaversine(gpsList);
        }
        totalDistance = totalDistance.add(segmentInternalDistance);
        
        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.selectActiveTasksByVehicleId(vehicleId);
            
            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);
    }
}