maxar-platform 1.0.0__tar.gz
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.
- maxar_platform-1.0.0/LICENSE.txt +21 -0
- maxar_platform-1.0.0/PKG-INFO +70 -0
- maxar_platform-1.0.0/README.md +41 -0
- maxar_platform-1.0.0/maxar_platform/__init__.py +32 -0
- maxar_platform-1.0.0/maxar_platform/api_keys.py +179 -0
- maxar_platform-1.0.0/maxar_platform/discovery.py +163 -0
- maxar_platform-1.0.0/maxar_platform/exceptions.py +124 -0
- maxar_platform-1.0.0/maxar_platform/monitoring.py +171 -0
- maxar_platform-1.0.0/maxar_platform/ogc/__init__.py +323 -0
- maxar_platform-1.0.0/maxar_platform/ogc/footprints.py +55 -0
- maxar_platform-1.0.0/maxar_platform/ogc/imagery.py +60 -0
- maxar_platform-1.0.0/maxar_platform/ogc/seamlines.py +51 -0
- maxar_platform-1.0.0/maxar_platform/ordering.py +189 -0
- maxar_platform-1.0.0/maxar_platform/raster_analytics.py +179 -0
- maxar_platform-1.0.0/maxar_platform/session.py +397 -0
- maxar_platform-1.0.0/maxar_platform/tasking.py +158 -0
- maxar_platform-1.0.0/maxar_platform/usage.py +53 -0
- maxar_platform-1.0.0/pyproject.toml +74 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Maxar Intelligence
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: maxar-platform
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Maxar Geospatial Platform SDK
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Marc Pfister
|
|
7
|
+
Author-email: marc.pfister@maxar.com
|
|
8
|
+
Requires-Python: >=3.9,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Provides-Extra: rasterio
|
|
16
|
+
Requires-Dist: OWSLib (>=0.29.3,<0.30.0)
|
|
17
|
+
Requires-Dist: affine (>=2.4.0,<3.0.0)
|
|
18
|
+
Requires-Dist: pendulum (>=3.0.0,<4.0.0)
|
|
19
|
+
Requires-Dist: pygeofilter (>=0.2.1,<0.3.0)
|
|
20
|
+
Requires-Dist: pyproj (>=3.6.1,<4.0.0)
|
|
21
|
+
Requires-Dist: pystac-client (>=0.7.5,<0.8.0)
|
|
22
|
+
Requires-Dist: pytest-coverage (>=0.0,<0.1)
|
|
23
|
+
Requires-Dist: rasterio (>=1.3.9,<2.0.0) ; extra == "rasterio"
|
|
24
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
25
|
+
Requires-Dist: requests-oauthlib (>=2.0.0,<3.0.0)
|
|
26
|
+
Project-URL: Documentation, https://developers.maxar.com/docs/developer-tools/python-sdk
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# maxar-platform
|
|
30
|
+
|
|
31
|
+
A Python SDK for working with [Maxar Geospatial Platform](https://pro.maxar.com/)
|
|
32
|
+
|
|
33
|
+
Version: 1.0.0
|
|
34
|
+
|
|
35
|
+
## Introduction
|
|
36
|
+
|
|
37
|
+
`maxar-platform` provides wrappers around the MGP APIs with a focus on simplicity and productivity. It wraps popular and proven Python geospatial packages whenever possible, but also offers a core HTTP session object to use as a basis for more custom functionality.
|
|
38
|
+
|
|
39
|
+
### APIs supported:
|
|
40
|
+
|
|
41
|
+
#### Discovery
|
|
42
|
+
|
|
43
|
+
Discovery is Maxar's catalog API. `maxar_platform` extends `pystac-client` to provide STAC search to the user.
|
|
44
|
+
|
|
45
|
+
#### Streaming APIs (OGC)
|
|
46
|
+
|
|
47
|
+
[`owslib`](https://owslib.readthedocs.io/en/latest/) methods are available for OGC access to imagery and image footprint OGC endpoints. In addition, `maxar-platform` provides a search client with a similar interface to the Discovery module that queries the images available from these endpoints
|
|
48
|
+
|
|
49
|
+
#### Ordering, Monitoring, and Tasking APIs
|
|
50
|
+
|
|
51
|
+
These endpoints can be accessed through matching modules and functions.
|
|
52
|
+
|
|
53
|
+
#### Raster Analytics
|
|
54
|
+
|
|
55
|
+
Open any image available in Raster Analytics with `rasterio` as if the image was a Cloud Optimized Geotiff. Pixel data is generated on-the-fly.
|
|
56
|
+
|
|
57
|
+
#### Authentication
|
|
58
|
+
|
|
59
|
+
An underlying `session` object handles authentication and communication with the APIs. Also included is a module for managing API Keys.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
`pip install maxar-platform`
|
|
64
|
+
|
|
65
|
+
To use the Raster Analytics module, you will also need to install [`rasterio`](https://rasterio.readthedocs.io/en/stable/).
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
See the [MGP Developer Site](https://developers.maxar.com/docs/developer-tools/python-sdk)
|
|
70
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# maxar-platform
|
|
2
|
+
|
|
3
|
+
A Python SDK for working with [Maxar Geospatial Platform](https://pro.maxar.com/)
|
|
4
|
+
|
|
5
|
+
Version: 1.0.0
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
`maxar-platform` provides wrappers around the MGP APIs with a focus on simplicity and productivity. It wraps popular and proven Python geospatial packages whenever possible, but also offers a core HTTP session object to use as a basis for more custom functionality.
|
|
10
|
+
|
|
11
|
+
### APIs supported:
|
|
12
|
+
|
|
13
|
+
#### Discovery
|
|
14
|
+
|
|
15
|
+
Discovery is Maxar's catalog API. `maxar_platform` extends `pystac-client` to provide STAC search to the user.
|
|
16
|
+
|
|
17
|
+
#### Streaming APIs (OGC)
|
|
18
|
+
|
|
19
|
+
[`owslib`](https://owslib.readthedocs.io/en/latest/) methods are available for OGC access to imagery and image footprint OGC endpoints. In addition, `maxar-platform` provides a search client with a similar interface to the Discovery module that queries the images available from these endpoints
|
|
20
|
+
|
|
21
|
+
#### Ordering, Monitoring, and Tasking APIs
|
|
22
|
+
|
|
23
|
+
These endpoints can be accessed through matching modules and functions.
|
|
24
|
+
|
|
25
|
+
#### Raster Analytics
|
|
26
|
+
|
|
27
|
+
Open any image available in Raster Analytics with `rasterio` as if the image was a Cloud Optimized Geotiff. Pixel data is generated on-the-fly.
|
|
28
|
+
|
|
29
|
+
#### Authentication
|
|
30
|
+
|
|
31
|
+
An underlying `session` object handles authentication and communication with the APIs. Also included is a module for managing API Keys.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
`pip install maxar-platform`
|
|
36
|
+
|
|
37
|
+
To use the Raster Analytics module, you will also need to install [`rasterio`](https://rasterio.readthedocs.io/en/stable/).
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
See the [MGP Developer Site](https://developers.maxar.com/docs/developer-tools/python-sdk)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provides
|
|
3
|
+
--------
|
|
4
|
+
|
|
5
|
+
Streamlined access to these Maxar Geospatial Platform APIs:
|
|
6
|
+
|
|
7
|
+
- Authentication
|
|
8
|
+
- Discovery Catalog
|
|
9
|
+
- OGC Image streaming, vectors, and metadata
|
|
10
|
+
- Ordering
|
|
11
|
+
- Monitoring
|
|
12
|
+
- Tasking
|
|
13
|
+
- Raster Analytics
|
|
14
|
+
|
|
15
|
+
Coming soon:
|
|
16
|
+
|
|
17
|
+
- 3D Analytics
|
|
18
|
+
- Change Monitoring
|
|
19
|
+
|
|
20
|
+
Reference
|
|
21
|
+
---------
|
|
22
|
+
|
|
23
|
+
https://developers.maxar.com/
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__version__ = "1.0.0"
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
BASE_URL = os.environ.get("MGP_BASE_URL") or "https://api.maxar.com/"
|
|
31
|
+
CLIENT_ID = os.environ.get("MAXAR_CLIENT_ID") or "mgp"
|
|
32
|
+
PACKAGE = "maxar_platform"
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API keys are an alternative to OAuth2 authentication.
|
|
3
|
+
|
|
4
|
+
They are more compact than OAuth2 bearer tokens which makes them easier to send as URL parameters.
|
|
5
|
+
|
|
6
|
+
Unlike bearer tokens which expire in two hours, API keys have a default 6 month lifespan. It is also possible to
|
|
7
|
+
set the expiration date as short as needed. API keys can not be renewed or extended, once they expire a new one must
|
|
8
|
+
be created.
|
|
9
|
+
|
|
10
|
+
API keys have the same roles and scopes as your account but can not be used on any of the administrative endpoints:
|
|
11
|
+
|
|
12
|
+
- Admin API
|
|
13
|
+
- API Key Service
|
|
14
|
+
- Oauth2 Authentication
|
|
15
|
+
|
|
16
|
+
API Key dictionaries use a custom representations that mask out the API key string when they are displayed/printed.
|
|
17
|
+
|
|
18
|
+
>>> key = generate_key("my key")
|
|
19
|
+
>>> print(key)
|
|
20
|
+
|
|
21
|
+
{'id': '5431dd15-3efe-418a-b04d-acdca3a4d281',
|
|
22
|
+
'name': 'my key',
|
|
23
|
+
'description': '',
|
|
24
|
+
'api_key': '*******',
|
|
25
|
+
'last_modified_date': '2023-07-20 19:44:07',
|
|
26
|
+
'created_date': '2023-07-20 19:44:07',
|
|
27
|
+
'expiration_date': '2024-03-12 18:33:23'}
|
|
28
|
+
|
|
29
|
+
The `api_key` value is not otherwise masked, be careful with:
|
|
30
|
+
|
|
31
|
+
>>> print(key['api_key'])
|
|
32
|
+
<your key will print out!>
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
Reference
|
|
36
|
+
---------
|
|
37
|
+
|
|
38
|
+
https://developers.maxar.com/docs/authentication/guides/api-key
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
import pprint
|
|
43
|
+
from posixpath import join
|
|
44
|
+
|
|
45
|
+
import pendulum
|
|
46
|
+
|
|
47
|
+
from maxar_platform import BASE_URL
|
|
48
|
+
from maxar_platform.exceptions import MissingAPIResource
|
|
49
|
+
from maxar_platform.session import session
|
|
50
|
+
|
|
51
|
+
__all__ = ["generate_key", "list_keys", "delete_key", "get_key"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
KEYS_URL = join(BASE_URL, "api-key/api/v2/api-key")
|
|
55
|
+
KEYS_KEY = join(KEYS_URL, "id/:id")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def generate_key(name="", description="", expiration=None, lifespan=None):
|
|
59
|
+
"""Generate a new API key
|
|
60
|
+
|
|
61
|
+
Keywords
|
|
62
|
+
----------
|
|
63
|
+
name: str, optional
|
|
64
|
+
Name of key, while optional highly recommended.
|
|
65
|
+
description: str, optional
|
|
66
|
+
Description of key.
|
|
67
|
+
expiration: datelike object
|
|
68
|
+
Expiration date. Can be Date or Datetime objects, or strings of dates in common formats. Maximum lifespan is 6 months.
|
|
69
|
+
lifespan: int
|
|
70
|
+
Lifespan of keys in days if you don't want to compute an expiration date.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
KeyDict
|
|
75
|
+
Key as Python dictionary with special `str` and `repr` methods to mask keys from accidental display.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
expires_format = "YYYY-MM-DD HH:mm:ss"
|
|
80
|
+
|
|
81
|
+
payload = {"name": name, "description": description}
|
|
82
|
+
|
|
83
|
+
if expiration and lifespan:
|
|
84
|
+
raise ValueError("Tokens can be given an `expiration` OR a `lifespan`, not both")
|
|
85
|
+
|
|
86
|
+
if expiration:
|
|
87
|
+
expires = pendulum.parse(expiration)
|
|
88
|
+
payload["expiration_date"] = expires.format(expires_format)
|
|
89
|
+
|
|
90
|
+
if lifespan:
|
|
91
|
+
expires = pendulum.now().add(days=lifespan)
|
|
92
|
+
payload["expiration_date"] = expires.format(expires_format)
|
|
93
|
+
|
|
94
|
+
return KeyDict(session.oauth_session.post(KEYS_URL, json=payload).json())
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def list_keys():
|
|
98
|
+
"""List keys associated with your user
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
list of KeyDict
|
|
103
|
+
All of user's keys, including expired ones
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
return [KeyDict(k) for k in session.oauth_session.get(KEYS_URL).json()]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def delete_key(key_or_id):
|
|
110
|
+
"""Delete an API key
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
key: KeyDict or str
|
|
115
|
+
Either a key dictionary, or a key's ID string
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
None
|
|
120
|
+
|
|
121
|
+
Note
|
|
122
|
+
----
|
|
123
|
+
A key's ID is the 'id' value, not the 'api_key' value that is used for authentication.
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
if isinstance(key_or_id, dict):
|
|
128
|
+
key_id = key_or_id["id"]
|
|
129
|
+
else:
|
|
130
|
+
key_id = key_or_id
|
|
131
|
+
url = KEYS_KEY.replace(":id", key_id)
|
|
132
|
+
try:
|
|
133
|
+
session.oauth_session.delete(url)
|
|
134
|
+
except MissingAPIResource:
|
|
135
|
+
raise MissingAPIResource(f"API Key with ID {key_id} was not found")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_key(**kwargs):
|
|
139
|
+
"""Get a key
|
|
140
|
+
|
|
141
|
+
Keywords
|
|
142
|
+
----------
|
|
143
|
+
id: str, optional
|
|
144
|
+
name: str, optional
|
|
145
|
+
description: str, optional
|
|
146
|
+
api_key: str, optional
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
KeyDict
|
|
151
|
+
The first key found matching the supplied keyword arguments.
|
|
152
|
+
|
|
153
|
+
Note
|
|
154
|
+
----
|
|
155
|
+
You can match to any ONE of the KeyDict fields, however in most cases you would use ID or name.
|
|
156
|
+
Matching is exact, there is no support for wildcards or other regex magic at this time.
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
if len(kwargs) > 1:
|
|
160
|
+
raise ValueError("API keys can only be retrieved on a single property")
|
|
161
|
+
for k, v in kwargs.items():
|
|
162
|
+
for key in list_keys():
|
|
163
|
+
if key[k] == v:
|
|
164
|
+
return KeyDict(key)
|
|
165
|
+
raise MissingAPIResource(f"API Key with {k} `{v}` was not found")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class KeyDict(dict):
|
|
169
|
+
"""A dictionary class for keys that masks the key value when printed or displayed"""
|
|
170
|
+
|
|
171
|
+
def __str__(self):
|
|
172
|
+
masked = self.copy()
|
|
173
|
+
masked["api_key"] = "**************"
|
|
174
|
+
return pprint.pformat(masked, sort_dicts=False)
|
|
175
|
+
|
|
176
|
+
def __repr__(self):
|
|
177
|
+
masked = self.copy()
|
|
178
|
+
masked["api_key"] = "*******"
|
|
179
|
+
return str(masked)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
""" Discovery API
|
|
2
|
+
|
|
3
|
+
Discovery is Maxar's data catalog. Since it implements STAC API, this SDK uses `pystac_client` for access.
|
|
4
|
+
|
|
5
|
+
For search, open a catalog or subcatalog to get a STAC client.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from maxar_platform.discovery import open_catalog
|
|
9
|
+
open_catalog() # returns the root catalog, or pass a subcatalog name like "imagery"
|
|
10
|
+
results = stac_api_client.search(...)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Reference
|
|
14
|
+
---------
|
|
15
|
+
https://developers.maxar.com/docs/discovery/
|
|
16
|
+
|
|
17
|
+
https://pystac-client.readthedocs.io/en/latest/usage.html#itemsearch
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
from copy import deepcopy
|
|
24
|
+
from posixpath import join
|
|
25
|
+
|
|
26
|
+
from pygeofilter.backends.cql2_json import to_cql2
|
|
27
|
+
from pygeofilter.parsers.cql2_text import parse as cql2_parse
|
|
28
|
+
from pystac_client import Client
|
|
29
|
+
from pystac_client.exceptions import APIError
|
|
30
|
+
from pystac_client.stac_api_io import StacApiIO
|
|
31
|
+
|
|
32
|
+
from maxar_platform import BASE_URL
|
|
33
|
+
from maxar_platform.session import session
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
from typing import Any, Dict, Optional
|
|
38
|
+
|
|
39
|
+
DISCOVERY_URL = join(BASE_URL, "discovery/v1/")
|
|
40
|
+
DISCOVERY_CATALOG = join(DISCOVERY_URL, "catalogs/:subCatalogId")
|
|
41
|
+
|
|
42
|
+
__all__ = ["stac_api_client", "open_catalog"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MaxarStacApiIO(StacApiIO):
|
|
46
|
+
|
|
47
|
+
area_based_calc = False
|
|
48
|
+
|
|
49
|
+
def request(
|
|
50
|
+
self,
|
|
51
|
+
href: str,
|
|
52
|
+
method: Optional[str] = None,
|
|
53
|
+
headers: Optional[Dict[str, str]] = None,
|
|
54
|
+
parameters: Optional[Dict[str, Any]] = None,
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Makes a request to an http endpoint
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
href (str): The request URL
|
|
60
|
+
method (Optional[str], optional): The http method to use, 'GET' or 'POST'.
|
|
61
|
+
Defaults to None, which will result in 'GET' being used.
|
|
62
|
+
headers (Optional[Dict[str, str]], optional): Additional headers to include
|
|
63
|
+
in request. Defaults to None.
|
|
64
|
+
parameters (Optional[Dict[str, Any]], optional): parameters to send with
|
|
65
|
+
request. Defaults to None.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
APIError: raised if the server returns an error response
|
|
69
|
+
|
|
70
|
+
Return:
|
|
71
|
+
str: The decoded response from the endpoint
|
|
72
|
+
|
|
73
|
+
Note:
|
|
74
|
+
This follows the default StacApiIO call but uses the SDK's session object. It also converts
|
|
75
|
+
cql2 text queries to json since the Discovery API currently doesn't accept text queries in
|
|
76
|
+
POST queries.
|
|
77
|
+
"""
|
|
78
|
+
if method == "POST":
|
|
79
|
+
if parameters.get("filter-lang") == "cql2-text":
|
|
80
|
+
ast = cql2_parse(parameters["filter"])
|
|
81
|
+
parameters["filter"] = json.loads(to_cql2(ast))
|
|
82
|
+
parameters["filter-lang"] = "cql2-json"
|
|
83
|
+
if self.area_based_calc and parameters:
|
|
84
|
+
if "bbox" in parameters or "intersects" in parameters:
|
|
85
|
+
parameters.update({"area-based-calc": True})
|
|
86
|
+
resp = session.post(url=href, headers=headers, json=parameters)
|
|
87
|
+
else:
|
|
88
|
+
params = deepcopy(parameters) or {}
|
|
89
|
+
if self.area_based_calc and parameters:
|
|
90
|
+
if "bbox" in parameters or "intersects" in parameters:
|
|
91
|
+
params.update({"area-based-calc": 1})
|
|
92
|
+
resp = session.get(url=href, headers=headers, params=params)
|
|
93
|
+
try:
|
|
94
|
+
return resp.content.decode("utf-8")
|
|
95
|
+
except Exception as err:
|
|
96
|
+
raise APIError(str(err))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def open_catalog(catalog=None):
|
|
100
|
+
"""Open a pystac_client connection to a Discovery catalog
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
catalog : str, default None
|
|
105
|
+
Catalog or subcatalog name to open, i.e. "imagery". If omitted the root catalog is used.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
pystac_client.Client
|
|
110
|
+
A Client object, configured to use Maxar authentication and area-based calculations
|
|
111
|
+
|
|
112
|
+
Reference
|
|
113
|
+
---------
|
|
114
|
+
https://pystac-client.readthedocs.io/en/latest/api.html#collection-client"""
|
|
115
|
+
|
|
116
|
+
if not catalog:
|
|
117
|
+
url = DISCOVERY_URL
|
|
118
|
+
else:
|
|
119
|
+
url = DISCOVERY_CATALOG.replace(":subCatalogId", catalog)
|
|
120
|
+
|
|
121
|
+
client = Client.open(url, stac_io=MaxarStacApiIO())
|
|
122
|
+
|
|
123
|
+
def abc_getter(self):
|
|
124
|
+
return self._stac_io.area_based_calc
|
|
125
|
+
|
|
126
|
+
def abc_setter(self, val):
|
|
127
|
+
if type(val) is not bool:
|
|
128
|
+
raise ValueError("Value must be a boolean")
|
|
129
|
+
self._stac_io.area_based_calc = val
|
|
130
|
+
|
|
131
|
+
setattr(
|
|
132
|
+
client.__class__,
|
|
133
|
+
"area_based_calc",
|
|
134
|
+
property(abc_getter, abc_setter, doc="Return metadata with area-based calculations"),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return client
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
stac_api_client = open_catalog()
|
|
141
|
+
""" A pre-configured `pystac-client` instance ready to search the root catalog.
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
Attributes
|
|
145
|
+
----------
|
|
146
|
+
area_based_calc : Bool
|
|
147
|
+
Toggles area-based calculations, default is False
|
|
148
|
+
|
|
149
|
+
Reference
|
|
150
|
+
---------
|
|
151
|
+
https://pystac-client.readthedocs.io/en/latest/api.html#collection-client """
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# This is handy for certain searches
|
|
155
|
+
CATID_TO_COLLECTION = {
|
|
156
|
+
"1010": "qb01",
|
|
157
|
+
"1020": "wv01",
|
|
158
|
+
"1030": "wv02",
|
|
159
|
+
"1040": "wv03-vnir",
|
|
160
|
+
"104A": "wv03-swir",
|
|
161
|
+
"1050": "ge01",
|
|
162
|
+
"1060": "ik02",
|
|
163
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
""" SDK Exceptions """
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"MissingDependency",
|
|
7
|
+
"APIServerException",
|
|
8
|
+
"BadAPIRequest",
|
|
9
|
+
"MissingAPIResource",
|
|
10
|
+
"OversizeRequestException",
|
|
11
|
+
"UnAuthorizedException",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
#########
|
|
15
|
+
# General Errors
|
|
16
|
+
#########
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MissingDependency(Exception):
|
|
20
|
+
"""A dependency required for this functionality has not been installed"""
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#########
|
|
26
|
+
# API Errors
|
|
27
|
+
#########
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class APIServerException(Exception):
|
|
31
|
+
"""The API service encountered an error"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, msg, *args, **kwargs):
|
|
34
|
+
msg = f"{msg} ({datetime.now()})"
|
|
35
|
+
super().__init__(msg, *args, **kwargs)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BadAPIRequest(Exception):
|
|
39
|
+
"""The API request sent was incorrectly formatted"""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MissingAPIResource(Exception):
|
|
45
|
+
"""An API request returned a 404"""
|
|
46
|
+
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class OversizeRequestException(Exception):
|
|
51
|
+
"""The API request sent was incorrectly formatted"""
|
|
52
|
+
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class UnAuthorizedException(Exception):
|
|
57
|
+
"""The API request's credentials were rejected"""
|
|
58
|
+
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
########
|
|
63
|
+
# Hook functions for Request.Session.hooks
|
|
64
|
+
########
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def messenger(request):
|
|
68
|
+
try:
|
|
69
|
+
payload = request.json()
|
|
70
|
+
message = payload.get("message")
|
|
71
|
+
messages = payload.get("error").get("error_messages")
|
|
72
|
+
if messages:
|
|
73
|
+
messages = "; ".join(messages)
|
|
74
|
+
return message or messages or request.text
|
|
75
|
+
except:
|
|
76
|
+
return request.text
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def oversize_request_hook(r, *args, **kwargs):
|
|
80
|
+
"""
|
|
81
|
+
Hook for handling oversize request errors from API.
|
|
82
|
+
"""
|
|
83
|
+
if r.status_code == 413 and "Request Entity Too Large" in r.json()["message"]:
|
|
84
|
+
raise OversizeRequestException(f"{messenger(r)}, Reduce size of attributes and try again.")
|
|
85
|
+
|
|
86
|
+
return r
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def bad_api_request_hook(r, *args, **kwargs):
|
|
90
|
+
"""
|
|
91
|
+
Hook for handling 400 errors from API.
|
|
92
|
+
"""
|
|
93
|
+
if r.status_code == 400:
|
|
94
|
+
raise BadAPIRequest(messenger(r))
|
|
95
|
+
return r
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def unauth_request_hook(r, *args, **kwargs):
|
|
99
|
+
"""
|
|
100
|
+
Hook for handling 401 errors from API.
|
|
101
|
+
"""
|
|
102
|
+
if r.status_code in [401, 403]:
|
|
103
|
+
raise UnAuthorizedException(
|
|
104
|
+
f"Server responded with {r.status_code} '{messenger(r)}', check your credentials and try again."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return r
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def missing_resource_hook(r, *args, **kwargs):
|
|
111
|
+
if r.status_code == 404:
|
|
112
|
+
raise MissingAPIResource(messenger(r))
|
|
113
|
+
|
|
114
|
+
return r
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def api_server_request_hook(r, *args, **kwargs):
|
|
118
|
+
"""
|
|
119
|
+
Hook for handling non-20X errors from API.
|
|
120
|
+
"""
|
|
121
|
+
if not r.ok:
|
|
122
|
+
raise APIServerException(messenger(r))
|
|
123
|
+
|
|
124
|
+
return r
|