diff --git a/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml new file mode 100644 index 0000000000..57684ea75d --- /dev/null +++ b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs new file mode 100644 index 0000000000..43bf0036a5 --- /dev/null +++ b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs @@ -0,0 +1,248 @@ +// 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 Colors = System.Drawing.Color; +using GeoViewInputEventArgs = Esri.ArcGISRuntime.Maui.GeoViewInputEventArgs; +using Point = Microsoft.Maui.Graphics.Point; + +namespace ArcGIS.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, 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.", + tags: new[] { "analysis overlay", "elevation", "field analysis", "interactive", "raster", "spatial analysis", "terrain", "viewshed", "visibility" })] + [ArcGIS.Samples.Shared.Attributes.OfflineData("aa97788593e34a32bcaae33947fdc271")] + public partial class ShowInteractiveViewshedInAnalysisOverlay : ContentPage + { + private ViewshedParameters _viewshedParameters; + private Graphic _observerGraphic; + private MapPoint _observerPosition; + private PointerGestureRecognizer _pointerGesture; + private bool _isInitialized; + private bool _isDragging; + private double _observerElevation = 20.0; + + private readonly SimpleMarkerSymbol _observerSymbol = new SimpleMarkerSymbol( + SimpleMarkerSymbolStyle.Circle, Colors.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 so pointer drags are used solely for placing the observer. + MyMapView.InteractionOptions = new MapViewInteractionOptions { IsPanEnabled = false }; + + // Subscribe to tap events for moving the observer (touch fallback). + MyMapView.GeoViewTapped += MyMapView_GeoViewTapped; + + // Add a pointer gesture recognizer for click-and-drag observer placement on desktop. + _pointerGesture = new PointerGestureRecognizer(); + _pointerGesture.PointerPressed += MapView_PointerPressed; + _pointerGesture.PointerMoved += MapView_PointerMoved; + _pointerGesture.PointerReleased += MapView_PointerReleased; + _pointerGesture.PointerExited += MapView_PointerExited; + MyMapView.GestureRecognizers.Add(_pointerGesture); + + // 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[] + { + Colors.Gray, // Not visible + Colors.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; + + // Clean up event handlers when the sample is unloaded. + Unloaded += SampleUnloaded; + } + catch (Exception ex) + { + await Application.Current.Windows[0].Page.DisplayAlert("Error initializing viewshed", ex.Message, "OK"); + } + } + + private void SampleUnloaded(object sender, EventArgs e) + { + MyMapView.GeoViewTapped -= MyMapView_GeoViewTapped; + + if (_pointerGesture != null) + { + _pointerGesture.PointerPressed -= MapView_PointerPressed; + _pointerGesture.PointerMoved -= MapView_PointerMoved; + _pointerGesture.PointerReleased -= MapView_PointerReleased; + _pointerGesture.PointerExited -= MapView_PointerExited; + MyMapView.GestureRecognizers.Remove(_pointerGesture); + _pointerGesture = null; + } + } + + // Update the observer position when the user taps on the map. + private void MyMapView_GeoViewTapped(object sender, GeoViewInputEventArgs e) + { + if (!_isInitialized || e.Location == null) return; + + SetObserverPosition(e.Location.X, e.Location.Y); + } + + // Begin dragging and place the observer at the pressed location. + private void MapView_PointerPressed(object sender, PointerEventArgs e) + { + if (!_isInitialized) return; + _isDragging = true; + UpdateObserverFromPointer(e); + } + + // Update the observer position as the pointer moves while dragging. + private void MapView_PointerMoved(object sender, PointerEventArgs e) + { + if (!_isInitialized || !_isDragging) return; + UpdateObserverFromPointer(e); + } + + // Finish dragging when the pointer is released. + private void MapView_PointerReleased(object sender, PointerEventArgs e) + { + _isDragging = false; + } + + // Stop dragging if the pointer leaves the map view. + private void MapView_PointerExited(object sender, PointerEventArgs e) + { + _isDragging = false; + } + + // Convert the pointer's screen position to a map location and update the observer. + private void UpdateObserverFromPointer(PointerEventArgs e) + { + Point? screenPoint = e.GetPosition(MyMapView); + if (screenPoint is null) return; + + MapPoint mapPoint = MyMapView.ScreenToLocation(screenPoint.Value); + if (mapPoint == null || double.IsNaN(mapPoint.X) || double.IsNaN(mapPoint.Y)) 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, ValueChangedEventArgs 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, ValueChangedEventArgs 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, CheckedChangedEventArgs e) + { + if (!_isInitialized || !e.Value) return; + + if (SamplingInterval0Radio.IsChecked) + _viewshedParameters.ElevationSamplingInterval = 0; + else if (SamplingInterval10Radio.IsChecked) + _viewshedParameters.ElevationSamplingInterval = 10; + else if (SamplingInterval20Radio.IsChecked) + _viewshedParameters.ElevationSamplingInterval = 20; + } + } +} \ No newline at end of file diff --git a/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md new file mode 100644 index 0000000000..245d9e8698 --- /dev/null +++ b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md @@ -0,0 +1,47 @@ +# Show interactive viewshed with analysis overlay + +Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position. + +![Show interactive viewshed with analysis overlay sample](showinteractiveviewshedinanalysisoverlay.jpg) + +## 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](https://www.arcgis.com/home/item.html?id=aa97788593e34a32bcaae33947fdc271) +(Data Copyright Scottish Government and SEPA (2014)). + +## Tags + +analysis overlay, elevation, field analysis, interactive, raster, spatial analysis, terrain, viewshed, visibility diff --git a/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json new file mode 100644 index 0000000000..663f4b9d60 --- /dev/null +++ b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json @@ -0,0 +1,38 @@ +{ + "category": "Analysis", + "description": "Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position.", + "formal_name": "ShowInteractiveViewshedInAnalysisOverlay", + "ignore": false, + "images": [ + "showinteractiveviewshedinanalysisoverlay.jpg" + ], + "keywords": [ + "analysis overlay", + "elevation", + "field analysis", + "interactive", + "raster", + "spatial analysis", + "terrain", + "viewshed", + "visibility" + ], + "offline_data": [], + "redirect_from": [], + "relevant_apis": [ + "AnalysisOverlay", + "Colormap", + "ColormapRenderer", + "ContinuousField", + "ContinuousFieldFunction", + "DiscreteFieldFunction", + "FieldAnalysis", + "ViewshedFunction", + "ViewshedParameters" + ], + "snippets": [ + "ShowInteractiveViewshedInAnalysisOverlay.xaml.cs", + "ShowInteractiveViewshedInAnalysisOverlay.xaml" + ], + "title": "Show interactive viewshed with analysis overlay" +} \ No newline at end of file diff --git a/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/showinteractiveviewshedinanalysisoverlay.jpg b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/showinteractiveviewshedinanalysisoverlay.jpg new file mode 100644 index 0000000000..e2b04b1e87 Binary files /dev/null and b/src/MAUI/Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/showinteractiveviewshedinanalysisoverlay.jpg differ diff --git a/src/MAUI/readme.md b/src/MAUI/readme.md index f136bea081..06e8bd256f 100644 --- a/src/MAUI/readme.md +++ b/src/MAUI/readme.md @@ -10,6 +10,7 @@ * [Show exploratory viewshed from camera in scene](Maui.Samples/Samples/Analysis/ShowExploratoryViewshedFromCameraInScene) - Analyze the exploratory viewshed for a camera showing the visible and obstructed areas from an observer's vantage point. * [Show exploratory viewshed from geoelement in scene](Maui.Samples/Samples/Analysis/ShowExploratoryViewshedFromGeoelementInScene) - Analyze the exploratory viewshed for an object (GeoElement) in a scene. * [Show exploratory viewshed from point in scene](Maui.Samples/Samples/Analysis/ShowExploratoryViewshedFromPointInScene) - Perform an exploratory viewshed analysis from a defined vantage point. +* [Show interactive viewshed with analysis overlay](Maui.Samples/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay) - Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position. ## Data diff --git a/src/Samples.Shared/Resources/FeaturedSamples.xml b/src/Samples.Shared/Resources/FeaturedSamples.xml index 15d646d3ca..9d57a124e2 100644 --- a/src/Samples.Shared/Resources/FeaturedSamples.xml +++ b/src/Samples.Shared/Resources/FeaturedSamples.xml @@ -4,6 +4,7 @@ ApplyMapAlgebra DistanceMeasurement + ShowInteractiveViewshedInAnalysisOverlay CreateKmlMultiTrack diff --git a/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.jpg b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.jpg new file mode 100644 index 0000000000..5f666f40fc Binary files /dev/null and b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.jpg differ diff --git a/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml new file mode 100644 index 0000000000..4f876406a8 --- /dev/null +++ b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs new file mode 100644 index 0000000000..35e828687a --- /dev/null +++ b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs @@ -0,0 +1,294 @@ +// 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 System; +using System.Drawing; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using Point = System.Windows.Point; + +namespace ArcGIS.WPF.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; + + // Clean up event handlers when the sample is unloaded. + Unloaded += SampleUnloaded; + + // Register mouse events for click-and-drag observer placement. + MyMapView.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent, + new MouseButtonEventHandler(MyMapView_MouseLeftButtonDown), true); + MyMapView.AddHandler(UIElement.PreviewMouseMoveEvent, + new MouseEventHandler(MyMapView_MouseMove), true); + MyMapView.AddHandler(UIElement.PreviewMouseLeftButtonUpEvent, + new MouseButtonEventHandler(MyMapView_MouseLeftButtonUp), true); + MyMapView.MouseLeave += MyMapView_MouseLeave; + MyMapView.LostMouseCapture += MyMapView_LostMouseCapture; + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error initializing viewshed"); + } + } + + #region Mouse interaction handlers + + // Start dragging and capture mouse to track movement outside the control. + private void MyMapView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (!_isInitialized) return; + _isDragging = true; + MyMapView.CaptureMouse(); + UpdateObserverFromScreenPoint(e.GetPosition(MyMapView)); + e.Handled = true; + } + + // Update the observer position as the mouse moves while dragging. + private void MyMapView_MouseMove(object sender, MouseEventArgs e) + { + if (!_isDragging || !_isInitialized) return; + + if (e.LeftButton != MouseButtonState.Pressed) + { + ResetDragState(); + return; + } + + UpdateObserverFromScreenPoint(e.GetPosition(MyMapView)); + } + + // Finalize observer placement when the mouse button is released. + private void MyMapView_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (!_isInitialized) return; + + if (_isDragging) + { + UpdateObserverFromScreenPoint(e.GetPosition(MyMapView)); + } + + ResetDragState(); + } + + // End dragging if the mouse leaves the map view. + private void MyMapView_MouseLeave(object sender, MouseEventArgs e) + { + if (!_isDragging || !_isInitialized) return; + UpdateObserverFromScreenPoint(e.GetPosition(MyMapView)); + ResetDragState(); + } + + // Reset the drag flag if mouse capture is lost. + private void MyMapView_LostMouseCapture(object sender, MouseEventArgs e) + { + _isDragging = false; + } + + // Stop dragging and release the mouse capture. + private void ResetDragState() + { + _isDragging = false; + if (MyMapView.IsMouseCaptured) + { + MyMapView.ReleaseMouseCapture(); + } + } + + #endregion Mouse interaction handlers + + #region Observer position + + // Convert a screen point to a map location and update the observer position. + private void UpdateObserverFromScreenPoint(Point screenPoint) + { + MapPoint mapPoint = MyMapView.ScreenToLocation(screenPoint); + if (mapPoint == null) return; + if (double.IsNaN(mapPoint.X) || double.IsNaN(mapPoint.Y)) 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; + } + + #endregion Observer position + + #region Parameter change handlers + + // Update the observer elevation and reposition the observer when the slider value changes. + private void OnObserverElevationChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (!_isInitialized || _observerPosition == null) return; + _observerElevation = e.NewValue; + SetObserverPosition(_observerPosition.X, _observerPosition.Y); + } + + // Update the target height viewshed parameter when the slider value changes. + private void OnTargetHeightChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (!_isInitialized) return; + _viewshedParameters.TargetHeight = e.NewValue; + } + + // Update the maximum radius viewshed parameter when the slider value changes. + private void OnMaxRadiusChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (!_isInitialized) return; + _viewshedParameters.MaxRadius = e.NewValue; + } + + // Update the field of view viewshed parameter when the slider value changes. + private void OnFieldOfViewChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (!_isInitialized) return; + _viewshedParameters.FieldOfView = e.NewValue; + } + + // Update the heading viewshed parameter when the slider value changes. + private void OnHeadingChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (!_isInitialized) return; + _viewshedParameters.Heading = e.NewValue; + } + + #endregion Parameter change handlers + + private void SampleUnloaded(object sender, RoutedEventArgs e) + { + MyMapView.RemoveHandler(UIElement.PreviewMouseLeftButtonDownEvent, + new MouseButtonEventHandler(MyMapView_MouseLeftButtonDown)); + MyMapView.RemoveHandler(UIElement.PreviewMouseMoveEvent, + new MouseEventHandler(MyMapView_MouseMove)); + MyMapView.RemoveHandler(UIElement.PreviewMouseLeftButtonUpEvent, + new MouseButtonEventHandler(MyMapView_MouseLeftButtonUp)); + MyMapView.MouseLeave -= MyMapView_MouseLeave; + MyMapView.LostMouseCapture -= MyMapView_LostMouseCapture; + } + + // 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; + } + } +} \ No newline at end of file diff --git a/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md new file mode 100644 index 0000000000..9124e4acea --- /dev/null +++ b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md @@ -0,0 +1,47 @@ +# Show interactive viewshed with analysis overlay + +Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position. + +![Show interactive viewshed with analysis overlay sample](ShowInteractiveViewshedInAnalysisOverlay.jpg) + +## 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](https://www.arcgis.com/home/item.html?id=aa97788593e34a32bcaae33947fdc271) +(Data Copyright Scottish Government and SEPA (2014)). + +## Tags + +analysis overlay, elevation, field analysis, interactive, raster, spatial analysis, terrain, viewshed, visibility diff --git a/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json new file mode 100644 index 0000000000..81ea914561 --- /dev/null +++ b/src/WPF/WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json @@ -0,0 +1,38 @@ +{ + "category": "Analysis", + "description": "Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position.", + "formal_name": "ShowInteractiveViewshedInAnalysisOverlay", + "ignore": false, + "images": [ + "ShowInteractiveViewshedInAnalysisOverlay.jpg" + ], + "keywords": [ + "analysis overlay", + "elevation", + "field analysis", + "interactive", + "raster", + "spatial analysis", + "terrain", + "viewshed", + "visibility" + ], + "offline_data": [], + "redirect_from": [], + "relevant_apis": [ + "AnalysisOverlay", + "Colormap", + "ColormapRenderer", + "ContinuousField", + "ContinuousFieldFunction", + "DiscreteFieldFunction", + "FieldAnalysis", + "ViewshedFunction", + "ViewshedParameters" + ], + "snippets": [ + "ShowInteractiveViewshedInAnalysisOverlay.xaml.cs", + "ShowInteractiveViewshedInAnalysisOverlay.xaml" + ], + "title": "Show interactive viewshed with analysis overlay" +} \ No newline at end of file diff --git a/src/WPF/readme.md b/src/WPF/readme.md index 39bdd7544a..970249f48e 100644 --- a/src/WPF/readme.md +++ b/src/WPF/readme.md @@ -10,6 +10,7 @@ * [Show exploratory viewshed from camera in scene](WPF.Viewer/Samples/Analysis/ShowExploratoryViewshedFromCameraInScene) - Analyze the exploratory viewshed for a camera showing the visible and obstructed areas from an observer's vantage point. * [Show exploratory viewshed from geoelement in scene](WPF.Viewer/Samples/Analysis/ShowExploratoryViewshedFromGeoelementInScene) - Analyze the exploratory viewshed for an object (GeoElement) in a scene. * [Show exploratory viewshed from point in scene](WPF.Viewer/Samples/Analysis/ShowExploratoryViewshedFromPointInScene) - Perform an exploratory viewshed analysis from a defined vantage point. +* [Show interactive viewshed with analysis overlay](WPF.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay) - Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position. ## Data diff --git a/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.jpg b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.jpg new file mode 100644 index 0000000000..af555893cb Binary files /dev/null and b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.jpg differ diff --git a/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml new file mode 100644 index 0000000000..810f1d02ac --- /dev/null +++ b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs new file mode 100644 index 0000000000..21d72371b1 --- /dev/null +++ b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/ShowInteractiveViewshedInAnalysisOverlay.xaml.cs @@ -0,0 +1,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; + } + } +} diff --git a/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md new file mode 100644 index 0000000000..9124e4acea --- /dev/null +++ b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.md @@ -0,0 +1,47 @@ +# Show interactive viewshed with analysis overlay + +Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position. + +![Show interactive viewshed with analysis overlay sample](ShowInteractiveViewshedInAnalysisOverlay.jpg) + +## 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](https://www.arcgis.com/home/item.html?id=aa97788593e34a32bcaae33947fdc271) +(Data Copyright Scottish Government and SEPA (2014)). + +## Tags + +analysis overlay, elevation, field analysis, interactive, raster, spatial analysis, terrain, viewshed, visibility diff --git a/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json new file mode 100644 index 0000000000..81ea914561 --- /dev/null +++ b/src/WinUI/ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay/readme.metadata.json @@ -0,0 +1,38 @@ +{ + "category": "Analysis", + "description": "Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position.", + "formal_name": "ShowInteractiveViewshedInAnalysisOverlay", + "ignore": false, + "images": [ + "ShowInteractiveViewshedInAnalysisOverlay.jpg" + ], + "keywords": [ + "analysis overlay", + "elevation", + "field analysis", + "interactive", + "raster", + "spatial analysis", + "terrain", + "viewshed", + "visibility" + ], + "offline_data": [], + "redirect_from": [], + "relevant_apis": [ + "AnalysisOverlay", + "Colormap", + "ColormapRenderer", + "ContinuousField", + "ContinuousFieldFunction", + "DiscreteFieldFunction", + "FieldAnalysis", + "ViewshedFunction", + "ViewshedParameters" + ], + "snippets": [ + "ShowInteractiveViewshedInAnalysisOverlay.xaml.cs", + "ShowInteractiveViewshedInAnalysisOverlay.xaml" + ], + "title": "Show interactive viewshed with analysis overlay" +} \ No newline at end of file diff --git a/src/WinUI/readme.md b/src/WinUI/readme.md index 570041adfa..a79732e1b4 100644 --- a/src/WinUI/readme.md +++ b/src/WinUI/readme.md @@ -10,6 +10,7 @@ * [Show exploratory viewshed from camera in scene](ArcGIS.WinUI.Viewer/Samples/Analysis/ShowExploratoryViewshedFromCameraInScene) - Analyze the exploratory viewshed for a camera showing the visible and obstructed areas from an observer's vantage point. * [Show exploratory viewshed from geoelement in scene](ArcGIS.WinUI.Viewer/Samples/Analysis/ShowExploratoryViewshedFromGeoelementInScene) - Analyze the exploratory viewshed for an object (GeoElement) in a scene. * [Show exploratory viewshed from point in scene](ArcGIS.WinUI.Viewer/Samples/Analysis/ShowExploratoryViewshedFromPointInScene) - Perform an exploratory viewshed analysis from a defined vantage point. +* [Show interactive viewshed with analysis overlay](ArcGIS.WinUI.Viewer/Samples/Analysis/ShowInteractiveViewshedInAnalysisOverlay) - Perform an interactive viewshed analysis to determine visible and non-visible areas from a given observer position. ## Data