通过矢量符号描述语言,可以知道符号具体形状;通过符号化指令,可以对指定的位置对点、线、面物标进行绘制;那落实到S-57数据中的一条条记录而言,具体调用哪些符号化指令,这就需要用到符号指令查询表(Look-Up Tables)。S-57物标目录中的每一类物标,都可以通过查询表获取其符号化指令。
若对于某物标获取不到其符号化指令,缺省值为:NODTA颜色填充,NODATA03图案填充,显示级别为0,被雷达图像覆盖,基本显示,组别为11050。
查询表结构
查询文件的编排规则为每一行数据的前四个字符,表示字段的名称;第5到10个字符,表示字段的长度;第10个字符之后的内容,表示字段的内容。
[
LUPT 29LU00491NILCONVYRL00008OLINES�
ATTC 16CATCON1�CONRAD3�
INST 83LS(DASH,4,CHGRD);SY(RACNSP01);TE(clr {0:0.0},VERCLR,3,1,2,15110,1,0,CHBLK,11)�
DISC 12DISPLAYBASE�
LUCM 612210�
],
// ......
[
LUPT 40LU00009NILACHAREA00003SPLAIN_BOUNDARIES�
ATTC 8CATACH8�
INST 43SY(ACHARE02);LS(DASH,2,CHMGF);CS(RESTRN01)�
DISC 9STANDARD�
LUCM 626220�
],
上面包含两条记录的相关说明,每个符号指令由如下字段构成(以首条记录为例):
- LUPT
- 2个字符 'LU' 表示查询表
- 5个字符 00491 表示标识符
- 3个字符 NIL=新版;ADD=新增;MOD=替换;DEL=删除
- 6个字符 物标目录类别 ‘CONVYR’(传送带)
- 1个字符 物标几何类别 'A'=面物标;'L'=线物标;'P'=点物标
- 5个字符 显示优先级 00008
- 1个字符 雷达优先级 'O'=在雷达图像上;'S'=在雷达图像下
- 最后其他字符 显示设置
面物标可以是:'PLAIN_BOUNDARIES'(简单边界)或 'SYMBOLIZED_ BOUNDARIES'(符号化边界)
点物标可以是:'SIMPLIFIED'(简单符号)或 'PAPER_CHART'(纸质海图符号)
线物标只能是:'LINES'简单边界是用实线、虚线、点线去给区域描边;而符号化边界是用复杂线型填充区域边界。
简单符号是用简单的几何图形加颜色当作灯标、沉船等点物标的符号;而纸质海图符号则是用与纸质海图一样的符号标注点物标。
- ATTC 属性组合(可重复,也可空)
- 6个字符 表示属性
- 1~15个字符 表示属性性
不同属性之间由单元分隔符隔开,属性值可为空,也可用'?'表示。
示例中存在两组属性值:CATCON=1和CONRAD=3
- INST
- 符号指令 示例中为:用虚线描绘,线中点设置符号RACNSP01,同时显示格式文本
- DISC
- 显示模式 ‘DISPLAYBASE’=基础显示;�‘STANDARD’=标准显示;�‘OTHER’=其他显示
- LUCM
- 描述与说明
综上可知:对于线物标CONVYR(传送带),如果其包含属性值CATCON=1(传送带类型=带式)和CONRAD=3(雷达显著物标=安装雷达反射器),那么用虚线描边,线段中点设置符号RACNSP01,同时显示格式文本VERCLR(净空高度)。
读取查询表
仿照上述查询表记录结构,新建类S52LookUp
。
//查询表 单条记录
public class S52LookUp
{
//LUPT
public int RCID; //标识符
public UInt16 ObjectClassCode; //物标目录类别
public char ObjectType; //几何类别 'A' Area 'L' Line 'P' Point
public int Priority; //优先级
public char RaderPriority; //雷达优先级 'O'=在雷达图像上;'S'=在雷达图像下
//PLAIN_BOUNDARIES;SYMBOLIZED_BOUDARIES (Areas)
//SIMPLIFIED; PAPER_CHART (Points)
//LINES (Lines)
public string DisplaySet; //显示设置
//ATTC
public UInt16[] AttrCodes;
public string[] AttrValues;
//INST
public string[][] Instruction; //符号指令 拆分成二维数组
//DISC
public string DisplayCategory; //显示模式
//LUCM
public string ViewingGroup; //组号
//缺省值
public static S52LookUp Default
{
get
{
return new S52LookUp()
{
Priority = 0,
RaderPriority = 'S',
DisplayCategory = "DISPLAYBASE",
ViewingGroup = "11050",
Instructions = new string[][]
{
new string[] { "AC", "NODTA" },
new string[] { "AP", "PRTSUR01" }
}
};
}
}
}
将查询表文件S52LookupTable
添加进项目S57Parser
,新建单例模式的类S52LookUps
存储整个查询表,让其继承至List<S52LookUp>
:
public class S52LookUps : List<S52LookUp>
{
private static readonly S52LookUps instance = new S52LookUps();
//显示的static 构造函数
static S52LookUps() { }
private S52LookUps()
{
var lines = Encoding.ASCII.GetString(Properties.Resources.S52LookupTable).Split('\n');
S52LookUp obj = null;
for (int i = 0; i < lines.Length; i++)
{
if (string.IsNullOrWhiteSpace(lines[i])) continue;
if (lines[i][0] == '[') //开始
{
obj = new S52LookUp();
continue;
}
if (lines[i][0] == ']') //结束
{
this.Add(obj);
continue;
}
var line = lines[i];
var tag = line.Substring(0, 4);
var lastUtIndex = line.LastIndexOf(Helper.UT);
if (tag == "LUPT")
{
obj.RCID = int.Parse(line.Substring(11, 5));
obj.ObjectClassCode = S57Objects.Instance[line.Substring(19, 6)].Code;
obj.ObjectType = line[25];
obj.Priority = int.Parse(line.Substring(26, 5));
obj.RaderPriority = line[31];
obj.DisplaySet = line.Substring(32, lastUtIndex-32);
}
else if (tag == "ATTC")
{
var attc = line.Substring(9, lastUtIndex - 9);
if (string.IsNullOrWhiteSpace(attc)) continue;
var ats = attc.Split(Helper.UT);
obj.AttrCodes = new UInt16[ats.Length];
obj.AttrValues = new string[ats.Length];
for (int j = 0; j < ats.Length; j++)
{
obj.AttrCodes[j] = S57Attributes.Instance[ats[j].Substring(0, 6)].Code;
obj.AttrValues[j] = ats[j].Substring(6);
}
}
else if (tag == "INST")
{
string[] inst = line.Substring(9, lastUtIndex - 9).Split(';');
obj.Instructions = new string[inst.Length][];
for (int j = 0; j < inst.Length; j++)
{
obj.Instructions[j] = inst[j].Split('(', ',', ')');
}
}
else if (tag == "DISC") obj.DisplayCategory = line.Substring(9, lastUtIndex - 9);
else if (tag == "LUCM") obj.ViewingGroup = line.Substring(9, lastUtIndex - 9);
}
}
public static S52LookUps Instance => instance;
}
使用查询表
查询表中规定了物标的显示优先级、显示指令,是否被雷达图像覆盖等。海图绘制时,先遍历各物标,通过物标类别、几何类型,用户显示设置(对于面物标和点物标有两个显示方式)为查找条件,找到查询表匹配的记录。若匹配记录为0,则使用缺省指令;若匹配记录为1,则直接使用该指令;若匹配记录大于1,则需要利用属性值进一步筛选。
利用属性筛选的规则:
- 物标需包含查询表记录中所含有的属性,并且值和顺序也需要一样。如:查询表中属性值4,3,4与物标中对应属性值3,4,3或4,3是不匹配的,但与4,3,4,7一致,因为7不需要被符号化。
- 如果查询表中属性值缺失,则表明任何属性值(除了unknown)都匹配。
- 如果查询表中属性值为‘?’,则只有物标属性值=unknown才匹配。
编码实现
新增用户设置,记录面物标是显示符号化边界还是简单边界,点物标是显示简单符号还是纸质海图符号。
public static class MySettings
{
// ...
public static bool SYMBOLIZED_BOUDARIES = true; //显示符号化边界
public static bool PAPER_CHART = true; //显示纸质海图符号
public static string DisplayMode = "STANDARD"; //海图模式
}
修改类S57Feature
,添加字段public S52LookUp LookUp
及比例尺范围。
public class S57Feature : S57Record
{
// ......
public S52LookUp LookUp;
public int ScaleMinium => GetAttrValue(133, 0);
public int ScaleMaximum => GetAttrValue(132, 0);
}
工具类中S52Tools
中,新增静态方法S52LookUp FindLookUpEntry(S57Feature f)
,为特征记录设置符号指令:
public static S52LookUp FindLookUpEntry(S57Feature f)
{
//几何类型
var objType = f.PRIM == 1 ? 'P' : f.PRIM == 2 ? 'L' : 'A';
//物标类别+显示模式
var entries = S52LookUps.Instance.Where(x => x.ObjectClassCode == f.OBJL
&& x.ObjectType == objType && x.DisplayCategory == MySettings.DisplayMode);
if (entries.Count() == 0) return S52LookUp.Default;
if (entries.Count() == 1) return entries.First();
if (f.PRIM == 1)
{
entries = entries.Where(x => x.DisplaySet == (MySettings.PAPER_CHART ? "PAPER_CHART" : "SIMPLIFIED"));
}
else if (f.PRIM == 3)
{
entries = entries.Where(x => x.DisplaySet == (MySettings.SYMBOLIZED_BOUDARIES ? "SYMBOLIZED_BOUDARIES" : "PLAIN_BOUNDARIES"));
}
if (entries.Count() == 0) return S52LookUp.Default;
if (entries.Count() == 1 || f.AttrCodes == null) return entries.First();
var lookup = entries.First(); //默认第一项
foreach (var en in entries)
{
if (en.AttrCodes == null) continue;
var isOk = true;
for (int i = 0; i < en.AttrCodes.Length; i++)
{
var index = -1;
for (var m = 0; m < f.AttrCodes.Length; m++)
{
if (f.AttrCodes[m] == en.AttrCodes[i])
{
index = m;
break;
}
}
if (index >= 0)
{
if (en.AttrValues[i] == "" && f.AttrValues[index] == "") //缺失
{
isOk = false;
break;
}
if (en.AttrValues[i] == "?" && f.AttrValues[index] != "")
{
isOk = false;
break;
}
//属性的值不匹配
if (!f.AttrValues[index].StartsWith(en.AttrValues[i]))
{
isOk = false;
break;
}
}
else
{
//没找到对应的属性
isOk = false;
break;
}
}
if (isOk)
{
lookup = en;
break;
}
}
return lookup;
}