Skip to content

Commit 368767b

Browse files
committed
Add Point Cloud Extension summaries
1 parent d867796 commit 368767b

File tree

2 files changed

+190
-1
lines changed

2 files changed

+190
-1
lines changed

pystac/extensions/pointcloud.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from pystac.extensions.base import (
1010
ExtensionManagementMixin,
1111
PropertiesExtension,
12+
SummariesExtension,
1213
)
1314
from pystac.extensions.hooks import ExtensionHooks
15+
from pystac.summaries import RangeSummary
1416
from pystac.utils import map_opt, get_required
1517

1618
T = TypeVar("T", pystac.Item, pystac.Asset)
@@ -320,9 +322,16 @@ def to_dict(self) -> Dict[str, Any]:
320322
"""Returns a JSON-like dictionary representing this ``Statistic``."""
321323
return self.properties
322324

325+
def __eq__(self, o: object) -> bool:
326+
if not isinstance(o, Statistic):
327+
return NotImplemented
328+
return self.to_dict() == o.to_dict()
329+
323330

324331
class PointcloudExtension(
325-
Generic[T], PropertiesExtension, ExtensionManagementMixin[pystac.Item]
332+
Generic[T],
333+
PropertiesExtension,
334+
ExtensionManagementMixin[Union[pystac.Item, pystac.Collection]],
326335
):
327336
"""An abstract class that can be used to extend the properties of an
328337
:class:`~pystac.Item` or :class:`~pystac.Asset` with properties from the
@@ -473,6 +482,16 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> "PointcloudExtension[T]":
473482
f"Pointcloud extension does not apply to type '{type(obj).__name__}'"
474483
)
475484

485+
@classmethod
486+
def summaries(
487+
cls, obj: pystac.Collection, add_if_missing: bool = False
488+
) -> "SummariesPointcloudExtension":
489+
if not add_if_missing:
490+
cls.validate_has_extension(obj)
491+
else:
492+
cls.add_to(obj)
493+
return SummariesPointcloudExtension(obj)
494+
476495

477496
class ItemPointcloudExtension(PointcloudExtension[pystac.Item]):
478497
"""A concrete implementation of :class:`PointcloudExtension` on an :class:`~pystac.Item`
@@ -513,6 +532,59 @@ def __repr__(self) -> str:
513532
return f"<AssetPointcloudExtension Asset {self.repr_id}>"
514533

515534

535+
class SummariesPointcloudExtension(SummariesExtension):
536+
"""A concrete implementation of :class:`~SummariesExtension` that extends
537+
the ``summaries`` field of a :class:`~pystac.Collection` to include properties
538+
defined in the :stac-ext:`Point Cloud Extension <pointcloud>`.
539+
"""
540+
541+
@property
542+
def count(self) -> Optional[RangeSummary[int]]:
543+
return self.summaries.get_range(COUNT_PROP)
544+
545+
@count.setter
546+
def count(self, v: Optional[RangeSummary[int]]) -> None:
547+
self._set_summary(COUNT_PROP, v)
548+
549+
@property
550+
def type(self) -> Optional[List[Union[PhenomenologyType, str]]]:
551+
return self.summaries.get_list(TYPE_PROP)
552+
553+
@type.setter
554+
def type(self, v: Optional[List[Union[PhenomenologyType, str]]]) -> None:
555+
self._set_summary(TYPE_PROP, v)
556+
557+
@property
558+
def encoding(self) -> Optional[List[str]]:
559+
return self.summaries.get_list(ENCODING_PROP)
560+
561+
@encoding.setter
562+
def encoding(self, v: Optional[List[str]]) -> None:
563+
self._set_summary(ENCODING_PROP, v)
564+
565+
@property
566+
def density(self) -> Optional[RangeSummary[float]]:
567+
return self.summaries.get_range(DENSITY_PROP)
568+
569+
@density.setter
570+
def density(self, v: Optional[RangeSummary[float]]) -> None:
571+
self._set_summary(DENSITY_PROP, v)
572+
573+
@property
574+
def statistics(self) -> Optional[List[Statistic]]:
575+
return map_opt(
576+
lambda stats: [Statistic(d) for d in stats],
577+
self.summaries.get_list(STATISTICS_PROP),
578+
)
579+
580+
@statistics.setter
581+
def statistics(self, v: Optional[List[Statistic]]) -> None:
582+
self._set_summary(
583+
STATISTICS_PROP,
584+
map_opt(lambda stats: [s.to_dict() for s in stats], v),
585+
)
586+
587+
516588
class PointcloudExtensionHooks(ExtensionHooks):
517589
schema_uri: str = SCHEMA_URI
518590
prev_extension_ids = {"pointcloud"}

