块和分块服务
本章中,首先将介绍块和分块服务的基本概念和原理,然后介绍如何在MapGuide Studio中通过用户界面来配置图层使用分块服务和如何通过MapGuide分块服务API来创建和使用块。
1.1 块
对于AJAX Viewer和Fusion Viewer,MapGuide服务器都是将地图渲染为一张图像发送给客户端的,每次当用户在浏览器中缩放或平移地图的时候,通常都会要求MapGuide服务器重新发送地图到客户端。试想一下,如果每次都重新渲染地图,这将是一个不小的开销。那么,是否可以将已经渲染好的地图缓存下来,当客户端浏览地图的时候直接从缓存中取出那些已经渲染好的地图,从而降低MapGuide服务器的开销。这个方案听起来是可行的,而且在许多的商业软件都使用了这样的技术,MapGuide也采用了类似的技术。
那么,MapGuide是如何缓存已经渲染为图像的地图的呢?在介绍MapGuide的缓存技术之前,让我们先看看它必须解决的问题。
问题1:在大部分情况下,客户端需要的只是当前视图中需要显示部分的地图,而不是整个地图,如何只把当前视图对应的地图图像返回给客户端?
问题2:地图中图层不同的可见性设置会导致不同的地图渲染结果,如何缓存这些不同的地图图像?
问题3:在不同的地图比例尺下,地图需要被渲染为不同分辨率的图像,以保证总能获得清晰的地图。地图比例尺是一个连续的值,所以不可能在所有的地图比例尺为地图创建缓存,那么又该如何解决这个问题呢?
1.1.1 块
对于问题1,MapGuide采用的办法是将整个地图分割为固定大小的图像块进行缓存,这样服务器只需要将客户端当前视图对应的图像块返回给客户端,而客户端可以立即显示返回的块,无需等待所有块全部返回才显示地图。
可以通过MapGuide服务器配置文件ServerConfig.ini中TileServiceProperties部分的属性设置块的大小, DefaultTileSizeX属性用于设置块的宽度,DefaultTileSizeY属性用于设置块的高度,它们的单位都为像素,默认值都为300。
1.1.2 基地图、基层和基层组
对于问题2,MapGuide引入了基地图(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的名称。假设地图的资源ID为Library://Sample/Shanghai.MapDefinition,那么对应的文件基路径为“Sample_Shanghai”。如果是Session资源库,那么文件基路径等于Session ID加下划线、加地图资源ID的路径加下划线和地图资源ID的名称。假设地图资源ID为Session: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元素的值,查找与当前请求的比例尺最为接近的第n个FiniteDisplayScale元素 (从0开始计数),n这个值就是当前请求对应的基地图比例尺索引。
给定如下的地图定义,假设Viewer发送请求查看比例尺为1:2830的地图,我们可以看到当前请求的比例尺与第6个FiniteDisplayScale元素的值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["Non-Earth (Meter)",LOCAL_DATUM["Local Datum",0],UNIT["Meter", 1],AXIS["X",EAST],AXIS["Y",NORTH]]</CoordinateSystem> <Extents> <MinX>1880999.75</MinX> <MaxX>1899000.25</MaxX> <MinY>458999.75</MinY> <MaxY>463000.25</MaxY> </Extents> <BackgroundColor>ffffffff</BackgroundColor> <Metadata><MapDescription>Raster</MapDescription></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.ini中TileServiceProperties部分属性TileRowsPerFolder的值,将块的行号除以这个值然后取整,得到的值就是块所在的行组号。假设TileRowsPerFolder值为30,给定一个行号为50的块,那么它的行组号为1,行部分等于“R1”。
1.2.5 列
列部分等于英文字符“C”加块所在的列组。那么,如何确定一个块所在的列组呢?这取决于MapGuide服务器配置文件ServerConfig.ini中TileServiceProperties部分属性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为每个块的高度,他们使用的单位是块所对应地图空间参考系的单位。tileWidthMCS和tileHeightMCS的计算公式如下:
tileWidthMCS = tileWidth * metersPerPixel * scale / metersPerUnit;
tileHeightMCS = tileHeight * metersPerPixel * scale / metersPerUnit;
其中,scale为块所对应的比例尺,tileWidth和tileHeight为块的像素宽度和高度;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.ini中TilingServiceProperties部分用于配置分块服务,下表列出了所有可用的设置。
属性名称 |
描述 |
RenderOnly |
用于设定是否只是渲染Tile,而不将Tile缓存到服务器上,0代表假,1代表真。 |
TileCachePath |
用于指定Tile图像缓存的根目录。 |
TileColumnsPerFolder |
用于指定每个Tile列组文件夹中包含的列数。 |
TileRowsPerFolder |
用于指定每个Tile行组文件夹中包含的行数。 |
DefaultTileSizeX |
用于指定Tile的像素宽度。 |
DefaultTileSizeY |
用于指定Tile的像素高度。 |
ImageFormat |
用于指定生成的Tile的格式,它的值可以为PNG、PNG8、GIF或JPG。 |
1.4.2 获取块
如下两个方法GetTile(…)用于为获取块,mapDefinition用于指定一个地图定义的资源ID,map用于指定一个地图实例,baseMapLayerGroupName用于指定地图中基层组的名称,tileColumn和tileRow分别用于指定块所在的行组号和列组号,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