Click or drag to resize

KmlViewpoint Class

A KML viewpoint contains information about a viewer's position, either defined as a LookAt relationship to another object or a camera. See the guide documentation and samples for information about converting KmlViewpoints into Runtime Viewpoint objects.
Inheritance Hierarchy
SystemObject
  Esri.ArcGISRuntime.OgcKmlViewpoint

Namespace:  Esri.ArcGISRuntime.Ogc
Assembly:  Esri.ArcGISRuntime (in Esri.ArcGISRuntime.dll) Version: 100.7.0.0
Syntax
C#
public sealed class KmlViewpoint

The KmlViewpoint type exposes the following members.

Properties
  NameDescription
Public propertyCode exampleAltitudeMode
The KML altitude mode, which determines how altitude values should be interpreted.
Public propertyCode exampleHeading
Heading in degrees. Ranges from 0 (North) to 360, with 90 being East.
Public propertyCode exampleLocation
The viewpoint's location.
Public propertyCode examplePitch
Angle in degrees between the camera and the Location. 0 means the camera should point straight down towards the Earth. 90 means the camera is looking from/towards the horizon. Values between 90 and 180 indicate the camera should point towards the sky.
Public propertyCode exampleRange
Range is the distance in meters between the Location and the camera. This only applies to 'LookAt' viewpoints.
Public propertyCode exampleRoll
The rotation of the camera around the Z axis. Values range from -180 to 180 degrees.
Public propertyCode exampleType
The viewpoint's type (camera or LookAt). Camera viewpoints define the position of the camera directly. LookAt viewpoints define the position of the camera relative to a point of interest.
Top
Methods
  NameDescription
Public methodStatic memberCreateCameraViewpoint
Creates a KML viewpoint from the provided camera location.
Public methodStatic memberCreateLookAtViewpoint
Creates a KML viewpoint from the provided look-at location.
Public methodStatic memberCreateWithViewpoint
Creates a KML viewpoint from a Viewpoint.
Top
Examples

Android

Example Name: ListKmlContents

List the contents of a KML file. KML files can contain a hierarchy of features, including network links to other KML content.

Code example screen shot.

C#
// Copyright 2018 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 Android.App;
using Android.OS;
using Android.Widget;
using ArcGISRuntime.Samples.Managers;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Ogc;
using Esri.ArcGISRuntime.UI.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using ArcGISRuntime;

namespace ArcGISRuntimeXamarin.Samples.ListKmlContents
{
    [Activity (ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        "List KML contents",
        "Layers",
        "List the contents of a KML file.",
        "")]
    [ArcGISRuntime.Samples.Shared.Attributes.OfflineData("da301cb122874d5497f8a8f6c81eb36e")]
    [ArcGISRuntime.Samples.Shared.Attributes.AndroidLayoutAttribute("ListKmlContents.axml")]
    public class ListKmlContents : Activity
    {
        // Hold references to UI controls.
        private SceneView _mySceneView;
        private ListView _myDisplayList;

        // Hold a list of LayerDisplayVM; this is the ViewModel.
        private readonly ObservableCollection<LayerDisplayVM> _viewModelList = new ObservableCollection<LayerDisplayVM>();

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            Title = "List KML contents";

            CreateLayout();
            Initialize();
        }

        private async void Initialize()
        {
            // Add a basemap.
            _mySceneView.Scene = new Scene(Basemap.CreateImageryWithLabels());
            _mySceneView.Scene.BaseSurface.ElevationSources.Add(new ArcGISTiledElevationSource(new Uri("https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")));

            // Get the URL to the data.
            Uri kmlUrl = new Uri(DataManager.GetDataFolder("da301cb122874d5497f8a8f6c81eb36e", "esri_test_data.kmz"));

            // Create the KML dataset and layer.
            KmlDataset dataset = new KmlDataset(kmlUrl);
            KmlLayer layer = new KmlLayer(dataset);

            // Add the layer to the map.
            _mySceneView.Scene.OperationalLayers.Add(layer);

            try
            {
                await dataset.LoadAsync();

                // Build the ViewModel from the expanded list of layer infos.
                foreach (KmlNode node in dataset.RootNodes)
                {
                    // LayerDisplayVM is a custom type made for this sample to serve as the ViewModel; it is not a part of ArcGIS Runtime.
                    LayerDisplayVM nodeVm = new LayerDisplayVM(node, null);
                    _viewModelList.Add(nodeVm);
                    LayerDisplayVM.BuildLayerInfoList(nodeVm, _viewModelList);
                }

                // Create an array adapter for the content display
                ArrayAdapter adapter = new ArrayAdapter(this, Android.Resource.Layout.SimpleSpinnerItem, _viewModelList.Select(item => item.Name).ToList());

                // Apply the adapter
                _myDisplayList.Adapter = adapter;

                // Subscribe to selection change notifications
                _myDisplayList.ItemClick += MyDisplayList_ItemClick;
            }
            catch (Exception e)
            {
                new AlertDialog.Builder(this).SetMessage(e.ToString()).SetTitle("Error").Show();
            }
        }

