LiDAR UAV 3D Point-cloud and Contour Visualisation in WPF,iOS,Android & Javascript
Published: 2024-04-05 11:33:59

<!-- wp:paragraph -->
<p>It's been a topic I've wanted to write about for a while, how SciChart - High Performance Realtime Charts is actually much more than a chart component, it can be used for advanced graphics &amp; visualisation in many different sectors, but I lacked the good example to demonstrate the flexibility and power of SciChart.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In this blog post I'm going to walk you through the following:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><strong>how we can download, parse and load LiDAR data in C#</strong></li><li>then present some <strong>demos of visualizing LiDAR data</strong> using WPF (C#)</li><li>Showcase the same thing on a <strong>Mobile (iOS/Android device)</strong> (near bottom of article)</li><li>and even <strong>in the browser with JavaScript and WebGL</strong>. (at the bottom)</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>Read on to find out how we did this!</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="visualizing-lidar-data-in-3d">Visualizing LiDAR Data in 3D</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>So here was the idea I had. <strong>What about visualising 3D LiDAR data in SciChart? </strong>LiDAR data can be collected from a variety of sources: <strong>UAV drones </strong>(Unmanned Aerial Vehicles), <strong>self-driving cars</strong>, <strong>geological survey tools</strong> and more.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It can be used in a variety of scenarios: anything from <strong>defence/tactical operations </strong>to geological survey such as <strong>energy/mining</strong>, <strong>geospatial </strong>analysis, civil engineering, <strong>construction</strong> and more.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The power to visualize LiDAR / Geospatial data is sitting in most of our pockets in the form of an <strong>iPhone</strong> or <strong>Android phone</strong>, or in our cars or offices in the form of a <strong>tablet</strong> or <strong>laptop</strong>. Modern phones really are powerhouses of computing, often with quad-core processors and dedicated GPUs. You will be surprised with what you can do with one of these devices when paired with powerful graphics and visualisation software such as <a href="https://www.scichart.com">SciChart</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="where-to-get-lidar-data">Where to get LiDAR Data</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The problem I had was where to get a dataset. What is the dataset format? How large are the dataset files?</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Well, I basically googled 'LiDAR Dataset' and the first link was a <a href="https://data.gov.uk/dataset/80c522cc-e0bf-4466-8409-57a04c456197/lidar-composite-dsm-2017-1m">UK Government site with LiDAR composite data</a> for the entire United Kingdom. OK, That's probably a big dataset ... Luckily they had a <a href="https://environment.data.gov.uk/DefraDataDownload/?Mode=survey">nice website where you could select a region</a> and download sub datasets.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33007,"sizeSlug":"full","linkDestination":"custom"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/Defra-Survey-Data-Download-1024x627-1.jpg" alt="" class="wp-image-33007"/><figcaption>The <a href="https://environment.data.gov.uk/DefraDataDownload/?Mode=survey">Defra Survey Data Download</a> website allows you to select LiDAR datasets of the United Kingdom for download</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>I thought what would be an interesting location and decided on Central London. Zooming in to tile TQ38SW I grabbed a dataset of a 20 1km x 1km square tiles containing ASC (Ascii GIS format) files.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="the-asc-file-format">The ASC File Format</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The ASC file format is pretty simple. It's just a text-file which contains some header parameters then a uniform grid of heights. Each height is a floating point number corresponding to the altitude in metres. Each height-value is spaced CellSize metres from the previous, so you can consider the ASC file as a uniform NxM matrix or grid of heights. This is perfect, it corresponds perfectly to SciChart data formats :)</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33009,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/asc-file-format-1024x618-1.png" alt="" class="wp-image-33009"/><figcaption>The ASC LiDAR Data File Format is pretty simple, including some header parameters and an array of floating-point values&nbsp;</figcaption></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 id="wpf-windows-c-lidar-visualisation-demo">WPF (Windows) C# LiDAR Visualisation Demo</h2>
<!-- /wp:heading -->

<!-- wp:heading {"level":3} -->
<h3 id="reading-an-asc-lidar-gis-data-file-in-c-">Reading an ASC LiDAR/GIS Data File in C#</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>So the C# code to read an ASC LiDAR file took me about half an hour to write, as it's not a particularly complicated file format and entirely text based. The code is included below.</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"using System;nusing System.Collections.Generic;nusing System.Globalization;nusing System.IO;nusing System.Linq;nusing System.Threading.Tasks;nusing System.Windows.Media;nnnamespace SciChart.Lidar3DPointCloudDemon{n    public struct AscDatan    {n        public Listu003cfloatu003e XValues { get; set; }n        public Listu003cfloatu003e YValues { get; set; }n        public Listu003cfloatu003e ZValues { get; set; }n        public Listu003cColoru003e ColorValues { get; set; }n        public int CellSize { get; set; }n        public int XllCorner { get; set; }n        public int YllCorner { get; set; }n        public int NumberColumns { get; set; }n        public int NumberRows { get; set; }n        public int NoDataValue { get; set; }n    }ntn    public class AscReadern    {n        public static async Tasku003cAscDatau003e ReadFileToAscData(n            string filename, Funcu003cfloat, Coloru003e colorMapFunction, Actionu003cintu003e reportProgress = null)n        {n            AscData result = new AscData()n            {n                XValues = new Listu003cfloatu003e(),n                YValues = new Listu003cfloatu003e(),n                ZValues = new Listu003cfloatu003e(),n                ColorValues = new Listu003cColoru003e(),n            };nn            await Task.Run(() =u003en            {n                using (var file = File.OpenText(filename))n                {n                    // Load the ASC file format n                    result.NumberColumns = ReadInt(file, u0022ncolsu0022);n                    result.NumberRows = ReadInt(file, u0022nrowsu0022);n                    result.XllCorner = ReadInt(file, u0022xllcorneru0022);n                    result.YllCorner = ReadInt(file, u0022yllcorneru0022);n                    result.CellSize = ReadInt(file, u0022cellsizeu0022);n                    result.NoDataValue = ReadInt(file, u0022NODATA_valueu0022);nn                    // Generate X-values based off cell position n                    float[] xValuesRow = Enumerable.Range(0, result.NumberColumns).Select(x =u003e (float)x * result.CellSize).ToArray();nn                    for (int i = 0; i u003c result.NumberRows; i++)n                    {n                        // Read heights from the ASC file and generate Z-cell valuesn                        float[] heightValuesRow = ReadFloats(file, u0022 u0022, result.NoDataValue);n                        float[] zValuesRow = Enumerable.Repeat(0 + i * result.CellSize, result.NumberRows).Select(x =u003e (float)x).ToArray();nn                        result.XValues.AddRange(xValuesRow);n                        result.YValues.AddRange(heightValuesRow);n                        result.ZValues.AddRange(zValuesRow);nn                        if (colorMapFunction != null)n                        {n                            // Optional color-mapping of points based on height n                            Color[] colorValuesRow = heightValuesRown                                .Select(colorMapFunction)n                                .ToArray();n                            result.ColorValues.AddRange(colorValuesRow);n                        }nn                        // Optional report loading progress 0-100%n                        reportProgress?.Invoke((int)(100.0f * i / result.NumberRows));n                    }n                }n            });nn            return result;n        }nn        private static int ReadInt(StreamReader file, string prefix)n        {n            string line = file.ReadLine();n            line = line.Replace(prefix, u0022u0022).Trim();n            return Int32.Parse(line, CultureInfo.InvariantCulture);n        }nn        private static float[] ReadFloats(StreamReader file, string separator, float noDataValue)n        {n            string line = file.ReadLine();n            float[] values = line.Split(new[] {separator}, StringSplitOptions.RemoveEmptyEntries)n                .Select(x =u003en                {n                    float rawValue = float.Parse(x);n                    return rawValue == noDataValue ? float.NaN : rawValue;n                } ).ToArray();n            return values;n        }n    }n}n","proglang":"language-csharp"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-csharp">using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media;

