diff --git a/LASRead/LASFile.cs b/LASRead/LASFile.cs index 1cf84ae..7d9c51b 100644 --- a/LASRead/LASFile.cs +++ b/LASRead/LASFile.cs @@ -69,14 +69,23 @@ namespace LASFormat // 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); + //long VLRSize = Header.DataOffset - Header.HeaderSize; + //VLRStream = GetOffsetStream(source, VLRSize); // Grab a starting VLR - VLRStart = Header.HeaderSize; + //VLRStart = Header.HeaderSize; VLRHeader initial = new VLRHeader(); - initial.ReadRecords(VLRStream); - vlrCollection = new RecordCollection(ref VLRStream, VLRStart, Header.NumberVLRs, initial); - + 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; @@ -97,13 +106,13 @@ namespace LASFormat 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); + //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(EVLRStream); - evlrCollection = new RecordCollection(ref EVLRStream, EVLRStart, Header.NumberOfExtendedVLRs, evlrInitial); + 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 diff --git a/LASRead/LASFormat/DataHelpers.cs b/LASRead/LASFormat/DataHelpers.cs index 9700bc8..e5f0437 100644 --- a/LASRead/LASFormat/DataHelpers.cs +++ b/LASRead/LASFormat/DataHelpers.cs @@ -49,6 +49,7 @@ namespace LASRead.LASFormat for (int i = 0; i < length; i++) { characters[i] = (char)values[start + i]; + if (characters[i] == 0) characters[i] = '-'; } return characters; } diff --git a/LASRead/LASFormat/FileHeader.cs b/LASRead/LASFormat/FileHeader.cs index 6f47131..cf875f7 100644 --- a/LASRead/LASFormat/FileHeader.cs +++ b/LASRead/LASFormat/FileHeader.cs @@ -172,7 +172,7 @@ namespace LASRead.LASFormat /// New in 1.4
/// Start of the first Extended Variable Length Record (EVLR) /// - public ulong StartOfFirstExtendedVLR { get; set; } + public long StartOfFirstExtendedVLR { get; set; } /// /// New in 1.4
/// Number of Extended Variable Length Records @@ -296,7 +296,7 @@ namespace LASRead.LASFormat if (VersionMinor >= 4) { StartOfWaveformDPR = BitConverter.ToUInt64(inputHeader, 227); - StartOfFirstExtendedVLR = BitConverter.ToUInt64(inputHeader, 235); + StartOfFirstExtendedVLR = (long)BitConverter.ToUInt64(inputHeader, 235); NumberOfExtendedVLRs = BitConverter.ToUInt32(inputHeader, 243); NumberPointRecords = BitConverter.ToUInt64(inputHeader, 247); NumberPointsByReturn = DataHelpers.ToULongArray(inputHeader, 255, 120); diff --git a/LASRead/LASFormat/GeoKeyTag.cs b/LASRead/LASFormat/GeoKeyTag.cs new file mode 100644 index 0000000..f432d12 --- /dev/null +++ b/LASRead/LASFormat/GeoKeyTag.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LASRead.LASFormat +{ + + class GeoKeyTag // Tag = 34735 + { + // Header, first 8 bytes + short KeyDirectoryVersion; + short KeyRevision; + short MinorRevision; + short NumberOfKeys; + + + + /// + /// GeoTIFF Tags that are included to contain the format (especially coordinate) data of the file + /// Due to the structure of a TIFF file, data are stored in Keys - these keys often further link to data in another tag + /// LiDAR capture data from a section of New Zealand contianed the following keys: + /// 1 1 0 11 -- ? + /// 1024 0 1 1 -- Type of coordinates (1 == Projected System) + /// 1025 0 1 1 -- Raster system used (1 == Pixel is area) + /// 1026 34737 66 0 -- ASCII Text about the file (Specifically, 'NZGD2000 / New Zealand Transverse Mercator 2000 + NZVD2016 height' - between 0-66 bytes of tag 34737) + /// 2049 34737 9 66 -- ASCII Geographic Citation Parameters, between 66-66+9 bytes ('NZGD2000') + /// 2054 0 1 9102 -- Angular Units (9102 = Degrees) + /// 3072 0 1 2193 -- Projected Coordinate System Type (2193 is not an expected value + /// 3076 0 1 9001 -- Projection Units (9001 = meters) + /// 4096 0 1 7839 -- Vertical Coordinate System (7839 = NZVD2016) + /// 4097 34737 16 75 -- ASCII Vertical System Name ('NZVD2016 height') + /// 4098 0 1 1169 -- Vertical Datum + /// 4099 0 1 9001 -- Vertical Units (meters) + /// + struct Key + { + public short KeyID; + public short TIFFTagLocation; + public short Count; + public short Value_Offset; + } + + + + /// + /// Linear unit specifications - used in key ID 3076 + /// + enum Units + { + Undefined = 0, + Linear_Meter = 9001, + Linear_Foot = 9002, + Linear_Foot_US_Survey = 9003, + Linear_Foot_Modified_American = 9004, + Linear_Foot_Clarke = 9005, + Linear_Foot_Indian = 9006, + Linear_Link = 9007, + Linear_Link_Benoit = 9008, + Linear_Link_Sears = 9009, + Linear_Chain_Benoit = 9010, + Linear_Chain_Sears = 9011, + Linear_Yard_Sears = 9012, + Linear_Yard_Indian = 9013, + Linear_Fathom = 9014, + Linear_Mile_International_Nautical = 9015, + + Angular_Radian = 9101, + Angular_Degree = 9102, + Angular_Arc_Minute = 9103, + Angular_Arc_Second = 9104, + Angular_Grad = 9105, + Angular_Gon = 9106, + Angular_DMS = 9107, + Angular_DMS_Hemisphere = 9108, + } + + /// + /// Vertical system codes - (key 4096) + /// + enum VerticalTypeCodes + { + VertCS_Airy_1830_ellipsoid = 5001, + VertCS_Airy_Modified_1849_ellipsoid = 5002, + VertCS_ANS_ellipsoid = 5003, + VertCS_Bessel_1841_ellipsoid = 5004, + VertCS_Bessel_Modified_ellipsoid = 5005, + VertCS_Bessel_Namibia_ellipsoid = 5006, + VertCS_Clarke_1858_ellipsoid = 5007, + VertCS_Clarke_1866_ellipsoid = 5008, + VertCS_Clarke_1880_Benoit_ellipsoid = 5010, + VertCS_Clarke_1880_IGN_ellipsoid = 5011, + VertCS_Clarke_1880_RGS_ellipsoid = 5012, + VertCS_Clarke_1880_Arc_ellipsoid = 5013, + VertCS_Clarke_1880_SGA_1922_ellipsoid = 5014, + VertCS_Everest_1830_1937_Adjustment_ellipsoid = 5015, + VertCS_Everest_1830_1967_Definition_ellipsoid = 5016, + VertCS_Everest_1830_1975_Definition_ellipsoid = 5017, + VertCS_Everest_1830_Modified_ellipsoid = 5018, + VertCS_GRS_1980_ellipsoid = 5019, + VertCS_Helmert_1906_ellipsoid = 5020, + VertCS_INS_ellipsoid = 5021, + VertCS_International_1924_ellipsoid = 5022, + VertCS_International_1967_ellipsoid = 5023, + VertCS_Krassowsky_1940_ellipsoid = 5024, + VertCS_NWL_9D_ellipsoid = 5025, + VertCS_NWL_10D_ellipsoid = 5026, + VertCS_Plessis_1817_ellipsoid = 5027, + VertCS_Struve_1860_ellipsoid = 5028, + VertCS_War_Office_ellipsoid = 5029, + VertCS_WGS_84_ellipsoid = 5030, + VertCS_GEM_10C_ellipsoid = 5031, + VertCS_OSU86F_ellipsoid = 5032, + VertCS_OSU91A_ellipsoid = 5033, + VertCS_Newlyn = 5101, + VertCS_North_American_Vertical_Datum_1929 = 5102, + VertCS_North_American_Vertical_Datum_1988 = 5103, + VertCS_Yellow_Sea_1956 = 5104, + VertCS_Baltic_Sea = 5105, + VertCS_Caspian_Sea = 5106, + + NZ_Vertical = 7839 + } + } +} diff --git a/LASRead/LASFormat/Record.cs b/LASRead/LASFormat/Record.cs index 55753b0..03c4e28 100644 --- a/LASRead/LASFormat/Record.cs +++ b/LASRead/LASFormat/Record.cs @@ -39,7 +39,13 @@ namespace LASRead.LASFormat 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)); - sb.Append(header.ToString()); + for (int i = 0; i < data.Length; i += 2) + { + if (i % 8 == 0) sb.Append("\n"); + sb.Append($"{BitConverter.ToUInt16(data[i..])} "); + + } + //sb.Append(Environment.NewLine + new string(DataHelpers.ToCharArray(data))); return sb.ToString(); } } @@ -139,7 +145,7 @@ namespace LASRead.LASFormat 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("Record Length After Header: " + RecordLengthAfterHeader.ToString() + Environment.NewLine); sb.Append("Description: " + new string(DataHelpers.ToCharArray(Description)) + Environment.NewLine); return sb.ToString(); } @@ -151,10 +157,10 @@ namespace LASRead.LASFormat ///
class EVLRHeader : VLRHeader { - public new const int headerLength = 60; + public const int headerLength = 60; new public ulong RecordLengthAfterHeader { get; set; } - new public int HeaderLength { get => 60; } + new public int HeaderLength { get => headerLength; } public override bool ReadRecords(byte[] source) { @@ -198,7 +204,7 @@ namespace LASRead.LASFormat 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("Record Length After Header: " + RecordLengthAfterHeader.ToString() + Environment.NewLine); sb.Append("Description: " + new string(DataHelpers.ToCharArray(Description)) + Environment.NewLine); return sb.ToString(); } diff --git a/LASRead/LASFormat/RecordCollection.cs b/LASRead/LASFormat/RecordCollection.cs index 8f237e8..c66fe4a 100644 --- a/LASRead/LASFormat/RecordCollection.cs +++ b/LASRead/LASFormat/RecordCollection.cs @@ -9,7 +9,7 @@ namespace LASRead.LASFormat public class RecordCollection : IEnumerable { RecordEnumerator enumerator; - public RecordCollection(ref Stream source, ulong startPosition, uint maxItems, IRecordPayloadHeader firstHeader) + public RecordCollection(ref Stream source, long startPosition, uint maxItems, IRecordPayloadHeader firstHeader) { enumerator = new RecordEnumerator(ref source, startPosition, maxItems, firstHeader); } @@ -27,19 +27,22 @@ namespace LASRead.LASFormat public class RecordEnumerator : IEnumerator { Stream dataSource; - ulong streamStart; - ulong currentPosition; + long streamStart; + long currentPosition; uint currentCount; uint maxCount; + IRecordPayloadHeader evalHeader; - public RecordEnumerator(ref Stream source, ulong startPosition, uint maxItems, IRecordPayloadHeader firstHeader) + public RecordEnumerator(ref Stream source, long startPosition, uint maxItems, IRecordPayloadHeader firstHeader) { - dataSource = source; - streamStart = startPosition; - currentPosition = startPosition; - currentCount = 0; - maxCount = maxItems; - Current = new Record(firstHeader, (long)startPosition); + // Recieves the stream reference, the start position of the first record header, the number of record headers + dataSource = source; // Stream + streamStart = startPosition; // start of the first header + currentPosition = startPosition; // The current position of the enumerator + currentCount = 0; // Total number of items enumerated + maxCount = maxItems; // Maximum number of items to enumerate + evalHeader = firstHeader; + //Current = new Record(firstHeader, startPosition); } object IEnumerator.Current => Current; @@ -64,9 +67,16 @@ namespace LASRead.LASFormat 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); + // Advance the position in the stream to the beginning of the next record, after the header and data + if (Current != null) + { + currentPosition += Current.header.HeaderLength + Current.header.RecordLengthAfterHeader; + } + + dataSource.Position = currentPosition; + evalHeader.ReadRecords(dataSource); + Record nextRecord = new Record(evalHeader, currentPosition); + nextRecord.ReadData(dataSource); Current = nextRecord; dataSource.Position = oldPos; currentCount++;