Skip to content

DHI.Services.MIKECore – Internal Guide

This doc shows how to open and work with MIKE Zero files (dfs0/dfs2/dfsu) through DHI.Services.MIKECore. It covers the architecture, capabilities, configuration, file/ID conventions, and usage patterns. Per-domain API details still live with those services (TimeSeries, GIS, Map, Mesh, etc.); this is the big picture and how to leverage the providers.


What MIKECore is

A set of lightweight providers that read/write MIKE Zero files via the managed MIKE APIs:

  • dfs0 (point time series)
  • dfs2 (2D gridded rasters, time-varying)
  • dfsu (unstructured meshes, 2D/3D, time-varying)

MIKECore focuses on:

  • Direct file access through IFileSource (local folders by default; can be swapped for cloud/backed stores).
  • No ORM / no DB—it reads/writes MIKE files directly.
  • Vendor libraries: Generic.MikeZero.DFS.*, SkiaSharp for map rendering, and DHI GIS utilities for projections and geometry.

Key building blocks

IFileSource & file addressing

All providers receive either:

  • an IFileSource + relative path/prefix, or
  • a connection string that is usually just a file path (or a folder for map sources).

Internally each provider uses:

_fileSource.ReadDfs0 / ReadDfs2 / ReadDfsu(path, dfsFile => { ... })

This guarantees safe open/close and lets implementations hook alternative storage.

Projections & coordinates

  • The MIKE projection (WKT, lon/lat, orientation) is read from the file header.
  • The Cartography helpers handle Geo <-> projected transforms plus true-north rotation corrections where needed.

Delete values

  • dfs files encode missing data (DeleteValueFloat / DeleteValueDouble).
  • Providers translate delete values to nulls where it makes sense (e.g., time series values).

Time axes

  • Both equidistant and non-equidistant calendar axes are supported for reading.
  • When writing dfs0, we enforce equidistance if the target file axis is equidistant.

Providers at a glance

Area Type Class Read Write Notes
Maps (dfs2) Map source Dfs2MapSource Renders SKBitmap tiles/overlays. EPSG:3857 only for map output.
Maps (dfsu) Map source DfsuMapSource Contour rendering over meshes; EPSG:3857 output.
Time series (dfs0) Repo Dfs0TimeSeriesRepository Single file, single/multi item; add/set using temp file+builder.
Time series groups (dfs0) Repo Dfs0GroupedTimeSeriesRepository One dfs0 per “group”; merges items across files by path.
Time series (dfs2) Repo Dfs2TimeSeriesRepository Extracts a cell/area time series from rasters (with rotation fixes).
Time series (dfsu) Repo DfsuTimeSeriesRepository Extracts element/area time series (2D/3D layer aware).
Features (dfs2) Repo Dfs2FeatureRepository Builds polygon features for each cell at a time step; carries item attributes.
Features (dfsu) Repo DfsuFeatureRepository Builds element polygons; attributes per selected item/time.
Features (dfsu, contour polygons) Repo DfsuContouringFeatureRepository Generates contour areas from dfsu item values.
Rasters (dfs2) TimeStep server Dfs2TimeStepServer Returns a Raster (values + georef bounding box) per item & time.
Meshes (dfsu) Mesh repo DfsuMeshRepository Mesh metadata, time stamps, point/polygon aggregation, mesh data.

Registration patterns

Map example (dfs2)

var dfs2File = Path.Combine(appData, "dfs2", "R20141001.dfs2");

ServiceLocator.Register(
  new MapService(
    new Dfs2MapSource(dfs2File),                 // or Dfs2MapSource(new FileSource(root), "relative/path.dfs2")
    new MapStyleService(
      new MapStyleRepository(Path.Combine(appData, "styles.json"), SerializerOptionsDefault.Options)
    )
  ),
  "dfs2-map"
);

TimeSeries (dfs2) via config

"dfs2": {
  "$type": "DHI.Services.TimeSeries.WebApi.TimeSeriesServiceConnection, DHI.Services.TimeSeries.WebApi",
  "ConnectionString": "C:\\Temp\\R20141001.dfs2",
  "RepositoryType": "DHI.Services.Provider.MIKECore.Dfs2TimeSeriesRepository, DHI.Services.Provider.MIKECore",
  "Name": "MIKE dfs2 time series connection",
  "Id": "dfs2"
}

Use the assembly-qualified type names that match your build (namespace here is …MIKECore).


ID conventions & selection “dialects”

Most repos accept an ID string that encapsulates file, what to extract, and how to locate it. The exact parser classes (e.g., Dfs0TimeSeriesId, Dfs0GroupedTimeSeriesId, Dfs2DTimeSeriesId, DfsuFeatureCollectionId) live in the domain packages; here’s how they behave:

dfs0 (point time series)

  • Dfs0TimeSeriesRepository
    • GetAll() → one TimeSeries per item.
    • GetIds() can return short (itemName) or long IDs (includes quantity) based on DefaultLongId.
    • Get(id)/GetValues(id) → match by item number/name/quantity via Dfs0TimeSeriesId.
  • Dfs0GroupedTimeSeriesRepository
    • IDs are two parts: relative file path + item path (ObjId), wrapped by Dfs0GroupedTimeSeriesId.
    • Groups map to subfolders; each .dfs0 under that path is another group/file.

