leaflet与瓦片地图
leaflet是开源的JavaScript库,用于构建Web地图应用。
其需要提供一个瓦片地图服务器用于动态加载当前展示的地图背景页。
瓦片地图是由于其根据缩放等级相当于一级一级细化,上一级的一张图片在一次缩放过程中会变成粒度更细的四张图片,进而达到放大(反之缩小)的效果。由于其上下一级一级覆盖的模式,因此其被称为瓦片地图(类似瓦片一级盖着一级)。
在放大级别为1级时(也就是zoom=1),当前瓦片地图仅有4张图片。分别是左上,右上,左下,右下。
当放大级别为2级时(也就是zoom=2),当前瓦片地图会有16张图片。如前面所述,上一级的一张图片在一次缩放过程中会变成粒度更细的四张图片,因此zoom=1中左上的那种图片会一分为4,形成新的左上,右上,左下,右下。同理对另外3张,因此在zoom=2时会有4*4=16张。
以此类推,zoom=3时会有16*4=64张图片,zoom=4时会有64*4=256张图片,zoom=n时会有4^n张图片。
瓦片地图服务器api
在zoom=n时,会有4^n张图片,这些图片平铺在二维平面上,可以使用二维坐标(x,y)坐标进行索引。再加上缩放级别(zoom)相当于z轴,因此形成一个三维空间,用于索引不同缩放级别的不同位置的图片。因此一个瓦片地图服务器要索引一张图片最主要的就是提供这三维坐标(x,y,z)。
高德地图api:"http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"。
mapbox地图api:"https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}"
可以看到,这些api里面都会使用到索引地图所需要的三维坐标(x,y,z),不过在不同的服务中会额外提供不同的参数,如高德提供需要提供一个subdomains参数用于并行请求地图,mapbox需要提供id(一个固定字符串:"mapbox.street")和访问的token(需要到官网申请),
更具体的信息可以查看leaflet.js文档中的调用说明:tilelayer。
离线瓦片地图
瓦片地图服务器都是一个简单的web服务器,可以简单的使用一个爬虫下载对应的图片:
下面的代码是一个下载图片的示例,需要注意的是,
(1)这里仅下载了第四层的图片,也就是z坐标的值固定为4(zoom=4)。根据前面的描述,z=4时,该层共有4^4张图片,并且x的范围和y的范围一致,即x和y的最大值均为sqrt(4^4)=16。考虑到程序计数从0开始,因此x与y的实际范围应该是[0,15]。
(2)由于仅下载了第四层的图片,因此在使用瓦片的地图时候,限制了最大缩放级别和最小缩放级别均为4,即关闭了前端地图的缩放功能,且固定在第4级。
(3)此处由于下载量较小,因此采用的单线程,下载量较大时应该考虑多线程/多进程等方式下载。
(4)可以简单估算一下下载地图所需要的资源规模,先下一批小规模图片,然后判断下大规模图片时所需要的硬盘大小(会很大)。
(5)接(4),考虑到实际时候,并不需要下载非常完整的地图,比如我们仅需要下载中国地区的地图时,这时候就需要计算实际地图经纬度对应过来的瓦片地图索引范围,主要是依据目标地区在瓦片地图中的哪些位置,比如当目标在瓦片地图中的(x=1,y=1,z=1)位置时,那么仅需要下载该瓦片下的子瓦片,而不需要下载(x=0,y=0,z=1)或(x=0,y=1,z=1)或(x=1,y=0,z=1)这些瓦片。这个计算可能较为繁琐,此处不做进一步讨论。
(6)对于下载下来的图片可以构建一个web服务来作为自己的瓦片服务器,如flask(之前写的一个示例),vue,nginx,或者直接上spring boot等都行。方法很多,可以选择自己更加熟悉的方案来实现,主要做好三维坐标的映射即可。
"""
离线下载瓦片地图
"""
import requests
import os
import time
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
}
folder_path = './leaflet/'
# 瓦片地图api 注意这里只下载了4级缩放的图片
url_t = 'http://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={}&y={}&z=4'
for x in range(16):
for y in range(16):
url = url_t.format(x, y)
img = requests.get(url, headers=headers)
img_path = folder_path + "/" + str(x) + "/" + str(y)
if not os.path.exists(img_path):
os.makedirs(img_path)
img_name = img_path + "/4.png"
with open(img_name, "wb") as f:
f.write(img.content)
f.flush()
f.close()
time.sleep(1)
""" javascript
L.tileLayer('/static/leaflet/{x}/{y}/4.png', {
attribution: 'hopy',
minZoom: 4,
maxZoom: 4
}).addTo(mymap);
"""