文章目錄
上篇對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,那麼分析結果也會出現問題。因此,這類實現應該是在地圖渲染時進行投影和變換。