        private void MyDisplayList_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
        {
            // Get the KML node.
            LayerDisplayVM selectedItem = _viewModelList[e.Position];

            NavigateToNode(selectedItem.Node);
        }


        private void CreateLayout()
        {
            // Show the layout in the app
            SetContentView(Resource.Layout.ListKmlContents);

            // Get the views
            _myDisplayList = FindViewById<ListView>(Resource.Id.ListKmlContents_ContentList);
            _mySceneView = FindViewById<SceneView>(Resource.Id.ListKmlContents_MySceneView);
        }

        #region viewpoint_conversion

        private async void NavigateToNode(KmlNode node)
        {
            try
            {
                // Get a corrected Runtime viewpoint using the KmlViewpoint.
                bool viewpointNeedsAltitudeAdjustment;
                Viewpoint runtimeViewpoint = ViewpointFromKmlViewpoint(node, out viewpointNeedsAltitudeAdjustment);
                if (viewpointNeedsAltitudeAdjustment)
                {
                    runtimeViewpoint = await GetAltitudeAdjustedViewpointAsync(node, runtimeViewpoint);
                }

                // Set the viewpoint.
                if (runtimeViewpoint != null && !runtimeViewpoint.TargetGeometry.IsEmpty)
                {
                    await _mySceneView.SetViewpointAsync(runtimeViewpoint);
                }
            }
            catch (Exception e)
            {
                new AlertDialog.Builder(this).SetMessage(e.ToString()).SetTitle("Error").Show();
            }
        }

        private Viewpoint ViewpointFromKmlViewpoint(KmlNode node, out bool needsAltitudeFix)
        {
            KmlViewpoint kvp = node.Viewpoint;
            // If KmlViewpoint is specified, use it.
            if (kvp != null)
            {
                // Altitude adjustment is needed for everything except Absolute altitude mode.
                needsAltitudeFix = (kvp.AltitudeMode != KmlAltitudeMode.Absolute);
                switch (kvp.Type)
                {
                    case KmlViewpointType.LookAt:
                        return new Viewpoint(kvp.Location,
                            new Camera(kvp.Location, kvp.Range, kvp.Heading, kvp.Pitch, kvp.Roll));
                    case KmlViewpointType.Camera:
                        return new Viewpoint(kvp.Location,
                            new Camera(kvp.Location, kvp.Heading, kvp.Pitch, kvp.Roll));
                    default:
                        throw new InvalidOperationException("Unexpected KmlViewPointType: " + kvp.Type);
                }
            }

            if (node.Extent != null && !node.Extent.IsEmpty)
            {
                // When no altitude specified, assume elevation should be taken into account.
                needsAltitudeFix = true;

                // Workaround: it's possible for "IsEmpty" to be true but for width/height to still be zero.
                if (node.Extent.Width == 0 && node.Extent.Height == 0)
                {
                    // Defaults based on Google Earth.
                    return new Viewpoint(node.Extent, new Camera(node.Extent.GetCenter(), 1000, 0, 45, 0));
                }
                else
                {
                    Envelope tx = node.Extent;
                    // Add padding on each side.
                    double bufferDistance = Math.Max(node.Extent.Width, node.Extent.Height) / 20;
                    Envelope bufferedExtent = new Envelope(
                        tx.XMin - bufferDistance, tx.YMin - bufferDistance,
                        tx.XMax + bufferDistance, tx.YMax + bufferDistance,
                        tx.ZMin - bufferDistance, tx.ZMax + bufferDistance,
                        SpatialReferences.Wgs84);
                    return new Viewpoint(bufferedExtent);
                }
            }
            else
            {
                // Can't fly to.
                needsAltitudeFix = false;
                return null;
            }
        }

