块和分块服务

本章中,首先将介绍块和分块服务的基本概念和原理,然后介绍如何在MapGuide Studio中通过用户界面来配置图层使用分块服务和如何通过MapGuide分块服务API来创建和使用块。

1.1

对于AJAX ViewerFusion ViewerMapGuide服务器都是将地图渲染为一张图像发送给客户端的,每次当用户在浏览器中缩放或平移地图的时候,通常都会要求MapGuide服务器重新发送地图到客户端。试想一下,如果每次都重新渲染地图,这将是一个不小的开销。那么,是否可以将已经渲染好的地图缓存下来,当客户端浏览地图的时候直接从缓存中取出那些已经渲染好的地图,从而降低MapGuide服务器的开销。这个方案听起来是可行的,而且在许多的商业软件都使用了这样的技术,MapGuide也采用了类似的技术。

那么,MapGuide是如何缓存已经渲染为图像的地图的呢?在介绍MapGuide的缓存技术之前,让我们先看看它必须解决的问题。

问题1在大部分情况下,客户端需要的只是当前视图中需要显示部分的地图,而不是整个地图,如何只把当前视图对应的地图图像返回给客户端?

问题2地图中图层不同的可见性设置会导致不同的地图渲染结果,如何缓存这些不同的地图图像?

问题3在不同的地图比例尺下,地图需要被渲染为不同分辨率的图像,以保证总能获得清晰的地图。地图比例尺是一个连续的值,所以不可能在所有的地图比例尺为地图创建缓存,那么又该如何解决这个问题呢?

1.1.1

对于问题1MapGuide采用的办法是将整个地图分割为固定大小的图像块进行缓存,这样服务器只需要将客户端当前视图对应的图像块返回给客户端,而客户端可以立即显示返回的块,无需等待所有块全部返回才显示地图。

可以通过MapGuide服务器配置文件ServerConfig.iniTileServiceProperties部分的属性设置块的大小, DefaultTileSizeX属性用于设置块的宽度,DefaultTileSizeY属性用于设置块的高度,它们的单位都为像素,默认值都为300

1.1.2 基地图、基层和基层组

对于问题2MapGuide引入了基地图(Bae Map)、基层(Base Layer)和基层组(Base Layer Group)的概念来解决这个问题。与地图的概念类似,基地图也是由图层组组成,每个图层组由若干个图层组成,我们将基地图中的图层组和图层分别称为基层组和基层。但是,基地图与地图并不是平行的概念,基地图只是地图的组成部分,图6-1显示了地图定义的Schema,可以看到每个地图可以包含一个基地图BaseMapDefinition。与普通地图不同的是:

l 基地图只能由基层组组成,不能直接包含一个基层。所以,在图6-1中可以看到,BaseMapDefinition只包含了BaseMapLayerGroup

l 基层没有可见性设置,只有基层组具有可见性设置,基层的可见性由其所属的基层组决定。所以,在图6-1中可以看到,BaseMapLayerGroup包含Visible元素,而BaseMapLayer没有Visible元素。

块和分块服务

6-1 地图定义的Schema

由于只有基层组具有可见性设置,所以基础图层组的所有图层是在一起渲染。对于不同的基层组,它们被渲染为不同的图像块,缓存在不同的目录下。这样,就可以解决问题2

假设基地图中包含了两个基层组,一个基层组为可见,一个为不可见,当使用Viewer浏览地图时,只需要将可见基层组缓存的块返回给客户端;如果两个基层组都可见,那么两个基层组缓存的块都会返回给客户端,如果高层的基层没有透明区域,底层的层会被高层的基层隐藏。

1.1.3 基地图比例尺

基地图具有一组预定义的比例尺,它们由图6-1中的FiniteDisplayScale元素所定义,MapGuide只将这些比例尺下的地图图像缓存下来。当Viewer发送请求来查看某个比例尺的地图时,MapGuide会在预定义的比例尺中查找最接近当前请求的比例尺,返回此比例尺下缓存的块,这样就解决了问题3

1.1.4 创建基地图

MapGuide Studio提供了如图6-2所示的用户界面用于创建基地图,使用步骤如下:

1) MapGuide Studio中打开要添加基地图的地图。

2) Base Layers For Smooth Navigation面板中Layers By Group and Drawing Order下面,点击Base Layers(0 Groups, 0 Layers)

3) 点击Create A New Group按钮,你创建任意多个基层组,但必须至少创建一个基层组,基层组之间不可以嵌套。

4) 添加图层到基层组,不要将已经在加入地图中的图层加入基层组。

5) Set Fixed Scales for Incremental Zooming面板的Total number of scales文本框中输入比例尺的数目,Studio会自动创建一系列平均间隔的比例尺,并将它们显示在Generated scale list列表框中。

6) 保存对当前地图的修改。

块和分块服务

6-2 创建基地图的用户界面

1.2 块的缓存位置

块在MapGuide服务器中的缓存路径由如下五部分组成:

l 文件基路径

l 基地图比例尺索引

l 基层组

l

l

