ProConcepts 3D Analyst Layers - Esri/arcgis-pro-sdk GitHub Wiki

This concepts document covers the map authoring considerations for the three types of 3D Analyst data - TIN, Terrain and LAS. It augments the data concepts covered in ProConcepts 3D Analyst data. Refer to this document for information regarding these data types.

Language:      C#
Subject:       Map Authoring
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          10/06/2024
ArcGIS Pro:    3.4
Visual Studio: 2022
In this topic

TIN

The TinLayer is the visual representation of a TinDataset. Layer creation for TIN layers follows the same general pattern(s) used for other ArcGIS Pro layers. Namely, a local Uri to the layer data source is specified as the argument to LayerFactory.Instance.Createlayer(Uri dataUri,....) casting the result to a TinLayer:

 //Must be on the QueuedTask.Run()

 string path = @"d:\Data\Tin\TinDataset";
 var uri = new Uri(path);
 
 //Create the layer to the TIN
 var tinLayer = LayerFactory.Instance.CreateLayer(uri, map) as TinLayer;

TIN layers can also be created using the T CreateLayer<T>(LayerCreationParams layerDef, ...) overload with a TinLayerCreationParams instance as the LayerCreationParams argument. This pattern allows layer configuration options such as visibility, name, and renderers to be specified upfront prior to the layer being added to the map.

In this example, a TIN layer is created with a user-specified name and visibility of false:

 //Must be on the QueuedTask.Run()

 string path = @"d:\Data\Tin\TinDataset";
 var uri = new Uri(path);

 var createParams = new TinLayerCreationParams(uri);
 createParams.Name = "My TIN Layer";
 createParams.IsVisible = false;
 
  //Create the layer to the TIN
 var tinLayer = LayerFactory.Instance.CreateLayer<TinLayer>(createParams, map);

The TinLayerCreationParams object has numerous construction overloads including one taking the TinDataset object:

 //Must be on the QueuedTask.Run()

 var createParams = new TinLayerCreationParams(tinDataset);
 createParams.Name = "My TIN Layer";
 createParams.IsVisible = false;
 
  //Create the layer to the TIN
 var tinLayer = LayerFactory.Instance.CreateLayer<TinLayer>(createParams, map);

Rendering TIN data

Renderers are objects that store symbolization for layers and draw the data based on the stored symbolization rules. A FeatureLayer for example has a single renderer which is defined according to the geometry type (point, line, polygon etc) of the data. A TinLayer, however, can have many possible visualizations due to the nature of it's data; i.e. it consists of nodes, edges and triangles (for the TIN). As a consequence, the TinLayer can have multiple renderers assigned to the layer; with each renderer targeting a different feature surface type. The enumeration SurfaceRendererTarget reflects these different surface targets. You can choose to display just one of the TIN feature types, for example just the edges (using a single renderer); or, you can symbolize each feature type with separate symbology for each (i.e. using one renderer per TIN feature type).

Creating a renderer for the TIN, however, follows the same pattern as with feature layers, namely: create a renderer definition, call the CanCreateRenderer and CreateRenderer methods - which returns a CIM renderer object - in this case a CIMTinRenderer object; use the TIN layer CanSetRenderer and SetRenderer methods to assign the renderer to the correct SurfaceRendererTarget. Renderer definitions for the three types of surface layers (TIN, Terrain and LasDataset) derive from the base class TinRendererDefinition. There are many types of renderer options available - single symbol renderers, class breaks renderers, or unique value renderers.

Here is a table showing the different types of symbology renderers available and valid for TIN layers along with the appropriate SurfaceRendererTarget and TinRendererDefinition.

Surface Target Renderer Type TinRendererDefinition class
Points Simple TinNodeRendererDefinition
Points Elevation TinNodeClassBreaksRendererDefinition
Edges Simple TinEdgeRendererDefinition
Edges Edge Type TinBreaklineRendererDefinition
Contours Contours TinContourRendererDefinition
Surface Simple TinFaceRendererDefinition
Surface



Elevation



TinFaceClassBreaksRendererDefinition ***

(CursorType = ArcGIS.Core.CIM.TerrainDrawCursorType.FaceElevation)
Surface



Slope



TinFaceClassBreaksRendererDefinition

(CursorType = ArcGIS.Core.CIM.TerrainDrawCursorType.FaceSlope)
Surface Aspect TinFaceClassBreaksAspectRendererDefinition

*** This is the default renderer assigned when a layer is created if no renderer definitions are assigned.

