百度地圖2.0瓦片地址獲取(窗口內瓦片)

獲取百度地圖窗口內的當前顯示內容瓦片,能夠經過地圖對象的當前配置變量的計算從新獲得,所以調整窗口位置、縮放,能夠很容易的獲取所須要的瓦片的下載地址。下載瓦片,而後部署到對應的路徑下,就能夠實現地圖的離線。javascript

1.3版的百度地圖瓦片獲取是直接參考內部的代碼抽取整理而成的,而相關的理論沒有作過多的研究。當代碼遷移到2.0上的時候,代碼的變更很大,以致於想輕鬆的抽取代碼是不現實的。css

好在百度地圖實現的理論是不變的,所以理論上來講,1.3版的百度地圖和2.0版的百度地圖在瓦片上是同樣的,獲取流程上也是同樣的,只是某些參數的獲取方式可能改變了,好比直接不提供相關的功能的訪問。html

還好上次寫1.3版的百度地圖瓦片獲取代碼的時候,我多深刻了解了一下一些功能的實現,寫在註釋裏面了。對上次寫的博文《百度地圖2.0瓦片獲取》仍是不夠滿意的,由於上次寫的只能實現增量式瓦片地址獲取,而沒法一次性得到窗口內的全部瓦片地址。java

此次再次翻騰了一下1.3版的這部分代碼,感受仍是應該能夠移植的,所以結合瀏覽器的調試,探究了一下相關方法和屬性,寫出了新版的百度地圖2.0瓦片地址獲取程序,但願可以幫助到一些有須要的人。git

一樣的,下載程序不提供,自行嵌入某些程序下載吧。api

主要流程和方法描述:瀏覽器

對於頁面內的地圖,是有一個js對象map來承載的,後期異步的操做都是基於這個對象,獲取當前窗口內的瓦片地址也絕不例外。個人思路是,根據map對象,和給出一個回調方法,把當前窗口內的瓦片地址給計算出來,而後經過回調方法輸出出來。app

個人實現是,寫一個方法,全部變量對象都包在這個方法當中,這樣就不會污染外部程序;而後調用這個方法的時候,我返回另一個方法,這個方法的參數就是map和callback,當調用這個方法的時候,當前地圖對象窗口內的瓦片地址就會經過回調方法輸出出來。異步

就是這樣:oop

function downloadBaiduTiles(){
//……
return function(map, callback){findAllTiles(map, callback);};
}

而findAllTiles方法是這樣寫的:

function findAllTiles(map, callback){
    //var mapType = map.getMapType();// 地圖類型,如今裏面沒那麼多的可用數據了,直接不要了
    var zoomLevel = map.getZoom();// 放大倍數,如今是用getZoom了,原先是zoomLevel,如今沒了。
    var center = map.getCenter(); //這個中心可能須要轉換一下。先試試。 //map.mercatorCenter;// 原先獲取的中心座標
    center = convertLL2MC(center);//果真是須要轉換才能用啊
    //var cV = mapType.getZoomUnits(zoomLevel);// zoomLevel相關的一個指數,=2^(18-zoomLevel)
    var cV = Math.pow(2, 18-zoomLevel);//如今直接計算這個數。理論不變,代碼變了,咱們直接計算好了。
    var unitSize = cV * 256; //這個也只能本身計算了//mapType.getZoomFactor(zoomLevel);// 一個係數,=cV*256
    var longitudeUnits = Math.ceil(center.lng / unitSize);// center.lng是一個很大的數
    var latitudeUnits = Math.ceil(center.lat / unitSize);//這兩個不用解釋了吧
    var tileSize = 256; //這個直接給常亮吧。要不得是mapType.k.Ob//mapType.getTileSize();
    var cP = [ longitudeUnits, latitudeUnits, (center.lng - longitudeUnits * unitSize) / unitSize * tileSize,
            (center.lat - latitudeUnits * unitSize) / unitSize * tileSize ];
    var left = cP[0] - Math.ceil((map.width / 2 - cP[2]) / tileSize);
    var top = cP[1] - Math.ceil((map.height / 2 - cP[3]) / tileSize);
    var right = cP[0] + Math.ceil((map.width / 2 + cP[2]) / tileSize);
    var c0 = 0;
    //if (mapType === BMAP_PERSPECTIVE_MAP && map.getZoom() == 15) {//這句應該不起做用
    //    c0 = 1
    //}
    var bottom = cP[1] + Math.ceil((map.height / 2 + cP[3]) / tileSize) + c0;
    var xydata = [];
    for (var i = left; i < right; i++) {
        for (var j = top; j < bottom; j++) {
            xydata.push([ i, j ])
        }
    }
    //var zoom = map.getZoom();
    //這個地方能直接獲取瓦片地址的內部方法,挺好,省去了不少的代碼移植。
    var getTilesUrl = map.getMapType().getTileLayer().getTilesUrl;
    //循環獲取代碼吧
    for (var i = 0, len = xydata.length; i < len; i++) {
        var url = getTilesUrl({x:xydata[i][0],y:xydata[i][1]}, zoomLevel, "normal");
        var path = zoomLevel+"/"+xydata[i][0]+"/"+xydata[i][1]+".png";
        if(!!callback){
            callback(path, url);
        }
    }      
}

