Skip to content

DHI.Services.MIKECore for TimeSteps – Internal Guide (DFS2)

Serve time-stepped raster data from MIKE Core DFS2 files through the TimeSteps abstractions. This guide explains what the provider does, how it maps DFS2 concepts to our domain, how to wire it (in-process and via Web API), and how to consume values and rasters.

Related docs:


1) What this provider does

Dfs2TimeStepServer implements ITimeStepServer<string, object> on top of a DFS2 grid file:

  • Exposes the global timeline (first/last and all timestamps).
  • Exposes items (one per DFS2 item), with enriched metadata.
  • Returns values at a (itemId, dateTime) pair as a raster object (see §3).
  • Implements neighbor queries (first-after / last-before).
  • Handles both equidistant and non-equidistant time axes.

Packages you’ll typically reference:

<PackageReference Include="DHI.Services.TimeSteps" Version="*" />
<PackageReference Include="DHI.Services.Provider.MIKECore" Version="*" />
<!-- For Web API hosting -->
<PackageReference Include="DHI.Services.TimeSteps.WebApi" Version="*" />

2) Type & constructor

public sealed class Dfs2TimeStepServer : BaseTimeStepServer<string, object>
{
    public Dfs2TimeStepServer(IFileSource fileSource, string filePath);
    public Dfs2TimeStepServer(string filePath); // convenience
}
  • filePath may include [AppData]... when you call .Resolve() yourself; the Web API connection variant resolves it for you.
  • The 2-arg ctor lets you bring your own IFileSource if the DFS2 lives behind a custom file abstraction.
  • The provider throws early if the file does not exist.

3) Data model & mapping to DFS2

3.1 Items

  • GetItems() returns Item<string> where Id == Name == dfsItem.Name.
  • Item metadata adds:
    • dataType, valueType
    • quantity, unit
    • Grid/extent: x0, y0, x1, y1, dx, dy, nx, ny
    • projection (WKT)

Item IDs are DFS item names. They must not contain / if you call the Web API (single path segment). URL-encode if needed.

3.2 Timeline

  • GetDateTimes() returns an ascending list of timestamps.
  • Equidistant axis: uses TimeAxis.GetDateTimes().
  • Non-equidistant axis: reads the time for each step by asking the file (ReadItemTimeStep(1, i)).

3.3 Values (as rasters)

Get(itemId, dateTime) returns a Maybe<object>. When present, the object is an instance of this nested class:

// Provider-projected raster (inherits from BaseRaster)
public sealed class Raster : BaseRaster
{
    public Raster(DateTime dateTime, string name, IList<float> values, 
                  IDictionary<string, object>? metadata = null, IList<Permission>? permissions = null)
        : base(dateTime, name, values, metadata, permissions) { }

    public override SKBitmap ToBitmap() { /* default gradient */ }
}

Fields the provider sets:

  • Values — row-major float grid (DFS2 delete value rewritten to 0)
  • SizeSKSizeI(nx, ny)
  • PixelSizeSKSize(dx, dy)
  • GeoProjectionString — DFS projection WKT
  • GeoCenter — center of the bounding box
  • Metadata — same keys as on Item plus x0,y0,x1,y1

Extent calculation: For EqD2 axes, (x0,y0) and (x1,y1) are computed in geographic coordinates using the DFS2 projection (via Cartography), then expanded by half a pixel on each side so the bounding box encloses cell edges.

For non-EqD axes the code returns local coordinates for x1,y1 (TODO in provider comment). Plan accordingly if you consume extent information.

Rendering: The default ToBitmap() maps Values linearly between MinValue/MaxValue to a blue gradient (SkiaSharp). Override/extend on the consumer side if you need color maps or symbology.

For the BaseRaster object model and conventions, see ../rasters/domain_services_rasters_core.md.


4) Usage patterns (in-process)

4.1 Minimal read

var server  = new Dfs2TimeStepServer(@"C:\data\R20141001.dfs2");
var service = new TimeStepService<string, object>(server);

// Timeline
var times = service.GetDateTimes();

// Items
var items = service.GetItems();
var itemId = items.First().Id;