namespace SciChart.Lidar3DPointCloudDemo
{
    public struct AscData
    {
        public List&lt;float> XValues { get; set; }
        public List&lt;float> YValues { get; set; }
        public List&lt;float> ZValues { get; set; }
        public List&lt;Color> ColorValues { get; set; }
        public int CellSize { get; set; }
        public int XllCorner { get; set; }
        public int YllCorner { get; set; }
        public int NumberColumns { get; set; }
        public int NumberRows { get; set; }
        public int NoDataValue { get; set; }
    }

    public class AscReader
    {
        public static async Task&lt;AscData> ReadFileToAscData(
            string filename, Func&lt;float, Color> colorMapFunction, Action&lt;int> reportProgress = null)
        {
            AscData result = new AscData()
            {
                XValues = new List&lt;float>(),
                YValues = new List&lt;float>(),
                ZValues = new List&lt;float>(),
                ColorValues = new List&lt;Color>(),
            };

            await Task.Run(() =>
            {
                using (var file = File.OpenText(filename))
                {
                    // Load the ASC file format 
                    result.NumberColumns = ReadInt(file, "ncols");
                    result.NumberRows = ReadInt(file, "nrows");
                    result.XllCorner = ReadInt(file, "xllcorner");
                    result.YllCorner = ReadInt(file, "yllcorner");
                    result.CellSize = ReadInt(file, "cellsize");
                    result.NoDataValue = ReadInt(file, "NODATA_value");

                    // Generate X-values based off cell position 
                    float[] xValuesRow = Enumerable.Range(0, result.NumberColumns).Select(x => (float)x * result.CellSize).ToArray();

                    for (int i = 0; i &lt; result.NumberRows; i++)
                    {
                        // Read heights from the ASC file and generate Z-cell values
                        float[] heightValuesRow = ReadFloats(file, " ", result.NoDataValue);
                        float[] zValuesRow = Enumerable.Repeat(0 + i * result.CellSize, result.NumberRows).Select(x => (float)x).ToArray();

                        result.XValues.AddRange(xValuesRow);
                        result.YValues.AddRange(heightValuesRow);
                        result.ZValues.AddRange(zValuesRow);

                        if (colorMapFunction != null)
                        {
                            // Optional color-mapping of points based on height 
                            Color[] colorValuesRow = heightValuesRow
                                .Select(colorMapFunction)
                                .ToArray();
                            result.ColorValues.AddRange(colorValuesRow);
                        }

                        // Optional report loading progress 0-100%
                        reportProgress?.Invoke((int)(100.0f * i / result.NumberRows));
                    }
                }
            });

            return result;
        }

        private static int ReadInt(StreamReader file, string prefix)
        {
            string line = file.ReadLine();
            line = line.Replace(prefix, "").Trim();
            return Int32.Parse(line, CultureInfo.InvariantCulture);
        }

