All Downloads are FREE. Search and download functionalities are using the official Maven repository.

geotrellis.raster.io.geotiff.writer.GeoTiffWriter.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2014 Azavea.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package geotrellis.raster.io.geotiff.writer

import geotrellis.raster._
import geotrellis.raster.io._
import geotrellis.raster.io.geotiff._
import geotrellis.vector.Extent
import geotrellis.proj4.CRS

import geotrellis.raster.io.geotiff.tags.codes._
import scala.collection.mutable

import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import java.io.File
import java.io.FileOutputStream
import java.nio.ByteOrder

import spire.syntax.cfor._

object GeoTiffWriter {
  def write(geoTiff: GeoTiffData, path: String): Unit = {
    val fos = new FileOutputStream(new File(path))
    try {
      val dos = new DataOutputStream(fos)
      try {
        new GeoTiffWriter(geoTiff, dos).write()
      } finally {
        dos.close
      }
    } finally {
      fos.close
    }
  }

  def write(geoTiff: GeoTiffData): Array[Byte] = {
    val bos = new ByteArrayOutputStream()
    try {
      val dos = new DataOutputStream(bos)
      try {
        new GeoTiffWriter(geoTiff, dos).write()
        bos.toByteArray
      } finally {
        dos.close
      }
    } finally {
      bos.close
    }
  }
}

class GeoTiffWriter(geoTiff: GeoTiffData, dos: DataOutputStream) {
  implicit val toBytes: ToBytes =
    if(geoTiff.imageData.decompressor.byteOrder == ByteOrder.BIG_ENDIAN)
      BigEndianToBytes
    else
      LittleEndianToBytes

  val (fieldValues, offsetFieldValueBuilder) = TiffTagFieldValue.collect(geoTiff)
  val segments = geoTiff.imageData.compressedBytes
  val segmentCount = segments.size

  val tagFieldByteCount = (fieldValues.length + 1) * 12 // Tiff Tag Fields are 12 bytes long.

  val tagDataByteCount = {
    var s = 0
    cfor(0)(_ < fieldValues.length, _ + 1) { i =>
      val len = fieldValues(i).value.length
      // If value fits in 4 bytes, we store it in the offset,
      // so only count data more than 4 bytes.
      if(len > 4) {
        s += fieldValues(i).value.length
      }
    }

    // Account for offsetFieldValue size
    // each offset is an Int long.
    if(segmentCount > 1) {
      s += (segmentCount * 4)
    }

    s
  }

  // Used to immediately write values to our ultimate destination.
  var index: Int = 0
  def writeByte(value: Byte) { dos.writeByte(value); index += 1 }
  def writeBytes(value: Array[Byte]) { dos.write(value, 0, value.length); index += value.length }

  def writeShort(value: Int) { writeBytes(toBytes(value.toShort)) }
  def writeInt(value: Int) { writeBytes(toBytes(value)) }
  def writeLong(value: Long) { writeBytes(toBytes(value)) }
  def writeFloat(value: Float) { writeBytes(toBytes(value)) }
  def writeDouble(value: Double) { writeBytes(toBytes(value)) }

  def write(): Unit = {

    // Write the header that determines the endian
    if(geoTiff.imageData.decompressor.byteOrder == ByteOrder.BIG_ENDIAN) {
      val m = 'M'.toByte
      writeByte(m)
      writeByte(m)
    } else {
      val i = 'I'.toByte
      writeByte(i)
      writeByte(i)
    }

    // TIFF header code.
    writeShort(42.toShort)

    // Write tag start offset (immediately after this 4 byte integer)
    writeInt(index + 4)

    // Compute the offsetFieldValue
    val offsetFieldValue = {
      val imageDataStartOffset =
        index +
          2 + // Short for number of tags
          4 + // Int for next IFD address
          tagFieldByteCount + tagDataByteCount


      val offsets = Array.ofDim[Int](segmentCount)
      var offset = imageDataStartOffset
      cfor(0)(_ < segmentCount, _ + 1) { i =>
        offsets(i) = offset
        offset += segments(i).length
      }
      offsetFieldValueBuilder(offsets)
    }

    // Sort the fields by tag code.
    val sortedTagFieldValues = (offsetFieldValue :: fieldValues.toList).sortBy(_.tag).toArray

    // Write the number of tags
    writeShort(sortedTagFieldValues.length)

    // Write tag fields, sorted by tag code.
    val tagDataStartOffset = 
      index + 
        4 + // Int for next IFD address
        tagFieldByteCount

    var tagDataOffset = tagDataStartOffset

    cfor(0)(_ < sortedTagFieldValues.length, _ + 1) { i =>
      val TiffTagFieldValue(tag, fieldType, length, value) = sortedTagFieldValues(i)
      writeShort(tag)
      writeShort(fieldType)
      writeInt(length)
      if(value.length > 4) {
        writeInt(tagDataOffset)
        tagDataOffset += value.length
      } else {
        var i = 0
        while(i < value.length) {
          writeByte(value(i))
          i += 1
        }
        while(i < 4) {
          writeByte(0.toByte)
          i += 1
        }
      }
    }

    // Write 0 integer to indicate the end of the last IFD.
    writeInt(0)

    assert(index == tagDataStartOffset, s"Writer error: index at $index, should be $tagDataStartOffset")
    assert(tagDataOffset == tagDataStartOffset + tagDataByteCount)

    // write tag data
    cfor(0)(_ < sortedTagFieldValues.length, _ + 1) { i =>
      val TiffTagFieldValue(tag, fieldType, length, value) = sortedTagFieldValues(i)
      if(value.length > 4) {
        writeBytes(value)
      }
    }

    // Write the image data.
    cfor(0)(_ < segmentCount, _ + 1) { i =>
      writeBytes(segments(i))
    }

    dos.flush()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy