视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
OpenLayers心得文档
2025-10-02 15:37:45 责编:小OO
文档
作者博客:http://blog.3snews.net/?10624

OpenLayers项目分析——(一)项目介绍

网址: http://www.openlayers.org/ 

  OpenLayers 是由 MetaCarta公司开发的, 用于WebGIS客户端的JavaScript包,目前的最高版本是2.5 V,通过BSD License 发行。它实现访问地理空间数据的方法都符合行业标准,比如OpenGIS的WMS和WFS规范, OpenLayers采用纯面向对象的JavaScript方式开发,同时借用了Prototype框架和Rico库的一些组件。 

  采用OpenLayers作为客户端不存在浏览器依赖性。由于OpenLayers采用JavaScript语言实现,而应用于Web浏览器中的DOM(文档对象模型)由JavaScript实现,同时,Web浏览器(比如IE,FF等)都支持DOM 。 

  OpenLayers APIs采用动态类型脚本语言JavaScript编写,实现了类似与Ajax功能的无刷新更新页面,能够带给用户丰富的桌面体验(它本身就有一个Ajax类,用于实现Ajax功能)。 

  目前,OpenLayers所能够支持的Format有: XML、 GML、GeoJSON 、 GeoRSS、JSON、KML 、WFS、WKT( Well-Known Text )。在OPenlayers.Format名称空间下的各个类里,实现了具体读/写这些Format的解析器。 

  OpenLayers所能够利用的地图数据资源“丰富多彩”,在这方面提供给拥护较多的选择,比如 WMS、WFS 、 GoogleMap、KaMap、 MS VirtualEarth、WorldWind 等等。当然,也可以用简单的图片作为源。 

第一次使用OpenLayers: 

   先 到它的官方网站 http://www.openlayers.org 下载他的压缩包,解压后可以看到其中的一些目录和文件,拷贝目录下的OpenLayer.js、根目录下的lib目录、根目录下的img目录到你网站的Scripts目录下(当然,这个只是例子,您网站的目录结构您自己说得算,只要保证OpenLayers.js,/lib,/img在同一目录中即可)。  然后, 创建一个index.html作为查看地图的页面,导入OpenLayers.js和你将要创建的js 。 

  我们以加载WMS和GML文件为例。  

    

  

         var lon = 5;     // x-axis coodinate in map units 

        var lat = 40;    // y-axis coordinate in map units 

        var zoom = 5;    // number of zoom levels 

         var map, layer;   

     //声明变量map、layer;等同于 var map = null; var layer = null; 

         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.zoomToExtent(newOpenLayers.Bounds(-3.922119,44.335327, 

         4.866943,49.553833)); 

    //在Map对象上加载Layer对象,并用 map.zoomToExtent 函数使地图合适地显示 

     map.addLayer(new OpenLayers.Layer.GML("GML", "gml/polygon.xml")); 

     //再在刚加载的WMS文件上,加载一GML文件 

  剩下的工作就是,加上一些控件 OpenLayers.Control 之类的东西,比如 LayerSwitcher等。它们会在地图浏览的“窗口”上增加一些工具栏或是“按钮”,增加互动性和功能性。 

当然, Openlayers中的东西远不止这些,至于它的框架分析、APIs实现机制,会在后续文章中说出。写这个的过程,也是一个学习的过程,其中难免有不妥之处,热烈欢迎大家批评指正,相互交流。 

