Skip to content

DHI.Services.MIKECore for Time Series — Internal Developer Guide

This page is the developer-facing guide for consuming MIKE dfs0 / dfs2 / dfsu time series through the DHI.Services.TimeSeries stack. It explains the repository classes, how IDs work, how to read & write (where supported), performance/edge cases, JSON Connections, and copy-paste examples.

For MIKECore domain concepts (dfs formats, axes, EUM, projections), see MIKECore Providers. This page focuses on using them as time series providers.


What’s in the box

File type Repository Base type Read Write Grouped Notes
dfs0 (1D point series) Dfs0GroupedTimeSeriesRepository BaseGroupedUpdatableTimeSeriesRepository<string,double> One repo spans many .dfs0 files under a root; series are grouped by file path.
dfs0 (single file helper) Dfs0TimeSeriesRepository BaseUpdatableTimeSeriesRepository<string,double> For a single .dfs0 file. SetValues supports one item per file.
dfs2 (2D grid → series at location/area) Dfs2TimeSeriesRepository BaseTimeSeriesRepository<string,double> Reads a time series extracted from a dfs2 grid.
dfsu (unstructured grid → series at location/area) DfsuTimeSeriesRepository BaseTimeSeriesRepository<string,double> Supports point/area and (for 3D) LayerNumber.

IDs & addressing (super important)

dfs0 (grouped)

  • Repository: Dfs0GroupedTimeSeriesRepository
  • Series ID (what the repo expects)

    <relativeFilePath>;<itemName>
    

    Example: data/stations/test.dfs0;WaterLevel

  • FullName (what the object exposes) When you enumerate, you’ll get TimeSeries<string,double> with:

    • Name<itemName>
    • Group → directory/file group (derived from file path)
    • FullNameGroup + "/" + Name (e.g., data/stations/test.dfs0/WaterLevel)
  • Groups are path prefixes. Passing "data/stations" to GetByGroup filters all .dfs0 under that prefix.

Parsing helper used internally: Dfs0GroupedTimeSeriesId.Parse("path/file.dfs0;Item").

dfs0 (single file)

  • Repository: Dfs0TimeSeriesRepository
  • Item ID (inside a file): flexible formats accepted by Dfs0TimeSeriesId.Parse

    3                         // by item index (1-based)
    3(name:WaterLevel)        // by index + name
    3(name:WaterLevel)[eum:eumIWaterLevel] // add EUM item
    WaterLevel                // by name (if index not known)
    

DefaultLongId = true on this repo makes GetIds() include the [eum:...] part.

dfs2 / dfsu (extract a series from a gridded file)

  • Repository: Dfs2TimeSeriesRepository / DfsuTimeSeriesRepository
  • ID is a key=value list (order doesn’t matter). Required keys vary by mode:

    • By Element (dfs2 only): Item=<itemName>;ElementId=<cellIndex>
    • By XY: Item=<itemName>;X=<x>;Y=<y>[;FilePath=<file>][;LayerNumber=<n>]
    • By Lat/Lon: Item=<itemName>;Latitude=<lat>;Longitude=<lon>[;FilePath=<file>][;LayerNumber=<n>]
    • By polygon (area stat):
      • Geographic: Item=<item>;PolygonLngLat=lon1,lat1,lon2,lat2,...,lon1,lat1;AreaStatistic=Average|Maximum|Minimum
      • Projected: Item=<item>;PolygonXY=x1,y1,x2,y2,...,x1,y1;AreaStatistic=Average|Maximum|Minimum
    • Add file (if repo was built with a folder/prefix): FilePath=<relative-or-file> (use \ as | in JSON if needed; provider converts)
    • Vector reprojection to True North (dfs2 only):
      • From U/V pair: add UItem=<uName>;VItem=<vName> and set Item=<uName> or Item=<vName> (you’ll receive the chosen component rotated to True North/East).
      • For direction fields: ReprojectNorth=true and Item=<directionItem> (direction is rotated to True North).

Parser: Dfs2DTimeSeriesId. It validates you provided exactly one of: ElementId or (X,Y) or (Longitude,Latitude) or polygon(s). For 3D dfsu, add LayerNumber.


dfs0 — grouped repository

Construct

using DHI.Services.Provider.MIKECore;
using DHI.Services.TimeSeries;

// Option A: with a file source (implement IFileSource if you wrap Blob, S3, etc.)
IFileSource fs = new FileSource(@"C:\data");
var repo = new Dfs0GroupedTimeSeriesRepository(fs);