這個方法的實現百分之九十以上是和1.3版的同樣,區別在於,有些原先1.3提供的方法或變量都再也不提供了。還好,由於瓦片沒變,理論沒變,其實1.3版的程序裏面許多的參數也是能夠拿來直接用的。所以,能變通獲取的,我就變通獲取了,不能變通獲取的,我直接以常亮來代替。這問題應該是不大的。

主題的思路就是,要拿到當前地圖的放大倍數,其實就是層級。越大的層級劃分越細,瓦片數量越多,單個瓦片覆蓋的實際地理面積越小,顯示越精確。而後要能拿到地圖的中心座標。這個中心座標就是實際的經緯度信息。這個信息在內部信息化處理的時候是須要一個轉換的。這個轉換須要參考相關的理論,這裏就不給出了。地圖自己是能拿到當前窗口的寬度和高度的,所以經過上面的放大倍數、中心座標,是徹底能夠計算應該展現哪些瓦片的。而後瓦片地址的計算其實至關容易,就是一個url。這一點,1.3和2.0沒有區別,也就是說,百度並無區分1.3版瓦片和2.0版瓦片,也不必。要知道,全部瓦片加起來多是幾十G甚至是多少T計算的。

裏面的計算我就不說啦。

要實現裏面座標的轉換,還須要其餘一些常亮、方法的輔助。

var EARTHRADIUS = 6370996.81;
var MCBAND = [ 12890594.86, 8362377.87, 5591021, 3481989.83,
        1678043.12, 0 ];
var LLBAND = [ 75, 60, 45, 30, 15, 0 ];
var MC2LL = [
        [ 1.410526172116255e-8, 0.00000898305509648872,
-1.9939833816331, 200.9824383106796,
-187.2403703815547, 91.6087516669843,
-23.38765649603339, 2.57121317296198,
-0.03801003308653, 17337981.2 ],
        [ -7.435856389565537e-9, 0.000008983055097726239,
-0.78625201886289, 96.32687599759846,
-1.85204757529826, -59.36935905485877,
47.40033549296737, -16.50741931063887,
2.28786674699375, 10260144.86 ],
        [ -3.030883460898826e-8, 0.00000898305509983578,
0.30071316287616, 59.74293618442277,
7.357984074871, -25.38371002664745,
13.45380521110908, -3.29883767235584,
0.32710905363475, 6856817.37 ],
        [ -1.981981304930552e-8, 0.000008983055099779535,
0.03278182852591, 40.31678527705744,
0.65659298677277, -4.44255534477492,
0.85341911805263, 0.12923347998204,
-0.04625736007561, 4482777.06 ],
        [ 3.09191371068437e-9, 0.000008983055096812155,
0.00006995724062, 23.10934304144901,
-0.00023663490511, -0.6321817810242,
-0.00663494467273, 0.03430082397953,
-0.00466043876332, 2555164.4 ],
        [ 2.890871144776878e-9, 0.000008983055095805407,
-3.068298e-8, 7.47137025468032,
-0.00000353937994, -0.02145144861037,
-0.00001234426596, 0.00010322952773,
-0.00000323890364, 826088.5 ] ];


var LL2MC = [
        [ -0.0015702102444, 111320.7020616939,
1704480524535203, -10338987376042340,
26112667856603880, -35149669176653700,
26595700718403920, -10725012454188240,
1800819912950474, 82.5 ],
        [ 0.0008277824516172526, 111320.7020463578,
647795574.6671607, -4082003173.641316,
10774905663.51142, -15171875531.51559,
12053065338.62167, -5124939663.577472,
913311935.9512032, 67.5 ],
        [ 0.00337398766765, 111320.7020202162,
4481351.045890365, -23393751.19931662,
79682215.47186455, -115964993.2797253,
97236711.15602145, -43661946.33752821,
8477230.501135234, 52.5 ],
        [ 0.00220636496208, 111320.7020209128,
51751.86112841131, 3796837.749470245,
992013.7397791013, -1221952.21711287,
1340652.697009075, -620943.6990984312,
144416.9293806241, 37.5 ],
        [ -0.0003441963504368392, 111320.7020576856,
278.2353980772752, 2485758.690035394,
6070.750963243378, 54821.18345352118,
9540.606633304236, -2710.55326746645,
1405.483844121726, 22.5 ],
        [ -0.0003218135878613132, 111320.7020701615,
0.00369383431289, 823725.6402795718,
0.46104986909093, 2351.343141331292,
1.58060784298199, 8.77738589078284,
0.37238884252424, 7.45 ] ];



function Point(lng, lat){
    this.lng = lng;
    this.lat = lat;
}

