電子海圖系統(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是基于無(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數(shù)據(jù)采用NMEA0813標(biāo)準(zhǔn)格式進(jìn)行傳輸,其設(shè)備類型為AI,報(bào)文類型為VDM或VDO。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;
}
.... //其他消息類型的解碼
}