        private static float[] ReadFloats(StreamReader file, string separator, float noDataValue)
        {
            string line = file.ReadLine();
            float[] values = line.Split(new[] {separator}, StringSplitOptions.RemoveEmptyEntries)
                .Select(x =>
                {
                    float rawValue = float.Parse(x);
                    return rawValue == noDataValue ? float.NaN : rawValue;
                } ).ToArray();
            return values;
        }
    }
}
</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p>In this class ASCReader we have an async method ReadFileToAscData:</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"public static async Tasku003cAscDatau003e ReadFileToAscData(n            string filename, n            Funcu003cfloat, Coloru003e colorMapFunction,n            Actionu003cintu003e reportProgress = null)","proglang":"language-csharp"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-csharp">public static async Task&lt;AscData> ReadFileToAscData(
            string filename, 
            Func&lt;float, Color> colorMapFunction,
            Action&lt;int> reportProgress = null)</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p>This function accepts a filename, an optional callback function to map a height to a colour, and an optional callback to report progress from 0...100% on the loading operation. It turns out loading a single ASC LiDAR file from the Defra Survey takes a very short time but the whole method is async and setup for much larger files if you have them.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Once the data is loaded, in this case into a struct called AscData, which looks as follows:</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"public struct AscDatan{ntpublic Listu003cfloatu003e XValues { get; set; }ntpublic Listu003cfloatu003e YValues { get; set; }ntpublic Listu003cfloatu003e ZValues { get; set; }ntpublic Listu003cColoru003e ColorValues { get; set; }ntpublic int CellSize { get; set; }ntpublic int XllCorner { get; set; }ntpublic int YllCorner { get; set; }ntpublic int NumberColumns { get; set; }ntpublic int NumberRows { get; set; }ntpublic int NoDataValue { get; set; }n}n","proglang":"language-csharp"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-csharp">public struct AscData
{
public List&lt;float> XValues { get; set; }
public List&lt;float> YValues { get; set; }
public List&lt;float> ZValues { get; set; }
public List&lt;Color> ColorValues { get; set; }
public int CellSize { get; set; }
public int XllCorner { get; set; }
public int YllCorner { get; set; }
public int NumberColumns { get; set; }
public int NumberRows { get; set; }
public int NoDataValue { get; set; }
}
</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p>This struct contains all the data we need, including a List of X,Y,Z values (as floats), ColorValues (this list will be empty if you don't pass a colormap function to the AscReader) as well as other header information such as CellSize which specifies the spacing in metres between each data-point, XllCorner, YllCorner which specify the location of the top-left cell in the 'world', Number of columns/rows and the NoDataValue or null value.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 id="mapping-ascdata-asc-lidar-gis-data-format-to-scichart-wpf">Mapping AscData ASC LiDAR/GIS Data Format to SciChart WPF</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>For SciChart visualizing we transformed the NoDataValue to float.NaN, as SciChart is already setup to render NaN as a gap. We used floating-point datatype not double because the extra-precision of 64-bit is not needed for this example, and float uses half the memory, so why not.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Finally, we had to transform the ASC dataset to SciChart data types, and this part proved quite easy due to our flexible API for drawing 3D point-clouds and contour/topology maps.</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"// Transforming AscData to SciChart XyzDataSeries3D for point-cloudsn// npublic static async Tasku003cXyzDataSeries3Du003cfloatu003eu003e ParseToXyzDataSeries(AscData lidarData)n{ntvar xyzDataSeries3D = new XyzDataSeries3Du003cfloatu003e();nntawait Task.Run(() =u003ent{nttif (lidarData.ColorValues.IsNullOrEmpty())ntt{n            // When no Color map, just append XYZ pointsntttxyzDataSeries3D.Append(lidarData.XValues, lidarData.YValues, lidarData.ZValues);ntt}nttelsentt{n            // When a Color map exists, append XYZ points and colour datantttxyzDataSeries3D.Append(lidarData.XValues, lidarData.YValues, lidarData.ZValues, lidarData.ColorValues.Select(x =u003e new PointMetadata3D(x)));ntt}nt});nntreturn xyzDataSeries3D;n}n","proglang":"language-csharp"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-csharp">// Transforming AscData to SciChart XyzDataSeries3D for point-clouds
// 
public static async Task&lt;XyzDataSeries3D&lt;float>> ParseToXyzDataSeries(AscData lidarData)
{
var xyzDataSeries3D = new XyzDataSeries3D&lt;float>();

await Task.Run(() =>
{
if (lidarData.ColorValues.IsNullOrEmpty())
{
            // When no Color map, just append XYZ points
xyzDataSeries3D.Append(lidarData.XValues, lidarData.YValues, lidarData.ZValues);
}
else
{
            // When a Color map exists, append XYZ points and colour data
xyzDataSeries3D.Append(lidarData.XValues, lidarData.YValues, lidarData.ZValues, lidarData.ColorValues.Select(x => new PointMetadata3D(x)));
}
});

return xyzDataSeries3D;
}
</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p>We opted to choose a colour map and our ColorMapFunction simply mapped from height to a gradient brush, which we'll talk about below. This gives the added benefit of immediately being able to see the approximate elevation of items of interest based on the colour, which adds a fourth dimension to the visualisation.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 id="visualising-the-3d-point-cloud-in-wpf">Visualising the 3D Point Cloud in WPF</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The XyzDataSeries3D datatype in SciChart can be visualized with a point-cloud chart using the following code.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>XAML</strong></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In the XAML we declare a SciChart3DSurface, add an X,Y,Z axis and set some ChartModifiers for zooming, panning and orbiting the view.</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"u003cs3D:SciChart3DSurface x:Name=u0022SciChart3DSurfaceu0022 WorldDimensions=u00221000,200,1000u0022u003enntu003cs3D:SciChart3DSurface.Camerau003enttu003cs3D:Camera3D FarClip=u002210000u0022 Position=u0022800,1000,800u0022/u003entu003c/s3D:SciChart3DSurface.Camerau003enntu003cs3D:SciChart3DSurface.RenderableSeriesu003ennttu003c!u002du002d Renders the point cloud u002du002du003enttu003cs3D:ScatterRenderableSeries3D x:Name=u0022pointCloudu0022u003entttu003cs3D:ScatterRenderableSeries3D.PointMarkeru003enttttu003cs3D:PixelPointMarker3D Fill=u0022LimeGreenu0022/u003entttu003c/s3D:ScatterRenderableSeries3D.PointMarkeru003enttu003c/s3D:ScatterRenderableSeries3Du003entu003c/s3D:SciChart3DSurface.RenderableSeriesu003enntu003cs3D:SciChart3DSurface.XAxisu003enttu003cs3D:NumericAxis3D TextFormatting=u00220mu0022 AxisTitle=u0022X Distance (metres)u0022 /u003entu003c/s3D:SciChart3DSurface.XAxisu003entu003cs3D:SciChart3DSurface.YAxisu003enttu003cs3D:NumericAxis3D VisibleRange=u00220, 50u0022 TextFormatting=u00220mu0022 AxisTitle=u0022Height (metres)u0022 /u003entu003c/s3D:SciChart3DSurface.YAxisu003entu003cs3D:SciChart3DSurface.ZAxisu003enttu003cs3D:NumericAxis3D TextFormatting=u00220mu0022 AxisTitle=u0022Y Distance (metres)u0022 /u003entu003c/s3D:SciChart3DSurface.ZAxisu003enntu003c!u002du002d Enables interaction such as orbit, or movement on W-A-X-D key down u002du002du003entu003c!u002du002d Double click to reset zoom u002du002du003e ntu003cs3D:SciChart3DSurface.ChartModifieru003enttu003cs3D:ModifierGroup3Du003entttu003cs3D:OrbitModifier3D/u003entttu003cs3D:FreeLookModifier3D ExecuteWhen=u0022Ctrlu0022 MovementSpeed=u002220u0022/u003entttu003cs3D:MouseWheelZoomModifier3D/u003entttu003cs3D:ZoomExtentsModifier3D ResetPosition=u0022800,1000,800u0022 ResetTarget=u00220,25,0u0022 AnimateDurationMs=u0022500u0022/u003enttu003c/s3D:ModifierGroup3Du003entu003c/s3D:SciChart3DSurface.ChartModifieru003ennu003c/s3D:SciChart3DSurfaceu003e","proglang":"language-xml"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-xml">&lt;s3D:SciChart3DSurface x:Name="SciChart3DSurface" WorldDimensions="1000,200,1000">

&lt;s3D:SciChart3DSurface.Camera>
&lt;s3D:Camera3D FarClip="10000" Position="800,1000,800"/>
&lt;/s3D:SciChart3DSurface.Camera>

&lt;s3D:SciChart3DSurface.RenderableSeries>

&lt;!-- Renders the point cloud -->
&lt;s3D:ScatterRenderableSeries3D x:Name="pointCloud">
&lt;s3D:ScatterRenderableSeries3D.PointMarker>
&lt;s3D:PixelPointMarker3D Fill="LimeGreen"/>
&lt;/s3D:ScatterRenderableSeries3D.PointMarker>
&lt;/s3D:ScatterRenderableSeries3D>
&lt;/s3D:SciChart3DSurface.RenderableSeries>

&lt;s3D:SciChart3DSurface.XAxis>
&lt;s3D:NumericAxis3D TextFormatting="0m" AxisTitle="X Distance (metres)" />
&lt;/s3D:SciChart3DSurface.XAxis>
&lt;s3D:SciChart3DSurface.YAxis>
&lt;s3D:NumericAxis3D VisibleRange="0, 50" TextFormatting="0m" AxisTitle="Height (metres)" />
&lt;/s3D:SciChart3DSurface.YAxis>
&lt;s3D:SciChart3DSurface.ZAxis>
&lt;s3D:NumericAxis3D TextFormatting="0m" AxisTitle="Y Distance (metres)" />
&lt;/s3D:SciChart3DSurface.ZAxis>

&lt;!-- Enables interaction such as orbit, or movement on W-A-X-D key down -->
&lt;!-- Double click to reset zoom --> 
&lt;s3D:SciChart3DSurface.ChartModifier>
&lt;s3D:ModifierGroup3D>
&lt;s3D:OrbitModifier3D/>
&lt;s3D:FreeLookModifier3D ExecuteWhen="Ctrl" MovementSpeed="20"/>
&lt;s3D:MouseWheelZoomModifier3D/>
&lt;s3D:ZoomExtentsModifier3D ResetPosition="800,1000,800" ResetTarget="0,25,0" AnimateDurationMs="500"/>
&lt;/s3D:ModifierGroup3D>
&lt;/s3D:SciChart3DSurface.ChartModifier>

&lt;/s3D:SciChart3DSurface></pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p><strong>Code-behind</strong></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In the code-behind we load the ASC File using the AscReader class and setup our colour-map using SciChart's LinearColorMap class to convert each height-value into a linearly-interpolated colour in a gradient that we provided. The Gradient is mapped from 0 to 50 so in this example elevation 0m or below maps to DodgerBlue, while elevation 50m or above maps to Purple.</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"public partial class MainWindow : Windown{ntpublic MainWindow()nt{nttInitializeComponent();nnttReadLidarData();nt}nntprivate async void ReadLidarData()nt{ntt// The LinearColorMap type in SciChart allows you to generate a colour map based on a ntt// minimum and maximum value, e.g. min=0, max=50 means the gradient brush below is mapped into that rangentt// ntt// call .InitializeConstants() once and use ColorUtil.Lerp to interpolate the colormap for each data-value nttLinearColorMap colorMap = new LinearColorMap()ntt{ntttMinimum = 0,ntttMaximum = 50,ntttGradientStops = new []nttt{nttttnew ColorMapPoint() { Color = Colors.DodgerBlue.ToArgb(), Offset = 0},nttttnew ColorMapPoint() { Color = Colors.LimeGreen.ToArgb(), Offset = 0.2},nttttnew ColorMapPoint() { Color = Colors.Orange.ToArgb(), Offset = 0.5},nttttnew ColorMapPoint() { Color = Colors.OrangeRed.ToArgb(), Offset = 0.7},nttttnew ColorMapPoint() { Color = Colors.Purple.ToArgb(), Offset = 1},nttt}ntt};nttcolorMap.InitializeConstants();nntt// Read the ASC Lidar data file with optional color map datanttconst string filename = u0022LIDAR-DSM-2M-TQ38sw\\tq3080_DSM_2M.ascu0022;nttvar lidarData = await AscReader.ReadFileToAscData(filename, heightValue =u003e this.ColorMapFunction(heightValue, colorMap));nntt// Parse into SciChart formatnttpointCloud.DataSeries = await AscReader.ParseToXyzDataSeries(lidarData);nt}nntprivate Color ColorMapFunction(float heightValue, LinearColorMap colorMap)nt{ntt// Linearly interpolate each heightValue into a colour and return to the ASCReaderntt// This will be injected into the SciChart XyzDataSeries3D to colour points in the point-cloudnttuint argbColor = ColorUtil.Lerp(colorMap, heightValue);nttreturn ColorUtil.FromUInt(argbColor);nt}n}n","proglang":"language-csharp"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-csharp">public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

ReadLidarData();
}

