1. 引入
Geotrellis是如何读取Geotiff?先看官方文档中读取单波段GeoTiff的样例:
val path: String = "path/to/geotrellis/raster/data/geotiff-test-files/lzw_int32.tif"
val geoTiff: SinglebandGeoTiff = GeoTiffReader.readSingleband(path)
我们可以追踪GeoTiffReader.readSingleband
的定义,看看其中都做了哪些操作:
//实际调用
readSingleband(
StreamingByteReader(FileRangeReader(path)),
false, true, None // 假设不存在金字塔文件
)
def readSingleband(byteReader: ByteReader, streaming: Boolean, withOverviews: Boolean, byteReaderExternal: Option[ByteReader]): SinglebandGeoTiff = {
def getSingleband(geoTiffTile: GeoTiffTile, info: GeoTiffInfo): SinglebandGeoTiff =
SinglebandGeoTiff(
geoTiffTile,
info.extent,
info.crs,
info.tags,
info.options,
info.overviews.map { i => getSingleband(geoTiffSinglebandTile(i), i) }
)
// 读取Tiff文件的元数据信息
val info = GeoTiffInfo.read(byteReader, streaming, withOverviews, byteReaderExternal)
val geoTiffTile = geoTiffSinglebandTile(info)
getSingleband(geoTiffTile, info)
}
def geoTiffSinglebandTile(info: GeoTiffInfo): GeoTiffTile =
if(info.bandCount == 1) {
GeoTiffTile(
info.segmentBytes,
info.decompressor,
info.segmentLayout,
info.compression,
info.cellType,
Some(info.bandType),
info.overviews.map(geoTiffSinglebandTile)
)
} else {
GeoTiffMultibandTile(
info.segmentBytes,
info.decompressor,
info.segmentLayout,
info.compression,
info.bandCount,
info.cellType,
Some(info.bandType),
info.overviews.map(geoTiffMultibandTile)
).band(0)
}
可以看出,创建一个SinglebandGeoTiff
对象需要准备一个GeoTiffTile
对象和一个GeoTiffInfo
对象,而GeoTiffTile
对象的创建也需要GeoTiffInfo
对象.
因此,了解一切应从理解GeoTiffInfo
的创建开始,即Geotrellis如何读取Tiff文件的元数据.
1.1 Tiff格式简介
Tiff 文件内部结构按顺讯可以分成:
- 文件头信息区IFH:存储基础信息
- 大小端信息
- Tiff类型
- 到IFD的偏移量
- 图像文件目录IFD:存储图像属性的索引(IFD可能有多个)
- 图像数据区:实际的Tiff数据和定义的图像属性
接下来就看一下Geotrellis是如何读取IFH和IFD的.
2. GeoTiffInfo.read函数体
GeoTiffInfo是一个case类,和它的伴生类定义于GeoTiffInfo.scala
文件中.
GeoTiffInfo类实例由其伴生类的方法read
创建.
定义为:
object GeoTiffInfo {
def read(
byteReader: ByteReader,
streaming: Boolean,
withOverviews: Boolean,
byteReaderExternal: Option[ByteReader] = None
)
实际调用的时候,我们传入的byteReader是StreamingByteReader(FileRangeReader(path))
,在Geotrellis中还定义了从S3或Hadoop中读取.
我们可以一步一步拆解read函数体中的步骤,了解read方法的行为.
2.1 判断大小端(读取IFH阶段)
Tiff文件的字节序同时支持大端和小端,因此在读取一切信息之前,需要了解其字节序:
// 记录字节读取器的原始位置,因为是从本地文件中读取,因此oldPos其实是0.
// 对于从S3或Hadoop中读取比特,读取器位置或许会有所不同
val oldPos = byteReader.position
// 将字节读取器指针置零,因为大端小端信息记录在最开始
byteReader.position(0)
// 通过判断前两个比特的信息即可判断字节序类型
(byteReader.get.toChar, byteReader.get.toChar) match {
case ('I', 'I') => // 一般以Intel序列(I)方式代表小端在前
byteReader.order(ByteOrder.LITTLE_ENDIAN)
case ('M', 'M') => // 以摩托罗拉序列(M)代表大端在前
byteReader.order(ByteOrder.BIG_ENDIAN)
case _ => throw new MalformedGeoTiffException("incorrect byte order")
}
// 跳过大小端判断位置,开始其他信息的读取
byteReader.position(oldPos + 2)
2. 2 判断Tiff类型(读取IFH阶段)
在读取一切信息之前还需要知道Tiff是普通Tiff还是BigTiff.因为BigTiff的文件头与普通Tiff不一致,影响到后续信息的读取.
关于Tiff/BigTiff文件头的定义,可以参考这里,后面许多读取操作都与其中信息有关.
// 从刚才的指针继续读取2byte,跳到定义Tiff类型的位置
val tiffIdNumber = byteReader.getChar
if (tiffIdNumber != 42 && tiffIdNumber != 43)
throw new MalformedGeoTiffException(s"bad identification number (must be 42 or 43, was $tiffIdNumber (${tiffIdNumber.toInt}))")
// 42代表普通Tiff,43代表BigTiff
val tiffType = TiffType.fromCode(tiffIdNumber)
2.3 读取TiffTag信息(读取IFD阶段)
TiffTag是一系列元数据,包括基础信息和地理相关信息,是read方法最核心的功能.TiffTag是如何读取的呢?
// 读取基本TiffTag
val baseTiffTags: TiffTags =
tiffType match {
case Tiff => // 普通Tiff
// 根据头定义,获取普通Tiff文件的TiffTag指针偏移位置
val smallStart = byteReader.getInt
// 因为TiffTags.read函数的参数类型为long,所以需要转换一下,免得需要定义一个除了参数不一样其他都相同的多态函数
TiffTags.read(byteReader, smallStart.toLong)(IntTiffTagOffsetSize)
case _ => // BigTiff模式
// 根据头定义,直接偏移指针位置,并获取BigTiff文件的TiffTag指针偏移位置
byteReader.position(8)
val bigStart = byteReader.getLong
TiffTags.read(byteReader, bigStart)(LongTiffTagOffsetSize)
}
可以看出,具体的读取操作封装在TiffTags.read
中,那我们就进入TiffTag类看一下.
3. TiffTags.read函数体
TiffTag也是一个case类,与它的伴生类定义于TiffTags.scala
文件中.TiffTag类实例也由其伴生类的read
方法创建:
def read(
byteReader: ByteReader,
tagsStartPosition: Long)
(implicit ttos: TiffTagOffsetSize): TiffTags
- 隐式定义了区分Tiff类型的变量ttos
3.1 存在的问题与解决之道
Tifftag是一个case类,所以必须要在初始化的时候将全部参数准备好,因为case类的属性是不可更改的.
但对于Tiff文件这种具有诸多属性参数,且属性是否存在都不一定的情况,该如何处理?
当然可以先将全部参数读取出来,再经过处理统一赋值.那有没有更加优雅的方法呢?
当然有,整个Geotrellis大量使用了Monocle这个库.Monocle是一个可以方便操作不可变数据的库.这个以单片眼镜命名的库定义了一系列以光学仪器(如lens)命名的数据访问机制.使用这些机制,case类就可以在创建之后修改其不可变参数,大大简化了代码编写,具体细节将在代码中有所展现.
3.2 读取Tag的数量(IFD阶段)
由Tiff/BigTiff文件头定义可知,在全部Tag的开始,存储了Tag的数量,方便遍历:
- 普通 TIFF
位置 | 数据大小 | 描述 |
---|---|---|
0 | 16-bit | 0x4D4D constant |
2 | 16-bit | 0x002A version = standard TIFF |
4 | 32-bit | offset to first directory |
- BigTIFF
位置 | 数据大小 | 描述 |
---|---|---|
0 | 16-bit | 0x4D4D constant |
2 | 16-bit | 0x002B version = BigTIFF |
4 | 16-bit | 0x0008 bytesize of offsets |
6 | 16-bit | 0x0000 constant |
8 | 64-bit | offset to first directory |
val tagCount =
ttos match {
case IntTiffTagOffsetSize =>
byteReader.position(tagsStartPosition.toInt)
byteReader.getShort
case LongTiffTagOffsetSize =>
byteReader.position(tagsStartPosition)
byteReader.getLong // 理论上来讲Bigtiff可以存储远超普通TiffTag数量的Tag
}
3.3 实例化空的Tifftag类
var tiffTags = TiffTags()
可以看见,tiffTags在读取Tag之前就用空参数实例化了.不过TiffTags并非普通的case类.
可以看见,TiffTags类有一个特殊的宏注解Lenses
:
@Lenses("_") //Lenses注解
case class TiffTags(
metadataTags: MetadataTags = MetadataTags(), //这些参数在内部已经转变为PLens类型的参数
basicTags: BasicTags = BasicTags(),
//...
)
// 根据的Monocle定义
// 可以通过tiffTags._metadataTags读取/修改tiffTags.metadataTags参数
Lenses注解是Monocle定义的存储机制.可以参考这里了解API.使用这个注解后,该类的实例就可以通过Monocle定义的各种机制操作实例中的不可变参数.
3.4 读取实际的Tag元数据(读取IFD+数据体阶段)
Tiff数据是一种可以灵活扩展的数据,有充足的位置存储各种各样的元数据.因此被广泛采用.TiffTag采用了类似字典式的存储方式:在文件头位置存储索引,在数据体存储具体信息.
同时在这里也可以看出BigTiff理论上可以支持更多的元数据信息:
- 普通 TIFF的IFD定义
位置 | 数据大小 | 描述 |
---|---|---|
0 | 16-bit | number of directory entries |
--- 对于每一个条目 | ||
0 | 16-bit | tag identifying information for entry |
2 | 16-bit | data type of entry (for standard TIFF 14 types are defined, 0-13) |
4 | 32-bit | count of elements for entry |
8 | 32-bit | data itself (if <= 32-bits) or offset to data for entry |
--- 全部条目之后 | ||
2+n*12 | 32-bit | 到下一个IFD的偏移或0 |
- BigTIFF的IFD定义
位置 | 数据大小 | 描述 |
---|---|---|
0 | 64-bit | number of directory entries |
--- 对于每一个条目 | ||
0 | 16-bit | tag identifying information for entry |
2 | 16-bit | data type of entry (for BigTIFF 17 types are defined, 0-13, 16-18) |
4 | 64-bit | count of elements for entry |
12 | 64-bit | data itself (if <= 64-bits) or offset to data for entry |
--- 全部条目之后 | ||
8+n*20 | 64-bit | 到下一个IFD的偏移或0 |
var geoTags: Option[TiffTagMetadata] = None
// 遍历全部Tag
cfor(0)(_ < tagCount, _ + 1) { i =>
val tagMetadata =
ttos match {
case IntTiffTagOffsetSize =>
// 区分不同Tiff类型读取每一个Tag的信息,有了这些索引信息就可以读取到全部信息
TiffTagMetadata(
byteReader.getUnsignedShort, // 标签识别信息
byteReader.getUnsignedShort, // 数据类型
byteReader.getInt, // 元素数量/长度
byteReader.getInt // 数据偏移
)
case LongTiffTagOffsetSize =>
TiffTagMetadata(
byteReader.getUnsignedShort,
byteReader.getUnsignedShort,
byteReader.getLong,
byteReader.getLong
)
}
// Geotag并不直接写入到tiffTag内,而是先缓存起来
if (tagMetadata.tag == codes.TagCodes.GeoKeyDirectoryTag)
geoTags = Some(tagMetadata)
else // 根据读取的Tag描述信息读取具体的Tag,读取完成后再重新赋值给tiffTags
tiffTags = readTag(byteReader, tiffTags, tagMetadata)
}
// 在处理完全部TiffTag之后再单独处理GeoTags
geoTags match {
case Some(t) => tiffTags = readTag(byteReader, tiffTags, t)
case None =>
}
具体读取信息又被封装在readTag
函数中,以读取ModelPixelScale为例就了解读取Tag的一般模式:
private def readTag(byteReader: ByteReader, tiffTags: TiffTags, tagMetadata: TiffTagMetadata)(implicit ttos: TiffTagOffsetSize): TiffTags = {
(tagMetadata.tag, tagMetadata.fieldType) match {
// Tiff通过Tag Code和字段类型标识Tag
// ModelPixelScale的Tag Code是33550,在Geotrellis中定义为枚举ModelPixelScaleTag
// 全部TagCode枚举信息在TagCodes.scala中定义
case (ModelPixelScaleTag, _) =>
readModelPixelScaleTag(byteReader, tiffTags, tagMetadata)
//... 省略其他类似
}
}
private def readModelPixelScaleTag(byteReader: ByteReader, tiffTags: TiffTags, tagMetadata: TiffTagMetadata)(implicit ttos: TiffTagOffsetSize) = {
// 记录当前的偏移位置
val oldPos = byteReader.position
// 将指针定位到该Tag定义的偏移位置
byteReader.position(tagMetadata.offset)
// 根据ModelPixelScale的定义,读取值
val scaleX = byteReader.getDouble
val scaleY = byteReader.getDouble
val scaleZ = byteReader.getDouble
// 读取完数据,将指针回归原始位置
byteReader.position(oldPos)
// 修改不可变的case类,所以使用了monocle库
(tiffTags &|->
TiffTags._geoTiffTags ^|->
GeoTiffTags._modelPixelScale set(Some(scaleX, scaleY, scaleZ)))
}
在readModelPixelScaleTag
函数的最后,出现了一些奇怪的表达式,包含诸如&|->
,^|->
的操作符.这些都是Monocle定义的操作符.这里是TiffTag操作的精华,这三行代码展示了如何使用Monocle库修改对象的不可变参数.
在上文中提到,TiffTag类有一个名为Lenses
的宏注释,可以将case类的参数类型变为Monocle定义的Plens类型.我们实际上操作的已经不是原先在代码中定义的参数,而是对他们经过一层Monocle包装的对象.
在代码定义中.modelPixelScale位于TiffTag.geoTiffTags.modelPixelScale
(GeoTiffTags类也被相同的宏注释修饰了)中,在初始化时,是个空值.要修改这个值,按照Monocle的逻辑,需要按照如下方式进行:
-
tiffTags &|-> TiffTags._geoTiffTags
:返回一个ApplyLens
对象A,这个对象专门用来操作tiffTags._geoTiffTags
.-
&|->
操作符定义在这里,用于从原始对象中生成操作PLens的对象 - 这里的PLens是geoTiffTags
-
-
A ^|-> GeoTiffTags._modelPixelScale
:返回一个ApplyLens
对象B- 就像Lens的语义一样,像是透镜一般,我们又将视角从
tiffTags._geoTiffTags
对准了_geoTiffTags
内部的modelPixelScale
-
^|->
操作符定义也同样位于这里
- 就像Lens的语义一样,像是透镜一般,我们又将视角从
-
B.set(Some(scaleX, scaleY, scaleZ)
:给B的Plens设置值,就是给tiffTags._geoTiffTags._modelPixelScale
设置值 - 最终将这个修改后的tiffTag重新赋值给tiffTag
- 继续读取下一个Tag
- 随着最终读取完毕,TiffTag的信息也被全部补齐了
3.5 读取GeoTag元数据
GeoTags的存储方式稍微复杂一些:
- GeoTags自己定义了一系列Tag,将这些Tag的索引存储在
GeoKeyDirectoryTag
中 - 具体的Tag值与其他TiffTag存储在一起,但仅仅使用
GeoDoubleParamsTag
或GeoAsciiParamsTag
表明其数据类型,不再表明其具体的Tag- 全部的double(ascii)类型的GeoTag都存储在
GeoDoubleParamsTag
(GeoAsciiParamsTag
)指向的一个数组中 - 等待全部TiffTag读取完毕后,这些GeoTag也已经被读取了,只是需要通过
GeoKeyDirectoryTag
中存储的索引从这个大数组中找到具体对应的GeoTag与其偏移量即可读取.
- 全部的double(ascii)类型的GeoTag都存储在
因为大多数tagCode已经定义好了,新的TagCode不能与旧的重复.,在索引之上再建一层索引相当于扩充了可表示的范围,GeoTag可以更自由的增加其信息条目.
标准TiffTag只给GeoTags预留了这几个tagCode:
DecTagCode | HexTagCode | Tag名 | 描述 |
---|---|---|---|
34735 | 87AF | GeoKeyDirectoryTag | Used in interchangeable GeoTIFF files. |
34736 | 87B0 | GeoDoubleParamsTag | Used in interchangeable GeoTIFF files. |
34737 | 87B1 | GeoAsciiParamsTag | Used in interchangeable GeoTIFF files. |
有关GeoKeyDirectory的定义可以在这里找到.详细的GeoTags定义可以在这里找到.
// readTag中读取GeoTag相关的部分
private def readTag(byteReader: ByteReader, tiffTags: TiffTags, tagMetadata: TiffTagMetadata)(implicit ttos: TiffTagOffsetSize): TiffTags = {
(tagMetadata.tag, tagMetadata.fieldType) match {
case (GeoKeyDirectoryTag, _) =>
readGeoKeyDirectoryTag(byteReader, tiffTags, tagMetadata)
// 读取一切Double类型的Tag
case (_, DoublesFieldType) =>
readDoublesTag(byteReader, tiffTags, tagMetadata)
// ... 省略其他类似的
}
// 读取GeoTags中的Double值
private def readDoublesTag(byteReader: ByteReader, tiffTags: TiffTags, tagMetadata: TiffTagMetadata)(implicit ttos: TiffTagOffsetSize) = {
// 读取double数组
val doubles = byteReader.getDoubleArray(offset = tagMetadata.offset, length = tagMetadata.length)
tagMetadata.tag match {
// 如果是ModelTransformation类型的Tag
case ModelTransformationTag =>
// ... 省略
// 如果是Double类型的GeoTags,将其存储在tiffTags.geotifftags.doubles中
case DoublesTag => tiffTags &|->
TiffTags._geoTiffTags ^|->
GeoTiffTags._doubles set(Some(doubles))
case tag => tiffTags &|-> // 记录其他非标准Tag
TiffTags._nonStandardizedTags ^|->
NonStandardizedTags._doublesMap modify(_ + (tag -> doubles))
}
}
private def readGeoKeyDirectoryTag(byteReader: ByteReader, tiffTags: TiffTags, tagMetadata: TiffTagMetadata)(implicit ttos: TiffTagOffsetSize) = {
// 与readModelPixelScaleTag相同的逻辑
val oldPos = byteReader.position
byteReader.position(tagMetadata.offset)
// GeoKeyDirectory是一个索引,描述GeoTag的实际信息
val version = byteReader.getShort
val keyRevision = byteReader.getShort
val minorRevision = byteReader.getShort
val numberOfKeys = byteReader.getShort
val keyDirectoryMetadata = GeoKeyDirectoryMetadata(version, keyRevision,
minorRevision, numberOfKeys)
// 从geoKeyDirectory读取GeoTags
// 这个时候传入的tiffTags包含了先前读取到的全部普通TiffTag
val geoKeyDirectory = GeoKeyReader.read(byteReader,
tiffTags, GeoKeyDirectory(count = numberOfKeys))
// 与readModelPixelScaleTag相同的赋值逻辑
byteReader.position(oldPos)
(tiffTags &|->
TiffTags._geoTiffTags ^|->
GeoTiffTags._geoKeyDirectory set(Some(geoKeyDirectory)))
}
具体读取信息又被封装在GeoKeyReader.read
函数中:
object GeoKeyReader {
def read(byteReader: ByteReader, imageDirectory: TiffTags,
geoKeyDirectory: GeoKeyDirectory, index: Int = 0
): GeoKeyDirectory = {
// 读取Geotag的键值对
def readGeoKeyEntry(keyMetadata: GeoKeyMetadata,
geoKeyDirectory: GeoKeyDirectory): Option[GeoKeyDirectory] = keyMetadata.tiffTagLocation match {
// 如果是0,按照geotiff规范,数据直接存储于值偏移(offset)字段,类型为short
case 0 => Some(readShort(keyMetadata, geoKeyDirectory))
// 若不是short,则仅有Doubles和Asciis两种
case DoublesTag => Some(readDoubles(keyMetadata, geoKeyDirectory))
case AsciisTag => Some(readAsciis(keyMetadata, geoKeyDirectory))
case _ => None
}
// 读取Double类型为例
def readDoubles(keyMetadata: GeoKeyMetadata,
geoKeyDirectory: GeoKeyDirectory) = {
val doubles = imageDirectory
.geoTiffTags
.doubles // 在上方的代码段中我们读取到的double
.get
.drop(keyMetadata.valueOffset) // 从偏移位置开始的数组提取指定数量,得到实际值
.take(keyMetadata.count)
keyMetadata.keyID match {
// 与readModelPixelScaleTag相同的赋值逻辑,给对应的GeoTag赋值
case GeogLinearUnitSizeGeoKey => geoKeyDirectory &|->
GeoKeyDirectory._geogCSParameterKeys ^|->
GeogCSParameterKeys._geogLinearUnitSize set(Some(doubles(0)))
// ... 省略
}
// ...
// 省略readAsciis,与readDoubles类似
index match {
case geoKeyDirectory.count => geoKeyDirectory // 若实际的数量就是0,则GeoTags为空
case _ => { // 有GeoTags的情况
val keyEntryMetadata = GeoKeyMetadata(
byteReader.getUnsignedShort, // 具体Geotag类型
byteReader.getUnsignedShort, // tiffTagLocation
byteReader.getUnsignedShort, // 数量
byteReader.getUnsignedShort // 值偏移
)
readGeoKeyEntry(keyEntryMetadata, geoKeyDirectory) match {
case Some(updatedDirectory) => read(byteReader, imageDirectory, updatedDirectory, index + 1)
case None => geoKeyDirectory
}
}
}
}
}
4 读取额外的信息
我们从对TiffTags.read(byteReader,smallStart.toLong)(IntTiffTagOffsetSize)
的解读中跳出来,继续阅读接下来的代码.
需要记住,上面的代码结束后,当前的读取指针(byteReader的position)已经停在第一个IFD的末尾了.
因为Tiff文件支持子文件模式,因此Tiff可以将金字塔数据也存储在文件内,他们也有自己的IFD,我们可以从内部金字塔的IFD中读取更多GeoTag信息.我们需要从第一个IFD的末尾跳到下一个IFD,因为下一个IFD可能就是金字塔的IFD了.
// 额外的TiffTag列表
val tiffTagsList: List[TiffTags] = {
// 构建一个列表缓冲
val tiffTagsBuffer: ListBuffer[TiffTags] = ListBuffer()
// 假定存在内部金字塔
if(withOverviews) {
tiffType match {
case Tiff =>
// 如果有下一个IFD,就读取.ifdOffset为0表示没有下一个IFD了
// 如果存在金字塔IFD就记录,相当于每层金字塔都有一套TiffTag
var ifdOffset = byteReader.getInt
while (ifdOffset > 0) {
// 读取新的IFD内的信息
val ifdTiffTags = TiffTags.read(byteReader, ifdOffset)(IntTiffTagOffsetSize)
val subfileType = ifdTiffTags.nonBasicTags.newSubfileType.flatMap(NewSubfileType.fromCode)
// 假如该IFD的NewSubfileType值为1(即ReducedImage),则为金字塔的IFD,保留其中的tiffTag
if(subfileType.contains(ReducedImage)) tiffTagsBuffer += ifdTiffTags
// 前往下一个可能存在的IFD
ifdOffset = byteReader.getInt
}
case _ =>
// ... BigTiff与普通Tiff类似
}
}
tiffTagsBuffer.toList
}
5. 产生最终的GeoTiffInfo实例
def getGeoTiffInfo(tiffTags: TiffTags, overviews: List[GeoTiffInfo] = Nil): GeoTiffInfo = {
val interleaveMethod = tiffTags.interleaveMethod
val decompressor = Decompressor(tiffTags, byteReader.order)
val storageMethod: StorageMethod =
// 检测tif是否是带状存储
//https://geotrellis.readthedocs.io/en/v3.5.1/guide/core-concepts.html#striped
if(tiffTags.hasStripStorage) {
val rowsPerStrip: Int =
Striped(rowsPerStrip)
} else {
val blockCols =
(tiffTags
&|-> TiffTags._tileTags
^|-> TileTags._tileWidth get).get.toInt
val blockRows =
(tiffTags
&|-> TiffTags._tileTags
^|-> TileTags._tileLength get).get.toInt
Tiled(blockCols, blockRows)
}
// 对应TiffTags中的imageWidth
val cols = tiffTags.cols
// 对应TiffTags中的imageLength
val rows = tiffTags.rows
val bandType = tiffTags.bandType
val bandCount = tiffTags.bandCount
val segmentLayout = GeoTiffSegmentLayout(cols, rows, storageMethod, interleaveMethod, bandType)
// 本例中采用了非流模式
val segmentBytes: SegmentBytes =
if (streaming)
LazySegmentBytes(byteReader, tiffTags)
else
ArraySegmentBytes(byteReader, tiffTags)
val noDataValue =
(tiffTags
&|-> TiffTags._geoTiffTags
^|-> GeoTiffTags._gdalInternalNoData get)
val subfileType =
(tiffTags
&|-> TiffTags._nonBasicTags ^|->
NonBasicTags._newSubfileType get).flatMap(code => NewSubfileType.fromCode(code))
// 若原始影像没有压缩,则设为非压缩
// 否则一律设置为使用Deflate压缩
val compression =
decompressor match {
case NoCompression => NoCompression
case _ => DeflateCompression
}
val colorSpace = tiffTags.basicTags.photometricInterp
val colorMap = if (colorSpace == ColorSpace.Palette && tiffTags.basicTags.colorMap.nonEmpty) {
Option(IndexedColorMap.fromTiffPalette(tiffTags.basicTags.colorMap))
} else None
// 返回GeotiffInfo这个Case类的实例
GeoTiffInfo(
baseTiffTags.extent,
baseTiffTags.crs,
tiffTags.tags,
GeoTiffOptions(storageMethod, compression, colorSpace, colorMap, interleaveMethod, subfileType, tiffType),
bandType,
segmentBytes,
decompressor,
segmentLayout,
compression,
bandCount,
noDataValue,
overviews
)
}
val overviews: List[GeoTiffInfo] = {
// 将每层金字塔也制作为GeoTiffInfo对象
val list = tiffTagsList.map(getGeoTiffInfo(_))
// 若内部不存在金字塔,尝试使用外部金字塔.不过在本例中假设外部也没有金字塔
if(tiffTagsList.isEmpty && withOverviews)
byteReaderExternal
.map { reader =>
GeoTiffInfo.read(reader, streaming, withOverviews, None)
.toList
.map { _.copy(extent = baseTiffTags.extent, crs = baseTiffTags.crs) }
}
.getOrElse(list)
else list
}
// 生成的最终GeoTiffInfo实例
getGeoTiffInfo(baseTiffTags, overviews)
至此,TiffTag就全部读取完成了.