前言

在上两篇文章中我介绍了如何直接将Geotiff(一个或者多个)发布为TMS服务。这中间其实我遇到了一个问题,并且这个问题伴随Geotrellis的几乎所有使用案例,下面我详细讲述。

一、问题描述

无论在将Tiff文件使用Geotrellis导入Accumulo中还是直接将其发布为TMS服务,其实这中间都存在一个问题:当多个Tiff文件存在重叠部分的时候如何接边、去重叠以及在边界处的瓦片如何取出各Tiff文件中涉及到的数据,即保持瓦片显示效果的完整性。

这个问题可以说是一个问题也可以说是两个问题。当我们采用事先导入Accumulo中的方案的时候,这个问题不存在(下面会介绍不存在的原因),这也是我一直没有理会此问题的原因,而当我们直接加载Tiff文件为TMS服务的时候这个问题便出现了,当某一个瓦片对应的是两个或者多个Tiff文件的时候,无论选用哪一个文件都会导致最终的瓦片数据不全。接下来我们就来解决这个问题。

二、原理分析及最终效果

要解决这个问题,首先要搞明白Geotrellis是如何读取Tiff文件的。

Geotrellis使用HadoopGeoTiffRDD类将Tiff文件直接读取为RDD,主要方法如下:

def apply[I, K, V](path: Path, uriToKey: (URI, I) => K, options: Options)(implicit sc: SparkContext, rr: RasterReader[Options, (I, V)]): RDD[(K, V)] = {
val conf = configuration(path, options)
options.maxTileSize match {
case Some(tileSize) =>
val pathsAndDimensions: RDD[(Path, (Int, Int))] =
sc.newAPIHadoopRDD(
conf,
classOf[TiffTagsInputFormat],
classOf[Path],
classOf[TiffTags]
).mapValues { tiffTags => (tiffTags.cols, tiffTags.rows) } apply[I, K, V](pathsAndDimensions, uriToKey, options)
case None =>
sc.newAPIHadoopRDD(
conf,
classOf[BytesFileInputFormat],
classOf[Path],
classOf[Array[Byte]]
).mapPartitions(
_.map { case (p, bytes) =>
val (k, v) = rr.readFully(ByteBuffer.wrap(bytes), options)
uriToKey(p.toUri, k) -> v
},
preservesPartitioning = true
)
}
}

其中path为传入的路径,所以configuration方法是关键,其定义如下:

private def configuration(path: Path, options: Options)(implicit sc: SparkContext): Configuration = {
val conf = sc.hadoopConfiguration.withInputDirectory(path, options.tiffExtensions)
conf
}

withInputDirectory方法定义如下:

def withInputDirectory(path: Path, extensions: Seq[String]): Configuration = {
val searchPath = path.toString match {
case p if extensions.exists(p.endsWith) => path
case p =>
val extensionsStr = extensions.mkString("{", ",", "}")
new Path(s"$p/*$extensionsStr")
} withInputDirectory(searchPath)
} def withInputDirectory(path: Path): Configuration = {
val allFiles = HdfsUtils.listFiles(path, self)
if(allFiles.isEmpty) {
sys.error(s"$path contains no files.")
}
HdfsUtils.putFilesInConf(allFiles.mkString(","), self)
}

看完这两个方法我们是不是就豁然开朗了。extensions是一个Tiff文件扩展名的集合。当此文件是tiff文件的时候就直接读取他,如果不是的时候就以他为文件夹读取他下面的所有可能tiff文件,所以我上面说事先导入Accumulo时该问题不存在,因为那时我传入的正是文件夹,系统直接帮我达到了想要的结果。

所以我们就可以明白,如果你想让Geotrellis处理接边、重叠等问题可以直接传入包含所有要处理的Tiff数据的文件夹,这样系统就会自动处理(具体的接边、去重叠操作在rdd的union方法中,由UnionRDD类具体完成)。

所以我们的Tiff文件直接发布为TMS也可以才用同样的处理方式,只需要将文件夹传入即可,这样在涉及到重叠、接边区域的瓦片还能保证数据的完整性。但是这样又出现了另一个问题,如果一次读入所有文件势必会造成处理速度很慢,那么我们为什么不能只取出当前瓦片涉及到的文件呢,如果只涉及一个Tiff就取一个,如果涉及到多个Tiff就取多个。这样既不会造成数据缺失也不会造成读取缓慢。先来看一下最终效果。

