225 lines
8.7 KiB
C#
225 lines
8.7 KiB
C#
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; }
|
|
|
|
/// <summary>
|
|
/// Opens the LAS file from the provided stream
|
|
/// </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
|
|
|
|
// 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<value>' 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<PDR[type]> 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;
|
|
}
|
|
}
|
|
}
|