EncDotNet.S100
The batteries-included on-ramp for IHO S-100 nautical data. Open a dataset, read
its features, and render it to an image without hand-wiring feature or portrayal
catalogues — the official catalogues bundled in
EncDotNet.S100.Specifications are
discovered and wired for you.
Install
dotnet add package EncDotNet.S100
That single package transitively brings in the readers, the pipeline factory, the Lua/MoonSharp portrayal engine, the bundled specifications, and the headless Skia renderer.
Linux arm64 native dependency
If you publish a linux-arm64 application that uses this facade (or the Skia
renderer directly), reference the self-contained SkiaSharp native in your
executable project:
<!-- In your app's .csproj -->
<ItemGroup>
<PackageReference Include="SkiaSharp.NativeAssets.Linux" ExcludeAssets="all" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" />
</ItemGroup>
The regular SkiaSharp.NativeAssets.Linux arm64 libSkiaSharp.so declares
undefined uuid_* / FT_Get_BDF_Property symbols that abort the process once
fontconfig/freetype load on a normal arm64 desktop or container. The
…NoDependencies build is self-contained and renders on both x64 and arm64.
Native RID asset selection belongs to the final executable, so this package does
not force the swap on your behalf. See
issue #23.
Quickstart — render a dataset to PNG
using EncDotNet.S100;
using var dataset = S100Dataset.Open("chart.000"); // detects the product spec
using var renderer = new PngS100DatasetRenderer();
byte[] png = await renderer.RenderAsync(dataset); // bundled FC + PC
File.WriteAllBytes("out.png", png);
RenderAsync(dataset) is the one-call path: it uses the bundled feature and
portrayal catalogues for the dataset's product specification.
Render options
using EncDotNet.S100.Pipelines; // PaletteType
byte[] png = await renderer.RenderAsync(dataset, new S100RendererOptions
{
Width = 2048,
Height = 1536,
Palette = PaletteType.Night,
SymbolScale = 1.25,
TimeStep = 0, // time-aware products (S-104, S-111)
});
Open from a folder, ZIP, or exchange set
S100Dataset.OpenAsync opens a dataset inside any IAssetSource (a
FileSystemAssetSource folder, a ZipAssetSource archive, or a decorator over
either), detecting its product specification from the content just as Open does
for a loose file:
using EncDotNet.S100.Core;
using var zip = ZipAssetSource.Create("S101.zip");
using var dataset = await S100Dataset.OpenAsync(zip, "S-101/DATASET_FILES/101AA00DS0019.000");
S100ExchangeSet opens an S-100 exchange set from a folder, its CATALOG.XML,
or a .zip, and lists its datasets. An S-101 base cell and the sequential updates
the set ships for it are one entry, opened with the updates applied:
await using var exchangeSet = await S100ExchangeSet.OpenAsync("S101.zip");
foreach (var entry in exchangeSet.Datasets)
{
using var dataset = await entry.OpenAsync();
byte[] png = await renderer.RenderAsync(dataset);
}
For an encrypted (S-100 Part 15) exchange set, build an IDatasetKeyProvider
from the set's Catalogue (typically a PermitKeyProvider over an authenticated
permit) and read through exchangeSet.WithDecryption(keys); see
Reading protected exchange sets.
See Loading datasets for the full guide: caching, S-101 updates, custom asset sources, and the lower-level processor API.
Read features
Feature access lives on the feature catalogue, because decoding a feature's type name and attributes presupposes one:
var fc = S100FeatureCatalogue.Bundled(dataset.Spec.Name);
foreach (var summary in fc.EnumerateFeatures(dataset))
Console.WriteLine($"{summary.FeatureRef}: {summary.FeatureType}");
FeatureInfo? info = fc.GetFeature(dataset, someFeatureRef);
Custom catalogues
A layer pairs a dataset with the catalogues used to interpret and portray it. Supply your own to override the bundled defaults:
var layer = new S100Layer
{
Dataset = dataset,
PortrayalCatalogue = S100PortrayalCatalogue.FromAssetSource(myPortrayalSource),
FeatureCatalogue = S100FeatureCatalogue.FromStream(myFeatureCatalogueXml),
};
byte[] png = await renderer.RenderAsync(layer);
When FeatureCatalogue / PortrayalCatalogue are left null, the bundled
catalogue for the dataset's product specification is used.
Validate
dataset.Validate() runs the product's bundled validation rules and returns a
ValidationReport of findings, or null when the product has no rule pack. See
Custom catalogues and validation,
which also shows how to add your own rules.
Layering — the grow-up story
S100Layer is the composable unit, so growing from a single chart to a stacked
view (e.g. an S-101 chart under S-102 bathymetry and S-411 sea ice) is "add more
layers." Both paths ship today:
using var renderer = new PngS100DatasetRenderer();
// Single layer:
byte[] one = await renderer.RenderAsync(dataset);
// Composite — an ordered list, bottom-most first:
byte[] many = await renderer.RenderAsync(
new[]
{
new S100Layer { Dataset = enc }, // S-101
new S100Layer { Dataset = bathy }, // S-102
},
new S100CompositeOptions { Width = 2048, Height = 1536 });
The composite overload (IS100CompositeRenderer<byte[]>) drives the
renderer-neutral S-98 interoperability engine for cross-dataset paint
ordering and depth suppression (e.g. the S-101-under-S-102 interleave and the
R-101-102-B depth-shading suppression, S-98 Annex A §A-6.9.1), then paints all
layers against one shared viewport. Supply S100CompositeOptions.Viewport to
pin the framing, or leave it null to fit the union extent of all active
layers. The whole path is Mapsui-free.
Renderers are generic in their result
IS100DatasetRenderer<TResult> is parameterised on the result type.
PngS100DatasetRenderer implements IS100DatasetRenderer<byte[]> (PNG bytes).
The same abstraction extends to other encoders (JPEG/WebP), to in-memory bitmaps,
and — once the Mapsui decoupling
(#189) lands — to a
Mapsui layer renderer, without changing the contract.
Mapsui note. The public API of this package is Mapsui-free (it returns
byte[],FeatureSummary, andFeatureInfo— never Mapsui types). Mapsui is currently a transitive dependency of the underlying pipeline; when #189 lands it drops out with no change to this package's API.
À-la-carte (advanced)
This facade is purely additive. Advanced users who need full control can keep
using a per-spec reader with an injected catalogue, or drive
DatasetPipelineFactory
(EncDotNet.S100.Datasets.Pipelines)
directly.
Reuse and disposal
S100DatasetandPngS100DatasetRendererareIDisposable; dispose them.- Datasets are parsed lazily, on first use, so they read from their source after
Open/OpenAsyncreturns.S100Dataset.OpenAsync(source, …)andS100ExchangeSet.OpenAsync(source, …)borrow theIAssetSourceyou pass: keep it alive until the datasets are disposed, then dispose it yourself.S100ExchangeSet.OpenAsync(path)owns the source it creates; dispose datasets opened from an exchange set before the exchange set. - A
PngS100DatasetRendererinstance may render many datasets sequentially; it caches the bundled pipeline host so repeated renders reuse warmed catalogue parse caches. Concurrent use of one instance is not supported.