using System; using System.Collections; using System.Text; namespace LASRead.LASFormat { /// /// LAS Data Payloads are in the Point Data Record (PDR) format
/// PDR 0-5 share the same basic substructure (which is inherited from this interface,
/// PDR 6-10 share a slightly different substructure (more flags) ///
public interface IPointDataRecord { public int X { get; set; } public int Y { get; set; } public int Z { get; set; } public ushort? Intensity { get; set; } public byte ReturnNumberFlag_value { get; set; } public byte NumberOfReturnsFlag_value { get; set; } public byte ScanDirectionFlag_value { get; set; } public byte EdgeOfFlightLineFlag_value { get; set; } public byte Classification { get; set; } public sbyte ScanAngleRank { get; set; } public byte? UserData { get; set; } public ushort PointSourceID { get; set; } public bool ReadPoint(byte[] data); /// /// Reads the flags from the supplied byte /// /// /// bool ReadFlag(Tuple source); /// /// Generates a new payload object from the supplied data bytes /// /// Well-formed byte data (i.e. Read from file) /// A new payload object t IPointDataRecord ParsePoint(byte[] data); byte[] MergeFlags(); byte[] GetAsByteArray(); } class PDR0 : IPointDataRecord { const int headerSize = 20; public int X { get; set; } public int Y { get; set; } public int Z { get; set; } public ushort? Intensity { get; set; } public byte ReturnNumberFlag_value { get; set; } public byte NumberOfReturnsFlag_value { get; set; } public byte ScanDirectionFlag_value { get; set; } public byte EdgeOfFlightLineFlag_value { get; set; } public byte Classification { get; set; } public sbyte ScanAngleRank { get; set; } public byte? UserData { get; set; } public ushort PointSourceID { get; set; } public virtual bool ReadFlag(Tuple source) { // Note that typical Windows environments should be Little Endian; matching the expected data format. // This means for the number 3, // 7-6-5-4 3-2-1-0 // 0 0 0 0 0 0 1 1 // So return number flag = 2-1-0 (011) = 3 const byte full = 255; ReturnNumberFlag_value = (byte)(source.Item1 & (full >> 5)); // Right-shift mask by 5 to get only the first 3 bits NumberOfReturnsFlag_value = (byte)((source.Item1 >> 3) & (full >> 5)); // Right shift by 3, and & with 3 to get the returns ScanDirectionFlag_value = (byte)((source.Item1 >> 6) & (full >> 7)); EdgeOfFlightLineFlag_value = (byte)(source.Item1 >> 7); /* Big Endian ReturnNumberFlag_value = (byte)((source.Item1 >> 5)& (full >> 5)); // Right-shift by 5 to get only the first 3 bits NumberOfReturnsFlag_value = (byte)((source.Item1 >> 2) & (full >> 5)); // Right shift by 3, and & with 3 to get the returns ScanDirectionFlag_value = (byte)((source.Item1 >> 1) & (full >> 7)); EdgeOfFlightLineFlag_value = (byte)(source.Item1 & (full >> 7)); */ return true; } public virtual bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { X = BitConverter.ToInt32(data, 0); Y = BitConverter.ToInt32(data, 4); Z = BitConverter.ToInt32(data, 8); Intensity = BitConverter.ToUInt16(data, 12); ReadFlag(Tuple.Create(data[14], (byte)0)); Classification = data[15]; ScanAngleRank = (sbyte)data[16]; UserData = data[17]; PointSourceID = BitConverter.ToUInt16(data, 18); return true; } else return false; } public static bool VerifySize(byte[] source, int headerSize) { if (source.Length < headerSize) { return false; } return true; } public virtual IPointDataRecord ParsePoint(byte[] data) { PDR0 newPoint = new PDR0(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(string.Format("Point: {0}, {1}, {2} {3}", X, Y, Z, Environment.NewLine)); sb.Append("Intensity: " + Intensity.ToString() + Environment.NewLine); sb.Append("Return Number: " + ReturnNumberFlag_value.ToString() + Environment.NewLine); sb.Append("Number of Returns: " + NumberOfReturnsFlag_value.ToString() + Environment.NewLine); sb.Append("Scan Direction: " + (ReturnNumberFlag_value == 0 ? "+" : "-") + Environment.NewLine); sb.Append("Edge of Flight Line: " + (ReturnNumberFlag_value == 0 ? "no" : "yes") + Environment.NewLine); sb.Append("Classification: " + ((Classifications)Classification) + Environment.NewLine); sb.Append("Scan Angle Rank: " + ScanAngleRank.ToString() + Environment.NewLine); sb.Append("User Data: " + (UserData == 0 ? "no" : "yes") + Environment.NewLine); sb.Append("Point Data Source: " + PointSourceID.ToString() + Environment.NewLine); return sb.ToString(); } public byte[] MergeFlags() { int result = EdgeOfFlightLineFlag_value; result |= ReturnNumberFlag_value << 5; // Right-shift mask by 5 to get only the first 3 bits result |= NumberOfReturnsFlag_value << 3; // Right shift by 3, and & with 3 to get the returns result |= ScanDirectionFlag_value << 2; byte t = (byte)(result & 255); return new byte[] { t }; } public virtual byte[] GetAsByteArray() { byte[] result = new byte[headerSize]; BitConverter.GetBytes(X).CopyTo(result, 0); BitConverter.GetBytes(Y).CopyTo(result, 4); BitConverter.GetBytes(Y).CopyTo(result, 8); BitConverter.GetBytes(Intensity ?? 0).CopyTo(result, 12); MergeFlags().CopyTo(result, 14); result[15] = Classification; result[16] = (byte)ScanAngleRank; result[17] = UserData ?? 0; BitConverter.GetBytes(PointSourceID).CopyTo(result, 18); return result; } } class PDR1 : PDR0 { double GPSTime; const int headerSize = 28; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); GPSTime = BitConverter.ToDouble(data, 20); return true; } else return false; } public override IPointDataRecord ParsePoint(byte[] data) { PDR1 newPoint = new PDR1(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append("GPS Time: " + GPSTime.ToString() + Environment.NewLine); return sb.ToString(); } public override byte[] GetAsByteArray() { byte[] result = new byte[headerSize]; base.GetAsByteArray().CopyTo(result, 0); BitConverter.GetBytes(GPSTime).CopyTo(result, 20); return result; } } class PDR2 : PDR0 { ushort red; ushort green; ushort blue; const int headerSize = 26; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); red = BitConverter.ToUInt16(data, 20); green = BitConverter.ToUInt16(data, 22); blue = BitConverter.ToUInt16(data, 24); return true; } else return false; } public override IPointDataRecord ParsePoint(byte[] data) { PDR2 newPoint = new PDR2(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append(string.Format("RGB: {0} {1} {2} {3}", red, green, blue, Environment.NewLine)); return sb.ToString(); } public override byte[] GetAsByteArray() { byte[] result = new byte[headerSize]; base.GetAsByteArray().CopyTo(result, 0); BitConverter.GetBytes(red).CopyTo(result, 20); BitConverter.GetBytes(green).CopyTo(result, 22); BitConverter.GetBytes(blue).CopyTo(result, 24); return result; } } class PDR3 : PDR1 { ushort red; ushort green; ushort blue; const int headerSize = 34; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); red = BitConverter.ToUInt16(data, 28); green = BitConverter.ToUInt16(data, 30); blue = BitConverter.ToUInt16(data, 32); return true; } else return false; } public override IPointDataRecord ParsePoint(byte[] data) { PDR3 newPoint = new PDR3(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append(string.Format("RGB: {0} {1} {2} {3}", red, green, blue, Environment.NewLine)); return sb.ToString(); } public override byte[] GetAsByteArray() { byte[] result = new byte[headerSize]; base.GetAsByteArray().CopyTo(result, 0); BitConverter.GetBytes(red).CopyTo(result, 28); BitConverter.GetBytes(green).CopyTo(result, 30); BitConverter.GetBytes(blue).CopyTo(result, 32); return result; } } class PDR4 : PDR1 { byte wavePacketDescriptorIndex; ulong WaveformOffset; uint WaveformSize; float returnPointWaveform; float dx; float dy; float dz; const int headerSize = 57; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); wavePacketDescriptorIndex = data[28]; WaveformOffset = BitConverter.ToUInt64(data, 29); WaveformSize = BitConverter.ToUInt32(data, 37); returnPointWaveform = BitConverter.ToSingle(data, 41); dx = BitConverter.ToSingle(data, 45); dy = BitConverter.ToSingle(data, 49); dz = BitConverter.ToSingle(data, 53); return true; } else return false; } public override IPointDataRecord ParsePoint(byte[] data) { PDR4 newPoint = new PDR4(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append("WavePacket Index: " + wavePacketDescriptorIndex.ToString() + Environment.NewLine); sb.Append("Waveform Offset: " + WaveformOffset.ToString() + Environment.NewLine); sb.Append("Waveform Size: " + WaveformSize.ToString() + Environment.NewLine); sb.Append("Return Point Waveform: " + returnPointWaveform.ToString() + Environment.NewLine); sb.Append(string.Format("Delta Pos: dx={0} dy={1} dz={2} {3}", dx, dy, dz, Environment.NewLine)); return sb.ToString(); } public override byte[] GetAsByteArray() { byte[] result = new byte[headerSize]; base.GetAsByteArray().CopyTo(result, 0); result[28] = wavePacketDescriptorIndex; BitConverter.GetBytes(WaveformOffset).CopyTo(result, 29); BitConverter.GetBytes(WaveformSize).CopyTo(result, 37); BitConverter.GetBytes(returnPointWaveform).CopyTo(result, 41); BitConverter.GetBytes(dx).CopyTo(result, 45); BitConverter.GetBytes(dy).CopyTo(result, 49); BitConverter.GetBytes(dz).CopyTo(result, 53); return result; } } class PDR5 : PDR3 { byte wavePacketDescriptorIndex; ulong WaveformOffset; uint WaveformSize; float returnPointWaveform; float dx; float dy; float dz; const int headerSize = 63; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); wavePacketDescriptorIndex = data[34]; WaveformOffset = BitConverter.ToUInt64(data, 35); WaveformSize = BitConverter.ToUInt32(data, 43); returnPointWaveform = BitConverter.ToSingle(data, 47); dx = BitConverter.ToSingle(data, 51); dy = BitConverter.ToSingle(data, 55); dz = BitConverter.ToSingle(data, 59); return true; } else return false; } public static new IPointDataRecord ParsePoint(byte[] data) { PDR5 newPoint = new PDR5(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append("WavePacket Index: " + wavePacketDescriptorIndex.ToString() + Environment.NewLine); sb.Append("Waveform Offset: " + WaveformOffset.ToString() + Environment.NewLine); sb.Append("Waveform Size: " + WaveformSize.ToString() + Environment.NewLine); sb.Append("Return Point Waveform: " + returnPointWaveform.ToString() + Environment.NewLine); sb.Append(string.Format("Delta Pos: dx={0} dy={1} dz={2} {3}", dx, dy, dz, Environment.NewLine)); return sb.ToString(); } public override byte[] GetAsByteArray() { byte[] result = new byte[headerSize]; base.GetAsByteArray().CopyTo(result, 0); result[34] = wavePacketDescriptorIndex; BitConverter.GetBytes(WaveformOffset).CopyTo(result, 35); BitConverter.GetBytes(WaveformSize).CopyTo(result, 43); BitConverter.GetBytes(returnPointWaveform).CopyTo(result, 47); BitConverter.GetBytes(dx).CopyTo(result, 51); BitConverter.GetBytes(dy).CopyTo(result, 55); BitConverter.GetBytes(dz).CopyTo(result, 59); return result; } } class PDR6 : IPointDataRecord { const int headerSize = 30; // Inherited members public int X { get; set; } public int Y { get; set; } public int Z { get; set; } public ushort? Intensity { get; set; } public byte ReturnNumberFlag_value { get; set; } public byte NumberOfReturnsFlag_value { get; set; } public byte ScanDirectionFlag_value { get; set; } public byte EdgeOfFlightLineFlag_value { get; set; } public byte Classification { get; set; } public short ScanAngleRank { get; set; } public byte? UserData { get; set; } public ushort PointSourceID { get; set; } // Local members public byte ClassificationFlag_value { get; set; } public byte ScannerChannelFlag_value { get; set; } public double GPSTime1 { get; set; } sbyte IPointDataRecord.ScanAngleRank { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public bool ReadFlag(Tuple source) { const byte full = 255; ReturnNumberFlag_value = (byte)(source.Item1 & (full << 4)); NumberOfReturnsFlag_value = (byte)((source.Item1 >> 4) & (full << 4)); ClassificationFlag_value = (byte)(source.Item2 & (full << 4)); ScannerChannelFlag_value = (byte)((source.Item2 >> 4) & (full << 2)); ScanDirectionFlag_value = (byte)((source.Item2 >> 6) & (full << 1)); EdgeOfFlightLineFlag_value = (byte)(source.Item2 >> 7); return true; } public virtual bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { X = BitConverter.ToInt32(data, 0); Y = BitConverter.ToInt32(data, 4); Z = BitConverter.ToInt32(data, 8); Intensity = BitConverter.ToUInt16(data, 12); ReadFlag(Tuple.Create(data[14], data[15])); Classification = data[16]; UserData = data[17]; ScanAngleRank = BitConverter.ToInt16(data, 18); PointSourceID = BitConverter.ToUInt16(data, 20); return true; } else return false; } public virtual IPointDataRecord ParsePoint(byte[] data) { PDR6 newPoint = new PDR6(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(string.Format("Point: {0}, {1}, {2} {3}", X, Y, Z, Environment.NewLine)); sb.Append("Intensity: " + Intensity.ToString() + Environment.NewLine); sb.Append("Return Number: " + ReturnNumberFlag_value.ToString() + Environment.NewLine); sb.Append("Number of Returns: " + NumberOfReturnsFlag_value.ToString() + Environment.NewLine); sb.Append("Classification Value: " + ClassificationFlag_value + Environment.NewLine); sb.Append("Scanner Channel Value: " + ScannerChannelFlag_value + Environment.NewLine); sb.Append("Scan Direction: " + (ReturnNumberFlag_value == 0 ? "+" : "-") + Environment.NewLine); sb.Append("Edge of Flight Line: " + (ReturnNumberFlag_value == 0 ? "no" : "yes") + Environment.NewLine); sb.Append("Classification: " + ((Classifications)Classification) + Environment.NewLine); sb.Append("Scan Angle Rank: " + ScanAngleRank.ToString() + Environment.NewLine); sb.Append("User Data: " + (UserData == 0 ? "no" : "yes") + Environment.NewLine); sb.Append("Point Data Source: " + PointSourceID.ToString() + Environment.NewLine); sb.Append("GPS Time: " + GPSTime1.ToString() + Environment.NewLine); return sb.ToString(); } public byte[] MergeFlags() { int result = EdgeOfFlightLineFlag_value; result |= ReturnNumberFlag_value << 5; // Right-shift mask by 5 to get only the first 3 bits result |= NumberOfReturnsFlag_value << 3; // Right shift by 3, and & with 3 to get the returns result |= ScanDirectionFlag_value << 2; byte t = (byte)(result & 255); throw new NotImplementedException(); return new byte[] { t }; } public virtual byte[] GetAsByteArray() { byte[] result = new byte[headerSize]; BitConverter.GetBytes(X).CopyTo(result, 0); BitConverter.GetBytes(Y).CopyTo(result, 4); BitConverter.GetBytes(Y).CopyTo(result, 8); BitConverter.GetBytes(Intensity ?? 0).CopyTo(result, 12); MergeFlags().CopyTo(result, 14); result[15] = Classification; result[16] = (byte)ScanAngleRank; result[17] = UserData ?? 0; BitConverter.GetBytes(PointSourceID).CopyTo(result, 18); return result; } } class PDR7 : PDR6 { ushort red; ushort green; ushort blue; const int headerSize = 36; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); red = BitConverter.ToUInt16(data, 30); green = BitConverter.ToUInt16(data, 32); blue = BitConverter.ToUInt16(data, 34); return true; } else return false; } public override IPointDataRecord ParsePoint(byte[] data) { PDR7 newPoint = new PDR7(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append(string.Format("RGB: {0} {1} {2} {3}", red, green, blue, Environment.NewLine)); return sb.ToString(); } } class PDR8 : PDR7 { ushort nIR; const int headerSize = 38; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); nIR = BitConverter.ToUInt16(data, 36); return true; } else return false; } public override IPointDataRecord ParsePoint(byte[] data) { PDR8 newPoint = new PDR8(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append("Infrared: " + nIR.ToString() + Environment.NewLine); return sb.ToString(); } } class PDR9 : PDR6 { byte wavePacketDescriptorIndex; ulong byteOffsetToWaveformData; uint waveformPacketSize; float returnPointWaveformLocation; float dx; float dy; float dz; const int headerSize = 59; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); wavePacketDescriptorIndex = data[38]; byteOffsetToWaveformData = BitConverter.ToUInt64(data, 39); waveformPacketSize = BitConverter.ToUInt32(data, 47); returnPointWaveformLocation = BitConverter.ToSingle(data, 51); dx = BitConverter.ToSingle(data, 55); dy = BitConverter.ToSingle(data, 59); dz = BitConverter.ToSingle(data, 63); return true; } else return false; } public override IPointDataRecord ParsePoint(byte[] data) { PDR9 newPoint = new PDR9(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append("WavePacket Index: " + wavePacketDescriptorIndex.ToString() + Environment.NewLine); sb.Append("Waveform Offset: " + byteOffsetToWaveformData.ToString() + Environment.NewLine); sb.Append("Waveform Size: " + waveformPacketSize.ToString() + Environment.NewLine); sb.Append("Return Point Waveform: " + returnPointWaveformLocation.ToString() + Environment.NewLine); sb.Append(string.Format("Delta Pos: dx={0} dy={1} dz={2} {3}", dx, dy, dz, Environment.NewLine)); return sb.ToString(); } } class PDR10 : PDR8 { byte wavePacketDescriptorIndex; ulong byteOffsetToWaveformData; uint waveformPacketSize; float returnPointWaveformLocation; float dx; float dy; float dz; const int headerSize = 67; public override bool ReadPoint(byte[] data) { if (DataHelpers.VerifySize(data, headerSize)) { base.ReadPoint(data); wavePacketDescriptorIndex = data[30]; byteOffsetToWaveformData = BitConverter.ToUInt64(data, 31); waveformPacketSize = BitConverter.ToUInt32(data, 39); returnPointWaveformLocation = BitConverter.ToSingle(data, 43); dx = BitConverter.ToSingle(data, 47); dy = BitConverter.ToSingle(data, 51); dz = BitConverter.ToSingle(data, 55); return true; } else return false; } public override IPointDataRecord ParsePoint(byte[] data) { PDR10 newPoint = new PDR10(); newPoint.ReadPoint(data); return newPoint; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(base.ToString()); sb.Append("WavePacket Index: " + wavePacketDescriptorIndex.ToString() + Environment.NewLine); sb.Append("Waveform Offset: " + byteOffsetToWaveformData.ToString() + Environment.NewLine); sb.Append("Waveform Size: " + waveformPacketSize.ToString() + Environment.NewLine); sb.Append("Return Point Waveform: " + returnPointWaveformLocation.ToString() + Environment.NewLine); sb.Append(string.Format("Delta Pos: dx={0} dy={1} dz={2} {3}", dx, dy, dz, Environment.NewLine)); return sb.ToString(); } } }