1.2.1 文件基路径

如果是Library资源库,那么文件基路径等于地图资源ID的路径加下划线和地图资源ID的名称。假设地图的资源IDLibrary://Sample/Shanghai.MapDefinition,那么对应的文件基路径为“Sample_Shanghai”。如果是Session资源库,那么文件基路径等于Session ID加下划线、加地图资源ID的路径加下划线和地图资源ID的名称。假设地图资源IDSession:70ea89fe-0000-1000-8000-005056c00008_en//Sample/Shanghai.MapDefinition

那么对应的BasePath为“70ea89fe-0000-1000-8000-005056c00008_Sample_Shanghai”。

1.2.2 基地图比例尺索引

基地图比例尺索引部分等于英文字符“S”加当前基地图的比例尺索引,例如“S0”。 那么,如何确定基地图的比例尺索引呢?当Viewer发送请求来查看某个比例尺的地图时,MapGuide会遍历基地图定义中FiniteDisplayScale元素的值,查找与当前请求的比例尺最为接近的第nFiniteDisplayScale元素 (0开始计数)n这个值就是当前请求对应的基地图比例尺索引。

给定如下的地图定义,假设Viewer发送请求查看比例尺为12830的地图,我们可以看到当前请求的比例尺与第6FiniteDisplayScale元素的值2828.125最为接近,所以当前请求对应的基地图比例尺索引6

<?xml version="1.0" encoding="UTF-8"?>

<MapDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="MapDefinition-1.0.0.xsd">

<Name>New Map</Name>

<CoordinateSystem>LOCAL_CS[&quot;Non-Earth (Meter)&quot;,LOCAL_DATUM[&quot;Local Datum&quot;,0],UNIT[&quot;Meter&quot;, 1],AXIS[&quot;X&quot;,EAST],AXIS[&quot;Y&quot;,NORTH]]</CoordinateSystem>

<Extents>

<MinX>1880999.75</MinX>

<MaxX>1899000.25</MaxX>

<MinY>458999.75</MinY>

<MaxY>463000.25</MaxY>

</Extents>

<BackgroundColor>ffffffff</BackgroundColor>

<Metadata>&lt;MapDescription&gt;Raster&lt;/MapDescription&gt;</Metadata>

<BaseMapDefinition>

<FiniteDisplayScale>0.690460205078125</FiniteDisplayScale>

<FiniteDisplayScale>2.7618399999999999</FiniteDisplayScale>

<FiniteDisplayScale>11.047359999999999</FiniteDisplayScale>

<FiniteDisplayScale>44.189450000000001</FiniteDisplayScale>

<FiniteDisplayScale>176.75781000000001</FiniteDisplayScale>

<FiniteDisplayScale>707.03125</FiniteDisplayScale>

<FiniteDisplayScale>2828.125</FiniteDisplayScale>

<FiniteDisplayScale>11312.5</FiniteDisplayScale>

<FiniteDisplayScale>45250</FiniteDisplayScale>

<FiniteDisplayScale>181000</FiniteDisplayScale>

<BaseMapLayerGroup>

<Name>Raster</Name>

<Visible>true</Visible>

<ShowInLegend>true</ShowInLegend>

<ExpandInLegend>true</ExpandInLegend>

<LegendLabel></LegendLabel>

<BaseMapLayer>

<Name>g-07</Name>

<ResourceId>Library://Layers/g-07.LayerDefinition</ResourceId>

<Selectable>true</Selectable>

<ShowInLegend>true</ShowInLegend>

<LegendLabel>g-07</LegendLabel>

<ExpandInLegend>true</ExpandInLegend>

</BaseMapLayer>

</BaseMapLayerGroup>

</BaseMapDefinition>

</MapDefinition>

1.2.3 基层组

基层组部分等于基层组的名称。

1.2.4

行部分等于英文字符“S”加块所在的行组号。那么,如何确定一个块所在的行组呢?这取决于MapGuide服务器配置文件ServerConfig.iniTileServiceProperties部分属性TileRowsPerFolder的值,将块的行号除以这个值然后取整,得到的值就是块所在的行组号。假设TileRowsPerFolder值为30,给定一个行号为50的块,那么它的行组号为1,行部分等于“R1”。

1.2.5

列部分等于英文字符“C”加块所在的列组。那么,如何确定一个块所在的列组呢?这取决于MapGuide服务器配置文件ServerConfig.iniTileServiceProperties部分属TileColumnsPerFolder的值,将块的列号除以这个值然后取整,得到的值就是块所在的列组号。假设TileColumnsPerFolder值为30,给定一个列号为50的块,那么它的列组号为1,列部分等于“C1”。

1.3 计算块的坐标

给定一个块的行号tileColumn和列号tileRow,块左下角坐标(tileMinX, tileMaxX)和右上角坐标(tileMinY, tileMaxY)的计算公式如下:

tileMinX = mapMinX + tileColumn * tileWidthMCS;

tileMaxX = mapMinX + (tileColumn+1) * tileWidthMCS;

tileMinY = mapMaxY - (tileRow +1) * tileHeightMCS;

