Skip to content

DHI.Services.Rasters.WebApi — Internal Developer Guide

This document explains what the Rasters WebApi does, how it is structured, how to host it, and most importantly, how to use it from other services or tools. You don’t need the source code to follow along; everything you need is described here with concrete request/response examples.


1) What this service is

Rasters WebApi exposes HTTP endpoints for:

  • Serving radar images from on-disk repositories (several formats supported)
  • Performing zone-based rainfall analytics (intensity, depth, min/max/avg) over time ranges
  • Returning ready-to-draw PNG bitmaps of radar images and color legends
  • Managing zones (create, list, delete)

It’s designed as a thin HTTP facade over the domain library DHI.Services.Rasters:

  • The WebApi wires repositories (where and how images are loaded), services (business logic), and JSON serialization (so types travel cleanly over the wire).
  • It uses JWT Bearer authentication and ASP.NET Core authorization policies.
  • It ships with opinionated JSON serialization options (e.g., support for the Enumeration pattern and custom value converters).

2) High-level architecture

HTTP Client
    |
    v
ASP.NET Core (Controllers)
    |
    +--> RadarImagesController<TImage> --------+
    |                                          |
    |                      Services.Get<RadarImageService<TImage>>(connectionId)
    |                                          |
    +--> ZoneService <--------------------+----+
    |                                    |
    v                                    v
Radar repositories                   Zone repository
(on-disk; format-specific)          (JSON file in App_Data)

Key parts:

  • RadarImagesController<TImage> — generic controller that works for any radar image type you choose (e.g., ASCII, IRIS CAPPI).
  • Services.Get<...>(connectionId) — a simple service locator that resolves a named RadarImageService<TImage> you registered at startup (see “Hosting & wiring”).
  • ZoneService + ZoneRepository — zone storage in a JSON file under App_Data.
  • SerializerOptionsDefault — a singleton bundle of JSON options and converters used everywhere.

3) Authentication & authorization

  • AuthN: JWT Bearer (Authorization: Bearer <token>).
  • AuthZ:
    • Most endpoints require an authenticated user.
    • Zone creation and deletion require policy AdministratorsOnly, implemented as a claim http://schemas.microsoft.com/windows/2008/06/identity/claims/groupsid = "Administrators".

Typical appsettings

  • Tokens:Issuer, Tokens:Audience, and an RSA public key (Tokens:PublicRSAKey) are required to validate tokens.
  • HSTS settings and Swagger metadata can also be provided via configuration.

4) Connections, repositories, and image types

A “connection” is just a name you give to a RadarImageService<TImage> that knows how to read a specific folder/filename pattern.

At startup you register one or more connections:

ServiceLocator.Register(
  new RadarImageService<AsciiImage>(
    new DelimitedAsciiRepository(
      "[AppData]RadarImages;PM_{datetimeFormat}.txt;yyyyMMddHH_$$$".Resolve()
    )
  ),
  "ascii" // <-- connectionId
);
  • connectionId is the URL segment you will use in requests (e.g., /api/radarimages/ascii/...).
  • DelimitedAsciiRepository (and other repositories) point at a folder with radar files and specify how to parse the timestamp from file names.
  • The connection string syntax is "<folder>;<filePattern>;<dateTimeFormat>". Example with a counter per hour: C:\data\RadarImages;PM_{datetimeFormat}.txt;yyyyMMddHH_$$$
    • $$$ = “hours since base date” counter
    • ### = “days since base date” counter
    • {datetimeFormat} is replaced by the parser according to the dateTimeFormat.

The token [AppData] is resolved at runtime to the application’s App_Data folder (see “Hosting & wiring”).

Supported image formats (out of the box)

  • Delimited ASCII: DHI.Services.Rasters.Radar.DELIMITEDASCII.AsciiImage (+ DelimitedAsciiRepository)
  • ESRI ASCII: DHI.Services.Rasters.Radar.ESRIASCII.AsciiImage (+ EsriAsciiRepository)
  • IRIS CAPPI: DHI.Services.Rasters.Radar.IRISCAPPI.RadarImage (+ IRIS CAPPI-compatible repository; wire it similarly)

