Skip to content

Commit 376a254

Browse files
authored
Merge pull request #12 from Kuoste/11-migrate-writepoint-native-split-laspoint
Replacing marshalling with native pointer structs for considerable speedup
2 parents 0ec2643 + f3fe1f0 commit 376a254

5 files changed

Lines changed: 379 additions & 174 deletions

File tree

LasZipNetStandard/LasPoint.cs

Lines changed: 21 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
using System;
2-
using System.Runtime.InteropServices;
1+
using System;
2+
using System.Runtime.CompilerServices;
33

44
namespace Kuoste.LasZipNetStandard
55
{
6-
76
public class LasPoint
87
{
98
public double X { get; set; }
@@ -23,24 +22,25 @@ public class LasPoint
2322
public ushort Green { get; set; }
2423
public ushort Blue { get; set; }
2524

26-
internal static void ConvertPoint(LasZipPointStruct pointStruct, ref LasPoint lasPoint)
27-
{
28-
lasPoint.X = pointStruct.X;
29-
lasPoint.Y = pointStruct.Y;
30-
lasPoint.Z = pointStruct.Z;
31-
lasPoint.Intensity = pointStruct.Intensity;
32-
lasPoint.ReturnNumber = pointStruct.ReturnNumber;
33-
lasPoint.NumberOfReturns = pointStruct.NumberOfReturns;
34-
lasPoint.ScanDirectionFlag = pointStruct.ScanDirectionFlag;
35-
lasPoint.EdgeOfFlightLine = pointStruct.EdgeOfFlightLine;
36-
lasPoint.Classification = pointStruct.Classification;
37-
lasPoint.ScanAngleRank = pointStruct.ScanAngleRank;
38-
lasPoint.UserData = pointStruct.UserData;
39-
lasPoint.PointSourceId = pointStruct.PointSourceID;
40-
lasPoint.GpsTime = pointStruct.GpsTime;
41-
lasPoint.Red = pointStruct.Rgb[0];
42-
lasPoint.Green = pointStruct.Rgb[1];
43-
lasPoint.Blue = pointStruct.Rgb[2];
25+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
26+
internal static unsafe void ConvertPoint(LasZipPointNative* pointStruct, ref LasPoint lasPoint, double scaleFactorX, double scaleFactorY, double scaleFactorZ, double offsetX, double offsetY, double offsetZ)
27+
{
28+
lasPoint.X = pointStruct->X * scaleFactorX + offsetX;
29+
lasPoint.Y = pointStruct->Y * scaleFactorY + offsetY;
30+
lasPoint.Z = pointStruct->Z * scaleFactorZ + offsetZ;
31+
lasPoint.Intensity = pointStruct->Intensity;
32+
lasPoint.ReturnNumber = pointStruct->ReturnNumber;
33+
lasPoint.NumberOfReturns = pointStruct->NumberOfReturns;
34+
lasPoint.ScanDirectionFlag = pointStruct->ScanDirectionFlag;
35+
lasPoint.EdgeOfFlightLine = pointStruct->EdgeOfFlightLine;
36+
lasPoint.Classification = pointStruct->Classification;
37+
lasPoint.ScanAngleRank = pointStruct->ScanAngleRank;
38+
lasPoint.UserData = pointStruct->UserData;
39+
lasPoint.PointSourceId = pointStruct->PointSourceID;
40+
lasPoint.GpsTime = pointStruct->GpsTime;
41+
lasPoint.Red = pointStruct->Rgb[0];
42+
lasPoint.Green = pointStruct->Rgb[1];
43+
lasPoint.Blue = pointStruct->Rgb[2];
4444
}
4545

4646
public override bool Equals(object obj)
@@ -74,138 +74,4 @@ public override int GetHashCode()
7474
return HashCode.Combine(GpsTime, Z);
7575
}
7676
}
77-
78-
[StructLayout(LayoutKind.Sequential)]
79-
public struct LasZipPointStruct
80-
{
81-
public int X;
82-
public int Y;
83-
public int Z;
84-
public ushort Intensity;
85-
private byte bitField1; // ReturnNumber : 3, NumberOfReturns : 3, ScanDirectionFlag : 1, EdgeOfFlightLine : 1
86-
private byte bitField2; // Classification : 5, SyntheticFlag : 1, KeypointFlag : 1, WithheldFlag : 1
87-
public sbyte ScanAngleRank;
88-
public byte UserData;
89-
public ushort PointSourceID;
90-
91-
// LAS 1.4 only
92-
public short ExtendedScanAngle;
93-
private byte bitField3; // ExtendedPointType : 2, ExtendedScannerChannel : 2, ExtendedClassificationFlags : 4
94-
public byte ExtendedClassification;
95-
private byte bitField4; // ExtendedReturnNumber : 4, ExtendedNumberOfReturns : 4
96-
97-
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
98-
public byte[] Dummy;
99-
100-
public double GpsTime;
101-
102-
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
103-
public ushort[] Rgb;
104-
105-
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 29)]
106-
public byte[] WavePacket;
107-
108-
public int NumExtraBytes;
109-
110-
public IntPtr ExtraBytes; // Use IntPtr instead of byte* for safe pointer handling
111-
112-
public byte ReturnNumber
113-
{
114-
get => (byte)(bitField1 & 0b00000111);
115-
set => bitField1 = (byte)((bitField1 & 0b11111000) | (value & 0b00000111));
116-
}
117-
118-
public byte NumberOfReturns
119-
{
120-
get => (byte)((bitField1 >> 3) & 0b00000111);
121-
set => bitField1 = (byte)((bitField1 & 0b11000111) | ((value & 0b00000111) << 3));
122-
}
123-
124-
public byte ScanDirectionFlag
125-
{
126-
get => (byte)((bitField1 >> 6) & 0b00000001);
127-
set => bitField1 = (byte)((bitField1 & 0b10111111) | ((value & 0b00000001) << 6));
128-
}
129-
130-
public byte EdgeOfFlightLine
131-
{
132-
get => (byte)((bitField1 >> 7) & 0b00000001);
133-
set => bitField1 = (byte)((bitField1 & 0b01111111) | ((value & 0b00000001) << 7));
134-
}
135-
136-
public byte Classification
137-
{
138-
get => (byte)(bitField2 & 0b00011111);
139-
set => bitField2 = (byte)((bitField2 & 0b11100000) | (value & 0b00011111));
140-
}
141-
142-
public byte SyntheticFlag
143-
{
144-
get => (byte)((bitField2 >> 5) & 0b00000001);
145-
set => bitField2 = (byte)((bitField2 & 0b11011111) | ((value & 0b00000001) << 5));
146-
}
147-
148-
public byte KeypointFlag
149-
{
150-
get => (byte)((bitField2 >> 6) & 0b00000001);
151-
set => bitField2 = (byte)((bitField2 & 0b10111111) | ((value & 0b00000001) << 6));
152-
}
153-
154-
public byte WithheldFlag
155-
{
156-
get => (byte)((bitField2 >> 7) & 0b00000001);
157-
set => bitField2 = (byte)((bitField2 & 0b01111111) | ((value & 0b00000001) << 7));
158-
}
159-
160-
public byte ExtendedPointType
161-
{
162-
get => (byte)(bitField3 & 0b00000011);
163-
set => bitField3 = (byte)((bitField3 & 0b11111100) | (value & 0b00000011));
164-
}
165-
166-
public byte ExtendedScannerChannel
167-
{
168-
get => (byte)((bitField3 >> 2) & 0b00000011);
169-
set => bitField3 = (byte)((bitField3 & 0b11110011) | ((value & 0b00000011) << 2));
170-
}
171-
172-
public byte ExtendedClassificationFlags
173-
{
174-
get => (byte)((bitField3 >> 4) & 0b00001111);
175-
set => bitField3 = (byte)((bitField3 & 0b00001111) | ((value & 0b00001111) << 4));
176-
}
177-
178-
public byte ExtendedReturnNumber
179-
{
180-
get => (byte)(bitField4 & 0b00001111);
181-
set => bitField4 = (byte)((bitField4 & 0b11110000) | (value & 0b00001111));
182-
}
183-
184-
public byte ExtendedNumberOfReturns
185-
{
186-
get => (byte)((bitField4 >> 4) & 0b00001111);
187-
set => bitField4 = (byte)((bitField4 & 0b00001111) | ((value & 0b00001111) << 4));
188-
}
189-
190-
internal static LasZipPointStruct ConvertPoint(LasPoint lasPoint)
191-
{
192-
return new LasZipPointStruct()
193-
{
194-
X = (int)(lasPoint.X + 0.5),
195-
Y = (int)(lasPoint.Y + 0.5),
196-
Z = (int)(lasPoint.Z + 0.5),
197-
Intensity = lasPoint.Intensity,
198-
ReturnNumber = lasPoint.ReturnNumber,
199-
NumberOfReturns = lasPoint.NumberOfReturns,
200-
ScanDirectionFlag = lasPoint.ScanDirectionFlag,
201-
EdgeOfFlightLine = lasPoint.EdgeOfFlightLine,
202-
Classification = lasPoint.Classification,
203-
ScanAngleRank = lasPoint.ScanAngleRank,
204-
UserData = lasPoint.UserData,
205-
PointSourceID = lasPoint.PointSourceId,
206-
GpsTime = lasPoint.GpsTime,
207-
Rgb = new ushort[] { lasPoint.Red, lasPoint.Green, lasPoint.Blue, 0 }
208-
};
209-
}
210-
}
21177
}