我下载了14幅连续的srtm数据,采用上述方式发布的TMS最终结果如下图:

从中可以看出拼接的效果非常好,如果是只读取单幅Tiff的情况必然两幅之间会存在空白,采用这种逐一读取的方式,不仅结果完美,效率也较高。下面来介绍实现方案。

三、实现方案

整体实现方案如下:

  1. 判断并取出与请求的瓦片有交集的Tiff文件
  2. 将这些Tiff文件作为整体读取rdd并发布TMS

3.1 判断并取出与请求的瓦片有交集的Tiff文件

上一篇文章中已经大致介绍了此块内容,在这里再简要介绍一下更新后的版本,代码如下:

val files = HdfsUtils.listFiles(new Path(self.getHdfsUri), sc.hadoopConfiguration)
files.filter { s =>
if (HadoopGeoTiffRDD.Options.DEFAULT.tiffExtensions.exists(s.toString.endsWith)) {
val tiffExtent = getExtent(s)
extent.intersects(tiffExtent)
}
else
false
} def getExtent(path: Path) = {
val rdd = HadoopGeoTiffRDD.spatialMultiband(path)
rdd
.map { case (key, grid) =>
val ProjectedExtent(extent, crs) = key.getComponent[ProjectedExtent]
// Bounds are return to set the non-spatial dimensions of the KeyBounds;
// the spatial KeyBounds are set outside this call.
val boundsKey = key.translate(SpatialKey(0,0))
val cellSize = CellSize(extent, grid.cols, grid.rows)
HashMap(crs -> RasterCollection(crs, grid.cellType, cellSize, extent, KeyBounds(boundsKey, boundsKey), 1))
}
.reduce { (m1, m2) => m1.merged(m2){ case ((k,v1), (_,v2)) => (k,v1 combine v2) } }
.values.toSeq.head.extent
}

其中sc为SparkContext对象,hdfsPath为HDFS中Tiff文件夹路径,files即为此文件下面所有文件,getExtent函数获取传入Tiff文件的空间范围。filter操作过滤掉非Tiff文件以及与extent(瓦片的空间范围)不相交的Tiff文件。这样就可以得到所有与此瓦片有关的Tiff文件。

3.2 将这些Tiff文件作为整体读取rdd并发布TMS

此步又有两种思路(原谅我最近中国哲学简史看多了,总想往高大上的哲学上套一套):

  1. 将这些Tiff文件作为整体提交Geotrellis得到最终结果
  2. 读取每一幅tiff文件然后手动union

两种方案各有利弊,第一种需要自己写读取多个Tiff文件的方案,第二种需要我们手动union,在这里我都介绍一下。

3.2.1 读取多个Tiff文件

解决思路就是将多个Tiff文件提交到上述的conf中,这样系统就会自动帮我们读取。简单的说就是改写上述configuration函数。代码如下:

private def configuration(paths: Array[Path])(implicit sc: SparkContext): Configuration = {
val union = paths.mkString(",")
HdfsUtils.putFilesInConf(union, sc.hadoopConfiguration)
}

其中paths为上一步获取到的与瓦片有关的Tiff文件集合。只需要重写上面的读取Tiff的apply方法,将其中的configuration换成此函数即可。

3.2.2 逐一读取,手动union

思路清晰明了,代码如下:

paths.map(s => HadoopGeoTiffRDD.spatial(s))
.reduce((a, b) => a union b)

很简单的代码,先对Tiff文件集合进行map操作读取所有rdd,然后执行reduce操作,reduce执行的函数为union,即将两个rdd联合,意味着拼接和去重叠。这样也可解决问题。

四、总结

本文简单讲述了使用Geotrellis处理Tiff文件时的两个细节,通过这两个细节能够让我们对Geotreliis的核心更加了解,也能够使我们更加便捷和灵活的处理实际中碰到的关于数据方面的问题。

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

