通过AIS实现船舶追踪与照射

前些天突然接到个紧急的项目:某处需要实现对夜航船只进行追踪并用激光灯照射以保障夜航安全。这个项目紧急到什么程度呢?!现场激光灯都安装好了,还有三个星期就要验收了,但上家没搞定就甩给我们了:(

从技术上看,本项目没什么好写的,但正因为本项目如此之紧急,所以在工程上还有点意思,所以总结一下,以资参考。

系统组成

说起来,整个项目并不复杂。我完成的系统图如下:
在这里插入图片描述
其中:

一、现场采集器就是用之前在rust嵌入式开发之总结一文介绍过的我司标准板卡,主要完成:

  • 到两激光灯的RS485连接,并以Pelco-D球机控制协议进行控制
  • 和服务器的通信,实现远程控制
  • 提供命令行接口,完成激光灯的坐标读取等工程测试、标定等基础工作

本还需要完成到AIS基站的RS232串口连接来实时获取AIS数据,但AIS基站已经安装完毕,上家却没和设备厂家说如何取数据的事,所以AIS厂家也就没留RS232接口给我们,好在AIS厂家还提供了TcpServer能力,所以从AIS基站读取AIS数据的功能移到了应用服务器处。

二、应用服务器就是我之前介绍过的docker版TMS系统,有全套的业务快速开发、数据采集与处理能力。包括了两块:

  • 业务系统:java开发的tms业务后台,可以提供业务管控应用的低成本快速定制。本项目不需要向客户提供业务管控与应用,但需要制作几个测试操作界面,如模拟定位【即通过页面输入经纬度坐标,分别控制两灯的转动】、两灯的手动开关、开启关闭几个相关的debug能力以辅助调试、工程数据采集和验收

参考:jxTMS的设计思想

  • 数据系统:python开发的数据采集、解析、保存、处理系统,提供了完整的数据处理框架,可以快速而低成本的实现数据的处理与分析

参考:使用jxTMS采集数据之一

基于这两个系统,本项目一共只写了5个py文件,分别是:

1、site_ais_trace.py,在数据处理框架中定制一个站点。除站点的通用功能外【如设备管理、设备通信与远程控制等】,主要是提供激光灯的控制。代码非常简单,主要就是两个对象函数:

from module.ais_trace import ship, light, light_init
class site_ais_trace(site_packet):def __init__(self, name):#将激光灯的控制函数设置为本对象的control函数light.set_control_func(self.control)#继承父类super(site_ais_trace,self).__init__('site_ais_trace', name)#对激光灯进行初始化light_init()def control(self, slave=1, x=None, y=None, light=None):#控制功能也非常简单,就是向现场采集器下达三个远程控制命令#激光灯的控制是实现在现场控制器的laser_lamp_control函数中的#该函数为命令行和远程控制所共用rd = {'cmd':'laser_lamp_control'}if not x is None:#控制激光灯水平转动#根据设备手册,指定水平转动的值jxUtils.checkAssert(0 <= x and x <= 35999, 'x must in [0, 35999]')#根据laser_lamp_control函数的要求来设置参数rd['s'] = slaverd['n'] = 'x'rd['v'] = x#将控制命令下达给本站对应的现场控制器rs = self.systemCmd(rd)jxGo.log('info', f'site_ais_trace::control[{slave}]  x:{x} req:{rs}')time.sleep(0.05)if not y is None:#控制激光灯俯仰转动jxUtils.checkAssert(0 <= y and y <= 8999 or 27000 <= y and y <= 35999, 'y must in [0, 8999] or [27000, 35999]')rd['s'] = slaverd['n'] = 'y'rd['v'] = yrs = self.systemCmd(rd)jxGo.log('info', f'site_ais_trace::control[{slave}]  y:{y} req:{rs}')time.sleep(0.05)if not light is None:#控制激光灯灯光的开启与关闭jxUtils.checkAssert(light == 'close' or light == 'open', 'light must in open|close')rd['s'] = slaverd['n'] = 'light'rd['v'] = lightrs = self.systemCmd(rd)jxGo.log('info', f'site_ais_trace::control[{slave}]  light:{light} req:{rs}')time.sleep(0.05)

2、device_ais_trace.py,在数据处理框架中定制一个设备,用来从ais采集数据。主要就是设备的标准功能【如设备数据分析、保存、设备活跃性检测等】。代码同样非常简单,主要就是两个对象函数:

from module.tcpClient import tcpClient
from module.ais_trace import ship
#导入AIS解析
from ais.aismParser import aismParser#创建一个AIS解析器
_aismParser = aismParser()
class device_ais_trace(device):def __init__(self, name, mySite, conf):#AIS设备的手动配置工作:不执行超时检查、每条数据都保存等conf['timeOut'] = 0conf['timeOutCheckInterval'] = 0conf['saveDataInterval'] = 0conf['dataType'] = 'aism'#不将接收到的数据发送到数据总线上conf['dataBusInform'] = Falsesuper(device_ais_trace,self).__init__('device_ais_trace', name, mySite, conf)#本设备的数据不是标准的获取方法【通过现场采集器采集并发回】,需要用tcpClient手动获取self.client = tcpClient(self.recv, ip='xxx.xxx.xxx.xxx', port=pppp)self.client.connect()def recv(self, data):#解析读取到的AIS数据str = data.decode('utf-8')l, rc = _aismParser.receive(str)if rc:for d in l:#交ais_trace进行跟踪与照射的处理rd = ship.check(d)#如果需要照射,则保存接收到的AIS数据以备复查#由于该点的AIS基站天线配置的较强大,会接收到周围数十公里的AIS数据#该地是较大的港口,接收到的AIS数据量非常大,所以丢弃和本项目无关的AIS数据if not rd is None:d['light'] = rd#设备的receive函数是数据处理框架的标准接收接口,会自动完成:#设备状态检测、数据保存、数据广播【供其它感兴趣的应用使用】等工作self.receive(d)

3、main.py,数据系统主入口函数,主要完成系统启动、系统装配等工作。具体代码为:

#命令行处理
sn, sa = jxUtils.init()
#站点配置【下述的站点配置和站点订阅,也可以用web界面操作完成】
sc = {}
sc['type'] = 'site_ais_trace'
sc['name'] = 'xxx_站点名_xxx'
#设备列表
dl = []
sc['devices'] = dl
#添加设备配置
#本项目就一个ais设备
d = {}
d['type'] = 'device_ais_trace'
d['name'] = 'xxx_设备名_xxx'
dl.append(d)
#将站点添加到系统中
site.addSite(sc)
#通过mqtt订阅该站点以实现和站点的数据收发
from jx.jxUtils import _mqttClient
_mqttClient.subscribe('xxx_站点名_xxx')#启动一个pyService完成数据系统和业务系统的勾连,这样就可以通过业务系统中定制的web界面向数据系统下达命令了
jxUtils.startSlave(sn,sa)

注:一般用脚本来启动main.py,这样可以通过灵活的设置命令行参数来实现各种系统配置。本系统的启动脚本:

cd /home/tms/python/
python3 main_slave.py -n 'xxx_项目名' --startDeviceDataQuery --mqServerIP '127.0.0.1' --serviceName 'cwz01' --dataBus --dbName 'demoOrg_2255' --site --app --module --mqttServerIP '127.0.0.1' &

4、module/service_ais_trace.py,向标准的pyService处理框架中插入几个控制命令,实现业务系统对数据系统的命令,如在web界面直接下达开关灯命令:

from jx.mainService import mainService
from module.ais_trace import _light_1, _light_2def lightControl(params):jxGo.log('info',f'lightControl recv:{params}')slave = params.get('slave', 1)active = params.get('active', False)if slave == 1:if active:_light_1.light_open()else:_light_1.light_close()if slave == 2:if active:_light_2.light_open()else:_light_2.light_close()mainService.register('lightControl',lightControl)

这样就实现了通过web界面进行灯光开关的控制功能,在调试阶段非常的方便,需要增加什么样的调试功能,就可以非常迅速的实现了。

5、module/ais_trace.py,是本项目专门开发的ais跟踪计算模块,是本项目的核心算法模块。后文再展开介绍其处理逻辑

三、AIS基站启动tcpServer来供应AIS数据;两激光灯【附带一个摄像头,两者安装在同一个云台上】则是受控设备,接受ais_trace计算出来的x轴、y轴坐标,然后执行相应的旋转与俯仰、开关灯等动作。

AIS跟踪的基本原理

通过AIS数据来追踪船只位置,原理非常简单:

1、用AIS解析器来解析船只发送的AIS数据,得到船只的经纬度数据

2、根据经纬度计算出船只到本站的距离,从xxxx米开始追踪,到警戒区即开始照射【白天不照射】,警戒区划定为yyy米

注:追踪、照射的两阶段接力处理算法,是原本考虑当AIS数据发送频率太低时,通过扩大追踪范围尽可能多的收集船只位置数据,然后拟合出船只的航迹,再通过船速计算出船只下一刻的可能位置进行补点,以实现较好的照射效果。但到现场后,由于需要保障的夜航距离非常近,这一补点算法没有实施的必要,所以取消。但跟踪、控制的两阶段算法已经没时间进行重构了,只能保留

3、当船只进入警戒区后,两灯分别根据船只经纬度计算船只到灯的水平面【x轴】的张角和垂直面【y轴】的俯仰角,然后将其映射到激光灯坐标系下,再换算成激光灯对应的控制数据,最后发送给现场控制器下达到两灯执行

4、由于该站为旅游项目,激光灯下方各处都存在人员行走和休息区域,考虑到强光对人眼的威胁,所以有必要对灯光的照射范围进行限制,当计算出的【x, y】超出许可范围时,不照射

所以,本项目的核心就是地球平面直角坐标系中的各种三角函数的运算。主要涉及:

  • 经纬度转换
  • 根据经纬度计算两点间距离
  • 根据经纬度计算某点和本点的张角
  • 坐标系旋转
  • 不同坐标系的映射与变换

上述这些计算,只要大家初高中时三角函数学的还行,然后网上搜搜就完全可以写出来了。这里就不复赘述了。

有必要说明的只有一点,即:python的浮点数的精度问题。

python中常用的浮点数的精度在15-17位左右。而地球上经度一度差不多就是111公里,即1米就需要精确到百万分之九;地球半径我们取的是:6371004米,加上运算所需,所以浮点数的精度可能是不太够。

如果大家担心这一点,选择了Decimal,那就尽量设的大一点。

当然,就本项目来说,由于其它方面的误差太大,浮点数精度不够的影响可以忽略。

AIS跟踪的工程基准

本项目有4类7个基准数据,需要通过工程的方法加以确定。

这4类基准数据,是本项目保证追踪效果的基石:

  • 本站基准经纬度:用来确定船只和本站的距离,超过一定距离的船只就不需要跟踪了。本数据可以在本站主要位置选点,精度要求也不需要太高,反正误差几米不过是将原本可以不跟踪的船也纳入跟踪而已,这点计算量不值一提
  • 两灯的安装点经纬度:这是实现控制灯水平面旋转到船只所在方位的基础数据,自然是要求精度尽可能的高。但由于项目太紧急了,我的准备工作也自然不充分,没有借到差分的GPS设备。这种情况下经过和自己用手机打点的比较后,就选用了经过甲方确认的工程施工资料中的两灯经纬度【工程上的甩锅基本律:不准那是因为甲方提供的基准点数据的问题:)】
  • 两灯的海拔高度:这是实现控制灯垂直面俯仰,使得灯对照船只的基础数据。确定起来也简单,就以甲方提供的工程资料中所标注的平台海拔加上灯头到平台的高度作为两灯的海拔高度就可以了
  • 两灯安装后各灯的x轴零点朝向:这是完成计算后,将计算结果从经纬度坐标变换成激光灯的x轴坐标的基础数据。很遗憾,我们只有手机上的各种罗盘应用,这些应用都只给到度,简直郁闷至死!

