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.TimeSeriesstack. 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)FullName→Group + "/" + Name(e.g.,data/stations/test.dfs0/WaterLevel)
- Groups are path prefixes. Passing
"data/stations"toGetByGroupfilters all.dfs0under 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.Parse3 // 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 = trueon this repo makesGetIds()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
- Geographic:
- 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 setItem=<uName>orItem=<vName>(you’ll receive the chosen component rotated to True North/East). - For direction fields:
ReprojectNorth=trueandItem=<directionItem>(direction is rotated to True North).
- From U/V pair: add
- By Element (dfs2 only):
Parser:
Dfs2DTimeSeriesId. It validates you provided exactly one of:ElementIdor (X,Y) or (Longitude,Latitude) or polygon(s). For 3D dfsu, addLayerNumber.
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
.dfs0file contains only one item →SetValueswill 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)¶
SetValueson the single-file helper (Dfs0TimeSeriesRepository) throws if the file has more than one item. Use the grouped repo’sUpdateto 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.Savefor 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 (notfile;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.Metadatadoesn’t include them, the writer will set:FileTitle="Timeseries"ApplicationTitle="Timeseries"ApplicationVersion=10000BuilderDataType=TimeAxisType.CalendarNonEquidistantTimeAxis=TimeAxisType.CalendarNonEquidistantStatType=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
GroupedDiscreteTimeSeriesServiceConnectionfor dfs0 grouped (many series, many files).TimeSeriesServiceConnectionfor dfs2/dfsu (extract a single series per request).ConnectionString:- For dfs0 we usually point to a root folder;
[AppData]dfs0is 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).
- For dfs0 we usually point to a root folder;
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.Savefor atomic-ish updates.ConcurrentFile.GetFilePathis 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
nullin returnedITimeSeriesData<double>. -
Equidistant vs. non-equidistant If the target
.dfs0file has an equidistant axis and you callSetValues, your datetimes must be strictly equidistant or you’ll get an exception. -
Multi-item
.dfs0Supported on read and add/update via the grouped repo (it rewrites the entire file). Not supported by the single-file repo’sSetValues. UseUpdate(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 appliesLayerNumber. -
Vector reprojection (dfs2)
UItem/VItem+Itemlets you request either component, reprojected to True North/East by first converting (U,V)→(speed,dir), subtracting local grid rotation, then converting back.ReprojectNorth=truesubtracts 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: ✗