前言

上一篇中简单介绍了 COG 的概念和 Geotrellis 中引入 COG 的原因及简单的原理,本文为大家介绍如何在 Geotrellis 中使用 COG 来写入和读取 GeoTIFF数据。

一、写入数据——ETL

1.1 实现方案

其实这与之前的普通 ETL 操作在概念上是相似的,都是将原始数据转换成系统能用的数据的过程,这是宽泛的 ETL 的定义。在 Geotrellis 中实现很简单,与之前代码基本一致,只要切换一下 writer 类型以及最后建立金字塔额时候略有不同。实现方案如下:

val inputPath = "file://" + new File("in.tif").getAbsolutePath
val outputPath = "/your/catalog/path" def main(args: Array[String]): Unit = {
// Setup Spark to use Kryo serializer.
val conf =
new SparkConf()
.setMaster("local[*]")
.setAppName("Spark Tiler")
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.set("spark.kryo.registrator", "geotrellis.spark.io.kryo.KryoRegistrator") val sc = new SparkContext(conf)
try {
run(sc)
} finally {
sc.stop()
}
} def run(implicit sc: SparkContext) = {
val inputRdd: RDD[(ProjectedExtent, MultibandTile)] =
sc.hadoopMultibandGeoTiffRDD(inputPath) val (_, rasterMetaData) =
TileLayerMetadata.fromRdd(inputRdd, FloatingLayoutScheme(256)) val tiled: RDD[(SpatialKey, MultibandTile)] =
inputRdd
.tileToLayout(rasterMetaData.cellType, rasterMetaData.layout, Bilinear)
.repartition(100) val layoutScheme = ZoomedLayoutScheme(WebMercator, tileSize = 256) val (zoom, reprojected): (Int, RDD[(SpatialKey, MultibandTile)] with Metadata[TileLayerMetadata[SpatialKey]]) =
MultibandTileLayerRDD(tiled, rasterMetaData)
.reproject(WebMercator, layoutScheme, Bilinear) val attributeStore = FileAttributeStore(outputPath) val writer = FileCOGLayerWriter(attributeStore) val layerName = "layername" val cogLayer =
COGLayer.fromLayerRDD(
reprojected,
zoom,
compression = NoCompression,
maxTileSize = 4096
) val keyIndexes =
cogLayer.metadata.zoomRangeInfos.
map { case (zr, bounds) => zr -> ZCurveKeyIndexMethod.createIndex(bounds) }.
toMap writer.writeCOGLayer(layerName, cogLayer, keyIndexes)
}

执行 main 函数即可实现 COG 方式的 ETL 操作,其他部分与之前介绍过的 ingest 相同,主要区别在于 writer,此处为 FileCOGLayerWriter 实例,从名字可以看出这是一个文件系统的 COG writer,目前 Geotrellis 实现了三种,分别为 S3、Hadoop、File,这三种后端理论上都是对大量小文件支持不好的。

1.2 背后逻辑

下面来详细分析一下 Geotrellis 中 COG 实现原理。

首先看上面的 cogLayer 对象:

val cogLayer =
COGLayer.fromLayerRDD(
reprojected,
zoom,
compression = NoCompression,
maxTileSize = 4096
)

cogLayer 对象是 COGLayer 实例,fromLayerRDD 源码如下:

def fromLayerRDD[
K: SpatialComponent: Ordering: JsonFormat: ClassTag,
V <: CellGrid: ClassTag: ? => TileMergeMethods[V]: ? => TilePrototypeMethods[V]: ? => TileCropMethods[V]: GeoTiffBuilder
](
rdd: RDD[(K, V)] with Metadata[TileLayerMetadata[K]],
baseZoom: Int,
compression: Compression = Deflate,
maxTileSize: Int = 4096,
minZoom: Option[Int] = None
): COGLayer[K, V] = { if(minZoom.getOrElse(Double.NaN) != baseZoom.toDouble) {
if(rdd.metadata.layout.tileCols != rdd.metadata.layout.tileRows) {
sys.error("Cannot create Pyramided COG layer for non-square tiles.")
} if(!isPowerOfTwo(rdd.metadata.layout.tileCols)) {
sys.error("Cannot create Pyramided COG layer for tile sizes that are not power-of-two.")
}
} val layoutScheme =
ZoomedLayoutScheme(rdd.metadata.crs, rdd.metadata.layout.tileCols) if(rdd.metadata.layout != layoutScheme.levelForZoom(baseZoom).layout) {
sys.error(s"Tile Layout of layer and ZoomedLayoutScheme do not match. ${rdd.metadata.layout} != ${layoutScheme.levelForZoom(baseZoom).layout}")
} val keyBounds =
rdd.metadata.bounds match {
case kb: KeyBounds[K] => kb
case _ => sys.error(s"Cannot create COGLayer with empty Bounds")
} val cogLayerMetadata: COGLayerMetadata[K] =
COGLayerMetadata(
rdd.metadata.cellType,
rdd.metadata.extent,
rdd.metadata.crs,
keyBounds,
layoutScheme,
baseZoom,
minZoom.getOrElse(0),
maxTileSize
) val layers: Map[ZoomRange, RDD[(K, GeoTiff[V])]] =
cogLayerMetadata.zoomRanges.
sorted(Ordering[ZoomRange].reverse).
foldLeft(List[(ZoomRange, RDD[(K, GeoTiff[V])])]()) { case (acc, range) =>
if(acc.isEmpty) {
List(range -> generateGeoTiffRDD(rdd, range, layoutScheme, cogLayerMetadata.cellType, compression))
} else {
val previousLayer: RDD[(K, V)] = acc.head._2.mapValues { tiff =>
if(tiff.overviews.nonEmpty) tiff.overviews.last.tile
else tiff.tile
} val tmd: TileLayerMetadata[K] = cogLayerMetadata.tileLayerMetadata(range.maxZoom + 1)
val upsampledPreviousLayer =
Pyramid.up(ContextRDD(previousLayer, tmd), layoutScheme, range.maxZoom + 1)._2 val rzz = generateGeoTiffRDD(upsampledPreviousLayer, range, layoutScheme, cogLayerMetadata.cellType, compression) (range -> rzz) :: acc
}
}.
toMap COGLayer(layers, cogLayerMetadata)
}