You can add more formats by introducing a new repository and registering a connection the same way.


5) API Endpoints

All routes below are prefixed with:

/api/radarimages/{connectionId}

…where {connectionId} is the name you registered (e.g., ascii).

5.1 Radar image retrieval

Get image by timestamp

GET /api/radarimages/{connectionId}/{date}

  • date is an ISO datetime (YYYY-MM-DDTHH:mm:ss).
  • Returns the image object (JSON). If the image format stores reflectivity, you still receive raw values; conversion to intensity happens in analytics/bitmap endpoints when required.

Get last image

GET /api/radarimages/{connectionId}/last

Get last image before a timestamp

GET /api/radarimages/{connectionId}/lastbefore/{date}

Get first image after a timestamp

GET /api/radarimages/{connectionId}/firstafter/{date}

Batch “last before” for many timestamps

POST /api/radarimages/{connectionId}/list/lastbefore Body: ["2025-02-01T10:00:00","2025-02-01T11:15:00"]

Batch “first after” for many timestamps

POST /api/radarimages/{connectionId}/list/firstafter Body: ["2025-02-01T10:00:00","2025-02-01T11:15:00"]

5.2 Image availability (datetimes)

Last / first datetime

  • GET /api/radarimages/{connectionId}/datetime/last
  • GET /api/radarimages/{connectionId}/datetime/first

List datetimes in an interval

GET /api/radarimages/{connectionId}/datetimes?from=...&to=...

  • Both from and to are optional; without them you get the full range.

“First after” and “Last before” for many datetimes

  • POST /api/radarimages/{connectionId}/datetimes/firstafter
  • POST /api/radarimages/{connectionId}/datetimes/lastbefore Body: ["2025-02-01T10:00:00","2025-02-02T01:00:00"]

5.3 Zone-based analytics

All zone endpoints require a valid zone id. See “Zones API” for creating and listing zones.

Units

  • Intensities are usually mm/h (millimeters per hour).
  • Depth (accumulated rainfall) is mm.
  • If the underlying image is reflectivity, the API converts to intensity using Marshall–Palmer and default coefficients (unless specified otherwise in the domain layer).

Accumulated rainfall (depth) in an interval

GET /api/radarimages/{connectionId}/depth/{zoneId}?from=2025-02-01T00:00:00&to=2025-02-01T06:00:00

  • If to is omitted, it uses the last available image time.

Accumulated rainfall (depth) in the last N hours

GET /api/radarimages/{connectionId}/depth/{zoneId}/hours/{hours}

Time series of average intensity (mm/h)

GET /api/radarimages/{connectionId}/intensities/{zoneId}?from=...&to=...

  • Returns { "2025-02-01T00:10:00Z": 0.8, "2025-02-01T00:20:00Z": 1.1, ... }.

Max / Average intensity (mm/h) in an interval

  • GET /api/radarimages/{connectionId}/intensity/max/{zoneId}?from=...&to=...
  • GET /api/radarimages/{connectionId}/intensity/average/{zoneId}?from=...&to=...

Average intensity (mm/h) in the last N hours

GET /api/radarimages/{connectionId}/intensity/average/{zoneId}/hours/{hours}

Limits

  • For performance, the analytics layer imposes a maximum analysis span (default: 30 days). Larger requests will be rejected.
  • Internally, long spans are broken into smaller batches, so you can safely query multi-day windows up to that limit.

5.4 Bitmaps (PNG images)

You can request ready-to-draw bitmaps (image/png). The service auto-converts reflectivity to intensity when needed.

Image bitmap at a timestamp

GET /api/radarimages/{connectionId}/{date}/bitmap?style=IntensityDefault

  • If style is omitted, IntensityDefault is used.

Last image bitmap

GET /api/radarimages/{connectionId}/last/bitmap?style=IntensityDefault

Legend / color style bitmap

GET /api/radarimages/{connectionId}/style/{style}/bitmap?height=300&width=100