        // Asynchronously adjust the given viewpoint, taking into consideration elevation and KML altitude mode.
        private async Task<Viewpoint> GetAltitudeAdjustedViewpointAsync(KmlNode node, Viewpoint baseViewpoint)
        {
            // Get the altitude mode; assume clamp-to-ground if not specified.
            KmlAltitudeMode altMode = KmlAltitudeMode.ClampToGround;
            if (node.Viewpoint != null)
            {
                altMode = node.Viewpoint.AltitudeMode;
            }

            // If the altitude mode is Absolute, the base viewpoint doesn't need adjustment.
            if (altMode == KmlAltitudeMode.Absolute)
            {
                return baseViewpoint;
            }

            double altitude;
            Envelope lookAtExtent = baseViewpoint.TargetGeometry as Envelope;
            MapPoint lookAtPoint = baseViewpoint.TargetGeometry as MapPoint;

            if (lookAtExtent != null)
            {
                // Get the altitude for the extent.
                try
                {
                    altitude = await _mySceneView.Scene.BaseSurface.GetElevationAsync(lookAtExtent.GetCenter());
                }
                catch (Exception)
                {
                    altitude = 0;
                }

                // Apply elevation adjustment to the geometry.
                Envelope target;
                if (altMode == KmlAltitudeMode.ClampToGround)
                {
                    target = new Envelope(
                        lookAtExtent.XMin, lookAtExtent.YMin,
                        lookAtExtent.XMax, lookAtExtent.YMax,
                        altitude, lookAtExtent.Depth + altitude,
                        lookAtExtent.SpatialReference);
                }
                else
                {
                    target = new Envelope(
                        lookAtExtent.XMin, lookAtExtent.YMin,
                        lookAtExtent.XMax, lookAtExtent.YMax,
                        lookAtExtent.ZMin + altitude, lookAtExtent.ZMax + altitude,
                        lookAtExtent.SpatialReference);
                }

                if (node.Viewpoint != null)
                {
                    // Return adjusted geometry with adjusted camera if a viewpoint was specified on the node.
                    return new Viewpoint(target, baseViewpoint.Camera.Elevate(altitude));
                }
                else
                {
                    // Return adjusted geometry.
                    return new Viewpoint(target);
                }
            }
            else if (lookAtPoint != null)
            {
                // Get the altitude adjustment.
                try
                {
                    altitude = await _mySceneView.Scene.BaseSurface.GetElevationAsync(lookAtPoint);
                }
                catch (Exception)
                {
                    altitude = 0;
                }

                // Apply elevation adjustment to the geometry.
                MapPoint target;
                if (altMode == KmlAltitudeMode.ClampToGround)
                {
                    target = new MapPoint(lookAtPoint.X, lookAtPoint.Y, altitude, lookAtPoint.SpatialReference);
                }
                else
                {
                    target = new MapPoint(
                        lookAtPoint.X, lookAtPoint.Y, lookAtPoint.Z + altitude,
                        lookAtPoint.SpatialReference);
                }

                if (node.Viewpoint != null)
                {
                    // Return adjusted geometry with adjusted camera if a viewpoint was specified on the node.
                    return new Viewpoint(target, baseViewpoint.Camera.Elevate(altitude));
                }
                else
                {
                    // Google Earth defaults: 1000m away and 45-degree tilt.
                    return new Viewpoint(target, new Camera(target, 1000, 0, 45, 0));
                }
            }
            else
            {
                throw new InvalidOperationException("KmlNode has unexpected Geometry for its Extent: " +
                                                    baseViewpoint.TargetGeometry);
            }
        }

