Places Providers – Guide & Recipes¶
Use this to pick a Places provider, wire it in minutes, and avoid the common foot-guns.
See also (deep dives)¶
- Places MCLite (schema, mapping, behaviors)
- Places PostgreSQL (DDL, overrides, queries)
- Places Web API (routes, DTOs, status/legend endpoints)
TL;DR – which provider?¶
| Provider | Best when | Storage | Creates tables? | Notable |
|---|---|---|---|---|
| MCLite PlaceRepository | You already run MCLite and want Places to live alongside your GIS and time series | PostgreSQL / SQL Server / SQLite via MCLite | No — schema must pre-exist; workspace resolved via master.workspace |
Styles & time intervals are normalized per collection; scalar indicators are skipped; GIS feature validation required |
| PostgreSQL PlaceRepository | You want a lean, self-contained PostgreSQL backend with minimal ceremony | PostgreSQL | Yes — auto-bootstrap places + indicators (or your overrides) |
Simple schema, ON DELETE CASCADE for indicators; group regex filtering; table override via ;Table=… |
IDs, groups, and dependencies¶
- Place identity: in the Places domain we use a FullName (e.g.,
Stations/North/ID92_M16). - Groups are hierarchical:
GetByGroup("Stations")includesStations/*recursively. - GIS dependency (both providers): every Place points at a GIS feature (collection + attribute key/value). You must have a GIS repository wired.
- Time series dependency (both): indicators with
DataSourceType.TimeSeriesrequire at least one time series service.
MCLite specifics
- Requires a workspace → schema mapping in
master.workspace.- Palettes + time intervals are de-duplicated at the place-collection level; reads expand indicators per interval (auto-suffixing names).
5-minute wiring¶
A) MCLite backend (recommended when you already use MCLite)¶
// 1) GIS (MCLite) – required
ServiceLocator.Register(
new DHI.Services.GIS.GisService<string>(
new DHI.Services.Provider.MCLite.FeatureRepository("database=mc;workspace=ws2")), "feature-mclite");
// 2) Time series – swap CSV for your real repo
ServiceLocator.Register(
new DHI.Services.TimeSeries.DiscreteTimeSeriesService<string,double>(
new DHI.Services.TimeSeries.CSV.TimeSeriesRepository("[AppData]")), "csv");
// 3) Places (MCLite)
ServiceLocator.Register(
new DHI.Services.Places.PlaceService(
new DHI.Services.Provider.MCLite.PlaceRepository("database=mc;workspace=ws2"),
new() { ["csv"] = Services.Get<IDiscreteTimeSeriesService<string,double>>("csv") },
new(), // scalars (optional)
Services.Get<IGisService<string>>("feature-mclite")),
"places-mclite"); // ← connectionId
Web API (if you host it) → /api/places/places-mclite/...
DDL: MCLite does not create tables. Ensure
place_*tables +entity_type/description/metadataand workspace mapping exist.
B) PostgreSQL backend (lean & self-contained)¶
// Dependencies (GIS + TS) – keep or swap as needed
ServiceLocator.Register(new DHI.Services.GIS.GisService<string>(
new DHI.Services.Provider.ShapeFile.FeatureRepository("[AppData]shp")), "shp");
ServiceLocator.Register(new DHI.Services.TimeSeries.DiscreteTimeSeriesService<string,double>(
new DHI.Services.TimeSeries.CSV.TimeSeriesRepository("[AppData]")), "csv");
// Places (PostgreSQL)
var pg = "Server=localhost;Port=5432;Database=ProviderTest;User Id=postgres;Password=***"
// optional table overrides (order matters: places, indicators):
// + ";Table=gis.places_v2;Table=gis.indicators_v2"
;
ServiceLocator.Register(
new DHI.Services.Places.PlaceService(
new DHI.Services.Provider.PostgreSQL.PlaceRepository(pg),
new() { ["csv"] = Services.Get<IDiscreteTimeSeriesService<string,double>>("csv") },
new(),
Services.Get<IGisService<string>>("shp")),
"postgresql");
Web API → /api/places/postgresql/... (tables auto-created if missing)
The 6 calls you’ll actually use¶
// 1) Add a place with indicators
placeService.Add(place);
// 2) Update a place (both providers do delete+reinsert semantics)
placeService.Update(place);
// 3) Get one / many
var one = placeService.Get("Stations/North/ID92_M16");
var many = placeService.GetByGroup("Stations"); // recursive
// 4) Bulk status by indicator type (service convenience)
var statuses = placeService.GetIndicatorStatusByType("Rainfall", "Stations", DateTime.UtcNow);
// 5) List ids / full names (discovery)
var ids = placeService.GetIds();
var full = placeService.GetFullNames("Stations");
// 6) Remove
placeService.Remove("Stations/North/ID92_M16");
Minimal add example (works on both providers)¶
var featureId = new FeatureId(
featureCollectionId: "Stationer.shp",
attributeKey: "StatId",
attributeValue: "ID92_M16");
var place = new Place(
id: "Stations/North/ID92_M16",
name: "ID92_M16",
featureId: featureId,
group: "Stations/North");
// Time-series indicator with palette + time window
place.Indicators.Add("Rainfall", new Indicator(
dataSource: new DataSource(DataSourceType.TimeSeries, "csv", "timeseries.csv;Rain"),
styleCode: "0~15:#800080,#5500AB,#2A00D5,#0000FF",
timeInterval: TimeInterval.CreateRelativeToDateTime(-1, 0), // last day
aggregationType: DHI.Services.TimeSeries.AggregationType.Sum));
placeService.Add(place);
Behavior notes you’ll want to know¶
Common
- Update is implemented as remove + add; plan ids accordingly.
- Group filters are hierarchical; many hosts support
;nonrecursivesuffix for exact-level queries.
MCLite
- Schema must exist (no migrations). Workspace is resolved via
master.workspace. - GIS feature validation is enforced (feature class + attribute key/value must match a GIS feature).
- Intervals & styles are de-duplicated per collection; reads expand an indicator per interval (auto
-1,-2suffixes). - Scalars are ignored on write (time-series–based indicators only).
PostgreSQL
- Auto-creates
public.places&public.indicators; ON DELETE CASCADE from places → indicators. - Group filtering uses a regex anchored at start (case-sensitive by default) on
groupname. TimeIntervalencoding:All(start/endNULL),Fixed(OLE Automation dates instart/end),Relative*(day offsets).- Table override: append
;Table=myschema.places;Table=myschema.indicatorsto the connection string.
Foot-guns & troubleshooting¶
- “No feature is found.” → Check GIS connection +
FeatureId(collection, key, value). - Intervals multiply indicators (MCLite) → expected; names are auto-suffixed to avoid collisions.
- Colors missing (MCLite) → palette bands are
(label,color)pairs; ensure both are present in yourStyleCode. - Group queries case (PostgreSQL) → default is case-sensitive; keep consistent casing or switch to
~*in your fork. - Long IDs → PostgreSQL defaults to
varchar(255)for ids; widen columns if you need larger ids/entity keys.
Cheat-sheet: when to choose which¶
-
Choose MCLite if:
- You already store GIS + time series in MCLite and want Places co-located
- You need collection-level normalization of palettes/intervals
- You accept managing the schema up-front
-
Choose PostgreSQL if:
- You want to stand up Places fast with auto-DDL
- You want a minimal schema with simple ops & table overrides
- You’re fine managing palettes/intervals per indicator row