GeoTIFF / georaster-layer-for-leaflet

Display GeoTIFFs and soon other types of raster on your Leaflet Map
https://geotiff.github.io/georaster-layer-for-leaflet-example/
Apache License 2.0
296 stars 58 forks source link

endering Performance CloudOptimizedGeotiff Band calculation ( Question rather than a feature request) #142

Open davidoesch opened 6 months ago

davidoesch commented 6 months ago

Displaying this 4 band 16int COGTIFF file size approx 4GB in leaflet via

https://cms.geo.admin.ch/Topo/umweltbeobachtung/satromocogviewer.html?url=https://data.geo.admin.ch/ch.swisstopo.swisseo_s2-sr_v100/2024-03-07t103911/ch.swisstopo.swisseo_s2-sr_v100_mosaic_2024-03-07t103911_bands-10m.tif

It is a valid COG according to gdal with 4 overviews, compressed etc

-> results in very slow performance on loading.

Firefox: After approx 10 succesfull accesses to the GOG tif file I get an "unknown duration" Staus "waiting for response" as shown here enter image description here

Edge: I get Violation warnings in the console but tiles are loading after while

[Violation]'requestAnimationFrame' handler took 75ms
[Violation]'setTimeout' handler took 75ms

The data is loading quite fast, an I can observe the complete download finished enter image description here

but afterwards it takes quite a while until it is rendered , I can't find any suspicous in enter image description here

even if I use an RGB 8 bit File : RGB_COG_mosaic_2024-03-07t103911_bands-10m.tif it takes quite a while to render the image

https://cms.geo.admin.ch/Topo/umweltbeobachtung/satromocogviewer.html?url=https://cms.geo.admin.ch/test/topo/umweltbeobachtung/testcog/RGB_COG_mosaic_2024-03-07t103911_bands-10m.tif

I can't get the rendering/download accelerated : any ideas?

Here the complete code

<html>
<head>
    <title>COG viewer SATROMO</title>
    !-- Version 3.1 --> <!-- full stretch -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css"/>
    <style>
        #map {
            bottom: 0;
            left: 0;
            position: absolute;
            right: 0;
            top: 0;
        }
    </style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.6.0/proj4.js"></script>
<script src="https://unpkg.com/georaster"></script>
<script src="https://unpkg.com/georaster-layer-for-leaflet"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4leaflet/1.0.2/proj4leaflet.min.js"></script>
<! -- You might need this one as well
<script src="https://unpkg.com/leaflet-proj4leaflet"></script> 
--> 

<script>
    // Define custom CRS for EPSG 2056
   // Define custom CRS for EPSG 2056
    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: [4000.0, 3750.0, 3500.0, 3250.0, 3000.0, 2750.0, 2500.0, 2250.0, 2000.0, 1750.0, 1500.0, 1250.0, 1000.0, 750.0, 650.0, 500.0, 250.0, 100.0, 50.0, 20.0, 10.0, 5.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.25, 0.1, 0.05],
         origin: [2420000.0, 1350000.0]
     });

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

    var baseLayer = L.tileLayer('https://wmts.asit-asso.ch/wmts/1.0.0/asitvd.fond_pourortho/default/default/0/2056/{z}/{y}/{x}.png', {
        maxZoom: crs2056.options.resolutions.length,
        zIndex: 5
    }).addTo(map);

// Define a variable to keep track of the layer visibility
var layerVisible = true;

// Create a button to toggle the layer
var toggleButton = L.control({position: 'topleft'});
toggleButton.onAdd = function (map) {
    var div = L.DomUtil.create('div', 'toggle-button');
    // Set the initial button text based on the layer visibility
    div.innerHTML = '<button onclick="toggleLayer()">' + (layerVisible ? 'Overlay OFF' : 'OFF') + '</button>';
    return div;
};
toggleButton.addTo(map);

