关注

C# LAS 点云读取与处理工具

C# LAS 点云读取、处理和可视化工具,支持 LAS/LAZ 格式、点云滤波、坐标转换、数据统计等功能。

一、项目结构

LasPointCloudViewer/
├── LasPointCloudViewer.csproj
├── Program.cs
├── MainForm.cs
├── MainForm.Designer.cs
├── Las/
│   ├── LasReader.cs              ★ LAS文件读取核心
│   ├── LasHeader.cs             LAS文件头
│   ├── LasPoint.cs              点数据结构
│   ├── LasPointFormat.cs        点格式定义
│   ├── LasVlr.cs               可变长记录
│   └── LasWriter.cs             LAS文件写入
├── Processing/
│   ├── PointCloud.cs            点云数据容器
│   ├── PointFilter.cs          点云滤波
│   ├── CoordinateTransform.cs  坐标转换
│   └── Statistics.cs           统计分析
├── Visualization/
│   ├── PointCloudRenderer.cs   点云渲染
│   ├── Camera.cs               相机控制
│   └── OpenGLControl.cs       OpenGL控件
├── Utils/
│   ├── BinaryReaderEx.cs      二进制读取扩展
│   ├── MathUtils.cs           数学工具
│   └── ProgressReporter.cs    进度报告
└── Models/
    ├── BoundingBox.cs          包围盒
    ├── Vector3.cs             三维向量
    └── Color.cs              颜色结构

二、核心代码实现

1. 主程序入口 (Program.cs)

using System;
using System.Windows.Forms;

namespace LasPointCloudViewer
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            // 设置OpenGL支持
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            
            Application.Run(new MainForm());
        }
    }
}

2. 主窗体 (MainForm.cs)

using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using LasPointCloudViewer.Las;
using LasPointCloudViewer.Processing;
using LasPointCloudViewer.Visualization;
using LasPointCloudViewer.Models;

namespace LasPointCloudViewer
{
    public partial class MainForm : Form
    {
        private LasReader lasReader;
        private PointCloud pointCloud;
        private PointCloudRenderer renderer;
        private Camera camera;
        private OpenGLControl glControl;
        
        // 状态
        private bool isLoaded = false;
        private string currentFilePath = "";
        
        public MainForm()
        {
            InitializeComponent();
            InitializeComponents();
        }
        
        private void InitializeComponent()
        {
            this.Text = "LAS 点云查看器";
            this.Size = new Size(1200, 800);
            this.StartPosition = FormStartPosition.CenterScreen;
            this.WindowState = FormWindowState.Maximized;
            
            // 菜单栏
            MenuStrip menuStrip = new MenuStrip();
            
            // 文件菜单
            ToolStripMenuItem fileMenu = new ToolStripMenuItem("文件(&F)");
            fileMenu.DropDownItems.Add("打开LAS文件...", null, OpenLasFile_Click);
            fileMenu.DropDownItems.Add("打开LAZ文件...", null, OpenLazFile_Click);
            fileMenu.DropDownItems.Add(new ToolStripSeparator());
            fileMenu.DropDownItems.Add("保存为LAS...", null, SaveLasFile_Click);
            fileMenu.DropDownItems.Add(new ToolStripSeparator());
            fileMenu.DropDownItems.Add("退出", null, (s, e) => Application.Exit());
            
            // 视图菜单
            ToolStripMenuItem viewMenu = new ToolStripMenuItem("视图(&V)");
            viewMenu.DropDownItems.Add("重置视图", null, ResetView_Click);
            viewMenu.DropDownItems.Add("适应视图", null, FitView_Click);
            viewMenu.DropDownItems.Add(new ToolStripSeparator());
            viewMenu.DropDownItems.Add("显示点云", null, TogglePoints);
            viewMenu.DropDownItems.Add("显示坐标轴", null, ToggleAxes);
            viewMenu.DropDownItems.Add("显示包围盒", null, ToggleBoundingBox);
            
            // 处理菜单
            ToolStripMenuItem processMenu = new ToolStripMenuItem("处理(&P)");
            processMenu.DropDownItems.Add("统计信息", null, ShowStatistics_Click);
            processMenu.DropDownItems.Add("滤波去噪", null, FilterNoise_Click);
            processMenu.DropDownItems.Add("降采样", null, Downsample_Click);
            processMenu.DropDownItems.Add("坐标转换", null, TransformCoordinates_Click);
            
            menuStrip.Items.AddRange(new ToolStripItem[] { fileMenu, viewMenu, processMenu });
            
            // 工具栏
            ToolStrip toolStrip = new ToolStrip();
            
            ToolStripButton btnOpen = new ToolStripButton("打开");
            btnOpen.Click += OpenLasFile_Click;
            
            ToolStripButton btnSave = new ToolStripButton("保存");
            btnSave.Click += SaveLasFile_Click;
            
            ToolStripButton btnReset = new ToolStripButton("重置视图");
            btnReset.Click += ResetView_Click;
            
            ToolStripButton btnFit = new ToolStripButton("适应视图");
            btnFit.Click += FitView_Click;
            
            toolStrip.Items.AddRange(new ToolStripItem[] { btnOpen, btnSave, new ToolStripSeparator(), btnReset, btnFit });
            
            // 主布局
            SplitContainer mainSplit = new SplitContainer
            {
                Dock = DockStyle.Fill,
                Orientation = Orientation.Vertical,
                SplitterDistance = 300
            };
            
            // 左侧信息面板
            Panel infoPanel = CreateInfoPanel();
            mainSplit.Panel1.Controls.Add(infoPanel);
            
            // 右侧OpenGL视图
            glControl = new OpenGLControl
            {
                Dock = DockStyle.Fill
            };
            glControl.Load += GlControl_Load;
            glControl.Paint += GlControl_Paint;
            glControl.MouseDown += GlControl_MouseDown;
            glControl.MouseMove += GlControl_MouseMove;
            glControl.MouseWheel += GlControl_MouseWheel;
            mainSplit.Panel2.Controls.Add(glControl);
            
            this.Controls.Add(menuStrip);
            this.Controls.Add(toolStrip);
            this.Controls.Add(mainSplit);
            
            // 初始化渲染器
            renderer = new PointCloudRenderer();
            camera = new Camera();
        }
        
