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:
- TimeSteps Core: see the DHI.Services.TimeSteps — Internal Developer Guide
- TimeSteps Web API: DHI.Services.TimeSteps.WebApi — Internal Developer Guide
- Rasters Core: DHI.Services.Rasters — Internal Developer Guide
- MIKE Core overview: DHI.Services.MIKECore – Internal Guide
- Working TimeSteps Host (DFS2): https://github.com/DHI/DomainServicesSamples/tree/main/Host/TimeSteps
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
}
filePathmay 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
IFileSourceif 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()returnsItem<string>whereId == Name == dfsItem.Name.- Item metadata adds:
dataType,valueTypequantity,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)Size—SKSizeI(nx, ny)PixelSize—SKSize(dx, dy)GeoProjectionString— DFS projection WKTGeoCenter— center of the bounding boxMetadata— same keys as onItemplusx0,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
DeleteValueFloatis mapped to 0 inValues. If your consumer needsNaNinstead, 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:
GetFirstAfterandGetLastBeforewill 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/Lastreturnnullwhen 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.
- Reading a raster at
- 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
/itemsto list item IDs and ensure you’re using the DFS item name exactly. - Strange extents on non-EqD axes: Current implementation returns local
x1/y1for 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
ClaimsPrincipalinGetItems/Getand 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.