340 行
9.7 KiB
PHP
340 行
9.7 KiB
PHP
<?php
|
|
namespace Common\Model;
|
|
|
|
use Common\Model\BaseModel;
|
|
|
|
/**
|
|
* 骑手位置模型
|
|
* Class RiderLocationModel
|
|
* @package Common\Model
|
|
*/
|
|
class RiderLocationModel extends BaseModel
|
|
{
|
|
/**
|
|
* 表名
|
|
* @var string
|
|
*/
|
|
protected $tableName = 'rider_location';
|
|
|
|
/**
|
|
* 添加位置记录
|
|
* @param array $data
|
|
* @return int|false
|
|
*/
|
|
public function addLocation($data)
|
|
{
|
|
return $this->add($data);
|
|
}
|
|
|
|
/**
|
|
* 批量添加位置记录
|
|
* @param array $dataList
|
|
* @return int|false
|
|
*/
|
|
public function addLocationBatch($dataList)
|
|
{
|
|
return $this->addAll($dataList);
|
|
}
|
|
|
|
/**
|
|
* 获取骑手最新位置
|
|
* @param int $riderId 骑手ID
|
|
* @return array
|
|
*/
|
|
public function getLatestLocation($riderId)
|
|
{
|
|
return $this->where(['rider_id' => $riderId])
|
|
->order('add_time DESC')
|
|
->find();
|
|
}
|
|
|
|
/**
|
|
* 获取骑手位置轨迹
|
|
* @param int $riderId 骑手ID
|
|
* @param string $startTime 开始时间
|
|
* @param string $endTime 结束时间
|
|
* @param int $limit 限制数量
|
|
* @return array
|
|
*/
|
|
public function getLocationTrack($riderId, $startTime = '', $endTime = '', $limit = 100)
|
|
{
|
|
$where = ['rider_id' => $riderId];
|
|
|
|
if (!empty($startTime)) {
|
|
$where['add_time'][] = ['egt', $startTime];
|
|
}
|
|
|
|
if (!empty($endTime)) {
|
|
$where['add_time'][] = ['elt', $endTime];
|
|
}
|
|
|
|
return $this->where($where)
|
|
->order('add_time ASC')
|
|
->limit($limit)
|
|
->select();
|
|
}
|
|
|
|
/**
|
|
* 获取指定范围内的骑手
|
|
* @param float $centerLat 中心纬度
|
|
* @param float $centerLng 中心经度
|
|
* @param float $radius 半径(公里)
|
|
* @param array $riderIds 指定骑手ID列表(可选)
|
|
* @return array
|
|
*/
|
|
public function getRidersInRange($centerLat, $centerLng, $radius, $riderIds = [])
|
|
{
|
|
// 计算经纬度范围
|
|
$latRange = $radius / 111; // 1度纬度约等于111公里
|
|
$lngRange = $radius / (111 * cos(deg2rad($centerLat))); // 经度范围需要考虑纬度
|
|
|
|
$minLat = $centerLat - $latRange;
|
|
$maxLat = $centerLat + $latRange;
|
|
$minLng = $centerLng - $lngRange;
|
|
$maxLng = $centerLng + $lngRange;
|
|
|
|
$where = [
|
|
'lat' => ['between', [$minLat, $maxLat]],
|
|
'lng' => ['between', [$minLng, $maxLng]]
|
|
];
|
|
|
|
if (!empty($riderIds)) {
|
|
$where['rider_id'] = ['in', $riderIds];
|
|
}
|
|
|
|
// 获取每个骑手的最新位置
|
|
$subQuery = $this->field('rider_id, MAX(add_time) as max_time')
|
|
->where($where)
|
|
->group('rider_id')
|
|
->buildSql();
|
|
|
|
$locations = $this->alias('rl1')
|
|
->join("({$subQuery}) rl2 ON rl1.rider_id = rl2.rider_id AND rl1.add_time = rl2.max_time")
|
|
->field('rl1.*')
|
|
->select();
|
|
|
|
// 精确计算距离并过滤
|
|
$result = [];
|
|
foreach ($locations as $location) {
|
|
$distance = $this->calculateDistance($centerLat, $centerLng, $location['lat'], $location['lng']);
|
|
if ($distance <= $radius) {
|
|
$location['distance'] = $distance;
|
|
$result[] = $location;
|
|
}
|
|
}
|
|
|
|
// 按距离排序
|
|
usort($result, function($a, $b) {
|
|
return $a['distance'] <=> $b['distance'];
|
|
});
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* 计算两点间距离
|
|
* @param float $lat1 纬度1
|
|
* @param float $lng1 经度1
|
|
* @param float $lat2 纬度2
|
|
* @param float $lng2 经度2
|
|
* @return float 距离(公里)
|
|
*/
|
|
public function calculateDistance($lat1, $lng1, $lat2, $lng2)
|
|
{
|
|
if (empty($lat1) || empty($lng1) || empty($lat2) || empty($lng2)) {
|
|
return 999;
|
|
}
|
|
|
|
$earthRadius = 6371; // 地球半径,单位:公里
|
|
|
|
$lat1Rad = deg2rad($lat1);
|
|
$lng1Rad = deg2rad($lng1);
|
|
$lat2Rad = deg2rad($lat2);
|
|
$lng2Rad = deg2rad($lng2);
|
|
|
|
$deltaLat = $lat2Rad - $lat1Rad;
|
|
$deltaLng = $lng2Rad - $lng1Rad;
|
|
|
|
$a = sin($deltaLat / 2) * sin($deltaLat / 2) +
|
|
cos($lat1Rad) * cos($lat2Rad) *
|
|
sin($deltaLng / 2) * sin($deltaLng / 2);
|
|
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
|
|
|
|
return $earthRadius * $c;
|
|
}
|
|
|
|
/**
|
|
* 清理过期位置记录
|
|
* @param int $days 保留天数
|
|
* @return int
|
|
*/
|
|
public function cleanExpiredLocations($days = 7)
|
|
{
|
|
$expireDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
|
// 删除过期的位置记录
|
|
return $this->where(['add_time' => ['lt', $expireDate]])->delete();
|
|
}
|
|
|
|
/**
|
|
* 获取骑手活动统计
|
|
* @param int $riderId 骑手ID
|
|
* @param string $date 日期
|
|
* @return array
|
|
*/
|
|
public function getRiderActivityStats($riderId, $date = '')
|
|
{
|
|
if (empty($date)) {
|
|
$date = date('Y-m-d');
|
|
}
|
|
|
|
$startTime = $date . ' 00:00:00';
|
|
$endTime = $date . ' 23:59:59';
|
|
|
|
$where = [
|
|
'rider_id' => $riderId,
|
|
'add_time' => ['between', [$startTime, $endTime]]
|
|
];
|
|
|
|
$locations = $this->where($where)
|
|
->order('add_time ASC')
|
|
->select();
|
|
|
|
if (empty($locations)) {
|
|
return [
|
|
'total_distance' => 0,
|
|
'active_time' => 0,
|
|
'location_count' => 0,
|
|
'first_location_time' => '',
|
|
'last_location_time' => ''
|
|
];
|
|
}
|
|
|
|
// 计算总距离
|
|
$totalDistance = 0;
|
|
for ($i = 1; $i < count($locations); $i++) {
|
|
$distance = $this->calculateDistance(
|
|
$locations[$i-1]['lat'],
|
|
$locations[$i-1]['lng'],
|
|
$locations[$i]['lat'],
|
|
$locations[$i]['lng']
|
|
);
|
|
$totalDistance += $distance;
|
|
}
|
|
|
|
// 计算活动时间
|
|
$firstTime = strtotime($locations[0]['add_time']);
|
|
$lastTime = strtotime($locations[count($locations)-1]['add_time']);
|
|
$activeTime = $lastTime - $firstTime;
|
|
|
|
return [
|
|
'total_distance' => round($totalDistance, 2),
|
|
'active_time' => $activeTime,
|
|
'location_count' => count($locations),
|
|
'first_location_time' => $locations[0]['add_time'],
|
|
'last_location_time' => $locations[count($locations)-1]['add_time']
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 获取区域内骑手密度
|
|
* @param float $centerLat 中心纬度
|
|
* @param float $centerLng 中心经度
|
|
* @param float $radius 半径(公里)
|
|
* @param int $timeRange 时间范围(分钟)
|
|
* @return array
|
|
*/
|
|
public function getRiderDensity($centerLat, $centerLng, $radius, $timeRange = 30)
|
|
{
|
|
$startTime = date('Y-m-d H:i:s', strtotime("-{$timeRange} minutes"));
|
|
|
|
// 计算经纬度范围
|
|
$latRange = $radius / 111;
|
|
$lngRange = $radius / (111 * cos(deg2rad($centerLat)));
|
|
|
|
$minLat = $centerLat - $latRange;
|
|
$maxLat = $centerLat + $latRange;
|
|
$minLng = $centerLng - $lngRange;
|
|
$maxLng = $centerLng + $lngRange;
|
|
|
|
$where = [
|
|
'lat' => ['between', [$minLat, $maxLat]],
|
|
'lng' => ['between', [$minLng, $maxLng]],
|
|
'add_time' => ['egt', $startTime]
|
|
];
|
|
|
|
// 获取活跃骑手数量
|
|
$activeRiders = $this->where($where)
|
|
->field('rider_id')
|
|
->group('rider_id')
|
|
->select();
|
|
|
|
$riderCount = count($activeRiders);
|
|
$area = pi() * $radius * $radius; // 圆形区域面积
|
|
$density = $area > 0 ? $riderCount / $area : 0;
|
|
|
|
return [
|
|
'rider_count' => $riderCount,
|
|
'area' => round($area, 2),
|
|
'density' => round($density, 4),
|
|
'radius' => $radius,
|
|
'time_range' => $timeRange
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 预测骑手到达时间
|
|
* @param int $riderId 骑手ID
|
|
* @param float $destLat 目标纬度
|
|
* @param float $destLng 目标经度
|
|
* @param float $avgSpeed 平均速度(公里/小时)
|
|
* @return array
|
|
*/
|
|
public function predictArrivalTime($riderId, $destLat, $destLng, $avgSpeed = 20)
|
|
{
|
|
// 获取骑手最新位置
|
|
$latestLocation = $this->getLatestLocation($riderId);
|
|
|
|
if (empty($latestLocation)) {
|
|
return [
|
|
'estimated_time' => 0,
|
|
'distance' => 0,
|
|
'error' => '无法获取骑手位置'
|
|
];
|
|
}
|
|
|
|
// 计算距离
|
|
$distance = $this->calculateDistance(
|
|
$latestLocation['lat'],
|
|
$latestLocation['lng'],
|
|
$destLat,
|
|
$destLng
|
|
);
|
|
|
|
// 计算预计时间(分钟)
|
|
$estimatedTime = ($distance / $avgSpeed) * 60;
|
|
|
|
// 考虑交通因素,增加20%的时间
|
|
$estimatedTime *= 1.2;
|
|
|
|
return [
|
|
'estimated_time' => round($estimatedTime, 0),
|
|
'distance' => round($distance, 2),
|
|
'avg_speed' => $avgSpeed,
|
|
'current_location' => [
|
|
'lat' => $latestLocation['lat'],
|
|
'lng' => $latestLocation['lng'],
|
|
'record_time' => $latestLocation['add_time']
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 删除位置记录
|
|
* @param array $where 删除条件
|
|
* @return int
|
|
*/
|
|
public function deleteLocation($where)
|
|
{
|
|
return $this->where($where)->delete();
|
|
}
|
|
} |