Table of Contents

EncDotNet.S100.Core

Core abstractions and pipeline framework for working with S-100 based nautical chart data.

Overview

This library provides the foundational types used across the EncDotNet.S100 libraries, including:

  • Asset sources — IAssetSource abstraction for reading files from directories (FileSystemAssetSource) or ZIP archives (ZipAssetSource).
  • HDF5 abstractions — IHdf5File and IHdf5Group interfaces for reading HDF5 data without binding to a specific HDF5 library.
  • HDF5 reader exceptions — S100DatasetSchemaException (a required attribute/group is missing or malformed) and S100DatasetNotSupportedException (the file uses an optional spec feature the reader doesn't yet implement). Both carry product, file, group path, spec reference, and a .WithFile(...) helper used by processor layers to attach the source file name. S100DatasetSchemaException also carries an optional AdditionalContext (and .WithAdditionalContext(...) helper), preserved across .WithFile(...), that readers use to append explanatory context — for example a note that the dataset declares an unexpected product-specification edition that may explain the failure. Hdf5RequiredAttributeExtensions provides ReadRequiredDoubleAttribute / ReadRequiredInt64Attribute / ReadRequiredStringAttribute that translate backend "missing attribute" failures into these typed exceptions.
  • Lua scripting abstractions — ILuaEngine and ILuaContext interfaces for running sandboxed Lua portrayal scripts, plus the S100LuaHost host API.
  • Coverage pipeline — ICoverageSource, ICoverageRenderer<T>, CoveragePipeline, and supporting types (GridGeoreferencer, CoverageColorScheme, StyledCoverageLayer) for rendering gridded data. ICoveragePortrayalCatalogue.ResolveColorScheme returns CoverageColorScheme?; catalogues that only emit symbology (e.g. S-111's arrow-only portrayal) return null and the coverage renderers throw if invoked on a layer with a null scheme. CoveragePipeline.ProcessAsync accepts an optional Viewport? (and, for grids authored in a non-EPSG:4326 CRS, an ICrsTransform? wgs84ToNative); when supplied it uses GridRegion.FromViewport to sample only the cells that fall inside the viewport at the viewport's ground resolution — clamping the subset to the intersection of the viewport bbox and the grid extent, and deriving a stride so ground resolution ≤ cell size is honoured (issue #487). Coverage sampling applies on initial layer construction; live pan/zoom re-sampling of the built Mapsui coverage layer is a follow-up (see issue #486). The georeferencer on the emitted StyledCoverageLayer is built from the sampled subset's GridMetadata (subset-adjusted origin + stride-scaled spacing) rather than the source's full-grid metadata so subset+stride sampling is drawn in its true geographic location.
  • Vector pipeline — IVectorSource, IVectorPortrayalCatalogue, VectorPipeline, and the DrawingInstruction hierarchy (AreaInstruction, LineInstruction, PointInstruction, TextInstruction) modelled directly on the S-100 Part 9 display list. Portrayal-rule execution is pluggable behind IVectorRuleExecutor (the engine-agnostic rule stage the pipeline runs and merges). Two engine siblings implement it: Pipelines.Vector.Xslt.XsltRuleExecutor (S-100 Part 9 §9.4 — owns FeatureXML acquisition, rule selection, XSLT transformation, and display-list assembly) and Pipelines.Vector.Lua.LuaRuleExecutor (S-100 Part 9A). VectorPipeline itself is now reduced to running the built-in XSLT executor, appending the optional injected Lua executor's output, then applying the shared viewing-group / display-plane filter and priority sort; it no longer contains any XSLT-specific code. The Lua executor is a single product-agnostic executor driven entirely by injected seams (ILuaDataProvider / ILuaDataProviderFactory host bridge, LuaContextParameterBinding mariner→parameter mapping, IFeatureAnchorProvider, and IDrawingInstructionTransform), so S-101 and S-131 share one executor and supply only their product-specific seams. DrawingInstructionParser also lives here.
  • Validation framework (EncDotNet.S100.Validation) — spec-agnostic types for expressing normative-clause checks against the typed data models: IValidationRule<TModel>, ValidationRuleSet<TModel> (lint-pass runner that collects all findings and traps per-rule exceptions), ValidationFinding (rule id, severity, message, optional GeoPosition/BoundingBox, related feature id), ValidationSeverity, ValidationContext (carries ReferenceTime and an opaque IServiceProvider? for Tier-3 cross-dataset rules), ValidationReport, and a fluent ValidationRuleBuilder (RuleFor<T>("rule-id").Check(predicate, msg).Build() and .Yield(producer) for multi-finding rules). Per-spec rule packs live in the respective EncDotNet.S100.Datasets.Sxxx/Validation/ folder rather than in separate projects. All fifteen validated products (S-101, S-102, S-104, S-111, S-122, S-124, S-125, S-127, S-128, S-129, S-131, S-201, S-411, S-421, plus S-57 via delegation to S-101) ship a rule pack. S-401 (IEHG inland ENC) is read and portrayed but ships no rule pack — the S-101 pack asserts S-101 normative clauses and is deliberately not run against inland data, so Validate() reports "no rules available" for it. For vector products the rule pack reads from a spec-aligned façade (the S-101 pattern is S101DatasetView / S101FeatureView / S101AttributeView) instead of the raw reader types, keeping the door open for a future typed DataModel projection without breaking rule code. Coverage records expose a GroupPath field (BathymetryCoverage.GroupPath on S-102, WaterLevelCoverage.GroupPath on S-104) that rule packs use as the per-coverage ValidationFinding.RelatedFeatureId.
  • Part9DisplayListReader — parses the Part 9 display-list XML produced by XSLT-based portrayal pipelines (S-124 / S-129 / S-421) into the same unified DrawingInstruction hierarchy that S-101's Lua pipeline emits, so a single renderer can consume both.
  • Portrayal-instruction caching (Pipelines.Vector.Caching) — a cross-load cache of the post-pipeline DrawingInstruction list so re-opening a previously-portrayed dataset can skip the (for S-101, multi-second MoonSharp Part 9A Lua) portrayal run. IPortrayalInstructionCache.GetOrCompute(key, factory) is implemented by InMemoryPortrayalInstructionCache (bounded LRU, holds list references) and DiskPortrayalInstructionCache (persists each list as a .dlist sidecar via DrawingInstructionSerializer, with atomic temp+move writes, an LRU byte cap, and corruption / FormatVersion-mismatch treated as a miss so a stale or partial file never breaks a render). The caller is responsible for a key that fully captures every portrayal input; S101DatasetProcessor does this with a content hash over the dataset bytes, the feature- and portrayal-catalogue content (including overrides and Lua rules), and the engine assembly versions. Caching at the post-pipeline boundary (rather than raw Lua-emitted strings) means a hit reproduces the exact list a fresh run would, without bypassing any later stage.
  • Phased dataset metadata — DatasetMetadata (with SpecRef Spec, BoundingBox? Extent, int? HorizontalCrsEpsg, DisplayScaleRange? DisplayScale, TimeCoverage? TimeCoverage) is the product-agnostic "peek" result every dataset reader returns from its ReadMetadata path — the cheap facts (declared spec, geographic extent, display-scale window, temporal span) a host needs to place a dataset on the map and decide whether a full parse + portrayal is worth it, without doing that work. This makes loading a "loose" (catalogue-less) folder of datasets phased: probe many cheaply, frame a viewport from the union of their extents, and defer each full load until it is brought into view (issue #460). Only Spec is guaranteed; a null optional means "not cheaply available — fall back to a full load." Gml.GmlDatasetMetadata.Create(specName, declaredEdition, features) is the shared helper the GML products use to fold feature geometry into an extent. A cross-session metadata sidecar cache (Metadata namespace) persists this peek result so a later session need not re-parse: DatasetMetadataSerializer frames a DatasetMetadata into a small versioned binary blob (corruption / FormatVersion mismatch → miss), and IDatasetMetadataCache / DiskDatasetMetadataCache store one .dmeta sidecar per dataset keyed by the source file's last-write time + length (any mismatch, or an unwritable/corrupt entry, is a miss that never breaks loading), with atomic temp+move writes and an LRU byte cap — the same robustness contract as the portrayal-instruction cache (issue #467 WS3).
  • Shared types — IPortrayalCatalogue, ICrsTransform, Viewport, MarinerSettings (S-100 Part 9 §4.2 mariner selections, including the four depth contours and S-101 portrayal toggles such as FourShades, SimplifiedSymbols, RadarOverlay, NationalLanguage), DepthUnit and the DepthFormatting helper for locale-invariant depth conversion / formatting / parsing across metres, feet, fathoms, and combined fathoms-and-feet, BoundingBox, RgbaColor, ColorPalette.
  • Data-coverage geometry (DataModel.CoverageArea) — the EPSG:4326 (lat/lon, S-100 Part 10b §6.2) data-coverage footprint of a vector cell: an ExteriorRing plus optional InteriorRings (no-coverage holes). It is surfaced by the S-101/S-57 processors (from DataCoverage surfaces with categoryOfCoverage = 1) so a host can suppress a coarser cell's contribution where a finer, overlapping in-band cell provides coverage ("larger-scale-in", issue #438 Phase 2).
  • Spec-version assessment — SpecRef / CatalogueRef / SpecVersion plus SpecCompatibility.Classify(declared, implemented) (S-100 Edition 5.2.1 Part 2 §6) and SpecVersionAssessment. The assessment compares a dataset's declared product-specification edition against the edition(s) this application supports (never against the floating Feature/Portrayal Catalogue version, which would raise false alarms — and which the catalogue files cannot supply anyway, since an FC/PC declares only its own version, not the product-spec edition it targets); IsWarning is true only when the application supports an older edition on the same major or no edition on the declared major, and BuildMessage() renders the user-facing note. Gml.GmlDatasetIdentification.ReadDeclaredEdition(root) extracts the declared edition from a GML dataset's DatasetIdentificationInformation/productEdition (S-100 GML 5.0 or legacy 1.0 profile). See issue #248.
  • Dynamic feature sources (EncDotNet.S100.DynamicSources) — graphics-agnostic abstraction for push-driven point/track/area features (own-ship, AIS, route preview, sensor overlays, etc.) that sit alongside static datasets in the rendering surface: IDynamicFeatureSource (snapshot + Changed event), DynamicFeature (geometry vocabulary reused from the static vector pipeline — same GeometryType enum and (Latitude, Longitude) tuple convention), DynamicMotion sidecar for moving point features, DynamicVesselGeometry sidecar for vessel dimensions (length, beam, CCRP/GPS-antenna offsets per IEC 62388 — semantically matches AIS Type 5 dimA/dimB/dimC/dimD), DynamicSourceMetadata (carries DisplayName and RendererKey for DI-keyed renderer resolution in EncDotNet.S100.Renderers.Mapsui), DynamicFeaturesChanged + DynamicSourceChangeKind, and the optional DynamicFeatureTracker<TInbound> helper for adapters with aging semantics (AIS sleep/lost timers, stale-sensor styling). See docs/design/dynamic-feature-source.md for the full design rationale.

Installation

dotnet add package EncDotNet.S100.Core