S-100 feed format
Why it exists
An S-100 feed is this project's own, simple JSON format for publishing
datasets to other machines (issue #680). A feed can be served over HTTP by
s100 feed serve, or written as static files by s100 feed export for any web
host. Another machine's viewer then adds the feed's URL as an online catalogue
in its Library.
The Library can already read standard catalogues, but none of them fit this job:
- S-100
CATALOG.XMLdescribes only S-100 datasets. It cannot describe S-57 cells or loose datasets. - S-128 is heavyweight and carries no download URLs.
- NOAA's product catalogue is specific to NOAA ENCs.
A feed carries the same metadata the Library indexes locally: coverage polygons, bounds, editions, updates, display scales and usage bands. So a reader can show coverage before downloading anything.
Document
A feed is one JSON document, conventionally named feed.json. It is written
compact; it is shown indented here:
{
"format": "encdotnet-s100-feed",
"version": 1,
"title": "Ohio charts",
"generatedAt": "2026-09-25T23:27:38.364377+00:00",
"fingerprint": "local-v2:F053E74084A10F99E7ECB1E2AA827E5778C8B8A962D573D95EDB65B2D1E9F2B4",
"machine": "bridge-pc",
"items": [
{
"key": "ENC_ROOT/US4OH1MK",
"productSpec": "S-57",
"name": "US4OH1MK",
"title": "Lake Erie - Lake Center Offshore of Cleveland, OH",
"groupKey": "ENC_ROOT",
"edition": 1,
"update": 1,
"issueDate": "2024-05-07",
"updateApplicationDate": "2024-02-21",
"compilationScale": 90000,
"usageBand": 4,
"status": "unknown",
"bounds": {
"south": 42,
"west": -81.9,
"north": 42.3,
"east": -81.6,
"crossesAntimeridian": false,
"longitudeSpan": 0.3
},
"location": {
"kind": "remote",
"uri": "items/0d8d194517c460ed498b.zip",
"sizeBytes": 7664,
"lastModified": "2024-05-08T19:03:40+00:00",
"package": "0d8d194517c460ed498b",
"layout": {
"relativePath": "US4OH1MK/US4OH1MK.000",
"updateRelativePaths": ["US4OH1MK/US4OH1MK.001"],
"catalogueRelativePath": "CATALOG.031"
}
},
"properties": {
"producingAgency": "550",
"intendedUsage": "4"
}
}
]
}
- Header fields:
formatmust beencdotnet-s100-feed.machine(optional) is the publishing computer's name.s100 feed servesets it, and the viewer names a shared feed after it.- A reader rejects a
versionnewer than it knows.
fingerprint: changes whenever the published data changes. A server uses it as the HTTPETag, so readers can revalidate with conditional requests.- Items: have the shape of the Library index (
CollectionItem). Unknown properties are ignored. - What is published: only items whose files are present on the publishing machine.
- Coverage: S-100 items can carry
coveragepolygons as well asbounds. Inbounds,crossesAntimeridianandlongitudeSpanare derived and ignored on reading. A bounding box that crosses ±180° haswestgreater thaneast. - Item
location: alwaysremote, with these fields:uriis the item's download, relative to the feed's URL, so the same feed works behind any host, port or static web host.packageis the item's opaque id, a hash of its key. It never reveals, or reaches, a path on the publisher.layoutsays where the item's files lie inside the download.sizeBytesis the total size of those files, uncompressed.lastModifiedis when the newest of them changed.
Downloads
Each item downloads as a zip (items/<id>.zip). The zip holds the item's
exchange-set catalogue, base file and updates, at their paths relative to the
item's root. Files are never outside that root.
The reader extracts the zip into a folder of its own and opens the dataset at
layout.relativePath, with the catalogue and updates beside it. That is the
same layout it would have on the publishing machine.
Serving a feed
s100 feed serve <path> serves feed.json and the item zips over HTTP:
s100 feed serve charts/ --host 0.0.0.0
- Scope: by default the server listens on localhost only. On another
address it adds a random access token as the first path segment
(
http://<host>:8100/<token>/feed.json). Item URLs are relative, so they carry the token too. - Caching: the feed's
ETagis derived from its fingerprint, so an unchanged folder answers conditional requests with304 Not Modified.
See the CLI reference for the options.
Exporting a static feed
s100 feed export <path> --out <directory> writes feed.json and the item
zips as files, for any static web host:
s100 feed export charts/ --out site/charts
- Incremental: re-exporting rewrites only the items whose files changed.
- Always consistent:
feed.jsonis written last, so it always lists zips that exist. - Caching: a static host supplies its own caching headers, such as
ETagorLast-Modified. Readers revalidate with those.
Using a feed in the viewer
In the viewer, open Library → Online Catalogue, choose Add a catalogue by
URL and add the feed's URL with Check & add. The viewer recognises the feed from its format property and
lets you choose which products to include.
- On the map: the feed's items appear with their coverage.
- Downloads: each download goes to
downloads/feeds/<host>-<port>-<hash>/. - Revalidation: the feed is revalidated at most once a minute, with a conditional request.
Building feeds in code
using EncDotNet.S100.Collections;
using EncDotNet.S100.Collections.Feeds;
using EncDotNet.S100.Collections.Indexing;
// Index a folder in place, then publish what was indexed.
var index = await CollectionIndexer.CreateDefault()
.IndexAsync(new LocalFolderSource(Guid.NewGuid(), null, "/charts/ohio"));
var feed = S100Feed.FromIndex(index, "Ohio charts");
using (var output = File.Create("feed.json"))
S100Feed.Write(output, feed);
// Write one item's download.
var item = index.Items[0];
using (var zip = File.Create(S100Feed.ItemId(item) + ".zip"))
await S100FeedPackager.WriteZipAsync(zip, (LocalItemLocation)item.Location);
On the reading side, S100Feed.Read(stream, feedUri) resolves each item's
URL against the address the feed was fetched from.