ArcGIS Server 实时生成地图缓存

使用 ArcGIS Server 做地图发布,为了提升浏览性能,通常会使用现时比较流行的地图缓存技术(通俗的说法为“瓦片技 术”)。如目前的 MapABC GoogleMap 正是使用该技 术。

所谓的地图缓 存技术,就是按照一定的数学规则,把地图切成一定规格的图片保存到计算机硬盘里,当用户通过客户端浏览器访问地图服务时,服务器直接返回当前地图坐标区域 所对应的“瓦片”,从而达到降低服务器负担,提升地图浏览速度的效果。

地图缓存技术 一般针对相对稳定的数据,因为地图切为瓦片以后,以图片的形式存在,对于数据的变化(这里指的是数据的几何形状变化)不能及时的反应,这就是地图缓存技术 不足之处。要想地图的变化得到及时的反映,那就必须重建地图缓存。而重建地图缓存要视地图的区域范围和缓存的比例尺而定,时间为几分钟到几十个小时不等。 因此,缓存的管理是一件相对麻烦的事情。

对实时性要求 比较高的系统来说,一般不建议使用地图缓存技术。但地图缓存带来的性能的体验非常良好,因此可以在此基础上进行一些改动,使其适应地图的更新操作十分必 要。某些 WebGIS 系统由于涉及数据的编辑,数据更新频率较大,不适用缓存的方式发布,数据的实时性非常好,但地图的 浏览和刷新性能非常差(刷新性能与数据的大小和图层的渲染复杂度有关),大量占用服务器资源,多用户连接的时候导致服务器不稳定等。

经过反复的试 验,针对上述的需求,懒羊羊提出了以下的一种方案,以解决数据频繁变动和地图性能低下的问题。方案的基本思路:使用地图缓存技术对地图进行切片;编辑数据 的时候获取空间数据对应的瓦片(一张或者几张);计算这部分瓦片的地图范围,并在后台重新生成这个范围的地图图片;把新生成的图片替换这些旧有的瓦片。

具体的做法如下:

1.   创建一个非池化的服务,并生成地图缓存。这部分懒羊羊不作介绍。

2.   获取编辑的图形所对应的瓦片。一般来说,如果是点图形,对应的是一张 瓦片,线、面图形一个图形有可能落在多张瓦片上面。可以使用 ESRI.ArcGIS.ADF.ArcGISServer.TileCacheInfo 来获取瓦片的相关信息,但具 体落在那一张瓦片,那就必须了解地图切片原理。

1 ArcGIS Server 切片原 理与命名规则    设定一个原点作为地图 切片的起始点(默认是( 400 -400 ),这是个经纬度坐标,设 这个值可以把其他地区的数据连接进来,使不同服务的数据可以得到有效的拼接,有兴趣的同事可以研究一下),以一定的规格(长宽为 2 n 次方的像素)把地图切割成若干的 小图片,并以科学命名的方式存贮到计算机磁盘。命名的规则是各比例尺的图片放在名为 LXX 的 文件夹里面,第一个比例尺的文件夹名为 L00 ,第二个比例尺的问 L01 , 如此类推。比例尺文件夹(一下统称 L 文件夹)目录下还会有 R 开头的 文件夹, R 表示的 ROW ,当前比例尺的瓦片每一行 对应一个文件夹。 R 文件夹的命名方式是瓦片的行序列(用 rIndex 表示),把 rIndex 转为 8 16 进制,不足的在左边补 0 ,用代 码公式表示 FolderName = "R" + rIndex. ToString("x" ).PadLeft(8, '0' ) R 文件夹里面 保存的就是瓦片,瓦片的命名方式跟R 文件夹的命名方式相似,以字母C 开头,后面是瓦片在该行的列序号(用cIndex )表 示,后面依然是一个816 进制 FileName = "C" + rIndex. ToString("x" ).PadLeft(8, '0' )+ "."+ format.ToString()

2) 计算图形对应的瓦片(以点为例)

下面以加入一个点要素为例,说明如何去获取这个点对应的瓦片

首先获取地图服务的相关信息,其中 NotPooledServiceUrl 是字符串,对应当前服务的URL ,地图服务已切片