OpenLayers 项目完整分析——(二)源代码总体结构分析

  通过前面的项目介绍,我们大概已经知道 Openlayers是什么,能够做什么,有什么意义。接下来我们分析它怎么样,以及怎样实现的等问题。 

  这个图是从它的文档上截取的,旨在从感官上认识一下OpenLayers的类。下面分别介绍(文档中的类是按字母顺序排列的,也按这个顺序说吧): 

  我们看到在类的顶层“高高在上”的是OpenLayers,它为整个项目实现提供名称空间(JavaScript语言没有名称空间一说,但是它确实有自己的机制实现类似的功能,后面会说明),它直接拥有一常量  VERSION_NUMBER,以标识版本。 

    Ajax: 顾名思义,用于实现Ajax功能,只是OpenLayers的开发者们把它单独写到一个类里了,其中用到了Prototype.js框架里的一些东西。同时,设计的时候也考虑了跨浏览器的问题。 

   BaseTypes: 这里定制了OpenLayers中用到的 string,number  和  function。比如,OpenLayers. String. startsWith,用于测试一个字符串是否一以另一个字符串开头;OpenLayers. Number. limitSigDigs,用于整数的有效数位;OpenLayers. Function.bind,用于把某一函数绑定于对象等等。 

   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之类的。 OpenLayers中的事件机制是非常值得我们学习的,后面将具体讨论。 

   Feature: 我们知道:Feature是 geography 和attributes的集合。在OpenLayers中,特别地OpenLayers.Feature 类由一个marker 和一个lonla组成。 

OpenLayers. Feature.WFS与OpenLayers. Feature. Vector继承于它。 

   Format: 此类用于读/写各种格式的数据,它的子类都分别创建了各个格式的解析器。这些格式有: XML、 GML、GeoJSON 、 GeoRSS、JSON、KML 、WFS、WKT( Well-Known Text )。 

    Geometry: 怎么翻译呢,几何?是对地理对象的描述。它的子类有Collection、Curve、LinearRing、LineString、MultiLineString、MultiPoint、MultiPolygon、Point、Polygon、Rectangle、Surface,正是这些类的实例,构成了我们看到的地图。需要说明的是, Surface 类暂时还没有实现。 

   Handler: 这个类用于处理序列事件,可被激活和取消。同时,它也有命名类似于浏览器事件的方法。当一个 handler 被激活,处理事件的方法就会被注册到浏览器listener ,以响应相应的事件;当一个handler被取消,这些方法在事件中也会相应的被取消注册。Handler通过控件control被创建,而control通过icon表现。 

   Icon: 在计算机屏幕上以图标的形式呈现,有url、尺寸size和位置position3个属性。一般情况,它与  OpenLayers.Marker结合应用,表现为一个 Marker。 

   Layer: 图层。 

   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中,渲染功能是作为矢量图层的一个属性存在的,我们称之为渲染器,矢量图层就是通过这个渲染器提供的方法将矢量数据显示出来。以SVG 和VML为例,继承关系是这样的:      

  

至于 OpenLayers. Renderer. Elements为什么要存在,以及它的渲染机制,后面会说。 

   Tile: 设计这个类用于指明单个“瓦片”Tile,或者更小的分辨率。Tiles存储它们自身的信息,比如url和size等。它的类继承关系如下: 

      

   Util:“ 跑龙套”的类。 

  写到这里,可以看到OpenLayers 的类缠绕的挺麻烦的,接下来的文章将从代码部分分析更细部的东西。 

OpenLayers 项目分析——(三)BaseTypes

    先说基类型BaseTypes下,OpenLyers构建的“自己”的类。它们分别是:OpenLayers. LonLat、OpenLayers. Pixel、OpenLayers.Size、OpenLayers. Element、OpenLayers. Bounds和OpenLayers. Class。下面分别介绍:

  OpenLayers. LonLat:经纬度类,其实例为地图提供一经度、纬度对,即位置。有两个属性lon(x-axis coodinate )和lat(y-axis coordinate )。这里说明一下,怎么经纬度又与x轴坐标、y轴坐标纠缠在一起?是这样:当地图是在地理坐标投影下,它就是经纬度;不然就是地图上的x/y轴坐标。除构造函数外,实现了五个函数:

toShortString:function() 把坐标转换为字符串;

clone:function()  复制一个LonLat对象;

Add:function(lon,lat)  改变现有地图的位置;

  return new OpenLayers.LonLat(this.lon + lon, this.lat + lat);

equals:function(ll)  判断传入的lon,lat对是否与当前的相等;

