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 ...
随机推荐
- 在SDL工程中让SDL_ttf渲染汉字
有时候在关于SDL的博文中看到一些评论,说SDL对中文的支持不佳,因为当程序涉及中文时总是输出乱码. 照我个人观点,这里面很多都是误解.下面就根据我在windows下使用SDL的情况,说说我的观点. ...
- CSS3 Tranform 3D 的应用
CSS3 Tranform 3D 的应用 一.perspective 属性 1. 作用: 设置元素被查看位置的视图,类似于眼睛到屏幕的距离,一般跟 perspective-origin 共同作用在一个 ...
- each遍历的用法
- java继承属性相关介绍
这个只需要记住一点,父类的任何属性(变量可以看做属性),子类均可继承并覆盖,allType(father)-->changeAnyType(son)-->AnyType 这是父类的所有代表 ...
- Luogu 3375 【模板】KMP字符串匹配(KMP算法)
Luogu 3375 [模板]KMP字符串匹配(KMP算法) Description 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来 ...
- 【Luogu3807】【模板】卢卡斯定理(数论)
题目描述 给定\(n,m,p(1≤n,m,p≤10^5)\) 求 \(C_{n+m}^m mod p\) 保证\(P\)为\(prime\) \(C\)表示组合数. 一个测试点内包含多组数据. 输入输 ...
- 【BZOJ2431】逆序对数列(动态规划)
[BZOJ2431]逆序对数列(动态规划) 题面 Description 对于一个数列{ai},如果有i<j且ai>aj,那么我们称ai与aj为一对逆序对数.若对于任意一个由1~n自然数组 ...
- Bzoj4872: [Shoi2017]分手是祝愿
题面 Bzoj Sol 首先从大向小,能关就关显然是最优 然后 设\(f[i]\)表示剩下最优要按i个开关的期望步数,倒推过来就是 \[ f[i]=f[i-1]*i*inv[n]+f[i+1]*(n- ...
- mysql压缩包安装方式
从官网https://dev.mysql.com/downloads/mysql/上下载mysql-5.6.31-winx64.zip,将其解压,接下来的安装是通过命令来安装MySQL数据库的.(P. ...
- Angular2 - 概述
*Hi DAI, 我想学习 Angular2, 我应该怎么开始? 关于学习 Angular2, 我认为你应该按照下面的列表 概述: 在您为 Angular2 应用程序编写第一个代码之前, 这将为您提供 ...