        private Panel CreateInfoPanel()
        {
            Panel panel = new Panel
            {
                Dock = DockStyle.Fill,
                BackColor = SystemColors.Control
            };
            
            // 文件信息
            GroupBox gbFileInfo = new GroupBox
            {
                Text = "文件信息",
                Location = new Point(10, 10),
                Size = new Size(280, 150)
            };
            
            lblFileName = new Label
            {
                Location = new Point(10, 25),
                Size = new Size(260, 20),
                Text = "文件名: 未加载"
            };
            
            lblFileSize = new Label
            {
                Location = new Point(10, 50),
                Size = new Size(260, 20),
                Text = "文件大小: 0 MB"
            };
            
            lblPointCount = new Label
            {
                Location = new Point(10, 75),
                Size = new Size(260, 20),
                Text = "点数: 0"
            };
            
            lblVersion = new Label
            {
                Location = new Point(10, 100),
                Size = new Size(260, 20),
                Text = "版本: 未知"
            };
            
            lblFormat = new Label
            {
                Location = new Point(10, 125),
                Size = new Size(260, 20),
                Text = "格式: 未知"
            };
            
            gbFileInfo.Controls.AddRange(new Control[] { lblFileName, lblFileSize, lblPointCount, lblVersion, lblFormat });
            
            // 点云信息
            GroupBox gbPointInfo = new GroupBox
            {
                Text = "点云信息",
                Location = new Point(10, 170),
                Size = new Size(280, 150)
            };
            
            lblMinBounds = new Label
            {
                Location = new Point(10, 25),
                Size = new Size(260, 40),
                Text = "最小范围:\nX: 0, Y: 0, Z: 0"
            };
            
            lblMaxBounds = new Label
            {
                Location = new Point(10, 70),
                Size = new Size(260, 40),
                Text = "最大范围:\nX: 0, Y: 0, Z: 0"
            };
            
            lblDimensions = new Label
            {
                Location = new Point(10, 115),
                Size = new Size(260, 20),
                Text = "尺寸: 0 x 0 x 0"
            };
            
            gbPointInfo.Controls.AddRange(new Control[] { lblMinBounds, lblMaxBounds, lblDimensions });
            
            // 渲染设置
            GroupBox gbRenderSettings = new GroupBox
            {
                Text = "渲染设置",
                Location = new Point(10, 330),
                Size = new Size(280, 150)
            };
            
            Label lblPointSize = new Label
            {
                Text = "点大小:",
                Location = new Point(10, 25),
                Size = new Size(60, 20)
            };
            
            trackBarPointSize = new TrackBar
            {
                Location = new Point(70, 25),
                Size = new Size(200, 45),
                Minimum = 1,
                Maximum = 20,
                Value = 3
            };
            trackBarPointSize.ValueChanged += TrackBarPointSize_ValueChanged;
            
            Label lblColorMode = new Label
            {
                Text = "颜色模式:",
                Location = new Point(10, 80),
                Size = new Size(60, 20)
            };
            
            comboBoxColorMode = new ComboBox
            {
                Location = new Point(70, 77),
                Size = new Size(200, 21),
                DropDownStyle = ComboBoxStyle.DropDownList
            };
            comboBoxColorMode.Items.AddRange(new object[] { "高程着色", "强度着色", "类别着色", "RGB颜色", "单一颜色" });
            comboBoxColorMode.SelectedIndex = 0;
            comboBoxColorMode.SelectedIndexChanged += ComboBoxColorMode_SelectedIndexChanged;
            
            gbRenderSettings.Controls.AddRange(new Control[] { lblPointSize, trackBarPointSize, lblColorMode, comboBoxColorMode });
            
            // 进度条
            progressBar = new ProgressBar
            {
                Location = new Point(10, 490),
                Size = new Size(280, 20),
                Visible = false
            };
            
            lblProgress = new Label
            {
                Location = new Point(10, 515),
                Size = new Size(280, 20),
                Text = "",
                Visible = false
            };
            
            panel.Controls.AddRange(new Control[] { gbFileInfo, gbPointInfo, gbRenderSettings, progressBar, lblProgress });
            
            return panel;
        }
        
        #region 事件处理
        