LasZipNetStandard/LasZip.cs

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
using System;
1+
using System;
2+
using System.Runtime.CompilerServices;
23
using System.Runtime.InteropServices;
34

45
namespace Kuoste.LasZipNetStandard
56
{
6-
public class LasZip
7+
public class LasZip : IDisposable
78
{
9+
private bool _disposed = false;
810
private const string _lasZipDll = "laszip64";
911

1012
private IntPtr _pLasZipReader;
@@ -135,7 +137,8 @@ public void SetWriterHeader(LaszipHeaderStruct header)
135137
/// </summary>
136138
/// <param name="point"> Point data is read to this reference. </param>
137139
/// <exception cref="Exception"> Reading failed. </exception>
138-
public void ReadPoint(ref LasPoint point)
140+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
141+
public unsafe void ReadPoint(ref LasPoint point)
139142
{
140143
// Get the memory location for the point in LasZip library
141144
if (_pPointReader == IntPtr.Zero)
@@ -152,22 +155,18 @@ public void ReadPoint(ref LasPoint point)
152155
throw new Exception("Failed to read LasZip point");
153156
}
154157

155-
// Copy point data from C++ struct to C# class
156-
LasPoint.ConvertPoint(Marshal.PtrToStructure<LasZipPointStruct>(_pPointReader), ref point);
157-
158-
// Scale the coordinates and add offsets. Not using the laszip_get_coordinates in order to make things faster.
159-
point.X = point.X * _headerReader.ScaleFactorX + _headerReader.OffsetX;
160-
point.Y = point.Y * _headerReader.ScaleFactorY + _headerReader.OffsetY;
161-
point.Z = point.Z * _headerReader.ScaleFactorZ + _headerReader.OffsetZ;
158+
LasPoint.ConvertPoint((LasZipPointNative*)_pPointReader, ref point,
159+
_headerReader.ScaleFactorX, _headerReader.ScaleFactorY, _headerReader.ScaleFactorZ,
160+
_headerReader.OffsetX, _headerReader.OffsetY, _headerReader.OffsetZ);
162161
}
163162

164163
/// <summary>
165164
/// Writes the point given as reference.
166165
/// </summary>
167-
/// <param name="point"> LasPoint to write. Note that the method changes the coordinates
168-
/// of the point by applying the Offsets and ScaleFactors. </param>
166+
/// <param name="point"> LasPoint to write. The coordinates are scaled and offset
167+
/// using the header values before writing. </param>
169168
/// <exception cref="Exception"> Writing failed. </exception>
170-
public void WritePoint(ref LasPoint point)
169+
public unsafe void WritePoint(ref LasPoint point)
171170
{
172171
// Get the memory location for the point in LasZip library
173172
if (_pPointWriter == IntPtr.Zero)
@@ -178,12 +177,9 @@ public void WritePoint(ref LasPoint point)
178177
}
179178
}
180179

