Skip to content
This repository has been archived by the owner on Aug 18, 2022. It is now read-only.

Commit

Permalink
Add things about VLRs reading/writing in the docs
Browse files Browse the repository at this point in the history
  • Loading branch information
tmontaigu committed Jul 18, 2018
1 parent 5b406ba commit 82856dd
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 14 deletions.
3 changes: 3 additions & 0 deletions docs/basic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ To be able to write a las file you will need a :class:`pylas.lasdatas.base.LasBa
You obtain this type of object by using one of the function above,
use its method :meth:`pylas.lasdatas.base.LasBase.write` to write to a file or a stream.

.. _accessing_header:

Accessing the file header
=========================
Expand All @@ -72,6 +73,8 @@ You can access the header of a las file you read or opened by retrieving the 'he

you can see the accessible fields in :class:`pylas.headers.rawheader.RawHeader1_1` and its sub-classes.

.. _manipulating_vlrs:

Manipulating VLRs
=================

Expand Down
46 changes: 37 additions & 9 deletions docs/intro.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
============
Introduction
============
====================
What is a LAS file ?
====================

LAS is a public file format meant to exchange 3D point data, mostly used to exchange lidar point clouds.
LAZ is a **lossless** compression of the LAS format.
Expand All @@ -15,31 +15,35 @@ LAS files are organized in 3 main parts:
2) VLRs
3) Point Records

Header
------

The header contains information about the data such as its version, the point format (which tells the different
dimensions stored for each points).

See :ref:`accessing_header`

VLRs
----

After the header, LAS files may contain VLRs (Variable Length Record).
VLRs are meant to store additional information such as the SRS, description on extra dimensions added to the points.
VLRs are meant to store additional information such as the SRS (Spatial Reference System),
description on extra dimensions added to the points.

VLRs are divided in two parts:

1) header
2) payload

The payload is limited to 65,535 bytes (Because in the header, the length of the payload is stored on a uint16).

The last chunk of data (and the biggest one) contains the point records. In a LAS file, points are stored sequentially.
See :ref:`manipulating_vlrs`


Version 1.4 of the LAS specification added a last block following the point records: EVLRs (Extended Variable
Length Record) which are the same thing as VLRs but they can carry a higher payload (length of the payload is stored
on a uint64)

Point Records
-------------
The last chunk of data (and the biggest one) contains the point records. In a LAS file, points are stored sequentially.

The point records holds the point cloud data the LAS Spec specifies 10 point formats.
A point format describe the dimensions stored for each point in the record.
Expand All @@ -60,7 +64,25 @@ and LAS file version.
The names written in the tables below are the one you will have to use in
your code.

* Point Format 0 *
.. note::

The dimensions 'X', 'Y', 'Z' are signed integers without the scale and
offset applied. To access the coordinates as doubles simply use 'x', 'y' , 'z'

>>> import pylas
>>> las = pylas.read('pylastests/simple.las')
>>> las.X.dtype
dtype('int32')
>>> las.X
array([63701224, 63689633, 63678474, ..., 63750167, 63743327, 63734285])
>>> las.x.dtype
dtype('float64')
>>> las.x
array([637012.24, 636896.33, 636784.74, ..., 637501.67, 637433.27,
637342.85])


* Point Format 0

+----------------------+-----------+--------------+
| Dimensions | Type | Size (bit) |
Expand Down Expand Up @@ -186,3 +208,9 @@ the same dimensions plus some additional dimensions:
| z_t | floating | 32 |
+----------------------------+-----------+--------------+

EVLRs
-----

Version 1.4 of the LAS specification added a last block following the point records: EVLRs (Extended Variable
Length Record) which are the same thing as VLRs but they can carry a higher payload (length of the payload is stored
on a uint64)
80 changes: 78 additions & 2 deletions docs/lessbasic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,86 @@ and an array field of 3 doubles for each points.
.. note::

As the specification of the ExtraBytesVlr appeared in the 1.4 LAS Spec, pylas restrict the ability to
add new dimensions to file with version >= 1.4 even if it would be totally possible to define new dimension
on older versions.
(Maybe this should change?)
(Maybe this should change?)

Custom VLRs
===========

Provided you have a valid user_id and record_id (meaning that they are not taken by a VLR described in the LAS specification)
You can add you own VLRs to a file

Fast & Easy way
---------------

The fastest and easiest way to add your custom VLR to a file is to create a :class:`pylas.vlrs.rawvlr.VLR`,
set its record_data (which must be bytes) and add it to the VLR list.


>>> import pylas
>>> vlr = pylas.vlrs.VLR(user_id='A UserId', record_id=1, description='Example VLR')
>>> vlr
<VLR(user_id: 'A UserId', record_id: '1', data len: 0)>
>>> vlr.record_data = b'somebytes'
>>> vlr
<VLR(user_id: 'A UserId', record_id: '1', data len: 9)>
>>> las = pylas.create()
>>> las.vlrs
[]
>>> las.vlrs.append(vlr)
>>> las.vlrs
[<VLR(user_id: 'A UserId', record_id: '1', data len: 9)>]


Complete & Harder way
---------------------

While the way shown above is quick & easy it might not be perfect for complex VLRs.
Also when reading a file that has custom VLR, pylas won't be able to automatically parse its data
into a better structure, so you will have to find the VLR in the vlrs list and parse it yourself
one pylas is done.

One way to automate this task is to create your own Custom VLR Class that extends
:class:`pylas.vlrs.BaseKnownVLR` by implementing the missing methods pylas
will be able to automatically parse the VLR when reading the file & write it when saving the file.

>>> class CustomVLR(pylas.vlrs.BaseKnownVLR):
... def __init__(self):
... super().__init__()
... self.numbers = []
...
... @staticmethod
... def official_user_id():
... return "CustomId"
...
... @staticmethod
... def official_record_ids():
... return 1,
...
... def record_data_bytes(self):
... return bytes(self.numbers)
...
... def parse_record_data(self, record_data):
... self.numbers = [b for b in record_data]
...
... def __repr__(self):
... return "<MyCustomVLR>"

>>> cvlr = CustomVLR()
>>> cvlr.numbers
[]
>>> cvlr.numbers = [1,2, 3]
>>> las = pylas.create()
>>> las.vlrs.append(cvlr)
>>> las.vlrs
[<MyCustomVLR>]
>>> las = pylas.lib.write_then_read_again(las)
>>> las.vlrs
[<MyCustomVLR>]
>>> las.vlrs[0].numbers
[1, 2, 3]

10 changes: 10 additions & 0 deletions pylas/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
used directly by a user
"""
import copy
import io

from . import headers
from .lasdatas import las12, las14
Expand Down Expand Up @@ -54,6 +55,8 @@ def open_las(source, closefd=True):
stream = open(source, mode="rb")
if not closefd:
raise ValueError("Cannot use closefd with filename")
elif isinstance(source, bytes):
stream = io.BytesIO(source)
else:
stream = source
return LasReader(stream, closefd=closefd)
Expand Down Expand Up @@ -252,3 +255,10 @@ def convert(source_las, *, point_format_id=None, file_version=None):
header=header, vlrs=source_las.vlrs, points=points, evlrs=evlrs
)
return las12.LasData(header=header, vlrs=source_las.vlrs, points=points)


def write_then_read_again(las, do_compress=False):
out = io.BytesIO()
las.write(out, do_compress=do_compress)
out.seek(0)
return read_las(out)
3 changes: 2 additions & 1 deletion pylas/vlrs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .rawvlr import VLR_HEADER_SIZE
from .rawvlr import VLR_HEADER_SIZE, VLR
from . import geotiff
from .known import BaseKnownVLR
3 changes: 2 additions & 1 deletion pylas/vlrs/known.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
A KnownVLR is a VLR that we know how to parse its record_data
"""
import abc
import ctypes
from abc import abstractmethod

from .rawvlr import NULL_BYTE, BaseVLR, VLR
from ..extradims import get_type_for_extra_dim


class IKnownVLR:
class IKnownVLR(abc.ABC):
""" Interface that any KnownVLR must implement.
A KnownVLR is a VLR for which we know how to parse its record_data
Expand Down
2 changes: 1 addition & 1 deletion pylas/vlrs/rawvlr.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,6 @@ def from_raw(cls, raw_vlr):
return vlr

def __repr__(self):
return "<{}(user_id: '{}', record_id: '{}', data len: '{}')>".format(
return "<{}(user_id: '{}', record_id: '{}', data len: {})>".format(
self.__class__.__name__, self.user_id, self.record_id, len(self.record_data)
)

0 comments on commit 82856dd

Please sign in to comment.