符號
不同用途的燈標(biāo)、錨地邊界、岸上建筑物等物標(biāo)在海圖都有不同的符號,
符號所在畫布大小為32767個(gè)單位(每一單位代表0.01毫米)絮姆。
- 每個(gè)符號有一個(gè)全局唯一的名稱,也擁有一套坐標(biāo)系秩霍,坐標(biāo)原點(diǎn)位于左上角篙悯。
- 符號繪制的起點(diǎn)為(BoundingBoxX, BoundingBoxY),繪制區(qū)擁有一定高度(BoundingBoxHeight)與寬度(BoundingBoxWidth)铃绒。
- 符號還存在一個(gè)轉(zhuǎn)心(PivotPointX, PivotPointY)鸽照,但轉(zhuǎn)心不一定位于繪制區(qū)內(nèi)部。在指定坐標(biāo)處繪制符號時(shí)匿垄,應(yīng)該將轉(zhuǎn)心位于指定坐標(biāo)處移宅;如果需要旋轉(zhuǎn)符號归粉,則旋轉(zhuǎn)的中心也為轉(zhuǎn)心。
- 符號的具體形狀由一系列指令(Instructions漏峰,由矢量符號描述語言編寫)構(gòu)成糠悼。
矢量符號描述語言
S-52標(biāo)準(zhǔn)中,是用矢量符號描述語言(Vector Symbol Description Language)來定義這些符號形狀的浅乔。這些符號被廣泛用于電子海圖中定義點(diǎn)倔喂、復(fù)雜的線型及填充復(fù)雜的圖案。
矢量符號描述語言使用一支虛構(gòu)的“筆”靖苇,上繪畫席噩,然后記錄畫筆移動的位置,最終形式電子海圖所需的矢量符號贤壁。與屏幕坐標(biāo)一樣悼枢,畫布坐標(biāo)的原點(diǎn)(位置0,0)在圖形的左上角,x坐標(biāo)向右延伸脾拆,y坐標(biāo)向下延伸馒索。矢量符號描述語言畫筆的顏色、大小名船、移動等都是通過指令實(shí)現(xiàn)的绰上,其中:
- ; ??不同的指令用分號隔開
- , ??如果指令含有多個(gè)參數(shù),參數(shù)之間用逗號分隔渠驼;如果沒有參數(shù)蜈块,則無需添加逗號
-
SP ?顏色
???表示畫筆的顏色,該顏色用一個(gè)字母代表某種顏色標(biāo)記迷扇。SP指令中畫筆顏色對作用于其之后的指令百揭,直到新的畫筆出現(xiàn)為止。 -
ST ?透明度
???表示當(dāng)前顏色存在一定比例的透明度谋梭。一共四級(0~3)信峻,1級代表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)處,繪制完畢后癣亚,畫筆位置會更新丑掺,畫筆顏色及大小由之前指令確定。 -
CI ?半徑
???繪制指定半徑大小的圓述雾,圓心為當(dāng)前位置街州,繪制完畢后兼丰,畫筆位置不會更新。 -
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ù)。繪制完畢后趟紊,畫筆位置會更新到圓弧終點(diǎn)氮双。 -
PM ?n
???多邊形模式。用于存儲將要繪制的多邊形霎匈,直到多邊形被完全定義完戴差。例如,要定義多邊形铛嘱,需要將筆移到所需位置造挽,然后執(zhí)行PM 0進(jìn)入多邊形模式,然后指定適當(dāng)?shù)闹噶钜远x多邊形的形狀弄痹。如果還要定義子多邊形饭入,以PM 1指令結(jié)束形狀并定義下一個(gè)形狀,直到執(zhí)行PM 2退出多邊形模式肛真。 -
EP ?
???繪制之前存儲的多邊形的邊界谐丢。 -
FP ?
???填充之前存儲的多邊形。 -
SC ?符號名, 方向
???在指定的方向上繪制另一個(gè)符號蚓让,符號的轉(zhuǎn)心(Pivot point)位于當(dāng)前畫筆位置乾忱。 如果方向=0,表示符號維持原方向历极;方向=1窄瘟,表示方向?yàn)樽詈螽嫻P繪制的方向;方向=2趟卸,表示符號沿符號化方向旋轉(zhuǎn)90度蹄葱。
示例
SPA;SW1;PU1000,1000;PD1000,2000;
畫筆顏色'A',寬度為1個(gè)單位锄列,移動到(1000, 1000)图云,繪制垂直線段到(1000, 2000)。
SPB;SW2;PU1000,1000;PD1000,2000,2000,2000,2000,1000,1000,1000;
畫筆顏色'B'邻邮,寬度為2個(gè)單位竣况,移動到(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è)示例中的矩形(多邊形最后自動首尾相連),最后退出多邊形模式(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)(將會是(250, 150))校坑;最后畫一條直線到(250, 200)。
SPC;SW3;PU500,500,1000,1000;SCsample99,1;PD1000,500;
移動畫筆從(500, 500)到(1000, 1000)千诬,方位為135度耍目,沿該方向畫一個(gè)符號(sample99),符號的轉(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 矢量符號描述符號組成的指令
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)用過程及繪制結(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;",
}
);