cecil 0.0.19__tar.gz → 0.0.21__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.
Potentially problematic release.
This version of cecil might be problematic. Click here for more details.
- {cecil-0.0.19 → cecil-0.0.21}/PKG-INFO +1 -1
- {cecil-0.0.19 → cecil-0.0.21}/src/cecil/client.py +3 -19
- {cecil-0.0.19 → cecil-0.0.21}/src/cecil/models.py +9 -16
- cecil-0.0.21/src/cecil/version.py +1 -0
- cecil-0.0.19/notebooks/.ipynb_checkpoints/cecil-cross-comparisons-checkpoint.ipynb +0 -552
- cecil-0.0.19/notebooks/.ipynb_checkpoints/organisation_settings_test-checkpoint.ipynb +0 -6
- cecil-0.0.19/notebooks/.ipynb_checkpoints/planet_transformation_bugfix-checkpoint.ipynb +0 -62
- cecil-0.0.19/notebooks/.ipynb_checkpoints/test_auth_policy-checkpoint.ipynb +0 -6
- cecil-0.0.19/notebooks/cecil-cross-comparisons.ipynb +0 -552
- cecil-0.0.19/notebooks/organisation_settings_test.ipynb +0 -213
- cecil-0.0.19/notebooks/parquet.ipynb +0 -461
- cecil-0.0.19/notebooks/planet_transformation_bugfix.ipynb +0 -471
- cecil-0.0.19/notebooks/test_auth_policy.ipynb +0 -130
- cecil-0.0.19/src/cecil/version.py +0 -1
- {cecil-0.0.19 → cecil-0.0.21}/.editorconfig +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/.gitignore +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/CONTRIBUTING.md +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/LICENSE.txt +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/Makefile +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/README.md +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/pyproject.toml +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/src/cecil/__init__.py +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/src/cecil/errors.py +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/tests/__init__.py +0 -0
- {cecil-0.0.19 → cecil-0.0.21}/tests/test_client.py +0 -0
|
@@ -17,17 +17,15 @@ from .errors import (
|
|
|
17
17
|
)
|
|
18
18
|
from .models import (
|
|
19
19
|
AOI,
|
|
20
|
+
AOIRecord,
|
|
20
21
|
AOICreate,
|
|
21
22
|
DataRequest,
|
|
22
23
|
DataRequestCreate,
|
|
23
|
-
OrganisationCreate,
|
|
24
24
|
OrganisationSettings,
|
|
25
25
|
RecoverAPIKey,
|
|
26
26
|
RecoverAPIKeyRequest,
|
|
27
27
|
RotateAPIKey,
|
|
28
28
|
RotateAPIKeyRequest,
|
|
29
|
-
SignUpRequest,
|
|
30
|
-
SignUpResponse,
|
|
31
29
|
SnowflakeUserCredentials,
|
|
32
30
|
Transformation,
|
|
33
31
|
TransformationCreate,
|
|
@@ -54,9 +52,9 @@ class Client:
|
|
|
54
52
|
res = self._get(url=f"/v0/aois/{id}")
|
|
55
53
|
return AOI(**res)
|
|
56
54
|
|
|
57
|
-
def list_aois(self) -> List[
|
|
55
|
+
def list_aois(self) -> List[AOIRecord]:
|
|
58
56
|
res = self._get(url="/v0/aois")
|
|
59
|
-
return [
|
|
57
|
+
return [AOIRecord(**record) for record in res["records"]]
|
|
60
58
|
|
|
61
59
|
def create_data_request(self, aoi_id: str, dataset_id: str) -> DataRequest:
|
|
62
60
|
res = self._post(
|
|
@@ -128,20 +126,6 @@ class Client:
|
|
|
128
126
|
|
|
129
127
|
return RotateAPIKey(**res)
|
|
130
128
|
|
|
131
|
-
def sign_up(
|
|
132
|
-
self, organisation: Dict[str, str], user: Dict[str, str]
|
|
133
|
-
) -> SignUpResponse:
|
|
134
|
-
res = self._post(
|
|
135
|
-
url="/v0/sign-up",
|
|
136
|
-
model=SignUpRequest(
|
|
137
|
-
organisation=OrganisationCreate(**organisation),
|
|
138
|
-
user=UserCreate(**user),
|
|
139
|
-
),
|
|
140
|
-
skip_auth=True,
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
return SignUpResponse(**res)
|
|
144
|
-
|
|
145
129
|
def create_user(self, first_name: str, last_name: str, email: str) -> User:
|
|
146
130
|
res = self._post(
|
|
147
131
|
url="/v0/users",
|
|
@@ -15,6 +15,15 @@ class AOI(BaseModel):
|
|
|
15
15
|
created_by: str
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
class AOIRecord(BaseModel):
|
|
19
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
20
|
+
id: str
|
|
21
|
+
name: str
|
|
22
|
+
hectares: float
|
|
23
|
+
created_at: datetime.datetime
|
|
24
|
+
created_by: str
|
|
25
|
+
|
|
26
|
+
|
|
18
27
|
class AOICreate(BaseModel):
|
|
19
28
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
20
29
|
name: str
|
|
@@ -36,11 +45,6 @@ class DataRequestCreate(BaseModel):
|
|
|
36
45
|
dataset_id: str
|
|
37
46
|
|
|
38
47
|
|
|
39
|
-
class OrganisationCreate(BaseModel):
|
|
40
|
-
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
41
|
-
name: str
|
|
42
|
-
|
|
43
|
-
|
|
44
48
|
class OrganisationSettings(BaseModel):
|
|
45
49
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
46
50
|
monthly_data_request_limit: Optional[int] = None
|
|
@@ -104,14 +108,3 @@ class UserCreate(BaseModel):
|
|
|
104
108
|
first_name: str
|
|
105
109
|
last_name: str
|
|
106
110
|
email: str
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class SignUpRequest(BaseModel):
|
|
110
|
-
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
111
|
-
organisation: OrganisationCreate
|
|
112
|
-
user: UserCreate
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
class SignUpResponse(BaseModel):
|
|
116
|
-
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
117
|
-
message: str
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.21"
|
|
@@ -1,552 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cells": [
|
|
3
|
-
{
|
|
4
|
-
"cell_type": "markdown",
|
|
5
|
-
"metadata": {},
|
|
6
|
-
"source": [
|
|
7
|
-
"# Cross-comparisons on Cecil\n",
|
|
8
|
-
"*Tom Walker (tom[at]cecil.earth)*\n",
|
|
9
|
-
"\n",
|
|
10
|
-
"This notebook accompanies the Cecil newsletter ***[Dataset Evaluation](https://newsletter.cecil.earth/p/operating-in-spite-of-uncertainty)***. \n",
|
|
11
|
-
"\n",
|
|
12
|
-
"The notebook walks through doing some basic cross-comparisons of three plant biomass datasets on Cecil. The notebook:\n",
|
|
13
|
-
"\n",
|
|
14
|
-
"* Creates an AOI\n",
|
|
15
|
-
"* Makes data requests for three datasets\n",
|
|
16
|
-
"* Transforms the datasets to the same CRS and spatial resolution\n",
|
|
17
|
-
"* Queries the data and calculates summary statistics\n",
|
|
18
|
-
"* Creates three basic cross-comparison visualisations:\n",
|
|
19
|
-
" * Pairwise scatter graphs showing correlations of pixel AGB values across datasets\n",
|
|
20
|
-
" * Difference maps showing spatial variation in AGB values among datasets\n",
|
|
21
|
-
" * Time series graphs showing temporal variation in AGB values among datasets\n",
|
|
22
|
-
"\n",
|
|
23
|
-
"The notebook assumes you have followed the first three steps of the [getting started](https://docs.cecil.earth/Getting-started-111ef16bbbe48123aaa1d0a4bbd0a63d) guide to:\n",
|
|
24
|
-
"\n",
|
|
25
|
-
"1. Install the Cecil SDK.\n",
|
|
26
|
-
"2. Sign up for an account.\n",
|
|
27
|
-
"3. Configure the SDK with your user API key.\n",
|
|
28
|
-
"\n",
|
|
29
|
-
"\n",
|
|
30
|
-
"Once this is complete, begin by importing `GeoPandas`, `Matplotlib` and `Cecil`.\n",
|
|
31
|
-
"\n"
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"cell_type": "code",
|
|
36
|
-
"execution_count": null,
|
|
37
|
-
"metadata": {},
|
|
38
|
-
"outputs": [],
|
|
39
|
-
"source": [
|
|
40
|
-
"import geopandas as gpd\n",
|
|
41
|
-
"import matplotlib.pyplot as plt\n",
|
|
42
|
-
"plt.rcParams['axes.formatter.useoffset'] = False # removes offset on plots\n",
|
|
43
|
-
"\n",
|
|
44
|
-
"import cecil\n",
|
|
45
|
-
"client = cecil.Client()"
|
|
46
|
-
]
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"cell_type": "markdown",
|
|
50
|
-
"metadata": {},
|
|
51
|
-
"source": [
|
|
52
|
-
"___"
|
|
53
|
-
]
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"cell_type": "markdown",
|
|
57
|
-
"metadata": {},
|
|
58
|
-
"source": [
|
|
59
|
-
"## Create an AOI, make data requests and transform data\n",
|
|
60
|
-
"\n",
|
|
61
|
-
"### Create an AOI\n",
|
|
62
|
-
"\n",
|
|
63
|
-
"Use the code below to create an AOI on your account. Replace `aoi_name` with your own name and `aoi_geometry` with a geojson object containing a polygon of the AOI boundary.\n",
|
|
64
|
-
"\n",
|
|
65
|
-
"Store the AOI ID (`aoi.id`) so you can retrieve it later (see [SDK documentation](https://docs.cecil.earth/SDK-11fef16bbbe480b5a778ccae542cf34d) for details).\n",
|
|
66
|
-
"\n",
|
|
67
|
-
"*NB - you only need to create an AOI once.*"
|
|
68
|
-
]
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"cell_type": "code",
|
|
72
|
-
"execution_count": null,
|
|
73
|
-
"metadata": {},
|
|
74
|
-
"outputs": [],
|
|
75
|
-
"source": [
|
|
76
|
-
"# store aoi name and geometry (geojson polygon)\n",
|
|
77
|
-
"aoi_name = 'Kakadu National Park'\n",
|
|
78
|
-
"aoi_geometry = {\n",
|
|
79
|
-
" \"type\": \"Polygon\",\n",
|
|
80
|
-
" \"coordinates\": [\n",
|
|
81
|
-
" [\n",
|
|
82
|
-
" [132.52934211276073, -12.721072673008706],\n",
|
|
83
|
-
" [132.52934211276073, -12.730063400794094],\n",
|
|
84
|
-
" [132.54027735328083, -12.730063400794094],\n",
|
|
85
|
-
" [132.54027735328083, -12.721072673008706],\n",
|
|
86
|
-
" [132.52934211276073, -12.721072673008706]\n",
|
|
87
|
-
" ]\n",
|
|
88
|
-
" ]\n",
|
|
89
|
-
"}\n",
|
|
90
|
-
"\n",
|
|
91
|
-
"# create aoi on cecil\n",
|
|
92
|
-
"aoi = client.create_aoi(\n",
|
|
93
|
-
" name=aoi_name,\n",
|
|
94
|
-
" geometry=aoi_geometry\n",
|
|
95
|
-
")"
|
|
96
|
-
]
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
"cell_type": "code",
|
|
100
|
-
"execution_count": null,
|
|
101
|
-
"metadata": {},
|
|
102
|
-
"outputs": [],
|
|
103
|
-
"source": [
|
|
104
|
-
"# print aoi id\n",
|
|
105
|
-
"print(f'AOI ID: {aoi.id}')"
|
|
106
|
-
]
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
"cell_type": "markdown",
|
|
110
|
-
"metadata": {},
|
|
111
|
-
"source": [
|
|
112
|
-
"### Make data requests\n",
|
|
113
|
-
"\n",
|
|
114
|
-
"For this exercise, we will request data from three datasets:\n",
|
|
115
|
-
"\n",
|
|
116
|
-
"* [Chloris Aboveground Biomass Stock and Change (30 m)](https://docs.cecil.earth/Aboveground-biomass-stock-and-change-30-m-111ef16bbbe48163a502ed2bcb64fc72)\n",
|
|
117
|
-
"* [Kanop Screening (25 m)](https://docs.cecil.earth/Screening-25-m-111ef16bbbe481a780bce4de81a500a5)\n",
|
|
118
|
-
"* [Planet Forest Carbon Diligence dataset (30 m)](https://docs.cecil.earth/Forest-carbon-diligence-111ef16bbbe481d9a430c446c2bb4d04)\n",
|
|
119
|
-
"\n",
|
|
120
|
-
"The following code block stores dataset IDs and list prices (links above) and uses the AOI size (`aoi.hectares`) to estimate the total data acquisition cost for your AOI."
|
|
121
|
-
]
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
"cell_type": "code",
|
|
125
|
-
"execution_count": null,
|
|
126
|
-
"metadata": {},
|
|
127
|
-
"outputs": [],
|
|
128
|
-
"source": [
|
|
129
|
-
"# get dataset ids\n",
|
|
130
|
-
"chloris_dataset_id = 'e837b8b1-1002-4ff1-a4ca-fb6a7ac928fd'\n",
|
|
131
|
-
"kanop_dataset_id = 'f79af205-e45d-45b2-99fb-5108d83bd6f5'\n",
|
|
132
|
-
"planet_dataset_id = '53738a57-a889-43c9-8f7a-7cb306831700'\n",
|
|
133
|
-
"\n",
|
|
134
|
-
"# get acquisition costs\n",
|
|
135
|
-
"chloris_dataset_price = 0.09\n",
|
|
136
|
-
"kanop_dataset_price = 0.10\n",
|
|
137
|
-
"planet_dataset_price = 0.10\n",
|
|
138
|
-
"\n",
|
|
139
|
-
"# print cost\n",
|
|
140
|
-
"cost = chloris_dataset_price * aoi.hectares + kanop_dataset_price * aoi.hectares + planet_dataset_price * aoi.hectares\n",
|
|
141
|
-
"print(f'Approximate acquisition cost: ${round(cost, 2)}')"
|
|
142
|
-
]
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
"cell_type": "markdown",
|
|
146
|
-
"metadata": {},
|
|
147
|
-
"source": [
|
|
148
|
-
"Use the following code block to make a data request for each dataset (dataset IDs) against your AOI (`aoi.id`). \n",
|
|
149
|
-
"\n",
|
|
150
|
-
"Store data request IDs (`data_request.id`) so you can retrieve them later (see [SDK documentation](https://docs.cecil.earth/SDK-11fef16bbbe480b5a778ccae542cf34d)).\n",
|
|
151
|
-
"\n",
|
|
152
|
-
"*NB - you only need to make the data requests once. It may take some hours for a data request to complete.*"
|
|
153
|
-
]
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
"cell_type": "code",
|
|
157
|
-
"execution_count": null,
|
|
158
|
-
"metadata": {},
|
|
159
|
-
"outputs": [],
|
|
160
|
-
"source": [
|
|
161
|
-
"# request datasets\n",
|
|
162
|
-
"chloris_request = client.create_data_request(\n",
|
|
163
|
-
" aoi_id=aoi.id,\n",
|
|
164
|
-
" dataset_id=chloris_dataset_id\n",
|
|
165
|
-
")\n",
|
|
166
|
-
"\n",
|
|
167
|
-
"kanop_request = client.create_data_request(\n",
|
|
168
|
-
" aoi_id=aoi.id,\n",
|
|
169
|
-
" dataset_id=kanop_dataset_id\n",
|
|
170
|
-
")\n",
|
|
171
|
-
"\n",
|
|
172
|
-
"planet_request = client.create_data_request(\n",
|
|
173
|
-
" aoi_id=aoi.id,\n",
|
|
174
|
-
" dataset_id=planet_dataset_id\n",
|
|
175
|
-
")"
|
|
176
|
-
]
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
"cell_type": "code",
|
|
180
|
-
"execution_count": null,
|
|
181
|
-
"metadata": {},
|
|
182
|
-
"outputs": [],
|
|
183
|
-
"source": [
|
|
184
|
-
"# print data request ids\n",
|
|
185
|
-
"print(f'Chloris data request ID: {chloris_request.id}')\n",
|
|
186
|
-
"print(f'Kanop data request ID: {kanop_request.id}')\n",
|
|
187
|
-
"print(f'Planet data request ID: {planet_request.id}')"
|
|
188
|
-
]
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
"cell_type": "markdown",
|
|
192
|
-
"metadata": {},
|
|
193
|
-
"source": [
|
|
194
|
-
"### Transform the data\n",
|
|
195
|
-
"\n",
|
|
196
|
-
"Datasets often have different native coordinate reference systems (CRS) and spatial resolutions, which must be aligned before working with them together. \n",
|
|
197
|
-
"\n",
|
|
198
|
-
"Use the following code to transform all datasets to the same CRS and spatial resolution. Here we have chosen EPSG:4326 and 0.00025º (i.e. ~ 30 m on the equator).\n",
|
|
199
|
-
"\n",
|
|
200
|
-
"Store transformation IDs (`transformation.id`) so you can retrieve them later (see [SDK documentation](https://docs.cecil.earth/SDK-11fef16bbbe480b5a778ccae542cf34d)).\n",
|
|
201
|
-
"\n",
|
|
202
|
-
"*NB - you only need to do the transformations once.*"
|
|
203
|
-
]
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
"cell_type": "code",
|
|
207
|
-
"execution_count": null,
|
|
208
|
-
"metadata": {},
|
|
209
|
-
"outputs": [],
|
|
210
|
-
"source": [
|
|
211
|
-
"# choose transformation crs and spatial resolution\n",
|
|
212
|
-
"destination_crs = 'EPSG:4326'\n",
|
|
213
|
-
"destination_res = 0.00025\n",
|
|
214
|
-
"\n",
|
|
215
|
-
"# transform datasets\n",
|
|
216
|
-
"chloris_transformation = client.create_transformation(\n",
|
|
217
|
-
" data_request_id=chloris_request.id,\n",
|
|
218
|
-
" crs=destination_crs,\n",
|
|
219
|
-
" spatial_resolution=destination_res\n",
|
|
220
|
-
")\n",
|
|
221
|
-
"\n",
|
|
222
|
-
"kanop_transformation = client.create_transformation(\n",
|
|
223
|
-
" data_request_id=kanop_request.id,\n",
|
|
224
|
-
" crs=destination_crs,\n",
|
|
225
|
-
" spatial_resolution=destination_res\n",
|
|
226
|
-
")\n",
|
|
227
|
-
"\n",
|
|
228
|
-
"planet_transformation = client.create_transformation(\n",
|
|
229
|
-
" data_request_id=planet_request.id,\n",
|
|
230
|
-
" crs=destination_crs,\n",
|
|
231
|
-
" spatial_resolution=destination_res\n",
|
|
232
|
-
")"
|
|
233
|
-
]
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
"cell_type": "code",
|
|
237
|
-
"execution_count": null,
|
|
238
|
-
"metadata": {},
|
|
239
|
-
"outputs": [],
|
|
240
|
-
"source": [
|
|
241
|
-
"# print data request ids\n",
|
|
242
|
-
"print(f'Chloris transformation ID: {chloris_transformation.id}')\n",
|
|
243
|
-
"print(f'Kanop transformation ID: {kanop_transformation.id}')\n",
|
|
244
|
-
"print(f'Planet transformation ID: {planet_transformation.id}')"
|
|
245
|
-
]
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
"cell_type": "markdown",
|
|
249
|
-
"metadata": {},
|
|
250
|
-
"source": [
|
|
251
|
-
"---"
|
|
252
|
-
]
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
"cell_type": "markdown",
|
|
256
|
-
"metadata": {},
|
|
257
|
-
"source": [
|
|
258
|
-
"## Query and wrangle the data\n",
|
|
259
|
-
"\n",
|
|
260
|
-
"### Query data\n",
|
|
261
|
-
"\n",
|
|
262
|
-
"Retrieve your transformation IDs and use them to query the data from your Cecil database. "
|
|
263
|
-
]
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
"cell_type": "code",
|
|
267
|
-
"execution_count": null,
|
|
268
|
-
"metadata": {},
|
|
269
|
-
"outputs": [],
|
|
270
|
-
"source": [
|
|
271
|
-
"# store transformation ids (convenience)\n",
|
|
272
|
-
"chloris_tid = chloris_transformation.id\n",
|
|
273
|
-
"kanop_tid = kanop_transformation.id\n",
|
|
274
|
-
"planet_tid = planet_transformation.id"
|
|
275
|
-
]
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
"cell_type": "markdown",
|
|
279
|
-
"metadata": {},
|
|
280
|
-
"source": [
|
|
281
|
-
"The SQL query below:\n",
|
|
282
|
-
"\n",
|
|
283
|
-
"* Filters for selected transformation IDs\n",
|
|
284
|
-
"* Joins the datasets by pixel centroids (x, y) and year\n",
|
|
285
|
-
"* Selects aboveground biomass (AGB) variables from all providers, as well as pixel and year information\n",
|
|
286
|
-
"* Transforms Planet AGB from a carbon equivalent to total biomass (see [dataset documentation](https://docs.cecil.earth/Forest-carbon-diligence-111ef16bbbe481d9a430c446c2bb4d04))\n",
|
|
287
|
-
"* Returns the data into a data frame in your Python environment"
|
|
288
|
-
]
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
"cell_type": "code",
|
|
292
|
-
"execution_count": null,
|
|
293
|
-
"metadata": {},
|
|
294
|
-
"outputs": [],
|
|
295
|
-
"source": [
|
|
296
|
-
"df = client.query(f'''\n",
|
|
297
|
-
" SELECT\n",
|
|
298
|
-
" -- Basic variables\n",
|
|
299
|
-
" x, y, year,\n",
|
|
300
|
-
" ST_ASWKT(pixel_boundary) AS pixel_boundary,\n",
|
|
301
|
-
" -- Biomass variables\n",
|
|
302
|
-
" k.living_aboveground_biomass AS kanop_agb,\n",
|
|
303
|
-
" p.aboveground_live_carbon_density / 0.476 AS planet_agb,\n",
|
|
304
|
-
" c.aboveground_biomass_stock AS chloris_agb\n",
|
|
305
|
-
" FROM transformation_db.kanop.screening_25_m k\n",
|
|
306
|
-
" -- Join all datasets\n",
|
|
307
|
-
" JOIN transformation_db.planet.forest_carbon_diligence p USING (year, x, y)\n",
|
|
308
|
-
" JOIN transformation_db.chloris.aboveground_biomass_stock_and_change_30_m c USING (year, x, y)\n",
|
|
309
|
-
" -- Filter for chosen transformations\n",
|
|
310
|
-
" WHERE \n",
|
|
311
|
-
" (\n",
|
|
312
|
-
" k.transformation_id = '{kanop_tid}' OR\n",
|
|
313
|
-
" p.transformation_id = '{planet_tid}' OR\n",
|
|
314
|
-
" c.transformation_id = '{chloris_tid}'\n",
|
|
315
|
-
" )\n",
|
|
316
|
-
"''')"
|
|
317
|
-
]
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
"cell_type": "markdown",
|
|
321
|
-
"metadata": {},
|
|
322
|
-
"source": [
|
|
323
|
-
"### Wrangle data\n",
|
|
324
|
-
"\n",
|
|
325
|
-
"Convert the data into a GeoPandas data frame for downstream analysis and print the top 3 rows:"
|
|
326
|
-
]
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
"cell_type": "code",
|
|
330
|
-
"execution_count": null,
|
|
331
|
-
"metadata": {},
|
|
332
|
-
"outputs": [],
|
|
333
|
-
"source": [
|
|
334
|
-
"# convert data frame to geodataframe\n",
|
|
335
|
-
"gdf = gpd.GeoDataFrame(\n",
|
|
336
|
-
" df, \n",
|
|
337
|
-
" geometry=gpd.GeoSeries.from_wkt(\n",
|
|
338
|
-
" df['pixel_boundary'], \n",
|
|
339
|
-
" crs='EPSG:4326'\n",
|
|
340
|
-
" )\n",
|
|
341
|
-
")\n",
|
|
342
|
-
"\n",
|
|
343
|
-
"# inspect data frame\n",
|
|
344
|
-
"df.head(3)"
|
|
345
|
-
]
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
"cell_type": "markdown",
|
|
349
|
-
"metadata": {},
|
|
350
|
-
"source": [
|
|
351
|
-
"The code below creates the mean and coefficient of variation of AGB across providers for use in cross-comparisons at two levels:\n",
|
|
352
|
-
"\n",
|
|
353
|
-
"* AOI level - all pixels together, separating years\n",
|
|
354
|
-
"* Pixel level - all years together, separating pixels"
|
|
355
|
-
]
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
"cell_type": "code",
|
|
359
|
-
"execution_count": null,
|
|
360
|
-
"metadata": {},
|
|
361
|
-
"outputs": [],
|
|
362
|
-
"source": [
|
|
363
|
-
"# calculate mean among the three variables\n",
|
|
364
|
-
"gdf['mean_agb'] = gdf[['kanop_agb', 'planet_agb', 'chloris_agb']].apply(\n",
|
|
365
|
-
" lambda row: row.mean(), \n",
|
|
366
|
-
" axis=1\n",
|
|
367
|
-
")\n",
|
|
368
|
-
"\n",
|
|
369
|
-
"# calculate coefficient of variation among the three variables\n",
|
|
370
|
-
"gdf['cv_agb'] = gdf[['kanop_agb', 'planet_agb', 'chloris_agb']].apply(\n",
|
|
371
|
-
" lambda row: row.std() / row.mean(), \n",
|
|
372
|
-
" axis=1\n",
|
|
373
|
-
")\n",
|
|
374
|
-
"\n",
|
|
375
|
-
"# create df with mean per pixel coefficient of variation across all years (pixel level)\n",
|
|
376
|
-
"spatial_cv = gdf.groupby(['x', 'y']).agg({\n",
|
|
377
|
-
" 'cv_agb': 'mean',\n",
|
|
378
|
-
" 'geometry': 'first'\n",
|
|
379
|
-
"}).reset_index()\n",
|
|
380
|
-
"\n",
|
|
381
|
-
"# create df with annual mean cv and annual mean/sd of agb (aoi level)\n",
|
|
382
|
-
"temporal_stats = gdf.groupby('year').agg({\n",
|
|
383
|
-
" 'cv_agb': 'mean',\n",
|
|
384
|
-
" 'mean_agb': ['mean', 'std']\n",
|
|
385
|
-
"}).reset_index()\n",
|
|
386
|
-
"temporal_stats.columns = ['year', 'mean_cv', 'mean_agb', 'std_mean_agb']"
|
|
387
|
-
]
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
"cell_type": "markdown",
|
|
391
|
-
"metadata": {},
|
|
392
|
-
"source": [
|
|
393
|
-
"---"
|
|
394
|
-
]
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
"cell_type": "markdown",
|
|
398
|
-
"metadata": {},
|
|
399
|
-
"source": [
|
|
400
|
-
"## Cross-comparisons\n",
|
|
401
|
-
"\n",
|
|
402
|
-
"The following code blocks perform three basic visual cross-comparisons:\n",
|
|
403
|
-
"\n",
|
|
404
|
-
"1. Scatter graphs showing pairwise pixel correlations between all providers\n",
|
|
405
|
-
"2. Difference maps showing mean AGB and coefficient of variation (CV) across space\n",
|
|
406
|
-
"3. Time series graphs showing mean AGB and CV over time\n",
|
|
407
|
-
"\n",
|
|
408
|
-
"### Pairwise pixel correlations"
|
|
409
|
-
]
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
"cell_type": "code",
|
|
413
|
-
"execution_count": null,
|
|
414
|
-
"metadata": {},
|
|
415
|
-
"outputs": [],
|
|
416
|
-
"source": [
|
|
417
|
-
"# define function to create scatter graph with 1:1 line\n",
|
|
418
|
-
"def add_scatter(ax, df, x_col, y_col, x_label, y_label):\n",
|
|
419
|
-
" ax.scatter(df[x_col], df[y_col], alpha=0.5, s=10, color='darkgreen')\n",
|
|
420
|
-
" lims = [min(df[x_col].min(), df[y_col].min()),\n",
|
|
421
|
-
" max(df[x_col].max(), df[y_col].max())]\n",
|
|
422
|
-
" ax.plot(lims, lims, 'k--', lw=2, alpha=0.7)\n",
|
|
423
|
-
" ax.set_xlabel(x_label, fontsize=12)\n",
|
|
424
|
-
" ax.set_ylabel(y_label, fontsize=12)\n",
|
|
425
|
-
"\n",
|
|
426
|
-
"# create plotting canvas\n",
|
|
427
|
-
"fig, axes0 = plt.subplots(1, 3, figsize=(15, 5))\n",
|
|
428
|
-
"fig.suptitle('Pairwise pixel correspondence', fontsize=14)\n",
|
|
429
|
-
"\n",
|
|
430
|
-
"# add pairwise pixel plots to canvas\n",
|
|
431
|
-
"add_scatter(axes0[0], gdf, 'planet_agb', 'chloris_agb', 'Planet AGB (Mg/ha)', 'Chloris AGB (Mg/ha)')\n",
|
|
432
|
-
"add_scatter(axes0[1], gdf, 'planet_agb', 'kanop_agb', 'Planet AGB (Mg/ha)', 'Kanop AGB (Mg/ha)')\n",
|
|
433
|
-
"add_scatter(axes0[2], gdf, 'chloris_agb', 'kanop_agb', 'Chloris AGB (Mg/ha)', 'Kanop AGB (Mg/ha)')\n",
|
|
434
|
-
"\n",
|
|
435
|
-
"# plot\n",
|
|
436
|
-
"plt.tight_layout()"
|
|
437
|
-
]
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
"cell_type": "markdown",
|
|
441
|
-
"metadata": {},
|
|
442
|
-
"source": [
|
|
443
|
-
"### Difference maps"
|
|
444
|
-
]
|
|
445
|
-
},
|
|
446
|
-
{
|
|
447
|
-
"cell_type": "code",
|
|
448
|
-
"execution_count": null,
|
|
449
|
-
"metadata": {},
|
|
450
|
-
"outputs": [],
|
|
451
|
-
"source": [
|
|
452
|
-
"# create plotting canvas\n",
|
|
453
|
-
"fig, axes1 = plt.subplots(1, 2, figsize=(12, 6))\n",
|
|
454
|
-
"\n",
|
|
455
|
-
"# map of mean AGB (calculated in SQL query)\n",
|
|
456
|
-
"gdf.plot(\n",
|
|
457
|
-
" column='mean_agb', \n",
|
|
458
|
-
" ax=axes1[0], \n",
|
|
459
|
-
" cmap='viridis', \n",
|
|
460
|
-
" legend=True, \n",
|
|
461
|
-
" legend_kwds={'label': 'Mg/ha', 'shrink': 0.8}\n",
|
|
462
|
-
")\n",
|
|
463
|
-
"axes1[0].set_title('Mean per pixel AGB (Mg/ha)', fontsize=12)\n",
|
|
464
|
-
"\n",
|
|
465
|
-
"# map of mean per pixel CV\n",
|
|
466
|
-
"gdf.plot(\n",
|
|
467
|
-
" column='cv_agb', \n",
|
|
468
|
-
" ax=axes1[1], \n",
|
|
469
|
-
" cmap='YlOrRd', \n",
|
|
470
|
-
" legend=True,\n",
|
|
471
|
-
" legend_kwds={'shrink': 0.8}\n",
|
|
472
|
-
")\n",
|
|
473
|
-
"axes1[1].set_title('Mean per pixel CV', fontsize=12)\n",
|
|
474
|
-
"\n",
|
|
475
|
-
"# plot\n",
|
|
476
|
-
"plt.tight_layout()"
|
|
477
|
-
]
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
"cell_type": "markdown",
|
|
481
|
-
"metadata": {},
|
|
482
|
-
"source": [
|
|
483
|
-
"### Time series graphs"
|
|
484
|
-
]
|
|
485
|
-
},
|
|
486
|
-
{
|
|
487
|
-
"cell_type": "code",
|
|
488
|
-
"execution_count": null,
|
|
489
|
-
"metadata": {},
|
|
490
|
-
"outputs": [],
|
|
491
|
-
"source": [
|
|
492
|
-
"# create plotting canvas\n",
|
|
493
|
-
"fig, axes2 = plt.subplots(1, 2, figsize=(12, 6))\n",
|
|
494
|
-
"\n",
|
|
495
|
-
"# time series of mean AGB (line) ± standard deviation (ribbon)\n",
|
|
496
|
-
"axes2[0].plot(\n",
|
|
497
|
-
" temporal_stats['year'], \n",
|
|
498
|
-
" temporal_stats['mean_agb'], \n",
|
|
499
|
-
" 'o-', \n",
|
|
500
|
-
" color='darkgreen'\n",
|
|
501
|
-
")\n",
|
|
502
|
-
"axes2[0].fill_between(\n",
|
|
503
|
-
" temporal_stats['year'], \n",
|
|
504
|
-
" temporal_stats['mean_agb'] - temporal_stats['std_mean_agb'],\n",
|
|
505
|
-
" temporal_stats['mean_agb'] + temporal_stats['std_mean_agb'],\n",
|
|
506
|
-
" alpha=0.3, \n",
|
|
507
|
-
" color='darkgreen'\n",
|
|
508
|
-
")\n",
|
|
509
|
-
"axes2[0].set_ylabel('AGB (Mg/ha ± SD)')\n",
|
|
510
|
-
"axes2[0].set_xlabel('Year')\n",
|
|
511
|
-
"axes2[0].set_title('Mean AGB over time (Mg/ha ± SD)')\n",
|
|
512
|
-
"axes2[0].grid(True, alpha=0.3)\n",
|
|
513
|
-
"\n",
|
|
514
|
-
"# time series plot of mean CV\n",
|
|
515
|
-
"axes2[1].plot(\n",
|
|
516
|
-
" temporal_stats['year'], \n",
|
|
517
|
-
" temporal_stats['mean_cv'], \n",
|
|
518
|
-
" 'o-', \n",
|
|
519
|
-
" color='darkred'\n",
|
|
520
|
-
")\n",
|
|
521
|
-
"axes2[1].set_ylabel('Mean CV')\n",
|
|
522
|
-
"axes2[1].set_xlabel('Year')\n",
|
|
523
|
-
"axes2[1].set_title('Mean CV over time')\n",
|
|
524
|
-
"axes2[1].grid(True, alpha=0.3)\n",
|
|
525
|
-
"\n",
|
|
526
|
-
"# plot\n",
|
|
527
|
-
"plt.tight_layout()"
|
|
528
|
-
]
|
|
529
|
-
}
|
|
530
|
-
],
|
|
531
|
-
"metadata": {
|
|
532
|
-
"kernelspec": {
|
|
533
|
-
"display_name": "Python 3 (ipykernel)",
|
|
534
|
-
"language": "python",
|
|
535
|
-
"name": "python3"
|
|
536
|
-
},
|
|
537
|
-
"language_info": {
|
|
538
|
-
"codemirror_mode": {
|
|
539
|
-
"name": "ipython",
|
|
540
|
-
"version": 3
|
|
541
|
-
},
|
|
542
|
-
"file_extension": ".py",
|
|
543
|
-
"mimetype": "text/x-python",
|
|
544
|
-
"name": "python",
|
|
545
|
-
"nbconvert_exporter": "python",
|
|
546
|
-
"pygments_lexer": "ipython3",
|
|
547
|
-
"version": "3.12.4"
|
|
548
|
-
}
|
|
549
|
-
},
|
|
550
|
-
"nbformat": 4,
|
|
551
|
-
"nbformat_minor": 4
|
|
552
|
-
}
|