2.3.1 電子海圖系統(tǒng)解析及開(kāi)發(fā) 功能開(kāi)發(fā) - 接入AIS功能

電子海圖系統(tǒng)單純顯示海圖,并不能起多大的作用,只有將其作為信息集成平臺(tái),附加上各種航行相關(guān)的信息,才能發(fā)揮其功效,而其中一項(xiàng)不可或缺的是AIS的功能。船舶自動(dòng)識(shí)別系統(tǒng)(AIS)是一種新型的助航設(shè)備,其基本功能是將船舶的標(biāo)識(shí)信息、位置信息、運(yùn)動(dòng)參數(shù)和航行狀態(tài)等與船舶航行安全有關(guān)的重要數(shù)據(jù),通過(guò)VHF數(shù)據(jù)鏈路,廣播給周圍的船舶,以實(shí)現(xiàn)對(duì)本海區(qū)船舶的識(shí)別和監(jiān)視。1995年7月,瑞典、芬蘭首次提出“無(wú)線電AIS”的概念;2000年12月,IMO MSC73會(huì)議通過(guò)AIS強(qiáng)制性安裝議案。

AIS發(fā)展歷程

AIS是基于無(wú)線電主動(dòng)應(yīng)答的設(shè)備,當(dāng)船舶安裝了AIS后,就可以與其它裝有AIS的船舶自動(dòng)交換重要的航行數(shù)據(jù),包括:船名、船位、船型(大?。?、航線、航速、航向、轉(zhuǎn)向速度等等。駕駛員無(wú)須逐個(gè)查詢船舶,就可以獲得所有裝有AIS船舶的完整的交通動(dòng)態(tài)信息并進(jìn)行監(jiān)控和指揮。如果將定期航行和固定航線的船舶的相關(guān)信息加在傳送信息中,AIS將成為一種船舶報(bào)告系統(tǒng)。AIS設(shè)備一旦啟動(dòng),會(huì)根據(jù)當(dāng)前的航速以及轉(zhuǎn)向率按照一定的周期不斷主動(dòng)向外部發(fā)送上述信息,與此同時(shí),設(shè)備本身也在不斷的接收他船信息。

商船上都預(yù)留AIS Pilot Plug,用數(shù)據(jù)線插上后,就可以接收本船AIS接收到的信息。

AIS Pilot Plug

AIS數(shù)據(jù)采用NMEA0813標(biāo)準(zhǔn)格式進(jìn)行傳輸,其設(shè)備類型為AI,報(bào)文類型為VDMVDO。VDM報(bào)文表示數(shù)據(jù)報(bào)告來(lái)自他船;VDO報(bào)文表示數(shù)據(jù)報(bào)告來(lái)自本船。AIS數(shù)據(jù)正文被分為27種消息類型。每一種消息類型有不同的編碼規(guī)則,連接AIS接收機(jī),根據(jù)收到的串口數(shù)據(jù)判定其消息類型后,依次解析即可。各消息類型的格式詳情可參見(jiàn)https://gpsd.gitlab.io/gpsd/AIVDM.html。

例1:!AIVDM,1,1,,B,177KQJ5000G?tO`K>RA1wUbN0TKH,0*5C
  • 字段1 (!AIVDM):表示是一個(gè)移動(dòng)AIS基站數(shù)據(jù),并且該數(shù)據(jù)來(lái)自他船;
  • 字段2 (1):當(dāng)前報(bào)文被拆分的個(gè)數(shù),因?yàn)镹MEA 0183規(guī)定每條報(bào)文最大為82個(gè)字符限制,當(dāng)數(shù)據(jù)報(bào)文超過(guò)限制時(shí),就需要拆分,此處的1表示該報(bào)文被拆分分1個(gè)(即沒(méi)有被拆分);
  • 字段3 (1):是被拆分后報(bào)文片段的編號(hào);
  • 字段4 ():報(bào)文序列標(biāo)識(shí),當(dāng)同時(shí)存在多條需要分段的報(bào)文時(shí),該字段用于區(qū)分隸屬于不同消息報(bào)文,此處為空表示沒(méi)有同時(shí)收到多條報(bào)文;
  • 字段5 (B):無(wú)線電頻道代碼, AIS使用來(lái)自兩個(gè)VHF無(wú)線電信道的雙工的高端:AIS信道A為161.975Mhz(87B); AIS通道B為162.025Mhz(88B);
  • 字段6 (177KQJ5000G?tO`K>RA1wUbN0TKH):是數(shù)據(jù)有效負(fù)載,需先判斷其消息類型,然后按照各消息類型格式進(jìn)行解析;
  • 字段7(0):是填充數(shù)據(jù)有效載荷到6位邊界(從0到5)所需的填充位數(shù),等效地,從中減去5可以知道在最后6位半字節(jié)中有多少個(gè)最低有效位數(shù)據(jù)有效載荷應(yīng)被忽略;
  • 字段8 (5C):數(shù)據(jù)完整性校驗(yàn)和。

