獲取百度地圖窗口內的當前顯示內容瓦片,能夠經過地圖對象的當前配置變量的計算從新獲得,所以調整窗口位置、縮放,能夠很容易的獲取所須要的瓦片的下載地址。下載瓦片,而後部署到對應的路徑下,就能夠實現地圖的離線。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裏面關於瓦片地址計算的代碼啊,尤爲不要改爲離線版的!由於你要參照實際的地圖來下載這些瓦片啊。
要下載代碼,直接點擊這裏。