As per the table above, the TinLayer can have up to 4 renderers - one for each of the point, edge, contour and surface targets. The CanCreateRenderer and CanSetRenderer methods perform validation to ensure that the renderer definitions and renderers are applicable for the given TinLayer and intended surface target. For example CanSetRenderer will return false if you attempt to assign a renderer to the SurfaceRendererTarget.DirtyArea on a TinLayer. You must call these methods in your code prior to creating and setting the renderers to ensure you are assigning valid renderers to the appropriate surface targets. The CreateRenderer and SetRenderer methods will throw exceptions if invalid combinations are used.

Here is an example showing how to display the nodes of a TIN using a simple single symbol renderer:

  var nodeRendererDef = new TinNodeRendererDefinition();
  nodeRendererDef.SymbolTemplate = nodeSymbol.MakeSymbolReference();

  if (tinLayer.CanCreateRenderer(nodeRendererDef))
  {
    CIMTinRenderer renderer = tinLayer.CreateRenderer(nodeRendererDef);
    if (tinLayer.CanSetRenderer(renderer, SurfaceRendererTarget.Points))
      tinLayer.SetRenderer(renderer, SurfaceRendererTarget.Points);
  }

You can combine this with a renderer for displaying the surface elevation using an Equal interval class breaks renderer:

  var elevFaceClassBreaksEqual = new TinFaceClassBreaksRendererDefinition();
  // the default CursorType is Elevation
  // the default ClassificationMethod is EqualInterval
  // accept default breakCount, symbolTemplate, color ramp

  if (tinLayer.CanCreateRenderer(elevFaceClassBreaksEqual))
  {
    CIMTinRenderer renderer = tinLayer.CreateRenderer(elevFaceClassBreaksEqual);
    if (tinLayer.CanSetRenderer(renderer, SurfaceRendererTarget.Surface))
      tinLayer.SetRenderer(renderer, SurfaceRendererTarget.Surface);
  }

At this point the layer has 2 renderers attached:

    IReadOnlyList<CIMTinRenderer> renderers = tinLayer.GetRenderers();
    // renderer count = 2

Examples of the different types of TIN renderers can be found in the 3D Analyst Layer snippets.

Obtain the renderers of the TinLayer using the GetRenderers method. This returns a readonly list of CIMTinRenderer. Alternatively use the GetRenderersAsDictionary method to obtain the renderers in a dictionary structure according to SurfaceRendererTarget:

  //Must be on the QueuedTask.Run()

  // get the renderers as a list
  var renderers = tinLayer.GetRenderers();

  // get the renderers as a dictionary
  Dictionary<SurfaceRendererTarget, CIMTinRenderer> rendererDict = tinLayer.GetRenderersAsDictionary();
  
  // get the edge renderer
  if (rendererDict.ContainsKey(SurfaceRendererTarget.Edges))
    edgeRenderer = rendererDict[SurfaceRendererTarget.Edges];

You can also remove a renderer from a specific surface target from the TinLayer.

  //Must be on the QueuedTask.Run()
  
  tinLayer.RemoveRenderer(SurfaceRendererTarget.Contours);

When creating the TinLayer with the TinLayerCreationParams, the default behavior is to display the surface elevation with an equal interval class breaks renderer (a TinFaceClassBreaksRendererDefinition). Alternatively you can create the initial set of renderers using the TinLayerCreationParams.RendererDefinitions property. Here is an example showing a layer being created with a simple node renderer and a natural breaks renderer for the surface:

    var lcp = new TinLayerCreationParams(tinDataset);
    lcp.Name = "My TIN layer";
    lcp.IsVisible = true;

    // define the node renderer - use defaults
    var nodeRD = new TinNodeRendererDefinition();

    // define the face/surface renderer
    var faceRD = new TinFaceClassBreaksRendererDefinition();
    faceRD.ClassificationMethod = ClassificationMethod.NaturalBreaks;
    // accept default color ramp, breakCount

    // set up the renderer dictionary
    var rendererDict = new Dictionary<SurfaceRendererTarget, TinRendererDefinition>();
    rendererDict.Add(SurfaceRendererTarget.Points, nodeRD);
    rendererDict.Add(SurfaceRendererTarget.Surface, faceRD);

    // assign the dictionary to the creation params
    lcp.RendererDefinitions = rendererDict;

    // create the layer
    var tinLayer = LayerFactory.Instance.CreateLayer<TinLayer>(lcp, MapView.Active.Map);

Searching TIN data

