DHI.Services.GeoServer for GIS — Internal Developer Guide (WFS → FeatureCollection)¶
Audience: devs using
DHI.Services.GISwith GeoServer as a vector/feature source. For placeholders/auth/time/CRS details, see GeoServer Core.
What this provider does¶
- Calls GeoServer WFS (or any endpoint returning GeoJSON) using your templated query.
- Parses GeoJSON via NTS and converts to
DHI.Spatialgeometries. - Returns a
FeatureCollection<string>(features + schema/attributes). - Sets feature CRS if the GeoJSON root has
crs.properties.name.
Designed for WFS GetFeature. If pointed at WMS (images), it won’t work.
Capabilities & limitations¶
| Operation | Support | Notes |
|---|---|---|
Get(id) |
✓ | Main entry. Uses the templated URL. |
Get(id, associations, outSpatialReference) |
✓ | Delegates to Get(id). Params currently ignored. |
Get(id, filter, …) |
✓ | Delegates to Get(id). filter currently ignored. |
GetAll() |
✗ | Not implemented. |
GetEnvelope / GetFootprint |
✗ | Not implemented. |
GetIds() |
✓ | Lists layer names via GeoServer REST /rest/layers. |
| Auth | ✓ | Optional Basic Auth; prefer HTTPS. |
| Geometry | ✓ | 3D/Measured types are flattened to 2D (see Core). |
| Client | ✓ | Instance HttpClient (you may inject); no global header sharing. |
Building the HTTP request (query template)¶
Typical WFS (GeoJSON) template:
[group]/ows?service=WFS&version=1.1.0&request=GetFeature
&typeName=[group]:[layer]
&srsName=[crs]
&bbox=[boundingbox]
&outputFormat=application/json
If the ID carries Time, the provider appends
&time=yyyy-MM-ddThh:mm:ss.fffZ (12-hour, see Core).
If your template includes
[width]/[height], your ID must includeSize=....
ID grammar (what you pass as {id})¶
Parsed by GeoServerFeatureCollectionId (semicolon-separated key=value):
Group=<workspace>(string)Layer=<layerName>(string)Crs=<EPSG:XXXX>(string) — defaultEPSG:3857BoundingBox=<xmin,ymin,xmax,ymax>(double; culture-invariant.)Size=<width,height>(ints; only if your template uses[width]/[height])Time=<timestamp>ParseExact with formats:"s","u", or"O"(e.g.,2021-09-20T00:00:00Z)
Examples
Group=demo;Layer=roads;Crs=EPSG:4326;BoundingBox=12,55,13,56
Group=hydro;Layer=gauges;Crs=EPSG:3857;BoundingBox=1320000,7400000,1340000,7420000;Time=2021-09-20T00:00:00Z
Group=land;Layer=buildings;Size=1024,768;Crs=EPSG:3857;BoundingBox=12.0,55.0,13.0,56.0
Behavior: unknown keys are ignored (only documented keys affect substitution).
Wiring¶
A) Connections (Web API hosts)¶
"geoserver": {
"$type": "DHI.Services.GIS.WebApi.GisServiceConnection, DHI.Services.GIS.WebApi",
"ConnectionString": "BaseUrl=http://localhost:8080/geoserver/;Query=[group]/ows?service=WFS&version=1.1.0&request=GetFeature&typeName=[group]:[layer]&srsName=[crs]&bbox=[boundingbox]&outputFormat=application/json;UserName=user;Password=pass",
"RepositoryType": "DHI.Services.Provider.GeoServer.FeatureCollectionRepository, DHI.Services.Provider.GeoServer",
"Name": "GeoServer WFS",
"Id": "geoserver"
}
B) Programmatic¶
var query =
"[group]/ows?service=WFS&version=1.1.0&request=GetFeature" +
"&typeName=[group]:[layer]" +
"&srsName=[crs]" +
"&bbox=[boundingbox]" +
"&outputFormat=application/json";
ServiceLocator.Register(
new GisService(
new DHI.Services.Provider.GeoServer.FeatureCollectionRepository(
baseUrl: "https://my.geoserver.company/geoserver/",
query: query,
userName: "user",
password: "pass")),
"geoserver");
Examples¶
Fetch features for a viewport (EPSG:4326)
string id =
"Size=800,400;" +
"Crs=EPSG:4326;" +
$"BoundingBox={xmin},{ymin},{xmax},{ymax};" +
"Layer=states;Group=topp";
var repo = new FeatureCollectionRepository(
"BaseUrl=http://localhost/geoserver/;" +
"Query=wfs?service=WFS&version=1.0.0&request=GetFeature&typeName=[group]:[layer]&outputFormat=application/json&srsName=[crs]&bbox=[boundingbox];" +
"UserName=admin;Password=geoserver");
var maybe = repo.Get(id);
return maybe.HasValue ? Ok(maybe.Value) : NotFound();
List layer ids via REST
var repo = Services.Get<GisService<string>>("geoserver").Repository;
foreach (var name in repo.GetIds()) Console.WriteLine(name);
Performance tips¶
- Always pass a tight
BoundingBoxfor server-side clipping. - Use GeoServer layer/view config (or extend your template with CQL) to reduce attributes/feature counts.
- Consider pagination (
maxFeatures/count) in templates for large responses.
Troubleshooting¶
- HTTP 200 but empty → confirm endpoint is WFS with
outputFormat=application/json. - CRS missing → your server emitted RFC-7946 GeoJSON (no
crs); interpret in requestedCrs. - Layer 404 → verify
Group/Layerspelling and credentials. - Time weirdness → provider sends
hh(12-hour). If you needHH, use your own[time]placeholder or adjust code.
Design notes (under the hood)¶
FeatureCollectionRepositorybuilds the query by replacing placeholders; ifTimeis set, appends&time=…(see Core).- Before NTS parsing:
json.Replace("MULTILINESTRING Z","MULTILINESTRING"). - After conversion: if GeoJSON root has
crs.properties.name, assign to each featureGeometry.CRS. GetIds()hits/rest/layersand extractsname.