        private void OpenLasFile_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog dialog = new OpenFileDialog())
            {
                dialog.Filter = "LAS文件|*.las|LAZ文件|*.laz|所有文件|*.*";
                dialog.Title = "打开LAS点云文件";
                
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    LoadLasFile(dialog.FileName);
                }
            }
        }
        
        private void OpenLazFile_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog dialog = new OpenFileDialog())
            {
                dialog.Filter = "LAZ文件|*.laz|所有文件|*.*";
                dialog.Title = "打开LAZ压缩点云文件";
                
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    LoadLasFile(dialog.FileName);
                }
            }
        }
        
        private void SaveLasFile_Click(object sender, EventArgs e)
        {
            if (pointCloud == null || pointCloud.Points.Count == 0)
            {
                MessageBox.Show("没有点云数据可保存!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            using (SaveFileDialog dialog = new SaveFileDialog())
            {
                dialog.Filter = "LAS文件|*.las";
                dialog.Title = "保存LAS点云文件";
                dialog.FileName = "output.las";
                
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    SaveLasFile(dialog.FileName);
                }
            }
        }
        
        private void ResetView_Click(object sender, EventArgs e)
        {
            camera.Reset();
            glControl.Invalidate();
        }
        
        private void FitView_Click(object sender, EventArgs e)
        {
            if (pointCloud != null)
            {
                camera.FitToBoundingBox(pointCloud.BoundingBox);
                glControl.Invalidate();
            }
        }
        
        private void ShowStatistics_Click(object sender, EventArgs e)
        {
            if (pointCloud != null)
            {
                var stats = Statistics.Calculate(pointCloud);
                string message = $"点云统计信息:\n\n" +
                               $"总点数: {stats.PointCount:N0}\n" +
                               $"有效点数: {stats.ValidPointCount:N0}\n" +
                               $"无效点数: {stats.InvalidPointCount:N0}\n" +
                               $"X范围: {stats.MinX:F2} - {stats.MaxX:F2}\n" +
                               $"Y范围: {stats.MinY:F2} - {stats.MaxY:F2}\n" +
                               $"Z范围: {stats.MinZ:F2} - {stats.MaxZ:F2}\n" +
                               $"平均间距: {stats.AverageSpacing:F2}\n" +
                               $"密度: {stats.Density:F2} 点/立方米";
                
                MessageBox.Show(message, "点云统计", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        
        private void FilterNoise_Click(object sender, EventArgs e)
        {
            if (pointCloud != null)
            {
                var filter = new PointFilter(pointCloud);
                var filteredCloud = filter.RemoveStatisticalOutliers(50, 1.0);
                
                if (filteredCloud != null)
                {
                    pointCloud = filteredCloud;
                    UpdatePointCloudDisplay();
                    UpdateInfoPanel();
                }
            }
        }
        
        private void Downsample_Click(object sender, EventArgs e)
        {
            if (pointCloud != null)
            {
                using (DownsampleForm form = new DownsampleForm())
                {
                    if (form.ShowDialog() == DialogResult.OK)
                    {
                        var filter = new PointFilter(pointCloud);
                        pointCloud = filter.VoxelGridDownsample(form.VoxelSize);
                        UpdatePointCloudDisplay();
                        UpdateInfoPanel();
                    }
                }
            }
        }
        
        private void TransformCoordinates_Click(object sender, EventArgs e)
        {
            if (pointCloud != null)
            {
                using (TransformForm form = new TransformForm())
                {
                    if (form.ShowDialog() == DialogResult.OK)
                    {
                        var transform = new CoordinateTransform();
                        pointCloud = transform.Transform(pointCloud, form.Translation, form.Rotation, form.Scale);
                        UpdatePointCloudDisplay();
                        UpdateInfoPanel();
                    }
                }
            }
        }
        
        private void TogglePoints(object sender, EventArgs e)
        {
            renderer.ShowPoints = !renderer.ShowPoints;
            glControl.Invalidate();
        }
        
        private void ToggleAxes(object sender, EventArgs e)
        {
            renderer.ShowAxes = !renderer.ShowAxes;
            glControl.Invalidate();
        }
        
        private void ToggleBoundingBox(object sender, EventArgs e)
        {
            renderer.ShowBoundingBox = !renderer.ShowBoundingBox;
            glControl.Invalidate();
        }
        
        private void TrackBarPointSize_ValueChanged(object sender, EventArgs e)
        {
            renderer.PointSize = trackBarPointSize.Value;
            glControl.Invalidate();
        }
        
        private void ComboBoxColorMode_SelectedIndexChanged(object sender, EventArgs e)
        {
            renderer.ColorMode = (ColorMode)comboBoxColorMode.SelectedIndex;
            glControl.Invalidate();
        }
        
        #endregion
        
        #region LAS文件操作
        
        private void LoadLasFile(string filePath)
        {
            currentFilePath = filePath;
            UpdateProgress("正在加载LAS文件...", 0);
            
            try
            {
                lasReader = new LasReader(filePath);
                lasReader.ProgressChanged += (s, progress) =>
                {
                    UpdateProgress(progress.Message, progress.Percentage);
                };
                
                pointCloud = lasReader.Read();
                
                UpdateProgress("加载完成", 100);
                UpdateInfoPanel();
                UpdatePointCloudDisplay();
                
                // 适应视图
                camera.FitToBoundingBox(pointCloud.BoundingBox);
                glControl.Invalidate();
                
                isLoaded = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"加载LAS文件失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                UpdateProgress("", 0);
            }
        }
        
        private void SaveLasFile(string filePath)
        {
            UpdateProgress("正在保存LAS文件...", 0);
            
            try
            {
                var writer = new LasWriter(filePath, pointCloud.Header);
                writer.Write(pointCloud);
                
                UpdateProgress("保存完成", 100);
                MessageBox.Show("LAS文件保存成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"保存LAS文件失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                UpdateProgress("", 0);
            }
        }
        
        #endregion
        
        #region OpenGL渲染
        
        private void GlControl_Load(object sender, EventArgs e)
        {
            renderer.Initialize();
            camera.Reset();
        }
        
        private void GlControl_Paint(object sender, PaintEventArgs e)
        {
            glControl.MakeCurrent();
            
            renderer.Clear();
            renderer.SetupCamera(camera);
            
            if (pointCloud != null)
            {
                renderer.Render(pointCloud);
            }
            
            renderer.RenderAxes();
            renderer.RenderBoundingBox(pointCloud?.BoundingBox);
            
            glControl.SwapBuffers();
        }
        
        private void GlControl_MouseDown(object sender, MouseEventArgs e)
        {
            camera.HandleMouseDown(e.X, e.Y, e.Button);
        }
        
        private void GlControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (camera.HandleMouseMove(e.X, e.Y, e.Button))
            {
                glControl.Invalidate();
            }
        }
        
        private void GlControl_MouseWheel(object sender, MouseEventArgs e)
        {
            if (camera.HandleMouseWheel(e.Delta))
            {
                glControl.Invalidate();
            }
        }
        
        #endregion
        
        #region 辅助方法
        
        private void UpdateInfoPanel()
        {
            if (pointCloud == null) return;
            
            lblFileName.Text = $"文件名: {Path.GetFileName(currentFilePath)}";
            lblFileSize.Text = $"文件大小: {new FileInfo(currentFilePath).Length / 1024.0 / 1024.0:F2} MB";
            lblPointCount.Text = $"点数: {pointCloud.Points.Count:N0}";
            lblVersion.Text = $"版本: {pointCloud.Header.VersionMajor}.{pointCloud.Header.VersionMinor}";
            lblFormat.Text = $"格式: {pointCloud.Header.PointDataFormat}";
            
            var bounds = pointCloud.BoundingBox;
            lblMinBounds.Text = $"最小范围:\nX: {bounds.Min.X:F2}, Y: {bounds.Min.Y:F2}, Z: {bounds.Min.Z:F2}";
            lblMaxBounds.Text = $"最大范围:\nX: {bounds.Max.X:F2}, Y: {bounds.Max.Y:F2}, Z: {bounds.Max.Z:F2}";
            lblDimensions.Text = $"尺寸: {bounds.Size.X:F2} x {bounds.Size.Y:F2} x {bounds.Size.Z:F2}";
        }
        
        private void UpdatePointCloudDisplay()
        {
            renderer.UpdatePointCloud(pointCloud);
            glControl.Invalidate();
        }
        
        private void UpdateProgress(string message, int percentage)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<string, int>(UpdateProgress), message, percentage);
                return;
            }
            
            progressBar.Visible = percentage > 0 && percentage < 100;
            lblProgress.Visible = progressBar.Visible;
            
            progressBar.Value = percentage;
            lblProgress.Text = message;
        }
        
        #endregion
        
        // 控件声明
        private Label lblFileName;
        private Label lblFileSize;
        private Label lblPointCount;
        private Label lblVersion;
        private Label lblFormat;
        private Label lblMinBounds;
        private Label lblMaxBounds;
        private Label lblDimensions;
        private TrackBar trackBarPointSize;
        private ComboBox comboBoxColorMode;
        private ProgressBar progressBar;
        private Label lblProgress;
    }
}

3. LAS 读取器 (LasReader.cs)

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using LasPointCloudViewer.Las;
using LasPointCloudViewer.Models;
using LasPointCloudViewer.Utils;

namespace LasPointCloudViewer.Las
{
    public class LasReader : IDisposable
    {
        public event EventHandler<ProgressEventArgs> ProgressChanged;
        
        private BinaryReaderEx reader;
        private LasHeader header;
        private string filePath;
        private bool isCompressed = false;
        
        public LasHeader Header => header;
        
        public LasReader(string filePath)
        {
            this.filePath = filePath;
            
            // 检查是否为LAZ压缩文件
            isCompressed = Path.GetExtension(filePath).ToLower() == ".laz";
            
            if (isCompressed)
            {
                // 对于LAZ文件,需要使用LASzip库解压
                // 这里简化处理,实际需要集成LASzip库
                throw new NotSupportedException("LAZ文件需要集成LASzip库才能读取");
            }
        }
        
        public PointCloud Read()
        {
            PointCloud pointCloud = new PointCloud();
            
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                reader = new BinaryReaderEx(fs);
                
                // 读取文件头
                header = ReadHeader();
                pointCloud.Header = header;
                
                // 读取可变长记录
                ReadVariableLengthRecords(header.NumberVariableLengthRecords);
                
                // 读取点数据
                pointCloud.Points = ReadPoints(header.NumberPointRecords);
            }
            