其实,还有两灯的安装面的水平倾角这个基准数据。但原则上,我们肯定是要求水平安装的,而这在工程施工时也是最好矫正的。

但很遗憾,我看到激光灯的时候,某个灯的水平倾角肉眼可见的倾斜了,起码达到了十几度!而我是周一上去的,周五就要验收,还是台风天气,时不时的就风雨大作,大风天气下登高工作太过危险,所以干脆放弃了对两灯的安装面进行校准的想法。

这时,我们就面临两个关键的工程校准工作,这两者将直接决定项目成败:

  • 两灯的x轴零点朝向如何校准?
  • 两灯的水平倾角如何纠正?

既然手机罗盘只能达到度的精度,反正都已经是不尽如人意了,那干脆就用人眼视觉效果进行校准好了。

所以我的办法非常粗暴:

  • 在平台上选择了几个非常醒目的标识点,打这些点的GPS经纬度
  • 在不进行x轴校准的情况下计算出灯的x轴旋转度数
  • 手动将摄像头旋转到标识点,一点一点的精细操作摄像头对正该标识点
  • 从现场控制器通过命令行直接读取到此时激光灯的x轴数值

此时,直接读取到的x值和计算出的x值的差,就是激光灯x轴零点朝向的校准值。只要将几个标识点的校准值一一得到,就可以得到一个工程上比较满意的x轴零点朝向的校准值了。

