Pre loader

LiDAR UAV 3D Point-cloud and Contour Visualisation in WPF,iOS,Android & Javascript

LiDAR UAV 3D Point-cloud and Contour Visualisation in WPF,iOS,Android & Javascript

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 & visualisation in many different sectors, but I lacked the good example to demonstrate the flexibility and power of SciChart.

In this blog post I’m going to walk you through the following:

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

Read on to find out how we did this!

Visualizing LiDAR Data in 3D

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

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

The power to visualize LiDAR / Geospatial data is sitting in most of our pockets in the form of an iPhone or Android phone, or in our cars or offices in the form of a tablet or laptop. 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 SciChart.

Where to get LiDAR Data

The problem I had was where to get a dataset. What is the dataset format? How large are the dataset files?

Well, I basically googled ‘LiDAR Dataset’ and the first link was a UK Government site with LiDAR composite data for the entire United Kingdom. OK, That’s probably a big dataset … Luckily they had a nice website where you could select a region and download sub datasets.

The Defra Survey Data Download website allows you to select LiDAR datasets of the United Kingdom for download

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.

The ASC File Format

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 🙂

The ASC LiDAR Data File Format is pretty simple, including some header parameters and an array of floating-point values 

WPF (Windows) C# LiDAR Visualisation Demo

Reading an ASC LiDAR/GIS Data File in C#

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.

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<float> XValues { get; set; }
        public List<float> YValues { get; set; }
        public List<float> ZValues { get; set; }
        public List<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<AscData> ReadFileToAscData(
            string filename, Func<float, Color> colorMapFunction, Action<int> reportProgress = null)
            AscData result = new AscData()
                XValues = new List<float>(),
                YValues = new List<float>(),
                ZValues = new List<float>(),
                ColorValues = new List<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 < 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();


                        if (colorMapFunction != null)
                            // Optional color-mapping of points based on height 
                            Color[] colorValuesRow = heightValuesRow

                        // 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;

In this class ASCReader we have an async method ReadFileToAscData:

public static async Task<AscData> ReadFileToAscData(
            string filename, 
            Func<float, Color> colorMapFunction,
            Action<int> reportProgress = null)

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.

Once the data is loaded, in this case into a struct called AscData, which looks as follows:

public struct AscData
public List<float> XValues { get; set; }
public List<float> YValues { get; set; }
public List<float> ZValues { get; set; }
public List<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; }

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.

Mapping AscData ASC LiDAR/GIS Data Format to SciChart WPF

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.

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.

// Transforming AscData to SciChart XyzDataSeries3D for point-clouds
public static async Task<XyzDataSeries3D<float>> ParseToXyzDataSeries(AscData lidarData)
var xyzDataSeries3D = new XyzDataSeries3D<float>();

await Task.Run(() =>
if (lidarData.ColorValues.IsNullOrEmpty())
            // When no Color map, just append XYZ points
xyzDataSeries3D.Append(lidarData.XValues, lidarData.YValues, lidarData.ZValues);
            // 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;

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.

Visualising the 3D Point Cloud in WPF

The XyzDataSeries3D datatype in SciChart can be visualized with a point-cloud chart using the following code.


In the XAML we declare a SciChart3DSurface, add an X,Y,Z axis and set some ChartModifiers for zooming, panning and orbiting the view.

<s3D:SciChart3DSurface x:Name="SciChart3DSurface" WorldDimensions="1000,200,1000">

<s3D:Camera3D FarClip="10000" Position="800,1000,800"/>


<!-- Renders the point cloud -->
<s3D:ScatterRenderableSeries3D x:Name="pointCloud">
<s3D:PixelPointMarker3D Fill="LimeGreen"/>

<s3D:NumericAxis3D TextFormatting="0m" AxisTitle="X Distance (metres)" />
<s3D:NumericAxis3D VisibleRange="0, 50" TextFormatting="0m" AxisTitle="Height (metres)" />
<s3D:NumericAxis3D TextFormatting="0m" AxisTitle="Y Distance (metres)" />

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



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.

public partial class MainWindow : Window
public MainWindow()


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},

// 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);

Finally here’s the first result: a 3D Point-cloud with colour map visualizing the LiDAR/GIS dataset in WPF!