Just as you can search for nodes, edges and triangles on the TinDataset (see here), you can also search for these same elements on the TinLayer. Use SearchNodes, SearchEdges, and SearchTriangles to retrieve the individual elements. As there is no display filter or definition query that applies to TIN layers, searching via the layer will produce the same results as searching the TinDataset directly. Detailed information on the methods, parameters and return cursors is provided in the TIN dataset section here.

Here are some examples of node searching via the layer: .

  // search all nodes
  using (ArcGIS.Core.Data.Analyst3D.TinNodeCursor nodeCursor = tinLayer.SearchNodes(null))
  {
    while (nodeCursor.MoveNext())
    {
      using (ArcGIS.Core.Data.Analyst3D.TinNode node = nodeCursor.Current)
      {

      }
    }
  }
  // search within an extent
  ArcGIS.Core.Data.Analyst3D.TinNodeFilter nodeFilter = new ArcGIS.Core.Data.Analyst3D.TinNodeFilter();
  nodeFilter.FilterEnvelope = envelope;
  using (ArcGIS.Core.Data.Analyst3D.TinNodeCursor nodeCursor = tinLayer.SearchNodes(nodeFilter))
  {
    while (nodeCursor.MoveNext())
    {
      using (ArcGIS.Core.Data.Analyst3D.TinNode node = nodeCursor.Current)
      {

      }
    }
  }
  // search all "inside" nodes
  var nodeFilter = new ArcGIS.Core.Data.Analyst3D.TinNodeFilter();
  nodeFilter.FilterType = ArcGIS.Core.Data.Analyst3D.TinFilterType.InsideDataArea;
  using (ArcGIS.Core.Data.Analyst3D.TinNodeCursor nodeCursor = tinLayer.SearchNodes(nodeFilter))
  {
    while (nodeCursor.MoveNext())
    {
      using (ArcGIS.Core.Data.Analyst3D.TinNode node = nodeCursor.Current)
      {

      }
    }
  }
  // search for super nodes only
  var nodeFilter = new ArcGIS.Core.Data.Analyst3D.TinNodeFilter();
  nodeFilter.FilterEnvelope = tinDataset.GetSuperNodeExtent();
  nodeFilter.SuperNode = true;
  using (ArcGIS.Core.Data.Analyst3D.TinNodeCursor nodeCursor = tinLayer.SearchNodes(nodeFilter))
  {
    while (nodeCursor.MoveNext())
    {
      using (ArcGIS.Core.Data.Analyst3D.TinNode node = nodeCursor.Current)
      {

      }
    }
  }

Code examples for edge and triangle searching can be found in the 3D Analyst Data concepts here.

Line of Sight

Line of sight is used to determine if a target is visible from a given observer's viewpoint on or above a surface. And to determine which sections of the surface's profile between the observer and target are visible and not visible from the observer. Use the GetLineOfSight method on the TinLayer to perform this type of 3D analysis.

Note, that this method is not supported with TerrainLayer or LasDatasetLayer and will throw an InvalidOperationException if called with these layer types. The method also requires the use of a 3D Analyst license and a LicenseException exception will be thrown if one is not available.

Create a LineOfSightParams instance to specify the ObserverPoint and TargetPoint. If you use a MapTool to generate the observer and target points via a mouse click, be aware that you can, and should, control which surface is used for determining the XYZ location where you clicked. At the bottom of the Contents pane, there is a category named Elevation Surfaces, within which there is a category named Ground. The first (uppermost) item in the Ground category is the surface that will be used to determine the Z value on mouse click. There are different ways of adding a surface to this category; refer to https://pro.arcgis.com/en/pro-app/latest/help/mapping/layer-properties/elevation-surfaces.htm for details.