// Exact value
var t0 = times[0];
var raster = (Dfs2TimeStepServer.Raster)service.Get(itemId, t0);

// Neighbor queries
var next   = (Dfs2TimeStepServer.Raster)service.GetFirstAfter(itemId, t0);
var prev   = (Dfs2TimeStepServer.Raster)service.GetLastBefore(itemId, times[2]);

4.2 Save a PNG preview

var r = (Dfs2TimeStepServer.Raster)service.Get(itemId, t0);
using var bmp = r.ToBitmap();
using var fs = File.Create(@"C:\out\preview.png");
bmp.Encode(SKEncodedImageFormat.Png, 90).SaveTo(fs);

4.3 Bulk sparse read

var ask = new Dictionary<string, IEnumerable<DateTime>>
{
    [itemId] = new [] { times[0], times[5], times[10] }
};
var matrix = service.Get(ask);
// matrix[itemId][times[5]] -> Raster

5) Host it over HTTP (Web API)

The TimeSteps Web API already exposes generic routes. You only need to register this provider under a {connectionId}.

5.1 Register imperatively

// At startup (after building the app)
ServiceLocator.Register(
  new TimeStepService<string, object>(
      new Dfs2TimeStepServer("[AppData]R20141001.dfs2".Resolve())),
  "dfs2" // -> used as {connectionId}
);

Then call:

GET /api/timesteps/dfs2/datetimes
GET /api/timesteps/dfs2/items
GET /api/timesteps/dfs2/{itemId}/data/{date}

For the full controller matrix and error semantics, see the TimeSteps.WebApi guide.

5.2 Register via Connections (config)

connections.json

{
  "dfs2": {
    "$type": "DHI.Services.TimeSteps.WebApi.TimeStepServiceConnection, DHI.Services.TimeSteps.WebApi",
    "ConnectionString": "[AppData]R20141001.dfs2",
    "ServerType": "DHI.Services.Provider.MIKECore.Dfs2TimeStepServer, DHI.Services.Provider.MIKECore",
    "Name": "Dfs2 timestep connection",
    "Id": "dfs2"
  }
}

The Web API’s TimeStepServiceConnection resolves [AppData] automatically and constructs Dfs2TimeStepServer(ConnectionString).


6) Behavior details & design notes

  • Delete values: DFS2’s DeleteValueFloat is mapped to 0 in Values. If your consumer needs NaN instead, convert on the client side (or fork/extend the provider).
  • Projection: For EqD2, upper-right corner is computed in projected coords then converted back to geographic using Cartography. We then add/subtract half a pixel so extents bound pixel edges.
  • Neighbor range checks: GetFirstAfter and GetLastBefore will throw if the query time is at/after the last or at/before the first timestamp respectively. The Web API maps these to 400. GetFirst/Last return null when the series is empty.
  • ClaimsPrincipal: The current provider does not inspect user; it’s a hook for future secured sources (multi-tenant stores etc.). Enforce authorization at the API layer.
  • Performance:
    • Reading a raster at (item, time) is O(grid) due to file IO and conversion; consider application-level caching for hot paths.
    • On non-equidistant axes, we read the time for each step from the file to obtain the timeline—fine for normal sizes; cache if massive.
  • Item identity: Item IDs are exact DFS2 item names (string match). Use service.GetItems() to discover valid IDs.

7) End-to-end example (console)

using DHI.Services.TimeSteps;
using DHI.Services.Provider.MIKECore;
using SkiaSharp;

var path = @"C:\data\R20141001.dfs2";
var svc = new TimeStepService<string, object>(new Dfs2TimeStepServer(path));

var items = svc.GetItems();
Console.WriteLine($"Items: {string.Join(", ", items.Select(i => i.Id))}");

var t0 = svc.GetFirstDateTime();
var raster = (Dfs2TimeStepServer.Raster)svc.Get(items[0].Id, t0.Value);

Console.WriteLine($"Grid: {raster.Size.Width}x{raster.Size.Height}, unit={raster.PixelValueUnit}");
Console.WriteLine($"Extent: [{raster.Metadata["x0"]},{raster.Metadata["y0"]}]..[{raster.Metadata["x1"]},{raster.Metadata["y1"]}]");

