阅读:938回复:1
GIS地图开发
一、 地图是怎么做出来的
首先说一下地图是怎么出来的,可能你感觉是废话,但实际上很多人并不知道如何下手。我觉得这里需要先给你个思路准备:地图就是使用绘图语句画出来的! 从底层绘制地图,能使用的就是绘图函数,在.NET里,就是用Graphics类的方法,在窗口中绘制点、线、面、标准、栅格等,组合起来,就是一张地图(瓦片图方式除外)。 关于.NET的绘图,本文不进行讲解,如果你还还不熟悉,建议你先看看这方面资料。 二、 坐标转换-地图绘制的关键 .NET 提供了大量绘图方法,基本上都是以Graphics类的函数形式提供,包括各类几何形状、图像、文字的绘制,灵活运用这些方法,就可以画出精美的图出来。 假设你已熟悉.NET的绘图,这样就只有有一个问题要解决:图我会画了,但拿到地图元素一般为地理坐标(经纬度),应该画在地图上什么位置?这就需要涉及 到坐标转换问题。 先不考虑怎么实现,首先需要这么一个函数: /// <summary> /// 经纬度转换为屏幕坐标 /// </summary> /// <param name="xy">经纬度</param> /// <returns>屏幕坐标</returns> public Point WorldToScreen(PointF xy) 再一个,有时,还需要根据屏幕上点位置反算出它的经纬度,如在需要显示鼠标指针处的经纬度,所以还需要这么一个函数: /// <summary> /// 屏幕坐标转换为经纬度 /// </summary> /// <param name="xy">屏幕坐标</param> /// <returns>经纬度</returns> public PointF ScreenToWorld(Point xy) 有了这两个函数,就可以将以经纬度表示的地理坐标转换为屏幕坐标,然后再屏幕绘图了。 为了完成坐标转换,需要使用几个地图参数的变量:地图缩放倍数、地图中心点经纬度、地图大小,关于地图参数,可参考这篇文章: http://hi.baidu.com/geochenyj/blog/item/6b5c5c1294057557f819b835.html 另 外,还需要对地图进行缩放、平移,这些操作实质上也是对地图参数的操作,如放大就是对地图缩放倍数操作,平移就是对地图中心点进行操作,我们将这些操作也 写Coordinator类的方法。投影变换也作为坐标转换的一部分,Coordinator类还增加了投影方面方法,这个后面再讲。 将上面两个坐标转换函数和三个地图参数封装为一个类Coordinator。,的类如下所示: ![]() 三、 绘图 有 了坐标转换类Coordinator,就可以用经纬度数据来绘图了,如拿到某省的行政边界经纬度坐标数据,就可以将经纬度数据转换为屏幕坐标,然后用 Graphics的方法来画出来了,Graphics对象又从哪里来呢?可以从一个Image对象创建,也可以从一个控件的Paint事件中取得,总之, 有了坐标,发挥你的想象力,自己画吧。 在气象数据分析中,除了要绘制点、线、面、文字、栅格外,还需要绘制一些特殊符号,如风、天气现 象、云等。这些符号,可以用图片、天气字库、符号库来实现,图片方式实现简单,色彩丰富,但缩放效果不好;字库方式,需要安装字库,程序部署比较麻烦;符 号库方式代码编写较麻烦。FreeMicaps的天气现象符号采用符号库方式,祥见: http://blog.csdn.net/HZGJF/archive/2009/05/27/4220508.aspx 风符号和云量符号采用计算坐标绘制方式。 为了使用方便,FreeMicaps把符号绘制功能封装到三个符号类中,以静态方法提供。 ![]() .NET的绘图是对GDI+的封装,包括了对点、线、面等各种图形元素的封装,图形图像的绘制、坐标旋转,各种反走样和平滑等功能,功能十分强大(当然,效率不太高),利用它可以绘出漂亮的图形。 根据OGC标准,GIS系统首先需要对地图元素进行抽象和封装,但FreeMicaps中,经再三考虑,放弃了这种方式,一个是因为工作量比较大,另一个是因为我不敢保证能很好地进行封装,可能给插件开发带来麻烦,不如把绘图权完全交给图层,大家自由发挥。 四、 图层 为 了使绘图过程便于管理,可将绘图过程分为组,如可以将一张地图的绘制分为:绘制世界地图、绘制中国地图、绘制河流、填地名几个过程,每次绘图好像就是在一 张玻璃上绘制,叠加起来就形成了一张地图图,这里把每次绘图过程形象地称为一个图层。地图分层后,图层可以增删,每个图层可以单独进行隐藏、设置属性等, 更重要的是可以将利用面向对象技术把每个图层当做一个对象进行管理。详细介绍见:http://blog.csdn.net/HZGJF/archive/2008/10/03/3014558.aspx 对 图层进行抽象,它应该有一个图层绘制方法(Render),一个图层标题(LayerName),一个用于表示数据源的字符串(DataSource), 一个用于表示绘图样式的设置的LayerStyle,加上一些辅助方法属性,最终形成如下抽象图层类(CustomLayer),各种图层均从它继承: ![]() FreeMicaps中,每种数据对应一种图层类,为了使图层类编写方便,使用了设计模式中的模板方法,定义绘制流程,主程序在调用图层的Render()方法时,会自动判断是否已经读入数据,根据需要读数据绘图。 对 于一种类型数据,需要从CustomLayer继承新建一个图层类。各种类型数据图层的工作方式完全一样,仅在数据读取和绘制方面不同,所以,写新图层类 时,仅需实现DoLoad()和DoRender()两个抽象方法,完成读取数据和绘制图层代码即可。FreeMicaps里使用了字符串作为数据源标 识,通用GIS系统对数据源进行了抽象,我也尝试这么做,但代码过于复杂,增加图层开发难度,最终增大插件开发难度,所以放弃了。 前面 说了,一张地图有多个图层,所以还需要将图层放入一个列表,绘制地图时遍历图层,调用每个图层的Render()方法,画出一张完整的地图。对于图层列 表,大家马上会想到使用List类,但图层绘制是需要有顺序的,如在卫星云图上面叠加地名,需要先画卫星云图,再填地名,否则云图会把地名盖住,所以在图 层的样式(LayerStyle)中放了一个ZOrder属性,通过它来控制图层顺序。但由于List本身的排序方法是一种“非稳固排序”,也就是说当两 个图层的ZOrder相等时,它们的顺序是不确定的,为了避免这个问题,FreeMicaps从CollectionBase继承了一个类 LayerList,实现对图层的管理,并实现了IXmlSerializable接口,完成图层序列化功能。另外,还增加了添加图层、删除图层事件。 LayerList类如下: FreeMicaps中,每种数据对应一种图层类,为了使图层类编写方便,使用了设计模式中的模板方法,定义绘制流程,主程序在调用图层的Render()方法时,会自动判断是否已经读入数据,根据需要读数据绘图。 对 于一种类型数据,需要从CustomLayer继承新建一个图层类。各种类型数据图层的工作方式完全一样,仅在数据读取和绘制方面不同,所以,写新图层类 时,仅需实现DoLoad()和DoRender()两个抽象方法,完成读取数据和绘制图层代码即可。FreeMicaps里使用了字符串作为数据源标 识,通用GIS系统对数据源进行了抽象,我也尝试这么做,但代码过于复杂,增加图层开发难度,最终增大插件开发难度,所以放弃了。 前面 说了,一张地图有多个图层,所以还需要将图层放入一个列表,绘制地图时遍历图层,调用每个图层的Render()方法,画出一张完整的地图。对于图层列 表,大家马上会想到使用List类,但图层绘制是需要有顺序的,如在卫星云图上面叠加地名,需要先画卫星云图,再填地名,否则云图会把地名盖住,所以在图 层的样式(LayerStyle)中放了一个ZOrder属性,通过它来控制图层顺序。但由于List本身的排序方法是一种“非稳固排序”,也就是说当两 个图层的ZOrder相等时,它们的顺序是不确定的,为了避免这个问题,FreeMicaps从CollectionBase继承了一个类 LayerList,实现对图层的管理,并实现了IXmlSerializable接口,完成图层序列化功能。另外,还增加了添加图层、删除图层事件。 LayerList类如下: 五、 封装地图 有 了坐标转换类、图层类、图层列表类,就可以利用它们做出一个具有缩放平移、图层管理等功能的地图了,但为了更方便地对地图进行操作,还需要对这些类进行组 合封装。新建一个类WeatherMap,添加Coordinator和LayerList类的实例作为它的属性,为了更符合大家操作习惯,将 Coordinator类的实例作为私有成员,将地图坐标转换等方法加入WeatherMap类,也就是说地图坐标转换中,不访问 Coordinator,而要调用WeatherMap类的方法。类图如下: ![]() 再回到抽象图层类CustomLayer,它有一个成员Map,即为WeatherMap对象,在将图层加入图层列表时会自动赋值。在编写CustomLayer的子类时,可调用它来进行坐标转换和地图操作。 为 了使地图在绘制复杂图形过程中不至于假死,并在绘图过程中能随时中断绘图,如快速缩放平移地图中可终止前次绘图过程直接绘制最后一次,地图绘制使用了多线 程,但多线程增加了代码编写难度,特别是多线程操作UI,对程序流程造成了一定混乱,程序结构受到影响,所幸并不会对图层代码造成困难。 六、 再次封装-增加UI 上面已完成了地图绘制的核心代码,为了使代码编写更加容易,需要对WeatherMap类再次进行封装(MapView类),加入UI部分,即给地图加一个具有界面的壳,并在上面实现地图的操作如缩放、拖动功能。 MapView从PictureBox类继承,内建了WeatherMap类的实例,在MapView的Refresh()方法中调用WeatherMap.Render()对地图进行绘制。 为 了完成对地图的操作,FreeMicaps定义一个IMapTool接口,包含了鼠标和键盘操作方法,MapView类内建一个IMapTool接口成 员,MapView的鼠标和键盘操作,将被IMapTool接口的实例接管,在实现IMapTool接口的类中,可对地图做各种操作,如平移、缩放等操 作,这个对象可随时替换以实现不同方式的地图操作。在FreeMicaps中,已完成一个实现IMapTool接口的类ZoomTool,此类为默认的地 图缩放和平移工具。IMapTool接口类图如下: ![]() 另外,在MapView中,还引入了一个当前图层的概念CurrentLayer,用它来表示当前操作的图层,后面用它来实现图层元素拾取、图层工具条等功能。 MapView类图如下: ![]() 七、 总览 地图部分类关系图如下: ![]() 地图绘制部分活动图如下: ![]() 以 上已经介绍完FreeMicaps地图部分设计框架,相信大家的已对设计思路已有一定了解,此框架不仅适用于天气图分析软件,也适用于一般的GIS系统。 本文仅对FreeMicaps的地图部分框架进行了介绍,未涉及到具体的地图数据读取及绘制,这些将在下一篇文章中介绍。 |
|
|
1楼#
发布于:2015-05-11 10:15
最近,已有几位朋友在问关于图形绘制中的图层管理的问题,语言表达能力有限,电话里解释半天,对方可能让我搅得更糊涂了。整理一下思路,写出来可能会更清楚一些。
在Gis软件和一些图像处理软件中经常会有图层这个概念,如PhotoShop。使用图层的好处是可以将绘制的图像图形分层,对某层的操作不会影响其他层,并能增加删除隐藏图层,给人的感觉好像是在一层一层的玻璃上绘图,然后将这些玻璃叠起来形成一副完整的图像。 但在各类编程语言中并没有图层这个概念,图层只是程序员脑中虚拟的东西。最简单的,将每类图形的绘制过程单独写成一个过程,增删等图层管理就是对这些绘图过程进行控制,如画一副天气图写成伪代码就是这样子:
主程序里就可以控制绘制条件来画需要的图层了.是不是很简单,呵呵 但 是,有问题出来了,如果我改变了绘图条件,如将"显示填图"由"True"变为了"False",也就是隐藏了填图图层,怎么才能擦除填图层呢?擦掉已画 出来的图是不可能的了,就像我们手工画天气图一样,本想用橡皮擦掉等温线,但一擦连下面的等高线也擦掉了,计算机绘图更不允许出现这样的情况.解决的办法 很简单,重绘!你可能会说,"我只想去掉一层,你却将所有图层都重画一遍,多麻烦呀!",其实计算机最擅长做的就是这种麻烦而且重复的工作,重绘一次仅花 费不到1秒的时间.就像老板说你的等温线画的难看,与其擦掉等温线,还不如重拿一张空图再画一遍. 再就是图层管理问题了,上面的办法只 能解决有限个图层的情况,图层不能增删,只能隐藏,如果我需要不断在上面添加新图层,像再加一层云图.这就要用到面向对象了.我们可以将绘图过程封装成一 个个类,如"地图图层类"/"填图图层类"/"等值线图层类",主程序里用一个列表或动态数组,要添加那种图层就向这个列表里加上这个类的对象,在绘图过 程中遍历列表中的图层对象,调用它们的绘图方法,增删图层只是对图层列表中的对象进行增删.写成伪代码:
这 个是不是更简单?没学过面向对象的朋友可能还是不太理解,我还是用手工画天气图作为例子:老板给我们一张白纸说:“把今天的地面图画好给我!”,我则先来 找来地球仪,描出东亚地图,再找到地面资料,一站一站填好要素,再画等压线,三小时变压,天气区、槽线、锋面...画好后拿给老板一看,老板说:“谁让你 分析槽线和锋面了(水平不够),拿去重画!”晕倒!现实中这样肯定是不科学的,于是,我们定了一个预报流程,将工作分给了多个人,首先,印刷厂工人给我们 印好天气图底图,然后我们用填图机填好要素,预报员分析好等高线,槽线和锋面可让首席给我们定,于是一张天气图很快就画好了。程序中,印刷厂/填图机/预 报员/首席就是一个个图层对象,预报流程就是列表,我们可以在列表中增删对象以满足不通需要。面向对象博大精深,还需要在实际中多实践... FreeMicaps使用面向对象技术,不仅将读数据和绘图封装成类,还将他们用插件方式实现,这样增加一种数据类型只需要实现给定的接口并挂入主程序即可,道理和上面类似。 |
|
|