目前,有不少WebGIS開發包,ArcGIS API for JS、OpenLayers、LeafLetjs等爲咱們從事WebGIS開發的人封裝了不少強大的功能。咱們很方便的使用這些庫的時候,也讓咱們忽略了不少原理性的東西。html
好比說,我以前一直在被一個問題困擾,就是如何將一個點正確的顯示在瀏覽器屏幕的正確的位置,即經緯度座標和屏幕座標的轉換問題。直到我看到一位大牛的博客(點擊學習),裏面對WebGIS的原理進行了深刻的講解。看了他的文章後一直以爲,我寫這篇文章是多餘的。可是大神的文章裏面並無詳細講解原理的代碼實現。我的以爲仍是頗有必要經過實現相應功能的方式瞭解其原理,並且實現時仍是遇到了很多的問題,因此仍是寫了這篇文章。前端
在線地圖及參數python
以Arcgis online上的瓦片地圖爲例,服務中有幾個比較關鍵的使用到的參數。jquery
獲取地圖瓦片canvas
經過觀察arcgis地圖的瓦片組織方式,瀏覽器
http://cache1.arcgisonline.cn/ArcGIS/rest/services/ChinaOnlineCommunityOnlyENG/MapServer/tile/縮放等級/行號/列號
經過python程序將必定縮放等級的瓦片保存到本地 我只抓取了0-5級別的瓦片,並按照arcgis瓦片的保存方式存儲。ide
# -*- coding:utf-8 -*- import urllib2 import urllib import os import math def GetPage(geturl): req = urllib2.Request(geturl) user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 ' \ '(KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36' req.add_header('User-Agent', user_agent) response = urllib2.urlopen(req, timeout=10) page = response.read() return page for level in range(0,6): try: newdir = "MapTitles/"+str(level) os.makedirs(newdir.decode("utf-8")) except: pass for row in range(0,int(math.pow(2,level))): try: newdir = "MapTitles/"+str(level)+"/"+str(row) os.makedirs(newdir.decode("utf-8")) except: pass for col in range(0,int(math.pow(2,level))): f = open("MapTitles/"+str(level)+"/"+str(row)+"/"+str(col)+'.jpg', 'wb') dataurl = "http://cache1.arcgisonline.cn/ArcGIS/rest/services/ChinaOnlineCommunityOnlyENG/MapServer/tile/"+str(level)+"/"+str(row)+"/"+str(col) data = GetPage(dataurl) f.write(data) f.close() pass pass pass
展現頁面函數
展會頁面只含有一個canvas元素做爲地圖容器。學習
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>顯示瓦片地圖</title> </head> <body> <canvas width="1000px" height="700px" id="mapcv" style="margin: 10px"></canvas> </body> <script src="Libs/jquery-1.9.1.min.js"></script> <script src="Scripts/config.js"></script> <script src="Scripts/init.js"></script> </html>
配置信息url
在config.js裏面保存了相關的配置信息
var MapConfig={
RootDir:'MapTitles/',
ViewHeight:700,
ViewWidth:1000,
TitlePix:256,
Resolution:[156543.033928,78271.5169639999,39135.7584820001,19567.8792409999,
9783.93962049996,4891.96981024998,2445.98490512499,1222.99245256249,
611.49622628138,305.748113140558,152.874056570411,76.4370282850732,
38.2185141425366,19.1092570712683,9.55462853563415,4.77731426794937,
2.38865713397468,1.19432856685505],
Scale:[ 591657527.591555,295828763.795777,147914381.897889,73957190.948944,
36978595.474472,18489297.737236,9244648.868618,4622324.434309,2311162.217155,
1155581.108577,577790.554289,288895.277144,144447.638572,72223.819286,
36111.909643,18055.954822,9027.977411,4513.988705],
FullExtent:{
xmin : -20037507.0672,
ymin : -20036018.7354,
xmax : 20037507.0672,
ymax : 20102482.4102,
spatialReference : {
wkid : 102100
}
}
};
功能實現 init.js
上面只是把代碼列了出來,這一部分纔是我要講的終點(纔到重點☺)
① 肯定戰士的地圖中心點座標,以及縮放級別
② 計算當前窗口顯示的地圖範圍
咱們能夠根據窗口的中心點座標,窗口大小,以及當前縮放級別的Resolution能夠很容易經過計算獲得,當前窗口你能夠看到的地圖範圍。
//當前窗口顯示的範圍 minX=centerGeoPoint.x-(MapConfig.Resolution[level]*MapConfig.ViewWidth/2); maxX=centerGeoPoint.x+(MapConfig.Resolution[level]*MapConfig.ViewWidth/2); minY=centerGeoPoint.y-(MapConfig.Resolution[level]*MapConfig.ViewHeight/2); maxY=centerGeoPoint.y+(MapConfig.Resolution[level]*MapConfig.ViewHeight/2);
此處要注意一下地圖的行列號的起點在左上角,可是地圖左上角的投影座標x是最小的,y是最大的。也就會說行列號的起點在左上角,投影座標的起點在左下角。
③ 計算左上角起始行列號
//左上角開始的行列號 leftTopTitleRow = Math.floor(Math.abs(maxY-MapConfig.FullExtent.ymax)/MapConfig.Resolution[level]/MapConfig.TitlePix); leftTopTitleCol = Math.floor(Math.abs(minX-MapConfig.FullExtent.xmin)/MapConfig.Resolution[level]/MapConfig.TitlePix);
④ 計算實際地理範圍
//實際地理範圍 realMinX = MapConfig.FullExtent.xmin+leftTopTitleCol*MapConfig.TitlePix*MapConfig.Resolution[level]; realMaxY = MapConfig.FullExtent.ymax-leftTopTitleRow*MapConfig.TitlePix*MapConfig.Resolution[level];
咱們都知道,咱們獲取到的瓦片的範圍必定是大於顯示窗口的範圍。不然在窗口內顯示的地圖是不完整的
⑤ 計算左上角偏移像素
在將地圖瓦片拼接到窗口內是須要考慮到實際地理範圍與顯示地理範圍的偏移
//計算左上角偏移像素 offSetX = (realMinX-minX)/MapConfig.Resolution[level]; offSetY = (maxY-realMaxY)/MapConfig.Resolution[level];
⑥ 計算瓦片個數
得到瓦片個數後就能夠根據瓦片個數以及偏移後的起始瓦片位置,將每個瓦片拼接到canvas相應的位置上
//計算瓦片個數 xClipNum = Math.ceil((MapConfig.ViewHeight+Math.abs(offSetY))/MapConfig.TitlePix); yClipNum = Math.ceil((MapConfig.ViewWidth+Math.abs(offSetX))/MapConfig.TitlePix);
⑦ 前端繪製瓦片
var mapcv = document.getElementById("mapcv"); var myctx = mapcv.getContext("2d"); for(var i=0;i<xClipNum;i++){ for(var j=0;j<yClipNum;j++){ var beauty = new Image(); beauty.src = MapConfig.RootDir+level+"/"+(leftTopTitleRow+i)+"/"+(leftTopTitleCol+j)+".jpg"; var TitleImg={ img:null, x:0, y:0 }; TitleImg.img=beauty; TitleImg.x=offSetX+(j*MapConfig.TitlePix); TitleImg.y=offSetY+(i*MapConfig.TitlePix); TitlesArry.push(TitleImg); myctx.drawImage(TitleImg.img, TitleImg.x, TitleImg.y); } }
所有代碼
裏面涉及到了一個經緯度換投影座標的函數,詳情參考本人的另外一篇關於百度座標、WGS-8四、火星座標,以及投影座標與經緯度的轉換的文章(點擊跳轉)
$(document).ready(function(){ moveX = 0; moveY = 0; TitlesArry=[]; //設置將要現實的地圖中心點 centerGeoPoint={ x:116.337737, y:39.912465 }; centerGeoPoint=lonlatTomercator(centerGeoPoint); level = 5; //當前窗口顯示的範圍 minX=centerGeoPoint.x-(MapConfig.Resolution[level]*MapConfig.ViewWidth/2); maxX=centerGeoPoint.x+(MapConfig.Resolution[level]*MapConfig.ViewWidth/2); minY=centerGeoPoint.y-(MapConfig.Resolution[level]*MapConfig.ViewHeight/2); maxY=centerGeoPoint.y+(MapConfig.Resolution[level]*MapConfig.ViewHeight/2); //左上角開始的行列號 leftTopTitleRow = Math.floor(Math.abs(maxY-MapConfig.FullExtent.ymax)/MapConfig.Resolution[level]/MapConfig.TitlePix); leftTopTitleCol = Math.floor(Math.abs(minX-MapConfig.FullExtent.xmin)/MapConfig.Resolution[level]/MapConfig.TitlePix); //實際地理範圍 realMinX = MapConfig.FullExtent.xmin+leftTopTitleCol*MapConfig.TitlePix*MapConfig.Resolution[level]; realMaxY = MapConfig.FullExtent.ymax-leftTopTitleRow*MapConfig.TitlePix*MapConfig.Resolution[level]; //計算左上角偏移像素 offSetX = (realMinX-minX)/MapConfig.Resolution[level]; offSetY = (maxY-realMaxY)/MapConfig.Resolution[level]; //計算瓦片個數 xClipNum = Math.ceil((MapConfig.ViewHeight+Math.abs(offSetY))/MapConfig.TitlePix); yClipNum = Math.ceil((MapConfig.ViewWidth+Math.abs(offSetX))/MapConfig.TitlePix); //右下角行列號 rightBottomTitleRow = leftTopTitleRow+xClipNum-1; rightBottomTitleCol = leftTopTitleCol+yClipNum-1; realMaxX = MapConfig.FullExtent.xmin+(rightBottomTitleCol+1)*MapConfig.TitlePix*MapConfig.Resolution[level]; realMinY = MapConfig.FullExtent.ymax-(rightBottomTitleRow+1)*MapConfig.TitlePix*MapConfig.Resolution[level]; var mapcv = document.getElementById("mapcv"); var myctx = mapcv.getContext("2d"); for(var i=0;i<xClipNum;i++){ for(var j=0;j<yClipNum;j++){ var beauty = new Image(); beauty.src = MapConfig.RootDir+level+"/"+(leftTopTitleRow+i)+"/"+(leftTopTitleCol+j)+".jpg"; var TitleImg={ img:null, x:0, y:0 }; TitleImg.img=beauty; TitleImg.x=offSetX+(j*MapConfig.TitlePix); TitleImg.y=offSetY+(i*MapConfig.TitlePix); TitlesArry.push(TitleImg); myctx.drawImage(TitleImg.img, TitleImg.x, TitleImg.y); } } }); function lonlatTomercator(lonlat) { var mercator={x:0,y:0}; var x = lonlat.x *20037508.34/180; var y = Math.log(Math.tan((90+lonlat.y)*Math.PI/360))/(Math.PI/180); y = y *20037508.34/180; mercator.x = x; mercator.y = y; return mercator ; }
總結
但願對WebGIS的初學者理解瓦片地圖顯示原理能有幫助