wrapDateLine:function(maxExtent)  复制下(lon,lat),指定为边界的最大范围。

  OpenLayers. Pixel:像素类,在显示器上以(x,y)坐标的的形式呈现像素位置。有两个属性x坐标、y坐标,提供四个成员函数:

clone:function() 拷贝像素;

equals:function(px)  判断两像素是否相等;

add:function(x,y)  改变(x,y)使其成为新像素;

return new OpenLayers.Pixel(this.x + x, this.y + y);

offset:function(px)  调用add()使像素位置发生偏移。

  newPx = this.add(px.x, px.y);

  OpenLayers.Size:也有两个属性,宽度width、高度height。实现了两个成员函数:clone:function()和equals:function(sz)不多说了。

  OpenLayers. Element:在这个名称空间下,开发者写了好多API,有visible、toggle、hide、show、remove、getHeight、getDimensions和getStyle,以实现元素的显示、隐藏、删除、取得高度,取得范围等功能。以getHeight函数为例我们看看它的代码:

  /**

     * APIFunction: getHeight

     *  

     * Parameters:

     * element - {DOMElement}

     * 

     * Returns:

     * {Integer} The offset height of the element passed in

     */

    getHeight: function(element) {

        element = OpenLayers.Util.getElement(element);

        return element.offsetHeight;

    }

