1155 lines
53 KiB
PHP
1155 lines
53 KiB
PHP
<?php
|
||
namespace app\api\controller;
|
||
|
||
use app\common\controller\Api;
|
||
use think\Config;
|
||
use think\Db;
|
||
use think\Request;
|
||
use think\Session;
|
||
|
||
|
||
class YqDataHandle extends Api {
|
||
|
||
protected $noNeedLogin = ['*'];
|
||
protected $noNeedRight = ['*'];
|
||
//车辆信息处理
|
||
public function analog_trigger(){
|
||
// $ipt = input();
|
||
// $body = $ipt['body'];
|
||
$stttt = '{"dangerousGoodsType":"易燃液体,腐蚀性物质","certificateEffdate":"20191216","transCertificateCode":"500115068184","ownerName":"重庆戈林物流有限责任公司","licenseIssueOrganCode":"长寿区运管所","dataType":"position","certificateExpdate":"20231216","transCertificateWord":"渝","position":[{"vehicleNo":"渝A654321","positionTime":1609317985,"latitudeWgs84":29.8176429,"latitude":"29.8050100","longitudeWgs84":107.0190752,"vec1":0.0,"alititude":247.0,"plateColor":2,"longitude":"107.0072050","direction":341}],"vinNo":"LGAG4DY34B8005552","vehicleType":"Q11","businessScopeName":"危险品货物运输(第3类),危险品货物运输(第8类)"}';
|
||
$body = json_decode($stttt,true);
|
||
if ($body){
|
||
$str = $this->getWgs84(number_format($body['position'][0]['longitude'],7).','.number_format($body['position'][0]['latitude'],7));
|
||
$str['lon'] = '106.9822520';
|
||
$str['lat'] = '29.8299610';
|
||
$point=[
|
||
'lng'=>$str['lon'],
|
||
'lat'=>$str['lat'],
|
||
];
|
||
}else{
|
||
$this->alertMsg(300,'未接收到推送数据!');
|
||
}
|
||
|
||
//TODO 外层(大)围栏坐标集-只用判断,暂废弃(20210323)
|
||
//$big_path = "107.062736,29.891181;107.061300,29.897795;107.060726,29.901168;107.056430,29.902360;107.048547,29.902280;107.040363,29.903125;107.034322,29.903390;107.025101,29.904721;107.016010,29.905977;107.007779,29.906362;106.999695,29.905864;106.992628,29.904575;106.985715,29.901749;106.980395,29.898133;106.975513,29.893491;106.968060,29.886804;106.961906,29.880971;106.956901,29.875763;106.933588,29.853736;106.925125,29.841266;106.915339,29.824596;106.903938,29.804884;106.899749,29.793657;106.896866,29.777028;106.902065,29.771937;106.910574,29.767958;106.921226,29.763030;106.929984,29.760690;106.942019,29.758331;106.957773,29.738776;106.966653,29.733627;106.992518,29.718204;107.007523,29.715230;107.043405,29.707971;107.056169,29.709077;107.070341,29.713438;107.084227,29.718169;107.076353,29.742014;107.080633,29.759133;107.096674,29.776956;107.087634,29.780280;107.086198,29.792803;107.085341,29.801318;107.081194,29.809038;107.068617,29.824131;107.060616,29.839669;107.067773,29.852063;107.067761,29.872731;107.065890,29.883719;107.062879,29.890933";
|
||
|
||
//TODO 内层(小)围栏坐标集(20210323)
|
||
// $max_path = "107.01527,29.895601;106.997876,29.89281;106.991391,29.887939;106.985853,29.880405;106.981835,29.871026;106.960543,29.849142;106.950737,29.83564;106.945862,29.824089;106.943563,29.814148;106.945054,29.799754;106.942764,29.783607;106.970332,29.766519;106.983273,29.751322;107.017612,29.737776;107.050425,29.734115;107.064458,29.737683;107.064744,29.739318;107.058942,29.748447;107.055205,29.762378;107.071581,29.77504;107.086248,29.789923;107.088111,29.796775;107.084106,29.807387;107.080959,29.81142;107.068298,29.813474;107.06422,29.813406;107.060435,29.82112;107.050628,29.827207;107.04726,29.828623;107.046543,29.829966;107.04701,29.830878;107.04712,29.834263;107.044827,29.838826;107.053757,29.842875;107.056479,29.849047;107.057124,29.85153;107.056802,29.853468;107.060415,29.86047;107.061665,29.864841;107.054919,29.881592;107.052508,29.894119;107.040451,29.898403;107.028579,29.89536;107.014946,29.895825;106.997804,29.89278";
|
||
$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);
|
||
|
||
|
||
//周界二级区域
|
||
$one_region = Db::name('perimeter')->where(['region_lv'=>2,'is_del'=>1])->select();
|
||
foreach ($one_region as $onek => $onev){
|
||
$lv1 = explode(';',$onev['info']);
|
||
$lv1_arr = [];
|
||
foreach ($lv1 as $lv1K => $lv1V){
|
||
$ar = explode(',',$lv1V);
|
||
$lv1_arr[] = [
|
||
'lng' => $ar[0],
|
||
'lat' => $ar[1],
|
||
];
|
||
}
|
||
$lv1s = $this->is_point_in_polygon($point,$lv1_arr);
|
||
if ($lv1s){
|
||
//三级周界判断
|
||
$two_region = Db::name('perimeter')->where(['pid'=>$onev['id'],'is_del'=>1])->select();
|
||
if (!empty($two_region)){
|
||
foreach ($two_region as $twok => $twov){
|
||
if($twov['info']){
|
||
$lv2 = explode(';',$twov['info']);
|
||
$lv2_arr = [];
|
||
foreach ($lv2 as $lv2K => $lv2V){
|
||
$ar = explode(',',$lv2V);
|
||
$lv2_arr[] = [
|
||
'lng' => $ar[0],
|
||
'lat' => $ar[1],
|
||
];
|
||
}
|
||
$lv2s = $this->is_point_in_polygon($point,$lv2_arr);
|
||
if ($lv2s){
|
||
$msgs = $this->ruleJudge($twov,$body,$str);
|
||
$data['msgs'] = $msgs;
|
||
$this->alertMsg(200,'处理完成!',$data);
|
||
}
|
||
}
|
||
}
|
||
}else{
|
||
$this->ruleJudge($onev,$body,$str);
|
||
$this->alertMsg(200,'车辆在二级区域,车辆、报警数据更新!');
|
||
}
|
||
}
|
||
}
|
||
$vehicleInfo = $body['position'][0];
|
||
$data = [
|
||
'longitude' => $str['lon'],
|
||
'latitude' => $str['lat'],
|
||
'longitude_84' => $vehicleInfo['longitudeWgs84'],
|
||
'latitude_84' => $vehicleInfo['latitudeWgs84'],
|
||
'alititude' => $vehicleInfo['alititude'],
|
||
'vec1' => $vehicleInfo['vec1'],
|
||
'direction' => $vehicleInfo['direction'],
|
||
'positionTime' => date('Y-m-d H:i:s',$vehicleInfo['positionTime']),
|
||
// 'certificateEffdate' => $dataInfo['certificateEffdate'],
|
||
// 'certificateExpdate' => $dataInfo['certificateExpdate'],
|
||
// 'transCertificateCode' => $dataInfo['transCertificateCode'],
|
||
// 'ownerName' => $dataInfo['ownerName'],
|
||
// 'licenseIssueOrganCode' => $dataInfo['licenseIssueOrganCode'],
|
||
// 'transCertificateWord' => $dataInfo['transCertificateWord'],
|
||
// 'vinNo' => $dataInfo['vinNo'],
|
||
// 'vehicleType' => $dataInfo['vehicleType'],
|
||
// 'businessScopeName' => $dataInfo['businessScopeName'],
|
||
'is_del' => 1,
|
||
'perimeter_id' => '',//车辆当前定位
|
||
'vehicle_type' => 0,//车辆类型展示状态(-1区域外行驶;0正常;1超速;2禁行;3禁停;4未上报;5超最大数量)
|
||
'create_time' => date('Y-m-d H:i:s'),
|
||
];
|
||
$vehicle = Db::name($this->api_vehicle)->where('vehicleNo',$vehicleInfo['vehicleNo'])->field('id,longitude,latitude')->find();
|
||
if ($vehicle['id']){
|
||
$new_distance = $this->getDistance($str['lon'],$str['lat'],$vehicle['latitude'],$vehicle['longitude']);
|
||
$data['old_longitude'] = $vehicle['longitude'];
|
||
$data['old_latitude'] = $vehicle['latitude'];
|
||
$data['distance'] = 0;
|
||
Db::name($this->api_vehicle)->where('vehicleNo',$vehicleInfo['vehicleNo'])->update($data);
|
||
}else{
|
||
$data['vehicleNo'] = $vehicleInfo['vehicleNo'];
|
||
$data['plateColor'] = $vehicleInfo['plateColor'];
|
||
Db::name($this->api_vehicle)->insert($data);
|
||
}
|
||
$this->alertMsg(200,'车辆在一级区域,车辆数据更新!');
|
||
die;
|
||
|
||
|
||
|
||
|
||
|
||
|
||
//保存当前坐标点
|
||
// $line = [
|
||
// 'vehicleNo' => $v['vehicleNo'],
|
||
// 'coordinate' => number_format($longitude,7).','.number_format($latitude,7),
|
||
// 'create_time' => date('Y-m-d H:i:s'),
|
||
// ];
|
||
// Db::name('vehicle_line')->insert($line);
|
||
//最大区域判断 new一级区域判断
|
||
foreach ($one_region as $onek => $onev){
|
||
$lv1 = explode(';',$onev['info']);
|
||
$lv1_arr = [];
|
||
foreach ($lv1 as $lv1K => $lv1V){
|
||
$ar = explode(',',$lv1V);
|
||
$lv1_arr[] = [
|
||
'lng' => $ar[0],
|
||
'lat' => $ar[1],
|
||
];
|
||
}
|
||
$lv1s = $this->is_point_in_polygon($point,$lv1_arr);
|
||
if ($lv1s){
|
||
//二级区域判断
|
||
$two_region = Db::name('perimeter')->where(['pid'=>$onev['id'],'is_del'=>1])->select();
|
||
if (!empty($two_region)){
|
||
foreach ($two_region as $twok => $twov){
|
||
if($twov['info']){
|
||
$lv2 = explode(';',$twov['info']);
|
||
$lv2_arr = [];
|
||
foreach ($lv2 as $lv2K => $lv2V){
|
||
$ar = explode(',',$lv2V);
|
||
$lv2_arr[] = [
|
||
'lng' => $ar[0],
|
||
'lat' => $ar[1],
|
||
];
|
||
}
|
||
$lv2s = $this->is_point_in_polygon($point,$lv2_arr);
|
||
if ($lv2s){
|
||
//三级级区域判断
|
||
$three_region = Db::name('perimeter')->where(['pid'=>$twov['id'],'is_del'=>1])->select();
|
||
if (!empty($three_region)){
|
||
foreach ($three_region as $threek => $threev){
|
||
if($threev['info']){
|
||
$lv3 = explode(';',$threev['info']);
|
||
$lv3_arr = [];
|
||
foreach ($lv3 as $lv3K => $lv3V){
|
||
$ar = explode(',',$lv3V);
|
||
$lv3_arr[] = [
|
||
'lng' => $ar[0],
|
||
'lat' => $ar[1],
|
||
];
|
||
}
|
||
$lv3s = $this->is_point_in_polygon($point,$lv3_arr);
|
||
if ($lv3s){
|
||
$this->ruleJudge($threev,$body,$str);
|
||
$this->alertMsg(200,'车辆在三级区域,车辆、报警数据更新!');
|
||
}
|
||
}
|
||
}
|
||
}else{
|
||
$this->ruleJudge($twov,$body,$str);
|
||
$this->alertMsg(200,'车辆在二级区域,车辆、报警数据更新!');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}else{
|
||
$this->ruleJudge($onev,$body,$str);
|
||
$this->alertMsg(200,'车辆在一级区域,车辆、报警数据更新!');
|
||
}
|
||
}else{
|
||
}
|
||
}
|
||
$vehicle = Db::name($this->api_vehicle)->where('vehicleNo',$v['vehicleNo'])->field('id,longitude,latitude')->find();
|
||
$data = [
|
||
'longitude' => $longitude,
|
||
'latitude' => $latitude,
|
||
'longitude_84' => $v['longitudeWgs84'],
|
||
'latitude_84' => $v['latitudeWgs84'],
|
||
'alititude' => $v['alititude'],
|
||
'vec1' => $v['vec1'],
|
||
'direction' => $v['direction'],
|
||
'positionTime' => date('Y-m-d H:i:s',$v['positionTime']),
|
||
'vehicle_type' => 0,
|
||
// 'certificateEffdate' => $body['certificateEffdate'],
|
||
// 'certificateExpdate' => $body['certificateExpdate'],
|
||
// 'transCertificateCode' => $body['transCertificateCode'],
|
||
// 'ownerName' => $body['ownerName'],
|
||
// 'licenseIssueOrganCode' => $body['licenseIssueOrganCode'],
|
||
// 'transCertificateWord' => $body['transCertificateWord'],
|
||
// 'vinNo' => $body['vinNo'],
|
||
// 'vehicleType' => $body['vehicleType'],
|
||
// 'businessScopeName' => $body['businessScopeName'],
|
||
'perimeter_id' => 0,
|
||
'is_del' => 1,
|
||
'create_time' => date('Y-m-d H:i:s'),
|
||
];
|
||
if ($vehicle['id']){
|
||
$new_distance = $this->getDistance($latitude,$longitude,$vehicle['latitude'],$vehicle['longitude']);
|
||
if ($new_distance >= 15000){
|
||
$event_data = [
|
||
'name' => 'GPS瞬移【间距'.$new_distance.'】',
|
||
'type' => '7',//7为 GPS瞬移
|
||
'perimeter_point' => $latitude.','.$longitude,
|
||
'license' => $v['vehicleNo'],
|
||
'trigger_type' => 1,
|
||
'create_time' => date('Y-m-d H:i:s'),
|
||
'res_department' => 1,
|
||
];
|
||
Db::name($this->api_call_the_police)->insertGetId($event_data);
|
||
}
|
||
$data['old_longitude'] = $vehicle['longitude'];
|
||
$data['old_latitude'] = $vehicle['latitude'];
|
||
$data['distance'] = $new_distance;
|
||
Db::name($this->api_vehicle)->where('vehicleNo',$v['vehicleNo'])->update($data);
|
||
}else{
|
||
$data['vehicleNo'] = $v['vehicleNo'];
|
||
$data['plateColor'] = $v['plateColor'];
|
||
Db::name($this->api_vehicle)->insert($data);
|
||
}
|
||
$this->alertMsg(200,'车辆在一级区域外!');
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
* 同步长寿接口车辆数据到本地数据库
|
||
* 接收长寿接口格式的车辆数据并写入yq_vehicle表
|
||
* 同时根据企业周界规则进行校验并生成报警
|
||
*/
|
||
public function sync_vehicle()
|
||
{
|
||
$input = file_get_contents('php://input');
|
||
$body = json_decode($input, true);
|
||
|
||
// 调试日志
|
||
if (empty($input)) {
|
||
$this->error('请求体为空!');
|
||
}
|
||
|
||
if ($body === null) {
|
||
$this->error('JSON解析失败!请求内容:' . substr($input, 0, 500));
|
||
}
|
||
|
||
if (empty($body)) {
|
||
$this->error('请求体为空或JSON解析失败!');
|
||
}
|
||
|
||
if (!isset($body['data'])) {
|
||
$this->error('数据格式错误:缺少data字段!接收到的数据:' . json_encode($body, JSON_UNESCAPED_UNICODE));
|
||
}
|
||
|
||
// 如果data为空数组,直接返回成功(没有车辆数据需要同步)
|
||
if (empty($body['data']) || !is_array($body['data'])) {
|
||
$this->success('同步完成(无车辆数据)', ['success' => 0, 'fail' => 0, 'alarm_count' => 0]);
|
||
}
|
||
|
||
$insertData = [];
|
||
$updateData = [];
|
||
$vehicleList = []; // 用于规则校验的车辆列表
|
||
$successCount = 0;
|
||
$failCount = 0;
|
||
$alarmCount = 0;
|
||
$now = date('Y-m-d H:i:s');
|
||
|
||
// 收集所有车牌号
|
||
$allVehNos = [];
|
||
foreach ($body['data'] as $item) {
|
||
if (!empty($item['posList'])) {
|
||
foreach ($item['posList'] as $vehicle) {
|
||
if (!empty($vehicle['vehNo'])) {
|
||
$allVehNos[] = $vehicle['vehNo'];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量查询已存在的车辆
|
||
$existVehicles = [];
|
||
if (!empty($allVehNos)) {
|
||
$existList = Db::name('vehicle')->where('vehicleNo', 'in', $allVehNos)->column('id,longitude,latitude', 'vehicleNo');
|
||
$existVehicles = $existList ?: [];
|
||
}
|
||
|
||
foreach ($body['data'] as $item) {
|
||
if (empty($item['posList'])) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($item['posList'] as $vehicle) {
|
||
// 验证必要字段
|
||
if (empty($vehicle['vehNo']) || empty($vehicle['lon']) || empty($vehicle['lat'])) {
|
||
$failCount++;
|
||
continue;
|
||
}
|
||
|
||
// GCJ02坐标
|
||
$longitude = number_format($vehicle['lon'], 7);
|
||
$latitude = number_format($vehicle['lat'], 7);
|
||
|
||
// 处理定位时间
|
||
$positionTime = $now;
|
||
if (isset($vehicle['posTime'])) {
|
||
if (is_numeric($vehicle['posTime'])) {
|
||
$positionTime = date('Y-m-d H:i:s', $vehicle['posTime']);
|
||
} else {
|
||
$positionTime = $vehicle['posTime'];
|
||
}
|
||
}
|
||
|
||
$vehNo = $vehicle['vehNo'];
|
||
$speed = isset($vehicle['v1']) ? floatval($vehicle['v1']) : 0;
|
||
|
||
// 收集用于规则校验的车辆数据
|
||
$vehicleList[] = [
|
||
'vehNo' => $vehNo,
|
||
'longitude' => $longitude,
|
||
'latitude' => $latitude,
|
||
'speed' => $speed,
|
||
'color' => isset($vehicle['color']) ? $vehicle['color'] : 2,
|
||
];
|
||
|
||
if (isset($existVehicles[$vehNo])) {
|
||
// 更新数据
|
||
$updateData[] = [
|
||
'vehicleNo' => $vehNo,
|
||
'longitude' => $longitude,
|
||
'latitude' => $latitude,
|
||
'longitude_84' => isset($vehicle['originalLon']) ? $vehicle['originalLon'] : $vehicle['lon'],
|
||
'latitude_84' => isset($vehicle['originalLat']) ? $vehicle['originalLat'] : $vehicle['lat'],
|
||
'vec1' => $speed,
|
||
'direction' => isset($vehicle['dir']) ? $vehicle['dir'] : 0,
|
||
'positionTime' => $positionTime,
|
||
'is_del' => 1,
|
||
'vehicle_type' => 0,
|
||
'qr_color' => isset($vehicle['color']) ? $vehicle['color'] : 2,
|
||
'perimeter_id' => 0,
|
||
'old_longitude' => $existVehicles[$vehNo]['longitude'],
|
||
'old_latitude' => $existVehicles[$vehNo]['latitude'],
|
||
];
|
||
} else {
|
||
// 新增数据
|
||
$insertData[] = [
|
||
'vehicleNo' => $vehNo,
|
||
'plateColor' => isset($vehicle['color']) ? $vehicle['color'] : 2,
|
||
'longitude' => $longitude,
|
||
'latitude' => $latitude,
|
||
'longitude_84' => isset($vehicle['originalLon']) ? $vehicle['originalLon'] : $vehicle['lon'],
|
||
'latitude_84' => isset($vehicle['originalLat']) ? $vehicle['originalLat'] : $vehicle['lat'],
|
||
'vec1' => $speed,
|
||
'direction' => isset($vehicle['dir']) ? $vehicle['dir'] : 0,
|
||
'positionTime' => $positionTime,
|
||
'is_del' => 1,
|
||
'vehicle_type' => 0,
|
||
'qr_color' => isset($vehicle['color']) ? $vehicle['color'] : 2,
|
||
'perimeter_id' => 0,
|
||
'create_time' => $now,
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
try {
|
||
// 批量插入新车辆
|
||
if (!empty($insertData)) {
|
||
Db::name('vehicle')->insertAll($insertData);
|
||
$successCount += count($insertData);
|
||
}
|
||
|
||
// 批量更新已有车辆
|
||
foreach ($updateData as $data) {
|
||
$vehNo = $data['vehicleNo'];
|
||
unset($data['vehicleNo']);
|
||
Db::name('vehicle')->where('vehicleNo', $vehNo)->update($data);
|
||
$successCount++;
|
||
}
|
||
|
||
// 保存车辆轨迹数据到 vehicle_line 表
|
||
$trajectoryResult = $this->saveVehicleTrajectory($body['data'], $now);
|
||
if ($trajectoryResult['fail'] > 0) {
|
||
\think\Log::warning('车辆轨迹保存有失败: ' . json_encode($trajectoryResult, JSON_UNESCAPED_UNICODE));
|
||
}
|
||
|
||
// 执行规则校验并生成报警
|
||
$alarmCount = $this->checkVehicleRulesAndGenerateAlarms($vehicleList, $now);
|
||
|
||
} catch (\Exception $e) {
|
||
$failCount = count($insertData) + count($updateData);
|
||
$successCount = 0;
|
||
$lastError = $e->getMessage();
|
||
}
|
||
|
||
$result = [
|
||
'success' => $successCount,
|
||
'fail' => $failCount,
|
||
'alarm_count' => $alarmCount,
|
||
'trajectory' => isset($trajectoryResult) ? [
|
||
'success' => $trajectoryResult['success'],
|
||
'fail' => $trajectoryResult['fail']
|
||
] : null
|
||
];
|
||
if (isset($lastError)) {
|
||
$result['lastError'] = $lastError;
|
||
}
|
||
|
||
// 调试日志:记录数据格式(仅在前几次调用时记录)
|
||
static $debugCount = 0;
|
||
if ($debugCount < 3) {
|
||
$debugData = [
|
||
'data_structure' => [
|
||
'has_data' => isset($body['data']),
|
||
'data_type' => isset($body['data']) ? gettype($body['data']) : 'null',
|
||
'data_count' => isset($body['data']) && is_array($body['data']) ? count($body['data']) : 0,
|
||
'first_item_keys' => isset($body['data'][0]) && is_array($body['data'][0]) ? array_keys($body['data'][0]) : null,
|
||
'has_posList' => isset($body['data'][0]['posList']),
|
||
'posList_count' => isset($body['data'][0]['posList']) && is_array($body['data'][0]['posList']) ? count($body['data'][0]['posList']) : 0,
|
||
],
|
||
'trajectory_result' => isset($trajectoryResult) ? $trajectoryResult : null
|
||
];
|
||
\think\Log::info('sync_vehicle 接收数据格式: ' . json_encode($debugData, JSON_UNESCAPED_UNICODE));
|
||
$debugCount++;
|
||
}
|
||
|
||
$this->success('同步完成', $result);
|
||
}
|
||
|
||
/**
|
||
* 根据企业周界规则校验车辆并生成报警
|
||
* @param array $vehicleList 车辆列表
|
||
* @param string $now 当前时间
|
||
* @return int 报警数量
|
||
*/
|
||
private function checkVehicleRulesAndGenerateAlarms($vehicleList, $now)
|
||
{
|
||
if (empty($vehicleList)) {
|
||
return 0;
|
||
}
|
||
|
||
// 获取所有有坐标的企业周界配置
|
||
$perimeters = Db::name('perimeter')
|
||
->where('is_del', 1)
|
||
->where('info', '<>', '')
|
||
->field('id,name,info,is_stop,is_ban,is_hazard,stop_vehicle,feasible_vehicle,max_speed')
|
||
->select();
|
||
|
||
if (empty($perimeters)) {
|
||
return 0;
|
||
}
|
||
|
||
// 预处理周界坐标
|
||
$perimeterPolygons = [];
|
||
foreach ($perimeters as $perimeter) {
|
||
$polygon = $this->parsePerimeterCoords($perimeter['info']);
|
||
if (!empty($polygon)) {
|
||
$perimeterPolygons[] = [
|
||
'perimeter' => $perimeter,
|
||
'polygon' => $polygon
|
||
];
|
||
}
|
||
}
|
||
|
||
$alarms = [];
|
||
// 统计每个周界内的停止车辆数和行驶车辆数
|
||
$perimeterVehicleStats = [];
|
||
|
||
// 第一次遍历:统计每个周界内的车辆数
|
||
foreach ($vehicleList as $vehicle) {
|
||
$point = [
|
||
'lng' => $vehicle['longitude'],
|
||
'lat' => $vehicle['latitude']
|
||
];
|
||
|
||
foreach ($perimeterPolygons as $pp) {
|
||
$perimeterId = $pp['perimeter']['id'];
|
||
|
||
if ($this->is_point_in_polygon($point, $pp['polygon'])) {
|
||
if (!isset($perimeterVehicleStats[$perimeterId])) {
|
||
$perimeterVehicleStats[$perimeterId] = [
|
||
'stop_count' => 0,
|
||
'drive_count' => 0,
|
||
'vehicles' => []
|
||
];
|
||
}
|
||
|
||
if ($vehicle['speed'] == 0) {
|
||
$perimeterVehicleStats[$perimeterId]['stop_count']++;
|
||
} else {
|
||
$perimeterVehicleStats[$perimeterId]['drive_count']++;
|
||
}
|
||
$perimeterVehicleStats[$perimeterId]['vehicles'][] = $vehicle;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 第二次遍历:根据规则生成报警
|
||
foreach ($perimeterPolygons as $pp) {
|
||
$perimeter = $pp['perimeter'];
|
||
$perimeterId = $perimeter['id'];
|
||
|
||
if (!isset($perimeterVehicleStats[$perimeterId])) {
|
||
continue;
|
||
}
|
||
|
||
$stats = $perimeterVehicleStats[$perimeterId];
|
||
|
||
foreach ($stats['vehicles'] as $vehicle) {
|
||
$vehicleAlarms = [];
|
||
|
||
// 1. 超速校验
|
||
if ($perimeter['max_speed'] > 0 && $vehicle['speed'] > $perimeter['max_speed']) {
|
||
$vehicleAlarms[] = [
|
||
'name' => '超速行驶(限速' . $perimeter['max_speed'] . 'km/h,实际' . $vehicle['speed'] . 'km/h)',
|
||
'type' => 1, // 超速
|
||
];
|
||
}
|
||
|
||
// 2. 禁行区域校验
|
||
if ($perimeter['is_ban'] == 1 && $vehicle['speed'] > 0) {
|
||
$vehicleAlarms[] = [
|
||
'name' => '进入禁行区域(' . $perimeter['name'] . ')',
|
||
'type' => 2, // 禁行
|
||
];
|
||
}
|
||
|
||
// 3. 禁停区域校验(车辆速度为0表示停止)
|
||
if ($perimeter['is_stop'] == 1 && $vehicle['speed'] == 0) {
|
||
$vehicleAlarms[] = [
|
||
'name' => '在禁停区域停车(' . $perimeter['name'] . ')',
|
||
'type' => 3, // 禁停
|
||
];
|
||
}
|
||
|
||
// 4. 危险源区域预警
|
||
if ($perimeter['is_hazard'] == 1) {
|
||
$vehicleAlarms[] = [
|
||
'name' => '进入危险源区域(' . $perimeter['name'] . ')',
|
||
'type' => 6, // 危险源预警
|
||
];
|
||
}
|
||
|
||
// 5. 停车数量超限校验
|
||
if ($perimeter['stop_vehicle'] > 0 &&
|
||
$stats['stop_count'] > $perimeter['stop_vehicle'] &&
|
||
$vehicle['speed'] == 0) {
|
||
$vehicleAlarms[] = [
|
||
'name' => '停车数量超限(限' . $perimeter['stop_vehicle'] . '辆,当前' . $stats['stop_count'] . '辆)',
|
||
'type' => 5, // 超最大数量
|
||
];
|
||
}
|
||
|
||
// 6. 行驶车辆超限校验
|
||
if ($perimeter['feasible_vehicle'] > 0 &&
|
||
$stats['drive_count'] > $perimeter['feasible_vehicle'] &&
|
||
$vehicle['speed'] > 0) {
|
||
$vehicleAlarms[] = [
|
||
'name' => '行驶车辆超限(限' . $perimeter['feasible_vehicle'] . '辆,当前' . $stats['drive_count'] . '辆)',
|
||
'type' => 5, // 超最大数量
|
||
];
|
||
}
|
||
|
||
// 生成报警记录
|
||
foreach ($vehicleAlarms as $alarm) {
|
||
// 检查是否已存在相同的未处理报警(防止重复报警)
|
||
// 同一车辆、同一类型、同一天只报警一次
|
||
$existAlarm = Db::name('call_the_police')
|
||
->where('license', $vehicle['vehNo'])
|
||
->where('type', $alarm['type'])
|
||
->where('is_del', 1)
|
||
->whereTime('create_time', 'd')
|
||
->find();
|
||
|
||
if (!$existAlarm) {
|
||
$alarms[] = [
|
||
'name' => $alarm['name'],
|
||
'type' => $alarm['type'],
|
||
'license' => $vehicle['vehNo'],
|
||
'perimeter_point' => $vehicle['longitude'] . ',' . $vehicle['latitude'],
|
||
'trigger_type' => 1, // 自动触发
|
||
'is_del' => 1,
|
||
'create_time' => $now,
|
||
'res_department' => 1,
|
||
];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量插入报警记录
|
||
if (!empty($alarms)) {
|
||
Db::name('call_the_police')->insertAll($alarms);
|
||
}
|
||
|
||
return count($alarms);
|
||
}
|
||
|
||
/**
|
||
* 解析周界坐标字符串为多边形数组
|
||
* @param string $info 坐标字符串,格式: "lng1,lat1;lng2,lat2;..."
|
||
* @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' => trim($coords[0]),
|
||
'lat' => trim($coords[1])
|
||
];
|
||
}
|
||
}
|
||
|
||
return $polygon;
|
||
}
|
||
|
||
/**
|
||
* 测试接口:检查周界配置和车辆位置
|
||
* 用于调试报警生成功能
|
||
*/
|
||
public function test_alarm_check()
|
||
{
|
||
$result = [];
|
||
|
||
// 1. 查询所有有坐标的周界
|
||
$perimeters = Db::name('perimeter')
|
||
->where('is_del', 1)
|
||
->where('info', '<>', '')
|
||
->field('id,name,info,is_stop,is_ban,is_hazard,stop_vehicle,feasible_vehicle,max_speed')
|
||
->select();
|
||
|
||
$result['perimeter_count'] = count($perimeters);
|
||
$result['perimeters_with_rules'] = [];
|
||
|
||
foreach ($perimeters as $p) {
|
||
// 统计有规则设置的周界
|
||
if ($p['is_stop'] == 1 || $p['is_ban'] == 1 || $p['is_hazard'] == 1 ||
|
||
$p['max_speed'] > 0 || $p['stop_vehicle'] > 0 || $p['feasible_vehicle'] > 0) {
|
||
$result['perimeters_with_rules'][] = [
|
||
'id' => $p['id'],
|
||
'name' => $p['name'],
|
||
'is_stop' => $p['is_stop'],
|
||
'is_ban' => $p['is_ban'],
|
||
'is_hazard' => $p['is_hazard'],
|
||
'max_speed' => $p['max_speed'],
|
||
'stop_vehicle' => $p['stop_vehicle'],
|
||
'feasible_vehicle' => $p['feasible_vehicle'],
|
||
];
|
||
}
|
||
}
|
||
|
||
// 2. 查询最近同步的车辆
|
||
$vehicles = Db::name('vehicle')
|
||
->where('is_del', 1)
|
||
->field('vehicleNo,longitude,latitude,vec1,positionTime')
|
||
->order('positionTime desc')
|
||
->limit(10)
|
||
->select();
|
||
|
||
$result['recent_vehicles'] = $vehicles;
|
||
|
||
// 3. 查询今天的报警
|
||
$todayAlarms = Db::name('call_the_police')
|
||
->where('is_del', 1)
|
||
->whereTime('create_time', 'd')
|
||
->field('license,name,type,create_time')
|
||
->order('create_time desc')
|
||
->limit(20)
|
||
->select();
|
||
|
||
$result['today_alarms'] = $todayAlarms;
|
||
$result['today_alarm_count'] = count($todayAlarms);
|
||
|
||
// 4. 测试一辆车是否在某个周界内
|
||
if (!empty($vehicles) && !empty($perimeters)) {
|
||
$testVehicle = $vehicles[0];
|
||
$point = [
|
||
'lng' => $testVehicle['longitude'],
|
||
'lat' => $testVehicle['latitude']
|
||
];
|
||
|
||
$result['test_vehicle'] = $testVehicle['vehicleNo'];
|
||
$result['test_in_perimeters'] = [];
|
||
|
||
foreach ($perimeters as $p) {
|
||
$polygon = $this->parsePerimeterCoords($p['info']);
|
||
if (!empty($polygon) && $this->is_point_in_polygon($point, $polygon)) {
|
||
$result['test_in_perimeters'][] = [
|
||
'id' => $p['id'],
|
||
'name' => $p['name']
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
$this->success('测试结果', $result);
|
||
}
|
||
|
||
/**
|
||
* 手动创建测试报警
|
||
* 用于验证滚动报警显示功能
|
||
*/
|
||
public function create_test_alarm()
|
||
{
|
||
$now = date('Y-m-d H:i:s');
|
||
|
||
// 获取一辆最近的车辆
|
||
$vehicle = Db::name('vehicle')
|
||
->where('is_del', 1)
|
||
->field('vehicleNo,longitude,latitude')
|
||
->order('positionTime desc')
|
||
->find();
|
||
|
||
if (!$vehicle) {
|
||
$this->error('没有找到车辆数据');
|
||
}
|
||
|
||
// 创建一条测试报警(只使用存在的字段)
|
||
$alarmData = [
|
||
'name' => '测试报警-超速行驶(限速80km/h,实际100km/h)',
|
||
'type' => 1, // 超速
|
||
'license' => $vehicle['vehicleNo'],
|
||
'perimeter_point' => $vehicle['longitude'] . ',' . $vehicle['latitude'],
|
||
'trigger_type' => 1, // 自动触发
|
||
'is_del' => 1,
|
||
'create_time' => $now,
|
||
'res_department' => 1,
|
||
];
|
||
|
||
$id = Db::name('call_the_police')->insertGetId($alarmData);
|
||
|
||
$this->success('测试报警创建成功', [
|
||
'alarm_id' => $id,
|
||
'vehicle' => $vehicle['vehicleNo'],
|
||
'create_time' => $now
|
||
]);
|
||
}
|
||
|
||
|
||
/**
|
||
* 计算已上报和未上报车辆数量
|
||
* 已上报:长寿API车辆在司机端有上报记录(运单表或入园表)
|
||
* 未上报:长寿API车辆总数 - 已上报数
|
||
*/
|
||
public function get_report_count()
|
||
{
|
||
$input = file_get_contents('php://input');
|
||
$body = json_decode($input, true);
|
||
|
||
// 获取长寿API传过来的车牌列表
|
||
$apiVehicles = [];
|
||
if (!empty($body['vehicles']) && is_array($body['vehicles'])) {
|
||
$apiVehicles = $body['vehicles'];
|
||
}
|
||
|
||
$totalCount = count($apiVehicles);
|
||
$reportCount = 0;
|
||
|
||
if ($totalCount > 0) {
|
||
// 获取今天的日期范围
|
||
$todayStart = date('Y-m-d 00:00:00');
|
||
$todayEnd = date('Y-m-d 23:59:59');
|
||
|
||
// 查询今天运单表中已上报的车牌(tow_license 是牵引车牌号)
|
||
$waybillVehicles = Db::name('waybill')
|
||
->where('create_time', '>=', $todayStart)
|
||
->where('create_time', '<=', $todayEnd)
|
||
->column('tow_license');
|
||
|
||
// 查询今天入园表中已上报的车牌(tractor_license 是牵引车牌号)
|
||
// 注意:Park模型对应的表名是 not_waybill_report
|
||
$parkVehicles = Db::name('not_waybill_report')
|
||
->where('create_time', '>=', $todayStart)
|
||
->where('create_time', '<=', $todayEnd)
|
||
->column('tractor_license');
|
||
|
||
// 合并去重已上报的车牌
|
||
$reportedVehicles = array_unique(array_merge($waybillVehicles, $parkVehicles));
|
||
|
||
// 计算API车辆中有多少已上报
|
||
foreach ($apiVehicles as $vehNo) {
|
||
if (in_array($vehNo, $reportedVehicles)) {
|
||
$reportCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
$noReportCount = $totalCount - $reportCount;
|
||
|
||
$this->success('获取成功', [
|
||
'total_count' => $totalCount,
|
||
'report_count' => $reportCount,
|
||
'no_report_count' => $noReportCount
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 根据车牌号列表获取车辆的车辆码颜色(qr_color)
|
||
* 用于前端统计各颜色码的车辆数量
|
||
*/
|
||
public function get_vehicle_code_colors()
|
||
{
|
||
$input = file_get_contents('php://input');
|
||
$body = json_decode($input, true);
|
||
|
||
// 获取车牌号列表
|
||
$vehicleNos = [];
|
||
if (!empty($body['vehicles']) && is_array($body['vehicles'])) {
|
||
$vehicleNos = array_unique($body['vehicles']); // 去重
|
||
}
|
||
|
||
$vehicleCodeMap = [];
|
||
|
||
if (!empty($vehicleNos)) {
|
||
// 批量查询车辆的 qr_color(车辆码颜色)
|
||
$vehicles = Db::name('vehicle')
|
||
->where('vehicleNo', 'in', $vehicleNos)
|
||
->where('is_del', 'in', '1,2')
|
||
->field('vehicleNo, qr_color')
|
||
->select();
|
||
|
||
// 构建车牌号到车辆码颜色的映射
|
||
foreach ($vehicles as $vehicle) {
|
||
$vehicleCodeMap[$vehicle['vehicleNo']] = intval($vehicle['qr_color']); // 1=绿码, 2=黄码, 3=红码
|
||
}
|
||
|
||
// 对于没有在数据库中找到的车辆,默认设置为 2(黄码)
|
||
foreach ($vehicleNos as $vehNo) {
|
||
if (!isset($vehicleCodeMap[$vehNo])) {
|
||
$vehicleCodeMap[$vehNo] = 2; // 默认黄码
|
||
}
|
||
}
|
||
}
|
||
|
||
$this->success('获取成功', $vehicleCodeMap);
|
||
}
|
||
|
||
|
||
/**
|
||
* //TODO 判断一个坐标是否在一个多边形内(由多个坐标围成的)
|
||
* //TODO 基本思想是利用射线法,计算射线与多边形各边的交点,如果是偶数,则点在多边形外,否则
|
||
* //TODO 在多边形内。还会考虑一些特殊情况,如点在多边形顶点上,点在多边形边上等特殊情况。
|
||
* @param $point //指定点坐标
|
||
* @param $pts //多边形坐标,顺时针方向
|
||
* @return bool
|
||
*/
|
||
public function is_point_in_polygon($point, $pts) {
|
||
$N = count($pts);
|
||
$boundOrVertex = true; //如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
|
||
$intersectCount = 0;//cross points count of x
|
||
$precision = 2e-10; //浮点类型计算时候与0比较时候的容差
|
||
$p1 = 0;//neighbour bound vertices
|
||
$p2 = 0;
|
||
$p = $point; //测试点
|
||
|
||
$p1 = $pts[0];//left vertex
|
||
for ($i = 1; $i <= $N; ++$i) {//check all rays
|
||
// dump($p1);
|
||
if ($p['lng'] == $p1['lng'] && $p['lat'] == $p1['lat']) {
|
||
return $boundOrVertex;//p is an vertex
|
||
}
|
||
|
||
$p2 = $pts[$i % $N];//right vertex
|
||
if ($p['lat'] < min($p1['lat'], $p2['lat']) || $p['lat'] > max($p1['lat'], $p2['lat'])) {//ray is outside of our interests
|
||
$p1 = $p2;
|
||
continue;//next ray left point
|
||
}
|
||
|
||
if ($p['lat'] > min($p1['lat'], $p2['lat']) && $p['lat'] < max($p1['lat'], $p2['lat'])) {//ray is crossing over by the algorithm (common part of)
|
||
if($p['lng'] <= max($p1['lng'], $p2['lng'])){//x is before of ray
|
||
if ($p1['lat'] == $p2['lat'] && $p['lng'] >= min($p1['lng'], $p2['lng'])) {//overlies on a horizontal ray
|
||
return $boundOrVertex;
|
||
}
|
||
|
||
if ($p1['lng'] == $p2['lng']) {//ray is vertical
|
||
if ($p1['lng'] == $p['lng']) {//overlies on a vertical ray
|
||
return $boundOrVertex;
|
||
} else {//before ray
|
||
++$intersectCount;
|
||
}
|
||
} else {//cross point on the left side
|
||
$xinters = ($p['lat'] - $p1['lat']) * ($p2['lng'] - $p1['lng']) / ($p2['lat'] - $p1['lat']) + $p1['lng'];//cross point of lng
|
||
if (abs($p['lng'] - $xinters) < $precision) {//overlies on a ray
|
||
return $boundOrVertex;
|
||
}
|
||
|
||
if ($p['lng'] < $xinters) {//before ray
|
||
++$intersectCount;
|
||
}
|
||
}
|
||
}
|
||
} else {//special case when ray is crossing through the vertex
|
||
if ($p['lat'] == $p2['lat'] && $p['lng'] <= $p2['lng']) {//p crossing over p2
|
||
$p3 = $pts[($i+1) % $N]; //next vertex
|
||
if ($p['lat'] >= min($p1['lat'], $p3['lat']) && $p['lat'] <= max($p1['lat'], $p3['lat'])) { //p.lat lies between p1.lat & p3.lat
|
||
++$intersectCount;
|
||
} else {
|
||
$intersectCount += 2;
|
||
}
|
||
}
|
||
}
|
||
$p1 = $p2;//next ray left point
|
||
}
|
||
|
||
if ($intersectCount % 2 == 0) {//偶数在多边形外
|
||
return false;
|
||
} else { //奇数在多边形内
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存车辆轨迹数据到 vehicle_line 表
|
||
* @param array $data 车辆数据
|
||
* @param string $now 当前时间
|
||
* @return array 返回保存结果 ['success' => 成功数量, 'fail' => 失败数量, 'errors' => 错误信息]
|
||
*/
|
||
private function saveVehicleTrajectory($data, $now) {
|
||
$result = ['success' => 0, 'fail' => 0, 'errors' => []];
|
||
|
||
// 验证数据格式
|
||
if (empty($data) || !is_array($data)) {
|
||
$result['errors'][] = '轨迹数据为空或格式错误';
|
||
return $result;
|
||
}
|
||
|
||
// 处理车辆数据列表
|
||
$vehicleList = [];
|
||
|
||
foreach ($data as $item) {
|
||
// 兼容两种数据格式:
|
||
// 格式1: $item['posList'] 包含车辆数组
|
||
// 格式2: $item 直接就是车辆数据(有 vehNo, lon, lat 字段)
|
||
if (isset($item['posList']) && is_array($item['posList'])) {
|
||
// 格式1: 有 posList 字段
|
||
foreach ($item['posList'] as $vehicle) {
|
||
$vehicleList[] = $vehicle;
|
||
}
|
||
} elseif (isset($item['vehNo']) && isset($item['lon']) && isset($item['lat'])) {
|
||
// 格式2: 直接是车辆数据
|
||
$vehicleList[] = $item;
|
||
}
|
||
}
|
||
|
||
// 处理所有车辆数据
|
||
foreach ($vehicleList as $vehicle) {
|
||
try {
|
||
// 验证必要字段
|
||
if (empty($vehicle['vehNo']) || empty($vehicle['lon']) || empty($vehicle['lat'])) {
|
||
$result['fail']++;
|
||
$result['errors'][] = '车辆数据缺少必要字段: ' . json_encode($vehicle, JSON_UNESCAPED_UNICODE);
|
||
continue;
|
||
}
|
||
|
||
$vehNo = trim($vehicle['vehNo']);
|
||
$longitude = number_format(floatval($vehicle['lon']), 7);
|
||
$latitude = number_format(floatval($vehicle['lat']), 7);
|
||
|
||
// 验证坐标有效性
|
||
if ($longitude == 0 || $latitude == 0) {
|
||
$result['fail']++;
|
||
$result['errors'][] = "车辆 {$vehNo} 坐标无效: {$longitude}, {$latitude}";
|
||
continue;
|
||
}
|
||
|
||
$coordinate = $longitude . ',' . $latitude;
|
||
|
||
// 处理定位时间
|
||
$positionTime = $now;
|
||
if (isset($vehicle['posTime'])) {
|
||
if (is_numeric($vehicle['posTime'])) {
|
||
// 处理时间戳:如果是11位(毫秒),转换为10位(秒)
|
||
$timestamp = $vehicle['posTime'];
|
||
if ($timestamp > 9999999999) {
|
||
$timestamp = intval($timestamp / 1000); // 毫秒转秒
|
||
}
|
||
// 验证时间戳是否合理(1970-2100之间)
|
||
if ($timestamp > 0 && $timestamp < 4102444800) {
|
||
$positionTime = date('Y-m-d H:i:s', $timestamp);
|
||
} else {
|
||
// 时间戳异常,使用当前时间
|
||
$positionTime = $now;
|
||
}
|
||
} else {
|
||
$positionTime = $vehicle['posTime'];
|
||
}
|
||
}
|
||
|
||
// 查询当天是否已有该车辆的轨迹记录
|
||
// 使用更精确的日期查询,避免时区问题
|
||
$todayStart = date('Y-m-d 00:00:00');
|
||
$todayEnd = date('Y-m-d 23:59:59');
|
||
$lineRecord = Db::name('vehicle_line')
|
||
->where('vehicleNo', $vehNo)
|
||
->where('create_time', '>=', $todayStart)
|
||
->where('create_time', '<=', $todayEnd)
|
||
->where('is_del', 1)
|
||
->order('id desc')
|
||
->find();
|
||
|
||
// 获取速度信息(用于统计行驶/停止状态)
|
||
$speed = isset($vehicle['v1']) ? floatval($vehicle['v1']) : 0;
|
||
|
||
// 构建新的坐标点
|
||
$newPoint = [
|
||
'coordinate' => $coordinate,
|
||
'create_time' => $positionTime,
|
||
'speed' => $speed, // 保存轨迹点当时的速度
|
||
'last_coordinate_status' => 1, // 1:未处理, 2:已处理
|
||
'max_enter_status' => 1, // 1:未处理, 2:已处理
|
||
'max_out_status' => 1, // 1:未处理, 2:已处理
|
||
];
|
||
|
||
if ($lineRecord) {
|
||
// 更新现有记录,追加新坐标点
|
||
$lineData = json_decode($lineRecord['line'], true);
|
||
if (!is_array($lineData)) {
|
||
$lineData = [];
|
||
}
|
||
|
||
// 检查是否已存在相同坐标点(避免重复)
|
||
$exists = false;
|
||
foreach ($lineData as $point) {
|
||
if (isset($point['coordinate']) && $point['coordinate'] === $coordinate) {
|
||
$exists = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$exists) {
|
||
$lineData[] = $newPoint;
|
||
$updateResult = Db::name('vehicle_line')
|
||
->where('id', $lineRecord['id'])
|
||
->update(['line' => json_encode($lineData, JSON_UNESCAPED_UNICODE)]);
|
||
|
||
if ($updateResult !== false) {
|
||
$result['success']++;
|
||
} else {
|
||
$result['fail']++;
|
||
$result['errors'][] = "更新车辆 {$vehNo} 轨迹失败";
|
||
}
|
||
} else {
|
||
// 坐标点已存在,不算失败也不算成功
|
||
}
|
||
} else {
|
||
// 创建新记录前,再次检查(防止并发插入)
|
||
$checkRecord = Db::name('vehicle_line')
|
||
->where('vehicleNo', $vehNo)
|
||
->where('create_time', '>=', $todayStart)
|
||
->where('create_time', '<=', $todayEnd)
|
||
->where('is_del', 1)
|
||
->order('id desc')
|
||
->find();
|
||
|
||
if ($checkRecord) {
|
||
// 如果找到了记录,说明是并发插入,更新这条记录
|
||
$lineData = json_decode($checkRecord['line'], true);
|
||
if (!is_array($lineData)) {
|
||
$lineData = [];
|
||
}
|
||
|
||
// 检查是否已存在相同坐标点
|
||
$exists = false;
|
||
foreach ($lineData as $point) {
|
||
if (isset($point['coordinate']) && $point['coordinate'] === $coordinate) {
|
||
$exists = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$exists) {
|
||
$lineData[] = $newPoint;
|
||
$updateResult = Db::name('vehicle_line')
|
||
->where('id', $checkRecord['id'])
|
||
->update(['line' => json_encode($lineData, JSON_UNESCAPED_UNICODE)]);
|
||
|
||
if ($updateResult !== false) {
|
||
$result['success']++;
|
||
} else {
|
||
$result['fail']++;
|
||
$result['errors'][] = "更新车辆 {$vehNo} 轨迹失败(并发)";
|
||
}
|
||
}
|
||
} else {
|
||
// 确实没有记录,创建新记录
|
||
$lineData = [$newPoint];
|
||
$insertResult = Db::name('vehicle_line')->insert([
|
||
'vehicleNo' => $vehNo,
|
||
'line' => json_encode($lineData, JSON_UNESCAPED_UNICODE),
|
||
'create_time' => date('Y-m-d H:i:s'),
|
||
'is_del' => 1,
|
||
]);
|
||
|
||
if ($insertResult !== false) {
|
||
$result['success']++;
|
||
} else {
|
||
$result['fail']++;
|
||
$result['errors'][] = "插入车辆 {$vehNo} 轨迹失败";
|
||
}
|
||
}
|
||
}
|
||
} catch (\Exception $e) {
|
||
$result['fail']++;
|
||
$result['errors'][] = "保存车辆轨迹异常: " . $e->getMessage() . " - 车辆: " . (isset($vehicle['vehNo']) ? $vehicle['vehNo'] : '未知');
|
||
// 记录详细错误日志
|
||
\think\Log::error('保存车辆轨迹异常: ' . $e->getMessage() . PHP_EOL . $e->getTraceAsString());
|
||
}
|
||
}
|
||
|
||
// 如果有错误,记录日志
|
||
if ($result['fail'] > 0 && !empty($result['errors'])) {
|
||
$logData = [
|
||
'success' => $result['success'],
|
||
'fail' => $result['fail'],
|
||
'errors' => array_slice($result['errors'], 0, 10) // 只记录前10个错误
|
||
];
|
||
\think\Log::warning('车辆轨迹保存部分失败: ' . json_encode($logData, JSON_UNESCAPED_UNICODE));
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
}
|