|
KoalaGIS的在線地圖系統(tǒng),打算采用國內(nèi)外主要的幾個地圖服務(wù):google地圖、百度地圖、OpenStreet Map、天地圖。google、百度、openstreet的服務(wù)器性能好,速度快,但google由于政策原因,不確定因素太多,Openstreet Map個人感覺配圖不怎么好看,添加天地圖完全就是為了顧忌下咱們這個GIS專業(yè)的臉面,畢竟總要搞點(diǎn)專業(yè)的東西吧,雖然他很垃圾,但勝于無!
為此,需要在OpenLayers上擴(kuò)展一個百度地圖圖層出來,讓其能在Openlayers中顯示出來。為了疊加百度地圖,首先必須了解其瓦片的組織規(guī)則,通過對百度api的js文件解析和網(wǎng)上相關(guān)資料的收集,經(jīng)過程序測試,推論正確,現(xiàn)講解下其基本的參數(shù),以及編寫的KoalaGIS.Layer.WMTS.Baidu圖層。
百度地圖也是采用的魔卡托投影,個人感覺和google的web 墨卡托投影是一樣的,但據(jù)網(wǎng)友測試,兩者計算結(jié)果有點(diǎn)出入,我猜測可能是百度或者google地圖有偏移參數(shù)的影響吧,該文暫不分析此原因,且該問題本人還沒正式測試過,有待考證!
利用百度提供的API進(jìn)行坐標(biāo)轉(zhuǎn)換
var projection =new BMap.MercatorProjection(); var point = projection.lngLatToPoint(new BMap.Point(116.404, 39.915)); 得出的結(jié)果是12958175,4825923.77
用Openlayers提供的web墨卡托投影對該經(jīng)緯度投影得出的結(jié)果為:????
百度地圖的分辨率問題
對百度地圖api的js腳本進(jìn)行解析,可以看到該函數(shù):
可以看到兩個獲取分辨率相關(guān)的函數(shù)如下:
getZoomUnits: function(T) { return Math.pow(2, (18 - T)) //從這里可以看出,百度地圖的分辨率幾乎就是固定的2的N次冪來計算的 },

百度地圖的坐標(biāo)系統(tǒng):平面坐標(biāo)系的原點(diǎn)與經(jīng)緯度的原點(diǎn)一致,即赤道與0度經(jīng)線相交的位置

百度地圖瓦片編號方式:
百度地圖API在展示地圖時是將整個地圖圖片切割成若干圖塊來顯示的,當(dāng)?shù)貓D初始化或是地圖級別、中心點(diǎn)位置發(fā)生變化時,地圖API會根據(jù)當(dāng)前像素坐標(biāo)計算出視野內(nèi)需要的圖塊坐標(biāo)(也叫圖塊編號),從而加載對應(yīng)的圖塊用以顯示地圖。
百度地圖的圖塊坐標(biāo)原點(diǎn)與平面坐標(biāo)一致,從原點(diǎn)向右上方開始編號為0, 0:
如何通過一個坐標(biāo)和縮放級別計算出百度瓦片所在的行列號呢?
百度瓦片的計算方式很簡單,大致如下:
1.計算出坐標(biāo)的平面坐標(biāo)位置,瓦片的切割起止點(diǎn)就是0,0
2. 用平面坐標(biāo)除以分辨率就能得到坐標(biāo)到起點(diǎn)的像素大小,然后除以每張瓦片的大小取整就是瓦片的行列號了
cx = [( ( x - 0 ) / res ) / tileWidth]
cy = [( ( y - 0 ) / res ) / tileHeight]
分辨率前面已經(jīng)講過,res = Math.Pow(2, level - 18 );
3.舉例來說吧,第18級下,選取經(jīng)緯度為(116.404, 39.915)的坐標(biāo)點(diǎn),該點(diǎn)大致在北京天安門那,經(jīng)過投影得出其平面位置為(12958175, 4825923.77)采用百度的api計算出來的,用openlayers計算的不是該坐標(biāo)。利用上面的行列號計算公式推算出瓦片行列號為:50617, 18851。利用百度地圖提供的瓦片服務(wù)器地址,根據(jù)行列號和縮放級別,可以調(diào)用該瓦片地址:
OpenLayers中添加百度地圖
遇到的問題:
1,坐標(biāo)系不一致。
Openlayers中采用的坐標(biāo)系并非百度地圖的數(shù)學(xué)坐標(biāo)系
2,行列號的計算方式不一樣
百度地圖是從左到右,從下到上;但Openlayers和google以及通常的wmts服務(wù)都是從左到右,從上到下的!
解決辦法:坐標(biāo)系還是按照Openlayers的思路,所有的方式都不動,只是把他計算瓦片的方式改變就OK了。
KoalaGIS.Layer.WMTS.Baidu.js文件
代碼如下:
(function () { window.KoalaGIS = { VERSION: '1.0.0', Author: 'KolaGIS Studio' };
KoalaGIS.Layer = {};
KoalaGIS.Layer.WMTS = {};
})();
KoalaGIS.Layer.WMTS.Baidu = OpenLayers.Class(OpenLayers.Layer.XYZ, {
url: null,
tileOrigin: new OpenLayers.LonLat(-20037508.34, 20037508.34),
tileSize: new OpenLayers.Size(256, 256),
type: 'png',
useScales: false,
overrideDPI: false,
initialize: function (options) { this.resolutions = []; for (var i = 0; i < 19; i++) { this.resolutions[i] = Math.pow(2, 18 - i); }
OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
if (this.resolutions) { this.serverResolutions = this.resolutions; this.maxExtent = this.getMaxExtentForResolution(this.resolutions[0]); }
// this block steps through translating the values from the server layer JSON // capabilities object into values that we can use. This is also a helpful // reference when configuring this layer directly. if (this.layerInfo) { // alias the object var info = this.layerInfo;
// build our extents var startingTileExtent = new OpenLayers.Bounds( info.fullExtent.xmin, info.fullExtent.ymin, info.fullExtent.xmax, info.fullExtent.ymax );
// set our projection based on the given spatial reference. // esri uses slightly different IDs, so this may not be comprehensive this.projection = 'EPSG:' + info.spatialReference.wkid; this.sphericalMercator = (info.spatialReference.wkid == 102100);
// convert esri units into openlayers units (basic feet or meters only) this.units = (info.units == "esriFeet") ? 'ft' : 'm';
// optional extended section based on whether or not the server returned // specific tile information if (!!info.tileInfo) { // either set the tiles based on rows/columns, or specific width/height this.tileSize = new OpenLayers.Size( info.tileInfo.width || info.tileInfo.cols, info.tileInfo.height || info.tileInfo.rows );
// this must be set when manually configuring this layer this.tileOrigin = new OpenLayers.LonLat( info.tileInfo.origin.x, info.tileInfo.origin.y );
var upperLeft = new OpenLayers.Geometry.Point( startingTileExtent.left, startingTileExtent.top );
var bottomRight = new OpenLayers.Geometry.Point( startingTileExtent.right, startingTileExtent.bottom );
if (this.useScales) { this.scales = []; } else { this.resolutions = []; }
this.lods = []; for (var key in info.tileInfo.lods) { if (info.tileInfo.lods.hasOwnProperty(key)) { var lod = info.tileInfo.lods[key]; if (this.useScales) { this.scales.push(lod.scale); } else { this.resolutions.push(lod.resolution); }
var start = this.getContainingTileCoords(upperLeft, lod.resolution); lod.startTileCol = start.x; lod.startTileRow = start.y;
var end = this.getContainingTileCoords(bottomRight, lod.resolution); lod.endTileCol = end.x; lod.endTileRow = end.y; this.lods.push(lod); } }
this.maxExtent = this.calculateMaxExtentWithLOD(this.lods[0]); this.serverResolutions = this.resolutions; if (this.overrideDPI && info.tileInfo.dpi) { // see comment above for 'overrideDPI' OpenLayers.DOTS_PER_INCH = info.tileInfo.dpi; } } } },
getContainingTileCoords: function (point, res) { // return new OpenLayers.Pixel( // Math.max(Math.floor((point.x - this.tileOrigin.lon) / (this.tileSize.w * res)), 0), // Math.max(Math.floor((this.tileOrigin.lat - point.y) / (this.tileSize.h * res)), 0) // );
return new OpenLayers.Pixel( Math.floor((point.x - this.tileOrigin.lon) / (this.tileSize.w * res)), Math.floor((point.y - this.tileOrigin.lat) / (this.tileSize.h * res)) ); },
calculateMaxExtentWithLOD: function (lod) { // the max extent we're provided with just overlaps some tiles // our real extent is the bounds of all the tiles we touch
var numTileCols = (lod.endTileCol - lod.startTileCol) + 1; var numTileRows = (lod.endTileRow - lod.startTileRow) + 1;
var minX = this.tileOrigin.lon + (lod.startTileCol * this.tileSize.w * lod.resolution); var maxX = minX + (numTileCols * this.tileSize.w * lod.resolution);
var maxY = this.tileOrigin.lat - (lod.startTileRow * this.tileSize.h * lod.resolution); var minY = maxY - (numTileRows * this.tileSize.h * lod.resolution); return new OpenLayers.Bounds(minX, minY, maxX, maxY); },
calculateMaxExtentWithExtent: function (extent, res) { var upperLeft = new OpenLayers.Geometry.Point(extent.left, extent.top); var bottomRight = new OpenLayers.Geometry.Point(extent.right, extent.bottom); var start = this.getContainingTileCoords(upperLeft, res); var end = this.getContainingTileCoords(bottomRight, res); var lod = { resolution: res, startTileCol: start.x, startTileRow: start.y, endTileCol: end.x, endTileRow: end.y }; return this.calculateMaxExtentWithLOD(lod); },
getUpperLeftTileCoord: function (res) { var upperLeft = new OpenLayers.Geometry.Point( this.maxExtent.left, this.maxExtent.top); return this.getContainingTileCoords(upperLeft, res); },
getLowerRightTileCoord: function (res) { var bottomRight = new OpenLayers.Geometry.Point( this.maxExtent.right, this.maxExtent.bottom); return this.getContainingTileCoords(bottomRight, res); },
getMaxExtentForResolution: function (res) { var start = this.getUpperLeftTileCoord(res); var end = this.getLowerRightTileCoord(res);
var numTileCols = (end.x - start.x) + 1;
//var numTileRows = (end.y - start.y) + 1;
var numTileRows = (start.y - end.y) + 1;
var minX = this.tileOrigin.lon + (start.x * this.tileSize.w * res); var maxX = minX + (numTileCols * this.tileSize.w * res);
//var maxY = this.tileOrigin.lat - (start.y * this.tileSize.h * res); var maxY = this.tileOrigin.lat + (start.y * this.tileSize.h * res); var minY = maxY - (numTileRows * this.tileSize.h * res); return new OpenLayers.Bounds(minX, minY, maxX, maxY); },
clone: function (obj) { if (obj == null) { obj = new OpenLayers.Layer.ArcGISCache(this.name, this.url, this.options); } return OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); },
getMaxExtent: function () { var resolution = this.map.getResolution(); return this.maxExtent = this.getMaxExtentForResolution(resolution); },
getTileOrigin: function () { //debugger; var extent = this.getMaxExtent(); return new OpenLayers.LonLat(extent.left, extent.bottom); },
getURL: function (bounds) { //debugger;
var z = this.map.getZoom();
var res = this.getResolution();
// z = 18 - z;
// var res = Math.pow(2, z - 18);
// tile center var originTileX = (this.tileOrigin.lon + (res * this.tileSize.w / 2)); // var originTileY = (this.tileOrigin.lat - (res * this.tileSize.h / 2));
var originTileY = (this.tileOrigin.lat + (res * this.tileSize.h / 2));
originTileX = 0; originTileY = 0;
var center = bounds.getCenterLonLat(); //center.lat = 4825923.77; //center.lon = 12958175; var point = { x: center.lon, y: center.lat };
// var x = (Math.round(Math.abs((center.lon - originTileX) / (res * this.tileSize.w)))); // //var y = (Math.round(Math.abs((originTileY - center.lat) / (res * this.tileSize.h)))); // var y = (Math.round(Math.abs((center.lat - originTileY) / (res * this.tileSize.h))));
var x = (Math.round((center.lon - originTileX) / (res * this.tileSize.w))); //var y = (Math.round(Math.abs((originTileY - center.lat) / (res * this.tileSize.h)))); var y = (Math.round((center.lat - originTileY) / (res * this.tileSize.h)));
// x = Math.round(center.lon * 1 / this.tileSize.w); // y = Math.round(center.lat * 1 / this.tileSize.h);
//var x = Math.floor(Math.abs((center.lon) * res / this.tileSize.w)); //var y = (Math.round(Math.abs((originTileY - center.lat) / (res * this.tileSize.h)))); //var y = Math.floor(Math.abs((center.lat ) * res / this.tileSize.h));
//x = Math.round(Math.abs(x) / 256 ); // y = Math.round(Math.abs(y) / 256 );
// this prevents us from getting pink tiles (non-existant tiles) if (this.lods) { var lod = this.lods[this.map.getZoom()]; if ((x < lod.startTileCol || x > lod.endTileCol) || (y < lod.startTileRow || y > lod.endTileRow)) { return null; } } else { var start = this.getUpperLeftTileCoord(res); var end = this.getLowerRightTileCoord(res); // if ((x < start.x || x >= end.x) // || (y < start.y || y >= end.y)) { // return null; // }
if ((x < start.x || x >= end.x) || (y >= start.y || y < end.y)) { //return null; } }
// Construct the url string var url = this.url; var s = '' + x + y + z;
if (OpenLayers.Util.isArray(url)) { url = this.selectUrl(s, url); }
// Accessing tiles through ArcGIS Server uses a different path // structure than direct access via the folder structure. if (this.useArcGISServer) { // AGS MapServers have pretty url access to tiles url = url + '/tile/z/{y}/{x}'; } else { // The tile images are stored using hex values on disk. //x = 'C' + this.zeroPad(x, 8, 16); // y = 'R' + this.zeroPad(y, 8, 16); // z = 'L' + this.zeroPad(z, 2, 16); // url = url + '/{z}/y/{x}.' + this.type;
var x_str = 'x′;varystr=′{y}'; if (x < 0) x_str = 'Mx′;if(y<0)ystr=′M{y}'; url = url + '/u=x=' + x_str + ';y=' + y_str + ';z=${z};v=011;type=web&fm=44'; }
// Write the values into our formatted url url = OpenLayers.String.format(url, { 'x': Math.abs(x), 'y': Math.abs(y), 'z': z });
return url; },
zeroPad: function (num, len, radix) { var str = num.toString(radix || 10); while (str.length < len) { str = "0" + str; } return str; },
CLASS_NAME: 'KoalaGIS.Layer.WMTS.Baidu'
});
參考資料:
1.《百度地圖API詳解之地圖坐標(biāo)系統(tǒng)》http://www.cnblogs.com/jz1108/archive/2011/07/02/2095376.html
2.一撇一捺的博客 http://blog.sina.com.cn/simplester
|