Skip to content

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)


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") includes Stations/* 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.TimeSeries require 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

// 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/metadata and 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 ;nonrecursive suffix 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, -2 suffixes).
  • 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.
  • TimeInterval encoding: All (start/end NULL), Fixed (OLE Automation dates in start/end), Relative* (day offsets).
  • Table override: append ;Table=myschema.places;Table=myschema.indicators to 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 your StyleCode.
  • 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