目前,有很多WebGIS开发包,ArcGIS API for JS、OpenLayers、LeafLetjs等为我们从事WebGIS开发的人封装了很多强大的功能。我们很方便的使用这些库的时候,也让我们忽略了很多原理性的东西。

比如说,我之前一直在被一个问题困扰,就是如何将一个点正确的显示在浏览器屏幕的正确的位置,即经纬度坐标和屏幕坐标的转换问题。直到我看到一位大牛的博客(点击学习),里面对WebGIS的原理进行了深入的讲解。看了他的文章后一直觉得,我写这篇文章是多余的。但是大神的文章里面并没有详细讲解原理的代码实现。个人觉得还是很有必要通过实现相应功能的方式了解其原理,而且实现时还是遇到了不少的问题,所以还是写了这篇文章。

在线地图及参数

 Arcgis online上的瓦片地图为例,服务中有几个比较关键的使用到的参数。

  • Height、Weight:每个瓦片的宽度和高度
  • Resolution:每一个缩放级别下1像素代表的地图单位(投影坐标)
  • Initial Extent:瓦片地图的范围

获取地图瓦片

通过观察arcgis地图的瓦片组织方式,

http://cache1.arcgisonline.cn/ArcGIS/rest/services/ChinaOnlineCommunityOnlyENG/MapServer/tile/缩放等级/行号/列号

通过python程序将一定缩放等级的瓦片保存到本地 我只抓取了0-5级别的瓦片,并按照arcgis瓦片的保存方式存储。

# -*- 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>

配置信息

在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-84、火星坐标,以及投影坐标与经纬度的转换的文章(点击跳转

$(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的初学者理解瓦片地图显示原理能有帮助

WebGIS前端瓦片地图显示原理及实现的更多相关文章

  1. 从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(转载)

    从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理 1.前言   在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或经纬度)的换算方法, ...

  2. 从底层谈WebGIS 原理设计与实现(二):探究本质,WebGIS前端地图显示之地图比例尺换算原理

    从底层谈WebGIS 原理设计与实现(二):探究本质,WebGIS前端地图显示之地图比例尺换算原理 作者:naaoveGI…    文章来源:http://www.cnblogs.com/naaove ...

  3. 《前端之路》 之 前端 安全 XSS 原理以及防御手段

    什么是 XSS 一.XSS 什么是 XSS XSS,即 Cross Site Script , 翻译过来就是 跨站脚本攻击:为了和 css 有所区分,因而在安全领域被称为 XSS. 什么是 XSS 攻 ...

  4. 前端与编译原理 用js去运行js代码 js2run

    # 前端与编译原理 用js去运行js代码 js2run 前端与编译原理似乎相隔甚远,各种热门的框架都学不过来,那能顾及到这么多底层呢,前端开发者们似乎对编译原理的影响仅仅是"抽象语法树&qu ...

  5. (三)WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(核心)

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或 ...

  6. WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(核心)

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或 ...

  7. (二)探究本质,WebGIS前端地图显示之地图比例尺换算原理

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.没有豆浆机怎么办? 喝豆浆是我们早晨中基本必备的一环,油条豆浆,其 ...

  8. 原生JS实现一个简单的前端路由(原理)

    说一下前端路由实现的简要原理,以 hash 形式(也可以使用 History API 来处理)为例, 当 url 的 hash 发生变化时,触发 hashchange 注册的回调,回调中去进行不同的操 ...

  9. WEB前端-搜索引擎工作原理与SEO优化

    一.搜索引擎工作原理 搜索引擎的工作分为三个阶段,即爬行,索引和检索 1.爬行  搜索引擎具有网络爬虫或蜘蛛来执行爬网,每次抓取工具访问网页时,它都会复制该网页并将其网址添加到索引中. 在“蜘蛛”抓取 ...

随机推荐

  1. Mysql中日期时间型解析

  2. 超炫的时间轴jquery插件Timeline Portfolio

    Timeline Portfolio是一款按时间顺序专业显示事件的jquery时间轴插件,可以根据时间的先后嵌入各种媒体包括微博,视频和地图等.这个展现的模式非常适合设计师的作品集和个人简历的展示.T ...

  3. CentOS 7 部署、连接 数据库mariadb

    1.安装mariadb yum -y install mariadb* 2.开启/停止 systemctl start mariadb  #启动MariaDB systemctl stop maria ...

  4. android 类似QQ底部输入框弹出键盘和面板冲突 布局闪动处理方案(转)

    先看下效果 差不多就是解决这种冲突,布局闪动的 作者的githup :https://github.com/Jacksgong/JKeyboardPanelSwitch Android键盘面板冲突 布 ...

  5. 记一次DG搭建过程中ORA-09925: Unable to createaudit trail file 错误

    今天做Oracle DG  编写initorcl的时候,修改完以后,sqlplus就不能再登陆,一直报 ERROR: ORA-09925: Unable to createaudit trail fi ...

  6. atoi

    , KInvalid}; int g_nStatus=kValid; int StrToInt(const char *str) { g_nStatus=KInvalid; ; if((str!=NU ...

  7. IIS 挂载android的apk文件进行下载

    需要进行MIME的映射处理: 添加MIME映射:文件扩展名:.apk,MIME文件类型:application/vnd.android

  8. jQuery的each函数

    http://www.cnblogs.com/xiaojinhe2/archive/2011/10/12/2208740.html http://www.cnblogs.com/mabelstyle/ ...

  9. Java策略模式(Strategy模式) 之体验

    <JAVA与模式>之策略模式 在阎宏博士的<JAVA与模式>一书中开头是这样描述策略(Strategy)模式的: 策略模式属于对象的行为模式.其用意是针对一组算法,将每一个算法 ...

  10. 在JS中使用COM组件的方法

    首先创建一个COM组件,插入一个双接口Itest,在此接口上实现以下三个方法: STDMETHODIMP Ctest::test(void) //无输入输出参数 { // TODO: 在此添加实现代码 ...