I am attempting to display a 4-band Sentinel image where the RGB bands are the first 3 bands, each represented as uint16.

Reason: Currently viewers like COGEO tinyurl.com/3jy9jjcy do stretch each COG tile COGEO : each tile is stretched and therefore,

Expected result: we need on stretching over the entire zoomlevel like in QGIS QGIS : zoomlevel is stretched

To achieve this, I am using Leaflet with GeoRaster, based on an existing example. My goal is to: A) Enhance each RGB band using the formula band[i] = Math.cbrt(0.6 * band[i] - 0.035). B) Convert the result to RGB 8-bit for display.

I am encountering difficulties in parsing the channels, enhancing them, and converting them for display. In my JavaScript code my.js, I am struggling to access each band individually with new GeoRasterLayer: The resulting image is all black Leaflet as in  my.js

var layer = new GeoRasterLayer({
    attribution: "Unknown",
    georaster: georaster,
    resolution: 128,
    pixelValuesToColorFn: function(values) {
        // Stretch values using cube root function for the first three bands
        var stretchedValues = [
            Math.cbrt(0.6 * values[0] - 0.035), // Stretch first band
            Math.cbrt(0.6 * values[1] - 0.035), // Stretch second band
            Math.cbrt(0.6 * values[2] - 0.035)  // Stretch third band
        // Scale stretched values to 8-bit RGB
        var scaledValues = stretchedValues.map(value => Math.round(value * 255));
        return scaledValues;

There is no error thrown (exxept the one forovr which can be ignored)no error for the script above but black result

How can I properly access each band in the GeoTIFF file, apply the enhancement formula on zoomlevel, and show the result to RGB 8-bit for display using Leaflet and GeoRaster?

  • You write "I am encountering difficulties in parsing" and "I am struggling to access each band individually", but don't describe those difficulties in struggles. Please edit your question and add more details about what does not work and hot it does not work.
    – TomazicM
    Commented Feb 17 at 9:49
  • But generally I would say you can expect difficulties trying to process 1.6GB image on the client side in JS.
    – TomazicM
    Commented Feb 17 at 9:51
  • I have no clue where in the code above I have to start with the data manipulation. I s the approach via georaster.values.forEach((band, index) => { etc the right way? Or shall I do this via pixelValuesToColorFn approach? ''' var layer = new GeoRasterLayer({ attribution: "Unknown", georaster, pixelValuesToColorFn: values => values[1] === 42 ? '#ffffff' : '#000000', resolution: 128 }); layer.addTo(map);''' Accesing a 1-2GB COG Tiff works, see tinyurl.com/3jy9jjcy Commented Feb 17 at 11:09
  • I think it needs to be done using 'pixelValuesToColorFn' ..but no success, updated the example above Commented Feb 17 at 11:42

1 Answer 1


I have absolutely no knowledge about COG and GeoTIFF, so this answer is only how to make the code from question show at least something.

First problem: If you look at the content of georaster object, mins and maxs properties tell about min and max values for each channel. Since max values for all three channels are a little below 15000, calculated color values in scaledValues are way above 255.

Second problem: pixelValuesToColorFn option function should return CSS color definition as string, like for example: rgba(100, 50, 250, 1).

Third problem: Sentinel image is in EPSG:2056 projection, so to display it correctly in Leaflet map, this projection has to be used.

Fourth problem: Channel value 9999 stands for no value, so pixel should be transparent.

In the below code channel values are normalized to 255 value and some Swiss map with EPSG:2056 projection is used as a background:

var crs2056 = new L.Proj.CRS('EPSG:2056',
  "+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs",
    resolutions: [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5, 0.25, 0.125, 0.0625],
    origin: [2485071.58, 1299941.79],

var map = L.map('map', {
  crs: crs2056
}).setView([46, 8], 1);

var map4 = new L.tileLayer.wms("https://wms0.geo.admin.ch/", {
    layers: 'ch.vbs.milairspacechart',
    format: 'image/png',
    transparent: true,
    crs: crs2056

var url_to_geotiff_file = "/data/S2_L2A_SR_20240106T102329_20240106T102326_10M_run20240107.tif.png";

  .then(response => response.arrayBuffer())
  .then(arrayBuffer => {
    parseGeoraster(arrayBuffer).then(georaster => {      
      var layer = new GeoRasterLayer({
        georaster: georaster,
        resolution: 128,
        pixelValuesToColorFn: function(values) {
          var color, sv;
          if ((values[0] == 9999) && (values[1] == 9999) && (values[2] == 9999))
            color = 'rgba(0,0,0,0)';
          else {
            sv = [
              Math.round((values[0] / georaster.maxs[0]) * 255),
              Math.round((values[1] / georaster.maxs[1]) * 255),
              Math.round((values[2] / georaster.maxs[2]) * 255)
            color = 'rgba(' + sv[0] + ',' + sv[1] + ',' + sv[2] + ', 1)';
          return color;

This is how the result looks like:

enter image description here

Now to get desired/correct image just correct formulas have to be used for converting channel values to RGB.

P.S.: Since image has 1.6 GB, it took ages to show this in Firefox on Windows 10. Chrome and Edge both failed.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.