但是,那个水平倾角误差较大的灯就会有很明显的误差:根据某点得到的x轴零点朝向的校准值,算出来的其它点的x值的误差最大的能达到25度!!这时看到的效果,就是船只没有被套住,即激光灯旋转过去后,船只不在画面内。

由于水平倾角无法归零,所以最终我选择了最重要的航线为基准剖面,以该面的x轴零点朝向的校准值作为最终值来设定激光灯的基准数据。

至于激光灯【x, y】值的范围约束,只要旋转激光灯,避开有人区域后,通过现场控制器的控制台读取此时的x、y值即可,然后根据一系列的【x, y】,取其中的最大最小值,就能确定【x, y】值的范围约束了。

AIS跟踪的程序逻辑

通过上面的讨论,实现AIS追踪的逻辑是比较简单的,只实现了两个类:ship和light,分别代表航经的船只、两个需要控制的激光灯。

ship主要是接收到AIS数据后完成:

  • 计算船只到本站的距离,超过追踪距离的就丢弃
  • 更新ship对象的实时信息【经纬度、距离、航速、驶向还是驶离、计算出的航迹等等】
  • 请求两灯计算是否需要照射本船
  • 当船只驶离本站后,经过一段时间后将其删除,以避免数据积累耗光内存

light主要是接收到船只的照射请求后完成:

  • 检查是否需要照射,不在照射时间内就驳回请求
  • 根据船只的经纬度计算船只到本灯的距离,超过警戒距离的驳回请求
  • 根据经纬度计算船只到本灯的张角,并将计算结果变换到激光灯的水平坐标系
  • 根据上述得到的本灯x轴零点朝向计算出纠正后的x值
  • 如果x值超范围,则驳回请求
  • 根据船只到本灯的距离计算出本灯到船只的俯仰角,然后换算成激光灯的y值
  • 如果y值超范围,则驳回请求
  • 根据计算出的【x, y】值下达照射指令