        #endregion viewpoint_conversion
    }

    public class LayerDisplayVM
    {
        public KmlNode Node { get; }

        private LayerDisplayVM Parent { get; set; }

        private int NestLevel
        {
            get
            {
                if (Parent == null)
                {
                    return 0;
                }

                return Parent.NestLevel + 1;
            }
        }

        public LayerDisplayVM(KmlNode info, LayerDisplayVM parent)
        {
            Node = info;
            Parent = parent;
        }

        public string Name => new string(' ', NestLevel * 3) + Node.GetType().Name + " - " + Node.Name;

        public static void BuildLayerInfoList(LayerDisplayVM root, IList<LayerDisplayVM> result)
        {
            // Add the root node to the result list.
            result.Add(root);

            // Make the node visible.
            root.Node.IsVisible = true;

            // Recursively add children. KmlContainers and KmlNetworkLinks can both have children.
            var containerNode = root.Node as KmlContainer;
            var networkLinkNode = root.Node as KmlNetworkLink;

            List<KmlNode> children = new List<KmlNode>();
            if (containerNode != null)
            {
                children.AddRange(containerNode.ChildNodes);
            }

            if (networkLinkNode != null)
            {
                children.AddRange(networkLinkNode.ChildNodes);
            }

            foreach (KmlNode node in children)
            {
                // Create the view model for the sublayer.
                LayerDisplayVM layerVM = new LayerDisplayVM(node, root);

                // Recursively add children.
                BuildLayerInfoList(layerVM, result);
            }
        }
    }
}
Examples

Xamarin Forms Android

Example Name: ListKmlContents

List the contents of a KML file. KML files can contain a hierarchy of features, including network links to other KML content.

Code example screen shot.

C#
// Copyright 2018 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 System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using ArcGISRuntime.Samples.Managers;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Ogc;
using Xamarin.Forms;

