当前位置: 首页 > web >正文

【开发备忘】下载并本地部署天地图WMTS服务

参考两篇博客:
https://blog.csdn.net/qq_46133033/article/details/141054366
https://mp.weixin.qq.com/s/cKVHJdhTsn9In-H6qCi0ug

1、天地图WMTS服务说明
在天地图API官网上可以看到,天地图提供了很多的地图服务(WMTS),用于前端网页加载矢量或是栅格的瓦片地图,但是每一类服务都有_c和_w两种,表示不同的投影类型。首先需要说明的是为什么会有两种投影类型?
在这里插入图片描述
WMTS地图服务是符合OGC标准的一种在线地图服务,T for tiles。除了WMTS,还有一种服务叫TMS,是OSGeo提出的协议。两者最主要的区别就在于WMTS原点在地图投影平面左上角,而TMS则是左下角。可以看到天地图提供的都是WMTS。

但是具体如何切分瓦片,还与平面的投影方式有关,这叫TileMatrixSet的差异。例如基于经纬度投影的TileMatrixSet,就是以WGS1984坐标系(或者其他地理坐标系如CGCS2000)为坐标系投影的方法;而基于墨卡托投影的TileMatrixSet则是用Web Mercato投影。

两种瓦片区别有二:
1.前者原点坐标是(-180,90),后者是(-180,85.05112878°90),是墨卡托投影的极限纬度。
2.前者瓦片横纵比是2:1,后者是1:1。即,前者把平面竖着切成两份,横着切一份;后者横竖都切成两份。

具体到天地图里面,通过WMTS的GetCapbilities方法可以看到具体的瓦片细节(https://t0.tianditu.gov.cn/img_c/wmts?REQUEST=GetCapabilities&tk={tk})。可以看到img_c是EPSG::4490(就是CGCS2000),而img_w是EPSG::900913(就是EPSG:3857,web墨卡托);在同一层zoom下,img_c的列数是img_w的一半。

因此,作为底图来说其实用啥都可以,在前端加载的时候都是指定了TileMatrixSet的规则,前端库知道应该如何加载。
下面给出用cesium加载天地图的代码作为补充。这里不能只是给一个url,建议是需要指定layer、style、format和tileMatrixSetID参数,避免cesium自己在url里没找到,这些参数的数值在上面GetCapbilities方法里都能看到:

const tianditu = new Cesium.WebMapTileServiceImageryProvider({url: `https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&TILEMATRIX={TileMatrix}&TILEROW={TileRow}&TILECOL={TileCol}&FORMAT=tiles&tk=${tiandituTK}`,layer: 'img',style: 'default',format: 'tiles',tileMatrixSetID: 'w'
});

WMTS的本质,就是根据当前在线地图所在经纬度范围和缩放比例,计算对应的一系列瓦片号,然后分别请求并在对应位置渲染。每一个瓦片请求类似于https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TileCol=3&TileRow=1&TileMatrix=2&tk={tk}。

2、从经纬度计算瓦片号
我们想要爬取瓦片,首先也就要完成这个从经纬度计算瓦片号的过程。我推荐使用web墨卡托方法,也就是TileMatrixSet='w’的情况,因为OSM、Google等地图服务都是以这种方式切瓦片的,是最主流的方式,更容易测试,对此下面会继续介绍。
参考开头的博客,计算原理如图:
在这里插入图片描述
当然,直接贴出代码即可,不必管细节:

def deg2num_mercator(lat_deg, lon_deg, zoom):'''将经纬度转换为瓦片坐标'''lat_rad = math.radians(lat_deg)n = 2.0 ** zoomxtile = int((lon_deg + 180.0) / 360.0 * n)ytile = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)return xtile, ytile

更实际的情况是需要知道一定范围内的瓦片,代码如下,注意这里面有一个确保坐标顺序正确的过程,是因为瓦片原点在左上:

def get_tile_bounds(min_lat, min_lon, max_lat, max_lon, zoom):'''获取指定经纬度范围在指定缩放级别下的瓦片范围'''min_x, min_y = deg2num_mercator(min_lat, min_lon, zoom)max_x, max_y = deg2num_mercator(max_lat, max_lon, zoom)# 确保坐标顺序正确tile_x_min = min(min_x, max_x)tile_x_max = max(min_x, max_x)tile_y_min = min(min_y, max_y)tile_y_max = max(min_y, max_y)return tile_x_min, tile_y_min, tile_x_max, tile_y_max

下面就可以爬取单个瓦片了。
我用到了pyhttpx库,加上了请求头,否则直接request会被天地图拦截。
我是将瓦片结果以二进制的方式直接保存成png,注意这里面的目录结构是"/{z}/{y}/{x}.png",这和前面用cesium加载的时候url是一样的顺序(TILEMATRIX={TileMatrix}&TILEROW={TileRow}&TILECOL={TileCol}),保持一致更能避免出错。

def download_tile(url,x,y,z,tk):url1 = url.format(x=x, y=y, z=z, tk=tk)headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'}try:session = pyhttpx.HttpSession()r = session.get(url=url1,headers=headers)tile_filename = file_root_path+"/{z}/{y}/{x}.png".format(x=x, y=y, z=z)tile_filepath = os.path.dirname(tile_filename)#print(x,y,z)if not os.path.exists(tile_filepath):os.makedirs(tile_filepath)# 以二进制写入with open(tile_filename,"wb") as f:f.write(r.content)except Exception as e:print(f"未知错误: {e}")

