forked from GISGIT/GEE-Python-API
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdata.py
More file actions
executable file
·1749 lines (1443 loc) · 58.4 KB
/
Copy pathdata.py
File metadata and controls
executable file
·1749 lines (1443 loc) · 58.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
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
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
"""Singleton for the library's communication with the Earth Engine API."""
from __future__ import print_function
# Using lowercase function naming to match the JavaScript names.
# pylint: disable=g-bad-name
# pylint: disable=g-bad-import-order
import contextlib
import json
import threading
import time
import uuid
import httplib2
import six
from google_auth_httplib2 import AuthorizedHttp
from six.moves.urllib import parse
from . import _cloud_api_utils
from . import deprecation
from . import encodable
from . import serializer
import apiclient
from . import ee_exception
# OAuth2 credentials object. This may be set by ee.Initialize().
_credentials = None
# The base URL for all data calls. This is set by ee.Initialize().
_api_base_url = None
# The base URL for map tiles. This is set by ee.Initialize().
_tile_base_url = None
# The base URL for all Cloud API calls. This is set by ee.Initialize().
_cloud_api_base_url = None
# Google Cloud API key. This may be set by ee.Initialize().
_cloud_api_key = None
# Whether to use Cloud API when possible.
_use_cloud_api = False
# A resource object for making Cloud API calls.
_cloud_api_resource = None
# A resource object for making Cloud API calls and receiving raw return types.
_cloud_api_resource_raw = None
# The default user project to use when making Cloud API calls.
_cloud_api_user_project = None
# The http_transport to use.
_http_transport = None
# Whether the module has been initialized.
_initialized = False
# Sets the number of milliseconds to wait for a request before considering
# it timed out. 0 means no limit.
_deadline_ms = 0
class _ThreadLocals(threading.local):
def __init__(self):
# pylint: disable=super-init-not-called
# A function called when profile results are received from the server. Takes
# the profile ID as an argument. None if profiling is disabled.
#
# This is a thread-local variable because the alternative is to add a
# parameter to ee.data.send_, which would then have to be propagated from
# the assorted API call functions (ee.data.getInfo, ee.data.getMapId, etc.),
# and the user would have to modify each call to profile, rather than
# enabling profiling as a wrapper around the entire program (with
# ee.data.profiling, defined below).
self.profile_hook = None
_thread_locals = _ThreadLocals()
# The HTTP header through which profile results are returned.
# Lowercase because that's how httplib2 does things.
_PROFILE_RESPONSE_HEADER_LOWERCASE = 'x-earth-engine-computation-profile'
# The HTTP header through which profiling is requested when using the Cloud API.
_PROFILE_REQUEST_HEADER = 'X-Earth-Engine-Computation-Profiling'
# The HTTP header through which a user project override is provided.
_USER_PROJECT_OVERRIDE_HEADER = 'X-Goog-User-Project'
# Maximum number of times to retry a rate-limited request.
MAX_RETRIES = 5
# Maximum time to wait before retrying a rate-limited request (in milliseconds).
MAX_RETRY_WAIT = 120000
# Base time (in ms) to wait when performing exponential backoff in request
# retries.
BASE_RETRY_WAIT = 1000
# The default base URL for API calls.
DEFAULT_API_BASE_URL = 'https://earthengine.googleapis.com/api'
# The default base URL for media/tile calls.
DEFAULT_TILE_BASE_URL = 'https://earthengine.googleapis.com'
# The default base URL for Cloud API calls.
DEFAULT_CLOUD_API_BASE_URL = 'https://earthengine.googleapis.com'
# The default project to use for Cloud API calls.
DEFAULT_CLOUD_API_USER_PROJECT = 'earthengine-legacy'
# Asset types recognized by create_assets().
ASSET_TYPE_FOLDER = 'Folder'
ASSET_TYPE_IMAGE_COLL = 'ImageCollection'
# Cloud API versions of the asset types.
ASSET_TYPE_FOLDER_CLOUD = 'FOLDER'
ASSET_TYPE_IMAGE_COLL_CLOUD = 'IMAGE_COLLECTION'
# Max length of the above type names
MAX_TYPE_LENGTH = len(ASSET_TYPE_IMAGE_COLL_CLOUD)
# The maximum number of tasks to retrieve in each request to "/tasklist".
_TASKLIST_PAGE_SIZE = 500
def initialize(credentials=None,
api_base_url=None,
tile_base_url=None,
use_cloud_api=None,
cloud_api_base_url=None,
cloud_api_key=None,
project=None,
http_transport=None):
"""Initializes the data module, setting credentials and base URLs.
If any of the arguments are unspecified, they will keep their old values;
the defaults if initialize() has never been called before.
At least one of "credentials" and "cloud_api_key" must be provided. If both
are provided, both will be used; in this case, the API key's project must
match the credentials' project.
Args:
credentials: The OAuth2 credentials.
api_base_url: The EarthEngine REST API endpoint.
tile_base_url: The EarthEngine REST tile endpoint.
use_cloud_api: Whether to use the EarthEngine Cloud API rather than the
older REST API.
cloud_api_base_url: The EarthEngine Cloud API endpoint.
cloud_api_key: The API key to use with the Cloud API.
project: The default cloud project associated with the user.
http_transport: The http transport to use
"""
global _api_base_url, _tile_base_url, _credentials, _initialized
global _cloud_api_base_url, _use_cloud_api
global _cloud_api_resource, _cloud_api_resource_raw, _cloud_api_key
global _cloud_api_user_project, _http_transport
# If already initialized, only replace the explicitly specified parts.
if credentials is not None:
_credentials = credentials
if api_base_url is not None:
_api_base_url = api_base_url
elif not _initialized:
_api_base_url = DEFAULT_API_BASE_URL
if tile_base_url is not None:
_tile_base_url = tile_base_url
elif not _initialized:
_tile_base_url = DEFAULT_TILE_BASE_URL
if cloud_api_key is not None:
_cloud_api_key = cloud_api_key
if cloud_api_base_url is not None:
_cloud_api_base_url = cloud_api_base_url
elif not _initialized:
_cloud_api_base_url = DEFAULT_CLOUD_API_BASE_URL
if use_cloud_api is not None:
_use_cloud_api = use_cloud_api
_http_transport = http_transport
if _cloud_api_resource is None or _cloud_api_resource_raw is None:
_install_cloud_api_resource()
if project is not None:
_cloud_api_user_project = project
_cloud_api_utils.set_cloud_api_user_project(project)
else:
_cloud_api_utils.set_cloud_api_user_project(DEFAULT_CLOUD_API_USER_PROJECT)
_initialized = True
def reset():
"""Resets the data module, clearing credentials and custom base URLs."""
global _api_base_url, _tile_base_url, _credentials, _initialized
global _cloud_api_base_url, _use_cloud_api
global _cloud_api_resource, _cloud_api_resource_raw
global _cloud_api_key, _http_transport
_credentials = None
_api_base_url = None
_tile_base_url = None
_cloud_api_base_url = None
_use_cloud_api = False
_cloud_api_key = None
_cloud_api_resource = None
_cloud_api_resource_raw = None
_http_transport = None
_initialized = False
def _get_projects_path():
"""Returns the projects path to use for constructing a request."""
if _cloud_api_user_project is not None:
return 'projects/' + _cloud_api_user_project
else:
return 'projects/' + DEFAULT_CLOUD_API_USER_PROJECT
def _install_cloud_api_resource():
"""Builds or rebuilds the Cloud API resource object, if needed."""
global _cloud_api_resource, _cloud_api_resource_raw
global _http_transport
if not _use_cloud_api:
return
timeout = (_deadline_ms / 1000.0) or None
_cloud_api_resource = _cloud_api_utils.build_cloud_resource(
_cloud_api_base_url,
credentials=_credentials,
api_key=_cloud_api_key,
timeout=timeout,
headers_supplier=_make_request_headers,
response_inspector=_handle_profiling_response,
http_transport=_http_transport)
_cloud_api_resource_raw = _cloud_api_utils.build_cloud_resource(
_cloud_api_base_url,
credentials=_credentials,
api_key=_cloud_api_key,
timeout=timeout,
headers_supplier=_make_request_headers,
response_inspector=_handle_profiling_response,
http_transport=_http_transport,
raw=True)
def _make_request_headers():
"""Adds a header requesting profiling, if profiling is enabled."""
headers = {}
if _thread_locals.profile_hook:
headers[_PROFILE_REQUEST_HEADER] = '1'
if _cloud_api_user_project is not None:
headers[_USER_PROJECT_OVERRIDE_HEADER] = _cloud_api_user_project
if headers:
return headers
return None
def _handle_profiling_response(response):
"""Handles profiling annotations on Cloud API responses."""
# Call the profile hook if present. Note that this is done before we handle
# the content, so that profiles are reported even if the response is an error.
if (_thread_locals.profile_hook and
_PROFILE_RESPONSE_HEADER_LOWERCASE in response):
_thread_locals.profile_hook(response[_PROFILE_RESPONSE_HEADER_LOWERCASE])
def _execute_cloud_call(call, num_retries=MAX_RETRIES):
"""Executes a Cloud API call and translates errors to EEExceptions.
Args:
call: The Cloud API call, with all parameters set, ready to have execute()
called on it.
num_retries: How many times retryable failures should be retried.
Returns:
The value returned by executing that call.
Raises:
EEException if the call fails.
"""
try:
return call.execute(num_retries=num_retries)
except apiclient.errors.HttpError as e:
raise _translate_cloud_exception(e)
def _translate_cloud_exception(http_error):
"""Translates a Cloud API exception into an EEException.
Args:
http_error: An apiclient.errors.HttpError.
Returns:
An EEException bearing the error message from http_error.
"""
# The only sane way to get a message out of an HttpError is to use a protected
# method.
return ee_exception.EEException(http_error._get_reason()) # pylint: disable=protected-access
def setCloudApiKey(cloud_api_key):
"""Sets the Cloud API key parameter ("api_key") for all requests."""
global _cloud_api_key
_cloud_api_key = cloud_api_key
_install_cloud_api_resource()
def setCloudApiUserProject(cloud_api_user_project):
global _cloud_api_user_project
_cloud_api_user_project = cloud_api_user_project
_cloud_api_utils.set_cloud_api_user_project(_cloud_api_user_project)
def setDeadline(milliseconds):
"""Sets the timeout length for API requests.
Args:
milliseconds: The number of milliseconds to wait for a request
before considering it timed out. 0 means no limit.
"""
global _deadline_ms
_deadline_ms = milliseconds
_install_cloud_api_resource()
@contextlib.contextmanager
def profiling(hook):
# pylint: disable=g-doc-return-or-yield
"""Returns a context manager which enables or disables profiling.
If hook is not None, enables profiling for all API calls in its scope and
calls the hook function with all resulting profile IDs. If hook is null,
disables profiling (or leaves it disabled).
Args:
hook: A function of one argument which is called with each profile
ID obtained from API calls, just before the API call returns.
"""
saved_hook = _thread_locals.profile_hook
_thread_locals.profile_hook = hook
try:
yield
finally:
_thread_locals.profile_hook = saved_hook
@deprecation.Deprecated('Use getAsset')
def getInfo(asset_id):
"""Load info for an asset, given an asset id.
Args:
asset_id: The asset to be retrieved.
Returns:
The value call results, or None if the asset does not exist.
"""
if _use_cloud_api:
# Don't use getAsset as it will translate the exception, and we need
# to handle 404s specially.
try:
return _cloud_api_resource.projects().assets().get(
name=_cloud_api_utils.convert_asset_id_to_asset_name(asset_id),
prettyPrint=False).execute(num_retries=MAX_RETRIES)
except apiclient.errors.HttpError as e:
if e.resp.status == 404:
return None
else:
raise _translate_cloud_exception(e)
return send_('/info', {'id': asset_id})
def getAsset(asset_id):
"""Loads info for an asset, given an asset id.
Args:
asset_id: The asset to be retrieved.
Returns:
The asset's information, as an EarthEngineAsset.
"""
return _execute_cloud_call(_cloud_api_resource.projects().assets().get(
name=_cloud_api_utils.convert_asset_id_to_asset_name(asset_id),
prettyPrint=False))
@deprecation.Deprecated('Use listAssets or listImages')
def getList(params):
"""Get a list of contents for a collection asset.
Args:
params: An object containing request parameters with the
following possible values:
id (string) The asset id of the collection to list.
starttime (number) Start time, in msec since the epoch.
endtime (number) End time, in msec since the epoch.
fields (comma-separated strings) Field names to return.
Returns:
The list call results.
"""
if _use_cloud_api:
# Translate the parameter list to use listAssets or listImages. If it
# doesn't specify anything other than the ID and "num", use listAssets.
# Otherwise, use listImages.
if six.viewkeys(params) - set(['id', 'num']):
result = listImages(
_cloud_api_utils.convert_get_list_params_to_list_images_params(
params))
result = _cloud_api_utils.convert_list_images_result_to_get_list_result(
result)
else:
result = listAssets(
_cloud_api_utils.convert_get_list_params_to_list_assets_params(
params))
result = _cloud_api_utils.convert_list_assets_result_to_get_list_result(
result)
return result
return send_('/list', params)
def listImages(params):
return _execute_cloud_call(
_cloud_api_resource.projects().assets().listImages(**params))
def listAssets(params):
return _execute_cloud_call(
_cloud_api_resource.projects().assets().listAssets(**params))
def listBuckets(project=None):
if project is None:
project = _get_projects_path()
return _execute_cloud_call(
_cloud_api_resource.projects().listAssets(parent=project))
def getMapId(params):
"""Get a Map ID for a given asset.
Args:
params: An object containing visualization options with the
following possible values:
image - The image to render, as an Image or a JSON string.
The JSON string format is deprecated.
version - (number) Version number of image (or latest).
bands - (comma-separated strings) Comma-delimited list of
band names to be mapped to RGB.
min - (comma-separated numbers) Value (or one per band)
to map onto 00.
max - (comma-separated numbers) Value (or one per band)
to map onto FF.
gain - (comma-separated numbers) Gain (or one per band)
to map onto 00-FF.
bias - (comma-separated numbers) Offset (or one per band)
to map onto 00-FF.
gamma - (comma-separated numbers) Gamma correction
factor (or one per band).
palette - (comma-separated strings) A string of comma-separated
CSS-style color strings (single-band previews only). For example,
'FF0000,000000'.
format - (string) The desired map tile image format. If omitted, one is
chosen automatically. Can be 'jpg' (does not support transparency)
or 'png' (supports transparency).
Returns:
A dictionary containing:
- "mapid" and "token" strings: these identify the map.
- "tile_fetcher": a TileFetcher which can be used to fetch the tile
images, or to get a format for the tile URLs.
"""
if _use_cloud_api:
if isinstance(params['image'], six.string_types):
raise ee_exception.EEException('Image as JSON string not supported.')
if 'version' in params:
raise ee_exception.EEException(
'Image version specification not supported.')
request = {
'expression':
serializer.encode(params['image'], for_cloud_api=True),
'fileFormat':
_cloud_api_utils.convert_to_image_file_format(params.get('format')),
'bandIds':
_cloud_api_utils.convert_to_band_list(params.get('bands')),
}
# Only add visualizationOptions to the request if it's non-empty, as
# specifying it affects server behaviour.
visualizationOptions = _cloud_api_utils.convert_to_visualization_options(
params)
if visualizationOptions:
request['visualizationOptions'] = visualizationOptions
# Make it return only the name field, as otherwise it echoes the entire
# request, which might be large.
result = _execute_cloud_call(_cloud_api_resource.projects().maps().create(
parent=_get_projects_path(),
fields='name',
body=request))
map_name = result['name']
url_format = '%s/v1alpha/%s/tiles/{z}/{x}/{y}' % (_tile_base_url, map_name)
if _cloud_api_key:
url_format += '?key=%s' % _cloud_api_key
return {'mapid': map_name, 'token': '',
'tile_fetcher': TileFetcher(url_format, map_name=map_name)}
if not isinstance(params['image'], six.string_types):
params['image'] = params['image'].serialize()
params['json_format'] = 'v2'
result = send_('/mapid', params)
url_format = '%s/map/%s/{z}/{x}/{y}?token=%s' % (
_tile_base_url, result['mapid'], result['token'])
result['tile_fetcher'] = TileFetcher(url_format)
return result
def getTileUrl(mapid, x, y, z):
"""Generate a URL for map tiles from a Map ID and coordinates.
Args:
mapid: The Map ID to generate tiles for, a dictionary returned
by getMapId.
x: The tile x coordinate.
y: The tile y coordinate.
z: The tile zoom level.
Returns:
The tile URL.
"""
return mapid['tile_fetcher'].format_tile_url(x, y, z)
class TileFetcher(object):
"""A helper class to fetch image tiles."""
def __init__(self, url_format, map_name=None):
self._url_format = url_format
self._map_name = map_name
@property
def url_format(self):
"""Gets the URL format for this tile fetcher.
Returns:
A format string with {x}, {y}, and {z} placeholders.
If you are using the Cloud API, and have not provided an API
key, then this URL will require authorization. Use the credentials
provided to ee.Initialize() to provide this authorization. Alternatively,
use "fetch_tile" to fetch the tile data, which will handle the
authorization for you.
"""
return self._url_format
def format_tile_url(self, x, y, z):
"""Generates the URL for a particular tile.
Args:
x: The tile x coordinate.
y: The tile y coordinate.
z: The tile zoom level.
Returns:
The tile's URL.
"""
width = 2**z
x %= width
if x < 0:
x += width
return self.url_format.format(x=x, y=y, z=z)
def fetch_tile(self, x, y, z):
"""Fetches the map tile specified by (x, y, z).
This method uses any credentials that were specified to ee.Initialize().
Args:
x: The tile x coordinate.
y: The tile y coordinate.
z: The tile zoom level.
Returns:
The map tile image data bytes.
Raises:
EEException if the fetch fails.
"""
if _use_cloud_api:
return _execute_cloud_call(
_cloud_api_resource_raw.projects().maps().tiles().get(
parent=self._map_name, x=x, y=y, zoom=z,
), num_retries=MAX_RETRIES
)
return send_(
self.format_tile_url(x, y, z), {}, opt_method='GET', opt_raw=True)
@deprecation.Deprecated('Use computeValue')
def getValue(params):
"""Retrieve a processed value from the front end.
Args:
params: A dictionary containing:
json - (String) A JSON object to be evaluated.
Returns:
The value call results.
"""
params['json_format'] = 'v2'
return send_('/value', params)
def computeValue(obj):
"""Sends a request to compute a value.
Args:
obj: A ComputedObject whose value is desired.
Returns:
The result of evaluating that object on the server.
"""
if _use_cloud_api:
return _execute_cloud_call(_cloud_api_resource.projects().value().compute(
body={'expression': serializer.encode(obj, for_cloud_api=True)},
project=_get_projects_path(),
prettyPrint=False))['result']
return send_('/value', ({'json': obj.serialize(), 'json_format': 'v2'}))
@deprecation.Deprecated('Use getThumbId and makeThumbUrl')
def getThumbnail(params):
"""Get a Thumbnail for a given asset.
Args:
params: Parameters identical to getMapId, plus:
size - (a number or pair of numbers in format WIDTHxHEIGHT) Maximum
dimensions of the thumbnail to render, in pixels. If only one number
is passed, it is used as the maximum, and the other dimension is
computed by proportional scaling.
region - (E,S,W,N or GeoJSON) Geospatial region of the image
to render. By default, the whole image.
format - (string) Either 'png' (default) or 'jpg'.
Returns:
A thumbnail image as raw PNG data.
"""
if _use_cloud_api:
thumbid = params['image'].getThumbId(params)['thumbid']
return _execute_cloud_call(
_cloud_api_resource_raw.projects().thumbnails().getPixels(
name=thumbid
), num_retries=MAX_RETRIES
)
return send_('/thumb', params, opt_method='GET', opt_raw=True)
def getThumbId(params):
"""Get a Thumbnail ID for a given asset.
Args:
params: Parameters identical to getMapId, plus:
size - (a number or pair of numbers in format WIDTHxHEIGHT) Maximum
dimensions of the thumbnail to render, in pixels. If only one number
is passed, it is used as the maximum, and the other dimension is
computed by proportional scaling.
region - (E,S,W,N or GeoJSON) Geospatial region of the image
to render. By default, the whole image.
format - (string) Either 'png' (default) or 'jpg'.
Returns:
A dictionary containing "thumbid" and "token" strings, which identify the
thumbnail.
"""
if _use_cloud_api:
# We only really support accessing this method via ee.Image.getThumbURL,
# which folds almost all the parameters into the Image itself.
if isinstance(params['image'], six.string_types):
raise ee_exception.EEException('Image as JSON string not supported.')
if 'version' in params:
raise ee_exception.EEException(
'Image version specification not supported.')
if 'size' in params:
raise ee_exception.EEException(
'"size" not supported. Use "dimensions" and ee.Image.getThumbURL.')
if 'region' in params:
raise ee_exception.EEException(
'"region" not supported in call to ee.data.getThumbId. Use '
'ee.Image.getThumbURL.')
request = {
'expression':
serializer.encode(params['image'], for_cloud_api=True),
'fileFormat':
_cloud_api_utils.convert_to_image_file_format(params.get('format')),
'bandIds':
_cloud_api_utils.convert_to_band_list(params.get('bands')),
}
# Only add visualizationOptions to the request if it's non-empty, as
# specifying it affects server behaviour.
visualizationOptions = _cloud_api_utils.convert_to_visualization_options(
params)
if visualizationOptions:
request['visualizationOptions'] = visualizationOptions
# Make it return only the name field, as otherwise it echoes the entire
# request, which might be large.
result = _execute_cloud_call(
_cloud_api_resource.projects().thumbnails().create(
parent=_get_projects_path(),
fields='name',
body=request))
return {'thumbid': result['name'], 'token': ''}
request = params.copy()
request['getid'] = '1'
request['json_format'] = 'v2'
if not isinstance(request['image'], six.string_types):
request['image'] = request['image'].serialize()
if 'size' in request and isinstance(request['size'], (list, tuple)):
request['size'] = 'x'.join(map(str, request['size']))
return send_('/thumb', request)
def makeThumbUrl(thumbId):
"""Create a thumbnail URL from the given thumbid and token.
Args:
thumbId: An object containing a thumbnail thumbid and token.
Returns:
A URL from which the thumbnail can be obtained.
"""
if _use_cloud_api:
url = '%s/v1alpha/%s:getPixels' % (_tile_base_url, thumbId['thumbid'])
if _cloud_api_key:
url += '?key=%s' % _cloud_api_key
return url
return '%s/api/thumb?thumbid=%s&token=%s' % (
_tile_base_url, thumbId['thumbid'], thumbId['token'])
@deprecation.Deprecated('Use getThumbId')
def getDownloadId(params):
"""Get a Download ID.
Args:
params: An object containing visualization options with the following
possible values:
name - a base name to use when constructing filenames.
bands - a description of the bands to download. Must be an array of
dictionaries, each with the following keys:
id - the name of the band, a string, required.
crs - an optional CRS string defining the band projection.
crs_transform - an optional array of 6 numbers specifying an affine
transform from the specified CRS, in the order: xScale,
yShearing, xShearing, yScale, xTranslation and yTranslation.
dimensions - an optional array of two integers defining the width and
height to which the band is cropped.
scale - an optional number, specifying the scale in meters of the
band; ignored if crs and crs_transform is specified.
crs - a default CRS string to use for any bands that do not explicitly
specify one.
crs_transform - a default affine transform to use for any bands that do
not specify one, of the same format as the crs_transform of bands.
dimensions - default image cropping dimensions to use for any bands
that do not specify them.
scale - a default scale to use for any bands that do not specify one;
ignored if crs and crs_transform is specified.
region - a polygon specifying a region to download; ignored if crs
and crs_transform is specified.
Returns:
A dict containing a docid and token.
"""
params['json_format'] = 'v2'
if 'bands' in params and not isinstance(params['bands'], six.string_types):
params['bands'] = json.dumps(params['bands'])
return send_('/download', params)
@deprecation.Deprecated('Use getThumbId and makeThumbUrl')
def makeDownloadUrl(downloadId):
"""Create a download URL from the given docid and token.
Args:
downloadId: An object containing a download docid and token.
Returns:
A URL from which the download can be obtained.
"""
return '%s/api/download?docid=%s&token=%s' % (
_tile_base_url, downloadId['docid'], downloadId['token'])
def getTableDownloadId(params):
"""Get a Download ID.
Args:
params: An object containing table download options with the following
possible values:
format - The download format, CSV or JSON.
selectors - Comma separated string of selectors that can be used to
determine which attributes will be downloaded.
filename - The name of the file that will be downloaded.
Returns:
A dict containing a docid and token.
"""
params['json_format'] = 'v2'
return send_('/table', params)
def makeTableDownloadUrl(downloadId):
"""Create a table download URL from a docid and token.
Args:
downloadId: A table download id and token.
Returns:
A Url from which the download can be obtained.
"""
return '%s/api/table?docid=%s&token=%s' % (
_tile_base_url, downloadId['docid'], downloadId['token'])
def getAlgorithms():
"""Get the list of algorithms.
Returns:
The dictionary of algorithms. Each algorithm is a dictionary containing
the following fields:
"description" - (string) A text description of the algorithm.
"returns" - (string) The return type of the algorithm.
"args" - An array of arguments. Each argument specifies the following:
"name" - (string) The name of the argument.
"description" - (string) A text description of the argument.
"type" - (string) The type of the argument.
"optional" - (boolean) Whether the argument is optional or not.
"default" - A representation of the default value if the argument
is not specified.
"""
if _use_cloud_api:
return _cloud_api_utils.convert_algorithms(
_execute_cloud_call(
_cloud_api_resource.projects().algorithms().list(
project=_get_projects_path(),
prettyPrint=False)))
return send_('/algorithms', {}, 'GET')
def createAsset(value, opt_path=None, opt_force=False, opt_properties=None):
"""Creates an asset from a JSON value.
To create an empty image collection or folder, pass in a "value" object
with a "type" key whose value is "ImageCollection" or "Folder".
If you are using the Cloud API, use "IMAGE_COLLECTION" or "FOLDER".
Args:
value: An object describing the asset to create or a JSON string
with the already-serialized value for the new asset.
opt_path: An optional desired ID, including full path.
opt_force: True if asset overwrite is allowed
opt_properties: The keys and values of the properties to set
on the created asset.
Returns:
A description of the saved asset, including a generated ID.
"""
if _use_cloud_api:
if not isinstance(value, dict):
raise ee_exception.EEException('Asset cannot be specified as string.')
asset = value.copy()
if 'name' not in asset:
if not opt_path:
raise ee_exception.EEException(
'Either asset name or opt_path must be specified.')
asset['name'] = _cloud_api_utils.convert_asset_id_to_asset_name(opt_path)
if 'properties' not in asset and opt_properties:
asset['properties'] = opt_properties
asset['type'] = _cloud_api_utils.convert_asset_type_for_create_asset(
asset['type'])
parent, asset_id = _cloud_api_utils.split_asset_name(asset.pop('name'))
return _execute_cloud_call(_cloud_api_resource.projects().assets().create(
parent=parent,
assetId=asset_id,
body=asset,
overwrite=opt_force,
prettyPrint=False))
if not isinstance(value, six.string_types):
value = json.dumps(value)
args = {'value': value, 'json_format': 'v2'}
if opt_path is not None:
args['id'] = opt_path
args['force'] = opt_force
if opt_properties is not None:
args['properties'] = json.dumps(opt_properties)
return send_('/create', args)
def copyAsset(sourceId, destinationId, allowOverwrite=False
):
"""Copies the asset from sourceId into destinationId.
Args:
sourceId: The ID of the asset to copy.
destinationId: The ID of the new asset created by copying.
allowOverwrite: If True, allows overwriting an existing asset.
"""
if _use_cloud_api:
request = {
'destinationName':
_cloud_api_utils.convert_asset_id_to_asset_name(destinationId),
'overwrite':
allowOverwrite
}
_execute_cloud_call(_cloud_api_resource.projects().assets().copy(
sourceName=_cloud_api_utils.convert_asset_id_to_asset_name(sourceId),
body=request))
return
request = {
'sourceId': sourceId,
'destinationId': destinationId,
'allowOverwrite': allowOverwrite,
}
send_('/copy', request)
def renameAsset(sourceId, destinationId):
"""Renames the asset from sourceId to destinationId.
Args:
sourceId: The ID of the asset to rename.
destinationId: The new ID of the asset.
"""
if _use_cloud_api:
_execute_cloud_call(_cloud_api_resource.projects().assets().move(
sourceName=_cloud_api_utils.convert_asset_id_to_asset_name(sourceId),
body={
'destinationName':
_cloud_api_utils.convert_asset_id_to_asset_name(destinationId)
}))
return
send_('/rename', {
'sourceId': sourceId,
'destinationId': destinationId,
})
def deleteAsset(assetId):
"""Deletes the asset with the given id.
Args:
assetId: The ID of the asset to delete.
"""
if _use_cloud_api:
_execute_cloud_call(_cloud_api_resource.projects().assets().delete(
name=_cloud_api_utils.convert_asset_id_to_asset_name(assetId)))
return
send_('/delete', {'id': assetId})
def newTaskId(count=1):
"""Generate an ID for a long-running task.
Args:
count: Optional count of IDs to generate, one by default.
Returns:
A list containing generated ID strings.
"""
if _use_cloud_api:
return [str(uuid.uuid4()) for _ in six.moves.xrange(count)]
args = {'count': count}
return send_('/newtaskid', args)
@deprecation.Deprecated('Use listOperations')
def getTaskList():
"""Retrieves a list of the user's tasks.
Returns:
A list of task status dictionaries, one for each task submitted to EE by
the current user. These include currently running tasks as well as recently
canceled or failed tasks.
"""
if _use_cloud_api:
return [_cloud_api_utils.convert_operation_to_task(o)
for o in listOperations()]