乐趣区

关于离线地图图层下载的原理

概述

前段时间有个项目因为网络原因,需要再内网搭建一个地图,于是经过一个多星期的折腾,地图终于搭建完成,
这里我们地图图层的核心部分简单的介绍一下。网上也有专门的图层下载器,不过都是要收钱的,如果不想付钱,就可以根据原理自己下载即可。

离线地图

地图包括很多部分,其中图层是最基础的,其次是它 js 和 css,再次就是他们对应的地图数据,如:我搜索“网吧”能把附件的网吧标注出来一样。

图层

图层就是常说的瓦片地图,一种多分辨率层次的模型,从瓦片金字塔的底层到顶层,分辨率越来越低,但表示的地理范围不变。

 
 如图:

(图片来与百度百科)我这里以百度地图为例:

我们仔细查看,我们发现百度地图也是一个一个的图片,通过 CSS 样式的排版控制来显示地图,当上一个图层在下一个图层中又是 4 个图片,就这样一层一层的,一共 1 ~ 19 层

同时我们也能发现,图片是一个连着一个的,我们注意到每个图片都是这么一个个链接:

 https://ss1.bdstatic.com/8bo_dTSlR1gBo1vgoIiO_jowehsv/tile/?qt=vtile&x=815&y=214&z=12&styles=pl&udt=20190425&scaler=1&showtext=0
 https://ss0.bdstatic.com/8bo_dTSlR1gBo1vgoIiO_jowehsv/tile/?qt=vtile&x=815&y=213&z=12&styles=pl&udt=20190425&scaler=1&showtext=0
 其中,x=815&y=214&z=12 就是我们的图片坐标,X - 横坐标
 Y - 纵坐标
 Z - 图层
 ss1.bdstatic.com,ss2.bdstatic.com 等就是服务器集群的域名。

所以,这样我们就可以知道,我们如果下载图层,我们只需要知道图层的坐标即可下载。那么图层的坐标是如何计算得到呢?

百度图层算法

关于图层的算法,我大致介绍下,具体的算法还是查专业资料,O(∩_∩)O 哈哈~…

如图:

因为地球是园的,地图是平面的,所以就有一个转换过程,叫什么墨卡托投影(具体查专业资料),大致意思是假想一个与地轴方向一致的圆柱切或割于地球,按等角条件,将经纬网投影到圆柱面上,将圆柱面展为平面。
平面后我们就可以通过 横坐标、纵坐标,来计算点的距离,然后在换算为像素坐标。具体算法示例如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title> 地图坐标概念 </title>
<script src="http://api.map.baidu.com/api?v=2.0&ak=GVehn0frGnwbDWjRPxGI6GR4rmfqw7DG"></script>
</head>
<body>
    <div id="map_container" style="width: 500px; height: 320px;"></div>
    <script>
        
    var map = new BMap.Map('map_container', {defaultCursor : 'default'});
        
        // 设置中心点
        map.centerAndZoom(new BMap.Point(120.035641, 30.246635), 17);

        var TILE_SIZE = 256;

        // 点击一个坐标点,返回对应的坐标
        map.addEventListener('click', function(e) {
            
            var info = new BMap.InfoWindow('', {width : 260});
            var projection = this.getMapType().getProjection();

            var lngLat = e.point;
            var lngLatStr = "经纬度:" + lngLat.lng + "," + lngLat.lat;

            var worldCoordinate = projection.lngLatToPoint(lngLat);
            var worldCoordStr = "<br /> 平面坐标:" + worldCoordinate.x + "," + worldCoordinate.y;

            var pixelCoordinate = new BMap.Pixel(Math.floor(worldCoordinate.x * Math.pow(2, this.getZoom() - 18)),
                    Math.floor(worldCoordinate.y * Math.pow(2, this.getZoom() - 18)));
            
            var pixelCoordStr = "<br /> 像素坐标:" + pixelCoordinate.x + ","
                    + pixelCoordinate.y;

            var tileCoordinate = new BMap.Pixel(Math.floor(pixelCoordinate.x / 256), Math.floor(pixelCoordinate.y / 256));
            var tileCoordStr = "<br /> 图块坐标:" + tileCoordinate.x + "," + tileCoordinate.y;

            var viewportCoordinate = map.pointToPixel(lngLat);
            var viewportCoordStr = "<br /> 可视区域坐标:" + viewportCoordinate.x
                    + "," + viewportCoordinate.y;

            var overlayCoordinate = map.pointToOverlayPixel(lngLat);
            var overlayCoordStr = "<br /> 覆盖物坐标:" + overlayCoordinate.x + ","
                    + overlayCoordinate.y;

            info.setContent(lngLatStr + worldCoordStr + pixelCoordStr
                    + tileCoordStr + viewportCoordStr + overlayCoordStr);
            
            map.openInfoWindow(info, lngLat);
        });
    </script>
