`

OpenLayers项目分析

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

网址: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、MSVirtualEarth、WorldWind等等。当然,也可以用简单的图片作为源。



第一次使用OpenLayers:



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



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



    <script src="../lib/OpenLayers.js"></script>

  <script type="text/javascript">

        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 :定义底层类与定制JS内置类  



    先说基类型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.<i>"left-bottom=(5,42) right-top=(10,45)"</i>)

     */

    toString:function() {

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

                 + " right-top=(" + this.right + "," + this.top + ")" );

    }

结果类似于"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 < this.left)) {

                     this.left = bounds.left;}



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

                    this.bottom = bounds.bottom;}



                if ( (this.right == null) || (bounds.right > this.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 项目分析——(三)BaseTypes (续)
(三)BaseTypes: OpenLayers中定制JavaScript内置类



  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 数据解析—以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<featureNodes.length; 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<order.length; ++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中的控件



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

本篇文章来源于 GIS空间站 转载请以链接形式注明出处 网址:http://www.gissky.net/Article/1900.htm
分享到:
评论

相关推荐

    openlayers项目分析文档

    openlayers是一个开源得webgis项目,该文档收集了对openlayers开源webgis项目的总结,包括项目介绍、源代码总体结构分析、定义底层类与定制JS内置类、空间数据的组织与实现、数据渲染分析、地图表现、OpenLayers中的...

    webgis之openlayers全面解析pdf

    WebGIS开发基础、OpenLayers开发基础、OpenLayers快速入门、OpenLayers之多源数据加载、OpenLayers之图形绘制、OpenLayers之OGC、OpenLayers之高级功能,*后给出了OpenLayers之项目实战――水利信息在线分析服务系统...

    WebGIS之OpenLayers全面解析

    WebGIS开发基础、OpenLayers开发基础、OpenLayers快速入门、OpenLayers之多源数据加载、OpenLayers之图形绘制、OpenLayers之OGC、OpenLayers之高级功能,最后给出了OpenLayers之项目实战——水利信息在线分析服务...

    vue3+vite+openlayers6实现绘制扇形及环形组件代码

    内容概要:通过带着读者手写...阅读建议:此资源以开发绘制扇形及环形学习其原理地图容器,不仅是代码编写实现也更注重内容上的需求分析和方案设计,所以在学习的过程要结合这些内容一起来实践,并调试对应的代码。

    《WebGIS之OpenLayers全面解析》一书源码

    WebGIS开发基础、OpenLayers开发基础、OpenLayers快速入门、OpenLayers之多源数据加载、OpenLayers之图形绘制、OpenLayers之OGC、OpenLayers之高级功能,最后给出了OpenLayers之项目实战——水利信息在线分析服务...

    WebGIS之OpenLayers全面解析之源码

    WebGIS开发基础、OpenLayers开发基础、OpenLayers快速入门、OpenLayers之多源数据加载、OpenLayers之图形绘制、OpenLayers之OGC、OpenLayers之高级功能,最后给出了OpenLayers之项目实战——水利信息在线分析服务...

    webgis开发示例 for OpenLayers3

    基于openlayers3的webgis客户开发demo,功能丰富,可直接二次开发。定位、聚点图、热点图、个性图、路径动画、动画点、图形绘制、军标绘制、影像过滤、区域分割、地形渲染、最近点分析、弹出框、图表

    bcadr_map_vue:GIS Openlayers Vue

    bcadr_map_vue(GIS,openlayers,vue)简介是美丽华夏大数据研究院基于开源框架VUE和的OpenLayers开发的一款二维在线GIS应用程序。在多源数据加载,二维空间分析以及多种可视化效果等功能的支撑下,用户可以进行开...

    python项目棉花数据平台建设与可视化系统(django).zip

    棉花数据平台建设与可视化系统是一个基于Django框架的Web项目,旨在收集、整理和展示与棉花生产、市场和管理相关的数据。该系统允许用户通过图形界面查询棉花相关数据,并通过图表和地图等形式直观地展现分析结果。 ...

    基于JavaScript的WebGIS前端开发及优化

    近年来网站客户端开发规模越来越大并且有很多由AJAX开发的复杂页面, 系统中的客户端脚本大量增加,前端开发就产生了诸如代码量大、组织困难、难 ...把前端优化技术 应用到项目实现中,具体分析了各自的性能优化效果。

    wms源码java-risky:用于分析时间戳位置数据的工具,特别是来自AIS的船舶位置报告

    用于分析时间戳位置数据的工具,例如来自 AIS 的船只位置报告。 广泛使用来自 . 需要 Java 8。 状态:生产中 子项目 描述 解析 nmea 和 ais 消息 读取和发布字符串流的套接字广播(如 AIS) 漂移候选和碰撞候选检测...

    jsts:JavaScript拓扑套件

    该项目的主要目标是为Web制图应用程序提供一个完整的库,用于处理和分析简单的几何图形,但是JSTS也可以用作独立的几何图形库。 JSTS是通过将原始JTS Java源通过AST自动转换为保留AST转换而制成的,除了与I / O相关...

    ClimateMap:MétéoHack2019项目

    服务不足区域分析 预测 灵感 从7月8日到7月11日,邀请开发人员,数据科学家,技术专家,研究人员,专家,学者和所有天气数据爱好者使用ECCC收集和生成的数据,探索天气和气候等环境因素对加拿大人的近乎普遍的影响。...

    GISportal:基于Web的GIS工具,用于可视化和分析地理空间数据

    GIS门户网站是项目OpEc的一部分,并且随着eartH2Observe以及其他开发投资或特殊功能的赞助而继续发展。 实例实例 您可以在找到GISportal软件示例实例的完整列表。 概述 门户由三部分组成。 一个使用HTML , CSS和...

    ogv:打开地理数据查看器

    OGV Open Geodata Viewer是一个基于库的开源项目,由计划开发,目的是提供免费的在线地图查看器。...静态代码分析 为了对项目代码进行质量控制,并检测可能的错误和漏洞,将执行以下命令: npm r

    国家安全项目:富含地理信息的国家安全Web GIS平台

    产品特点兴趣点模块代理商管理模块事故学模块犯罪学模块冒险活动模块实时抗议模块游行队伍管理数据分析仪表板模块示范影片该演示中的关注区域是摩洛哥拉巴特的第四区。建于 。 。 。 。 。 。 。 。 等等。入门先决...

Global site tag (gtag.js) - Google Analytics