Implementing a Rendering Transformation

Rendering Transformations are a special kind of WPS process which run within the GeoServer WMS rendering pipeline to transform data in order to provide more effective visualization. This section describes how to implement a rendering transformation process in Java.

Rendering transformations are very general, and can transform both the content and the format of input data. Content transformation typically involves complex geospatial processing which requires access to the entire dataset (in contrast to geometry transformations, which operate on a single spatial feature at a time). Format transformation converts from vector to raster or vice-versa in order to produce an output format appropriate to the desired visualization (for instance, a raster for displaying continuous surfaces, or vector data for displaying discrete objects).

For more information about the function and use of rendering transformations within GeoServer, refer to the Rendering Transformations section of the GeoServer User Guide.

Lifecycle of a Rendering Transformation

To implement a rendering transformation it is useful to understand their lifecycle and operation within GeoServer. A rendering transformation is invoked in an SLD by providing a <Transformation> element inside a <FeatureTypeStyle>. This element specifies the name of the transformation process and the names and values of the process parameters. As an example, the following is a portion of an SLD which uses the gs:Heatmap transformation:

 1         <FeatureTypeStyle>
 2           <Transformation>
 3             <ogc:Function name="gs:Heatmap">
 4               <ogc:Function name="parameter">
 5                 <ogc:Literal>data</ogc:Literal>
 6               </ogc:Function>
 7               <ogc:Function name="parameter">
 8                 <ogc:Literal>weightAttr</ogc:Literal>
 9                 <ogc:Literal>pop2000</ogc:Literal>
10               </ogc:Function>
11               <ogc:Function name="parameter">
12                 <ogc:Literal>radiusPixels</ogc:Literal>
13                 <ogc:Function name="env">
14                   <ogc:Literal>radius</ogc:Literal>
15                   <ogc:Literal>100</ogc:Literal>
16                 </ogc:Function>
17               </ogc:Function>
18               <ogc:Function name="parameter">
19                 <ogc:Literal>pixelsPerCell</ogc:Literal>
20                 <ogc:Literal>10</ogc:Literal>
21               </ogc:Function>
22               <ogc:Function name="parameter">
23                 <ogc:Literal>outputBBOX</ogc:Literal>
24                 <ogc:Function name="env">
25                   <ogc:Literal>wms_bbox</ogc:Literal>
26                 </ogc:Function>
27               </ogc:Function>
28               <ogc:Function name="parameter">
29                 <ogc:Literal>outputWidth</ogc:Literal>
30                 <ogc:Function name="env">
31                   <ogc:Literal>wms_width</ogc:Literal>
32                 </ogc:Function>
33               </ogc:Function>
34               <ogc:Function name="parameter">
35                 <ogc:Literal>outputHeight</ogc:Literal>
36                 <ogc:Function name="env">
37                   <ogc:Literal>wms_height</ogc:Literal>
38                 </ogc:Function>
39               </ogc:Function>
40             </ogc:Function>
41           </Transformation>
42           ...

During WMS requests which uses an SLD specifying a transformation, the arguments passed to the transformation process are assembled from the parameters and values specified in the SLD. Some argument values may be determined dynamically from SLD variables (as shown above in lines 22-40).

Before the transformation process is executed, it is given an opportunity to rewrite the query GeoServer makes to the source datastore, via the optional invertQuery or invertGridGeometry methods. This allows a transformation to enlarge the query extent, since some kinds of transformations may need to include data which lies outside the original query window.

The query is then performed against the source datastore, and the transformation process is executed against the resulting dataset. The transformation returns a computed output dataset of the same or different format. If the output has a different coordinate system (CRS) than the requested map it is reprojected automatically. Finally, the output dataset is passed on through the rendering pipeline to be styled by the symbolizers defined in the SLD <FeatureTypeStyle>.

Transformation process class

Like other WPS processes, rendering transformations are implememented as Java classes. A process class implements the GSProcess marker interface, and is registered with GeoServer via an applicationContext.xml file. For further information about the basic steps for creating, building and deploying a GeoServer WPS process in Java refer to the Implementing a WPS Process section.

WPS processes must provide metadata about themselves and their parameters. The easiest way to do this is to use the GeoTools annotation-based Process API, which uses Java annotations to specify metadata. For example, the code below shows the process metadata specified for the gs:Heatmap rendering transformation:

@DescribeProcess(title = "Heatmap",
             description = "Computes a heatmap surface over a set of irregular data points as a GridCoverage.")