</body>
</html>

天地图图层算法

天地图原理也类似,其坐标点和图层的算法如下:

package com.topinfo.tdt;


/**
 * @ClassName:  Test2   
 * @Description: 根据坐标获取对应的瓦片行、列 
 * @author: 杨攀
 * @date:   2019 年 2 月 20 日 下午 2:00:46     
 * @Copyright: 2019 www.tuxun.net Inc. All rights reserved.
 */
public class Test2 {public static void main(String[] args){
        
        int level = 18;
        
        // 左上角经纬度
        double leftTop_lon = 115.422051;
        double leftTop_lat = 40.978643;
        
        // 右下角经纬度  
        double rightBotton_lon = 117.383319;
        double rightBotton_lat = 39.455766;
        
        System.out.println (getTileNumber (leftTop_lat, leftTop_lon, level));
        System.out.println (getTileNumber (rightBotton_lat, rightBotton_lon, level));
       
    }

    public static String getTileNumber(final double lat,final double lon,final int level){int xtile = (int) Math.floor ((lon + 180) / 360 * (1 << level));
        int ytile = (int) Math.floor ((1 - Math.log (Math.tan (Math.toRadians (lat)) + 1 / Math.cos (Math.toRadians (lat))) / Math.PI) / 2 * (1 << level));
        if (xtile < 0) xtile = 0;
        if (xtile >= (1 << level)) xtile = ((1 << level) - 1);
        if (ytile < 0) ytile = 0;
        if (ytile >= (1 << level)) ytile = ((1 << level) - 1);
        return (xtile + "/" + ytile);
    }
}

下载使用

下载图层

原理我们懂了,其实就是把你要下载的地图左上角、和右下角坐标拿到,然后两个 for 循环即可。
比如:我们 下载杭州的 地图,我们先通过地图提供的获取行政区划 API,一般都会有地图的左上角、和右下角的坐标,比如:天地图

它这里的四个角其实就是 左上角、和右下角的坐标,不信你们可以直接试试,知道左上角、和右下角的坐标后,通过上面的算法,计算出左上角、和右下角的图层在 Z 层的 X、Y 的坐标,剩下的就是下一个下载方法进行下载了,可以写一个下载连接池进行下载。
在下载的时候,这里给出两个建议:
1、把最后一层(百度是 19 层,天地图是 18 层)单独线程下载,其他的层单独线程下载,最后一层非常大。
2、下载太多的话,最好别在公司下,否则别人以为你是在攻击,会禁用你们公司的 IP,会导致你们公司后面无法访问地图,需要跟他们客户沟通才能解禁,O(∩_∩)O 哈哈~~

竟然不能提交附件,我放到百度网盘了:
链接: https://pan.baidu.com/s/1nyix… 提取码: 8ttf 复制这段内容后打开百度网盘手机 App,操作更方便哦

网盘中包括
1、百度的离线 js 和算法 demo,不包括下载的方法,自己实现一个即可。
2、天地图的图层下载示例,因为我们只是一锤子买卖,平常几乎不会离线地图,各位可以优化性能,可以修改为多线程的下载,可以字符串连接等,优化你们自己处理啊,我只是抛砖引玉一下。不包括离线的 js,离线 js 改起来也简单的。

修改 JS 的离线

百度的 JS 离线,网上有很多,我们只需要下载稍微修改下,见上面的网盘链接。
天地图 JS 离线就相对比较麻烦,我也是断断续续修改了好几天才修改好,需要注意我们在测试离线的时候,先把网络断了,清空浏览器缓存(这步很重要,因为有些是缓存到 locaStorage 中),然后在测试,把报错的地方统统修改掉,或者把需要用到的 js,css 提前下载到本地,然后本地引用即可,

具体代码 JS 我就不传了,因为都比较简单,只要细心都不是问题的。

退出移动版