181-
// Scale the coordinates and add offsets. Not using the laszip_set_coordinates in order to make things faster.
182-
point.X = (point.X - _headerWriter.OffsetX) / _headerWriter.ScaleFactorX;
183-
point.Y = (point.Y - _headerWriter.OffsetY) / _headerWriter.ScaleFactorY;
184-
point.Z = (point.Z - _headerWriter.OffsetZ) / _headerWriter.ScaleFactorZ;
185-
186-
Marshal.StructureToPtr(LasZipPointStruct.ConvertPoint(point), _pPointWriter, false);
180+
LasZipPointNative.WriteFromLasPoint((LasZipPointNative*)_pPointWriter, point,
181+
_headerWriter.ScaleFactorX, _headerWriter.ScaleFactorY, _headerWriter.ScaleFactorZ,
182+
_headerWriter.OffsetX, _headerWriter.OffsetY, _headerWriter.OffsetZ);
187183

188184
// Write point
189185
if (laszip_write_point(_pLasZipWriter) != 0)
@@ -232,5 +228,52 @@ public void DestroyWriter()
232228

233229
_pLasZipWriter = IntPtr.Zero;
234230
}
231+
232+
/// <summary>
233+
/// Releases all resources used by the LasZip instance.
234+
/// </summary>
235+
public void Dispose()
236+
{
237+
Dispose(true);
238+
GC.SuppressFinalize(this);
239+
}
240+
241+
/// <summary>
242+
/// Releases the unmanaged resources and optionally releases the managed resources.
243+
/// </summary>
244+
/// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
245+
protected virtual void Dispose(bool disposing)
246+
{
247+
if (_disposed)
248+
return;
249+
250+
// Clean up unmanaged resources
251+
if (_pLasZipReader != IntPtr.Zero)
252+
{
253+
laszip_close_reader(_pLasZipReader);
254+
laszip_destroy(_pLasZipReader);
255+
_pLasZipReader = IntPtr.Zero;
256+
_pPointReader = IntPtr.Zero;
257+
}
258+
259+
if (_pLasZipWriter != IntPtr.Zero)
260+
{
261+
laszip_close_writer(_pLasZipWriter);
262+
laszip_destroy(_pLasZipWriter);
263+
_pLasZipWriter = IntPtr.Zero;
264+
_pPointWriter = IntPtr.Zero;
265+
_pHeaderWriter = IntPtr.Zero;
266+
}
267+
268+
_disposed = true;
269+
}
270+
271+
/// <summary>
272+
/// Finalizer to ensure unmanaged resources are released.
273+
/// </summary>
274+
~LasZip()
275+
{
276+
Dispose(false);
277+
}
235278
}
236279
}

LasZipNetStandard/LasZipNetStandard.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<PackageReadmeFile>README.md</PackageReadmeFile>
1313
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
1414
<RootNamespace>Kuoste.LasZipNetStandard</RootNamespace>
15+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1516
</PropertyGroup>
1617

1718
<ItemGroup>

0 commit comments

Comments
 (0)