Skip to content

DHI.Services.MCLite for GIS Maps — Internal Developer Guide

This provider is group-aware (layers live inside groups), so you’ll work with GroupedMapService.

Deep-dive on MCLite’s domain model, DB schema, tables, and helper utilities lives here: MCLite Providers


1) What this provider gives you

1.1 GroupedMapSource : BaseGroupedMapSource

A catalog + rendering provider backed by MIKE Cloud Lite DB.

  • Catalog (groups & layers)
    • GetAll(), GetByGroup(group), Get(fullname), GetFullNames(group)
    • Group queries accept tree-options: ;nonrecursive, ;groupsonly, or both.
  • Raster rendering (PostgreSQL only)
    • Returns SKBitmap via an internal PostgresRasterBitmapProvider.
    • Uses DB-resident raster tiles; supports time windows.
  • Time axis (PostgreSQL only)
    • GetDateTimes(fullname)SortedSet<DateTime> (asc).
  • Streaming artifacts (PostgreSQL only)
    • GetStream(fullname) → ASCII grid (AAIGrid, text/plain; charset=us-ascii).

! DB support matrix:

  • PostgreSQL ✓ full support (render/time/stream/catalog)
  • SQL Server ✗ throws NotSupportedException for GetMap, GetDateTimes, GetStream
  • SQLite ✗ same as above Catalog browsing works across flavours, but map/time/stream require PostgreSQL.

2) Connection string & DB discovery

Construct the provider with a single connection string:

database=<db>;host=<host>;port=5432;user id=<user>;password=<pwd>;dbflavour=PostgreSQL;workspace=<workspace1>
  • dbflavour defaults to PostgreSQL if omitted.
  • workspace resolves to the schema (provider auto-discovers the actual schema name).
  • For SQLite/SQL Server, the provider can query the catalog, but render/time/stream will throw.

The provider internally normalizes:

  • Schema delimiter: . (PG/SQL Server) or _ (SQLite)
  • Parameter prefix: @
  • Builds an ADO.NET connection string appropriate for the flavour.

3) Rendering model & parameters

GroupedMapSource.GetMap(...) signature (via IGroupedMapSource → service):

SKBitmap GetMap(
  MapStyle style, string crs, BoundingBox bbox, int width, int height,
  string sourceId, DateTime? dateTime, string item, Parameters parameters);

In MCLite:

  • sourceIdunused (pass "").
  • itemunused.
  • Required provider parameters (in parameters):
    • rasterid (string) → raster table/name to render.
    • srs (string) → CRS when crs argument is empty.
  • Optional provider parameter:
    • time (string) → "start\end" (backslash separator). Either part may be omitted for open-ended filtering.

Other notable behaviors:

  • CRS: If crs argument is empty, the provider uses parameters["srs"].
  • Extent: The BoundingBox is converted into the provider’s MapRectangle (upper-left / lower-right order).
  • Early-out intersection: The provider checks the view-window vs a precomputed raster overview envelope (table *_overview). If there’s no intersection, it returns null early to save work. Intersection results are memoized in a small in-memory dictionary keyed by view + raster.

The MapStyle argument is accepted to comply with the core interface. Actual color mapping is handled inside the raster pipeline; the DB metadata can carry a default symbology. If your scenario needs explicit palette-driven coloring, apply it upstream or in the rendering pipeline that feeds this provider.


4) Group & layer catalog

You interact with the catalog through GroupedMapService:

  • GetAll() → every Layer with Group/Name, BoundingBox, CoordinateSystem, and rich Metadata.
  • GetByGroup("workspace1/raster") → layers in (nested) group.
  • Tree options on the group string:
    • ";nonrecursive" → only direct children.
    • ";groupsonly" → only subgroups as “fullnames”.
    • ";nonrecursive;groupsonly" → both flags.

Layer.Metadata (PG) includes:

  • Id, Name, TableName, GroupId
  • RasterType, DefaultSymbology
  • IsTemporal, EumType, EumUnit, TimeZoneUtc
  • Exchangeable, IsPublic (if present)
  • Srid (from raster_columns)
  • Min, Max, BoundingBox (computed from ST_Envelope(rast); Min/Max from stat_min/stat_max or ST_SummaryStats fallback)

5) Using it from Core (GroupedMapService)

5.1 Programmatic setup

using DHI.Services.GIS.Maps;
using DHI.Services.Provider.MCLite;
using SkiaSharp;
using Spatial;

// 1) Build the provider (PostgreSQL)
var cs = "database=hydrodb;host=10.0.0.5;port=5432;user id=postgres;password=secret;dbflavour=PostgreSQL;workspace=workspace1";
var groupedSource = new GroupedMapSource(cs);

// 2) Optional style service (only used for legends or other providers needing MapStyle)
// Here we skip and pass null, which means `style` strings are treated as StyleCodes by MapService.
// For GroupedMapService, you can still inject a MapStyleService if you also use legends.
MapStyleService styleSvc = null;

