pystac-ext-pointcloud 1.0.0rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
"""Implements the :stac-ext:`Point Cloud Extension <pointcloud>`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Generic,
|
|
9
|
+
Literal,
|
|
10
|
+
TypeVar,
|
|
11
|
+
cast,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
import pystac
|
|
15
|
+
from pystac.extensions.base import (
|
|
16
|
+
ExtensionManagementMixin,
|
|
17
|
+
PropertiesExtension,
|
|
18
|
+
SummariesExtension,
|
|
19
|
+
)
|
|
20
|
+
from pystac.extensions.hooks import ExtensionHooks
|
|
21
|
+
from pystac.summaries import RangeSummary
|
|
22
|
+
from pystac.utils import StringEnum, get_required, map_opt
|
|
23
|
+
|
|
24
|
+
#: Generalized version of :class:`~pystac.Item`, :class:`~pystac.Asset`,
|
|
25
|
+
#: or :class:`~pystac.ItemAssetDefinition`
|
|
26
|
+
T = TypeVar("T", pystac.Item, pystac.Asset, pystac.ItemAssetDefinition)
|
|
27
|
+
|
|
28
|
+
SCHEMA_URI: str = "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json"
|
|
29
|
+
PREFIX: str = "pc:"
|
|
30
|
+
|
|
31
|
+
COUNT_PROP = PREFIX + "count"
|
|
32
|
+
TYPE_PROP = PREFIX + "type"
|
|
33
|
+
ENCODING_PROP = PREFIX + "encoding"
|
|
34
|
+
SCHEMAS_PROP = PREFIX + "schemas"
|
|
35
|
+
DENSITY_PROP = PREFIX + "density"
|
|
36
|
+
STATISTICS_PROP = PREFIX + "statistics"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PhenomenologyType(StringEnum):
|
|
40
|
+
"""Valid values for the ``pc:type`` field in the :stac-ext:`Pointcloud Item
|
|
41
|
+
Properties <pointcloud#item-properties>`."""
|
|
42
|
+
|
|
43
|
+
LIDAR = "lidar"
|
|
44
|
+
EOPC = "eopc"
|
|
45
|
+
RADAR = "radar"
|
|
46
|
+
SONAR = "sonar"
|
|
47
|
+
OTHER = "other"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SchemaType(StringEnum):
|
|
51
|
+
"""Valid values for the ``type`` field in a :stac-ext:`Schema Object
|
|
52
|
+
<pointcloud#schema-object>`."""
|
|
53
|
+
|
|
54
|
+
FLOATING = "floating"
|
|
55
|
+
UNSIGNED = "unsigned"
|
|
56
|
+
SIGNED = "signed"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Schema:
|
|
60
|
+
"""Defines a schema for dimension of a pointcloud (e.g., name, size, type)
|
|
61
|
+
|
|
62
|
+
Use :meth:`Schema.create` to create a new instance of ``Schema`` from
|
|
63
|
+
properties.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
properties: dict[str, Any]
|
|
67
|
+
|
|
68
|
+
def __init__(self, properties: dict[str, Any]) -> None:
|
|
69
|
+
self.properties = properties
|
|
70
|
+
|
|
71
|
+
def apply(self, name: str, size: int, type: SchemaType) -> None:
|
|
72
|
+
"""Sets the properties for this Schema.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
name : The name of dimension.
|
|
76
|
+
size : The size of the dimension in bytes. Whole bytes are supported.
|
|
77
|
+
type : Dimension type. Valid values are ``floating``, ``unsigned``, and
|
|
78
|
+
``signed``
|
|
79
|
+
"""
|
|
80
|
+
self.properties["name"] = name
|
|
81
|
+
self.properties["size"] = size
|
|
82
|
+
self.properties["type"] = type
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def create(cls, name: str, size: int, type: SchemaType) -> Schema:
|
|
86
|
+
"""Creates a new Schema.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
name : The name of dimension.
|
|
90
|
+
size : The size of the dimension in bytes. Whole bytes are supported.
|
|
91
|
+
type : Dimension type. Valid values are ``floating``, ``unsigned``, and
|
|
92
|
+
``signed``
|
|
93
|
+
"""
|
|
94
|
+
c = cls({})
|
|
95
|
+
c.apply(name=name, size=size, type=type)
|
|
96
|
+
return c
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def size(self) -> int:
|
|
100
|
+
"""Gets or sets the size value."""
|
|
101
|
+
return cast(int, get_required(self.properties.get("size"), self, "size"))
|
|
102
|
+
|
|
103
|
+
@size.setter
|
|
104
|
+
def size(self, v: int) -> None:
|
|
105
|
+
if not isinstance(v, int):
|
|
106
|
+
raise pystac.STACError(f"size must be an int! Invalid input: {v}")
|
|
107
|
+
|
|
108
|
+
self.properties["size"] = v
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def name(self) -> str:
|
|
112
|
+
"""Gets or sets the name property for this Schema."""
|
|
113
|
+
return cast(str, get_required(self.properties.get("name"), self, "name"))
|
|
114
|
+
|
|
115
|
+
@name.setter
|
|
116
|
+
def name(self, v: str) -> None:
|
|
117
|
+
self.properties["name"] = v
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def type(self) -> SchemaType:
|
|
121
|
+
"""Gets or sets the type property. Valid values are ``floating``, ``unsigned``,
|
|
122
|
+
and ``signed``."""
|
|
123
|
+
return cast(SchemaType, get_required(self.properties.get("type"), self, "type"))
|
|
124
|
+
|
|
125
|
+
@type.setter
|
|
126
|
+
def type(self, v: SchemaType) -> None:
|
|
127
|
+
self.properties["type"] = v
|
|
128
|
+
|
|
129
|
+
def __repr__(self) -> str:
|
|
130
|
+
return "<Schema name={} size={} type={}>".format(
|
|
131
|
+
self.properties.get("name"),
|
|
132
|
+
self.properties.get("size"),
|
|
133
|
+
self.properties.get("type"),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def to_dict(self) -> dict[str, Any]:
|
|
137
|
+
"""Returns this schema as a dictionary."""
|
|
138
|
+
return self.properties
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Statistic:
|
|
142
|
+
"""Defines a single statistic for Pointcloud channel or dimension
|
|
143
|
+
|
|
144
|
+
Use :meth:`Statistic.create` to create a new instance of
|
|
145
|
+
``Statistic`` from property values."""
|
|
146
|
+
|
|
147
|
+
properties: dict[str, Any]
|
|
148
|
+
|
|
149
|
+
def __init__(self, properties: dict[str, Any]) -> None:
|
|
150
|
+
self.properties = properties
|
|
151
|
+
|
|
152
|
+
def apply(
|
|
153
|
+
self,
|
|
154
|
+
name: str,
|
|
155
|
+
position: int | None = None,
|
|
156
|
+
average: float | None = None,
|
|
157
|
+
count: int | None = None,
|
|
158
|
+
maximum: float | None = None,
|
|
159
|
+
minimum: float | None = None,
|
|
160
|
+
stddev: float | None = None,
|
|
161
|
+
variance: float | None = None,
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Sets the properties for this Statistic.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
name : REQUIRED. The name of the channel.
|
|
167
|
+
position : Optional position of the channel in the schema.
|
|
168
|
+
average : Optional average of the channel.
|
|
169
|
+
count : Optional number of elements in the channel.
|
|
170
|
+
maximum : Optional maximum value of the channel.
|
|
171
|
+
minimum : Optional minimum value of the channel.
|
|
172
|
+
stddev : Optional standard deviation of the channel.
|
|
173
|
+
variance : Optional variance of the channel.
|
|
174
|
+
"""
|
|
175
|
+
self.properties["name"] = name
|
|
176
|
+
self.properties["position"] = position
|
|
177
|
+
self.properties["average"] = average
|
|
178
|
+
self.properties["count"] = count
|
|
179
|
+
self.properties["maximum"] = maximum
|
|
180
|
+
self.properties["minimum"] = minimum
|
|
181
|
+
self.properties["stddev"] = stddev
|
|
182
|
+
self.properties["variance"] = variance
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def create(
|
|
186
|
+
cls,
|
|
187
|
+
name: str,
|
|
188
|
+
position: int | None = None,
|
|
189
|
+
average: float | None = None,
|
|
190
|
+
count: int | None = None,
|
|
191
|
+
maximum: float | None = None,
|
|
192
|
+
minimum: float | None = None,
|
|
193
|
+
stddev: float | None = None,
|
|
194
|
+
variance: float | None = None,
|
|
195
|
+
) -> Statistic:
|
|
196
|
+
"""Creates a new Statistic class.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
name : REQUIRED. The name of the channel.
|
|
200
|
+
position : Optional position of the channel in the schema.
|
|
201
|
+
average : Optional average of the channel.
|
|
202
|
+
count : Optional number of elements in the channel.
|
|
203
|
+
maximum : Optional maximum value of the channel.
|
|
204
|
+
minimum : Optional minimum value of the channel.
|
|
205
|
+
stddev : Optional standard deviation of the channel.
|
|
206
|
+
variance : Optional variance of the channel.
|
|
207
|
+
"""
|
|
208
|
+
c = cls({})
|
|
209
|
+
c.apply(
|
|
210
|
+
name=name,
|
|
211
|
+
position=position,
|
|
212
|
+
average=average,
|
|
213
|
+
count=count,
|
|
214
|
+
maximum=maximum,
|
|
215
|
+
minimum=minimum,
|
|
216
|
+
stddev=stddev,
|
|
217
|
+
variance=variance,
|
|
218
|
+
)
|
|
219
|
+
return c
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def name(self) -> str:
|
|
223
|
+
"""Gets or sets the name property."""
|
|
224
|
+
return cast(str, get_required(self.properties.get("name"), self, "name"))
|
|
225
|
+
|
|
226
|
+
@name.setter
|
|
227
|
+
def name(self, v: str) -> None:
|
|
228
|
+
if v is not None:
|
|
229
|
+
self.properties["name"] = v
|
|
230
|
+
else:
|
|
231
|
+
self.properties.pop("name", None)
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def position(self) -> int | None:
|
|
235
|
+
"""Gets or sets the position property."""
|
|
236
|
+
return self.properties.get("position")
|
|
237
|
+
|
|
238
|
+
@position.setter
|
|
239
|
+
def position(self, v: int | None) -> None:
|
|
240
|
+
if v is not None:
|
|
241
|
+
self.properties["position"] = v
|
|
242
|
+
else:
|
|
243
|
+
self.properties.pop("position", None)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def average(self) -> float | None:
|
|
247
|
+
"""Gets or sets the average property."""
|
|
248
|
+
return self.properties.get("average")
|
|
249
|
+
|
|
250
|
+
@average.setter
|
|
251
|
+
def average(self, v: float | None) -> None:
|
|
252
|
+
if v is not None:
|
|
253
|
+
self.properties["average"] = v
|
|
254
|
+
else:
|
|
255
|
+
self.properties.pop("average", None)
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def count(self) -> int | None:
|
|
259
|
+
"""Gets or sets the count property."""
|
|
260
|
+
return self.properties.get("count")
|
|
261
|
+
|
|
262
|
+
@count.setter
|
|
263
|
+
def count(self, v: int | None) -> None:
|
|
264
|
+
if v is not None:
|
|
265
|
+
self.properties["count"] = v
|
|
266
|
+
else:
|
|
267
|
+
self.properties.pop("count", None)
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def maximum(self) -> float | None:
|
|
271
|
+
"""Gets or sets the maximum property."""
|
|
272
|
+
return self.properties.get("maximum")
|
|
273
|
+
|
|
274
|
+
@maximum.setter
|
|
275
|
+
def maximum(self, v: float | None) -> None:
|
|
276
|
+
if v is not None:
|
|
277
|
+
self.properties["maximum"] = v
|
|
278
|
+
else:
|
|
279
|
+
self.properties.pop("maximum", None)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def minimum(self) -> float | None:
|
|
283
|
+
"""Gets or sets the minimum property."""
|
|
284
|
+
return self.properties.get("minimum")
|
|
285
|
+
|
|
286
|
+
@minimum.setter
|
|
287
|
+
def minimum(self, v: float | None) -> None:
|
|
288
|
+
if v is not None:
|
|
289
|
+
self.properties["minimum"] = v
|
|
290
|
+
else:
|
|
291
|
+
self.properties.pop("minimum", None)
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def stddev(self) -> float | None:
|
|
295
|
+
"""Gets or sets the stddev property."""
|
|
296
|
+
return self.properties.get("stddev")
|
|
297
|
+
|
|
298
|
+
@stddev.setter
|
|
299
|
+
def stddev(self, v: float | None) -> None:
|
|
300
|
+
if v is not None:
|
|
301
|
+
self.properties["stddev"] = v
|
|
302
|
+
else:
|
|
303
|
+
self.properties.pop("stddev", None)
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def variance(self) -> float | None:
|
|
307
|
+
"""Gets or sets the variance property."""
|
|
308
|
+
return self.properties.get("variance")
|
|
309
|
+
|
|
310
|
+
@variance.setter
|
|
311
|
+
def variance(self, v: float | None) -> None:
|
|
312
|
+
if v is not None:
|
|
313
|
+
self.properties["variance"] = v
|
|
314
|
+
else:
|
|
315
|
+
self.properties.pop("variance", None)
|
|
316
|
+
|
|
317
|
+
def __repr__(self) -> str:
|
|
318
|
+
return f"<Statistic statistics={str(self.properties)}>"
|
|
319
|
+
|
|
320
|
+
def to_dict(self) -> dict[str, Any]:
|
|
321
|
+
"""Returns this statistic as a dictionary."""
|
|
322
|
+
return self.properties
|
|
323
|
+
|
|
324
|
+
def __eq__(self, o: object) -> bool:
|
|
325
|
+
if not isinstance(o, Statistic):
|
|
326
|
+
return NotImplemented
|
|
327
|
+
return self.to_dict() == o.to_dict()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class PointcloudExtension(
|
|
331
|
+
Generic[T],
|
|
332
|
+
PropertiesExtension,
|
|
333
|
+
ExtensionManagementMixin[pystac.Item | pystac.Collection],
|
|
334
|
+
):
|
|
335
|
+
"""An abstract class that can be used to extend the properties of an
|
|
336
|
+
:class:`~pystac.Item` or :class:`~pystac.Asset` with properties from the
|
|
337
|
+
:stac-ext:`Point Cloud Extension <pointcloud>`. This class is generic over the type
|
|
338
|
+
of STAC Object to be extended (e.g. :class:`~pystac.Item`,
|
|
339
|
+
:class:`~pystac.Asset`).
|
|
340
|
+
|
|
341
|
+
To create a concrete instance of :class:`PointcloudExtension`, use the
|
|
342
|
+
:meth:`PointcloudExtension.ext` method. For example:
|
|
343
|
+
|
|
344
|
+
.. code-block:: python
|
|
345
|
+
|
|
346
|
+
>>> item: pystac.Item = ...
|
|
347
|
+
>>> pc_ext = PointcloudExtension.ext(item)
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
name: Literal["pc"] = "pc"
|
|
351
|
+
|
|
352
|
+
def apply(
|
|
353
|
+
self,
|
|
354
|
+
count: int,
|
|
355
|
+
type: PhenomenologyType | str,
|
|
356
|
+
encoding: str,
|
|
357
|
+
schemas: list[Schema],
|
|
358
|
+
density: float | None = None,
|
|
359
|
+
statistics: list[Statistic] | None = None,
|
|
360
|
+
) -> None:
|
|
361
|
+
"""Applies Pointcloud extension properties to the extended Item.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
count : REQUIRED. The number of points in the cloud.
|
|
365
|
+
type : REQUIRED. Phenomenology type for the point cloud. Possible valid
|
|
366
|
+
values might include lidar, eopc, radar, sonar, or otherThe type of file
|
|
367
|
+
or data format of the cloud.
|
|
368
|
+
encoding : REQUIRED. Content encoding or format of the data.
|
|
369
|
+
schemas : REQUIRED. A sequential array of items
|
|
370
|
+
that define the dimensions and their types.
|
|
371
|
+
density : Number of points per square unit area.
|
|
372
|
+
statistics : A sequential array of items mapping to
|
|
373
|
+
pc:schemas defines per-channel statistics.
|
|
374
|
+
"""
|
|
375
|
+
self.count = count
|
|
376
|
+
self.type = type
|
|
377
|
+
self.encoding = encoding
|
|
378
|
+
self.schemas = schemas
|
|
379
|
+
self.density = density
|
|
380
|
+
self.statistics = statistics
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def count(self) -> int:
|
|
384
|
+
"""Gets or sets the number of points in the Item."""
|
|
385
|
+
return get_required(self._get_property(COUNT_PROP, int), self, COUNT_PROP)
|
|
386
|
+
|
|
387
|
+
@count.setter
|
|
388
|
+
def count(self, v: int) -> None:
|
|
389
|
+
self._set_property(COUNT_PROP, v, pop_if_none=False)
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def type(self) -> PhenomenologyType | str:
|
|
393
|
+
"""Gets or sets the phenomenology type for the point cloud."""
|
|
394
|
+
return get_required(self._get_property(TYPE_PROP, str), self, TYPE_PROP)
|
|
395
|
+
|
|
396
|
+
@type.setter
|
|
397
|
+
def type(self, v: PhenomenologyType | str) -> None:
|
|
398
|
+
self._set_property(TYPE_PROP, v, pop_if_none=False)
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def encoding(self) -> str:
|
|
402
|
+
"""Gets or sets the content encoding or format of the data."""
|
|
403
|
+
return get_required(self._get_property(ENCODING_PROP, str), self, ENCODING_PROP)
|
|
404
|
+
|
|
405
|
+
@encoding.setter
|
|
406
|
+
def encoding(self, v: str) -> None:
|
|
407
|
+
self._set_property(ENCODING_PROP, v, pop_if_none=False)
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def schemas(self) -> list[Schema]:
|
|
411
|
+
"""Gets or sets the list of :class:`Schema` instances defining
|
|
412
|
+
dimensions and types for the data.
|
|
413
|
+
"""
|
|
414
|
+
result = get_required(
|
|
415
|
+
self._get_property(SCHEMAS_PROP, list[dict[str, Any]]), self, SCHEMAS_PROP
|
|
416
|
+
)
|
|
417
|
+
return [Schema(s) for s in result]
|
|
418
|
+
|
|
419
|
+
@schemas.setter
|
|
420
|
+
def schemas(self, v: list[Schema]) -> None:
|
|
421
|
+
self._set_property(SCHEMAS_PROP, [x.to_dict() for x in v], pop_if_none=False)
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def density(self) -> float | None:
|
|
425
|
+
"""Gets or sets the number of points per square unit area."""
|
|
426
|
+
return self._get_property(DENSITY_PROP, float)
|
|
427
|
+
|
|
428
|
+
@density.setter
|
|
429
|
+
def density(self, v: float | None) -> None:
|
|
430
|
+
self._set_property(DENSITY_PROP, v)
|
|
431
|
+
|
|
432
|
+
@property
|
|
433
|
+
def statistics(self) -> list[Statistic] | None:
|
|
434
|
+
"""Gets or sets the list of :class:`Statistic` instances describing
|
|
435
|
+
the pre-channel statistics. Elements in this list map to elements in the
|
|
436
|
+
:attr:`PointcloudExtension.schemas` list."""
|
|
437
|
+
result = self._get_property(STATISTICS_PROP, list[dict[str, Any]])
|
|
438
|
+
return map_opt(lambda stats: [Statistic(s) for s in stats], result)
|
|
439
|
+
|
|
440
|
+
@statistics.setter
|
|
441
|
+
def statistics(self, v: list[Statistic] | None) -> None:
|
|
442
|
+
set_value = map_opt(lambda stats: [s.to_dict() for s in stats], v)
|
|
443
|
+
self._set_property(STATISTICS_PROP, set_value)
|
|
444
|
+
|
|
445
|
+
@classmethod
|
|
446
|
+
def get_schema_uri(cls) -> str:
|
|
447
|
+
return SCHEMA_URI
|
|
448
|
+
|
|
449
|
+
@classmethod
|
|
450
|
+
def ext(cls, obj: T, add_if_missing: bool = False) -> PointcloudExtension[T]:
|
|
451
|
+
"""Extends the given STAC Object with properties from the :stac-ext:`Point Cloud
|
|
452
|
+
Extension <pointcloud>`.
|
|
453
|
+
|
|
454
|
+
This extension can be applied to instances of :class:`~pystac.Item` or
|
|
455
|
+
:class:`~pystac.Asset`.
|
|
456
|
+
|
|
457
|
+
Raises:
|
|
458
|
+
|
|
459
|
+
pystac.ExtensionTypeError : If an invalid object type is passed.
|
|
460
|
+
"""
|
|
461
|
+
if isinstance(obj, pystac.Item):
|
|
462
|
+
cls.ensure_has_extension(obj, add_if_missing)
|
|
463
|
+
return cast(PointcloudExtension[T], ItemPointcloudExtension(obj))
|
|
464
|
+
elif isinstance(obj, pystac.Asset):
|
|
465
|
+
if obj.owner is not None and not isinstance(obj.owner, pystac.Item):
|
|
466
|
+
raise pystac.ExtensionTypeError(
|
|
467
|
+
"Pointcloud extension does not apply to Collection Assets."
|
|
468
|
+
)
|
|
469
|
+
cls.ensure_owner_has_extension(obj, add_if_missing)
|
|
470
|
+
return cast(PointcloudExtension[T], AssetPointcloudExtension(obj))
|
|
471
|
+
elif isinstance(obj, pystac.ItemAssetDefinition):
|
|
472
|
+
cls.ensure_owner_has_extension(obj, add_if_missing)
|
|
473
|
+
return cast(PointcloudExtension[T], ItemAssetsPointcloudExtension(obj))
|
|
474
|
+
else:
|
|
475
|
+
raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
|
|
476
|
+
|
|
477
|
+
@classmethod
|
|
478
|
+
def summaries(
|
|
479
|
+
cls, obj: pystac.Collection, add_if_missing: bool = False
|
|
480
|
+
) -> SummariesPointcloudExtension:
|
|
481
|
+
cls.ensure_has_extension(obj, add_if_missing)
|
|
482
|
+
return SummariesPointcloudExtension(obj)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class ItemPointcloudExtension(PointcloudExtension[pystac.Item]):
|
|
486
|
+
"""A concrete implementation of :class:`PointcloudExtension` on an
|
|
487
|
+
:class:`~pystac.Item` that extends the properties of the Item to include
|
|
488
|
+
properties defined in the :stac-ext:`Point Cloud Extension <pointcloud>`.
|
|
489
|
+
|
|
490
|
+
This class should generally not be instantiated directly. Instead, call
|
|
491
|
+
:meth:`PointcloudExtension.ext` on an :class:`~pystac.Item` to extend it.
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
item: pystac.Item
|
|
495
|
+
properties: dict[str, Any]
|
|
496
|
+
|
|
497
|
+
def __init__(self, item: pystac.Item):
|
|
498
|
+
self.item = item
|
|
499
|
+
self.properties = item.properties
|
|
500
|
+
|
|
501
|
+
def __repr__(self) -> str:
|
|
502
|
+
return f"<ItemPointcloudExtension Item id={self.item.id}>"
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
class AssetPointcloudExtension(PointcloudExtension[pystac.Asset]):
|
|
506
|
+
"""A concrete implementation of :class:`PointcloudExtension` on an
|
|
507
|
+
:class:`~pystac.Asset` that extends the Asset fields to include properties defined
|
|
508
|
+
in the :stac-ext:`Point Cloud Extension <pointcloud>`.
|
|
509
|
+
|
|
510
|
+
This class should generally not be instantiated directly. Instead, call
|
|
511
|
+
:meth:`PointcloudExtension.ext` on an :class:`~pystac.Asset` to extend it.
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
asset_href: str
|
|
515
|
+
"""The ``href`` value of the :class:`~pystac.Asset` being extended."""
|
|
516
|
+
|
|
517
|
+
properties: dict[str, Any]
|
|
518
|
+
"""The :class:`~pystac.Asset` fields, including extension properties."""
|
|
519
|
+
|
|
520
|
+
additional_read_properties: Iterable[dict[str, Any]] | None = None
|
|
521
|
+
"""If present, this will be a list containing 1 dictionary representing the
|
|
522
|
+
properties of the owning :class:`~pystac.Item`."""
|
|
523
|
+
|
|
524
|
+
def __init__(self, asset: pystac.Asset):
|
|
525
|
+
self.asset_href = asset.href
|
|
526
|
+
self.properties = asset.extra_fields
|
|
527
|
+
if asset.owner and isinstance(asset.owner, pystac.Item):
|
|
528
|
+
self.additional_read_properties = [asset.owner.properties]
|
|
529
|
+
self.repr_id = f"href={asset.href} item.id={asset.owner.id}"
|
|
530
|
+
else:
|
|
531
|
+
self.repr_id = f"href={asset.href}"
|
|
532
|
+
|
|
533
|
+
def __repr__(self) -> str:
|
|
534
|
+
return f"<AssetPointcloudExtension Asset {self.repr_id}>"
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
class ItemAssetsPointcloudExtension(PointcloudExtension[pystac.ItemAssetDefinition]):
|
|
538
|
+
properties: dict[str, Any]
|
|
539
|
+
asset_defn: pystac.ItemAssetDefinition
|
|
540
|
+
|
|
541
|
+
def __init__(self, item_asset: pystac.ItemAssetDefinition):
|
|
542
|
+
self.asset_defn = item_asset
|
|
543
|
+
self.properties = item_asset.properties
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class SummariesPointcloudExtension(SummariesExtension):
|
|
547
|
+
"""A concrete implementation of :class:`~pystac.extensions.base.SummariesExtension`
|
|
548
|
+
that extends the ``summaries`` field of a :class:`~pystac.Collection` to include
|
|
549
|
+
properties defined in the :stac-ext:`Point Cloud Extension <pointcloud>`.
|
|
550
|
+
"""
|
|
551
|
+
|
|
552
|
+
@property
|
|
553
|
+
def count(self) -> RangeSummary[int] | None:
|
|
554
|
+
return self.summaries.get_range(COUNT_PROP)
|
|
555
|
+
|
|
556
|
+
@count.setter
|
|
557
|
+
def count(self, v: RangeSummary[int] | None) -> None:
|
|
558
|
+
self._set_summary(COUNT_PROP, v)
|
|
559
|
+
|
|
560
|
+
@property
|
|
561
|
+
def type(self) -> list[PhenomenologyType | str] | None:
|
|
562
|
+
return self.summaries.get_list(TYPE_PROP)
|
|
563
|
+
|
|
564
|
+
@type.setter
|
|
565
|
+
def type(self, v: list[PhenomenologyType | str] | None) -> None:
|
|
566
|
+
self._set_summary(TYPE_PROP, v)
|
|
567
|
+
|
|
568
|
+
@property
|
|
569
|
+
def encoding(self) -> list[str] | None:
|
|
570
|
+
return self.summaries.get_list(ENCODING_PROP)
|
|
571
|
+
|
|
572
|
+
@encoding.setter
|
|
573
|
+
def encoding(self, v: list[str] | None) -> None:
|
|
574
|
+
self._set_summary(ENCODING_PROP, v)
|
|
575
|
+
|
|
576
|
+
@property
|
|
577
|
+
def density(self) -> RangeSummary[float] | None:
|
|
578
|
+
return self.summaries.get_range(DENSITY_PROP)
|
|
579
|
+
|
|
580
|
+
@density.setter
|
|
581
|
+
def density(self, v: RangeSummary[float] | None) -> None:
|
|
582
|
+
self._set_summary(DENSITY_PROP, v)
|
|
583
|
+
|
|
584
|
+
@property
|
|
585
|
+
def statistics(self) -> list[Statistic] | None:
|
|
586
|
+
return map_opt(
|
|
587
|
+
lambda stats: [Statistic(d) for d in stats],
|
|
588
|
+
self.summaries.get_list(STATISTICS_PROP),
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
@statistics.setter
|
|
592
|
+
def statistics(self, v: list[Statistic] | None) -> None:
|
|
593
|
+
self._set_summary(
|
|
594
|
+
STATISTICS_PROP,
|
|
595
|
+
map_opt(lambda stats: [s.to_dict() for s in stats], v),
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
class PointcloudExtensionHooks(ExtensionHooks):
|
|
600
|
+
schema_uri: str = SCHEMA_URI
|
|
601
|
+
prev_extension_ids = {"pointcloud"}
|
|
602
|
+
stac_object_types = {pystac.STACObjectType.ITEM}
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
POINTCLOUD_EXTENSION_HOOKS: ExtensionHooks = PointcloudExtensionHooks()
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pystac-ext-pointcloud
|
|
3
|
+
Version: 1.0.0rc0
|
|
4
|
+
Summary: Point Cloud extension for PySTAC
|
|
5
|
+
Project-URL: Documentation, https://pystac.readthedocs.io
|
|
6
|
+
Project-URL: Repository, https://github.com/stac-utils/pystac
|
|
7
|
+
Project-URL: Issues, https://github.com/stac-utils/pystac/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Discussions, https://github.com/radiantearth/stac-spec/discussions/categories/stac-software
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: STAC,catalog,imagery,pointcloud,pystac,raster
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: pystac-core
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# pystac-ext-pointcloud
|
|
26
|
+
|
|
27
|
+
[PySTAC](https://pypi.org/project/pystac/) extension package for the [Point Cloud Extension](https://github.com/stac-extensions/pointcloud).
|
|
28
|
+
This extension provides fields for describing point cloud data, including point count, encoding, density, schemas, and statistics.
|
|
29
|
+
|
|
30
|
+
## Supported versions
|
|
31
|
+
|
|
32
|
+
- [v1.0.0](https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json)
|
|
33
|
+
|
|
34
|
+
## Versioning
|
|
35
|
+
|
|
36
|
+
This package's version corresponds to the version of the extension specification it targets.
|
|
37
|
+
When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `1.0.0.post1`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
pystac/extensions/pointcloud.py,sha256=Kt0i8gWV1YF0dC9BXqP06ATOrQJh59YvXDpjjKQRY7o,20413
|
|
2
|
+
pystac/extensions/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pystac_ext_pointcloud-1.0.0rc0.dist-info/METADATA,sha256=0F9Q0mZz6TGiBXLiHhyxzvsN40U-AIoA2N7vKOB6Pe8,1826
|
|
4
|
+
pystac_ext_pointcloud-1.0.0rc0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
5
|
+
pystac_ext_pointcloud-1.0.0rc0.dist-info/RECORD,,
|