private async void ReadLidarData()
{
// The LinearColorMap type in SciChart allows you to generate a colour map based on a 
// minimum and maximum value, e.g. min=0, max=50 means the gradient brush below is mapped into that range
// 
// call .InitializeConstants() once and use ColorUtil.Lerp to interpolate the colormap for each data-value 
LinearColorMap colorMap = new LinearColorMap()
{
Minimum = 0,
Maximum = 50,
GradientStops = new []
{
new ColorMapPoint() { Color = Colors.DodgerBlue.ToArgb(), Offset = 0},
new ColorMapPoint() { Color = Colors.LimeGreen.ToArgb(), Offset = 0.2},
new ColorMapPoint() { Color = Colors.Orange.ToArgb(), Offset = 0.5},
new ColorMapPoint() { Color = Colors.OrangeRed.ToArgb(), Offset = 0.7},
new ColorMapPoint() { Color = Colors.Purple.ToArgb(), Offset = 1},
}
};
colorMap.InitializeConstants();

// Read the ASC Lidar data file with optional color map data
const string filename = "LIDAR-DSM-2M-TQ38sw\tq3080_DSM_2M.asc";
var lidarData = await AscReader.ReadFileToAscData(filename, heightValue => this.ColorMapFunction(heightValue, colorMap));

// Parse into SciChart format
pointCloud.DataSeries = await AscReader.ParseToXyzDataSeries(lidarData);
}

