diff --git a/src/cm95pbf/csp.py b/src/cm95pbf/csp.py index 570fda3..0c8e70e 100644 --- a/src/cm95pbf/csp.py +++ b/src/cm95pbf/csp.py @@ -105,8 +105,12 @@ def resolve_depcnt02_instruction( if object_class == "DEPARE": drval1 = _float(attrs, "DRVAL1", 0.0) drval2 = _float(attrs, "DRVAL2", drval1) - if drval1 <= settings.safety_contour_m <= drval2: - safe = True + # Match OpenCPN DEPCNT02 control flow exactly: the chart's + # next-safe-contour fallback is only considered when DRVAL1 is + # already deeper than the mariner safety contour. + if drval1 <= settings.safety_contour_m: + if drval2 >= settings.safety_contour_m: + safe = True elif next_safe_contour_m is not None: safe = abs(drval1 - next_safe_contour_m) < 1e-4 else: @@ -605,9 +609,17 @@ def materialize_mariner_runtime_facts( out["depth_max_m"] = depth_max if name == "DEPCNT02": - contour = _optional_float(attrs.get("VALDCO")) - if contour is not None: - out["contour_depth_m"] = contour + if object_class == "DEPARE": + depth_min = _optional_float(attrs.get("DRVAL1")) + depth_max = _optional_float(attrs.get("DRVAL2")) + if depth_min is not None: + out["depth_min_m"] = depth_min + if depth_max is not None: + out["depth_max_m"] = depth_max + else: + contour = _optional_float(attrs.get("VALDCO")) + if contour is not None: + out["contour_depth_m"] = contour if name == "SOUNDG02" and sounding_depth_m is not None: out["s52_sounding_depth_m"] = float(sounding_depth_m) diff --git a/web/s52_runtime_executor.js b/web/s52_runtime_executor.js index 3427c40..aa378c7 100644 --- a/web/s52_runtime_executor.js +++ b/web/s52_runtime_executor.js @@ -81,7 +81,16 @@ function dynamicRules(procedure,props,settings,context){ 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=[]; + let safe=false; + if(String(props.s52_source_object_class||"")==="DEPARE"){ + const d1=numberValue(props,"depth_min_m",0),d2=numberValue(props,"depth_max_m",d1); + if(d1<=settings.safetyContourM)safe=d2>=settings.safetyContourM; + else safe=context.effectiveSafetyContourM!==null&&Math.abs(d1-context.effectiveSafetyContourM)<1e-4; + }else{ + const contour=numberValue(props,"contour_depth_m",0); + safe=Math.abs(contour-settings.safetyContourM)<1e-4||(context.effectiveSafetyContourM!==null&&Math.abs(contour-context.effectiveSafetyContourM)<1e-4); + } + const 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 [];