zoukankan      html  css  js  c++  java
  • 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)表示,后面依然是一个8位16进制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 originPT = 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;

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


    source =serviceOutputDir + "\\" + source;

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

    1.
    要注意的几个地方
    1)
    这种方法更新瓦片,设计到修改服务器端文件的操作,因此必须要给地图缓存所在的文件夹设定IIS用户可控制的权限(NTFS磁盘格式下必须设定)
    2)
    这里只设计点的更新操作,由于一个点在某一比例下只对应一张瓦片,因此,尽管有十几个级别的缓存,也只是更换十几张图片,出图和替换的时间不长,不会占用服务器太长的时间去处理(包括连接地图服务大约20s左右)。当然,编程人员也可以通过设定缓存的级别,更新不同比例尺下的瓦片,这样更为实在些。如果设计到线和面的更新,那必须限制一下区域范围,因为区域范围很大的话,处理时间会很漫长(如用户不小心把一个与地市相仿的区域的面提交更新了,这就等同于把整个城市的缓存做一次)
    3)
    上面使用到了池化和非池化两个服务(这两个服务都是使用同一个mxd进行发布,这点值得注意),估计会有些疑惑。池化和非池化服务的区别可以参考一下esri的说明文档,这里不多作介绍。如果使用已经建立缓存的服务作为输出新图片的服务,那么出来的图片就根本没有改变,原原本本是原来的瓦片,因此必须创建一个池化的没有建立缓存的服务作为出图专用的服务,这样数据的更新可以马上反映到服务中去。
    4)
    缓存的更新操作是在添加要素并保存之后执行的。为了提高用户的体验,可以在后台开辟一个线程去执行瓦片的更新程序,前端使用graphicsLayer把要素绘制出来。这样当前操作用户可以第一时间看到目前的状况,其他用户连接进来的时候,看到的已经是最新的地图缓存了。


     

  • 相关阅读:
    2021.1.28 个人rating赛补题报告
    2021.1.23 个人rating赛补题报告
    2021.1.23 个人rating赛补题报告
    2020.12.14 个人训练赛补题报告
    2020.11.28 2020团体程序设计天梯赛补题报告
    2020.12.3 Codeforces Beta Round #73(Div2)补题报告
    Xhorse VVDI Prog V5.0.6 is Ready for BCM2 Adapter
    Program 2021 Ford Bronco All Keys Lost using VVDI Key Tool Plus
    Xhorse VVDI Prog V5.0.4 Software Update in July 2021
    How to use Xhorse VVDI2 to Exchange BMW FEM/BDC Module?
  • 原文地址:https://www.cnblogs.com/ericgisser/p/gis_cache.html
Copyright © 2011-2022 走看看