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

發佈了13 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章