这里唯一需要额外考虑的就是:在航迹上最后一个允许照射点下达照射指令后,有可能就不再发出控制指令了,不可能让激光灯就这么一直照射着。所以,在下达照射指令后,必须启动一个防呆处理:如果过几分钟还没收到新的控制指令,就直接关灯。

结语

本项目从软件编写的角度来看非常简单,但由于工程上的各种坑,所以需要综合应用多种工程手段进行应对来保证照射效果。

正应了笔者反复说的那句话:程序员首先是一个工程师,工程师就是要解决问题的,而解决问题要靠我们经过长期训练所掌握的一整套的方法论

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/42269.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java应用系统设计与实现--学生信息管理系统(附解决方案源码)

一、实验目的及要求 1.1实验目的 掌握Java GUI编程技术&#xff0c;了解Swing框架的使用。 掌握MySQL数据库的基本操作&#xff0c;了解如何在Java中连接和操作数据库。 掌握用户权限管理的基本概念和实现方法。 提升综合运用所学知识设计和实现一个完整应用系统的能力…

windows机器免密登录linux主机

1. 正常连接需要输入密码 ssh root1.1.1.1 2. 在Windows上生成SSH密钥对&#xff08;如果你还没有的话&#xff09;&#xff1a; ssh-keygen 3. scp将id_rsa.pub传输到对应的主机 4.对应机器上查看 5.从windows上免密登录

