The data layers response comes in a GeoTIFF file . You can use your own tooling to get the data you are interested in. For example, imagine you have a GeoTIFF image showing temperature values across a region. Using TypeScript, you can map low temperatures to blue colors and high temperatures to red to create a colorful image that's instantly understandable for visualizing temperature patterns.
This TypeScript code is designed to take special image files called GeoTIFFs and display them on a website using an HTML canvas (like a digital picture frame). The code uses the following components:
- GeoTIFF images:GeoTIFFs can store multiple layers of image data, making them useful for maps or scientific analysis.
- RGB Images:These are the types of images we're most familiar with (like photos). Every pixel has red, green, and blue values that determine the color.
- Palettes:These are like paint sets. They contain a list of predefined colors that can be used to color images.
This page shows how to get the pixel data values (the information stored in individual pixels of a digital image, including color values and other attributes) and calculates the latitude and longitude from the GeoTIFF and stores it in a TypeScript object.
The following code snippet shows the type definition where we store the data of interest in this example. Fields and type of data is a " type " in TypeScript. For this specific example, we chose to allow type checking, reducing type errors and adding reliability to your code, making it easier to maintain. Define a type to store that data in order to return multiple values like the pixel values and the lat/long bounding box.
export interface GeoTiff { width : number ; height : number ; rasters : Array<number> []; bounds : Bounds ; }
Core functions
The code has several functions that work together:
-
renderRGB
: Takes an RGB GeoTIFF image and optionally a mask (for transparency), creates a website canvas element, loops through each pixel of the GeoTIFF, and colors the corresponding pixel on the canvas. -
renderPalette
: Takes a GeoTIFF with a single layer of data and a color palette, maps the GeoTIFF data values to the colors in the palette, creates a new RGB image using the palette colors, and callsrenderRGB
to display the image on the canvas.
/** * Renders an RGB GeoTiff image into an HTML canvas. * * The GeoTiff image must include 3 rasters (bands) which * correspond to [Red, Green, Blue] in that order. * * @param {GeoTiff} rgb GeoTiff with RGB values of the image. * @param {GeoTiff} mask Optional mask for transparency, defaults to opaque. * @return {HTMLCanvasElement} Canvas element with the rendered image. */ export function renderRGB ( rgb : GeoTiff , mask? : GeoTiff ) : HTMLCanvasElement { // Create an HTML canvas to draw the image. // https://www.w3schools.com/tags/canvas_createimagedata.asp const canvas = document . createElement ( 'canvas' ); // Set the canvas size to the mask size if it's available, // otherwise set it to the RGB data layer size. canvas . width = mask ? mask.width : rgb.width ; canvas . height = mask ? mask.height : rgb.height ; // Since the mask size can be different than the RGB data layer size, // we calculate the "delta" between the RGB layer size and the canvas/mask // size. For example, if the RGB layer size is the same as the canvas size, // the delta is 1. If the RGB layer size is smaller than the canvas size, // the delta would be greater than 1. // This is used to translate the index from the canvas to the RGB layer. const dw = rgb . width / canvas . width ; const dh = rgb . height / canvas . height ; // Get the canvas image data buffer. const ctx = canvas . getContext ( '2d' ) ! ; const img = ctx . getImageData ( 0 , 0 , canvas . width , canvas . height ); // Fill in every pixel in the canvas with the corresponding RGB layer value. // Since Javascript doesn't support multidimensional arrays or tensors, // everything is stored in flat arrays and we have to keep track of the // indices for each row and column ourselves. for ( let y = 0 ; y < canvas . height ; y ++ ) { for ( let x = 0 ; x < canvas . width ; x ++ ) { // RGB index keeps track of the RGB layer position. // This is multiplied by the deltas since it might be a different // size than the image size. const rgbIdx = Math . floor ( y * dh ) * rgb . width + Math . floor ( x * dw ); // Mask index keeps track of the mask layer position. const maskIdx = y * canvas . width + x ; // Image index keeps track of the canvas image position. // HTML canvas expects a flat array with consecutive RGBA values. // Each value in the image buffer must be between 0 and 255. // The Alpha value is the transparency of that pixel, // if a mask was not provided, we default to 255 which is opaque. const imgIdx = y * canvas . width * 4 + x * 4 ; img . data [ imgIdx + 0 ] = rgb . rasters [ 0 ][ rgbIdx ]; // Red img . data [ imgIdx + 1 ] = rgb . rasters [ 1 ][ rgbIdx ]; // Green img . data [ imgIdx + 2 ] = rgb . rasters [ 2 ][ rgbIdx ]; // Blue img . data [ imgIdx + 3 ] = mask // Alpha ? mask . rasters [ 0 ][ maskIdx ] * 255 : 255 ; } } // Draw the image data buffer into the canvas context. ctx . putImageData ( img , 0 , 0 ); return canvas ; }
Helper Functions
The code also includes several helper functions that enable additional functionality:
-
createPalette
: Creates a list of colors to be used for coloring images based on a list of hexadecimal color codes. -
colorToRGB
: Converts a color code like "#FF00FF" into its red, green, and blue components. -
normalize
,lerp
,clamp
: Mathematical helper functions for image processing.
/** * Renders a single value GeoTiff image into an HTML canvas. * * The GeoTiff image must include 1 raster (band) which contains * the values we want to display. * * @param {GeoTiff} data GeoTiff with the values of interest. * @param {GeoTiff} mask Optional mask for transparency, defaults to opaque. * @param {string[]} colors Hex color palette, defaults to ['000000', 'ffffff']. * @param {number} min Minimum value of the data range, defaults to 0. * @param {number} max Maximum value of the data range, defaults to 1. * @param {number} index Raster index for the data, defaults to 0. * @return {HTMLCanvasElement} Canvas element with the rendered image. */ export function renderPalette ({ data , mask , colors , min , max , index , } : { data : GeoTiff ; mask? : GeoTiff ; colors? : string []; min? : number ; max? : number ; index? : number ; }) : HTMLCanvasElement { // First create a palette from a list of hex colors. const palette = createPalette ( colors ?? [ '000000' , 'ffffff' ]); // Normalize each value of our raster/band of interest into indices, // such that they always map into a value within the palette. const indices = data . rasters [ index ?? 0 ] . map (( x ) = > normalize ( x , max ?? 1 , min ?? 0 )) . map (( x ) = > Math . round ( x * ( palette . length - 1 ))); return renderRGB ( { ... data , // Map each index into the corresponding RGB values. rasters : [ indices . map (( i : number ) = > palette [ i ]. r ), indices . map (( i : number ) = > palette [ i ]. g ), indices . map (( i : number ) = > palette [ i ]. b ), ], }, mask , ); } /** * Creates an {r, g, b} color palette from a hex list of colors. * * Each {r, g, b} value is a number between 0 and 255. * The created palette is always of size 256, regardless of the number of * hex colors passed in. Inbetween values are interpolated. * * @param {string[]} hexColors List of hex colors for the palette. * @return {{r, g, b}[]} RGB values for the color palette. */ export function createPalette(hexColors: string[]): { r: number; g: number; b: number }[] { // Map each hex color into an RGB value. const rgb = hexColors.map(colorToRGB); // Create a palette with 256 colors derived from our rgb colors. const size = 256; const step = (rgb.length - 1) / (size - 1); return Array(size) .fill(0) .map((_, i) => { // Get the lower and upper indices for each color. const index = i * step; const lower = Math.floor(index); const upper = Math.ceil(index); // Interpolate between the colors to get the shades. return { r: lerp(rgb[lower].r, rgb[upper].r, index - lower), g: lerp(rgb[lower].g, rgb[upper].g, index - lower), b: lerp(rgb[lower].b, rgb[upper].b, index - lower), }; }); } /** * Convert a hex color into an {r, g, b} color. * * @param {string} color Hex color like 0099FF or #0099FF. * @return {{r, g, b}} RGB values for that color. */ export function colorToRGB ( color : string ) : { r : number ; g : number ; b : number } { const hex = color . startsWith ( '#' ) ? color . slice ( 1 ) : color ; return { r : parseInt ( hex . substring ( 0 , 2 ), 16 ), g : parseInt ( hex . substring ( 2 , 4 ), 16 ), b : parseInt ( hex . substring ( 4 , 6 ), 16 ), }; } /** * Normalizes a number to a given data range. * * @param {number} x Value of interest. * @param {number} max Maximum value in data range, defaults to 1. * @param {number} min Minimum value in data range, defaults to 0. * @return {number} Normalized value. */ export function normalize ( x : number , max : number = 1 , min : number = 0 ) : number { const y = ( x - min ) / ( max - min ); return clamp ( y , 0 , 1 ); } /** * Calculates the linear interpolation for a value within a range. * * @param {number} x Lower value in the range, when `t` is 0. * @param {number} y Upper value in the range, when `t` is 1. * @param {number} t "Time" between 0 and 1. * @return {number} Inbetween value for that "time". */ export function lerp ( x : number , y : number , t : number ) : number { return x + t * ( y - x ); } /** * Clamps a value to always be within a range. * * @param {number} x Value to clamp. * @param {number} min Minimum value in the range. * @param {number} max Maximum value in the range. * @return {number} Clamped value. */ export function clamp ( x : number , min : number , max : number ) : number { return Math . min ( Math . max ( x , min ), max ); }