---
title: WMTS简述
date: 2020-03-21
author: ac
tags:
- WMTS
- OGC
- GeoServer
categories:
- GIS
---
## 1. WMTS(Web Map Tile Service)
为了更快的将地图数据在前端显示,可以为一些数据不会变更或变动较小的服务创建地图缓存(Cache)。WMTS是一种采用图像金字塔的方式将地图服务按照预先设置的某种切图策略创建的地图缓存服务。
### 1.1 地图缓存
地图缓存是一个包含了不同比例尺下整个地图范围的地图切片目录,是预先按照显示切图等级、比例尺、切图原点、DPI、图片大小等参数创建的静态图片,用于提高地图服务的响应速度。数据格式通常采用`PNG`或`JPG`。

当前端向地图服务器请求时可以直接根据切图等级、行号、列号获取已缓存的图片,不用像动态地图服务那样根据地图当前范围动态地生成图片再响应到前端。
`GeoServer`创建缓存的方式有两种:
- 一种是当用户查看地图时创建浏览的地图范围及相应等级下的部分缓存,主要优点是,它不需要预处理,并且仅缓存已请求的数据,因此也可节省磁盘空间。该方法的缺点是地图查看只会间歇性地加速,从而降低了用户体验的质量;
- 另一种方式是通过Seed创建,缺点是Seed可能是非常耗时和磁盘消耗的过程。
### 1.2 切图原理
#### 基础概念
- Scale:比例尺,即地图上的一厘米代表着实际上的多少厘米。原 scale 中表示的实际单位是厘米
- Resolution:分辨率,实际含义代表当前地图范围内,1像素代表多少地图单位(地图单位/像素),地图单位取决于数据本身的空间参考。
- dpi :代表每英寸的像素数
#### 地图比例尺的换算
在配置切片策略的时候,对金字塔的每个级别(切图的比例尺参数)都需要一个level 和 resolution ,这些是用于计算屏幕上1像素代表的实际距离计算。

