geotrellis使用(三十九)COG 写入更新

前言

前面介绍过了如何在 ETL 的时候更新 Layer,使得能够在大数据量的时候完成 ETL 操作,同时前两篇文章也介绍了 COG 以及如何在 Geotrellis 中实现 COG 的读取。本文介绍如何在进行 COG 方式 ETL 的时候实现 Layer 的更新。

一、实现

1.1 原理分析

其实实现 COG 方式的 Layer 更新就是把上述两种方式结合起来,唯一的区别在于普通的 ETL 操作更新的时候需要合并的是同一个 Layer 下编号相同的瓦片,而 COG 方式的 ETL 更新的时候需要合并的是同一个 Layer 下编号相同的 GeoTiff 文件,明白了这一点实现起来就很容易了。

1.2 实现方案

上一篇文章中讲了如何实现 COG 的数据写入,执行写入操作的是最后一行代码:

writer.writeCOGLayer(layerName, cogLayer, keyIndexes)

其中 writer 是 FileCOGLayerWriter 实例或者其他 COGLayerWriter 实例,layerName 表示写入的层,cogLayer 为需要写入的数据。

所以理论上实现方式为首先判断此 Layer 是否存在,如果存在则更新之,否则执行上述 writeCOGLayer 方法。

其实 writeCOGLayer 方法已经帮我们实现了这一步,只需要传入一个 GeoTiff 的 merge 方法即可。merge 的类型为 (GeoTiff[V], GeoTiff[V]) => GeoTiff[V],V 为 Tile 或者 MultibandTile 类型,其实就是如何将两个 GeoTiff 合并成一个 GeoTiff。

这就很简单了,只需要写一个此方法即可,如下:

def merge(v1: GeoTiff[V], v2: GeoTiff[V]) = {
    val tile: V = v2.tile merge v1.tile
    val extent = v2.extent combine v1.extent
    val crs = v2.crs
    GeoTiffBuilder[V].makeGeoTiff(
      tile, extent, crs, Tags(Map(), Nil), GeoTiffOptions.DEFAULT
    )
}

只需要将此方法传入即可,在 writeCOGLayer 方法中的下述方法会自动完成 update 操作:

case Some(merge) if uriExists(path) =>
    val old = GeoTiffReader[V].read(path, decompress = false, streaming = true)
    val merged = merge(cog, old)
    merged.write(path, true)
    // collect VRT metadata
    (0 until merged.bandCount)
      .map { b =>
        val idx = Index.encode(keyIndex.toIndex(key), maxWidth)
        (idx.toLong, vrt.simpleSource(s"$idx.$Extension", b + 1, merged.cols, merged.rows, merged.extent))
      }
      .foreach(samplesAccumulator.add)

这也正与我们的分析一样,此方法将两个 tiff 合并成一个写入。

1.3 效果

编译执行两次数据 COG 方式导入,可以看到两个数据完美的拼接在一起,继续放大,然而居然出问题了,中间有些 zoom 下的结合处瓦片不翼而飞了,这是什么原因?为什么仅仅是有些 zoom 下的丢失了?

其实静下心来分析就不难知道,存在的问题一定在我们自己写的 merge 方法中,并且是合并后的 Tiff 文件未实现 COG 造成的,因为没有实现 COG 导致有些 zoom 下无法读取,所以取不到数据。

1.4 优化

明白了这一点优化起来就很容易了,只需要看一下 Geotrellis 是如何生成 COG 方式的 Tiff 的,我们也按照此方式生成合并后的 Tiff 即可。

private def generateGeoTiffRDD[
 K: SpatialComponent: Ordering: JsonFormat: ClassTag,
 V <: CellGrid: ClassTag: ? => TileMergeMethods[V]: ? => TilePrototypeMethods[V]: ? => TileCropMethods[V]: GeoTiffBuilder
](
 rdd: RDD[(K, V)],
 zoomRange: ZoomRange ,
 layoutScheme: ZoomedLayoutScheme,
 cellType: CellType,
 compression: Compression
): RDD[(K, GeoTiff[V])] = {
 val kwFomat = KryoWrapper(implicitly[JsonFormat[K]])
 val crs = layoutScheme.crs

 val minZoomLayout = layoutScheme.levelForZoom(zoomRange.minZoom).layout
 val maxZoomLayout = layoutScheme.levelForZoom(zoomRange.maxZoom).layout

 val options: GeoTiffOptions =
  GeoTiffOptions(
    storageMethod = Tiled(maxZoomLayout.tileCols, maxZoomLayout.tileRows),
    compression = compression
  )

 rdd.
  mapPartitions { partition =>
    partition.map { case (key, tile) =>
      val extent: Extent = key.getComponent[SpatialKey].extent(maxZoomLayout)
      val minZoomSpatialKey = minZoomLayout.mapTransform(extent.center)

      (key.setComponent(minZoomSpatialKey), (key, tile))
    }
  }.
  groupByKey(new HashPartitioner(rdd.partitions.length)).
  mapPartitions { partition =>
    val keyFormat = kwFomat.value
    partition.map { case (key, tiles) =>
      val cogExtent = key.getComponent[SpatialKey].extent(minZoomLayout)
      val centerToCenter: Extent = {
        val h = maxZoomLayout.cellheight / 2
        val w = maxZoomLayout.cellwidth / 2
        Extent(
          xmin = cogExtent.xmin + w,
          ymin = cogExtent.ymin + h,
          xmax = cogExtent.xmax - w,
          ymax = cogExtent.ymax - h)
      }
      val cogTileBounds: GridBounds = maxZoomLayout.mapTransform.extentToBounds(centerToCenter)
      val cogLayout: TileLayout = maxZoomLayout.layoutForBounds(cogTileBounds).tileLayout

      val segments = tiles.map { case (key, value) =>
        val SpatialKey(col, row) = key.getComponent[SpatialKey]
        (SpatialKey(col - cogTileBounds.colMin, row - cogTileBounds.rowMin), value)
      }

      val cogTile = GeoTiffBuilder[V].makeTile(
        segments.iterator,
        cogLayout,
        cellType,
        Tiled(cogLayout.tileCols, cogLayout.tileRows),
        compression)

      val cogTiff = GeoTiffBuilder[V].makeGeoTiff(
        cogTile, cogExtent, crs,
        Tags(Map("GT_KEY" -> keyFormat.write(key).prettyPrint), Nil),
        options
      ).withOverviews(NearestNeighbor)

      (key, cogTiff)
    }
  }
}