此函数返回类型正是 COGLayer,其两个属性分别为 layers 和 cogLayerMetadata。

cogLayerMetadata 是 COGLayerMetadata 对象,表示 COG 层的元数据信息,包含每层对应的瓦片范围等,这个与传统的元数据很接近,唯一不同的在于此处使用了 ZommRange 的概念,即“ 1 层”可能对应多个 Zoom,而不再是 1 对 1 的关系,这样能够更进一步的节省存储空间,在处理速度和存储空间上做了综合考虑。

layers 是 Map[ZoomRange, RDD[(K, GeoTiff[V])]] 对象,ZoomRange 即为上述元数据中的每层的 zoom 最大和最小值,RDD[(K, GeoTiff[V])] 是 spark rdd 对象,即每一个层级范围对应一个 Tiff 对象,从此可以看出,COG 方式 ETL 后每层存储的不再是 Tile,而是 Tiff 文件,这个 Tiff 文件是 COG 类型的,当用户请求某个瓦片的时候直接从对应的 Tiff 文件中读出需要的部分即可。

最后调用 writer.writeCOGLayer(layerName, cogLayer, keyIndexes) 即可将元数据信息和 Tiff 数据写入相应的位置,完成 ETL 过程。

此处还需要注意的是为了防止单个 Tiff 文件过大, Geotrellis 对每一层进行了分割处理,这样每一层可能会得到多个 Tiff 文件,而为了达到 COG 的真实效果,又引入了 GDAL 中 VRT 的概念(参见http://www.gdal.org/gdal_vrttut.html),其中很详细的讲述了 VRT 的格式和意义,简单来说 VRT 就是将多个 Tiff 文件合并成一个虚拟的 Tiff 文件。

二、读取数据

数据做了 ETL 后,就可以读取出来并进行相应的处理。

2.1 实现方案

其实现方式也基本与传统方式相同,代码如下:

val catalogPath = new java.io.File("/your/catalog/path").getAbsolutePath
val fileValueReader = FileCOGValueReader(catalogPath)
val key = SpatialKey(x, y)
val tile = fileValueReader.reader(LayerId("layername", z)).read(key)

这样就能根据瓦片的 x、y 编号和 z(zoom)取到对应的瓦片。

2.2 原理

主要代码在 COGValueReader 类中的 baseReader 方法中 read 方法,如下:

GeoTiffReader[V].read(uri, decompress = false, streaming = true)
.getOverview(overviewIndex)
.crop(gridBounds)
.tile

传统方式存储的是切割好的瓦片,可以直接定位到确定的瓦片,这里是完全符合 COG 方式的读取方式。getOverview 获取到对应层(z)的 Tiff 文件,crop 对 Tiff 根据需要的范围(x、y)进行切割,tile 函数将其转为瓦片。

三、总结

本文介绍了如何在 Geotrellis 中如何进行 COG 方式的 ETL 操作,实现了全新的数据写入和读取方式。

Geotrellis系列文章链接地址http://www.cnblogs.com/shoufengwei/p/5619419.html

geotrellis使用(三十八)COG 写入和读取的更多相关文章

  1. 《手把手教你》系列技巧篇(三十八)-java+ selenium自动化测试-日历时间控件-下篇(详解教程)

    1.简介 理想很丰满现实很骨感,在应用selenium实现web自动化时,经常会遇到处理日期控件点击问题,手工很简单,可以一个个点击日期控件选择需要的日期,但自动化执行过程中,完全复制手工这样的操作就 ...

  2. NeHe OpenGL教程 第三十八课:资源文件

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  3. Java进阶(三十八)快速排序

    Java进阶(三十八)快速排序 前言 有没有既不浪费空间又可以快一点的排序算法呢?那就是"快速排序"啦!光听这个名字是不是就觉得很高端呢. 假设我们现在对"6 1 2 7 ...

  4. SQL注入之Sqli-labs系列第三十八关、第三十九关,第四十关(堆叠注入)

    0x1 堆叠注入讲解 (1)前言 国内有的称为堆查询注入,也有称之为堆叠注入.个人认为称之为堆叠注入更为准确.堆叠注入为攻击者提供了很多的攻击手段,通过添加一个新 的查询或者终止查询,可以达到修改数据 ...

  5. 微信小程序把玩(三十八)获取设备信息 API

    原文:微信小程序把玩(三十八)获取设备信息 API 获取设备信息这里分为四种, 主要属性: 网络信息wx.getNetWorkType, 系统信息wx.getSystemInfo, 重力感应数据wx. ...

  6. bp(net core)+easyui+efcore实现仓储管理系统——入库管理之二(三十八)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  7. Deep learning:三十八(Stacked CNN简单介绍)

    http://www.cnblogs.com/tornadomeet/archive/2013/05/05/3061457.html 前言: 本节主要是来简单介绍下stacked CNN(深度卷积网络 ...

  8. Java并发编程原理与实战三十八:多线程调度器(ScheduledThreadPoolExecutor)

    在前面介绍了java的多线程的基本原理信息:线程池的原理与使用 本文对这个java本身的线程池的调度器做一个简单扩展,如果还没读过上一篇文章,建议读一下,因为这是调度器的核心组件部分. 我们如果要用j ...

  9. 【FastDev4Android框架开发】打造QQ6.X最新版本号側滑界面效果(三十八)

    转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50253925 本文出自:[江清清的博客] (一).前言: [好消息] ...

随机推荐

  1. 【Bootstrap】 一些提示信息插件

    前端总是会有很多信息提示的时候,最简单的可以用javascript自带的alert,confirm等.这些虽然和js的配合很好,但是很丑. bootstrap给我们提供了一些不同的方案比如modal的 ...

  2. 记一次mysql故障处理

    突然间,个人网站崩溃了!相信这个报错作为运维都应该清楚的,是数据库宕机了. 数据库我采用mysql 5.1.63,上机查看错误日志: 171010 10:11:01 mysqld_safe Start ...

  3. java并发编程基础 --- 7章节 java中的13个原子操作类

    当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,比如变量 i=1,A线程更新 i+1,B线程也更新 I+1,经过两个线程的操作之后可能 I不等于3,而是等于2.因为A和B线程更 ...

  4. 两种设计模式和XML解析

    两种设计模式 1.单例模式 模式的保证步骤:单例(是说在一个类中只能有一个对象)三条件 1.1类构造设置私有  private  Play() { } 1.2 定义一个私有的静态的  类类型  变量 ...

  5. Mysql中一级缓存二级缓存区别

    一级缓存: 也称本地缓存,sqlSession级别的缓存.一级缓存是一直开启的:与数据库同一次会话期间查询到的数据会放在本地缓存中. 如果需要获取相同的数据,直接从缓存中拿,不会再查数据库. 一级缓存 ...

  6. Git常用命令(二)------ 远程库操作

    本文总结自廖雪峰网站. Git支持多种协议,git://使用ssh协议,速度最快,也可使用https等协议. 对远程库操作: 推送: 1.先远程建立一个Repo库,f 2.远程和本地关联: git r ...

  7. (译文)开始学习Vue.js特性--Scoped Slots

    什么是scoped slots A scoped slot is a special type of slot that functions as a reusable template (that ...

  8. 如何从RxJava升级到RxJava2

    如何从RxJava升级到RxJava2. RxJava2已经推出有一年半的时间,由于之前RxJava已经在现有项目中广泛使用,而RxJava2在除了很多命名外并没有太多革新,所以相信有很多人跟我一样都 ...

  9. nodejs安装和卸载

    Ubuntu 上安装 Node.js 方式一:直接安装    一.安装 1.$ sudo apt-get install nodejs2.$ sudo apt-get install npm二.升级 ...

  10. 2017-2018-1 Java演绎法 第三周 作业

    团队任务:团队展示与选题 团队展示 队员学号及姓名 学号 姓名 主要负责工作 20162315 马军 日常统计,项目部分代码 20162316 刘诚昊 项目部分代码,代码质量测试 20162317 袁 ...