使用 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 把要素繪製出來。這樣當前操作用戶可以第一時間看到目前的狀況,其他用戶連接進來的時候,看到的已 經是最新的地圖緩存了。