美文网首页GeotrellisGIS后端
How it works(16) Geotrellis是如何读取

How it works(16) Geotrellis是如何读取

作者: 默而识之者 | 来源:发表于2021-03-29 22:58 被阅读0次

    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是如何读取

        本文链接:https://www.haomeiwen.com/subject/tbwihltx.html