terrakio-core 0.4.3__tar.gz → 0.4.4__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 terrakio-core might be problematic. Click here for more details.
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/PKG-INFO +2 -1
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/pyproject.toml +2 -1
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/__init__.py +3 -1
- terrakio_core-0.4.4/terrakio_core/accessors.py +477 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/async_client.py +23 -38
- terrakio_core-0.4.4/terrakio_core/client.py +133 -0
- terrakio_core-0.4.4/terrakio_core/convenience_functions/convenience_functions.py +521 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/endpoints/auth.py +8 -1
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/endpoints/mass_stats.py +13 -9
- terrakio_core-0.4.4/terrakio_core/endpoints/model_management.py +1128 -0
- terrakio_core-0.4.4/terrakio_core/sync_client.py +597 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core.egg-info/PKG-INFO +2 -1
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core.egg-info/SOURCES.txt +1 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core.egg-info/requires.txt +1 -0
- terrakio_core-0.4.3/terrakio_core/client.py +0 -134
- terrakio_core-0.4.3/terrakio_core/convenience_functions/convenience_functions.py +0 -529
- terrakio_core-0.4.3/terrakio_core/endpoints/model_management.py +0 -1472
- terrakio_core-0.4.3/terrakio_core/sync_client.py +0 -289
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/README.md +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/setup.cfg +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/config.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/endpoints/dataset_management.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/endpoints/group_management.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/endpoints/space_management.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/endpoints/user_management.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/exceptions.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/helper/bounded_taskgroup.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/helper/decorators.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core/helper/tiles.py +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core.egg-info/dependency_links.txt +0 -0
- {terrakio_core-0.4.3 → terrakio_core-0.4.4}/terrakio_core.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: terrakio-core
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Core components for Terrakio API clients
|
|
5
5
|
Author-email: Yupeng Chao <yupeng@haizea.com.au>
|
|
6
6
|
Project-URL: Homepage, https://github.com/HaizeaAnalytics/terrakio-python-api
|
|
@@ -24,6 +24,7 @@ Requires-Dist: geopandas>=0.13.0
|
|
|
24
24
|
Requires-Dist: google-cloud-storage>=2.0.0
|
|
25
25
|
Requires-Dist: scipy>=1.7.0
|
|
26
26
|
Requires-Dist: nest_asyncio
|
|
27
|
+
Requires-Dist: onnxruntime>=1.10.0
|
|
27
28
|
Provides-Extra: ml
|
|
28
29
|
Requires-Dist: torch>=2.7.1; extra == "ml"
|
|
29
30
|
Requires-Dist: scikit-learn>=1.7.0; extra == "ml"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "terrakio-core"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.4"
|
|
8
8
|
authors = [
|
|
9
9
|
{name = "Yupeng Chao", email = "yupeng@haizea.com.au"},
|
|
10
10
|
]
|
|
@@ -31,6 +31,7 @@ dependencies = [
|
|
|
31
31
|
"google-cloud-storage>=2.0.0",
|
|
32
32
|
"scipy>=1.7.0",
|
|
33
33
|
"nest_asyncio",
|
|
34
|
+
"onnxruntime>=1.10.0",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.optional-dependencies]
|
|
@@ -5,10 +5,12 @@ Terrakio Core
|
|
|
5
5
|
Core components for Terrakio API clients.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "0.4.
|
|
8
|
+
__version__ = "0.4.4"
|
|
9
9
|
|
|
10
10
|
from .async_client import AsyncClient
|
|
11
11
|
from .sync_client import SyncClient as Client
|
|
12
|
+
from . import accessors
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
__all__ = [
|
|
14
16
|
"AsyncClient",
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import geopandas as gpd
|
|
3
|
+
import xarray as xr
|
|
4
|
+
import numpy as np
|
|
5
|
+
from typing import Optional, Union, List
|
|
6
|
+
|
|
7
|
+
@pd.api.extensions.register_dataframe_accessor("geo")
|
|
8
|
+
class GeoXarrayAccessor:
|
|
9
|
+
"""
|
|
10
|
+
Custom accessor for GeoDataFrames containing xarray datasets or dataarrays.
|
|
11
|
+
Handles both direct xarray objects and lists containing xarray objects.
|
|
12
|
+
Can aggregate across time when time dimension has been expanded into the index.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, pandas_obj):
|
|
16
|
+
self._obj = pandas_obj
|
|
17
|
+
self._validate()
|
|
18
|
+
|
|
19
|
+
def _validate(self):
|
|
20
|
+
"""Validate that the DataFrame has the expected structure."""
|
|
21
|
+
if not isinstance(self._obj, gpd.GeoDataFrame):
|
|
22
|
+
raise AttributeError("Can only use .geo accessor with GeoDataFrames")
|
|
23
|
+
|
|
24
|
+
# Check for columns with xarray data (including lists containing xarray objects)
|
|
25
|
+
self._xarray_columns = []
|
|
26
|
+
for col in self._obj.columns:
|
|
27
|
+
if col != 'geometry':
|
|
28
|
+
sample_value = self._obj[col].iloc[0] if len(self._obj) > 0 else None
|
|
29
|
+
|
|
30
|
+
# Check if it's directly an xarray object
|
|
31
|
+
if isinstance(sample_value, (xr.Dataset, xr.DataArray)):
|
|
32
|
+
self._xarray_columns.append(col)
|
|
33
|
+
# Check if it's a list containing xarray objects
|
|
34
|
+
elif isinstance(sample_value, list) and len(sample_value) > 0:
|
|
35
|
+
if isinstance(sample_value[0], (xr.Dataset, xr.DataArray)):
|
|
36
|
+
self._xarray_columns.append(col)
|
|
37
|
+
|
|
38
|
+
if not self._xarray_columns:
|
|
39
|
+
raise AttributeError("No xarray Dataset or DataArray columns found")
|
|
40
|
+
|
|
41
|
+
def _extract_xarray_object(self, value):
|
|
42
|
+
"""Extract xarray object from various formats (direct object, list, etc.)."""
|
|
43
|
+
if isinstance(value, (xr.Dataset, xr.DataArray)):
|
|
44
|
+
return value
|
|
45
|
+
elif isinstance(value, list) and len(value) > 0:
|
|
46
|
+
if isinstance(value[0], (xr.Dataset, xr.DataArray)):
|
|
47
|
+
return value[0] # Take the first item from the list
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def _get_target_columns(self, columns: Optional[List[str]] = None) -> List[str]:
|
|
51
|
+
"""
|
|
52
|
+
Get the list of columns to operate on.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
columns: List of column names to operate on. If None, uses all xarray columns.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of column names to operate on
|
|
59
|
+
"""
|
|
60
|
+
if columns is None:
|
|
61
|
+
return self._xarray_columns
|
|
62
|
+
|
|
63
|
+
# Validate that specified columns exist and contain xarray data
|
|
64
|
+
invalid_columns = [col for col in columns if col not in self._xarray_columns]
|
|
65
|
+
if invalid_columns:
|
|
66
|
+
raise ValueError(f"Columns {invalid_columns} are not valid xarray columns. "
|
|
67
|
+
f"Available xarray columns: {self._xarray_columns}")
|
|
68
|
+
|
|
69
|
+
return columns
|
|
70
|
+
|
|
71
|
+
def _should_aggregate_by_geometry(self, dim: Optional[Union[str, List[str]]] = None) -> bool:
|
|
72
|
+
"""
|
|
73
|
+
Determine if we should aggregate by geometry (i.e., time dimension was expanded to index).
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
dim: Dimension(s) being reduced over
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if we should group by geometry and aggregate across time rows
|
|
80
|
+
"""
|
|
81
|
+
if dim is None:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
dims_to_reduce = [dim] if isinstance(dim, str) else dim
|
|
85
|
+
|
|
86
|
+
# Check if 'time' is in the dimensions to reduce and if we have a MultiIndex with time
|
|
87
|
+
if 'time' in dims_to_reduce:
|
|
88
|
+
if hasattr(self._obj.index, 'names') and self._obj.index.names:
|
|
89
|
+
# Check if time is one of the index levels
|
|
90
|
+
return 'time' in self._obj.index.names
|
|
91
|
+
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
def _get_geometry_level_name(self) -> Optional[str]:
|
|
95
|
+
"""Get the name of the geometry level in the MultiIndex."""
|
|
96
|
+
if hasattr(self._obj.index, 'names') and self._obj.index.names:
|
|
97
|
+
# Look for the level that's not 'time' - this should be the geometry level
|
|
98
|
+
for name in self._obj.index.names:
|
|
99
|
+
if name != 'time':
|
|
100
|
+
return name
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
def _apply_reduction(self, reduction_func: str, dim: Optional[Union[str, List[str]]] = None,
|
|
104
|
+
columns: Optional[List[str]] = None, **kwargs):
|
|
105
|
+
"""
|
|
106
|
+
Apply a reduction function to specified xarray datasets/dataarrays in the GeoDataFrame.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
reduction_func: Name of the xarray reduction method (e.g., 'mean', 'sum', 'std')
|
|
110
|
+
dim: Dimension(s) to reduce over. If None, reduces over all dimensions
|
|
111
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
112
|
+
**kwargs: Additional arguments to pass to the reduction function
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
GeoDataFrame with reduced xarray data
|
|
116
|
+
"""
|
|
117
|
+
target_columns = self._get_target_columns(columns)
|
|
118
|
+
|
|
119
|
+
# Check if we need to aggregate by geometry (time dimension expanded to index)
|
|
120
|
+
if self._should_aggregate_by_geometry(dim):
|
|
121
|
+
return self._apply_temporal_aggregation(reduction_func, dim, target_columns, **kwargs)
|
|
122
|
+
else:
|
|
123
|
+
return self._apply_spatial_reduction(reduction_func, dim, target_columns, **kwargs)
|
|
124
|
+
|
|
125
|
+
def _apply_temporal_aggregation(self, reduction_func: str, dim: Union[str, List[str]],
|
|
126
|
+
target_columns: List[str], **kwargs):
|
|
127
|
+
"""
|
|
128
|
+
Apply aggregation across time by grouping by geometry.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
reduction_func: Name of the reduction method
|
|
132
|
+
dim: Dimension(s) being reduced (should include 'time')
|
|
133
|
+
target_columns: Columns to operate on
|
|
134
|
+
**kwargs: Additional arguments
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
GeoDataFrame with time-aggregated data
|
|
138
|
+
"""
|
|
139
|
+
geometry_level = self._get_geometry_level_name()
|
|
140
|
+
if geometry_level is None:
|
|
141
|
+
raise ValueError("Could not identify geometry level in MultiIndex")
|
|
142
|
+
|
|
143
|
+
# Check if specific columns were requested for time aggregation
|
|
144
|
+
if target_columns != self._xarray_columns:
|
|
145
|
+
print("Warning: Cannot aggregate time on a single column. Aggregating all xarray columns instead.")
|
|
146
|
+
target_columns = self._xarray_columns
|
|
147
|
+
|
|
148
|
+
# Group by geometry level
|
|
149
|
+
grouped = self._obj.groupby(level=geometry_level)
|
|
150
|
+
|
|
151
|
+
result_data = []
|
|
152
|
+
result_geometries = []
|
|
153
|
+
result_index = []
|
|
154
|
+
|
|
155
|
+
for geometry_key, group in grouped:
|
|
156
|
+
# For each geometry, collect all xarray objects across time
|
|
157
|
+
# The geometry is the group key itself (from the MultiIndex)
|
|
158
|
+
new_row = {}
|
|
159
|
+
|
|
160
|
+
for col in target_columns:
|
|
161
|
+
xarray_objects = []
|
|
162
|
+
|
|
163
|
+
# Collect all xarray objects for this geometry across different times
|
|
164
|
+
for _, row in group.iterrows():
|
|
165
|
+
xr_data = self._extract_xarray_object(row[col])
|
|
166
|
+
if xr_data is not None:
|
|
167
|
+
xarray_objects.append(xr_data)
|
|
168
|
+
|
|
169
|
+
if xarray_objects:
|
|
170
|
+
try:
|
|
171
|
+
# Concatenate along a new 'time' dimension
|
|
172
|
+
if isinstance(xarray_objects[0], xr.DataArray):
|
|
173
|
+
# Create time coordinate
|
|
174
|
+
time_coords = list(range(len(xarray_objects)))
|
|
175
|
+
concatenated = xr.concat(xarray_objects, dim='time')
|
|
176
|
+
concatenated = concatenated.assign_coords(time=time_coords)
|
|
177
|
+
elif isinstance(xarray_objects[0], xr.Dataset):
|
|
178
|
+
time_coords = list(range(len(xarray_objects)))
|
|
179
|
+
concatenated = xr.concat(xarray_objects, dim='time')
|
|
180
|
+
concatenated = concatenated.assign_coords(time=time_coords)
|
|
181
|
+
else:
|
|
182
|
+
raise TypeError(f"Unsupported xarray type: {type(xarray_objects[0])}")
|
|
183
|
+
|
|
184
|
+
# Apply the reduction function over the time dimension
|
|
185
|
+
if hasattr(concatenated, reduction_func):
|
|
186
|
+
if 'skipna' not in kwargs and reduction_func in ['mean', 'sum', 'std', 'var', 'min', 'max', 'median', 'quantile']:
|
|
187
|
+
kwargs['skipna'] = True
|
|
188
|
+
|
|
189
|
+
reduced_data = getattr(concatenated, reduction_func)(dim='time', **kwargs)
|
|
190
|
+
|
|
191
|
+
# Check if result should be converted to scalar
|
|
192
|
+
if isinstance(reduced_data, xr.DataArray) and reduced_data.size == 1:
|
|
193
|
+
try:
|
|
194
|
+
scalar_value = float(reduced_data.values)
|
|
195
|
+
reduced_data = scalar_value
|
|
196
|
+
except (ValueError, TypeError):
|
|
197
|
+
pass
|
|
198
|
+
elif isinstance(reduced_data, xr.Dataset) and len(reduced_data.dims) == 0:
|
|
199
|
+
try:
|
|
200
|
+
vars_list = list(reduced_data.data_vars.keys())
|
|
201
|
+
if len(vars_list) == 1:
|
|
202
|
+
var_name = vars_list[0]
|
|
203
|
+
scalar_value = float(reduced_data[var_name].values)
|
|
204
|
+
reduced_data = scalar_value
|
|
205
|
+
except (ValueError, TypeError, KeyError):
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
# Maintain original format (list vs direct)
|
|
209
|
+
original_format = group[col].iloc[0]
|
|
210
|
+
if isinstance(original_format, list):
|
|
211
|
+
new_row[col] = [reduced_data]
|
|
212
|
+
else:
|
|
213
|
+
new_row[col] = reduced_data
|
|
214
|
+
else:
|
|
215
|
+
raise AttributeError(f"'{type(concatenated).__name__}' object has no attribute '{reduction_func}'")
|
|
216
|
+
|
|
217
|
+
except Exception as e:
|
|
218
|
+
print(f"Warning: Could not apply {reduction_func} to geometry {geometry_key}, column {col}: {e}")
|
|
219
|
+
# Keep the first value as fallback
|
|
220
|
+
new_row[col] = group[col].iloc[0]
|
|
221
|
+
else:
|
|
222
|
+
# No xarray data found, keep first value
|
|
223
|
+
new_row[col] = group[col].iloc[0]
|
|
224
|
+
|
|
225
|
+
result_data.append(new_row)
|
|
226
|
+
result_geometries.append(geometry_key)
|
|
227
|
+
result_index.append(geometry_key)
|
|
228
|
+
|
|
229
|
+
# Create result GeoDataFrame
|
|
230
|
+
# Create a normal DataFrame with just the data columns
|
|
231
|
+
result_df = pd.DataFrame(result_data, index=result_index)
|
|
232
|
+
|
|
233
|
+
# Add geometry as a temporary column
|
|
234
|
+
result_df['_temp_geom'] = result_geometries
|
|
235
|
+
|
|
236
|
+
# Convert to GeoDataFrame using the temporary geometry column
|
|
237
|
+
result_gdf = gpd.GeoDataFrame(result_df, geometry='_temp_geom')
|
|
238
|
+
|
|
239
|
+
# Drop the temporary geometry column (the geometry is now properly set as the active geometry)
|
|
240
|
+
result_gdf = result_gdf.drop(columns=['_temp_geom'])
|
|
241
|
+
|
|
242
|
+
result_gdf.index.name = geometry_level
|
|
243
|
+
|
|
244
|
+
return result_gdf
|
|
245
|
+
|
|
246
|
+
def _apply_spatial_reduction(self, reduction_func: str, dim: Optional[Union[str, List[str]]],
|
|
247
|
+
target_columns: List[str], **kwargs):
|
|
248
|
+
"""
|
|
249
|
+
Apply reduction to spatial dimensions within each xarray object.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
reduction_func: Name of the reduction method
|
|
253
|
+
dim: Spatial dimension(s) to reduce over
|
|
254
|
+
target_columns: Columns to operate on
|
|
255
|
+
**kwargs: Additional arguments
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
GeoDataFrame with spatially reduced data
|
|
259
|
+
"""
|
|
260
|
+
result_gdf = self._obj.copy()
|
|
261
|
+
|
|
262
|
+
for col in target_columns:
|
|
263
|
+
new_data = []
|
|
264
|
+
for idx, row in self._obj.iterrows():
|
|
265
|
+
original_value = row[col]
|
|
266
|
+
xr_data = self._extract_xarray_object(original_value)
|
|
267
|
+
|
|
268
|
+
if xr_data is not None:
|
|
269
|
+
try:
|
|
270
|
+
# Apply the reduction function
|
|
271
|
+
if hasattr(xr_data, reduction_func):
|
|
272
|
+
# Ensure skipna=True is set by default for most reduction functions
|
|
273
|
+
if 'skipna' not in kwargs and reduction_func in ['mean', 'sum', 'std', 'var', 'min', 'max', 'median', 'quantile']:
|
|
274
|
+
kwargs['skipna'] = True
|
|
275
|
+
reduced_data = getattr(xr_data, reduction_func)(dim=dim, **kwargs)
|
|
276
|
+
|
|
277
|
+
# Check if the result is a scalar and convert to float if so
|
|
278
|
+
if isinstance(reduced_data, xr.DataArray):
|
|
279
|
+
if reduced_data.size == 1:
|
|
280
|
+
try:
|
|
281
|
+
scalar_value = float(reduced_data.values)
|
|
282
|
+
reduced_data = scalar_value
|
|
283
|
+
except (ValueError, TypeError):
|
|
284
|
+
pass
|
|
285
|
+
elif isinstance(reduced_data, xr.Dataset):
|
|
286
|
+
try:
|
|
287
|
+
if len(reduced_data.dims) == 0:
|
|
288
|
+
vars_list = list(reduced_data.data_vars.keys())
|
|
289
|
+
if len(vars_list) == 1:
|
|
290
|
+
var_name = vars_list[0]
|
|
291
|
+
scalar_value = float(reduced_data[var_name].values)
|
|
292
|
+
reduced_data = scalar_value
|
|
293
|
+
except (ValueError, TypeError, KeyError):
|
|
294
|
+
pass
|
|
295
|
+
|
|
296
|
+
# Keep the same format as original (list vs direct)
|
|
297
|
+
if isinstance(original_value, list):
|
|
298
|
+
new_data.append([reduced_data])
|
|
299
|
+
else:
|
|
300
|
+
new_data.append(reduced_data)
|
|
301
|
+
else:
|
|
302
|
+
raise AttributeError(f"'{type(xr_data).__name__}' object has no attribute '{reduction_func}'")
|
|
303
|
+
except Exception as e:
|
|
304
|
+
# If reduction fails, keep original data
|
|
305
|
+
print(f"Warning: Could not apply {reduction_func} to row {idx}, column {col}: {e}")
|
|
306
|
+
new_data.append(original_value)
|
|
307
|
+
else:
|
|
308
|
+
# If it's not xarray data, keep as is
|
|
309
|
+
new_data.append(original_value)
|
|
310
|
+
|
|
311
|
+
result_gdf[col] = new_data
|
|
312
|
+
|
|
313
|
+
return result_gdf
|
|
314
|
+
|
|
315
|
+
def mean(self, dim: Optional[Union[str, List[str]]] = None, columns: Optional[List[str]] = None, **kwargs):
|
|
316
|
+
"""
|
|
317
|
+
Calculate mean of xarray datasets/dataarrays.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
321
|
+
If spatial dims like 'x', 'y', reduces within each xarray object.
|
|
322
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
323
|
+
**kwargs: Additional arguments for the reduction function
|
|
324
|
+
"""
|
|
325
|
+
return self._apply_reduction('mean', dim=dim, columns=columns, **kwargs)
|
|
326
|
+
|
|
327
|
+
def sum(self, dim: Optional[Union[str, List[str]]] = None, columns: Optional[List[str]] = None, **kwargs):
|
|
328
|
+
"""
|
|
329
|
+
Calculate sum of xarray datasets/dataarrays.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
333
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
334
|
+
**kwargs: Additional arguments for the reduction function
|
|
335
|
+
"""
|
|
336
|
+
return self._apply_reduction('sum', dim=dim, columns=columns, **kwargs)
|
|
337
|
+
|
|
338
|
+
def std(self, dim: Optional[Union[str, List[str]]] = None, columns: Optional[List[str]] = None, **kwargs):
|
|
339
|
+
"""
|
|
340
|
+
Calculate standard deviation of xarray datasets/dataarrays.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
344
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
345
|
+
**kwargs: Additional arguments for the reduction function
|
|
346
|
+
"""
|
|
347
|
+
return self._apply_reduction('std', dim=dim, columns=columns, **kwargs)
|
|
348
|
+
|
|
349
|
+
def var(self, dim: Optional[Union[str, List[str]]] = None, columns: Optional[List[str]] = None, **kwargs):
|
|
350
|
+
"""
|
|
351
|
+
Calculate variance of xarray datasets/dataarrays.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
355
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
356
|
+
**kwargs: Additional arguments for the reduction function
|
|
357
|
+
"""
|
|
358
|
+
return self._apply_reduction('var', dim=dim, columns=columns, **kwargs)
|
|
359
|
+
|
|
360
|
+
def min(self, dim: Optional[Union[str, List[str]]] = None, columns: Optional[List[str]] = None, **kwargs):
|
|
361
|
+
"""
|
|
362
|
+
Calculate minimum of xarray datasets/dataarrays.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
366
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
367
|
+
**kwargs: Additional arguments for the reduction function
|
|
368
|
+
"""
|
|
369
|
+
return self._apply_reduction('min', dim=dim, columns=columns, **kwargs)
|
|
370
|
+
|
|
371
|
+
def max(self, dim: Optional[Union[str, List[str]]] = None, columns: Optional[List[str]] = None, **kwargs):
|
|
372
|
+
"""
|
|
373
|
+
Calculate maximum of xarray datasets/dataarrays.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
377
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
378
|
+
**kwargs: Additional arguments for the reduction function
|
|
379
|
+
"""
|
|
380
|
+
return self._apply_reduction('max', dim=dim, columns=columns, **kwargs)
|
|
381
|
+
|
|
382
|
+
def median(self, dim: Optional[Union[str, List[str]]] = None, columns: Optional[List[str]] = None, **kwargs):
|
|
383
|
+
"""
|
|
384
|
+
Calculate median of xarray datasets/dataarrays.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
388
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
389
|
+
**kwargs: Additional arguments for the reduction function
|
|
390
|
+
"""
|
|
391
|
+
return self._apply_reduction('median', dim=dim, columns=columns, **kwargs)
|
|
392
|
+
|
|
393
|
+
def quantile(self, q: float, dim: Optional[Union[str, List[str]]] = None,
|
|
394
|
+
columns: Optional[List[str]] = None, **kwargs):
|
|
395
|
+
"""
|
|
396
|
+
Calculate quantile of xarray datasets/dataarrays.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
q: Quantile to compute (between 0 and 1)
|
|
400
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
401
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
402
|
+
**kwargs: Additional arguments for the reduction function
|
|
403
|
+
"""
|
|
404
|
+
return self._apply_reduction('quantile', dim=dim, columns=columns, q=q, **kwargs)
|
|
405
|
+
|
|
406
|
+
def count(self, dim: Optional[Union[str, List[str]]] = None, columns: Optional[List[str]] = None, **kwargs):
|
|
407
|
+
"""
|
|
408
|
+
Count non-NaN values in xarray datasets/dataarrays.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
dim: Dimension(s) to reduce over. If 'time', aggregates across time rows for each geometry.
|
|
412
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
413
|
+
**kwargs: Additional arguments for the reduction function
|
|
414
|
+
"""
|
|
415
|
+
return self._apply_reduction('count', dim=dim, columns=columns, **kwargs)
|
|
416
|
+
|
|
417
|
+
def to_values(self, columns: Optional[List[str]] = None):
|
|
418
|
+
"""
|
|
419
|
+
Extract scalar values from xarray dataarrays and add them as new columns.
|
|
420
|
+
Useful when dataarrays have been reduced to single values.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
columns: List of column names to operate on. If None, operates on all xarray columns
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
GeoDataFrame with extracted values as new columns
|
|
427
|
+
"""
|
|
428
|
+
result_gdf = self._obj.copy()
|
|
429
|
+
target_columns = self._get_target_columns(columns)
|
|
430
|
+
|
|
431
|
+
for col in target_columns:
|
|
432
|
+
values_to_add = []
|
|
433
|
+
for idx, row in self._obj.iterrows():
|
|
434
|
+
xr_data = self._extract_xarray_object(row[col])
|
|
435
|
+
if isinstance(xr_data, xr.DataArray):
|
|
436
|
+
try:
|
|
437
|
+
if xr_data.size == 1:
|
|
438
|
+
scalar_value = float(xr_data.values)
|
|
439
|
+
values_to_add.append(scalar_value)
|
|
440
|
+
else:
|
|
441
|
+
values_to_add.append(np.nan) # Can't convert non-scalar to value
|
|
442
|
+
except (ValueError, TypeError):
|
|
443
|
+
values_to_add.append(np.nan)
|
|
444
|
+
else:
|
|
445
|
+
values_to_add.append(np.nan)
|
|
446
|
+
|
|
447
|
+
# Add new column with scalar values
|
|
448
|
+
new_col_name = f"{col}_value"
|
|
449
|
+
result_gdf[new_col_name] = values_to_add
|
|
450
|
+
|
|
451
|
+
return result_gdf
|
|
452
|
+
|
|
453
|
+
def info(self):
|
|
454
|
+
"""Print information about xarray datasets/dataarrays in the GeoDataFrame."""
|
|
455
|
+
print(f"GeoDataFrame shape: {self._obj.shape}")
|
|
456
|
+
print(f"Columns: {list(self._obj.columns)}")
|
|
457
|
+
print(f"Xarray columns: {self._xarray_columns}")
|
|
458
|
+
print(f"Index structure: {self._obj.index.names if hasattr(self._obj.index, 'names') else 'Simple index'}")
|
|
459
|
+
print(f"Geometry column name: {self._obj.geometry.name if hasattr(self._obj.geometry, 'name') else 'No geometry name'}")
|
|
460
|
+
|
|
461
|
+
if hasattr(self._obj.index, 'names') and 'time' in self._obj.index.names:
|
|
462
|
+
print("Note: Time dimension appears to be expanded into the index.")
|
|
463
|
+
print("Use dim='time' to aggregate across time rows for each geometry.")
|
|
464
|
+
|
|
465
|
+
for col in self._xarray_columns:
|
|
466
|
+
print(f"\n--- Column: {col} ---")
|
|
467
|
+
sample_data = self._extract_xarray_object(self._obj[col].iloc[0]) if len(self._obj) > 0 else None
|
|
468
|
+
if isinstance(sample_data, xr.Dataset):
|
|
469
|
+
print(f"Type: xarray.Dataset")
|
|
470
|
+
print(f"Variables: {list(sample_data.data_vars.keys())}")
|
|
471
|
+
print(f"Dimensions: {list(sample_data.dims.keys())}")
|
|
472
|
+
print(f"Coordinates: {list(sample_data.coords.keys())}")
|
|
473
|
+
elif isinstance(sample_data, xr.DataArray):
|
|
474
|
+
print(f"Type: xarray.DataArray")
|
|
475
|
+
print(f"Dimensions: {list(sample_data.dims)}")
|
|
476
|
+
print(f"Shape: {sample_data.shape}")
|
|
477
|
+
print(f"Data type: {sample_data.dtype}")
|