feat: execute ordered S-52 runtime rules
This commit is contained in:
@@ -19,27 +19,28 @@
|
||||
]
|
||||
},
|
||||
"RUNTIME_DEPARE": {
|
||||
"status": "PREVIEW_ONLY",
|
||||
"fidelity": "DEFAULT_MARINER_SETTINGS_PREVIEW",
|
||||
"status": "IMPLEMENTED",
|
||||
"fidelity": "ORDERED_RUNTIME_RULE_IR",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "web/kyushu_compare.html",
|
||||
"path": "web/s52_runtime_executor.js",
|
||||
"contains": [
|
||||
"runtime-preview-depare",
|
||||
"runtimeDepthColorExpression"
|
||||
"RuntimeExecutor",
|
||||
"dynamicRules"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"RUNTIME_DEPCNT": {
|
||||
"status": "PREVIEW_ONLY",
|
||||
"fidelity": "DEFAULT_MARINER_SETTINGS_PREVIEW",
|
||||
"status": "IMPLEMENTED",
|
||||
"fidelity": "SAFE_LOOKUP_QUAPOS_AND_SIDE_EFFECTS",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "web/kyushu_compare.html",
|
||||
"path": "web/s52_runtime_executor.js",
|
||||
"contains": [
|
||||
"runtime-preview-depcnt",
|
||||
"updateRuntimeSafetyContour"
|
||||
"RuntimeExecutor",
|
||||
"s52_safe_dashed_rule_chain",
|
||||
"scamin:100000001"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -49,22 +50,22 @@
|
||||
"fidelity": "TEXT_VALUE_ONLY_NOT_S52_GLYPH_COMPOSITION",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "web/kyushu_compare.html",
|
||||
"path": "web/s52_runtime_executor.js",
|
||||
"contains": [
|
||||
"runtime-preview-soundg"
|
||||
"soundingRules"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"RUNTIME_OBSTRN": {
|
||||
"status": "PREVIEW_ONLY",
|
||||
"fidelity": "POINT_SYMBOL_RUNTIME_ONLY_AREA_LINE_PENDING",
|
||||
"status": "IMPLEMENTED",
|
||||
"fidelity": "ORDERED_POINT_LINE_AREA_RULE_CHAIN",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "web/kyushu_compare.html",
|
||||
"path": "web/s52_runtime_executor.js",
|
||||
"contains": [
|
||||
"runtime-preview-hazard-symbols",
|
||||
"runtimeHazardSymbol",
|
||||
"hazardRules",
|
||||
"operationFeatures",
|
||||
"OBSTRN04",
|
||||
"ISODGR51"
|
||||
]
|
||||
@@ -72,14 +73,14 @@
|
||||
]
|
||||
},
|
||||
"RUNTIME_WRECKS": {
|
||||
"status": "PREVIEW_ONLY",
|
||||
"fidelity": "POINT_SYMBOL_RUNTIME_ONLY_AREA_LINE_PENDING",
|
||||
"status": "IMPLEMENTED",
|
||||
"fidelity": "ORDERED_POINT_LINE_AREA_RULE_CHAIN",
|
||||
"evidence": [
|
||||
{
|
||||
"path": "web/kyushu_compare.html",
|
||||
"path": "web/s52_runtime_executor.js",
|
||||
"contains": [
|
||||
"runtime-preview-hazard-symbols",
|
||||
"runtimeHazardSymbol",
|
||||
"hazardRules",
|
||||
"operationFeatures",
|
||||
"WRECKS02"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -620,6 +620,10 @@ def materialize_mariner_runtime_facts(
|
||||
)
|
||||
|
||||
if name in {"OBSTRN04", "WRECKS02"}:
|
||||
valsou = _optional_float(attrs.get("VALSOU"))
|
||||
out["s52_valsou_present"] = int(valsou is not None)
|
||||
if valsou is not None:
|
||||
out["s52_valsou_m"] = valsou
|
||||
hazard_depth = _runtime_hazard_depth(
|
||||
name, object_class, geometry_type, attrs, associated_depth_ranges
|
||||
)
|
||||
@@ -634,6 +638,17 @@ def materialize_mariner_runtime_facts(
|
||||
out["s52_surrounding_max_drval1_m"] = max(lows)
|
||||
if highs:
|
||||
out["s52_surrounding_min_drval2_m"] = min(highs)
|
||||
quality = resolve_quapnt01_instruction(attrs)
|
||||
out["s52_hazard_quality_rule_chain"] = materialize_rule_ir(
|
||||
quality, attrs, source="nested_csp", procedure="QUAPNT01"
|
||||
)
|
||||
if name == "OBSTRN04" and attrs.get("OBJNAM") not in (None, ""):
|
||||
out["s52_hazard_name_rule_chain"] = materialize_rule_ir(
|
||||
"TX(OBJNAM,1,2,3,'15118',-1,-1,CHBLK,26)",
|
||||
attrs,
|
||||
source="nested_csp",
|
||||
procedure="OBJNAM",
|
||||
)
|
||||
|
||||
scalar_codes = {
|
||||
"QUAPOS": "s52_quapos_code",
|
||||
|
||||
@@ -56,6 +56,10 @@ DELIVERY_PROPERTY_ALLOWLIST = frozenset(
|
||||
"s52_display_base_promoted",
|
||||
"s52_hazard_depth_m",
|
||||
"s52_hazard_procedure",
|
||||
"s52_hazard_quality_rule_chain",
|
||||
"s52_hazard_name_rule_chain",
|
||||
"s52_valsou_present",
|
||||
"s52_valsou_m",
|
||||
"s52_isolated_danger",
|
||||
"s52_surrounding_max_drval1_m",
|
||||
"s52_surrounding_min_drval2_m",
|
||||
|
||||
@@ -170,6 +170,8 @@ USAGE_VALUE_FIELDS = (
|
||||
"s52_area_color",
|
||||
"s52_area_pattern_code",
|
||||
"s52_line_color",
|
||||
"s52_line1_color",
|
||||
"s52_line2_color",
|
||||
"s52_line_complex_code",
|
||||
"s52_text1_color",
|
||||
"s52_text2_color",
|
||||
@@ -184,6 +186,10 @@ USAGE_PRESENCE_FIELDS = (
|
||||
"s52_text1_value",
|
||||
"s52_text2_value",
|
||||
"s52_text3_value",
|
||||
"s52_line1_style",
|
||||
"s52_line1_width",
|
||||
"s52_line2_style",
|
||||
"s52_line2_width",
|
||||
)
|
||||
|
||||
|
||||
@@ -245,9 +251,13 @@ def _scan_pbf_usage(path: Path) -> dict[str, Any]:
|
||||
render_kind_counts["AP"][placement] += 1
|
||||
else:
|
||||
geometry_ignored[("AP", layer_name, geometry_type)] += 1
|
||||
if "s52_line_color" in props:
|
||||
line_slots = [
|
||||
slot for slot in (1, 2)
|
||||
if f"s52_line{slot}_color" in props
|
||||
]
|
||||
if line_slots or "s52_line_color" in props:
|
||||
if line_geometry:
|
||||
render_kind_counts["LS"][placement] += 1
|
||||
render_kind_counts["LS"][placement] += max(1, len(line_slots))
|
||||
else:
|
||||
geometry_ignored[("LS", layer_name, geometry_type)] += 1
|
||||
if "s52_line_complex_code" in props:
|
||||
@@ -540,7 +550,7 @@ def _compile_style(
|
||||
materialized_priority_fields = {
|
||||
"AC": ("s52_area_color",),
|
||||
"AP": ("s52_area_pattern_code",),
|
||||
"LS": ("s52_line_color",),
|
||||
"LS": ("s52_line_color", "s52_line1_color", "s52_line2_color"),
|
||||
"LC": ("s52_line_complex_code",),
|
||||
"SY": ("s52_symbol_code",),
|
||||
"TX/TE": ("s52_text1_value", "s52_text2_value", "s52_text3_value"),
|
||||
@@ -578,6 +588,8 @@ def _compile_style(
|
||||
for field in (
|
||||
"s52_area_color",
|
||||
"s52_line_color",
|
||||
"s52_line1_color",
|
||||
"s52_line2_color",
|
||||
"s52_text1_color",
|
||||
"s52_text2_color",
|
||||
"s52_text3_color",
|
||||
@@ -690,42 +702,77 @@ def _compile_style(
|
||||
)
|
||||
compiled_placements["AP"].add((layer_name, priority))
|
||||
|
||||
if (
|
||||
"line" in caps
|
||||
and priority in opcode_priorities["LS"]
|
||||
and _field_used(usage, layer_name, "s52_line_color", priority)
|
||||
):
|
||||
_add_layer(
|
||||
layers,
|
||||
compiled_counts,
|
||||
"LS",
|
||||
{
|
||||
"id": f"{prefix}-ls-{layer_id}",
|
||||
"type": "line",
|
||||
"source": source_id,
|
||||
"source-layer": layer_name,
|
||||
"filter": _base_filter(
|
||||
priority=priority,
|
||||
categories=categories,
|
||||
required_field="s52_line_color",
|
||||
),
|
||||
"paint": {
|
||||
"line-color": color_line,
|
||||
"line-width": [
|
||||
"to-number",
|
||||
["get", "s52_line_width"],
|
||||
float(config.get("line_width_fallback", 1.0)),
|
||||
],
|
||||
"line-dasharray": [
|
||||
"case",
|
||||
["==", ["get", "s52_line_style"], "DASH"],
|
||||
["literal", [4, 2]],
|
||||
["literal", [1, 0]],
|
||||
],
|
||||
if "line" in caps and priority in opcode_priorities["LS"]:
|
||||
for slot in (1, 2):
|
||||
color_field = f"s52_line{slot}_color"
|
||||
if not _field_used(usage, layer_name, color_field, priority):
|
||||
continue
|
||||
style_field = f"s52_line{slot}_style"
|
||||
width_field = f"s52_line{slot}_width"
|
||||
_add_layer(
|
||||
layers,
|
||||
compiled_counts,
|
||||
"LS",
|
||||
{
|
||||
"id": f"{prefix}-ls{slot}-{layer_id}",
|
||||
"type": "line",
|
||||
"source": source_id,
|
||||
"source-layer": layer_name,
|
||||
"filter": _base_filter(
|
||||
priority=priority,
|
||||
categories=categories,
|
||||
required_field=color_field,
|
||||
),
|
||||
"layout": {"line-cap": "round"},
|
||||
"paint": {
|
||||
"line-color": _color_expression(
|
||||
color_field, active_colors, fallback
|
||||
),
|
||||
"line-width": [
|
||||
"to-number", ["get", width_field],
|
||||
float(config.get("line_width_fallback", 1.0)),
|
||||
],
|
||||
"line-dasharray": [
|
||||
"match", ["get", style_field],
|
||||
"DASH", ["literal", [4, 2]],
|
||||
"DOTT", ["literal", [1, 2]],
|
||||
["literal", [1, 0]],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
compiled_placements["LS"].add((layer_name, priority))
|
||||
)
|
||||
compiled_placements["LS"].add((layer_name, priority))
|
||||
|
||||
if _field_used(usage, layer_name, "s52_line_color", priority):
|
||||
_add_layer(
|
||||
layers,
|
||||
compiled_counts,
|
||||
"LS",
|
||||
{
|
||||
"id": f"{prefix}-ls-legacy-{layer_id}",
|
||||
"type": "line",
|
||||
"source": source_id,
|
||||
"source-layer": layer_name,
|
||||
"filter": _base_filter(
|
||||
priority=priority,
|
||||
categories=categories,
|
||||
required_field="s52_line_color",
|
||||
forbidden_field="s52_line1_color",
|
||||
),
|
||||
"layout": {"line-cap": "round"},
|
||||
"paint": {
|
||||
"line-color": color_line,
|
||||
"line-width": ["to-number", ["get", "s52_line_width"], 1],
|
||||
"line-dasharray": [
|
||||
"match", ["get", "s52_line_style"],
|
||||
"DASH", ["literal", [4, 2]],
|
||||
"DOTT", ["literal", [1, 2]],
|
||||
["literal", [1, 0]],
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
compiled_placements["LS"].add((layer_name, priority))
|
||||
if (
|
||||
"line" in caps
|
||||
and lc_mapping
|
||||
@@ -1172,6 +1219,8 @@ def _compile_style(
|
||||
for field in (
|
||||
"s52_area_color",
|
||||
"s52_line_color",
|
||||
"s52_line1_color",
|
||||
"s52_line2_color",
|
||||
"s52_text1_color",
|
||||
"s52_text2_color",
|
||||
"s52_text3_color",
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
</div>
|
||||
|
||||
<script src="/assets/maplibre-gl.js"></script>
|
||||
<script src="/s52_runtime_executor.js"></script>
|
||||
<script>
|
||||
const REFERENCE_STYLE =
|
||||
"https://pbf.jpcn.ccwu.cc/style/style.navsea-delivery-full-semantic-extentfix.json";
|
||||
@@ -129,8 +130,10 @@ const RUNTIME_HAZARD_SOURCE_LAYERS=[
|
||||
"s52_points"
|
||||
];
|
||||
let runtimeEffectiveSafetyContourM=null;
|
||||
let runtimeRuleExecutor=null;
|
||||
|
||||
function removeRuntimePreviewLayers(map){
|
||||
if(runtimeRuleExecutor){runtimeRuleExecutor.remove();runtimeRuleExecutor=null;}
|
||||
for(const id of RUNTIME_LAYER_IDS){
|
||||
if(map.getLayer(id))map.removeLayer(id);
|
||||
}
|
||||
@@ -393,11 +396,12 @@ function addRuntimeHazardLayer(map){
|
||||
function installRuntimePreviewLayers(map){
|
||||
removeRuntimePreviewLayers(map);
|
||||
if(!debugRuntimePreview.checked || !map.getSource("cm95"))return;
|
||||
addRuntimeDepthAreaLayer(map);
|
||||
addRuntimeContourLayer(map);
|
||||
addRuntimeSoundingLayer(map);
|
||||
addRuntimeHazardLayer(map);
|
||||
setTimeout(updateRuntimeSafetyContour,0);
|
||||
runtimeRuleExecutor=new CM95S52Runtime.RuntimeExecutor(map,{settings:RUNTIME_DEFAULTS});
|
||||
runtimeRuleExecutor.install();
|
||||
}
|
||||
|
||||
function refreshRuntimeRuleExecutor(){
|
||||
if(runtimeRuleExecutor)runtimeRuleExecutor.refresh();
|
||||
}
|
||||
|
||||
const CA_COLOR = Object.freeze({
|
||||
@@ -720,12 +724,12 @@ leftMap.on("sourcedata",function(e){
|
||||
if(debugRuntimePreview.checked)updateRuntimeSafetyContour();
|
||||
if(e.isSourceLoaded){
|
||||
refreshLightArcFeatures();
|
||||
refreshRuntimeHazards();
|
||||
refreshRuntimeRuleExecutor();
|
||||
}
|
||||
});
|
||||
leftMap.on("idle",function(){
|
||||
refreshLightArcFeatures();
|
||||
refreshRuntimeHazards();
|
||||
refreshRuntimeRuleExecutor();
|
||||
});
|
||||
leftMap.on("render",scheduleLightArcDraw);
|
||||
leftMap.on("resize",scheduleLightArcDraw);
|
||||
|
||||
@@ -27,6 +27,7 @@ def main():
|
||||
report_root = release_dir / "reports" / "delivery"
|
||||
manifest_path = release_dir / "release_manifest_v1.json"
|
||||
html_path = web_root / "kyushu_compare.html"
|
||||
runtime_executor_path = web_root / "s52_runtime_executor.js"
|
||||
assets_root = project_root / "build" / "fukuoka_s52_web" / "site" / "assets"
|
||||
s52_style_path = project_root / "build" / "s52_style_compiler_release_57e72826db1d" / "style.s52.json"
|
||||
s52_sprite_root = project_root / "build" / "s52_resources_v2" / "final"
|
||||
@@ -35,6 +36,7 @@ def main():
|
||||
pbf_root,
|
||||
manifest_path,
|
||||
html_path,
|
||||
runtime_executor_path,
|
||||
assets_root / "maplibre-gl.js",
|
||||
assets_root / "maplibre-gl.css",
|
||||
s52_style_path,
|
||||
@@ -65,6 +67,13 @@ def main():
|
||||
self.send_bytes(html_path.read_bytes(), "text/html; charset=utf-8")
|
||||
return
|
||||
|
||||
if path == "/s52_runtime_executor.js":
|
||||
self.send_bytes(
|
||||
runtime_executor_path.read_bytes(),
|
||||
"application/javascript; charset=utf-8",
|
||||
)
|
||||
return
|
||||
|
||||
if path == "/health":
|
||||
payload = {
|
||||
"status": "ok",
|
||||
|
||||
88
web/s52_runtime_executor.js
Normal file
88
web/s52_runtime_executor.js
Normal file
@@ -0,0 +1,88 @@
|
||||
(function(root){
|
||||
"use strict";
|
||||
|
||||
const DEFAULTS=Object.freeze({shallowContourM:2,safetyContourM:3,safetyDepthM:3,deepContourM:6,twoShades:false,depthUnit:"metres",heightUnit:"metres",symbolizedBoundaries:false});
|
||||
const COLORS=Object.freeze({CHBLK:"#070707",CHBRN:"#b19139",CHGRF:"#6d7473",CHMGD:"#c545c3",CSTLN:"#6d7473",DEPCN:"#7d898c",DEPSC:"#525a5c",DEPIT:"#83b295",DEPVS:"#73b6ef",DEPMS:"#98c5f2",DEPMD:"#bad5e1",DEPDW:"#d4eaee",LITRD:"#f15469",LITGN:"#68e456",LITYW:"#f4da48",OUTLW:"#070707"});
|
||||
|
||||
function jsonValue(value,fallback){if(value===undefined||value===null||value==="")return fallback;if(typeof value!=="string")return value;try{return JSON.parse(value);}catch(_err){return fallback;}}
|
||||
function numberValue(props,key,fallback=null){const value=Number(props&&props[key]);return Number.isFinite(value)?value:fallback;}
|
||||
function listValue(value){const parsed=jsonValue(value,value);if(Array.isArray(parsed))return parsed.map(Number).filter(Number.isFinite);if(parsed===undefined||parsed===null||parsed==="")return [];return String(parsed).replace(/^\[|\]$/g,"").split(",").map(Number).filter(Number.isFinite);}
|
||||
function rule(opcode,args,extra){return Object.assign({opcode:opcode,args:(args||[]).map(String),source:"runtime"},extra||{});}
|
||||
function storedRules(props,key){const value=jsonValue(props&&props[key],[]);return Array.isArray(value)?value:[];}
|
||||
function soundingRules(props,settings,depthOverride){
|
||||
const sourceDepth=depthOverride===undefined?numberValue(props,"s52_sounding_depth_m",null):Number(depthOverride);if(sourceDepth===null||!Number.isFinite(sourceDepth))return [];
|
||||
let depth=sourceDepth>40000?99999:sourceDepth<-1000?0:sourceDepth,safety=settings.safetyDepthM;
|
||||
if(settings.depthUnit==="feet"){depth/=0.3048;safety/=0.3048;}else if(settings.depthUnit==="fathoms"){depth/=0.3048*6;safety/=0.3048*6;}
|
||||
depth+=depth>0?0.01:-0.01;if(settings.depthUnit==="feet"&&Math.abs(depth)>0&&Math.abs(depth)<31)depth=Math.sign(depth)*Math.floor(Math.abs(depth)+0.5);
|
||||
const prefix=depth<=safety?"SOUNDS":"SOUNDG",codes=[];
|
||||
if(numberValue(props,"s52_sounding_swept",0)===1)codes.push(prefix+"B1");if(numberValue(props,"s52_sounding_low_quality",0)===1)codes.push(prefix+"C2");
|
||||
const absolute=Math.abs(depth),leading=Math.trunc(absolute);
|
||||
if(absolute<10){codes.push(prefix+"1"+leading);const fraction=Math.trunc(Math.abs(absolute-leading)*10);if(fraction>0)codes.push(prefix+"5"+fraction);if(depth<0)codes.push(prefix+"A1");}
|
||||
else if(absolute<31&&absolute-Math.floor(leading)!==0){const fraction=Math.trunc((absolute-Math.floor(leading))*10);if(leading>=10)codes.push(prefix+"2"+Math.trunc(leading/10));codes.push(prefix+"1"+(leading%10));if(fraction>0)codes.push(prefix+"5"+fraction);if(depth<0)codes.push(prefix+"A1");}
|
||||
else if(absolute<100){const a=Math.trunc(leading/10),b=leading%10;if(depth<0)codes.push(prefix+"2"+a,prefix+"1"+b,prefix+"A1");else codes.push(prefix+"1"+a,prefix+"0"+b);}
|
||||
else{const digits=String(leading).split(""),positions={3:[2,1,0],4:[2,1,0,4],5:[3,2,1,0,4]}[digits.length]||[];positions.forEach((position,index)=>codes.push(prefix+position+digits[index]));}
|
||||
return codes.map(code=>rule("SOUNDG",[code],{procedure:"SOUNDG03",depth:depth,depth_unit:settings.depthUnit}));
|
||||
}
|
||||
function qualityRules(props){return storedRules(props,"s52_hazard_quality_rule_chain");}function nameRules(props){return storedRules(props,"s52_hazard_name_rule_chain");}function sideEffect(values){return rule("SE",[],{side_effects:values});}
|
||||
function hazardIsolated(props,settings){const depth=numberValue(props,"s52_hazard_depth_m",null),expsou=numberValue(props,"s52_expsou_code",0);let danger=depth===null&&expsou!==1;if(!danger&&(expsou===1||(depth!==null&&depth<=settings.safetyContourM))){const surrounding=numberValue(props,"s52_surrounding_max_drval1_m",null);if(surrounding!==null&&surrounding>=settings.safetyContourM&&expsou!==1)danger=true;}return danger;}
|
||||
function hazardRules(props,settings){
|
||||
const csp=String(props.s52_runtime_csp||""),geom=String(props.s52_source_geometry||"POINT").toUpperCase(),objectClass=String(props.s52_source_object_class||"");
|
||||
const watlev=numberValue(props,"s52_watlev_code",-9),quapos=numberValue(props,"s52_quapos_code",0),valsouPresent=numberValue(props,"s52_valsou_present",0)===1,valsou=valsouPresent?numberValue(props,"s52_valsou_m",null):null,quasou7=listValue(props.s52_quasou_codes).includes(7);
|
||||
const isolated=csp==="WRECKS02"&&quasou7?false:hazardIsolated(props,settings),isolatedSymbol=isolated&&![1,2].includes(watlev),prefix=isolated?[sideEffect({display_category:"Displaybase"})]:[],q=qualityRules(props),name=nameRules(props);
|
||||
const sy=code=>rule("SY",[code],{procedure:csp}),line=(style,width,color)=>rule("LS",[style,width,color],{procedure:csp}),lc=code=>rule("LC",[code],{procedure:csp}),ac=color=>rule("AC",[color],{procedure:csp});
|
||||
const sound=()=>soundingRules(Object.assign({},props,{s52_sounding_swept:listValue(props.s52_tecsou_codes).includes(6)?1:0,s52_sounding_low_quality:(listValue(props.s52_quasou_codes).some(x=>[3,4,5,8,9].includes(x))||listValue(props.s52_status_codes).includes(18)||(quapos>=2&&quapos<10))?1:0}),settings,valsou);
|
||||
if(csp==="OBSTRN04"){
|
||||
if(geom==="POINT"){
|
||||
if(isolatedSymbol)return prefix.concat([sy("ISODGR51")],q,name);let main=null,showSound=false;
|
||||
if(valsouPresent){if(valsou<=20){if(objectClass==="UWTROC"){main=sy([4,5].includes(watlev)?"UWTROC04":"DANGER51");showSound=![4,5].includes(watlev);}else if(watlev===-9){main=sy("DANGER01");showSound=true;}else if([1,2].includes(watlev))main=sy("LNDARE01");else if(watlev===3){main=sy("DANGER52");showSound=true;}else if([4,5].includes(watlev)){main=sy("DANGER53");showSound=true;}else{main=sy("DANGER51");showSound=true;}}else{main=sy("DANGER52");showSound=true;}}
|
||||
else if(objectClass==="UWTROC")main=sy(watlev===2?"LNDARE01":watlev===3?"UWTROC03":"UWTROC04");else main=sy([1,2].includes(watlev)?"OBSTRN11":[4,5].includes(watlev)?"OBSTRN03":"OBSTRN01");
|
||||
return prefix.concat(main?[main]:[],showSound?sound():[],q,name);
|
||||
}
|
||||
if(geom==="LINE"){if(quapos>=2&&quapos<10)return prefix.concat([lc(isolated?"LOWACC41":"LOWACC31")],name);if(isolated)return prefix.concat([line("DOTT",2,"CHBLK")],name);const base=line(valsouPresent&&valsou>20?"DASH":"DOTT",2,"CHBLK");return prefix.concat([base],valsouPresent&&valsou<=20?sound():[],name);}
|
||||
if(isolatedSymbol)return prefix.concat([ac("DEPVS"),rule("AP",["FOULAR01"]),line("DOTT",2,"CHBLK"),sy("ISODGR51")],q,name);
|
||||
if(valsouPresent)return prefix.concat([line(valsou<=20?"DOTT":"DASH",2,"CHBLK")],sound(),q,name);if([1,2].includes(watlev))return prefix.concat([ac("CHBRN"),line("SOLD",2,"CSTLN")],q,name);if(watlev===4)return prefix.concat([ac("DEPIT"),line("DASH",2,"CSTLN")],q,name);
|
||||
const foul=numberValue(props,"s52_catobs_code",-9)===6&&[3,5].includes(watlev);return prefix.concat([ac("DEPVS")],foul?[rule("AP",["FOULAR01"])]:[],[line("DOTT",2,"CHBLK")],q,name);
|
||||
}
|
||||
if(csp!=="WRECKS02")return [];
|
||||
if(geom==="POINT"){
|
||||
if(isolatedSymbol)return prefix.concat([sy("ISODGR51")],q);
|
||||
if(valsouPresent){const out=[sy(valsou<settings.safetyContourM?"DANGER51":"DANGER52"),rule("TX",["Wk"],{text:{value:"Wk",hjust:"3",vjust:"1",xoff:"2",yoff:"0",color:"CHBLK",group:"21"}})];if(quasou7)out.push(sy("WRECKS07"));return prefix.concat(out,sound(),q);}
|
||||
const catwrk=numberValue(props,"s52_catwrk_code",-9),catPresent=numberValue(props,"s52_catwrk_code_present",0)===1,watPresent=numberValue(props,"s52_watlev_code_present",0)===1;let code=null;
|
||||
if(catPresent&&watPresent){if(catwrk===1&&watlev===3)code="WRECKS04";else if(catwrk===2&&watlev===3)code="WRECKS05";else if([4,5].includes(catwrk)||[1,2,4,5].includes(watlev))code="WRECKS01";else code="WRECKS05";}return prefix.concat(code?[sy(code)]:[],q);
|
||||
}
|
||||
let first;if(quapos>=2&&quapos<10)first=lc("LOWACC41");else if(isolated||(valsouPresent&&valsou<=20))first=line("DOTT",2,"CHBLK");else if(valsouPresent)first=line("DASH",2,"CHBLK");else if([1,2].includes(watlev))first=line("SOLD",2,"CSTLN");else if(watlev===4)first=line("DASH",2,"CSTLN");else first=line("DOTT",2,"CSTLN");
|
||||
if(valsouPresent&&valsou<=20)return prefix.concat([first],isolatedSymbol?[sy("ISODGR51")]:[],q,sound());if(valsouPresent)return prefix.concat([first],isolatedSymbol?[sy("ISODGR51")]:[],q);const color=[1,2].includes(watlev)?"CHBRN":watlev===4?"DEPIT":"DEPVS";return prefix.concat([first,ac(color)],isolatedSymbol?[sy("ISODGR51")]:[],q);
|
||||
}
|
||||
function dynamicRules(procedure,props,settings,context){
|
||||
if(procedure==="RESARE02")return storedRules(props,settings.symbolizedBoundaries?"s52_resare_symbolized_rule_chain":"s52_resare_plain_rule_chain");if(procedure==="LIGHTS05")return storedRules(props,settings.heightUnit==="feet"?"s52_lights_feet_rule_chain":"s52_lights_metres_rule_chain");if(procedure==="SOUNDG02")return soundingRules(props,settings);if(procedure==="OBSTRN04"||procedure==="WRECKS02")return hazardRules(props,settings);
|
||||
if(procedure==="DEPARE01"||procedure==="DEPARE02"){
|
||||
const d1=numberValue(props,"depth_min_m",-1);let d2=numberValue(props,"depth_max_m",d1+0.01);if(d2<=d1)d2=d1+0.01;let color="DEPIT";if(d1>=0&&d2>0)color="DEPVS";
|
||||
if(settings.twoShades){if(d1>=settings.safetyContourM&&d2>settings.safetyContourM)color="DEPDW";}else{if(d1>=settings.shallowContourM&&d2>settings.shallowContourM)color="DEPMS";if(d1>=settings.safetyContourM&&d2>settings.safetyContourM)color="DEPMD";if(d1>=settings.deepContourM&&d2>settings.deepContourM)color="DEPDW";}
|
||||
if(props.s52_source_object_class==="DRGARE"&&!Object.prototype.hasOwnProperty.call(props,"depth_min_m"))color="DEPMD";const out=[rule("AC",[color],{procedure:procedure})];if(props.s52_source_object_class==="DRGARE")out.push(rule("AP",["DRGARE01"]),rule("LS",["DASH","1","CHGRF"]));return out;
|
||||
}
|
||||
if(procedure==="DEPCNT02"){
|
||||
const contour=numberValue(props,"contour_depth_m",0),safe=context.effectiveSafetyContourM!==null&&Math.abs(contour-context.effectiveSafetyContourM)<1e-4,q=numberValue(props,"s52_quapos_code",0);let out=[];
|
||||
if(q>=2&&q<10)out=safe?storedRules(props,"s52_safe_dashed_rule_chain"):[rule("LS",["DASH","1","DEPCN"])];else if(q===0)out=safe?storedRules(props,"s52_safe_solid_rule_chain"):[rule("LS",["SOLD","1","DEPCN"])];
|
||||
if(safe){if(!out.length)out=[rule("LS",[q>=2?"DASH":"SOLD","2","DEPSC"])];out.unshift(sideEffect({display_category:"Displaybase",scamin:100000001}));}return out;
|
||||
}return [];
|
||||
}
|
||||
function expandRuleChain(props,settings,context){const initial=storedRules(props,"s52_rule_chain"),out=[];initial.forEach(item=>{if(item&&item.opcode==="CS"&&item.dynamic)dynamicRules(String(item.procedure||((item.args||[])[0])||""),props,settings,context).forEach(x=>out.push(x));else if(item)out.push(item);});return out.map((item,index)=>Object.assign({},item,{sequence:index}));}
|
||||
function operationFeatures(feature,settings,context,resourceExists){
|
||||
const props=feature.properties||{},geometry=feature.geometry||{},geomType=String(geometry.type||""),chain=expandRuleChain(props,settings,context),missing=[],output=[];
|
||||
const effects=Object.assign({},...chain.filter(x=>x.opcode==="SE").map(x=>x.side_effects||{}));
|
||||
chain.forEach(item=>{const opcode=String(item.opcode||""),args=item.args||[];if(opcode==="OP")return;if(opcode==="SE"){output.push({type:"Feature",id:String(feature.id==null?"":feature.id)+":"+item.sequence,geometry:geometry,properties:Object.assign({runtime_opcode:"SE",runtime_sequence:item.sequence},effects)});return;}if(["AC","AP"].includes(opcode)&&!["Polygon","MultiPolygon"].includes(geomType))return;if(["LS","LC"].includes(opcode)&&!["LineString","MultiLineString","Polygon","MultiPolygon"].includes(geomType))return;if(["SY","SOUNDG","AP","LC"].includes(opcode)&&args[0]&&resourceExists&&!resourceExists(String(args[0]))){missing.push(String(args[0]));return;}
|
||||
const draw=Object.assign({runtime_opcode:opcode,runtime_sequence:item.sequence,runtime_resource:String(args[0]||""),runtime_missing_resources:missing.join(",")},effects);if(opcode==="AC")draw.runtime_color=COLORS[args[0]]||COLORS.CHMGD;if(opcode==="LS"){draw.runtime_line_style=String(args[0]||"SOLD");draw.runtime_line_width=Number(args[1]||1);draw.runtime_color=COLORS[args[2]]||COLORS.CHBLK;}if(opcode==="TX"){draw.runtime_text=String((item.text&&item.text.value)||args[0]||"");draw.runtime_color=COLORS[(item.text&&item.text.color)||"CHBLK"]||COLORS.CHBLK;draw.runtime_text_xoff=Number((item.text&&item.text.xoff)||0);draw.runtime_text_yoff=Number((item.text&&item.text.yoff)||0);}if(opcode==="SOUNDG"){draw.runtime_opcode="SOUNDG";draw.runtime_soundg_index=output.filter(x=>x.properties.runtime_opcode==="SOUNDG").length;}output.push({type:"Feature",id:String(feature.id==null?"":feature.id)+":"+item.sequence,geometry:geometry,properties:draw});
|
||||
});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;}
|
||||
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();}
|
||||
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-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-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();const seen=new Set(),features=[];this.sourceLayers().forEach(layer=>{let rows=[];try{rows=this.map.querySourceFeatures("cm95",{sourceLayer:layer})||[];}catch(_err){return;}rows.forEach(feature=>{if(!feature.properties||!feature.properties.s52_rule_chain)return;const key=[layer,feature.id,JSON.stringify(feature.geometry),feature.properties.s52_rule_chain].join("|");if(seen.has(key))return;seen.add(key);operationFeatures(feature,this.settings,{effectiveSafetyContourM:this.effectiveSafetyContourM},name=>this.map.hasImage(name)).forEach(item=>features.push(item));});});const source=this.map.getSource(this.sourceId);if(source&&source.setData)source.setData({type:"FeatureCollection",features:features});}
|
||||
}
|
||||
const api={DEFAULTS:DEFAULTS,COLORS:COLORS,jsonValue:jsonValue,soundingRules:soundingRules,hazardRules:hazardRules,dynamicRules:dynamicRules,expandRuleChain:expandRuleChain,operationFeatures:operationFeatures,RuntimeExecutor:RuntimeExecutor};root.CM95S52Runtime=api;if(typeof module!=="undefined"&&module.exports)module.exports=api;
|
||||
})(typeof globalThis!=="undefined"?globalThis:this);
|
||||
Reference in New Issue
Block a user