LAS-Read/LASRead/LASFile.cs

220 lines
8.4 KiB
C#
Raw Normal View History

2021-03-13 21:31:51 +13:00
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
2021-03-13 21:31:51 +13:00
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
2021-03-13 21:31:51 +13:00
// 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; }
/// <summary>
/// Opens the LAS file from the provided stream
2021-03-13 21:31:51 +13:00
/// </summary>
/// <param name="source"></param>
public LASFile(Stream source)
{
this.source = source;
Header = new FileHeader();
Header.ReadHeader(source); // Reads intrinsics from the stream, such as
2021-03-13 21:31:51 +13:00
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.
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<PDR*> 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;
}
/// <summary>
/// Gets a new stream of the segment from the provided stream.<br />
/// Prefers to exist as a <see cref="MemoryStream"/>, though will create a temporary file
/// if the data is too large to store in memory.
/// </summary>
/// <param name="source">The stream to read from</param>
/// <param name="segmentSize">The total size of the data</param>
/// <param name="memoryLimit">The maximum memory we allocate</param>
/// <param name="bufferSize">The size of the buffer</param>
/// <returns></returns>
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);
}
/// <summary>
/// Creates a new .las file
/// </summary>
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;
}
}
}