<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Analysis objects | 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: var(--calcite-color-foreground-1);
#menu calcite-action-bar {
--calcite-action-bar-background-color: var(--calcite-color-foreground-2);
--calcite-action-background-color: var(--calcite-color-foreground-2);
--calcite-action-background-color-hover: var(--calcite-color-foreground-1);
--calcite-action-background-color-press: var(--calcite-color-foreground-3);
--calcite-label-margin-bottom: 0;
#buttons calcite-button {
<arcgis-scene item-id="d6eefc2b1e984e1eaf1c290588a52c55">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
<arcgis-compass slot="top-left"> </arcgis-compass>
<div id="menu" slot="top-right" style="display: none">
<h4>Analysis objects</h4>
<calcite-action-bar id="action-bar" layout="horizontal" expand-disabled>
<calcite-label id="promptText"> Choose an analysis type. </calcite-label>
<calcite-label id="selectionPromptText" style="display: none">
>To edit an existing analysis, select it by hovering and clicking on its
<div id="buttons" style="display: none">
<calcite-button id="clearButton" appearance="outline-fill" kind="neutral"
<calcite-button id="doneButton">Done</calcite-button>
DirectLineMeasurementAnalysis,
] = await $arcgis.import([
"@arcgis/core/geometry/SpatialReference.js",
"@arcgis/core/geometry/Polygon.js",
"@arcgis/core/geometry/Point.js",
"@arcgis/core/analysis/AreaMeasurementAnalysis.js",
"@arcgis/core/analysis/DirectLineMeasurementAnalysis.js",
"@arcgis/core/analysis/LineOfSightAnalysis.js",
"@arcgis/core/analysis/ViewshedAnalysis.js",
"@arcgis/core/analysis/Viewshed.js",
"@arcgis/core/analysis/DimensionAnalysis.js",
"@arcgis/core/analysis/LengthDimension.js",
"@arcgis/core/analysis/SliceAnalysis.js",
"@arcgis/core/analysis/SlicePlane.js",
"@arcgis/core/core/promiseUtils.js",
const viewElement = document.querySelector("arcgis-scene");
const menu = document.getElementById("menu");
const actionBar = document.getElementById("action-bar");
const promptText = document.getElementById("promptText");
const selectionPromptText = document.getElementById("selectionPromptText");
const buttons = document.getElementById("buttons");
const clearButton = document.getElementById("clearButton");
const doneButton = document.getElementById("doneButton");
// Array of all the analysis tools used in the sample.
name: "Area measurement",
analysis: createAreaMeasurementAnalysis(),
name: "Direct line measurement",
analysis: createDirectLineMeasurementAnalysis(),
analysis: createLineOfSightAnalysis(),
analysis: createViewshedAnalysis(),
analysis: createDimensionAnalysis(),
analysis: createSliceAnalysis(),
/**********************************************
* Setup the UI/UX and add the programamtically created analysis to the analyses collection.
*********************************************/
// Create a UI element with a tooltip for each analysis tool.
for (const tool of tools) {
const actionElement = setupActionElement(tool);
actionBar.appendChild(actionElement);
// If "Done" is clicked, stop the active tool (if any) and reset the UI.
doneButton.addEventListener("click", () => {
// If "Clear" is clicked, remove existing analysis from the view (if any) and update the UI.
clearButton.addEventListener("click", () => {
clearAnalysis(activeTool.analysis);
selectionPromptText.style.display = "none";
clearButton.style.display = "none";
// If Esc is pressed, stop the active tool (if any) and reset the UI.
viewElement.addEventListener("arcgisViewKeyDown", (event) => {
if (event.detail.key === "Escape") {
// Wait for the view to be ready.
await viewElement.viewOnReady();
// Add the analysis tools to it and save their analysisViews for later access.
// Promise.all() returns all whenAnalysisView Promises as one single Promise.
tools.map(async (tool) => {
viewElement.analyses.add(tool.analysis);
tool.analysisView = await viewElement.whenAnalysisView(tool.analysis);
// Make the menu visible.
menu.style.display = "block";
/**********************************************
* Functions for creating and updating the UI/UX.
*********************************************/
function setupActionElement(tool) {
const actionElement = document.createElement("calcite-action");
actionElement.icon = tool.icon;
actionElement.appearance = "solid";
actionElement.addEventListener("click", () => onActionElementClick(tool, actionElement));
const actionTooltip = setupActionTooltip(tool, actionElement);
actionElement.appendChild(actionTooltip);
function setupActionTooltip(tool, referenceActionElement) {
const tooltip = document.createElement("calcite-tooltip");
tooltip.placement = "top";
tooltip.referenceElement = referenceActionElement;
tooltip.textContent = tool.name;
tooltip.closeOnClick = true;
function onActionElementClick(tool, actionElement) {
if (!actionElement.active) {
// If a non-active tool was picked, stop the previous active tool (if any) and reset the UI.
// Set the new active tool and start placing the analysis.
// Update menu and show the button controls.
// If the previously active tool is picked, stop it and reset the UI.
function stopActiveTool() {
// Stop any placing, make the analysis not interactive, and null the active tool reference.
abortController?.abort();
activeTool.analysisView.interactive = false;
// Reset the action bar and update the UI.
for (const action of actionBar.children) {
action.removeAttribute("active");
updateUI(activeActionElement);
function updateUI(actionElement) {
// If the function was triggered by a click on an actionElement,
// update the action bar, buttons, and prompt text.
// If an already active actionElement was clicked, hide the buttons and reset the UI.
// Otherwise show UI for the new tool.
const resetUI = actionElement === activeActionElement ? true : false;
buttons.style.display = resetUI ? "none" : "flex";
actionElement.active = resetUI ? false : true;
promptText.textContent = resetUI
? "Choose an analysis type."
: "Click in view to start placing " +
analysisTypeToName(activeTool?.analysis.type) +
// Update currently active action element for future reference.
activeActionElement = resetUI ? null : actionElement;
// Only if the active tool has any analysis present, show the "Clear" button
// and an extra hint about selection for viewsheds and dimensions.
const activeAnalysisPresent = checkIfAnalysisPresent();
if (activeAnalysisPresent) {
clearButton.style.display = "flex";
selectionPromptText.style.display =
activeTool?.analysis.type === "viewshed" || activeTool?.analysis.type === "dimension"
clearButton.style.display = "none";
selectionPromptText.style.display = "none";
function checkIfAnalysisPresent() {
const analysis = activeTool?.analysis;
case "direct-line-measurement":
return analysis.startPoint !== null && analysis.endPoint !== null;
return analysis.geometry !== null;
return analysis.observer !== null;
return analysis.shape !== null;
return analysis.viewsheds?.length > 0;
return analysis.dimensions?.length > 0;
/**********************************************
* Functions for placing and clearing the analyses.
*********************************************/
async function placeContinuous() {
// Stop any previous placing and create a new controller.
abortController?.abort();
abortController = new AbortController();
// Get a reference to the signal for the new placement operation.
const { signal } = abortController;
// After one analysis is placed, call the place() method again.
// This is done until the placing is aborted.
while (!signal.aborted) {
await activeTool.analysisView.place({
// Update the UI e.g. show "Clear" button afer an anaysis was added to an empty analysis object.
// Avoid logging the abort errors.
throwIfNotAbortError(error);
// Remove the controller if this was the last started placement.
if (abortController?.signal === signal) {
function clearAnalysis(analysis) {
case "direct-line-measurement":
analysis.startPoint = null;
analysis.endPoint = null;
analysis.geometry = null;
analysis.observer = null;
analysis.dimensions = [];
/**********************************************
* Functions to create the analyses.
*********************************************/
function createDirectLineMeasurementAnalysis() {
return new DirectLineMeasurementAnalysis({
startPoint: newPoint(-8238827, 4971466, 3),
endPoint: newPoint(-8238819, 4971537, 3),
function createAreaMeasurementAnalysis() {
const roofPolygon = new Polygon({
spatialReference: SpatialReference.WebMercator,
return new AreaMeasurementAnalysis({
function createSliceAnalysis() {
return new SliceAnalysis({
position: newPoint(-8238840, 4971700, 21),
function createLineOfSightAnalysis() {
return new LineOfSightAnalysis({
position: newPoint(-8238825, 4971538, 48),
position: newPoint(-8238903, 4971649, 2),
position: newPoint(-8238866, 4971629, 19),
position: newPoint(-8238825, 4971880, 2),
position: newPoint(-8238791, 4971784, 2),
function createViewshedAnalysis() {
return new ViewshedAnalysis({
observer: newPoint(-8238868, 4971525, 48),
horizontalFieldOfView: 55,
function createDimensionAnalysis() {
return new DimensionAnalysis({
startPoint: newPoint(-8238805.863420386, 4971633.739526394, 3.8),
endPoint: newPoint(-8238815.848786024, 4971563.1027141735, 3.8),
startPoint: newPoint(-8238805.863404014, 4971633.739529291, 3.8),
endPoint: newPoint(-8238805.863404014, 4971633.739529291, 22.8),
/**********************************************
* General helper functions.
*********************************************/
// Convenience function to have all points in the same spatial reference
function newPoint(x, y, z) {
spatialReference: SpatialReference.WebMercator,
// Only log errors that are not abort errors.
function throwIfNotAbortError(error) {
if (!promiseUtils.isAbortError(error)) {
function analysisTypeToName(text) {
return text.replace(/-/g, " ");