AIS的有效數(shù)據(jù)中的字符串以特殊方式編碼(六位碼),即每個(gè)字符由六位二制數(shù)表示。將數(shù)據(jù)的二進(jìn)制切成連續(xù)的六位字節(jié),每個(gè)六位字節(jié)映射到一個(gè)ASCII字符,最后不足六位的則填充相應(yīng)位數(shù)的0。

Char ASCII Bits Char ASCII Bits Char ASCII Bits Char ASCII Bits
"0" 48 000000 "@" 64 010000 "P" 80 100000 "h" 104 110000
"1" 49 000001 "A" 65 010001 "Q" 81 100001 "i" 105 110001
"2" 50 000010 "B" 66 010010 "R" 82 100010 "j" 106 110010
"3" 51 000011 "C" 67 010011 "S" 83 100011 "k" 107 110011
"4" 52 000100 "D" 68 010100 "T" 84 100100 "l" 108 110100
"5" 53 000101 "E" 69 010101 "U" 85 100101 "m" 109 110101
"6" 54 000110 "F" 70 010110 "V" 86 100110 "n" 110 110110
"7" 55 000111 "G" 71 010111 "W" 87 100111 "o" 111 110111
"8" 56 001000 "H" 72 011000 "`" 96 101000 "p" 112 111000
"9" 57 001001 "I" 73 011001 "a" 97 101001 "q" 113 111001
":" 58 001010 "J" 74 011010 "b" 98 101010 "r" 114 111010
";" 59 001011 "K" 75 011011 "c" 99 101011 "s" 115 111011
"<" 60 001100 "L" 76 011100 "d" 100 101100 "t" 116 111100
"=" 61 001101 "M" 77 011101 "e" 101 101101 "u" 117 111101
">" 62 001110 "N" 78 011110 "f" 102 101110 "v" 118 111110
"?" 63 001111 "O" 79 011111 "g" 103 101111 "w" 119 111111

AIS數(shù)據(jù)解析

將NMEA0813的字段6提取出來(lái),得到二進(jìn)制串,前6位表示AIS的消息類型。

消息類型 描述 消息類型 描述
1 A類位置報(bào)告 15 詢問(wèn)
2 A類位置報(bào)告(分配時(shí)間) 16 指配模式命令
3 A類位置報(bào)告(對(duì)詢問(wèn)的回應(yīng)) 17 GNSS廣播二進(jìn)制消息
4 基站報(bào)告 18 標(biāo)準(zhǔn)的B類設(shè)備位置報(bào)告
5 船舶靜態(tài)和航行相關(guān)數(shù)據(jù) 19 擴(kuò)展的B類設(shè)備位置報(bào)告
6 尋址二進(jìn)制消息 20 數(shù)據(jù)鏈路管理消息
7 二進(jìn)制確認(rèn) 21 助航設(shè)備報(bào)告
8 二進(jìn)制廣播消息 22 信道管理
9 標(biāo)準(zhǔn)的SAR航空器位置報(bào)告 23 群組指配命令
10 UTC/日期詢問(wèn) 24 靜態(tài)數(shù)據(jù)報(bào)告
11 UTC/日期響應(yīng) 25 單時(shí)隙二進(jìn)制消息
12 尋址安全相關(guān)信息 26 帶有通信狀態(tài)的多時(shí)隙二進(jìn)制消息
13 安全相關(guān)確認(rèn) 27 大量程AIS廣播消息
14 安全相關(guān)廣播消息

