符號(hào)
不同用途的燈標(biāo)、錨地邊界、岸上建筑物等物標(biāo)在海圖都有不同的符號(hào),

符號(hào)所在畫布大小為32767個(gè)單位(每一單位代表0.01毫米)。
- 每個(gè)符號(hào)有一個(gè)全局唯一的名稱,也擁有一套坐標(biāo)系,坐標(biāo)原點(diǎn)位于左上角。
- 符號(hào)繪制的起點(diǎn)為(BoundingBoxX, BoundingBoxY),繪制區(qū)擁有一定高度(BoundingBoxHeight)與寬度(BoundingBoxWidth)。
- 符號(hào)還存在一個(gè)轉(zhuǎn)心(PivotPointX, PivotPointY),但轉(zhuǎn)心不一定位于繪制區(qū)內(nèi)部。在指定坐標(biāo)處繪制符號(hào)時(shí),應(yīng)該將轉(zhuǎn)心位于指定坐標(biāo)處;如果需要旋轉(zhuǎn)符號(hào),則旋轉(zhuǎn)的中心也為轉(zhuǎn)心。
- 符號(hào)的具體形狀由一系列指令(Instructions,由矢量符號(hào)描述語(yǔ)言編寫)構(gòu)成。
矢量符號(hào)描述語(yǔ)言
S-52標(biāo)準(zhǔn)中,是用矢量符號(hào)描述語(yǔ)言(Vector Symbol Description Language)來(lái)定義這些符號(hào)形狀的。這些符號(hào)被廣泛用于電子海圖中定義點(diǎn)、復(fù)雜的線型及填充復(fù)雜的圖案。
矢量符號(hào)描述語(yǔ)言使用一支虛構(gòu)的“筆”,上繪畫,然后記錄畫筆移動(dòng)的位置,最終形式電子海圖所需的矢量符號(hào)。與屏幕坐標(biāo)一樣,畫布坐標(biāo)的原點(diǎn)(位置0,0)在圖形的左上角,x坐標(biāo)向右延伸,y坐標(biāo)向下延伸。矢量符號(hào)描述語(yǔ)言畫筆的顏色、大小、移動(dòng)等都是通過(guò)指令實(shí)現(xiàn)的,其中:
- ; ??不同的指令用分號(hào)隔開
- , ??如果指令含有多個(gè)參數(shù),參數(shù)之間用逗號(hào)分隔;如果沒有參數(shù),則無(wú)需添加逗號(hào)
-
SP ?顏色
???表示畫筆的顏色,該顏色用一個(gè)字母代表某種顏色標(biāo)記。SP指令中畫筆顏色對(duì)作用于其之后的指令,直到新的畫筆出現(xiàn)為止。 -
ST ?透明度
???表示當(dāng)前顏色存在一定比例的透明度。一共四級(jí)(0~3),1級(jí)代表25%的透明度。 -
SW ?畫筆寬度
???規(guī)定畫筆寬度為幾個(gè)單位,每一個(gè)單位代表0.3毫米。 -
PU ?x坐標(biāo), y坐標(biāo) [,x, y, ... x, y]
???指的是畫筆移到坐標(biāo)(x, y)處,此時(shí)并沒有繪制。 -
PD ?x坐標(biāo), y坐標(biāo) [,x, y, ... x, y]
???指的是畫筆從當(dāng)前位置畫到坐標(biāo)(x, y)處,繪制完畢后,畫筆位置會(huì)更新,畫筆顏色及大小由之前指令確定。 -
CI ?半徑
???繪制指定半徑大小的圓,圓心為當(dāng)前位置,繪制完畢后,畫筆位置不會(huì)更新。 -
AA ?x坐標(biāo), y坐標(biāo), 角度
???繪制圓弧,參數(shù)中的坐標(biāo)(x, y)為弧線圓心的位置,弧線的起點(diǎn)為畫筆當(dāng)前位置。當(dāng)角度為正時(shí),表示逆時(shí)針旋轉(zhuǎn),否則為順時(shí)針旋轉(zhuǎn)指定的角度數(shù)。繪制完畢后,畫筆位置會(huì)更新到圓弧終點(diǎn)。 -
PM ?n
???多邊形模式。用于存儲(chǔ)將要繪制的多邊形,直到多邊形被完全定義完。例如,要定義多邊形,需要將筆移到所需位置,然后執(zhí)行PM 0進(jìn)入多邊形模式,然后指定適當(dāng)?shù)闹噶钜远x多邊形的形狀。如果還要定義子多邊形,以PM 1指令結(jié)束形狀并定義下一個(gè)形狀,直到執(zhí)行PM 2退出多邊形模式。 -
EP ?
???繪制之前存儲(chǔ)的多邊形的邊界。 -
FP ?
???填充之前存儲(chǔ)的多邊形。 -
SC ?符號(hào)名, 方向
???在指定的方向上繪制另一個(gè)符號(hào),符號(hào)的轉(zhuǎn)心(Pivot point)位于當(dāng)前畫筆位置。 如果方向=0,表示符號(hào)維持原方向;方向=1,表示方向?yàn)樽詈螽嫻P繪制的方向;方向=2,表示符號(hào)沿符號(hào)化方向旋轉(zhuǎn)90度。
示例
SPA;SW1;PU1000,1000;PD1000,2000;
畫筆顏色'A',寬度為1個(gè)單位,移動(dòng)到(1000, 1000),繪制垂直線段到(1000, 2000)。
SPB;SW2;PU1000,1000;PD1000,2000,2000,2000,2000,1000,1000,1000;
畫筆顏色'B',寬度為2個(gè)單位,移動(dòng)到(1000, 1000),繪制線段到(1000, 2000),接著一直繪制到(2000, 2000),(2000, 1000),(1000, 1000),最終形成一個(gè)矩形。
SPB;ST2;PM0;PU1000,1000;PD1000,2000,2000,2000,2000,1000;PM2;FP;
進(jìn)入多邊形模式(PM0),繪制上個(gè)示例中的矩形(多邊形最后自動(dòng)首尾相連),最后退出多邊形模式(PM2),用畫筆顏色'B',50%的透明度,填充多邊形。
PU100,100;PM0;CI50;PM2;SPE;ST0;FP;SPA;EP;
以(100, 100)為圓心,150為半徑構(gòu)造一個(gè)圓。然后設(shè)置畫筆顏色‘E',透明度為0%,填充該圓。重新設(shè)置畫筆顏色'A',為圓描邊。
SPU;SW1;PU100,100;PD200,100;AA200,150,-90;PD250,200;
從(100, 100)開始,畫一條水平線到(200, 100);接著以(200, 100)為起點(diǎn),以(200, 150)為圓心,順序針畫一個(gè)90度的圓弧,畫筆移到圓弧的終點(diǎn)(將會(huì)是(250, 150));最后畫一條直線到(250, 200)。
SPC;SW3;PU500,500,1000,1000;SCsample99,1;PD1000,500;
移動(dòng)畫筆從(500, 500)到(1000, 1000),方位為135度,沿該方向畫一個(gè)符號(hào)(sample99),符號(hào)的轉(zhuǎn)心位于(1000, 1000)處;接著從(1000, 1000),用顏色'C',寬度為3個(gè)單位的畫筆畫一條直線到(1000, 500)。
編碼實(shí)現(xiàn)
新建一個(gè)靜態(tài)的工具類S52Tools,其中方法DrawSymbol實(shí)現(xiàn)上述指令:
public static class S52Tools
{
//ca 畫布
//colors 顏色字典,索引是SP指令后的字母
//instructions 矢量符號(hào)描述符號(hào)組成的指令
public static void DrawSymbol(SKCanvas ca, Dictionary<char, SKColor> colors, List<string> instructions)
{
bool isPMExist = false;
int width = 1; //默認(rèn)寬度
var color = S52Colors.Instance["NODTA"]; //默認(rèn)顏色
var pen = new SKPaint() { Color = color, StrokeWidth = width, Style = SKPaintStyle.Stroke }; //默認(rèn)畫筆
SKPath path = new SKPath();
foreach (var command in instructions)
{
SKPath path = new SKPath();
string[] subcomm = command.Split(';');
foreach (var item in subcomm)
{
if (item.Length >= 2)
{
int transparency = 0;
switch (item.Substring(0, 2))
{
case "SP": //獲取顏色
color = colors[item[2]];
pen.Color = color;
break;
case "ST": //獲取透明度
transparency = item[2] - 48;
color = color.WithAlpha((byte)(255 - transparency * 255 / 4));
pen.Color = color;
break;
case "SW": //獲取畫筆寬度
width = (item[2] - 48) * 30; // 30 = 0.3/0.01
pen.StrokeWidth = width;
break;
case "PU": //起點(diǎn) PU500,500,1000,1000;
string[] pu = item.Substring(2).Split(',');
for (int i = 0; i < pu.Length-1; i+=2)
{
path.MoveTo(new SKPoint(int.Parse(pu[i]), int.Parse(pu[i+1])));
}
break;
case "PD": //終點(diǎn) PD; PD500,500; PD500,500,1000,1000;
string[] pd = item.Substring(2).Split(',');
if (pd.Length == 1) //PD;
{
ca.DrawPoint(path.LastPoint, pen);
}
else
{
for (int i = 0; i < pd.Length - 1; i += 2)
{
SKPoint end = new SKPoint(int.Parse(pd[i]), int.Parse(pd[i + 1]));
if (path.LastPoint == end)
{
ca.DrawPoint(end, pen);
continue;
}
path.LineTo(end);
}
}
break;
case "CI":
int radius = int.Parse(item.Substring(2));
path.AddCircle(path.LastPoint.X, path.LastPoint.Y, radius);
break;
case "AA":
string[] xyd = item.Substring(2).Split(',');
var x = int.Parse(xyd[0]);
var y = int.Parse(xyd[1]);
var d = int.Parse(xyd[2]);
var r = (float)Math.Sqrt(GeoTools.DistanceSqrd(x, y, (int)path.LastPoint.X, (int)path.LastPoint.Y));
var rect = new SKRect(x - r, y - r, x + r, y + r);
//從正北起算,而繪制時(shí)以正東起算,所以需要減掉90度
var startAngle = GeoTools.AngleBetweenTwoPoints((int)path.LastPoint.X, (int)path.LastPoint.Y, x, y);
path.ArcTo(rect, (float)startAngle - 90, -1 * d, false);
break;
case "PM":
switch (item[2] - 48)
{
case 0: isPMExist = true; break;
case 1: path.Close(); break;
case 2: isPMExist = false; break;
}
break;
case "EP":
pen.Style = SKPaintStyle.Stroke;
ca.DrawPath(path, pen);
break;
case "FP":
pen.Style = SKPaintStyle.Fill;
ca.DrawPath(path, pen);
break;
case "SC": //ECDIS中并沒有用到該指令,暫不用解析
throw new Exception("S52 [SC]繪制沒有解析");
default:
throw new Exception($"S52 [{item.Substring(0, 2)}]繪制沒有解析");
}
}
}
}
if (!isPMExist)
{
pen.Style = SKPaintStyle.Stroke;
ca.DrawPath(path, pen);
}
}
}
調(diào)用過(guò)程及繪制結(jié)果如下:
ca.Scale(0.1f);
S52Tools.DrawSymbol(
ca, //畫布
new Dictionary<char, SKColor> { //顏色字典
{ 'A', SKColors.Red },
{ 'B', SKColors.Blue },
{ 'C', SKColors.Green },
},
new List<string>() { //指令
"SPB;ST2;PM0;PU1000,1000;PD1000,2000,2000,2000,2000,1000;PM2;FP;",
"SPA;SW2;PU1000,1000;PD1000,2000,2000,2000,2000,1000,1000,1000;",
"SPC;SW2;PU1000,1000;PD2000,1000;AA2000,1500,-90;PD2500,2000;",
}
);