During the calculation the elevation of the observer point (let's call it "observerZ") is derived as follows: if you provide a Z-aware (3D) observer point, observerZ will be set to its Z value; if you provide a 2D observer point, then observerZ will be set to the Z value of the surface at the XY of the observer point. The target point's Z value is calculated in the same manner. If you wish to specify height offsets for the points, use the ObserverHeightOffset or TargetHeightOffset properties. These values are added to the Z values of the observer and target points respectively. It is recommended that you provide a positive ObserverHeightOffset so that the observerZ is above the surface rather than being embedded on it.

You can also choose to have the calculation make adjustments for curvature and refraction with the ApplyCurvature or ApplyRefraction properties. Note that for the two flags to be valid, the spatial reference of the surface must be in a projected coordinate system and have Z coordinate units defined. Use RefractionFactor to set a custom refraction factor if you wish to use something other than the default refraction factor of 0.13.

It is important to call CanGetLineOfSight prior to GetLineOfSight to perform some checking on the parameters to ensure a possible result can be found and that your chosen parameters are compatible with the underlying surface. Note a return of "true" from CanGetLineOfSight does not preclude GetLineOfSight from still throwing an exception due to other possible calculation errors. Callers should always use appropriate exception handling techniques to avoid failures.

Internally, GetLineOfSight generates an imaginary straight 3D line (called "the sight line") from the observer point (at observerZ) to the target point (at targetZ). It then interpolates (drapes) this line onto the surface, creating an imaginary profile line. The typical results consist of a "visible" line (possibly a multipart) representing the portion of the profile line which is visible from the observer point (at observerZ), and an "invisible" line (also, possibly a multipart) representing the portion of the profile line which is not visible from the observer point (at observerZ). If the sight line intersects the surface at least once, then an obstruction point is created at the first intersection point which is closest to the observer point. The obstruction point, if created, will always lie along the profile line, but will typically not occur at the end of the first visible line part.

Results from the analysis are returned in a LineOfSightResult object. The key properties of this class are

  • IsTargetVisibleFromObserverPoint - true if the target point is visible from the observer point, otherwise false.
  • Visible Line - The visible parts of the profile line. If the profile line is totally obstructed this will be null.
  • Invisible Line - The invisible parts of the profile line. If there is no obstruction, this will be null.
  • Obstruction Point - The location of the first obstruction on the sight line. If there is no obstruction, this will be null.

You can visualize the geometry results in your map by adding them to the overlay or saving them into feature classes depending on your use case.

LineOfSight.png

A complete sample that illustrates the line of sight functionality is available in the arcgis-pro-sdk-community-samples repository. Get Line Of Sight Sample. Here is a snippet showing how to execute a line of sight analysis for an observer and target point:

  var losParams = new LineOfSightParams();
  losParams.ObserverPoint = observerPoint;
  losParams.TargetPoint = targetPoint;

  // add offsets if appropriate
  // losParams.ObserverHeightOffset = observerOffset;
  // losParams.TargetHeightOffset = targerOffset;

  // set output spatial reference
  losParams.OutputSpatialReference = MapView.Active.Map.SpatialReference;

  LineOfSightResult results = null;
  try
  {
    if (tinLayer.CanGetLineOfSight(losParams))
      results = tinLayer.GetLineOfSight(losParams);
  }
  catch (Exception ex)
  {
    // log exception message
  }
   
  // process results
  if (results != null)
  {
    bool targetIsVisibleFromObserverPoint = results.IsTargetVisibleFromObserverPoint;
    bool targetVisibleFromVisibleLine = results.IsTargetVisibleFromVisibleLine;
    bool targetVisibleFromInVisibleLine = results.IsTargetVisibleFromInvisibleLine;

    // add to overlay
    if (results.VisibleLine != null)
      MapView.Active.AddOverlay(results.VisibleLine, visibleLineSymbol.MakeSymbolReference());
    if (results.InvisibleLine != null)
      MapView.Active.AddOverlay(results.VisibleLine, invisibleLineSymbol.MakeSymbolReference());
    if (results.ObstructionPoint != null)
      MapView.Active.AddOverlay(results.ObstructionPoint, obstructionPointSymbol.MakeSymbolReference());
  }  

Terrain

The TerrainLayer is the visual representation of a Terrain. Layer creation for Terrain layers follows the same general pattern(s) used for other ArcGIS Pro layers. Namely, a local Uri to the layer data source is specified as the argument to LayerFactory.Instance.Createlayer(Uri dataUri,....) casting the result to a TerrainLayer:

 //Must be on the QueuedTask.Run()

 string path = @"d:\Data\Terrain\filegdb_Containing_A_Terrain.gdb\nameOfTerrain";
 var uri = new Uri(path);
 
 //Create the layer to the Terrain
 var terrainLayer = LayerFactory.Instance.CreateLayer(uri, map) as TerrainLayer;

Terrain layers can also be created using the T CreateLayer<T>(LayerCreationParams layerDef, ...) overload and a TerrainLayerCreationParams instance as the LayerCreationParams argument. This pattern allows additional layer configuration options to be specified upfront. In this example, a Terrain layer is created with a default name and visibility:

 //Must be on the QueuedTask.Run()

 string path = @"d:\Data\Terrain\filegdb_Containing_A_Terrain.gdb\nameOfTerrain";
 var uri = new Uri(path);

 var createParams = new TerrainLayerCreationParams(uri);
 createParams.Name = "My Terrain Layer";
 createParams.IsVisible = false;
 
  //Create the layer to the terrain
 var terrainLayer = LayerFactory.Instance.CreateLayer<TerrainLayer>(createParams, map);

The TerrainLayerCreationParams object has numerous construction overloads including one taking the Terrain object.

 //Must be on the QueuedTask.Run()

 var createParams = new TerrainLayerCreationParams(terrain);
 createParams.Name = "My Terrain Layer";
 createParams.IsVisible = false;
 
  //Create the layer to the terrain
 var terrainLayer = LayerFactory.Instance.CreateLayer<TerrainLayer>(createParams, map);

Rendering Terrain data

A TerrainLayer and a TinLayer behave similarly in their ability to support multiple renderers. Review the previous section on Rendering TIN data for explanations on surface layer rendering.

Here is a table showing the different types of symbology renderers available and valid for Terrain layers:

Surface Target Renderer Type TinRendererDefinition class
Points Elevation TerrainPointClassBreaksRendererDefinition
Edges Edge Type TinBreaklineRendererDefinition ***
Contours Contours TinContourRendererDefinition
Surface Simple TinFaceRendererDefinition
Surface



Elevation



TinFaceClassBreaksRendererDefinition ***

(CursorType = ArcGIS.Core.CIM.TerrainDrawCursorType.FaceElevation)
Surface



Slope



TinFaceClassBreaksRendererDefinition

(CursorType = ArcGIS.Core.CIM.TerrainDrawCursorType.FaceSlope)
Surface Aspect TinFaceClassBreaksAspectRendererDefinition
Dirty Area TerrainDirtyAreaRendererDefinition

*** These are the default renderers assigned when a layer is created if no renderer definitions are assigned.

A TerrainLayer can have up to 5 different renderers (recall: a TINLayer can have up to 4)- one each for the point, edge, contours, surface and dirty areas targets. Use the dirty area renderer to show where edits have been made to the terrain dataset and where it needs to be rebuilt.

Here is an example showing how to create and assign a dirty area renderer:

  var dirtyAreaRendererDef = new TerrainDirtyAreaRendererDefinition();
  // accept default labels, symbolTemplate

  if (terrainLayer.CanCreateRenderer(dirtyAreaRendererDef))
  {
    CIMTinRenderer renderer = terrainLayer.CreateRenderer(dirtyAreaRendererDef);
    if (terrainLayer.CanSetRenderer(renderer, SurfaceRendererTarget.DirtyArea))
      terrainLayer.SetRenderer(renderer, SurfaceRendererTarget.DirtyArea);
  }

You must call CanCreateRenderer and CanSetRenderer in your code prior to creating and setting the renderers to ensure you are assigning valid renderers to the appropriate surface targets for the TerrainLayer. The CreateRenderer and SetRenderer methods of the TerrainLayer will throw exceptions if invalid combinations are used.

As with the TinLayer, the TerrainLayerCreationParams object allows you to define the initial set of renderers using the TerrainLayerCreationParams.RendererDefinitions property when creating a TerrainLayer. If no renderers are specified, the default is to display the edges according to edge type (a TinBreaklineRendererDefinition) along with surface elevation with an equal interval class breaks renderer (a TinFaceClassBreaksRendererDefinition).

Here is an example showing a layer being created with an edge type renderer, a natural breaks renderer and a dirty area renderer for the surface:

    var lcp = new TerrainLayerCreationParams(terrain);
    lcp.Name = "My Terrain layer";
    lcp.IsVisible = true;

    // define the edge type renderer - use defaults
    var edgeRD = new TinBreaklineRendererDefinition();

    // define the face/surface renderer
    var faceRD = new TinFaceClassBreaksRendererDefinition();
    faceRD.ClassificationMethod = ClassificationMethod.NaturalBreaks;
    // accept default color ramp, breakCount

    // define the dirty area renderer - use defaults
    var dirtyAreaRD = new TerrainDirtyAreaRendererDefinition();
    
    // set up the renderer dictionary
    var rendererDict = new Dictionary<SurfaceRendererTarget, TinRendererDefinition>();
    rendererDict.Add(SurfaceRendererTarget.Edges, edgeRD);
    rendererDict.Add(SurfaceRendererTarget.Surface, faceRD);
    rendererDict.Add(SurfaceRendererTarget.DirtyArea, dirtyAreaRD);

    // assign the dictionary to the creation params
    lcp.RendererDefinitions = rendererDict;

    // create the layer
    var terrainLayer = LayerFactory.Instance.CreateLayer<TerrainLayer>(lcp, MapView.Active.Map);

LAS Dataset

The LasDatasetLayer is the visual representation of a LasDataset. Layer creation for LAS dataset layers follows the same general pattern(s) used for other ArcGIS Pro layers. Namely, a local Uri to the layer data source is specified as the argument to LayerFactory.Instance.Createlayer(Uri dataUri,....) casting the result to a LasDatasetLayer:

 //Must be on the QueuedTask.Run()

 string path = @"d:\Data\LASDataset.lasd";
 var uri = new Uri(path);
 
 //Create the layer to the LAS dataset
 var lasDatasetLayer = LayerFactory.Instance.CreateLayer(uri, map) as LasDatasetLayer;

LAS dataset layers can also be created using the T CreateLayer<T>(LayerCreationParams layerDef, ...) overload with a LasDatasetLayerCreationParams instance as the LayerCreationParams argument. This pattern allows additional layer configuration options to be specified upfront.

In this example, a LAS dataset layer is created with a user-specified name and visibility of false:

 //Must be on the QueuedTask.Run()

 string path = @"d:\Data\LASDataset.lasd";
 var uri = new Uri(path);

 var createParams = new LasDatasetLayerCreationParams(uri);
 createParams.Name = "My LAS Layer";
 createParams.IsVisible = false;
 
  //Create the layer to the LAS dataset
 var lasDatasetLayer = LayerFactory.Instance.CreateLayer<LasDatasetLayer>(createParams, map);

The LasDatasetLayerCreationParams object has numerous construction overloads including one taking the LasDataset object:

 //Must be on the QueuedTask.Run()

 var createParams = new TerrainLayerCreationParams(lasDataset);
 createParams.Name = "My LAS Layer";
 createParams.IsVisible = false;
 
  //Create the layer to the LAS dataset
 var lasDatasetLayer = LayerFactory.Instance.CreateLayer<LasDatasetLayer>(createParams, map);

Rendering LAS data

As with the other surface layers a LasDatasetLayer can also have multiple renderers for displaying the different surface types. Review the previous section on Rendering TIN data for explanations on surface layer rendering.

Here is a table showing the different types of symbology renderers available and valid for LAS dataset layers.

Surface Target Renderer Type TinRendererDefinition class
Points



Elevation



LasStretchRendererDefinition ***

(StretchAttribute = ArcGIS.Core.CIM.LASStretchAttribute.Elevation)
Points Elevation Classified LasPointClassBreaksRendererDefinition
Points



Intensity



LasStretchRendererDefinition

(StretchAttribute = ArcGIS.Core.CIM.LASStretchAttribute.Intensity)
Points



Scan Angle



LasStretchRendererDefinition

(StretchAttribute = ArcGIS.Core.CIM.LASStretchAttribute.ScanAngle)
Points


Classification


LasUniqueValueRendererDefinition

(AttributeType = LasAttributeType.Classification)
Points


Return Number


LasUniqueValueRendererDefinition

(AttributeType = LasAttributeType.ReturnNumber)
Points


Number of Returns


LasUniqueValueRendererDefinition

(AttributeType = LasAttributeType.NumberOfReturns)
Points


Point Source ID


LasUniqueValueRendererDefinition

(AttributeType = LasAttributeType.PointSourceID)
Edges Simple TinEdgeRendererDefinition
Edges Edge Type TinBreaklineRendererDefinition
Contours Contours TinContourRendererDefinition
Surface Simple TinFaceRendererDefinition
Surface



Elevation



TinFaceClassBreaksRendererDefinition

(CursorType = ArcGIS.Core.CIM.TerrainDrawCursorType.FaceElevation)
Surface



Slope



TinFaceClassBreaksRendererDefinition

(CursorType = ArcGIS.Core.CIM.TerrainDrawCursorType.FaceSlope)
Surface Aspect TinFaceClassBreaksAspectRendererDefinition

*** This is the default renderer assigned when a layer is created if no renderer definitions are assigned.

The LasDatasetLayer (same as the TINLayer) can have a maximum of 4 renderers - one for each of the point, edge, contour and surface targets. You must call CanCreateRenderer and CanSetRenderer in your code prior to creating and setting the renderers to ensure you are assigning valid renderers to the appropriate surface targets for the LasDatasetLayer.

As with the other surface layer types, use the LasDatasetLayerCreationParams.RendererDefinitions property to assign initial renderers prior to creating the layer.

Filters

As a LAS dataset can reference many LAS files and surface constraints you can manipulate which points and surface constraints are drawn using a display filter. Being able to separate out data based on different values allows you to analyze and visualize the data quickly and efficiently.

The display filter allows you to define a set of classification code(s) and/or return value(s) and/or classification flags that are stored with the points in the layer to manipulate the display. The various classification codes, return values, and classification flags are detailed below:

Classification codes:
Classification codes are used to define the type of surface, or surfaces, that reflected the lidar pulse. Classification codes follow the American Society for Photogrammetry and Remote Sensing (ASPRS)* for LAS formats 1.1, 1.2, 1.3, and 1.4 and include codes for building, ground, water, vegetation, and so on. The classification codes present on a given LasDatasetLayer can be retrieved via the Lasdataset.GetUniqueClassCodes method.

Classification codes set in the list of classification codes on the filter are always included. A classification code that is not present in the list is assumed to be excluded by the filter.

*The complete set of available classification codes from ASPRS.

Return Values
When a lidar pulse is emitted from a sensor, it can have multiple return values depending on the nature of the surfaces the pulses encounter. The first returns will typically be associated with the highest objects encountered (e.g. tops of trees or buildings) and the last returns with the lowest objects encountered (e.g. the ground).

pcl-return-filter.png

The return values that can be specified are represented as LasReturnType enums. The return values present on a given LasDatasetLayer can be retrieved via the LasDataset.GetUniqueReturns method.

Classification flags
In many cases, when a classification is carried out on lidar data, points can fall into more than one classification category. In these cases, classification flags are specified in the lidar data to provide a secondary description or classification for the points. Classification flag values include synthetic, key-point, withheld, and overlap (see below).

The set of flags and their description is as follow:

Flag Notes
Synthetic The point was created by a technique other than LIDAR collection such as digitized from a photogrammetric stereo model or by traversing a waveform
Key-point The point is considered to be a model key-point and thus generally should not be withheld in a thinning algorithm
Withheld The point should not be included in processing (synonymous with Deleted)
Overlap The point is within the overlap region of two or more swaths or takes. Setting this bit is not mandatory (unless, of course, it is mandated by a particular delivery specification) but allows Classification of overlap points to be preserved.

There are a few different ways of setting a display filter. You can use a coarse grained filter with the LasPointDisplayFilterType enum and the LasDatasetLayer.SetDisplayFilter method. A few of the most common filter options are included in this enumeration - all points, ground points, non ground points, first return points:

      // display only ground points
      lasLayer.SetDisplayFilter(LasPointDisplayFilterType.Ground);

      // display first return points
      lasLayer.SetDisplayFilter(LasPointDisplayFilterType.FirstReturnPoints);

Alternatively you can specify a list of classification codes with a different overload of SetDisplayFilter. Or a list of return types with this overload of SetDisplayFilter:

      // set display filter to a set of classification codes
      List<int> classifications = new List<int>() { 4, 5, 7, 10 };
      lasLayer.SetDisplayFilter(classifications);

      // set display filter to a set of returns
      List<ArcGIS.Core.Data.Analyst3D.LasReturnType> returns = new List<ArcGIS.Core.Data.Analyst3D.LasReturnType>()
              { ArcGIS.Core.Data.Analyst3D.LasReturnType.ReturnFirstOfMany};
      lasLayer.SetDisplayFilter(returns);

If you need even more control you can use a LasPointDisplayFilter to define the filter. This object allows you to set a combination of returns, classification codes and point flags. You can also modify the set of active surface constraints using this method. Use this overload of SetDisplayFIlter in this situation:

      // set up a display filter
      var newDisplayFilter = new LasPointDisplayFilter();
      newDisplayFilter.Returns = new List<ArcGIS.Core.Data.Analyst3D.LasReturnType>()
              { ArcGIS.Core.Data.Analyst3D.LasReturnType.ReturnFirstOfMany, 
                ArcGIS.Core.Data.Analyst3D.LasReturnType.ReturnLastOfMany};
      newDisplayFilter.ClassCodes = new List<int>() { 2, 4 };
      newDisplayFilter.KeyPoints = true;
      newDisplayFilter.WithheldPoints = false;
      newDisplayFilter.SyntheticPoints = false;
      newDisplayFilter.NotFlagged = false;
      lasLayer.SetDisplayFilter(returns);

Retrieve the current display filter using the GetDisplayFilter method.

Active Surface Constraints

Surface constraints are surface features stored in either geodatabase feature classes or shapefiles, and are usually derived from some sort of remote-sensing technique, such as photogrammetry, that the LAS dataset references. You can retrieve the set of constraints using a LasDataset.GetSurfaceConstraints call. When filtering the LAS data on the layer you have the ability to turn the surface constraints on or off individually via the SetActiveSurfaceConstraints method - independent of any other filter components. Alternatively you can use the LasPointDisplayFilter to set the active surface constraints in conjunction with other filter options in one call.

Use GetActiveSurfaceConstraints to obtain the set of surface constraints that are currently active.

  // get the set of surface constraints from the dataset
  var surfaceConstraints = lasDataset.GetSurfaceConstraints();

  // get the set of surface cosnstraints that are active from the layer
  var activeSurfaceConstraints = lasDatasetLayer.GetActiveSurfaceConstraints();

  // clear all surface constraints (i.e. none are active)
  lasDatasetLayer.SetActiveSurfaceConstraints(null);

  // set only the first surface constraint active
  List<string> newActive = new List<string>();
  newActive.Add(surfaceConstraints[0].DataSourceName);
  lasDatasetLayer.SetActiveSurfaceConstraints(newActive);

Searching LAS data

You can search for points from the layer using SearchPoints. If a display filter has been set on the layer, then the filter is respected and only points that match the search filter AND display filter will be returned.

Iterate through the LasPointCursor and retrieve the individual LasPoint objects with the MoveNext. Alternatively if you are processing large numbers of results and interested in only the coordinates or file index and point ID identifiers, use the MoveNextArray method with pre-initialized arrays to gain faster performance. You cannot mix using MoveNext and MoveNextArray within the same search result.

  // search all points
  using (ArcGIS.Core.Data.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(null))
  {
    while (ptCursor.MoveNext())
    {
      using (ArcGIS.Core.Data.Analyst3D.LasPoint point = ptCursor.Current)
      {

      }
    }
  }
  // search within an extent
  ArcGIS.Core.Data.Analyst3D.LasPointFilter pointFilter = new ArcGIS.Core.Data.Analyst3D.LasPointFilter();
  pointFilter.FilterGeometry = envelope;
  using (ArcGIS.Core.Data.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(pointFilter))
  {
    while (ptCursor.MoveNext())
    {
      using (ArcGIS.Core.Data.Analyst3D.LasPoint point = ptCursor.Current)
      {

      }
    }
  }
  // search all points and process with a set size of array retrieving only coordinates
  using (ArcGIS.Core.Data.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(null))
  {
    int count;
    Coordinate3D[] lasPointsRetrieved = new Coordinate3D[10000];
    while (ptCursor.MoveNextArray(lasPointsRetrieved, null, null, null, out count))
    {
      var points = lasPointsRetrieved.ToList();
      
      // ...
    }
  }
  // search within an extent
  // use MoveNextArray retrieving coordinates, fileIndex and pointIds
  ArcGIS.Core.Data.Analyst3D.LasPointFilter pointFilter = new ArcGIS.Core.Data.Analyst3D.LasPointFilter();
  pointFilter.FilterGeometry = envelope;
  using (ArcGIS.Core.Data.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(pointFilter))
  {
    int count;
    Coordinate3D[] lasPointsRetrieved = new Coordinate3D[50000];
    int[] fileIndexes = new int[50000];
    double[] pointIds = new double[50000];
    while (ptCursor.MoveNextArray(lasPointsRetrieved, null, fileIndexes, pointIds, out count))
    {
      var points = lasPointsRetrieved.ToList();

    }
  }

Eye Dome Lighting

Eye-dome lighting (EDL) is a shading technique that improves the perception of depth and contour when viewing LAS datasets. An overview of this concept can be found here: Eye Dome Lighting.

Get and set whether EDL is enabled on the layer with IsEyeDomeLightingEnabled and SetEyeDomeLightingEnabled,

EDL strength adjusts the intensity of the resulting effects. You can adjust its value to any value between 0% and 100%. EDL is disabled when the value is set to 0%. The default is 50%. The larger the value, the stronger is the perception of depth. Use EyeDomeLightingStrength and SetEyeDomeLightingStrength to get or set the EDL strength.

EDL radius controls the width of effects. Its values range between 1 and 5. The default value is 2 pixels wide. However, a larger radius will slow down the performance. Use EyeDomeLightingRadius and SetEyeDomeLightingRadius to get and set the EDL radius respectively.

  var isEnabled = lasDatasetLayer.IsEyeDomeLightingEnabled;
  var strength = lasDatasetLayer.EyeDomeLightingStrength;

  // enable EDL and set the strength and radius
  lasDatasetLayer.SetEyeDomeLightingEnabled(true);
  lasDatasetLayer.SetEyeDomeLightingStrength(72);
  lasDatasetLayer.SetEyeDomeLightingRadius(3);
⚠️ **GitHub.com Fallback** ⚠️