在通常操作中,AIS收發(fā)器將每2到10秒廣播一次位置報(bào)告(對(duì)應(yīng)消息1、2或3),具體取決于航行過(guò)程中船舶的速度,以及每3分鐘在錨定和靜止的船舶上的位置報(bào)告,每6分鐘發(fā)送一次消息5,更多詳細(xì)信息請(qǐng)參見(jiàn)IALA Technical Clarifications on Recommendation ITU-R M.1371-1的第2.3部分。消息6用于符合內(nèi)河未加密結(jié)構(gòu)化擴(kuò)展消息系統(tǒng)。消息8通常用于私有加密消息,例如軍事演習(xí)中的位置傳輸。消息12、14用于文本消息,名義上與安全有關(guān),但也用于流量控制和偶爾的聊天用途。對(duì)于商船而言,實(shí)際中,很少見(jiàn)到除了1、3、4、5、18和24以外的消息,許多AIS發(fā)射器從不發(fā)射它們。

以消息1、2和3為例,為共享導(dǎo)航信息的公共報(bào)告結(jié)構(gòu),被稱為通用導(dǎo)航橫塊, 總共168位,占用一個(gè)AIVDM語(yǔ)句。其具體格式如下:

位置 長(zhǎng)度 名稱 類型 描述
0-5 6 消息類型 u 取值: 1-3
6-7 2 重復(fù)次數(shù)指示 u 指示重發(fā)的次數(shù)
缺省為0,3表示不再重發(fā)
8-37 30 MMSI u 九位數(shù)字的MMSI號(hào)碼
38-41 4 航行狀態(tài) e 0:動(dòng)力航行中
1:錨泊
2:失控
3:操作受限
4:受吃水限制
5:錨鏈系泊
6:擱淺
7:捕撈中
8:風(fēng)帆動(dòng)力航行
9:高速船
10:地效翼船
11~13:留作未來(lái)用
14:自動(dòng)識(shí)別搜救器已激活
15:未定義(默認(rèn)值)
42-49 8 轉(zhuǎn)向速率 (ROT) I3 真實(shí)值=(原始值/4.733)2
原始值=128,表明未提供
50-59 10 對(duì)地航速 (SOG) U1 以1/10kn表示的速度
60-60 1 艙位精度 b 1:高精度;0:低精度
61-88 28 經(jīng)度 I4 用1/10000分表示的經(jīng)度 (西經(jīng)為-)
89-115 27 緯度 I4 用1/10000分表示的緯度 (南緯為-)
116-127 12 對(duì)地航向 (COG) U1 以1/10度表示的航向
128-136 9 船首真航向 (HDG) u 范圍:0 ~ 359,511表示未提供
137-142 6 時(shí)間戳 u 以秒表示的時(shí)間戳
143-144 2 操縱指示符 e 0:未提供(默認(rèn)值)
1:無(wú)特殊操縱
2:特殊操作(如區(qū)域通行)
145-147 3 x 未被使用
148-148 1 RAIM標(biāo)志 b 0:未使用RAIM
1:使用RAIM
149-167 19 無(wú)線電狀態(tài) u 無(wú)線電系統(tǒng)的診斷信息

類型一列中,各字母代表:u(無(wú)符號(hào)整型),U(無(wú)符號(hào)整型,顯示成浮點(diǎn)型),i(有符號(hào)整型),I(有符號(hào)整型,顯示成浮點(diǎn)型),b(布爾型),e(枚舉型),x(為空或保留位),t(六位碼代表的字符串),d(二進(jìn)制數(shù)據(jù)),a(數(shù)組類型,‘^’前代表數(shù)組的長(zhǎng)度)。

