Skip to content
View inMAUIWPFWinUIView on GitHub

Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position.

Show interactive viewshed with analysis overlay sample

Use case

A viewshed analysis calculates the visible and non-visible areas from an observer's location, based on factors such as elevation and topographic features. For example, an interactive viewshed analysis can be used to identify which areas can be seen from a helicopter moving along a given flight path for monitoring wildfires while taking parameters such as height, field of view, and heading into account to give immediate visual feedback. A user could further extend their viewshed analysis calculations by using map algebra to e.g. only return viewshed results in geographical areas not covered in forest if they have an additional land cover raster dataset.

Note: This analysis is a form of "data-driven analysis", which means the analysis is calculated at the resolution of the data rather than the resolution of the display.

How to use the sample

The sample loads with a viewshed analysis initialized from an elevation raster covering the Isle of Arran, Scotland. Transparent green shows the area visible from the observer position, and grey shows the non-visible areas. Move the observer position by clicking and dragging over the island to interactively evaluate the viewshed result and display it in the analysis overlay. Alternatively, click on the map to see the viewshed from the clicked location. Use the control panel to explore how the viewshed analysis results change when adjusting the observer elevation, target height, maximum radius, field of view, heading and elevation sampling interval. As you move the observer and update the viewshed parameters, the analysis overlay refreshes to show the evaluated viewshed result.

How it works

  1. Create a Map and set it on a MapView.
  2. Add a GraphicsOverlay to draw the observer point and an AnalysisOverlay to the map view.
  3. Create a ContinuousField from a raster file containing elevation data.
  4. Create and configure ViewshedParameters, passing in a MapPoint as the observer position for the viewshed.
  5. Create a ContinuousFieldFunction from the continuous field.
  6. Create a ViewshedFunction using the continuous field function and viewshed parameters, then convert it to a DiscreteFieldFunction.
  7. Create a ColormapRenderer from a Colormap with colors that represent visible and non-visible results.
  8. Create a FieldAnalysis from the discrete field function and colormap renderer, then add it to the AnalysisOverlay's collection of analysis objects to display the results. As parameter values change, the result is recalculated and redrawn automatically.

Relevant API

  • AnalysisOverlay
  • Colormap
  • ColormapRenderer
  • ContinuousField
  • ContinuousFieldFunction
  • DiscreteFieldFunction
  • FieldAnalysis
  • ViewshedFunction
  • ViewshedParameters

About the data

The sample uses a 10m resolution digital terrain elevation raster of the Isle of Arran, Scotland (Data Copyright Scottish Government and SEPA (2014)).

Tags

analysis overlay, elevation, field analysis, interactive, raster, spatial analysis, terrain, viewshed, visibility

Sample Code

ShowInteractiveViewshedInAnalysisOverlay.xaml.csShowInteractiveViewshedInAnalysisOverlay.xaml.csShowInteractiveViewshedInAnalysisOverlay.xaml
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright 2026 Esri.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
// language governing permissions and limitations under the License.

using ArcGIS.Samples.Managers;
using Esri.ArcGISRuntime.Analysis;
using Esri.ArcGISRuntime.Analysis.Visibility;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Rasters;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.UI.GeoAnalysis;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using System;
using System.Drawing;
using System.Threading.Tasks;

namespace ArcGIS.WinUI.Samples.ShowInteractiveViewshedInAnalysisOverlay
{
    [ArcGIS.Samples.Shared.Attributes.Sample(
        name: "Show interactive viewshed with analysis overlay",
        category: "Analysis",
        description: "Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position.",
        instructions: "The sample loads with a viewshed analysis initialized from an elevation raster covering the Isle of Arran, Scotland. Transparent green shows the area visible from the observer position, and grey shows the non-visible areas. Move the observer position by clicking and dragging over the island to interactively evaluate the viewshed result and display it in the analysis overlay. Alternatively, click on the map to see the viewshed from the clicked location. Use the control panel to explore how the viewshed analysis results change when adjusting the observer elevation, target height, maximum radius, field of view, heading and elevation sampling interval. As you move the observer and update the viewshed parameters, the analysis overlay refreshes to show the evaluated viewshed result.",
        tags: new[] { "analysis overlay", "elevation", "field analysis", "interactive", "raster", "spatial analysis", "terrain", "viewshed", "visibility" })]
    [ArcGIS.Samples.Shared.Attributes.OfflineData("aa97788593e34a32bcaae33947fdc271")]
    public partial class ShowInteractiveViewshedInAnalysisOverlay
    {
        private ViewshedParameters _viewshedParameters;
        private Graphic _observerGraphic;
        private MapPoint _observerPosition;
        private bool _isDragging;
        private bool _isInitialized;
        private double _observerElevation = 20.0;

        private readonly SimpleMarkerSymbol _observerSymbol = new SimpleMarkerSymbol(
            SimpleMarkerSymbolStyle.Circle, Color.FromArgb(255, 0, 94, 255), 10);

        public ShowInteractiveViewshedInAnalysisOverlay()
        {
            InitializeComponent();
            _ = Initialize();
        }