// Option B: with a root folder (uses FileSource under the hood)
var repo2 = new Dfs0GroupedTimeSeriesRepository(@"C:\data");

Discover & read

// List everything under root (recurses file paths)
foreach (var ts in repo.GetAll())
    Console.WriteLine(ts.FullName); // e.g., data/stations/a.dfs0/WaterLevel

// Narrow to a folder prefix
foreach (var ts in repo.GetByGroup(@"data\stations"))
    Console.WriteLine($"{ts.FullName}");

// Single series entity
var id = @"data\stations\a.dfs0;WaterLevel";
var entity = repo.Get(id).Value; // Name, Group, Metadata set (from dfs0 header)
Console.WriteLine($"{entity.Name} in {entity.Group}");

// Values
var values = repo.GetValues(id).Value;
Console.WriteLine($"Points: {values.Count}");

Add a series (new file or merge into existing)

var data = new TimeSeriesData<double>();
data.Append(new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0.12);
data.Append(new DateTime(2025, 1, 1, 1, 0, 0, DateTimeKind.Utc), 0.18);

var ts = new TimeSeries<string,double>(
    id:    @"data\stations\a.dfs0;WaterLevel", // IMPORTANT: id format is "file;item"
    name:  "WaterLevel",
    group: @"data\stations")                   // derived group; not used by the writer
{
    Unit     = "m",
    Quantity = "WaterLevel"
};

// If the file doesn't exist → creates it.
// If it exists → reads all existing items, unions with this item, and rewrites the file.
repo.Add(ts);

Update values

  • If the .dfs0 file contains only one itemSetValues will rewrite that file with your new time axis and values.
  • If the file has multiple items, call Update(TimeSeries) to rewrite the whole file replacing just that one item:
// Read existing → replace its Data → Update
var id = @"data\stations\a.dfs0;WaterLevel";
var existing = repo.Get(id).Value;
existing.Data = data; // new data
repo.Update(existing);

Remove(id) removes the item from the file; if it was the only item, it deletes the file.

Notes & limitations (dfs0)

  • SetValues on the single-file helper (Dfs0TimeSeriesRepository) throws if the file has more than one item. Use the grouped repo’s Update to rewrite a multi-item file.
  • If the target file is equidistant in time, your input datetimes must be equidistant (the writer verifies).
  • The writer uses temp files and IFileSource.Save for safe replace.
  • RemoveValues(...) is not implemented for dfs0 (both grouped & single file).
  • Batch GetValues(IEnumerable<string>) on the grouped repo returns a dictionary keyed by the inner item id (not file;item). Ensure you don’t pass duplicate item names across files, or prefer single-id reads.

dfs2 — extract time series from a 2D grid

Construct

using DHI.Services.Provider.MIKECore;

// Option A: direct file
var repo = new Dfs2TimeSeriesRepository(@"C:\data\R20141001.dfs2");

// Option B: file source + prefix (allows FilePath=... in the id)
IFileSource fs = new FileSource(@"C:\data");
var repo2 = new Dfs2TimeSeriesRepository(fs, filePathOrPrefix: null);

Read at a point (XY)

var id = "Item=WaterDepth;X=123.4;Y=567.8;FilePath=R20141001.dfs2";
var entity = repo.Get(id).Value;            // validates availability
var values = repo.GetValues(id).Value;      // full time series

Read at a point (Lat/Lon)

var id = "Item=WaterDepth;Latitude=55.676;Longitude=12.568";
var values = repo2.GetValues(id).Value; // uses repo2 prefix or FilePath= in the id

Area statistics over polygon (Average/Max/Min)

var id = "Item=SignificantWaveHeight;PolygonLngLat=12.0,55.0,12.2,55.0,12.2,55.2,12.0,55.2,12.0,55.0;AreaStatistic=Average";
var hs = repo2.GetValues(id).Value;

Reproject to True North (vectors & directions)

UV → TrueNorth/TrueEast component

// Assuming the grid stores U=East, V=North, but with local grid rotation
var idU = "Item=U;UItem=U;VItem=V;X=1234;Y=5678";
var uTrue = repo.GetValues(idU).Value; // U reprojected to True East/West

var idV = "Item=V;UItem=U;VItem=V;X=1234;Y=5678";
var vTrue = repo.GetValues(idV).Value; // V reprojected to True North/South

Direction → True North

var idDir = "Item=CurrentDirection;X=1234;Y=5678;ReprojectNorth=true";
var dirTrueNorth = repo.GetValues(idDir).Value; // direction corrected by local true north angle