例2:177KQJ5000G?tO`K>RA1wUbN0TKH
1 7 7 K Q J 5 0 0 0
000001 000111 000111 011011 100001 011010 000101 000000 000000 000000
G ? t O ` K > R A 1
010111 001111 111100 011111 101000 011011 001110 100010 010001 000001
w U b N 0 T K H
111111 100101 101010 011110 000000 100100 011011 011000
位置 二進(jìn)制串 描述
0-5 000001 消息類型:1
6-7 00 表示重發(fā)次數(shù)為0
8-37 0111 000111 011011 100001 011010 00 MMSI:477553000
38-41 0101 航行狀態(tài):5
42-49 000000 00 ROT:0
50-59 0000 000000 SOG:0
60-60 0 艙位精度:0
61-88 10111 001111 111100 011111 10100 經(jīng)度:-73407500 = -122.345834
89-115 0 011011 001110 100010 010001 00 緯度:28549700 = 47.582833
116-127 0001 111111 10 COG:510 = 51.0
128-136 0101 10101 HDG:181
137-142 0 01111 時(shí)間戳:15
143-144 0 0 操縱指示符:0
145-147 000
148-148 0 RAIM標(biāo)志:0
149-167 0 100100 011011 011000 無(wú)線電狀態(tài)

在解碼AIS數(shù)據(jù)中,需要將ASCII碼轉(zhuǎn)化成6位碼,然后大量對(duì)二進(jìn)制位進(jìn)行操作。因此新建擴(kuò)展類BitArrayExt,添加如下方法:

    public static class BitArrayExt
    {
        //獲取無(wú)符號(hào)整型值
        public static uint GetUInt(this BitArray bits, int offset, int bitCnt)
        {
            uint res = 0;
            for (int i = 0; i < bitCnt; i++)
            {
                if (bits[offset + i])
                {
                    res += (uint)1 << (bitCnt - 1 - i);
                }
            }

            return res;
        }

        //獲取有符號(hào)整型值
        public static int GetInt(this BitArray bits, int offset, int bitCnt)
        {
            var res = (int)GetUInt(bits, offset, bitCnt);
            int msb = 1 << (bitCnt - 1);
            bool isNegative = (res & msb) != 0;

            if (isNegative)
            {
                const int allOnesExceptLsb = -2;
                int signBits = allOnesExceptLsb << (bitCnt - 1);
                res |= signBits;
            }

            return res;
        }

        //獲取布爾值
        public static bool GetBit(this BitArray bits, int offset)
        {
            return bits[offset];
        }

        //獲取6位碼對(duì)應(yīng)的字符串
        public static string GetString(this BitArray bits, int offset, int bitCnt)
        {
            var res = "";
            var cnt = 0;
            while (cnt < bitCnt)
            {
                var c = (byte)GetUInt(bits, offset, 6);
                if (c < 32) c = (byte)(c + 64);

                //@ 跳過(guò)
                if(c != 64) res += (char) c;

                cnt += 6;
                offset += 6;
            }
            
            return res;
        }

        public static BitArray GetData(this BitArray bits, int offset, int bitCnt)
        {
            var len = bitCnt;
            if (bitCnt + offset < bits.Length) len = bits.Length - offset;
            
            var res = new BitArray(len);
            for (int i = 0; i < len; i++)
            {
                res[i] = bits[offset + i];
            }

            return res;
        }
    }