ESP32 蓝牙网关实践:BLE 设备数据采集与 MQTT 云平台发布(附代码示例)

摘要: 本文详细介绍了如何使用 ESP32 构建强大的蓝牙网关&#xff0c;实现蓝牙设备与 Wi-Fi/互联网之间的无缝连接和数据桥接。文章涵盖了连接和桥接功能、数据处理和分析能力&#xff0c;并提供了详细的代码示例和 Mermaid 生成的图表&#xff0c;助您轻松构建自己的蓝牙网关解…

树(相关知识点)

目录 结点的度&#xff1a;某一个结点所含有字数的个数 叶节点&#xff1a;最后一个结点 非终端节点:不是叶结点 兄弟结点&#xff1a;亲兄弟结点 树的度&#xff1a;最大节点的度 层次&#xff1a;根为第一层&#xff0c;根的子结点为第二层&#xff0c;以此类推 森林&am…

微软拼音输入法不显示选字框问题

问题展示&#xff1a;不显示选字框 解决方式 打开兼容性即可&#xff08;估计是升级带来的bug&#xff09;

Android使用http加载自建服务器静态网页

最终效果如下图&#xff0c;成功加载了电脑端的静态网页内容&#xff0c;这是一个xml文件。 电脑端搭建http服务器 使用“Apache Http Server”&#xff0c;下载地址是&#xff1a;https://httpd.apache.org/download.cgi。具体操作步骤&#xff0c;参考&#xff1a;Apache …

深度学习与CV入门

文章目录 前言历史 前言 历史 tensorflow可以安装Tensorboard第三方库用于展示效果 TensorFlow工作流程&#xff1a;p6-4:20 使用tf.data加载数据。使用tf.data实例化读取训练数据和测试数据模型的建立与调试:使用动态图模式Eager Execution和著名的神经网络高层API框架Ker…

关于忠诚:忠于自己的良知、理想、信念

关于忠诚&#xff1a; 当我们面对公司、上司、爱人、恋人、合作伙伴还是某件事&#xff0c;会纠结离开还是留下&#xff0c;这里我们要深知忠诚的定义&#xff0c;我们不是忠诚于某个人、某件事、或者某个机构&#xff0c;而是忠诚于自己的良知&#xff0c;忠诚于自己的理想和…

1.1 常用文件管理命令

文章目录 前言正式学习文件系统常用的指令总结 前言 现在自己想做一个简单的编译器&#xff0c;但是安装环境就感觉非常难受&#xff0c;反正 linux 也是必须要学的&#xff0c;虽然&#xff0c;非常紧迫&#xff0c;但是很多事情着急也没有用&#xff0c;所以&#xff0c;现在…

ctfshow-web入门-文件上传(web151-web160)

目录 1、web151 2、web152 3、web153 4、web154 5、web155 6、web156 7、web157 8、web158 9、web159 10、web160 1、web151 试了下前端只能传 png 后缀的 将一句话木马改成 png 后缀&#xff0c;上传后用 burpsuite 抓包 绕过前端检测后&#xff0c;改回 php 后缀&am…