Dates only (no values)

var t = repo.GetDateTimes("Item=WaterDepth;X=123.4;Y=567.8").Value;

Writes are not supported for dfs2.


dfsu — extract time series from an unstructured grid

Construct

var repo = new DfsuTimeSeriesRepository(@"C:\data\mesh.dfsu");  // file OR folder (concurrent)

Read at a point (XY or Lat/Lon)

var id = "Item=Salinity;Latitude=55.676;Longitude=12.568";
var sal = repo.GetValues(id).Value;

Area statistics using polygon

var id = "Item=Salinity;PolygonXY=700000.0,6175000.0,700500.0,6175000.0,700500.0,6175500.0,700000.0,6175500.0,700000.0,6175000.0;AreaStatistic=Maximum";
var maxSal = repo.GetValues(id).Value;

3D dfsu: specific layer

var id = "Item=Temperature;Latitude=55.676;Longitude=12.568;LayerNumber=3";
var t = repo.GetValues(id).Value;

Dates only

var dates = repo.GetDateTimes("Item=Temperature;X=123.4;Y=567.8").Value;

Writes are not supported for dfsu.


Metadata (dfs0)

When creating a new .dfs0 (single-file repo’s Add):

  • If your TimeSeries.Metadata doesn’t include them, the writer will set:

    • FileTitle = "Timeseries"
    • ApplicationTitle = "Timeseries"
    • ApplicationVersion = 10000
    • BuilderDataType = TimeAxisType.CalendarNonEquidistant
    • TimeAxis = TimeAxisType.CalendarNonEquidistant
    • StatType = StatType.NoStat

When reading, both grouped & single-file repos call SetMetadata(_fileSource, filePath, item) to populate your entity’s metadata from the file header.


Connections (Web API)

Drop a connections JSON like below to wire providers behind the DHI.Services.TimeSeries.WebApi layer.

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",

  "dfs0-grouped": {
    "$type": "DHI.Services.TimeSeries.WebApi.GroupedDiscreteTimeSeriesServiceConnection, DHI.Services.TimeSeries.WebApi",
    "ConnectionString": "[AppData]dfs0",
    "RepositoryType": "DHI.Services.Provider.MIKECore.Dfs0GroupedTimeSeriesRepository, DHI.Services.Provider.MIKECore",
    "Name": "MIKE dfs0 grouped time series connection",
    "Id": "dfs0-grouped"
  },

  "dfs0": {
    "$type": "DHI.Services.TimeSeries.WebApi.TimeSeriesServiceConnection, DHI.Services.TimeSeries.WebApi",
    "ConnectionString": "[AppData]dfs0",
    "RepositoryType": "DHI.Services.Provider.MIKECore.Dfs0TimeSeriesRepository, DHI.Services.Provider.MIKECore",
    "Name": "MIKE dfs0 time series connection",
    "Id": "dfs0"
  },

  "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"
  },

  "dfsu": {
    "$type": "DHI.Services.TimeSeries.WebApi.TimeSeriesServiceConnection, DHI.Services.TimeSeries.WebApi",
    "ConnectionString": "C:\\Data\\mesh.dfsu",
    "RepositoryType": "DHI.Services.Provider.MIKECore.DfsuTimeSeriesRepository, DHI.Services.Provider.MIKECore",
    "Name": "MIKE dfsu time series connection",
    "Id": "dfsu"
  }
}

Notes

  • GroupedDiscreteTimeSeriesServiceConnection for dfs0 grouped (many series, many files).
  • TimeSeriesServiceConnection for dfs2/dfsu (extract a single series per request).
  • ConnectionString:
    • For dfs0 we usually point to a root folder; [AppData]dfs0 is a typical app token mapping to e.g. %PROGRAMDATA%\<app>\dfs0.
    • For dfs2/dfsu pass an absolute path to a file (or a folder when the repo supports concurrent wrappers).

Once loaded, your HTTP surface looks like:

GET  /api/timeseries/dfs0/{encodedFullName}/values
GET  /api/timeseries/dfs2/{encodedId}/values
GET  /api/timeseries/dfsu/{encodedId}/values
POST /api/timeseries/dfs0               // add (dfs0 only)
PUT  /api/timeseries/dfs0/{encodedFullName}/values  // set/replace (dfs0)
DELETE /api/timeseries/dfs0/{encodedFullName}       // remove (dfs0)