tests/extensions/test_pointcloud.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
SchemaType,
1616
Statistic,
1717
)
18+
from pystac.summaries import RangeSummary
1819
from tests.utils import TestCases, assert_to_from_dict
1920

2021

@@ -336,3 +337,119 @@ def test_asset_ext_add_to(self) -> None:
336337
_ = PointcloudExtension.ext(asset, add_if_missing=True)
337338

338339
self.assertIn(PointcloudExtension.get_schema_uri(), item.stac_extensions)
340+
341+
342+
class PointcloudSummariesTest(unittest.TestCase):
343+
def setUp(self) -> None:
344+
self.maxDiff = None
345+
self.collection = pystac.Collection.from_file(
346+
TestCases.get_path("data-files/collections/multi-extent.json")
347+
)
348+
349+
def test_count(self) -> None:
350+
collection = self.collection.clone()
351+
summaries_ext = PointcloudExtension.summaries(collection, True)
352+
count_range = RangeSummary(1000, 10000)
353+
354+
summaries_ext.count = count_range
355+
356+
self.assertEqual(
357+
summaries_ext.count,
358+
count_range,
359+
)
360+
361+
summaries_dict = collection.to_dict()["summaries"]
362+
363+
self.assertEqual(
364+
summaries_dict["pc:count"],
365+
count_range.to_dict(),
366+
)
367+
368+
def test_type(self) -> None:
369+
collection = self.collection.clone()
370+
summaries_ext = PointcloudExtension.summaries(collection, True)
371+
type_list = [PhenomenologyType.LIDAR, "something"]
372+
373+
summaries_ext.type = type_list
374+
375+
self.assertEqual(
376+
summaries_ext.type,
377+
type_list,
378+
)
379+
380+
summaries_dict = collection.to_dict()["summaries"]
381+
382+
self.assertEqual(
383+
summaries_dict["pc:type"],
384+
type_list,
385+
)
386+
387+
def test_encoding(self) -> None:
388+
collection = self.collection.clone()
389+
summaries_ext = PointcloudExtension.summaries(collection, True)
390+
encoding_list = ["LASzip"]
391+
392+
summaries_ext.encoding = encoding_list
393+
394+
self.assertEqual(
395+
summaries_ext.encoding,
396+
encoding_list,
397+
)
398+
399+
summaries_dict = collection.to_dict()["summaries"]
400+
401+
self.assertEqual(
402+
summaries_dict["pc:encoding"],
403+
encoding_list,
404+
)
405+
406+
def test_density(self) -> None:
407+
collection = self.collection.clone()
408+
summaries_ext = PointcloudExtension.summaries(collection, True)
409+
density_range = RangeSummary(500.0, 1000.0)
410+
411+
summaries_ext.density = density_range
412+
413+
self.assertEqual(
414+
summaries_ext.density,
415+
density_range,
416+
)
417+
418+
summaries_dict = collection.to_dict()["summaries"]
419+
420+
self.assertEqual(
421+
summaries_dict["pc:density"],
422+
density_range.to_dict(),
423+
)
424+
425+
def test_statistics(self) -> None:
426+
collection = self.collection.clone()
427+
summaries_ext = PointcloudExtension.summaries(collection, True)
428+
statistics_list = [
429+
Statistic(
430+
{
431+
"average": 637294.1783,
432+
"count": 10653336,
433+
"maximum": 639003.73,
434+
"minimum": 635577.79,
435+
"name": "X",
436+
"position": 0,
437+
"stddev": 967.9329805,
438+
"variance": 936894.2548,
439+
}
440+
)
441+
]
442+
443+
summaries_ext.statistics = statistics_list
444+
445+
self.assertEqual(
446+
summaries_ext.statistics,
447+
statistics_list,
448+
)
449+
450+
summaries_dict = collection.to_dict()["summaries"]
451+
452+
self.assertEqual(
453+
summaries_dict["pc:statistics"],
454+
[s.to_dict() for s in statistics_list],
455+
)

0 commit comments

Comments
 (0)