这是 Geotrellis 中的 COG Tiff 生成代码,重点在于最下面的 GeoTiffBuilder[V].makeGeoTiff 方法,可以看到与我们上面的方式稍微有些不同,只需要按照其修改即可。如下:

def merge(v1: GeoTiff[V], v2: GeoTiff[V]) = {
    val tile: V = v2.tile merge v1.tile
    val extent = v2.extent combine v1.extent
    val crs = v2.crs
    GeoTiffBuilder[V].makeGeoTiff(
      tile, extent, crs, v2.tags, v2.options), GeoTiffOptions.DEFAULT
    ).withOverviews(NearestNeighbor)
}

主要变化在于 Tiff 的 tag 使用已有 Tiff 的 tag,这样会添加 GT_KEY 标签,添加了已有 Tiff 的options,并添加了 withOverviews 方法,这样就能满足 COG 的要求,生成符合 COG 格式的 Geotrellis 下的 Tiff 文件。

三、总结

本文介绍了如何实现 COG 模式下 ETL 的 Layer 更新操作,只要想明白原理,其实代码本就不复杂,这也是我对待码农工作的个人感悟:重要的在于编程思维、解决问题能力的培养,而不是具体的代码。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员的SOD蜜

单数据库,多数据库,单实例,多实例不同情况下的数据访问效率测试

最近公司的项目准备优化一下系统的性能,希望在数据库方面看有没有提升的空间,目前压力测试发现数据库服务器压力还不够大,Web服务器压力也不是很大的情况下,前台页面...

24110
来自专栏xingoo, 一个梦想做发明家的程序员

Spark源码分析 之 Driver和Excutor是怎么跑起来的?(2.2.0版本)

今天抽空回顾了一下Spark相关的源码,本来想要了解一下Block的管理机制,但是看着看着就回到了SparkContext的创建与使用。正好之前没有正式的整理...

2089
来自专栏Hadoop实操

安装CDSW数据磁盘初始化异常问题分析

本文主要讲述基于Kerberos环境下的CDH5.13.1版本安装CDSW1.3.0数据磁盘初始化异常问题分析及解决办法。

1102
来自专栏大数据智能实战

Spark Hbase读取操作的一些总结与测试

Spark连接HBase实现查询的操作有好多种步骤,其中常用的是直接调用Hbase本身提供的写入和读出的接口。 然而不少人在此基础上进行了各种封装,有的支持sp...

2647
来自专栏牛客网

美团Android三面面试经历

之前在阿里实习,回来后只参加了美团的面试。最后有幸拿到了阿里的转正offer和点评平台的offer。这里简单地把我在美团面试过程中记录的一些问题分享一下,总体来...

4786
来自专栏数据之美

Hive Lock 那些事儿

0、背景 最近两天数据仓库中一张核心表遭遇了锁的问题,导致数据插入失败,影响挺大,之前一直没注意到这个问题,借此总结一下这块的知识和遇到的坑。 hive 在 0...

4085
来自专栏owent

对atbus的小数据包的优化

atbus是我按之前的思路写得服务器消息通信中间件,目标是简化服务器通信的流程,能够自动选择最优路线,自动的断线重连和通信通道维护。能够跨平台并且高效。

992
来自专栏JAVA高级架构

最全技术面试180题:阿里11面试+网易+百度+美团!

1541
来自专栏岑玉海

Spark的机器学习算法mlib的例子运行

  Spark自带了机器学习的算法mlib,页面网址 http://spark.incubator.apache.org/docs/latest/mllib-g...

4075
来自专栏xingoo, 一个梦想做发明家的程序员

Spark源码分析 之 Driver和Excutor是怎么跑起来的?(2.2.0版本)

今天抽空回顾了一下Spark相关的源码,本来想要了解一下Block的管理机制,但是看着看着就回到了SparkContext的创建与使用。正好之前没有正式的整理...

2117

扫码关注云+社区