private Color ColorMapFunction(float heightValue, LinearColorMap colorMap)
{
// Linearly interpolate each heightValue into a colour and return to the ASCReader
// This will be injected into the SciChart XyzDataSeries3D to colour points in the point-cloud
uint argbColor = ColorUtil.Lerp(colorMap, heightValue);
return ColorUtil.FromUInt(argbColor);
}
}
</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p>Finally here's the first result: a 3D Point-cloud with colour map visualizing the LiDAR/GIS dataset in WPF!</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33011,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/SciChart-WPF-3D-LiDAR-GIS-data-point-cloud-1024x640-1.jpg" alt="" class="wp-image-33011"/><figcaption>Visualising a 1km x 1km LiDAR dataset with 3D Point Cloud in SciChart WPF</figcaption></figure>
<!-- /wp:image -->

<!-- wp:heading {"level":3} -->
<h3 id="adding-contour-plots-and-topology-in-wpf">Adding Contour Plots and Topology in WPF</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>OK so this looks cool but notice in the image below, while the roads and river Thames is clearly visible, <strong>the buildings look kind of ghostly and are hard to make out</strong>. There's a lot of data here which is easy to ignore as the point-cloud doesn't have any solidity to it.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33013,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/wpf-lidar-data-pointcloud-visualisation-can-be-improve-1024x640-1.jpg" alt="" class="wp-image-33013"/><figcaption>Point-cloud data alone leads to visualisations which are difficult to interpret. This can be improved with contours / topology visualisation.</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>A solution to this is to add contours and topology visualisation to the same 3D graph. SciChart also supports a Surface Mesh 3D Chart type which allows you to visualize a uniform grid of height-values as a surface or mesh, add contours, wireframe and more. We can even add a height-map legend to make it easier for the human-eye to map colour to height.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 id="the-surface-mesh-contours-code-in-c-">The Surface Mesh / Contours Code in C#</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Transforming the AscData struct earlier into a dataformat to be read by SciChart's Surface Mesh / Topology map. Here's the code:</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>XAML</strong></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>SciChart WPF requires a SurfaceMeshRenderableSeries3D to render the data as a topology / height-map. I've set this to render solid-mesh with contours, and the contours are automatically calculated by our GPU shaders by applying a MeshColorPalette.</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"u003cGridu003entu003cGrid.Resourcesu003enttu003c!u002du002d Define the Surface Mesh colour map. 0 to 1 is mapped to SurfaceMesh.Minimum/Maximum u002du002du003e nttu003c!u002du002d In this ase DodgerBlue maps to 0m or below and purple to 50m or above u002du002du003e nttu003cLinearGradientBrush x:Key=u0022ColorMapu0022 StartPoint=u00220,0u0022 EndPoint=u00220,1u0022u003entttu003cGradientStop Offset=u00220u0022 Color=u0022DodgerBlueu0022/u003entttu003cGradientStop Offset=u00220.2u0022 Color=u0022LimeGreenu0022/u003entttu003cGradientStop Offset=u00220.5u0022 Color=u0022Orangeu0022/u003entttu003cGradientStop Offset=u00220.7u0022 Color=u0022OrangeRedu0022/u003entttu003cGradientStop Offset=u00221.0u0022 Color=u0022Purpleu0022/u003enttu003c/LinearGradientBrushu003entu003c/Grid.Resourcesu003enntu003cs3D:SciChart3DSurface x:Name=u0022SciChart3DSurfaceu0022 WorldDimensions=u00221000,200,1000u0022u003ennttu003cs3D:SciChart3DSurface.RenderableSeriesu003enntttu003c!u002du002d Renders the contours and topography map u002du002du003entttu003c!u002du002d Try different DrawMeshAs modes such as Contours, SolidMesh, SolidMeshWireframe etc u002du002du003entttu003cs3D:SurfaceMeshRenderableSeries3D x:Name=u0022surfaceMeshu0022 DrawMeshAs=u0022SolidWithContoursu0022 nttttttttttt   MeshPaletteMode=u0022HeightMapInterpolatedu0022nttttttttttt   ContourStroke=u0022Azureu0022 ContourStrokeThickness=u00222u0022 Minimum=u00220u0022 Maximum=u002250u0022 nttttttttttt   Opacity=u00221u0022u003enttttu003cs3D:SurfaceMeshRenderableSeries3D.MeshColorPaletteu003entttttu003cs3D:BrushColorPalette Brush=u0022{StaticResource ColorMap}u0022/u003enttttu003c/s3D:SurfaceMeshRenderableSeries3D.MeshColorPaletteu003entttu003c/s3D:SurfaceMeshRenderableSeries3Du003enttu003c/s3D:SciChart3DSurface.RenderableSeriesu003ennttu003c!u002du002d X,Y,Z Axis, modifiers omitted for brevity u002du002du003enntu003c/s3D:SciChart3DSurfaceu003ennu003c/Gridu003e","proglang":"language-xml"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-xml">&lt;Grid>
&lt;Grid.Resources>
&lt;!-- Define the Surface Mesh colour map. 0 to 1 is mapped to SurfaceMesh.Minimum/Maximum --> 
&lt;!-- In this ase DodgerBlue maps to 0m or below and purple to 50m or above --> 
&lt;LinearGradientBrush x:Key="ColorMap" StartPoint="0,0" EndPoint="0,1">
&lt;GradientStop Offset="0" Color="DodgerBlue"/>
&lt;GradientStop Offset="0.2" Color="LimeGreen"/>
&lt;GradientStop Offset="0.5" Color="Orange"/>
&lt;GradientStop Offset="0.7" Color="OrangeRed"/>
&lt;GradientStop Offset="1.0" Color="Purple"/>
&lt;/LinearGradientBrush>
&lt;/Grid.Resources>

&lt;s3D:SciChart3DSurface x:Name="SciChart3DSurface" WorldDimensions="1000,200,1000">

&lt;s3D:SciChart3DSurface.RenderableSeries>

&lt;!-- Renders the contours and topography map -->
&lt;!-- Try different DrawMeshAs modes such as Contours, SolidMesh, SolidMeshWireframe etc -->
&lt;s3D:SurfaceMeshRenderableSeries3D x:Name="surfaceMesh" DrawMeshAs="SolidWithContours" 
   MeshPaletteMode="HeightMapInterpolated"
   ContourStroke="Azure" ContourStrokeThickness="2" Minimum="0" Maximum="50" 
   Opacity="1">