// 3) Group-aware service
var groupedSvc = new GroupedMapService(groupedSource, styleSvc);

5.2 List layers and groups

// All layers
var layers = groupedSvc.GetAll();
foreach (var l in layers)
  Console.WriteLine($"{l.Group}/{l.Name}  bbox:{l.BoundingBox}  srid:{l.Metadata["Srid"]}");

// Fullnames just under group (non-recursive)
var names = groupedSvc.GetFullNames("workspace1/raster;nonrecursive");

5.3 Render a map (PostgreSQL)

var bbox = BoundingBox.Parse("1110000,6400000,1130000,6420000"); // CRS must match your base map
var p = new Parameters {
  { "srs",      "EPSG:3857" },      // used if `crs` arg is empty
  { "rasterid", "RainGrid_1h" },    // table/raster name in DB
  { "time",     "2024-04-01T00:00:00Z\\2024-04-02T00:00:00Z" } // optional time window
};

using var bmp = groupedSvc.GetMap(
  style: "rain",          // id if style service is present, else treated as StyleCode (not required for MCLite’s pipeline)
  crs: "EPSG:3857",
  boundingBox: bbox,
  width: 1280,
  height: 720,
  sourceId: "",           // unused
  dateTime: null,         // use `time` window instead
  item: "",               // unused
  parameters: p);

if (bmp != null)
{
  using var fs = File.OpenWrite("mclite-map.png");
  bmp.Encode(SKEncodedImageFormat.Png, 95).SaveTo(fs);
}

5.4 Time axis (PostgreSQL)

// Fullname uses the same encoding you get from GetAll/GetFullNames
var times = groupedSvc.GetDateTimes("workspace1/raster/RainGrid_1h");
// ...filter, then call GetMap with a corresponding "time" window in Parameters if you prefer ranges.

5.5 Download an ASCII grid (PostgreSQL)

var (maybeStream, contentType, fileName) = groupedSvc.GetStream("workspace1/raster/RainGrid_1h");
if (maybeStream.HasValue)
{
  using var s = maybeStream.Value;
  using var outFs = File.Create(fileName);
  s.CopyTo(outFs);
}

If the layer does not exist, you’ll get an empty Maybe and an empty filename/type. For Postgres rasters, the stream contains ST_AsGDALRaster(ST_Transform(rast,4326),'AAIGrid').


6) Wiring it up

A) Using the Connections module (WebApi hosts)

Add this entry to connections.json:

"mclite": {
  "$type": "DHI.Services.GIS.WebApi.GroupedMapServiceConnection, DHI.Services.GIS.WebApi",
  "MapSourceConnectionString": "database=…;host=…;port=5432;user id=…;password=…;dbflavour=PostgreSQL",
  "MapSourceType": "DHI.Services.Provider.MCLite.GroupedMapSource, DHI.Services.Provider.MCLite",
  "Name": "MCLite (PG)",
  "Id": "mclite"
}

What you get via WebApi:

  • WMS-like map endpoint:
GET /api/maps?request=GetMap&service=wms&version=1.3.0
    &width=1024&height=768
    &styles=rain                           // resolved if style repo is wired in the host
    &layers=mclite                         // connection id
    &crs=EPSG:3857
    &bbox=1110000,6400000,1130000,6420000
    &rasterid=RainGrid_1h                  // provider-specific (forwarded to Parameters)
    &time=2024-04-01T00:00:00Z\2024-04-01T06:00:00Z

Provider-specific keys (anything not reserved by the controller) are forwarded to the provider’s Parameters. For MCLite you’ll typically pass rasterid, optionally time, and either crs query or srs (if you want the provider to derive CRS).

  • Grouped catalog endpoints (because this is a grouped connection):

  • GET /api/maps/mclite/layers?group=workspace1/raster

  • GET /api/maps/mclite/layers/fullnames?group=workspace1/raster;nonrecursive
  • GET /api/maps/mclite/layers/{fullname}
  • GET /api/maps/mclite/layers/{fullname}/stream/ascii (ASCII grid)

  • Time axis:

  • GET /api/maps/mclite/datetimes/{fullname}

The WebApi enforces the same Postgres-only constraints at runtime (non-PG calls will translate to 4xx/5xx with the provider’s NotSupportedException).

B) Programmatic registration

using DHI.Services;
using DHI.Services.GIS.Maps;
using DHI.Services.Provider.MCLite;

// Connection string to your MCLite (PostgreSQL) workspace
var cs = "database=hydrodb;host=10.0.0.5;port=5432;user id=postgres;password=secret;dbflavour=PostgreSQL;workspace=workspace1";

// Optional style service (null is fine)
MapStyleService styleSvc = null;

// Register the grouped service for dependency-less consumption
ServiceLocator.Register(
  new GroupedMapService(new GroupedMapSource(cs), styleSvc),
  "mclite");