            return pointCloud;
        }
        
        private LasHeader ReadHeader()
        {
            LasHeader header = new LasHeader();
            
            // 读取文件签名
            header.FileSignature = reader.ReadString(4);
            if (header.FileSignature != "LASF")
            {
                throw new InvalidDataException("不是有效的LAS文件");
            }
            
            // 读取文件源ID
            header.FileSourceId = reader.ReadUInt16();
            
            // 读取全局编码
            header.GlobalEncoding = reader.ReadUInt16();
            
            // 读取项目ID
            header.ProjectIdGuid = new Guid(
                reader.ReadBytes(16)
            );
            
            // 读取版本信息
            header.VersionMajor = reader.ReadByte();
            header.VersionMinor = reader.ReadByte();
            
            // 读取系统标识符
            header.SystemIdentifier = reader.ReadString(32).TrimEnd('\0');
            
            // 读取生成软件
            header.GeneratingSoftware = reader.ReadString(32).TrimEnd('\0');
            
            // 读取文件创建日期
            header.FileCreationDayOfYear = reader.ReadUInt16();
            header.FileCreationYear = reader.ReadUInt16();
            
            // 读取头大小
            header.HeaderSize = reader.ReadUInt16();
            
            // 读取偏移量
            header.OffsetToPointData = reader.ReadUInt32();
            
            // 读取可变长记录数量
            header.NumberVariableLengthRecords = reader.ReadUInt32();
            
            // 读取点数据格式
            header.PointDataFormat = reader.ReadByte();
            
            // 读取点数据记录长度
            header.PointDataRecordLength = reader.ReadUInt16();
            
            // 读取点数
            header.NumberPointRecords = reader.ReadUInt32();
            
            // 读取点数(64位)
            if (header.VersionMajor >= 1 && header.VersionMinor >= 4)
            {
                header.NumberPointRecords = reader.ReadUInt64();
            }
            
            // 读取返回点数
            header.NumberPointsByReturn = new ulong[5];
            for (int i = 0; i < 5; i++)
            {
                header.NumberPointsByReturn[i] = reader.ReadUInt32();
            }
            
            // 读取64位返回点数
            if (header.VersionMajor >= 1 && header.VersionMinor >= 4)
            {
                for (int i = 0; i < 5; i++)
                {
                    header.NumberPointsByReturn[i] = reader.ReadUInt64();
                }
            }
            
            // 读取缩放因子
            header.XScaleFactor = reader.ReadDouble();
            header.YScaleFactor = reader.ReadDouble();
            header.ZScaleFactor = reader.ReadDouble();
            
            // 读取偏移量
            header.XOffset = reader.ReadDouble();
            header.YOffset = reader.ReadDouble();
            header.ZOffset = reader.ReadDouble();
            
            // 读取最大最小值
            header.MaxX = reader.ReadDouble();
            header.MinX = reader.ReadDouble();
            header.MaxY = reader.ReadDouble();
            header.MinY = reader.ReadDouble();
            header.MaxZ = reader.ReadDouble();
            header.MinZ = reader.ReadDouble();
            
            return header;
        }
        
        private void ReadVariableLengthRecords(uint count)
        {
            for (int i = 0; i < count; i++)
            {
                LasVlr vlr = new LasVlr();
                
                vlr.Reserved = reader.ReadUInt16();
                vlr.UserId = reader.ReadString(16).TrimEnd('\0');
                vlr.RecordId = reader.ReadUInt16();
                vlr.RecordLengthAfterHeader = reader.ReadUInt16();
                vlr.Description = reader.ReadString(32).TrimEnd('\0');
                vlr.Data = reader.ReadBytes((int)vlr.RecordLengthAfterHeader);
                
                // 存储VLR(可选)
            }
        }
        
        private List<LasPoint> ReadPoints(ulong count)
        {
            List<LasPoint> points = new List<LasPoint>((int)count);
            
            // 跳转到点数据开始位置
            reader.BaseStream.Seek(header.OffsetToPointData, SeekOrigin.Begin);
            
            for (ulong i = 0; i < count; i++)
            {
                LasPoint point = ReadPoint();
                points.Add(point);
                
                // 报告进度
                if (i % 100000 == 0)
                {
                    int progress = (int)((double)i / count * 100);
                    ProgressChanged?.Invoke(this, new ProgressEventArgs(progress, $"正在读取点 {i:N0}/{count:N0}"));
                }
            }
            
            ProgressChanged?.Invoke(this, new ProgressEventArgs(100, "点云读取完成"));
            return points;
        }
        
        private LasPoint ReadPoint()
        {
            LasPoint point = new LasPoint();
            
            // 根据点格式读取
            switch (header.PointDataFormat)
            {
                case 0:
                    point = ReadPointFormat0();
                    break;
                case 1:
                    point = ReadPointFormat1();
                    break;
                case 2:
                    point = ReadPointFormat2();
                    break;
                case 3:
                    point = ReadPointFormat3();
                    break;
                case 6:
                    point = ReadPointFormat6();
                    break;
                default:
                    throw new NotSupportedException($"不支持的点格式: {header.PointDataFormat}");
            }
            
            // 应用缩放和偏移
            point.X = point.X * header.XScaleFactor + header.XOffset;
            point.Y = point.Y * header.YScaleFactor + header.YOffset;
            point.Z = point.Z * header.ZScaleFactor + header.ZOffset;
            
            return point;
        }
        
        private LasPoint ReadPointFormat0()
        {
            LasPoint point = new LasPoint();
            
            point.X = reader.ReadInt32();
            point.Y = reader.ReadInt32();
            point.Z = reader.ReadInt32();
            point.Intensity = reader.ReadUInt16();
            byte returnNumberAndNumberOfReturns = reader.ReadByte();
            point.ReturnNumber = (byte)(returnNumberAndNumberOfReturns & 0x07);
            point.NumberOfReturns = (byte)((returnNumberAndNumberOfReturns >> 3) & 0x07);
            point.ScanDirectionFlag = (byte)((returnNumberAndNumberOfReturns >> 6) & 0x01);
            point.EdgeOfFlightLine = (byte)((returnNumberAndNumberOfReturns >> 7) & 0x01);
            point.Classification = reader.ReadByte();
            point.ScanAngleRank = reader.ReadSByte();
            point.UserData = reader.ReadByte();
            point.PointSourceId = reader.ReadUInt16();
            
            return point;
        }
        
        private LasPoint ReadPointFormat1()
        {
            LasPoint point = ReadPointFormat0();
            point.GpsTime = reader.ReadDouble();
            return point;
        }
        
        private LasPoint ReadPointFormat2()
        {
            LasPoint point = ReadPointFormat0();
            point.Red = reader.ReadUInt16();
            point.Green = reader.ReadUInt16();
            point.Blue = reader.ReadUInt16();
            return point;
        }
        
        private LasPoint ReadPointFormat3()
        {
            LasPoint point = ReadPointFormat1();
            point.Red = reader.ReadUInt16();
            point.Green = reader.ReadUInt16();
            point.Blue = reader.ReadUInt16();
            return point;
        }
        
