diff --git a/examples/cog-math-multisource.js b/examples/cog-math-multisource.js index 0ba74e9d80..1f1d6f3583 100644 --- a/examples/cog-math-multisource.js +++ b/examples/cog-math-multisource.js @@ -59,7 +59,7 @@ const map = new Map({ ], view: new View({ center: [1900000, 6100000], - zoom: 15, + zoom: 13, minZoom: 10, }), }); diff --git a/examples/cog-math.js b/examples/cog-math.js index a02b7cd234..ca9dcdfc9f 100644 --- a/examples/cog-math.js +++ b/examples/cog-math.js @@ -80,13 +80,11 @@ const map = new Map({ { // visible red, band 1 in the style expression above url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/B04.tif', - nodata: 0, max: 10000, }, { // near infrared, band 2 in the style expression above url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/B08.tif', - nodata: 0, max: 10000, }, ], diff --git a/examples/cog.html b/examples/cog.html index 2328e224ec..708b47bdde 100644 --- a/examples/cog.html +++ b/examples/cog.html @@ -4,8 +4,7 @@ title: Cloud Optimized GeoTIFF (COG) shortdesc: Rendering a COG as a tiled layer. docs: > Tiled data from a Cloud Optimized GeoTIFF (COG) can be rendered as a layer. In this - example, a single 3-band GeoTIFF is used to render RGB data. The `nodata` property is - used to avoid rendering pixels where all three bands are 0. + example, a single 3-band GeoTIFF is used to render RGB data. tags: "cog" ---
diff --git a/examples/cog.js b/examples/cog.js index e264069327..9dc5bc4afb 100644 --- a/examples/cog.js +++ b/examples/cog.js @@ -26,7 +26,6 @@ const map = new Map({ sources: [ { url: 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/TCI.tif', - nodata: 0, }, ], }), diff --git a/src/ol/source/GeoTIFF.js b/src/ol/source/GeoTIFF.js index b01f16411b..af20f311d2 100644 --- a/src/ol/source/GeoTIFF.js +++ b/src/ol/source/GeoTIFF.js @@ -191,6 +191,12 @@ class GeoTIFFSource extends DataTile { */ this.samplesPerPixel_; + /** + * @type {Array>} + * @private + */ + this.nodataValues_; + /** * @type {boolean} * @private @@ -249,6 +255,7 @@ class GeoTIFFSource extends DataTile { let tileSizes; let resolutions; const samplesPerPixel = new Array(sources.length); + const nodataValues = new Array(sources.length); let minZoom = 0; const sourceCount = sources.length; @@ -261,8 +268,14 @@ class GeoTIFFSource extends DataTile { const sourceTileSizes = new Array(imageCount); const sourceResolutions = new Array(imageCount); + nodataValues[sourceIndex] = new Array(imageCount); + for (let imageIndex = 0; imageIndex < imageCount; ++imageIndex) { const image = images[imageIndex]; + const nodataValue = image.getGDALNoData(); + nodataValues[sourceIndex][imageIndex] = + nodataValue === null ? NaN : nodataValue; + const wantedSamples = this.sourceInfo_[sourceIndex].bands; samplesPerPixel[sourceIndex] = wantedSamples ? wantedSamples.length @@ -351,13 +364,39 @@ class GeoTIFFSource extends DataTile { } this.samplesPerPixel_ = samplesPerPixel; - const sourceInfo = this.sourceInfo_; - for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) { - if (sourceInfo[sourceIndex].nodata !== undefined) { + this.nodataValues_ = nodataValues; + + // decide if we need to add an alpha band to handle nodata + outer: for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) { + // option 1: source is configured with a nodata value + if (this.sourceInfo_[sourceIndex].nodata !== undefined) { this.addAlpha_ = true; break; } + + const values = nodataValues[sourceIndex]; + + // option 2: check image metadata for limited bands + const bands = this.sourceInfo_[sourceIndex].bands; + if (bands) { + for (let i = 0; i < bands.length; ++i) { + if (!isNaN(values[bands[i]])) { + this.addAlpha_ = true; + break outer; + } + } + continue; + } + + // option 3: check image metadata for all bands + for (let imageIndex = 0; imageIndex < values.length; ++imageIndex) { + if (!isNaN(values[imageIndex])) { + this.addAlpha_ = true; + break outer; + } + } } + const additionalBands = this.addAlpha_ ? 1 : 0; this.bandCount = samplesPerPixel.reduce((accumulator, value) => { @@ -410,6 +449,7 @@ class GeoTIFFSource extends DataTile { const pixelCount = size[0] * size[1]; const dataLength = pixelCount * bandCount; + const nodataValues = this.nodataValues_; return Promise.all(requests).then(function (sourceSamples) { const data = new Uint8ClampedArray(dataLength); @@ -430,8 +470,6 @@ class GeoTIFFSource extends DataTile { const gain = 255 / (max - min); const bias = -min * gain; - const nodata = source.nodata; - for ( let sampleIndex = 0; sampleIndex < samplesPerPixel[sourceIndex]; @@ -444,6 +482,17 @@ class GeoTIFFSource extends DataTile { if (!addAlpha) { data[dataIndex] = value; } else { + let nodata = source.nodata; + if (nodata === undefined) { + let bandIndex; + if (source.bands) { + bandIndex = source.bands[sampleIndex]; + } else { + bandIndex = sampleIndex; + } + nodata = nodataValues[sourceIndex][bandIndex]; + } + if (sourceValue !== nodata) { transparent = false; data[dataIndex] = value; diff --git a/test/browser/spec/ol/source/geotiff.test.js b/test/browser/spec/ol/source/geotiff.test.js index 4efb2199a1..de76b67e98 100644 --- a/test/browser/spec/ol/source/geotiff.test.js +++ b/test/browser/spec/ol/source/geotiff.test.js @@ -25,7 +25,9 @@ describe('ol.source.GeoTIFF', function () { it('configures itself from source metadata', function (done) { source.on('change', () => { - expect(source.bandCount).to.be(3); + expect(source.addAlpha_).to.be(true); + expect(source.bandCount).to.be(4); + expect(source.nodataValues_).to.eql([[0]]); expect(source.getTileGrid().getResolutions().length).to.be(1); expect(source.projection.getCode()).to.be('EPSG:4326'); done();