7) Query parameter cheat-sheet (MCLite provider)

Key Type Required Applies to Meaning
rasterid string Yes GetMap Raster table/name in Postgres to render
srs string No GetMap CRS used only if crs arg is empty (EPSG:XXXX)
time string No GetMap Backslash-separated time range: "start\end". Either part can be omitted (open-ended range)

Reserved by controller (not forwarded): request,service,version,width,height,styles,layers,crs,bbox,item,timestamp,filepath Everything else is forwarded to Parameters.


8) Behavioral notes

  • PostgreSQL only for render/time/stream: SQLite and SQL Server will throw with clear error messages.
  • CRS handling: If you provide crs in the service call (or WMS query), that wins. Otherwise set srs in Parameters.
  • Intersection fast-path: If your view doesn’t intersect the raster overview envelope, GetMap returns null (caller should handle it).
  • Concurrency: The raster provider uses internal monitors (transformMonitor, pyramidMonitor) to guard transformations/pyramid generation; calls are safe from basic races.
  • Large rasters: The provider constructs a MemoryStream from PostGIS, then SKBitmap.Decode(...). Always dispose your bitmaps promptly.
  • Metadata richness: You can surface min/max, SRID, and true extents directly from Layer.Metadata; use it to drive dynamic legends and scale bars.

9) End-to-end examples (WebApi)

Map image (curl)

curl -G "https://host/api/maps" \
  --data-urlencode "request=GetMap" \
  --data-urlencode "service=wms" \
  --data-urlencode "version=1.3.0" \
  --data-urlencode "width=1280" \
  --data-urlencode "height=720" \
  --data-urlencode "styles=rain" \
  --data-urlencode "layers=mclite" \
  --data-urlencode "crs=EPSG:3857" \
  --data-urlencode "bbox=1110000,6400000,1130000,6420000" \
  --data-urlencode "rasterid=RainGrid_1h" \
  --data-urlencode "time=2024-04-01T00:00:00Z\\2024-04-01T06:00:00Z" \
  --output mclite.png

List fullnames (non-recursive)

GET /api/maps/mclite/layers/fullnames?group=workspace1/raster;nonrecursive

Download ASCII grid

GET /api/maps/mclite/layers/workspace1/raster/RainGrid_1h/stream/ascii

Available times

GET /api/maps/mclite/datetimes/workspace1/raster/RainGrid_1h

10) Reference: members you’ll actually touch

  • DHI.Services.Provider.MCLite.GroupedMapSource
    • Get(string id)Maybe<Layer>
    • GetAll(), GetByGroup(group), GetFullNames(group) (supports ;nonrecursive, ;groupsonly)
    • GetMap(...) (PostgreSQL only) — requires rasterid (+ optional srs, time)
    • GetDateTimes(fullname) (PostgreSQL only)
    • GetStream(fullname) → ASCII grid (PostgreSQL only)
  • DHI.Services.GIS.Maps.GroupedMapService
    • Thin facade you call from app/WebApi; forwards to the provider and (optionally) resolves styles.

11) Troubleshooting

  • “GetMap is not supported for SQLServer/SQLite” You’re not on PostgreSQL. Switch dbflavour=PostgreSQL and point to a PG instance with PostGIS rasters.
  • Null bitmap returned View doesn’t intersect raster overview envelope, or rasterid incorrect. Log your inputs and check raster presence.
  • Weird CRS/shift Ensure your crs/srs matches the map client and base map. Confirm SRID in Layer.Metadata["Srid"].
  • Missing min/max Some rasters may lack stat_min/stat_max; the provider falls back to ST_SummaryStats. Consider precomputing stats.
  • Time filtering ignored Check your table’s date_time column and the "start\end" string; either side may be omitted but format must be ISO.

12) Copy-paste snippets

Connections (WebApi) — grouped MCLite

"mclite": {
  "$type": "DHI.Services.GIS.WebApi.GroupedMapServiceConnection, DHI.Services.GIS.WebApi",
  "MapSourceConnectionString": "database=…;host=…;port=5432;user id=…;password=…;dbflavour=PostgreSQL",
  "MapSourceType": "DHI.Services.Provider.MCLite.GroupedMapSource, DHI.Services.Provider.MCLite",
  "Name": "MCLite (PG)",
  "Id": "mclite"
}

Programmatic registration

var cs = "database=hydrodb;host=10.0.0.5;port=5432;user id=postgres;password=secret;dbflavour=PostgreSQL;workspace=workspace1";
ServiceLocator.Register(
  new DHI.Services.GIS.Maps.GroupedMapService(
    new DHI.Services.Provider.MCLite.GroupedMapSource(cs),
    mapStyleService: null),
  "mclite");

With this provider in place, you can browse layer catalogs from MCLite, render rasters directly out of PostGIS (with time filtering), and expose it all over the WebApi using either connections.json or programmatic wiring.