/// <summary>
        /// Read the LAS common header structure.
        /// NOTE: I don't want to compile /unsafe, so the only way to do this is by interpreting the byte stream directly. Normally I would used a fixed struct to do this.
        /// </summary>
        /// <param name="data"></param>
        /// <returns>A public header block strucuture which is filled in from the bytes in data[]</returns>
        protected LASPublicHeaderBlock ReadLASHeader(ref byte[] data)
        {
            LASPublicHeaderBlock header = new LASPublicHeaderBlock();

            long pos = 0; //current position in file

            header.FileSignature = ReadString(ref pos, ref data, 4);
            if (header.FileSignature != "LASF")
            {
                Debug.Log("LidarLASReader.ReadLASHeader: Error reading header, FileSignature expected 'LASF' but found '" + header.FileSignature + "'. Load aborted.");
                return(header);
            }
            header.FileSourceID                  = ReadUnsignedShort(ref pos, ref data);
            header.GlobalEncoding                = ReadUnsignedShort(ref pos, ref data);
            header.ProjectID1                    = ReadUnsignedLong(ref pos, ref data);
            header.ProjectID2                    = ReadUnsignedShort(ref pos, ref data);
            header.ProjectID3                    = ReadUnsignedShort(ref pos, ref data);
            header.ProjectID4                    = ReadString(ref pos, ref data, 8);
            header.VersionMajor                  = ReadByte(ref pos, ref data);
            header.VersionMinor                  = ReadByte(ref pos, ref data);
            header.SystemIdentifier              = ReadString(ref pos, ref data, 32);
            header.GeneratingSoftware            = ReadString(ref pos, ref data, 32);
            header.FileCreationDayofYear         = ReadUnsignedShort(ref pos, ref data);
            header.FileCreationYear              = ReadUnsignedShort(ref pos, ref data);
            header.HeaderSize                    = ReadUnsignedShort(ref pos, ref data);
            header.OffsetToPointData             = ReadUnsignedLong(ref pos, ref data);
            header.NumberofVariableLengthRecords = ReadUnsignedLong(ref pos, ref data);
            header.PointDataRecordFormat         = (byte)(ReadByte(ref pos, ref data) & 0x7f); //strip the top bit off as the unsigned char original type seems to set the MSB
            header.PointDataRecordLength         = ReadUnsignedShort(ref pos, ref data);
            header.LegacyNumberofPointRecords    = ReadUnsignedLong(ref pos, ref data);
            header.LegacyNumberofPointsByReturn  = new UInt32[] {
                ReadUnsignedLong(ref pos, ref data),
                ReadUnsignedLong(ref pos, ref data),
                ReadUnsignedLong(ref pos, ref data),
                ReadUnsignedLong(ref pos, ref data),
                ReadUnsignedLong(ref pos, ref data)
            };
            header.XScaleFactor = ReadDouble(ref pos, ref data);
            header.YScaleFactor = ReadDouble(ref pos, ref data);
            header.ZScaleFactor = ReadDouble(ref pos, ref data);
            header.XOffset      = ReadDouble(ref pos, ref data);
            header.YOffset      = ReadDouble(ref pos, ref data);
            header.ZOffset      = ReadDouble(ref pos, ref data);
            header.MaxX         = ReadDouble(ref pos, ref data);
            header.MinX         = ReadDouble(ref pos, ref data);
            header.MaxY         = ReadDouble(ref pos, ref data);
            header.MinY         = ReadDouble(ref pos, ref data);
            header.MaxZ         = ReadDouble(ref pos, ref data);
            header.MinZ         = ReadDouble(ref pos, ref data);
            //TODO: these are only in the 1.4 spec files
            float version = header.VersionMajor + header.VersionMajor / 10; //so 1,4 become 1.4

            if (version >= 1.4)
            {
                header.StartofWaveformDataPacketRecord          = ReadUnsignedLongLong(ref pos, ref data);
                header.StartofFirstExtendedVariableLengthRecord = ReadUnsignedLong(ref pos, ref data); //See note above on long long values
                header.NumberofExtendedVariableLengthRecords    = ReadUnsignedLong(ref pos, ref data);
                header.NumberofPointRecords   = ReadUnsignedLongLong(ref pos, ref data);
                header.NumberofPointsByReturn = new UInt64[15];
                for (int i = 0; i < 15; i++)
                {
                    header.NumberofPointsByReturn[i] = ReadUnsignedLongLong(ref pos, ref data);
                }
            }
            else
            {
                //spec says if they're not used they should be zero
                header.StartofWaveformDataPacketRecord          = 0;
                header.StartofFirstExtendedVariableLengthRecord = 0;
                header.NumberofExtendedVariableLengthRecords    = 0;
                header.NumberofPointRecords   = 0;
                header.NumberofPointsByReturn = new UInt64[15];
                for (int i = 0; i < 15; i++)
                {
                    header.NumberofPointsByReturn[i] = 0;
                }
            }
            return(header);
        }
        /// <summary>
        /// Read in the point cloud data and store the points in the coverage data grid.
        /// For the moment, the maximum value for each grid square is recorded in order to make a height map later. This might need to be changed at a later date.
        /// TODO: store the (half a million) points as point data, rather than as a coverage. AND do something with the classification.
        /// NOTE: the data is an XY grid on the ground with Z as the height. Original projection is OSGB36.
        /// </summary>
        /// <param name="header"></param>
        /// <param name="data"></param>
        protected void ReadLASPoints(LASPublicHeaderBlock header, ref byte[] data)
        {
            //set up my coverage grid to put the data into - I'm making this square
            double width       = header.MaxX - header.MinX;
            double height      = header.MaxY - header.MinY;
            double maxDim      = Math.Max(width, height);
            double gridspacing = 1.0f; //1.0 metre resolution grid
            int    isize       = (int)Math.Ceiling(maxDim / gridspacing);

            this.cellsize  = (float)(maxDim / (float)isize); //this is the spacing on the XY axes to match the value in the height field (Z)
            CoverageData   = new float[isize, isize];
            Classification = new byte[isize, isize];
            for (int y = 0; y < isize; y++)
            {
                for (int x = 0; x < isize; x++)
                {
                    CoverageData[x, y] = 0;                                                         // -float.MaxValue;
                }
            }
            ulong NumPoints = Math.Max(header.LegacyNumberofPointRecords, header.NumberofPointRecords); //you have to use the correct one based on version
            long pos        = header.OffsetToPointData;
            uint PDRFormat  = header.PointDataRecordFormat;                                             //0..10 designates which format of point data is being used - all points are the same format (preferred formats in LAS 1.4 are 6-10)
            for (ulong i = 0; i < NumPoints; i++)
            {
                LASPointDataRecordFormat P = null;
                switch (PDRFormat)
                {
                case 0: P = new LASPointDataRecordFormat0(); break;

                case 1: P = new LASPointDataRecordFormat1(); break;

                case 2: P = new LASPointDataRecordFormat2(); break;

                case 3: P = new LASPointDataRecordFormat3(); break;

                case 4: P = new LASPointDataRecordFormat4(); break;

                case 5: P = new LASPointDataRecordFormat5(); break;

                case 6: P = new LASPointDataRecordFormat6(); break;

                case 7: P = new LASPointDataRecordFormat7(); break;

                case 8: P = new LASPointDataRecordFormat8(); break;

                case 9: P = new LASPointDataRecordFormat9(); break;

                case 10: P = new LASPointDataRecordFormat10(); break;
                }
                P.FromData(ref pos, ref data);
                //apply scale and offset factor from header to point integer offset data to get final point
                double X = P.X * header.XScaleFactor + header.XOffset;
                double Y = P.Y * header.YScaleFactor + header.YOffset;
                double Z = P.Z * header.ZScaleFactor + header.ZOffset;
                //and do something with the data - TODO: classification might be interesting?
                int   gx  = (int)((X - header.MinX) / gridspacing);
                int   gy  = (int)((Y - header.MinY) / gridspacing);
                float val = (float)Z;
                if ((gx >= isize) || (gy >= isize) || (gx < 0) || (gy < 0))
                {
                    Debug.Log("LidarLASReader.ReadLASPoints: Error, index out of range, gx=" + gx + " gy=" + gy + " isize=" + isize);
                    continue; //skip it
                }
                if (val > CoverageData[gx, gy])
                {
                    CoverageData[gx, gy] = val;                             //max filter point cloud data
                }
                //HACK! I want to see the classification data from this, so I've added the following line - NOTE: there might be conflicts with different classifications of points in the same grid
                Classification[gx, gy] = ((LASPointDataRecordFormat1)P).Classification; //HACK! might not be a classification point data record format!
            }
        }
 /// <summary>
 /// Do the work...
 /// </summary>
 protected void ReadLASLidar()
 {
     byte[] bytes = File.ReadAllBytes(Filename);
     Header = ReadLASHeader(ref bytes);
     ReadLASPoints(Header, ref bytes);
 }