terrakio-core 0.3.8__tar.gz → 0.3.9__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.3.8 → terrakio_core-0.3.9}/PKG-INFO +2 -1
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/pyproject.toml +2 -1
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/__init__.py +3 -3
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/async_client.py +1 -5
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/client.py +4 -2
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/convenience_functions/convenience_functions.py +51 -69
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/sync_client.py +1 -3
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core.egg-info/PKG-INFO +2 -1
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core.egg-info/requires.txt +1 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/README.md +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/setup.cfg +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/config.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/endpoints/auth.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/endpoints/dataset_management.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/endpoints/group_management.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/endpoints/mass_stats.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/endpoints/model_management.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/endpoints/space_management.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/endpoints/user_management.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/exceptions.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/helper/bounded_taskgroup.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/helper/decorators.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core/helper/tiles.py +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core.egg-info/SOURCES.txt +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/terrakio_core.egg-info/dependency_links.txt +0 -0
- {terrakio_core-0.3.8 → terrakio_core-0.3.9}/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.3.
|
|
3
|
+
Version: 0.3.9
|
|
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
|
|
@@ -22,6 +22,7 @@ Requires-Dist: xarray>=2023.1.0
|
|
|
22
22
|
Requires-Dist: shapely>=2.0.0
|
|
23
23
|
Requires-Dist: geopandas>=0.13.0
|
|
24
24
|
Requires-Dist: google-cloud-storage>=2.0.0
|
|
25
|
+
Requires-Dist: scipy>=1.7.0
|
|
25
26
|
Requires-Dist: nest_asyncio
|
|
26
27
|
Provides-Extra: ml
|
|
27
28
|
Requires-Dist: torch>=2.7.1; extra == "ml"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "terrakio-core"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.9"
|
|
8
8
|
authors = [
|
|
9
9
|
{name = "Yupeng Chao", email = "yupeng@haizea.com.au"},
|
|
10
10
|
]
|
|
@@ -29,6 +29,7 @@ dependencies = [
|
|
|
29
29
|
"shapely>=2.0.0",
|
|
30
30
|
"geopandas>=0.13.0",
|
|
31
31
|
"google-cloud-storage>=2.0.0",
|
|
32
|
+
"scipy>=1.7.0",
|
|
32
33
|
"nest_asyncio",
|
|
33
34
|
]
|
|
34
35
|
|
|
@@ -5,12 +5,12 @@ Terrakio Core
|
|
|
5
5
|
Core components for Terrakio API clients.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "0.3.
|
|
8
|
+
__version__ = "0.3.9"
|
|
9
9
|
|
|
10
10
|
from .async_client import AsyncClient
|
|
11
|
-
from .sync_client import SyncClient
|
|
11
|
+
from .sync_client import SyncClient as Client
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
14
|
"AsyncClient",
|
|
15
|
-
"
|
|
15
|
+
"Client"
|
|
16
16
|
]
|
|
@@ -90,7 +90,6 @@ class AsyncClient(BaseClient):
|
|
|
90
90
|
"""Parse response based on content type."""
|
|
91
91
|
content_type = response.headers.get('content-type', '').lower()
|
|
92
92
|
content = await response.read()
|
|
93
|
-
|
|
94
93
|
if 'json' in content_type:
|
|
95
94
|
return json.loads(content.decode('utf-8'))
|
|
96
95
|
elif 'csv' in content_type:
|
|
@@ -117,7 +116,6 @@ class AsyncClient(BaseClient):
|
|
|
117
116
|
except aiohttp.ClientError as e:
|
|
118
117
|
raise APIError(f"Request failed: {e}")
|
|
119
118
|
else:
|
|
120
|
-
# this means that we used the with statement, and we already have a session
|
|
121
119
|
try:
|
|
122
120
|
async with self._session.request(method, url, **kwargs) as response:
|
|
123
121
|
response.raise_for_status()
|
|
@@ -131,7 +129,6 @@ class AsyncClient(BaseClient):
|
|
|
131
129
|
feature: Union[Dict[str, Any], ShapelyGeometry],
|
|
132
130
|
in_crs: str = "epsg:4326",
|
|
133
131
|
out_crs: str = "epsg:4326",
|
|
134
|
-
output: str = "csv",
|
|
135
132
|
resolution: int = -1,
|
|
136
133
|
geom_fix: bool = False,
|
|
137
134
|
validated: bool = True,
|
|
@@ -145,7 +142,6 @@ class AsyncClient(BaseClient):
|
|
|
145
142
|
feature (Union[Dict[str, Any], ShapelyGeometry]): The geographic feature
|
|
146
143
|
in_crs (str): Input coordinate reference system
|
|
147
144
|
out_crs (str): Output coordinate reference system
|
|
148
|
-
output (str): Output format ('csv' or 'netcdf')
|
|
149
145
|
resolution (int): Resolution parameter
|
|
150
146
|
geom_fix (bool): Whether to fix the geometry (default False)
|
|
151
147
|
validated (bool): Whether to use validated data (default True)
|
|
@@ -167,7 +163,7 @@ class AsyncClient(BaseClient):
|
|
|
167
163
|
"feature": feature,
|
|
168
164
|
"in_crs": in_crs,
|
|
169
165
|
"out_crs": out_crs,
|
|
170
|
-
"output":
|
|
166
|
+
"output": "netcdf",
|
|
171
167
|
"resolution": resolution,
|
|
172
168
|
"expr": expr,
|
|
173
169
|
"buffer": geom_fix,
|
|
@@ -22,10 +22,12 @@ class BaseClient():
|
|
|
22
22
|
self.key = api_key
|
|
23
23
|
|
|
24
24
|
config = read_config_file( DEFAULT_CONFIG_FILE, logger = self.logger)
|
|
25
|
+
|
|
25
26
|
if self.url is None:
|
|
26
27
|
self.url = config.get('url')
|
|
27
|
-
|
|
28
|
-
self.key
|
|
28
|
+
|
|
29
|
+
if self.key is None:
|
|
30
|
+
self.key = config.get('key')
|
|
29
31
|
|
|
30
32
|
self.token = config.get('token')
|
|
31
33
|
|
|
@@ -9,6 +9,8 @@ from ..exceptions import APIError, ConfigurationError
|
|
|
9
9
|
from ..helper.bounded_taskgroup import BoundedTaskGroup
|
|
10
10
|
from ..helper.tiles import tiles
|
|
11
11
|
import uuid
|
|
12
|
+
import xarray as xr
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
async def zonal_stats(
|
|
14
16
|
client,
|
|
@@ -35,7 +37,8 @@ async def zonal_stats(
|
|
|
35
37
|
resolution (int): Resolution parameter
|
|
36
38
|
geom_fix (bool): Whether to fix the geometry (default False)
|
|
37
39
|
Returns:
|
|
38
|
-
geopandas.GeoDataFrame: GeoDataFrame with
|
|
40
|
+
geopandas.GeoDataFrame: GeoDataFrame with variable dataarrays in separate columns.
|
|
41
|
+
Each row represents one geometry with full time-dimensional dataarrays.
|
|
39
42
|
|
|
40
43
|
Raises:
|
|
41
44
|
ValueError: If concurrency is too high
|
|
@@ -51,7 +54,7 @@ async def zonal_stats(
|
|
|
51
54
|
completed_count = 0
|
|
52
55
|
lock = asyncio.Lock()
|
|
53
56
|
|
|
54
|
-
async def process_geometry(geom
|
|
57
|
+
async def process_geometry(geom):
|
|
55
58
|
"""Process a single geometry"""
|
|
56
59
|
nonlocal completed_count
|
|
57
60
|
|
|
@@ -59,19 +62,20 @@ async def zonal_stats(
|
|
|
59
62
|
feature = {
|
|
60
63
|
"type": "Feature",
|
|
61
64
|
"geometry": mapping(geom),
|
|
62
|
-
"properties": {
|
|
65
|
+
"properties": {}
|
|
63
66
|
}
|
|
64
|
-
|
|
67
|
+
# Request xarray dataset
|
|
68
|
+
result = await client.geoquery(expr=expr, feature=feature,
|
|
65
69
|
in_crs=in_crs, out_crs=out_crs, resolution=resolution, geom_fix=geom_fix)
|
|
66
|
-
|
|
67
70
|
if isinstance(result, dict) and result.get("error"):
|
|
68
|
-
error_msg = f"Request
|
|
71
|
+
error_msg = f"Request failed: {result.get('error_message', 'Unknown error')}"
|
|
69
72
|
if result.get('status_code'):
|
|
70
|
-
error_msg = f"Request
|
|
73
|
+
error_msg = f"Request failed with status {result['status_code']}: {result.get('error_message', 'Unknown error')}"
|
|
71
74
|
raise APIError(error_msg)
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
# Ensure we got an xarray Dataset
|
|
77
|
+
if not isinstance(result, xr.Dataset):
|
|
78
|
+
raise ValueError(f"Expected xarray Dataset, got {type(result)}")
|
|
75
79
|
|
|
76
80
|
async with lock:
|
|
77
81
|
completed_count += 1
|
|
@@ -88,11 +92,11 @@ async def zonal_stats(
|
|
|
88
92
|
try:
|
|
89
93
|
async with BoundedTaskGroup(max_concurrency=conc) as tg:
|
|
90
94
|
tasks = [
|
|
91
|
-
tg.create_task(process_geometry(gdf.geometry.iloc[idx]
|
|
95
|
+
tg.create_task(process_geometry(gdf.geometry.iloc[idx]))
|
|
92
96
|
for idx in range(len(gdf))
|
|
93
97
|
]
|
|
94
98
|
all_results = [task.result() for task in tasks]
|
|
95
|
-
|
|
99
|
+
|
|
96
100
|
except* Exception as eg:
|
|
97
101
|
for e in eg.exceptions:
|
|
98
102
|
if hasattr(e, 'response'):
|
|
@@ -100,73 +104,51 @@ async def zonal_stats(
|
|
|
100
104
|
raise
|
|
101
105
|
|
|
102
106
|
client.logger.info("All requests completed! Processing results...")
|
|
107
|
+
|
|
103
108
|
|
|
104
109
|
if not all_results:
|
|
105
110
|
raise ValueError("No valid results were returned for any geometry")
|
|
106
|
-
|
|
107
|
-
combined_df = pd.concat(all_results, ignore_index=True)
|
|
108
111
|
|
|
109
|
-
|
|
112
|
+
|
|
113
|
+
result_rows = []
|
|
114
|
+
geometries = []
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
# Results should be in the same order as input geometries
|
|
117
|
+
for i, dataset in enumerate(all_results):
|
|
118
|
+
# Get the original geometry by index
|
|
119
|
+
original_geom = gdf.geometry.iloc[i]
|
|
120
|
+
# Create single row for this geometry
|
|
121
|
+
new_row = {}
|
|
122
|
+
|
|
123
|
+
# Copy original GeoDataFrame attributes
|
|
124
|
+
for col in gdf.columns:
|
|
125
|
+
if col != 'geometry':
|
|
126
|
+
new_row[col] = gdf.iloc[i][col]
|
|
127
|
+
# Store the full dataarray for each variable (with time dimension intact)
|
|
128
|
+
data_vars = list(dataset.data_vars.keys())
|
|
129
|
+
for var_name in data_vars:
|
|
130
|
+
var_data = dataset[var_name]
|
|
131
|
+
new_row[f'{var_name}_dataarray'] = var_data
|
|
132
|
+
result_rows.append(new_row)
|
|
133
|
+
geometries.append(original_geom)
|
|
134
|
+
# Create the result GeoDataFrame with default integer index
|
|
135
|
+
result_gdf = GeoDataFrame(result_rows, geometry=geometries)
|
|
136
|
+
if inplace:
|
|
137
|
+
# Clear original gdf and replace with result_gdf content
|
|
138
|
+
gdf.drop(gdf.index, inplace=True)
|
|
139
|
+
gdf.drop(gdf.columns, axis=1, inplace=True)
|
|
116
140
|
|
|
117
|
-
|
|
141
|
+
# Copy all data from result_gdf to gdf
|
|
142
|
+
for col in result_gdf.columns:
|
|
143
|
+
gdf[col] = result_gdf[col].values
|
|
118
144
|
|
|
119
|
-
|
|
120
|
-
|
|
145
|
+
# Ensure it remains a GeoDataFrame with correct geometry
|
|
146
|
+
gdf.geometry = result_gdf.geometry
|
|
121
147
|
|
|
122
|
-
|
|
123
|
-
new_row = {}
|
|
124
|
-
|
|
125
|
-
for col in gdf.columns:
|
|
126
|
-
if col != 'geometry':
|
|
127
|
-
new_row[col] = gdf.loc[geom_idx, col]
|
|
128
|
-
|
|
129
|
-
for col in result_cols:
|
|
130
|
-
new_row[col] = row[col]
|
|
131
|
-
|
|
132
|
-
result_rows.append(new_row)
|
|
133
|
-
geometries.append(gdf.geometry.iloc[geom_idx])
|
|
134
|
-
|
|
135
|
-
multi_index = pd.MultiIndex.from_tuples(
|
|
136
|
-
combined_df.index.tolist(),
|
|
137
|
-
names=['geometry_index', 'time']
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
result_gdf = GeoDataFrame(
|
|
141
|
-
result_rows,
|
|
142
|
-
geometry=geometries,
|
|
143
|
-
index=multi_index
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
if inplace:
|
|
147
|
-
return result_gdf
|
|
148
|
-
else:
|
|
149
|
-
return result_gdf
|
|
148
|
+
return None
|
|
150
149
|
else:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
result_cols = [col for col in combined_df.columns if col not in ['_geometry_index']]
|
|
154
|
-
|
|
155
|
-
geom_idx_to_row = {}
|
|
156
|
-
for idx, row in combined_df.iterrows():
|
|
157
|
-
geom_idx = int(row['_geometry_index'])
|
|
158
|
-
geom_idx_to_row[geom_idx] = row
|
|
159
|
-
|
|
160
|
-
for col in result_cols:
|
|
161
|
-
if col not in result_gdf.columns:
|
|
162
|
-
result_gdf[col] = None
|
|
163
|
-
|
|
164
|
-
for geom_idx, row in geom_idx_to_row.items():
|
|
165
|
-
result_gdf.loc[geom_idx, col] = row[col]
|
|
166
|
-
if inplace:
|
|
167
|
-
return None
|
|
168
|
-
else:
|
|
169
|
-
return result_gdf
|
|
150
|
+
|
|
151
|
+
return result_gdf
|
|
170
152
|
|
|
171
153
|
async def create_dataset_file(
|
|
172
154
|
client,
|
|
@@ -166,7 +166,6 @@ class SyncClient:
|
|
|
166
166
|
feature: Union[Dict[str, Any], ShapelyGeometry],
|
|
167
167
|
in_crs: str = "epsg:4326",
|
|
168
168
|
out_crs: str = "epsg:4326",
|
|
169
|
-
output: str = "csv",
|
|
170
169
|
resolution: int = -1,
|
|
171
170
|
geom_fix: bool = False,
|
|
172
171
|
**kwargs
|
|
@@ -179,7 +178,6 @@ class SyncClient:
|
|
|
179
178
|
feature (Union[Dict[str, Any], ShapelyGeometry]): The geographic feature
|
|
180
179
|
in_crs (str): Input coordinate reference system
|
|
181
180
|
out_crs (str): Output coordinate reference system
|
|
182
|
-
output (str): Output format ('csv' or 'netcdf')
|
|
183
181
|
resolution (int): Resolution parameter
|
|
184
182
|
geom_fix (bool): Whether to fix the geometry (default False)
|
|
185
183
|
**kwargs: Additional parameters to pass to the WCS request
|
|
@@ -195,7 +193,7 @@ class SyncClient:
|
|
|
195
193
|
feature=feature,
|
|
196
194
|
in_crs=in_crs,
|
|
197
195
|
out_crs=out_crs,
|
|
198
|
-
output=
|
|
196
|
+
output="netcdf",
|
|
199
197
|
resolution=resolution,
|
|
200
198
|
geom_fix=geom_fix,
|
|
201
199
|
**kwargs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: terrakio-core
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
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
|
|
@@ -22,6 +22,7 @@ Requires-Dist: xarray>=2023.1.0
|
|
|
22
22
|
Requires-Dist: shapely>=2.0.0
|
|
23
23
|
Requires-Dist: geopandas>=0.13.0
|
|
24
24
|
Requires-Dist: google-cloud-storage>=2.0.0
|
|
25
|
+
Requires-Dist: scipy>=1.7.0
|
|
25
26
|
Requires-Dist: nest_asyncio
|
|
26
27
|
Provides-Extra: ml
|
|
27
28
|
Requires-Dist: torch>=2.7.1; extra == "ml"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|