From f430800a45d3492a60fdf286b2f719965edf8197 Mon Sep 17 00:00:00 2001 From: Brychan Dempsey Date: Sat, 13 Mar 2021 21:31:51 +1300 Subject: [PATCH] Initial files --- .gitattributes | 63 +++ .gitignore | 340 ++++++++++++ LASRead.sln | 37 ++ LASRead/App.xaml | 9 + LASRead/App.xaml.cs | 17 + LASRead/AssemblyInfo.cs | 10 + LASRead/LASFile.cs | 210 ++++++++ LASRead/LASFormat/DataHelpers.cs | 172 +++++++ LASRead/LASFormat/FileHeader.cs | 406 +++++++++++++++ LASRead/LASFormat/PDRCollection.cs | 114 ++++ LASRead/LASFormat/PointDataRecord.cs | 714 ++++++++++++++++++++++++++ LASRead/LASFormat/Record.cs | 205 ++++++++ LASRead/LASFormat/RecordCollection.cs | 82 +++ LASRead/LASRead.csproj | 9 + LASRead/MainWindow.xaml | 13 + LASRead/MainWindow.xaml.cs | 55 ++ LASRead/RawPoints.cs | 59 +++ PrintLasData/LasInteractor.csproj | 12 + PrintLasData/Program.cs | 140 +++++ README.md | 3 + tests/Tests.csproj | 19 + tests/UnitTest1.cs | 23 + 22 files changed, 2712 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LASRead.sln create mode 100644 LASRead/App.xaml create mode 100644 LASRead/App.xaml.cs create mode 100644 LASRead/AssemblyInfo.cs create mode 100644 LASRead/LASFile.cs create mode 100644 LASRead/LASFormat/DataHelpers.cs create mode 100644 LASRead/LASFormat/FileHeader.cs create mode 100644 LASRead/LASFormat/PDRCollection.cs create mode 100644 LASRead/LASFormat/PointDataRecord.cs create mode 100644 LASRead/LASFormat/Record.cs create mode 100644 LASRead/LASFormat/RecordCollection.cs create mode 100644 LASRead/LASRead.csproj create mode 100644 LASRead/MainWindow.xaml create mode 100644 LASRead/MainWindow.xaml.cs create mode 100644 LASRead/RawPoints.cs create mode 100644 PrintLasData/LasInteractor.csproj create mode 100644 PrintLasData/Program.cs create mode 100644 README.md create mode 100644 tests/Tests.csproj create mode 100644 tests/UnitTest1.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ce6fdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,340 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb \ No newline at end of file diff --git a/LASRead.sln b/LASRead.sln new file mode 100644 index 0000000..72eabb7 --- /dev/null +++ b/LASRead.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30611.23 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LASRead", "LASRead\LASRead.csproj", "{6E6BF63D-11CA-4F6B-8864-FC21A6E9244C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{AB8F159A-BAB1-4CE0-AC0A-392733381916}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LasInteractor", "PrintLasData\LasInteractor.csproj", "{BEEFD9AE-42F8-472E-82E6-CE1A8A115243}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6E6BF63D-11CA-4F6B-8864-FC21A6E9244C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E6BF63D-11CA-4F6B-8864-FC21A6E9244C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E6BF63D-11CA-4F6B-8864-FC21A6E9244C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E6BF63D-11CA-4F6B-8864-FC21A6E9244C}.Release|Any CPU.Build.0 = Release|Any CPU + {AB8F159A-BAB1-4CE0-AC0A-392733381916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB8F159A-BAB1-4CE0-AC0A-392733381916}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB8F159A-BAB1-4CE0-AC0A-392733381916}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB8F159A-BAB1-4CE0-AC0A-392733381916}.Release|Any CPU.Build.0 = Release|Any CPU + {BEEFD9AE-42F8-472E-82E6-CE1A8A115243}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEEFD9AE-42F8-472E-82E6-CE1A8A115243}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEEFD9AE-42F8-472E-82E6-CE1A8A115243}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEEFD9AE-42F8-472E-82E6-CE1A8A115243}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A730301B-3D24-4CAC-B787-5E811AD58068} + EndGlobalSection +EndGlobal diff --git a/LASRead/App.xaml b/LASRead/App.xaml new file mode 100644 index 0000000..b7a47bb --- /dev/null +++ b/LASRead/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/LASRead/App.xaml.cs b/LASRead/App.xaml.cs new file mode 100644 index 0000000..c979ebf --- /dev/null +++ b/LASRead/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace LASRead +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/LASRead/AssemblyInfo.cs b/LASRead/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/LASRead/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/LASRead/LASFile.cs b/LASRead/LASFile.cs new file mode 100644 index 0000000..557197f --- /dev/null +++ b/LASRead/LASFile.cs @@ -0,0 +1,210 @@ +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 + { + private const int V = 16384; + private const int V1 = V * (ushort.MaxValue +1); + + // File Structure (v1.4): + // FileHeader + // VLRs (Variable Length Records) + // PDRs (Point Data Records) + // 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); + + 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 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; + } + } +} diff --git a/LASRead/LASFormat/DataHelpers.cs b/LASRead/LASFormat/DataHelpers.cs new file mode 100644 index 0000000..9700bc8 --- /dev/null +++ b/LASRead/LASFormat/DataHelpers.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace LASRead.LASFormat +{ + static class DataHelpers + { + /// + /// Converts an array of characters into an array of bytes. Trims values > 255 to 255. + /// + /// + /// + public static byte[] ToByteArray(char[] characters) + { + byte[] outBytes = new byte[characters.Length]; + for (int i = 0; i < characters.Length; i++) + { + outBytes[i] = (byte)Math.Min((ushort)255, characters[i]); + } + return outBytes; + } + + public static byte[] ToByteArray(uint[] values) + { + byte[] outBytes = new byte[4 * values.Length]; + for (int i = 0; i < values.Length; i++) + { + byte[] tBytes = BitConverter.GetBytes(values[i]); + tBytes.CopyTo(outBytes, i * 4); + } + return outBytes; + } + public static byte[] ToByteArray(ulong[] values) + { + byte[] outBytes = new byte[8 * values.Length]; + for (int i = 0; i < values.Length; i++) + { + byte[] tBytes = BitConverter.GetBytes(values[i]); + tBytes.CopyTo(outBytes, i * 8); + } + return outBytes; + } + + public static char[] ToCharArray(byte[] values, int start, int length) + { + char[] characters = new char[length]; + for (int i = 0; i < length; i++) + { + characters[i] = (char)values[start + i]; + } + return characters; + } + + public static char[] ToCharArray(byte[] values) + { + return ToCharArray(values, 0, values.Length); + } + + public static uint[] ToUintArray(byte[] values, int start, int length) + { + uint[] tUints = new uint[length / 4]; + for (int i = 0; i < length / 4; i++) + { + tUints[i] = BitConverter.ToUInt32(values, start + i * 4); + } + return tUints; + } + + public static ulong[] ToULongArray(byte[] values, int start, int length) + { + ulong[] tULongs = new ulong[length / 8]; + + for (int i = 0; i < length / 8; i++) + { + tULongs[i] = BitConverter.ToUInt64(values, start + i * 8); + } + return tULongs; + } + + public static bool VerifySize(byte[] source, int headerSize) + { + if (source.Length < headerSize) + { + return false; + } + return true; + } + + + public static byte[] ReadBytes(Stream s, int count) + { + byte[] bytes = new byte[count]; + s.Read(bytes, 0, count); + return bytes; + } + } + enum Classifications + { + Created, + Unclassified, + Ground, + Vegetation_Low, + Vegetation_Med, + Vegetation_High, + Building, + LowPoint, + r8, + Water, + Rail, + RoadSurface, + r12, + Wire_Guard, + Wire_Conductor, + TransmissionTower, + Wire_StructureConnector, + BridgeDeck, + HighNoise, + OverheadStructure, + IgnoredGround, + Snow, + TemporalExclusion, + r23, + r24, + r25, + r26, + r27, + r28, + r29, + r30, + r31, + r32, + r33, + r34, + r36, + r37, + r38, + r39, + r40, + r41, + r42, + r43, + r44, + r45, + r46, + r47, + r48, + r49, + r50, + r51, + r52, + r53, + r54, + r55, + r56, + r57, + r58, + r59, + r60, + r61, + r62, + r63, + u64, + u65, + u66, + u67, + u68, + u69, + u70 + } +} diff --git a/LASRead/LASFormat/FileHeader.cs b/LASRead/LASFormat/FileHeader.cs new file mode 100644 index 0000000..95f3488 --- /dev/null +++ b/LASRead/LASFormat/FileHeader.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Printing.IndexedProperties; +using System.Text; +using System.Windows.Markup; + +namespace LASRead.LASFormat +{ + class LASFormatDetail + { + /* Primative Types: + * (LAS = C#) + * char = byte + * uchar = char + * short = int16 + * ushort = uint16 + * long = int + * ulong = uint + * long long = int64 + * ulong long = uint64 + * float = float + * double = double + * string = 1-byte, ASCII chars, null terminated unless at max length (therefore no termination) + */ + + /* Public header: + * char[4] - signature + * ushort - file id + * ushort encoding + * ulong ?- guid 1 + * ushort ?- guid 2 + * ushort ?- guid 3 + * uchar[8] ?- guid 4 + * uchar + */ + + } + public class FileHeader + { + ushort headerSize; + // Follows the correct order of the header + char[] fileSignature; + ushort fileSourceID; + ushort globalEncoding; + // Optional Fields are filled with zero + uint GUIDData1 = 0; + ushort GUIDData2 = 0; + ushort GUIDData3 = 0; + byte[] GUIDData4 = new byte[] {0,0,0,0,0,0,0,0}; + byte versionMajor; + byte versionMinor; + char[] systemIdentifier; + char[] generatingSoftware; + ushort fileCreationDayOfYear; + ushort fileCreationYear; + uint dataOffset; + uint numberVLRs; // Variable Length Records + byte pointDataRecordFormat; + ushort pointDataRecordLength; + uint legacyNumberOfPointRecords; + uint[] legacyNumberOfPointByReturn; + double x_scaleFactor; + double y_scaleFactor; + double z_scaleFactor; + double x_offset; + double y_offset; + double z_offset; + double x_max; + double x_min; + double y_max; + double y_min; + double z_max; + double z_min; + ulong startOfWaveformDPR; // Data Packet Record + ulong startOfFirstExtendedVLR; + uint numberOfExtendedVLRs; + ulong numberPointRecords; + ulong[] numberPointsByReturn; + /// + /// Signature of the data. Should always be "LASF" + /// + public char[] FileSignature { get => fileSignature; set => fileSignature = value; } + /// + /// ID Associated with the data. Intended to differentiate between different sources + /// + public ushort FileSourceID { get => fileSourceID; set => fileSourceID = value; } + /// + /// Flags Indicating the formats of certain data
+ /// 0 : GPS Time Format (0 - Week time; 1 - standard GPS Time)
+ /// 1 : Waveforms included (depricated)
+ /// 2 : Waveforms in associated .wdp file (mutex with 1:)
+ /// 3 : Point returns are synthetic
+ /// 4 : Coordinate Reference System is WKT, else GeoTIFF
+ /// 5-15 : Reserved
+ ///
+ public ushort GlobalEncoding { get => globalEncoding; set => globalEncoding = value; } + public uint GUIDData11 { get => GUIDData1; set => GUIDData1 = value; } + public ushort GUIDData21 { get => GUIDData2; set => GUIDData2 = value; } + public ushort GUIDData31 { get => GUIDData3; set => GUIDData3 = value; } + public byte[] GUIDData41 { get => GUIDData4; set => GUIDData4 = value; } + public byte VersionMajor { get => versionMajor; set => versionMajor = value; } + public byte VersionMinor { get => versionMinor; set => versionMinor = value; } + /// + /// Identifies the hardware used to collect the data
+ /// Can be MERGE, MODIFICATION, EXTRACTION, TRANSFORMATION, OTHER, or a custom expression + ///
+ public char[] SystemIdentifier { get => systemIdentifier; set => systemIdentifier = value; } + /// + /// Identifies the software used to encode the data + /// + public char[] GeneratingSoftware { get => generatingSoftware; set => generatingSoftware = value; } + /// + /// The day of the year this file was created + /// + public ushort FileCreationDayOfYear { get => fileCreationDayOfYear; set => fileCreationDayOfYear = value; } + /// + /// The year this data was collected + /// + public ushort FileCreationYear { get => fileCreationYear; set => fileCreationYear = value; } + /// + /// The size of this header + /// + public ushort HeaderSize { get => headerSize; set => headerSize = value; } + /// + /// The offset of the first Point-Data-Record (PDR) + /// + public uint DataOffset { get => dataOffset; set => dataOffset = value; } + /// + /// The number of Variable Length Records (VLRs) present + /// + public uint NumberVLRs { get => numberVLRs; set => numberVLRs = value; } + /// + /// An integer representing the format of the PDR data.
+ /// See + ///
+ public byte PointDataRecordFormat { get => pointDataRecordFormat; set => pointDataRecordFormat = value; } + /// + /// The length of a PDR record + /// + public ushort PointDataRecordLength { get => pointDataRecordLength; set => pointDataRecordLength = value; } + /// + /// Number of point records for legacy compatibility (32 bits) + /// + public uint LegacyNumberOfPointRecords { get => legacyNumberOfPointRecords; set => legacyNumberOfPointRecords = value; } + /// + /// todo + /// + public uint[] LegacyNumberOfPointByReturn { get => legacyNumberOfPointByReturn; set => legacyNumberOfPointByReturn = value; } + /// + /// Scale factor of the X axis. Multiply by this value to get the true value + /// + public double X_scaleFactor { get => x_scaleFactor; set => x_scaleFactor = value; } + /// + /// Scale factor of the Y axis. Multiply by this value to get the true value + /// + public double Y_scaleFactor { get => y_scaleFactor; set => y_scaleFactor = value; } + /// + /// Scale factor of the Z axis. Multiply by this value to get the true value + /// + public double Z_scaleFactor { get => z_scaleFactor; set => z_scaleFactor = value; } + /// + /// Offset of the X axis.
+ /// Calculate Coordinates like so:
+ /// X_final = X * X_scaleFactor + X_offset + ///
+ public double X_offset { get => x_offset; set => x_offset = value; } + /// + /// Offset of the Y axis.
+ /// Calculate Coordinates like so:
+ /// Y_final = Y * Y_scaleFactor + Y_offset + ///
+ public double Y_offset { get => y_offset; set => y_offset = value; } + /// + /// Offset of the Z axis.
+ /// Calculate Coordinates like so:
+ /// Z_final = Z * Z_scaleFactor + Z_offset + ///
+ public double Z_offset { get => z_offset; set => z_offset = value; } + /// + /// Largest X value in the data + /// + public double X_max { get => x_max; set => x_max = value; } + /// + /// Smallest X value in the data + /// + public double X_min { get => x_min; set => x_min = value; } + /// + /// Largest Y value in the data + /// + public double Y_max { get => y_max; set => y_max = value; } + /// + /// Smallest Y value in the data + /// + public double Y_min { get => y_min; set => y_min = value; } + /// + /// Largest Z value in the data + /// + public double Z_max { get => z_max; set => z_max = value; } + /// + /// Smallest Z value in the data + /// + public double Z_min { get => z_min; set => z_min = value; } + /// + /// New in 1.4
+ /// Start of the Waveform Data Packet Records + ///
+ public ulong StartOfWaveformDPR { get => startOfWaveformDPR; set => startOfWaveformDPR = value; } + /// + /// New in 1.4
+ /// Start of the first Extended Variable Length Record (EVLR) + ///
+ public ulong StartOfFirstExtendedVLR { get => startOfFirstExtendedVLR; set => startOfFirstExtendedVLR = value; } + /// + /// New in 1.4
+ /// Number of Extended Variable Length Records + ///
+ public uint NumberOfExtendedVLRs { get => numberOfExtendedVLRs; set => numberOfExtendedVLRs = value; } + /// + /// New in 1.4
+ /// Number of point records (64 bits) + ///
+ public ulong NumberPointRecords { get => numberPointRecords; set => numberPointRecords = value; } + /// + /// New in 1.4
+ /// todo + ///
+ public ulong[] NumberPointsByReturn { get => numberPointsByReturn; set => numberPointsByReturn = value; } + + public byte[] GetAsByteArray() + { + byte[] endBytes = new byte[headerSize]; + DataHelpers.ToByteArray(fileSignature).CopyTo(endBytes,0); //4 + BitConverter.GetBytes(fileSourceID).CopyTo(endBytes, 4); // 2 + BitConverter.GetBytes(globalEncoding).CopyTo(endBytes, 6); // 2 + BitConverter.GetBytes(GUIDData1).CopyTo(endBytes, 8); // 4 + BitConverter.GetBytes(GUIDData2).CopyTo(endBytes, 12); // 2 + BitConverter.GetBytes(GUIDData3).CopyTo(endBytes, 14); // 2 + GUIDData4.CopyTo(endBytes, 16); // 8 + endBytes[24] = versionMajor; + endBytes[25] = versionMinor; + DataHelpers.ToByteArray(systemIdentifier).CopyTo(endBytes, 26); // 32 Bytes + DataHelpers.ToByteArray(GeneratingSoftware).CopyTo(endBytes, 58); // 32 Bytes + BitConverter.GetBytes(fileCreationDayOfYear).CopyTo(endBytes, 90); // 2 + BitConverter.GetBytes(fileCreationYear).CopyTo(endBytes, 92); // 2 + BitConverter.GetBytes(headerSize).CopyTo(endBytes, 94); // 2 + BitConverter.GetBytes(dataOffset).CopyTo(endBytes, 96); // 4 + BitConverter.GetBytes(numberVLRs).CopyTo(endBytes, 100); // 4 + endBytes[104] = pointDataRecordFormat; + BitConverter.GetBytes(pointDataRecordLength).CopyTo(endBytes, 105); // 2 + BitConverter.GetBytes(legacyNumberOfPointRecords).CopyTo(endBytes, 107); // 4 + DataHelpers.ToByteArray(LegacyNumberOfPointByReturn).CopyTo(endBytes, 111); // 20 bytes + BitConverter.GetBytes(X_scaleFactor).CopyTo(endBytes, 131); // 8 + BitConverter.GetBytes(Y_scaleFactor).CopyTo(endBytes, 139); // 8 + BitConverter.GetBytes(Z_scaleFactor).CopyTo(endBytes, 147); // 8 + BitConverter.GetBytes(X_offset).CopyTo(endBytes, 155); // 8 + BitConverter.GetBytes(Y_offset).CopyTo(endBytes, 163); // 8 + BitConverter.GetBytes(Z_offset).CopyTo(endBytes, 171); // 8 + BitConverter.GetBytes(X_max).CopyTo(endBytes, 179); // 8 + BitConverter.GetBytes(X_min).CopyTo(endBytes, 187); // 8 + BitConverter.GetBytes(Y_max).CopyTo(endBytes, 195); // 8 + BitConverter.GetBytes(Y_min).CopyTo(endBytes, 203); // 8 + BitConverter.GetBytes(Z_max).CopyTo(endBytes, 211); // 8 + BitConverter.GetBytes(Z_min).CopyTo(endBytes, 219); // 8 + if (VersionMajor >= 1 && VersionMinor >= 4) + { + BitConverter.GetBytes(startOfWaveformDPR).CopyTo(endBytes, 227); // 8 + BitConverter.GetBytes(StartOfFirstExtendedVLR).CopyTo(endBytes, 235); // 8 + BitConverter.GetBytes(NumberOfExtendedVLRs).CopyTo(endBytes, 243); // 4 + BitConverter.GetBytes(NumberPointRecords).CopyTo(endBytes, 247); // 8 + DataHelpers.ToByteArray(NumberPointsByReturn).CopyTo(endBytes, 255); // 120 Bytes + } + return endBytes; + } + /// + /// Reads the header from the provided source + /// + /// + /// The expected size of the header + public bool ReadHeader(Stream source) + { + source.Position = 94; + byte[] headerSizeBytes = new byte[2]; + source.Read(headerSizeBytes, 0, 2); + headerSize = BitConverter.ToUInt16(headerSizeBytes, 0); + if (headerSize < 375) + { + // Assert the version is less than 1.4, where records after z-min were added. + source.Position = 24; + byte major = (byte)source.ReadByte(); + byte minor = (byte)source.ReadByte(); + if (major >= 1 && minor > 3) + { + throw new InvalidDataException("Header is too small for data format >= v1.4"); + } + } + source.Position = 0; + + byte[] inputHeader = new byte[headerSize]; + source.Read(inputHeader, 0, HeaderSize); + + FileSignature = new char[] { (char)inputHeader[0], (char)inputHeader[1], (char)inputHeader[2], (char)inputHeader[3] }; + FileSourceID = BitConverter.ToUInt16(inputHeader, 4); + GlobalEncoding = BitConverter.ToUInt16(inputHeader, 6); + GUIDData11 = BitConverter.ToUInt32(inputHeader, 8); + GUIDData21 = BitConverter.ToUInt16(inputHeader, 12); + GUIDData31 = BitConverter.ToUInt16(inputHeader, 14); + GUIDData41 = new byte[] { inputHeader[16], inputHeader[17], inputHeader[18], inputHeader[19], inputHeader[20], inputHeader[21], inputHeader[22], inputHeader[23]}; + VersionMajor = inputHeader[24]; + VersionMinor = inputHeader[25]; + SystemIdentifier = DataHelpers.ToCharArray(inputHeader, 26, 32); + GeneratingSoftware = DataHelpers.ToCharArray(inputHeader, 58, 32); + FileCreationDayOfYear = BitConverter.ToUInt16(inputHeader, 90); + FileCreationYear = BitConverter.ToUInt16(inputHeader, 92); + // 94 is headerSize + DataOffset = BitConverter.ToUInt32(inputHeader, 96); + NumberVLRs = BitConverter.ToUInt32(inputHeader, 100); + PointDataRecordFormat = inputHeader[104]; + PointDataRecordLength = BitConverter.ToUInt16(inputHeader, 105); + LegacyNumberOfPointRecords = BitConverter.ToUInt32(inputHeader, 107); + LegacyNumberOfPointByReturn = DataHelpers.ToUintArray(inputHeader, 111, 20); + X_scaleFactor = BitConverter.ToDouble(inputHeader, 131); + Y_scaleFactor = BitConverter.ToDouble(inputHeader, 139); + Z_scaleFactor = BitConverter.ToDouble(inputHeader, 147); + X_offset = BitConverter.ToDouble(inputHeader, 155); + Y_offset = BitConverter.ToDouble(inputHeader, 163); + z_offset = BitConverter.ToDouble(inputHeader, 171); + X_max = BitConverter.ToDouble(inputHeader, 179); + X_min = BitConverter.ToDouble(inputHeader, 187); + Y_max = BitConverter.ToDouble(inputHeader, 195); + Y_min = BitConverter.ToDouble(inputHeader, 203); + Z_max = BitConverter.ToDouble(inputHeader, 211); + Z_min = BitConverter.ToDouble(inputHeader, 219); + if (versionMinor >= 4) + { + StartOfWaveformDPR = BitConverter.ToUInt64(inputHeader, 227); + StartOfFirstExtendedVLR = BitConverter.ToUInt64(inputHeader, 235); + NumberOfExtendedVLRs = BitConverter.ToUInt32(inputHeader, 243); + numberPointRecords = BitConverter.ToUInt64(inputHeader, 247); + NumberPointsByReturn = DataHelpers.ToULongArray(inputHeader, 255, 120); + } + return true; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("Signature: " + new string(FileSignature) + Environment.NewLine); + sb.Append("SourceID: " + FileSourceID.ToString() + Environment.NewLine); + sb.Append("Global Encoding: " + GlobalEncoding.ToString() + Environment.NewLine); + sb.Append("GUIDData1: " + GUIDData11.ToString() + Environment.NewLine); + sb.Append("GUIDData2: " + GUIDData21.ToString() + Environment.NewLine); + sb.Append("GUIDData3: " + GUIDData31.ToString() + Environment.NewLine); + sb.Append("GUIDData4: "); + foreach (byte data in GUIDData41) + { + if (data != 0) + { + sb.Append((char)data); + } + } + sb.Append(Environment.NewLine); + sb.Append("Version: " + VersionMajor.ToString() + "." + VersionMinor.ToString() + Environment.NewLine); + sb.Append("System Identifier: "); sb.Append(SystemIdentifier); sb.Append(Environment.NewLine); + sb.Append("Generating Software: "); sb.Append(GeneratingSoftware); sb.Append(Environment.NewLine); + DateTime creationDate = new DateTime(FileCreationYear, 1, 1); + creationDate = creationDate.AddDays(FileCreationDayOfYear); + sb.Append("Creation Date: " + creationDate.ToShortDateString() + Environment.NewLine); + sb.Append("Header Length: " + HeaderSize.ToString() + Environment.NewLine); + sb.Append("Data Offset: " + DataOffset.ToString() + Environment.NewLine); + sb.Append("Number VRLs: " + NumberVLRs.ToString() + Environment.NewLine); + sb.Append("PointDataFormat: " + PointDataRecordFormat.ToString() + Environment.NewLine); + sb.Append("PointDataLength: " + PointDataRecordLength.ToString() + Environment.NewLine); + sb.Append("Legacy #PDRs: " + LegacyNumberOfPointRecords.ToString() + Environment.NewLine); + sb.Append("Legacy #PDR returns: "); + foreach (uint legacyReturn in LegacyNumberOfPointByReturn) + { + sb.Append(legacyReturn); + sb.Append(" "); + } + sb.Append(Environment.NewLine); + sb.Append("X Scale Factor: " + X_scaleFactor.ToString() + Environment.NewLine); + sb.Append("Y Scale Factor: " + Y_scaleFactor.ToString() + Environment.NewLine); + sb.Append("Z Scale Factor: " + Z_scaleFactor.ToString() + Environment.NewLine); + sb.Append("X Offset: " + X_offset.ToString() + Environment.NewLine); + sb.Append("Y Offset: " + Y_offset.ToString() + Environment.NewLine); + sb.Append("Z Offset: " + Z_offset.ToString() + Environment.NewLine); + sb.Append("X Max: " + X_max.ToString() + Environment.NewLine); + sb.Append("X Min: " + X_min.ToString() + Environment.NewLine); + sb.Append("Y Max: " + Y_max.ToString() + Environment.NewLine); + sb.Append("Y Min: " + Y_min.ToString() + Environment.NewLine); + sb.Append("Z Max: " + Z_max.ToString() + Environment.NewLine); + sb.Append("Z Min: " + Z_min.ToString() + Environment.NewLine); + if (versionMinor >= 4) + { + sb.Append("Waveform Start: " + StartOfWaveformDPR.ToString() + Environment.NewLine); + sb.Append("Extended VLR Start: " + StartOfFirstExtendedVLR.ToString() + Environment.NewLine); + sb.Append("Number eVLRs: " + NumberOfExtendedVLRs.ToString() + Environment.NewLine); + sb.Append("Number Point Records: " + NumberPointRecords.ToString() + Environment.NewLine); + sb.Append("NPR by Return: " + NumberPointsByReturn.ToString() + Environment.NewLine); + } + return sb.ToString(); + } + } +} diff --git a/LASRead/LASFormat/PDRCollection.cs b/LASRead/LASFormat/PDRCollection.cs new file mode 100644 index 0000000..01fc794 --- /dev/null +++ b/LASRead/LASFormat/PDRCollection.cs @@ -0,0 +1,114 @@ +using LASRead.LASFormat; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace LASRead.LASFormat +{ + public class PDRCollection : IEnumerable where T : IPointDataRecord + { + readonly PDREnumerator enumerator; + public PDRCollection(FileHeader fileHeader, Stream baseStream, IPointDataRecord initialRecord) + { + enumerator = new PDREnumerator(fileHeader, baseStream, initialRecord); + } + public PDREnumerator GetEnumerator() + { + return enumerator; + } + + public string TEst() + { + return "sted"; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return enumerator; + } + } + /// + /// Enumerates through the points in a .las file + /// + public class PDREnumerator: IEnumerator where T : IPointDataRecord + { + readonly FileHeader baseHeader; + readonly Stream baseStream; + // readonly int headerSize; + //ulong endOfData; + ushort dataLength; + + public PDREnumerator(FileHeader fileHeader, Stream baseStream, IPointDataRecord initialRecord) + { + Position = 0; + baseHeader = fileHeader; + this.baseStream = baseStream; + //endOfData = (fileHeader.StartOfFirstExtendedVLR == 0 ? (ulong)baseStream.Length : fileHeader.StartOfFirstExtendedVLR); + Current = (T)initialRecord; + dataLength = fileHeader.PointDataRecordLength; + + } + // Stream which the data is sourced from + + + // Current payload + + /// + /// Gets the type of the payload + /// + /// + public Type GetPDRFormat() + { + return Current.GetType(); + } + + public int EstimateRemainder() + { + return (int)((baseStream.Length - Position) / baseHeader.PointDataRecordLength); + } + + object IEnumerator.Current => Current; + + public T Current { get; private set; } + + public long Position { get; private set; } + + + public bool MoveNext() + { + if (Position + dataLength >= baseStream.Length) + { + return false; + } + else + { + long lastPos = baseStream.Position; + if (baseStream.Position != Position) + { + baseStream.Position = Position; + } + Position += dataLength; + IPointDataRecord newRecord = (IPointDataRecord)Activator.CreateInstance(Current.GetType()); + byte[] nextData = new byte[dataLength]; + baseStream.Position = Position; + baseStream.Read(nextData, 0, dataLength); + bool result = newRecord.ReadPoint(nextData); + Current = (T)newRecord; + baseStream.Position = lastPos; + return result; + } + } + + public void Reset() + { + throw new NotImplementedException(); + } + + public void Dispose() + { + //throw new NotImplementedException(); + } + } +} diff --git a/LASRead/LASFormat/PointDataRecord.cs b/LASRead/LASFormat/PointDataRecord.cs new file mode 100644 index 0000000..353a0ed --- /dev/null +++ b/LASRead/LASFormat/PointDataRecord.cs @@ -0,0 +1,714 @@ +using System; +using System.Collections; +using System.Text; + +namespace LASRead.LASFormat +{ + /// + /// LAS Data Payloads are in the Point Data Record (PDR) format
+ /// PDR 0-5 share the same basic substructure (which is inherited from this interface,
+ /// PDR 6-10 share a slightly different substructure (more flags) + ///
+ public interface IPointDataRecord + { + int X { get; set; } + int Y { get; set; } + int Z { get; set; } + + Nullable Intensity { get; set; } + byte ReturnNumberFlag_value { get; set; } + byte NumberOfReturnsFlag_value { get; set; } + byte ScanDirectionFlag_value { get; set; } + byte EdgeOfFlightLineFlag_value { get; set; } + byte Classification { get; set; } + sbyte ScanAngleRank { get; set; } + + Nullable UserData { get; set; } + ushort PointSourceID { get; set; } + bool ReadPoint(byte[] data); + + /// + /// Reads the flags from the supplied byte + /// + /// + /// + bool ReadFlag(Tuple source); + /// + /// Generates a new payload object from the supplied data bytes + /// + /// Well-formed byte data (i.e. Read from file) + /// A new payload object t + IPointDataRecord ParsePoint(byte[] data); + + byte[] MergeFlags(); + + byte[] GetAsByteArray(); + } + + class PDR0 : IPointDataRecord + { + int x; + int y; + int z; + ushort? intensity; + byte returnNumberFlag_value; + byte numberOfReturnsFlag_value; + byte scanDirectionFlag_value; + byte edgeOfFlightLineFlag_value; + byte classification; + sbyte scanAngleRank; + byte? userData; + ushort pointSourceID; + + public static readonly int headerSize = 20; + + public int X { get => x; set => x = value; } + public int Y { get => y; set => y = value; } + public int Z { get => z; set => z = value; } + public ushort? Intensity { get => intensity; set => intensity = value; } + public byte ReturnNumberFlag_value { get => returnNumberFlag_value; set => returnNumberFlag_value = value; } + public byte NumberOfReturnsFlag_value { get => numberOfReturnsFlag_value; set => numberOfReturnsFlag_value = value; } + public byte ScanDirectionFlag_value { get => scanDirectionFlag_value; set => scanDirectionFlag_value = value; } + public byte EdgeOfFlightLineFlag_value { get => edgeOfFlightLineFlag_value; set => edgeOfFlightLineFlag_value = value; } + public byte Classification { get => classification; set => classification = value; } + public sbyte ScanAngleRank { get => scanAngleRank; set => scanAngleRank = value; } + public byte? UserData { get => userData; set => userData = value; } + public ushort PointSourceID { get => pointSourceID; set => pointSourceID = value; } + + public virtual bool ReadFlag(Tuple source) + { + // Note that typical Windows environments should be Little Endian; matching the expected data format. + // This means for the number 3, + // 7-6-5-4 3-2-1-0 + // 0 0 0 0 0 0 1 1 + // So return number flag = 2-1-0 (011) = 3 + const byte full = 255; + ReturnNumberFlag_value = (byte)(source.Item1 & (full >> 5)); // Right-shift mask by 5 to get only the first 3 bits + NumberOfReturnsFlag_value = (byte)((source.Item1 >> 3) & (full >> 5)); // Right shift by 3, and & with 3 to get the returns + ScanDirectionFlag_value = (byte)((source.Item1 >> 6) & (full >> 7)); + EdgeOfFlightLineFlag_value = (byte)(source.Item1 >> 7); + + /* Big Endian + ReturnNumberFlag_value = (byte)((source.Item1 >> 5)& (full >> 5)); // Right-shift by 5 to get only the first 3 bits + NumberOfReturnsFlag_value = (byte)((source.Item1 >> 2) & (full >> 5)); // Right shift by 3, and & with 3 to get the returns + ScanDirectionFlag_value = (byte)((source.Item1 >> 1) & (full >> 7)); + EdgeOfFlightLineFlag_value = (byte)(source.Item1 & (full >> 7)); */ + return true; + } + + public virtual bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + x = BitConverter.ToInt32(data, 0); + y = BitConverter.ToInt32(data, 4); + z = BitConverter.ToInt32(data, 8); + intensity = BitConverter.ToUInt16(data, 12); + ReadFlag(Tuple.Create(data[14], (byte)0)); + classification = data[15]; + scanAngleRank = (sbyte)data[16]; + userData = data[17]; + pointSourceID = BitConverter.ToUInt16(data, 18); + return true; + } + else return false; + + } + + public static bool VerifySize(byte[] source, int headerSize) + { + if (source.Length < headerSize) + { + return false; + } + return true; + } + + public virtual IPointDataRecord ParsePoint(byte[] data) + { + PDR0 newPoint = new PDR0(); + newPoint.ReadPoint(data); + return newPoint; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(string.Format("Point: {0}, {1}, {2} {3}", X, Y, Z, Environment.NewLine)); + sb.Append("Intensity: " + Intensity.ToString() + Environment.NewLine); + sb.Append("Return Number: " + ReturnNumberFlag_value.ToString() + Environment.NewLine); + sb.Append("Number of Returns: " + NumberOfReturnsFlag_value.ToString() + Environment.NewLine); + sb.Append("Scan Direction: " + (returnNumberFlag_value == 0 ? "+" : "-") + Environment.NewLine); + sb.Append("Edge of Flight Line: " + (returnNumberFlag_value == 0 ? "no" : "yes") + Environment.NewLine); + sb.Append("Classification: " + ((Classifications)Classification) + Environment.NewLine); + sb.Append("Scan Angle Rank: " + ScanAngleRank.ToString() + Environment.NewLine); + sb.Append("User Data: " + (userData == 0 ? "no" : "yes") + Environment.NewLine); + sb.Append("Point Data Source: " + PointSourceID.ToString() + Environment.NewLine); + + return sb.ToString(); + } + + public byte[] MergeFlags() + { + int result = EdgeOfFlightLineFlag_value; + result |= ReturnNumberFlag_value << 5; // Right-shift mask by 5 to get only the first 3 bits + result |= NumberOfReturnsFlag_value << 3; // Right shift by 3, and & with 3 to get the returns + result |= ScanDirectionFlag_value << 2; + byte t = (byte)(result & 255); + return new byte[] { t }; + } + + public virtual byte[] GetAsByteArray() + { + byte[] result = new byte[headerSize]; + BitConverter.GetBytes(X).CopyTo(result, 0); + BitConverter.GetBytes(Y).CopyTo(result, 4); + BitConverter.GetBytes(Y).CopyTo(result, 8); + BitConverter.GetBytes(Intensity ?? 0).CopyTo(result, 12); + MergeFlags().CopyTo(result, 14); + result[15] = Classification; + result[16] = (byte)scanAngleRank; + result[17] = userData ?? 0; + BitConverter.GetBytes(PointSourceID).CopyTo(result, 18); + return result; + } + } + class PDR1 : PDR0 + { + double GPSTime; + new public static readonly int headerSize = 28; + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + GPSTime = BitConverter.ToDouble(data, 20); + return true; + } + else return false; + + } + public override IPointDataRecord ParsePoint(byte[] data) + { + PDR1 newPoint = new PDR1(); + newPoint.ReadPoint(data); + return newPoint; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append("GPS Time: " + GPSTime.ToString() + Environment.NewLine); + return sb.ToString(); + } + + public override byte[] GetAsByteArray() + { + byte[] result = new byte[headerSize]; + base.GetAsByteArray().CopyTo(result, 0); + BitConverter.GetBytes(GPSTime).CopyTo(result, 20); + return result; + } + } + class PDR2 : PDR0 + { + ushort red; + ushort green; + ushort blue; + new public static readonly int headerSize = 26; + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + red = BitConverter.ToUInt16(data, 20); + green = BitConverter.ToUInt16(data, 22); + blue = BitConverter.ToUInt16(data, 24); + return true; + } + else return false; + + } + public override IPointDataRecord ParsePoint(byte[] data) + { + PDR2 newPoint = new PDR2(); + newPoint.ReadPoint(data); + return newPoint; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append(string.Format("RGB: {0} {1} {2} {3}", red, green, blue, Environment.NewLine)); + return sb.ToString(); + } + public override byte[] GetAsByteArray() + { + byte[] result = new byte[headerSize]; + base.GetAsByteArray().CopyTo(result, 0); + BitConverter.GetBytes(red).CopyTo(result, 20); + BitConverter.GetBytes(green).CopyTo(result, 22); + BitConverter.GetBytes(blue).CopyTo(result, 24); + return result; + } + } + class PDR3 : PDR1 + { + ushort red; + ushort green; + ushort blue; + new public static readonly int headerSize = 34; + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + red = BitConverter.ToUInt16(data, 28); + green = BitConverter.ToUInt16(data, 30); + blue = BitConverter.ToUInt16(data, 32); + return true; + } + else return false; + + } + public override IPointDataRecord ParsePoint(byte[] data) + { + PDR3 newPoint = new PDR3(); + newPoint.ReadPoint(data); + return newPoint; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append(string.Format("RGB: {0} {1} {2} {3}", red, green, blue, Environment.NewLine)); + return sb.ToString(); + } + public override byte[] GetAsByteArray() + { + byte[] result = new byte[headerSize]; + base.GetAsByteArray().CopyTo(result, 0); + BitConverter.GetBytes(red).CopyTo(result, 28); + BitConverter.GetBytes(green).CopyTo(result, 30); + BitConverter.GetBytes(blue).CopyTo(result, 32); + return result; + } + } + + class PDR4 : PDR1 + { + byte wavePacketDescriptorIndex; + ulong WaveformOffset; + uint WaveformSize; + float returnPointWaveform; + float dx; + float dy; + float dz; + + new public static readonly int headerSize = 57; + + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + wavePacketDescriptorIndex = data[28]; + WaveformOffset = BitConverter.ToUInt64(data, 29); + WaveformSize = BitConverter.ToUInt32(data, 37); + returnPointWaveform = BitConverter.ToSingle(data, 41); + dx = BitConverter.ToSingle(data, 45); + dy = BitConverter.ToSingle(data, 49); + dz = BitConverter.ToSingle(data, 53); + return true; + } + else return false; + + } + public override IPointDataRecord ParsePoint(byte[] data) + { + PDR4 newPoint = new PDR4(); + newPoint.ReadPoint(data); + return newPoint; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append("WavePacket Index: " + wavePacketDescriptorIndex.ToString() + Environment.NewLine); + sb.Append("Waveform Offset: " + WaveformOffset.ToString() + Environment.NewLine); + sb.Append("Waveform Size: " + WaveformSize.ToString() + Environment.NewLine); + sb.Append("Return Point Waveform: " + returnPointWaveform.ToString() + Environment.NewLine); + sb.Append(string.Format("Delta Pos: dx={0} dy={1} dz={2} {3}", dx, dy, dz, Environment.NewLine)); + return sb.ToString(); + } + + public override byte[] GetAsByteArray() + { + byte[] result = new byte[headerSize]; + base.GetAsByteArray().CopyTo(result, 0); + result[28] = wavePacketDescriptorIndex; + BitConverter.GetBytes(WaveformOffset).CopyTo(result, 29); + BitConverter.GetBytes(WaveformSize).CopyTo(result, 37); + BitConverter.GetBytes(returnPointWaveform).CopyTo(result, 41); + BitConverter.GetBytes(dx).CopyTo(result, 45); + BitConverter.GetBytes(dy).CopyTo(result, 49); + BitConverter.GetBytes(dz).CopyTo(result, 53); + return result; + } + } + class PDR5 : PDR3 + { + byte wavePacketDescriptorIndex; + ulong WaveformOffset; + uint WaveformSize; + float returnPointWaveform; + float dx; + float dy; + float dz; + + new public static readonly int headerSize = 63; + + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + wavePacketDescriptorIndex = data[34]; + WaveformOffset = BitConverter.ToUInt64(data, 35); + WaveformSize = BitConverter.ToUInt32(data, 43); + returnPointWaveform = BitConverter.ToSingle(data, 47); + dx = BitConverter.ToSingle(data, 51); + dy = BitConverter.ToSingle(data, 55); + dz = BitConverter.ToSingle(data, 59); + return true; + } + else return false; + + } + public static new IPointDataRecord ParsePoint(byte[] data) + { + PDR5 newPoint = new PDR5(); + newPoint.ReadPoint(data); + return newPoint; + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append("WavePacket Index: " + wavePacketDescriptorIndex.ToString() + Environment.NewLine); + sb.Append("Waveform Offset: " + WaveformOffset.ToString() + Environment.NewLine); + sb.Append("Waveform Size: " + WaveformSize.ToString() + Environment.NewLine); + sb.Append("Return Point Waveform: " + returnPointWaveform.ToString() + Environment.NewLine); + sb.Append(string.Format("Delta Pos: dx={0} dy={1} dz={2} {3}", dx, dy, dz, Environment.NewLine)); + return sb.ToString(); + } + public override byte[] GetAsByteArray() + { + byte[] result = new byte[headerSize]; + base.GetAsByteArray().CopyTo(result, 0); + result[34] = wavePacketDescriptorIndex; + BitConverter.GetBytes(WaveformOffset).CopyTo(result, 35); + BitConverter.GetBytes(WaveformSize).CopyTo(result, 43); + BitConverter.GetBytes(returnPointWaveform).CopyTo(result, 47); + BitConverter.GetBytes(dx).CopyTo(result, 51); + BitConverter.GetBytes(dy).CopyTo(result, 55); + BitConverter.GetBytes(dz).CopyTo(result, 59); + return result; + } + } + + class PDR6 : IPointDataRecord + { + int x; + int y; + int z; + ushort? intensity; + byte returnNumberFlag_value; // NB: 4 bits here + byte numberOfReturnsFlag_value; // 4 + byte classificationFlag_value; // 4 + byte scannerChannelFlag_value; // 2 + byte scanDirectionFlag_value; // 1 + byte edgeOfFlightLineFlag_value; // 1 + byte classification; + short scanAngleRank; + byte? userData; + ushort pointSourceID; + double GPSTime; + + public static readonly int headerSize = 30; + // Inherited members + public int X { get => x; set => x = value; } + public int Y { get => y; set => y = value; } + public int Z { get => z; set => z = value; } + public ushort? Intensity { get => intensity; set => intensity = value; } + public byte ReturnNumberFlag_value { get => returnNumberFlag_value; set => returnNumberFlag_value = value; } + public byte NumberOfReturnsFlag_value { get => numberOfReturnsFlag_value; set => numberOfReturnsFlag_value = value; } + public byte ScanDirectionFlag_value { get => scanDirectionFlag_value; set => scanDirectionFlag_value = value; } + public byte EdgeOfFlightLineFlag_value { get => edgeOfFlightLineFlag_value; set => edgeOfFlightLineFlag_value = value; } + public byte Classification { get => classification; set => classification = value; } + public short ScanAngleRank { get => scanAngleRank; set => scanAngleRank = value; } + public byte? UserData { get => userData; set => userData = value; } + public ushort PointSourceID { get => pointSourceID; set => pointSourceID = value; } + // Local members + public byte ClassificationFlag_value { get => classificationFlag_value; set => classificationFlag_value = value; } + public byte ScannerChannelFlag_value { get => scannerChannelFlag_value; set => scannerChannelFlag_value = value; } + public double GPSTime1 { get => GPSTime; set => GPSTime = value; } + + sbyte IPointDataRecord.ScanAngleRank { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public bool ReadFlag(Tuple source) + { + const byte full = 255; + + ReturnNumberFlag_value = (byte)(source.Item1 & (full << 4)); + NumberOfReturnsFlag_value = (byte)((source.Item1 >> 4) & (full << 4)); + ClassificationFlag_value = (byte)(source.Item2 & (full << 4)); + ScannerChannelFlag_value = (byte)((source.Item2 >> 4) & (full << 2)); + ScanDirectionFlag_value = (byte)((source.Item2 >> 6) & (full << 1)); + EdgeOfFlightLineFlag_value = (byte)(source.Item2 >> 7); + return true; + } + + public virtual bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + x = BitConverter.ToInt32(data, 0); + y = BitConverter.ToInt32(data, 4); + z = BitConverter.ToInt32(data, 8); + intensity = BitConverter.ToUInt16(data, 12); + ReadFlag(Tuple.Create(data[14], data[15])); + classification = data[16]; + userData = data[17]; + scanAngleRank = BitConverter.ToInt16(data, 18); + pointSourceID = BitConverter.ToUInt16(data, 20); + return true; + } + else return false; + } + public virtual IPointDataRecord ParsePoint(byte[] data) + { + PDR6 newPoint = new PDR6(); + newPoint.ReadPoint(data); + return newPoint; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(string.Format("Point: {0}, {1}, {2} {3}", X, Y, Z, Environment.NewLine)); + sb.Append("Intensity: " + Intensity.ToString() + Environment.NewLine); + sb.Append("Return Number: " + ReturnNumberFlag_value.ToString() + Environment.NewLine); + sb.Append("Number of Returns: " + NumberOfReturnsFlag_value.ToString() + Environment.NewLine); + sb.Append("Classification Value: " + ClassificationFlag_value + Environment.NewLine); + sb.Append("Scanner Channel Value: " + ScannerChannelFlag_value + Environment.NewLine); + sb.Append("Scan Direction: " + (returnNumberFlag_value == 0 ? "+" : "-") + Environment.NewLine); + sb.Append("Edge of Flight Line: " + (returnNumberFlag_value == 0 ? "no" : "yes") + Environment.NewLine); + sb.Append("Classification: " + ((Classifications)Classification) + Environment.NewLine); + sb.Append("Scan Angle Rank: " + ScanAngleRank.ToString() + Environment.NewLine); + sb.Append("User Data: " + (userData == 0 ? "no" : "yes") + Environment.NewLine); + sb.Append("Point Data Source: " + PointSourceID.ToString() + Environment.NewLine); + sb.Append("GPS Time: " + GPSTime1.ToString() + Environment.NewLine); + return sb.ToString(); + } + + public byte[] MergeFlags() + { + int result = EdgeOfFlightLineFlag_value; + result |= ReturnNumberFlag_value << 5; // Right-shift mask by 5 to get only the first 3 bits + result |= NumberOfReturnsFlag_value << 3; // Right shift by 3, and & with 3 to get the returns + result |= ScanDirectionFlag_value << 2; + byte t = (byte)(result & 255); + throw new NotImplementedException(); + return new byte[] { t }; + } + + public virtual byte[] GetAsByteArray() + { + byte[] result = new byte[headerSize]; + BitConverter.GetBytes(X).CopyTo(result, 0); + BitConverter.GetBytes(Y).CopyTo(result, 4); + BitConverter.GetBytes(Y).CopyTo(result, 8); + BitConverter.GetBytes(Intensity ?? 0).CopyTo(result, 12); + MergeFlags().CopyTo(result, 14); + result[15] = Classification; + result[16] = (byte)scanAngleRank; + result[17] = userData ?? 0; + BitConverter.GetBytes(PointSourceID).CopyTo(result, 18); + return result; + } + } + + class PDR7 : PDR6 + { + ushort red; + ushort green; + ushort blue; + + new public static readonly int headerSize = 36; + + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + red = BitConverter.ToUInt16(data, 30); + green = BitConverter.ToUInt16(data, 32); + blue = BitConverter.ToUInt16(data, 34); + + return true; + } + else return false; + + } + public override IPointDataRecord ParsePoint(byte[] data) + { + PDR7 newPoint = new PDR7(); + newPoint.ReadPoint(data); + return newPoint; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append(string.Format("RGB: {0} {1} {2} {3}", red, green, blue, Environment.NewLine)); + return sb.ToString(); + } + } + class PDR8 : PDR7 + { + ushort nIR; + + new public static readonly int headerSize = 38; + + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + nIR = BitConverter.ToUInt16(data, 36); + + return true; + } + else return false; + + } + public override IPointDataRecord ParsePoint(byte[] data) + { + PDR8 newPoint = new PDR8(); + newPoint.ReadPoint(data); + return newPoint; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append("Infrared: " + nIR.ToString() + Environment.NewLine); + return sb.ToString(); + } + } + + class PDR9 : PDR6 + { + byte wavePacketDescriptorIndex; + ulong byteOffsetToWaveformData; + uint waveformPacketSize; + float returnPointWaveformLocation; + float dx; + float dy; + float dz; + + new public static readonly int headerSize = 59; + + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + wavePacketDescriptorIndex = data[38]; + byteOffsetToWaveformData = BitConverter.ToUInt64(data, 39); + waveformPacketSize = BitConverter.ToUInt32(data, 47); + returnPointWaveformLocation = BitConverter.ToSingle(data, 51); + dx = BitConverter.ToSingle(data, 55); + dy = BitConverter.ToSingle(data, 59); + dz = BitConverter.ToSingle(data, 63); + + return true; + } + else return false; + + } + public override IPointDataRecord ParsePoint(byte[] data) + { + PDR9 newPoint = new PDR9(); + newPoint.ReadPoint(data); + return newPoint; + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append("WavePacket Index: " + wavePacketDescriptorIndex.ToString() + Environment.NewLine); + sb.Append("Waveform Offset: " + byteOffsetToWaveformData.ToString() + Environment.NewLine); + sb.Append("Waveform Size: " + waveformPacketSize.ToString() + Environment.NewLine); + sb.Append("Return Point Waveform: " + returnPointWaveformLocation.ToString() + Environment.NewLine); + sb.Append(string.Format("Delta Pos: dx={0} dy={1} dz={2} {3}", dx, dy, dz, Environment.NewLine)); + return sb.ToString(); + } + } + + class PDR10 : PDR8 + { + byte wavePacketDescriptorIndex; + ulong byteOffsetToWaveformData; + uint waveformPacketSize; + float returnPointWaveformLocation; + float dx; + float dy; + float dz; + + new public static readonly int headerSize = 67; + + public override bool ReadPoint(byte[] data) + { + if (DataHelpers.VerifySize(data, headerSize)) + { + base.ReadPoint(data); + wavePacketDescriptorIndex = data[30]; + byteOffsetToWaveformData = BitConverter.ToUInt64(data, 31); + waveformPacketSize = BitConverter.ToUInt32(data, 39); + returnPointWaveformLocation = BitConverter.ToSingle(data, 43); + dx = BitConverter.ToSingle(data, 47); + dy = BitConverter.ToSingle(data, 51); + dz = BitConverter.ToSingle(data, 55); + + return true; + } + else return false; + + } + public override IPointDataRecord ParsePoint(byte[] data) + { + PDR10 newPoint = new PDR10(); + newPoint.ReadPoint(data); + return newPoint; + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(base.ToString()); + sb.Append("WavePacket Index: " + wavePacketDescriptorIndex.ToString() + Environment.NewLine); + sb.Append("Waveform Offset: " + byteOffsetToWaveformData.ToString() + Environment.NewLine); + sb.Append("Waveform Size: " + waveformPacketSize.ToString() + Environment.NewLine); + sb.Append("Return Point Waveform: " + returnPointWaveformLocation.ToString() + Environment.NewLine); + sb.Append(string.Format("Delta Pos: dx={0} dy={1} dz={2} {3}", dx, dy, dz, Environment.NewLine)); + return sb.ToString(); + } + } +} diff --git a/LASRead/LASFormat/Record.cs b/LASRead/LASFormat/Record.cs new file mode 100644 index 0000000..ea2f83b --- /dev/null +++ b/LASRead/LASFormat/Record.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace LASRead.LASFormat +{ + public class Record + { + readonly byte[] data; + public readonly IRecordPayloadHeader header; + readonly long position; + /// + /// Creates a new Variable Length Record + /// + /// The parsed header of the object + /// The position of this header in the source stream + public Record(IRecordPayloadHeader header, long position) + { + this.header = header; + data = new byte[header.RecordLengthAfterHeader]; + this.position = position; + } + /// + /// Reads the data payload associated with this header + /// + /// The source stream + /// A bool representing success or failed + public bool ReadData(Stream s) + { + long pos = s.Position; + s.Position = position + header.HeaderLength; + s.Read(data, 0, header.RecordLengthAfterHeader); + s.Position = pos; + return true; + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(header.ToString() + Environment.NewLine); + sb.Append(string.Format("Binary Data @{0}, of length {1}{2}", position + header.HeaderLength, header.HeaderLength, Environment.NewLine)); + return sb.ToString(); + } + } + + public interface IRecordPayloadHeader + { + int HeaderLength { get; } + ushort Reserved { get; set; } + byte[] Id { get; set; } + ushort RecordID { get; set; } + ushort RecordLengthAfterHeader { get; set; } + byte[] Description { get; set; } + IPointDataRecord Payload { get; set; } + + bool VerifyRecord(byte[] source); + + bool ReadRecords(byte[] source); + bool ReadRecords(Stream source); + + IRecordPayloadHeader ParseRecord(byte[] source); + IRecordPayloadHeader ParseRecord(Stream source); + } + + class VLRHeader : IRecordPayloadHeader + { + public const int headerLength = 54; + + + public ushort Reserved { get; set; } + public byte[] Id { get; set; } + public ushort RecordID { get; set; } + public ushort RecordLengthAfterHeader { get; set; } + public byte[] Description { get; set; } + public IPointDataRecord Payload { get; set; } + public int HeaderLength { get => headerLength; } + + public bool VerifyRecord(byte[] source) + { + if (BitConverter.ToUInt16(source, 0) != 0) + { + return false; + } + return true; + } + + public virtual bool ReadRecords(byte[] source) + { + if (source.Length != 54) + { + return false; + } + Reserved = BitConverter.ToUInt16(source, 0); + Id = new byte[16]; + Array.Copy(source, 2, Id, 0, 16); + RecordID = BitConverter.ToUInt16(source, 18); + RecordLengthAfterHeader = BitConverter.ToUInt16(source, 20); + Description = new byte[32]; + Array.Copy(source, 22, Description, 0, 32); + return true; + } + public bool ReadRecords(Stream source) + { + byte[] bytes = new byte[headerLength]; + source.Read(bytes, 0, headerLength); + ReadRecords(bytes); + return true; + } + + public virtual byte[] GetRecords() + { + byte[] bytes = new byte[54]; + bytes[0] = 0; + bytes[1] = 0; + Id.CopyTo(bytes, 2); + BitConverter.GetBytes(RecordID).CopyTo(bytes, 18); + BitConverter.GetBytes(RecordLengthAfterHeader).CopyTo(bytes, 20); + Description.CopyTo(bytes, 22); + return bytes; + } + + public virtual IRecordPayloadHeader ParseRecord(byte[] source) + { + VLRHeader newHeader = new VLRHeader(); + newHeader.ReadRecords(source); + return newHeader; + } + public virtual IRecordPayloadHeader ParseRecord(Stream source) + { + byte[] bytes = new byte[headerLength]; + source.Read(bytes, 0, headerLength); + return ParseRecord(bytes); + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("Reserved: " + Reserved.ToString() + Environment.NewLine); + sb.Append("User ID: " + new string(DataHelpers.ToCharArray(Id)) + Environment.NewLine); + sb.Append("Record ID: " + RecordID.ToString() + Environment.NewLine); + sb.Append("Record Length After Header: " + RecordID.ToString() + Environment.NewLine); + sb.Append("Description: " + new string(DataHelpers.ToCharArray(Description)) + Environment.NewLine); + return base.ToString(); + } + + + } + /// + /// Extended Variable Length Records differ from VLRs in the maximum data that can be saved, and header size + /// + class EVLRHeader : VLRHeader + { + public new const int headerLength = 60; + + new public ulong RecordLengthAfterHeader { get; set; } + new public int HeaderLength { get => 60; } + + public override bool ReadRecords(byte[] source) + { + if (source.Length != 60) + { + return false; + } + Reserved = BitConverter.ToUInt16(source, 0); + Id = new byte[16]; + Array.Copy(source, 2, Id, 0, 16); + RecordID = BitConverter.ToUInt16(source, 18); + RecordLengthAfterHeader = BitConverter.ToUInt64(source, 20); + Description = new byte[32]; + Array.Copy(source, 28, Description, 0, 32); + return true; + + } + + public override byte[] GetRecords() + { + byte[] bytes = new byte[60]; + bytes[0] = 0; + bytes[1] = 0; + Id.CopyTo(bytes, 2); + BitConverter.GetBytes(RecordID).CopyTo(bytes, 18); + BitConverter.GetBytes(RecordLengthAfterHeader).CopyTo(bytes, 20); + Description.CopyTo(bytes, 28); + return bytes; + } + + public override IRecordPayloadHeader ParseRecord(byte[] source) + { + EVLRHeader newHeader = new EVLRHeader(); + newHeader.ReadRecords(source); + return newHeader; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("Reserved: " + Reserved.ToString() + Environment.NewLine); + sb.Append("User ID: " + new string(DataHelpers.ToCharArray(Id)) + Environment.NewLine); + sb.Append("Record ID: " + RecordID.ToString() + Environment.NewLine); + sb.Append("Record Length After Header: " + RecordID.ToString() + Environment.NewLine); + sb.Append("Description: " + new string(DataHelpers.ToCharArray(Description)) + Environment.NewLine); + return base.ToString(); + } + } +} diff --git a/LASRead/LASFormat/RecordCollection.cs b/LASRead/LASFormat/RecordCollection.cs new file mode 100644 index 0000000..41a6c74 --- /dev/null +++ b/LASRead/LASFormat/RecordCollection.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace LASRead.LASFormat +{ + public class RecordCollection : IEnumerable + { + RecordEnumerator enumerator; + public RecordCollection(Stream source, ulong startPosition, uint maxItems, IRecordPayloadHeader firstHeader) + { + enumerator = new RecordEnumerator(source, startPosition, maxItems, firstHeader); + } + public IEnumerator GetEnumerator() + { + return enumerator; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return enumerator; + } + + } + public class RecordEnumerator : IEnumerator + { + Stream dataSource; + ulong streamStart; + ulong currentPosition; + uint currentCount; + uint maxCount; + + public RecordEnumerator(Stream source, ulong startPosition, uint maxItems, IRecordPayloadHeader firstHeader) + { + dataSource = source; + streamStart = startPosition; + currentPosition = startPosition; + currentCount = 0; + maxCount = maxItems; + Current = new Record(firstHeader, (long)startPosition); + } + + object IEnumerator.Current => Current; + + public Record Current { get; private set; } + + public void Dispose() + { + dataSource = null; + streamStart = 0; + currentPosition = 0; + currentCount = 0; + maxCount = 0; + } + + public bool MoveNext() + { + if (currentCount >= maxCount) + { + return false; + } + else + { + long oldPos = dataSource.Position; + currentPosition = (ulong)Current.header.HeaderLength + currentPosition; + dataSource.Position = (long)currentPosition; + Record nextRecord = new Record(Current.header.ParseRecord(dataSource), (long)currentPosition); + Current = nextRecord; + dataSource.Position = oldPos; + currentCount++; + return true; + } + } + + public void Reset() + { + throw new NotImplementedException(); + } + } +} diff --git a/LASRead/LASRead.csproj b/LASRead/LASRead.csproj new file mode 100644 index 0000000..6c68d0e --- /dev/null +++ b/LASRead/LASRead.csproj @@ -0,0 +1,9 @@ + + + + WinExe + netcoreapp3.1 + true + + + \ No newline at end of file diff --git a/LASRead/MainWindow.xaml b/LASRead/MainWindow.xaml new file mode 100644 index 0000000..c49727e --- /dev/null +++ b/LASRead/MainWindow.xaml @@ -0,0 +1,13 @@ + + +