阶段三:项目开发---搭建项目前后端系统基础架构:任务11:搭建项目后台系统基础架构

任务描述 1、了解搭建民航后端框架 2、使用IDEA创建基于SpringBoot、MyBatis、MySQL、Redis的Java项目 3、以原项目为参照搭建项目所涉及到的各个业务和底层服务 4、以原项目为例&#xff0c;具体介绍各个目录情况并参照创建相关文件夹 任务指导 1、讲框架的选择和原理 …

《梦醒蝶飞:释放Excel函数与公式的力量》9.4 NPV函数

9.4 NPV函数 NPV函数是Excel中用于计算净现值的函数。净现值&#xff08;Net Present Value, NPV&#xff09;是财务管理和投资决策中常用的指标&#xff0c;用于评估投资项目的价值。NPV表示的是未来一系列现金流的现值总和减去初始投资后的余额。 9.4.1 函数简介 NPV函数通…

微信小程序订单发货管理接入

订单发货管理接入指引&#xff1a;https://mp.weixin.qq.com/cgi-bin/announce?token1148555877&actiongetannouncement&key11671435333v04b2&version1&langzh_CN&platform2https://mp.weixin.qq.com/cgi-bin/announce?token1148555877&actiongetann…

32位Arm嵌入式开发Ubuntu环境设置

32位Arm嵌入式开发Ubuntu环境设置 今天在调试一块32位ARM A7开发板时老是不成功&#xff0c;我装的是Ubuntu22.04版&#xff0c;在终端下运行工具链里的gdb程序居然报了一大堆错误&#xff0c;缺这个缺那个&#xff0c;按照提示装了一遍&#xff0c;再运行发现需要Python2.7环境…

【机器学习】基于密度的聚类算法:DBSCAN详解

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 基于密度的聚类算法&#xff1a;DBSCAN详解引言DBSCAN的基本概念点的分类聚类过…

QThread moveToThread的妙用

官方文档描述 总结就是移动到线程的对象不能有父对象&#xff0c;执行start即起一个线程&#xff0c;示例是将myObject移动到主线程中。QT中这种方式起一个线程是非常简单的。 示例描述以及代码 描述往Communicate线程中频繁添加任务&#xff0c;等任务结束的时候统计计算的结…

001,函数指针是一种特殊的指针,它指向的是一个函数地址,可以存储函数并作为参数传递,也可以用于动态绑定和回调函数

函数指针是一种特殊的指针 001&#xff0c;函数指针是一种特殊的指针&#xff0c;它指向的是一个函数地址&#xff0c;可以存储函数并作为参数传递&#xff0c;也可以用于动态绑定和回调函数 文章目录 函数指针是一种特殊的指针前言总结 前言 这是ai回答的标准答案 下面我们…

如何监控和分析 PostgreSQL 中的查询执行计划?

文章目录 一、为什么监控和分析查询执行计划很重要二、PostgreSQL 中用于获取查询执行计划的方法三、理解查询执行计划的关键元素四、通过示例分析查询执行计划五、优化查询执行计划的常见策略六、使用工具辅助分析七、结合实际案例的详细分析八、总结 在 PostgreSQL 数据库中&…

[LoaderRunner] 关于LoaderRunner的基本使用

LoadRunner环境搭建 LoadRunner运行的环境参考以下文档&#xff1a;Docs 介绍LoadRunner LoadRunner是什么 LoadRunner是性能测试工具&#xff0c;对软件或者系统的性能进行评估 为什么使用LoadRunner LoadRunner具有以下的优势&#xff1a; LoadRunner相比于其他的测试工具…

Python视觉轨迹几何惯性单元超维计算结构算法

&#x1f3af;要点 &#x1f3af;视觉轨迹几何惯性单元超维计算结构算法 | &#x1f3af;超维计算结构视觉场景理解 | &#x1f3af;超维计算结构算法解瑞文矩阵 | &#x1f3af;超维矢量计算递归神经算法 &#x1f36a;语言内容分比 &#x1f347;Python蒙特卡罗惯性导航 蒙…