(十一)WebGIS中要素(Feature)的设计
目录
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/。
1.前言
在GIS中元素一般分为点元素,线元素,面元素以及symbol元素(特殊的点元素)等。与此对应,图层可以分为点图层,线图层,面图层以及标注图层等。从第9章到第10章,我给大家讲解了什么是矢量数据、矢量数据的来源、矢量数据的构造、以及矢量数据中的地理坐标与屏幕坐标之间的转换。在了解了这些概念和算法以及流程后,这一章我们将开始讲解设计出一个矢量图层前的最后一步,设计WebGIS中的要素(Feature)。
2.要素设计的思路
2.1 需求
要素应该具有如下功能:
(1)能够绘制出矢量数据的Shape。
(2)能够存储矢量数据的atrributes。
(3)可以响应鼠标事件。
(4)可以响应自定义事件,如自定义的一系列地图事件。
(5)能够添加入Canvas组成一个矢量图层。
(6)要素是有很多种类的,比如点、线、面等。
2.2 基本分析
(1)在前面我们已经阐述了要素的本质,即UIComponent。因为UIComponent是前端语言中已经封装好了的类,我们只用继承其即可,如此我们便同时能实现上面所说的第三个需求以及第五个需求。
(2) 需求中要求要素中能够绘制出矢量数据的Shape。所以得定义类似于Draw这样的功能函数,以解决第一个需求。
(3)要素中还需要有个一变量存放attributes,即FeatureInfo以解决第二个需求。
(4)设计的类中还要定义一个监听事件的函数和一个移除监听事件的函数,即:addMapEventListener和removeMapEventListener,以解决第四个需求。
2.3 扩展分析
2.3.1重绘
当地图刷新,比如放大和缩小时,此时的UIComponent必须重绘,否则地图上的Shape将不再在地图上表示正确的位置,于是这里对Feature的设计有两点需要注意:
(1)对地图放大和缩小进行监听。
(2)增加重绘函数,即reDraw函数。当地图放大和缩小时触发重绘函数。
2.3.2 基类
需求中提到了要素具有多种类型,我们设计时需要将这几种类型均涉及到的属性和函数抽象出来作为一个基类,即Feature类。可以抽象出来的属性和类有:
(1)reDraw和Draw函数
(2)FeatureInfo属性
(3)addMapEventListener和removeMapEventListener函数
(4)对地图放大缩小事件的监听,此功能的实现可以放在Feature的构造函数中。
2.3.3 hook到主框架
在绘制shape时,我们需要用到地理坐标与平面坐标互相转换的函数,即上一章提到过的geoXYToScreenXY和screenXYToGeoXY。但是这两种方法是不可能封装到Feature类中的。因为这两种方法为系统中通用方法,不是专为Feature类而存在。所以这两个方法是放在主框架类中,这里我将这个主框架定义为FrameMap。于是,我们的Feature类中还应该有可以注入主框架的属性,即Map。
3.UML设计
基于上述分析,我们现在要开始真正的设计要素了。我们首先定义一个Feature类,其继承于UIComponent,然后再根据需求定义一系列具体的基础要素类,比如PointFeature、LineFeature和PolygonFeature等。并且如果需求出现变更,原有的基础要素已经不能满足需求时,可以在基础要素上进行继承从而进一步扩展基础要素类。
如下是最后设计出的要素类的UML图:
4.问题及解决方法
4.1如何控制每种基础要素类中要求shape以不同样式展现的需求?
在实际应用中,对同一个要素,用户需要的展现方式可能不尽相同。比如对面要素,有时候需要展现成黄色,有时候需要展现成蓝色等,而有时候需要面要素内的填充为网格状,有时候需要面在移动上去时变色,有时候又不需要等等。
解决此问题的方法,并不是去为每种需求重新将基础要素继承,然后修改draw方式,如果这样的话,代码的重复量太大。在设计模式中,我们有一个原则是能用组合时尽量用组合,能不用继承时尽量不用继承,因为继承是种高度的耦合,派生类和基类被紧紧的绑在一起,灵活性大大降低,而且,滥用继承,也会使继承树变得又大又复杂,很难理解和维护。在这个需求里,目前用不上组合,但是继承就更不需要了。
我们的解决方法一般是:
(1)首先将需求分类。外观上:一类是改变颜色的,一类是改变要素内部构造的。行为上:一类是需要某些监听并且做出相关变化的,一类是不需要某些监听的。这样我们可以在具体要素类,比如polygon类中添加两个共有属性,一个是DrewType,一个是ActionType。如果不希望把控制行为的重担都放在polygon类中,我们也可以不用定义ActionType,此类中本来就设计有addMapEventListener和removeMapEventListener。可以开放给调用者,让其自行判断。
(2)然后我们在Draw函数中,通过判断DrawType来进行绘制方面的控制。每个具体要素类中有自己定义的内部透明度(Sopacity)、外部透明度(opacity)以及内部填充色(SColor)和外部填充色(color)以及填充图标(Symbol)等等绘制方面的属性。回到上面的需求中,我们一类是想改变要素颜色,一类是想改变内部构造。还是假设这是一个对于polygon要素的需求,我们把DrawType定义为0和1。当DrawType为0时,我们将使用Sopacity,opacity、Scolor和color等绘画要素进行绘制,这些要素均是有默认值的公有属性,调用者可以自己设定。但是当DrawType是1时,这时候会将Symbol进行加载,然后改变绘制polygon的方式,将Symbol作为内部填充物进行填充。同样,Symbol也是有默认值的公有属性,调用者可以自己设定。
4.2地图发生平移时,Canvas的左上角坐标也出现平移,如何解决此时要素重绘时要素偏移问题?
在后面探讨设计WebGIS的地图平移功能时,我们会讲到每次地图被拖拽后,矢量图层(Canvas)的左上角坐标均会加减相同的屏幕平移量。这时候如果仅仅是将要素中的地理坐标转换为屏幕坐标后就直接将其绘制在UIComponent,然后添加到该Canvas里,便会出现绘制的要素不在地图上实际的地点的现象,即发生了偏移。这个问题是一个有一定难度的问题,我会在以后的WebGIS基础功能设计篇里专门花一个章节跟大家探讨这个问题。
5.通过设计模式再次探讨我们的设计
以上的设计基本上是遵循了面向对象的设计思维。不过,依据设计模式的原则,此中依然有可以改进的地方。
(1)根据依赖倒转原则,我们可以定义一个接口,比如IFeature,然后基类继承于此接口,这样可以通过接口来规范基类应该实现哪些方法。
(2)我们可以考虑使用简单工厂模式,从而进一步减少界面层类和Feature类中的耦合度。
不过,设计模式除非真有效用时再用才比较好,目前的元素设计在实际的编写代码中已经很好用了,当用到工厂类时,需要再次编写很多类出来,而且效果也不一定好。
6.总结
这一章里我们将设计矢量图层前的最后一个障碍克服了,但是,我在这个章节中还是留下了一个没有解答的问题:地图平移后的要素重绘问题。这个问题,我会在以后的章节中专门跟大家一起探讨。下一章,我们就开始走完WebGIS栅格图层和矢量图层设计这个系列的最后一站了,希望大家持续关注。
——欢迎转载,但保留版权,请于明显处标明出处:http://www.cnblogs.com/naaoveGIS