arcgis-spatial-analysis
About arcgis-spatial-analysis
A skill for ArcGIS developers working with 3D spatial analysis, feature statistics, and feature reduction. It documents analysis object setup, query patterns, clustering, binning, and common API pitfalls.
Key features
- 01Configure 3D analysis objects
- 02Run feature queries with statistics
- 03Cluster features and aggregate fields
- 04Configure feature binning
- 05Document API pitfalls and correct properties
Use cases
- 01Build visibility or elevation analyses
- 02Measure 3D areas, distances, or volumes
- 03Summarize feature data by groups or spatial extent
- 04Reduce dense features with clusters or bins
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ saschabrunnerch/ arcgis-maps-sdk-js-ai-context --skill arcgis-spatial-analysisAfter installing, type/arcgis-spatial-analysisin Claude Code to confirm it loaded.
Details
From the project READMEArcGIS Spatial Analysis
Use this skill for spatial analysis objects (3D analyses), feature queries with statistics, and feature reduction (clustering/binning).
For geometry operators (buffer, union, intersect, clip, offset, etc.), see
arcgis-geometry-operations. For routing, geocoding, and geoprocessing REST services, seearcgis-rest-services.
Import Patterns
Direct ESM Imports
import ViewshedAnalysis from "@arcgis/core/analysis/ViewshedAnalysis.js";
import Viewshed from "@arcgis/core/analysis/Viewshed.js";
import LineOfSightAnalysis from "@arcgis/core/analysis/LineOfSightAnalysis.js";
import ElevationProfileAnalysis from "@arcgis/core/analysis/ElevationProfileAnalysis.js";
import SliceAnalysis from "@arcgis/core/analysis/SliceAnalysis.js";
import ShadowCastAnalysis from "@arcgis/core/analysis/ShadowCastAnalysis.js";
Dynamic Imports (CDN)
const [ViewshedAnalysis, Viewshed] = await $arcgis.import([
"@arcgis/core/analysis/ViewshedAnalysis.js",
"@arcgis/core/analysis/Viewshed.js",
]);
Analysis Objects (3D)
Analysis objects are added to a SceneView's analyses collection. All analysis types require SceneView (not MapView).
ElevationProfileAnalysis
import ElevationProfileAnalysis from "@arcgis/core/analysis/ElevationProfileAnalysis.js";
import * as reactiveUtils from "@arcgis/core/core/reactiveUtils.js";
const analysis = new ElevationProfileAnalysis({
profiles: [
{ type: "ground", color: "brown" },
{ type: "input", color: "blue" },
],
displayUnits: {
distance: "meters",
elevation: "meters",
},
});
view.analyses.add(analysis);
analysis.geometry = polyline;
// Get analysis view for results
const analysisView = await view.whenAnalysisView(analysis);
reactiveUtils.watch(
() => analysisView.progress,
(progress) => {
if (progress === 1) {
console.log("Results:", analysisView.results);
}
},
);
Interactive Analysis Placement
Analysis views support interactive placement via the place() method:
import * as promiseUtils from "@arcgis/core/core/promiseUtils.js";
const analysisView = await view.whenAnalysisView(analysis);
const abortController = new AbortController();
try {
await analysisView.place({ signal: abortController.signal });
} catch (error) {
if (!promiseUtils.isAbortError(error)) throw error;
}
// To cancel placement
abortController.abort();
ViewshedAnalysis
Uses a viewsheds collection of Viewshed objects (not direct observer property).
import ViewshedAnalysis from "@arcgis/core/analysis/ViewshedAnalysis.js";
import Viewshed from "@arcgis/core/analysis/Viewshed.js";
const viewshed = new Viewshed({
observer: {
type: "point",
x: -122.4,
y: 37.8,
z: 100,
spatialReference: { wkid: 4326 },
},
farDistance: 1000,
heading: 45,
tilt: 90,
horizontalFieldOfView: 120,
verticalFieldOfView: 90,
});
const analysis = new ViewshedAnalysis({
viewsheds: [viewshed],
});
view.analyses.add(analysis);
// Add more viewsheds dynamically
analysis.viewsheds.add(
new Viewshed({
observer: {
type: "point",
x: -122.41,
y: 37.79,
z: 80,
spatialReference: { wkid: 4326 },
},
farDistance: 500,
heading: 0,
tilt: 90,
horizontalFieldOfView: 360,
verticalFieldOfView: 90,
}),
);
// Clear all viewsheds
analysis.clear();
LineOfSightAnalysis
Uses LineOfSightAnalysisObserver and LineOfSightAnalysisTarget wrapper objects.
import LineOfSightAnalysis from "@arcgis/core/analysis/LineOfSightAnalysis.js";
import LineOfSightAnalysisObserver from "@arcgis/core/analysis/LineOfSightAnalysisObserver.js";
import LineOfSightAnalysisTarget from "@arcgis/core/analysis/LineOfSightAnalysisTarget.js";
const analysis = new LineOfSightAnalysis({
observer: new LineOfSightAnalysisObserver({
position: {
type: "point",
x: -122.4,
y: 37.8,
z: 100,
spatialReference: { wkid: 4326 },
},
}),
targets: [
new LineOfSightAnalysisTarget({
position: {
type: "point",
x: -122.41,
y: 37.81,
z: 50,
spatialReference: { wkid: 4326 },
},
}),
new LineOfSightAnalysisTarget({
position: {
type: "point",
x: -122.42,
y: 37.79,
z: 75,
spatialReference: { wkid: 4326 },
},
}),
],
});
view.analyses.add(analysis);
ShadowCastAnalysis
Uses date, startTimeOfDay, endTimeOfDay, and mode properties.
import ShadowCastAnalysis from "@arcgis/core/analysis/ShadowCastAnalysis.js";
const analysis = new ShadowCastAnalysis({
date: new Date("2025-06-21"),
startTimeOfDay: 21600000, // 6:00 AM (milliseconds from midnight)
endTimeOfDay: 57600000, // 4:00 PM
mode: "total-duration", // "total-duration", "discrete", "min-duration"
utcOffset: -7, // UTC offset in hours
});
view.analyses.add(analysis);
SliceAnalysis
Uses a shape property of type SlicePlane (not plane).
import SliceAnalysis from "@arcgis/core/analysis/SliceAnalysis.js";
import SlicePlane from "@arcgis/core/analysis/SlicePlane.js";
const analysis = new SliceAnalysis({
shape: new SlicePlane({
position: {
type: "point",
x: -122.4,
y: 37.8,
z: 50,
spatialReference: { wkid: 4326 },
},
heading: 0,
tilt: 0,
width: 200,
height: 200,
}),
tiltEnabled: false,
excludeGroundSurface: false,
});
view.analyses.add(analysis);
AreaMeasurementAnalysis
import AreaMeasurementAnalysis from "@arcgis/core/analysis/AreaMeasurementAnalysis.js";
const analysis = new AreaMeasurementAnalysis();
view.analyses.add(analysis);
// Set geometry after user draws or programmatically
analysis.geometry = polygon;
// Watch for results
const analysisView = await view.whenAnalysisView(analysis);
reactiveUtils.watch(
() => analysisView.result,
(result) => {
if (result) {
console.log("Area:", result.area?.value, result.area?.unit);
console.log(
"Perimeter:",
result.perimeterLength?.value,
result.perimeterLength?.unit,
);
}
},
{ initial: true },
);
DistanceMeasurementAnalysis
Uses a geometry property (Polyline), not startPoint/endPoint.
import DistanceMeasurementAnalysis from "@arcgis/core/analysis/DistanceMeasurementAnalysis.js";
const analysis = new DistanceMeasurementAnalysis({
geometry: {
type: "polyline",
paths: [
[
[-122.4, 37.8, 0],
[-122.41, 37.81, 0],
],
],
spatialReference: { wkid: 4326 },
},
});
view.analyses.add(analysis);
VolumeMeasurementAnalysis
import VolumeMeasurementAnalysis from "@arcgis/core/analysis/VolumeMeasurementAnalysis.js";
const analysis = new VolumeMeasurementAnalysis();
view.analyses.add(analysis);
analysis.geometry = polygon;
// Watch for results
const analysisView = await view.whenAnalysisView(analysis);
reactiveUtils.watch(
() => analysisView.result,
(result) => {
if (result) {
console.log(
"Cut volume:",
result.cutVolume?.value,
result.cutVolume?.unit,
);
console.log(
"Fill volume:",
result.fillVolume?.value,
result.fillVolume?.unit,
);
}
},
{ initial: true },
);
DimensionAnalysis
import DimensionAnalysis from "@arcgis/core/analysis/DimensionAnalysis.js";
import LengthDimension from "@arcgis/core/analysis/LengthDimension.js";
const dimension = new LengthDimension({
startPoint: {
type: "point",
x: -122.4,
y: 37.8,
z: 0,
spatialReference: { wkid: 4326 },
},
endPoint: {
type: "point",
x: -122.41,
y: 37.81,
z: 0,
spatialReference: { wkid: 4326 },
},
});
const analysis = new DimensionAnalysis({
dimensions: [dimension],
});
view.analyses.add(analysis);
Analysis Types Summary
| Analysis | Use Case | Constructor Key |
|---|---|---|
| ViewshedAnalysis | Visible area from observer | viewsheds (Collection of Viewshed) |
| LineOfSightAnalysis | Visibility between points | observer, targets (wrapper objects) |
| ShadowCastAnalysis | Shadow visualization | date, startTimeOfDay, endTimeOfDay, mode |
| SliceAnalysis | Cross-section of 3D content | shape (SlicePlane) |
| ElevationProfileAnalysis | Terrain profile along a line | profiles, geometry (Polyline) |
| AreaMeasurementAnalysis | 3D area measurement | geometry (Polygon) |
| DistanceMeasurementAnalysis | 3D distance measurement | geometry (Polyline) |
| VolumeMeasurementAnalysis | Cut/fill volume | geometry (Polygon) |
| DimensionAnalysis | Length dimensions in 3D | dimensions (LengthDimension[]) |
Feature Queries with Statistics
Basic Statistics
const query = featureLayer.createQuery();
query.outStatistics = [
{
statisticType: "sum",
onStatisticField: "population",
outStatisticFieldName: "totalPop",
},
{
statisticType: "avg",
onStatisticField: "population",
outStatisticFieldName: "avgPop",
},
{
statisticType: "max",
onStatisticField: "population",
outStatisticFieldName: "maxPop",
},
{
statisticType: "min",
onStatisticField: "population",
outStatisticFieldName: "minPop",
},
{
statisticType: "count",
onStatisticField: "population",
outStatisticFieldName: "count",
},
{
statisticType: "stddev",
onStatisticField: "population",
outStatisticFieldName: "stdDev",
},
];
const result = await featureLayer.queryFeatures(query);
console.log(result.features[0].attributes);
Group By Statistics
const query = featureLayer.createQuery();
query.groupByFieldsForStatistics = ["state"];
query.outStatistics = [
{
statisticType: "sum",
onStatisticField: "population",
outStatisticFieldName: "totalPop",
},
];
query.orderByFields = ["totalPop DESC"];
const result = await featureLayer.queryFeatures(query);
// Returns one feature per state with total population
Spatial Statistics
const query = featureLayer.createQuery();
query.geometry = view.extent;
query.spatialRelationship = "intersects";
query.outStatistics = [
{
statisticType: "count",
onStatisticField: "ObjectID",
outStatisticFieldName: "featureCount",
},
];
const result = await featureLayer.queryFeatures(query);
console.log("Features in view:", result.features[0].attributes.featureCount);
Feature Reduction
Clustering
const clusterConfig = {
type: "cluster",
clusterRadius: "100px",
clusterMinSize: "24px",
clusterMaxSize: "60px",
popupTemplate: {
title: "Cluster summary",
content: "This cluster represents {cluster_count} features.",
fieldInfos: [
{
fieldName: "cluster_count",
format: { digitSeparator: true, places: 0 },
},
],
},
labelingInfo: [
{
deconflictionStrategy: "none",
labelExpressionInfo: {
expression: "Text($feature.cluster_count, '#,###')",
},
symbol: {
type: "text",
color: "white",
font: { size: "12px", weight: "bold" },
},
labelPlacement: "center-center",
},
],
};
featureLayer.featureReduction = clusterConfig;
// Toggle clustering
featureLayer.featureReduction = null; // Disable
featureLayer.featureReduction = clusterConfig; // Enable
Cluster with Aggregated Fields
const clusterConfig = {
type: "cluster",
clusterRadius: "100px",
fields: [
{
name: "avg_magnitude",
statisticType: "avg",
onStatisticField: "magnitude",
},
{
name: "total_count",
statisticType: "count",
onStatisticField: "ObjectID",
},
],
renderer: {
type: "simple",
symbol: {
type: "simple-marker",
style: "circle",
color: "#69dcff",
},
visualVariables: [
{
type: "size",
field: "total_count",
stops: [
{ value: 1, size: 8 },
{ value: 100, size: 40 },
],
},
],
},
};
Binning
const binConfig = {
type: "binning",
fixedBinLevel: 4,
renderer: {
type: "simple",
symbol: {
type: "simple-fill",
outline: { color: "white", width: 0.5 },
},
visualVariables: [
{
type: "color",
field: "aggregateCount",
stops: [
{ value: 1, color: "#feebe2" },
{ value: 50, color: "#fbb4b9" },
{ value: 100, color: "#f768a1" },
{ value: 500, color: "#c51b8a" },
{ value: 1000, color: "#7a0177" },
],
},
],
},
popupTemplate: {
title: "Bin",
content: "{aggregateCount} features in this bin",
},
};
featureLayer.featureReduction = binConfig;
Common Pitfalls
-
Analysis objects are SceneView-only: All analysis types (viewshed, line of sight, shadow cast, etc.) require 3D SceneView. They do not work in MapView.
-
ViewshedAnalysis uses a viewsheds collection: Do not set
observerdirectly on ViewshedAnalysis. CreateViewshedobjects and add them to theviewshedscollection:// Anti-pattern new ViewshedAnalysis({ observer: point, farDistance: 1000 }); // Correct new ViewshedAnalysis({ viewsheds: [new Viewshed({ observer: point, farDistance: 1000 })], }); -
Cluster field names: Use
cluster_count(notclusterCount) to access the feature count in cluster popups and labels. -
Statistics queries return features: Statistics queries return features with calculated attributes, not raw numbers. Access results via
result.features[0].attributes. -
Analysis progress: Check
analysisView.progress === 1or watchanalysisView.resultto know when analysis results are ready. Don't read results before they're computed. -
SliceAnalysis uses
shapenotplane: The property isshapeof typeSlicePlane, notplane:// Anti-pattern new SliceAnalysis({ plane: slicePlane }); // Correct new SliceAnalysis({ shape: slicePlane });
Reference Samples
analysis-viewshed- Interactive viewshed analysis in 3Danalysis-objects- Multiple analysis types in a single sceneanalysis-elevation-profile- Elevation profile along a lineanalysis-shadow-cast- Shadow cast duration visualizationanalysis-area-measurement- Area measurement on 3D surfacesanalysis-volume-measurement- Cut/fill volume measurementline-of-sight- Line of sight visibility analysisfeaturereduction-cluster- Feature clusteringfeaturereduction-binning- Feature binning for aggregationfeaturereduction-cluster-aggregate-fields- Clusters with aggregated statistics
Related Skills
- See
arcgis-geometry-operationsfor client-side geometry operations (buffer, union, intersect, etc.). - See
arcgis-rest-servicesfor server-side REST services (routing, geocoding, geoprocessing). - See
arcgis-coordinates-projectionfor coordinate projection and formatting. - See
arcgis-layersfor FeatureLayer queries and layer configuration.