1、寫在前面
在使用高德地圖API和百度地圖API的時候,如果要加載地圖服務如WMS,WMTS等,這些地圖服務常用的投影坐標系是EPSG:3857。加載上去會發現存在偏移,因為投影坐標系不一致。
高德的坐標系是GCJ-02,而百度的坐標系是在GCJ-02再次偏移的BD-09,這些坐標系是沒有收錄在EPSG中的,所以無法用Proj.4庫來做坐標轉換。
我們是否可以通過整體的偏移來做呢?不行的,因為GCJ-02坐標系相對於WMS坐標系的偏差是非線性隨機的。這么做感覺就是在為難國內的開發者,一方面不能不使用WGS坐標,因為這個是國際通用的,另一方面又在設置重重障礙讓WGS坐標和GCJ-02坐標難以轉化。
不過也不是束手無策的,高德和百度都有提供單點的坐標轉換功能,我們可以利用單點的坐標轉換來實現切片的偏移。也有一個開源的項目 gcoord 融合了百度高德的轉化
2、思路
百度高德在請求切片圖層的時候,對於每一個切片來說,切片的BBOX坐標是可以計算出來的。在默認情況下,會使用計算出來的BBOX坐標請求WMS或是WMTS服務,這樣是有偏差的。我們可以對計算出來的BBOX坐標進行單點偏移,使用偏移后的BBOX坐標請求地圖服務就可以實現地圖的吻合。
3、實現
百度地圖:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html,
body {
margin: 0;
padding: 0;
}
#allmap {
width: 100%;
height: 100vh;
}
</style>
<script type="text/javascript" src="./util.js"></script>
<script src="https://unpkg.com/gcoord/dist/gcoord.js"></script>
</head>
<body>
<div id="allmap"></div>
<script type="text/javascript">
var map = null;
//百度地圖API功能
function loadJScript() {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://api.map.baidu.com/api?v=2.0&ak=你的秘鑰&callback=init";
document.body.appendChild(script);
}
function init() {
map = new BMap.Map("allmap"); // 創建Map實例
var point = new BMap.Point(116.997528, 36.676244); // 創建點坐標
map.centerAndZoom(point, 15);
map.enableScrollWheelZoom(); //啟用滾輪放大縮小
addWMSLayer();
map.addEventListener("click", e => {
console.log(e);
})
}
function addWMSLayer() {
let tileLayer = new BMap.TileLayer({
transparentPng: true
})
// 不同的分辨率,數值上等於一像素對應的距離
var resolutions = []
for (var i = 0; i < 19; i++) {
resolutions[i] = Math.pow(2, 18 - i);
}
tileLayer.getTilesUrl = function (tileCoord, zoom) {
// x,y瓦片左上角橫縱坐標(米)
var x = tileCoord.x;
var y = tileCoord.y;
var resolution = resolutions[zoom];
var tileWidth = 256;
// 計算瓦片的BBOX
var minx = x * tileWidth * resolution;
var miny = y * tileWidth * resolution;
var maxx = (x + 1) * tileWidth * resolution;
var maxy = (y + 1) * tileWidth * resolution;
// 將BBOX的BD-09米制坐標轉為EPSG:3857
var bottomLeft = gcoord.transform(
[minx, miny],
gcoord.BD09MC,
gcoord.EPSG3857
);
var topRight = gcoord.transform(
[maxx, maxy],
gcoord.BD09MC,
gcoord.EPSG3857
);
let bbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];
// wms參數
let baseUrl = "http://{host}:{port}/geoserver/{workspace}/wms?";
let params = {
SERVICE: "WMS",
VERSION: "1.1.1",
REQUEST: "GetMap",
FORMAT: "image/png",
TRANSPARENT: true,
LAYERS: "{workspace:layerName}",
WIDTH: 256,
HEIGHT: 256,
SRS: "EPSG:3857"
}
var url = baseUrl + objToParams(params) + `&bbox=${bbox.join(",")}`
return url;
}
map.addTileLayer(tileLayer);
}
window.onload = loadJScript; //異步加載地圖
</script>
</body>
</html>
高德地圖:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>異步加載地圖</title>
<link rel="stylesheet" href="https://cache.amap.com/lbs/static/main1119.css" />
<script src="https://unpkg.com/gcoord/dist/gcoord.js"></script>
<script src="./util.js"></script>
</head>
<body>
<div id="container"></div>
<script>
var map = null;
function onApiLoaded() {
map = new AMap.Map('container', {
center: [117.11116783127187, 36.67459186502283],
zoom: 15
});
map.on("click", e => {
console.log(e);
})
addWMSLayer();
}
// 切片轉經度
function tile2lon(x, z) {
return (x / Math.pow(2, z) * 360 - 180);
}
// 切片轉緯度
function tile2lat(y, z) {
var n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
}
function addWMSLayer() {
var resolutions = []
for (var i = 0; i < 19; i++) {
resolutions[i] = Math.pow(2, 18 - i);
}
var tileLayer = new AMap.TileLayer({
tileSize: 256,
tileUrl: function (x, y, z) {
// 獲取瓦片BBOX的經緯度坐標,這里和百度坐標的轉化不一樣,因為沒有在gcoord中找到GCJ-02米制坐標轉化,只好通過經緯度了
let xmin = tile2lon(x, z);
let xmax = tile2lon(x + 1, z);
let ymin = tile2lat(y + 1, z);
let ymax = tile2lat(y, z);
// 將BBOX的GCJ02坐標轉為EPSG:3857
let t1 = gcoord.transform([xmin, ymin], gcoord.GCJ02, gcoord.EPSG3857);
let t2 = gcoord.transform([xmax, ymax], gcoord.GCJ02, gcoord.EPSG3857);
// wms配置
let url = 'http://{host}:{port}/geoserver/{workspace}/wms?';
let params = {
service: "WMS",
version: "1.1.0",
transparent: true,
request: "GetMap",
layers: "{workspace:layerName}",
width: 256,
height: 256,
srs: "EPSG:3857",
format: "image/png",
bbox: [t1[0], t1[1], t2[0], t2[1]].join(',')
};
// 構建查詢字符串
let str = objToParams(params);
return url + str;
},
zIndex: 10
})
map.add(tileLayer);
}
var url = 'https://webapi.amap.com/maps?v=1.4.15&key=你的秘鑰&callback=onApiLoaded';
var jsapi = document.createElement('script');
jsapi.charset = 'utf-8';
jsapi.src = url;
document.head.appendChild(jsapi);
</script>
</body>
</html>
幫助類
// 對象轉為查詢字符串
function objToParams(obj){
return Object.keys(obj).map(function (key) {
return "".concat(encodeURIComponent(key), "=").concat(encodeURIComponent(obj[key]));
}).join('&');
}