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. 依赖库
- OpenGL:用于点云渲染
- LASzip:用于LAZ文件解压(需要单独集成)
- System.Drawing:用于颜色处理
5. 扩展建议
-
集成LASzip库
// 使用LASzipNet NuGet包 Install-Package LaszipNet -
添加更多点云处理算法
- 法线估计
- 曲面重建
- 点云配准
- 特征提取
-
支持更多文件格式
- PLY
- PCD
- XYZ
- E57
-
添加测量工具
- 距离测量
- 面积测量
- 体积计算
这个工具可以直接用于:
- 激光雷达数据处理
- 地形建模
- 建筑BIM建模
- 林业调查
- 电力线路巡检
- 自动驾驶地图制作
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/cici15874/article/details/161793442