新建靜態(tài)類AISParser,根據(jù)不同消息類型,根據(jù)其標(biāo)準(zhǔn)進(jìn)行解碼,解碼結(jié)果以鍵值對(duì)的形式封裝進(jìn)哈希表中:

    public static class AISParser
    {
        //當(dāng)報(bào)文存在拆分時(shí),存儲(chǔ)上一拆分報(bào)文的信息。當(dāng)報(bào)文全部收集齊時(shí),需清除。
        private static Dictionary<string, string> sequenceDic = new Dictionary<string, string>();
        
        public static Hashtable ParseSentence(string sentence)
        {
            int fragmentCount;               //字段2 拆分總數(shù)
            int fragmentNo;                  //字段3 拆分序號(hào)
            string multiSequenceMessageId;   //字段4 報(bào)文序列標(biāo)識(shí)
            char? channelCode;               //字段5 頻道代碼
            BitArray payload;                //字段6 數(shù)據(jù)負(fù)載
            int bitCount;                    //負(fù)載長(zhǎng)度
            int padding;                     //字段7 填充位數(shù)
        
            var ss = sentence.Split(','); 
            fragmentCount = int.Parse(ss[1]);
            fragmentNo = int.Parse(ss[2]);
            multiSequenceMessageId = ss[3];
            channelCode = ss[4].Length > 0 ? ss[4][0] : default(char?);
            var payloadAscii = ss[5];
            padding = int.Parse(ss[6]);

            if (fragmentCount == fragmentNo) //報(bào)文已集齊
            {
                if (fragmentNo != 1) //存在分組
                {
                    if (sequenceDic.ContainsKey(multiSequenceMessageId))
                    {
                        payloadAscii = sequenceDic[multiSequenceMessageId] + payloadAscii;
                        sequenceDic.Remove(multiSequenceMessageId);
                    }
                    else
                    {
                        throw new Exception($"{multiSequenceMessageId}:{fragmentNo}/{fragmentCount} 數(shù)據(jù)缺失");
                    }
                }

                //開(kāi)始解析
                if (!string.IsNullOrEmpty(payloadAscii))
                {
                    bitCount = payloadAscii.Length * 6 - padding;
                    payload = new BitArray(bitCount);
            
                    for (int i = 0; i < payloadAscii.Length; i++)
                    {
                        //將Ais的Ascii碼轉(zhuǎn)化成6位碼
                        var c = (byte)payloadAscii[i];
                        c -= 48;
                        if (c > 40) c -= 8;

                        //將6位碼存儲(chǔ)到BitArray中
                        for (int j = 0; j < 6; j++)
                        {
                            if(i * 6 + j < bitCount) payload[i * 6 + j] = (c >> (5-j)) % 2 == 1;
                        }
                    }

            
                    //獲取消息類型
                    var messageId = payload.GetUInt(0, 6);
                    switch (messageId)
                    {
                        case 1:
                            return AisMessage_1(payload);
                        ... //其他消息類型的解碼
                        default:
                            throw new Exception($"暫不支持解析AIS消息類型[{messageId}]");
                    }
                    
                }
            }
            else //存在報(bào)文拆分
            {
                if (fragmentNo == 1) //分組中的第一條
                {
                    if (sequenceDic.ContainsKey(multiSequenceMessageId))
                    {
                        sequenceDic.Remove(multiSequenceMessageId);
                    }
                    
                    sequenceDic.Add(multiSequenceMessageId, payloadAscii);
                }
                else
                {
                    if (sequenceDic.ContainsKey(multiSequenceMessageId))
                    {
                        sequenceDic[multiSequenceMessageId] += payloadAscii;
                    }
                    else
                    {
                        throw new Exception($"{multiSequenceMessageId}:{fragmentNo}/{fragmentCount} 數(shù)據(jù)缺失");
                    }
                }
            }

            return null;
        }

        private static Hashtable AisMessage_1(BitArray bits)
        {
            var rot = bits.GetInt(42, 8);
            var hdg = bits.GetUInt(128, 9);
            var lon = bits.GetInt(61, 28);
            var lat = bits.GetInt(89, 27);
            var cog = bits.GetUInt(116, 12);
            
            var res = new Hashtable
            {
                {"MessageType", bits.GetUInt(0, 6)},
                {"RepeatIndicator", bits.GetUInt(6, 2)},
                {"MMSI", bits.GetUInt(8, 30)},
                {"NavigationalStatus", bits.GetUInt(38, 4)},
                {"RateOfTurn", rot == 128 ? default(double?) : Math.Pow(rot/4.733, 2)},
                {"SpeedOverGround", bits.GetUInt(50, 10)/10.0},
                {"PositionAccuracy", bits.GetBit(60)},
                {"Longitude", lon == 0x6791AC0 ? default(double?) : lon/600000.0},
                {"Latitude", lat == 0x3412140 ? default(double?) : lat/600000.0},
                {"CourseOverGround", cog == 0xE10 ? default(double?) : cog/10.0},
                {"TrueHeading", hdg == 511 ? default(uint?) : hdg},
                {"TimeStamp", bits.GetUInt(137, 6)},
                {"ManeuverIndicator", bits.GetUInt(143, 2)},
                {"Spare", bits.GetUInt(145, 3)},
                {"RAIMFlag", bits.GetBit(148)},
                {"SyncState", bits.GetUInt(149, 2)},
                {"SlotTimeOut", bits.GetUInt(151, 3)},
                {"SubMessage", bits.GetUInt(154, 14)},
            };
            return res;
        }

        ....  //其他消息類型的解碼
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容