// 获取服务的相关信息

            ESRI.ArcGIS.ADF.ArcGISServer.MapServerProxy mapserver_dcom = new ESRI.ArcGIS.ADF.ArcGISServer.MapServerProxy (NotPooledServiceUrl);

            ESRI.ArcGIS.ADF.ArcGISServer.MapServerInfo mapi = mapserver_dcom.GetServerInfo(mapserver_dcom.GetDefaultMapName());

            String defaultMapName = mapserver_dcom.GetDefaultMapName();

            ESRI.ArcGIS.ADF.ArcGISServer.MapDescription pMapDescription = mapi.DefaultMapDescription;

            ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN mape = mapi.FullExtent as ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN ;

            ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN mapiExtent = (ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN )mapi.Extent;

 

然后获取这个这个地图服 务的瓦片信息

// 获取瓦片的图片格式

string cacheTileFormat = mapserver_dcom.GetTileImageInfo(defaultMapName).CacheTileFormat;

string imageType = cacheTileFormat.StartsWith("PNG" ) ? ".png" : ".jpg" ;

// 获取瓦片的相关信息

ESRI.ArcGIS.ADF.ArcGISServer.TileCacheInfo tCacheInfo = mapserver_dcom.GetTileCacheInfo(defaultMapName);

// 瓦片的原点(ags 默认值为(-400,400), 在切片的时候可以设定这个值)

PointN riginPT = tCacheInfo.TileOrigin as PointN ;

 

接着定义其他相关的信息

double envCenterX = pPoint.X;  //pPoint 为新加入的点要素的图形

double envCenterY = pPoint.Y;

ESRI.ArcGIS.ADF.ArcGISServer.LODInfo [] lodInfos = tCacheInfo.LODInfos;

 

int tColCenter, tRowCenter;              //pPoint 所在的瓦片对应的列、行

double tileWidth, tileHeight;            // 对应瓦片的长度和宽度(计算后为地 图单位)

double tileXMin, tileYMin;               //pPoint 所在的瓦片的左下角坐标

double tileXMax, tileYMax;               //pPoint 所在的瓦片的右上角坐标

 

通过计算找到这个点对应的 各级缓存的瓦片的路径(根据上述所说的切片原理和命名规则算出来)

foreach (ESRI.ArcGIS.ADF.ArcGISServer.LODInfo li in lodInfos)

{

        tileWidth = tCacheInfo.TileCols * li.Resolution;

        tColCenter = (int )Math .Floor((envCenterX - (double )originPT.X) / tileWidth);

        tileXMin = (double )originPT.X + (tColCenter * tileWidth);

        tileHeight = tCacheInfo.TileRows * li.Resolution;

        tRowCenter = (int )Math .Floor(((double )originPT.Y - envCenterY) / tileHeight);

        tileYMin = ((double )originPT.Y - (tRowCenter * tileHeight)) - tileHeight;

        tileXMax = tileXMin + tileWidth;

        tileYMax = tileYMin + tileHeight;

        string tUrl = cacheDir + "//Layers//_alllayers" + "//L" + li.LevelID.ToString().PadLeft(2, '0' )

         + "//R" + tRowCenter.ToString("x" ).PadLeft(8, '0' )

         + "//C" + tColCenter.ToString("x" ).PadLeft(8, '0' )

         + imageType;

         UpdateTile(tileXMin, tileYMin, tileXMax, tileYMax, tUrl, tCacheInfo, poolService)

         // 分别更新各个比例尺下的瓦片,其中poolService 是一个MapServerProxy

  }

 

计算瓦片行列值的公式完全 可以通过切片原理推出来

Column = Floor((Point of interest X – Tile origin X) / Ground width of a tile)

Row = Floor((Tile origin Y – Point of interest Y) / Ground height of a tile)

 

最后是通过生成新的图片去取代原来的瓦片, 以达到局部更新的效果(也就是上面的 UpdateTile 函数的工作 )。 实现的思路是通过传入单张瓦片对应的四个角的坐标去定义一个 Envelope ,输出这个Envelope 区域的图片,并替换掉对应url 的瓦 片。

ESRI.ArcGIS.ADF.ArcGISServer.MapServerInfo agsSoapMapServerInfo = mapserver_dcom.GetServerInfo(mapserver_dcom.GetDefaultMapName());

