了解了电子海图的基础知识及数据模型,我们可以着手解析电子海图了。在此,我们新建如下解决方案,并添加一个类库项目S57Parser(用于解析电子海图)和控制台程序S57MapInfo(用于验证解析结果)。
电子海图允许的数据格式如下:
格式 | 精度=w | 数据类型 |
---|---|---|
A | *) | 字符型 |
I | *) | 整型 |
R | *) | 浮点型 |
B | 二进制位串 | |
@ | 子字段标记 | |
b1w | 1, 2, 4 | 二进制式无符号整型 |
b2w | 1, 2, 4 | 二进制式有符号整型 |
*) 用X(n)表示,其中n表示长度为n字节;若不指定n,则表示长度不定;
若未指定格式,则表示可变长度的字符串,该字符串结束标志为单元终止符。
字符串编码格式默认为ASCII,但为了能正常显示如中文等非ASCII语言,S-57中将字符集分为三级,第一级用不同的终止符以间隔子字段的值:
级别 | 字符集 | 单元终止符UT | 字段终止符FT | 删除符 |
---|---|---|---|---|
级别0 | ASCII文本 | (1/15) | (1/14) | (7/15) |
级别1 | ISO 8859-1,拉丁字母(如西欧拉丁语) | (1/15) | (1/14) | (7/15) |
级别2 | 通用字符集(Unicode)(如中文,日文等) | (0/0)(1/15) | (0/0)(1/14) | (0/0)(7/15) |
为演示方便,S-57标准编码中涉及到的特殊的不可打印字符,用下列字符表示。
特殊字符 | 字节编码 | 表示字符 |
---|---|---|
空格 | (2/0) | □ |
单元终止符UT | (1/15) | △ |
字段终止符FT | (1/15) | ▽ |
NULL | (0/0) | ▪ |
二进制数 | ▲ |
电子海图都是由二进制编码的,因此需要新建一个工具类BytesHelper.cs,帮助读取二进制数据:
public class BytesHelper
{
/// <summary>
/// 加载电子海图及其更新文件
/// </summary>
/// <param name="filePath">文件路径</param>
public static void Load(string filePath)
{
var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
Position = 0;
ENCBytes = new byte[fs.Length];
fs.Read(BytesHelper.ENCBytes, 0, (int)fs.Length);
fs.Dispose();
}
public static int Position = 0; //存储当前解析到的位置
public static byte[] ENCBytes; //存储的电子海图数据
//字符集级别
private static Encoding asciiEncoding = new ASCIIEncoding(); //级别0 Ascii
private static Encoding latin1Encoding = Encoding.GetEncoding(28591); //级别1 ISO 8859-1;
private static Encoding unicodeEncoding = new UnicodeEncoding(false, true); //级别2 unicode
/// <summary>
/// 读取字节为字符串
/// </summary>
/// <param name="len">要读取的字节数</param>
/// <param name="lexicalLevel">字符集级别</param>
/// <returns>字符串</returns>
public static string GetString(int len, int lexicalLevel = 0)
{
string str = null;
if (lexicalLevel == 0)
{
str = asciiEncoding.GetString(ENCBytes, Position, len);
}
else if (lexicalLevel == 1)
{
str = latin1Encoding.GetString(ENCBytes, Position, len);
}
else
{
str = unicodeEncoding.GetString(ENCBytes, Position, len);
}
Position += len;
return str;
}
/// <summary>
/// 读取一个字符
/// </summary>
/// <returns>字符</returns>
public static char GetChar()
{
return (char)ENCBytes[Position++];
}
/// <summary>
/// 读取字节为整数
/// </summary>
/// <param name="len">字节数</param>
/// <returns>整数</returns>
public static int GetInteger(int len)
{
var num = 0;
for (int j = 0; j < len; j++)
{
num = num * 10 + (ENCBytes[Position++] - 48);
}
return num;
}
/// <summary>
/// 读取字节为整数
/// </summary>
/// <param name="len">字节数</param>
/// <returns>浮点数</returns>
public static double GetDouble(int len)
{
return double.Parse(GetString(len));
}
/// <summary>
/// 读取二进制位串为长整型
/// </summary>
/// <param name="len">字节数</param>
/// <returns>长整型</returns>
public static ulong GetBitStr(int len)
{
ulong res = 0;
for (int i = 0; i < len; i++)
{
res |= ((ulong)ENCBytes[Position++] << i * 8);
}
return res;
}
/// <summary>
/// 读取1字节为无符号整型
/// </summary>
/// <returns>无符号整型</returns>
public static byte GetByte()
{
return ENCBytes[Position++];
}
/// <summary>
/// 读取2字节为无符号整型
/// </summary>
/// <returns>无符号整型</returns>
public static UInt16 GetUInt16()
{
return (UInt16)(ENCBytes[Position++] | ENCBytes[Position++] << 8);
}
/// <summary>
/// 读取4字节为无符号整型
/// </summary>
/// <returns>无符号整型</returns>
public static UInt32 GetUInt32()
{
return (ENCBytes[Position++]
| (UInt32)ENCBytes[Position++] << 8
| (UInt32)ENCBytes[Position++] << 16
| (UInt32)ENCBytes[Position++] << 24);
}
/// <summary>
/// 读取1字节为有符号整型
/// </summary>
/// <returns>有符号整型</returns>
public static sbyte GetSByte()
{
return (sbyte)ENCBytes[Position++];
}
/// <summary>
/// 读取2字节为有符号整型
/// </summary>
/// <returns>有符号整型</returns>
public static Int16 GetInt16()
{
return (Int16)(ENCBytes[Position++] | ENCBytes[Position++] << 8);
}
/// <summary>
/// 读取3字节为有符号整型
/// </summary>
/// <returns>有符号整型</returns>
public static Int32 GetInt32()
{
return (ENCBytes[Position++]
| (Int32)ENCBytes[Position++] << 8
| (Int32)ENCBytes[Position++] << 16
| (Int32)ENCBytes[Position++] << 24);
}
}