Learn how to create and manage a client-side viewshed analysis in a 3D scene.

This tutorial uses a viewshed analysis to walk through a typical analysis lifecycle: create the analysis object, add it to the scene, obtain its analysis view, place or edit it interactively, update its properties, and clear it when you need to reset the workflow.

Place additional viewsheds in the scene, cancel placement, limit the field of view, and clear the analysis.

Prerequisites

Steps

Create a new pen

  1. To get started, either complete the Display a scene tutorial or .

Get an access token

You need an access token An access token is an authorization string that provides access to secure ArcGIS content, data, and services. Its capabilities are determined by the privileges it supports. It is obtained by implementing API key authentication, User authentication, or App authentication. Learn more with the correct privileges to access the location services ArcGIS Location Services, also referred to as Location Services, are services hosted by Esri that provide geospatial functionality for developing mapping applications. They include the ArcGIS Basemap Styles service, ArcGIS Static Basemap Tiles service, ArcGIS Places service, ArcGIS Geocoding service, ArcGIS Routing service, ArcGIS GeoEnrichment service, and ArcGIS Elevation service. An ArcGIS Location Platform or ArcGIS Online account is required to use the services. Learn more used in this tutorial.

  1. Go to the Create an API key tutorial and create an API key An API key is a long-lived access token created using API key credentials. They are valid for up to one year and are typically embedded directly into client applications. Learn more with the following privilege(s) Privileges are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. Learn more :

    • Privileges
    • Location services > Basemaps

  2. In CodePen, set the apiKey property on the global esriConfig variable to your access token.

    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };

To learn about other ways to get an access token, go to Types of authentication.

Load the web scene

Use the portal item ID An item ID is a unique identifier representing a single item stored, managed, and accessed in a portal, such as a web map, hosted layer, or file. Learn more on the arcgis-scene component to load a web scene that provides the 3D context for the analysis.

  1. Replace the arcgis-scene from the Display a Scene starter with one that loads the web scene and includes zoom, navigation toggle, and compass components.

    3 collapsed lines
    <html>
    <body>
    <arcgis-scene
    basemap="arcgis/topographic"
    ground="world-elevation"
    camera-position="-118.808, 33.961, 2000"
    camera-tilt="75"
    >
    </arcgis-scene>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    </arcgis-scene>
    3 collapsed lines
    </body>
    </html>

Add analysis controls

Add a small UI panel for placing, canceling, and clearing viewsheds.

  1. Add CSS for the control panel and instructional text.

    15 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    142 collapsed lines
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>
  2. Add a calcite-card with buttons and a switch to control the analysis workflow.

    50 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    5 collapsed lines
    </arcgis-scene>
    </body>
    </html>

Add the analysis modules

Use $arcgis.import to load the modules required for the analysis object, its item type, and the placement workflow.

  1. In a new <script> at the bottom of the <body>, import the modules.

    65 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    </script>
    3 collapsed lines
    </body>
    </html>

Create the analysis object

Create a ViewshedAnalysis and add an initial Viewshed so the scene starts with a concrete analysis object already in place.

  1. Get references to the scene and UI elements, then wait for the scene to be ready.

    75 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    99 collapsed lines
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>
  2. Create a ViewshedAnalysis, add a programmatic Viewshed, and then add the analysis to the scene’s analyses collection.

    84 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    81 collapsed lines
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>

Get the analysis view

The analysis object stores the state of the analysis. The analysis view exposes interactive, view-specific behavior such as selection and placement.

  1. Call whenAnalysisView() to obtain the ViewshedAnalysisView3D.

  2. Set interactive to true so the selected viewshed can be edited in the scene.

  3. Set selectedViewshed to the programmatic viewshed so it is active immediately.

    102 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    77 collapsed lines
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>

Place additional viewsheds interactively

To create a custom analysis workflow, call place() on the analysis view when the user clicks the button in your application UI.

  1. Add event listeners and create an AbortController variable to track the active placement operation.

    106 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    73 collapsed lines
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>
  2. Add a small helper function to keep the UI in sync while placement starts and stops.

    110 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    63 collapsed lines
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>
  3. Create a startPlacing() function that starts placement by calling analysisView.place().

    120 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    41 collapsed lines
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>
  4. Run the app and click Place viewshed. Click once in the scene to set the observer point, then click again to set the target.

Cancel placement with AbortController

Interactive placement should be cancellable from your own UI.

  1. Add a stopPlacing() function that calls abort() on the current controller.

    142 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    33 collapsed lines
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>
  2. Click Place viewshed, then click Cancel before finishing the placement to stop the operation.

Update the selected viewshed

After a viewshed exists in the analysis, you can modify its properties directly.

  1. Add a function that limits the selected viewshed’s horizontal and vertical field of view.

    150 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    13 collapsed lines
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    </script>
    </body>
    </html>
  2. Run the app and use the switch while editing a viewshed to keep its field of view within the defined maximum values.

  3. When a viewshed is selected in the scene, press Delete on the keyboard to remove only that viewshed.

Clear the analysis

