ArcGIS Server 動態生成緩存並替換瓦片(C#+AE)

使用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 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;
//把虛擬路徑轉換爲服務器磁盤驅動器對應的物理路徑,其中serviceOutputDir爲ags的默認輸出位置
source =serviceOutputDir + "\\" + source; 
System.IO.File.Copy(source, tileUrl, true);   //複製到瓦片對應目錄並將其替換

1. 要注意的幾個地方

1) 這種方法更新瓦片,設計到修改服務器端文件的操作,因此必須要給地圖緩存所在的文件夾設定IIS用戶可控制的權限(NTFS磁盤格式下必須設定)

2) 這裏只設計點的更新操作,由於一個點在某一比例下只對應一張瓦片,因此,儘管有十幾個級別的緩存,也只是更換十幾張圖片,出圖和替換的時間不長,不會佔用服務器太長的時間去處理(包括連接地圖服務大約20s左右)。當然,編程人員也可以通過設定緩存的級別,更新不同比例尺下的瓦片,這樣更爲實在些。如果設計到線和麪的更新,那必須限制一下區域範圍,因爲區域範圍很大的話,處理時間會很漫長(如用戶不小心把一個與地市相仿的區域的面提交更新了,這就等同於把整個城市的緩存做一次)

3) 上面使用到了池化和非池化兩個服務(這兩個服務都是使用同一個mxd進行發佈,這點值得注意),估計會有些疑惑。池化和非池化服務的區別可以參考一下esri的說明文檔,這裏不多作介紹。如果使用已經建立緩存的服務作爲輸出新圖片的服務,那麼出來的圖片就根本沒有改變,原原本本是原來的瓦片,因此必須創建一個池化的沒有建立緩存的服務作爲出圖專用的服務,這樣數據的更新可以馬上反映到服務中去。

4) 緩存的更新操作是在添加要素並保存之後執行的。爲了提高用戶的體驗,可以在後臺開闢一個線程去執行瓦片的更新程序,前端使用graphicsLayer把要素繪製出來。這樣當前操作用戶可以第一時間看到目前的狀況,其他用戶連接進來的時候,看到的已經是最新的地圖緩存了。

轉自:http://www.cnblogs.com/ericgisser/archive/2009/10/13/gis_cache.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章