        private async Task Initialize()
        {
            // Create a map with the imagery basemap style.
            MyMapView.Map = new Map(BasemapStyle.ArcGISImagery)
            {
                InitialViewpoint = new Viewpoint(55.610000, -5.200346, 100000)
            };

            // Disable panning to allow click-and-drag interaction for observer placement.
            MyMapView.InteractionOptions = new MapViewInteractionOptions { IsPanEnabled = false };

            // Create and add a graphics overlay for the observer marker.
            var graphicsOverlay = new GraphicsOverlay();
            MyMapView.GraphicsOverlays.Add(graphicsOverlay);

            // Create and add an analysis overlay for the viewshed.
            var analysisOverlay = new AnalysisOverlay();
            MyMapView.AnalysisOverlays.Add(analysisOverlay);

            try
            {
                // Get the path to the locally stored elevation raster file.
                string rasterPath = DataManager.GetDataFolder("aa97788593e34a32bcaae33947fdc271", "arran.tif");

                // Create a continuous field from the elevation raster file.
                var continuousField = await ContinuousField.CreateAsync(new[] { rasterPath }, 0);

                // Set the initial observer position over the Isle of Arran.
                _observerPosition = new MapPoint(-579246.504, 7479619.947, _observerElevation, SpatialReferences.WebMercator);

                // Add the observer graphic.
                _observerGraphic = new Graphic(_observerPosition, _observerSymbol);
                graphicsOverlay.Graphics.Add(_observerGraphic);

                // Configure the viewshed parameters.
                _viewshedParameters = new ViewshedParameters
                {
                    ObserverPosition = _observerPosition,
                    TargetHeight = 20.0,
                    MaxRadius = 8000,
                    FieldOfView = 150,
                    Heading = 10,
                    ElevationSamplingInterval = 0
                };

                // Create a ContinuousFieldFunction from the continuous field.
                var continuousFieldFunction = ContinuousFieldFunction.Create(continuousField);

                // Create a ViewshedFunction and convert to a DiscreteFieldFunction for visible/not-visible classification.
                var viewshedFunction = new ViewshedFunction(continuousFieldFunction, _viewshedParameters);
                var discreteViewshed = viewshedFunction.ToDiscreteFieldFunction();

                // Create a colormap renderer with visible/not-visible colors.
                var colors = new[]
                {
                    Color.Gray,                         // Not visible
                    Color.FromArgb(128, 136, 204, 132)  // Visible (translucent green)
                };
                var colormap = Colormap.Create(colors);
                var colormapRenderer = new ColormapRenderer(colormap);

                // Create a field analysis with the discrete viewshed function and renderer.
                var fieldAnalysis = new FieldAnalysis(discreteViewshed, colormapRenderer);

                // Add the field analysis to the overlay.
                analysisOverlay.Analyses.Add(fieldAnalysis);

                _isInitialized = true;
            }
            catch (Exception ex)
            {
                var dialog = new MessageDialog2(ex.Message, "Error initializing viewshed");
                await dialog.ShowAsync();
            }
        }

        // Start dragging and update the observer position on pointer press.
        private void MyMapView_PointerPressed(object sender, PointerRoutedEventArgs e)
        {
            if (!_isInitialized) return;
            _isDragging = true;
            UpdateObserverFromScreenPoint(e.GetCurrentPoint(MyMapView).Position);
        }

        // Update the observer position as the pointer moves while dragging.
        private void MyMapView_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (!_isDragging || !_isInitialized) return;
            UpdateObserverFromScreenPoint(e.GetCurrentPoint(MyMapView).Position);
        }

        // Finalize observer placement when the pointer is released.
        private void MyMapView_PointerReleased(object sender, PointerRoutedEventArgs e)
        {
            if (!_isDragging || !_isInitialized) return;
            UpdateObserverFromScreenPoint(e.GetCurrentPoint(MyMapView).Position);
            _isDragging = false;
        }

        // Convert a screen point to a map location and update the observer position.
        private void UpdateObserverFromScreenPoint(Windows.Foundation.Point screenPoint)
        {
            MapPoint mapPoint = MyMapView.ScreenToLocation(screenPoint);
            if (mapPoint == null) return;

            SetObserverPosition(mapPoint.X, mapPoint.Y);
        }

        // Update the observer position and viewshed parameters with the new coordinates.
        private void SetObserverPosition(double x, double y)
        {
            _observerPosition = new MapPoint(x, y, _observerElevation, SpatialReferences.WebMercator);
            _viewshedParameters.ObserverPosition = _observerPosition;
            _observerGraphic.Geometry = _observerPosition;
        }

        // Update the observer elevation and reposition the observer when the slider value changes.
        private void OnObserverElevationChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
        {
            if (!_isInitialized || _observerPosition == null) return;
            ObserverElevationValue.Text = $"{ObserverElevationSlider.Value:0} m";
            _observerElevation = e.NewValue;
            SetObserverPosition(_observerPosition.X, _observerPosition.Y);
        }

        // Update the viewshed parameters when slider values change.
        private void OnViewshedParameterChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
        {
            if (!_isInitialized) return;

            TargetHeightValue.Text = $"{TargetHeightSlider.Value:0} m";
            MaxRadiusValue.Text = $"{MaxRadiusSlider.Value:0} m";
            FieldOfViewValue.Text = $"{FieldOfViewSlider.Value:0}\u00b0";
            HeadingValue.Text = $"{HeadingSlider.Value:0}\u00b0";

            _viewshedParameters.TargetHeight = TargetHeightSlider.Value;
            _viewshedParameters.MaxRadius = MaxRadiusSlider.Value;
            _viewshedParameters.FieldOfView = FieldOfViewSlider.Value;
            _viewshedParameters.Heading = HeadingSlider.Value;
        }

        // Update the elevation sampling interval when a radio button is selected.
        private void OnSamplingIntervalChanged(object sender, RoutedEventArgs e)
        {
            if (!_isInitialized) return;

            if (SamplingInterval0Radio.IsChecked == true)
                _viewshedParameters.ElevationSamplingInterval = 0;
            else if (SamplingInterval10Radio.IsChecked == true)
                _viewshedParameters.ElevationSamplingInterval = 10;
            else if (SamplingInterval20Radio.IsChecked == true)
                _viewshedParameters.ElevationSamplingInterval = 20;
        }
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.