File: vlrlist.py

package info (click to toggle)
python-laspy 2.5.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,928 kB
  • sloc: python: 9,065; makefile: 20
file content (216 lines) | stat: -rw-r--r-- 7,077 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import logging
from typing import BinaryIO, List

import numpy as np

from ..utils import read_string, write_as_c_string
from .known import IKnownVLR, vlr_factory
from .vlr import VLR

logger = logging.getLogger(__name__)

RESERVED_LEN = 2
USER_ID_LEN = 16
DESCRIPTION_LEN = 32


class VLRList(list):
    """Class responsible for managing the vlrs"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def index(self, value, start: int = 0, stop: int = None) -> int:
        if stop is None:
            stop = len(self)
        if isinstance(value, str):
            for i, vlr in enumerate(self[start:stop]):
                if vlr.__class__.__name__ == value:
                    return i + start
            raise ValueError(f"VLR '{value}' could not be found in the list")
        else:
            return super().index(value, start, stop)

    def get_by_id(self, user_id="", record_ids=(None,)):
        """Function to get vlrs by user_id and/or record_ids.
        Always returns a list even if only one vlr matches the user_id and record_id

        >>> import laspy
        >>> from laspy.vlrs.known import ExtraBytesVlr, WktCoordinateSystemVlr
        >>> las = laspy.read("tests/data/extrabytes.las")
        >>> las.vlrs
        [<ExtraBytesVlr(extra bytes structs: 5)>]
        >>> las.vlrs.get(WktCoordinateSystemVlr.official_user_id())
        []
        >>> las.vlrs.get(WktCoordinateSystemVlr.official_user_id())[0]
        Traceback (most recent call last):
        IndexError: list index out of range
        >>> las.vlrs.get_by_id(ExtraBytesVlr.official_user_id())
        [<ExtraBytesVlr(extra bytes structs: 5)>]
        >>> las.vlrs.get_by_id(ExtraBytesVlr.official_user_id())[0]
        <ExtraBytesVlr(extra bytes structs: 5)>

        Parameters
        ----------
        user_id: str, optional
                 the user id
        record_ids: iterable of int, optional
                    THe record ids of the vlr(s) you wish to get

        Returns
        -------
        :py:class:`list`
            a list of vlrs matching the user_id and records_ids

        """
        return [
            vlr
            for vlr in self
            if (user_id == "" or vlr.user_id == user_id)
            and (record_ids == (None,) or vlr.record_id in record_ids)
        ]

    def get(self, vlr_type: str) -> List[IKnownVLR]:
        """Returns the list of vlrs of the requested type
        Always returns a list even if there is only one VLR of type vlr_type.

        >>> import laspy
        >>> las = laspy.read("tests/data/extrabytes.las")
        >>> las.vlrs
        [<ExtraBytesVlr(extra bytes structs: 5)>]
        >>> las.vlrs.get("WktCoordinateSystemVlr")
        []
        >>> las.vlrs.get("WktCoordinateSystemVlr")[0]
        Traceback (most recent call last):
        IndexError: list index out of range
        >>> las.vlrs.get('ExtraBytesVlr')
        [<ExtraBytesVlr(extra bytes structs: 5)>]
        >>> las.vlrs.get('ExtraBytesVlr')[0]
        <ExtraBytesVlr(extra bytes structs: 5)>


        Parameters
        ----------
        vlr_type: str
                  the class name of the vlr

        Returns
        -------
        :py:class:`list`
            a List of vlrs matching the user_id and records_ids

        """
        return [v for v in self if v.__class__.__name__ == vlr_type]

    def extract(self, vlr_type: str) -> List[IKnownVLR]:
        """Returns the list of vlrs of the requested type
        The difference with get is that the returned vlrs will be removed from the list

        Parameters
        ----------
        vlr_type: str
                  the class name of the vlr

        Returns
        -------
        list
            a List of vlrs matching the user_id and records_ids

        """
        kept_vlrs, extracted_vlrs = [], []
        for vlr in self:
            if vlr.__class__.__name__ == vlr_type:
                extracted_vlrs.append(vlr)
            else:
                kept_vlrs.append(vlr)
        self.clear()
        self.extend(kept_vlrs)
        return extracted_vlrs

    def __repr__(self):
        return "[{}]".format(", ".join(repr(vlr) for vlr in self))

    @classmethod
    def read_from(
        cls, data_stream: BinaryIO, num_to_read: int, extended: bool = False
    ) -> "VLRList":
        """Reads vlrs and parse them if possible from the stream

        Parameters
        ----------
        data_stream : io.BytesIO
                      stream to read from
        num_to_read : int
                      number of vlrs to be read

        extended : bool
                      whether the vlrs are regular vlr or extended vlr

        Returns
        -------
        laspy.vlrs.vlrlist.VLRList
            List of vlrs

        """
        vlrlist = cls()
        for _ in range(num_to_read):
            data_stream.read(RESERVED_LEN)
            user_id = data_stream.read(USER_ID_LEN).split(b"\0")[0].decode()
            record_id = int.from_bytes(
                data_stream.read(2), byteorder="little", signed=False
            )
            if extended:
                record_data_len = int.from_bytes(
                    data_stream.read(8), byteorder="little", signed=False
                )
            else:
                record_data_len = int.from_bytes(
                    data_stream.read(2), byteorder="little", signed=False
                )
            description = read_string(data_stream, DESCRIPTION_LEN)
            record_data_bytes = data_stream.read(record_data_len)

            vlr = VLR(user_id, record_id, description, record_data_bytes)

            vlrlist.append(vlr_factory(vlr))

        return vlrlist

    def write_to(
        self,
        stream: BinaryIO,
        as_extended: bool = False,
        encoding_errors: str = "strict",
    ) -> int:
        bytes_written = 0
        for vlr in self:
            record_data = vlr.record_data_bytes()

            stream.write(b"\0\0")
            write_as_c_string(stream, vlr.user_id, USER_ID_LEN)
            stream.write(vlr.record_id.to_bytes(2, byteorder="little", signed=False))
            if as_extended:
                stream.write(
                    len(record_data).to_bytes(8, byteorder="little", signed=False)
                )
            else:
                max_length = np.iinfo("uint16").max
                if len(record_data) > max_length:
                    raise ValueError(
                        f"VLR record_date length ({len(record_data)}) exceeds the maximum length ({max_length})"
                    )
                stream.write(
                    len(record_data).to_bytes(2, byteorder="little", signed=False)
                )
            write_as_c_string(
                stream,
                vlr.description,
                DESCRIPTION_LEN,
                encoding_errors=encoding_errors,
            )
            stream.write(record_data)

            bytes_written += 54 if not as_extended else 60
            bytes_written += len(record_data)

        return bytes_written