Files
park/application/api/controller/YqScreen.php
MeSHard b22d09bd39 init
2025-12-01 11:19:23 +08:00

1631 lines
66 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\api\controller;
use app\admin\model\Admin;
use app\admin\model\yq\alarm\Alarm;
use app\admin\model\yq\alarm\Trend;
use app\admin\model\yq\base_config\EventType;
use app\admin\model\yq\electronic_waybill\Waybill;
use app\admin\model\yq\park\Park;
use app\admin\model\yq\perimeter\EnterpriseCheck;
use app\admin\model\yq\vehicle\LineLog;
use app\admin\model\yq\vehicle\ParkLineLog;
use app\common\controller\Api;
use app\admin\model\yq\vehicle\Vehicle;
use app\admin\model\yq\base_config\Perimeter;
use fast\Date;
use think\Db;
class YqScreen extends Api
{
protected $noNeedLogin = ['*'];
protected $noNeedRight = ['*'];
//登录查验
public function login_check()
{
// public function login_check($param)
// {
//
// if (empty($param['ad']) || empty($param['salt'])) {
// $this->error( '异常操作!', ['status' => false]);
// }
// $where['admin_username'] = $param['ad'];
// $where['admin_pwd_salt'] = $param['salt'];
// $admin = Db::name('admin')->where($where)->find();
$admin = Admin::get(1);
if ($admin) {
$this->success('正常!', ['status' => true]);
} else {
$this->error('失败!', ['status' => false]);
}
}
//大屏地图车辆列表
public function screen_list()
{
$map['is_del'] = array('in', '1,2');
$field = 'id,vehicleNo,longitude,latitude,longitude_84,latitude_84,vec1,vehicle_type,is_waybill,
is_violations,qr_color,positionTime,is_del,zai';
$vehicle_list = Vehicle::where($map)->field($field)->select();
//待优化运单查询
$w_field = 'tow_license,w_mission,waybill_loading,waybill_unloading,sale_product,cargocount,driver_tel';
$day_waybill_list = Waybill::whereTime('waybill_date', 'd')->field($w_field)->select();
$temp = array_column($day_waybill_list, 'tow_license');
if ($vehicle_list) {
foreach ($vehicle_list as $k => $v) {
$vehicle_list[$k]['waybill_status'] = false;
$v_id = in_array($v['vehicleNo'], $temp);
if ($v_id) {
$vehicle_list[$k]['waybill_status'] = true;
$waybill_info = Waybill::whereTime('waybill_date', 'd')->where('tow_license', $v['vehicleNo'])->field($w_field)->find();
$vehicle_list[$k]['waybill_info'] = $waybill_info;
}
}
}
//明宇停车场count车辆总数enter_count当日进入车辆数当日出去车辆数
$mingyu['count'] = Vehicle::where($map)->where('perimeter_id', 76)->field('id')->count();
$mingyu['enter_count'] = LineLog::whereTime('create_time', 'd')->where(['is_del' => 1, 'perimeter_id' => 76, 'type' => 1])->count();
$mingyu['out_count'] = LineLog::whereTime('create_time', 'd')->where(['is_del' => 1, 'perimeter_id' => 76, 'type' => 2])->count();
$data['vehicle_list'] = $vehicle_list;
$data['mingyu'] = $mingyu;
$this->success('请求成功!', $data);
}
//大屏地图车辆列表
public function screen_list22()
{
$map['is_del'] = array('in', '1,2');
$field = 'id,vehicleNo,longitude,latitude,longitude_84,latitude_84,vec1,vehicle_type,is_waybill,
is_violations,qr_color,positionTime,is_del,zai';
$vehicle_list = Vehicle::where($map)->field($field)->select();
if ($vehicle_list) {
foreach ($vehicle_list as $k => $v) {
$w_field = 'w_mission,waybill_loading,waybill_unloading,sale_product,cargocount,driver_tel';
$waybill_info = Db::name('waybill_c')->whereTime('waybill_date', 'd')->where('tow_license', $v['vehicleNo'])->field($w_field)->find();
$vehicle_list[$k]['waybill_status'] = false;
if ($waybill_info) {
$vehicle_list[$k]['waybill_status'] = true;
$vehicle_list[$k]['waybill_info'] = $waybill_info;
}
}
}
//明宇停车场count车辆总数enter_count当日进入车辆数当日出去车辆数
$mingyu['count'] = Vehicle::where($map)->where('perimeter_id', 76)->field('id')->count();
$mingyu['enter_count'] = LineLog::whereTime('create_time', 'd')->where(['is_del' => 1, 'perimeter_id' => 76, 'type' => 1])->count();
$mingyu['out_count'] = LineLog::whereTime('create_time', 'd')->where(['is_del' => 1, 'perimeter_id' => 76, 'type' => 2])->count();
$data['vehicle_list'] = $vehicle_list;
$data['mingyu'] = $mingyu;
$this->success('请求成功!', $data);
}
//大屏-总览
public function overview()
{
$map['is_del'] = 1;
//园区基本情况
//一级企业 二级企业 三级企业
$info['enterprise_count'] = Perimeter::group('region_lv')->where($map)->field('region_lv,count(id) as num')->select();
//片区情况 - 统计每个片区的车辆数量取前3个
$areaStats = $this->getAreaVehicleStatistics();
$info['area'] = [];
// 取前3个片区的车辆数量如果不足3个则补0
for ($i = 0; $i < 3; $i++) {
$info['area'][] = isset($areaStats[$i]) ? $areaStats[$i]['value'] : 0;
}
//道路情况 - 统计道路上的车辆数量总和
$roadStats = $this->getRoadVehicleStatistics();
$info['road_count'] = 0;
if ($roadStats) {
foreach ($roadStats as $road) {
$info['road_count'] += $road['count'];
}
}
//卡口流量统计
$info['road_checkpoints'] = [
'name' => ['卡1', '卡2', '卡2', '卡2', '卡2', '卡2'],//卡口名称
'drive_in' => [35, 32, 36, 47, 14, 60],//驶入
'drive_out' => [47, 20, 16, 52, 40, 18]//驶出
];
//近30天报警统计
$info['alarm_count'] = [];
// 计算30天前的日期
$startDate = date('Y-m-d 00:00:00', strtotime('-30 days'));
$endDate = date('Y-m-d 23:59:59');
$event_type = EventType::select();
foreach ($event_type as $k => $v) {
$info['alarm_count'][] = [
'name' => $v['name'],
'value' => Alarm::where('type', $v['id'])
->where('create_time', '>=', $startDate)
->where('create_time', '<=', $endDate)
->count()
];
}
//报警处理情况 - 统计近30天的报警处理情况
$alarmMap = [
'is_del' => 1,
];
// 已处理res_status = 1已响应或 feedback_status = 1已反馈
$processed = Alarm::where($alarmMap)
->where('create_time', '>=', $startDate)
->where('create_time', '<=', $endDate)
->where(function($query) {
$query->where('res_status', 1)
->whereOr('feedback_status', 1);
})
->count();
// 未处理res_status = 0未响应且 feedback_status = 0未反馈
$untreated = Alarm::where($alarmMap)
->where('create_time', '>=', $startDate)
->where('create_time', '<=', $endDate)
->where('res_status', 0)
->where('feedback_status', 0)
->count();
$he = $processed + $untreated;
$info['alarm_handling'] = [
'count' => array_sum(array_column($info['alarm_count'], 'value')),
'processed' => $processed,
'untreated' => $untreated,
'he' => $he,
'processed_ratio' => $he == 0 ? 0 : round(($processed / $he) * 100, 2),
'untreated_ratio' => $he == 0 ? 0 : round(($untreated / $he) * 100, 2),
];
//园区危化品车辆统计 - 在线/离线根据定位时间判断10分钟内更新为在线
$vehicleMap = ['is_del' => ['in', '1,2']];
// 计算10分钟前的时间戳
$onlineTimeThreshold = time() - 600; // 10分钟 = 600秒
// 查询所有车辆
$allVehicles = Vehicle::where($vehicleMap)
->field('id,vehicleNo,positionTime')
->select();
$online = 0;
$offline = 0;
if ($allVehicles) {
foreach ($allVehicles as $vehicle) {
if (empty($vehicle['positionTime'])) {
$offline++;
continue;
}
// 将定位时间转换为时间戳
$positionTimestamp = is_numeric($vehicle['positionTime'])
? $vehicle['positionTime']
: strtotime($vehicle['positionTime']);
// 如果定位时间在10分钟内则是在线
if ($positionTimestamp >= $onlineTimeThreshold) {
$online++;
} else {
$offline++;
}
}
}
$he = $online + $offline;
$info['vehicle'] = [
'online' => $online,
'offline' => $offline,
'online_ratio' => $he == 0 ? 0 : round(($online / $he) * 100, 2),
'offline_ratio' => $he == 0 ? 0 : round(($offline / $he) * 100, 2)
];
//危化品车流量top5 - 按装货单位统计车流量(装货+卸货)
$waybillMap = [
'is_del' => 1,
'waybill_loading' => ['<>', ''], // 装货单位不为空
];
// 统计每个装货单位的装货数量
$loadingStats = Db::name('waybill')
->where($waybillMap)
->where('w_mission', '装货')
->field('waybill_loading, COUNT(*) as count')
->group('waybill_loading')
->select();
// 统计每个装货单位的卸货数量
$unloadingStats = Db::name('waybill')
->where($waybillMap)
->where('w_mission', '卸货')
->field('waybill_loading, COUNT(*) as count')
->group('waybill_loading')
->select();
// 构建装货单位到数量的映射
$loadingMap = [];
$unloadingMap = [];
if ($loadingStats) {
foreach ($loadingStats as $stat) {
$loadingMap[$stat['waybill_loading']] = intval($stat['count']);
}
}
if ($unloadingStats) {
foreach ($unloadingStats as $stat) {
$unloadingMap[$stat['waybill_loading']] = intval($stat['count']);
}
}
// 合并所有装货单位,计算总车流量(装货+卸货)
$allEnterprises = array_unique(array_merge(array_keys($loadingMap), array_keys($unloadingMap)));
$trafficData = [];
foreach ($allEnterprises as $enterprise) {
$loadingCount = isset($loadingMap[$enterprise]) ? $loadingMap[$enterprise] : 0;
$unloadingCount = isset($unloadingMap[$enterprise]) ? $unloadingMap[$enterprise] : 0;
$totalCount = $loadingCount + $unloadingCount;
$trafficData[] = [
'waybill_loading' => $enterprise,
'loading' => $loadingCount,
'unloading' => $unloadingCount,
'total' => $totalCount
];
}
// 按总车流量排序取前5个
usort($trafficData, function($a, $b) {
return $b['total'] - $a['total'];
});
$top5 = array_slice($trafficData, 0, 5);
// 构建返回数据
$nameList = [];
$loadingList = [];
$unloadingList = [];
foreach ($top5 as $item) {
$nameList[] = [
'waybill_loading' => $item['waybill_loading'],
'count' => $item['total']
];
$loadingList[] = $item['loading'];
$unloadingList[] = $item['unloading'];
}
// 如果不足5个用空数据补齐
while (count($nameList) < 5) {
$nameList[] = ['waybill_loading' => '', 'count' => 0];
$loadingList[] = 0;
$unloadingList[] = 0;
}
$info['traffic_volume'] = [
'name' => $nameList,
'loading' => $loadingList, // 装货数量
'unloading' => $unloadingList // 卸货数量
];
//全年运单统计 - 统计当前年份每个月的运单数量
$year = date('Y');
$startDate = "{$year}-01-01 00:00:00";
$endDate = "{$year}-12-31 23:59:59";
// 查询实际数据(只统计未删除的运单)
$waybillMap = ['is_del' => 1];
$actualStats = Db::name('waybill')
->where($waybillMap)
->where('create_time', '>=', $startDate)
->where('create_time', '<=', $endDate)
->field("DATE_FORMAT(create_time, '%Y-%m') as month, COUNT(*) as count")
->group("DATE_FORMAT(create_time, '%Y-%m')")
->select();
$monthlyData = [];
foreach ($actualStats as $stat) {
$month = date('m', strtotime($stat['month']));
$monthlyData[$month] = [
'month' => $month,
'year_month' => $stat['month'],
'count' => intval($stat['count']),
];
}
// 构建完整的12个月数据包含空月份
$fullYearStats = [];
for ($i = 1; $i <= 12; $i++) {
$month = str_pad($i, 2, '0', STR_PAD_LEFT);
$yearMonth = "{$year}-{$month}";
if (isset($monthlyData[$month])) {
// 有数据的月份
$fullYearStats[] = $monthlyData[$month];
} else {
// 无数据的月份补零
$fullYearStats[] = [
'month' => $month,
'year_month' => $yearMonth,
'count' => 0,
];
}
}
$info['year'] = $fullYearStats;
$this->success('请求成功!', $info);
}
//大屏-车辆码
public function vehicle_code()
{
$map['is_del'] = array('in', '1,2');
//绿码车数量
$info['green_code_count'] = Vehicle::where($map)->where('qr_color', 1)->count();
//黄码车数量
$info['yellow_code_count'] = Vehicle::where($map)->where('qr_color', 2)->count();
//红码车数量
$info['red_code_count'] = Vehicle::where($map)->where('qr_color', 3)->count();
//报警列表 - 只显示当天的报警
$wh_police['is_del'] = 1;
$wh_police['type'] = array('in', '1,2,3,4,5,6');
$field = 'license,name,trigger_type,create_time';
// 查询当天的报警
$police_list['count'] = Alarm::where($wh_police)->whereTime('create_time', 'd')->count();
$police_list['list'] = Alarm::where($wh_police)->whereTime('create_time', 'd')->field($field)->order('create_time desc')->select();
// 如果当天没有报警显示最近7天的报警
if ($police_list['count'] == 0) {
$police_list['count'] = Alarm::where($wh_police)->whereTime('create_time', '-7 days')->count();
$police_list['list'] = Alarm::where($wh_police)->whereTime('create_time', '-7 days')->field($field)->order('create_time desc')->limit(50)->select();
}
$data['info'] = $info;//基础信息-左
$data['police_list'] = $police_list;//运单信息-右3
$this->success('请求成功!', $data);
}
//大屏-车辆动态
public function vehicle_trend()
{
$map['is_del'] = array('in', '1,2');
$park_info = Db::name('perimeter')->where('id', 1)->field('enter_vehicle,out_vehicle')->find();
// 园区围栏坐标(用于判断进出园)
$max_path_json = '[{"lng":"107.01527","lat":"29.895601"},{"lng":"106.997876","lat":"29.89281"},{"lng":"106.991391","lat":"29.887939"},{"lng":"106.985853","lat":"29.880405"},{"lng":"106.981835","lat":"29.871026"},{"lng":"106.960543","lat":"29.849142"},{"lng":"106.950737","lat":"29.83564"},{"lng":"106.945862","lat":"29.824089"},{"lng":"106.943563","lat":"29.814148"},{"lng":"106.945054","lat":"29.799754"},{"lng":"106.942764","lat":"29.783607"},{"lng":"106.970332","lat":"29.766519"},{"lng":"106.983273","lat":"29.751322"},{"lng":"107.017612","lat":"29.737776"},{"lng":"107.050425","lat":"29.734115"},{"lng":"107.064458","lat":"29.737683"},{"lng":"107.064744","lat":"29.739318"},{"lng":"107.058942","lat":"29.748447"},{"lng":"107.055205","lat":"29.762378"},{"lng":"107.071581","lat":"29.77504"},{"lng":"107.086248","lat":"29.789923"},{"lng":"107.088111","lat":"29.796775"},{"lng":"107.084106","lat":"29.807387"},{"lng":"107.080959","lat":"29.81142"},{"lng":"107.068298","lat":"29.813474"},{"lng":"107.06422","lat":"29.813406"},{"lng":"107.060435","lat":"29.82112"},{"lng":"107.050628","lat":"29.827207"},{"lng":"107.04726","lat":"29.828623"},{"lng":"107.046543","lat":"29.829966"},{"lng":"107.04701","lat":"29.830878"},{"lng":"107.04712","lat":"29.834263"},{"lng":"107.044827","lat":"29.838826"},{"lng":"107.053757","lat":"29.842875"},{"lng":"107.056479","lat":"29.849047"},{"lng":"107.057124","lat":"29.85153"},{"lng":"107.056802","lat":"29.853468"},{"lng":"107.060415","lat":"29.86047"},{"lng":"107.061665","lat":"29.864841"},{"lng":"107.054919","lat":"29.881592"},{"lng":"107.052508","lat":"29.894119"},{"lng":"107.040451","lat":"29.898403"},{"lng":"107.028579","lat":"29.89536"},{"lng":"107.014946","lat":"29.895825"},{"lng":"106.997804","lat":"29.89278"}]';
$max_path_arr = json_decode($max_path_json, true);
// 初始化变量
$enterByHour = array_fill(0, 24, 0);
$outByHour = array_fill(0, 24, 0);
// 先从 vehicle_park_line_log 表查询(如果已有数据)
// 注意vehicle_park_line_log 表的 is_del 字段可能不存在或值不同,所以不添加 is_del 条件
$info['enter_count'] = ParkLineLog::whereTime('create_time', 'd')->where('type', 1)->where('is_del', 1)->count();
$info['out_count'] = ParkLineLog::whereTime('create_time', 'd')->where('type', 2)->where('is_del', 1)->count();
// 如果 vehicle_park_line_log 表没有数据,从轨迹数据实时统计
if ($info['enter_count'] == 0 && $info['out_count'] == 0) {
$enterCount = 0;
$outCount = 0;
// 查询当天的轨迹数据
$todayStart = date('Y-m-d 00:00:00');
$todayEnd = date('Y-m-d 23:59:59');
$vehicleLines = Db::name('vehicle_line')
->where('create_time', '>=', $todayStart)
->where('create_time', '<=', $todayEnd)
->where('is_del', 1)
->field('vehicleNo,line')
->select();
if (empty($vehicleLines)) {
// 如果没有轨迹数据返回0
$info['enter_count'] = 0;
$info['out_count'] = 0;
} else {
foreach ($vehicleLines as $lineRecord) {
$lineData = json_decode($lineRecord['line'], true);
if (!is_array($lineData) || empty($lineData)) {
continue;
}
// 遍历轨迹点,判断进出状态(允许同一辆车多次进出)
foreach ($lineData as $o => $point) {
if (empty($point['coordinate']) || empty($point['create_time'])) {
continue;
}
// 解析坐标
$coords = explode(',', $point['coordinate']);
if (count($coords) < 2) {
continue;
}
$point_coord = [
'lng' => floatval($coords[0]),
'lat' => floatval($coords[1])
];
// 判断当前点是否在园区内
$vehicle_exist = $this->is_point_in_polygon($point_coord, $max_path_arr);
// 判断上一个点是否在园区内
$up_exist = false;
if ($o > 0 && isset($lineData[$o-1]['coordinate'])) {
$up_coords = explode(',', $lineData[$o-1]['coordinate']);
if (count($up_coords) >= 2) {
$up_point = [
'lng' => floatval($up_coords[0]),
'lat' => floatval($up_coords[1])
];
$up_exist = $this->is_point_in_polygon($up_point, $max_path_arr);
}
} elseif ($o == 0) {
// 第一个点,假设上一个点在园区外
$up_exist = false;
}
// 判断是否发生进出变化
if ($vehicle_exist && !$up_exist) {
// 从园区外进入园区内 - 进园
$enterCount++;
// 统计该小时
$pointTime = strtotime($point['create_time']);
if ($pointTime !== false) {
$hour = (int)date('H', $pointTime);
if ($hour >= 0 && $hour < 24) {
$enterByHour[$hour]++;
}
}
} elseif (!$vehicle_exist && $up_exist) {
// 从园区内离开到园区外 - 出园
$outCount++;
// 统计该小时
$pointTime = strtotime($point['create_time']);
if ($pointTime !== false) {
$hour = (int)date('H', $pointTime);
if ($hour >= 0 && $hour < 24) {
$outByHour[$hour]++;
}
}
}
}
}
$info['enter_count'] = $enterCount;
$info['out_count'] = $outCount;
}
}
// 统计每小时进出园次数
$info['park'] = [];
$time_arr = [];
for ($x = 0; $x < 24; $x++) {
$time_arr[$x]['s_time'] = date('Y-m-d ') . $x . ':00:00';
$time_arr[$x]['e_time'] = date('Y-m-d ') . $x . ':59:59';
$info['park']['time'][] = $x;
}
// 如果 vehicle_park_line_log 表有数据,从表查询
if ($info['enter_count'] > 0 || $info['out_count'] > 0) {
foreach ($time_arr as $k => $v) {
$enter = ParkLineLog::where('create_time', '>=', $v['s_time'])
->where('create_time', '<=', $v['e_time'])
->where('type', 1)
->where('is_del', 1)
->count();
$out = ParkLineLog::where('create_time', '>=', $v['s_time'])
->where('create_time', '<=', $v['e_time'])
->where('type', 2)
->where('is_del', 1)
->count();
$info['park']['enter'][] = $enter;
$info['park']['out'][] = $out;
}
} else {
// 如果表没有数据,使用从轨迹数据统计的结果
foreach ($time_arr as $k => $v) {
$info['park']['enter'][] = isset($enterByHour[$k]) ? $enterByHour[$k] : 0;
$info['park']['out'][] = isset($outByHour[$k]) ? $outByHour[$k] : 0;
}
}
//片区车流统计 - 统计每个企业区域region_type=6内的车辆数量
$info['area'] = $this->getAreaVehicleStatistics();
//道路情况 - 统计每条道路上的真实车辆数量
$info['road_count'] = $this->getRoadVehicleStatistics();
// //园区停止车辆数
// $info['stop_count'] = Vehicle::where($map)->where('vec1', 0)->count();
// //园区行驶车辆数
// $info['driving_count'] = Vehicle::where($map)->where('vec1', '>', 0)->count();
// 园区内行驶停止车辆统计 - 使用轨迹数据统计每个小时的车辆数量
// 获取园区围栏坐标(使用与 vehicle_log 相同的围栏)
$max_path_json = '[{"lng":"107.01527","lat":"29.895601"},{"lng":"106.997876","lat":"29.89281"},{"lng":"106.991391","lat":"29.887939"},{"lng":"106.985853","lat":"29.880405"},{"lng":"106.981835","lat":"29.871026"},{"lng":"106.960543","lat":"29.849142"},{"lng":"106.950737","lat":"29.83564"},{"lng":"106.945862","lat":"29.824089"},{"lng":"106.943563","lat":"29.814148"},{"lng":"106.945054","lat":"29.799754"},{"lng":"106.942764","lat":"29.783607"},{"lng":"106.970332","lat":"29.766519"},{"lng":"106.983273","lat":"29.751322"},{"lng":"107.017612","lat":"29.737776"},{"lng":"107.050425","lat":"29.734115"},{"lng":"107.064458","lat":"29.737683"},{"lng":"107.064744","lat":"29.739318"},{"lng":"107.058942","lat":"29.748447"},{"lng":"107.055205","lat":"29.762378"},{"lng":"107.071581","lat":"29.77504"},{"lng":"107.086248","lat":"29.789923"},{"lng":"107.088111","lat":"29.796775"},{"lng":"107.084106","lat":"29.807387"},{"lng":"107.080959","lat":"29.81142"},{"lng":"107.068298","lat":"29.813474"},{"lng":"107.06422","lat":"29.813406"},{"lng":"107.060435","lat":"29.82112"},{"lng":"107.050628","lat":"29.827207"},{"lng":"107.04726","lat":"29.828623"},{"lng":"107.046543","lat":"29.829966"},{"lng":"107.04701","lat":"29.830878"},{"lng":"107.04712","lat":"29.834263"},{"lng":"107.044827","lat":"29.838826"},{"lng":"107.053757","lat":"29.842875"},{"lng":"107.056479","lat":"29.849047"},{"lng":"107.057124","lat":"29.85153"},{"lng":"107.056802","lat":"29.853468"},{"lng":"107.060415","lat":"29.86047"},{"lng":"107.061665","lat":"29.864841"},{"lng":"107.054919","lat":"29.881592"},{"lng":"107.052508","lat":"29.894119"},{"lng":"107.040451","lat":"29.898403"},{"lng":"107.028579","lat":"29.89536"},{"lng":"107.014946","lat":"29.895825"},{"lng":"106.997804","lat":"29.89278"}]';
$max_path_arr = json_decode($max_path_json, true);
// 获取当前时间
$currentTime = time();
$currentHour = (int)date('H', $currentTime);
$todayDate = date('Y-m-d', $currentTime);
// 初始化今天24小时的数据数组00:00-23:59
$fullStats = [];
$fullStats2 = [];
for ($i = 0; $i < 24; $i++) {
$hourTimeShort = sprintf('%02d:00', $i);
$fullStats[] = [
'hour_time' => $hourTimeShort,
'order_count' => 0
];
$fullStats2[] = [
'hour_time' => $hourTimeShort,
'order_count' => 0
];
}
// 查询当天的轨迹数据
$todayStart = date('Y-m-d 00:00:00');
$todayEnd = date('Y-m-d 23:59:59');
$vehicleLines = Db::name('vehicle_line')
->where('create_time', '>=', $todayStart)
->where('create_time', '<=', $todayEnd)
->where('is_del', 1)
->field('vehicleNo,line')
->select();
// 批量获取所有车辆的速度信息(用于旧数据备选,如果轨迹点没有保存速度)
$allVehicleNos = [];
foreach ($vehicleLines as $lineRecord) {
$allVehicleNos[] = $lineRecord['vehicleNo'];
}
$allVehicleNos = array_unique($allVehicleNos);
$vehicleSpeedMap = [];
if (!empty($allVehicleNos)) {
$vehicles = Db::name('vehicle')
->where('vehicleNo', 'in', $allVehicleNos)
->where($map)
->field('vehicleNo,vec1')
->select();
foreach ($vehicles as $v) {
$vehicleSpeedMap[$v['vehicleNo']] = $v['vec1'];
}
}
// 统计每个小时的车辆数量(使用集合去重,避免同一车辆在同一小时被重复计算)
$hourVehicleMap = []; // [小时索引 => [vehicleNo => true]]
$speedStats = ['has_speed_in_point' => 0, 'no_speed_in_point' => 0, 'driving_count' => 0, 'stop_count' => 0];
foreach ($vehicleLines as $lineRecord) {
$lineData = json_decode($lineRecord['line'], true);
if (!is_array($lineData) || empty($lineData)) {
continue;
}
$vehicleNo = $lineRecord['vehicleNo'];
$defaultSpeed = isset($vehicleSpeedMap[$vehicleNo]) ? $vehicleSpeedMap[$vehicleNo] : 0;
// 遍历轨迹点,按小时统计
foreach ($lineData as $point) {
if (empty($point['coordinate']) || empty($point['create_time'])) {
continue;
}
// 解析坐标
$coords = explode(',', $point['coordinate']);
if (count($coords) < 2) {
continue;
}
$point_coord = [
'lng' => floatval($coords[0]),
'lat' => floatval($coords[1])
];
// 判断是否在园区内
if (!$this->is_point_in_polygon($point_coord, $max_path_arr)) {
continue;
}
// 获取轨迹点的时间
$pointTime = strtotime($point['create_time']);
if ($pointTime === false) {
continue;
}
// 计算轨迹点属于哪个小时(今天的小时)
$pointHour = (int)date('H', $pointTime);
$pointDate = date('Y-m-d', $pointTime);
// 只统计今天的数据
if ($pointDate !== $todayDate) {
continue;
}
// 直接使用小时作为索引0-23
$index = $pointHour;
// 初始化小时数据
if (!isset($hourVehicleMap[$index])) {
$hourVehicleMap[$index] = ['stop' => [], 'driving' => []];
}
// 优先使用轨迹点保存的速度信息
// 如果轨迹点没有speed字段旧数据跳过该点不进行统计
// 因为使用车辆当前速度来判断历史轨迹点是不准确的
if (!isset($point['speed'])) {
// 旧数据没有speed字段跳过统计
$speedStats['no_speed_in_point']++;
continue;
}
$pointSpeed = floatval($point['speed']);
$speedStats['has_speed_in_point']++;
// 根据速度判断是停止还是行驶速度大于0.1km/h视为行驶避免浮点数精度问题
// 使用集合去重,同一车辆在同一小时只统计一次
if ($pointSpeed > 0.1) {
$hourVehicleMap[$index]['driving'][$vehicleNo] = true;
$speedStats['driving_count']++;
} else {
$hourVehicleMap[$index]['stop'][$vehicleNo] = true;
$speedStats['stop_count']++;
}
}
}
// 调试信息(可以注释掉,或记录到日志)
// \think\Log::info('车辆行驶停止统计调试信息: ' . json_encode($speedStats, JSON_UNESCAPED_UNICODE));
// 将统计结果填充到数组中24小时
for ($i = 0; $i < 24; $i++) {
if (isset($hourVehicleMap[$i])) {
$fullStats[$i]['order_count'] = count($hourVehicleMap[$i]['stop']);
$fullStats2[$i]['order_count'] = count($hourVehicleMap[$i]['driving']);
}
}
// 只返回过去12小时的数据用于前端显示
// 从当前小时往前推12小时
$resultStats = [];
$resultStats2 = [];
for ($i = 11; $i >= 0; $i--) {
$targetTime = $currentTime - $i * 3600;
$targetHour = (int)date('H', $targetTime);
$hourTimeShort = date('H:00', $targetTime);
$resultStats[] = [
'hour_time' => $hourTimeShort,
'order_count' => isset($fullStats[$targetHour]) ? $fullStats[$targetHour]['order_count'] : 0
];
$resultStats2[] = [
'hour_time' => $hourTimeShort,
'order_count' => isset($fullStats2[$targetHour]) ? $fullStats2[$targetHour]['order_count'] : 0
];
}
$info['stop_count'] = $resultStats;
$info['driving_count'] = $resultStats2;
// 园区内车辆重载和空载
$info['heavy'] = [];
$info['empty'] = [];
$this->success('请求成功!', $info);
}
//大屏-运单
public function waybill_trend()
{
//运单周数量
// 获取当前日期(包含时间,用于精确查询)
$endDate = date('Y-m-d 23:59:59');
// 计算7天前的日期从当天0点开始
$startDate = date('Y-m-d 00:00:00', strtotime('-6 days'));
// 查询实际数据(只统计未删除的运单)
$map_waybill['is_del'] = 1;
$actualStats = Db::name('waybill')
->where($map_waybill)
->where('create_time', '>=', $startDate)
->where('create_time', '<=', $endDate)
->field("DATE_FORMAT(create_time, '%Y-%m-%d') as date,
COUNT(*) as count")
->group("DATE_FORMAT(create_time, '%Y-%m-%d')")
->select();
// 转换为以日期为键的数组方便查找
$week = ['周日','周一','周二','周三','周四','周五','周六'];
$dailyData = [];
foreach ($actualStats as $stat) {
$weekdayNum = date('w', strtotime($stat['date'])); // 0-6表示周日到周六
$dailyData[$stat['date']] = [
'date' => $stat['date'],
'weekday' => $week[$weekdayNum], // 转换为中文星期
'count' => intval($stat['count']),
];
}
// 构建完整的7天数据包含空日期
$fullWeekStats = [];
for ($i = 6; $i >= 0; $i--) {
$currentDate = date('Y-m-d', strtotime("-{$i} days"));
$weekdayNum = date('w', strtotime($currentDate)); // 0-6表示周日到周六
$weekday = $week[$weekdayNum];
if (isset($dailyData[$currentDate])) {
// 有数据的日期
$fullWeekStats[] = $dailyData[$currentDate];
} else {
// 无数据的日期补零
$fullWeekStats[] = [
'date' => $currentDate,
'weekday' => $weekday,
'count' => 0,
];
}
}
$info['waybill'] = $fullWeekStats;
//企业货物前10 - 按装货单位统计货物总量
$map_enterprise['is_del'] = 1;
$map_enterprise['waybill_loading'] = ['<>', '']; // 装货单位不为空
// 按装货单位分组统计每个企业的货物总量cargocount的SUM
$enterpriseList = Db::name('waybill')
->where($map_enterprise)
->field('waybill_loading, SUM(cargocount) as total_count')
->group('waybill_loading')
->order('total_count desc')
->limit(10)
->select();
$enterpriseNames = [];
$enterpriseNums = [];
if ($enterpriseList) {
foreach ($enterpriseList as $item) {
$enterpriseNames[] = $item['waybill_loading'] ? $item['waybill_loading'] : '未知企业';
$enterpriseNums[] = floatval($item['total_count']); // 货物总量(吨)
}
}
// 如果数据不足10条用空数据补齐前端需要固定10条
$count = count($enterpriseNames);
if ($count < 10) {
for ($i = $count; $i < 10; $i++) {
$enterpriseNames[] = '';
$enterpriseNums[] = 0;
}
}
$info['enterprise'] = [
'name' => $enterpriseNames,
'num' => $enterpriseNums
];
//当日片区货物输入 - 统计装货的运单
$info['in_area'] = $this->getAreaCargoStatistics('in');
//当日片区货物输出 - 统计卸货的运单
$info['out_area'] = $this->getAreaCargoStatistics('out');
$this->success('请求成功!', $info);
}
//大屏-未查验运单列表
public function uncheck_waybill_list()
{
$limit = input('limit', 20); // 默认20条
$map['is_del'] = 1; // 未删除
$map['check_status'] = 0; // 未查验状态
$field = 'id,waybill_order,tow_license,sale_product,waybill_loading,waybill_unloading,waybill_date,create_time';
$list = Waybill::where($map)
->field($field)
->order('create_time desc')
->limit($limit)
->select();
$result = [];
if ($list) {
foreach ($list as $k => $v) {
// 转换为数组格式
$item = [
'id' => $v['id'],
'waybill_order' => $v['waybill_order'] ? $v['waybill_order'] : '',
'tow_license' => $v['tow_license'] ? $v['tow_license'] : '',
'sale_product' => $v['sale_product'] ? $v['sale_product'] : '',
'waybill_loading' => $v['waybill_loading'] ? $v['waybill_loading'] : '',
'waybill_unloading' => $v['waybill_unloading'] ? $v['waybill_unloading'] : '',
];
// 格式化时间
if ($v['waybill_date']) {
$item['waybill_date'] = date('Y-m-d H:i:s', strtotime($v['waybill_date']));
} else {
$item['waybill_date'] = $v['create_time'] ? date('Y-m-d H:i:s', strtotime($v['create_time'])) : '';
}
$result[] = $item;
}
}
$this->success('请求成功!', $result);
}
//大屏-每日企业车辆进出记录
public function enterprise_vehicle($param)
{
if (empty($param['id'])) {
$this->error('异常操作!');
}
$data['vehicle_info'] = Db::name('perimeter')->where('id', $param['id'])->field('stop_vehicle,vehicle_count,enter_vehicle,out_vehicle')->find();
$this->error('请求成功!', $data);
}
//大屏-车辆报警列表
public function vehicle_alarm_list()
{
$page = input('page', 1);
$limit = input('limit', 10);
if (input('key')) {
$map['name'] = array('like', "%" . input('key') . "%");
}
//获取车牌号 - 支持通过车辆ID或车牌号查询
$map['is_del'] = array('neq', -1);
$vehicleNo = input('vehicleNo');
if (is_numeric($vehicleNo)) {
// 如果是数字当作车辆ID处理
$map['license'] = Vehicle::where('id', $vehicleNo)->value('license');
} else {
// 如果是字符串,当作车牌号处理
$map['license'] = $vehicleNo;
}
$list = Alarm::where($map)->order('create_time desc')->page($page)->limit($limit)->select();
$count = Alarm::where($map)->count();
$data['pages'] = ceil($count / $limit);
$data['list'] = $list;
$this->success('请求成功!', $data);
}
//大屏按钮-车辆详情
public function vehicle_detail()
{
$param = input();
if (empty($param['id'])) {
$this->error('异常操作!');
}
$field = 'vehicleNo,plateColor,ownerName,businessScopeName,transdCertificateCode,certificateEffdate,certificateExpdate,licenseIssueOrganCode';
// 支持通过车辆ID或车牌号查询
if (is_numeric($param['id'])) {
// 如果是数字当作车辆ID处理
$vehicle_info = Vehicle::where('id', $param['id'])->field($field)->find();
} else {
// 如果是字符串当作车牌号处理先查询车辆ID
$vehicleId = Vehicle::where('vehicleNo', $param['id'])->value('id');
if (empty($vehicleId)) {
$this->error('未找到该车辆信息!');
}
$vehicle_info = Vehicle::where('id', $vehicleId)->field($field)->find();
}
$url = 'http://47.108.219.88:5000/api/httpserver/vehicleInfo?vehicleNo=' . rawurlencode($vehicle_info['vehicleNo']) . '&plateColor=' . $vehicle_info['plateColor'];
$header = array();
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$json = json_decode($output, true);
if (empty($json['body'])) {
$list = [
'vehicleNo' => '',
'ownerName' => '',
'licenseIssueOrganCode' => '',
'transCertificateCode' => '',
'transCertificateWord' => '',
'businessScopeName' => '',
'certificateEffdate' => '',
'certificateExpdate' => '',
];
} else {
$list = $json['body'];
}
$vehicle_info = array_merge($vehicle_info, $list);
$this->success('操作成功!', $vehicle_info);
}
//大屏按钮-车辆入园记录列表
public function vehicle_park_list()
{
// 支持通过车辆ID或车牌号查询
$vehicleNoInput = input('vehicleNo');
if (is_numeric($vehicleNoInput)) {
// 如果是数字当作车辆ID处理
$param['vehicleNo'] = Vehicle::where('id', $vehicleNoInput)->value('vehicleNo');
} else {
// 如果是字符串,当作车牌号处理
$param['vehicleNo'] = $vehicleNoInput;
}
if (empty($param['vehicleNo'])) {
$this->error('异常操作!');
}
$page = input('page', 1);
$limit = input('limit', 10);
$map['is_del'] = array('neq', -1);
$map['tractor_license'] = $param['vehicleNo'];
$field = 'tractor_license,mission,status,create_time';
$list = Park::where($map)->order('create_time desc')->field($field)->page($page)->limit($limit)->select();
$count = Park::where($map)->count();
$data['pages'] = ceil($count / $limit);
$data['list'] = $list;
$this->success('请求成功!', $data);
}
//大屏按钮-车辆运单记录列表
public function vehicle_waybill_list()
{
// 支持通过车辆ID或车牌号查询
$vehicleNoInput = input('vehicleNo');
if (is_numeric($vehicleNoInput)) {
// 如果是数字当作车辆ID处理
$param['vehicleNo'] = Vehicle::where('id', $vehicleNoInput)->value('vehicleNo');
} else {
// 如果是字符串,当作车牌号处理
$param['vehicleNo'] = $vehicleNoInput;
}
if (empty($param['vehicleNo'])) {
$this->error('异常操作!');
}
$page = input('page', 1);
$limit = input('limit', 10);
if (isset($param['mission']) && !empty($param['mission'])) {
$map['w_mission'] = $param['mission'];
}
$map['is_del'] = array('neq', -1);
$map['tow_license'] = $param['vehicleNo'];
$list = Waybill::where($map)->order('create_time desc')->page($page)->limit($limit)->select();
$count = Waybill::where($map)->count();
if ($list) {
foreach ($list as $k => &$v) {
$v->check_name = '';
$v->check_status_record = '';
$record_list = EnterpriseCheck::where('waybill_id', $v['id'])->field('check_uid,check_status')->find();
if ($record_list) {
$v->check_name = Perimeter::where('id', $record_list['check_uid'])->value('name');
$v->check_status_record = $record_list['check_status'];
}
}
}
$data['pages'] = ceil($count / $limit);
$data['list'] = $list;
$this->success('请求成功!', $data);
}
//大屏按钮-车辆行驶轨迹
public function vehicle_history()
{
$param = input();
// 支持通过车辆ID或车牌号查询
$vehicleNoInput = input('vehicleNo');
if (is_numeric($vehicleNoInput)) {
// 如果是数字当作车辆ID处理
$vehicleNo = Vehicle::where('id', $vehicleNoInput)->value('vehicleNo');
} else {
// 如果是字符串,当作车牌号处理
$vehicleNo = $vehicleNoInput;
}
if (empty($vehicleNo)) {
$this->error('异常操作!');
}
// 处理日期参数:如果提供了 reservation日期使用该日期否则使用今天
$reservation = input('reservation', '');
if ($reservation) {
// 验证日期格式
$date = strtotime($reservation);
if ($date === false) {
$this->error('日期格式错误!');
}
$dateStr = date('Y-m-d', $date);
} else {
$dateStr = date('Y-m-d');
}
// 查询指定日期的轨迹数据
$table = 'vehicle_log';
$map['vehicleNo'] = $vehicleNo;
// 添加日期过滤:查询指定日期的数据
// vehicle_log 表使用 positionTime 或 V_carettime 字段存储时间
$dateStart = $dateStr . ' 00:00:00';
$dateEnd = $dateStr . ' 23:59:59';
// 查询指定日期的轨迹数据
// 优先使用 positionTime 字段,如果为空则使用 V_carettime 字段
$list = Db::name($table)
->where($map)
->where(function($query) use ($dateStart, $dateEnd) {
// 使用 positionTime 字段进行日期过滤(优先)
$query->where('positionTime', '>=', $dateStart)
->where('positionTime', '<=', $dateEnd);
})
->whereOr(function($query) use ($map, $dateStart, $dateEnd) {
// 如果 positionTime 为空或无效,使用 V_carettime 字段
$query->where($map)
->where(function($q) {
$q->where('positionTime', '')
->whereOr('positionTime', 'null')
->whereOr('positionTime', null);
})
->where('V_carettime', '>=', $dateStart)
->where('V_carettime', '<=', $dateEnd);
})
->field('longitude,latitude,positionTime,V_carettime')
->order('id asc')
->select();
// 如果还是没有结果,尝试只使用 V_carettime 字段(兼容旧数据)
if (empty($list)) {
$list = Db::name($table)
->where($map)
->where('V_carettime', '>=', $dateStart)
->where('V_carettime', '<=', $dateEnd)
->field('longitude,latitude')
->order('id asc')
->select();
}
// 如果仍然没有结果,尝试查询 vehicle_line 表(轨迹数据可能存储在这里)
if (empty($list)) {
// vehicle_line 表存储的是 JSON 格式的轨迹数据
$lineRecord = Db::name('vehicle_line')
->where('vehicleNo', $vehicleNo)
->where('create_time', '>=', $dateStart)
->where('create_time', '<=', $dateEnd)
->where('is_del', 1)
->field('line')
->order('id asc')
->select();
if ($lineRecord && count($lineRecord) > 0) {
// 解析 JSON 格式的轨迹数据
$list = [];
foreach ($lineRecord as $record) {
$lineData = json_decode($record['line'], true);
if (is_array($lineData) && count($lineData) > 0) {
foreach ($lineData as $point) {
if (isset($point['coordinate']) && !empty($point['coordinate'])) {
$coords = explode(',', $point['coordinate']);
if (count($coords) >= 2) {
$list[] = [
'longitude' => floatval($coords[0]),
'latitude' => floatval($coords[1])
];
}
}
}
}
}
}
}
$coordinate = '';
if ($list && count($list) > 0) {
foreach ($list as $k => $v) {
if (empty($v['longitude']) || empty($v['latitude'])) {
continue;
}
$str = $this->getWgs84($v['longitude'] . ',' . $v['latitude']);
$coordinate .= $str['lon'] . ',' . $str['lat'] . ';';
}
if (!empty($coordinate)) {
$coordinate = substr($coordinate, 0, strlen($coordinate) - 1);
}
}
// 如果没有查询到数据,返回默认值
if (empty($coordinate)) {
$coordinate = '106.964108,29.793287';//默认初始值
}
$data['coordinate'] = $coordinate;
$this->success('请求成功!', $data);
}
//车辆码 一小时请求一次
public function getHourData()
{
//园区当月车辆进出列表
$month_arr = [];
$m = date('m');
$y = date('Y');
$month = Date::days_in_month(date($m), date($y));
for ($i = 0; $i < $month; $i++) {
// 格式化日期确保是两位数2025-11-01 而不是 2025-11-1
$day = str_pad($i + 1, 2, '0', STR_PAD_LEFT);
$month_arr[$i]['month'] = $y . '-' . $m . '-' . $day;
$month_arr[$i]['green_count'] = 0;
$month_arr[$i]['yellow_count'] = 0;
$month_arr[$i]['red_count'] = 0;
}
// 方案1优先使用 vehicle_park_line_log 表(如果表存在且有数据)
// 方案2如果 vehicle_park_line_log 表没有数据,则使用 vehicle 表统计每天首次出现的车辆
foreach ($month_arr as $k => $v) {
$dateStr = $v['month'];
$dateStart = $dateStr . ' 00:00:00';
$dateEnd = $dateStr . ' 23:59:59';
// 先尝试从 vehicle_park_line_log 表查询
$park_log = ParkLineLog::where('is_del', 1)
->where('type', 1)
->whereTime('create_time', 'between', [$dateStart, $dateEnd])
->field('vehicleNo,create_time')
->select();
if ($park_log && count($park_log) > 0) {
// 使用 vehicle_park_line_log 表的数据
foreach ($park_log as $a => $b) {
$code = Vehicle::where('vehicleNo', $b['vehicleNo'])
->where('is_del', 'in', '1,2')
->value('qr_color');
if ($code == 1) {
$month_arr[$k]['green_count'] += 1;
} elseif ($code == 2) {
$month_arr[$k]['yellow_count'] += 1;
} elseif ($code == 3) {
$month_arr[$k]['red_count'] += 1;
}
}
} else {
// 如果 vehicle_park_line_log 表没有数据,使用 vehicle 表统计
// 统计每天首次出现的车辆(按车牌号去重)
$vehicles = Db::name('vehicle')
->where('is_del', 'in', '1,2')
->where('create_time', '>=', $dateStart)
->where('create_time', '<=', $dateEnd)
->field('vehicleNo, qr_color')
->group('vehicleNo')
->select();
if ($vehicles) {
foreach ($vehicles as $vehicle) {
$code = $vehicle['qr_color'];
if ($code == 1) {
$month_arr[$k]['green_count'] += 1;
} elseif ($code == 2) {
$month_arr[$k]['yellow_count'] += 1;
} elseif ($code == 3) {
$month_arr[$k]['red_count'] += 1;
}
}
}
}
}
$info['month_arr'] = $month_arr;//车辆码 一个小时自动请求
//当日园区进入车辆数
$park_same_day_count = ParkLineLog::whereTime('create_time', 'd')->where(['is_del' => 1, 'type' => 1])->count();
//当日入园上报数
$apply_count = Park::whereTime('create_time', 'd')->where('status', 1)->count();
//当日运单上报数
$waybill_count = Waybill::whereTime('create_time', 'd')->where('is_del', 1)->count();
$report_count = $apply_count + $waybill_count;
$info['park_same_day_count'] = $park_same_day_count;
$info['report_count'] = $report_count;
//当日入园未上报数
$info['no_report_count'] = $park_same_day_count - $report_count;
$this->success('请求成功!', $info);
}
public function getWgs84($par)
{
$result = array(); // 转换后的结果
$tokens = preg_split('/[\r\n]+/', $par);
foreach ($tokens as $token) {
if (false !== strpos($token, '=')) {
list($key, $value) = explode('=', $token, 2);
$result[$key] = $value;
} else
$result[] = $token;
}
foreach ($result as $k => $v) {
if ($v) {
$Varr = explode(',', $v);
$res = $this->bd_decrypt($Varr[0], $Varr[1]);
$arr = [
'lon' => number_format($res['gg_lon'], 6),
'lat' => number_format($res['gg_lat'], 6),
];
}
}
return $arr;
}
//BD-09(百度) 坐标转换成 GCJ-02(火星,高德) 坐标
//@param bd_lon 百度经度
//@param bd_lat 百度纬度
public function bd_decrypt($bd_lon, $bd_lat)
{
$x_pi = 3.14159265358979324 * 3000.0 / 180.0;
$x = $bd_lon - 0.0065;
$y = $bd_lat - 0.006;
$z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * $x_pi);
$theta = atan2($y, $x) - 0.000003 * cos($x * $x_pi);
$data['gg_lon'] = $z * cos($theta);
$data['gg_lat'] = $z * sin($theta);
return $data;
}
/**
* 统计每个片区的车辆数量
* @return array
*/
private function getAreaVehicleStatistics()
{
// 查询所有企业区域region_type=6作为片区
$areas = Perimeter::where('region_type', 6)
->where('is_del', 1)
->where('info', '<>', '')
->field('id,name,info')
->select();
if (empty($areas)) {
// 如果没有配置片区,返回空数组
return [];
}
// 查询所有车辆
$vehicles = Vehicle::where('is_del', 'in', '1,2')
->field('id,vehicleNo,longitude,latitude')
->select();
$areaStats = [];
// 遍历每个片区
foreach ($areas as $area) {
// 解析片区坐标
$polygon = $this->parsePerimeterCoords($area['info']);
if (empty($polygon)) {
continue;
}
$vehicleCount = 0;
// 统计在该片区内的车辆数量
foreach ($vehicles as $vehicle) {
if (empty($vehicle['longitude']) || empty($vehicle['latitude'])) {
continue;
}
$point = [
'lng' => floatval($vehicle['longitude']),
'lat' => floatval($vehicle['latitude'])
];
if ($this->is_point_in_polygon($point, $polygon)) {
$vehicleCount++;
}
}
$areaStats[] = [
'name' => $area['name'],
'value' => $vehicleCount
];
}
// 如果没有统计数据,返回空数组
return $areaStats;
}
/**
* 统计每条道路上的车辆数量
* @return array
*/
private function getRoadVehicleStatistics()
{
// 查询所有道路region_type=5
$roads = Perimeter::where('region_type', 5)
->where('is_del', 1)
->where('info', '<>', '')
->field('id,name,info')
->select();
if (empty($roads)) {
// 如果没有配置道路,返回空数组
return [];
}
// 查询所有车辆
$vehicles = Vehicle::where('is_del', 'in', '1,2')
->field('id,vehicleNo,longitude,latitude')
->select();
$roadStats = [];
// 遍历每条道路
foreach ($roads as $road) {
// 解析道路坐标
$polygon = $this->parsePerimeterCoords($road['info']);
if (empty($polygon)) {
// 如果没有坐标信息返回0
$roadStats[] = [
'id' => $road['id'],
'name' => $road['name'],
'count' => 0
];
continue;
}
$vehicleCount = 0;
// 统计在该道路上的车辆数量
foreach ($vehicles as $vehicle) {
if (empty($vehicle['longitude']) || empty($vehicle['latitude'])) {
continue;
}
$point = [
'lng' => floatval($vehicle['longitude']),
'lat' => floatval($vehicle['latitude'])
];
if ($this->is_point_in_polygon($point, $polygon)) {
$vehicleCount++;
}
}
$roadStats[] = [
'id' => $road['id'],
'name' => $road['name'],
'count' => $vehicleCount
];
}
// 如果没有统计数据,返回空数组
return $roadStats;
}
/**
* 统计片区货物输入/输出
* @param string $type 'in' 表示货物输入(装货),'out' 表示货物输出(卸货)
* @return array
*/
private function getAreaCargoStatistics($type = 'in')
{
// 查询所有企业区域region_type=6作为片区
$areas = Perimeter::where('region_type', 6)
->where('is_del', 1)
->field('id,name')
->select();
if (empty($areas)) {
// 如果没有配置片区,返回空数组
return [];
}
// 获取当天的日期使用waybill_date字段表示提货日期
$todayDate = date('Y-m-d');
// 根据类型确定查询字段和条件
if ($type === 'in') {
// 货物输入:统计装货的运单
$enterpriseField = 'waybill_loading';
$missionCondition = '装货';
} else {
// 货物输出:统计卸货的运单
$enterpriseField = 'waybill_unloading';
$missionCondition = '卸货';
}
// 查询当日的运单数据(按提货日期统计)
$waybillMap = [
'is_del' => 1,
'w_mission' => $missionCondition,
$enterpriseField => ['<>', ''], // 装货/卸货单位不为空
];
$waybills = Db::name('waybill')
->where($waybillMap)
->whereTime('waybill_date', 'd') // 使用waybill_date字段统计当天的运单
->field("{$enterpriseField} as enterprise_name, SUM(cargocount) as total_count")
->group($enterpriseField)
->select();
// 构建企业名称到货物数量的映射
$enterpriseCargoMap = [];
if ($waybills) {
foreach ($waybills as $waybill) {
$enterpriseName = $waybill['enterprise_name'];
$enterpriseCargoMap[$enterpriseName] = floatval($waybill['total_count']);
}
}
// 统计每个片区的货物总量
$areaStats = [];
foreach ($areas as $area) {
$areaName = $area['name'];
$totalCargo = 0;
// 遍历运单数据,匹配装货/卸货单位与片区名称
// 这里使用名称匹配,如果装货/卸货单位包含片区名称,则计入该片区
foreach ($enterpriseCargoMap as $enterpriseName => $cargoCount) {
// 如果企业名称与片区名称匹配(完全匹配或包含关系)
if ($enterpriseName == $areaName || strpos($enterpriseName, $areaName) !== false || strpos($areaName, $enterpriseName) !== false) {
$totalCargo += $cargoCount;
}
}
$areaStats[] = [
'name' => $areaName,
'value' => round($totalCargo, 2) // 保留2位小数
];
}
// 如果没有统计数据至少返回片区名称值为0
if (empty($areaStats)) {
foreach ($areas as $area) {
$areaStats[] = [
'name' => $area['name'],
'value' => 0
];
}
}
return $areaStats;
}
/**
* 解析周界坐标字符串为多边形数组
* @param string $info 坐标字符串格式lng,lat;lng,lat;...
* @return array
*/
private function parsePerimeterCoords($info)
{
if (empty($info)) {
return [];
}
$polygon = [];
$points = explode(';', $info);
foreach ($points as $point) {
$coords = explode(',', $point);
if (count($coords) >= 2) {
$polygon[] = [
'lng' => floatval(trim($coords[0])),
'lat' => floatval(trim($coords[1]))
];
}
}
return $polygon;
}
/**
* 判断一个坐标是否在一个多边形内
* @param array $point 点坐标 ['lng' => x, 'lat' => y]
* @param array $pts 多边形坐标数组
* @return bool
*/
private function is_point_in_polygon($point, $pts)
{
$N = count($pts);
if ($N < 3) {
return false;
}
$boundOrVertex = true;
$intersectCount = 0;
$precision = 2e-10;
$p1 = 0;
$p2 = 0;
$p = $point;
$p1 = $pts[0];
for ($i = 1; $i <= $N; ++$i) {
if ($p['lng'] == $p1['lng'] && $p['lat'] == $p1['lat']) {
return $boundOrVertex;
}
$p2 = $pts[$i % $N];
if ($p['lat'] < min($p1['lat'], $p2['lat']) || $p['lat'] > max($p1['lat'], $p2['lat'])) {
$p1 = $p2;
continue;
}
if ($p['lat'] > min($p1['lat'], $p2['lat']) && $p['lat'] < max($p1['lat'], $p2['lat'])) {
if($p['lng'] <= max($p1['lng'], $p2['lng'])){
if ($p1['lat'] == $p2['lat'] && $p['lng'] >= min($p1['lng'], $p2['lng'])) {
return $boundOrVertex;
}
if ($p1['lng'] == $p2['lng']) {
if ($p1['lng'] == $p['lng']) {
return $boundOrVertex;
} else {
++$intersectCount;
}
} else {
$xinters = ($p['lat'] - $p1['lat']) * ($p2['lng'] - $p1['lng']) / ($p2['lat'] - $p1['lat']) + $p1['lng'];
if (abs($p['lng'] - $xinters) < $precision) {
return $boundOrVertex;
}
if ($p['lng'] < $xinters) {
++$intersectCount;
}
}
}
} else {
if ($p['lat'] == $p2['lat'] && $p['lng'] <= $p2['lng']) {
$p3 = $pts[($i+1) % $N];
if ($p['lat'] >= min($p1['lat'], $p3['lat']) && $p['lat'] <= max($p1['lat'], $p3['lat'])) {
++$intersectCount;
} else {
$intersectCount += 2;
}
}
}
$p1 = $p2;
}
if ($intersectCount % 2 == 0) {
return false;
} else {
return true;
}
}
}