geotiffjs / geotiff.js

geotiff.js is a small library to parse TIFF files for visualization or analysis. It is written in pure JavaScript, and is usable in both the browser and node.js applications.
https://geotiffjs.github.io/
MIT License
860 stars 181 forks source link

geotiff files with empty tiles or strips cannot be read #304

Open otfried-cheong opened 2 years ago

otfried-cheong commented 2 years ago

We regularly encounter geotiff files that geotiff.js cannot read (it throws an exception in readRasters).

After some debugging it turns out that the problem is that these images contain tiles or strips with zero bytes:

$ tiffinfo -s a-3006.tif 
TIFF Directory at offset 0xc02f6 (787190)
  Image Width: 1399 Image Length: 915
  Tile Width: 1024 Tile Length: 16
  Bits/Sample: 8
  Sample Format: unsigned integer
  Compression Scheme: AdobeDeflate
  Photometric Interpretation: RGB color
  Samples/Pixel: 3
  Planar Configuration: single image plane
  Predictor: none 1 (0x1)
  116 Tiles:
      0: [    1328,       73]
      1: [    1401,     3852]
...
    102: [  743780,     7319]
    103: [  751099,     1565]
    104: [  752664,     8413]
    105: [  761077,     1501]
    106: [  762578,     8437]
    107: [  771015,     2171]
    108: [  773186,     8007]
    109: [  781193,      756]
    110: [  781949,     3307]
    111: [       0,        0]
    112: [  785256,     1933]
    113: [       0,        0]
    114: [       0,        0]
    115: [       0,        0]

I'm not sure if this is actually legal according to the tiff spec, but there seems to be some commonly used software around that produces such files - it simply omits the tile when it only contains nodata.

I have fixed this for our use case by adding this:

     if (byteCount === 0) {                                                      
      const outSize = this.planarConfiguration === 2                            
      ? this.getBlockHeight(y) * this.getTileWidth()                            
      : this.getBlockHeight(y) * this.getTileWidth() * this.getSamplesPerPixel(\
);                                                                              
      const data = new ArrayBuffer(outSize);                                    
      const bytes = new Uint8Array(data);                                       
      bytes.fill(this.getGDALNoData());                                         
      return { x, y, sample, data};                                             
    }                                                                           

in geotiffimage.js getTileOrStrip just before the fetch. This isn't quite correct - the type of the buffer should be correct for the sample - but it works for the files I've tested with.

Unfortunately I don't have an example file that I can share (and haven't figured out how to produce one).

constantinius commented 2 years ago

@otfried-cheong

Thanks for submitting this issue!

This is indeed a common practice, GDAL even has a documentation section about this.

This feature was simply not yet requested, and I never came around to actually implement it. The solution you propose looks not too bad to me, I would be happy to accept it as a PR, if you'd like to create one. Of course, we would need to come up with a proper way to test this, though.

otfried-cheong commented 2 years ago

Thanks, that link explains a lot. Now I finally know how to create such files myself:

gdal_translate -of GTiff -co "SPARSE_OK=TRUE" -co "COMPRESS=DEFLATE" a.tif b.tif

I will prepare a PR with the patch and test cases.

zoltan-mihalyi commented 5 months ago

+1