To restart the workflow, clear the analysis and remove its current viewsheds.

  1. Add a resetAnalysis() function that stops any active placement and clears the analysis.

    170 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Create and manage a viewshed analysis</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.1/"></script>
    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    #viewshedControls {
    width: 280px;
    }
    #viewshedControls calcite-button {
    display: flex;
    }
    #promptText {
    display: none;
    margin-top: 0.5rem;
    }
    #limitFOV {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    }
    </style>
    </head>
    <body>
    <arcgis-scene id="viewElement" item-id="f212abf31a424b77a5a8dde92302423b">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
    <arcgis-compass slot="top-left"></arcgis-compass>
    <calcite-card id="viewshedControls" slot="top-right">
    <calcite-button id="placeButton">Place viewshed</calcite-button>
    <calcite-button id="cancelButton" style="display: none">Cancel</calcite-button>
    <calcite-button id="resetButton" appearance="outline">Clear viewsheds</calcite-button>
    <div id="promptText">
    <em>Click in the scene to place an observer point and then set the target.</em>
    </div>
    <calcite-label layout="inline-space-between" id="limitFOV">
    Limit maximum field of view
    <calcite-switch checked id="limitFOVSwitch"></calcite-switch>
    </calcite-label>
    </calcite-card>
    </arcgis-scene>
    <script type="module">
    const [promiseUtils, reactiveUtils, SpatialReference, Viewshed, ViewshedAnalysis] = await $arcgis.import([
    "@arcgis/core/core/promiseUtils.js",
    "@arcgis/core/core/reactiveUtils.js",
    "@arcgis/core/geometry/SpatialReference.js",
    "@arcgis/core/analysis/Viewshed.js",
    "@arcgis/core/analysis/ViewshedAnalysis.js",
    ]);
    const viewElement = document.getElementById("viewElement");
    const placeButton = document.getElementById("placeButton");
    const cancelButton = document.getElementById("cancelButton");
    const resetButton = document.getElementById("resetButton");
    const promptText = document.getElementById("promptText");
    const limitFOVSwitch = document.getElementById("limitFOVSwitch");
    await viewElement.viewOnReady();
    const viewshedAnalysis = new ViewshedAnalysis();
    const programmaticViewshed = new Viewshed({
    observer: {
    spatialReference: SpatialReference.WebMercator,
    x: -9754426,
    y: 5143111,
    z: 330,
    },
    farDistance: 900,
    tilt: 84,
    heading: 63,
    horizontalFieldOfView: 85,
    verticalFieldOfView: 60,
    });
    viewshedAnalysis.viewsheds.add(programmaticViewshed);
    viewElement.analyses.add(viewshedAnalysis);
    const analysisView = await viewElement.whenAnalysisView(viewshedAnalysis);
    analysisView.interactive = true;
    analysisView.selectedViewshed = programmaticViewshed;
    let abortController = null;
    placeButton.addEventListener("click", startPlacing);
    function updateUI() {
    const placing = abortController !== null;
    placeButton.style.display = placing ? "none" : "flex";
    cancelButton.style.display = placing ? "flex" : "none";
    promptText.style.display = placing ? "block" : "none";
    }
    updateUI();
    async function startPlacing() {
    abortController?.abort();
    abortController = new AbortController();
    const { signal } = abortController;
    updateUI();
    try {
    await analysisView.place({ signal });
    } catch (error) {
    if (!promiseUtils.isAbortError(error)) {
    throw error;
    }
    } finally {
    if (abortController?.signal === signal) {
    abortController = null;
    }
    updateUI();
    }
    }
    cancelButton.addEventListener("click", stopPlacing);
    function stopPlacing() {
    abortController?.abort();
    abortController = null;
    updateUI();
    }
    limitFOVSwitch.addEventListener("calciteSwitchChange", applyFieldOfViewLimit);
    reactiveUtils.watch(
    () => analysisView?.selectedViewshed,
    () => {
    applyFieldOfViewLimit();
    },
    );
    function applyFieldOfViewLimit() {
    const selectedViewshed = analysisView.selectedViewshed;
    if (!selectedViewshed || !limitFOVSwitch.checked) {
    return;
    }
    selectedViewshed.horizontalFieldOfView = Math.min(selectedViewshed.horizontalFieldOfView, 120);
    selectedViewshed.verticalFieldOfView = Math.min(selectedViewshed.verticalFieldOfView, 90);
    }
    resetButton.addEventListener("click", resetAnalysis);
    function resetAnalysis() {
    stopPlacing();
    viewshedAnalysis.clear();
    analysisView.selectedViewshed = null;
    }
    5 collapsed lines
    </script>
    </body>
    </html>
  2. Run the app and click Place viewshed. Click once in the scene to set the observer point, then click again to set the target. Click Cancel to stop placement, use the switch while editing a viewshed to limit the field of view, and click Clear viewsheds to reset the workflow.

Run the app

In CodePen, run your code to display the scene with one programmatic viewshed, then place more viewsheds, cancel placement, constrain the selected viewshed’s field of view, remove a selected viewshed with Delete on the keyboard, and clear the analysis to start again.

What’s next?

Learn how to use other analyses in the Introduction to analyses, the Analysis objects sample, and the full set of analysis samples.