<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Terrain analysis with raster functions | Sample | ArcGIS Maps SDK for JavaScript</title>
<!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
<script type="module" src="https://js.arcgis.com/5.0/"></script>
background-color: var(--calcite-color-background);
justify-content: space-evenly;
fill: var(--calcite-color-text-1);
stroke: var(--calcite-color-border-input);
fill: var(--calcite-color-brand);
border: 1px solid var(--calcite-color-border-input);
background-color: rgb(0, 122, 194);
<body class="calcite-mode-dark">
<arcgis-scene item-id="dac0b9b188e146baa504c9cfb6eb8d00">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
<arcgis-compass slot="top-left"></arcgis-compass>
<arcgis-expand slot="top-left">
<arcgis-legend></arcgis-legend>
<arcgis-basemap-toggle slot="bottom-left"></arcgis-basemap-toggle>
<div id="terrainAnalysisDiv" slot="top-right">
<calcite-segmented-control id="analysisModeSegmentedControl">
<calcite-segmented-control-item icon-start="surface" value="Custom" checked
>Custom</calcite-segmented-control-item
<calcite-segmented-control-item icon-start="altitude" value="Elevation"
>Elevation</calcite-segmented-control-item
<calcite-segmented-control-item icon-start="take-pedestrian-ramp" value="Slope"
>Slope</calcite-segmented-control-item
</calcite-segmented-control>
<calcite-label scale="s">
ticks="10"></calcite-slider>
<div id="customAnalysisDiv">
<calcite-label scale="s">
max-label="Max Elevation"
min-label="Min Elevation"></calcite-slider>
<calcite-label scale="s">
min-label="Min Slope"></calcite-slider>
<calcite-label scale="s">
<div id="compassRoseDiv">
<svg id="compassRoseSvg" xmlns="http://www.w3.org/2000/svg" viewBox="-50 -50 100 100">
points="0,-40 5,-15 -5,-15"
class="roseDirection"></polygon>
<text x="0" y="-45" text-anchor="middle">N</text>
points="22,-22 15,-5 5,-15"
class="roseDirection"></polygon>
<text x="30" y="-30" text-anchor="middle">NE</text>
<polygon points="40,0 15,5 15,-5" data-aspect="e" class="roseDirection"></polygon>
<text x="45" y="2" text-anchor="middle">E</text>
class="roseDirection active"></polygon>
<text x="30" y="30" text-anchor="middle">SE</text>
class="roseDirection active"></polygon>
<text x="0" y="48" text-anchor="middle">S</text>
points="-22,22 -15,5 -5,15"
class="roseDirection active"></polygon>
<text x="-30" y="30" text-anchor="middle">SW</text>
points="-40,0 -15,-5 -15,5"
class="roseDirection"></polygon>
<text x="-45" y="2" text-anchor="middle">W</text>
points="-22,-22 -5,-15 -15,-5"
class="roseDirection"></polygon>
<text x="-30" y="-30" text-anchor="middle">NW</text>
<calcite-label scale="s">
<calcite-popover reference-element="colorButton" closable>
<calcite-color-picker id="colorPicker" format="rgb" closable></calcite-color-picker>
<button id="colorButton"></button>
const [RasterFunction, rasterFunctionUtils, ImageryTileLayer] = await $arcgis.import([
"@arcgis/core/layers/support/RasterFunction.js",
"@arcgis/core/layers/support/rasterFunctionUtils.js",
"@arcgis/core/layers/ImageryTileLayer.js",
const customAnalysisParams = {
elevation: { min: 2000, max: 9000 },
slope: { min: 10, max: 90 },
let activeAnalysisMode = "Custom"; // "Custom" | "Elevation" | "Slope"
/* ------------------------------ */
/* -- Slope - Raster Functions -- */
const slope = rasterFunctionUtils.slope({
// Remap slope values (degrees) to categorical ranges
const remapSlope = rasterFunctionUtils.remap({
{ range: [30, 35], output: 30 },
{ range: [35, 40], output: 35 },
{ range: [40, 45], output: 40 },
{ range: [45, 90], output: 45 },
// Map categorical ranges to RGB colors
const colorMapSlope = rasterFunctionUtils.colormap({
/* --------------------------------- */
/* -- Elevation - Raster Function -- */
const colorMapElevation = rasterFunctionUtils.colormap({
colorRampName: "elevation1",
/* -------------------------------------------- */
/* ---- Custom Analysis - Raster Functions ---- */
let customColor = [0, 122, 194];
function createCustomAnalysis(color = customColor) {
// Mask out elevation outside of parameter range
const elevationMask = rasterFunctionUtils.mask({
[customAnalysisParams.elevation.min, customAnalysisParams.elevation.max],
noDataInterpretation: "match-any",
// Compute slope on masked elevation
const slopeFunction = rasterFunctionUtils.slope({
// Mask out slopes outside of parameter range
const slopeMask = rasterFunctionUtils.mask({
includedRanges: [[customAnalysisParams.slope.min, customAnalysisParams.slope.max]],
noDataInterpretation: "match-any",
// Map included slopes >= 0 to 1
const greaterThanSlope0 = rasterFunctionUtils.greaterThanEqual({
// Compute aspect on masked elevation
const aspectFunction = rasterFunctionUtils.aspect({
// Map aspect as 1 (include) or 0 (exclude) according to parameters
const remapAspectFunction = rasterFunctionUtils.remap({
{ range: [-Infinity, 0], output: 1 }, // Include flats
{ range: [360, Infinity], output: 1 }, // Include flats
{ range: [337.5, 360], output: +customAnalysisParams.aspects.N },
{ range: [0, 22.5], output: +customAnalysisParams.aspects.N },
{ range: [22.5, 67.5], output: +customAnalysisParams.aspects.NE },
{ range: [67.5, 112.5], output: +customAnalysisParams.aspects.E },
{ range: [112.5, 157.5], output: +customAnalysisParams.aspects.SE },
{ range: [157.5, 202.5], output: +customAnalysisParams.aspects.S },
{ range: [202.5, 247.5], output: +customAnalysisParams.aspects.SW },
{ range: [247.5, 292.5], output: +customAnalysisParams.aspects.W },
{ range: [292.5, 337.5], output: +customAnalysisParams.aspects.NW },
// Combine slope and aspect rasters
const combineAspectSlope = rasterFunctionUtils.booleanAnd({
raster: greaterThanSlope0,
raster2: remapAspectFunction,
// Assign an RGB color to all raster cells with a value of 1
const colorMapFinal = rasterFunctionUtils.colormap({
colormap: [[1, ...color]],
raster: combineAspectSlope,
const opacitySlider = document.getElementById("opacitySlider");
opacitySlider.addEventListener("calciteSliderInput", (event) => {
analysisLayer.opacity = 1 - event.target.value / 100;
const elevationSlider = document.getElementById("elevationSlider");
elevationSlider.addEventListener("calciteSliderChange", (event) => {
customAnalysisParams.elevation = {
min: event.target.minValue,
max: event.target.maxValue,
analysisLayer.rasterFunction = createCustomAnalysis(customColor);
const slopeSlider = document.getElementById("slopeSlider");
slopeSlider.addEventListener("calciteSliderChange", (event) => {
customAnalysisParams.slope = { min: event.target.minValue, max: event.target.maxValue };
analysisLayer.rasterFunction = createCustomAnalysis(customColor);
const roseDirections = document.getElementsByClassName("roseDirection");
for (const roseDirection of roseDirections) {
roseDirection.addEventListener("click", () => {
const active = roseDirection.classList.toggle("active");
const aspect = roseDirection.getAttribute("data-aspect").toUpperCase();
customAnalysisParams.aspects[aspect] = active;
analysisLayer.rasterFunction = createCustomAnalysis(customColor);
const colorPicker = document.getElementById("colorPicker");
const colorButton = document.getElementById("colorButton");
colorPicker.addEventListener("calciteColorPickerChange", (event) => {
const color = event.target.value;
colorButton.style.backgroundColor = `rgb(${color.r}, ${color.g}, ${color.b})`;
customColor = [color.r, color.g, color.b];
analysisLayer.rasterFunction = createCustomAnalysis(customColor);
const customAnalysisDiv = document.getElementById("customAnalysisDiv");
const aspectCompass = document.getElementById("compassRoseSvg");
const analysisModeSegmentedControl = document.getElementById("analysisModeSegmentedControl");
const legendExpand = document.querySelector("arcgis-expand");
analysisModeSegmentedControl.addEventListener("calciteSegmentedControlChange", (event) => {
activeAnalysisMode = event.target.value;
if (activeAnalysisMode === "Custom") {
customAnalysisDiv.style.display = "block";
aspectCompass.style.display = "block";
customAnalysisDiv.style.display = "none";
aspectCompass.style.display = "none";
analysisLayer.renderer = null;
switch (activeAnalysisMode) {
analysisLayer.title = activeAnalysisMode + " | °";
analysisLayer.rasterFunction = colorMapSlope;
analysisLayer.title = activeAnalysisMode + " | m.a.s.l";
analysisLayer.rasterFunction = colorMapElevation;
analysisLayer.title = activeAnalysisMode + " Analysis";
analysisLayer.rasterFunction = createCustomAnalysis(customColor);
// Setup layer with initial raster function
const analysisLayer = new ImageryTileLayer({
url: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer",
title: "Custom Analysis",
rasterFunction: createCustomAnalysis(customColor),
// Access Basemap Toggle component
const arcgisBasemapToggle = document.querySelector("arcgis-basemap-toggle");
arcgisBasemapToggle.nextBasemap = "hybrid";
// Access Scene component and add the layer
const viewElement = document.querySelector("arcgis-scene");
await viewElement.viewOnReady();
viewElement.map.add(analysisLayer);