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
SKBitmapvia an internalPostgresRasterBitmapProvider. - Uses DB-resident raster tiles; supports time windows.
- Returns
- 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
NotSupportedExceptionforGetMap,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>
dbflavourdefaults to PostgreSQL if omitted.workspaceresolves 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:
sourceId– unused (pass"").item– unused.- Required provider parameters (in
parameters):rasterid(string) → raster table/name to render.srs(string) → CRS whencrsargument 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
crsargument is empty, the provider usesparameters["srs"]. - Extent: The
BoundingBoxis converted into the provider’sMapRectangle(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
MapStyleargument 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()→ everyLayerwithGroup/Name,BoundingBox,CoordinateSystem, and richMetadata.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,GroupIdRasterType,DefaultSymbologyIsTemporal,EumType,EumUnit,TimeZoneUtcExchangeable,IsPublic(if present)Srid(fromraster_columns)Min,Max,BoundingBox(computed fromST_Envelope(rast);Min/Maxfromstat_min/stat_maxorST_SummaryStatsfallback)
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;nonrecursiveGET /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
crsin the service call (or WMS query), that wins. Otherwise setsrsinParameters. - Intersection fast-path: If your view doesn’t intersect the raster overview envelope,
GetMapreturns 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
MemoryStreamfrom PostGIS, thenSKBitmap.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.GroupedMapSourceGet(string id)→Maybe<Layer>GetAll(),GetByGroup(group),GetFullNames(group)(supports;nonrecursive,;groupsonly)GetMap(...)(PostgreSQL only) — requiresrasterid(+ optionalsrs,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=PostgreSQLand point to a PG instance with PostGIS rasters. - Null bitmap returned
View doesn’t intersect raster overview envelope, or
rasteridincorrect. Log your inputs and check raster presence. - Weird CRS/shift
Ensure your
crs/srsmatches the map client and base map. Confirm SRID inLayer.Metadata["Srid"]. - Missing min/max
Some rasters may lack
stat_min/stat_max; the provider falls back toST_SummaryStats. Consider precomputing stats. - Time filtering ignored
Check your table’s
date_timecolumn 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.