Available style values (case-sensitive display names):

  • IntensityDefault
  • IntensityLightYellowToRed
  • IntensityLightYellowToRedLogarithmic
  • ReflectivityDefault
  • ReflectivityLightYellowToRed

6) Zones API

Base path: /api/zones

Zones are collections of pixel weights (1-based raster coordinates) that define an area of interest. Pixel weights must sum (≈) to 1.0 (validated).

Endpoints

  • Get zone by id GET /api/zones/{id}

  • List zones GET /api/zones

  • Count zones GET /api/zones/count

  • List zone ids GET /api/zones/ids

  • Create zone (AdministratorsOnly) POST /api/zones Body is a ZoneDTO:

    {
        "Id": "City.Center",
        "Name": "City Center",
        "Type": "Polygon",
        "ImageSize": { "Width": 480, "Height": 480 },
        "PixelWeights": [
        { "Pixel": { "Col": 101, "Row": 210 }, "Weight": { "Value": 0.25 } },
        { "Pixel": { "Col": 102, "Row": 210 }, "Weight": { "Value": 0.25 } },
        { "Pixel": { "Col": 101, "Row": 211 }, "Weight": { "Value": 0.25 } },
        { "Pixel": { "Col": 102, "Row": 211 }, "Weight": { "Value": 0.25 } }
        ]
    }
    

    Notes:

    • Type must be one of: Point, Line string, Polygon (exact display names).
    • ImageSize must match the raster dimensions the zone applies to.
    • PixelWeights must (approximately) sum to 1.0.
  • Delete zone (AdministratorsOnly) DELETE /api/zones/{id}

Storage

By default, zones are stored in a JSON file under App_Data (see “Hosting & wiring”). The repository class resolves the actual path from your configuration and environment.


7) JSON formats & serialization

The API uses a standardized System.Text.Json configuration via SerializerOptionsDefault:

  • No camel-casing by default (property naming policy is null).
  • Nulls are omitted.
  • Converters for:
    • .NET enums as strings.
    • The internal Enumeration pattern (so types like ColorGradientType, ZoneType, etc. serialize cleanly).
    • Polymorphic connection & parameter types (for scenarios where connections are created from configuration).
    • Zone types and zone dictionaries.
    • PixelValueType (reflectivity vs intensity).

You generally don’t need to worry about these—just consume and produce the JSON shown in the examples above.


8) Hosting & wiring

Minimal host

  1. Program.cs Standard ASP.NET Core CreateHostBuilder(...).UseStartup<Startup>().

  2. Startup.ConfigureServices(...)

  3. Configure JWT authentication.

  4. Add authorization policies.
  5. Enable API Versioning (default 1.0; via header api-version or query api-version/version/ver).
  6. Add Controllers + JSON options from SerializerOptionsDefault.
  7. Add HSTS/Swagger (optional but recommended).
  8. Register a ZoneRepository that points to a JSON file in App_Data:

    services.AddScoped<IZoneRepository>(provider => new Rasters.WebApi.ZoneRepository("zones.json"));
    
  9. Startup.Configure(...)

  10. Standard ASP.NET Core pipeline + Swagger UI, HTTPS redirection, response compression, authn/z, etc.

  11. Set App_Data:

    var contentRootPath = Configuration.GetValue("AppConfiguration:ContentRootPath", env.ContentRootPath);
    AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(contentRootPath, "App_Data"));
    
  12. Register at least one radar connection:

    ServiceLocator.Register(
        new RadarImageService<AsciiImage>(
        new DelimitedAsciiRepository(
            "[AppData]RadarImages;PM_{datetimeFormat}.txt;yyyyMMddHH_$$$".Resolve()
        )
        ),
        "ascii"
    );
    
  13. Controller binding Add a concrete controller for the image type you’re exposing:

public class RadarImagesController : RadarImagesController<AsciiImage>
{
    public RadarImagesController(IZoneRepository zoneRepository) : base(zoneRepository) {}
}

You can host multiple concrete controllers if you want to expose multiple image types under different routing prefixes.

Running

  • Place your radar files in the folder referenced by your repository connection string (often under App_Data\RadarImages).
  • Place your zones.json (or let the API write it after creating zones).
  • Start the WebApi; open Swagger UI to explore the endpoints.

