using LASRead; using LASRead.LASFormat; using Microsoft.Windows.Themes; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Security.AccessControl; using System.Text; namespace LASFormat { public class LASFile : IDisposable { // based on the LAS format specifications from asprs - The imaging and Geospatial Information Society - https://www.asprs.org/divisions-committees/lidar-division/laser-las-file-format-exchange-activities // Parses LAS files, such that the data is in a more readily-usable format // // Todos: // - Split a LAS file into subsections, probably about 100m^2 // - generate heightmaps from sections, 0-255 per point // - Output heightmaps in a Unity- and Unreal- ready format // - Ouput vertex colours for each point, if available // - Allow exporting different layers of a file private const int V = 16384; private const int V1 = V * (ushort.MaxValue +1); // File Structure (v1.4): // File Header (a.k.a. Public Header Block) // VLRs (Variable Length Records) - Any number of these (max 65535 bytes) // PDRs (Point Data Records) - Any number of // EVLRs (Extended VRLs) Stream source; Stream VLRStream; Stream PDRStream; Stream EVLRStream; FileHeader header; ulong VLRStart; ulong PDRStart; ulong EVLRStart; ulong WaveformStart; public RecordCollection vlrCollection; public RecordCollection evlrCollection; public dynamic points; public FileHeader Header { get => header; private set => header = value; } public Type PointsType { get; private set; } public Type PDRType { get; private set; } /// /// Opens the LAS file from the provided stream /// /// public LASFile(Stream source) { this.source = source; Header = new FileHeader(); Header.ReadHeader(source); // Reads intrinsics from the stream, such as // Reflective PDR type PDRType = Type.GetType("LASRead.LASFormat.PDR" + Header.PointDataRecordFormat.ToString()); // As the header has now been read, we can now create some underlying streams, to act as our object sources. // Set our current source position to the end of the header. source.Position = Header.HeaderSize; long VLRSize = Header.DataOffset - Header.HeaderSize; VLRStream = GetOffsetStream(source, VLRSize); // Grab a starting VLR VLRStart = Header.HeaderSize; VLRHeader initial = new VLRHeader(); initial.ReadRecords(VLRStream); vlrCollection = new RecordCollection(VLRStream, VLRStart, Header.NumberVLRs, initial); // Grab a starting PDR PDRStart = Header.DataOffset; source.Position = (long)PDRStart; long PDRSize = ((Header.VersionMajor >= 1 && Header.VersionMinor >= 4) ? (long)Header.StartOfFirstExtendedVLR : source.Length) - (long)PDRStart; PDRStream = GetOffsetStream(source, PDRSize); byte[] pdrInitial = new byte[Header.PointDataRecordLength]; PDRStream.Read(pdrInitial, 0, Header.PointDataRecordLength); // Convert the PDRF to our PDR types. Uses System.Reflections to find the value and avoid the use of switch-case. // Use generics to identify the PDR type. // i.e., Reflections looks for the class 'LASRead.LASFormat.PDR' and uses that as the type of the following methods // at runtime. // We use Activator.CreateInstance to instance this unknown type (which we assert must be an instance of IPointDataRecord) Type t = Type.GetType("LASRead.LASFormat.PDR" + Header.PointDataRecordFormat.ToString()); IPointDataRecord initialPoint = (IPointDataRecord)Activator.CreateInstance(t); initialPoint.ReadPoint(pdrInitial); // Using generics to dynamically create a PDRCollection storage points = Activator.CreateInstance(typeof(PDRCollection<>).MakeGenericType(PDRType), new object[] { Header, PDRStream, initialPoint}); PointsType = Type.GetTypeArray(new object[] { points })[0]; // Grab a starting EVLR EVLRStart = Header.StartOfFirstExtendedVLR; source.Position = (long)EVLRStart; long EVLRSize = (Header.StartOfWaveformDPR == 0 ? source.Length : (long)Header.StartOfWaveformDPR) - (long)Header.StartOfFirstExtendedVLR; EVLRStream = GetOffsetStream(source, EVLRSize); EVLRHeader evlrInitial = new EVLRHeader(); evlrInitial.ReadRecords(EVLRStream); evlrCollection = new RecordCollection(EVLRStream, EVLRStart, Header.NumberOfExtendedVLRs, evlrInitial); // Finally, set the stream back to the starting position source.Position = 0; // TODO: Grab a starting Waveform // WaveformStart = Header.StartOfWaveformDPR; } /// /// Gets a new stream of the segment from the provided stream.
/// Prefers to exist as a , though will create a temporary file /// if the data is too large to store in memory. ///
/// The stream to read from /// The total size of the data /// The maximum memory we allocate /// The size of the buffer /// private Stream GetOffsetStream(Stream source, long segmentSize, int memoryLimit, int bufferSize) { long dataStart = source.Position; Stream newStream; if (segmentSize <= memoryLimit) { newStream = new MemoryStream((int)segmentSize); } else { string fileName = Path.GetTempFileName(); FileInfo fileInfo = new FileInfo(fileName); newStream = fileInfo.Create(FileMode.OpenOrCreate, FileSystemRights.FullControl, FileShare.Read, V, FileOptions.DeleteOnClose, null); } byte[] buffer = new byte[bufferSize]; while (source.Position - dataStart + bufferSize <= segmentSize) { source.Read(buffer); newStream.Write(buffer); } int remaining = (int)segmentSize - (int)(source.Position - dataStart); if (remaining > 0) { source.Read(buffer, 0, remaining); newStream.Write(buffer, 0, remaining); } return newStream; } private Stream GetOffsetStream(Stream source, long segmentSize) { return GetOffsetStream(source, segmentSize, V1, V); } /// /// Creates a new .las file /// public LASFile() { source = null; header = new FileHeader(); VLRStream = new MemoryStream(); PDRStream = new MemoryStream(); EVLRStream = new MemoryStream(); } public void Dispose() { header = null; if (source != null) { source.Dispose(); } if (VLRStream != null) { VLRStream.Dispose(); } if (PDRStream != null) { PDRStream.Dispose(); } if (EVLRStream != null) { EVLRStream.Dispose(); } if (vlrCollection != null) { vlrCollection.GetEnumerator().Dispose(); vlrCollection = null; } if (evlrCollection != null) { evlrCollection.GetEnumerator().Dispose(); evlrCollection = null; } if (points != null) { points = null; } } } public class LASFile_v12 { // File Structure (v1.2): // FileHeader // VLRs (Variable Length Records) // PDRs (Point Data Records) Stream source; FileHeader header; VLRHeader vlrs; public LASFile VerifyVersion() { if (header.VersionMajor >= 1 && header.VersionMinor > 2) { return new LASFile(source); } else return null; } } }