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 APIs采用动态类型脚本语言JavaScript编写,实现了类似与Ajax功能的无刷新更新页面。

    OpenLayers所能够利用的地图数据资源“丰富多彩”,在这方面提供给拥护较多的选择,比如WMS、WFSGoogleMap、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

ConsoleOpenLayers.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此类用于读/写各种格式的数据,它的子类都分别创建了各个格式的解析器。这些格式有:XMLGML、GeoJSONGeoRSS、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类提供了一些虚方法,以供其子类继承,像setExtentdrawFeaturedrawGeometryeraseFeatureseraseGeometry等。

    Elements继承Renderer,具体实现渲染的类又继承Renderer类。之所以这样设计,是因为不同的矢量格式数据需要共享相应的函数,在Elements这个类中封装一下。这个类的核心是drawGeometrydrawGeometryNode两个函数。其中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.SVGOpenLayers. Renderer.VML两个类中实现的,就是实现Elements提供的虚方法,比如drawPointdrawCircledrawLineStringdrawLinearRingdrawLinedrawPolygondrawSurface。         

//以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);
  OpenLayers.Map类实现的函数APIMethod是分组的,比如Layer Functions、Control Functions、Popup Functions、Container Div Functions、Zoom, Center, Pan Functions、Layer Options、Baselayer Functions、Zooming Functions、Translation Functions。其中,最关键的是 Layer Functions和Control Functions,因为就是Layer对象和Control对象构成了map的主体。
下面从每组函数中挑选出一两个来,看看具体实现过程。
Layer Functions:
就看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。


OK!

这两个月的笔记都记录在有道云笔记上了,所以现在花时间将其批量放到博客上,所以若有问题指教,或者技术问题讨论请联系我,徐明:[email protected]。也可以直接评论,但可能回复不够及时。










你可能感兴趣的:(源码,框架,Gis,OpenLayers,地图)