Skip to content

Commit c370713

Browse files
committed
new sample: show interactive viewshed in analysis overlay
1 parent 271b5af commit c370713

19 files changed

Lines changed: 1468 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ContentPage x:Class="ArcGIS.Samples.ShowInteractiveViewshedInAnalysisOverlay.ShowInteractiveViewshedInAnalysisOverlay"
3+
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
5+
xmlns:esriUI="clr-namespace:Esri.ArcGISRuntime.Maui;assembly=Esri.ArcGISRuntime.Maui">
6+
<Grid Style="{DynamicResource EsriSampleContainer}">
7+
<esriUI:MapView x:Name="MyMapView" Style="{DynamicResource EsriSampleGeoView}" />
8+
<Border Style="{DynamicResource EsriSampleControlPanel}"
9+
MinimumWidthRequest="350">
10+
<ScrollView>
11+
<Grid ColumnDefinitions="Auto,*,Auto"
12+
ColumnSpacing="5"
13+
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto"
14+
RowSpacing="10">
15+
<Label Grid.Row="0"
16+
Grid.Column="0"
17+
HorizontalTextAlignment="End"
18+
VerticalTextAlignment="Center">
19+
Observer Elevation
20+
</Label>
21+
<Slider x:Name="ObserverElevationSlider"
22+
Grid.Row="0"
23+
Grid.Column="1"
24+
Minimum="2"
25+
Maximum="200"
26+
MaximumTrackColor="CadetBlue"
27+
MinimumTrackColor="CadetBlue"
28+
Value="20"
29+
ValueChanged="OnObserverElevationChanged" />
30+
<Label x:Name="ObserverElevationValue"
31+
Grid.Row="0"
32+
Grid.Column="2"
33+
VerticalTextAlignment="Center"
34+
Text="20 m" />
35+
36+
<Label Grid.Row="1"
37+
Grid.Column="0"
38+
HorizontalTextAlignment="End"
39+
VerticalTextAlignment="Center">
40+
Target Height
41+
</Label>
42+
<Slider x:Name="TargetHeightSlider"
43+
Grid.Row="1"
44+
Grid.Column="1"
45+
Minimum="2"
46+
Maximum="1000"
47+
MaximumTrackColor="CadetBlue"
48+
MinimumTrackColor="CadetBlue"
49+
Value="20"
50+
ValueChanged="OnViewshedParameterChanged" />
51+
<Label x:Name="TargetHeightValue"
52+
Grid.Row="1"
53+
Grid.Column="2"
54+
VerticalTextAlignment="Center"
55+
Text="20 m" />
56+
57+
<Label Grid.Row="2"
58+
Grid.Column="0"
59+
HorizontalTextAlignment="End"
60+
VerticalTextAlignment="Center">
61+
Maximum Radius
62+
</Label>
63+
<Slider x:Name="MaxRadiusSlider"
64+
Grid.Row="2"
65+
Grid.Column="1"
66+
Minimum="2500"
67+
Maximum="20000"
68+
MaximumTrackColor="CadetBlue"
69+
MinimumTrackColor="CadetBlue"
70+
Value="8000"
71+
ValueChanged="OnViewshedParameterChanged" />
72+
<Label x:Name="MaxRadiusValue"
73+
Grid.Row="2"
74+
Grid.Column="2"
75+
VerticalTextAlignment="Center"
76+
Text="8000 m" />
77+
78+
<Label Grid.Row="3"
79+
Grid.Column="0"
80+
HorizontalTextAlignment="End"
81+
VerticalTextAlignment="Center">
82+
Field of View
83+
</Label>
84+
<Slider x:Name="FieldOfViewSlider"
85+
Grid.Row="3"
86+
Grid.Column="1"
87+
Minimum="5"
88+
Maximum="360"
89+
MaximumTrackColor="CadetBlue"
90+
MinimumTrackColor="CadetBlue"
91+
Value="150"
92+
ValueChanged="OnViewshedParameterChanged" />
93+
<Label x:Name="FieldOfViewValue"
94+
Grid.Row="3"
95+
Grid.Column="2"
96+
VerticalTextAlignment="Center"
97+
Text="150°" />
98+
99+
<Label Grid.Row="4"
100+
Grid.Column="0"
101+
HorizontalTextAlignment="End"
102+
VerticalTextAlignment="Center">
103+
Heading
104+
</Label>
105+
<Slider x:Name="HeadingSlider"
106+
Grid.Row="4"
107+
Grid.Column="1"
108+
Minimum="0"
109+
Maximum="360"
110+
MaximumTrackColor="CadetBlue"
111+
MinimumTrackColor="CadetBlue"
112+
Value="10"
113+
ValueChanged="OnViewshedParameterChanged" />
114+
<Label x:Name="HeadingValue"
115+
Grid.Row="4"
116+
Grid.Column="2"
117+
VerticalTextAlignment="Center"
118+
Text="10°" />
119+
120+
<Label Grid.Row="5"
121+
Grid.Column="0"
122+
Grid.ColumnSpan="3"
123+
VerticalTextAlignment="Center">
124+
Elevation Sampling Interval (m)
125+
</Label>
126+
<HorizontalStackLayout Grid.Row="6"
127+
Grid.Column="0"
128+
Grid.ColumnSpan="3"
129+
Spacing="5">
130+
<RadioButton x:Name="SamplingInterval0Radio"
131+
Content="0"
132+
IsChecked="True"
133+
CheckedChanged="OnSamplingIntervalChanged" />
134+
<RadioButton x:Name="SamplingInterval10Radio"
135+
Content="10"
136+
CheckedChanged="OnSamplingIntervalChanged" />
137+
<RadioButton x:Name="SamplingInterval20Radio"
138+
Content="20"
139+
CheckedChanged="OnSamplingIntervalChanged" />
140+
</HorizontalStackLayout>
141+
</Grid>
142+
</ScrollView>
143+
</Border>
144+
<Label Text="Raster data Copyright Scottish Government and SEPA (2014)"
145+
Grid.RowSpan="2"
146+
Grid.ColumnSpan="2"
147+
HorizontalOptions="End"
148+
VerticalOptions="End"
149+
Margin="0,0,10,30"
150+
FontSize="11"
151+
FontAttributes="Italic"
152+
TextColor="White" />
153+
</Grid>
154+
</ContentPage>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2026 Esri.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
5+
//
6+
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
7+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
8+
// language governing permissions and limitations under the License.
9+
10+
using ArcGIS.Samples.Managers;
11+
using Esri.ArcGISRuntime.Analysis;
12+
using Esri.ArcGISRuntime.Analysis.Visibility;
13+
using Esri.ArcGISRuntime.Geometry;
14+
using Esri.ArcGISRuntime.Mapping;
15+
using Esri.ArcGISRuntime.Rasters;
16+
using Esri.ArcGISRuntime.Symbology;
17+
using Esri.ArcGISRuntime.UI;
18+
using Esri.ArcGISRuntime.UI.GeoAnalysis;
19+
using Colors = System.Drawing.Color;
20+
using GeoViewInputEventArgs = Esri.ArcGISRuntime.Maui.GeoViewInputEventArgs;
21+
22+
namespace ArcGIS.Samples.ShowInteractiveViewshedInAnalysisOverlay
23+
{
24+
[ArcGIS.Samples.Shared.Attributes.Sample(
25+
name: "Show interactive viewshed with analysis overlay",
26+
category: "Analysis",
27+
description: "Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position.",
28+
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, 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.",
29+
tags: new[] { "analysis overlay", "elevation", "field analysis", "interactive", "raster", "spatial analysis", "terrain", "viewshed", "visibility" })]
30+
[ArcGIS.Samples.Shared.Attributes.OfflineData("aa97788593e34a32bcaae33947fdc271")]
31+
public partial class ShowInteractiveViewshedInAnalysisOverlay : ContentPage
32+
{
33+
private ViewshedParameters _viewshedParameters;
34+
private ViewshedFunction _viewshedFunction;
35+
private GraphicsOverlay _graphicsOverlay;
36+
private Graphic _observerGraphic;
37+
private MapPoint _observerPosition;
38+
private bool _isInitialized;
39+
private double _observerElevation = 20.0;
40+
41+
private readonly SimpleMarkerSymbol _observerSymbol = new SimpleMarkerSymbol(
42+
SimpleMarkerSymbolStyle.Circle, Colors.FromArgb(255, 0, 94, 255), 10);
43+
44+
public ShowInteractiveViewshedInAnalysisOverlay()
45+
{
46+
InitializeComponent();
47+
_ = Initialize();
48+
}
49+
50+
private async Task Initialize()
51+
{
52+
// Create a map with the imagery basemap style.
53+
MyMapView.Map = new Map(BasemapStyle.ArcGISImagery)
54+
{
55+
InitialViewpoint = new Viewpoint(55.610000, -5.200346, 50000)
56+
};
57+
58+
// Disable panning to allow tap-and-drag interaction for observer placement.
59+
MyMapView.InteractionOptions = new MapViewInteractionOptions { IsPanEnabled = false };
60+
61+
// Subscribe to tap events for moving the observer.
62+
MyMapView.GeoViewTapped += MyMapView_GeoViewTapped;
63+
64+
// Create and add a graphics overlay for the observer marker.
65+
_graphicsOverlay = new GraphicsOverlay();
66+
MyMapView.GraphicsOverlays.Add(_graphicsOverlay);
67+
68+
// Create and add an analysis overlay for the viewshed.
69+
var analysisOverlay = new AnalysisOverlay();
70+
MyMapView.AnalysisOverlays.Add(analysisOverlay);
71+
72+
try
73+
{
74+
// Get the path to the locally stored elevation raster file.
75+
string rasterPath = DataManager.GetDataFolder("aa97788593e34a32bcaae33947fdc271", "arran.tif");
76+
77+
// Create a continuous field from the elevation raster file.
78+
var continuousField = await ContinuousField.CreateAsync(new[] { rasterPath }, 0);
79+
80+
// Set the initial observer position over the Isle of Arran.
81+
_observerPosition = new MapPoint(-579246.504, 7479619.947, _observerElevation, SpatialReferences.WebMercator);
82+
83+
// Add the observer graphic.
84+
_observerGraphic = new Graphic(_observerPosition, _observerSymbol);
85+
_graphicsOverlay.Graphics.Add(_observerGraphic);
86+
87+
// Configure the viewshed parameters.
88+
_viewshedParameters = new ViewshedParameters
89+
{
90+
ObserverPosition = _observerPosition,
91+
TargetHeight = 20.0,
92+
MaxRadius = 8000,
93+
FieldOfView = 150,
94+
Heading = 10,
95+
ElevationSamplingInterval = 0
96+
};
97+
98+
// Create a ContinuousFieldFunction from the continuous field.
99+
var continuousFieldFunction = ContinuousFieldFunction.Create(continuousField);
100+
101+
// Create a ViewshedFunction and convert to a DiscreteFieldFunction for visible/not-visible classification.
102+
_viewshedFunction = new ViewshedFunction(continuousFieldFunction, _viewshedParameters);
103+
var discreteViewshed = _viewshedFunction.ToDiscreteFieldFunction();
104+
105+
// Create a colormap renderer with visible/not-visible colors.
106+
var colors = new[]
107+
{
108+
Colors.Gray, // Not visible
109+
Colors.FromArgb(128, 136, 204, 132) // Visible (translucent green)
110+
};
111+
var colormap = Colormap.Create(colors);
112+
var colormapRenderer = new ColormapRenderer(colormap);
113+
114+
// Create a field analysis with the discrete viewshed function and renderer.
115+
var fieldAnalysis = new FieldAnalysis(discreteViewshed, colormapRenderer);
116+
117+
// Add the field analysis to the overlay.
118+
analysisOverlay.Analyses.Add(fieldAnalysis);
119+
120+
_isInitialized = true;
121+
122+
// Clean up event handlers when the sample is unloaded.
123+
Unloaded += SampleUnloaded;
124+
}
125+
catch (Exception ex)
126+
{
127+
await Application.Current.Windows[0].Page.DisplayAlert("Error initializing viewshed", ex.Message, "OK");
128+
}
129+
}
130+
131+
private void SampleUnloaded(object sender, EventArgs e)
132+
{
133+
MyMapView.GeoViewTapped -= MyMapView_GeoViewTapped;
134+
}
135+
136+
// Update the observer position when the user taps on the map.
137+
private void MyMapView_GeoViewTapped(object sender, GeoViewInputEventArgs e)
138+
{
139+
if (!_isInitialized || e.Location == null) return;
140+
141+
SetObserverPosition(e.Location.X, e.Location.Y);
142+
}
143+
144+
// Update the observer position and viewshed parameters with the new coordinates.
145+
private void SetObserverPosition(double x, double y)
146+
{
147+
_observerPosition = new MapPoint(x, y, _observerElevation, SpatialReferences.WebMercator);
148+
_viewshedParameters.ObserverPosition = _observerPosition;
149+
_observerGraphic.Geometry = _observerPosition;
150+
}
151+
152+
// Update the observer elevation and reposition the observer when the slider value changes.
153+
private void OnObserverElevationChanged(object sender, ValueChangedEventArgs e)
154+
{
155+
ObserverElevationValue.Text = $"{ObserverElevationSlider.Value:0} m";
156+
if (!_isInitialized || _observerPosition == null) return;
157+
_observerElevation = e.NewValue;
158+
SetObserverPosition(_observerPosition.X, _observerPosition.Y);
159+
}
160+
161+
// Update the viewshed parameters when slider values change.
162+
private void OnViewshedParameterChanged(object sender, ValueChangedEventArgs e)
163+
{
164+
TargetHeightValue.Text = $"{TargetHeightSlider.Value:0} m";
165+
MaxRadiusValue.Text = $"{MaxRadiusSlider.Value:0} m";
166+
FieldOfViewValue.Text = $"{FieldOfViewSlider.Value:0}\u00b0";
167+
HeadingValue.Text = $"{HeadingSlider.Value:0}\u00b0";
168+
169+
if (!_isInitialized) return;
170+
171+
_viewshedParameters.TargetHeight = TargetHeightSlider.Value;
172+
_viewshedParameters.MaxRadius = MaxRadiusSlider.Value;
173+
_viewshedParameters.FieldOfView = FieldOfViewSlider.Value;
174+
_viewshedParameters.Heading = HeadingSlider.Value;
175+
}
176+
177+
// Update the elevation sampling interval when a radio button is selected.
178+
private void OnSamplingIntervalChanged(object sender, CheckedChangedEventArgs e)
179+
{
180+
if (!_isInitialized || !e.Value) return;
181+
182+
if (SamplingInterval0Radio.IsChecked)
183+
_viewshedParameters.ElevationSamplingInterval = 0;
184+
else if (SamplingInterval10Radio.IsChecked)
185+
_viewshedParameters.ElevationSamplingInterval = 10;
186+
else if (SamplingInterval20Radio.IsChecked)
187+
_viewshedParameters.ElevationSamplingInterval = 20;
188+
}
189+
}
190+
}

0 commit comments

Comments
 (0)