function convertor (point, ll2mc) {
    if (!point || !ll2mc) {
        return
    }
    // 經度的轉換比較簡單,一個簡單的線性轉換就能夠了。
    // 0、1的數量級別是這樣的-0.0015702102444, 111320.7020616939
    var x = ll2mc[0] + ll2mc[1] * Math.abs(point.lng);
    // 先計算一個線性關係,其中9的數量級是這樣的:67.5,a的估值大約是一個個位數
    var a = Math.abs(point.lat) / ll2mc[9];
    // 維度的轉換相對比較複雜,y=b+ca+da^2+ea^3+fa^4+ga^5+ha^6
    // 其中,a是維度的線性轉換,而最終值則是一個六次方的多項式,二、三、四、五、六、七、8的數值大約是這樣的:
    // 278.2353980772752, 2485758.690035394,
    // 6070.750963243378, 54821.18345352118,
    // 9540.606633304236, -2710.55326746645,
    // 1405.483844121726,
    // 這意味着維度會變成一個很大的數,大到多少很難說
    var y = ll2mc[2] + ll2mc[3] * a + ll2mc[4] * a * a + ll2mc[5] * a
    * a * a + ll2mc[6] * a * a * a * a + ll2mc[7] * a
    * a * a * a * a + ll2mc[8] * a * a * a * a
    * a * a;
    // 整個計算是基於絕對值的,符號位最後補回去就好了
    x *= (point.lng < 0 ? -1 : 1);
    y *= (point.lat < 0 ? -1 : 1);
    // 產生一個新的點座標。果真不同了啊
    return new Point(x, y)
}

function lngLatToMercator(T) {
    return convertLL2MC(T);
}

function getLoop(value, min, max) {
    while (value > max) {
        value -= max - min
    }
    while (value < min) {
        value += max - min
    }
    return value
}

function convertLL2MC (point) {
    var point1;
    var ll2mc;
    point.lng = getLoop(point.lng, -180, 180);// 標準化到區間內
    point.lat = getLoop(point.lat, -74, 74);// 標準化到區間內
    point1 = new Point(point.lng, point.lat);
    // 查找LLBAND的維度字典,字典由大到小排序,找到則中止
    for (var i = 0; i < LLBAND.length; i++) {
        if (point1.lat >= LLBAND[i]) {
            ll2mc = LL2MC[i];
            break;
        }
    }
    // 若是沒有找到,則反過來找。找到即中止。
    if (!ll2mc) {
        for (var i = LLBAND.length - 1; i >= 0; i--) {
            if (point1.lat <= -LLBAND[i]) {
                ll2mc = LL2MC[i];
                break;
            }
        }
    }
    var newPoint = convertor(point, ll2mc);
    var point = new Point(newPoint.lng.toFixed(2), newPoint.lat.toFixed(2));
    return point;
}

這裏面我就再也不解釋了,都是理論相關的。

好啦,引入這個js,而後獲取窗口內的百度地圖的瓦片吧。

下面給出的html是基於上一版的瓦片地址獲取參考代碼的,作了一點修改:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>百度離線版2.0DEMO</title>
<script type="text/javascript" src="js/apiv2.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/bmap.css"/>
<script type="text/javascript" src="js/findtiles.js"></script>
</head>
<body>
<div style="width:520px;height:340px;border:1px solid gray" id="container"></div>
<br>
<input type="button" value="findtiles" onclick="findtiles();">
<br>
<div id="list"></div>
</body>
</html>
<script type="text/javascript">
var map = new BMap.Map("container",{mapType: BMAP_NORMAL_MAP});      //設置衛星圖爲底圖
var point = new BMap.Point(111.404, 40.915);    // 建立點座標
map.centerAndZoom(point,5);                     // 初始化地圖,設置中心點座標和地圖級別。

//map.addControl(new BMap.MapTypeControl());
map.addControl(new BMap.NavigationControl());
map.enableScrollWheelZoom();                  // 啓用滾輪放大縮小。
map.enableKeyboard();                         // 啓用鍵盤操做。  
//map.setCurrentCity("北京");          // 設置地圖顯示的城市 此項是必須設置的



var marker = new BMap.Marker(point);
map.addOverlay(marker); 

var polyline = new BMap.Polyline([
        new BMap.Point(111.404, 40.915),
        new BMap.Point(112.404, 42.915),
        new BMap.Point(113.404, 39.915),
        new BMap.Point(114.404, 42.915),
        new BMap.Point(115.404, 39.915),
        new BMap.Point(116.404, 42.915)
    ], {strokeColor:"blue", strokeWeight:2, strokeOpacity:0.5});
map.addOverlay(polyline);


function addUrls(path, url){
    var div = document.getElementById("list");
    var anchor = document.createElement("a");
    anchor.href = url;
    anchor.innerHTML = path;
    div.appendChild(anchor);
    var br = document.createElement("br");
    div.appendChild(br);
}


function findtiles(){
    var findAllTiles = downloadBaiduTiles();
    findAllTiles(map, addUrls);
}

</script>

提醒一點,不要修改apiv2.0.min.js裏面關於瓦片地址計算的代碼啊,尤爲不要改爲離線版的!由於你要參照實際的地圖來下載這些瓦片啊。

要下載代碼,直接點擊這裏

相關文章
相關標籤/搜索