ESRI.ArcGIS.ADF.ArcGISServer.MapDescription agsSoapMapDescription = agsSoapMapServerInfo.DefaultMapDescription;

SRI.ArcGIS.ADF.ArcGISServer.EnvelopeN pEnv = new ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN ();

pEnv.XMin = xMin;

pEnv.YMin = yMin;

pEnv.XMax = xMax;

pEnv.YMax = yMax;

agsSoapMapDescription.MapArea.Extent = pEnv;   // 设定了出图区域

 

// 设定出图图 片的格式, dpi ,长宽等

ESRI.ArcGIS.ADF.ArcGISServer.ImageDescription agsImageDes = new ESRI.ArcGIS.ADF.ArcGISServer.ImageDescription ();

ESRI.ArcGIS.ADF.ArcGISServer.ImageType agsImageType = new ESRI.ArcGIS.ADF.ArcGISServer.ImageType ();

agsImageType.ImageFormat = ESRI.ArcGIS.ADF.ArcGISServer.esriImageFormat .esriImagePNG;

agsImageType.ImageReturnType = ESRI.ArcGIS.ADF.ArcGISServer.esriImageReturnType .esriImageReturnURL;

ESRI.ArcGIS.ADF.ArcGISServer.ImageDisplay agsImageDis = new ESRI.ArcGIS.ADF.ArcGISServer.ImageDisplay ();

agsImageDis.ImageDPI = tCacheInfo.DPI;

agsImageDis.ImageHeight = tCacheInfo.TileCols;

agsImageDis.ImageWidth = tCacheInfo.TileRows;

agsImageDes.ImageType = agsImageType;

agsImageDes.ImageDisplay = agsImageDis;

 

// 出图和替换瓦片

ESRI.ArcGIS.ADF.ArcGISServer.MapImage agsMapimage = mapserver_dcom.ExportMapImage(agsSoapMapDescription, agsImageDes);

 string httpUrl = agsMapimage.ImageURL;// 输出图 片的虚拟路径

 string imgName = httpUrl.Substring(httpUrl.Length - 44, 44);// 提取图片 的名称

 string source = imgName;

// 把虚拟路径转换为服务器磁盘驱动器对应的物理路径,其中serviceOutputDirags 的默认输出 位置

 source =serviceOutputDir + "//" + source; 

 System.IO.File .Copy(source, tileUrl, true );   // 复制到瓦 片对应目录并将其替换

 

1.   要注意的几个地方

1)   这种方法更新瓦片,设计到修改服务器端文件的操作,因此必须要给地图缓存所在的文件夹设定 IIS 用户可控制的权限( NTFS 磁 盘格式下必须设定)

2)   这里只设计点的更新操作,由于一个点在某一比例下只对应一张瓦片,因此,尽管有十几个级别的缓存,也 只是更换十几张图片,出图和替换的时间不长,不会占用服务器太长的时间去处理(包括连接地图服务大约 20s 左 右)。当然,编程人员也可以通过设定缓存的级别,更新不同比例尺下的瓦片,这样更为实在些。如果设计到线和面的更新,那必须限制一下区域范围,因为区域范 围很大的话,处理时间会很漫长(如用户不小心把一个与地市相仿的区域的面提交更新了,这就等同于把整个城市的缓存做一次)

3)   上面使用到了池化和非池化两个服务(这两个服务都是使用同一个 mxd 进行发布,这点值得注意),估计会有些疑惑。池化和非池化服务的区别可以参考一下 esri 的说明文档,这里不多作介绍。如果使用已经建立缓存的服务作为输出新图片的服务,那么出来的图片就根 本没有改变,原原本本是原来的瓦片,因此必须创建一个池化的没有建立缓存的服务作为出图专用的服务,这样数据的更新可以马上反映到服务中去。

4)   缓存的更新操作是在添加要素并保存之后执行的。为了提高用户的体验,可以在后台开辟一个线程去执行瓦 片的更新程序,前端使用 graphicsLayer 把要素绘制出来。这样当前操作用户可以第一时间看到目前的状况,其他用户连接进来的时候,看到的已 经是最新的地图缓存了。

 

 

本文章转自:http://www.gisall.com/html/65/25865-471.html

你可能感兴趣的:(server,String,服务器,url,图形,磁盘)