using var bmp = raster.ToBitmap();
using var fs = File.Create(@"C:\out\dfs2_first.png");
bmp.Encode(SKEncodedImageFormat.Png, 90).SaveTo(fs);

8) Web API quickstart (host)

If you don’t have a host yet, clone the sample:

Key bits from the sample Startup:

// [AppData] resolver base
AppDomain.CurrentDomain.SetData("DataDirectory",
    Path.Combine(env.ContentRootPath, "App_Data"));

// Register provider (imperative)
ServiceLocator.Register(
  new TimeStepService<string, object>(
    new Dfs2TimeStepServer("[AppData]R20141001.dfs2".Resolve())),
  "dfs2");

Then hit, for example:

GET /api/timesteps/dfs2/datetimes
GET /api/timesteps/dfs2/items
GET /api/timesteps/dfs2/{itemId}/data/{date}

(See the Web API guide for full endpoint list and JWT/Swagger wiring.)


9) Troubleshooting

  • “File ‘…’ does not exist.” The provider validates existence at construction. Check resolved path (especially with [AppData]), and file permissions.
  • 404 on exact GET (Web API): Timestamp not in the DFS timeline, or no value returned for that item at that time.
  • 400 on neighbor queries: The query time is outside the first/last range.
  • Item not found: Use /items to list item IDs and ensure you’re using the DFS item name exactly.
  • Strange extents on non-EqD axes: Current implementation returns local x1/y1 for non-EqD (see TODO in code). If you need geographic extents there, transform on the consumer side for now.
  • Delete values appear as 0: By design in this provider. Convert to NaN/mask in your visualization if needed.

10) Reference — public members used by the framework

// Timeline
IList<DateTime> GetDateTimes(ClaimsPrincipal user = null);
DateTime? GetFirstDateTime(ClaimsPrincipal user = null);
DateTime? GetLastDateTime(ClaimsPrincipal user = null);
Maybe<object> GetFirstAfter(string itemId, DateTime t, ClaimsPrincipal user = null);
Maybe<object> GetLastBefore(string itemId, DateTime t, ClaimsPrincipal user = null);

// Items
IEnumerable<Item<string>> GetItems(ClaimsPrincipal user = null);

// Values
Maybe<object> Get(string itemId, DateTime t, ClaimsPrincipal user = null);

// Bulk
IDictionary<string, IDictionary<DateTime, object>> 
    Get(IDictionary<string, IEnumerable<DateTime>> ids, ClaimsPrincipal user = null);

These are consumed by TimeStepService<string, object> and, transitively, by the Web API controller.


11) Going further

  • Add caching of rasters if your clients poll the same (item,time) frequently.
  • Add a color-map parameter on your client to control ToBitmap() rendering.
  • If you need per-item auth, inspect ClaimsPrincipal in GetItems/Get and filter accordingly.
  • For tiling or WMS-like output, combine this provider with the Rasters utilities from ../rasters/domain_services_rasters_core.md.

Appendix: Wiring recap

A) In-process only

var svc = new TimeStepService<string, object>(
    new Dfs2TimeStepServer("[AppData]R20141001.dfs2".Resolve()));

B) Web API + ServiceLocator

ServiceLocator.Register(
    new TimeStepService<string, object>(
        new Dfs2TimeStepServer("[AppData]R20141001.dfs2".Resolve())),
    "dfs2");

C) Web API + Connections

"dfs2": {
  "$type": "DHI.Services.TimeSteps.WebApi.TimeStepServiceConnection, DHI.Services.TimeSteps.WebApi",
  "ConnectionString": "[AppData]R20141001.dfs2",
  "ServerType": "DHI.Services.Provider.MIKECore.Dfs2TimeStepServer, DHI.Services.Provider.MIKECore",
  "Name": "Dfs2 timestep connection",
  "Id": "dfs2"
}

You’re set. Plug in your DFS2 file, register the provider, and your time-stepped rasters are available both in-process and over HTTP.