Skip to content

瓦片

什么是瓦片地图

瓦片地图是将整个世界地图,根据地图层级,分割成小地图(瓦片)。在使用时,将小地图再拼接成大地图。

为什么需要瓦片地图

假设我有一张非常高清的世界地图,那么我可以通过放大这张图片来看到世界各个地方,但会产生如下问题

  1. 图片会非常大,不仅存储困难,客户端加载时机器的性能也不够
  2. 图片太大,传输时间太长,用户体验较差

所以才有了瓦片地图,瓦片地图右如下优点

  1. 渐进加载:按照你想要看的层级加载,高层级的瓦片覆盖范围小但细节多,低层级的瓦片覆盖范围大但细节少
  2. 局部加载:仅需加载所要显示的区域的瓦片
  3. 高效缓存:加载过的瓦片,下次加载时,可从缓存中获取
  4. 高性能:将地图按照层级切片后,做为静态图片资源放置于服务端,可快速获取

制作瓦片地图

投影

首先要三维的地球转换为平面,这就需要投影。常用的投影方式有很多,Web 中常用的投影是 Web Mercator 投影

Web Mercator 投影有如下特点

  1. Web Mercator 当地球是一个球形而非椭球,球体半径等于 WGS84 长半轴的长度,即 6378137 米,投影出来的地图是一个正方形
  2. Web Mercator 数据覆盖范围在经度(-180° ~ 180°),纬度(-85.051129° ~ 85.051129°)
  3. Web Mercator 的坐标系
    • 原点:0° 纬线和 0° 经线的交点,位于投影地图的中心点
    • X 轴:水平,向东(右)为正
    • Y 轴:竖直,向北(上)为正

地图配图

设置在地图上显示的内容。举个例子,在 10 层时,河流只需要展示一条蓝色的线,而在 17 层时河流需要展示名字

可以使用 maputnik 进行配图

切片

  1. 按照地图等级细分,从顶部开始(zoomLevel=0)往下,zoomLevel 依次递增,形成一个金字塔体系。zoomLevel 越大则地图越详细,
  2. 按照地图的层级,将每一层分割为 2zoomLevel2zoomLevel{2^{zoomLevel}} * {2^{zoomLevel}} 张图片,图片大小一致,通常分辨率为 256256256 * 256
  3. 每一层图片独立编号,编号规则每个瓦片地图服务商可能不同,但都能通过 zoomLevel、x、y 定位到该瓦片

常用的协议如下

WMS

OGC 定义的协议,用于请求任意区域的渲染地图图像。服务端根据客户端提供的限制条件,返回符合限制条件的地图图像。

WMS 主要属于动态地图服务,即地图是服务器在每次接到客户请求时立刻生成的,特别适用于数据在不断被更新的地图服务,当然对服务器的压力比较大

WMS-C

WMS-C(Web Mapping Service - Cached)是 OSGeo 创建的 WMS 扩展,目的在于提供一种预先缓存数据的方法,以提升地图请求的速度

TMS

TMS(Tile map service)支持 EPSG:4326(Web Mercator),坐标系如下

  • 原点:左下角 (-20037508.34, -20037508.34)
  • X 轴:向东(右)为正
  • Y 轴:向北(上)为正

请求示例,TMS 常常返回一个 XML 文件

https://sandcastle.cesium.com/CesiumUnminified/Assets/Textures/NaturalEarthII/tilemapresource.xml
<TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0">
<Title>NE2_HR_LC_SR_W_DR_recolored.tif</Title>
<Abstract></Abstract>
<SRS>EPSG:4326</SRS>
<BoundingBox
miny="-90.00000000000000"
minx="-180.00000000000000"
maxy="90.00000000000000"
maxx="180.00000000000000"
/>
<Origin y="-90.00000000000000" x="-180.00000000000000" />
<TileFormat width="256" height="256" mime-type="image/jpg" extension="jpg" />
<TileSets profile="geodetic">
<TileSet href="0" units-per-pixel="0.70312500000000" order="0" />
<TileSet href="1" units-per-pixel="0.35156250000000" order="1" />
<TileSet href="2" units-per-pixel="0.17578125000000" order="2" />
</TileSets>
</TileMap>;

实际请求的 URL

Terminal window
https://sandcastle.cesium.com/CesiumUnminified/Assets/Textures/NaturalEarthII/0/1/0.jpg

