如何基于数据库中单条仅含单个经纬度的多组数据计算车辆行驶距离
解决数据库中多组车辆定位数据的行驶距离计算问题
你已经搞定了两点距离计算的核心函数,现在卡壳在怎么把数据库里零散的车辆定位点串起来算总行驶距离对吧?核心思路就是把同车辆的连续定位点配对(前一个点是起点,后一个是终点),然后依次计算每段距离再累加。下面给你两种实用方案:
方案一:通过SQL查询获取连续点对(推荐,适合大数据量)
首先得通过SQL把每个定位点和它的下一个点(同vehicle_id,按id排序——这里默认id递增代表定位时间先后)关联起来,拿到每一段行程的起点和终点坐标。
方式1:使用窗口函数(MySQL 8.0+ / MariaDB 10.2+)
窗口函数LEAD()可以直接抓取同组内的下一行数据,写法简洁高效:
SELECT dc.vehicle_id, dc.latitude AS start_lat, dc.longitude AS start_lon, LEAD(dc.latitude) OVER (PARTITION BY dc.vehicle_id ORDER BY dc.id) AS end_lat, LEAD(dc.longitude) OVER (PARTITION BY dc.vehicle_id ORDER BY dc.id) AS end_lon FROM distance_calculate dc ORDER BY dc.vehicle_id, dc.id;
查询结果里,每个点都会带上它的下一个点坐标,最后一个点的end_lat和end_lon会是NULL(因为没有后续点)。
方式2:使用自连接(兼容低版本MySQL)
如果你的MySQL版本不支持窗口函数,用自连接也能实现:
SELECT dc1.vehicle_id, dc1.latitude AS start_lat, dc1.longitude AS start_lon, dc2.latitude AS end_lat, dc2.longitude AS end_lon FROM distance_calculate dc1 LEFT JOIN distance_calculate dc2 ON dc1.vehicle_id = dc2.vehicle_id AND dc2.id = dc1.id + 1 ORDER BY dc1.vehicle_id, dc1.id;
⚠️ 注意:这个写法依赖id是连续递增的,如果id可能有断层(比如删除过数据),建议用专门的时间字段(如果表中有)来排序,或者用变量生成连续序号。
方案二:PHP端处理查询结果
拿到上述SQL的结果后,就可以循环调用你已有的distance函数,累加每一段的距离了。这里给你兼容多车辆的完整示例:
// 假设你已经通过PDO/MySQLi从数据库获取到点对数据,存储在$points数组中 $points = [ ['vehicle_id' => 1, 'start_lat' => '23.247137', 'start_lon' => '69.700527', 'end_lat' => '23.252473', 'end_lon' => '69.685735'], ['vehicle_id' => 1, 'start_lat' => '23.252473', 'start_lon' => '69.685735', 'end_lat' => '23.249922', 'end_lon' => '69.686488'], // ... 其他点对数据 ]; $totalDistances = []; $unit = 'K'; // 可选单位:K=公里,M=英里,N=海里 foreach ($points as $pointPair) { // 跳过没有终点的最后一个点 if (empty($pointPair['end_lat']) || empty($pointPair['end_lon'])) { continue; } $vid = $pointPair['vehicle_id']; // 把字符串类型的经纬度转成浮点数,避免计算错误 $startLat = (float)$pointPair['start_lat']; $startLon = (float)$pointPair['start_lon']; $endLat = (float)$pointPair['end_lat']; $endLon = (float)$pointPair['end_lon']; // 调用你的距离计算函数 $segmentDistance = distance($startLat, $startLon, $endLat, $endLon, $unit); // 按车辆ID累加总距离 if (!isset($totalDistances[$vid])) { $totalDistances[$vid] = 0; } $totalDistances[$vid] += $segmentDistance; } // 输出所有车辆的总行驶距离 foreach ($totalDistances as $vid => $distance) { echo "车辆ID {$vid} 的总行驶距离:" . number_format($distance, 2) . " {$unit}\n"; } // 你的距离计算函数(保持原逻辑不变) function distance($lat1, $lon1, $lat2, $lon2, $unit) { if (($lat1 == $lat2) && ($lon1 == $lon2)) { return 0; }else { $theta = $lon1 - $lon2; $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta)); $dist = acos($dist); $dist = rad2deg($dist); $miles = $dist * 60 * 1.1515; $unit = strtoupper($unit); if ($unit == "K") { return ($miles * 1.609344); } else if ($unit == "N") { return ($miles * 0.8684); } else { return $miles; } } }
关键注意事项
- 数据顺序正确性:一定要确保定位点按时间顺序排列,如果表中有
timestamp字段,优先用它排序,避免id不连续导致的顺序错误。 - 数据类型转换:数据库里
latitude和longitude是varchar类型,必须转成浮点数才能参与计算,否则会出现字符串运算的错误。 - 空值处理:最后一个点没有后续点,循环时要跳过,避免传入空值导致函数报错。
内容的提问来源于stack exchange,提问作者Hitesh Tripathi
相关产品推荐
相关产品推荐