这里涉及到文档对象模型DOM的一些东西,函数本身很简单,最后返回元素的高度。

  OpenLayers. Bounds:在这个类中,数据以四个浮点型数left, bottom, right, top 的格式存储,它是一个像盒子一样的范围。它实现了三个描述一个Bound的函数:toString、toArray和toBBOX。其中,toString的代码如下:

  /** 

     * APIMethod: toString

     * 

     * Returns:

     * {String} String representation of bounds object. 

     *          (ex."left-bottom=(5,42) right-top=(10,45)")

     */

    toString:function() {

        return ( "left-bottom=(" + this.left + 

                 + " right-top=(" + this.right + 

    }

结果类似于"left-bottom=(5,42) right-top=(10,45)"

  三个Bound数据来源函数:fromString、fromArray和fromSize;

五个获取对象属性的函数:getWidth、getHeight、getSize、getCenterPixel、getCenterLonLat;

余下还有:add:function(x,y),extend:function(object),containsLonLat,containsPixel,contains,intersectsBounds,containsBounds,determineQuadrant,wrapDateLine。以函数extend为例,看看源码。

    extend:function(object) {

        var bounds = null;

        if (object) {

            switch(object.CLASS_NAME) {

                case "OpenLayers.LonLat":    

                    bounds = new OpenLayers.Bounds    (object.lon, object.lat, object.lon, object.lat);

                    break;

                case "OpenLayers.Geometry.Point":

                    bounds = new OpenLayers.Bounds(object.x, object.y,object.x, object.y);

                    break;                 

                case "OpenLayers.Bounds":   

                    bounds = object;

                    break;

            }

            if (bounds) {

                if ( (this.left == null) || (bounds.left < thi                     s.left)) {

                     this.left = bounds.left;}

                if ( (this.bottom == null) || (bounds.bottom <                     this.bottom) ) {

                    this.bottom = bounds.bottom;} 

                if ( (this.right == null) || (bounds.right > t                    his.right) ) {

                    this.right = bounds.right;}

                if ( (this.top == null) || (bounds.top > this.                    top) ) { 

                    this.top = bounds.top;}

            }

        }

    }

可以看出,对Bounds的扩展可以有三种形式:point, lonlat, 或者bounds,计算的条件是零坐标是在屏幕的左上角。

  OpenLayers. Class:这个类是OpenLayers 中的“大红人”,只要创建其他类就得用它,同时也实现了多重继承。用法如下:

  单继承创建:class = OpenLayers.Class(prototype);

  多继承创建:class = OpenLayers.Class(Class1, Class2, prototype);

    净说底层类了,对js内置类的扩展下回写。 

欢迎转载,请注明出处。

  OpenLayers不仅“自己”写了一些底层的类,像上回说的那些都是。同时也定制了一些JS的一些内置类,即对JS内置类的扩展。这个扩展主要包含3类:String,Number,Function,存在于BaseTypes.js文件中。

  String:

OpenLayers对string类型定制了8个方法,分别是startsWith、contains、trim和camelize;还有另外4个方法:String. startsWith、String. contains、String.trim和String. Camelize,它们将会在3.0Version中被删除,可能是以前版本遗留下来的,这里就不说它们了。

  //Test whether a string starts with another string. 

  startsWith: function(str, sub) {

    return (str.indexOf(sub) == 0);

    }

 

  //Test whether a string contains another string.

    contains: function(str, sub) {

        return (str.indexOf(sub) != -1);

    }

 

    //Removes leading and trailing whitespace characters from a string.

    trim: function(str) {

        return str.replace(/^\\s*(.*?)\\s*$/, "$1");    

    }

 

   //Camel-case a hyphenated string.

  //Ex."chicken-head"becomes"chickenHead

   //and"-chicken-head"becomes"ChickenHead".

   // “骆驼”化带有连字符的字符串。

   camelize: function(str) {

        var oStringList = str.split('-');

        var camelizedString = oStringList[0];

        for (var i = 1; i < oStringList.length; i++) {

            var s = oStringList[i];

            camelizedString += s.charAt(0).toUpperCase() + s.substring(1);

        }

        return camelizedString;

    }

 

Number:

项目仅对number类型扩展了一个方法OpenLayers. Number. limitSigDigs(还有一个方法Number. limitSigDigs,同样在3.0中会删除)。

    //Limit the number of significant digits on an integer.

    limitSigDigs: function(num, sig) {

        var fig;

        if(sig > 0) {

            fig = parseFloat(num.toPrecision(sig));

        } else {

            fig = 0;

        }

        return fig;

    }

 

Function:

扩展了两个方法bind 和bindAsEventListener(同样存在Function.bind和Function. bindAsEventListener两个被“遗弃”的函数)。

    //Bind a function to an object.  

    //Method to easily create closures with'this' altered.

    bind: function(func, object) {

        // create a reference to all arguments past the second one

        var args = Array.prototype.slice.apply(arguments, [2]);

        return function() {

            // Push on any additional arguments from the actual function call.

            // These will come after those sent to the bind call.

            var newArgs = args.concat(

                Array.prototype.slice.apply(arguments, [0])

            );

            return func.apply(object, newArgs);

        };

    }

 

    //Bind a function to an object, and configure it to receive the event

    //object as first parameter when called. 

    bindAsEventListener: function(func, object) {

        return function(event) {

            return func.call(object, event || window.event);

        };

    }

这里说说这两个方法。

首先看看bind方法,这是一个能够被Function的实例得到的方法,如下所示:

Function.prototype.bind = function() {

var _method = this, args = [], object = arguments[0];

for (var i = 1; i < arguments.length; i++)

args.push(arguments[i]);

return function(moreargs) {

for (var i = 0; i < arguments.length; i++)

args.push(arguments[i]);

return  _method.apply(object, args);

}

};

_method 代表Function实例自身,bind可接收多个参数,不过它绑定是是第一个参数,该参数是一个function或者是调用环境,后面的都是执行函数的参数。

Function.prototype.bindAsEventListener = function(object) {

var  _method = this;

return function(event) {

return  _method.call(object, event || window.event);

}

};

这里只是将object作为_method 引用的环境,就是说现在可以在object对象中这样使用,

object. _method (event||window.event)。

也许你注意到了Funtion扩展的两个方法一个用到了call而另一个用的是apply,其实这两个并没有什么太大的区别,只是参数传递的形式不同,如若没有参数要传递,那么这两个是一样的:

apply(obj[,argumentsArray]),call(obj[,arg1[,arg2…]])。 

OpenLayers项目分析——(四)空间数据的组织与实现

  提到数据,先思考几个问题: 

  GIS,核心是什么?数据?平台?服务?   

  空间数据的特征、表达方式? 

  地理数据的模型(结构)? 

  在OpenLayers空间数据的实现主要存在 OpenLayers. Geometry类及其子类中。我们先看下面的 两个图片,表现了这些类的继承关系。从图上可以清楚的看出MultiPoint、Polygon和MultiLineString 这三个类实现了多重继承,即直接继承于Geometry类,又继承于Collection类(为什么要这样实现?)。 

   OpenLyers对于Geometry对象的组织是这样的,其实最 基础 的就是点,然后MultiPoint由点构成,继承自Openlayers.Geometry.Collection,而LinearRing,LineString均由Point构 成, 

Polygon由OpenLayers.Geometry.LinearRing构成。OpenLyers在解析数据时候,将所有的面、线包含的点全部都对象化为Openlayers.Geometry.Point。有人测试这里面存在问题:解析矢量数据慢,甚至在点数多的情况下,会使浏览器“崩溃”掉。想想是有道理的:OpenLyers在解析数据时候,将所有的面、线包含的点全部都对象化为点对象t,并首先将所有的对象读取到内存,得到一个Feature的集合,然后将这个集合提交给渲染器进行渲染。这样渲染起来当然慢了。至于为什么要这样,可能是 OpenLayers项目本身在标准上,在框架结构上做的比较好,更细部的东西还得优化呀。可话又说回来,OpenLayers作为一个优秀的开源JS框架,学习借鉴的意义要比应用的意义大吧。   

 

 

  下面以Point和Collection为例来说明其内部实现过程,先看Point。 

  我们知道一个点就是一个坐标对(x,y)嘛,当然它得有两个属性x,y。在point类里,提供了六个成员函数,分别是 clone、distanceTo、equals、move、rotate和resize。看看计算两点距离的函数是怎么写的: 

distanceTo: function(point) { 

        var distance = 0.0; 

        if ( (this.x != null) && (this.y != null) &&  

             (point != null) && (point.x != null) && (point.y != null) ) {              

             var dx2 = Math.pow(this.x - point.x, 2); 

             var dy2 = Math.pow(this.y - point.y, 2); 

             distance = Math.sqrt( dx2 + dy2 ); 

        } 

        return distance; 

    } 

  在collection集合对象中,可以存放同一类型的地理对象,也可以放不同的地理对象。定义了一个属性 component ,以数组对象的形式存储组成 collection对象的“组件”。别的不说了,看一个获取集合大小的函数 getLength : 

    getLength: function() { 

        var length = 0.0; 

        for (var i = 0; i < this.components.length; i++) {

            length += this.components[i].getLength(); 

        } 

        return length; 

    } 

   细心的朋友也许会发现,每一个基类都有一个 destroy 函数。它是 OpenLayers实现的垃圾回收机制,以防止内存泄露,优化性能: 

    /* APIMethod: destroy 

     * Destroy this geometry. 

     */ 

    destroy: function () { 

        this.components.length = 0; 

        this.components = null; 

    } 。 

 

OpenLayers项目分析——(五) 数据解析——以GML为例

  前面也提到过,OpenLayers设计是符合标准的,有良好的框架结构和实现机制,非常值得学习。OpenLayers支持的格式比较多,有XML、GML、GeoJSON、GeoRSS、JSON、KML、WFS等。这回主要以GML为例来看OpenLayers 数据的解析过程。

  先来了解一下GML:

  GML (Geography Markup Language)即地理标识语言,它由OGC(开放式地理信息系统协会)于1999年提出,目前版本是3.0。GML是XML在地理空间信息领域的应用。利用GML可以存储和发布各种特征的地理信息,并控制地理信息在Web浏览器中的显示。地理空间互联网络作为全球信息基础架构的一部分,已成为Internet上技术追踪的热点。许多公司和相关研究机构通过Web将众多的地理信息源集成在一起,向用户提供各种层次的应用服务,同时支持本地数据的开发和管理。GML可以在地理空间Web领域完成了同样的任务。GML技术的出现是地理空间数据管理方法的一次飞跃。

  介绍一篇文章:GML3.0的WebGlS研究。

  我们从总体上来把握一下OpenLayers对于GML数据的解析,首先通过调用得到GML文本数据,然后通过Formate.GML类的read方法来解析这个文本,解析得到Geometry对象,然后Geometry对象用相应的渲染器画出来。其实解析得到还是那些基本的Point呀、LineString呀之类的Geometry对象,就是我们在地图上看到的那些内容。

  下面看其实现过程:

  //read()函数读取数据,获取特征列表

    read: function(data) {

        if(typeof data == "string") { 

            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);

        }

        var featureNodes = this.getElementsByTagNameNS           (data.documentElement,this.gmlns,   this.featureName);

        var features = [];

        for(var i=0; i            var feature = this.parseFeature(featureNodes[i]);

            if(feature) {

                features.push(feature);

            }

        }

        return features;

    }

 

  //函数parseFeature()是OpenLayers中GML数据格式解析的核心,就是它创建地理对象 

    //和其属性。              

  //实际上,每一个Foramt 子类都实现了这个成员函数,完成类似的功能。

    parseFeature: function(node) {

        // only accept on geometry per feature - look for highest "order"

        var order = ["MultiPolygon", "Polygon

                     "MultiLineString", "LineString

                     "MultiPoint", "Point"];

        var type, nodeList, geometry, parser;

        for(var i=0; i            type = order[i];

            nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);

            if(nodeList.length > 0) {

                // only deal with first geometry of this type

                var parser = this.parseGeometry[type.toLowerCase()];

                if(parser) {

                    geometry = parser.apply(this, [nodeList[0]]);

                } else {

                    OpenLayers.Console.error("Unsupported geometry type: " +

                                             type);

                }

                // stop looking for different geometry types

                break;

            }

        }        

        // construct feature (optionally with attributes)

        var attributes;

        if(this.extractAttributes) {

            attributes = this.parseAttributes(node);

        }

        var feature = new OpenLayers.Feature.Vector(geometry, attributes);

        // assign fid - this can come from a "fid" or "id" attribute

        var childNode = node.firstChild;

        var fid;

        while(childNode) {

            if(childNode.nodeType == 1) {

                fid = childNode.getAttribute("fid") ||

                      childNode.getAttribute("id");

                if(fid) {

                    break;

                }

            }

            childNode = childNode.nextSibling;

        }

        feature.fid = fid;

        return feature;

    }

 

  剩下就是由具体的函数parse and bulid基本的地理对象(还有Attribute),包括point、multipoint、linestring、multilinestring、polygon、multipolygon等,然后在write出来。

