WebGIS前端瓦片地图显示原理及实现
目前,有很多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前端瓦片地图显示原理及实现的更多相关文章
- 从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(转载)
从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理 1.前言 在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或经纬度)的换算方法, ...
- 从底层谈WebGIS 原理设计与实现(二):探究本质,WebGIS前端地图显示之地图比例尺换算原理
从底层谈WebGIS 原理设计与实现(二):探究本质,WebGIS前端地图显示之地图比例尺换算原理 作者:naaoveGI… 文章来源:http://www.cnblogs.com/naaove ...
- 《前端之路》 之 前端 安全 XSS 原理以及防御手段
什么是 XSS 一.XSS 什么是 XSS XSS,即 Cross Site Script , 翻译过来就是 跨站脚本攻击:为了和 css 有所区分,因而在安全领域被称为 XSS. 什么是 XSS 攻 ...
- 前端与编译原理 用js去运行js代码 js2run
# 前端与编译原理 用js去运行js代码 js2run 前端与编译原理似乎相隔甚远,各种热门的框架都学不过来,那能顾及到这么多底层呢,前端开发者们似乎对编译原理的影响仅仅是"抽象语法树&qu ...
- (三)WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(核心)
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或 ...
- WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(核心)
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或 ...
- (二)探究本质,WebGIS前端地图显示之地图比例尺换算原理
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.没有豆浆机怎么办? 喝豆浆是我们早晨中基本必备的一环,油条豆浆,其 ...
- 原生JS实现一个简单的前端路由(原理)
说一下前端路由实现的简要原理,以 hash 形式(也可以使用 History API 来处理)为例, 当 url 的 hash 发生变化时,触发 hashchange 注册的回调,回调中去进行不同的操 ...
- WEB前端-搜索引擎工作原理与SEO优化
一.搜索引擎工作原理 搜索引擎的工作分为三个阶段,即爬行,索引和检索 1.爬行 搜索引擎具有网络爬虫或蜘蛛来执行爬网,每次抓取工具访问网页时,它都会复制该网页并将其网址添加到索引中. 在“蜘蛛”抓取 ...
随机推荐
- 用JS添加文本框案例代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- iOS 之 alcatraz (插件管理器)
1. 安装 1.1. 打开命令行 curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/deploy/Scripts/ins ...
- Select与Epoll比较
一.问题引出 联系区别 问题的引出,当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解 ...
- 将 Callout 容器添加到移动设备应用程序中
在移动设备应用程序中,callout 是在应用程序顶部弹出的容器.该容器可以容纳一个或多个组件,并且支持不同类型的布局. callout 容器可以是模态或非模态容器.模态容器在其关闭之前接受所有的键盘 ...
- Hadoop权威指南:压缩
Hadoop权威指南:压缩 [TOC] 文件压缩的两个好处: 减少储存文件所需要的磁盘空间 加速数据在网络和磁盘上的传输 压缩格式总结: 压缩格式 工具 算法 文件扩展名 是否可切分 DEFLATE ...
- css3 3d小demo
css3 3d案例总结 最近入坑 Web 动画,所以把自己的学习过程记录一下分享给大家.就把最近做的比较好的给大家分享下 1.旋转拼图 首先看下效果 代码主要由HTML和CSS3组成,应该说还是比较简 ...
- 浅谈stream数据流
汴水流,泗水流,流到瓜州古渡头, 吴山点点愁. 我们知道水是源源不断的, 抽刀断水水更流, 斩不断, 理还乱, 是水流.(技术贴, 本文权当读者没学过古诗). 在一些语言里, 我们的前辈把数据 ...
- STM32单片机学习心得——概述
我校的课程真是跟不上时代发展,甚至还在教授8051/8052单片机的内容,于是不甘寂寞的我就自己踏入了STM32单片机的坑-- 首先,我现在大二,刚学完模拟电子技术,还没有学习数字电路技术,于是自学单 ...
- c#入门系列——基础篇
c#与VB的区别 刚接触c#发现c#与vb还是有所不同的--它可以在控制台显示.它比vb多出来了一些东西.代码规范上跟VB也稍有不同.....暂时就发现这么多,正在努力发现中. c#的代码结构 ...
- Log4net配置与使用简要说明
log4net详细配置:http://logging.apache.org/log4net/本文描述如有错误,以官网的说明为准;p 一:先来看看log4net中的几个概念: 1. log4net继承机 ...