实现一个渲染变换(Rendering Transformation)

简介

渲染转换是一种特殊的WPS过程,它运行在Geoserver WMS渲染管道中,对数据进行转换,以提供更有效的可视化。本节介绍如何在Java中实现呈现转换过程

呈现转换非常通用,可以转换输入数据的内容和格式。内容转换通常涉及复杂的地理空间处理,需要访问整个数据集(与几何转换不同,几何学转换一次只对单个空间特征进行操作)。格式转换从矢量转换到栅格,反之亦然,以产生适合于所需可视化的输出格式(例如,用于显示连续曲面的瓦片,或用于显示离散对象的矢量数据)。

有关Geoserver中渲染转换功能的更多信息,请参阅Geoserver用户指南的渲染转换部分。

渲染转换的生命周期

要实现渲染转换,首先就要了解它在GeoServer中的生命周期以及执行的操作。一个渲染转换是在SLD中通过在要素中声明 要素完成的。这个元素()生命了转换过程的名称和值以及渲染参数。
下面是实现gs:Heatmap转换在SLD中声明的代码:

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

在WMS请求中我们使用SLD来指定变换,所有的参数都是在SLD文档中指定的参数。一些参数值必须由SLD变量指定。参考上图。
在执行转换过程之前,可以通过可选的反向查询(invertQuery)或invertGridGeometry方法,将查询Geoserver所做的查询重写到源数据存储。这允许转换扩大查询范围,因为某些类型的转换可能需要包含位于原始查询窗口之外的数据。渲染变换是可以扩大查询范围的,因为某些类型的转换可能需要包含位于原始查询数据之外的数据。

然后对源数据存储执行查询,并对结果数据集执行转换过程。转换返回的数据集要么格式相同要么格式不同。如果坐标系和需求不一样还会被自动转换。最后,输出数据集通过渲染管道传递,由SLD的中定义的符号符进行样式设置。

转换过程类 (Transformation process class)

和其它WPS过程类似,渲染转换的实现也是通过Java类的。一个服务过程(process)需要实现GSProcess marker接口。并且需要通过applicationContext.xml文件来注册到GeoServer中。另外如果需要了解更多关于如果构建以及如何注册等信息请参阅WPS服务构建章节(上一篇哦)。

WPS服务必须提供关于这个服务的元数据以及参数。最简单的方法是使用GeoTools annotation-based Process API,它使用了Java注解来指定元数据。例如,下面的代码展示了渲染变换(gs:Heatmap)元数据指定的过程。

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

GeoServer对于每个渲染变换实现单个实例。这意味着呈现转换类必须是无状态的,使得我们可以通过调用他们来处理不同的请求。这是通过避免在类中声明任何实例变量来确保的。对于复杂的转换,可能需要实现一个辅助类,以允许使用实例变量。

译者注:这里面的意思应该是每个WPS服务实现都是有一个类不会,那么在多线程情况下,如果里面有变量可能会是线程不安全的,所以变量建议通过别的类来存储,这样避免变量串了。

执行方法(execute method)

像所有的服务过程类(process classes)一样,一个渲染转换类必须实现一个执行方法,来教会GeoServer如何执行转换。执行方法的签名(DescribeResult、DescribeParameter)指定输入参数的类型和过程结果。

一个Heatmap变换的执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@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)

一个受支持的输入参数是通过execute方法的参数传入的。元数据则是通过@DescribeParameter注解获取的。

要接受要转换的输入数据,过程必须定义一个输入参数,类型为SimpleFeatureCollection或GridCoverage2D。在GeoServer支持数据作为参数传入,来执行过程,但是需要指定上面所说的参数。可以定义任意数量的其他参数。参数可以是强制性的,也可以是可选的(如果不存在,可选参数的值为NULL)。可以通过定义数组值参数来接受值列表.

有些转换需要有关请求地图的范围和坐标系统的信息,并要求图像的宽度和高度。这些需要的情况包括:

1、转换操作依赖请求分辨率;
2、转换操作计算的是一个栅格结果,需要目标坐标系来达到一个最佳效果;

这些值可以从SLD预定义变量中获得,并通过ReferencedEncrype和Integer类型的参数传入。(有关所有可用预定义变量的详细信息,请参阅“用户指南”中的SLD部分中的变量替换。)

在Heatmap变换的情况下,请求分辨率会被用于整个数据范围内的像素半径参数,另外输出栅格也会在需要的坐标系下进行计算避免不被期望的投影。

为了支持这个变换需要定义outputBBOX、outputWidth以及outputHeight参数。这些是由预定义的SLD变量提供的,如上面SLD片段。

变换输出(Transformation output)

变换的输出是一个新的( SimpleFeatureCollection 或者 GridCoverage2D类型的)数据集,返回类型需要在execute的返回类型中指定。名称以及元数据由execute方法的@DescribeResult注解负责提供。

如果输出数据集数据的坐标系与地图输出的坐标系不符,GeoServer将会自己动调整坐标。另外我们可以通过设置请求坐标系来解决避免自动转换(一定要明确坐标系)。

查询重写(Query rewriting)

如果需要,渲染转换有能力改变对源数据集的查询。允许扩展要读取的数据的范围,对于某些类型的转换(特别是通过计算围绕输入的空间窗口来确定结果的那些)是必要的。这也允许控制查询优化(例如,确保几何抽取不阻止点特征被读取)。

译者注:我的理解就是读取的范围可以扩大或者精炼。

查询重写是通过提供InvertQuery或invertGridGeometry方法来实现的。这些方法的一般签名是:

1
2

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

targetQuery参数是请求的查询构造体。

targetGridGeometry是被请求的输出地图的地理参考范围。他不会被用在数据查询汇总,但可能需要与转换参数一起使
用,以确定如何重写查询。例如,如果在输出单元中指定了参数,那么输出范围信息将该值转换为与输入CRS相适应的值。

自己实现?

此外,这些方法可以接受为执行方法定义的任意数量的输入参数。如果定义了这些参数,他们也必须通过@DescribeParameter参数在execute中进行注解。

invertQuery方法

这个方法会在渲染变换处理矢量数据是调用(输入数据的类型是SimpleFeatureCollection)。

这个方法会返回一个新的查询值,包含了范围或查询优化所需的任何更改。它被用于查询资源数据集。

热力图渲染过程实现了invertQuery来增大查询范围。这个查询范围则是通过与radiusPixels参数相关的ground size决定的。

The Heatmap process implements the invertQuery method in order to enlarge the query extent by the ground size corresponding to the radiusPixels parameter.
这句ground size是啥?

为了允许将像素尺寸转换为ground distance,还需要提供输出地图范围的输入参数。所实现的方法的签名是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 方法

这个方法会在渲染栅格数据时(输入格式是GridCoverage2D)时被调用。该方法返回一个新的GridGeometry值,用作对源栅格数据集的查询范围。

在总结

总结一下 ,渲染变换的重点是:

1、再输入的时候必须有个输入参数是FeatureCollection或者GridCoverage2D。
2、在输入参数中包含地图范围和图像尺寸是很有用的。
3、输出是必须有单一一个输出结果是FeatureCollection类型或者GridCoverage2D类型。
4、invertQuery和invertGridGeometry方法为可选项,可以被提供来重写数据查询。
5、注意渲染变换代码的无状态问题。