View in MAUI WPF WinUI View 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, tap on the map to see the viewshed from the tapped 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 ShowInteractiveViewshedInAnalysisOverlay.xaml ShowInteractiveViewshedInAnalysisOverlay.xaml.cs
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="ArcGIS.Samples.ShowInteractiveViewshedInAnalysisOverlay.ShowInteractiveViewshedInAnalysisOverlay"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:esriUI="clr-namespace:Esri.ArcGISRuntime.Maui;assembly=Esri.ArcGISRuntime.Maui">
<Grid Style="{DynamicResource EsriSampleContainer}">
<esriUI:MapView x:Name="MyMapView" Style="{DynamicResource EsriSampleGeoView}" />
<Border
MaximumWidthRequest="460"
MinimumWidthRequest="380"
Style="{DynamicResource EsriSampleControlPanel}">
<ScrollView>
<Grid
Padding="8,6"
ColumnDefinitions="Auto,3*,Auto"
ColumnSpacing="6"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto"
RowSpacing="2">
<Label
Grid.Row="0"
Grid.Column="0"
FontAttributes="Bold"
FontSize="12"
LineBreakMode="NoWrap"
Text="Observer Elevation:"
VerticalTextAlignment="Center" />
<Slider
x:Name="ObserverElevationSlider"
Grid.Row="0"
Grid.Column="1"
Maximum="200"
Minimum="2"
ValueChanged="OnObserverElevationChanged"
Value="20" />
<Label
x:Name="ObserverElevationValue"
Grid.Row="0"
Grid.Column="2"
FontSize="12"
HorizontalTextAlignment="End"
Text="20 m"
VerticalTextAlignment="Center"
WidthRequest="55" />
<Label
Grid.Row="1"
Grid.Column="0"
FontAttributes="Bold"
FontSize="12"
LineBreakMode="NoWrap"
Text="Target Height:"
VerticalTextAlignment="Center" />
<Slider
x:Name="TargetHeightSlider"
Grid.Row="1"
Grid.Column="1"
Maximum="1000"
Minimum="2"
ValueChanged="OnViewshedParameterChanged"
Value="20" />
<Label
x:Name="TargetHeightValue"
Grid.Row="1"
Grid.Column="2"
FontSize="12"
HorizontalTextAlignment="End"
Text="20 m"
VerticalTextAlignment="Center"
WidthRequest="55" />
<Label
Grid.Row="2"
Grid.Column="0"
FontAttributes="Bold"
FontSize="12"
LineBreakMode="NoWrap"
Text="Maximum Radius:"
VerticalTextAlignment="Center" />
<Slider
x:Name="MaxRadiusSlider"
Grid.Row="2"
Grid.Column="1"
Maximum="20000"
Minimum="2500"
ValueChanged="OnViewshedParameterChanged"
Value="8000" />
<Label
x:Name="MaxRadiusValue"
Grid.Row="2"
Grid.Column="2"
FontSize="12"
HorizontalTextAlignment="End"
Text="8000 m"
VerticalTextAlignment="Center"
WidthRequest="70" />
<Label
Grid.Row="3"
Grid.Column="0"
FontAttributes="Bold"
FontSize="12"
LineBreakMode="NoWrap"
Text="Field of View:"
VerticalTextAlignment="Center" />
<Slider
x:Name="FieldOfViewSlider"
Grid.Row="3"
Grid.Column="1"
Maximum="360"
Minimum="5"
ValueChanged="OnViewshedParameterChanged"
Value="150" />
<Label
x:Name="FieldOfViewValue"
Grid.Row="3"
Grid.Column="2"
FontSize="12"
HorizontalTextAlignment="End"
Text="150°"
VerticalTextAlignment="Center"
WidthRequest="55" />
<Label
Grid.Row="4"
Grid.Column="0"
FontAttributes="Bold"
FontSize="12"
LineBreakMode="NoWrap"
Text="Heading:"
VerticalTextAlignment="Center" />
<Slider
x:Name="HeadingSlider"
Grid.Row="4"
Grid.Column="1"
Maximum="360"
Minimum="0"
ValueChanged="OnViewshedParameterChanged"
Value="10" />
<Label
x:Name="HeadingValue"
Grid.Row="4"
Grid.Column="2"
FontSize="12"
HorizontalTextAlignment="End"
Text="10°"
VerticalTextAlignment="Center"
WidthRequest="55" />
<Label
Grid.Row="5"
Grid.Column="0"
Grid.ColumnSpan="3"
FontAttributes="Bold"
FontSize="12"
Text="Elevation Sampling Interval (m):"
VerticalTextAlignment="Center" />
<HorizontalStackLayout
Grid.Row="6"
Grid.Column="0"
Grid.ColumnSpan="3"
Spacing="12">
<RadioButton
x:Name="SamplingInterval0Radio"
CheckedChanged="OnSamplingIntervalChanged"
Content="0"
IsChecked="True" />
<RadioButton
x:Name="SamplingInterval10Radio"
CheckedChanged="OnSamplingIntervalChanged"
Content="10" />
<RadioButton
x:Name="SamplingInterval20Radio"
CheckedChanged="OnSamplingIntervalChanged"
Content="20" />
</HorizontalStackLayout>
</Grid>
</ScrollView>
</Border>
<Label
Grid.RowSpan="2"
Grid.ColumnSpan="2"
Margin="0,0,10,30"
FontAttributes="Italic"
FontSize="11"
HorizontalOptions="End"
Text="Raster data Copyright Scottish Government and SEPA (2014)"
TextColor="White"
VerticalOptions="End" />
</Grid>
</ContentPage>