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
{
2021-11-16 14:02:05 +13:00
// 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):
2021-11-16 14:02:05 +13:00
// 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>
2021-11-16 14:02:05 +13:00
/// 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 ( ) ;
2021-11-16 14:02:05 +13:00
Header . ReadHeader ( source ) ; // Reads intrinsics from the stream, such as
2021-03-13 21:31:51 +13:00
2021-11-17 12:29:35 +13:00
// Reflective PDR type
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 ) ;
2021-11-17 12:29:35 +13:00
2021-03-13 21:31:51 +13:00
// 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.
2021-11-17 12:29:35 +13:00
// 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)
2021-03-13 21:31:51 +13:00
Type t = Type . GetType ( "LASRead.LASFormat.PDR" + Header . PointDataRecordFormat . ToString ( ) ) ;
IPointDataRecord initialPoint = ( IPointDataRecord ) Activator . CreateInstance ( t ) ;
initialPoint . ReadPoint ( pdrInitial ) ;
2021-11-17 12:29:35 +13:00
// Using generics to dynamically create a PDRCollection<PDR[type]> storage
2021-03-13 21:31:51 +13:00
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 ;
}
}
}