WMTS

WMTS(Web map tile service)是 OGC 创建的协议。支持 EPSG:3857(WGS84) and EPSG:4326(Web Mercator)

WMTS 的地图是服务器预先制作好的瓦片,这种方法可以提高 Web 服务的性能和伸缩性,特别适合于数据相对静态、不再更新或更新频率很低的数据。

  • 原点:左上角 (-20037508.34, 20037508.34)
  • X 轴:向东(右)为正
  • Y 轴:向南(下)为正

瓦片等级(zoom level)最小为 0,最大为 24

请求示例

https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/WMTS?tilematrix=3&layer=World_Imagery&style=default&tilerow=2&tilecol=6&tilematrixset=default028mm&format=image%2Fjpeg&service=WMTS&version=1.0.0&request=GetTile

queryString 如下所示

参数名参数值示例意义
layerstringWorld_Imagery图层,例如街道图,卫星图
stylestringdefault样式,某些服务可能存在预设样式,例如 night
tilematrixsetstringdefault028mm瓦片集,定义了一套规则,说明了如何将地图划分为一系列标准化的瓦片,可包含瓦片大小,切分规则等等
tilematrixnumber3当前级别的瓦片级别,可以参照 zoomLevel 来理解
tilerownumber2瓦片第几行
tilecolnumber6瓦片第几列
formatstringimage/jpeg图片格式
servicestringWMTS服务类型
versionstring1.0.0服务版本
requeststringGetTile方法

XYZ

XYZ 没有标准的元数据机制,图像通过 REST API 提供,URL 为 http://.../Z/X/Y.png,其中 Z 为层级,X、Y 为瓦片编号

Google Maps / OpenStreetMap 坐标系如下

请求示例

https://tile.openstreetmap.org/7/45/59.png

加载

投影

将大地坐标转换投影坐标

// 投影,经纬度转投影坐标
function lonLatToMeters(lon: number, lat: number) {
const x = (EarthRadius * lon * Math.PI) / 180;
const y = EarthRadius * Math.atanh(Math.sin((lat * Math.PI) / 180));
return [x, y];
}
// 投影坐标转经纬度
function metersToLonLat(x: number, y: number) {
const lon = x / ((EarthRadius * Math.PI) / 180);
const lat =
(180 / Math.PI) * Math.atan2(Math.sinh(y / EarthRadius), Math.cos(lon));
return [lon, lat];
}

坐标系修正

根据地图服务商进行坐标系修正,以 Google Map 和 Web 墨卡托投影为例

可以看到,地图服务商(Google Map)的原点位于左上角,而 Web 墨卡托投影的原点位于中心,因此需要修正坐标系

  1. X 坐标:以投影坐标 X + 地球横向长度 / 2 ,即为地图服务商 X 坐标
  2. Y 坐标:地球纵向周长/ 2- 以投影坐标 Y ,即为地图服务商的 Y 坐标

由于在 Web 墨卡托投影中,将地球看作球体,则横、纵向的周长一致,因此可以简化为如下计算公式

const EarthRadius = 6378137; // 地球半径
const Perimeter = 2 * Math.PI * EarthRadius; // 地球周长
const [x, y] = lonLatToMeters(lng, lat);
// 修正坐标系,假设是Google Map,即原点为左上角
x = x + Perimeter / 2;
y = -y + Perimeter / 2;

计算瓦片位置

根据地图层级,投影坐标,瓦片分辨率计算出瓦片位置,并根据瓦片位置,从地图服务商获取瓦片

const TileSize = 256;
const n = Math.pow(2, z); // 计算一侧有多少张瓦片
// 知道了总共有多少张图片,每张图片分辨率是多大,则可以计算出1px表示多少m
const resolution = Perimeter / n / TileSize;
// 知道了1px表示多少m,那么x米需要多少px即可算出,一张图片的像素大小知道,则可计算出需要图片位置
const X = Math.floor(x / resolution / TileSize);
const Y = Math.floor(y / resolution / TileSize);

以此就可以计算出中心的瓦片,再根据视口大小,计算出需要加载的周围瓦片

多域名

由于浏览器对同域名的请求限制,服务商考虑并发请求会提供子域名,例如

常用的概念

矢量瓦片

参考