using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Printing.IndexedProperties; using System.Text; using System.Windows.Markup; namespace LASRead.LASFormat { class LASFormatDetail { /* Primative Types: * (LAS = C#) * char = byte * uchar = char * short = int16 * ushort = uint16 * long = int * ulong = uint * long long = int64 * ulong long = uint64 * float = float * double = double * string = 1-byte, ASCII chars, null terminated unless at max length (therefore no termination) */ /* Public header: * char[4] - signature * ushort - file id * ushort encoding * ulong ?- guid 1 * ushort ?- guid 2 * ushort ?- guid 3 * uchar[8] ?- guid 4 * uchar */ } public class FileHeader { ushort headerSize; // Follows the correct order of the header char[] fileSignature; ushort fileSourceID; ushort globalEncoding; // Optional Fields are filled with zero uint GUIDData1 = 0; ushort GUIDData2 = 0; ushort GUIDData3 = 0; byte[] GUIDData4 = new byte[] {0,0,0,0,0,0,0,0}; byte versionMajor; byte versionMinor; char[] systemIdentifier; char[] generatingSoftware; ushort fileCreationDayOfYear; ushort fileCreationYear; uint dataOffset; uint numberVLRs; // Variable Length Records byte pointDataRecordFormat; ushort pointDataRecordLength; uint legacyNumberOfPointRecords; uint[] legacyNumberOfPointByReturn; double x_scaleFactor; double y_scaleFactor; double z_scaleFactor; double x_offset; double y_offset; double z_offset; double x_max; double x_min; double y_max; double y_min; double z_max; double z_min; ulong startOfWaveformDPR; // Data Packet Record ulong startOfFirstExtendedVLR; uint numberOfExtendedVLRs; ulong numberPointRecords; ulong[] numberPointsByReturn; /// /// Signature of the data. Should always be "LASF" /// public char[] FileSignature { get => fileSignature; set => fileSignature = value; } /// /// ID Associated with the data. Intended to differentiate between different sources /// public ushort FileSourceID { get => fileSourceID; set => fileSourceID = value; } /// /// Flags Indicating the formats of certain data
/// 0 : GPS Time Format (0 - Week time; 1 - standard GPS Time)
/// 1 : Waveforms included (depricated)
/// 2 : Waveforms in associated .wdp file (mutex with 1:)
/// 3 : Point returns are synthetic
/// 4 : Coordinate Reference System is WKT, else GeoTIFF
/// 5-15 : Reserved
///
public ushort GlobalEncoding { get => globalEncoding; set => globalEncoding = value; } public uint GUIDData11 { get => GUIDData1; set => GUIDData1 = value; } public ushort GUIDData21 { get => GUIDData2; set => GUIDData2 = value; } public ushort GUIDData31 { get => GUIDData3; set => GUIDData3 = value; } public byte[] GUIDData41 { get => GUIDData4; set => GUIDData4 = value; } public byte VersionMajor { get => versionMajor; set => versionMajor = value; } public byte VersionMinor { get => versionMinor; set => versionMinor = value; } /// /// Identifies the hardware used to collect the data
/// Can be MERGE, MODIFICATION, EXTRACTION, TRANSFORMATION, OTHER, or a custom expression ///
public char[] SystemIdentifier { get => systemIdentifier; set => systemIdentifier = value; } /// /// Identifies the software used to encode the data /// public char[] GeneratingSoftware { get => generatingSoftware; set => generatingSoftware = value; } /// /// The day of the year this file was created /// public ushort FileCreationDayOfYear { get => fileCreationDayOfYear; set => fileCreationDayOfYear = value; } /// /// The year this data was collected /// public ushort FileCreationYear { get => fileCreationYear; set => fileCreationYear = value; } /// /// The size of this header /// public ushort HeaderSize { get => headerSize; set => headerSize = value; } /// /// The offset of the first Point-Data-Record (PDR) /// public uint DataOffset { get => dataOffset; set => dataOffset = value; } /// /// The number of Variable Length Records (VLRs) present /// public uint NumberVLRs { get => numberVLRs; set => numberVLRs = value; } /// /// An integer representing the format of the PDR data.
/// See ///
public byte PointDataRecordFormat { get => pointDataRecordFormat; set => pointDataRecordFormat = value; } /// /// The length of a PDR record /// public ushort PointDataRecordLength { get => pointDataRecordLength; set => pointDataRecordLength = value; } /// /// Number of point records for legacy compatibility (32 bits) /// public uint LegacyNumberOfPointRecords { get => legacyNumberOfPointRecords; set => legacyNumberOfPointRecords = value; } /// /// todo /// public uint[] LegacyNumberOfPointByReturn { get => legacyNumberOfPointByReturn; set => legacyNumberOfPointByReturn = value; } /// /// Scale factor of the X axis. Multiply by this value to get the true value /// public double X_scaleFactor { get => x_scaleFactor; set => x_scaleFactor = value; } /// /// Scale factor of the Y axis. Multiply by this value to get the true value /// public double Y_scaleFactor { get => y_scaleFactor; set => y_scaleFactor = value; } /// /// Scale factor of the Z axis. Multiply by this value to get the true value /// public double Z_scaleFactor { get => z_scaleFactor; set => z_scaleFactor = value; } /// /// Offset of the X axis.
/// Calculate Coordinates like so:
/// X_final = X * X_scaleFactor + X_offset ///
public double X_offset { get => x_offset; set => x_offset = value; } /// /// Offset of the Y axis.
/// Calculate Coordinates like so:
/// Y_final = Y * Y_scaleFactor + Y_offset ///
public double Y_offset { get => y_offset; set => y_offset = value; } /// /// Offset of the Z axis.
/// Calculate Coordinates like so:
/// Z_final = Z * Z_scaleFactor + Z_offset ///
public double Z_offset { get => z_offset; set => z_offset = value; } /// /// Largest X value in the data /// public double X_max { get => x_max; set => x_max = value; } /// /// Smallest X value in the data /// public double X_min { get => x_min; set => x_min = value; } /// /// Largest Y value in the data /// public double Y_max { get => y_max; set => y_max = value; } /// /// Smallest Y value in the data /// public double Y_min { get => y_min; set => y_min = value; } /// /// Largest Z value in the data /// public double Z_max { get => z_max; set => z_max = value; } /// /// Smallest Z value in the data /// public double Z_min { get => z_min; set => z_min = value; } /// /// New in 1.4
/// Start of the Waveform Data Packet Records ///
public ulong StartOfWaveformDPR { get => startOfWaveformDPR; set => startOfWaveformDPR = value; } /// /// New in 1.4
/// Start of the first Extended Variable Length Record (EVLR) ///
public ulong StartOfFirstExtendedVLR { get => startOfFirstExtendedVLR; set => startOfFirstExtendedVLR = value; } /// /// New in 1.4
/// Number of Extended Variable Length Records ///
public uint NumberOfExtendedVLRs { get => numberOfExtendedVLRs; set => numberOfExtendedVLRs = value; } /// /// New in 1.4
/// Number of point records (64 bits) ///
public ulong NumberPointRecords { get => numberPointRecords; set => numberPointRecords = value; } /// /// New in 1.4
/// todo ///
public ulong[] NumberPointsByReturn { get => numberPointsByReturn; set => numberPointsByReturn = value; } public byte[] GetAsByteArray() { byte[] endBytes = new byte[headerSize]; DataHelpers.ToByteArray(fileSignature).CopyTo(endBytes,0); //4 BitConverter.GetBytes(fileSourceID).CopyTo(endBytes, 4); // 2 BitConverter.GetBytes(globalEncoding).CopyTo(endBytes, 6); // 2 BitConverter.GetBytes(GUIDData1).CopyTo(endBytes, 8); // 4 BitConverter.GetBytes(GUIDData2).CopyTo(endBytes, 12); // 2 BitConverter.GetBytes(GUIDData3).CopyTo(endBytes, 14); // 2 GUIDData4.CopyTo(endBytes, 16); // 8 endBytes[24] = versionMajor; endBytes[25] = versionMinor; DataHelpers.ToByteArray(systemIdentifier).CopyTo(endBytes, 26); // 32 Bytes DataHelpers.ToByteArray(GeneratingSoftware).CopyTo(endBytes, 58); // 32 Bytes BitConverter.GetBytes(fileCreationDayOfYear).CopyTo(endBytes, 90); // 2 BitConverter.GetBytes(fileCreationYear).CopyTo(endBytes, 92); // 2 BitConverter.GetBytes(headerSize).CopyTo(endBytes, 94); // 2 BitConverter.GetBytes(dataOffset).CopyTo(endBytes, 96); // 4 BitConverter.GetBytes(numberVLRs).CopyTo(endBytes, 100); // 4 endBytes[104] = pointDataRecordFormat; BitConverter.GetBytes(pointDataRecordLength).CopyTo(endBytes, 105); // 2 BitConverter.GetBytes(legacyNumberOfPointRecords).CopyTo(endBytes, 107); // 4 DataHelpers.ToByteArray(LegacyNumberOfPointByReturn).CopyTo(endBytes, 111); // 20 bytes BitConverter.GetBytes(X_scaleFactor).CopyTo(endBytes, 131); // 8 BitConverter.GetBytes(Y_scaleFactor).CopyTo(endBytes, 139); // 8 BitConverter.GetBytes(Z_scaleFactor).CopyTo(endBytes, 147); // 8 BitConverter.GetBytes(X_offset).CopyTo(endBytes, 155); // 8 BitConverter.GetBytes(Y_offset).CopyTo(endBytes, 163); // 8 BitConverter.GetBytes(Z_offset).CopyTo(endBytes, 171); // 8 BitConverter.GetBytes(X_max).CopyTo(endBytes, 179); // 8 BitConverter.GetBytes(X_min).CopyTo(endBytes, 187); // 8 BitConverter.GetBytes(Y_max).CopyTo(endBytes, 195); // 8 BitConverter.GetBytes(Y_min).CopyTo(endBytes, 203); // 8 BitConverter.GetBytes(Z_max).CopyTo(endBytes, 211); // 8 BitConverter.GetBytes(Z_min).CopyTo(endBytes, 219); // 8 if (VersionMajor >= 1 && VersionMinor >= 4) { BitConverter.GetBytes(startOfWaveformDPR).CopyTo(endBytes, 227); // 8 BitConverter.GetBytes(StartOfFirstExtendedVLR).CopyTo(endBytes, 235); // 8 BitConverter.GetBytes(NumberOfExtendedVLRs).CopyTo(endBytes, 243); // 4 BitConverter.GetBytes(NumberPointRecords).CopyTo(endBytes, 247); // 8 DataHelpers.ToByteArray(NumberPointsByReturn).CopyTo(endBytes, 255); // 120 Bytes } return endBytes; } /// /// Reads the header from the provided source /// /// /// The expected size of the header public bool ReadHeader(Stream source) { source.Position = 94; byte[] headerSizeBytes = new byte[2]; source.Read(headerSizeBytes, 0, 2); headerSize = BitConverter.ToUInt16(headerSizeBytes, 0); if (headerSize < 375) { // Assert the version is less than 1.4, where records after z-min were added. source.Position = 24; byte major = (byte)source.ReadByte(); byte minor = (byte)source.ReadByte(); if (major >= 1 && minor > 3) { throw new InvalidDataException("Header is too small for data format >= v1.4"); } } source.Position = 0; byte[] inputHeader = new byte[headerSize]; source.Read(inputHeader, 0, HeaderSize); FileSignature = new char[] { (char)inputHeader[0], (char)inputHeader[1], (char)inputHeader[2], (char)inputHeader[3] }; FileSourceID = BitConverter.ToUInt16(inputHeader, 4); GlobalEncoding = BitConverter.ToUInt16(inputHeader, 6); GUIDData11 = BitConverter.ToUInt32(inputHeader, 8); GUIDData21 = BitConverter.ToUInt16(inputHeader, 12); GUIDData31 = BitConverter.ToUInt16(inputHeader, 14); GUIDData41 = new byte[] { inputHeader[16], inputHeader[17], inputHeader[18], inputHeader[19], inputHeader[20], inputHeader[21], inputHeader[22], inputHeader[23]}; VersionMajor = inputHeader[24]; VersionMinor = inputHeader[25]; SystemIdentifier = DataHelpers.ToCharArray(inputHeader, 26, 32); GeneratingSoftware = DataHelpers.ToCharArray(inputHeader, 58, 32); FileCreationDayOfYear = BitConverter.ToUInt16(inputHeader, 90); FileCreationYear = BitConverter.ToUInt16(inputHeader, 92); // 94 is headerSize DataOffset = BitConverter.ToUInt32(inputHeader, 96); NumberVLRs = BitConverter.ToUInt32(inputHeader, 100); PointDataRecordFormat = inputHeader[104]; PointDataRecordLength = BitConverter.ToUInt16(inputHeader, 105); LegacyNumberOfPointRecords = BitConverter.ToUInt32(inputHeader, 107); LegacyNumberOfPointByReturn = DataHelpers.ToUintArray(inputHeader, 111, 20); X_scaleFactor = BitConverter.ToDouble(inputHeader, 131); Y_scaleFactor = BitConverter.ToDouble(inputHeader, 139); Z_scaleFactor = BitConverter.ToDouble(inputHeader, 147); X_offset = BitConverter.ToDouble(inputHeader, 155); Y_offset = BitConverter.ToDouble(inputHeader, 163); z_offset = BitConverter.ToDouble(inputHeader, 171); X_max = BitConverter.ToDouble(inputHeader, 179); X_min = BitConverter.ToDouble(inputHeader, 187); Y_max = BitConverter.ToDouble(inputHeader, 195); Y_min = BitConverter.ToDouble(inputHeader, 203); Z_max = BitConverter.ToDouble(inputHeader, 211); Z_min = BitConverter.ToDouble(inputHeader, 219); if (versionMinor >= 4) { StartOfWaveformDPR = BitConverter.ToUInt64(inputHeader, 227); StartOfFirstExtendedVLR = BitConverter.ToUInt64(inputHeader, 235); NumberOfExtendedVLRs = BitConverter.ToUInt32(inputHeader, 243); numberPointRecords = BitConverter.ToUInt64(inputHeader, 247); NumberPointsByReturn = DataHelpers.ToULongArray(inputHeader, 255, 120); } return true; } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append("Signature: " + new string(FileSignature) + Environment.NewLine); sb.Append("SourceID: " + FileSourceID.ToString() + Environment.NewLine); sb.Append("Global Encoding: " + GlobalEncoding.ToString() + Environment.NewLine); sb.Append("GUIDData1: " + GUIDData11.ToString() + Environment.NewLine); sb.Append("GUIDData2: " + GUIDData21.ToString() + Environment.NewLine); sb.Append("GUIDData3: " + GUIDData31.ToString() + Environment.NewLine); sb.Append("GUIDData4: "); foreach (byte data in GUIDData41) { if (data != 0) { sb.Append((char)data); } } sb.Append(Environment.NewLine); sb.Append("Version: " + VersionMajor.ToString() + "." + VersionMinor.ToString() + Environment.NewLine); sb.Append("System Identifier: "); sb.Append(SystemIdentifier); sb.Append(Environment.NewLine); sb.Append("Generating Software: "); sb.Append(GeneratingSoftware); sb.Append(Environment.NewLine); DateTime creationDate = new DateTime(FileCreationYear, 1, 1); creationDate = creationDate.AddDays(FileCreationDayOfYear); sb.Append("Creation Date: " + creationDate.ToShortDateString() + Environment.NewLine); sb.Append("Header Length: " + HeaderSize.ToString() + Environment.NewLine); sb.Append("Data Offset: " + DataOffset.ToString() + Environment.NewLine); sb.Append("Number VRLs: " + NumberVLRs.ToString() + Environment.NewLine); sb.Append("PointDataFormat: " + PointDataRecordFormat.ToString() + Environment.NewLine); sb.Append("PointDataLength: " + PointDataRecordLength.ToString() + Environment.NewLine); sb.Append("Legacy #PDRs: " + LegacyNumberOfPointRecords.ToString() + Environment.NewLine); sb.Append("Legacy #PDR returns: "); foreach (uint legacyReturn in LegacyNumberOfPointByReturn) { sb.Append(legacyReturn); sb.Append(" "); } sb.Append(Environment.NewLine); sb.Append("X Scale Factor: " + X_scaleFactor.ToString() + Environment.NewLine); sb.Append("Y Scale Factor: " + Y_scaleFactor.ToString() + Environment.NewLine); sb.Append("Z Scale Factor: " + Z_scaleFactor.ToString() + Environment.NewLine); sb.Append("X Offset: " + X_offset.ToString() + Environment.NewLine); sb.Append("Y Offset: " + Y_offset.ToString() + Environment.NewLine); sb.Append("Z Offset: " + Z_offset.ToString() + Environment.NewLine); sb.Append("X Max: " + X_max.ToString() + Environment.NewLine); sb.Append("X Min: " + X_min.ToString() + Environment.NewLine); sb.Append("Y Max: " + Y_max.ToString() + Environment.NewLine); sb.Append("Y Min: " + Y_min.ToString() + Environment.NewLine); sb.Append("Z Max: " + Z_max.ToString() + Environment.NewLine); sb.Append("Z Min: " + Z_min.ToString() + Environment.NewLine); if (versionMinor >= 4) { sb.Append("Waveform Start: " + StartOfWaveformDPR.ToString() + Environment.NewLine); sb.Append("Extended VLR Start: " + StartOfFirstExtendedVLR.ToString() + Environment.NewLine); sb.Append("Number eVLRs: " + NumberOfExtendedVLRs.ToString() + Environment.NewLine); sb.Append("Number Point Records: " + NumberPointRecords.ToString() + Environment.NewLine); sb.Append("NPR by Return: " + NumberPointsByReturn.ToString() + Environment.NewLine); } return sb.ToString(); } } }