URL encoding hints

  • dfs0 ids are "file;item". The Web API typically works with the entity’s FullName (file/item). If you expose ids directly, URL-encode the ;.
  • dfs2/dfsu ids are key=value;... strings. URL-encode them as a whole, e.g.:

    /api/timeseries/dfs2/Item%3DWaterDepth%3BLatitude%3D55.676%3BLongitude%3D12.568/values
    

Example: end-to-end (dfs0 via Web API)

# List names (implementation dependent: your API may expose a /fullnames endpoint)
curl "$BASE/api/timeseries/dfs0/fullnames?api-version=1"

# Create or merge a series into data/stations/a.dfs0
curl -X POST "$BASE/api/timeseries/dfs0?api-version=1" \
  -H "Content-Type: application/json" \
  -d '{
        "id"      : "data/stations/a.dfs0;WaterLevel",
        "name"    : "WaterLevel",
        "group"   : "data/stations",
        "quantity": "WaterLevel",
        "unit"    : "m",
        "data"    : [["2025-01-01T00:00:00Z",0.12],["2025-01-01T01:00:00Z",0.18]]
      }'

# Replace a series in a multi-item file (the server will call repo.Update(...))
curl -X PUT "$BASE/api/timeseries/dfs0/data%2Fstations%2Fa.dfs0%2FWaterLevel/values?api-version=1" \
  -H "Content-Type: application/json" \
  -d '{"dateTimes":["2025-01-01T00:00:00Z"],"values":[0.42]}'

# Get values
curl "$BASE/api/timeseries/dfs0/data%2Fstations%2Fa.dfs0%2FWaterLevel/values?api-version=1"

Example: dfs2 & dfsu via Web API

# dfs2 — point by lat/lon (direction corrected to true north)
curl "$BASE/api/timeseries/dfs2/Item%3DCurrentDirection%3BLatitude%3D55.676%3BLongitude%3D12.568%3BReprojectNorth%3Dtrue/values?api-version=1"

# dfs2 — polygon average (lng/lat ring closed)
curl "$BASE/api/timeseries/dfs2/Item%3DHS%3BPolygonLngLat%3D12.0%2C55.0%2C12.2%2C55.0%2C12.2%2C55.2%2C12.0%2C55.2%2C12.0%2C55.0%3BAreaStatistic%3DAverage/values?api-version=1"

# dfsu — point, 3D layer
curl "$BASE/api/timeseries/dfsu/Item%3DTemperature%3BLatitude%3D55.676%3BLongitude%3D12.568%3BLayerNumber%3D3/values?api-version=1"

Implementation behavior & tips

  • Concurrency & temp files Writers use temp files + IFileSource.Save for atomic-ish updates. ConcurrentFile.GetFilePath is honored in dfs2/dfsu constructors to safely open files that might be used elsewhere.

  • Delete values (missing data) When reading, delete values in dfs formats become null in returned ITimeSeriesData<double>.

  • Equidistant vs. non-equidistant If the target .dfs0 file has an equidistant axis and you call SetValues, your datetimes must be strictly equidistant or you’ll get an exception.

  • Multi-item .dfs0 Supported on read and add/update via the grouped repo (it rewrites the entire file). Not supported by the single-file repo’s SetValues. Use Update(TimeSeries) on the grouped repo to replace one item.

  • dfsu area selection For polygons, the provider finds intersecting elements; for 3D, it maps through FindTopLayerElements() and applies LayerNumber.

  • Vector reprojection (dfs2)

    • UItem/VItem + Item lets you request either component, reprojected to True North/East by first converting (U,V)→(speed,dir), subtracting local grid rotation, then converting back.
    • ReprojectNorth=true subtracts the local True North angle from the direction item’s values.

Quick reference

dfs0 (grouped)

  • ID: path\to\file.dfs0;ItemName
  • Create: Add(TimeSeries) (creates or merges)
  • Replace (multi-item): Update(TimeSeries) (rewrites file)
  • Delete: Remove(id) (removes item; deletes file if last)
  • Read: Get(id), GetValues(id), GetByGroup(pathPrefix), GetFullNames(pathPrefix)

dfs2

  • ID: Item=X;Latitude=..;Longitude=.. (or XY / Polygon / ElementId)
  • Read: Get(id), GetValues(id), GetDateTimes(id)
  • Write: ✗

dfsu

  • ID: Item=X;Latitude=..;Longitude=..[;LayerNumber=n] (or XY / Polygon)
  • Read: Get(id), GetValues(id), GetDateTimes(id)
  • Write: ✗