-
Notifications
You must be signed in to change notification settings - Fork 3
RDPS implementation with additional extensions #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 23 commits
d737a9d
6bde69e
911cbe9
b5f0de9
76b37a7
5e5541a
4d6aba0
3866246
138f0dd
cd5e801
eb86c50
bfdd825
49804f5
99868fc
24066be
90c30c1
9ea89d7
fc8806b
ad2bba9
6c45ef2
730763c
0ca5211
2e6a69e
a8c299d
ec29535
933352f
27e2a40
92bc676
57a6836
d8c7b97
2c207a7
c03b5cf
126aa4c
40fda07
c968b28
b75bb54
f22b369
7e6a764
c462ecd
e38ad09
dd7e480
4597fe9
29dc7e2
ea75a9b
26d9135
acaeea8
af3d1fe
ffa1021
5b60a09
af32a48
e24834a
e442359
85f70f1
faae3a1
24a546d
0b10d2a
0443acd
dfe1437
387baa7
11029e4
f904c4c
01ac7da
43a5d0b
29b2890
293a37e
64e39de
660265d
afbe940
64c33b6
b73c216
5b668e9
84ab682
1a0b8aa
4366e6b
227a7d5
30054ea
0dadae7
49f0d4a
185ddac
8cdbc59
3f033c2
1fea5bd
07696c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,3 +34,6 @@ build | |
| # Old Submodule Path | ||
| # Could be used locally | ||
| pyessv-archive/ | ||
|
|
||
| # Temp files downloaded | ||
| temp_files/ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is good and aligned with https://github.com/stac-utils/pystac/tree/main/pystac/extensions. Maybe consider opening a PR directly over there and push the change upstream.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For cross ref, I created this PR: stac-utils/pystac#1592
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean using the PR code version directly to import cf from pystac?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Using the latest commit hash of the branch.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just did, but the presence of the I can think of (1) adding a
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Try this first, and if that doesn't work, will see how to handle it.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes that's what I did and it didn't work
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. I suggest we wait and see what comes out from stac-utils/pystac#1592 (comment). Maybe it could be done fairly soon and avoid the issue altogether. If not, I guess it would be easier to keep the code as is in the meantime with a FIXME note until the PR is merged. Since the commit reference is not simple replacement in the dependencies, I think other workarounds would imply too many changes or manual steps leading to setup errors. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| """CF Extension Module.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import functools | ||
| from dataclasses import dataclass | ||
| from typing import ( | ||
| Any, | ||
| Generic, | ||
| Iterable, | ||
| List, | ||
| Literal, | ||
| Optional, | ||
| TypeVar, | ||
| Union, | ||
| cast, | ||
| get_args, | ||
| ) | ||
|
|
||
| import pystac | ||
| from dataclasses_json import dataclass_json | ||
| from pystac.extensions import item_assets | ||
| from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension, S | ||
|
|
||
| from STACpopulator.stac_utils import ServiceType | ||
|
|
||
| T = TypeVar("T", pystac.Collection, pystac.Item, pystac.Asset, item_assets.AssetDefinition) | ||
| SchemaName = Literal["cf"] | ||
| SCHEMA_URI = "https://stac-extensions.github.io/cf/v0.2.0/schema.json" | ||
| PREFIX = f"{get_args(SchemaName)[0]}:" | ||
| PARAMETER_PROP = PREFIX + "parameter" | ||
|
|
||
|
|
||
| def add_ext_prefix(name: str) -> str: | ||
| """Return the given name prefixed with this extension's prefix.""" | ||
| return PREFIX + name if "datetime" not in name else name | ||
|
|
||
|
|
||
| @dataclass_json | ||
| @dataclass | ||
| class CFParameter: | ||
| """CFParameter.""" | ||
|
|
||
| name: str | ||
| unit: Optional[str] | ||
|
|
||
| def __repr__(self) -> str: | ||
| """Return string repr.""" | ||
| return f"<CFParameter name={self.name}, unit={self.unit}>" | ||
|
|
||
|
|
||
| class CFHelper: | ||
| """CFHelper.""" | ||
|
|
||
| def __init__(self, variables: dict[str, any]) -> None: | ||
| """Take a STAC item variables to identify CF parameters metadata.""" | ||
| self.variables = variables | ||
|
|
||
| @functools.cached_property | ||
| def parameters(self) -> List[CFParameter]: | ||
| """Extracts cf:parameter-like information from item_data.""" | ||
| parameters = [] | ||
|
|
||
| for _, var in self.variables.items(): | ||
|
||
| attrs = var.get("attributes", {}) | ||
| name = attrs.get("standard_name") # Get the required standard name | ||
| if not name: | ||
| # Skip if no valid name | ||
| continue | ||
|
|
||
| unit = attrs.get("units") or "" | ||
|
||
| parameters.append(CFParameter(name=name, unit=unit)) | ||
|
|
||
| return parameters | ||
|
|
||
|
|
||
| class CFExtension( | ||
| Generic[T], | ||
| PropertiesExtension, | ||
| ExtensionManagementMixin[Union[pystac.Asset, pystac.Item, pystac.Collection]], | ||
| ): | ||
| """CF Metadata Extension.""" | ||
|
|
||
| @property | ||
| def name(self) -> SchemaName: | ||
| """Return the schema name.""" | ||
| return get_args(SchemaName)[0] | ||
|
|
||
| @property | ||
| def parameter(self) -> List[dict[str, Any]] | None: | ||
| """Get or set the CF parameter(s).""" | ||
| return self._get_property(PARAMETER_PROP, int) | ||
|
|
||
| @parameter.setter | ||
| def parameter(self, v: List[dict[str, Any]] | None) -> None: | ||
| self._set_property(PARAMETER_PROP, v) | ||
|
|
||
| def apply( | ||
| self, | ||
| parameters: Union[List[CFParameter], List[dict[str, Any]]], | ||
| ) -> None: | ||
| """Apply CF Extension properties to the extended :class:`~pystac.Item` or :class:`~pystac.Asset`.""" | ||
| if not isinstance(parameters[0], dict): | ||
| parameters = [p.to_dict() for p in parameters] | ||
| self.parameter = parameters | ||
|
|
||
| @classmethod | ||
| def get_schema_uri(cls) -> str: | ||
| """Return this extension's schema URI.""" | ||
| return SCHEMA_URI | ||
|
|
||
| @classmethod | ||
| def has_extension(cls, obj: S) -> bool: | ||
| """Return True iff the object has an extension for that matches this class' schema URI.""" | ||
| # FIXME: this override should be removed once an official and versioned schema is released | ||
| # ignore the original implementation logic for a version regex | ||
| # since in our case, the VERSION_REGEX is not fulfilled (ie: using 'main' branch, no tag available...) | ||
| ext_uri = cls.get_schema_uri() | ||
| return obj.stac_extensions is not None and any(uri == ext_uri for uri in obj.stac_extensions) | ||
|
|
||
| @classmethod | ||
| def ext(cls, obj: T, add_if_missing: bool = False) -> CFExtension[T]: | ||
| """Extend the given STAC Object with properties from the :stac-ext:`CF Extension <cf>`. | ||
| This extension can be applied to instances of :class:`~pystac.Item`, :class:`~pystac.Asset`, or :class:`~pystac.Collection`. | ||
| Raises | ||
| ------ | ||
| pystac.ExtensionTypeError : If an invalid object type is passed. | ||
| """ | ||
| if isinstance(obj, pystac.Collection): | ||
| cls.ensure_has_extension(obj, add_if_missing) | ||
| return cast(CFExtension[T], CollectionCFExtension(obj)) | ||
| elif isinstance(obj, pystac.Item): | ||
| cls.ensure_has_extension(obj, add_if_missing) | ||
| return cast(CFExtension[T], ItemCFExtension(obj)) | ||
| elif isinstance(obj, pystac.Asset): | ||
| cls.ensure_owner_has_extension(obj, add_if_missing) | ||
| return cast(CFExtension[T], AssetCFExtension(obj)) | ||
| elif isinstance(obj, item_assets.AssetDefinition): | ||
| cls.ensure_owner_has_extension(obj, add_if_missing) | ||
| return cast(CFExtension[T], ItemAssetsCFExtension(obj)) | ||
| else: | ||
| raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) | ||
|
|
||
|
|
||
| class ItemCFExtension(CFExtension[pystac.Item]): | ||
| """ | ||
| A concrete implementation of :class:`CFExtension` on an :class:`~pystac.Item`. | ||
| Extends the properties of the Item to include properties defined in the | ||
| :stac-ext:`CF Extension <cf>`. | ||
| This class should generally not be instantiated directly. Instead, call | ||
| :meth:`CFExtension.ext` on an :class:`~pystac.Item` to extend it. | ||
| """ | ||
|
|
||
| def __init__(self, item: pystac.Item) -> None: | ||
| self.item = item | ||
| self.properties = item.properties | ||
|
|
||
| def get_assets( | ||
| self, | ||
| service_type: Optional[ServiceType] = None, | ||
| ) -> dict[str, pystac.Asset]: | ||
| """Get the item's assets where eo:bands are defined. | ||
| Args: | ||
| service_type: If set, filter the assets such that only those with a | ||
| matching :class:`~STACpopulator.stac_utils.ServiceType` are returned. | ||
| Returns | ||
| ------- | ||
| Dict[str, Asset]: A dictionary of assets that match ``service_type`` | ||
| if set or else all of this item's assets were service types are defined. | ||
| """ | ||
| return { | ||
| key: asset | ||
| for key, asset in self.item.get_assets().items() | ||
| if (service_type is ServiceType and service_type.value in asset.extra_fields) | ||
|
||
| or any(ServiceType.from_value(field, default=None) is ServiceType for field in asset.extra_fields) | ||
| } | ||
|
|
||
| def __repr__(self) -> str: | ||
| """Return repr.""" | ||
| return f"<ItemCFExtension Item id={self.item.id}>" | ||
|
|
||
|
|
||
| class ItemAssetsCFExtension(CFExtension[item_assets.AssetDefinition]): | ||
| """Extention for CF item assets.""" | ||
|
|
||
| properties: dict[str, Any] | ||
| asset_defn: item_assets.AssetDefinition | ||
|
|
||
| def __init__(self, item_asset: item_assets.AssetDefinition) -> None: | ||
| self.asset_defn = item_asset | ||
| self.properties = item_asset.properties | ||
|
|
||
|
|
||
| class AssetCFExtension(CFExtension[pystac.Asset]): | ||
| """ | ||
| A concrete implementation of :class:`CFExtension` on an :class:`~pystac.Asset`. | ||
| Extends the Asset fields to include properties defined in the | ||
| :stac-ext:`CF Extension <cf>`. | ||
| This class should generally not be instantiated directly. Instead, call | ||
| :meth:`CFExtension.ext` on an :class:`~pystac.Asset` to extend it. | ||
| """ | ||
|
|
||
| asset_href: str | ||
| """The ``href`` value of the :class:`~pystac.Asset` being extended.""" | ||
|
|
||
| properties: dict[str, Any] | ||
| """The :class:`~pystac.Asset` fields, including extension properties.""" | ||
|
|
||
| additional_read_properties: Optional[Iterable[dict[str, Any]]] = None | ||
| """If present, this will be a list containing 1 dictionary representing the | ||
| properties of the owning :class:`~pystac.Item`.""" | ||
|
|
||
| def __init__(self, asset: pystac.Asset) -> None: | ||
| self.asset_href = asset.href | ||
| self.properties = asset.extra_fields | ||
| if asset.owner and isinstance(asset.owner, pystac.Item): | ||
| self.additional_read_properties = [asset.owner.properties] | ||
|
|
||
| def __repr__(self) -> str: | ||
| """Return repr.""" | ||
| return f"<AssetCFExtension Asset href={self.asset_href}>" | ||
|
|
||
|
|
||
| class CollectionCFExtension(CFExtension[pystac.Collection]): | ||
| """Extension for CF data.""" | ||
|
|
||
| def __init__(self, collection: pystac.Collection) -> None: | ||
| self.collection = collection | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| """Contact data model.""" | ||
|
|
||
| from dataclasses import dataclass, field | ||
| from typing import List, Optional | ||
|
|
||
| import pystac | ||
| from dataclasses_json import LetterCase, config, dataclass_json | ||
|
|
||
| SCHEMA_URI = "https://stac-extensions.github.io/contacts/v0.1.1/schema.json" | ||
|
||
|
|
||
|
|
||
| @dataclass_json | ||
| @dataclass | ||
| class Info: | ||
| """Gives contact information for and their "roles".""" | ||
|
|
||
| value: str | ||
| """The actual contact information, e.g. the phone number or the email address.""" | ||
|
|
||
| roles: Optional[List[str]] = None | ||
| """The type(s) of this contact information, e.g. whether it's at work or at home.""" | ||
|
|
||
|
|
||
| @dataclass_json | ||
| @dataclass | ||
| class Address: | ||
| """Physical location at which contact can be made.""" | ||
|
|
||
| deliveryPoint: Optional[List[str]] = field(metadata=config(letter_case=LetterCase.CAMEL), default=None) | ||
| """Address lines for the location, for example a street name and a house number.""" | ||
|
|
||
| city: Optional[str] = None | ||
| """City for the location.""" | ||
|
|
||
| administrativeArea: Optional[str] = field(metadata=config(letter_case=LetterCase.CAMEL), default=None) | ||
| """State or province of the location.""" | ||
|
|
||
| postalCode: Optional[str] = field(metadata=config(letter_case=LetterCase.CAMEL), default=None) | ||
| """ ZIP or other postal code.""" | ||
|
|
||
| country: Optional[str] = None | ||
| """Country of the physical address.""" | ||
|
|
||
|
|
||
| @dataclass_json | ||
| @dataclass | ||
| class Contact: | ||
| """Provides information about a contact.""" | ||
|
|
||
| name: Optional[str] = None | ||
| """The name of the responsible person. Required if organization is missing.""" | ||
|
|
||
| organization: Optional[str] = None | ||
| """Organization or affiliation of the contact. Required if name is missing""" | ||
|
|
||
| identifier: Optional[str] = None | ||
| """A value uniquely identifying the contact.""" | ||
|
|
||
| position: Optional[str] = None | ||
| """The name of the role or position of the responsible person.""" | ||
|
|
||
| description: Optional[str] = None | ||
| """Detailed multi-line description to fully explain the STAC entity.""" | ||
|
|
||
| logo: Optional[pystac.Link] = None | ||
| """Graphic identifying the contact.""" | ||
|
|
||
| phones: Optional[List[Info]] = None | ||
| """Telephone numbers at which contact can be made.""" | ||
|
|
||
| emails: Optional[List[Info]] = None | ||
| """Email address at which contact can be made.""" | ||
|
|
||
| addresses: Optional[List[Address]] = None | ||
| """Physical location at which contact can be made.""" | ||
|
|
||
| links: Optional[List[pystac.Link]] = None | ||
| """Links related to the contact.""" | ||
|
|
||
| contactInstructions: Optional[str] = field(metadata=config(letter_case=LetterCase.CAMEL), default=None) | ||
| """Supplemental instructions on how or when to contact the responsible party.""" | ||
|
|
||
| roles: Optional[List[str]] = None | ||
| """The set of named duties, job functions and/or permissions associated with this contact.""" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a new location that we're storing temporary files on disk? Why not use the users temp director or their cache directory if you want these files to persist for longer?