前言
要说清楚这个题目对我来说可能都不是一件简单的事情, 我简单尝试.
研究 GIS 的人应该都清楚在 GIS 中最常用的技术是瓦片技术, 无论是传统的栅格瓦片还是比较新颖的矢量瓦片, 一旦将数据切好瓦片就会造成其层级固定, 假如说 0 - 11 级, 请求此层级范围内数据的时候能够正常响应, 但是当用户请求超过最高级 (假如为 12) 的时候该如何处理呢? 传统方式只能返回 404 , 即显示空白数据, 然而有没有更好的方式呢, 能够使得用户在请求超过最高级数据的时候能够优雅的并且正确的返回数据而不是直接 404. 这个问题可以用手动挡汽车和自动挡汽车进行类比, 传统方式就像手动挡, 最大只能到 5 档, 而现在的需求是希望挡位能变的更高些. 但愿我已经清晰的说明了此问题, 本文对此原理和实现方案进行简单探讨.
一, 实现
1.1 原理分析
这个解决方案倒是很容易想象, 当超过最大层级 (以下简称 zoom) 的时候 (> 11 级) 我们只需要读出最大 zoom(11 级)的此范围内数据对应的瓦片, 然后将此瓦片根据此范围进行切割并重新采样到 256 * 256 即可.
这里面涉及到了瓦片的金字塔体系的一些常用概念. 首先层级越大表示分辨率越高, 即显示出来的数据越清晰, 每提高一层数据量增加 4 倍, 即一个低层级的瓦片包含了比他高一层级的四个瓦片, 整个看下来便像一个金字塔一样; 而常用的每个瓦片的大小为 256 * 256, 直白的说就是一个 256 * 256 的 PNG 或者 JPG 图片, 当然也可以是其他尺寸, 每个瓦片对应一个 x,y,z 编号, x,y 代表瓦片的行列号, z 代表瓦片的 zoom, 屏幕范围内数据所有瓦片按照 x,y,z 正常排列显示出来便得到了整个地图(或者其他数据, 如遥感等), 就像房顶的瓦片一样, 所以称为瓦片技术. 有关具体技术和描述可以百度之.
1.2 实现方案
有了上面的分析, 其实这件事情应该已经不困难了.
1.2.1 层级
首先获取当前数据的最大层级并判断当前请求是否大于此层级.
- def getMaxZoom(layerName: String) =
- attributeStore.layerIds
- .groupBy(_.name)(layerName)
- .map(_.zoom)
- .max
- def exist(layerId: LayerId) =
- attributeStore.layerExists(layerId)
第一个函数取到当前 layerName 数据的最大层级, 其中 attributeStore 为你当前 backend 的后台, 可以参考此前文章.
exist 函数判断当前 layerId 是否存在, layerId 包含 name 信息和 zoom 信息. 当然此处你可以直接判断此 layerId 的 zoom 是否大于第一个函数取到的 maxZoom, 但是此处我这么写也是埋下一个伏笔, 会在后文介绍.
1.2.2 取到请求瓦片的范围
想要取到最大层的数据首先要取到瓦片包含数据的范围, 这个范围我们只能根据所请求瓦片的 z,y,z 获得, 如下:
- val layerId: LayerId = LayerId(name, maxZoom)
- val rmd = attributeStore.read[TileLayerMetadata[SpatialKey]](layerId, Fields.metadata)
val layoutLevel = ZoomedLayoutScheme(rmd.crs).levelForZoom(rmd.extent, z)
val mapTransform = MapKeyTransform(rmd.crs, layoutLevel.layout.layoutCols, layoutLevel.layout.layoutRows)
val targetExtent = mapTransform(x, y)
首先取到 maxZoom 层的元数据, 根据投影 (rmd.crs), 范围(rmd.extent) 及 zoom 信息, 获取到当前 z 层的 layout, 这个具体细节涉及到金字塔理论, 大意是根据投影, 范围和层级就可以取到瓦片的编号和范围情况, 最终也正是根据 x,y 计算出瓦片数据范围 targetExtent.
1.2.3 取到最大层级对应瓦片
有了瓦片的范围, 我们就可以在最大曾中取出此瓦片, 如下:
val GridBounds(nx, ny, _, _) = rmd.mapTransform(targetExtent)
- val sourceExtent = rmd.mapTransform(nx, ny)
- val maxZoomTile = tileReader.reader[SpatialKey, Tile](layerId).read(SpatialKey(nx, ny))
tileReader 是 ValueReader 对象, 同样与所采用的 backend 有关. 其中 nx,ny 正是 maxZoom 层对应的瓦片编号, 此处同样用到金字塔理论, 高层级的瓦片必然包含在比他层级低的某一个瓦片里, 即 sourceExtent 必然能够完全覆盖 targetExtent.
1.2.4 将瓦片重采样到所请求的 zoom
现在只需要我们对获取到的瓦片进行裁切并重采样到 256 * 256 即可, 如下:
val targetTile = sourceTile.resample(sourceExtent, RasterExtent(targetExtent, 256, 256))
这样就获取到了最终的请求瓦片, 将此瓦片返回浏览器等其他请求源即可.
1.3 效果
展示一下最终效果:
11 级瓦片是正常取得的瓦片, 12 级瓦片即为通过此种方式由 11 级瓦片重采样得到的.
二, 进一步思考
做产品和做项目有着本质的区别, 一个项目可能只需要考虑到通用情况即可, 而产品则必须考虑到方方面面, 还记得我在上面留的伏笔吗, 在那里我没有采用判断所请求 zoom 是否大于 maxZoom 的方式, 而是直接判断 exist, 这里面有个逻辑问题. 假如切瓦片的时候不是每一层都有切到, 必然我切了 0 - 5,7 - 11, 而没有切第 6 层, 那么采用这种方式肯定是有问题的, 并且出现这种情况的时候整个逻辑都需要重新修改, 因为第 6 层的某个瓦片肯定包含了 2 ^ (2 ^ 5) 个 11 层 (maxZoom) 的瓦片, 这样我们就不能简单的只取出一个, 而应该将其全部取出并进行拼接然后再重采样.
再进一步思考, 碰到这种方式的时候我们是不是可以取出第 5 层或者第 7 层的某个 / 些瓦片而不是非要 maxZoom 层的, 因为接近的层数据更相似(此处牵扯到层级可视化表达的问题).
所以这些都需要我们丰富和设计好逻辑, 只有这些都考虑清楚才能设计出完美的产品. 具体代码此处就不放出了, 如果有需要可以探讨.
三, 总结
本文介绍了如何在所请求的瓦片层级不存在的情况时通过取出最大层或者相近层的瓦片并进行重采样操作, 从而优雅的返回瓦片数据.
Geotrellis 系列文章链接地址 http://www.cnblogs.com/shoufengwei/p/5619419.html
来源: https://www.cnblogs.com/shoufengwei/p/8973677.html