geotrellis使用(三十六)瓦片入库更新图层
前言
Geotrellis 是针对大数据量栅格数据进行分布式空间计算的框架,这一点毋庸置疑,并且无论采取何种操作,其实都是先将大块的数据切割成一定大小的小数据(专业术语为瓦片),这是分治的思想,也是分布式计算的精髓,所以使用 Geotrellis 的第一步工作就是要将数据切片(无论是存储在内存中还是进行持久化),然而即使其能力再“大”在实际工作中也难以处理以下几种需求:
- 全球(大范围)高分辨率遥感影像数据,数据量在 TB 级;
- 局部地区数据更新;
- 不同时间数据融合。
这几种情况下我们都很难或者没有办法同时对这些数据进行处理,可行的方案就是执行更新操作或者分批处理。在 Geotrellis 框架中提供了数据的 ETL 接口,但是只能进行 write 操作,并不能进行 update 操作,write 操作会覆盖掉此图层中已有数据,并且相邻数据之间无法进行拼接,导致接边处数据缺失,所以分批处理只能写到不同的图层,这又给数据的调用计算等处理造成很大的麻烦。本文在原有 ETL 的基础上简单介绍如何实现同层瓦片的 update 操作。
一、原生 ETL
1.1 ETL 工作流程介绍
ETL 完成的工作是将数据切割成瓦片并进行持久化,在 Geotrellis 中你可以将数据直接放在内存中(虽然也未提供现成的解决方案,我前面的文章简单介绍了如何实现),也可以将数据放在 Accumulo、HBASE 等分布式数据库或者是 HDFS 和 普通文件系统中。实现代码在 geotrellis.spark.etl 包下的 Etl 类中,调用 ingest 方法的时候传入不同的参数即可实现数据入库的操作,此部分前面也已经介绍过,这里不再赘述。ingest 方法主要代码如下:
val etl = Etl(conf, modules)
val sourceTiles = etl.load[I, V]
val (zoom, tiled) = etl.tile(sourceTiles)
etl.save[K, V](LayerId(etl.input.name, zoom), tiled)
整个流程为首先使用 load 函数读取原始数据,再调用 tile 函数对数据进行切割,而后调用 save 函数将切割后的瓦片进行持久化。所以只要在 save 方法中判断要存放数据的图层是否存在,如果不存在执行已有操作,如果存在则执行 update 操作。
1.2 save 方法介绍
原生 save 方法如下:
def save[
K: SpatialComponent: TypeTag,
V <: CellGrid: TypeTag: ? => TileMergeMethods[V]: ? => TilePrototypeMethods[V]
](
id: LayerId,
rdd: RDD[(K, V)] with Metadata[TileLayerMetadata[K]],
saveAction: SaveAction[K, V, TileLayerMetadata[K]] = SaveAction.DEFAULT[K, V, TileLayerMetadata[K]]
): Unit = {
implicit def classTagK = ClassTag(typeTag[K].mirror.runtimeClass(typeTag[K].tpe)).asInstanceOf[ClassTag[K]]
implicit def classTagV = ClassTag(typeTag[V].mirror.runtimeClass(typeTag[V].tpe)).asInstanceOf[ClassTag[V]]
val outputPlugin =
combinedModule
.findSubclassOf[OutputPlugin[K, V, TileLayerMetadata[K]]]
.find { _.suitableFor(output.backend.`type`.name) }
.getOrElse(sys.error(s"Unable to find output module of type '${output.backend.`type`.name}'"))
def savePyramid(zoom: Int, rdd: RDD[(K, V)] with Metadata[TileLayerMetadata[K]]): Unit = {
val currentId = id.copy(zoom = zoom)
outputPlugin(currentId, rdd, conf, saveAction)
scheme match {
case Left(s) =>
if (output.pyramid && zoom >= 1) {
val (nextLevel, nextRdd) = Pyramid.up(rdd, s, zoom, output.getPyramidOptions)
savePyramid(nextLevel, nextRdd)
}
case Right(_) =>
if (output.pyramid)
logger.error("Pyramiding only supported with layoutScheme, skipping pyramid step")
}
}
savePyramid(id.zoom, rdd)
logger.info("Done")
}
主要逻辑在 savePyramid 函数中(scala 支持内部函数),其中 outputPlugin(currentId, rdd, conf, saveAction) 是将瓦片持久化的关键操作,val outputPlugin = ... 是取到持久化的种类,这里无需过多考虑,只要考虑成是 Accumulo 或者其他种类即可,所以 outputPlugin(currentId, rdd, conf, saveAction) 调用了 OutputPlugin 类型的 apply 方法,如下:
def apply(
id: LayerId,
rdd: RDD[(K, V)] with Metadata[M],
conf: EtlConf,
saveAction: SaveAction[K, V, M] = SaveAction.DEFAULT[K, V, M]
): Unit = {
implicit val sc = rdd.sparkContext
saveAction(attributes(conf), writer(conf), id, rdd)
}
saveAction 默认取了 SaveAction.DEFAULT[K, V, M],这是定义在 ETL 类中的一个方法,是的,此处传入了一个方法, saveAction(attributes(conf), writer(conf), id, rdd) 实际执行了下述方法:
def DEFAULT[K, V, M] = {
(_: AttributeStore, writer: Writer[LayerId, RDD[(K, V)] with Metadata[M]], layerId: LayerId, rdd: RDD[(K, V)] with Metadata[M]) =>
writer.write(layerId, rdd)
}
可以看到最后调用的是 writer.write(layerId, rdd),此处 writer 根据持久化对象不同而不同,在 Accumulo 中为 AccumuloLayerWriter。
到此我们便清楚了 save 方法的工作流程以及整个 ETL 操作的工作流程,下面开始对其进行改造。
二、改造 ETL
本文仅针对瓦片数据持久化放到 Accumulo 数据库中进行介绍,并未如原代码一样对所有情况进行自动适配,其他持久化方式只需判断和修改对应的 LayerWriter 实例即可。
2.1 改造 save 方法
首先判断持久化对象中是否已存在此图层,代码如下:
val currentId: LayerId = ...
val instance = conf.outputProfile.get.asInstanceOf[AccumuloProfile].getInstance.get
val attributeStore = AccumuloAttributeStore(instance)
val exist = attributeStore.layerExists(currentId)
首先取到持久化的实例,本文直接指定为 Accumulo 类型,而后获取 AccumuloAttributeStore 对象,此对象相当于是元数据,其中存储图层的范围层级等信息,最后通过 layerExists 方法即可得到图层是否存在。
如果图层不存在则直接调用原生的 outputPlugin(currentId, rdd, conf) 即可,如果图层已经存在则执行下述操作:
AccumuloLayerWriter(instance = instance, conf.output.backend.path.toString, AccumuloLayerWriterOptions(SocketWriteStrategy()))
.update(currentId, rdd, (v1: V, v2: V) => v1.merge(v2))
此处需要特别指出的是 AccumuloLayerWriterOptions(SocketWriteStrategy()),此句指明了 Accumulo 的操作策略,按照官方说法,使用 SocketWriteStrategy 会导致操作变慢,切不能针对大量数据的导入操作,使用 HdfsWriteStrategy 支持 Accumulo 大批量导入操作(个人猜测是 Accumulo 数据存放在 HDFS 中,首先把数据写入 HDFS 然后再并行持久化到 Accumulo,所以可以进行大量数据操作)。虽然看上去 HdfsWriteStrategy 非常完美,但是问题在于使用此策略无法执行 update 操作,会报错。鱼和熊掌不能兼得,需要根据实际情况进行选择和设计。
这样就可实现图层中瓦片的更新操作。
2.2 Key Index
当然写到这并没有完成工作,如果仅在 save 函数中完成上述改造,再真正的 update 的时候会报错,提示 key index 超出定义的范围,需要重新定义。还记得上面说的 attributeStore 吧,通过此方法可以取到元数据信息,此处的 key index 也写在元数据中,key index 说白了就是瓦片编号的范围,我们都知道瓦片是根据编号进行请求的,那么一块数据就会有一个编号范围,所以图层不存在的时候执行的是 write 方法,写入的是当时数据瓦片编号范围,但是真正执行 update 的时候一般肯定是跟第一次数据范围不同的,于是提示你需要更新编号的范围。这个问题很容易解决,我们只需要在第一次写入的时候将数据范围设置成全球即可。
在 tile 方法的 resizingTileRDD 方法定义如下:
def resizingTileRDD(
rdd: RDD[(I, V)],
floatMD: TileLayerMetadata[K],
targetLayout: LayoutDefinition
): RDD[(K, V)] with Metadata[TileLayerMetadata[K]] = {
// rekey metadata to targetLayout
val newSpatialBounds = KeyBounds(targetLayout.mapTransform(floatMD.extent))
val tiledMD = floatMD.copy(
bounds = floatMD.bounds.setSpatialBounds(newSpatialBounds),
layout = targetLayout
)
// > 1 means we're upsampling during tiling process
val resolutionRatio = floatMD.layout.cellSize.resolution / targetLayout.cellSize.resolution
val tilerOptions = Tiler.Options(
resampleMethod = method,
partitioner = new HashPartitioner(
partitions = (math.pow(2, (resolutionRatio - 1) * 2) * rdd.partitions.length).toInt))
val tiledRDD = rdd.tileToLayout[K](tiledMD, tilerOptions)
ContextRDD(tiledRDD, tiledMD)
}
val newSpatialBounds = KeyBounds(targetLayout.mapTransform(floatMD.extent)) 是获取到当前数据在此 zoom 下的瓦片编号范围,那么我们只需要将此处改成整个范围即可,如下:
val newSpatialBounds = KeyBounds(
SpatialKey(0, 0),
SpatialKey(
col = targetLayout.layoutCols,
row = targetLayout.layoutRows
))
这样即可实现正常的 update 操作。
三、总结
阅读此文需要对 Geotrellis 框架有整体了解并熟悉其基本使用,可以参考本系列博客,使用 geotrellis 也需要对 scala 有所掌握,scala 语法在我接触过的所有语言中应当是比较灵活的,灵活就导致麻烦。。。。
本文简单介绍了如何实现 ETL 过程的 update 操作。这是我失业后写的第一篇博客,失业后整个人对所有事情的理解更上了一步,无论是对技术还是生活都有更多的感悟,生活和技术都需要慢慢品味。
Geotrellis系列文章链接地址http://www.cnblogs.com/shoufengwei/p/5619419.html
geotrellis使用(三十六)瓦片入库更新图层的更多相关文章
- 程序员编程艺术第三十六~三十七章、搜索智能提示suggestion,附近点搜索
第三十六~三十七章.搜索智能提示suggestion,附近地点搜索 作者:July.致谢:caopengcs.胡果果.时间:二零一三年九月七日. 题记 写博的近三年,整理了太多太多的笔试面试题,如微软 ...
- Java进阶(三十六)深入理解Java的接口和抽象类
Java进阶(三十六)深入理解Java的接口和抽象类 前言 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太 ...
- Gradle 1.12用户指南翻译——第三十六章. Sonar Runner 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- NeHe OpenGL教程 第三十六课:从渲染到纹理
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- 第三百三十六节,web爬虫讲解2—urllib库中使用xpath表达式—BeautifulSoup基础
第三百三十六节,web爬虫讲解2—urllib库中使用xpath表达式—BeautifulSoup基础 在urllib中,我们一样可以使用xpath表达式进行信息提取,此时,你需要首先安装lxml模块 ...
- centos shell脚本编程2 if 判断 case判断 shell脚本中的循环 for while shell中的函数 break continue test 命令 第三十六节课
centos shell脚本编程2 if 判断 case判断 shell脚本中的循环 for while shell中的函数 break continue test 命令 ...
- “全栈2019”Java第三十六章:类
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 微信小程序把玩(三十六)Storage API
原文:微信小程序把玩(三十六)Storage API 其实这个存储在新建Demo的时候就已经用到了就是存储就是那个logs日志,数据存储主要分为同步和异步 异步存储方法: 存数据 wx.setStor ...
- 剑指Offer(三十六):两个链表的第一个公共结点
剑指Offer(三十六):两个链表的第一个公共结点 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.ne ...
随机推荐
- windows驱动程序wdf--KMDF获取应用程序数据缓冲区地址
有3种常用方式:METHOD_BUFFERED METHOD_IN_DIRECT METHOD_OUT_DIRECT 还有METHOD_NEITHER,<windows设备驱动WDF开发 ...
- SDL 在指定窗口中绘图
SDL默认会自动创建绘图窗口,可以通过设置环境变量,让其在指定窗口绘图.代码如下: [cpp] view plaincopyprint? char sdl_var[64]; sprintf(sdl_v ...
- e.preventDefault()和e.stopPropagation()以及return false的作用和区别
前段时间开发中,遇到一个父元素和子元素都有事件时,发现会出现事件冒泡现象,虽然知道ev.stopPropagation()和ev.preventDefault()其中一个是阻止事件冒泡和阻止默认行为, ...
- GridView 翻页 索引超出范围
事件回顾 今天GridView翻页时,又遇到错误:索引超出范围.必须为非负值并小于集合大小. 这是当时的PageIndexChanging和RowCommand两个事件的后台代码 protected ...
- Ubuntu出现ERR_PROXY_CONNECTION_FAILED错误解决方案
我是Ubuntu新手,因为想查看国外的资料,然后安装了灯笼,结果打开谷歌浏览器出现了ERR_PROXY_CONNECTION_FAILED错误,未连接到互联网,代理服务器出现错误,然后Firefox也 ...
- 【BZOJ3670】动物园(KMP算法)
[BZOJ3670]动物园(KMP算法) 题面 BZOJ 题解 神TM阅读理解题 看完题目之后 想暴力: 搞个倍增数组来跳\(next\) 每次暴跳\(next\) 复杂度\(O(Tnlogn)\) ...
- XCTF(77777-2)
题目链接:http://47.52.137.90:20000 这道题目和前面的那道题目大致一样,只不过是过滤的函数不一样 检查过滤函数的方式就不写了,直接来解题 检查函数发现过滤了ord ascii ...
- Centos samba 服务配置
1背景 转到Linux有段时间了,vim操作还不能应对工程代码,之前一直都是Gnome桌面 + Clion 作开发环境,无奈在服务器上没有这样的环境, 看同事是(Windows)Source Insi ...
- java枚举类型举例(基础)
enum Mycolor{红色,绿色,蓝色}; public class asd { public static void main(String[] args) { Mycolor[] allcol ...
- 状压dp入门
状压dp的含义 在我们解决动态规划题目的时候,dp数组最重要的一维就是保存状态信息,但是有些题目它的具有dp的特性,并且状态较多,如果直接保存的可能需要三维甚至多维数组,这样在题目允许的内存下势必是开 ...