假设地图的坐标单位是米,dpi为96(ArcGIS中dpi默认是96, OGC标准输出的resolution is 90 DPI(25.4/0.28))
> 1英寸=2.54厘米; 1英寸=96像素;
>
> 最终换算的单位是米;
>
> 如果当前地图比例尺为1:125000000,则代表图上1米等于实地125000000米;
>
> 米和像素间的换算公式:
>
> 1英寸=0.0254米=96像素
>
> 1像素=0.0254/96 米
>
> 则根据1:125000000比例尺,图上1像素代表实地距离是 125000000\*0.0254/96 = 33072.9166666667米 , 即 resolution = scale*0.0254/dpi
#### 瓦片行列号的计算
假设,地图的切图原点(通常是地图的左上角(minx,maxy))是(x0,y0),地图的瓦片大小是tileSize,地图屏幕上1像素代表的实际距是resolution。计算坐标点(x,y)所在的瓦片的行列号的公式是:
col = floor((x - x0 )/( tileSize *resolution)
row = floor((y0 - y )/( tileSize *resolution))
resolution = scale*0.0254/dpi
在`OpenGIS`的 `WMTS`标准实现中,确定比例尺分母时采用的是标准显示像素尺寸(1个像素大小)0.28 mm × 0.28 mm。虽然实际的像素大小并不可知,但0.28 mm 对于当前的显示器是较为普遍的实际尺寸。所以在切图的块阵中有:
pixelSpan = scaleDenominator ×0.28 10-3 / metersPerUnit(crs);
tileSpanX = tileWidth ×pixelSpan;
tileSpanY = tileHeight ×pixelSpan;
tileMatrixMaxX = tileMatrixMinX + tileSpanX ×matrixWidth;
tileMatrixMinY = tileMatrixMaxY - tileSpanY ×matrixHeight;
备注:
- `metersPerUnit(crs)`:坐标参照系下长度单位与米之间的转换因子
- 地理坐标系时,单位为度则 `metersPerUnit`:6378137*Math.PI*2/360=111319.49079327358;
- 投影坐标系时,单位为米即 `metersPerUnit`:1
- `scaleDenominator` :比例尺分母
- `matrixWidth`:以图块为单位的宽 ,可以理解为x方向上的图块数量
- `matrixHeight`:以图块的单位的高 ,可以理解为y方向上的图块数量
当知道请求的边界范围框 bBox(bBoxMinX, bBoxMinY, bBoxMaxX, bBoxMaxY) 时:
```javascript
// 为补偿浮点计算的不准确性
epsilon = 1e-6
tileMinCol = floor((bBoxMinX - tileMatrixMinX) / tileSpanX + epsilon)
tileMaxCol = floor((bBoxMaxX - tileMatrixMinX) / tileSpanX - epsilon)
tileMinRow = floor((tileMatrixMaxY - bBoxMaxY) / tileSpanY + epsilon)
tileMaxRow = floor((tileMatrixMaxY - bBoxMinY) / tileSpanY - epsilon)
// 避免超出范围
if (tileMinCol < 0) tileMinCol = 0
if (tileMaxCol >= matrixWidth) tileMaxCol = matrixWidth-1
if (tileMinRow < 0) tileMinRow = 0
if (tileMaxRow >= matrixHeight) tileMaxRow = matrixHeight-1
```
#### 切图策略grids的确定
在新建切图策略时,需要先确定坐标系(即确定metersPerUnit)和边界bounds,图块大小默认为256*256的方形。计算思路是根据下列公式逆推出pixelSpan和scaleDenominator :
pixelSpan = scaleDenominator ×0.28 10-3 / metersPerUnit(crs);
tileSpanX = tileWidth ×pixelSpan;
tileSpanY = tileHeight ×pixelSpan;
tileMatrixMaxX = tileMatrixMinX + tileSpanX ×matrixWidth;
tileMatrixMinY = tileMatrixMaxY - tileSpanY ×matrixHeight;
当第一次点击"Add zoom level"时,设置第一级别,需要先确定`X`方向还是`Y`方向的图块数量为1,即 matrixWidth=1 还是 matrixHeight =1,可以理解为**第一个级别应在X或Y的单个方向上的图块数量为以1**,后面的级别以四叉树金字塔结构往下分割。由 :
(tileMatrixMaxX -tileMatrixMinX) /tileWidth = pixelSpan×matrixWidth = resX;
(tileMatrixMaxY -tileMatrixMinY) /tileHeight = pixelSpan×matrixHeight = resY;
取较小的图块数量为1;
如果 resX 小于 resY ,则 matrixWidth =1,这是可以得到这个级别的另一个方向的图块数量,即 matrixHeight = Math.round(resY / resX),这个时候 resX 可以理解为在这个比例尺下X方向上的像素大小,而Y方向的像素大小为 resY/matrixHeight ,取这两个方向上的像素最大值为当前比例尺下图像的像素尺寸,即
pixelSpan = Math.Max(resX,resY/matrixHeight )
在根据开始的边界bounds的左上角(切图原点),重新确定边界:
tileMatrixMaxX = tileMatrixMinX + tileWidth × pixelSpan×matrixWidth;
tileMatrixMinY = tileMatrixMaxY - tileHeight × pixelSpan×matrixHeight;
最后确定比例尺分母 :
scaleDenominator = pixelSpan * metersPerUnit(crs) /2.8 * 10^-4
**根据上图数据进行验算**
`GeoServer`源码中`GridSetFactory.java`中创建`GridSet`的方法:
```java
public static GridSet createGridSet(String name, SRS srs, BoundingBox extent, boolean alignTopLeft, int levels, Double metersPerUnit, double pixelSize, int tileWidth, int tileHeight, boolean yCoordinateFirst) {
double extentWidth = extent.getWidth();
double extentHeight = extent.getHeight();
double resX = extentWidth / (double)tileWidth;//48.37030696171132
double resY = extentHeight / (double)tileHeight;//42.398664126933
int tilesWide;
int tilesHigh;
if (resX <= resY) {
tilesWide = 1;
tilesHigh = (int)Math.round(resY / resX);
resY /= (double)tilesHigh;
} else {
tilesHigh = 1;
tilesWide = (int)Math.round(resX / resY); //1
resX /= (double)tilesWide;//48.37030696171132
}
//取这两个方向上的像素最大值为当前比例尺下的像素尺寸
double res = Math.max(resX, resY);//48.37030696171132
//根据开始的边界bounds的左上角(切图原点),重新确定边界
//extent: 12950267.080187673,4859560.510258355,12962649.87876987,4870414.56827485
//adjExtent:12950267.080187673,4859560.510258355,12962649.87876987,4871943.308840553
double adjustedExtentWidth = (double)(tilesWide * tileWidth) * res;
double adjustedExtentHeight = (double)(tilesHigh * tileHeight) * res;
BoundingBox adjExtent = new BoundingBox(extent);
adjExtent.setMaxX(adjExtent.getMinX() + adjustedExtentWidth);
if (alignTopLeft) {
adjExtent.setMinY(adjExtent.getMaxY() - adjustedExtentHeight);
} else {
adjExtent.setMaxY(adjExtent.getMinY() + adjustedExtentHeight);
}
double[] resolutions = new double[levels];
resolutions[0] = res;
for(int i = 1; i < levels; ++i) {
resolutions[i] = resolutions[i - 1] / 2.0D;
}
return createGridSet(name, srs, adjExtent, alignTopLeft, resolutions, (double[])null, metersPerUnit, pixelSize, (String[])null, tileWidth, tileHeight, yCoordinateFirst);
}
```
## 2. WMTS Opration
> 1. GetCapabilities:获取服务的元信息
> 2. GetTile:获取缓存的瓦片
> 3. GetFeatureInfo:获取点选的要素信息
### 2.1 GetCapabilities
获取元数据信息,包含服务支持的操作、提供wmts服务的图层及其拥有的切图策略、输出格式等等信息。
**操作示例**:
http://localhost:8080/geoserver/gwc/service/wmts?service=WMTS&version=1.0.0&request=GetCapabilities
**响应示例**:

### 2.2 GetTile
获取缓存的瓦片。
**操作示例**:
http://localhost:8080/geoserver/gwc/service/wmts?layer=topp%3Astates&style=&tilematrixset=EPSG%3A4326&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fpng&TileMatrix=EPSG%3A4326%3A5&TileCol=12&TileRow=8
**响应示例**:

### 2.3 GetFeatureInfo
获取点选的要素信息,其中的参数J为所在图块中的行号,I为列号。
**操作示例**:
http://localhost:8080/geoserver/gwc/service/wmts?VERSION=1.0.0&LAYER=topp:states&STYLE=&TILEMATRIX=EPSG:4326:5&TILEMATRIXSET=EPSG:4326&SERVICE=WMTS&FORMAT=image/png&SERVICE=WMTS&REQUEST=GetFeatureInfo&INFOFORMAT=text/html&TileCol=12&TileRow=9&I=213&J=11
**响应示例**:
