How to display a Geo tiff file in Azure Maps

Uppalapati Vinay Kumar 40 Reputation points
2023-07-06T14:38:32.6166667+00:00

Display the Geo tiff file stored in blob storage on Azure Maps.

Azure Maps
Azure Maps
An Azure service that provides geospatial APIs to add maps, spatial analytics, and mobility solutions to apps.
834 questions
0 comments No comments
{count} votes

Accepted answer
  1. rbrundritt 20,921 Reputation points Microsoft Employee Moderator
    2023-07-10T21:48:16.38+00:00

    I've put together a simple example that loads a GeoTiff on the map directly in the browser. It doesn't do any optimizations, so it can be slow when the image is more than a few megabytes in size. Once this 26MB GeoTiff is loaded into the browser, it can take 30 seconds to a minute to convert it into an image and display it on the map. Note that for GeoTiff files larger than a few megabytes you should convert them into tiles on the server side rather than loading them directly into a browser. The download time alone would be slow (took me nearly 2 minutes to download with a fast internet connection).

    To make this work I used the following library's/tools:

    Here is the source code for a functional app that loads a GeoTiff image from a URL (if on a COR's enabled endpoint), or from a local file.

    <!DOCTYPE html>
    <html>
    <head>
        <title>Azure Maps GeoTiff Viewer</title>
    
        <meta charset="utf-8" />
        <meta http-equiv="x-ua-compatible" content="IE=Edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    
        <!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
        <link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" type="text/css" />
        <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>
    
        <!-- Load GeoTiff.js library -->
        <script src="https://cdn.jsdelivr.net/npm/geotiff"></script>
    
        <!-- Load proj4js library so we can convert bounding boxes to the required projection. -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.9.0/proj4.min.js"></script>
    
        <!-- Load geotiff-geokeys-to-proj4 library to convert GeoTiff geokey information to proj4js projection information. -->
        <script src="https://cdn.jsdelivr.net/npm/geotiff-geokeys-to-proj4@2022.9.7/main-dist.min.js"></script>
    
        <script>
            var map, imageLayer, datasource, loadingScreen;
            var geoTiffUrl = 'https://ltimgpstorageacc.blob.core.windows.net/ltimgpblob/no-overviews.tif';
    
            function GetMap() {
                loadingScreen = document.getElementById('loadingScreen');
    
                //Initialize a map instance.
                map = new atlas.Map('myMap', {
                    view: 'Auto',
    
                    //Add authentication details for connecting to Azure Maps.
                    authOptions: {
                        authType: 'subscriptionKey',
                        subscriptionKey: '<Your Azure Maps Key>'
                    }
                });
    
                //Add some data to the map so that we can see how the rendering of data also changes.
                map.events.add('ready', function () {
    
                    //Optionally load the style picker control.
                    map.controls.add(new atlas.control.StyleControl({
                        mapStyles: 'all'
                    }), {
                        position: 'top-right'
                    });
    
                    //Optionally, create a layer for displaying the bounding box of the GeoTiff.
                    datasource = new atlas.source.DataSource();
                    map.sources.add(datasource);
    
                    map.layers.add(new atlas.layer.LineLayer(datasource, null, {
                        strokeColor: 'red',
                        strokeWidth: 5
                    }));
                });
            }
    
            async function loadRemoteGeoTiff() {
                loadingScreen.style.display = '';
                const response = await fetch(document.getElementById('urlInput').value);
                const arrayBuffer = await response.arrayBuffer();
                const tiff = await GeoTIFF.fromArrayBuffer(arrayBuffer);
                await loadGeoTiff(tiff);
            }
    
            async function loadLocalGeoTiff() {
                loadingScreen.style.display = '';
                const input = document.getElementById('file');
                const tiff = await GeoTIFF.fromBlob(input.files[0]);
                await loadGeoTiff(tiff);
            }
    
            async function loadGeoTiff(tiff) {
                //Remove any previously loaded image layer.
                if (imageLayer) {
                    map.layers.remove(imageLayer);
                    datasource.clear();
                }
    
                //Get the image data for the tiff.
                const image = await tiff.getImage();
    
                const width = image.getWidth();
                const height = image.getHeight();
    
                //Get the bounding box of the image.
                const bounds = image.getBoundingBox();
    
                if (bounds == null) {
                    alert('No bounding box found in GeoTiff file.');
                    return;
                }
    
                //Convert bounding box projection.
                const geoKeys = image.getGeoKeys();
                const projObj = geokeysToProj4.toProj4(geoKeys);
                const projection = proj4('WGS84', projObj.proj4);
    
                var minXY = projection.inverse([bounds[0], bounds[1]]);
                var maxXY = projection.inverse([bounds[2], bounds[3]]);
    
                const bbox = [minXY[0], minXY[1], maxXY[0], maxXY[1]];
    
                //Zoom the map into where the GeoTiff area.
                map.setCamera({
                    bounds: bbox,
                    padding: 20
                });
    
                //Optionally, draw a polygon around the bounding box area.
                datasource.setShapes(atlas.math.boundingBoxToPolygon(bbox));
    
                //Use a decoder pool for faster processing of GeoTiff image.
                const pool = new GeoTIFF.Pool();
    
                //Load the RGB data from the GeoTiff file.
                const rgb = await image.readRGB({
                    pool
                });
    
                //Convert GeoTiff to png by drawing the RGB data to a canvas.
                const canvas = document.createElement('canvas');
                canvas.width = width;
                canvas.height = height;
    
                const ctx = canvas.getContext('2d');
                const imageData = ctx.getImageData(0, 0, width, height);
                const data = imageData.data;
    
                let j = 0;
                for (let i = 0; i < rgb.length; i += 3) {
                    data[j] = rgb[i];
                    data[j + 1] = rgb[i + 1];
                    data[j + 2] = rgb[i + 2];
                    data[j + 3] = 255;
                    j += 4;
                }
    
                ctx.putImageData(imageData, 0, 0);
    
                const pngUrl = canvas.toDataURL();
    
                //Get the corner coordinates of the image.
                var corners = [
                    [minXY[0], maxXY[1]],   //top left - North west
                    maxXY,                  //top right - North east
                    [maxXY[0], minXY[1]],   //bottom right - South east
                    minXY                   //bottom left - South west
                ];
    
                //Create an image layer and add it to the map below the labels.
                imageLayer = new atlas.layer.ImageLayer({                
                    url: pngUrl,
                    coordinates: corners
                });
                map.layers.add(imageLayer, 'labels');
    
                setTimeout(() => {
                    loadingScreen.style.display = 'none';
                }, 5000)            
            }
        </script>
    
        <style>
            html, body, #myMap, #loadingScreen {
                margin: 0;
                padding: 0;
                width: 100%;
                height: 100%;
            }
    
            .sidePanel {
                position: absolute;
                top: 10px;
                left: 10px;
                background-color: white;
                border-radius: 5px;
                padding: 5px;
            }
    
            h1 {
                font-size: 16px;
            }
    
            #loadingScreen {
                position: absolute;
                top: 0;
                left: 0;
                background-color: rgba(0,0,0,0.5);
            }
    
            #loadingScreen span {
                position: absolute;
                top: 50%;
                left: calc(50% - 30px);
                font-size: 18px;
                color: white;
            }
        </style>
    </head>
    <body onload="GetMap()">
        <div id="myMap"></div>
    
        <div class="sidePanel">
            <h1>Azure Maps GeoTiff Viewer</h1>
            <table>
                <tr>
                    <td>From URL: </td>
                    <td><input id="urlInput" type="text" value=""/></td>
                    <td><input type="button" onclick="loadRemoteGeoTiff()" value="Load"/></td>
                </tr>
    
                <tr>
                    <td colspan="3" style="text-align:center">or</td>
                </tr>
    
                <tr>
                    <td>Local file: </td>
                    <td colspan="2" style="text-align:center">
                        <input type="file" id="file" onchange="loadLocalGeoTiff()">
                    </td>
                </tr>
            </table>
        </div>
    
        <div id="loadingScreen" style="display:none;">
            <span>Loading...</span>
        </div>
    </body>
    </html>
    

    Here is a screenshot of the provide image overlaid on top the map.

    enter image description here

    There is a lot of potential improvements that could be made to this example to improve performance. Here are a couple of ideas:

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. rbrundritt 20,921 Reputation points Microsoft Employee Moderator
    2023-07-06T16:33:58.4833333+00:00

    A GeoTiff image can be an image of different color bands which browsers don't support directly. Often each pixel can contain a metric per pixel instead, and require you to assign a color to for each metric before converting it into a visible image. GeoTiffs need to be converted to a standard image type (PNG, JPEG). If it is a smaller file, it is possible to do this within the browser by reading the data and drawing an image on a canvas. There are libraries available that can help with this: https://geotiffjs.github.io/

    If the file is larger, which is often the case with GeoTiff files, if should be hosted on the server side and converted into map tiles and overlaid on the map like that. TiTiler is a commonly used library for this purpose: https://developmentseed.org/titiler/ Taking this route would make it easy to view the GeoTiff in different map controls (Azure Maps Android/iOS/Web, or 3rd party controls like Cesium, Leaflet....).

    If you only have a couple of GeoTiff images, you could convert them to tiles on your computer using a tool like gdal2tiles or QGIS.
    https://www.youtube.com/watch?v=VlUg1lQJhms


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.