Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ConstantTile with NoData: support idempotent CellType conversions [#3553](https://github.com/locationtech/geotrellis/pull/3553)
- GeoTiffReader Unknown tags skip fix [#3557](https://github.com/locationtech/geotrellis/pull/3557)
- Fix implicitNotFound error and rename RGBAMethods implicit class [#3563](https://github.com/locationtech/geotrellis/pull/3563)
- Set 'role' attribute in GDALMetadata tag written to tiff header, to match GDAL behaviour. [#3496](https://github.com/locationtech/geotrellis/issues/3496)

## [3.7.1] - 2024-01-08

Expand Down
14 changes: 13 additions & 1 deletion raster/src/main/scala/geotrellis/raster/io/geotiff/Tags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ case class Tags(headTags: Map[String, String], bandTags: List[Map[String, String
val bandTagsXml: Seq[scala.xml.Elem] =
bandTags.zipWithIndex.flatMap { case (map, i) =>
map.toSeq.map { case (key, value) =>
<Item name={key} sample={i.toString}>{value}</Item>
if (Tags.TAG_ROLES.contains(key.toLowerCase()))
<Item name={key} sample={i.toString} role={key.toLowerCase}>{value}</Item>
else
<Item name={key} sample={i.toString}>{value}</Item>
}
}

Expand All @@ -65,4 +68,13 @@ object Tags {

final val AREA_OR_POINT = "AREA_OR_POINT"
final val TIFFTAG_DATETIME = "TIFFTAG_DATETIME"
final val TIFFTAG_IMAGEDESCRIPTION = "TIFFTAG_IMAGEDESCRIPTION"
final val TAG_ROLES = Set(
"offset",
"scale",
"description",
"unittype",
"colorinterp"
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ import geotrellis.raster.testkit._
import geotrellis.vector.Extent

import java.io._

import org.scalatest.{BeforeAndAfterAll, Inspectors}
import org.scalatest.matchers.should.Matchers
import org.scalatest.funspec.AnyFunSpec

import scala.xml.{Text, XML}

class GeoTiffWriterSpec extends AnyFunSpec with Matchers with BeforeAndAfterAll with RasterMatchers with TileBuilders with GeoTiffTestUtils {

override def afterAll() = purge
Expand Down Expand Up @@ -113,15 +114,59 @@ class GeoTiffWriterSpec extends AnyFunSpec with Matchers with BeforeAndAfterAll

val newTag1 = ("SOME_CUSTOM_TAG1" -> "1234567890123456789012345678901")
val newTag2 = ("SOME_CUSTOM_TAG2" -> "12345678901234567890123456789012")
val newTag3 = ("OFFSET" -> "43")
val newTag4 = ("DESCRIPTION" -> "band description")
val newTag5 = ("SCALE" -> "0.1")
val headTags = geoTiff.tags.headTags + newTag1 + newTag2
val bandTags = geoTiff.tags.bandTags.map(_ + newTag1 + newTag2)
val bandTags = geoTiff.tags.bandTags.map(_ + newTag1 + newTag2 + newTag3 + newTag4 + newTag5)

val taggedTiff = geoTiff.copy(tags = Tags(headTags, bandTags))

GeoTiffWriter.write(taggedTiff, path)

addToPurge(path)

val tags = TiffTags.read(path)

val expectedXML = XML.loadString("""<GDALMetadata>
<Item name="SOME_CUSTOM_TAG2">12345678901234567890123456789012</Item>
<Item name="TAG_TYPE">HEAD</Item>
<Item name="SOME_CUSTOM_TAG1">1234567890123456789012345678901</Item>
<Item name="HEADTAG">1</Item>
<Item name="SOME_CUSTOM_TAG2" sample="0">12345678901234567890123456789012</Item>
<Item name="DESCRIPTION" sample="0" role="description">band description</Item>
<Item name="TAG_TYPE" sample="0">BAND1</Item>
<Item name="BANDTAG" sample="0">1</Item>
<Item name="OFFSET" sample="0" role="offset">43</Item>
<Item name="SOME_CUSTOM_TAG1" sample="0">1234567890123456789012345678901</Item>
<Item name="SCALE" sample="0" role="scale">0.1</Item>
<Item name="SOME_CUSTOM_TAG2" sample="1">12345678901234567890123456789012</Item>
<Item name="DESCRIPTION" sample="1" role="description">band description</Item>
<Item name="TAG_TYPE" sample="1">BAND2</Item>
<Item name="BANDTAG" sample="1">2</Item>
<Item name="OFFSET" sample="1" role="offset">43</Item>
<Item name="SOME_CUSTOM_TAG1" sample="1">1234567890123456789012345678901</Item>
<Item name="SCALE" sample="1" role="scale">0.1</Item>
<Item name="SOME_CUSTOM_TAG2" sample="2">12345678901234567890123456789012</Item>
<Item name="DESCRIPTION" sample="2" role="description">band description</Item>
<Item name="TAG_TYPE" sample="2">BAND3</Item>
<Item name="BANDTAG" sample="2">3</Item>
<Item name="OFFSET" sample="2" role="offset">43</Item>
<Item name="SOME_CUSTOM_TAG1" sample="2">1234567890123456789012345678901</Item>
<Item name="SCALE" sample="2" role="scale">0.1</Item>
<Item name="SOME_CUSTOM_TAG2" sample="3">12345678901234567890123456789012</Item>
<Item name="DESCRIPTION" sample="3" role="description">band description</Item>
<Item name="TAG_TYPE" sample="3">BAND4</Item>
<Item name="BANDTAG" sample="3">4</Item>
<Item name="OFFSET" sample="3" role="offset">43</Item>
<Item name="SOME_CUSTOM_TAG1" sample="3">1234567890123456789012345678901</Item>
<Item name="SCALE" sample="3" role="scale">0.1</Item>
</GDALMetadata>""")
val expectedItems = expectedXML.child.filter(_.isInstanceOf[scala.xml.Elem]).sortBy(_.attribute("name").get.text).sortBy(_.attribute("sample").getOrElse(Text("")).text)
val actualItems = XML.loadString(tags.geoTiffTags.metadata.get).child.filter(_.isInstanceOf[scala.xml.Elem]).sortBy(_.attribute("name").get.text).sortBy(_.attribute("sample").getOrElse(Text("")).text)

actualItems.zip(expectedItems).foreach(t=> t._1 should equal (t._2))

val actual = MultibandGeoTiff(path).tags
val expected = taggedTiff.tags

Expand Down