SharpMap介绍及源码分析
本文发表于《3sNews新闻周刊》第一期,有删改,请勿转载。
SharpMap是一个基于.net 2.0使用C#开发的Map渲染类库,可以渲染各类GIS数据(目前支持ESRI Shape和PostGIS格式),可应用于桌面和Web程序。
其网址为:http://sharpmap.iter.dk/
SharpMap的发布许可(License)为GNU General Public License,开发者为Morten Nielsen(http://www.iter.dk/)。目前的稳定版本为0.8(9.0beta已发布),代码行数近10000行,实现了以下功能:
支持的数据格式:
PostGreSQL/PostGIS,ESRI Shapefile
支持WMS layers
支持ECW 和 JPEG2000 栅格数据格式
Windows Forms 控件,可以移动和缩放
通过HttpHandler支持ASP.net程序
点、线、多边形、多点、多线和多多边形等几何类型
几何集合(GeometryCollections)等OpenGIS Simple Features Specification
可通过Data Providers(增加数据类型支持)、Layer Types(增加层类型)和Geometry Types等扩展
图形使用GDI+渲染,支持anti-aliased等
专题图
可以看出,SharpMap目前可以算是一个实现了最基本功能的GIS系统,但一些很重要的功能,例如投影,比例尺,空间分析,图形的属性信息,查询检索等等,还没有或者还在开发中。一个好消息是,作者在SharpMap的网站写到:Diego Guidi(NetTopologySuite的开发者)已经创建了一个SharpMap和NTS之间的一个连接,这样,就可以在SharpMap中使用NTS的空间变换、缓冲区等功能。
笔者之所以在这里分析ShrapMap,出于以下原因:
SharpMap足够小(小于10000行),且具备了一个GIS软件的基本功能,容易下手;
基于.net和C#开发;
开放源码(不开放就没有办法分析,废话);
SharpMap还在开发中,可以通过跟踪其源码学习提高。
一项技术或者一个工具,知其然和知其所以然,对于应用的深度和熟练程度还是具有很大的影响,特别是程序开发。分析SharpMap,不一定是要使用SharpMap,是希望通过分析SharpMap,可以了解一个GIS系统的纵剖面,从而可以更好的进行GIS的应用和开发。
源码结构
以下是SharpMap在VS 2005下的Class视图和Solution视图,可以看出SharpMap由SharpMap和其他14个次级名称空间组成,其中SharpMap名称空间下的Map类为这个系统的核心所在。
Map类,位于SharpMap命名空间下,通过创建Map对象的实例来生成地图。Map对象由包含Layer对象组成Layers集合,通过GetMap方法来Render地图。
Converts名称空间,提供数据转换服务。
Forms名称空间,包含MapImage控件,一个简单的User Control(用户控件),封装了Map类,用于Windows Form编程。
Geometries名称空间,包括了SharpMap要使用到的各种几何类及其接口类,例如点、线、面等类。
是SharpMap的基础之一,所有几何对象都继承自Geometry这个抽象类,其中定义了几何对象应该具备的公共操作,例如大小、ID、外接矩阵、几何运算等等。
Layers名称空间,包括了ILayer接口,Layer集合类等,代表地图的图层。
Layer是一个抽象类,实现了ILayer接口,Layer目前有3个子类,分别是VectorLayer、LabelLayer和WmsLayer,分别代3种不同数据类型的图层。
Providers名称空间,包括了IProvider接口和Shape文件、PostGIS数据的读取实现。该名称空间为SharpMap提供数据读(写)支持,通过面向接口的设计,可以比较容易的增加各类数据格式。
Rendering名称空间,目前包括矢量渲染器类和几个专题图渲染器类,该类可以将几何对象根据其Style设置渲染为一个System.Drawing.Graphics对象。
Styles名称空间,该名称空间主要提供了图层的样式设置类,例如线样式、点样式、填充样式等.
Utilities名称空间包括Algorithms类(目前仅实现了一个方法);Providers类,是Provider的一个Helper,应用了反射机制;Surrogates主要用于系统的Pen和Brush的序列化;Transform提供了从图片坐标到地理坐标的互相变换,也即桌面GIS的二次开发中经常使用的屏幕坐标和地理坐标的转换,主要用于地图的渲染、交互操作等。
Utilities.SpatialIndexing用于对象的空间索引,我们后面还会继续介绍,Web名称空间实现了HttpHandler和Caching类,用于网络环境。
运行机制
通过剖析其名称空间,我们对SharpMap源码的结构和组成有了大概的了解,下来我们通过上面的应用实例来剖析其运行机制。
SharpMap.Map myMap = new SharpMap.Map(picMap.Size);
这句代码创建了一个新的Map的对象,Map对象包括了中点、大小、缩放比例、图层等字段,Layers集合对象包括了地图的各个图层,可以通过Layers.Add方法增加新的图层,通过GetLayerByName方法返回某个图层。缩放等方法是通过Center和Zoom属性来控制的。当地图图层修改和地图渲染后会触发相应的事件。
接着就需要新建不同的Layer对象,设置其属性和数据源以及样式。例如:
SharpMap.Layers.VectorLayer myLayer = new SharpMap.Layers.VectorLayer("My layer");
string ConnStr = "Server=127.0.0.1;Port=5432;User Id=postgres;Password=password;Database=myGisDb;";
//创建图层的数据源
myLayer.DataSource = new SharpMap.Providers.PostGIS(ConnStr, "myTable", "the_geom", 32632);
不同的Providers对象,例如PostGIS或者Shape对象负责打开相应的空间数据集,读取数据,返回部分或者全部的空间对象。
接着就需要设置Layer的样式,例如填充、线形等属性。目前,Style对象只是简单的封装了System.Drawing.Pen、System.Drawing.Brush、System.Drawing.Bitmap等对象。并增加图层到Map的Layers集合:
myMap.Layers.Add(myLayer);
我们可以通过设置Map的Center、Zoom、Size等属性来进行Map操作,例如缩放、平移等操作,例如封装Map对象为一个User Control控件,主要的动作就是操作Map的各个属性。
最后,我们就可以通过GetMap对象返回一个System.Drawing.Image对象,代表目前的地图,用于显示或输出:
System.Drawing.Image imgMap = myMap.GetMap(); //Renders the map
GetMap是整个SharpMap的核心之一,我们来一步步剖析其流程。
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(img);
g.Clear(this.BackColor);
foreach (SharpMap.ILayer layer in this.Layers)
{
if(layer.Enabled && layer.MaxVisible>=this.Zoom &&
layer.MinVisible<this.Zoom)
layer.Render(g,this);
}
if (MapRendered != null) MapRendered(g); //Fire render event
g.Dispose();
return img;
以上代码就是GetMap的全部代码,首先创建一个System.Drawing.Image对象,然后为其创建一个System.Drawing.Graphics对象,通过这个对象来渲染地图到这个Image对象,最后返回这个对象。
其核心在于循环所有图层,如果其在显示范围之内,则调用Layer对象的Render方法,渲染这个图层。
抽象类Layer中的Render方法为:
if(LayerRendered!=null) LayerRendered(this, g);
VectorLayer子类的Render方法为:
List<SharpMap.Geometries.Geometry> features = this.DataSource.GetFeaturesInView(map.Envelope);
//Linestring outlines is drawn by drawing the layer once with a thicker line
//before drawing the "inline" on top.
if (this.Style.EnableOutline)
{
foreach (SharpMap.Geometries.Geometry feature in features)
{
//首先绘制所有图形对象的线轮廓
}
}
//double i = 0;
foreach (Geometries.Geometry feature in features)
{
switch (feature.GeometryType)
{
//根据不同的图形绘制其
}
}
base.Render(g,map);
这段代码首先根据地图的显示范围,获取范围内的所有对象:GetFeaturesInView,然后逐次绘制不同这些对象。绘制对象时,调用了SharpMap.Rendering对象的不同方法。在SharpMap.Rendering对象中,不同几何对象的绘制方法最终调用了.net的System.Drawing中的GDI+的绘制方法,完成地图的绘制。
需要说明的是不同的数据源的GetFeaturesInView方法实现方法是不同的,目前版本的Shape数据源的Provider是使用了空间索引算法,PostGIS数据源则通过PostGIS的空间索引接口来获取需要的数据。
系统效率的好坏,基本上就在于这里,一个是如何获取视图内的空间对象,关键在于空间索引;一个是渲染机制,例如使用DirectX加快渲染。
这样,我们就基本完成了对SharpMap的剖析。SharpMap是一个刚刚启动不久的项目,开发者到目前为止只有一人,所以,实现的功能有限,有些设计也不是很成熟,例如缺乏空间分析和检索功能,但完全可以作为一个非常好的教学系统来使用。
从作者Morten Nielsen了解到,该项目是从2005年夏天开始的,目的是:
It was a good case for learning some of the new .NET 2.0 features and for getting "under the hood" of many of the GIS-related algorithms needed, like spatial indexing, spatial translations and topology rules.
通过项目,可以学习.net 2.0的一些新的特性,而且可以了解GIS相关的一些底层算法,例如空间索引、空间变换、拓扑规则等。
从中我们是否可以看到对技术的一种不同的态度,少一些空谈,多一些务实,这是笔者个人的感受。
后续文章:
SharpMap深度分析:地图渲染、坐标和比例尺
SharpMap深度分析:地图数据Provider