9) Practical examples

Replace {host} with your base URL and {token} with a valid JWT. Replace ascii with your actual connectionId.

Get the latest image metadata

curl -H "Authorization: Bearer {token}" \
  "{host}/api/radarimages/ascii/last?api-version=1"

Get datetimes available today

curl -H "Authorization: Bearer {token}" \
  "{host}/api/radarimages/ascii/datetimes?from=2025-02-01T00:00:00&to=2025-02-01T23:59:59"

Get intensity time series for a zone

curl -H "Authorization: Bearer {token}" \
  "{host}/api/radarimages/ascii/intensities/City.Center?from=2025-02-01T00:00:00&to=2025-02-01T06:00:00"

Example response:

{
  "2025-02-01T00:10:00": 0.7,
  "2025-02-01T00:20:00": 0.9,
  "2025-02-01T00:30:00": 1.2
}

Get accumulated depth (mm) in last 6 hours

curl -H "Authorization: Bearer {token}" \
  "{host}/api/radarimages/ascii/depth/City.Center/hours/6"

Get a PNG bitmap for the latest image (IntensityDefault)

curl -H "Authorization: Bearer {token}" \
  -o latest.png \
  "{host}/api/radarimages/ascii/last/bitmap?style=IntensityDefault"

Get a PNG legend for a style

curl -H "Authorization: Bearer {token}" \
  -o legend.png \
  "{host}/api/radarimages/ascii/style/IntensityLightYellowToRed/bitmap?height=300&width=120"

Create a zone

curl -X POST -H "Authorization: Bearer {token}" -H "Content-Type: application/json" \
  -d '{
    "Id": "City.Center",
    "Name": "City Center",
    "Type": "Polygon",
    "ImageSize": { "Width": 480, "Height": 480 },
    "PixelWeights": [
      { "Pixel": { "Col": 101, "Row": 210 }, "Weight": { "Value": 0.25 } },
      { "Pixel": { "Col": 102, "Row": 210 }, "Weight": { "Value": 0.25 } },
      { "Pixel": { "Col": 101, "Row": 211 }, "Weight": { "Value": 0.25 } },
      { "Pixel": { "Col": 102, "Row": 211 }, "Weight": { "Value": 0.25 } }
    ]
  }' \
  "{host}/api/zones"

10) Troubleshooting

  • 404 / Not found

    • The connectionId must be registered at startup.
    • The requested timestamp must exactly match an available image time (use the datetimes endpoint to discover).
    • The zoneId must exist.
  • 400/500 when requesting bitmaps

    • The style must match one of the known ColorGradientType display names exactly.
  • “Maximum analysis timespan exceeded”

    • Analytics requests are bounded (default 30 days). Split long requests into smaller windows.
  • Pixel weights must sum to \~1.0

    • Zone creation will be rejected if weights don’t add up (rounded to 3 decimals).
  • ESRI/Delimited/IRIS CAPPI file locations

    • Double-check the connection string and that [AppData] resolves to your content root’s App_Data (set in Startup.Configure).

11) Extensibility

  • Add a new radar format

    • Implement a repository that loads your files into a radar image type (conforming to IRadarImage).
    • Register it at startup with a new connectionId.
    • Optionally, expose a dedicated RadarImagesController<YourImageType> for clarity.
  • Dynamic connections via connection types

    • The WebApi includes helper connection wrappers (e.g., EsriAsciiRadarImageServiceConnection, DelimitedAsciiRadarImageServiceConnection, IrisCappiRadarImageServiceConnection) that support [AppData] resolution when creating services dynamically (e.g., from a connections catalog). If you use such a catalog, supply:
      • RepositoryType — fully-qualified .NET type name of your repository.
      • ConnectionString — using the same <folder>;<filePattern>;<dateTimeFormat> syntax.
    • The wrapper Create() method will instantiate the repository and produce a RadarImageService<TImage>.
  • Custom color styles

    • Define new ColorGradientTypes in the domain library and they’ll immediately work in /style/{style}/bitmap and image rendering.