最后,下载所有的瓦片代码如下,要注意每个tk单日访问量只有10000,zoom不能直接从1到19全爬。

file_root_path = "xxx"
url = "https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TileCol={x}&TileRow={y}&TileMatrix={z}&tk={tk}"
tk = "xxx"for zoom in range(1,10):print(zoom)x_min, y_min, x_max, y_max=get_tile_bounds(left_bottom['lat'], left_bottom['lon'], right_top['lat'], right_top['lon'], zoom)for x in range(x_min,x_max+1):for y in range(y_min,y_max+1):download_tile(url,x,y,zoom,tk)

目录结果如下,这显示了zoom=12,y=1678的情况
在这里插入图片描述

3、发布服务与测试

首先,为了验证数据的下载和存储是正确的,我们可以用QGIS添加本地目录为XYZ服务,注意目录后面的写法也是"/{z}/{y}/{x}.png"。其他啥都不用配置的情况下(下图中我勾选了min max zoom,非必要),就可以直接加载了。
在这里插入图片描述
通过这个服务,你再叠加一些其他的已知的shp,就很容易判断正确与否了。我还用天地图自己的服务一起叠加验证,两者是完全一致的,说明瓦片组织没有问题。
这里需要说明,QGIS的XYZ服务默认就是按照web墨卡托的切片方式来访问的,这一点我没有在官方api中验证,但是在外网论坛上有相关回答。这里也就是为什么我建议用img_w而非img_c。当然,你用img_c按照上面的流程绝对也可以发布一个服务,但是地理位置就是错的了。

这里隐含了一个重要的事实,上面并没有点出来,就是当你有了一套按照特定方式组织的瓦片之后,不需要补充地理信息,不需要把png转成GeoTiff,只需要按照特定方式访问,就可以实现地理信息的匹配。

因此,对于得到的瓦片目录,不需要再用GeoServer发布,而是直接随便找个服务器,tomcat也好,apache也行,iis也ok,把瓦片目录放进去,就可以在cesium等地方正确加载了。如下面的代码所示,我把整个目录命名为img,偷懒直接放在了geoserver的静态资源目录,就可以顺利加载了。这也说明了这里的layer、style、format属性都不是必要的(当然tileMatrixSetID不能少)。

const tianditu = new Cesium.WebMapTileServiceImageryProvider({url:'http://localhost:8080/geoserver/www/mytiles/img/{TileMatrix}/{TileRow}/{TileCol}.png',layer: 'img',style: 'default',format: 'tiles',tileMatrixSetID: 'w',
});

很神奇吧,原来加载WMTS根本不需要发布WMTS。
当然,我没有测试非Cesium的情况,如果有朋友试过可以交流分享,我猜是类似的原理啦。

http://www.xdnf.cn/news/17069.html

相关文章:

  • 文本换行问题
  • Node.js 操作 MySQL
  • SpringAI的使用
  • Mysql的MVCC是什么
  • 基于开源AI智能客服、AI智能名片与S2B2C商城小程序的餐饮行业私域流量运营策略研究
  • 数据结构-双链表
  • 【数据分享】各省粮食外贸依存度、粮食波动率等粮食相关数据合集(2011-2022)(获取方式看文末)
  • MCP革命:AI世界的“USB-C”接口如何重塑智能体与外部工具的连接
  • 信号传播速度与延时
  • 机器学习——下采样(UnderSampling),解决类别不平衡问题,案例:逻辑回归 信用卡欺诈检测
  • 【2025ICCV-目标检测方向】WaveMamba:用于 RGB-红外目标检测的小波驱动曼巴融合
  • 从零开始实现Qwen3(Dense架构)
  • template<typename R = void> 意义
  • 构建企业级Web应用:AWS全栈架构深度解析
  • ⭐CVPR2025 FreeUV:无真值 3D 人脸纹理重建框架
  • IDEA查看源码利器XCodeMap插件
  • DMDRS产品概述和安装部署
  • 服务端⾼并发分布式结构演进之路
  • 每日面试题19:深拷贝和浅拷贝的区别
  • All the Mods 9 - To the Sky - atm9sky 局域网联机报错可能解决方法
  • 玩转 Playwright 有头与无头模式:消除差异,提升爬虫稳定性
  • 人声伴奏分离API:音乐智能处理的强大工具
  • 提升工作效率的利器:Qwen3 大语言模型
  • [LeetCode优选算法专题一双指针——有效三角形的个数]
  • Android 之 图片加载(Fresco/Picasso/Glide)
  • [硬件电路-140]:模拟电路 - 信号处理电路 - 锁定放大器概述、工作原理、常见芯片、管脚定义
  • 多模态大模型综述:BLIP-2详解(第二篇)
  • GraphRAG:基于知识图谱的检索增强生成技术解析
  • 【QT】常⽤控件详解(二)windowOpacitycursorfontsetToolTipfocusPolicystyleSheet
  • 设计模式学习[17]---组合模式