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

1155 lines
53 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\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;
}
}