public class HeatmapProcess implements GeoServerProcess {

GeoServer instantiates a single instance of each rendering transformation class. This means that rendering transformation classes must be stateless, since they may be called concurrently to service different requests. This is ensured by avoiding declaring any instance variables within the class. For complex transformations it may be desirable to implement an auxiliary class to allow the use of instance variables.

execute method

Like all process classes, a rendering transformation class must declare an execute method, which is called by GeoServer to perform the transformation. The signature of the execute method specifies the types of the input parameters and the process result.

The declaration of the execute method for the Heatmap transformation is:

@DescribeResult(name = "result", description = "The heat map surface as a raster")
public GridCoverage2D execute(

  // tranformation input data
  @DescribeParameter(name = "data", description = "Features containing the data points")
    SimpleFeatureCollection obsFeatures,

  // process parameters
  @DescribeParameter(name = "radiusPixels",
                     description = "Radius to use for the kernel, in pixels")
    Integer argRadiusPixels,
  @DescribeParameter(name = "weightAttr",
              description = "Featuretype attribute containing the point weight value",
                     min = 0, max = 1)
     String valueAttr,
  @DescribeParameter(name = "pixelsPerCell",
                     description = "Number of pixels per grid cell (default = 1)",
                     min = 0, max = 1)
    Integer argPixelsPerCell,

  // output map parameters
  @DescribeParameter(name = "outputBBOX",
                     description = "Georeferenced bounding box of the output")
    ReferencedEnvelope argOutputEnv,
  @DescribeParameter(name = "outputWidth", description = "Width of the output raster")
    Integer argOutputWidth,
  @DescribeParameter(name = "outputHeight", description = "Height of the output raster")
    Integer argOutputHeight,

  ) throws ProcessException {
  ...

Input parameters

The supported process input parameters are defined as parameters to the execute method. The metadata for them is supplied via @DescribeParameter annotations.

To accept the input data to be transformed, the process must define one input parameter of type SimpleFeatureCollection or GridCoverage2D. In the invoking SLD only the name of this parameter is specified, since GeoServer provides the dataset to be transformed as the parameter value.

Any number of other parameters can be defined. Parameters can be mandatory or optional (optional parameters have a value of null if not present). Lists of values can be accepted by defining an array-valued parameter.

Some transformations require information about the request map extent and coordinate system, and request image width and height. Situations where these are required include:

  • the transformation operation depends on the request resolution

  • the transformation computes a raster result in the request coordinate system to ensure optimal visual quality

These values can be obtained from SLD predefined variables and passed in via parameters of types ReferencedEnvelope and Integer. (See the Variable Substitution in SLD section in the User Guide for details of all predefined variables available.)

In the case of the Heatmap transformation, the request resolution is used to determine the ground size of the radiusPixels parameter, and the output raster is computed in the request coordinate system to avoid undesired reprojection. To support this the transformation defines the required outputBBOX, outputWidth and outputHeight parameters. These are supplied by predefined SLD variables as shown in lines 22-40 of the SLD snippet above.

Transformation output

The output of a transformation is a new dataset of type SimpleFeatureCollection or GridCoverage2D. This is specified as the return type of the execute method. Name and description metadata is provided by the @DescribeResult annotation on the execute method.

If the output dataset is not in the coordinate system requested for map output, GeoServer reprojects it automatically. As noted in the previous section, there may be situations where it is desirable to avoid this. In this case the transformation must ensure that the output has the appropriate CRS.

Query rewriting

If required, the rendering transformation has the ability to alter the query made against the source dataset. This allows expanding the extent of the data to be read, which is necessary for some kinds of transformations (in particular, ones whose result is determined by computing over a spatial window around the input). This also allows controlling query optimizations (for instance, ensuring that geometry decimation does not prevent point features from being read).

Query rewriting is performed by providing one of the methods invertQuery or invertGridGeometry.

These methods have the general signature of:

X invertX( [inputParam,]* Query targetQuery, GridGeometry targetGridGeometry)

The targetQuery parameter is the query constructed from the original request.

The targetGridGeometry parameter is the georeferenced extent of the requested output map. It is not used in the dataset query, but may be needed for use in conjunction with the transformation parameters to determine how to rewrite the query. For instance, if a parameter is specified in output units, the output extent information is required to transform the value into units applicable in the input CRS.

In addition, these methods can accept any number of the input parameters defined for the execute method. If defined these parameters must be annotated with @DescribeParameter in the same way as in the execute method.

invertQuery method

This method is called when the rendering tranformation applies to vector data (the data input is of type SimpleFeatureCollection).

The method returns a new Query value, which contains any required alterations of extent or query optimizations. This is used to query the source dataset.

The Heatmap process implements the invertQuery method in order to enlarge the query extent by the ground size corresponding to the radiusPixels parameter. To allow converting the pixel size into a ground distance the input parameters providing the output map extents are also required. The signature of the implemented method is:

public Query invertQuery(
        @DescribeParameter(name = "radiusPixels",
                       description = "Radius to use for the kernel", min = 0, max = 1)
          Integer argRadiusPixels,
        // output image parameters
        @DescribeParameter(name = "outputBBOX",
                       description = "Georeferenced bounding box of the output")
          ReferencedEnvelope argOutputEnv,
        @DescribeParameter(name = "outputWidth",
                       description = "Width of the output raster")
          Integer argOutputWidth,
        @DescribeParameter(name = "outputHeight",
                       description = "Height of the output raster")
          Integer argOutputHeight,

        Query targetQuery, GridGeometry targetGridGeometry
   ) throws ProcessException {
        ...

invertGridGeometry method

This method is called when the rendering tranformation applies to raster data (the data input is of type GridCoverage2D).

The method returns a new GridGeometry value, which is used as the query extent against the source raster dataset.

Summary

In summary, the key features of a rendering transformation process class are:

  • There must be an input parameter which is a FeatureCollection or a GridCoverage2D

  • It may be useful to have input parameters which provide the request map extent and image dimensions

  • There must be a single result of type FeatureCollection or GridCoverage2D

  • The optional invertQuery or invertGridGeometry methods may be supplied to rewrite the initial data query

  • The transformation process class must be stateless