2.2.9 電子海圖系統(tǒng)解析及開發(fā) 海圖顯示 - 矢量符號(hào)描述語(yǔ)言

符號(hào)

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

符號(hào)尺度及轉(zhuǎn)心示意圖

符號(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;",
        }
    );
矢量符號(hào)指令繪制結(jié)果
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容