How it works(16) Geotrellis是如何读取GeoTiff的(A) 读取元数据

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的逻辑,需要按照如下方式进行:

  1. tiffTags &|-> TiffTags._geoTiffTags:返回一个ApplyLens对象A,这个对象专门用来操作tiffTags._geoTiffTags.
    1. &|->操作符定义在这里,用于从原始对象中生成操作PLens的对象
    2. 这里的PLens是geoTiffTags
  2. A ^|-> GeoTiffTags._modelPixelScale:返回一个ApplyLens对象B
    1. 就像Lens的语义一样,像是透镜一般,我们又将视角从tiffTags._geoTiffTags对准了_geoTiffTags内部的modelPixelScale
    2. ^|->操作符定义也同样位于这里
  3. B.set(Some(scaleX, scaleY, scaleZ):给B的Plens设置值,就是给tiffTags._geoTiffTags._modelPixelScale设置值
  4. 最终将这个修改后的tiffTag重新赋值给tiffTag
  5. 继续读取下一个Tag
  6. 随着最终读取完毕,TiffTag的信息也被全部补齐了

3.5 读取GeoTag元数据

GeoTags的存储方式稍微复杂一些:

  • GeoTags自己定义了一系列Tag,将这些Tag的索引存储在GeoKeyDirectoryTag
  • 具体的Tag值与其他TiffTag存储在一起,但仅仅使用GeoDoubleParamsTagGeoAsciiParamsTag表明其数据类型,不再表明其具体的Tag
    • 全部的double(ascii)类型的GeoTag都存储在GeoDoubleParamsTag(GeoAsciiParamsTag)指向的一个数组中
    • 等待全部TiffTag读取完毕后,这些GeoTag也已经被读取了,只是需要通过GeoKeyDirectoryTag中存储的索引从这个大数组中找到具体对应的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就全部读取完成了.

你可能感兴趣的:(How it works(16) Geotrellis是如何读取GeoTiff的(A) 读取元数据)