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 (or more, especially if using the same scale worldwide)
// - Output heightmaps in a Unity- and Unreal- ready format
// - Ouput vertex colours for each point, if available
// - Allow exporting different layers of a file
// - Link to a raster map provider and dynamically load into the aforementioned engine
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(source);
vlrCollection = new RecordCollection(ref source, Header.HeaderSize, Header.NumberVLRs, initial);
// !---------------------------------------------------------------
// NB: at this point, the actual coordinate system is unknown - there is no obvious
// conversions for this data
// - An example are New Zealand coordinates, which are presented as Transverse Mercator easting and northing distances in meters,
// rather than Geodetic Datum latitude and longitude points in degrees
// To resolve coordinates on a world-wide scale, we'll need to identify the coordinate data type and convert to a
// standard coordinate system
// !---------------------------------------------------------------
// A VLR with a user ID of LASF_Projection\0 contains the required coordinate system:
// E.g.: 2016 scan information from LINZ contains the following transform string: 'NZGD2000 / New Zealand Transverse Mercator 2000 + NZVD2016 height|NZGD2000|NZVD2016 height|'
// 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 = Header.StartOfFirstExtendedVLR;
//long EVLRSize = (Header.StartOfWaveformDPR == 0 ? source.Length : (long)Header.StartOfWaveformDPR) - (long)Header.StartOfFirstExtendedVLR;
//EVLRStream = GetOffsetStream(source, EVLRSize);
EVLRHeader evlrInitial = new EVLRHeader();
evlrInitial.ReadRecords(source);
evlrCollection = new RecordCollection(ref EVLRStream, Header.StartOfFirstExtendedVLR, 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);
}
newStream.Position = 0;
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;
}
}
}