EncDotNet.S100.Datasets.S421
Library for reading and portraying IHO S-421 (Route Plan) datasets.
S-421 defines a standard exchange format for ship route plans — waypoints, leg information, schedules, and action points — encoded as GML conforming to the S-100 framework.
Features
- Parse S-421 GML datasets (S-100 Part 10b encoding)
- Extract route features (
Route,RouteWaypoints,RouteWaypoint,RouteSchedules,RouteSchedule,RouteWaypointLeg,RouteActionPoints,RouteActionPoint) - Extract information types (
RouteInfo, etc.) - Resolve
xlink:hrefcross-references between objects (e.g. aRouteto itsRouteWaypointscollection, aRouteWaypointsto its individualRouteWaypoints) - Convert to S-100 Part 9 FeatureXML for portrayal pipeline consumption
- XSLT-based portrayal via the bundled S-421 Portrayal Catalogue
Overview
Key types include:
S421Dataset— root model containing parsed features, information types, and dataset identification.ReadMetadata()(plus staticReadMetadata(path)/ReadMetadata(stream)) is the phased-loading "peek" path (issue #460): it returns aDatasetMetadatawith the declared spec and the raw WGS-84 extent folded from feature geometry (nullwhen the dataset carries only geometry-less container features), skipping the XSLT portrayal pipeline.S421Feature— a feature with type code, optional geometry, simple attributes, complex attributes, andxlinkreferences.S421InformationType— a non-spatial information type instance (e.g.RouteInfo).GmlReference(namespaceEncDotNet.S100.Features, in theEncDotNet.S100.Corepackage) — anxlink:hrefreference from one object to another.S421ComplexAttribute— a complex attribute instance containing sub-attribute values.S100GeometryType— shared enum (fromEncDotNet.S100.Core) describing the geometry primitive type of a feature (None,Point,Curve,Surface).S421FeatureXmlSource—IFeatureXmlSourceadapter that projects anS421Datasetinto S-100 Part 9 FeatureXML for XSLT portrayal rules.S421PortrayalCatalogue—IVectorPortrayalCatalogueimplementation that loads XSLT rules, symbols, line styles, area fills, and color palettes.
Strongly-typed data model
The types above expose the dataset as a feature bag — schema-agnostic, well
suited for the portrayal pipeline, but inconvenient when client code wants to
inspect routes, waypoints, or schedules directly. The
EncDotNet.S100.Datasets.S421.DataModel namespace layers a strongly-typed
projection on top:
S421RoutePlan.From(dataset, out diagnostics)— projects anS421Datasetinto a typed object graph rooted atS421Route, resolvingxlink:hrefcross-references and parsing primitive values (int,double,bool,DateTimeOffset).S421RoutewithInfo,Waypoints,Legs,ActionPoints,Schedules.S421Waypointwith typedOutgoingLegandIncomingLegbidirectional navigation along the route.S421Legwith typedStartWaypoint/EndWaypointendpoint references, so consumers holding a leg can reach both of its bounding waypoints without coordinate matching.S421ActionPoint,S421Schedule+ variants (Manual / Calculated / Recommended),S421ScheduleElement.- Anything the typed model does not understand is preserved on each object's
ExtraAttributesdictionary, so extension and future-edition attributes round-trip verbatim. - Projection failures (unresolved references, unparseable date/times) surface
as
ProjectionDiagnosticentries (fromEncDotNet.S100.DataModel) rather than exceptions.
Shared abstractions. As of Pass 1 of the typed-model initiative, the projection layer consumes the cross-cutting
ProjectionDiagnostic,DiagnosticSeverity,GeoPosition,XlinkResolver,AttributeParser, andExtraAttributeshelpers fromEncDotNet.S100.DataModelinEncDotNet.S100.Core. The previous per-spec types (S421ProjectionDiagnostic,S421DiagnosticSeverity,S421Reference) have been removed.
Installation
dotnet add package EncDotNet.S100.Datasets.S421
Quick start
using EncDotNet.S100.Datasets.S421;
var dataset = S421Dataset.Open("RTE-TEST-GMIN.s421.gml");
Console.WriteLine($"Dataset: {dataset.DatasetIdentifier}");
Console.WriteLine($"Features: {dataset.Features.Length}");
Console.WriteLine($"Information types: {dataset.InformationTypes.Length}");
foreach (var wp in dataset.Features.Where(f => f.FeatureType == "RouteWaypoint"))
{
var (lat, lon) = wp.Points[0];
Console.WriteLine($" Waypoint {wp.Attributes["routeWaypointID"]}: {lat}, {lon}");
}
Quick start (typed model)
using EncDotNet.S100.DataModel;
using EncDotNet.S100.Datasets.S421;
using EncDotNet.S100.Datasets.S421.DataModel;
var dataset = S421Dataset.Open("RTE-TEST-GMIN.s421.gml");
var plan = S421RoutePlan.From(dataset, out var diagnostics);
Console.WriteLine($"Route {plan.Route.RouteId} ed.{plan.Route.EditionNumber}");
Console.WriteLine($" Author: {plan.Route.Info?.Author}");
Console.WriteLine($" Vessel: {plan.Route.Info?.Vessel?.Name} (MMSI {plan.Route.Info?.Vessel?.Mmsi})");
foreach (var wp in plan.Route.Waypoints)
{
var leg = wp.OutgoingLeg;
Console.WriteLine(
$" WP{wp.WaypointNumber} {wp.Position.Latitude:F4}, {wp.Position.Longitude:F4}"
+ (leg is not null ? $" → leg {leg.Id}" : ""));
}
// Walk the route via typed endpoint references — no href parsing required.
var cursor = plan.Route.Waypoints.FirstOrDefault();
while (cursor?.OutgoingLeg is { } leg)
{
Console.WriteLine($" leg {leg.Id}: {leg.StartWaypoint?.Id} → {leg.EndWaypoint?.Id}");
cursor = leg.EndWaypoint;
}
foreach (var d in diagnostics) Console.WriteLine(d);