From 6cd6274194307c9b4addc767a2ee4c4ae7a26bdf Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Mon, 21 Sep 2026 21:55:06 +0800 Subject: [PATCH] fix: layer runtime fills below S-52 symbols --- web/s52_runtime_executor.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/s52_runtime_executor.js b/web/s52_runtime_executor.js index 49fe341..5f99972 100644 --- a/web/s52_runtime_executor.js +++ b/web/s52_runtime_executor.js @@ -124,11 +124,11 @@ function operationFeatures(feature,settings,context,resourceExists){ });output.missingResources=missing;output.sideEffects=chain.filter(x=>x.opcode==="SE").map(x=>x.side_effects||{});return output; } class RuntimeExecutor{ - constructor(map,options){this.map=map;this.settings=Object.assign({},DEFAULTS,(options||{}).settings||{});this.sourceId=(options||{}).sourceId||"cm95-runtime-rules";this.layerIds=[];this.effectiveSafetyContourM=null;this.maxSequence=31;this.symbolMetadata={};this.lightLabelState=new LightLabelState();this.caFeatures=[];} + constructor(map,options){this.map=map;this.settings=Object.assign({},DEFAULTS,(options||{}).settings||{});this.sourceId=(options||{}).sourceId||"cm95-runtime-rules";this.layerIds=[];this.effectiveSafetyContourM=null;this.maxSequence=31;this.symbolMetadata={};this.lightLabelState=new LightLabelState();this.caFeatures=[];this.staticSymbolAnchor=null;} remove(){this.layerIds.forEach(id=>{if(this.map.getLayer(id))this.map.removeLayer(id);});this.layerIds=[];if(this.map.getSource(this.sourceId))this.map.removeSource(this.sourceId);} - install(){this.remove();this.map.addSource(this.sourceId,{type:"geojson",data:{type:"FeatureCollection",features:[]}});for(let seq=0;seq<=this.maxSequence;seq++)this.addSequence(seq);this.refresh();if(typeof fetch==="function")fetch("/s52/symbol-metadata.json").then(response=>response.json()).then(metadata=>{this.symbolMetadata=metadata||{};this.refresh();}).catch(error=>console.warn("S-52 symbol metadata unavailable",error));} - addLayer(layer){this.map.addLayer(layer);this.layerIds.push(layer.id);}filter(seq,opcode){return ["all",["==",["get","runtime_sequence"],seq],["==",["get","runtime_opcode"],opcode]];} - addSequence(seq){const base={source:this.sourceId};this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-ac`,type:"fill",filter:this.filter(seq,"AC"),paint:{"fill-color":["get","runtime_color"],"fill-opacity":0.92}}));this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-ap`,type:"fill",filter:this.filter(seq,"AP"),paint:{"fill-pattern":["get","runtime_resource"],"fill-opacity":0.85}}));this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-ls`,type:"line",filter:this.filter(seq,"LS"),layout:{"line-cap":"round"},paint:{"line-color":["get","runtime_color"],"line-width":["get","runtime_line_width"],"line-dasharray":["match",["get","runtime_line_style"],"DASH",["literal",[4,2]],"DOTT",["literal",[1,2]],["literal",[1,0]]]}}));this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-lc`,type:"line",filter:this.filter(seq,"LC"),paint:{"line-pattern":["get","runtime_resource"],"line-width":5}}));this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-sy`,type:"symbol",filter:this.filter(seq,"SY"),layout:{"icon-image":["get","runtime_resource"],"icon-offset":["coalesce",["get","runtime_icon_offset"],["literal",[0,0]]],"icon-allow-overlap":true,"icon-ignore-placement":true}}));this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-soundg`,type:"symbol",filter:this.filter(seq,"SOUNDG"),layout:{"icon-image":["get","runtime_resource"],"icon-offset":["coalesce",["get","runtime_icon_offset"],["literal",[0,0]]],"icon-allow-overlap":true,"icon-ignore-placement":true}}));this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-tx`,type:"symbol",filter:this.filter(seq,"TX"),layout:{"text-field":["get","runtime_text"],"text-size":10,"text-offset":["literal",[0,0]],"text-allow-overlap":false,"text-optional":true},paint:{"text-color":["get","runtime_color"],"text-halo-color":"rgba(255,255,255,.65)","text-halo-width":0.5}}));} + install(){this.remove();const layers=(this.map.getStyle()||{}).layers||[];const anchor=layers.find(layer=>layer&&layer.source==="cm95"&&layer.type==="symbol");this.staticSymbolAnchor=anchor?anchor.id:null;this.map.addSource(this.sourceId,{type:"geojson",data:{type:"FeatureCollection",features:[]}});for(let seq=0;seq<=this.maxSequence;seq++)this.addSequence(seq);this.refresh();if(typeof fetch==="function")fetch("/s52/symbol-metadata.json").then(response=>response.json()).then(metadata=>{this.symbolMetadata=metadata||{};this.refresh();}).catch(error=>console.warn("S-52 symbol metadata unavailable",error));} + addLayer(layer,beforeId){const anchor=beforeId&&this.map.getLayer(beforeId)?beforeId:undefined;this.map.addLayer(layer,anchor);this.layerIds.push(layer.id);}filter(seq,opcode){return ["all",["==",["get","runtime_sequence"],seq],["==",["get","runtime_opcode"],opcode]];} + addSequence(seq){const base={source:this.sourceId},belowSymbols=this.staticSymbolAnchor;this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-ac`,type:"fill",filter:this.filter(seq,"AC"),paint:{"fill-color":["get","runtime_color"],"fill-opacity":0.92}}),belowSymbols);this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-ap`,type:"fill",filter:this.filter(seq,"AP"),paint:{"fill-pattern":["get","runtime_resource"],"fill-opacity":0.85}}),belowSymbols);this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-ls`,type:"line",filter:this.filter(seq,"LS"),layout:{"line-cap":"round"},paint:{"line-color":["get","runtime_color"],"line-width":["get","runtime_line_width"],"line-dasharray":["match",["get","runtime_line_style"],"DASH",["literal",[4,2]],"DOTT",["literal",[1,2]],["literal",[1,0]]]}}),belowSymbols);this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-lc`,type:"line",filter:this.filter(seq,"LC"),paint:{"line-pattern":["get","runtime_resource"],"line-width":5}}),belowSymbols);this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-sy`,type:"symbol",filter:this.filter(seq,"SY"),layout:{"icon-image":["get","runtime_resource"],"icon-offset":["coalesce",["get","runtime_icon_offset"],["literal",[0,0]]],"icon-allow-overlap":true,"icon-ignore-placement":true}}));this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-soundg`,type:"symbol",filter:this.filter(seq,"SOUNDG"),layout:{"icon-image":["get","runtime_resource"],"icon-offset":["coalesce",["get","runtime_icon_offset"],["literal",[0,0]]],"icon-allow-overlap":true,"icon-ignore-placement":true}}));this.addLayer(Object.assign({},base,{id:`runtime-rule-${seq}-tx`,type:"symbol",filter:this.filter(seq,"TX"),layout:{"text-field":["get","runtime_text"],"text-size":10,"text-offset":["literal",[0,0]],"text-allow-overlap":false,"text-optional":true},paint:{"text-color":["get","runtime_color"],"text-halo-color":"rgba(255,255,255,.65)","text-halo-width":0.5}}));} sourceLayers(){const set=new Set();((this.map.getStyle()||{}).layers||[]).forEach(layer=>{if(layer.source==="cm95"&&layer["source-layer"])set.add(layer["source-layer"]);});return Array.from(set);} updateSafety(){const values=[];try{(this.map.querySourceFeatures("cm95",{sourceLayer:"depth_contour"})||[]).forEach(f=>{const v=numberValue(f.properties,"contour_depth_m",null);if(v!==null&&v>=this.settings.safetyContourM)values.push(v);});}catch(_err){}this.effectiveSafetyContourM=values.length?Math.min.apply(null,values):null;} refresh(){if(!this.map.getSource("cm95")||!this.map.isSourceLoaded("cm95"))return;this.updateSafety();this.lightLabelState.reset();const seen=new Set(),features=[],context={effectiveSafetyContourM:this.effectiveSafetyContourM,lightLabelState:this.lightLabelState,symbolMetadata:this.symbolMetadata};this.sourceLayers().forEach(layer=>{let rows=[];try{rows=this.map.querySourceFeatures("cm95",{sourceLayer:layer})||[];}catch(_err){return;}rows.forEach(feature=>{if(!feature.properties)return;const presentations=jsonValue(feature.properties.s52_light_presentations,[]),variants=[],topDynamic=hasDynamicCsRule(feature.properties.s52_rule_chain),topProcedure=String(feature.properties.s52_runtime_csp||feature.properties.s52_csp_resolved||feature.properties.s52_csp||"");if(topDynamic)variants.push(feature);if(feature.properties.s52_lights_metres_rule_chain&&!(topDynamic&&topProcedure==="LIGHTS05")){variants.push(Object.assign({},feature,{type:"Feature",id:String(feature.id==null?"":feature.id)+":light:metres",geometry:feature.geometry,properties:Object.assign({},feature.properties,{s52_rule_chain:feature.properties.s52_lights_metres_rule_chain})}));}if(Array.isArray(presentations))presentations.forEach((presentation,index)=>variants.push(Object.assign({},feature,{type:"Feature",id:String(feature.id==null?"":feature.id)+":light:"+index,geometry:feature.geometry,properties:Object.assign({},feature.properties,presentation)})));variants.forEach(variant=>{if(!variant.properties.s52_rule_chain)return;const key=[layer,variant.id,JSON.stringify(variant.geometry),variant.properties.s52_rule_chain].join("|");if(seen.has(key))return;seen.add(key);operationFeatures(variant,this.settings,context,name=>{if(!this.symbolMetadata||!Object.keys(this.symbolMetadata).length)return true;return Object.prototype.hasOwnProperty.call(this.symbolMetadata,name);}).forEach(item=>features.push(item));});});});const caSeen=new Set();this.caFeatures=features.filter(item=>{if(item.properties.runtime_opcode!=="CA")return false;const key=JSON.stringify([item.geometry&&item.geometry.coordinates,item.properties.runtime_args_json]);if(caSeen.has(key))return false;caSeen.add(key);return true;});const visibleOps=new Set(["AC","AP","LS","LC","SY","SOUNDG","TX"]),mapFeatures=features.filter(item=>visibleOps.has(item.properties.runtime_opcode));const source=this.map.getSource(this.sourceId);if(source&&source.setData)source.setData({type:"FeatureCollection",features:mapFeatures});}