From 2f61bc887ff5756163042c96800bf57f3d7350c8 Mon Sep 17 00:00:00 2001 From: achung Date: Wed, 28 May 2025 14:53:58 -0700 Subject: [PATCH 1/3] Add multiband cog option in config --- .../src/metaconfigs/layer-image-config.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/configure/src/metaconfigs/layer-image-config.json b/configure/src/metaconfigs/layer-image-config.json index 2482b0077..9ffa84d21 100644 --- a/configure/src/metaconfigs/layer-image-config.json +++ b/configure/src/metaconfigs/layer-image-config.json @@ -114,6 +114,24 @@ }, { "name": "Cloud-Optimized GeoTiffs (COG)", + "components": [ + { + "field": "cogBands", + "name": "Tile Bands", + "description": "Which bands from the COG from which to generate tiles. Defaults to '1,2,3' as RGB or '1' if it's a Transformed 32-bit COG. Can be a single number or a comma-separated list of numbers. Order matters.", + "type": "textarray", + "width": 4 + }, + { + "field": "cogBandsQuery", + "name": "Query Bands", + "description": "Which bands from the COG upon which to perform queries. Defaults to value of 'Tile Bands'.", + "type": "textarray", + "width": 4 + } + ] + }, + { "components": [ { "field": "cogTransform", From 4aabdf50f234b4cbbce7a1ad35fa96b4d11291cd Mon Sep 17 00:00:00 2001 From: achung Date: Mon, 9 Jun 2025 14:27:53 -0700 Subject: [PATCH 2/3] Display correct user input band in image layer and correct identifier bands --- src/essence/Basics/Map_/Map_.js | 86 ++++++++++++++++--- .../Tools/Identifier/IdentifierTool.js | 43 +++++++++- 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index 95ec88ed6..6fa602cff 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -1488,6 +1488,36 @@ function makeDataLayer(layerObj) { allLayersLoaded() } +function getPixelValue(georaster, values) { + // Default behavior from + // https://github.com/GeoTIFF/georaster-layer-for-leaflet/blob/b57bd2039cd23aca1e4e01efcd6963eb3fc4bbb4/src/georaster-layer-for-leaflet.ts#L888-L910 + const numberOfValues = values.length; + if (numberOfValues == 1) { + const value = values[0] + if (georaster.palette) { + const [r, g, b, a] = georaster.palette[value] + return `rgba(${r},${g},${b},${a / 255})` + } else if (georaster.georasters[0].mins) { + const { mins, ranges } = georaster.georasters[0] + return georaster.scale((values[0] - mins[0]) / ranges[0]).hex() + } else if (georaster.currentStats.mins) { + const min = georaster.currentStats.mins[0] + const range = georaster.currentStats.ranges[0] + return georaster.scale((values[0] - min) / range).hex() + } + } else if (numberOfValues === 2) { + return `rgb(${values[0]},${values[1]},0)` + } else if (numberOfValues === 3) { + return `rgb(${values[0]},${values[1]},${values[2]})` + } else if (numberOfValues === 4) { + return `rgba(${values[0]},${values[1]},${values[2]},${values[3] / 255})` + } else if (numberOfValues > 4) { + // Use the first 3 bands by default + return `rgb(${values[0]},${values[1]},${values[2]})` + } +} + + function makeImageLayer(layerObj) { let layerUrl = L_.getUrl(layerObj.type, layerObj.url, layerObj) if (!F_.isUrlAbsolute(layerUrl)) { @@ -1506,22 +1536,11 @@ function makeImageLayer(layerObj) { const cogColormap = F_.getIn(L_.layers.data[layerObj.name], 'cogColormap') + let b = layerObj.cogBands; + parseGeoraster(layerUrl) .then((georaster) => { let pixelValuesToColorFn = null - if ( - F_.getIn( - L_.layers.data[layerObj.name], - 'variables.hideNoDataValue' - ) === true - ) { - pixelValuesToColorFn = (values) => { - // https://github.com/GeoTIFF/georaster-layer-for-leaflet/issues/16 - return values[0] === georaster.noDataValue - ? null - : `rgb(${values[0]},${values[1]},${values[2]})` - } - } const imageInfo = F_.getIn( L_.layers.data[layerObj.name], @@ -1638,12 +1657,51 @@ function makeImageLayer(layerObj) { } } + // Handle the case where we do not want to hide noDataValue + if ( + georaster.noDataValue != null && + georaster.noDataValue === pixelValue + ) { + return [0, 0, 0] + } + return evaluate_cmap( scaledPixelValue, colormap || IMAGE_DEFAULT_COLOR_RAMP, reverse ) } + } else { + if (b != null && Math.max(...b) > georaster.numberOfRasters) { + console.warn(`WARNING - User input band values must be within range of available bands in the image.` + + ` Ignoring user input bands.` + + `\nUser input bands: ${b}` + + `\nAvailable bands in the image: ${georaster.numberOfRasters}`) + } + + pixelValuesToColorFn = (values) => { + const updatedValues = [...values]; + // If user overrides the band order in the configure page + if (b != null) { + // User input band values must be within the range of available bands in the COG + if (Math.max(...b) <= values.length) { + updatedValues[0] = values[b[0] - 1] + updatedValues[1] = values[b[1] - 1] + updatedValues[2] = values[b[2] - 1] + } + } + + const haveDataForAllBands = updatedValues.every(value => value !== undefined && value !== georaster.noDataValue) + + // If the user does not want to hide the no data values + if (!hideNoDataValue && !haveDataForAllBands) { + return getPixelValue(georaster, updatedValues) + } + + if (haveDataForAllBands) { + return getPixelValue(georaster, updatedValues) + } + } } L_.layers.layer[layerObj.name] = new GeoRasterLayer({ @@ -1667,7 +1725,7 @@ function makeImageLayer(layerObj) { allLayersLoaded() }) .catch((e) => { - console.warn(`WARNING - Unable to load image: ${layerUrl}`) + console.warn(`WARNING - Unable to load image: ${layerUrl}\nError: ${e}`) L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true L_.layers.layer[layerObj.name] = null diff --git a/src/essence/Tools/Identifier/IdentifierTool.js b/src/essence/Tools/Identifier/IdentifierTool.js index db7ec0e0f..18465930e 100644 --- a/src/essence/Tools/Identifier/IdentifierTool.js +++ b/src/essence/Tools/Identifier/IdentifierTool.js @@ -349,12 +349,40 @@ var IdentifierTool = { IdentifierTool.vars.data[ IdentifierTool.activeLayerNames[i] ] || {} + + let bands = 1 + + if (L_.layers.data[IdentifierTool.activeLayerNames[i]].type === 'image') { + let georasters = L_.layers.layer[IdentifierTool.activeLayerNames[i]]?.georasters[0] + let b = + L_.layers.data[IdentifierTool.activeLayerNames[i]].cogBandsQuery || + L_.layers.data[IdentifierTool.activeLayerNames[i]].cogBands + if (b != null && Math.max(...b) > georasters.numberOfRasters) { + console.warn(`WARNING - User input band values must be within range of available bands in the image.` + + ` Ignoring user input bands.` + + `\nUser input bands: ${b}` + + `\nAvailable bands in the image: ${georasters.numberOfRasters}`) + // Default to maximum of 3 bands + bands = Math.min(georasters.numberOfRasters, 3) + } else if (b != null) { + // If the cog band is overwritten in the settings + bands = [...b] + } else if (georasters && georasters.numberOfRasters > 0) { + let georasters = L_.layers.layer[IdentifierTool.activeLayerNames[i]]?.georasters[0] + if (georasters.numberOfRasters <= 3) { + bands = georasters.numberOfRasters + } else { + // Default to 3 bands if there are more than 3 bands + bands = 3 // georasters.numberOfRasters + } + } + } IdentifierTool.vars.data[ IdentifierTool.activeLayerNames[i] ].data = [ { url: IdentifierTool.activeLayerURLs[i], - bands: 1, + bands: bands, units: L_.layers.data[IdentifierTool.activeLayerNames[i]] .cogUnits || '', @@ -804,6 +832,17 @@ function queryDataValue(url, lng, lat, numBands, layerUUID, callback) { dataPath = 'Missions/' + L_.mission + '/' + url } + let bands = '[[1,' + numBands + ']]' + if (L_.layers.data[layerUUID].type == 'image') { + if (Array.isArray(numBands)) { + if (numBands.length > 1) { + bands = [...numBands] + } else { + bands = '[' + numBands + ']' + } + } + } + dataPath = IdentifierTool.fillURLParameters(dataPath, layerUUID) calls.api( @@ -813,7 +852,7 @@ function queryDataValue(url, lng, lat, numBands, layerUUID, callback) { x: lat, y: lng, xyorll: 'll', - bands: '[[1,' + numBands + ']]', + bands: bands, path: dataPath, }, (data) => { From 10f24e379ade2750f6144162a38e34d3805e8a4a Mon Sep 17 00:00:00 2001 From: achung Date: Mon, 16 Jun 2025 11:35:45 -0700 Subject: [PATCH 3/3] Account for cases where number of user input for Tile Bands is less than available bands --- src/essence/Basics/Map_/Map_.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index 6fa602cff..540576ca6 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -1685,9 +1685,9 @@ function makeImageLayer(layerObj) { if (b != null) { // User input band values must be within the range of available bands in the COG if (Math.max(...b) <= values.length) { - updatedValues[0] = values[b[0] - 1] - updatedValues[1] = values[b[1] - 1] - updatedValues[2] = values[b[2] - 1] + updatedValues[0] = values[b[0] - 1] || 0 + updatedValues[1] = values[b[1] - 1] || 0 + updatedValues[2] = values[b[2] - 1] || 0 } }