上篇對SharpMap的分析文章裡,一個重點就是地圖的渲染流程和機制,這裡就不專門介紹這個問題了 ,只是就坐標的一些細節問題分析一下。
地圖都有一個單位(Unit)、比例尺(Zoom)的概念,還有投影的問題。對於Unit,一般使用Km、m或 者經緯度來表示。一幅地圖,在其所有數據的Unit和投影都一致的情況下,在繪制這些對象到地圖時,就 要根據比例尺進行坐標轉換;同時,在進行地圖的縮放、移動、拾取等操作的時候,鼠標的坐標是桌面的 坐標系統,也要轉換到地圖坐標系統(一般稱為World Coordinates System,簡稱WCS)。
首先來看比例(Zoom)在Map類裡的定義:
private double _Zoom; public double Zoom { get { return _Zoom; } set { if (value < _MinimumZoom) _Zoom = _MinimumZoom; else if (value > _MaximumZoom) _Zoom = _MaximumZoom; else _Zoom = value; if (MapViewOnChange != null) MapViewOnChange(); } }
這個Zoom表示使用地圖Unit表示的地圖寬度。例如地圖單位是Km,那麼如果目前地圖的寬度是500Km, Zoom就是500。這個和Mapinfo中Zoom的概念是一致的。
那麼在渲染的時候,就要對所有對象進行坐標轉換,轉換為要渲染的圖片的坐標系統,然後調用GDI+ 進行渲染。
對於對象的渲染,定義在Layer的名稱空間裡,在VectorLayer類的Render方法裡,根據Geometry對象 的層次依次遍歷各個對象,然後調用Rendering名稱空間的VectorRenderer的各個方法來渲染不同的點、 線、面等對象。
在渲染具體對象時,我們看到這些方法都調用了一個TransformToImage的方法,而這個方法定義在不 同的Geometry名稱空間的不同類裡,目的是由空間對象經過坐標變換後返回一個.net的繪圖對象。
我們把這個流程整理如下:
Map對象GetMap方法→GetMap方法遍歷其Layer,調用Layer的Render方法→各個Layer開始渲染自己, 對於柵格和WMS層,返回范圍內的圖片即可,主要是VectorLayer的渲染→VectorLayer調用自己 DataSource Provider的GetFeaturesInView方法,返回范圍內的對象到一個列表→依次遍歷列表的各個對 象,調用Rendering名稱空間的VectorRenderer的各個方法來渲染不同的點、線、面等對象→渲染這些對 象前,調用幾何對象的TransformToImage方法,返回一個.net的繪圖對象→GDI+根據Style渲染
在最後一步,各個對象調用的TransformToImage方法其實是逐次轉換這個對象的各個點。而點的坐標 轉換定義在Utilities.Transform下,有2個方法:
public static System.Drawing.PointF WorldtoMap(SharpMap.Geometries.Point p, SharpMap.Map map)
和
public static SharpMap.Geometries.Point MapToWorld(System.Drawing.PointF p, SharpMap.Map map)
分別轉換WCS坐標到Image坐標和轉換Image坐標到WCS坐標。
這是轉換代碼:
System.Drawing.PointF result = new System.Drawing.Point(); double Height = (map.Zoom * map.Size.Height) / map.Size.Width; double left = map.Center.X - map.Zoom/2; double top = map.Center.Y + Height/2; double pxSize = map.Zoom / map.Size.Width; result.X = (float)Math.Round(((p.X - left) / pxSize), 0); result.Y = (float)Math.Round(((top - p.Y) / pxSize), 0); return result;
left和top表示當前地圖的左上角坐標,Height是高度,需要通過Zoom和Height來換算一下,也許寫作 map.Zoom * (map.Size.Height / map.Size.Width)更好理解一點。pxSize相當於在最終的圖片上的一個 單位相當於WCS的多少單位,這樣,(p.X - left) / pxSize就是橫坐標,縱坐標由於圖片y軸相反,因此 是(top - p.Y) / pxSize。有過Dos或者Windows圖形編程經驗的人對於這樣的代碼應該是非常熟悉。
這段代碼的計算left、height、top、pxSize這些參數的語句其實應該在Map每次更改Zoom時計算比較 好,因為這個函數會被調用非常多次(每個點都要轉換坐標),不過這些都是優化的話了,可以放在系統 穩定以後。
不同的地圖單位和投影下的地圖渲染操作
SharpMap目前還沒有Unit的問題,在Map和Layer裡也沒有定義Unit,投影在新版0.90的beta裡有部分 代碼。象ArcGIS和Mapinfo都支持動態投影,也就是對Map定義一個Unit和投影,對不同的Layer定義一個 投影和Unit,他可以自動的轉換這些Layer的Unit到地圖,然後疊加顯示。
這樣的話,在顯示(渲染)時,就需要對所有對象都要進行投影和尺度變換。雖然似乎在打開數據的 時候進行轉換,但是由於對於空間數據庫,一次打開所有數據已經越來越不可能,而且對數據作分析的時 候,如果數據打開時轉換了數據的Unit,那麼分析結果也會出現問題。因此,這類實現應該是在地圖渲染 時進行投影和變換。