OpenLayers 源码分析
目录
介绍
OpenLayers官网网址:http://www.openlayers.org/
OpenLayers 是由MetaCarta公司开发的,开源的,用于WebGIS客户端的JavaScript包,目前的最高版本是3.0V,通过BSD
License 发行。3.0V是支持三维的,与2.x框架不同,我介绍的是2.x,选用的是OpenLayers-2.13。
它实现访问地理空间数据的方法都符合行业标准,比如OpenGIS的WMS和WFS规范,
OpenLayers采用纯面向对象的JavaScript方式开发,同时借用了Prototype框架和Rico库的一些组件。
OpenLayers所能够利用的地图数据资源“丰富多彩”,在这方面提供给拥护较多的选择,比如WMS、WFS、GoogleMap、KaMap、MSVirtualEarth、WorldWind等等。当然,也可以用简单的图片作为源。
查看地图的页面,我们以加载WMS和GML文件为例,
示例:
map = new OpenLayers.Map('map');
//实例化一个地图类OpenLayers.Map
layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
"http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} );
//以WMS的格式实例化图层类OpenLayers.Layer
map.addLayer(layer);
//在Map对象上加载Layer对象,并用map.zoomToExtent函数使地图合适地显示
map.addLayer(new OpenLayers.Layer.GML("GML", "gml/polygon.xml"));
//再在刚加载的WMS文件上,加载一GML文件
源码结构分析
OpenLayers的类:
BaseTypes:这里定制了OpenLayers中用到的string,number 和 function。
Console:OpenLayers.Console,此名称空间用于调试和把错误等输出到“控制台”上,需要结合使用../Firebug/firebug.js。
Control:我们通常所说的控件类,它提供各种各样的控件,比如上节中说的图层开关LayerSwitcher,编辑工具条EditingToolbar等等。加载控件的例子:
class = new OpenLayers.Map('map', { controls: [] });
map.addControl(new OpenLayers.Control.PanZoomBar());
map.addControl(new OpenLayers.Control.MouseToolbar());
Events:用于实现OpenLayers的事件机制。具体来说,OpenLayers中的事件分为两种,一种是浏览器事件,例如mouseup,mousedown之类的;另外一种是自定义的,如addLayer之类的。
Feature:我们知道:Feature是geography 和attributes的集合。在OpenLayers中,特别地OpenLayers.Feature
类由一个marker 和一个lonlat组成。
Format:此类用于读/写各种格式的数据,它的子类都分别创建了各个格式的解析器。这些格式有:XML、GML、GeoJSON、GeoRSS、JSON、KML、WFS、WKT(Well-Known
Text)。
Geometry:是对地理对象的描述。它的子类有Collection、Curve、LinearRing、LineString、MultiLineString、MultiPoint、MultiPolygon、Point、Polygon、Rectangle、Surface,正是这些类的实例,构成了我们看到的地图。
Handler:这个类用于处理序列事件,可被激活和取消。同时,它也有命名类似于浏览器事件的方法。当一个handler 被激活,处理事件的方法就会被注册到浏览器监听器listener
,以响应相应的事件;当一个handler被取消,这些方法在事件监听器中也会相应的被取消注册。Handler通过控件control被创建,而control通过icon表现。
Icon:在计算机屏幕上以图标的形式呈现,有url、尺寸size和位置position3个属性。一般情况,它与OpenLayers.Marker结合应用,表现为一个Marker。
Map:网业中动态地图。它就像容器,可向里面添加图层Layer和控件Control。实际上,单个Map是毫无意义的,正是Layer和Control成就了它。
Marker:它的实例是OpenLayers.LonLat 和OpenLayers.Icon的集合。通俗一点儿说,Icon附上一定的经纬度就是Marker。
Popup:地图上一个小巧的层,实现地图“开关”功能。使用例子:
Class = new OpenLayers.Popup("chicken",
new OpenLayers.LonLat(5,40),
new OpenLayers.Size(200,200),"example popup",true);
map.addPopup(popup);
Renderer:渲染类。在OpenLayers中,渲染功能是作为矢量图层的一个属性存在的,我们称之为渲染器,矢量图层就是通过这个渲染器提供的方法将矢量数据显示出来。
Tile:设计这个类用于指明单个“瓦片”Tile,或者更小的分辨率。Tiles存储它们自身的信息,比如url和size等。
Util:“跑龙套”的类。
OpenLayers JavaScript Mapping Library
这里主要讨论OpenLayers.
Renderer这个类及其子类。
Renderer类提供了一些虚方法,以供其子类继承,像setExtent、drawFeature、drawGeometry、eraseFeatures、eraseGeometry等。
Elements继承Renderer,具体实现渲染的类又继承Renderer类。之所以这样设计,是因为不同的矢量格式数据需要共享相应的函数,在Elements这个类中封装一下。这个类的核心是drawGeometry和drawGeometryNode两个函数。其中drawGeometry调用了drawGeometryNode,创建出基本的地理对象。
drawGeometry: function(geometry, style, featureId) {
var className = geometry.CLASS_NAME;
if ((className == "OpenLayers.Geometry.Collection") ||
(className == "OpenLayers.Geometry.MultiPoint") ||
(className == "OpenLayers.Geometry.MultiLineString") ||
(className == "OpenLayers.Geometry.MultiPolygon")) {
for (var i = 0; i < geometry.components.length; i++) {
this.drawGeometry(geometry.components[i], style, featureId);
}
return;
};
//first we create the basic node and add it to the root
var nodeType = this.getNodeType(geometry);
var node = this.nodeFactory(geometry.id, nodeType, geometry);
node._featureId = featureId;
node._geometryClass = geometry.CLASS_NAME;
node._style = style;
this.root.appendChild(node);
//now actually draw the node, and style it
this.drawGeometryNode(node, geometry);
}
具体实现渲染的方法在OpenLayers.
Renderer.SVG和OpenLayers. Renderer.VML两个类中实现的,就是实现Elements提供的虚方法,比如drawPoint、drawCircle、drawLineString、drawLinearRing、drawLine、drawPolygon、drawSurface等。
//以drawCircle为例看看具体的实现过程:
drawCircle: function(node, geometry, radius) {
if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
var resolution = this.getResolution();
node.style.left = (geometry.x /resolution).toFixed() - radius;
node.style.top = (geometry.y /resolution).toFixed() - radius;
var diameter = radius * 2;
node.style.width = diameter;
node.style.height = diameter;
}
}
地图表现
它显示的地图是什么,是怎么显示的,又是怎么实现的?——暂且把这个问题叫做地图表现。
我觉得最关键的就是Map类,把这个类分析清楚了,问题就解决了一大半了。
Map类有两个常量:Z_INDEX_BASE和EVENT_TYPES,不说了,可顾名而思其意。
再看它定义的一些属性:div(The element that contains the map)、baseLayer(The currently selected base layer)、events(An
events object that handles all events on the map)。是这样,web页的div通过以id或name的形式获得map对象,然后layers和control在加载到map上,表现为地图。顺便说一句,控件control和事件event是相关联的,这以后会说。
OpenLayers.Map类提供了两种实例化方式,举例来看:
// create a map with default options in an element with the id "map1"
var map = new OpenLayers.Map("map1");
// create a map with non-default options in an element with id "map2"
//Optional object with properties to tag onto the map.
var options = {
maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
maxResolution: 156543,
units: 'meters',
projection: "EPSG:41001"
};
var map = new OpenLayers.Map("map2", options);
Options、Baselayer Functions、Zooming Functions、Translation Functions。其中,最关键的是Layer Functions和Control Functions,因为就是Layer对象和Control对象构成了map的主体。
下面从每组函数中挑选出一两个来,看看具体实现过程。
就看addLayer函数吧,下面的addLayers就是调用的它,代码如下:
addLayer: function (layer) {
for(var i=0; i < this.layers.length; i++) {
if (this.layers[i] == layer) {
var msg = "You tried to add the layer: " + layer.name +
" to the map, but it has already been added";
OpenLayers.Console.warn(msg);
return false;
}
}
layer.div.style.overflow = "";
this.setLayerZIndex(layer, this.layers.length);
if (layer.isFixed) {
this.viewPortDiv.appendChild(layer.div);
} else {
this.layerContainerDiv.appendChild(layer.div);
}
this.layers.push(layer);
layer.setMap(this);
if (layer.isBaseLayer) {
if (this.baseLayer == null) {
// set the first baselaye we add as the baselayer
this.setBaseLayer(layer);
} else {
layer.setVisibility(false);
}
} else {
layer.redraw();
}
this.events.triggerEvent("addlayer");
}
可以看到其中涉及到layer的一些方法,下一回具体介绍OpenLayers. Layer类。
Control Functions:
addControl: function (control, px) {
this.controls.push(control);
this.addControlToMap(control, px);
}
可以看出,添加控件的过程是由controls.Push()和addControlToMap()两个函数共同完成的。
addControlToMap: function (control, px) {
// If a control doesn't have a div at this point, it belongs in the
// viewport.
control.outsideViewport = (control.div != null);
control.setMap(this);
var div = control.draw(px);
if (div) {
if(!control.outsideViewport) {
div.style.zIndex = this.Z_INDEX_BASE['Control'] +
this.controls.length;
this.viewPortDiv.appendChild( div );
}
}
}
Popup Functions:这组函数和上两组函数相似,是在地图上添加或删除Popup 对象。
显然,zoomIn和zoomOut都使用了getZoom方法,放大就是让zoom加1,缩小减1。
Zoom, Center, Pan Functions:
//Allows user to pan by a value of screen pixels
pan: function(dx, dy) {
// getCenter
var centerPx = this.getViewPortPxFromLonLat(this.getCenter());
// adjust
var newCenterPx = centerPx.add(dx, dy);
// only call setCenter if there has been a change
if (!newCenterPx.equals(centerPx)) {
var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
this.setCenter(newCenterLonLat);
}
}
Zooming Functions:
这里就看看放大缩小函数吧:
zoomIn: function() {
this.zoomTo(this.getZoom() + 1);
}
zoomOut: function() {
this.zoomTo(this.getZoom() - 1);
}
控件
OpenLayers中的控件,是通过加载到地图上而起作用的,也算地图表现的一部分。同时,控件需要对地图发生作用,所以每个控件也持有对地图(map对象)的引用。
前面说过,控件是于事件相关联的。具体的说就是控件的实现是依赖于事件绑定的,每个OpenLayers.Control及其子类的实例都会持有一个handler的引用的。
那么,怎么来创建并添加一个控件呢?用下面的语句:
//实例化一个控件
var control1 = new OpenLayers.Control({div: myDiv})
//向地图中添加控件
var map = new OpenLayers.Map('map', { controls: [] });
map.addControl(control1 );
对一些常用的OpenLayers控件,项目本身都封装好了,用下面的语句添加:
map.addControl(new OpenLayers.Control.PanZoomBar());
map.addControl(new OpenLayers.Control.MouseToolbar());
map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
map.addControl(new OpenLayers.Control.Permalink());
map.addControl(new OpenLayers.Control.Permalink('permalink'));
map.addControl(new OpenLayers.Control.MousePosition());
map.addControl(new OpenLayers.Control.OverviewMap());
map.addControl(new OpenLayers.Control.KeyboardDefaults());
先看看OpenLayers. Control基类的实现过程,再选择几个典型的子类分析一下。
OpenLayers. Control:
//设置控件的map属性,即控件所引用的地图
setMap: function(map) {
this.map = map;
if (this.handler) {
this.handler.setMap(map);
}
}
//drew方法,当控件准备显示在地图上是被调用。当然,这个方法只对有图标的控件起
//作用。
draw: function (px) {
if (this.div == null) {
this.div = OpenLayers.Util.createDiv();
this.div.id = this.id;
this.div.className = this.displayClass;
}
if (px != null) {
this.position = px.clone();
}
this.moveTo(this.position);
return this.div;
}
前面说过,OpenLayers.Control及其子类的实例都是会持有一个handler的引用的,因为每个控件起作用时,鼠标事件都是不一样的,这需要动态的绑定和接触绑定。在OpenLayers.Control中是通过active和deactive两个方法实现,就是动态的激活和注销。
//激活方法
activate: function () {
if (this.active) {
return false;
}
if (this.handler) {
this.handler.activate();
}
this.active = true;
return true;
}
//注销方法
deactivate: function () {
if (this.active) {
if (this.handler) {
this.handler.deactivate();
}
this.active = false;
return true;
}
return false;
}
再来看OpenLayers.Control的子类,即各类“特色”控件。选鹰眼控件OpenLayers. Control. OverviewMap和矢量编辑工具条控件OpenLayers. Control. EditingToolbar来说。
顺便说一句,OpenLayers中的控件有些是需要图标的,像EditingToolbar,有些是不需要的,像OpenLayers. Control. DragPan。
OpenLayers. Control. OverviewMap:
“鹰眼”实际上也是地图导航的一种形式,在外部形态上跟图层开关控件有点儿像。
添加鹰眼控件的语句:
map.addControl(new OpenLayers.Control.OverviewMap());
在它实现的成员函数中,draw函数是核心,继承基类OpenLayers.Control,在地图中显示这个控件。
此控件关联了一些浏览器事件,比如
rectMouseDown: function (evt) {
if(!OpenLayers.Event.isLeftClick(evt)) return;
this.rectDragStart = evt.xy.clone();
this.performedRectDrag = false;
OpenLayers.Event.stop(evt);
}
OpenLayers. Control. EditingToolbar:
OpenLayers从2.3版就对矢量编辑进行了支持,就是图上右上角几个图标。完成点、线、面的编辑功能。
同样,它也是用drew方法激活:
draw: function() {
Var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
this.activateControl(this.controls[0]);
return div;
}
下面的代码是使用此控件的具体过程:
var map, layer;
map = new OpenLayers.Map( 'map', { controls: [] } );
layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
"http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} );
map.addLayer(layer);
vlayer = new OpenLayers.Layer.Vector( "Editable" );
map.addLayer(vlayer);
map.addControl(new OpenLayers.Control.PanZoomBar());
map.addControl(new OpenLayers.Control.EditingToolbar(vlayer));
map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
事件机制
OpenLayers中的事件封装是其一大亮点,非常值得学习。
说到事件机制,在宏观上不得不涉及控件OpenLayers.Control类、OpenLayers. Marker类、OpenLayers.Icon类等。是这样,在外观上控件通过Marker和Icon表现出来,而事件包含在控件之后,用他们自己的话说就是:The controls that wrap handlers define the methods that correspond to these abstract events 。顺便再说一句,控件实现的核心是handler类,每个控件中都包含对handler的引用,通过active和deactive两个方法,实现动态的激活和注销。
OpenLayers中的事件有两种:
一种是浏览器事件(比如onclick,onmouseup等),
另一种是自定义的事件。自定义的事件像addLayer ,addControl等,不象浏览器事件会绑定相应的dom节点,它是与layer、map等关联的。
OpenLayers中支持的浏览器事件类型有(以常量的形式提供的):
BROWSER_EVENTS: [
"mouseover", "mouseout",
"mousedown", "mouseup", "mousemove",
"click", "dblclick",
"resize", "focus", "blur" ]
看看构造函数的的实现过程:
<span style="font-family:微软雅黑;color:#333333;"> </span>initialize: function (object, element, eventTypes, fallThrough) {
this.object = object;
this.element = element;
this.eventTypes = eventTypes;
this.fallThrough = fallThrough;
this.listeners = {};
// keep a bound copy of handleBrowserEvent() so that we can
// pass the same function to both Event.observe() and .stopObserving()
this.eventHandler = OpenLayers.Function.bindAsEventListener(
this.handleBrowserEvent, this
);
// if eventTypes is specified, create a listeners list for each
// custom application event.
if (this.eventTypes != null) {
for (var i = 0; i < this.eventTypes.length; i++) {
this.addEventType(this.eventTypes[i]);
}
}
// if a dom element is specified, add a listeners list
// for browser events on the element and register them
if (this.element != null) {
this.attachToElement(element);
}
}
可以这样理解:
initialize(object, element, eventTypes, fallThrough)方法会将以数组eventTypes的每个元素为key建立哈希表listeners,表中每个键对应一个数组。还会给this.eventHandler赋值,它实际只是一个包装了triggerEvent事件触发函数的方法,所有的事件,包括浏览器事件和自定义事件都是通过它来中转的。
然后initialize将所有的浏览器事件放入listeners中,并为其绑定相应的dom节点element和this.eventHandler事件处理函数OpenLayers.Event.observe(element, eventType, this.eventHandler),节点上事件触发的时候会把事件传给this.eventHandler,它调用triggerEvent,从而将事件传出来。
来看其他的成员函数:
- addEventType:Add a new event type to this events object;
- attachToElement:把浏览器事件关联到相应的DOM元素上;
- register: Register an event on the events object.
register: function (type, obj, func) {
if (func != null) {
if (obj == null) {
obj = this.object;
}
var listeners = this.listeners[type];
if (listeners != null) {
listeners.push( {obj: obj, func: func} );
}
}
}
其中,func参数是预先定义的回调函数。
- unregister:注销方法;
- remove:Remove all listeners for a given event type.
- triggerEvent:Trigger a specified registered event。
这两个月的笔记都记录在有道云笔记上了,所以现在花时间将其批量放到博客上,所以若有问题指教,或者技术问题讨论请联系我邮箱:507153809@qq.com。也可以直接评论,但可能回复不够及时。
转载自:https://blog.csdn.net/mingzai624/article/details/44599721