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.爬行 搜索引擎具有网络爬虫或蜘蛛来执行爬网,每次抓取工具访问网页时,它都会复制该网页并将其网址添加到索引中. 在“蜘蛛”抓取 ...
随机推荐
- 兼容IE6及以上的导航栏子菜单栏滑过显示隐藏效果
;(function(window){ var li = document.getElementById('parentnav').getElementsByTagName('li')[ ...
- Mysql中常用的函数汇总
Mysql中常用的函数汇总: 一.数学函数abs(x) 返回x的绝对值bin(x) 返回x的二进制(oct返回八进制,hex返回十六进制)ceiling(x) 返回大于x的最小整数值exp(x) 返回 ...
- Eclipse 自动生成getter 和 setter
示例: public String View;//右击View > Source > Generate Getters and Setters... public String view; ...
- TTP 错误 404.3 - Not Found 由于扩展配置问题而无法提供您请求的页面。如果该页面是脚本,请添加处理程序。如果应下载文件,请添加 MIME 映射。
解决办法一: 控制面板->打开或关闭windows功能->Internet信息服务->万维网服务->应用程序开发功能. 勾选上“.net扩展性”和“ASP.NET”,保存后,重 ...
- Android之com.nostra13.universalimageloader加载图片抛出OutOfMemroyError错误的多种解决办法
com.nostra13.universalimageloader是用来加载图片非常好的框架,但是也有问题,一旦图片过多的话,很容易就会提示OutOfMemroyError错误,也就是内存溢出的问题, ...
- iOS多线程的七大对象理解
1用面向对象的观点去理解,进程和线程,同步和异步,并行和串行,还有主线程的主队列,的七者关系 进程:程序不运行时就是一堆代码,运行时就是一堆的进程的组合,进程是程序运行的基本单位. 线程:线程是进程的 ...
- 给Ubuntu 16.04更换更新源
给自家的Ubuntu下载软件速度有点慢,毕竟是从国外下载软件,就想更换到国内比较好的更新源(就是这些软件所在的服务器),一般直接百度Ubuntu更新源就能出来一大堆,这时候最好是找和自己Ubuntu版 ...
- 在ubuntu linux 中编写一个自己的python脚本
在ubuntu linux 中编写一个自己的简单的bash脚本. 实现功能:终端中输入简单的命令(以pmpy为例(play music python),为了区别之前说的bash脚本添加了py后缀),来 ...
- C++ 构造函数和析构函数的调用顺序、虚析构函数的作用
构造函数和析构函数的调用顺序 构造函数的调用顺序: 当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的构造函数,依次类推,直至到达最底层的目标派生类的构造函数为止. 析构函数的调用书序: ...
- SysLog简介和java操作实例
什么是SysLog syslog协议属于一种主从式协议:syslog发送端会传送出一个小的文字讯息(小于1024字节)到syslog接收端.接收端通常名为“syslogd”.“syslog daemo ...