Table of Contents

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, and FeatureInfo — 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

  • S100Dataset and PngS100DatasetRenderer are IDisposable; dispose them.
  • Datasets are parsed lazily, on first use, so they read from their source after Open/OpenAsync returns. S100Dataset.OpenAsync(source, …) and S100ExchangeSet.OpenAsync(source, …) borrow the IAssetSource you 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 PngS100DatasetRenderer instance 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.