DHI.Services.PostgreSQL for GIS MapStyle — Internal Developer Guide¶
This document is for DHI developers who want to store and manage map styles in PostgreSQL and use them across the GIS Maps stack (e.g., MIKECore/DFS2-DFSU maps, GeoServer WMS, custom map sources).
For deeper provider details and shared infrastructure, see PostgreSQL Providers.
1) What this provider is¶
DHI.Services.Provider.PostgreSQL.MapStyleRepository is a drop-in implementation of IMapStyleRepository backed by PostgreSQL. It’s intentionally format-agnostic:
- For MIKECore sources,
MapStyle.StyleCodetypically contains palette/threshold definitions used by the contouring pipeline. - For GeoServer WMS,
StyleCodecan hold SLD XML (or a zipped SLD) if you choose to store it centrally. - For any custom source,
StyleCodeis an opaque text blob you interpret in your renderer.
Why use it¶
- Centralize styles (edit once, every renderer updates).
- Share the same IDs across services (WebApi + background jobs).
- Built-in table bootstrap on start (creates table if missing).
- Simple CRUD with minimal coupling.
2) Data model & bootstrap¶
At startup, the repository ensures the (default) table exists:
CREATE TABLE IF NOT EXISTS public.map_style (
id varchar(255) PRIMARY KEY,
name varchar(255),
style_code text
);
You can override the table (and/or schema) via the connection string using the Table= addition (details below).
The bootstrap does not create non-
publicschemas. If you point togis.map_style, make suregisexists.
3) Repository API (what you can do)¶
MapStyleRepository implements IMapStyleRepository:
| Method | Description |
|---|---|
Add(MapStyle) |
Insert a new style (id, name, style_code). |
Contains(id) |
SELECT EXISTS… for a quick existence check. |
Get(id) |
Fetch a single style (id, name, style_code). |
GetAll() |
Enumerate all styles. |
GetIds() |
Enumerate all IDs only. |
Remove(id) |
Delete by id. |
Update(MapStyle) |
Update name and style_code by id. |
Count() |
Count rows. |
Internally it uses Npgsql and is intentionally synchronous to keep surface area small in web hosts.
4) StyleCode conventions (consumer-driven)¶
The repository does not enforce a format. Recommended patterns:
4.1 MIKECore palettes (example)¶
Used by Dfs2MapSource / DfsuMapSource:
0~10:#0000FF,#00FFFF,#00FF00,#FFFF00,#FF0000
- Left side defines the range (
0~10). - Right side are band colors (discrete) in order.
- Optionally keep JSON alongside if you prefer a richer schema.
4.2 GeoServer SLD (example)¶
When used for WMS styling:
<?xml version="1.0" encoding="UTF-8"?>
<sld:StyledLayerDescriptor …>
<!-- your rules, color maps, etc. -->
</sld:StyledLayerDescriptor>
Your GeoServer map source may still upload styles through GeoServer’s REST API; this repo is just a neutral store if you want one source of truth.
5) Connection strings & options¶
Minimum:
Server=localhost;Port=5432;Database=ProviderTest;User Id=postgres;Password=your_pw
Optional table override:
Server=…;Database=…;User Id=…;Password=…;Table=gis.map_style
- If
Table=is present, the repo will use that instead of the defaultpublic.map_style. - The bootstrap checks for table existence and creates it in that schema when missing.
Security/ops (recommended):
- Use pooled connections (Npgsql default).
- Prefer a least-privilege role limited to
SELECT/INSERT/UPDATE/DELETEon the map_style table. - Use SSL in production (
Ssl Mode=Require;Trust Server Certificate=falseif properly CA-signed).
6) Wiring it up¶
You can integrate the PostgreSQL MapStyle repository via connections.json (WebApi hosts) or programmatically.
A) Using the Connections module (WebApi hosts)¶
Add this to connections.json (as requested):
"dfs2-map-postgresql": {
"$type": "DHI.Services.GIS.WebApi.MapServiceConnection, DHI.Services.GIS.WebApi",
"MapSourceConnectionString": "[AppData]dfs2\\R20141001.dfs2",
"MapSourceType": "DHI.Services.Provider.MIKECore.Dfs2MapSource, DHI.Services.Provider.MIKECore",
"MapStyleConnectionString": "Server=localhost;Port=5432;Database=ProviderTest;User Id=postgres;Password=Solutions!",
"MapStyleRepositoryType": "DHI.Services.Provider.PostgreSQL.MapStyleRepository, DHI.Services.Provider.PostgreSQL",
"Name": "DFS2 with PostgreSQL MapStyle",
"Id": "dfs2-map-postgresql"
}
That gives you a MapService where:
- Source = MIKECore DFS2 (
Dfs2MapSource) - Style repo = PostgreSQL (
MapStyleRepository)
You can now request maps by style id stored in PG:
GET /api/maps/dfs2-map-postgresql?request=GetMap&width=1280&height=720
&styles=rain // <-- style id in PG
&crs=EPSG:3857
&bbox=1110000,6400000,1130000,6420000
&item=Water%20Level
×tamp=2014-10-01T10:00:00Z
B) Programmatic registration¶
using DHI.Services;
using DHI.Services.GIS.Maps;
using DHI.Services.Provider.MIKECore;
using DHI.Services.Provider.PostgreSQL;
// Map source: MIKECore DFS2
var dfs2Path = Path.Combine(appData, "dfs2", "R20141001.dfs2");
var mapSource = new Dfs2MapSource(dfs2Path);
// Map styles: PostgreSQL
var pg = "Server=localhost;Port=5432;Database=ProviderTest;User Id=postgres;Password=Solutions!";
// Optional: override table (schema must exist)
// pg += ";Table=gis.map_style";
var styleRepo = new DHI.Services.Provider.PostgreSQL.MapStyleRepository(pg);
var styleSvc = new MapStyleService(styleRepo);
// Register a MapService under a key
ServiceLocator.Register(new MapService(mapSource, styleSvc), "dfs2-map-postgresql");
// Example usage later:
var bbox = BoundingBox.Parse("1110000,6400000,1130000,6420000");
using var img = ServiceLocator.Resolve<MapService>("dfs2-map-postgresql").GetMap(
style: "rain", // style id stored in PostgreSQL
crs: "EPSG:3857",
boundingBox: bbox, width: 1280, height: 720,
sourceId: "", // ctor already fixed the file
dateTime: new DateTime(2014,10,1,10,0,0, DateTimeKind.Utc),
item: "Water Level",
parameters: Parameters.Empty);
7) Seeding & managing styles¶
You can seed styles either via SQL or through the repo API.
7.1 SQL¶
INSERT INTO public.map_style (id, name, style_code)
VALUES
('rain', 'Rainfall (0–10mm, discrete)',
'0~10:#0000FF,#00FFFF,#00FF00,#FFFF00,#FF0000');
7.2 Repository (C#)¶
var repo = new DHI.Services.Provider.PostgreSQL.MapStyleRepository(pgConnString);
if (!repo.Contains("rain"))
{
repo.Add(new MapStyle("rain", "Rainfall (0–10mm, discrete)",
"0~10:#0000FF,#00FFFF,#00FF00,#FFFF00,#FF0000"));
}
var style = repo.Get("rain").OrDefault();
repo.Update(new MapStyle(style.Id, "Rainfall (v2)",
"0~20:#0000FF,#00BFFF,#00FFFF,#00FF7F,#7CFC00,#FFFF00,#FFA500,#FF0000"));
8) End-to-end example with MIKECore¶
var pg = "Server=localhost;Port=5432;Database=ProviderTest;User Id=postgres;Password=Solutions!";
var styleSvc = new MapStyleService(new DHI.Services.Provider.PostgreSQL.MapStyleRepository(pg));
var svc = new MapService(new Dfs2MapSource(@"C:\data\dfs2\R20141001.dfs2"), styleSvc);
// Render using a style stored in PG
var bbox = BoundingBox.Parse("1110000,6400000,1130000,6420000");
using var bmp = svc.GetMap(
style: "rain", crs: "EPSG:3857",
boundingBox: bbox, width: 1024, height: 768,
sourceId: "", dateTime: null, item: "3",
parameters: new Parameters { { "isoline", "Contour" } });
using var fs = File.OpenWrite("dfs2-postgres-style.png");
bmp.Encode(SKEncodedImageFormat.Png, 90).SaveTo(fs);
9) Troubleshooting¶
- Schema not found: If you set
Table=gis.map_style, ensureCREATE SCHEMA gis;exists. The bootstrap creates the table, not the schema. - Permissions: Use a role with DML on the table, not superuser.
- Migrations: The table shape is small and stable. If you need additional fields, prefer a new table (
Table=override) to avoid breaking existing services. - Connection pooling: Npgsql pools by default; keep your repository instances long-lived (scoped/singleton in DI).
- Large SLDs:
style_codeisTEXT; it’s safe for large SLDs or JSON payloads.
10) What you’ll actually touch¶
DHI.Services.Provider.PostgreSQL.MapStyleRepository- Ctors:
(connectionString)or(connectionString, ILogger) - Optional:
;Table=<schema>.<table>in connection string
- Ctors:
MapServicewiring:- Any
IMapSource(e.g., MIKECore DFS2/DFSU) - This style repo for centralized styles
- Any
With the PostgreSQL MapStyle repository in place, you can standardize styles across all map sources, swap style backends without touching renderers, and manage styles centrally via SQL or the repository API—while the rest of the GIS Maps pipeline remains unchanged.