网址: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文件
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. 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);
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基类的实现过程,再选择几个典型的子类分析一下。
//设置控件的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。
“鹰眼”实际上也是地图导航的一种形式,在外部形态上跟图层开关控件有点儿像。
添加鹰眼控件的语句:
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从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,从而将事件传出来。
来看其他的成员函数:
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参数是预先定义的回调函数。
OK!
这两个月的笔记都记录在有道云笔记上了,所以现在花时间将其批量放到博客上,所以若有问题指教,或者技术问题讨论请联系我,徐明:[email protected]。也可以直接评论,但可能回复不够及时。