记录一次排查 GeoTrellis 进行影像切片时的错误排查,主要是探讨 GeoTrellis 提供的两种默认切片布局:
WebMercator 和 Latlng,如何切全球范围的影像数据,并通过 Cesium 进行可视化。
1 常见的两种 TMS 瓦片布局
这里我主要是介绍目前用的最多的,也是 Cesium 等 WebGIS 框架中提供的两种默认切片布局,分别是经纬度与 Web 墨卡托,在 Cesium 中对应布局参数为:WebMercatorTilingScheme 和 GeographicTilingScheme
CesiumJS 能用的 TMS 目前只支持两种切片方案(TilingScheme
):
- 0 级瓦片有 2 个的地理坐标系投影
GeographicTilingScheme
,EPSG:4326- 基于 WGS84 的经纬度投影,使用度作为单位。
- 瓦片布局通常是从左下角 (90°S, 180°W) 到右上角 (90°N, 180°E)。
- 这种布局方式较常见于一些以全球范围地理数据为主的应用场景。
- 瓦片分割通常按照经纬度直接划分,因此与墨卡托投影的矩形分割不同。
0 级瓦片有 2 个的投影,是直接以经纬度数值展平成平面,众所周知:
纬度跨度:经度跨度 = 180:360 = 1:2
所以
GeographicTilingScheme
的样子就是一个一比二的矩形,刚好就在 0 级瓦片时有两个,后续就按常规的四叉树切分即可
- 0 级瓦片只有 1 个的 Web 墨卡托投影
WebMercatorTilingScheme
,EPSG:3857- 这是目前最常用的地图投影方式之一,尤其在网络地图应用中被广泛采用。
- 坐标系为球形墨卡托(Pseudo-Mercator),单位是米,覆盖范围为全球(85.05113°S 至 85.05113°N 的纬度范围)。
- 瓦片的分辨率和缩放级别与常见的 Web 地图服务(如 Google Maps、OpenStreetMap)兼容。
而
WebMercator
投影后的坐标系 xy 值域是 [-20037508.34, 20037508.34]^2,是一个正方形,所以可以按单个0级瓦片进行四叉树切分
2 GeoTrellis 提供的切片布局
GeoTrellis 也是默认提供了两种切片布局,分别是 WebMercator 和 Latlng,阅读其源码,得知,其实就是使用了
EPSG:3857
和EPSG:4326
,下面代码是如何使用 GeoTrellis 构造布局。
import geotrellis.proj4.{CRS, LatLng, WebMercator}// WebMercator 布局val layoutScheme: ZoomedLayoutScheme = ZoomedLayoutScheme(CRS.fromEpsgCode(3857), tileSize = 256)val layoutScheme: ZoomedLayoutScheme = ZoomedLayoutScheme(WebMercator, tileSize = 256)// Latlng 布局,以下两种方式一样val layoutScheme: ZoomedLayoutScheme = ZoomedLayoutScheme(CRS.fromEpsgCode(4326), tileSize = 256)val layoutScheme: ZoomedLayoutScheme = ZoomedLayoutScheme(Latlng, tileSize = 256)
- 存在的问题
GeoTrellis 提供的 WebMercator 布局与我们熟知的 Web 墨卡托,布局一致,可以正常在 Cesium 中进行加载,但是其提供的 Latlng 布局,与我们熟知的地理坐标系投影不一样,其 0 级瓦片只有 1 个,因此不能在 Cesium 中正常加载。
3 解决方案
我认为主要有三种解决手段:
- 方案一:在 GeoTrellis 中自定义一种瓦片布局,使其向 Cesium 兼容
- 方案二:在 Cesium 中自定义一种瓦片布局,使其向 GeoTrellis 兼容
- 方案三:在 GeoTrellis 切片时,先裁剪掉维度85°以外的区域,然后使用 WebMercator 切片方式
这三种方式,前两种我都已经测试过,可以解决不兼容的问题,第三种方式我推测也是可以。
3.1 方案一
- 通过 LayoutDefinition 构造自定义瓦片布局
val tileSize: Int = 256val initialExtent: Extent = Extent(-180, -90, 180, 90)val tileLayout = TileLayout(2, 1, tileSize, tileSize)val layoutScheme: LayoutDefinition = LayoutDefinition(initialExtent, tileLayout)val (zoom, reprojected): (Int, RDD[(SpatialKey, MultibandTile)] with Metadata[TileLayerMetadata[SpatialKey]]) = coverage.reproject(CRS.fromEpsgCode(4326), layoutScheme)
- Cesium 中加载 TMS 服务
var tmsLayer = viewer.imageryLayers.addImageryProvider( new Cesium.UrlTemplateImageryProvider({ tilingScheme: new Cesium.GeographicTilingScheme(), url: "http://10.101.240.60/tms_test/{z}/{x}/{y}.png", }));
但是这种方式有如个缺点,用上述代码定义的 layoutScheme
生成的 reprojected
会被默认指定为0级,即 zoom = 0,这是因为:
LayoutDefinition
是单一层级布局:
LayoutDefinition
只是一个单独的布局定义,它没有层级的概念。- 当你手动定义
TileLayout
和Extent
,GeoTrellis 会将其视为当前分辨率下的初始层级(zoom = 0
),而不会根据瓦片数量或地理范围自动推导层级。
未使用层级推导机制:
ZoomedLayoutScheme
提供了自动化的多层级划分逻辑,基于分辨率的缩小比例计算每个层级的zoom
。- 手动定义的
LayoutDefinition
不会通过ZoomedLayoutScheme
的逻辑进行层级映射。
3.2 方案二
- GeoTrellis 中就使用其提供的经纬度布局方案
// Latlng 布局,以下两种方式一样val layoutScheme: ZoomedLayoutScheme = ZoomedLayoutScheme(CRS.fromEpsgCode(4326), tileSize = 256)val layoutScheme: ZoomedLayoutScheme = ZoomedLayoutScheme(Latlng, tileSize = 256)
- 在 Cesium 中基于 GeographicTilingScheme 修改布局参数,得到自定义的布局方案,并在加载时指定布局
const customTilingScheme = new Cesium.GeographicTilingScheme({ rectangle: Cesium.Rectangle.fromDegrees( -180.0, -90.0, 180.0, 90.0 ), // 瓦片覆盖范围 numberOfLevelZeroTilesX: 1, // 0 级横向瓦片数(默认是 2) numberOfLevelZeroTilesY: 1, // 0 级纵向瓦片数(默认是 1)});var tmsLayer = viewer.imageryLayers.addImageryProvider( new Cesium.UrlTemplateImageryProvider({), tilingScheme: customTilingScheme, url: "http://10.101.240.60/tms_test/{z}/{x}/{y}.png", }));
3.3 方案三
方案三主要是考虑到 EPSG:3857 支持的纬度范围在:85.05113°S 至 85.05113°N,而极地区域的数据如果我们不是很需要的话,实际上我们可以在读取影像数据为 RDD 时就将这部分数据过滤掉,因为比较简单,就没有给出代码,推测也是可以达到效果的。