结合前面的“OpenLayers空间数据的组织”,我们可以看到OpenLayers在解析获取GML数据的时候,比如涉及到面、线的时候,总是以点为基础构建的。有的朋友做过测试,说这时候,直接用SVG画出来,性能上会好很多(具体没测试过,不想多说什么)。

OpenLayers项目分析——(六)数据渲染分析

  实际上,OpenLayers的整个表现过程是这样的:通过调用获取数据,然后各种格式的解析器解析数据,在用所谓的渲染器渲染后加到图层上,最后再结合相应的控件表现出来,成为一幅我们看到的“动态”地图。 

  这里主要讨论 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; 

        } 

    } 

OpenLayers项目分析——(七)地图表现

        一开始看到OpenLayers,就有一个问题。就是它作为WebGIS的前端,通俗地说,是“显示”地图的。那么,它显示的地图是什么,是怎么显示的,又是怎么实现的?——暂且把这个问题叫做地图表现。我觉得最关键的就是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 对象。

  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);

    }

显然,zoomIn和zoomOut都使用了getZoom方法,放大就是让zoom加1,缩小减1。

OpenLayers项目分析——(八)地图表现(续)

  上一回说到OpenLayers.Map类,这回介绍组成Map的主体部分OpenLayers. Layer类,先从其实现细节上分析,看它是怎么设计出来的。关于它许许多多的子类,即各种图层,想单独写一篇。

  OpenLayers. Layer提供了一个EVENT_TYPES常量,用于支持关于图层的应用事件类型,这些事件有"loadstart", "loadend", "loadcancel", "Visibilitychanged"。

  它“固有”的3个属性:id,name,div。其中,id和name是layer的身份,在对图层进行操作的时候,就是用它们标志的。至于div,大家都制知道,DOM元素,用于存放图层。

  定义的map、event属性,是图层对象对map、event对象的引用;projection属性,设置默认情况下,地图的投影,同时也设置maxExtent, maxResolution, 和units 。

  来看看构造函数:

     * name - {String} The layer name

     * options - {Object} Hashtable of extra options to tag onto the layer

     */

    initialize: function(name, options) {

        this.addOptions(options);

        this.name = name;

        

        if (this.id == null) {

            this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");

            this.div = OpenLayers.Util.createDiv();

            this.div.style.width = "100%";

            this.div.style.height = "100%";

            this.div.id = this.id;

            this.events = new OpenLayers.Events(this, this.div, 

                                                this.EVENT_TYPES);

        }

        if (this.wrapDateLine) {

            this.displayOutsideMaxExtent = true;

        }

    }

OpenLayers中每一个类的构造函数都是以initialize命名的。

  再看看其成员函数:

  destroy函数,相当于析构函数;

  onMapResize,removeMap 虚函数,提供给子类继承;

  //移动函数

  moveTo:function(bounds, zoomChanged, dragging) {

        var display = this.visibility;

        if (!this.isBaseLayer) {

            display = display && this.inRange;

        }

        this.display(display);

    }

  下面一组函数是Baselayer Functions函数,就是layer是Baselayer 的话,所用的函数。

比如,initResolutions、getResolution、getExtent等。

  通过这两次的分析,可以发现,Map和Layers的关系:它们是相互引用的。实际上是这样,OpenLayers的Map类主要包含了对每个图层的引用,对每个控件的引用,对事件的引用,对装载容器的引用(其实就是那些map上层的div)以及对pop的引用,而其自身又包含有大量的方法和属性。图层主要包含了对map的引用,对自身div容器的引用以及事件的引用,而图层自身又包含了大量的属性和方法。map引用了layer,而layer又引用了map,这里就直接形成了循环引用关系。

  这样的组成和关联关系,每动一下,就会涉及到大量的对象,影响了性能。

OpenLayers项目分析——(九)控件

  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中的事件封装是其一大亮点,非常值得学习。说到事件机制,在宏观上不得不涉及控件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" ] 

   看看构造函数的的实现过程:

    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。

OpenLayers分析——(十一)体系结构

好久没来更新了,感觉仍有点儿兴奋……

以前做的一些OpenLayers的分析,又整理了一下——以前弄的图片没有显示,现在基本可以了,发上来,如果有需要了解的朋友可以瞅瞅。

写的不好,请大家原谅了,呵呵……

   一般来说,我们了解一个事物,先是从轮廓、外观结构去认识,然后再从内部更细部的去探究。拿做软件来说吧(就比如OpenLayers),先是在文档设计它的框架体系,有个总体的结构,然后是各个模块的设计,再下来就是具体写代码等。如果要分析一个做好的项目,恰恰与此相反,从具体的代码中分析总结出系统框架(想当初,开发者在开发OpenLayers 的时候,带有项目框架的开发文档会是人手一册的吧)。总结它的框架结构,由于本人水平有限,觉得很难,能写到哪儿算哪儿吧。

 这张图基本上把OpenLayers的体系结构勾勒出来了,也就是我们看到的浏览器上地图的内部抽象表示。

图上最底层的是OpenLayers的数据源Image、GML等等,实际上,它们都是OpenLayers.Layer的子孙类。这些数据经过渲染器OpenLayers.Renderer渲染,然后显示在地图的图层Layer上。我们把整个地图看作一个容器,这个地图容器中还有一些特别的层和控件等。除此之外,还有绑定在Map和Layer上的一系列的待请求的事件。下载本文

显示全文
专题