cecil 0.0.28__tar.gz → 0.0.35__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.
@@ -9,3 +9,4 @@ __pycache__
9
9
  dist
10
10
  tmp
11
11
  venv
12
+ .venv
@@ -0,0 +1,23 @@
1
+ ## Development installation
2
+
3
+ Install packaging/distribution tools and linter:
4
+
5
+ ```shell
6
+ pip install hatch twine black
7
+ ```
8
+
9
+ From top-level repo directory, install the package in editable mode:
10
+
11
+ ```shell
12
+ pip install -e .
13
+ ```
14
+
15
+ Local edits to the package will immediately take effect.
16
+
17
+ Get the PyPI Test API Key from 1Password and add it to `~/.pypirc`:
18
+
19
+ ```bash
20
+ [testpypi]
21
+ username = __token__
22
+ password = <PyPI Test API Key>
23
+ ```
cecil-0.0.35/PKG-INFO ADDED
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: cecil
3
+ Version: 0.0.35
4
+ Summary: Python SDK for Cecil Earth
5
+ License-Expression: MIT
6
+ License-File: LICENSE.txt
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: dask==2025.11.0
13
+ Requires-Dist: pydantic<3.0.0,>=2.11.9
14
+ Requires-Dist: requests<3.0.0,>=2.32.5
15
+ Requires-Dist: rioxarray==0.19.0
16
+ Requires-Dist: snowflake-connector-python[pandas]<4.0.0,>=3.17.4
17
+ Requires-Dist: xarray==2025.11.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Cecil SDK
21
+
22
+ Please refer to the Cecil documentation:
23
+
24
+ https://docs.cecil.earth
cecil-0.0.35/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Cecil SDK
2
+
3
+ Please refer to the Cecil documentation:
4
+
5
+ https://docs.cecil.earth
@@ -16,12 +16,12 @@ classifiers = [
16
16
  "Operating System :: OS Independent",
17
17
  ]
18
18
  dependencies = [
19
- "dask==2025.9.1",
19
+ "dask==2025.11.0",
20
20
  "pydantic>=2.11.9,<3.0.0",
21
21
  "requests>=2.32.5,<3.0.0",
22
22
  "rioxarray==0.19.0",
23
23
  "snowflake-connector-python[pandas]>=3.17.4,<4.0.0",
24
- "xarray==2025.6.1"
24
+ "xarray==2025.11.0"
25
25
  ]
26
26
 
27
27
  [tool.hatch.version]
@@ -1,16 +1,15 @@
1
1
  import os
2
+ from typing import Dict, List, Optional
3
+ from warnings import warn
2
4
 
3
5
  import pandas as pd
4
6
  import requests
5
7
  import snowflake.connector
6
- import xarray
7
-
8
+ from cryptography.hazmat.primitives import serialization
8
9
  from pydantic import BaseModel
9
10
  from requests import auth
10
- from cryptography.hazmat.primitives import serialization
11
- from typing import Dict, List, Optional
12
- from warnings import warn
13
11
 
