diff --git a/CHANGELOG.md b/CHANGELOG.md index 869f316d6e..2dafe429c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - GeoTiffRasterSource is not threadsafe enough [#3265](https://github.com/locationtech/geotrellis/pull/3265) - Fix ShortArrayTileResult result ArrayTile fulfillment [#3268](https://github.com/locationtech/geotrellis/pull/3268) - GeoTiffRasterSource TiledToLayout reads by SpatialKey can produce non-256x256 tiles [3267](https://github.com/locationtech/geotrellis/issues/3267) +- Fix Overviews Read Incorrectly when Per Dataset Masks Present [#3269](https://github.com/locationtech/geotrellis/issues/3269) ## [3.4.0] - 2020-06-19 diff --git a/raster/data/geotiff-test-files.zip b/raster/data/geotiff-test-files.zip index 53162e6163..136dd4f314 100644 Binary files a/raster/data/geotiff-test-files.zip and b/raster/data/geotiff-test-files.zip differ diff --git a/raster/src/main/scala/geotrellis/raster/io/geotiff/NewSubfileType.scala b/raster/src/main/scala/geotrellis/raster/io/geotiff/NewSubfileType.scala index 939816f4dd..65fc59bda2 100644 --- a/raster/src/main/scala/geotrellis/raster/io/geotiff/NewSubfileType.scala +++ b/raster/src/main/scala/geotrellis/raster/io/geotiff/NewSubfileType.scala @@ -19,22 +19,44 @@ package geotrellis.raster.io.geotiff /** * A general indication of the kind of data contained in this subfile. * URL: https://www.awaresystems.be/imaging/tiff/tifftags/newsubfiletype.html + * URL #2: https://exiftool.org/TagNames/EXIF.html */ abstract sealed class NewSubfileType extends Serializable { val code: Int } +/** Full resolution image */ +case object FullResolutionImage extends NewSubfileType { val code = 0 } /** Reduced-resolution version of another image in this TIFF file */ case object ReducedImage extends NewSubfileType { val code = 1 } /** Single page of a multi-page image (see the PageNumber field description) */ case object Page extends NewSubfileType { val code = 2 } /** Transparency mask for another image in this TIFF file */ case object Mask extends NewSubfileType { val code = 4 } +/** Reduced-resolution version of a transparency mask */ +case object ReducedImageMask extends NewSubfileType { val code = 5 } +/** Transparency mask of multi-page image */ +case object MultiPageMask extends NewSubfileType { val code = 6 } +/** Transparency mask of reduced-resolution multi-page image */ +case object MultiPageReducedImageMask extends NewSubfileType { val code = 7 } +/** Depth map */ +case object Depth extends NewSubfileType { val code = 8 } +/** Depth map of reduced-resolution image */ +case object ReducedImageDepth extends NewSubfileType { val code = 9 } +/** Enhanced image data */ +case object Enhanced extends NewSubfileType { val code = 10 } object NewSubfileType { def fromCode(code: Long): Option[NewSubfileType] = fromCode(code.toInt) def fromCode(code: Int): Option[NewSubfileType] = code match { + case FullResolutionImage.code => Some(FullResolutionImage) case ReducedImage.code => Some(ReducedImage) case Page.code => Some(Page) case Mask.code => Some(Mask) + case ReducedImageMask.code => Some(ReducedImageMask) + case MultiPageMask.code => Some(MultiPageMask) + case MultiPageReducedImageMask.code => Some(MultiPageReducedImageMask) + case Depth.code => Some(Depth) + case ReducedImageDepth.code => Some(ReducedImageDepth) + case Enhanced.code => Some(Enhanced) case _ => None } } diff --git a/raster/src/main/scala/geotrellis/raster/io/geotiff/reader/GeoTiffInfo.scala b/raster/src/main/scala/geotrellis/raster/io/geotiff/reader/GeoTiffInfo.scala index b851cc0f91..0cb3415fdc 100644 --- a/raster/src/main/scala/geotrellis/raster/io/geotiff/reader/GeoTiffInfo.scala +++ b/raster/src/main/scala/geotrellis/raster/io/geotiff/reader/GeoTiffInfo.scala @@ -160,13 +160,21 @@ object GeoTiffInfo { case Tiff => var ifdOffset = byteReader.getInt while (ifdOffset > 0) { - tiffTagsBuffer += TiffTags.read(byteReader, ifdOffset)(IntTiffTagOffsetSize) + val ifdTiffTags = TiffTags.read(byteReader, ifdOffset)(IntTiffTagOffsetSize) + // TIFF Reader supports only overviews at this point + // Overview is a reduced-resolution IFD + val subfileType = ifdTiffTags.nonBasicTags.newSubfileType.flatMap(NewSubfileType.fromCode) + if(subfileType.contains(ReducedImage)) tiffTagsBuffer += ifdTiffTags ifdOffset = byteReader.getInt } case _ => var ifdOffset = byteReader.getLong while (ifdOffset > 0) { - tiffTagsBuffer += TiffTags.read(byteReader, ifdOffset)(LongTiffTagOffsetSize) + val ifdTiffTags = TiffTags.read(byteReader, ifdOffset)(LongTiffTagOffsetSize) + // TIFF Reader supports only overviews at this point + // Overview is a reduced-resolution IFD + val subfileType = ifdTiffTags.nonBasicTags.newSubfileType.flatMap(NewSubfileType.fromCode) + if(subfileType.contains(ReducedImage)) tiffTagsBuffer += ifdTiffTags ifdOffset = byteReader.getLong } } diff --git a/raster/src/main/scala/geotrellis/raster/io/geotiff/tags/GeoKeyDirectory.scala b/raster/src/main/scala/geotrellis/raster/io/geotiff/tags/GeoKeyDirectory.scala index 6041cfc586..7660aeae9c 100644 --- a/raster/src/main/scala/geotrellis/raster/io/geotiff/tags/GeoKeyDirectory.scala +++ b/raster/src/main/scala/geotrellis/raster/io/geotiff/tags/GeoKeyDirectory.scala @@ -18,9 +18,7 @@ package geotrellis.raster.io.geotiff.tags import ProjectionTypesMap._ import geotrellis.raster.io.geotiff.reader.MalformedGeoTiffException -import geotrellis.raster.io.geotiff.util._ -import monocle.syntax._ import monocle.macros.Lenses import io.circe._ import io.circe.generic.semiauto._ diff --git a/raster/src/test/scala/geotrellis/raster/io/geotiff/reader/SinglebandGeoTiffReaderSpec.scala b/raster/src/test/scala/geotrellis/raster/io/geotiff/reader/SinglebandGeoTiffReaderSpec.scala index 65a94d87f1..2b19271285 100644 --- a/raster/src/test/scala/geotrellis/raster/io/geotiff/reader/SinglebandGeoTiffReaderSpec.scala +++ b/raster/src/test/scala/geotrellis/raster/io/geotiff/reader/SinglebandGeoTiffReaderSpec.scala @@ -24,10 +24,7 @@ import geotrellis.util.Filesystem import spire.syntax.cfor._ import org.scalatest._ -class SinglebandGeoTiffReaderSpec extends FunSpec - with RasterMatchers - with GeoTiffTestUtils { - +class SinglebandGeoTiffReaderSpec extends FunSpec with RasterMatchers with GeoTiffTestUtils { def geoTiff(storage: String, cellType: String): SinglebandGeoTiff = SinglebandGeoTiff(geoTiffPath(s"uncompressed/$storage/${cellType}.tif")) @@ -137,6 +134,28 @@ class SinglebandGeoTiffReaderSpec extends FunSpec } } + it("should read tiff with masks and mask overviews correct (skip everything that is not a reduced image)") { + // sizes of overviews, starting with the base ifd + val sizes = List(1024 -> 1024, 512 -> 512) + + val tiff = SinglebandGeoTiff(geoTiffPath("overviews/per-dataset-mask.tif")) + val tile = tiff.tile + + tiff.getOverviewsCount should be (1) + tile.isNoDataTile should be (false) + + tile.cols -> tile.rows should be (sizes(0)) + + tiff.overviews.zip(sizes.tail).foreach { case (ovrTiff, ovrSize) => + val ovrTile = ovrTiff.tile + + ovrTiff.getOverviewsCount should be (0) + ovrTile.isNoDataTile should be (false) + + ovrTile.cols -> ovrTile.rows should be (ovrSize) + } + } + it("should pick up the tiff overview correct (AutoHigherResolution test)") { def cellSizesSequence(cellSize: CellSize): List[CellSize] = { val CellSize(w, h) = cellSize