 Look-up 表變成程序:拆解 S-101 的 215 條渲染規(guī)則)
上一篇拆 PC 分發(fā)件時留了個坑目錄里登記著 215 個規(guī)則文件的引用但解開包只看到數(shù)據(jù)——符號怎么畫有 XML顏色怎么配有 colorProfile唯獨沒有什么條件下畫什么的邏輯。S-52 時代的 Look-up 表好歹是一張能讀的表S-101 把它變成了 215 個 Lua 文件——210 個物標(biāo)規(guī)則外加 5 個PortrayalAPI、S100Scripting之類的框架與公共文件。這些文件不在圖示表達包里而是以 CSPConditional Symbology Procedure條件制圖程序源碼的形式隨產(chǎn)品規(guī)范發(fā)布——IHO 官網(wǎng)上各產(chǎn)品規(guī)范的 Portrayal 附件里就能下到。本文拆的這批對應(yīng)S-101 Ed 2.0.0 配套的圖示表達目錄 v2.0.0——215 個 Lua 文件逐個實讀數(shù)目正好與目錄登記對上。這件事的分量值得單獨說一句條件制圖從給人讀的文檔變成了給機器跑的程序。S-52 PresLib 時代PL 3.4 / PL 4.0 里的條件邏輯是偽代碼每家渲染器自己翻譯成 C——翻譯過程就是實現(xiàn)分歧的溫床。S-101 干脆把官方實現(xiàn)Lua連同文檔一起發(fā)你要做的不是照著文檔寫而是把這段 Lua 嵌進去跑。這篇挑三個代表拆開最小的——錨泊船規(guī)則四十來行當(dāng)入門樣本最燒腦的——深度區(qū)和它的鄰居們管安全等深線怎么畫最后一族干脆不用 Lua——換 XSLT 引擎的規(guī)則。最小的規(guī)則錨泊船AnchorBerth.lua全文 43 行是理解 CSP 結(jié)構(gòu)的好樣本。核心判斷只有兩處local symbol ACHBRT07 -- default for categoryOfCargo ! 7 if contains(7, feature.categoryOfCargo) then symbol ANCBDNG2 end第一個條件就值得停下屬性直接改寫符號。錨泊船裝的是危險品categoryOfCargo 含 7符號從普通錨泊換成危險品錨泊標(biāo)——船員掃一眼符號就知道這片錨地里混著危險品船。這行代碼在 S-52 里對應(yīng) Look-up 表里的一條帶條件的行語義沒變只是載體從表行變成了 if。幾何類型分支是第二處if feature.PrimitiveType PrimitiveType.Point then ... elseif feature.PrimitiveType PrimitiveType.Surface then ... featurePortrayal:SimpleLineStyle(dash,0.64,CHMGF) featurePortrayal:AddInstructions(LineInstruction:_simple_)點要素給符號面要素除了符號還要畫邊界——邊界不引用外部線樣式文件而是用SimpleLineStyle內(nèi)聯(lián)一條簡單的虛線0.64 寬、CHMGF 淺品紅。S-52 里這個區(qū)分寫在 Look-up 表的兩個不同條目里現(xiàn)在變成了顯式的 if-else。還有一處容易被忽略但很關(guān)鍵if contextParameters.RadarOverlay then featurePortrayal:AddInstructions(ViewingGroup:26220;DrawingPriority:15;DisplayPlane:OverRadar) else featurePortrayal:AddInstructions(...DisplayPlane:UnderRadar)contextParameters是船員設(shè)置不是要素屬性。雷達疊加開著錨泊符號畫在雷達圖像上層關(guān)著就畫在下層。同一個物標(biāo)畫面層級跟著用戶參數(shù)走——這就是為什么渲染器沒法把畫在哪層寫死也是 CSP 簽名里帶contextParameters的原因。要素屬性、幾何類型、用戶上下文三股輸入在這里匯合。最后一個小細節(jié)要素帶名字時規(guī)則追加一條文本注記且內(nèi)容走模板——EncodeString(GetFeatureName(feature, contextParameters), Nr %s)。圖上Nr 7這樣的錨泊編號格式是規(guī)則里定的不是渲染器拼的。連一個前綴字符串的歸屬都被規(guī)范收走了。順帶看一眼指令協(xié)議本身AddInstructions吃一個分號分隔的字符串ViewingGroup:26220;DrawingPriority:15;DisplayPlane:UnderRadar——分組、優(yōu)先級、顯示面一條指令流。渲染器要做的只是實現(xiàn)這個指令集的解釋器規(guī)則怎么寫它一概不關(guān)心。最燒腦的規(guī)則深度區(qū)和它的鄰居們DEPARE03.lua有 150 余行處理的是海圖上最要命的問題安全等深線到底畫在哪。前文講過安全等深線是船員按吃水設(shè)的參數(shù)改一個數(shù)整張圖的填色分布重算。重算在 S-101 里的實體就是這段 Luaif depthRangeMinimumValue contextParameters.SafetyContour then unsafe true else safe true end水深小于安全等深線這片深度區(qū)標(biāo)記為 unsafe——但真正的重頭戲在后面。深度區(qū)畫邊界時要判斷我這條邊是不是安全等深線而判斷依據(jù)不是自己的屬性是鄰居是誰for curveAssociation in feature:GetFlattenedSpatialAssociations() do local associatedFeatures curveAssociation.AssociatedFeatures ...DEPARE03 遍歷自己每條共享邊把共邊的要素撈出來分堆共邊的是DepthContour看它的深度值是不是恰好等于安全等深線loc_safety。是LandArea、UnsurveyedArea那要看對方的水線效果waterLevelEffect——陸地和未測量區(qū)貼著的邊按 unsafe 處理。連內(nèi)陸水域都有特判River、Lake、Canal、DockArea、LockBasin 算一組還要看線性建筑物的水線效果屬性。這段代碼里埋著一句真實工程味的注釋-- NOTE: S-52 PL 3.4 disagrees with PL 4.0.1 on the inlandWaterShared and watlev. -- Going with 3.4 since 4.0.1 doesnt apply the proper weight when safe water is -- next to a land area.S-52 預(yù)庫 3.4 版和 4.0.1 版在內(nèi)陸水域的判定上標(biāo)準(zhǔn)自己打架了官方實現(xiàn)選邊站 3.4理由寫在注釋里。條件制圖程序化的一個意外好處就在這實現(xiàn)分歧沒法再藏在各家 C 里它被顯式地擺在了代碼注釋中型式認可時審的就是這一行。DEPARE03 里還有兩處值得圈出來。其一位置質(zhì)量直接映射到線型if qualityOfPosition and qualityOfPosition ~ 1 and qualityOfPosition ~ 10 and qualityOfPosition ~ 11 then featurePortrayal:SimpleLineStyle(dash,0.64,DEPSC)這條邊如果位置測量質(zhì)量不達標(biāo)qualityOfPosition 不在可信值列表里安全等深線畫成虛線——測得不準(zhǔn)的等深線在圖上就該長得不確定。數(shù)據(jù)質(zhì)量參與渲染決策這是 S-100 系列相對 S-57 最實質(zhì)的進步之一而它就落在這幾行里。其二規(guī)則是可組合的。文件頭require RESCSP03、require SAFCON01、require SEABED01——管制區(qū)注記、安全水深標(biāo)注、海床顯示各是獨立的 CSP被 DEPARE03 按需調(diào)用。215 個規(guī)則文件不是 215 個孤立函數(shù)是一張調(diào)用網(wǎng)。連性能樁都標(biāo)準(zhǔn)化了Debug.StartPerformance(Lua Code - DEPARE03)官方實現(xiàn)自帶計時。順帶一提長度上的冠軍另有其人——礙航物 OBSTRN07.lua 有 188 行沉船、適淹礁、水線效果的多層嵌套判成迷宮不過讀懂了深度區(qū)其它規(guī)則都是它的變奏。另一族不用 Lua 的 XSLTLua 不是唯一載體。S-111 表面流、S-123 海上無線電服務(wù)這批產(chǎn)品規(guī)范的圖示表達用的是XSLT——規(guī)則文件長這樣S-123 的海警站42 行xsl:template matchCoastguardStation[primitivePoint] priority1 pointInstruction ... viewingGroup12310/viewingGroup displayPlaneOVERRADAR/displayPlane symbol referenceCostGuardStattion/ /pointInstruction xsl:if testcommunicationChannel! textInstruction.../textInstruction /xsl:if /xsl:transform對比 AnchorBerth 那段 Lua語義一一對應(yīng)模板匹配管幾何類型[primitivePoint]xsl:if管條件注記輸出的是指令 XML 而不是指令流字符串。一個是命令式Lua 主動 AddInstructions一個是聲明式XSLT 匹配后產(chǎn)出 XML條件制圖的語義是同一套。順帶一個彩蛋注意引用里那個CostGuardStattion——雙寫的 t官方分發(fā)件原樣帶著這個拼寫錯誤。同一個包里還有拼錯的Meterorological文件名少個 o 的版本和正確版本并存——一個 S-123 包貢獻兩處拼寫彩蛋標(biāo)準(zhǔn)是工程產(chǎn)物這件事在細節(jié)里到處都是實錘。為什么兩套并存XSLT 是 S-52 預(yù)庫時代就確立的技術(shù)路線這批產(chǎn)品規(guī)范延續(xù)了它S-101 較新規(guī)則換成了 Lua。但血統(tǒng)是一致的——S-131 的線樣式文件頭里照樣寫著sourceS52Preslib4.0下一篇細看這個文件PresLib 的符號資產(chǎn)在新框架里繼續(xù)服役。對我們做渲染器的人實際含義是解析器要么支持兩種規(guī)則引擎要么在接入不同產(chǎn)品規(guī)范前先做一層轉(zhuǎn)換。我們的做法是工具里雙引擎并列同一個 S-131 分發(fā)包實測吃下 41 個 Lua 規(guī)則S-123 分發(fā)包實測吃下 26 個 XSLT 規(guī)則文件87 個 XML、657 個符號覆蓋 11 個物標(biāo)兩條管道各自出渲染預(yù)覽。這件事的真正含義把邏輯標(biāo)準(zhǔn)化成可執(zhí)行程序改變的不只是寫法渲染器退化成解釋器。指令集ViewingGroup / PointInstruction / LineInstruction / …成為唯一穩(wěn)定接口規(guī)則包整體可替換——IHO 發(fā)新版 PC換包不換碼。對照 S-52 時代每家把 Look-up 翻譯進 C升級一次預(yù)庫等于重新翻譯一次。實現(xiàn)分歧無處可藏。3.4 對 4.0.1 的取舍寫在官方源碼注釋里誰跟誰不一致、跟了誰全都可查。型式認可審顯示對不對時審的不再是你按文檔理解對了嗎而是你跑的是不是這段代碼。測試有了錨點。S-64 的官方測試數(shù)據(jù)集為什么能當(dāng)標(biāo)準(zhǔn)答案前文說過現(xiàn)在補上另一半——數(shù)據(jù)集喂進來規(guī)則跑出去兩側(cè)都是標(biāo)準(zhǔn)件對不對得上逐條可比。規(guī)則文件、繪圖資源、顏色配置三樣湊齊渲染器才算拿到了完整的畫法。想親手翻這 215 個規(guī)則工具頁的 PC 解析器支持上傳整個分發(fā)包目錄每個物標(biāo)的渲染預(yù)覽、規(guī)則源碼、符號實圖都在里面S-101 和 S-123 的包都能直接吃。下一篇進 S-131港口基礎(chǔ)設(shè)施產(chǎn)品——S-100 的產(chǎn)品譜系從航海走向港口運營連系船柱和岸電設(shè)施都成了物標(biāo)。圖示表達規(guī)則換到新場景里怎么組織本篇雙引擎實測用的那個 S-131 PC 2.0.0 分發(fā)包到時候正好登場。我是夜航海圖做海圖與地圖渲染開發(fā)的工程師。博客「航圖筆記」同步更新全部文章歡迎 RSS 訂閱這個系列持續(xù)更新S-100 全家族拆解中關(guān)注博主不迷路完整在線工具與最新文章見 nightchart.cn。