// Function to toggle the layer
function toggleLayer() {
    if (layerVisible) {
        map.removeLayer(baseLayer);
    } else {
        baseLayer.addTo(map);
    }
    // Update the button text based on the layer visibility
    document.querySelector('.toggle-button button').innerText = layerVisible ? 'Overlay ON' : 'Overlay OFF';
    // Invert the layer visibility state
    layerVisible = !layerVisible;
}

    const url_to_geotiff_file = new URLSearchParams(location.search).get("url");
    console.log("url_to_geotiff_file:", url_to_geotiff_file);

    if (!url_to_geotiff_file || !url_to_geotiff_file.endsWith(".tif")) {
        // If no URL is provided, or if it doesn't end with ".tif", display a message for non-supported imagery
        document.getElementById("map").innerHTML = "<h3>Non-supported imagery</h3>";
    } else if (url_to_geotiff_file.includes("ch.swisstopo.swisseo_vhi")) {
        console.log("This is VHI");

        // Parse the GeoTIFF data
        parseGeoraster(url_to_geotiff_file).then(function (georaster) {
            console.log("georaster:", georaster);
            // based on https://custom-scripts.sentinel-hub.com/custom-scripts/sentinel-2/l2a_optimized/    

        // Execute code for ch.swisstopo.swisseo_vhi
        // For example:https://cms.geo.admin.ch/test/topo/umweltbeobachtung/testcog/ch.swisstopo.swisseo_vhi_v100_mosaic_2023-10-16T235959_bands-10m.tif
        // yourVHICodeHere();
        // Define color map
    // Define the function to map pixel values to colors
    const pixelValuesToColorFn = value => {
        // Define your logic here to map pixel values to colors
        // For example, you can use a color scale or custom logic based on specific thresholds
    const scale = chroma.scale([
            '#C7BB95',
            '#FEFEE1',
            '#6E9F62',
            '#032816',
            'black'
          ]).domain([
            0,
            10,
            40,
            60,
            80
          ]);
        // Here's a simple example using a color scale:
        const color = scale(value).hex(); // Assuming 'scale' is defined elsewhere
        console.log("color:", color);
        return color;
    };

        // Modify the pixelValuesToColorFn function
        const layer = new GeoRasterLayer({
            attribution: "Contains modified Copernicus Sentinel data 2024, (c) Openstreetmap contributors provided by ASIT",
            debugLevel: 0,
            georaster,
            resolution: 256,
            opacity: 1,
            resampleMethod: "nearest",
            pixelValuesToColorFn 
        });

            layer.addTo(map);
            map.fitBounds(layer.getBounds());
         });

    } else if (url_to_geotiff_file.includes("ch.swisstopo.swisseo_s2-sr")) {
        console.log("This is S2 SR");

        // Parse the GeoTIFF data
        parseGeoraster(url_to_geotiff_file).then(function (georaster) {
            console.log("georaster:", georaster);
            // based on https://custom-scripts.sentinel-hub.com/custom-scripts/sentinel-2/l2a_optimized/    

        // Contrast enhance / highlight compress
        const maxR = 3.0; // max reflectance
        const midR = 0.13;
        const sat = 1.2;
        const gamma = 1.8;

        function evaluatePixel(val0,val1,val2) {
            //console.log("val0:", val0, "val1:", val1, "val2:", val2);
            const rgbLin = satEnh(sAdj(val0), sAdj(val1), sAdj(val2));
            //console.log("rgbLin:", rgbLin);
            //console.log("sRGB0:", sRGB(rgbLin[0]), "sRGB1:", sRGB(rgbLin[1]), "sRGB2:", sRGB(rgbLin[2]));
            return [sRGB(rgbLin[0]), sRGB(rgbLin[1]), sRGB(rgbLin[2])];
        }

        function sAdj(a) {
            return adjGamma(adj(a, midR, 1, maxR));
        }

        const gOff = 0.01;
        const gOffPow = Math.pow(gOff, gamma);
        const gOffRange = Math.pow(1 + gOff, gamma) - gOffPow;

        function adjGamma(b) {
            return (Math.pow((b + gOff), gamma) - gOffPow) / gOffRange;
        }

        // Saturation enhancement
        function satEnh(r, g, b) {
            const avgS = (r + g + b) / 3.0 * (1 - sat);
            return [clip(avgS + r * sat), clip(avgS + g * sat), clip(avgS + b * sat)];
        }

        function clip(s) {
            return s < 0 ? 0 : s > 1 ? 1 : s;
        }

        //contrast enhancement with highlight compression
        function adj(a, tx, ty, maxC) {
            var ar = clip(a / maxC, 0, 1);
            return ar * (ar * (tx / maxC + ty - 1) - ty) / (ar * (2 * tx / maxC - 1) - tx / maxC);
        }

        const sRGB = (c) => c <= 0.0031308 ? (12.92 * c) : (1.055 * Math.pow(c, 0.41666666666) - 0.055);

            const layer = new GeoRasterLayer({
                attribution: "Contains modified Copernicus Sentinel data 2024, (c) Openstreetmap contributors provided by ASIT",
                debugLevel: 0,
                georaster,
                resolution: 256,
                opacity:1,
                resampleMethod: "nearest",
                pixelValuesToColorFn: values => {
                    // Check if any of the bands have a value of 9999 or 0
                    if (values.some(value => value === 9999 )) {
                    //if (values.some(value => value === 9999 || value === 0)) {
                        // If any band has a value of 9999 or 0, return transparent color
                        return 'rgba(0, 0, 0, 0)'; // Transparent color
                    } else {
                        // Apply min-max normalization to each band
                        const r = Math.round(evaluatePixel(values[0]/10000, values[1]/10000, values[2]/10000)[0] * 255);
                        const g = Math.round(evaluatePixel(values[0]/10000, values[1]/10000, values[2]/10000)[1] * 255);
                        const b = Math.round(evaluatePixel(values[0]/10000, values[1]/10000, values[2]/10000)[2] * 255);

                        // Return RGB color
                        return `rgba(${r},${g},${b},1)`;
                    }
                }
            });

            layer.addTo(map);
            map.fitBounds(layer.getBounds());
        });
    } else {
    console.log("This is RGB data");
     // Parse the GeoTIFF data
        parseGeoraster(url_to_geotiff_file).then(function (georaster) {
            console.log("georaster:", georaster);
            const layer = new GeoRasterLayer({
                attribution: "Contains user data, (c) Openstreetmap contributors provided by ASIT",
                debugLevel: 0,
                georaster,
                resolution: 256,
                opacity: 1,

            });
            layer.addTo(map);
            map.fitBounds(layer.getBounds());
        });
    };

    // Prompt user for GeoTIFF URL if none is provided
    if (!url_to_geotiff_file) {
        const defaultGeoTIFFURL = "https://data.geo.admin.ch/ch.swisstopo.swisseo_s2-sr_v100/2024-02-23t103031/ch.swisstopo.swisseo_s2-sr_v100_mosaic_2024-02-23t103031_bands-10m.tif";
        const userGeoTIFFURL = prompt("Please enter the URL for the RGB GeoTIFF file (EPSG:2056):", defaultGeoTIFFURL);
        if (userGeoTIFFURL) {
            const parser = new URL(window.location);
            parser.searchParams.set("url", userGeoTIFFURL);
            window.location = parser.href;
        } else {
            document.getElementById("map").innerHTML = "<h3>No GeoTIFF URL provided</h3>";
        }
}

</script>
</body>
jfoclpf commented 5 months ago

How do you expect the front-end will render 4GB of raster data? The data needs to be downloaded and processed by the browser.

You should divide that in small GeoTIFF tiles or reduce resolution

davidoesch commented 5 months ago

Nope the data needs not to be downloaded as a whole : only partial ( 204) since it is cogtif and cloudnative