dfs2 / dfsu (grids / meshes)

Selections are expressed via Dfs2DTimeSeriesId/DfsuFeatureCollectionId etc., supporting:

  • Item: by name or number.
  • Request types:
    • Point selection:
      • Geo: Longitude, Latitude
      • XY: local/projected X,Y
    • Polygon selection:
      • PolygonGeo: arrays of lon/lat
      • PolygonXY: arrays of X,Y
    • Element (dfsu): integer element id; for 3D you can also pass LayerNumber.
  • Area statistic for polygons: Average | Maximum | Minimum.
  • Reprojection (dfs2 vector fields):
    • None
    • UV (rotate U/V and return one component)
    • SpeedDirection (apply rotation to direction, keep speed)

The concrete text format of the ID is handled by the …Id classes—construct them explicitly in code if you’re not calling the Web API parser.


Using each provider

1) Dfs2MapSource / DfsuMapSource (maps)

CRS restriction: map output must be requested in EPSG:3857.

var map = new Dfs2MapSource("C:\\data\\R20141001.dfs2");
var dates = map.GetDateTimes(null); // or pass sub-path/id when using a folder+prefix

var bmp = map.GetMap(
  style: mapStyle,
  crs: "EPSG:3857",
  boundingBox: new BoundingBox(minX, minY, maxX, maxY),
  width: 1024,
  height: 768,
  filePath: "",                  // for folder/prefix sources: "subdir/file.dfs2"
  dateTime: dates.Max,           // pick timestep
  item: "Current speed",         // or "1" by item number
  parameters: new Parameters {
    { "includevector", true },
    { "isoline", "None" },       // None | Major10 | Major5 | Major2
    { "vectorcolor", "#000000" },
    { "usevectorfixedlength", false },
    { "vectormaxlength", 10 },
    { "vectornumberhorizontal", 0 },
    { "scale", 1d },
    { "coloringtype", "DiscreteColoring" }, // or ContinuousColoring
    { "shadingtype", "ShadedContour" }      // or FlatShading, etc.
  }
);
  • Thresholds come from the MapStyle palette; we convert palette stops into (value, bandColor) for the contouring engine.
  • GetLayerInfo() returns layer bounds and projection from the file.

2) Dfs0TimeSeriesRepository (single file)

Read values:

var repo = new Dfs0TimeSeriesRepository("C:\\data\\gauge.dfs0");
foreach (var id in repo.GetIds()) { /* ids per item */ }

var maybe = repo.Get(id);
var ts = maybe.HasValue ? maybe.Value : null;   // TimeSeries<string,double>
var values = repo.GetValues(id).Value;          // ITimeSeriesData<double> (nulls for deletes)

Write/Update:

// SetValues overwrites the file with the single item’s values.
// If file axis is equidistant, incoming datetimes must be equidistant.
repo.SetValues(id, values);

// Add creates a new dfs0 (builder) when file doesn’t exist.
repo.Add(new TimeSeries<string,double>(id, "ItemName") { Data = values });

// Not implemented in this repo (throws): Remove/Update per item.
// Use Grouped repo for multi-item editing (below).

3) Dfs0GroupedTimeSeriesRepository (multiple files, grouped)

Treat each .dfs0 under a root as a group, and each item inside as a separate series.

var grp = new Dfs0GroupedTimeSeriesRepository("C:\\data\\ts-root");

// Add or update a series into the right file (merges items).
grp.Add(series);

// Bulk fetch values efficiently by grouping per file internally
var bag = grp.GetValues(new[]{ id1, id2, id3 }); // IDictionary<string, ITimeSeriesData<double>>

// Rename/update by replacing one item
grp.Update(series);

// Remove a series (rewrites the file without that item)
grp.Remove(id);

How it works: we copy the target file to a temp, compute the new set of items (read old + apply change), write the full set to temp using dfs0 builder, then save back through _fileSource.

4) Dfs2TimeSeriesRepository (extract time series from grids)

Create one time series from a cell or polygon:

  • Point (Geo/XY/Element) → we map to cell indices (j,k) accounting for grid spacing and true-north rotation.
  • Polygon (PolygonGeo/PolygonXY) → we collect all covered cells and aggregate per time step (Average/Min/Max).
  • Vector items → U/V reprojection is supported. We can:
    • return rotated U or V components (ReprojectType.UV), or
    • correct direction for SpeedDirection fields.
var repo = new Dfs2TimeSeriesRepository("C:\\data\\R20141001.dfs2");

var id = /* build Dfs2DTimeSeriesId for Item="U", RequestType=Geo, Lon/Lat, Reproject=UV with UItem="U", VItem="V" */;
var meta = repo.Get(id);                  // metadata shell (name)
var values = repo.GetValues(id).Value;    // ITimeSeriesData<double>
var times = repo.GetDateTimes(id).Value;  // SortedSet<DateTime>