&lt;s3D:SurfaceMeshRenderableSeries3D.MeshColorPalette>
&lt;s3D:BrushColorPalette Brush="{StaticResource ColorMap}"/>
&lt;/s3D:SurfaceMeshRenderableSeries3D.MeshColorPalette>
&lt;/s3D:SurfaceMeshRenderableSeries3D>
&lt;/s3D:SciChart3DSurface.RenderableSeries>

&lt;!-- X,Y,Z Axis, modifiers omitted for brevity -->

&lt;/s3D:SciChart3DSurface>

&lt;/Grid></pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p><strong>Code-Behind</strong></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In code we simply have to transform the AscData to UniformGridDataSeries3D to display the uniform mesh.</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"surfaceMesh.DataSeries = await AscReader.ParseToGridDataSeries(lidarData);nn// Where ParseToGridDataSeries is implemented asnpublic static async Tasku003cUniformGridDataSeries3Du003cfloatu003eu003e ParseToGridDataSeries(AscData lidarData)n{n    UniformGridDataSeries3Du003cfloatu003e uniformGridDataSeries = null;nn    await Task.Run(() =u003en    {n        // Create the UniformGridDataSeries3Dn        uniformGridDataSeries = new UniformGridDataSeries3Du003cfloatu003e(lidarData.NumberColumns, lidarData.NumberRows);n        // Set the step X,Z to AscData.CellSize in metresn        uniformGridDataSeries.StartX = 0;n        uniformGridDataSeries.StepX = lidarData.CellSize;n        uniformGridDataSeries.StartZ = 0;n        uniformGridDataSeries.StepZ = lidarData.CellSize;nn        // Fill the 2-dimensional array with height valuesn        int index = 0;n        for (int z = 0; z u003c lidarData.NumberRows; z++)n        {n            for (int x = 0; x u003c lidarData.NumberColumns; x++)n            {n                uniformGridDataSeries.InternalArray[z][x] = lidarData.YValues[index++];n            }n        }n    });nn    return uniformGridDataSeries;n}","proglang":"language-csharp"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-csharp">surfaceMesh.DataSeries = await AscReader.ParseToGridDataSeries(lidarData);

