terrakio-core 0.4.98.1b5__py3-none-any.whl → 0.4.98.1b7__py3-none-any.whl
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/endpoints/mass_stats.py +584 -303
- terrakio_core/endpoints/model_management.py +0 -125
- {terrakio_core-0.4.98.1b5.dist-info → terrakio_core-0.4.98.1b7.dist-info}/METADATA +1 -1
- {terrakio_core-0.4.98.1b5.dist-info → terrakio_core-0.4.98.1b7.dist-info}/RECORD +5 -5
- {terrakio_core-0.4.98.1b5.dist-info → terrakio_core-0.4.98.1b7.dist-info}/WHEEL +0 -0
|
@@ -1,34 +1,34 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import os
|
|
2
3
|
import time
|
|
3
|
-
import typer
|
|
4
4
|
from typing import Dict, Any, Optional, List
|
|
5
|
-
from dateutil import parser
|
|
6
5
|
|
|
6
|
+
import aiohttp
|
|
7
|
+
import typer
|
|
8
|
+
from dateutil import parser
|
|
7
9
|
from rich.console import Console
|
|
8
10
|
from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
|
9
11
|
|
|
10
12
|
from ..exceptions import (
|
|
13
|
+
CancelAllTasksError,
|
|
14
|
+
CancelCollectionTasksError,
|
|
15
|
+
CancelTaskError,
|
|
16
|
+
CollectionAlreadyExistsError,
|
|
17
|
+
CollectionNotFoundError,
|
|
11
18
|
CreateCollectionError,
|
|
19
|
+
DeleteCollectionError,
|
|
20
|
+
DownloadFilesError,
|
|
12
21
|
GetCollectionError,
|
|
13
|
-
|
|
14
|
-
CollectionNotFoundError,
|
|
15
|
-
CollectionAlreadyExistsError,
|
|
22
|
+
GetTaskError,
|
|
16
23
|
InvalidCollectionTypeError,
|
|
17
|
-
|
|
24
|
+
ListCollectionsError,
|
|
18
25
|
ListTasksError,
|
|
19
|
-
UploadRequestsError,
|
|
20
|
-
UploadArtifactsError,
|
|
21
|
-
GetTaskError,
|
|
22
26
|
TaskNotFoundError,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
CancelCollectionTasksError,
|
|
26
|
-
CancelAllTasksError,
|
|
27
|
+
UploadArtifactsError,
|
|
28
|
+
UploadRequestsError,
|
|
27
29
|
)
|
|
28
30
|
from ..helper.decorators import require_api_key
|
|
29
31
|
|
|
30
|
-
import aiohttp # Make sure this is imported at the top
|
|
31
|
-
|
|
32
32
|
|
|
33
33
|
class MassStats:
|
|
34
34
|
def __init__(self, client):
|
|
@@ -156,6 +156,7 @@ class MassStats:
|
|
|
156
156
|
|
|
157
157
|
self.console.print(f"[bold green]All {number_of_jobs} jobs finished![/bold green]")
|
|
158
158
|
|
|
159
|
+
# below are functions related to collection
|
|
159
160
|
@require_api_key
|
|
160
161
|
async def create_collection(
|
|
161
162
|
self,
|
|
@@ -202,45 +203,6 @@ class MassStats:
|
|
|
202
203
|
|
|
203
204
|
return response
|
|
204
205
|
|
|
205
|
-
@require_api_key
|
|
206
|
-
async def delete_collection(
|
|
207
|
-
self,
|
|
208
|
-
collection: str,
|
|
209
|
-
full: Optional[bool] = False,
|
|
210
|
-
outputs: Optional[list] = [],
|
|
211
|
-
data: Optional[bool] = False
|
|
212
|
-
) -> Dict[str, Any]:
|
|
213
|
-
"""
|
|
214
|
-
Delete a collection by name.
|
|
215
|
-
|
|
216
|
-
Args:
|
|
217
|
-
collection: The name of the collection to delete (required)
|
|
218
|
-
full: Delete the full collection (optional, defaults to False)
|
|
219
|
-
outputs: Specific output folders to delete (optional, defaults to empty list)
|
|
220
|
-
data: Whether to delete raw data (xdata folder) (optional, defaults to False)
|
|
221
|
-
|
|
222
|
-
Returns:
|
|
223
|
-
API response as a dictionary confirming deletion
|
|
224
|
-
|
|
225
|
-
Raises:
|
|
226
|
-
CollectionNotFoundError: If the collection is not found
|
|
227
|
-
DeleteCollectionError: If the API request fails due to unknown reasons
|
|
228
|
-
"""
|
|
229
|
-
payload = {
|
|
230
|
-
"full": full,
|
|
231
|
-
"outputs": outputs,
|
|
232
|
-
"data": data
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
response, status = await self._client._terrakio_request("DELETE", f"collections/{collection}", json=payload)
|
|
236
|
-
|
|
237
|
-
if status != 200:
|
|
238
|
-
if status == 404:
|
|
239
|
-
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
240
|
-
raise DeleteCollectionError(f"Delete collection failed with status {status}", status_code=status)
|
|
241
|
-
|
|
242
|
-
return response
|
|
243
|
-
|
|
244
206
|
@require_api_key
|
|
245
207
|
async def get_collection(self, collection: str) -> Dict[str, Any]:
|
|
246
208
|
"""
|
|
@@ -303,6 +265,46 @@ class MassStats:
|
|
|
303
265
|
|
|
304
266
|
return response
|
|
305
267
|
|
|
268
|
+
@require_api_key
|
|
269
|
+
async def delete_collection(
|
|
270
|
+
self,
|
|
271
|
+
collection: str,
|
|
272
|
+
full: Optional[bool] = False,
|
|
273
|
+
outputs: Optional[list] = [],
|
|
274
|
+
data: Optional[bool] = False
|
|
275
|
+
) -> Dict[str, Any]:
|
|
276
|
+
"""
|
|
277
|
+
Delete a collection by name.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
collection: The name of the collection to delete (required)
|
|
281
|
+
full: Delete the full collection (optional, defaults to False)
|
|
282
|
+
outputs: Specific output folders to delete (optional, defaults to empty list)
|
|
283
|
+
data: Whether to delete raw data (xdata folder) (optional, defaults to False)
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
API response as a dictionary confirming deletion
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
CollectionNotFoundError: If the collection is not found
|
|
290
|
+
DeleteCollectionError: If the API request fails due to unknown reasons
|
|
291
|
+
"""
|
|
292
|
+
payload = {
|
|
293
|
+
"full": full,
|
|
294
|
+
"outputs": outputs,
|
|
295
|
+
"data": data
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
response, status = await self._client._terrakio_request("DELETE", f"collections/{collection}", json=payload)
|
|
299
|
+
|
|
300
|
+
if status != 200:
|
|
301
|
+
if status == 404:
|
|
302
|
+
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
303
|
+
raise DeleteCollectionError(f"Delete collection failed with status {status}", status_code=status)
|
|
304
|
+
|
|
305
|
+
return response
|
|
306
|
+
|
|
307
|
+
# below are functions related to tasks
|
|
306
308
|
@require_api_key
|
|
307
309
|
async def list_tasks(
|
|
308
310
|
self,
|
|
@@ -332,34 +334,109 @@ class MassStats:
|
|
|
332
334
|
raise ListTasksError(f"List tasks failed with status {status}", status_code=status)
|
|
333
335
|
|
|
334
336
|
return response
|
|
337
|
+
|
|
338
|
+
@require_api_key
|
|
339
|
+
async def get_task(
|
|
340
|
+
self,
|
|
341
|
+
task_id: str
|
|
342
|
+
) -> Dict[str, Any]:
|
|
343
|
+
"""
|
|
344
|
+
Get task information by task ID.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
task_id: ID of task to track
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
API response as a dictionary containing task information
|
|
351
|
+
|
|
352
|
+
Raises:
|
|
353
|
+
TaskNotFoundError: If the task is not found
|
|
354
|
+
GetTaskError: If the API request fails due to unknown reasons
|
|
355
|
+
"""
|
|
356
|
+
response, status = await self._client._terrakio_request("GET", f"tasks/info/{task_id}")
|
|
357
|
+
|
|
358
|
+
if status != 200:
|
|
359
|
+
if status == 404:
|
|
360
|
+
raise TaskNotFoundError(f"Task {task_id} not found", status_code=status)
|
|
361
|
+
raise GetTaskError(f"Get task failed with status {status}", status_code=status)
|
|
362
|
+
|
|
363
|
+
return response
|
|
364
|
+
|
|
365
|
+
@require_api_key
|
|
366
|
+
async def cancel_task(
|
|
367
|
+
self,
|
|
368
|
+
task_id: str
|
|
369
|
+
) -> Dict[str, Any]:
|
|
370
|
+
"""
|
|
371
|
+
Cancel a task by task ID.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
task_id: ID of task to cancel
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
API response as a dictionary containing task information
|
|
378
|
+
|
|
379
|
+
Raises:
|
|
380
|
+
TaskNotFoundError: If the task is not found
|
|
381
|
+
CancelTaskError: If the API request fails due to unknown reasons
|
|
382
|
+
"""
|
|
383
|
+
response, status = await self._client._terrakio_request("POST", f"tasks/cancel/{task_id}")
|
|
384
|
+
|
|
385
|
+
if status != 200:
|
|
386
|
+
if status == 404:
|
|
387
|
+
raise TaskNotFoundError(f"Task {task_id} not found", status_code=status)
|
|
388
|
+
raise CancelTaskError(f"Cancel task failed with status {status}", status_code=status)
|
|
389
|
+
|
|
390
|
+
return response
|
|
335
391
|
|
|
336
392
|
@require_api_key
|
|
337
|
-
async def
|
|
393
|
+
async def cancel_collection_tasks(
|
|
338
394
|
self,
|
|
339
395
|
collection: str
|
|
340
396
|
) -> Dict[str, Any]:
|
|
341
397
|
"""
|
|
342
|
-
|
|
398
|
+
Cancel all tasks for a collection.
|
|
343
399
|
|
|
344
400
|
Args:
|
|
345
401
|
collection: Name of collection
|
|
346
|
-
|
|
402
|
+
|
|
347
403
|
Returns:
|
|
348
|
-
API response as a dictionary containing the
|
|
404
|
+
API response as a dictionary containing task information for the collection
|
|
349
405
|
|
|
350
406
|
Raises:
|
|
351
407
|
CollectionNotFoundError: If the collection is not found
|
|
352
|
-
|
|
408
|
+
CancelCollectionTasksError: If the API request fails due to unknown reasons
|
|
353
409
|
"""
|
|
354
|
-
response, status = await self._client._terrakio_request("
|
|
355
|
-
|
|
410
|
+
response, status = await self._client._terrakio_request("POST", f"collections/{collection}/cancel")
|
|
411
|
+
|
|
356
412
|
if status != 200:
|
|
357
413
|
if status == 404:
|
|
358
414
|
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
359
|
-
raise
|
|
415
|
+
raise CancelCollectionTasksError(f"Cancel collection tasks failed with status {status}", status_code=status)
|
|
416
|
+
|
|
417
|
+
return response
|
|
418
|
+
|
|
419
|
+
@require_api_key
|
|
420
|
+
async def cancel_all_tasks(
|
|
421
|
+
self
|
|
422
|
+
) -> Dict[str, Any]:
|
|
423
|
+
"""
|
|
424
|
+
Cancel all tasks for the current user.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
API response as a dictionary containing task information for all tasks
|
|
428
|
+
|
|
429
|
+
Raises:
|
|
430
|
+
CancelAllTasksError: If the API request fails due to unknown reasons
|
|
431
|
+
"""
|
|
432
|
+
response, status = await self._client._terrakio_request("POST", "tasks/cancel")
|
|
433
|
+
|
|
434
|
+
if status != 200:
|
|
435
|
+
raise CancelAllTasksError(f"Cancel all tasks failed with status {status}", status_code=status)
|
|
360
436
|
|
|
361
437
|
return response
|
|
362
438
|
|
|
439
|
+
# below are functions related to the web ui and needs to be deleted in the future
|
|
363
440
|
@require_api_key
|
|
364
441
|
async def upload_artifacts(
|
|
365
442
|
self,
|
|
@@ -395,53 +472,68 @@ class MassStats:
|
|
|
395
472
|
raise UploadArtifactsError(f"Upload artifacts failed with status {status}", status_code=status)
|
|
396
473
|
|
|
397
474
|
return response
|
|
398
|
-
|
|
475
|
+
|
|
399
476
|
@require_api_key
|
|
400
|
-
async def
|
|
477
|
+
async def zonal_stats(
|
|
401
478
|
self,
|
|
402
|
-
|
|
479
|
+
collection: str,
|
|
480
|
+
id_property: str,
|
|
481
|
+
column_name: str,
|
|
482
|
+
expr: str,
|
|
483
|
+
resolution: Optional[int] = 1,
|
|
484
|
+
in_crs: Optional[str] = "epsg:4326",
|
|
485
|
+
out_crs: Optional[str] = "epsg:4326"
|
|
403
486
|
) -> Dict[str, Any]:
|
|
404
487
|
"""
|
|
405
|
-
|
|
488
|
+
Run zonal stats over uploaded geojson collection.
|
|
406
489
|
|
|
407
490
|
Args:
|
|
408
|
-
|
|
409
|
-
|
|
491
|
+
collection: Name of collection
|
|
492
|
+
id_property: Property key in geojson to use as id
|
|
493
|
+
column_name: Name of new column to add
|
|
494
|
+
expr: Terrak.io expression to evaluate
|
|
495
|
+
resolution: Resolution of request (optional, defaults to 1)
|
|
496
|
+
in_crs: CRS of geojson (optional, defaults to "epsg:4326")
|
|
497
|
+
out_crs: Desired output CRS (optional, defaults to "epsg:4326")
|
|
498
|
+
|
|
410
499
|
Returns:
|
|
411
500
|
API response as a dictionary containing task information
|
|
412
501
|
|
|
413
502
|
Raises:
|
|
414
|
-
|
|
503
|
+
CollectionNotFoundError: If the collection is not found
|
|
415
504
|
GetTaskError: If the API request fails due to unknown reasons
|
|
416
505
|
"""
|
|
417
|
-
|
|
506
|
+
payload = {
|
|
507
|
+
"id_property": id_property,
|
|
508
|
+
"column_name": column_name,
|
|
509
|
+
"expr": expr,
|
|
510
|
+
"resolution": resolution,
|
|
511
|
+
"in_crs": in_crs,
|
|
512
|
+
"out_crs": out_crs
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
response, status = await self._client._terrakio_request("POST", f"collections/{collection}/zonal_stats", json=payload)
|
|
418
516
|
|
|
419
517
|
if status != 200:
|
|
420
518
|
if status == 404:
|
|
421
|
-
raise
|
|
422
|
-
raise GetTaskError(f"
|
|
519
|
+
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
520
|
+
raise GetTaskError(f"Zonal stats failed with status {status}", status_code=status)
|
|
423
521
|
|
|
424
522
|
return response
|
|
425
523
|
|
|
426
524
|
@require_api_key
|
|
427
|
-
async def
|
|
525
|
+
async def zonal_stats_transform(
|
|
428
526
|
self,
|
|
429
527
|
collection: str,
|
|
430
|
-
|
|
431
|
-
skip_existing: Optional[bool] = True,
|
|
432
|
-
force_loc: Optional[bool] = None,
|
|
433
|
-
server: Optional[str] = None
|
|
528
|
+
consumer: str
|
|
434
529
|
) -> Dict[str, Any]:
|
|
435
530
|
"""
|
|
436
|
-
|
|
531
|
+
Transform raw data in collection. Creates a new collection.
|
|
437
532
|
|
|
438
533
|
Args:
|
|
439
534
|
collection: Name of collection
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
skip_existing: Skip existing data
|
|
443
|
-
server: Server to use
|
|
444
|
-
|
|
535
|
+
consumer: Post processing script (file path or script content)
|
|
536
|
+
|
|
445
537
|
Returns:
|
|
446
538
|
API response as a dictionary containing task information
|
|
447
539
|
|
|
@@ -449,53 +541,127 @@ class MassStats:
|
|
|
449
541
|
CollectionNotFoundError: If the collection is not found
|
|
450
542
|
GetTaskError: If the API request fails due to unknown reasons
|
|
451
543
|
"""
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
544
|
+
if os.path.isfile(consumer):
|
|
545
|
+
with open(consumer, 'r') as f:
|
|
546
|
+
script_content = f.read()
|
|
547
|
+
else:
|
|
548
|
+
script_content = consumer
|
|
549
|
+
|
|
550
|
+
files = {
|
|
551
|
+
'consumer': ('script.py', script_content, 'text/plain')
|
|
552
|
+
}
|
|
458
553
|
|
|
459
|
-
response, status = await self._client._terrakio_request(
|
|
554
|
+
response, status = await self._client._terrakio_request(
|
|
555
|
+
"POST",
|
|
556
|
+
f"collections/{collection}/transform",
|
|
557
|
+
files=files
|
|
558
|
+
)
|
|
460
559
|
|
|
461
560
|
if status != 200:
|
|
462
561
|
if status == 404:
|
|
463
562
|
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
464
|
-
raise GetTaskError(f"
|
|
563
|
+
raise GetTaskError(f"Transform failed with status {status}", status_code=status)
|
|
465
564
|
|
|
466
565
|
return response
|
|
467
566
|
|
|
567
|
+
async def _upload_requests(
|
|
568
|
+
self,
|
|
569
|
+
collection: str
|
|
570
|
+
) -> Dict[str, Any]:
|
|
571
|
+
"""
|
|
572
|
+
Retrieve signed url to upload requests for a collection.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
collection: Name of collection
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
API response as a dictionary containing the upload URL
|
|
579
|
+
|
|
580
|
+
Raises:
|
|
581
|
+
CollectionNotFoundError: If the collection is not found
|
|
582
|
+
UploadRequestsError: If the API request fails due to unknown reasons
|
|
583
|
+
"""
|
|
584
|
+
response, status = await self._client._terrakio_request("GET", f"collections/{collection}/upload/requests")
|
|
585
|
+
|
|
586
|
+
if status != 200:
|
|
587
|
+
if status == 404:
|
|
588
|
+
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
589
|
+
raise UploadRequestsError(f"Upload requests failed with status {status}", status_code=status)
|
|
590
|
+
|
|
591
|
+
return response
|
|
592
|
+
|
|
468
593
|
@require_api_key
|
|
469
|
-
async def
|
|
594
|
+
async def _upload_file(self, file_path: str, url: str, use_gzip: bool = True):
|
|
595
|
+
"""
|
|
596
|
+
Helper method to upload a JSON file to a signed URL.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
file_path: Path to the JSON file
|
|
600
|
+
url: Signed URL to upload to
|
|
601
|
+
use_gzip: Whether to compress the file with gzip
|
|
602
|
+
"""
|
|
603
|
+
try:
|
|
604
|
+
with open(file_path, 'r') as file:
|
|
605
|
+
json_data = json.load(file)
|
|
606
|
+
except FileNotFoundError:
|
|
607
|
+
raise FileNotFoundError(f"JSON file not found: {file_path}")
|
|
608
|
+
except json.JSONDecodeError as e:
|
|
609
|
+
raise ValueError(f"Invalid JSON in file {file_path}: {e}")
|
|
610
|
+
|
|
611
|
+
return await self._upload_json_data(json_data, url, use_gzip)
|
|
612
|
+
|
|
613
|
+
@require_api_key
|
|
614
|
+
async def _upload_json_data(self, json_data, url: str, use_gzip: bool = True):
|
|
615
|
+
"""
|
|
616
|
+
Helper method to upload JSON data directly to a signed URL.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
json_data: JSON data (dict or list) to upload
|
|
620
|
+
url: Signed URL to upload to
|
|
621
|
+
use_gzip: Whether to compress the data with gzip
|
|
622
|
+
"""
|
|
623
|
+
if hasattr(json, 'dumps') and 'ignore_nan' in json.dumps.__code__.co_varnames:
|
|
624
|
+
dumps_kwargs = {'ignore_nan': True}
|
|
625
|
+
else:
|
|
626
|
+
dumps_kwargs = {}
|
|
627
|
+
|
|
628
|
+
if use_gzip:
|
|
629
|
+
import gzip
|
|
630
|
+
body = gzip.compress(json.dumps(json_data, **dumps_kwargs).encode('utf-8'))
|
|
631
|
+
headers = {
|
|
632
|
+
'Content-Type': 'application/json',
|
|
633
|
+
'Content-Encoding': 'gzip'
|
|
634
|
+
}
|
|
635
|
+
else:
|
|
636
|
+
body = json.dumps(json_data, **dumps_kwargs).encode('utf-8')
|
|
637
|
+
headers = {
|
|
638
|
+
'Content-Type': 'application/json'
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
response = await self._client._regular_request("PUT", url, data=body, headers=headers)
|
|
642
|
+
return response
|
|
643
|
+
|
|
644
|
+
@require_api_key
|
|
645
|
+
async def generate_data(
|
|
470
646
|
self,
|
|
471
647
|
collection: str,
|
|
472
|
-
|
|
473
|
-
filters: list[str],
|
|
474
|
-
aoi: dict,
|
|
475
|
-
samples: int,
|
|
476
|
-
crs: str,
|
|
477
|
-
tile_size: int,
|
|
478
|
-
res: float,
|
|
648
|
+
file_path: str,
|
|
479
649
|
output: str,
|
|
480
|
-
|
|
650
|
+
skip_existing: Optional[bool] = True,
|
|
651
|
+
force_loc: Optional[bool] = None,
|
|
481
652
|
server: Optional[str] = None
|
|
482
653
|
) -> Dict[str, Any]:
|
|
483
654
|
"""
|
|
484
|
-
Generate
|
|
655
|
+
Generate data for a collection.
|
|
485
656
|
|
|
486
657
|
Args:
|
|
487
658
|
collection: Name of collection
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
crs: CRS of AOI
|
|
493
|
-
tile_size: Pixel width and height of samples
|
|
494
|
-
res: Resolution of samples
|
|
495
|
-
output: Sample output type
|
|
496
|
-
year_range: Optional year range filter
|
|
659
|
+
file_path: Path to the file to upload
|
|
660
|
+
output: Output type (str)
|
|
661
|
+
force_loc: Write data directly to the cloud under this folder
|
|
662
|
+
skip_existing: Skip existing data
|
|
497
663
|
server: Server to use
|
|
498
|
-
|
|
664
|
+
|
|
499
665
|
Returns:
|
|
500
666
|
API response as a dictionary containing task information
|
|
501
667
|
|
|
@@ -503,81 +669,34 @@ class MassStats:
|
|
|
503
669
|
CollectionNotFoundError: If the collection is not found
|
|
504
670
|
GetTaskError: If the API request fails due to unknown reasons
|
|
505
671
|
"""
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
672
|
+
await self.create_collection(
|
|
673
|
+
collection = collection
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
upload_urls = await self._upload_requests(
|
|
677
|
+
collection = collection
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
url = upload_urls['url']
|
|
681
|
+
|
|
682
|
+
await self._upload_file(file_path, url)
|
|
683
|
+
|
|
684
|
+
payload = {"output": output, "skip_existing": skip_existing}
|
|
516
685
|
|
|
517
|
-
if
|
|
518
|
-
payload["
|
|
686
|
+
if force_loc is not None:
|
|
687
|
+
payload["force_loc"] = force_loc
|
|
519
688
|
if server is not None:
|
|
520
689
|
payload["server"] = server
|
|
521
690
|
|
|
522
|
-
response, status = await self._client._terrakio_request("POST", f"collections/{collection}/
|
|
691
|
+
response, status = await self._client._terrakio_request("POST", f"collections/{collection}/generate_data", json=payload)
|
|
523
692
|
|
|
524
693
|
if status != 200:
|
|
525
694
|
if status == 404:
|
|
526
695
|
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
527
|
-
raise GetTaskError(f"
|
|
696
|
+
raise GetTaskError(f"Generate data failed with status {status}", status_code=status)
|
|
528
697
|
|
|
529
698
|
return response
|
|
530
699
|
|
|
531
|
-
# @require_api_key
|
|
532
|
-
# async def post_processing(
|
|
533
|
-
# self,
|
|
534
|
-
# collection: str,
|
|
535
|
-
# folder: str,
|
|
536
|
-
# consumer: str
|
|
537
|
-
# ) -> Dict[str, Any]:
|
|
538
|
-
# """
|
|
539
|
-
# Run post processing for a collection.
|
|
540
|
-
|
|
541
|
-
# Args:
|
|
542
|
-
# collection: Name of collection
|
|
543
|
-
# folder: Folder to store output
|
|
544
|
-
# consumer: Post processing script
|
|
545
|
-
|
|
546
|
-
# Returns:
|
|
547
|
-
# API response as a dictionary containing task information
|
|
548
|
-
|
|
549
|
-
# Raises:
|
|
550
|
-
# CollectionNotFoundError: If the collection is not found
|
|
551
|
-
# GetTaskError: If the API request fails due to unknown reasons
|
|
552
|
-
# """
|
|
553
|
-
# # payload = {
|
|
554
|
-
# # "folder": folder,
|
|
555
|
-
# # "consumer": consumer
|
|
556
|
-
# # }
|
|
557
|
-
# # we have the consumer as a string, we need to read in the file and then pass in the content
|
|
558
|
-
# with open(consumer, 'rb') as f:
|
|
559
|
-
# files = {
|
|
560
|
-
# 'consumer': ('consumer.py', f.read(), 'text/plain')
|
|
561
|
-
# }
|
|
562
|
-
# data = {
|
|
563
|
-
# 'folder': folder
|
|
564
|
-
# }
|
|
565
|
-
|
|
566
|
-
# # response, status = await self._client._terrakio_request("POST", f"collections/{collection}/post_process", json=payload)
|
|
567
|
-
# response, status = await self._client._terrakio_request(
|
|
568
|
-
# "POST",
|
|
569
|
-
# f"collections/{collection}/post_process",
|
|
570
|
-
# files=files,
|
|
571
|
-
# data=data
|
|
572
|
-
# )
|
|
573
|
-
# if status != 200:
|
|
574
|
-
# if status == 404:
|
|
575
|
-
# raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
576
|
-
# raise GetTaskError(f"Post processing failed with status {status}", status_code=status)
|
|
577
|
-
|
|
578
|
-
# return response
|
|
579
|
-
|
|
580
|
-
|
|
581
700
|
@require_api_key
|
|
582
701
|
async def post_processing(
|
|
583
702
|
self,
|
|
@@ -600,22 +719,24 @@ class MassStats:
|
|
|
600
719
|
CollectionNotFoundError: If the collection is not found
|
|
601
720
|
GetTaskError: If the API request fails due to unknown reasons
|
|
602
721
|
"""
|
|
603
|
-
|
|
722
|
+
await self.create_collection(
|
|
723
|
+
collection = collection
|
|
724
|
+
)
|
|
725
|
+
|
|
604
726
|
with open(consumer, 'rb') as f:
|
|
605
727
|
form = aiohttp.FormData()
|
|
606
|
-
form.add_field('folder', folder)
|
|
728
|
+
form.add_field('folder', folder)
|
|
607
729
|
form.add_field(
|
|
608
|
-
'consumer',
|
|
609
|
-
f.read(),
|
|
610
|
-
filename='consumer.py',
|
|
611
|
-
content_type='text/x-python'
|
|
730
|
+
'consumer',
|
|
731
|
+
f.read(),
|
|
732
|
+
filename='consumer.py',
|
|
733
|
+
content_type='text/x-python'
|
|
612
734
|
)
|
|
613
735
|
|
|
614
|
-
# Send using data= with FormData object (NOT files=)
|
|
615
736
|
response, status = await self._client._terrakio_request(
|
|
616
737
|
"POST",
|
|
617
738
|
f"collections/{collection}/post_process",
|
|
618
|
-
data=form
|
|
739
|
+
data=form
|
|
619
740
|
)
|
|
620
741
|
|
|
621
742
|
if status != 200:
|
|
@@ -626,95 +747,137 @@ class MassStats:
|
|
|
626
747
|
return response
|
|
627
748
|
|
|
628
749
|
@require_api_key
|
|
629
|
-
async def
|
|
750
|
+
async def training_samples(
|
|
630
751
|
self,
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
752
|
+
name: str,
|
|
753
|
+
aoi: str,
|
|
754
|
+
expression_x: str,
|
|
755
|
+
filter_x: str = "skip",
|
|
756
|
+
filter_x_rate: float = 1,
|
|
757
|
+
expression_y: str = "skip",
|
|
758
|
+
filter_y: str = "skip",
|
|
759
|
+
filter_y_rate: float = 1,
|
|
760
|
+
samples: int = 1000,
|
|
761
|
+
tile_size: float = 256,
|
|
762
|
+
crs: str = "epsg:3577",
|
|
763
|
+
res: float = 10,
|
|
764
|
+
res_y: float = None,
|
|
765
|
+
skip_test: bool = False,
|
|
766
|
+
start_year: int = None,
|
|
767
|
+
end_year: int = None,
|
|
768
|
+
bucket: str = None,
|
|
769
|
+
server: str = None,
|
|
770
|
+
extra_filters: list[str] = None,
|
|
771
|
+
extra_filters_rate: list[float] = None,
|
|
772
|
+
extra_filters_res: list[float] = None
|
|
773
|
+
) -> dict:
|
|
639
774
|
"""
|
|
640
|
-
|
|
775
|
+
Generate an AI dataset using specified parameters.
|
|
641
776
|
|
|
642
777
|
Args:
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
778
|
+
name: Name of the collection to create
|
|
779
|
+
aoi: Path to GeoJSON file containing area of interest
|
|
780
|
+
expression_x: Expression for X data (features)
|
|
781
|
+
filter_x: Filter expression for X data (default: "skip")
|
|
782
|
+
filter_x_rate: Filter rate for X data (default: 1)
|
|
783
|
+
expression_y: Expression for Y data (labels) (default: "skip")
|
|
784
|
+
filter_y: Filter expression for Y data (default: "skip")
|
|
785
|
+
filter_y_rate: Filter rate for Y data (default: 1)
|
|
786
|
+
samples: Number of samples to generate (default: 1000)
|
|
787
|
+
tile_size: Size of tiles in pixels (default: 256)
|
|
788
|
+
crs: Coordinate reference system (default: "epsg:3577")
|
|
789
|
+
res: Resolution for X data (default: 10)
|
|
790
|
+
res_y: Resolution for Y data, defaults to res if None
|
|
791
|
+
skip_test: Skip expression validation test (default: False)
|
|
792
|
+
start_year: Start year for temporal filtering
|
|
793
|
+
end_year: End year for temporal filtering
|
|
794
|
+
bucket: Storage bucket name
|
|
795
|
+
server: Server to use for processing
|
|
796
|
+
extra_filters: Additional filter expressions
|
|
797
|
+
extra_filters_rate: Rates for additional filters
|
|
798
|
+
extra_filters_res: Resolutions for additional filters
|
|
650
799
|
|
|
651
800
|
Returns:
|
|
652
|
-
|
|
801
|
+
Response containing task_id and collection name
|
|
653
802
|
|
|
654
803
|
Raises:
|
|
655
804
|
CollectionNotFoundError: If the collection is not found
|
|
656
|
-
GetTaskError: If the API request fails
|
|
805
|
+
GetTaskError: If the API request fails
|
|
806
|
+
TypeError: If extra filters have mismatched rate and resolution lists
|
|
657
807
|
"""
|
|
658
|
-
|
|
659
|
-
"id_property": id_property,
|
|
660
|
-
"column_name": column_name,
|
|
661
|
-
"expr": expr,
|
|
662
|
-
"resolution": resolution,
|
|
663
|
-
"in_crs": in_crs,
|
|
664
|
-
"out_crs": out_crs
|
|
665
|
-
}
|
|
808
|
+
expressions = [{"expr": expression_x, "res": res, "prefix": "x"}]
|
|
666
809
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
if status != 200:
|
|
670
|
-
if status == 404:
|
|
671
|
-
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
672
|
-
raise GetTaskError(f"Zonal stats failed with status {status}", status_code=status)
|
|
810
|
+
res_y = res_y or res
|
|
673
811
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
812
|
+
if expression_y != "skip":
|
|
813
|
+
expressions.append({"expr": expression_y, "res": res_y, "prefix": "y"})
|
|
814
|
+
|
|
815
|
+
filters = []
|
|
816
|
+
if filter_x != "skip":
|
|
817
|
+
filters.append({"expr": filter_x, "res": res, "rate": filter_x_rate})
|
|
818
|
+
|
|
819
|
+
if filter_y != "skip":
|
|
820
|
+
filters.append({"expr": filter_y, "res": res_y, "rate": filter_y_rate})
|
|
821
|
+
|
|
822
|
+
if extra_filters:
|
|
823
|
+
try:
|
|
824
|
+
extra_filters_combined = zip(extra_filters, extra_filters_res, extra_filters_rate, strict=True)
|
|
825
|
+
except TypeError:
|
|
826
|
+
raise TypeError("Extra filters must have matching rate and resolution.")
|
|
827
|
+
|
|
828
|
+
for expr, filter_res, rate in extra_filters_combined:
|
|
829
|
+
filters.append({"expr": expr, "res": filter_res, "rate": rate})
|
|
830
|
+
|
|
831
|
+
if start_year is not None:
|
|
832
|
+
for expr_dict in expressions:
|
|
833
|
+
expr_dict["expr"] = expr_dict["expr"].replace("{year}", str(start_year))
|
|
834
|
+
|
|
835
|
+
for filter_dict in filters:
|
|
836
|
+
filter_dict["expr"] = filter_dict["expr"].replace("{year}", str(start_year))
|
|
837
|
+
|
|
838
|
+
if not skip_test:
|
|
839
|
+
for expr_dict in expressions:
|
|
840
|
+
test_request = self._client.model._generate_test_request(expr_dict["expr"], crs, -1)
|
|
841
|
+
await self._client._terrakio_request("POST", "geoquery", json=test_request)
|
|
842
|
+
|
|
843
|
+
for filter_dict in filters:
|
|
844
|
+
test_request = self._client.model._generate_test_request(filter_dict["expr"], crs, -1)
|
|
845
|
+
await self._client._terrakio_request("POST", "geoquery", json=test_request)
|
|
846
|
+
|
|
847
|
+
with open(aoi, 'r') as f:
|
|
848
|
+
aoi_data = json.load(f)
|
|
849
|
+
|
|
850
|
+
await self.create_collection(
|
|
851
|
+
collection = name,
|
|
852
|
+
bucket = bucket,
|
|
853
|
+
collection_type = "basic"
|
|
854
|
+
)
|
|
701
855
|
|
|
702
|
-
|
|
703
|
-
|
|
856
|
+
payload = {
|
|
857
|
+
"expressions": expressions,
|
|
858
|
+
"filters": filters,
|
|
859
|
+
"aoi": aoi_data,
|
|
860
|
+
"samples": samples,
|
|
861
|
+
"crs": crs,
|
|
862
|
+
"tile_size": tile_size,
|
|
863
|
+
"res": res,
|
|
864
|
+
"output": "nc",
|
|
865
|
+
"year_range": [start_year, end_year],
|
|
866
|
+
"server": server
|
|
704
867
|
}
|
|
705
868
|
|
|
706
|
-
|
|
707
|
-
"POST",
|
|
708
|
-
f"collections/{collection}/transform",
|
|
709
|
-
files=files
|
|
710
|
-
)
|
|
869
|
+
task_id_dict, status = await self._client._terrakio_request("POST", f"collections/{name}/training_samples", json=payload)
|
|
711
870
|
|
|
712
871
|
if status != 200:
|
|
713
872
|
if status == 404:
|
|
714
|
-
raise CollectionNotFoundError(f"Collection {
|
|
715
|
-
raise GetTaskError(f"
|
|
873
|
+
raise CollectionNotFoundError(f"Collection {name} not found", status_code=status)
|
|
874
|
+
raise GetTaskError(f"Training sample failed with status {status}", status_code=status)
|
|
716
875
|
|
|
717
|
-
|
|
876
|
+
task_id = task_id_dict["task_id"]
|
|
877
|
+
|
|
878
|
+
await self._client.mass_stats.track_progress(task_id)
|
|
879
|
+
|
|
880
|
+
return {"task_id": task_id, "collection": name}
|
|
718
881
|
|
|
719
882
|
@require_api_key
|
|
720
883
|
async def download_files(
|
|
@@ -723,25 +886,32 @@ class MassStats:
|
|
|
723
886
|
file_type: str,
|
|
724
887
|
page: Optional[int] = 0,
|
|
725
888
|
page_size: Optional[int] = 100,
|
|
726
|
-
folder: Optional[str] = None
|
|
889
|
+
folder: Optional[str] = None,
|
|
890
|
+
url: Optional[bool] = True
|
|
727
891
|
) -> Dict[str, Any]:
|
|
728
892
|
"""
|
|
729
|
-
Get list of signed urls to download files in collection.
|
|
893
|
+
Get list of signed urls to download files in collection, or download the files directly.
|
|
730
894
|
|
|
731
895
|
Args:
|
|
732
896
|
collection: Name of collection
|
|
733
|
-
file_type:
|
|
897
|
+
file_type: Type of files to download - must be either 'raw' or 'processed'
|
|
734
898
|
page: Page number (optional, defaults to 0)
|
|
735
899
|
page_size: Number of files to return per page (optional, defaults to 100)
|
|
736
900
|
folder: If processed file type, which folder to download files from (optional)
|
|
901
|
+
url: If True, return signed URLs; if False, download files directly (optional, defaults to True)
|
|
737
902
|
|
|
738
903
|
Returns:
|
|
739
|
-
API response as a dictionary containing list of download URLs
|
|
904
|
+
API response as a dictionary containing list of download URLs (if url=True),
|
|
905
|
+
or a dictionary with downloaded file information (if url=False)
|
|
740
906
|
|
|
741
907
|
Raises:
|
|
742
908
|
CollectionNotFoundError: If the collection is not found
|
|
743
909
|
DownloadFilesError: If the API request fails due to unknown reasons
|
|
910
|
+
ValueError: If file_type is not 'raw' or 'processed'
|
|
744
911
|
"""
|
|
912
|
+
if file_type not in ['raw', 'processed']:
|
|
913
|
+
raise ValueError(f"file_type must be either 'raw' or 'processed', got '{file_type}'")
|
|
914
|
+
|
|
745
915
|
params = {"file_type": file_type}
|
|
746
916
|
|
|
747
917
|
if page is not None:
|
|
@@ -758,78 +928,189 @@ class MassStats:
|
|
|
758
928
|
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
759
929
|
raise DownloadFilesError(f"Download files failed with status {status}", status_code=status)
|
|
760
930
|
|
|
761
|
-
|
|
931
|
+
if url:
|
|
932
|
+
return response
|
|
933
|
+
|
|
934
|
+
downloaded_files = []
|
|
935
|
+
files_to_download = response.get('files', []) if isinstance(response, dict) else []
|
|
936
|
+
|
|
937
|
+
async with aiohttp.ClientSession() as session:
|
|
938
|
+
for file_info in files_to_download:
|
|
939
|
+
try:
|
|
940
|
+
file_url = file_info.get('url')
|
|
941
|
+
filename = file_info.get('file', '')
|
|
942
|
+
group = file_info.get('group', '')
|
|
943
|
+
|
|
944
|
+
if not file_url:
|
|
945
|
+
downloaded_files.append({
|
|
946
|
+
'filename': filename,
|
|
947
|
+
'group': group,
|
|
948
|
+
'error': 'No URL provided'
|
|
949
|
+
})
|
|
950
|
+
continue
|
|
951
|
+
|
|
952
|
+
async with session.get(file_url) as file_response:
|
|
953
|
+
if file_response.status == 200:
|
|
954
|
+
content = await file_response.read()
|
|
955
|
+
|
|
956
|
+
output_dir = folder if folder else "downloads"
|
|
957
|
+
if group:
|
|
958
|
+
output_dir = os.path.join(output_dir, group)
|
|
959
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
960
|
+
filepath = os.path.join(output_dir, filename)
|
|
961
|
+
|
|
962
|
+
with open(filepath, 'wb') as f:
|
|
963
|
+
f.write(content)
|
|
964
|
+
|
|
965
|
+
downloaded_files.append({
|
|
966
|
+
'filename': filename,
|
|
967
|
+
'group': group,
|
|
968
|
+
'filepath': filepath,
|
|
969
|
+
'size': len(content)
|
|
970
|
+
})
|
|
971
|
+
else:
|
|
972
|
+
downloaded_files.append({
|
|
973
|
+
'filename': filename,
|
|
974
|
+
'group': group,
|
|
975
|
+
'error': f"Failed to download: HTTP {file_response.status}"
|
|
976
|
+
})
|
|
977
|
+
except Exception as e:
|
|
978
|
+
downloaded_files.append({
|
|
979
|
+
'filename': file_info.get('file', 'unknown'),
|
|
980
|
+
'group': file_info.get('group', ''),
|
|
981
|
+
'error': str(e)
|
|
982
|
+
})
|
|
983
|
+
|
|
984
|
+
return {
|
|
985
|
+
'collection': collection,
|
|
986
|
+
'downloaded_files': downloaded_files,
|
|
987
|
+
'total': len(downloaded_files)
|
|
988
|
+
}
|
|
762
989
|
|
|
763
990
|
@require_api_key
|
|
764
|
-
async def
|
|
991
|
+
async def gen_and_process(
|
|
765
992
|
self,
|
|
766
|
-
|
|
767
|
-
|
|
993
|
+
collection: str,
|
|
994
|
+
requests_file: str,
|
|
995
|
+
output: str,
|
|
996
|
+
folder: str,
|
|
997
|
+
consumer: str,
|
|
998
|
+
extra: Optional[Dict[str, Any]] = None,
|
|
999
|
+
force_loc: Optional[bool] = False,
|
|
1000
|
+
skip_existing: Optional[bool] = True,
|
|
1001
|
+
server: Optional[str] = None
|
|
1002
|
+
) -> Dict[str, Any]:
|
|
768
1003
|
"""
|
|
769
|
-
|
|
1004
|
+
Generate data and run post-processing in a single task.
|
|
770
1005
|
|
|
771
1006
|
Args:
|
|
772
|
-
|
|
1007
|
+
collection: Name of collection
|
|
1008
|
+
requests_file: Path to JSON file containing request configurations
|
|
1009
|
+
output: Output type (str)
|
|
1010
|
+
folder: Folder to store output
|
|
1011
|
+
consumer: Path to post processing script
|
|
1012
|
+
extra: Additional configuration parameters (optional)
|
|
1013
|
+
force_loc: Write data directly to the cloud under this folder (optional, defaults to False)
|
|
1014
|
+
skip_existing: Skip existing data (optional, defaults to True)
|
|
1015
|
+
server: Server to use (optional)
|
|
773
1016
|
|
|
774
1017
|
Returns:
|
|
775
1018
|
API response as a dictionary containing task information
|
|
776
1019
|
|
|
777
1020
|
Raises:
|
|
778
|
-
|
|
779
|
-
|
|
1021
|
+
CollectionNotFoundError: If the collection is not found
|
|
1022
|
+
GetTaskError: If the API request fails due to unknown reasons
|
|
780
1023
|
"""
|
|
781
|
-
|
|
1024
|
+
await self.create_collection(collection=collection)
|
|
1025
|
+
|
|
1026
|
+
upload_urls = await self._upload_requests(collection=collection)
|
|
1027
|
+
url = upload_urls['url']
|
|
1028
|
+
await self._upload_file(requests_file, url)
|
|
1029
|
+
|
|
1030
|
+
with open(consumer, 'rb') as f:
|
|
1031
|
+
form = aiohttp.FormData()
|
|
1032
|
+
form.add_field('output', output)
|
|
1033
|
+
form.add_field('force_loc', str(force_loc).lower())
|
|
1034
|
+
form.add_field('skip_existing', str(skip_existing).lower())
|
|
1035
|
+
|
|
1036
|
+
if server is not None:
|
|
1037
|
+
form.add_field('server', server)
|
|
1038
|
+
|
|
1039
|
+
form.add_field('extra', json.dumps(extra or {}))
|
|
1040
|
+
form.add_field('folder', folder)
|
|
1041
|
+
form.add_field(
|
|
1042
|
+
'consumer',
|
|
1043
|
+
f.read(),
|
|
1044
|
+
filename='consumer.py',
|
|
1045
|
+
content_type='text/x-python'
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
response, status = await self._client._terrakio_request(
|
|
1049
|
+
"POST",
|
|
1050
|
+
f"collections/{collection}/gen_and_process",
|
|
1051
|
+
data=form
|
|
1052
|
+
)
|
|
1053
|
+
|
|
782
1054
|
if status != 200:
|
|
783
1055
|
if status == 404:
|
|
784
|
-
raise
|
|
785
|
-
raise
|
|
1056
|
+
raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
|
|
1057
|
+
raise GetTaskError(f"Gen and process failed with status {status}", status_code=status)
|
|
786
1058
|
|
|
787
1059
|
return response
|
|
788
1060
|
|
|
789
1061
|
@require_api_key
|
|
790
|
-
async def
|
|
1062
|
+
async def create_pyramids(
|
|
791
1063
|
self,
|
|
792
|
-
|
|
793
|
-
|
|
1064
|
+
name: str,
|
|
1065
|
+
levels: int,
|
|
1066
|
+
config: Dict[str, Any]
|
|
1067
|
+
) -> Dict[str, Any]:
|
|
794
1068
|
"""
|
|
795
|
-
|
|
1069
|
+
Create pyramid tiles for a dataset.
|
|
796
1070
|
|
|
797
1071
|
Args:
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
raise CancelCollectionTasksError(f"Cancel collection tasks failed with status {status}", status_code=status)
|
|
813
|
-
|
|
814
|
-
return response
|
|
815
|
-
|
|
816
|
-
@require_api_key
|
|
817
|
-
async def cancel_all_tasks(
|
|
818
|
-
self
|
|
819
|
-
):
|
|
820
|
-
"""
|
|
821
|
-
Cancel all tasks for the current user.
|
|
1072
|
+
name: Dataset name
|
|
1073
|
+
levels: Maximum zoom level for pyramid (e.g., 8)
|
|
1074
|
+
config: Full pyramid configuration dictionary containing:
|
|
1075
|
+
- name: Dataset name (will override the name parameter)
|
|
1076
|
+
- bucket: GCS bucket name (e.g., "terrakio")
|
|
1077
|
+
- products: List of product names (e.g., ["air_temp", "prec"])
|
|
1078
|
+
- path: Path pattern (e.g., "pyramids/%s_%s_%03d_%03d_%02d.snp")
|
|
1079
|
+
- data_type: Data type (e.g., "float32")
|
|
1080
|
+
- i_max: Maximum i index
|
|
1081
|
+
- j_max: Maximum j index
|
|
1082
|
+
- x_size: Tile size in x (e.g., 400)
|
|
1083
|
+
- y_size: Tile size in y (e.g., 400)
|
|
1084
|
+
- dates_iso8601: List of ISO8601 date strings
|
|
1085
|
+
- no_data: No data value (e.g., -9999.0)
|
|
822
1086
|
|
|
823
1087
|
Returns:
|
|
824
|
-
API response
|
|
1088
|
+
API response with task_id
|
|
825
1089
|
|
|
826
1090
|
Raises:
|
|
827
|
-
|
|
1091
|
+
GetTaskError: If the API request fails
|
|
828
1092
|
"""
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1093
|
+
await self.create_collection(collection = name)
|
|
1094
|
+
|
|
1095
|
+
pyramid_request = {
|
|
1096
|
+
'name': name,
|
|
1097
|
+
'max_zoom': levels,
|
|
1098
|
+
**config
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
response, status = await self._client._terrakio_request(
|
|
1102
|
+
"POST",
|
|
1103
|
+
"tasks/pyramids",
|
|
1104
|
+
json=pyramid_request
|
|
1105
|
+
)
|
|
1106
|
+
|
|
832
1107
|
if status != 200:
|
|
833
|
-
raise
|
|
1108
|
+
raise GetTaskError(
|
|
1109
|
+
f"Pyramid creation failed with status {status}: {response}",
|
|
1110
|
+
status_code=status
|
|
1111
|
+
)
|
|
1112
|
+
|
|
1113
|
+
task_id = response["task_id"]
|
|
1114
|
+
await self.track_progress(task_id)
|
|
834
1115
|
|
|
835
|
-
return
|
|
1116
|
+
return {"task_id": task_id}
|
|
@@ -59,131 +59,6 @@ class ModelManagement:
|
|
|
59
59
|
}
|
|
60
60
|
return req
|
|
61
61
|
|
|
62
|
-
@require_api_key
|
|
63
|
-
async def generate_ai_dataset(
|
|
64
|
-
self,
|
|
65
|
-
name: str,
|
|
66
|
-
aoi: str,
|
|
67
|
-
expression_x: str,
|
|
68
|
-
filter_x: str = "skip",
|
|
69
|
-
filter_x_rate: float = 1,
|
|
70
|
-
expression_y: str = "skip",
|
|
71
|
-
filter_y: str = "skip",
|
|
72
|
-
filter_y_rate: float = 1,
|
|
73
|
-
samples: int = 1000,
|
|
74
|
-
tile_size: float = 256,
|
|
75
|
-
crs: str = "epsg:3577",
|
|
76
|
-
res: float = 10,
|
|
77
|
-
res_y: float = None,
|
|
78
|
-
skip_test: bool = False,
|
|
79
|
-
start_year: int = None,
|
|
80
|
-
end_year: int = None,
|
|
81
|
-
bucket: str = None,
|
|
82
|
-
server: str = None,
|
|
83
|
-
extra_filters: list[str] = None,
|
|
84
|
-
extra_filters_rate: list[float] = None,
|
|
85
|
-
extra_filters_res: list[float] = None
|
|
86
|
-
) -> dict:
|
|
87
|
-
"""
|
|
88
|
-
Generate an AI dataset using specified parameters.
|
|
89
|
-
|
|
90
|
-
Args:
|
|
91
|
-
name (str): Name of the collection to create
|
|
92
|
-
aoi (str): Path to GeoJSON file containing area of interest
|
|
93
|
-
expression_x (str): Expression for X data (features)
|
|
94
|
-
filter_x (str): Filter expression for X data (default: "skip")
|
|
95
|
-
filter_x_rate (float): Filter rate for X data (default: 1)
|
|
96
|
-
expression_y (str): Expression for Y data (labels) (default: "skip")
|
|
97
|
-
filter_y (str): Filter expression for Y data (default: "skip")
|
|
98
|
-
filter_y_rate (float): Filter rate for Y data (default: 1)
|
|
99
|
-
samples (int): Number of samples to generate (default: 1000)
|
|
100
|
-
tile_size (float): Size of tiles in pixels (default: 256)
|
|
101
|
-
crs (str): Coordinate reference system (default: "epsg:3577")
|
|
102
|
-
res (float): Resolution for X data (default: 10)
|
|
103
|
-
res_y (float): Resolution for Y data, defaults to res if None
|
|
104
|
-
skip_test (bool): Skip expression validation test (default: False)
|
|
105
|
-
start_year (int): Start year for temporal filtering
|
|
106
|
-
end_year (int): End year for temporal filtering
|
|
107
|
-
bucket (str): Storage bucket name
|
|
108
|
-
server (str): Server to use for processing
|
|
109
|
-
extra_filters (list[str]): Additional filter expressions
|
|
110
|
-
extra_filters_rate (list[float]): Rates for additional filters
|
|
111
|
-
extra_filters_res (list[float]): Resolutions for additional filters
|
|
112
|
-
|
|
113
|
-
Returns:
|
|
114
|
-
dict: Response containing task_id and collection name
|
|
115
|
-
|
|
116
|
-
Raises:
|
|
117
|
-
APIError: If the API request fails
|
|
118
|
-
TypeError: If extra filters have mismatched rate and resolution lists
|
|
119
|
-
"""
|
|
120
|
-
expressions = [{"expr": expression_x, "res": res, "prefix": "x"}]
|
|
121
|
-
|
|
122
|
-
res_y = res_y or res
|
|
123
|
-
|
|
124
|
-
if expression_y != "skip":
|
|
125
|
-
expressions.append({"expr": expression_y, "res": res_y, "prefix": "y"})
|
|
126
|
-
|
|
127
|
-
filters = []
|
|
128
|
-
if filter_x != "skip":
|
|
129
|
-
filters.append({"expr": filter_x, "res": res, "rate": filter_x_rate})
|
|
130
|
-
|
|
131
|
-
if filter_y != "skip":
|
|
132
|
-
filters.append({"expr": filter_y, "res": res_y, "rate": filter_y_rate})
|
|
133
|
-
|
|
134
|
-
if extra_filters:
|
|
135
|
-
try:
|
|
136
|
-
extra_filters_combined = zip(extra_filters, extra_filters_res, extra_filters_rate, strict=True)
|
|
137
|
-
except TypeError:
|
|
138
|
-
raise TypeError("Extra filters must have matching rate and resolution.")
|
|
139
|
-
|
|
140
|
-
for expr, filter_res, rate in extra_filters_combined:
|
|
141
|
-
filters.append({"expr": expr, "res": filter_res, "rate": rate})
|
|
142
|
-
|
|
143
|
-
if start_year is not None:
|
|
144
|
-
for expr_dict in expressions:
|
|
145
|
-
expr_dict["expr"] = expr_dict["expr"].replace("{year}", str(start_year))
|
|
146
|
-
|
|
147
|
-
for filter_dict in filters:
|
|
148
|
-
filter_dict["expr"] = filter_dict["expr"].replace("{year}", str(start_year))
|
|
149
|
-
|
|
150
|
-
# this is making request to the server that is being used when doing the initialization
|
|
151
|
-
if not skip_test:
|
|
152
|
-
for expr_dict in expressions:
|
|
153
|
-
test_request = self._generate_test_request(expr_dict["expr"], crs, -1)
|
|
154
|
-
await self._client._terrakio_request("POST", "geoquery", json=test_request)
|
|
155
|
-
|
|
156
|
-
for filter_dict in filters:
|
|
157
|
-
test_request = self._generate_test_request(filter_dict["expr"], crs, -1)
|
|
158
|
-
await self._client._terrakio_request("POST", "geoquery", json=test_request)
|
|
159
|
-
|
|
160
|
-
with open(aoi, 'r') as f:
|
|
161
|
-
aoi_data = json.load(f)
|
|
162
|
-
|
|
163
|
-
await self._client.mass_stats.create_collection(
|
|
164
|
-
collection=name,
|
|
165
|
-
bucket=bucket,
|
|
166
|
-
collection_type="basic"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
task_id_dict = await self._client.mass_stats.training_samples(
|
|
170
|
-
collection=name,
|
|
171
|
-
expressions=expressions,
|
|
172
|
-
filters=filters,
|
|
173
|
-
aoi=aoi_data,
|
|
174
|
-
samples=samples,
|
|
175
|
-
year_range=[start_year, end_year],
|
|
176
|
-
crs=crs,
|
|
177
|
-
tile_size=tile_size,
|
|
178
|
-
res=res,
|
|
179
|
-
output="nc",
|
|
180
|
-
server=server
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
task_id = task_id_dict["task_id"]
|
|
184
|
-
|
|
185
|
-
await self._client.mass_stats.track_progress(task_id)
|
|
186
|
-
|
|
187
62
|
@require_api_key
|
|
188
63
|
async def _get_url_for_upload_model_and_script(self, expression: str, model_name: str, script_name: str) -> str:
|
|
189
64
|
"""
|
|
@@ -11,13 +11,13 @@ terrakio_core/convenience_functions/zonal_stats.py,sha256=7PI--RI0hiF1pzZ7_7hqty
|
|
|
11
11
|
terrakio_core/endpoints/auth.py,sha256=5WvAO39aLsbJAVtxISwiOZseKr3B9I5tHjamPRehDBQ,8327
|
|
12
12
|
terrakio_core/endpoints/dataset_management.py,sha256=jpwftiKOI59NhXXypqm6wtILIkfyjy9NfYifRIvhZS0,16791
|
|
13
13
|
terrakio_core/endpoints/group_management.py,sha256=V0KOGTXwmePBFeym55G_m3ONR-jVj2IU4OVLiL5UKz4,15869
|
|
14
|
-
terrakio_core/endpoints/mass_stats.py,sha256=
|
|
15
|
-
terrakio_core/endpoints/model_management.py,sha256=
|
|
14
|
+
terrakio_core/endpoints/mass_stats.py,sha256=6BVD6aVXcG9t-yOjPyTpwsNM5ScoYCAYnoUWl76Cqdw,41460
|
|
15
|
+
terrakio_core/endpoints/model_management.py,sha256=tvJ4BOBsyXKKH430byGH25CkLIzXWgxaPaL0CvL8_0Y,51341
|
|
16
16
|
terrakio_core/endpoints/space_management.py,sha256=YWb55nkJnFJGlALJ520DvurxDqVqwYtsvqQPWzxzhDs,2266
|
|
17
17
|
terrakio_core/endpoints/user_management.py,sha256=L_g4ysrh2xyz_JbObUU_tCxgHxisrDUPntWgQOs15GE,7709
|
|
18
18
|
terrakio_core/helper/bounded_taskgroup.py,sha256=wiTH10jhKZgrsgrFUNG6gig8bFkUEPHkGRT2XY7Rgmo,677
|
|
19
19
|
terrakio_core/helper/decorators.py,sha256=L6om7wmWNgCei3Wy5U0aZ-70OzsCwclkjIf7SfQuhCg,2289
|
|
20
20
|
terrakio_core/helper/tiles.py,sha256=lcLCO6KiP05lCI9vngo3zCZJ6Z9C0pUxHSQS4H58EHc,2699
|
|
21
|
-
terrakio_core-0.4.98.
|
|
22
|
-
terrakio_core-0.4.98.
|
|
23
|
-
terrakio_core-0.4.98.
|
|
21
|
+
terrakio_core-0.4.98.1b7.dist-info/METADATA,sha256=TRow24aLBbKOYmCNkUOO2V7qMlhqC5s2HS2lfgIyMqA,1184
|
|
22
|
+
terrakio_core-0.4.98.1b7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
terrakio_core-0.4.98.1b7.dist-info/RECORD,,
|
|
File without changes
|