2323)
2424from pystac .extensions .hooks import ExtensionHooks
2525import pystac .extensions .item_assets as item_assets
26+ from pystac .extensions .raster import RasterExtension
2627from pystac .serialization .identify import STACJSONDescription , STACVersionID
2728from pystac .utils import get_required , map_opt
2829
3637CLASSES_PROP : str = PREFIX + "classes"
3738RASTER_BANDS_PROP : str = "raster:bands"
3839
40+
3941class Classification :
4042 properties : Dict [str , Any ]
4143
@@ -48,9 +50,7 @@ def apply(
4850 name : str ,
4951 description : Optional [str ] = None ,
5052 title : Optional [str ] = None ,
51- color_hint : Optional [
52- str
53- ] = None ,
53+ color_hint : Optional [str ] = None ,
5454 nodata : Optional [bool ] = None ,
5555 ) -> None :
5656 self .value = value
@@ -62,7 +62,9 @@ def apply(
6262
6363 color_hint_pattern = re .compile ("^([0-9A-F]{6})$" )
6464 match = color_hint_pattern .match (color_hint )
65- assert color_hint is None or match is not None and match .group () == color_hint , "Must format color hints as '^([0-9A-F]{6})$'"
65+ assert (
66+ color_hint is None or match is not None and match .group () == color_hint
67+ ), "Must format color hints as '^([0-9A-F]{6})$'"
6668
6769 @classmethod
6870 def create (
@@ -165,7 +167,7 @@ def apply(
165167 classes : List [Classification ],
166168 roles : Optional [List [str ]] = None ,
167169 description : Optional [str ] = None ,
168- name : Optional [str ] = None
170+ name : Optional [str ] = None ,
169171 ) -> None :
170172 self .offset = offset
171173 self .length = length
@@ -177,11 +179,9 @@ def apply(
177179 assert offset >= 0 , "Non-negative offsets only"
178180 assert length >= 1 , "Positive field lengths only"
179181 assert len (classes ) > 0 , "Must specify at least one class"
180- assert roles is None or len (roles ) > 0 , "When set, roles must contain at least one item"
181-
182- ## Ensure complete coverage of bit fields in classes
183- # class_coverage = set([c.value for c in classes])
184- # assert set(range(0, 2**length)) - class_coverage == set(), "Classes must cover the complete range of values"
182+ assert (
183+ roles is None or len (roles ) > 0
184+ ), "When set, roles must contain at least one item"
185185
186186 @classmethod
187187 def create (
@@ -222,7 +222,10 @@ def length(self, v: int) -> None:
222222
223223 @property
224224 def classes (self ) -> List [Classification ]:
225- return [Classification (d ) for d in get_required (self .properties .get ("classes" ), self , "classes" )]
225+ return [
226+ Classification (d )
227+ for d in get_required (self .properties .get ("classes" ), self , "classes" )
228+ ]
226229
227230 @classes .setter
228231 def classes (self , v : List [Classification ]) -> None :
@@ -262,29 +265,24 @@ def name(self, v: Optional[str]) -> None:
262265 self .properties .pop ("name" , None )
263266
264267 def __repr__ (self ) -> str :
265- return f"<Bitfield offset={ self .offset } length={ self .length } classes={ self .classes } >"
268+ return (
269+ f"<Bitfield offset={ self .offset } length={ self .length } "
270+ "classes={self.classes}>"
271+ )
266272
267273 def to_dict (self ) -> Dict [str , Any ]:
268274 return self .properties
269275
270276
271277def __fetch_from_dict (d : Dict [str , Any ]) -> Optional [Union [Classification , Bitfield ]]:
272- assert not (CLASSES_PROP in d and BITFIELDS_PROP in d ), f"Cannot define both { CLASSES_PROP } and { BITFIELDS :PROP} in the same entity"
278+ assert not (
279+ CLASSES_PROP in d and BITFIELDS_PROP in d
280+ ), f"Cannot define both { CLASSES_PROP } and { BITFIELDS_PROP } in the same entity"
273281
274282 if CLASSES_PROP in d :
275- return list (
276- map (
277- lambda item : Classification (item ),
278- d [CLASSES_PROP ]
279- )
280- )
283+ return list (map (lambda item : Classification (item ), d [CLASSES_PROP ]))
281284 elif BITFIELDS_PROP in d :
282- return list (
283- map (
284- lambda item : Bitfield (item ),
285- d [BITFIELDS_PROP ]
286- )
287- )
285+ return list (map (lambda item : Bitfield (item ), d [BITFIELDS_PROP ]))
288286 else :
289287 return None
290288
@@ -299,7 +297,12 @@ def apply(
299297 classes : Optional [List [Classification ]] = None ,
300298 bitfields : Optional [List [Bitfield ]] = None ,
301299 ) -> None :
302- # assert classes is None and bitfields is not None or bitfields is None and classes is not None, "Must set exactly one of `classes` or `bitfields`"
300+ assert (
301+ classes is None
302+ and bitfields is not None
303+ or bitfields is None
304+ and classes is not None
305+ ), "Must set exactly one of `classes` or `bitfields`"
303306 self .classes = classes
304307 self .bitfields = bitfields
305308
@@ -348,14 +351,17 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T]
348351 elif isinstance (obj , pystac .Asset ):
349352 cls .validate_owner_has_extension (obj , add_if_missing )
350353 return cast (ClassificationExtension [T ], AssetClassificationExtension (obj ))
351- elif isinstance (obj , pystac .Collection ):
352- cls .validate_has_extension (obj , add_if_missing )
353- return cast (ClassificationExtension [T ], CollectionClassificationExtension (obj ))
354354 elif isinstance (obj , item_assets .AssetDefinition ):
355- return cast (ClassificationExtension [T ], ItemAssetsClassificationExtension (obj ))
355+ cls .validate_has_extension (
356+ cast (Union [pystac .Item , pystac .Collection ], obj .owner ), add_if_missing
357+ )
358+ return cast (
359+ ClassificationExtension [T ], ItemAssetsClassificationExtension (obj )
360+ )
356361 else :
357362 raise pystac .ExtensionTypeError (
358- f"Classification extension does not apply to type '{ type (obj ).__name__ } '"
363+ "Classification extension does not apply to type "
364+ f"'{ type (obj ).__name__ } '"
359365 )
360366
361367 @classmethod
@@ -383,11 +389,11 @@ def _get_classes(self) -> Optional[List[Classification]]:
383389
384390 return None
385391
386- def _get_bitfields (self ) -> Optional [List [Bitfields ]]:
392+ def _get_bitfields (self ) -> Optional [List [Bitfield ]]:
387393 bitfields = self ._get_property (BITFIELDS_PROP , List [Dict [str , Any ]])
388394
389395 if bitfields is not None :
390- return [Bitfields (b ) for b in bitfields ]
396+ return [Bitfield (b ) for b in bitfields ]
391397
392398 return None
393399
@@ -403,13 +409,10 @@ class AssetClassificationExtension(ClassificationExtension[pystac.Asset]):
403409
404410 def _get_classes (self ) -> Optional [List [Classification ]]:
405411 if CLASSES_PROP not in self .properties :
406- if ( self .asset .owner and RasterExtension .has_extension (self .asset .owner ) ):
412+ if self .asset .owner and RasterExtension .has_extension (self .asset .owner ):
407413 bnds = RasterExtension .ext (self .asset ).bands
408414 return list (
409- map (
410- lambda b : Classification (b .properties .get (CLASSES_PROP )),
411- bnds
412- )
415+ map (lambda b : Classification (b .properties .get (CLASSES_PROP )), bnds )
413416 )
414417 else :
415418 return None
@@ -418,13 +421,10 @@ def _get_classes(self) -> Optional[List[Classification]]:
418421
419422 def _get_bitfields (self ) -> Optional [List [Bitfield ]]:
420423 if BITFIELDS_PROP not in self .properties :
421- if ( self .asset .owner and RasterExtension .has_extension (self .asset .owner ) ):
424+ if self .asset .owner and RasterExtension .has_extension (self .asset .owner ):
422425 bnds = RasterExtension .ext (self .asset ).bands
423426 return list (
424- map (
425- lambda b : Bitfield (b .properties .get (BITFIELDS_PROP )),
426- bnds
427- )
427+ map (lambda b : Bitfield (b .properties .get (BITFIELDS_PROP )), bnds )
428428 )
429429 else :
430430 return None
@@ -442,7 +442,9 @@ def __repr__(self) -> str:
442442 return f"<AssetClassificationExtension Asset href={ self .asset_href } >"
443443
444444
445- class ItemAssetsClassificationExtension (ClassificationExtension [item_assets .AssetDefinition ]):
445+ class ItemAssetsClassificationExtension (
446+ ClassificationExtension [item_assets .AssetDefinition ]
447+ ):
446448 properties : Dict [str , Any ]
447449 asset_defn : item_assets .AssetDefinition
448450
@@ -454,52 +456,30 @@ def __repr__(self) -> str:
454456 return f"<ItemAssetsClassificationExtension AssetDefinition={ self .asset_defn } "
455457
456458
457- class CollectionClassificationExtension (ClassificationExtension [pystac .Collection ]):
458- """A concrete implementation of :class:`ClassificationExtension` on a
459- :class:`~pystac.Collection` that extends the properties of a Collection to
460- include properties defined in the :stac_ext:`Classification Extension
461- <classification>`.
462- """
463- collection : pystac .Collection
464- properties : Dict [str , Any ]
459+ class SummariesClassificationExtension (SummariesExtension ):
460+ @property
461+ def classes (self ) -> Optional [List [Classification ]]:
462+ return map_opt (
463+ lambda classes : [Classification (c ) for c in classes ],
464+ self .summaries .get_list (CLASSES_PROP ),
465+ )
465466
466- def __init__ ( self , collection : pystac . Collection ):
467- self . collection = collection
468- self .properties = collection . assets
467+ @ classes . setter
468+ def classes ( self , v : Optional [ List [ Classification ]]) -> None :
469+ self ._set_summary ( CLASSES_PROP , map_opt ( lambda x : [ c . to_dict () for c in x ], v ))
469470
470- def _get_classes (self ) -> Optional [List [Optional [Classification ]]]:
471- classes = __fetch_from_dict (self .properties )
472- if classes is None and ItemAssetsExtension .has_extension (self .collection ):
473- classes = __fetch_from_dict (ItemAssetsExtension .ext (self .collection ).item_assets )
474- return list (filter (lambda x : isinstance (x , Classification ), classes ))
471+ @property
472+ def bitfields (self ) -> Optional [List [Bitfield ]]:
473+ return map_opt (
474+ lambda bitfields : [Bitfield (b ) for b in bitfields ],
475+ self .summaries .get_list (BITFIELDS_PROP ),
476+ )
475477
476- def _get_bitfields (self ) -> Optional [List [Bitfield ]]:
477- pass
478-
479- # class SummariesClassificationExtension(SummariesExtension):
480- # @property
481- # def classes(self) -> Optional[List[Classification]]:
482- # return map_opt(
483- # lambda classes: [Classification(c) for c in classes],
484- # self.summaries.get_list(CLASSES_PROP),
485- # )
486-
487- # @classes.setter
488- # def classes(self, v: Optional[List[Classification]]) -> None:
489- # self._set_summary(CLASSES_PROP, map_opt(lambda x: [c.to_dict() for c in x], v))
490-
491- # @property
492- # def bitfields(self) -> Optional[List[Bitfield]]:
493- # return map_opt(
494- # lambda bitfields: [Bitfield(b) for b in bitfields],
495- # self.summaries.get_list(BITFIELDS_PROP),
496- # )
497-
498- # @bitfields.setter
499- # def bitfields(self, v: Optional[List[Bitfield]]) -> None:
500- # self._set_summary(
501- # BITFIELDS_PROP, map_opt(lambda x: [b.to_dict() for b in x], v)
502- # )
478+ @bitfields .setter
479+ def bitfields (self , v : Optional [List [Bitfield ]]) -> None :
480+ self ._set_summary (
481+ BITFIELDS_PROP , map_opt (lambda x : [b .to_dict () for b in x ], v )
482+ )
503483
504484
505485class ClassificationExtensionHooks (ExtensionHooks ):
0 commit comments