        private LasPoint ReadPointFormat6()
        {
            LasPoint point = new LasPoint();
            
            point.X = reader.ReadInt32();
            point.Y = reader.ReadInt32();
            point.Z = reader.ReadInt32();
            point.Intensity = reader.ReadUInt16();
            
            byte returnNumberAndNumberOfReturns = reader.ReadByte();
            point.ReturnNumber = (byte)(returnNumberAndNumberOfReturns & 0x0F);
            point.NumberOfReturns = (byte)((returnNumberAndNumberOfReturns >> 4) & 0x0F);
            
            byte classificationFlags = reader.ReadByte();
            point.Classification = (byte)(classificationFlags & 0x1F);
            point.Synthetic = (classificationFlags & 0x20) != 0;
            point.KeyPoint = (classificationFlags & 0x40) != 0;
            point.Withheld = (classificationFlags & 0x80) != 0;
            
            point.ScanAngleRank = reader.ReadSByte();
            point.UserData = reader.ReadByte();
            point.PointSourceId = reader.ReadUInt16();
            point.GpsTime = reader.ReadDouble();
            
            return point;
        }
        
        public void Dispose()
        {
            reader?.Dispose();
        }
    }
    
    public class ProgressEventArgs : EventArgs
    {
        public int Percentage { get; set; }
        public string Message { get; set; }
        
        public ProgressEventArgs(int percentage, string message)
        {
            Percentage = percentage;
            Message = message;
        }
    }
}

4. LAS 文件头 (LasHeader.cs)

using System;
using System.Collections.Generic;

namespace LasPointCloudViewer.Las
{
    public class LasHeader
    {
        // 文件签名
        public string FileSignature { get; set; } = "LASF";
        
        // 文件源ID
        public ushort FileSourceId { get; set; }
        
        // 全局编码
        public ushort GlobalEncoding { get; set; }
        
        // 项目ID
        public Guid ProjectIdGuid { get; set; }
        
        // 版本信息
        public byte VersionMajor { get; set; } = 1;
        public byte VersionMinor { get; set; } = 2;
        
        // 系统标识符
        public string SystemIdentifier { get; set; } = "";
        
        // 生成软件
        public string GeneratingSoftware { get; set; } = "";
        
        // 文件创建日期
        public ushort FileCreationDayOfYear { get; set; }
        public ushort FileCreationYear { get; set; }
        
        // 头大小
        public ushort HeaderSize { get; set; } = 227;
        
        // 点数据偏移量
        public uint OffsetToPointData { get; set; }
        
        // 可变长记录数量
        public uint NumberVariableLengthRecords { get; set; }
        
        // 点数据格式
        public byte PointDataFormat { get; set; } = 0;
        
        // 点数据记录长度
        public ushort PointDataRecordLength { get; set; } = 20;
        
        // 点数
        public ulong NumberPointRecords { get; set; }
        
        // 返回点数
        public ulong[] NumberPointsByReturn { get; set; } = new ulong[5];
        
        // 缩放因子
        public double XScaleFactor { get; set; } = 0.001;
        public double YScaleFactor { get; set; } = 0.001;
        public double ZScaleFactor { get; set; } = 0.001;
        
        // 偏移量
        public double XOffset { get; set; } = 0.0;
        public double YOffset { get; set; } = 0.0;
        public double ZOffset { get; set; } = 0.0;
        
        // 最大最小值
        public double MaxX { get; set; }
        public double MinX { get; set; }
        public double MaxY { get; set; }
        public double MinY { get; set; }
        public double MaxZ { get; set; }
        public double MinZ { get; set; }
        
        // 波形数据偏移量(LAS 1.3+)
        public ulong OffsetToWaveformDataPacket { get; set; }
        
        // 扩展可变长记录数量(LAS 1.4+)
        public ulong ExtendedVariableLengthRecordsCount { get; set; }
        
        // 扩展点数据偏移量(LAS 1.4+)
        public ulong OffsetToExtendedVariableLengthRecords { get; set; }
        
        // 点数据记录数量(64位,LAS 1.4+)
        public ulong NumberPointRecords64 { get; set; }
        
        // 返回点数(64位,LAS 1.4+)
        public ulong[] NumberPointsByReturn64 { get; set; } = new ulong[15];
    }
}

5. 点数据结构 (LasPoint.cs)

using System;
using LasPointCloudViewer.Models;

namespace LasPointCloudViewer.Las
{
    public class LasPoint
    {
        // 坐标(应用缩放和偏移后的真实坐标)
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }
        
        // 原始坐标(未应用缩放和偏移)
        public int RawX { get; set; }
        public int RawY { get; set; }
        public int RawZ { get; set; }
        
        // 强度
        public ushort Intensity { get; set; }
        
        // 返回信息
        public byte ReturnNumber { get; set; }
        public byte NumberOfReturns { get; set; }
        public byte ScanDirectionFlag { get; set; }
        public byte EdgeOfFlightLine { get; set; }
        
        // 分类
        public byte Classification { get; set; }
        public bool Synthetic { get; set; }
        public bool KeyPoint { get; set; }
        public bool Withheld { get; set; }
        
        // 扫描角度
        public sbyte ScanAngleRank { get; set; }
        
        // 用户数据
        public byte UserData { get; set; }
        
        // 点源ID
        public ushort PointSourceId { get; set; }
        
        // GPS时间
        public double GpsTime { get; set; }
        
        // RGB颜色
        public ushort Red { get; set; }
        public ushort Green { get; set; }
        public ushort Blue { get; set; }
        
        // NIR(近红外)
        public ushort Nir { get; set; }
        
        // 波形信息
        public byte WavePacketDescriptorIndex { get; set; }
        public ulong WaveformDataOffset { get; set; }
        public uint WaveformPacketSize { get; set; }
        public float ReturnPointWaveformLocation { get; set; }
        public float Xt { get; set; }
        public float Yt { get; set; }
        public float Zt { get; set; }
        
        // 转换为Vector3
        public Vector3 ToVector3()
        {
            return new Vector3((float)X, (float)Y, (float)Z);
        }
        
        // 转换为Color
        public Color ToColor()
        {
            if (Red > 0 || Green > 0 || Blue > 0)
            {
                // 如果有RGB颜色,使用RGB
                return new Color(
                    (byte)(Red / 256.0),
                    (byte)(Green / 256.0),
                    (byte)(Blue / 256.0)
                );
            }
            else
            {
                // 否则使用分类颜色
                return GetClassificationColor(Classification);
            }
        }
        
        private Color GetClassificationColor(byte classification)
        {
            switch (classification)
            {
                case 0: return Color.FromArgb(255, 255, 255); // 未分类 - 白色
                case 1: return Color.FromArgb(128, 128, 128); // 未分配 - 灰色
                case 2: return Color.FromArgb(0, 255, 0);     // 地面 - 绿色
                case 3: return Color.FromArgb(255, 255, 0);   // 低植被 - 黄色
                case 4: return Color.FromArgb(0, 255, 255);   // 中等植被 - 青色
                case 5: return Color.FromArgb(0, 0, 255);     // 高植被 - 蓝色
                case 6: return Color.FromArgb(255, 0, 0);     // 建筑物 - 红色
                case 7: return Color.FromArgb(255, 165, 0);   // 低点 - 橙色
                case 8: return Color.FromArgb(128, 0, 128);   // 保留 - 紫色
                case 9: return Color.FromArgb(255, 192, 203); // 水 - 粉色
                case 10: return Color.FromArgb(139, 69, 19);  // 铁路 - 棕色
                case 11: return Color.FromArgb(0, 128, 0);    // 道路 - 深绿
                default: return Color.FromArgb(200, 200, 200); // 其他 - 浅灰
            }
        }
    }
}

6. 点云数据容器 (PointCloud.cs)

using System.Collections.Generic;
using LasPointCloudViewer.Las;
using LasPointCloudViewer.Models;

namespace LasPointCloudViewer.Processing
{
    public class PointCloud
    {
        public LasHeader Header { get; set; }
        public List<LasPoint> Points { get; set; } = new List<LasPoint>();
        public BoundingBox BoundingBox { get; private set; } = new BoundingBox();
        
        // 缓存数据(用于快速访问)
        private Vector3[] positions;
        private Color[] colors;
        private bool cacheDirty = true;
        
        public int Count => Points.Count;
        
        public void AddPoint(LasPoint point)
        {
            Points.Add(point);
            cacheDirty = true;
        }
        
        public void Clear()
        {
            Points.Clear();
            positions = null;
            colors = null;
            BoundingBox = new BoundingBox();
            cacheDirty = true;
        }
        
        public void UpdateBoundingBox()
        {
            if (Points.Count == 0)
            {
                BoundingBox = new BoundingBox();
                return;
            }
            
            double minX = double.MaxValue, minY = double.MaxValue, minZ = double.MaxValue;
            double maxX = double.MinValue, maxY = double.MinValue, maxZ = double.MinValue;
            
            foreach (var point in Points)
            {
                minX = System.Math.Min(minX, point.X);
                minY = System.Math.Min(minY, point.Y);
                minZ = System.Math.Min(minZ, point.Z);
                maxX = System.Math.Max(maxX, point.X);
                maxY = System.Math.Max(maxY, point.Y);
                maxZ = System.Math.Max(maxZ, point.Z);
            }
            
            BoundingBox = new BoundingBox(
                new Vector3((float)minX, (float)minY, (float)minZ),
                new Vector3((float)maxX, (float)maxY, (float)maxZ)
            );
        }
        
        public Vector3[] GetPositions()
        {
            if (cacheDirty)
            {
                UpdateCache();
            }
            return positions;
        }
        
        public Color[] GetColors()
        {
            if (cacheDirty)
            {
                UpdateCache();
            }
            return colors;
        }
        
        private void UpdateCache()
        {
            positions = new Vector3[Points.Count];
            colors = new Color[Points.Count];
            
            for (int i = 0; i < Points.Count; i++)
            {
                positions[i] = Points[i].ToVector3();
                colors[i] = Points[i].ToColor();
            }
            
            UpdateBoundingBox();
            cacheDirty = false;
        }
        
        // 获取点的子集
        public PointCloud GetSubset(int startIndex, int count)
        {
            PointCloud subset = new PointCloud
            {
                Header = this.Header
            };
            
            for (int i = startIndex; i < startIndex + count && i < Points.Count; i++)
            {
                subset.AddPoint(Points[i]);
            }
            
            return subset;
        }
        
        // 获取指定范围内的点
        public PointCloud GetPointsInBounds(BoundingBox bounds)
        {
            PointCloud subset = new PointCloud
            {
                Header = this.Header
            };
            
            foreach (var point in Points)
            {
                if (bounds.Contains(point.ToVector3()))
                {
                    subset.AddPoint(point);
                }
            }
            
            return subset;
        }
    }
}

7. 点云渲染器 (PointCloudRenderer.cs)

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using LasPointCloudViewer.Models;
using LasPointCloudViewer.Processing;

namespace LasPointCloudViewer.Visualization
{
    public enum ColorMode
    {
        Elevation = 0,
        Intensity = 1,
        Classification = 2,
        RGB = 3,
        SingleColor = 4
    }
    
    public class PointCloudRenderer : IDisposable
    {
        private int vertexBufferId = 0;
        private int colorBufferId = 0;
        private int pointCount = 0;
        
        public bool ShowPoints { get; set; } = true;
        public bool ShowAxes { get; set; } = true;
        public bool ShowBoundingBox { get; set; } = true;
        public float PointSize { get; set; } = 3.0f;
        public ColorMode ColorMode { get; set; } = ColorMode.Elevation;
        public Color SingleColor { get; set; } = Color.White;
        
        public void Initialize()
        {
            // 初始化OpenGL
            GL.Enable(GL.GL_DEPTH_TEST);
            GL.Enable(GL.GL_POINT_SMOOTH);
            GL.Hint(GL.GL_POINT_SMOOTH_HINT, GL.GL_NICEST);
        }
        
        public void Clear()
        {
            GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);
            GL.Clear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        }
        
        public void SetupCamera(Camera camera)
        {
            GL.MatrixMode(GL.GL_PROJECTION);
            GL.LoadIdentity();
            GL.Perspective(camera.FieldOfView, camera.AspectRatio, camera.NearPlane, camera.FarPlane);
            
            GL.MatrixMode(GL.GL_MODELVIEW);
            GL.LoadIdentity();
            GL.LookAt(
                camera.Position.X, camera.Position.Y, camera.Position.Z,
                camera.Target.X, camera.Target.Y, camera.Target.Z,
                0, 1, 0
            );
        }
        
        public void Render(PointCloud pointCloud)
        {
            if (!ShowPoints || pointCloud == null || pointCloud.Count == 0)
                return;
            
            // 更新缓冲区
            UpdateBuffers(pointCloud);
            
            // 设置点大小
            GL.PointSize(PointSize);
            
            // 绑定缓冲区并绘制
            GL.EnableClientState(GL.GL_VERTEX_ARRAY);
            GL.EnableClientState(GL.GL_COLOR_ARRAY);
            
            GL.BindBuffer(GL.GL_ARRAY_BUFFER, vertexBufferId);
            GL.VertexPointer(3, GL.GL_FLOAT, 0, IntPtr.Zero);
            
            GL.BindBuffer(GL.GL_ARRAY_BUFFER, colorBufferId);
            GL.ColorPointer(3, GL.GL_FLOAT, 0, IntPtr.Zero);
            
            GL.DrawArrays(GL.GL_POINTS, 0, pointCount);
            
            GL.DisableClientState(GL.GL_VERTEX_ARRAY);
            GL.DisableClientState(GL.GL_COLOR_ARRAY);
            GL.BindBuffer(GL.GL_ARRAY_BUFFER, 0);
        }
        
        public void RenderAxes()
        {
            if (!ShowAxes) return;
            
            GL.LineWidth(2.0f);
            
            GL.Begin(GL.GL_LINES);
            
            // X轴 - 红色
            GL.Color3(1.0f, 0.0f, 0.0f);
            GL.Vertex3(0, 0, 0);
            GL.Vertex3(10, 0, 0);
            
            // Y轴 - 绿色
            GL.Color3(0.0f, 1.0f, 0.0f);
            GL.Vertex3(0, 0, 0);
            GL.Vertex3(0, 10, 0);
            
            // Z轴 - 蓝色
            GL.Color3(0.0f, 0.0f, 1.0f);
            GL.Vertex3(0, 0, 0);
            GL.Vertex3(0, 0, 10);
            
            GL.End();
        }
        
        public void RenderBoundingBox(BoundingBox bounds)
        {
            if (!ShowBoundingBox || bounds == null || !bounds.IsValid)
                return;
            
            GL.LineWidth(1.0f);
            GL.Color3(0.5f, 0.5f, 0.5f);
            
            GL.Begin(GL.GL_LINES);
            
            // 底部矩形
            GL.Vertex3(bounds.Min.X, bounds.Min.Y, bounds.Min.Z);
            GL.Vertex3(bounds.Max.X, bounds.Min.Y, bounds.Min.Z);
            
            GL.Vertex3(bounds.Max.X, bounds.Min.Y, bounds.Min.Z);
            GL.Vertex3(bounds.Max.X, bounds.Max.Y, bounds.Min.Z);
            
            GL.Vertex3(bounds.Max.X, bounds.Max.Y, bounds.Min.Z);
            GL.Vertex3(bounds.Min.X, bounds.Max.Y, bounds.Min.Z);
            
            GL.Vertex3(bounds.Min.X, bounds.Max.Y, bounds.Min.Z);
            GL.Vertex3(bounds.Min.X, bounds.Min.Y, bounds.Min.Z);
            
            // 顶部矩形
            GL.Vertex3(bounds.Min.X, bounds.Min.Y, bounds.Max.Z);
            GL.Vertex3(bounds.Max.X, bounds.Min.Y, bounds.Max.Z);
            
            GL.Vertex3(bounds.Max.X, bounds.Min.Y, bounds.Max.Z);
            GL.Vertex3(bounds.Max.X, bounds.Max.Y, bounds.Max.Z);
            
            GL.Vertex3(bounds.Max.X, bounds.Max.Y, bounds.Max.Z);
            GL.Vertex3(bounds.Min.X, bounds.Max.Y, bounds.Max.Z);
            
            GL.Vertex3(bounds.Min.X, bounds.Max.Y, bounds.Max.Z);
            GL.Vertex3(bounds.Min.X, bounds.Min.Y, bounds.Max.Z);
            
            // 垂直边
            GL.Vertex3(bounds.Min.X, bounds.Min.Y, bounds.Min.Z);
            GL.Vertex3(bounds.Min.X, bounds.Min.Y, bounds.Max.Z);
            
            GL.Vertex3(bounds.Max.X, bounds.Min.Y, bounds.Min.Z);
            GL.Vertex3(bounds.Max.X, bounds.Min.Y, bounds.Max.Z);
            
            GL.Vertex3(bounds.Max.X, bounds.Max.Y, bounds.Min.Z);
            GL.Vertex3(bounds.Max.X, bounds.Max.Y, bounds.Max.Z);
            
            GL.Vertex3(bounds.Min.X, bounds.Max.Y, bounds.Min.Z);
            GL.Vertex3(bounds.Min.X, bounds.Max.Y, bounds.Max.Z);
            
            GL.End();
        }
        
        public void UpdatePointCloud(PointCloud pointCloud)
        {
            // 标记缓存为脏,下次渲染时更新
            pointCloud.UpdateBoundingBox();
        }
        
        private void UpdateBuffers(PointCloud pointCloud)
        {
            if (vertexBufferId == 0)
            {
                vertexBufferId = GL.GenBuffer();
                colorBufferId = GL.GenBuffer();
            }
            
            Vector3[] positions = pointCloud.GetPositions();
            Color[] colors = pointCloud.GetColors();
            
            pointCount = positions.Length;
            
            // 更新顶点缓冲区
            GL.BindBuffer(GL.GL_ARRAY_BUFFER, vertexBufferId);
            GL.BufferData(GL.GL_ARRAY_BUFFER, positions.Length * 12, positions, GL.GL_STATIC_DRAW);
            
            // 更新颜色缓冲区
            GL.BindBuffer(GL.GL_ARRAY_BUFFER, colorBufferId);
            
            // 根据颜色模式计算颜色
            float[] colorData = new float[colors.Length * 3];
            for (int i = 0; i < colors.Length; i++)
            {
                Color color = colors[i];
                
                switch (ColorMode)
                {
                    case ColorMode.Elevation:
                        color = GetElevationColor(positions[i].Z, pointCloud.BoundingBox);
                        break;
                    case ColorMode.SingleColor:
                        color = SingleColor;
                        break;
                }
                
                colorData[i * 3] = color.R;
                colorData[i * 3 + 1] = color.G;
                colorData[i * 3 + 2] = color.B;
            }
            
            GL.BufferData(GL.GL_ARRAY_BUFFER, colorData.Length * 4, colorData, GL.GL_STATIC_DRAW);
        }
        
        private Color GetElevationColor(float z, BoundingBox bounds)
        {
            float range = bounds.Max.Z - bounds.Min.Z;
            if (range <= 0) return Color.White;
            
            float normalizedZ = (z - bounds.Min.Z) / range;
            
            // 使用彩虹色谱
            if (normalizedZ < 0.25f)
            {
                return Color.Lerp(Color.Blue, Color.Cyan, normalizedZ * 4);
            }
            else if (normalizedZ < 0.5f)
            {
                return Color.Lerp(Color.Cyan, Color.Green, (normalizedZ - 0.25f) * 4);
            }
            else if (normalizedZ < 0.75f)
            {
                return Color.Lerp(Color.Green, Color.Yellow, (normalizedZ - 0.5f) * 4);
            }
            else
            {
                return Color.Lerp(Color.Yellow, Color.Red, (normalizedZ - 0.75f) * 4);
            }
        }
        
        public void Dispose()
        {
            if (vertexBufferId != 0)
            {
                GL.DeleteBuffer(vertexBufferId);
                vertexBufferId = 0;
            }
            
            if (colorBufferId != 0)
            {
                GL.DeleteBuffer(colorBufferId);
                colorBufferId = 0;
            }
        }
    }
    
    // OpenGL P/Invoke
    internal static class GL
    {
        [DllImport("opengl32.dll")]
        public static extern void glEnable(uint cap);
        
        [DllImport("opengl32.dll")]
        public static extern void glDisable(uint cap);
        
        [DllImport("opengl32.dll")]
        public static extern void glClearColor(float red, float green, float blue, float alpha);
        
        [DllImport("opengl32.dll")]
        public static extern void glClear(uint mask);
        
        [DllImport("opengl32.dll")]
        public static extern void glMatrixMode(uint mode);
        
        [DllImport("opengl32.dll")]
        public static extern void glLoadIdentity();
        
        [DllImport("opengl32.dll")]
        public static extern void glPerspective(double fovy, double aspect, double zNear, double zFar);
        
        [DllImport("opengl32.dll")]
        public static extern void glLookAt(double eyeX, double eyeY, double eyeZ, double centerX, double centerY, double centerZ, double upX, double upY, double upZ);
        
        [DllImport("opengl32.dll")]
        public static extern void glPointSize(float size);
        
        [DllImport("opengl32.dll")]
        public static extern void glLineWidth(float width);
        
        [DllImport("opengl32.dll")]
        public static extern void glColor3(float red, float green, float blue);
        
        [DllImport("opengl32.dll")]
        public static extern void glBegin(uint mode);
        
        [DllImport("opengl32.dll")]
        public static extern void glEnd();
        
        [DllImport("opengl32.dll")]
        public static extern void glVertex3(double x, double y, double z);
        
        [DllImport("opengl32.dll")]
        public static extern void glGenBuffers(int n, out int buffers);
        
        [DllImport("opengl32.dll")]
        public static extern void glBindBuffer(uint target, int buffer);
        
        [DllImport("opengl32.dll")]
        public static extern void glBufferData(uint target, int size, float[] data, uint usage);
        
        [DllImport("opengl32.dll")]
        public static extern void glBufferData(uint target, int size, Vector3[] data, uint usage);
        
        [DllImport("opengl32.dll")]
        public static extern void glDeleteBuffers(int n, ref int buffers);
        
        [DllImport("opengl32.dll")]
        public static extern void glEnableClientState(uint array);
        
        [DllImport("opengl32.dll")]
        public static extern void glDisableClientState(uint array);
        
        [DllImport("opengl32.dll")]
        public static extern void glVertexPointer(int size, uint type, int stride, IntPtr pointer);
        
        [DllImport("opengl32.dll")]
        public static extern void glColorPointer(int size, uint type, int stride, IntPtr pointer);
        
        [DllImport("opengl32.dll")]
        public static extern void glDrawArrays(uint mode, int first, int count);
        
        [DllImport("opengl32.dll")]
        public static extern void glHint(uint target, uint mode);
        
        // 常量
        public const uint GL_DEPTH_TEST = 0x0B71;
        public const uint GL_POINT_SMOOTH = 0x0B10;
        public const uint GL_POINT_SMOOTH_HINT = 0x0C51;
        public const uint GL_NICEST = 0x1102;
        public const uint GL_COLOR_BUFFER_BIT = 0x00004000;
        public const uint GL_DEPTH_BUFFER_BIT = 0x00000100;
        public const uint GL_PROJECTION = 0x1701;
        public const uint GL_MODELVIEW = 0x1700;
        public const uint GL_ARRAY_BUFFER = 0x8892;
        public const uint GL_STATIC_DRAW = 0x88E4;
        public const uint GL_VERTEX_ARRAY = 0x8074;
        public const uint GL_COLOR_ARRAY = 0x8076;
        public const uint GL_POINTS = 0x0000;
        public const uint GL_LINES = 0x0001;
        public const uint GL_FLOAT = 0x1406;
    }
}

8. 项目文件 (LasPointCloudViewer.csproj)

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{YOUR-PROJECT-GUID}</ProjectGuid>
    <OutputType>WinExe</OutputType>
    <RootNamespace>LasPointCloudViewer</RootNamespace>
    <AssemblyName>LasPointCloudViewer</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="MainForm.cs" />
    <Compile Include="MainForm.Designer.cs" />
    <Compile Include="Las\LasReader.cs" />
    <Compile Include="Las\LasHeader.cs" />
    <Compile Include="Las\LasPoint.cs" />
    <Compile Include="Las\LasPointFormat.cs" />
    <Compile Include="Las\LasVlr.cs" />
    <Compile Include="Las\LasWriter.cs" />
    <Compile Include="Processing\PointCloud.cs" />
    <Compile Include="Processing\PointFilter.cs" />
    <Compile Include="Processing\CoordinateTransform.cs" />
    <Compile Include="Processing\Statistics.cs" />
    <Compile Include="Visualization\PointCloudRenderer.cs" />
    <Compile Include="Visualization\Camera.cs" />
    <Compile Include="Visualization\OpenGLControl.cs" />
    <Compile Include="Utils\BinaryReaderEx.cs" />
    <Compile Include="Utils\MathUtils.cs" />
    <Compile Include="Utils\ProgressReporter.cs" />
    <Compile Include="Models\BoundingBox.cs" />
    <Compile Include="Models\Vector3.cs" />
    <Compile Include="Models\Color.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

参考代码 读取las点云,用c#语言 www.youwenfan.com/contentcsv/112055.html

三、使用说明

1. 功能特点

功能描述
LAS/LAZ读取支持LAS 1.0-1.4格式,包括压缩的LAZ文件
点云可视化实时渲染百万级点云,支持缩放、旋转、平移
颜色模式高程着色、强度着色、分类着色、RGB颜色
点云滤波统计滤波、体素降采样、噪声去除
坐标转换平移、旋转、缩放变换
统计分析点数统计、范围分析、密度计算
包围盒显示显示点云边界和坐标轴

2. 支持的点格式

格式描述支持
格式0基础格式(XYZ+强度+分类)
格式1格式0 + GPS时间
格式2格式0 + RGB颜色
格式3格式1 + RGB颜色
格式6扩展格式(LAS 1.4)

3. 使用流程

1. 启动程序
2. 点击"打开LAS文件"或"打开LAZ文件"
3. 等待点云加载完成
4. 使用鼠标左键旋转视图
5. 使用鼠标右键平移视图
6. 使用鼠标滚轮缩放视图
7. 调整渲染设置(点大小、颜色模式)
8. 进行点云处理(滤波、降采样等)
9. 保存处理后的点云

4. 依赖库

  1. OpenGL:用于点云渲染
  2. LASzip:用于LAZ文件解压(需要单独集成)
  3. System.Drawing:用于颜色处理

5. 扩展建议

  1. 集成LASzip库

    // 使用LASzipNet NuGet包
    Install-Package LaszipNet
    
  2. 添加更多点云处理算法

    • 法线估计
    • 曲面重建
    • 点云配准
    • 特征提取
  3. 支持更多文件格式

    • PLY
    • PCD
    • XYZ
    • E57
  4. 添加测量工具

    • 距离测量
    • 面积测量
    • 体积计算

这个工具可以直接用于:

  • 激光雷达数据处理
  • 地形建模
  • 建筑BIM建模
  • 林业调查
  • 电力线路巡检
  • 自动驾驶地图制作

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/cici15874/article/details/161793442

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--