This sample demonstrates how to create a custom tile layer that displays elevation data requested from the ArcGIS world elevation service. Elevation changes are displayed in different shades of yellow. The brighter and stronger the color, the higher the elevation. Any elevation values higher than 6000 meters are colored with a complete yellow color.
Creating a custom tile layer
To create a custom TileLayer you must call createSubclass() on BaseTileLayer. We’ll name the custom layer LercLayer with properties for setting the minimum elevation, maximum elevation, and the url template for requesting each elevation tile.
const LercLayer = BaseTileLayer.createSubclass({ properties: { urlTemplate: null, minElevation: 0, maxElevation: 4000, },
//override required methods});The fetchTile() method of LercLayer must be overridden so we can process raw elevation values and assign different shades of yellow to each pixel based on the elevation value. Once the url for a tile is returned, we can request the elevation data for that tile using esriRequest.
Since the world elevation service uses Esri LERC format, we must decode the data using LercDecode(). This returns a simple array of elevations. Once we have elevation values for each pixel, we can draw elevation on the canvas by assigning RGBA values to the imageData.
// fetch tiles visible in the viewasync fetchTile(level, row, col, options) { const url = this.getTileUrl(level, row, col);
// requested encoded elevation information // the signal option ensures that obsolete requests are aborted const response = await esriRequest(url, { responseType: "array-buffer", signal: options?.signal, });
// create a canvas to draw the processed image const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const width = this.tileInfo.size[0]; const height = this.tileInfo.size[1];
canvas.width = width; canvas.height = height;
// uncompress raw elevation values in lerc format into a pre-allocated array of elevation values. const lerc = LercDecode(response.data, { noDataValue: 0 }); // Array of elevation values const pixels = lerc.pixels[0];
// stats for min, max and no data values for uncompressed elevation const stats = lerc.statistics[0];
// set the min and max elevation values set by the layer const min = this.minElevation; const max = this.maxElevation; const noDataValue = stats.noDataValue;
// Create a new blank image data object with the specified dimensions. // The imageData represents the underlying pixel data of the canvas. const imageData = context.createImageData(width, height);
// get one-dimensional array containing the data in the RGBA order, // with integer values between 0 and 255 (included). const data = imageData.data;
const factor = 256 / (max - min); let value = 0; let j;
// Loop through the elevation values and assign RGBA values. for (let i = 0; i < width * height; i++) { // map tile size is 256x256. Elevation values have a // tile size of 257 so we skip the last elevation // whenever "i" is incremented by 256 to jump to the next row. j = i + Math.floor(i / width); // Scale the elevation value to determine the yellow shade. value = (pixels[j] - min) * factor;
// create RGBA value for the pixel data[i * 4] = value; data[i * 4 + 1] = value; data[i * 4 + 2] = 0; data[i * 4 + 3] = pixels[i] === noDataValue ? 0 : value; }
// The elevation change image and ready for display context.putImageData(imageData, 0, 0);
return canvas;}The custom layer is added to a map and assigned to the map component.
const lercLayer = new LercLayer({ urlTemplate: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer/tile/{z}/{y}/{x}", maxElevation: 6000,});
viewElement.map = new Map({ basemap: "dark-gray-vector", layers: [lercLayer],});