tileMaxY = mapMaxY - tileRow * tileHeightMCS;

其中,mapMinX为块对应地图左下角的X坐标值,mapMaxY为右上角的Y坐标值,tileWidthMCS为的每个块的宽度,tileHeightMCS为每个块的高度,他们使用的单位是块所对应地图空间参考系的单位。tileWidthMCStileHeightMCS的计算公式如下:

tileWidthMCS = tileWidth * metersPerPixel * scale / metersPerUnit;

tileHeightMCS = tileHeight * metersPerPixel * scale / metersPerUnit;

其中,scale为块所对应的比例尺,tileWidthtileHeight为块的像素宽度和高度;metersPerPixel 为像素与米的转换系数,MapGuide总是使用96 DPI,即96个像素等于一英寸;meterPerUnit为块所对应地图空间参考系单位与单位米的转换系数。

开源版MapGuide中的方法MgServerRenderingService::RenderTile(...)正好实现了计算一个块坐标值的逻辑,那么我们就以这个方法为例展示一下如何计算一个块的坐标值。

MgByteReader* MgServerRenderingService::RenderTile(MgMap* map,

CREFSTRING baseMapLayerGroupName,

INT32 tileColumn,

INT32 tileRow)

{

......

// 获取地图的比例尺索引

double scale = map->GetViewScale();

INT32 scaleIndex = map->FindNearestFiniteDisplayScaleIndex(scale);

......

// 按照layer group的名称获取layer group对象

Ptr<MgLayerGroupCollection> layerGroups = map->GetLayerGroups();

Ptr<MgLayerGroup> baseGroup = layerGroups->GetItem(baseMapLayerGroupName);

......

// 根据比例尺索引获取对应比例尺的值

scale = map->GetFiniteDisplayScaleAt(scaleIndex);

// 确保当前地图使用了与Tile相同的DPI

map->SetDisplayDpi(MgTileParameters::tileDPI);

// 获取地图的范围,从而得到地图左上角和右下角的坐标值

Ptr<MgEnvelope> mapExtent = map->GetMapExtent();

Ptr<MgCoordinate> pt00 = mapExtent->GetLowerLeftCoordinate();

Ptr<MgCoordinate> pt11 = mapExtent->GetUpperRightCoordinate();

double mapMinX = rs_min(pt00->GetX(), pt11->GetX());

double mapMaxX = rs_max(pt00->GetX(), pt11->GetX());

double mapMinY = rs_min(pt00->GetY(), pt11->GetY());

double mapMaxY = rs_max(pt00->GetY(), pt11->GetY());

// 获得Tile所对应地图空间参考系单位与单位米的转换系数

double metersPerUnit = map->GetMetersPerUnit();

// 计算得到像素与单位米的转换系数

double metersPerPixel = METERS_PER_INCH / MgTileParameters::tileDPI;

double tileWidthMCS = (double)MgTileParameters::tileWidth *

metersPerPixel * scale / metersPerUnit;

double tileHeightMCS = (double)MgTileParameters::tileHeight *

metersPerPixel * scale / metersPerUnit;

// 计算得到Tile左下角和右上角的坐标

double tileMinX = mapMinX + (double)(tileColumn ) * tileWidthMCS;

double tileMaxX = mapMinX + (double)(tileColumn+1) * tileWidthMCS;

double tileMinY = mapMaxY - (double)(tileRow +1) * tileHeightMCS;

double tileMaxY = mapMaxY - (double)(tileRow ) * tileHeightMCS;

......

}

1.4 分块服务

分块服务是用来管理分块的一种服务,它提供了获取块和清除缓存块的功能。

1.4.1 设置分块服务

MapGuide服务器配置文件ServerConfig.iniTilingServiceProperties部分用于配置分块服务,下表列出了所有可用的设置。

属性名称

描述

RenderOnly

用于设定是否只是渲染Tile,而不将Tile缓存到服务器上,0代表假,1代表真。

TileCachePath

用于指定Tile图像缓存的根目录。

TileColumnsPerFolder

用于指定每个Tile列组文件夹中包含的列数。

TileRowsPerFolder

用于指定每个Tile行组文件夹中包含的行数。

DefaultTileSizeX

用于指定Tile的像素宽度。

DefaultTileSizeY

用于指定Tile的像素高度。

ImageFormat

用于指定生成的Tile的格式,它的值可以为PNGPNG8GIFJPG

1.4.2 获取块

如下两个方法GetTile(…)用于为获取块,mapDefinition用于指定一个地图定义的资源IDmap用于指定一个地图实例,baseMapLayerGroupName用于指定地图中基层组的名称,tileColumntileRow分别用于指定块所在的行组号和列组号,scaleIndex用于指定块的比例尺索引。

MgByteReader GetTile(MgResourceIdentifier mapDefinition,

String baseMapLayerGroupName,

int tileColumn, int tileRow, int scaleIndex);

MgByteReader GetTile(MgMap map,

String baseMapLayerGroupName,

int tileColumn, int tileRow, int scale