12
+ import xarray
14
13
  from .errors import (
15
14
  Error,
16
15
  _handle_bad_request,
@@ -35,11 +34,15 @@ from .models import (
35
34
  TransformationCreate,
36
35
  User,
37
36
  UserCreate,
38
- DataRequestMetadata,
39
- DataRequestParquetFiles,
37
+ SubscriptionMetadata,
38
+ SubscriptionParquetFiles,
39
+ SubscriptionListFiles,
40
+ Subscription,
41
+ SubscriptionCreate,
40
42
  )
41
43
  from .version import __version__
42
44
  from .xarray import load_xarray
45
+ from .xarray import load_xarray_v2
43
46
 
44
47
 
45
48
  class Client:
@@ -69,6 +72,11 @@ class Client:
69
72
  def create_data_request(
70
73
  self, aoi_id: str, dataset_id: str, external_ref: Optional[str] = None
71
74
  ) -> DataRequest:
75
+ warn(
76
+ "create_data_request() is deprecated, use create_subscription() instead.",
77
+ DeprecationWarning,
78
+ stacklevel=2,
79
+ )
72
80
  res = self._post(
73
81
  url="/v0/data-requests",
74
82
  model=DataRequestCreate(
@@ -78,22 +86,120 @@ class Client:
78
86
  return DataRequest(**res)
79
87
 
80
88
  def get_data_request(self, id: str) -> DataRequest:
89
+ warn(
90
+ "get_data_request() is deprecated, use get_subscription() instead.",
91
+ DeprecationWarning,
92
+ stacklevel=2,
93
+ )
81
94
  res = self._get(url=f"/v0/data-requests/{id}")
82
95
  return DataRequest(**res)
83
96
 
84
97
  def list_data_requests(self) -> List[DataRequest]:
98
+ warn(
99
+ "list_data_requests() is deprecated, use list_subscriptions() instead.",
100
+ DeprecationWarning,
101
+ stacklevel=2,
102
+ )
85
103
  res = self._get(url="/v0/data-requests")
86
104
  return [DataRequest(**record) for record in res["records"]]
87
105
 
88
- def load_xarray(self, data_request_id: str) -> xarray.Dataset:
89
- res = self._get(url=f"/v0/data-requests/{data_request_id}/metadata")
90
- metadata = DataRequestMetadata(**res)
91
- return load_xarray(metadata)
106
+ def list_subscriptions(self) -> List[Subscription]:
107
+ res = self._get(url="/v0/subscriptions")
108
+ return [Subscription(**record) for record in res["records"]]
92
109
 
93
- def load_dataframe(self, data_request_id: str) -> pd.DataFrame:
94
- res = self._get(url=f"/v0/data-requests/{data_request_id}/parquet-files")
95
- metadata = DataRequestParquetFiles(**res)
96
- df = pd.concat((pd.read_parquet(f) for f in metadata.files))
110
+ def create_subscription(
111
+ self, aoi_id: str, dataset_id: str, external_ref: Optional[str] = None
112
+ ) -> Subscription:
113
+ res = self._post(
114
+ url="/v0/subscriptions",
115
+ model=SubscriptionCreate(
116
+ aoi_id=aoi_id, dataset_id=dataset_id, external_ref=external_ref
117
+ ),
118
+ )
119
+
120
+ return Subscription(**res)
121
+
122
+ def get_subscription(self, id: str) -> Subscription:
123
+ res = self._get(url=f"/v0/subscriptions/{id}")
124
+ return Subscription(**res)
125
+
126
+ def load_xarray(
127
+ self,
128
+ subscription_id: Optional[str] = None,
129
+ data_request_id: Optional[str] = None,
130
+ ) -> xarray.Dataset:
131
+ if subscription_id is None and data_request_id is None:
132
+ raise TypeError("load_xarray() missing argument: 'subscription_id'")
133
+
134
+ if subscription_id is not None and data_request_id is not None:
135
+ raise ValueError(
136
+ "load_xarray() only accepts one argument but two were provided"
137
+ )
138
+
139
+ if data_request_id:
140
+ warn(
141
+ "data_request_id is deprecated, use subscription_id instead.",
142
+ DeprecationWarning,
143
+ stacklevel=2,
144
+ )
145
+ subscription_id = data_request_id
146
+
147
+ res = SubscriptionMetadata(
148
+ **self._get(url=f"/v0/subscriptions/{subscription_id}/metadata")
149
+ )
150
+ return load_xarray(res)
151
+
152
+ def _load_xarray_v2(
153
+ self,
154
+ subscription_id: Optional[str] = None,
155
+ data_request_id: Optional[str] = None,
156
+ ) -> xarray.Dataset:
157
+ if subscription_id is None and data_request_id is None:
158
+ raise TypeError("load_xarray_v2() missing argument: 'subscription_id'")
159
+
160
+ if subscription_id is not None and data_request_id is not None:
161
+ raise ValueError(
162
+ "load_xarray_v2() only accepts one argument but two were provided"
163
+ )
164
+
165
+ if data_request_id:
166
+ warn(
167
+ "data_request_id is deprecated, use subscription_id instead.",
168
+ DeprecationWarning,
169
+ stacklevel=2,
170
+ )
171
+ subscription_id = data_request_id
172
+
173
+ res = SubscriptionListFiles(
174
+ **self._get(url=f"/v0/subscriptions/{subscription_id}/files/tiff")
175
+ )
176
+ return load_xarray_v2(res)
177
+
178
+ def load_dataframe(
179
+ self,
180
+ subscription_id: Optional[str] = None,
181
+ data_request_id: Optional[str] = None,
182
+ ) -> pd.DataFrame:
183
+ if subscription_id is None and data_request_id is None:
184
+ raise TypeError("load_dataframe missing argument: 'subscription_id'")
185
+
186
+ if subscription_id is not None and data_request_id is not None:
187
+ raise ValueError(
188
+ "load_dataframe only accepts one argument but two were provided"
189
+ )
190
+
191
+ if data_request_id:
192
+ warn(
193
+ "data_request_id is deprecated, use subscription_id instead.",
194
+ DeprecationWarning,
195
+ stacklevel=2,
196
+ )
197
+ subscription_id = data_request_id
198
+
199
+ res = SubscriptionParquetFiles(
200
+ **self._get(url=f"/v0/subscriptions/{subscription_id}/parquet-files")
201
+ )
202
+ df = pd.concat((pd.read_parquet(f) for f in res.files))
97
203
  return df[
98
204
  [col for col in df.columns if col not in ("organisation_id", "created_at")]
99
205
  ]
@@ -202,12 +308,12 @@ class Client:
202
308
  def update_organisation_settings(
203
309
  self,
204
310
  *,
205
- monthly_data_request_limit,
311
+ monthly_subscription_limit,
206
312
  ) -> OrganisationSettings:
207
313
  res = self._post(
208
314
  url="/v0/organisation/settings",
209
315
  model=OrganisationSettings(
210
- monthly_data_request_limit=monthly_data_request_limit,
316
+ monthly_subscription_limit=monthly_subscription_limit,
211
317
  ),
212
318
  )
213
319
  return OrganisationSettings(**res)
@@ -1,7 +1,7 @@
1
1
  import datetime
2
2
  from typing import Dict, Optional, List
3
3
 
4
- from pydantic import BaseModel, ConfigDict, SecretStr
4
+ from pydantic import BaseModel, ConfigDict, Field, SecretStr
5
5
  from pydantic.alias_generators import to_camel
6
6
 
7
7
 
@@ -49,7 +49,9 @@ class DataRequestCreate(BaseModel):
49
49
 
50
50
  class OrganisationSettings(BaseModel):
51
51
  model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
52
- monthly_data_request_limit: Optional[int] = None
52
+ monthly_subscription_limit: Optional[int] = Field(
53
+ alias="monthlyDataRequestLimit",
54
+ )
53
55
 
54
56
 
55
57
  class RecoverAPIKey(BaseModel):
@@ -126,7 +128,7 @@ class File(BaseModel):
126
128
  bands: List[Band]
127
129
 
128
130
 
129
- class DataRequestMetadata(BaseModel):
131
+ class SubscriptionMetadata(BaseModel):
130
132
  model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
131
133
  provider_name: str
132
134
  dataset_id: str
@@ -137,6 +139,55 @@ class DataRequestMetadata(BaseModel):
137
139
  files: List[File]
138
140
 
139
141
 
140
- class DataRequestParquetFiles(BaseModel):
142
+ class Bucket(BaseModel):
143
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
144
+ name: str
145
+ prefix: str
146
+
147
+
148
+ class BucketCredentials(BaseModel):
149
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
150
+ access_key_id: str
151
+ secret_access_key: str
152
+ session_token: str
153
+ expiration: datetime.datetime
154
+
155
+
156
+ class FileMapping(BaseModel):
157
+ type: str
158
+ bands: List
159
+
160
+
161
+ class SubscriptionListFiles(BaseModel):
162
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
163
+ provider_name: str
164
+ dataset_id: str
165
+ dataset_name: str
166
+ aoi_id: str
167
+ data_request_id: str
168
+ bucket: Bucket
169
+ credentials: BucketCredentials
170
+ allowed_actions: List
171
+ file_mapping: Dict[str, FileMapping]
172
+
173
+
174
+ class SubscriptionParquetFiles(BaseModel):
141
175
  model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
142
176
  files: List[str]
177
+
178
+
179
+ class Subscription(BaseModel):
180
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
181
+ id: str
182
+ aoi_id: str
183
+ dataset_id: str
184
+ external_ref: Optional[str]
185
+ created_at: datetime.datetime
186
+ created_by: str
187
+
188
+
189
+ class SubscriptionCreate(BaseModel):
190
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
191
+ aoi_id: str
192
+ dataset_id: str
193
+ external_ref: Optional[str]
@@ -0,0 +1 @@
1
+ __version__ = "0.0.35"
@@ -0,0 +1,193 @@
1
+ import re
2
+ import time
3
+ from datetime import datetime
4
+
5
+ import boto3
6
+ import dask
7
+ import rasterio
8
+ import rasterio.session
9
+ import rioxarray
10
+ import xarray
11
+
12
+ from .models import SubscriptionMetadata, SubscriptionListFiles
13
+
14
+ # v1
15
+
16
+
17
+ def load_xarray(metadata: SubscriptionMetadata) -> xarray.Dataset:
18
+ data_vars = {}
19
+
20
+ for f in metadata.files:
21
+ try:
22
+ dataset = _retry_with_exponential_backoff(_load_file, 5, 1, 2, f.url)
23
+ except Exception as e:
24
+ raise ValueError(f"failed to load file: {e}")
25
+
26
+ for b in f.bands:
27
+ band = dataset.sel(band=b.number, drop=True)
28
+
29
+ if b.time and b.time_pattern:
30
+ t = datetime.strptime(b.time, b.time_pattern)
31
+ band = band.expand_dims("time")
32
+ band = band.assign_coords(time=[t])
33
+
34
+ band.name = b.variable_name
35
+
36
+ if b.variable_name not in data_vars:
37
+ data_vars[b.variable_name] = []
38
+
39
+ data_vars[b.variable_name].append(band)
40
+
41
+ for variable_name, time_series in data_vars.items():
42
+ if "time" in time_series[0].dims:
43
+ data_vars[variable_name] = xarray.concat(
44
+ time_series, dim="time", join="exact"
45
+ )
46
+ else:
47
+ data_vars[variable_name] = time_series[0]
48
+
49
+ return xarray.Dataset(
50
+ data_vars=data_vars,
51
+ attrs={
52
+ "provider_name": metadata.provider_name,
53
+ "dataset_name": metadata.dataset_name,
54
+ "dataset_id": metadata.dataset_id,
55
+ "aoi_id": metadata.aoi_id,
56
+ "subscription_id": metadata.data_request_id,
57
+ },
58
+ )
59
+
60
+
61
+ def _retry_with_exponential_backoff(
62
+ func, retries, start_delay, multiplier, *args, **kwargs
63
+ ):
64
+ delay = start_delay
65
+ for attempt in range(1, retries + 1):
66
+ try:
67
+ return func(*args, **kwargs)
68
+ except Exception as e:
69
+ if attempt == retries:
70
+ raise e
71
+ time.sleep(delay)
72
+ delay *= multiplier
73
+ return None
74
+
75
+
76
+ def _load_file(url: str):
77
+ return rioxarray.open_rasterio(
78
+ url,
79
+ chunks={"x": 2000, "y": 2000},
80
+ )
81
+
82
+
83
+ # v2
84
+
85
+
86
+ def load_xarray_v2(res: SubscriptionListFiles) -> xarray.Dataset:
87
+ session = boto3.session.Session(
88
+ aws_access_key_id=res.credentials.access_key_id,
89
+ aws_secret_access_key=res.credentials.secret_access_key,
90
+ aws_session_token=res.credentials.session_token,
91
+ )
92
+
93
+ keys = _list_keys_v2(session, res.bucket.name, res.bucket.prefix)
94
+
95
+ if not keys:
96
+ return xarray.Dataset()
97
+
98
+ timestamp_pattern = re.compile(r"\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2}")
99
+ data_vars = {}
100
+
101
+ with rasterio.env.Env(
102
+ session=rasterio.session.AWSSession(session),
103
+ ):
104
+ first_file = rioxarray.open_rasterio(
105
+ f"s3://{res.bucket.name}/{keys[0]}", chunks="auto"
106
+ )
107
+
108
+ for key in keys:
109
+ filename = key.split("/")[-1]
110
+
111
+ file_info = res.file_mapping.get(filename)
112
+ if not file_info:
113
+ continue
114
+
115
+ timestamp_str = timestamp_pattern.search(key).group()
116
+
117
+ for band_num, var_name in enumerate(file_info.bands, start=1):
118
+ lazy_array = dask.array.from_delayed(
119
+ dask.delayed(_load_file_v2)(
120
+ session, f"s3://{res.bucket.name}/{key}", band_num
121
+ ),
122
+ shape=(
123
+ first_file.rio.height,
124
+ first_file.rio.width,
125
+ ),
126
+ dtype=file_info.type,
127
+ )
128
+ band_da = xarray.DataArray(
129
+ lazy_array,
130
+ dims=("y", "x"),
131
+ coords={
132
+ "y": first_file.y.values,
133
+ "x": first_file.x.values,
134
+ },
135
+ # attrs=first_file.attrs.copy() # TODO: is it the same for all files?
136
+ )
137
+ # band_da.encoding = first_file.encoding.copy() # TODO: is it the same for all files?
138
+ band_da.rio.write_crs(first_file.rio.crs, inplace=True)
139
+ band_da.rio.write_transform(first_file.rio.transform(), inplace=True)
140
+
141
+ band_da.name = var_name
142
+
143
+ # Dataset with time dimension
144
+ if timestamp_str != "0000/00/00/00/00/00":
145
+ t = datetime.strptime(timestamp_str, "%Y/%m/%d/%H/%M/%S")
146
+ band_da = band_da.expand_dims("time")
147
+ band_da = band_da.assign_coords(time=[t])
148
+
149
+ if var_name not in data_vars:
150
+ data_vars[var_name] = []
151
+
152
+ data_vars[var_name].append(band_da)
153
+
154
+ for var_name, time_series in data_vars.items():
155
+ if "time" in time_series[0].dims:
156
+ data_vars[var_name] = xarray.concat(time_series, dim="time", join="exact")
157
+ else:
158
+ data_vars[var_name] = time_series[0]
159
+
160
+ return xarray.Dataset(
161
+ data_vars=data_vars,
162
+ attrs={
163
+ "provider_name": res.provider_name,
164
+ "dataset_name": res.dataset_name,
165
+ "dataset_id": res.dataset_id,
166
+ "aoi_id": res.aoi_id,
167
+ "subscription_id": res.data_request_id,
168
+ },
169
+ )
170
+
171
+
172
+ def _load_file_v2(aws_session: boto3.session.Session, url: str, band_num: int):
173
+ with rasterio.env.Env(
174
+ session=rasterio.session.AWSSession(aws_session),
175
+ ):
176
+ with rasterio.open(url) as src:
177
+ return src.read(band_num)
178
+
179
+
180
+ def _list_keys_v2(session: boto3.session.Session, bucket_name, prefix) -> list[str]:
181
+ s3_client = session.client("s3")
182
+ paginator = s3_client.get_paginator("list_objects_v2")
183
+ page_iterator = paginator.paginate(
184
+ Bucket=bucket_name,
185
+ Prefix=prefix,
186
+ )
187
+
188
+ keys = []
189
+ for page in page_iterator:
190
+ for obj in page.get("Contents", []):
191
+ keys.append(obj["Key"])
192
+
193
+ return keys
@@ -1,21 +0,0 @@
1
- ## Development installation
2
-
3
- Install packaging/distribution tools:
4
-
5
- ```shell
6
- pip install hatch twine
7
- ```
8
-
9
- Install linter
10
-
11
- ```shell
12
- pip install black
13
- ```
14
-
15
- From top-level repo directory, install the package in editable mode:
16
-
17
- ```shell
18
- pip install -e .
19
- ```
20
-
21
- Local edits to the package will immediately take effect.
cecil-0.0.28/PKG-INFO DELETED
@@ -1,122 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: cecil
3
- Version: 0.0.28
4
- Summary: Python SDK for Cecil Earth
5
- License-Expression: MIT
6
- License-File: LICENSE.txt
7
- Classifier: Development Status :: 4 - Beta
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Programming Language :: Python :: 3
11
- Requires-Python: >=3.10
12
- Requires-Dist: dask==2025.9.1
13
- Requires-Dist: pydantic<3.0.0,>=2.11.9
14
- Requires-Dist: requests<3.0.0,>=2.32.5
15
- Requires-Dist: rioxarray==0.19.0
16
- Requires-Dist: snowflake-connector-python[pandas]<4.0.0,>=3.17.4
17
- Requires-Dist: xarray==2025.6.1
18
- Description-Content-Type: text/markdown
19
-
20
- # Cecil SDK
21
-
22
- [![PyPI - Version](https://img.shields.io/pypi/v/cecil-sdk.svg)](https://pypi.org/project/cecil-sdk)
23
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cecil-sdk.svg)](https://pypi.org/project/cecil-sdk)
24
-
25
- -----
26
-
27
- ## Table of Contents
28
-
29
- - [Installation](#installation)
30
- - [Authentication](#authentication)
31
- - [License](#license)
32
- - [Examples](#examples)
33
-
34
- ## Installation
35
-
36
- ```shell
37
- pip install cecil
38
- ```
39
-
40
- ## Authentication
41
-
42
- Set `CECIL_API_KEY` environment variable to your Cecil API key.
43
-
44
- ## Examples
45
-
46
- ### Create an AOI and data request using the Cecil client
47
-
48
- ```python
49
- import cecil
50
-
51
- client = cecil.Client()
52
-
53
- my_aoi = client.create_aoi(
54
- name="My AOI",
55
- geometry={
56
- "type": "Polygon",
57
- "coordinates": [
58
- [
59
- [145.410408835, -42.004083838],
60
- [145.410408835, -42.004203978],
61
- [145.410623191, -42.004203978],
62
- [145.410623191, -42.004083838],
63
- [145.410408835, -42.004083838],
64
- ]
65
- ],
66
- },
67
- )
68
-
69
- # Get dataset ID from docs.cecil.earth -> Datasets
70
- planet_forest_carbon_diligence_id = "c2dd4f55-56f6-4d05-aae3-ba7c1dcd812f"
71
-
72
- my_data_request = client.create_data_request(
73
- aoi_id=my_aoi.id,
74
- dataset_id=planet_forest_carbon_diligence_id,
75
- )
76
-
77
- print(client.get_data_request(my_data_request.id))
78
- ```
79
-
80
- ### Create a transformation using the Cecil client
81
-
82
- ```python
83
- my_transformation = client.create_transformation(
84
- data_request_id=my_data_request.id,
85
- crs="EPSG:4326",
86
- spatial_resolution=0.005,
87
- )
88
-
89
- print(client.get_transformation(my_transformation.id))
90
- ```
91
-
92
- ### Query data (once transformation is completed)
93
-
94
- ```python
95
- df = client.query(f'''
96
- SELECT *
97
- FROM
98
- planet.forest_carbon_diligence
99
- WHERE
100
- transformation_id = '{my_transformation.id}'
101
- ''')
102
- ```
103
-
104
- ### Other client methods:
105
-
106
- ```python
107
- client.list_aois()
108
-
109
- client.get_aoi(my_aoi.id)
110
-
111
- client.list_data_requests()
112
-
113
- client.get_data_request(my_data_request.id)
114
-
115
- client.list_transformations()
116
-
117
- client.get_transformation(my_transformation.id)
118
- ```
119
-
120
- ## License
121
-
122
- `cecil` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
cecil-0.0.28/README.md DELETED
@@ -1,103 +0,0 @@
1
- # Cecil SDK
2
-
3
- [![PyPI - Version](https://img.shields.io/pypi/v/cecil-sdk.svg)](https://pypi.org/project/cecil-sdk)
4
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cecil-sdk.svg)](https://pypi.org/project/cecil-sdk)
5
-
6
- -----
7
-
8
- ## Table of Contents
9
-
10
- - [Installation](#installation)
11
- - [Authentication](#authentication)
12
- - [License](#license)
13
- - [Examples](#examples)
14
-
15
- ## Installation
16
-
17
- ```shell
18
- pip install cecil
19
- ```
20
-
21
- ## Authentication
22
-
23
- Set `CECIL_API_KEY` environment variable to your Cecil API key.
24
-
25
- ## Examples
26
-
27
- ### Create an AOI and data request using the Cecil client
28
-
29
- ```python
30
- import cecil
31
-
32
- client = cecil.Client()
33
-
34
- my_aoi = client.create_aoi(
35
- name="My AOI",
36
- geometry={
37
- "type": "Polygon",
38
- "coordinates": [
39
- [
40
- [145.410408835, -42.004083838],
41
- [145.410408835, -42.004203978],
42
- [145.410623191, -42.004203978],
43
- [145.410623191, -42.004083838],
44
- [145.410408835, -42.004083838],
45
- ]
46
- ],
47
- },
48
- )
49
-
50
- # Get dataset ID from docs.cecil.earth -> Datasets
51
- planet_forest_carbon_diligence_id = "c2dd4f55-56f6-4d05-aae3-ba7c1dcd812f"
52
-
53
- my_data_request = client.create_data_request(
54
- aoi_id=my_aoi.id,
55
- dataset_id=planet_forest_carbon_diligence_id,
56
- )
57
-
58
- print(client.get_data_request(my_data_request.id))
59
- ```
60
-
61
- ### Create a transformation using the Cecil client
62
-
63
- ```python
64
- my_transformation = client.create_transformation(
65
- data_request_id=my_data_request.id,
66
- crs="EPSG:4326",
67
- spatial_resolution=0.005,
68
- )
69
-
70
- print(client.get_transformation(my_transformation.id))
71
- ```
72
-
73
- ### Query data (once transformation is completed)
74
-
75
- ```python
76
- df = client.query(f'''
77
- SELECT *
78
- FROM
79
- planet.forest_carbon_diligence
80
- WHERE
81
- transformation_id = '{my_transformation.id}'
82
- ''')
83
- ```
84
-
85
- ### Other client methods:
86
-
87
- ```python
88
- client.list_aois()
89
-
90
- client.get_aoi(my_aoi.id)
91
-
92
- client.list_data_requests()
93
-
94
- client.get_data_request(my_data_request.id)
95
-
96
- client.list_transformations()
97
-
98
- client.get_transformation(my_transformation.id)
99
- ```
100
-
101
- ## License
102
-
103
- `cecil` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -1 +0,0 @@
1
- __version__ = "0.0.28"
@@ -1,74 +0,0 @@
1
- import os
2
- import rioxarray
3
- import xarray
4
-
5
- from datetime import datetime
6
-
7
- from .errors import Error
8
- from .models import DataRequestMetadata
9
-
10
- os.environ["GDAL_NUM_THREADS"] = "1"
11
- os.environ["GDAL_DISABLE_READDIR_ON_OPEN"] = "FALSE"
12
-
13
-
14
- def align_pixel_grids(time_series):
15
- # Use the first timestep as reference
16
- reference_da = time_series[0]
17
- aligned_series = [reference_da]
18
-
19
- # Align all other timesteps to the reference grid
20
- for i, da in enumerate(time_series[1:], 1):
21
- try:
22
- aligned_da = da.rio.reproject_match(reference_da)
23
- aligned_series.append(aligned_da)
24
- except Exception as e:
25
- raise Error
26
- continue
27
-
28
- return aligned_series
29
-
30
-
31
- def load_xarray(metadata: DataRequestMetadata) -> xarray.Dataset:
32
- data_vars = {}
33
-
34
- for f in metadata.files:
35
- dataset = rioxarray.open_rasterio(
36
- f.url,
37
- chunks={"x": 2000, "y": 2000},
38
- )
39
-
40
- for b in f.bands:
41
- band = dataset.sel(band=b.number, drop=True)
42
-
43
- if b.time and b.time_pattern:
44
- time = datetime.strptime(b.time, b.time_pattern)
45
- band = band.expand_dims("time")
46
- band = band.assign_coords(time=[time])
47
-
48
- band.name = b.variable_name
49
-
50
- if b.variable_name not in data_vars:
51
- data_vars[b.variable_name] = []
52
-
53
- data_vars[b.variable_name].append(band)
54
-
55
- for variable_name, time_series in data_vars.items():
56
- if "time" in time_series[0].dims:
57
- # time_series = align_pixel_grids(time_series)
58
- data_vars[variable_name] = xarray.concat(
59
- time_series, dim="time", join="exact"
60
- )
61
- else:
62
- data_vars[variable_name] = time_series[0]
63
-
64
- return xarray.Dataset(
65
- data_vars=data_vars,
66
- attrs={
67
- "provider_name": metadata.provider_name,
68
- "dataset_id": metadata.dataset_id,
69
- "dataset_name": metadata.dataset_name,
70
- "dataset_crs": metadata.dataset_crs,
71
- "aoi_id": metadata.aoi_id,
72
- "data_request_id": metadata.data_request_id,
73
- },
74
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes