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 { int X { get; set; } int Y { get; set; } int Z { get; set; } Nullable Intensity { get; set; } byte ReturnNumberFlag_value { get; set; } byte NumberOfReturnsFlag_value { get; set; } byte ScanDirectionFlag_value { get; set; } byte EdgeOfFlightLineFlag_value { get; set; } byte Classification { get; set; } sbyte ScanAngleRank { get; set; } Nullable UserData { get; set; } ushort PointSourceID { get; set; } 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 { int x; int y; int z; ushort? intensity; byte returnNumberFlag_value; byte numberOfReturnsFlag_value; byte scanDirectionFlag_value; byte edgeOfFlightLineFlag_value; byte classification; sbyte scanAngleRank; byte? userData; ushort pointSourceID; public static readonly int headerSize = 20; public int X { get => x; set => x = value; } public int Y { get => y; set => y = value; } public int Z { get => z; set => z = value; } public ushort? Intensity { get => intensity; set => intensity = value; } public byte ReturnNumberFlag_value { get => returnNumberFlag_value; set => returnNumberFlag_value = value; } public byte NumberOfReturnsFlag_value { get => numberOfReturnsFlag_value; set => numberOfReturnsFlag_value = value; } public byte ScanDirectionFlag_value { get => scanDirectionFlag_value; set => scanDirectionFlag_value = value; } public byte EdgeOfFlightLineFlag_value { get => edgeOfFlightLineFlag_value; set => edgeOfFlightLineFlag_value = value; } public byte Classification { get => classification; set => classification = value; } public sbyte ScanAngleRank { get => scanAngleRank; set => scanAngleRank = value; } public byte? UserData { get => userData; set => userData = value; } public ushort PointSourceID { get => pointSourceID; set => pointSourceID = value; } 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; new public static readonly 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; new public static readonly 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; new public static readonly 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; new public static readonly 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; new public static readonly 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 { int x; int y; int z; ushort? intensity; byte returnNumberFlag_value; // NB: 4 bits here byte numberOfReturnsFlag_value; // 4 byte classificationFlag_value; // 4 byte scannerChannelFlag_value; // 2 byte scanDirectionFlag_value; // 1 byte edgeOfFlightLineFlag_value; // 1 byte classification; short scanAngleRank; byte? userData; ushort pointSourceID; double GPSTime; public static readonly int headerSize = 30; // Inherited members public int X { get => x; set => x = value; } public int Y { get => y; set => y = value; } public int Z { get => z; set => z = value; } public ushort? Intensity { get => intensity; set => intensity = value; } public byte ReturnNumberFlag_value { get => returnNumberFlag_value; set => returnNumberFlag_value = value; } public byte NumberOfReturnsFlag_value { get => numberOfReturnsFlag_value; set => numberOfReturnsFlag_value = value; } public byte ScanDirectionFlag_value { get => scanDirectionFlag_value; set => scanDirectionFlag_value = value; } public byte EdgeOfFlightLineFlag_value { get => edgeOfFlightLineFlag_value; set => edgeOfFlightLineFlag_value = value; } public byte Classification { get => classification; set => classification = value; } public short ScanAngleRank { get => scanAngleRank; set => scanAngleRank = value; } public byte? UserData { get => userData; set => userData = value; } public ushort PointSourceID { get => pointSourceID; set => pointSourceID = value; } // Local members public byte ClassificationFlag_value { get => classificationFlag_value; set => classificationFlag_value = value; } public byte ScannerChannelFlag_value { get => scannerChannelFlag_value; set => scannerChannelFlag_value = value; } public double GPSTime1 { get => GPSTime; set => GPSTime = value; } 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; new public static readonly 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; new public static readonly 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; new public static readonly 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; new public static readonly 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(); } } }