// Where ParseToGridDataSeries is implemented as
public static async Task&lt;UniformGridDataSeries3D&lt;float>> ParseToGridDataSeries(AscData lidarData)
{
    UniformGridDataSeries3D&lt;float> uniformGridDataSeries = null;

    await Task.Run(() =>
    {
        // Create the UniformGridDataSeries3D
        uniformGridDataSeries = new UniformGridDataSeries3D&lt;float>(lidarData.NumberColumns, lidarData.NumberRows);
        // Set the step X,Z to AscData.CellSize in metres
        uniformGridDataSeries.StartX = 0;
        uniformGridDataSeries.StepX = lidarData.CellSize;
        uniformGridDataSeries.StartZ = 0;
        uniformGridDataSeries.StepZ = lidarData.CellSize;

        // Fill the 2-dimensional array with height values
        int index = 0;
        for (int z = 0; z &lt; lidarData.NumberRows; z++)
        {
            for (int x = 0; x &lt; lidarData.NumberColumns; x++)
            {
                uniformGridDataSeries.InternalArray[z][x] = lidarData.YValues[index++];
            }
        }
    });

    return uniformGridDataSeries;
}</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:heading {"level":3} -->
<h3 id="the-wpf-surface-mesh-3d-result">The WPF Surface Mesh 3D Result</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>With an opacity of 1.0 the surface mesh looks like this. Height-mapping to the surface-mesh, the colour map and contours are all computed automatically by SciChart, which places contours at set user-defined intervals and maps height-values to colour and displacement in GPU shader programs.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33015,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/Surface-mesh-contour-wpf-topology-lidar-data-1024x640-1.jpg" alt="" class="wp-image-33015"/><figcaption>A SurfaceMeshRenderableSeries3D in SciChart WPF rendering the LiDAR/GIS data as a height-map with contours.&nbsp;</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>At full opacity it looks nice but it's hard to see the information about the depth of the field there, so by quickly adding a Legend control with slider for the opacity to the 3D Viewport we can change the transparency of the Point Cloud and Surface Mesh to achieve the desired visual effect. And here it is in all it's glory below!</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
{% embed https://www.youtube.com/embed/GtDuk9-HX9A %}
<figure class="kg-card kg-embed-card kg-card-hascaption">
<figcaption>Watch the 1 minute video above to see the WPF LiDAR Demo</figcaption>
</figure>
<!-- /wp:html -->

<!-- wp:heading {"level":3} -->
<h3 id="get-the-wpf-lidar-demo-code">Get the WPF LiDAR Demo Code</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>You can get the code for this demo from the <a href="https://github.com/ABTSoftware/SciChart.Wpf.Examples/tree/SciChart_v6_Release/Sandbox/CustomerExamples/Lidar3DPointCloudDemo">SciChart.Wpf.Examples Github page</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="ios-android-mobile-lidar-point-cloud-visualization">iOS/Android Mobile LiDAR Point Cloud Visualization</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>It turns out the same thing can be achieved on mobile (iOS / Android device). SciChart is a cross-platform data-visualisation library and with its <a href="https://www.scichart.com/scichart-wpf-v6-the-worlds-fastest-wpf-charts/">proprietary 3D graphics engine codenamed Visual Xccelerator</a>, it can plot the same kind of data on <strong>Windows</strong>, <strong>iOS</strong>, <strong>Android</strong>, <strong>Mac OSX</strong> and even <strong>browser applications with WebGL. </strong></p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33017,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/lidar-ipad-demo_ipadair2_spacegrey_landscape-solidback-1024x737-1.jpg" alt="" class="wp-image-33017"/><figcaption>SciChart for iOS/Android supports 3D Chart and DataVisualisation types including point-cloud, 3D Scatter/bubble chart, 3D Surface mesh, contours and more</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>We ported the above LiDAR / GIS Point-cloud and contour topology demo into an iOS app written in Swift in just an hour, and this resulted in the same stunning performance and graphics as our Windows demo. Take a look at the video below to see SciChart.iOS visualising this same dataset.</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
{% embed https://www.youtube.com/embed/FYVZ96VVyOw %}
<figure class="kg-card kg-embed-card kg-card-hascaption">
<figcaption>The LiDAR Point-Cloud / Topology Map Contours demo running on an iPad Air 2 using SciChart.iOS. We also support Android applications written in Java/Kotlin and Xamarin!</figcaption>
</figure>
<!-- /wp:html -->

<!-- wp:paragraph -->
<p>The source-code for the iOS demo is found at our Github repository under <a href="https://github.com/abtsoftware/scichart.ios.examples">SciChart.iOS.Examples</a> -&gt; Featured Applications -&gt; LiDAR Demo. Download the code today and check it our if you're interested in this kind of visualisation on iOS.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="javascript-lidar-point-cloud-visualization">Javascript LiDAR Point Cloud Visualization</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We mentioned our graphics engine was cross-platform, well, recently we have compiled it to <strong>WebAssembly </strong>with <strong>WebGL</strong> to create cutting-edge high performance charts for Javascript and Typescript applications. You can see a preview of <a href="/scichart-js-fast-javascript-chart-progress-update-april-2020/">SciChart.js - High Performance Javascript Charts here</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>SciChart.js supports the same types as our Windows and Mobile 2D/3D datavisualisation SDKs. The point-cloud and SurfaceMesh types exist here and we have <a href="/scichart-js-fast-javascript-3d-charts-update-june-2020/">3D Charts for Javascript/WebGL applications</a> too.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33019,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/image-5-1.png" alt="" class="wp-image-33019"/><figcaption>SciChart.js - Fast, Realtime Javascript 2D/3D Charts can visualise LiDAR data as point cloud and surface mesh with contours in a web browser using WebAssembly and WebGL</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Using our proprietary cross-platform graphics engine Visual Xccelerator, SciChart.js is able to bring you fast 2D/3D Charts and Data Visualization in the browser with stunning performance through the use of WebGL and WebAssembly.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The source-code looks remarkably similar to our WPF (Windows) and mobile versions, where we load the dataset, create a SciChart3DSurface and use the ScatterRenderableSeries3D with PixelPointMarker to render the dataset. Add a camera and some interactivity modifiers and you have a ready-made Javascript LiDAR visualisation application in just 100 lines of code!</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"import { createSciChart3DSurface } from u0022../../../../../../srcu0022;nimport { CameraController } from u0022../../../../../../src/Charting3D/CameraControlleru0022;nimport { MouseWheelZoomModifier3D } from u0022../../../../../../src/Charting3D/ChartModifiers/MouseWheelZoomModifier3Du0022;nimport { OrbitModifier3D } from u0022../../../../../../src/Charting3D/ChartModifiers/OrbitModifier3Du0022;nimport { XyzDataSeries3D } from u0022../../../../../../src/Charting3D/Model/DataSeries/XyzDataSeries3Du0022;nimport { Vector3 } from u0022../../../../../../src/Charting3D/Vector3u0022;nimport { NumericAxis3D } from u0022../../../../../../src/Charting3D/Visuals/Axis/NumericAxis3Du0022;nimport { PixelPointMarker3D } from u0022../../../../../../src/Charting3D/Visuals/PointMarkers/DefaultPointMarkersu0022;nimport { ScatterRenderableSeries3D } from u0022../../../../../../src/Charting3D/Visuals/RenderableSeries/ScatterRenderableSeries3Du0022;nimport { TSciChart3D } from u0022../../../../../../src/types/TSciChart3Du0022;nnimport { EColor } from u0022../../../../../../src/types/Coloru0022;nimport { EColorMapMode, TLinearColorMap } from u0022../../../../../../src/types/TLinearColorMapu0022;nimport { linearColorMapLerp } from u0022../../../../../../src/utils/colorUtilu0022;nimport { AscData, AscReader } from u0022./AscReaderu0022;nnexport const divElementId = u0022chart1u0022;nntype TMetadata = {n    vertexColorAbgr: number;n    pointScale: number;n};nnexport const drawExample = async () =u003e {n    const { sciChart3DSurface, wasmContext } = await createSciChart3DSurface(divElementId, 1080, 720);n    sciChart3DSurface.camera = new CameraController(wasmContext, {n        position: new Vector3(300, 300, 300),n        target: new Vector3(0, 50, 0)n    });nn    sciChart3DSurface.chartModifiers.add(new MouseWheelZoomModifier3D());n    sciChart3DSurface.chartModifiers.add(new OrbitModifier3D());nn    sciChart3DSurface.xAxis = new NumericAxis3D(wasmContext, { axisTitle: u0022X Axisu0022 });n    sciChart3DSurface.yAxis = new NumericAxis3D(wasmContext, { axisTitle: u0022Y Axisu0022 });n    sciChart3DSurface.zAxis = new NumericAxis3D(wasmContext, { axisTitle: u0022Z Axisu0022 });nn    const defaultPointMarker = new PixelPointMarker3D(wasmContext, { fill: u0022#00FF00u0022 });n    // const defaultPointMarker = new SpherePointMarker3D(wasmContext, { size: 1, fill: u0022#00FF00u0022 });n    const series = new ScatterRenderableSeries3D(wasmContext, { pointMarker: defaultPointMarker });nn    series.dataSeries = getData(wasmContext);n    sciChart3DSurface.renderableSeries.add(series);nn    return { sciChart3DSurface, wasmContext };n};nnfunction getData(wasmContext: TSciChart3D) {n    // The LinearColorMap type in SciChart allows you to generate a colour map based on an    // minimum and maximum value, e.g. min=0, max=50 means the gradient brush below is mapped into that rangen    //n    const colorMap: TLinearColorMap = {n        Minimum: 0,n        Maximum: 50,n        Mode: EColorMapMode.Interpolated,n        GradientStops: [n            { color: EColor.DodgerBlue, offset: 0 },n            { color: EColor.LimeGreen, offset: 0.2 },n            { color: EColor.Orange, offset: 0.5 },n            { color: EColor.OrangeRed, offset: 0.7 },n            { color: EColor.Purple, offset: 1 }n        ]n    };nn    // Read the ASC Lidar data file with optional color map datan    const reader: AscReader = new AscReader(height =u003e colorMapFunction(height, colorMap));n    const ascData: AscData = reader.readFileToAscData(u0022Point cloudu0022);nn    // Prepare metadatan    const meta: TMetadata[] = ascData.ColorValues.map(c =u003e ({n        vertexColorAbgr: c,n        pointScale: 0n    }));nn    const xyzDataSeries = new XyzDataSeries3D(wasmContext);n    xyzDataSeries.appendRange(ascData.XValues, ascData.YValues, ascData.ZValues, meta);nn    return xyzDataSeries;n}nnfunction colorMapFunction(heightValue: number, colorMap: TLinearColorMap): number {n    // Linearly interpolate each heightValue into a colour and return to the ASCReadern    // This will be injected into the SciChart XyzDataSeries3D to colour points in the point-cloudn    return linearColorMapLerp(colorMap, heightValue);n}n"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-js">import { createSciChart3DSurface } from "../../../../../../src";
import { CameraController } from "../../../../../../src/Charting3D/CameraController";
import { MouseWheelZoomModifier3D } from "../../../../../../src/Charting3D/ChartModifiers/MouseWheelZoomModifier3D";
import { OrbitModifier3D } from "../../../../../../src/Charting3D/ChartModifiers/OrbitModifier3D";
import { XyzDataSeries3D } from "../../../../../../src/Charting3D/Model/DataSeries/XyzDataSeries3D";
import { Vector3 } from "../../../../../../src/Charting3D/Vector3";
import { NumericAxis3D } from "../../../../../../src/Charting3D/Visuals/Axis/NumericAxis3D";
import { PixelPointMarker3D } from "../../../../../../src/Charting3D/Visuals/PointMarkers/DefaultPointMarkers";
import { ScatterRenderableSeries3D } from "../../../../../../src/Charting3D/Visuals/RenderableSeries/ScatterRenderableSeries3D";
import { TSciChart3D } from "../../../../../../src/types/TSciChart3D";

import { EColor } from "../../../../../../src/types/Color";
import { EColorMapMode, TLinearColorMap } from "../../../../../../src/types/TLinearColorMap";
import { linearColorMapLerp } from "../../../../../../src/utils/colorUtil";
import { AscData, AscReader } from "./AscReader";

export const divElementId = "chart1";

type TMetadata = {
    vertexColorAbgr: number;
    pointScale: number;
};

export const drawExample = async () => {
    const { sciChart3DSurface, wasmContext } = await createSciChart3DSurface(divElementId, 1080, 720);
    sciChart3DSurface.camera = new CameraController(wasmContext, {
        position: new Vector3(300, 300, 300),
        target: new Vector3(0, 50, 0)
    });

    sciChart3DSurface.chartModifiers.add(new MouseWheelZoomModifier3D());
    sciChart3DSurface.chartModifiers.add(new OrbitModifier3D());

    sciChart3DSurface.xAxis = new NumericAxis3D(wasmContext, { axisTitle: "X Axis" });
    sciChart3DSurface.yAxis = new NumericAxis3D(wasmContext, { axisTitle: "Y Axis" });
    sciChart3DSurface.zAxis = new NumericAxis3D(wasmContext, { axisTitle: "Z Axis" });

    const defaultPointMarker = new PixelPointMarker3D(wasmContext, { fill: "#00FF00" });
    // const defaultPointMarker = new SpherePointMarker3D(wasmContext, { size: 1, fill: "#00FF00" });
    const series = new ScatterRenderableSeries3D(wasmContext, { pointMarker: defaultPointMarker });

    series.dataSeries = getData(wasmContext);
    sciChart3DSurface.renderableSeries.add(series);

    return { sciChart3DSurface, wasmContext };
};

function getData(wasmContext: TSciChart3D) {
    // The LinearColorMap type in SciChart allows you to generate a colour map based on a
    // minimum and maximum value, e.g. min=0, max=50 means the gradient brush below is mapped into that range
    //
    const colorMap: TLinearColorMap = {
        Minimum: 0,
        Maximum: 50,
        Mode: EColorMapMode.Interpolated,
        GradientStops: [
            { color: EColor.DodgerBlue, offset: 0 },
            { color: EColor.LimeGreen, offset: 0.2 },
            { color: EColor.Orange, offset: 0.5 },
            { color: EColor.OrangeRed, offset: 0.7 },
            { color: EColor.Purple, offset: 1 }
        ]
    };

    // Read the ASC Lidar data file with optional color map data
    const reader: AscReader = new AscReader(height => colorMapFunction(height, colorMap));
    const ascData: AscData = reader.readFileToAscData("Point cloud");

    // Prepare metadata
    const meta: TMetadata[] = ascData.ColorValues.map(c => ({
        vertexColorAbgr: c,
        pointScale: 0
    }));

    const xyzDataSeries = new XyzDataSeries3D(wasmContext);
    xyzDataSeries.appendRange(ascData.XValues, ascData.YValues, ascData.ZValues, meta);

    return xyzDataSeries;
}

function colorMapFunction(heightValue: number, colorMap: TLinearColorMap): number {
    // Linearly interpolate each heightValue into a colour and return to the ASCReader
    // This will be injected into the SciChart XyzDataSeries3D to colour points in the point-cloud
    return linearColorMapLerp(colorMap, heightValue);
}
</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:heading -->
<h2 id="what-is-scichart-find-out-more"><em><strong>What is SciChart / Find out more </strong></em></h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p><em><em><em><em><em><em><em><em>SciChart provides high performance real</em></em></em></em></em></em>-<em><em><em><em><em><em>time chart components on the WPF (Windows), iOS, Android</em></em></em></em>,<em><em><em><em> Xamarin</em></em></em></em>, Mac OSX and </em></em></em>Javascript <em><em><em><em><em><em><em>platforms. It is our goal to create the best cross-platform Native WPF, iOS, Android and JavaScript 2D &amp; 3D Charts</em></em></em></em></em></em></em> and Data Visualisation toolkit<em><em><em><em><em><em><em> in the world, focusing on performance, developer productivity, ease of use, depth of features and enterprise-grade tech support.</em></em></em></em></em></em></em></em></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><em><em><em><em><em><em><em><em>If you have a question about what SciChart's </em></em></em></em>WPF, Mobile, or <em><em><em><em>WebGL / WebAssembly Charts can offer you, or if you would like to get a quote, please <a href="https://www.scichart.com/contact-us/">contact us</a>. Our friendly, helpful support team will be glad to help!</em></em></em></em></em></em></em></em></p>
<!-- /wp:paragraph -->