geotrellis使用(三十三)关于Geotrellis读取Geotiff的两个细节的更多相关文章

  1. geotrellis使用(四)geotrellis数据处理部分细节

    前面写了几篇博客介绍了Geotrellis的简单使用,具体链接在文后,今天我主要介绍一下Geotrellis在数据处理的过程中需要注意的细节,或者一些简单的经验技巧以供参考. 一.直接操作本地Geot ...

  2. NeHe OpenGL教程 第三十三课:TGA文件

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

  3. JAVA之旅(三十三)——TCP传输,互相(伤害)传输,复制文件,上传图片,多并发上传,多并发登录

    JAVA之旅(三十三)--TCP传输,互相(伤害)传输,复制文件,上传图片,多并发上传,多并发登录 我们继续网络编程 一.TCP 说完UDP,我们就来说下我们应该重点掌握的TCP了 TCP传输 Soc ...

  4. FreeSql (三十三)CodeFirst 类型映射

    前面有介绍过几篇 CodeFirst 内容文章,有 <(二)自动迁移实体>(https://www.cnblogs.com/FreeSql/p/11531301.html) <(三) ...

  5. COJ967 WZJ的数据结构(负三十三)

    WZJ的数据结构(负三十三) 难度级别:C: 运行时间限制:7000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 请你设计一个数据结构,完成以下功能: 给定一个大 ...

  6. COJ 0967 WZJ的数据结构(负三十三)

    WZJ的数据结构(负三十三) 难度级别:E: 运行时间限制:7000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 请你设计一个数据结构,完成以下功能: 给定一个大 ...

  7. 三十三、Java图形化界面设计——布局管理器之null布局(空布局)

    摘自http://blog.csdn.net/liujun13579/article/details/7774267 三十三.Java图形化界面设计--布局管理器之null布局(空布局) 一般容器都有 ...

  8. Gradle 1.12用户指南翻译——第三十三章. PMD 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  9. “全栈2019”Java多线程第三十三章:await与signal/signalAll

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. hadoop系列四:mapreduce的使用(二)

    转载请在页首明显处注明作者与出处 一:说明 此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的一些内容,如hadoop,spark,storm,机器学习等. 当前使用的hadoop版本为2.6 ...

  2. $MarkDown$ 中使用$ \LaTeX$ 数学式

    最近看了些机器学习的书籍, 想写点笔记记录下. 由于需要使用到很多的数学推导, 所以就看了下如何在 Markdown 中插入数学式,发现在 Markdown 中可以直接插入 LaTeX 数学式. 排版 ...

  3. 转每天一个linux命令(8):cp 命令

    cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一.一般情况下,shell会设置一个别名,在命令行下复制文件时,如果目标文件已经存在,就会询问是否覆盖,不管你是否使用-i参数.但是如果是 ...

  4. 线性代数-矩阵-【2】矩阵生成 C和C++实现

    矩阵的知识点之多足以写成一本线性代数. 所以我们把矩阵的运算封装成矩阵类.以C++为主进行详解. 点击这里可以跳转至 [1]矩阵汇总:http://www.cnblogs.com/HongYi-Lia ...

  5. GridView用法详解

    前台页面: Default.aspx <%@ Page Language="C#" AutoEventWireup="true" CodeFile=&qu ...

  6. [WPF]如何调试Data Binding

    前言 在WPF开发中,将ViewModel中对象绑定到UI上时,会出现明明已经将数据对象Binding到UI,但是UI上就是不显示等等的问题.这篇博客将介绍WPF Data Binding相关的内容, ...

  7. css 弹性盒兼容性写法,直接复制粘贴

    看这个定义弹性布局盒子display:-webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; disp ...

  8. 如何使用EF优雅的配置一对一的关系

    在这两天的时间已经有两位同事问到EF(Code First)如何配置一对一的关系,这个说难也不难,说简单吧,一旦设计跑偏那么在Coding的过程中将会很痛苦. 先举个很简单的例子,两个类User和Pr ...

  9. nhibernate教程(4)--条件查询(Criteria Query)

    NHibernate之旅(4):探索查询之条件查询(Criteria Query) 2008-10-16 18:20 by 李永京, 44341 阅读, 43 评论, 收藏,  编辑 本节内容 NHi ...

  10. 初入PHP,(for循环~水仙花数)

    找出100-999之间的所有"水仙花数".所谓水仙花数是指一个三位 数,各位数字的立方和等于该数本身.(如153次方=1的3次方+5的3次方+3的3次方)并输出这些数字 想想153 ...