namespace ArcGISRuntimeXamarin.Samples.ListKmlContents
{
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        "List KML contents",
        "Layers",
        "List the contents of a KML file.",
        "")]
    [ArcGISRuntime.Samples.Shared.Attributes.OfflineData("da301cb122874d5497f8a8f6c81eb36e")]
    public partial class ListKmlContents : ContentPage
    {
        // Hold a list of LayerDisplayVM; this is the ViewModel.
        private readonly ObservableCollection<LayerDisplayVM> _viewModelList = new ObservableCollection<LayerDisplayVM>();

        public ListKmlContents()
        {
            InitializeComponent();
            Initialize();
        }

        private async void Initialize()
        {
            // Add a basemap.
            MySceneView.Scene = new Scene(Basemap.CreateImageryWithLabels());
            MySceneView.Scene.BaseSurface.ElevationSources.Add(new ArcGISTiledElevationSource(new Uri("https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")));

            // Get the URL to the data.
            Uri kmlUrl = new Uri(DataManager.GetDataFolder("da301cb122874d5497f8a8f6c81eb36e", "esri_test_data.kmz"));

            // Create the KML dataset and layer.
            KmlDataset dataset = new KmlDataset(kmlUrl);
            KmlLayer layer = new KmlLayer(dataset);

            // Add the layer to the map.
            MySceneView.Scene.OperationalLayers.Add(layer);

            try
            {
                await dataset.LoadAsync();

                // Build the ViewModel from the expanded list of layer infos.
                foreach (KmlNode node in dataset.RootNodes)
                {
                    // LayerDisplayVM is a custom type made for this sample to serve as the ViewModel; it is not a part of ArcGIS Runtime.
                    LayerDisplayVM nodeVm = new LayerDisplayVM(node, null);
                    _viewModelList.Add(nodeVm);
                    LayerDisplayVM.BuildLayerInfoList(nodeVm, _viewModelList);
                }

                // Update the list of layers, using the root node from the list.
                LayerTreeView.ItemsSource = _viewModelList;
            }
            catch (Exception e)
            {
                await Application.Current.MainPage.DisplayAlert("Error", e.ToString(), "OK");
            }
        }

        private void LayerTreeView_OnSelectionChanged(object sender, SelectedItemChangedEventArgs e)
        {
            // Get the KML node.
            LayerDisplayVM selectedItem = (LayerDisplayVM) e.SelectedItem;

            NavigateToNode(selectedItem.Node);
        }

        #region viewpoint_conversion

        private async void NavigateToNode(KmlNode node)
        {
            try
            {
                // Get a corrected Runtime viewpoint using the KmlViewpoint.
                bool viewpointNeedsAltitudeAdjustment;
                Viewpoint runtimeViewpoint = ViewpointFromKmlViewpoint(node, out viewpointNeedsAltitudeAdjustment);
                if (viewpointNeedsAltitudeAdjustment)
                {
                    runtimeViewpoint = await GetAltitudeAdjustedViewpointAsync(node, runtimeViewpoint);
                }

                // Set the viewpoint.
                if (runtimeViewpoint != null && !runtimeViewpoint.TargetGeometry.IsEmpty)
                {
                    await MySceneView.SetViewpointAsync(runtimeViewpoint);
                }
            }
            catch (Exception e)
            {
                await Application.Current.MainPage.DisplayAlert("Error", e.ToString(), "OK");
            }
        }

        private Viewpoint ViewpointFromKmlViewpoint(KmlNode node, out bool needsAltitudeFix)
        {
            KmlViewpoint kvp = node.Viewpoint;
            // If KmlViewpoint is specified, use it.
            if (kvp != null)
            {
                // Altitude adjustment is needed for everything except Absolute altitude mode.
                needsAltitudeFix = (kvp.AltitudeMode != KmlAltitudeMode.Absolute);
                switch (kvp.Type)
                {
                    case KmlViewpointType.LookAt:
                        return new Viewpoint(kvp.Location,
                            new Camera(kvp.Location, kvp.Range, kvp.Heading, kvp.Pitch, kvp.Roll));
                    case KmlViewpointType.Camera:
                        return new Viewpoint(kvp.Location,
                            new Camera(kvp.Location, kvp.Heading, kvp.Pitch, kvp.Roll));
                    default:
                        throw new InvalidOperationException("Unexpected KmlViewPointType: " + kvp.Type);
                }
            }

            if (node.Extent != null && !node.Extent.IsEmpty)
            {
                // When no altitude specified, assume elevation should be taken into account.
                needsAltitudeFix = true;

                // Workaround: it's possible for "IsEmpty" to be true but for width/height to still be zero.
                if (node.Extent.Width == 0 && node.Extent.Height == 0)
                {
                    // Defaults based on Google Earth.
                    return new Viewpoint(node.Extent, new Camera(node.Extent.GetCenter(), 1000, 0, 45, 0));
                }
                else
                {
                    Envelope tx = node.Extent;
                    // Add padding on each side.
                    double bufferDistance = Math.Max(node.Extent.Width, node.Extent.Height) / 20;
                    Envelope bufferedExtent = new Envelope(
                        tx.XMin - bufferDistance, tx.YMin - bufferDistance,
                        tx.XMax + bufferDistance, tx.YMax + bufferDistance,
                        tx.ZMin - bufferDistance, tx.ZMax + bufferDistance,
                        SpatialReferences.Wgs84);
                    return new Viewpoint(bufferedExtent);
                }
            }
            else
            {
                // Can't fly to.
                needsAltitudeFix = false;
                return null;
            }
        }

        // Asynchronously adjust the given viewpoint, taking into consideration elevation and KML altitude mode.
        private async Task<Viewpoint> GetAltitudeAdjustedViewpointAsync(KmlNode node, Viewpoint baseViewpoint)
        {
            // Get the altitude mode; assume clamp-to-ground if not specified.
            KmlAltitudeMode altMode = KmlAltitudeMode.ClampToGround;
            if (node.Viewpoint != null)
            {
                altMode = node.Viewpoint.AltitudeMode;
            }

            // If the altitude mode is Absolute, the base viewpoint doesn't need adjustment.
            if (altMode == KmlAltitudeMode.Absolute)
            {
                return baseViewpoint;
            }

            double altitude;
            Envelope lookAtExtent = baseViewpoint.TargetGeometry as Envelope;
            MapPoint lookAtPoint = baseViewpoint.TargetGeometry as MapPoint;

            if (lookAtExtent != null)
            {
                // Get the altitude for the extent.
                try
                {
                    altitude = await MySceneView.Scene.BaseSurface.GetElevationAsync(lookAtExtent.GetCenter());
                }
                catch (Exception)
                {
                    altitude = 0;
                }

                // Apply elevation adjustment to the geometry.
                Envelope target;
                if (altMode == KmlAltitudeMode.ClampToGround)
                {
                    target = new Envelope(
                        lookAtExtent.XMin, lookAtExtent.YMin,
                        lookAtExtent.XMax, lookAtExtent.YMax,
                        altitude, lookAtExtent.Depth + altitude,
                        lookAtExtent.SpatialReference);
                }
                else
                {
                    target = new Envelope(
                        lookAtExtent.XMin, lookAtExtent.YMin,
                        lookAtExtent.XMax, lookAtExtent.YMax,
                        lookAtExtent.ZMin + altitude, lookAtExtent.ZMax + altitude,
                        lookAtExtent.SpatialReference);
                }

                if (node.Viewpoint != null)
                {
                    // Return adjusted geometry with adjusted camera if a viewpoint was specified on the node.
                    return new Viewpoint(target, baseViewpoint.Camera.Elevate(altitude));
                }
                else
                {
                    // Return adjusted geometry.
                    return new Viewpoint(target);
                }
            }
            else if (lookAtPoint != null)
            {
                // Get the altitude adjustment.
                try
                {
                    altitude = await MySceneView.Scene.BaseSurface.GetElevationAsync(lookAtPoint);
                }
                catch (Exception)
                {
                    altitude = 0;
                }

                // Apply elevation adjustment to the geometry.
                MapPoint target;
                if (altMode == KmlAltitudeMode.ClampToGround)
                {
                    target = new MapPoint(lookAtPoint.X, lookAtPoint.Y, altitude, lookAtPoint.SpatialReference);
                }
                else
                {
                    target = new MapPoint(
                        lookAtPoint.X, lookAtPoint.Y, lookAtPoint.Z + altitude,
                        lookAtPoint.SpatialReference);
                }

                if (node.Viewpoint != null)
                {
                    // Return adjusted geometry with adjusted camera if a viewpoint was specified on the node.
                    return new Viewpoint(target, baseViewpoint.Camera.Elevate(altitude));
                }
                else
                {
                    // Google Earth defaults: 1000m away and 45-degree tilt.
                    return new Viewpoint(target, new Camera(target, 1000, 0, 45, 0));
                }
            }
            else
            {
                throw new InvalidOperationException("KmlNode has unexpected Geometry for its Extent: " +
                                                    baseViewpoint.TargetGeometry);
            }
        }

        #endregion viewpoint_conversion
    }

    public class LayerDisplayVM
    {
        public KmlNode Node { get; set; }

        private List<LayerDisplayVM> Children { get; set; }

        private LayerDisplayVM Parent { get; set; }

        private int NestLevel
        {
            get
            {
                if (Parent == null)
                {
                    return 0;
                }

                return Parent.NestLevel + 1;
            }
        }

        public LayerDisplayVM(KmlNode info, LayerDisplayVM parent)
        {
            Node = info;
            Parent = parent;
        }

        public string Name => new string(' ', NestLevel * 3) + Node.GetType().Name + " - " + Node.Name;

        public static void BuildLayerInfoList(LayerDisplayVM root, IList<LayerDisplayVM> result)
        {
            // Add the root node to the result list.
            result.Add(root);

            // Make the node visible.
            root.Node.IsVisible = true;

            // Initialize the child collection for the root.
            root.Children = new List<LayerDisplayVM>();

            // Recursively add children. KmlContainers and KmlNetworkLinks can both have children.
            var containerNode = root.Node as KmlContainer;
            var networkLinkNode = root.Node as KmlNetworkLink;

            List<KmlNode> children = new List<KmlNode>();
            if (containerNode != null)
            {
                children.AddRange(containerNode.ChildNodes);
            }

            if (networkLinkNode != null)
            {
                children.AddRange(networkLinkNode.ChildNodes);
            }

            foreach (KmlNode node in children)
            {
                // Create the view model for the sublayer.
                LayerDisplayVM layerVm = new LayerDisplayVM(node, root);

                // Add the sublayer to the root's sublayer collection.
                root.Children.Add(layerVm);

                // Recursively add children.
                BuildLayerInfoList(layerVm, result);
            }
        }
    }
}
XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:esriUI="clr-namespace:Esri.ArcGISRuntime.Xamarin.Forms;assembly=Esri.ArcGISRuntime.Xamarin.Forms"
             x:Class="ArcGISRuntimeXamarin.Samples.ListKmlContents.ListKmlContents">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="150" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Label Text="Tap to zoom to KML content." HorizontalTextAlignment="Center" Grid.Row="0" />
        <ListView x:Name="LayerTreeView" Grid.Row="1" ItemSelected="LayerTreeView_OnSelectionChanged">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextCell Text="{Binding Name}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <esriUI:SceneView x:Name="MySceneView" Grid.Row="2" />
    </Grid>
</ContentPage>
See Also
Additional Examples
Hyperlink to ExampleDescription
ListKmlContentsList the contents of a KML file. KML files can contain a hierarchy of features, including network links to other KML content.