5) DfsuTimeSeriesRepository (extract time series from meshes)

  • Select by element id, point (finds intersecting element), or polygon (multiple elements).
  • Supports 3D via LayerNumber (top layer is computed and offset).
  • Aggregation over multiple elements: Average | Min | Max.
var repo = new DfsuTimeSeriesRepository("C:\\data\\HD.dfsu");

var id = /* Dfs2DTimeSeriesId-like with Item="Water Level", RequestType=PolygonGeo, coords... */;
var ts = repo.Get(id).Value;              // series metadata
var vals = repo.GetValues(id).Value;      // values with nulls for delete
var tms  = repo.GetDateTimes(id).Value;

6) Dfs2FeatureRepository (cells as polygons with attributes)

Build a FeatureCollection<string> at a specific time:

  • Each grid cell becomes a polygon with attributes for each item’s value at that timestep.
  • Respects an optional BoundingBox filter.
var repo = new Dfs2FeatureRepository("C:\\data\\R20141001.dfs2");
var featureId = /* Dfs2FeatureCollectionId with File, DateTime, optional BoundingBox */;
var fc = repo.Get(featureId.ToString()).Value;

7) DfsuFeatureRepository (elements as polygons)

  • Returns polygon per mesh element.
  • You can request a specific item or all items (values attached as attributes).
  • Optional DateTime filter (otherwise 0).
var repo = new DfsuFeatureRepository("C:\\data\\HD.dfsu");
var featureId = /* DfsuFeatureCollectionId with File, optional Item, optional DateTime */;
var fc = repo.Get(featureId.ToString()).Value;

8) DfsuContouringFeatureRepository (contour polygons)

  • Generates contour polygons for a dfsu item/time step using DfsContouring.
  • Use ContourLevels in the ID to control bands.
  • Boundary elements get set to a floor value to close contours at the domain edge.
var repo = new DfsuContouringFeatureRepository("C:\\data\\HD.dfsu");
var featureId = /* DfsuFeatureCollectionId with Item, DateTime, ContourLevels=[…] */;
var contours = repo.Get(featureId.ToString()).Value; // features with "Value" attribute

9) Dfs2TimeStepServer (rasters per item/time)

  • Returns a Raster (values array + georef metadata) for a given item and DateTime.
  • Reprojects the envelope to geographic.
  • Replaces file delete values with 0 in the raster payload (you can post-process that to null).
var ts = new Dfs2TimeStepServer("C:\\data\\R20141001.dfs2");
var dtList = ts.GetDateTimes();
var items  = ts.GetItems();

var rasterMaybe = ts.Get(items.First().Id, dtList.First());
var raster = (Dfs2TimeStepServer.Raster)rasterMaybe.Value;
// raster.Size, raster.GeoCenter, raster.Metadata[…], raster.ToBitmap()

10) DfsuMeshRepository (mesh metadata, values & aggregations)

  • MeshInfo: items, full date range, bounding box, projection.
  • Point extraction: time series at a geo point (element intersection).
  • Polygon aggregation: Average/Min/Max over intersected elements. Average = weighted by area (or area intersection if polygon provided).
  • Batch polygon aggregation is parallelized.
var repo = new DfsuMeshRepository(new FileSource("C:\\data"));
foreach (var m in repo.GetAll()) { /* mesh files under root */ }

var id = "C:\\data\\HD.dfsu";
var dates = repo.GetDateTimes(id);

var p = new Spatial.Point(12.34, 55.67); // lon/lat
var dr = new DateRange(dates.First(), dates.Last());

var seriesAtPoint = repo.GetValues(id, p, dr);
var avgOverPoly   = repo.GetAggregatedValues(id, AggregationType.Average, "Water Level", dr);               // whole domain
var avgOverAOI    = repo.GetAggregatedValues(id, AggregationType.Average, "Water Level", myPolygon, dr);   // AOI

Writing dfs0 files – how it’s done

  • We always write through a temp file then stream it via _fileSource.Save(...).
  • For equidistant axes, we validate that incoming timestamps are equidistant.
  • For multi-item editing, use Dfs0GroupedTimeSeriesRepository, which:
    1. Reads all items,
    2. Applies your add/update/remove,
    3. Writes the complete item set back to temp,
    4. Saves the file.

Metadata defaults applied if missing when creating new files:

  • FileTitle, ApplicationTitle, ApplicationVersion
  • BuilderDataType, TimeAxis (defaults to CalendarNonEquidistant)
  • StatType (defaults to NoStat)

Performance notes

  • Sequential reads: Where possible we sort items by ItemNumber before reading to minimize file seeking (e.g., in dfs2 UV pairing).
  • Bulk extraction: For grouped dfs0 values, we batch per file.
  • dfsu polygon aggregation: we precompute weights (either element area or area overlap) and then read each time step once, applying weights for each polygon.
  • Parallelism: Some repo calls parallelize over ids (with Partitioner)—wrap callers in try/catch for AggregateException.

Swapping storage

All providers are built on IFileSource:

  • Local: new FileSource(rootFolder)
  • Cloud or remote: implement IFileSource with Exists, GetFilePaths, ReadDfs*, Save, Copy, Build, etc., and pass it into the constructors. Everything else stays the same.