Visualising a 1km x 1km LiDAR dataset with 3D Point Cloud in SciChart WPF

Adding Contour Plots and Topology in WPF

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

Point-cloud data alone leads to visualisations which are difficult to interpret. This can be improved with contours / topology visualisation.

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.

The Surface Mesh / Contours Code in C#

Transforming the AscData struct earlier into a dataformat to be read by SciChart’s Surface Mesh / Topology map. Here’s the code:


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.

<!-- Define the Surface Mesh colour map. 0 to 1 is mapped to SurfaceMesh.Minimum/Maximum --> 
<!-- In this ase DodgerBlue maps to 0m or below and purple to 50m or above --> 
<LinearGradientBrush x:Key="ColorMap" StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="DodgerBlue"/>
<GradientStop Offset="0.2" Color="LimeGreen"/>
<GradientStop Offset="0.5" Color="Orange"/>
<GradientStop Offset="0.7" Color="OrangeRed"/>
<GradientStop Offset="1.0" Color="Purple"/>

<s3D:SciChart3DSurface x:Name="SciChart3DSurface" WorldDimensions="1000,200,1000">


<!-- Renders the contours and topography map -->
<!-- Try different DrawMeshAs modes such as Contours, SolidMesh, SolidMeshWireframe etc -->
<s3D:SurfaceMeshRenderableSeries3D x:Name="surfaceMesh" DrawMeshAs="SolidWithContours" 
   ContourStroke="Azure" ContourStrokeThickness="2" Minimum="0" Maximum="50" 
<s3D:BrushColorPalette Brush="{StaticResource ColorMap}"/>

<!-- X,Y,Z Axis, modifiers omitted for brevity -->




In code we simply have to transform the AscData to UniformGridDataSeries3D to display the uniform mesh.

surfaceMesh.DataSeries = await AscReader.ParseToGridDataSeries(lidarData);

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

    await Task.Run(() =>
        // Create the UniformGridDataSeries3D
        uniformGridDataSeries = new UniformGridDataSeries3D<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 < lidarData.NumberRows; z++)
            for (int x = 0; x < lidarData.NumberColumns; x++)
                uniformGridDataSeries.InternalArray[z][x] = lidarData.YValues[index++];

    return uniformGridDataSeries;

The WPF Surface Mesh 3D Result

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.

A SurfaceMeshRenderableSeries3D in SciChart WPF rendering the LiDAR/GIS data as a height-map with contours. 

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!


Watch the 1 minute video above to see the WPF LiDAR Demo

Get the WPF LiDAR Demo Code

You can get the code for this demo from the SciChart.Wpf.Examples Github page.

iOS/Android Mobile LiDAR Point Cloud Visualization

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 proprietary 3D graphics engine codenamed Visual Xccelerator, it can plot the same kind of data on Windows, iOS, Android, Mac OSX and even browser applications with WebGL.

SciChart for iOS/Android supports 3D Chart and DataVisualisation types including point-cloud, 3D Scatter/bubble chart, 3D Surface mesh, contours and more

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.


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!

The source-code for the iOS demo is found at our Github repository under SciChart.iOS.Examples -> Featured Applications -> LiDAR Demo. Download the code today and check it our if you’re interested in this kind of visualisation on iOS.

Javascript LiDAR Point Cloud Visualization

We mentioned our graphics engine was cross-platform, well, recently we have compiled it to WebAssembly with WebGL to create cutting-edge high performance charts for Javascript and Typescript applications. You can see a preview of SciChart.js – High Performance Javascript Charts here.

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 3D Charts for Javascript/WebGL applications too.

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

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.

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!

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); = 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);

    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[] = => ({
        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);

What is SciChart / Find out more

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

If you have a question about what SciChart’s WPF, Mobile, or WebGL / WebAssembly Charts can offer you, or if you would like to get a quote, please contact us. Our friendly, helpful support team will be glad to help!

By Andrew Burnett-Thompson | Jun 23, 2020
CEO / Founder of SciChart. Masters (MEng) and PhD in Electronics & Signal Processing.Follow me on LinkedIn for more SciChart content, or twitter at @drandrewbt.

Leave a Reply