3.3 一颗奔腾的心——基于 OpenLayers 的地图小工具
提到小工具,就不得不提到 OpenLayers 。 OpenLayers (主页是 http://www.openlayers.org )是由 MetaCarta 最初发起的,用于在网页界面上展示地图的一套 Javascript 脚本框架。
MapGuide 的 Fusion 框架最为核心的地图 Widget ,就是采用了 OpenLayers 框架。 Fusion 框架对 OpenLayers 进行包装,为其添加了更多的功能,从而使之能够更符合 Fusion 框架并与其余 Fusion 组件进行交互。
3.3.1 起名是个学问——术语不一致引发的问题
非常有趣的是,由于 OpenLayers 所采用的术语跟 Fusion 采用的并不是十分一致,甚至 Fusion 不同部分采用的术语也不是十分一致,所以同一个名字在不同地方表达的意思却很可能并不相同。所以,在详细介绍 Fusion 如何对 OpenLayers 进行包装之前,有必要对两者之间的术语进行一下区分,以免当您阅读到相关材料时感到疑惑。
OpenLayers 认为,用户看到的由多个图层 (layer) 组成的一张地图 (map) 。地图本身与加载的数据源格式无关,与数据源格式相关的是图层。因此,地图类只有一个( OpenLayers.Map ),而图层类却有很多,这些类都以 OpenLayers.Layer 作为命名空间,如 OpenLayers.Layer.Google/Yahoo/MapGuide/WMS/Vector 等等。
这样的结构有一个问题,那就是所有的图层之间是平行的关系,这是很不利于图层管理的。我们假设有这样一张地图:该地图包含有十二个图层,有九个来自于不同数据源的图层和三个位于顶层的 Vector 层。那么,我如果想用代码去处理所有 Vector 的层,就必须遍历所有的层,依次比较是不是 Vector 层,再对 Vector 层进行处理。解决这个问题的方案很简单,那就是引入层级结构,允许用户把图层分组。比如上面的例子中,把三个 Vector 分成一组,比如命名为“标记”组,届时只需要对标记组中的每一个层进行处理即可。
这也正是 Fusion 对于 OpenLayers 众多改进中的一个。而问题也正是这里引入的:地图 Widget 在给这种层级结构命名的时候,出人意料地采用了另外的命名方式:地图 Widget 把图层组命名为“地图组” (map group) ,把组里面的图层命名为 (map) 。下面代码是示例数据中 Library://Samples/Sheboygan/FlexibleLayouts/Slate.ApplicationDefinition 布局文件对于地图定义的那一部分,用户可以在 MapGuide Studio 中通过点击位于 Map 面板上的 “Edit Map Group” 按钮来查看这部分内容。
<?xml version="1.0" encoding="utf-8"?> <MapSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <MapGroupType id="Sheboygan"> <Map> <Type>MapGuide</Type> <SingleTile>true</SingleTile> <Extension> <ResourceId>Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition</ResourceId> <SelectionAsOverlay>true</SelectionAsOverlay> <SelectionColor>0x0000FFA0</SelectionColor> </Extension> </Map> <Extension /> </MapGroupType> </MapSet> |
代码 3-4 布局文件中地图定义的部分代码
其实, MapGuide 这样做是有原因的。这个问题源于不同产品之间定位的不同: OpenLayers 框架的目的是在同一张地图上面显示多个数据源。所以在 OpenLayers 看来,一个来自 MapGuide 的地图仅仅是一个层而已。然而,在 MapGuide 内部,一张 MapGuide 地图内是包含多个 Layer ,具体表现就是一个 MapDefinition 里可以包含多个 LayerDefinition 。所以,如果 Fusion 把 MapGuide 叫做一个 Layer 的话,又该如何称呼里面的 Layer 呢?
鉴于这样的考虑, Fusion 才做了这种概念上的映射: Fusion 中的地图 Widget 对应 OpenLayers 里面的地图; Fusion 里面的地图组是一个逻辑概念,从而形成一种逻辑上的层级结构以便管理; Fusion 中的地图实际对应于 OpenLayers 里面的图层。
但事情到这里还没有结束,接下来的事情可能会让您有些意外:在编写地图 Widget 实现代码时,为了能够便于与 OpenLayers 中的类对应起来, Fusion 将地图(也就是 OpenLayers 中的图层)称为 Layers (注意,不是 Layer ),并要求每一个地图的代码都继承自 Fusion.Layers 。比如, MapGuide 的 Layers 的名称就叫做 Fusion.Layers.MapGuide 。
下表总结了 Fusion 与 OpenLayers 之间这种概念映射关系
Fusion 概念 | Fusion 代码命名 | OpenLayers 概念 | OpenLayers 代码命名 |
地图 | Fusion.Layers | 图层 | OpenLayers.Layer |
地图组 |
| 无对应概念 |
|
地图 Widget | Fusion.Widget.Map | 地图 | OpenLayers.Map |
表 3-2 Fusion 与 OpenLayers 之间的概念映射关系
在阅读 Fusion 和 OpenLayers 的源代码或学习他们的 API 时,一定要注意两者术语上的区别。
本节中,我们提到了 Fusion 引入了地图组这一个概念。这只是 Fusion 对于 OpenLayers 众多改动中的一个。下面我们来看看到底 Fusion 对 OpenLayers 还做了哪些改动。
3.3.2 改头换面—— Fusion 对于 OpenLayers 的改进
为了便于 Fusion 的其他组件能够与地图进行交互, Fusion 对于 OpenLayers 进行了较为全面的包装。一般来说,如果您不是开发 Fusion.Layers 的开发人员,除了使用一些 OpenLayers 的一些工具性的函数之外,您甚至都不需要知道 OpenLayers 的存在。但是,仅仅进行包装是不够的,由于 OpenLayers 与 Fusion 定位的不同, Fusion 必须对 OpenLayers 进行扩展才能适应更为复杂的模型。
1. Fusion 中添加了选择集的概念。 OpenLayers 在同一张地图里面显示多种不同数据源的方面确实做的很好,遗憾的是,它缺少选择集这一至关重要的概念。对于 OpenLayers 来说,选择集完全是可有可无的,因为它的目的在于将地图展示出来,而且,很多地图根本就没有选择集这样的 API ,比如 Google 地图等等。但对于 Fusion 则不同,我们很难想象没有选择集,用户该如何利用 MapGuide 进行管理。所以, Fusion 加入了选择集这个概念,并且要求实现 Layers 的地图(比如 MapGuide )实现选择集功能。我们可以看到, Fusion.Widget.Map 中不但有诸如 get/set/clear/hasSelection 这样控制和读取选择集的函数,而且有 MAP_SELECTION_ON 和 MAP_SELECTION_OFF 这两个事件来通知监听者地图选择集的当前状况。
2. Fusion 开放了更多的事件。借助于 Fusion 自己独立实现的事件机制, Fusion 允许用户接收到更多种类的事件,比如 Session 是否已创建、地图当前忙碌与否、选择集状态变更、当前图层(这个是 Fusion 中的图层,不是 OpenLayers 的)变化等等。
3. Fusion 允许地图的实现类返回自身支持哪些比例尺,这就使得用户可以直观的知道自己当前缩放地图到什么程度。
4. 允许用户随时设置当前地图的背景图片和地图上的光标形状。这对于直观地反应地图当前状态是是否有用的。
5. 支持右键菜单。虽然在浏览器上实现右键菜单相对简单一些,但是通过使用地图 Widget 的 setContextMenu ,代码编写者就可以直接把已经准备好的 div 作为右键菜单,再也不需要直接与底层鼠标事件打交道了。
3.3.3 地图标签——地图的定义
前面在介绍 Fusion 与 OpenLayers 术语不同的时候,摘录了应用程序定义中对于地图部分的定义。通过解析这个定义, Fusion 了解了应该如何加载该地图。下面,我们就来看看这个定义中到底都定义了哪些东西。
在 Fusion 中,一个地图组用一个 MapGroup 进行标签定义, MapGroup 里面的 Map 标签就是对于一个地图的定义了。
1. Type: 该标签标示了地图的类型。所有 MapGuide 地图该标签的值均为 MapGuide 。当 Fusion 读取到该地图的 Type 时,就会用对应的 “Fusion.Layers. 标签值 ” 来初始化该地图。比如 MapGuide 地图就会用 Fusion.Layer.MapGuide 来初始化。
2. SingleTile: 如果该项为真,则表示该项不采用分块服务。
3. Extension: 该于扩展 Map 标签,来为地图初始化提供更多的信息。各个 Fusion.Layers 的实现类可以自行决定其需要的内容,以及如何解释这些内容。对于 MapGuide 而言,有以下常见的扩展:
a) ResourceId: MapDefinition 的资源 Id ,通过该 Id , Fusion 可以知道加载哪一个地图定义。
b) SelectionAsOverLay: 如果该项是 true ,那么将会使用 GETDYNAMICOVERLAY 来获取地图,否则采用 GETMAPIMAGE 来获取地图。前者是新版本才支持的,可以把选择集和地图本身绘制成两张地图。如果您使用的 MapGuide 版本比较旧,您可以把该项设置成为 false
c) SelectionColor: 该项表示用什么颜色来显示选中的要素。
了解到了这些信息, Fusion 就了解该如何加载地图了。