纯客户端的地图量算

 一般地图量算指的是距离量算和面积量算。

 在线的各类地图也都提供了纯客户端的地图量算——不需要和服务器交互,技术上用三角函数就可以,虽然本文也讨论这个话题,但是会比较下适用于不同情况的量算。

 简单事情简单做:直接计算地图坐标系的距离和面积。贴OpenLayers的代码。

 线段距离量算:

    getLength: function() {

 

        var length = 0.0;
        if ( this.components && (this.components.length > 1)) {
            for(var i=1, len=this.components.length; i<len; i++) {
                length += this.components[i-1].distanceTo(this.components[i]);
            }
        }
        return length;
    }

 通过节点间的距离和,得到距离长度,而两点间的距离计算,a2+b2=c2的三角函数再熟悉不过了。

 多边形面积量算:

    getArea: function() {
        var area = 0.0;
        if ( this.components && (this.components.length > 2)) {
            var sum = 0.0;
            for (var i=0, len=this.components.length; i<len - 1; i++) {
                var b = this.components[i];
                var c = this.components[i+1];
                sum += (b.x + c.x) * (c.y - b.y);
            }
            area = - sum / 2.0;
        }
        return area;
    }

 有了以上的公式,就能完成基本的客户端量算功能了。 

 现在出现了一个问题,这种基于地图的量算准确吗?碰到了经纬度地图的话结果难道是度为单位?

 由于地图是经过地图投影之后显示到二维坐标系上的,所以地图上两点之间的直线映射到地球球面之后,可能就不是两点之间的最短距离了(当然如果地图本身是等角投影就没问题了,这里就不再深入讨论有关投影的知识了),误差产生了。这个误差对科学研究很重要,但对于我们日常生活来说,可能并不重要,为什么呢?通常我们的测距、测面积都是在城市级别,甚至只是在街道级别,那么这相对投影所带来的地图变形来说,微不足道,因此如果是这方面的应用,那么以上的测距和测面积的算法足够用了。但是,如果非要更加精确的计算结果该如何办?下面就讲讲在客户端实现近似精确的计算方法:

 测地距离,摘自OpenLayers:

     getGeodesicLength: function(projection) {
        var geom = this;  // so we can work with a clone if needed
        if(projection) {
            var gg = new OpenLayers.Projection("EPSG:4326");
            if(!gg.equals(projection)) {
                geom = this.clone().transform(projection, gg);
            }
        }
        var length = 0.0;
        if(geom.components && (geom.components.length > 1)) {
            var p1, p2;
            for(var i=1, len=geom.components.length; i<len; i++) {
                p1 = geom.components[i-1];
                p2 = geom.components[i];
                // this returns km and requires lon/lat properties
                length += OpenLayers.Util.distVincenty(
                    {lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
                );
            }
        }
        // convert to m
        return length * 1000;
    }

 考虑到投影的问题,首先要对目标几何对象做投影转换,转换为EPSG:4326,然后基于该大地参考系,计算两点间的球面距离,相比于非测地距离量算,这里的重点是在一个特定的大地参考系下计算两点或多点的球面距离,因此结果也更加精确。

 测地面积,摘自OpenLayers:

    getGeodesicArea: function(projection) {

 

        var ring = this;  // so we can work with a clone if needed
        if(projection) {
            var gg = new OpenLayers.Projection("EPSG:4326");
            if(!gg.equals(projection)) {
                ring = this.clone().transform(projection, gg);
            }
        }
        var area = 0.0;
        var len = ring.components && ring.components.length;
        if(len > 2) {
            var p1, p2;
            for(var i=0; i<len-1; i++) {
                p1 = ring.components[i];
                p2 = ring.components[i+1];
                area += OpenLayers.Util.rad(p2.x - p1.x) *
                        (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
                        Math.sin(OpenLayers.Util.rad(p2.y)));
            }
            area = area * 6378137.0 * 6378137.0 / 2.0;
        }
        return area;
    }

 同样的,首先对目标几何对象做投影转换,转成我们所熟知的EPSG:4326,然后再计算球面面积。球面面积计算也不同于平面多边形的面积计算,需要立体几何的知识了,呵呵。

 一般,这种计算更适合在经纬度地图中使用,普通的米制投影地图可以使用简单的方法,城市级的量算误差基本可以忽略。当然,也不能排除对距离和面积特别敏感的案例,之前也接触过一些在通信领域的案例,最终使用了后面这种测地算法。

 

 最后,说说在iClient中如何用的问题。

 iClient自身是带了服务器的量算功能的,而如果希望使用客户端量算的话,iClient for JavaScript已经在Geometry的公共接口中提供了,可自行查找;而iClient for Flex没有提供类似的功能,那么最好借助openscales-geometry库中所带的方法——getLength/getGeodesicLength/getArea/getGeodesicArea获得帮助。为了简化openscales和iClient for Flex之间的几何对象转换关系,可以用https://github.com/SuperMap/OpenScales-FeatureTransform 项目提供的方法来处理。

 

 

 

 

posted @ 2013-08-11 13:23  dulvyizhihua  阅读(2348)  评论(4编辑  收藏  举报