大學畢業工做已經兩年了,上學那會就很想研讀一份開源GIS的源碼,苦於本身知識和理解有限,而市面上也沒有什麼由淺入深講解開源gis原理的書籍,大多都是開源項目簡介以及項目的簡單應用。對於初級程序員想讀懂一個成熟的GIS開源項目的困難點主要有三點,1.開發經驗和gis原理理解不足。2.一個開源項目是一個按部就班的過程,若是不是從項目很小的時候跟進,等項目持續更新幾年後邏輯就會變得很複雜,小白很難經過Dbug調試來理清楚整個項目的原理。3.GIS屬於小行業,國內基本沒有國人主導的開源GIS項目,這方面的文章書籍比較欠缺。因此個人想法是經過重寫一個GIS項目,還原到項目的初始狀態,由淺入深人講解GIS原理,對於GIS開發從業者不只要知其然,更要知其因此然。瞭解GIS底層的開發技術提高本身的競爭力。git
個人文章思路,代碼上傳到個人github上面,github地址:https://github.com/HuHongYong/ATtuingMap,歡迎你們star 一下,每一篇文章對應的代碼生成released版本,方便後期找到文章對應的版本版本代碼,以下:程序員
本次開源項目的選擇是SharpMap,選擇這個項目的緣由主要有兩點,1.SharpMap是一套簡單易用的小型GIS平臺,核心代碼1萬多行,能夠用於開發桌面GIS應用和簡單的BS程序。支持多種GIS數據格式,支持空間查詢,可輸出精美的地圖。可瞭解GIS核心原理。2.開發語言.net,本人工做中使用的核心開發語言,並且如今.net core 3.0開始支持winform、wpf等windows桌面開發技術的跨平臺應用的開發,固然這個講原理就不使用.net core 了,等.net core桌面開發穩定之後能夠作一下跨平臺。github
本節500行編寫了GIS基本框架,開發環境是vs2017,使用.net framework4.5 ,實現了讀取shp點數據,並實現點數據的渲染,以及屏幕座標向空間座標轉換的基礎GIS功能。項目的核心結構以下:c#
1.Map 只包含一個Map類,是該GIS項目的核心類。windows
2.Geometries 全部點、線、面等幾何類的定義和幾何類的方法,這些類都繼承了抽象類Geometry,有利於與幾何類的擴展,移植,複用。數組
3.Layers 地圖的圖層類,該類的核心成員就是下面的四、五、6,構成了一個圖層對象的總體。微信
4.Rendering 提供了用於繪製空間數據的功能,使用c#System.Drawing.Graphics進行渲染繪製。框架
5.Styles 提供圖層的樣式,例如點的大小、顏色等。函數
6.Data 數據的讀取接口。例如shapefile文件的讀取。工具
7.Utilities 工具類,一些公用的方法。
shape文件由ESRI開發,一個ESRI(Environmental Systems Research Institute)的shape文件包括一個主文件,一個索引文件,和一個dBASE表。其中主文件的後綴就是.shp。
.shp文件包含文件頭,數據記錄兩部分。文件頭是固定的100個字節組成,結構以下,數據記錄包含着座標記錄信息是文件的數據核心。
.shp文件頭解析,使用的是c# BinaryReader讀取字節流,brShapeFile.BaseStream.Seek(36, 0)這個方法是指定位到第36個字節,接着brShapeFile.ReadInt32()是指從第37個字節開始讀4個字節。
/// <summary> /// 讀取和解析.shp文件的文件頭 /// </summary> private void ParseHeader (){ fsShapeFile = new FileStream(_Filename, FileMode.Open, FileAccess.Read); brShapeFile = new BinaryReader(fsShapeFile, Encoding.Unicode); brShapeFile.BaseStream.Seek(0, 0); //讀取四個字節,檢查文件頭 if (brShapeFile.ReadInt32() != 170328064) { //文件真實的編碼是9994, //170328064的16進製爲0x0a27,交換字節順序後就是0x270a,十進制就是9994了 throw (new ApplicationException("無效的Shapefile文件 (.shp)")); } //五個沒有被使用的int32整數 brShapeFile.BaseStream.Seek(24, 0); //獲取文件長度,包括文件頭 _FileSize = 2 * SwapByteOrder(brShapeFile.ReadInt32()); //讀取幾何類型 _ShapeType = (ShapeTypeEnum)brShapeFile.ReadInt32(); //讀取數據的外包矩形 brShapeFile.BaseStream.Seek(36, 0); _Envelope = new BoundingBox(brShapeFile.ReadDouble(), brShapeFile.ReadDouble(), brShapeFile.ReadDouble(), brShapeFile.ReadDouble()); //經過.shp文件獲取數據條數 // 跳過文件頭讀取 brShapeFile.BaseStream.Seek(100, 0); // 幾何數據記錄開始位置 long offset = 100; //遍歷數據創建功能包含在數據文件的數量 while (offset < _FileSize) { ++_FeatureCount; brShapeFile.BaseStream.Seek(offset + 4, 0); //跳過長度 int data_length = 2 * SwapByteOrder(brShapeFile.ReadInt32()); if ((offset + data_length) > _FileSize) { --_FeatureCount; } offset += data_length; // 添加記錄數據長度 offset += 8; // +添加每條數據記錄頭的大小 } _OffsetOfRecord = new int[_FeatureCount]; //brShapeFile.Close(); //fsShapeFile.Close(); }
讀取數據記錄,因爲本次不讀取.shx索引文件,咱們讀取數據生成一個索引數組,方便咱們讀取數據,經過索引值來讀取對應得點數據,代碼以下:
/// <summary> /// 生成矢量文件索引 /// </summary> private void PopulateIndexes() { //記錄當前位置的指針 long old_position = brShapeFile.BaseStream.Position; //跳過文件頭 brShapeFile.BaseStream.Seek(100, 0); //矢量文件記錄開始位置 long offset = 100; for (int x = 0; x < _FeatureCount; ++x) { _OffsetOfRecord[x] = (int)offset; brShapeFile.BaseStream.Seek(offset + 4, 0); //跳過的長度 int data_length = 2 * SwapByteOrder(brShapeFile.ReadInt32()); offset += data_length; // 添加記錄數據長度 offset += 8; // +添加每條數據記錄頭的大小 } // 返回指針的原始位置 brShapeFile.BaseStream.Seek(old_position, 0); }
/// <summary> /// 從.shp文件中讀取並解析幾何對象 /// </summary> /// <param name="oid"></param> /// <returns></returns> private Geometry ReadGeometry(int oid) { brShapeFile.BaseStream.Seek(_OffsetOfRecord[oid] + 8, 0); ShapeTypeEnum type = (ShapeTypeEnum)brShapeFile.ReadInt32(); //Shape type if (type== ShapeTypeEnum.Null) { return null; } if (type==ShapeTypeEnum.Point) { return new Point(brShapeFile.ReadDouble(), brShapeFile.ReadDouble()); } else { throw (new ApplicationException("Shapefile 文件類型 " + _ShapeType.ToString() + " 不支持")); } }
這一節主要講一下如何全圖展示全部數據。全圖顯示存在兩種狀態
1.空間座標外包矩形寬高比(寬/高)>畫布屏幕座標寬高比(寬/高) 以下圖
該狀態下展現就以座標點左右方向佔滿整個寬度,真實座標點轉爲屏幕座標點的函數。
/// <summary> /// 空間座標轉屏幕座標 /// </summary> /// <param name="p"></param> /// <param name="map"></param> /// <returns></returns> public static PointF WorldtoMap(Point p, Map map) { PointF result = new System.Drawing.Point(); //在該種狀況下,求出的height值爲,除去上下空白座標的高度,如上圖所示 double height = (map.Zoom * map.Size.Height) / map.Size.Width; double left = map.Center.X - map.Zoom * 0.5; double top = map.Center.Y + height * 0.5 * map.PixelAspectRatio; result.X = (float)((p.X - left) / map.PixelWidth); result.Y = (float)((top - p.Y) / map.PixelHeight); return result; }
2.空間座標外包矩形寬高比(寬/高)<畫布屏幕座標寬高比(寬/高) 以下圖
該狀態下展現就以座標點左右方向佔滿整個寬度,真實座標點轉爲屏幕座標點的函數。
/// <summary> /// 空間座標轉屏幕座標 /// </summary> /// <param name="p"></param> /// <param name="map"></param> /// <returns></returns> public static PointF WorldtoMap(Point p, Map map) { PointF result = new System.Drawing.Point(); //在該種狀況下,求出的height值爲,整個屏幕高度,如上圖所示 double height = (map.Zoom * map.Size.Height) / map.Size.Width; double left = map.Center.X - map.Zoom * 0.5; double top = map.Center.Y + height * 0.5 * map.PixelAspectRatio; result.X = (float)((p.X - left) / map.PixelWidth); result.Y = (float)((top - p.Y) / map.PixelHeight); return result; }
使用c#System.Drawing.Graphics進行渲染繪製點。
/// <summary> /// 在地圖上繪製點 /// </summary> public static void DrawPoint(Graphics g, Point point, Brush b, float size, Map map) { if (point == null) return; PointF pp = Transform.WorldtoMap(point, map); Matrix startingTransform = g.Transform; float width = size; float height = size; g.FillEllipse(b, (int)pp.X - width / 2, (int)pp.Y - height / 2 , width, height); }
第一節簡單的講了一下.shp數據的讀取,以及全圖狀況下空間座標與屏幕座標相互轉換。固然只講了核心功能,具體不明白的能夠調試代碼進行本身探索。下一節主要講一下GIS平移縮放問題核心功能。
github項目地址:https://github.com/HuHongYong/ATtuingMap
做者:ATtuing
出處:http://www.cnblogs.com/ATtuing
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。