| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930 |
- #!/usr/bin/env python
- """Singleton for the library's communication with the Earth Engine API."""
- # 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 platform
- import re
- import threading
- import uuid
- import sys
- from google_auth_httplib2 import AuthorizedHttp
- from . import __version__
- from . import _cloud_api_utils
- from . import deprecation
- from . import encodable
- from . import oauth
- from . import serializer
- import googleapiclient
- from . import ee_exception
- from google.oauth2.credentials import Credentials
- # 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
- # 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 API client version number to send when making requests.
- _cloud_api_client_version = 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'
- # The HTTP header used to indicate the version of the client library used.
- _API_CLIENT_VERSION_HEADER = 'X-Goog-Api-Client'
- # Optional HTTP header returned to display initialization-time messages.
- _INIT_MESSAGE_HEADER = 'x-earth-engine-init-message' # lowercase for httplib2
- # 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,
- 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.
- cloud_api_base_url: The EarthEngine Cloud API endpoint.
- cloud_api_key: The API key to use with the Cloud API.
- project: The client project ID or number to use when making API calls.
- http_transport: The http transport to use
- """
- global _api_base_url, _tile_base_url, _credentials, _initialized
- global _cloud_api_base_url
- global _cloud_api_resource, _cloud_api_resource_raw, _cloud_api_key
- global _cloud_api_user_project, _http_transport
- global _cloud_api_client_version
- # 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 __version__ is not None:
- version = __version__
- _cloud_api_client_version = version
- _http_transport = http_transport
- _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 get_persistent_credentials():
- """Read persistent credentials from ~/.config/earthengine.
- Raises EEException with helpful explanation if credentials don't exist.
- Returns:
- OAuth2Credentials built from persistently stored refresh_token
- """
- try:
- return Credentials(None, **oauth.get_credentials_arguments())
- except IOError:
- raise ee_exception.EEException(
- 'Please authorize access to your Earth Engine account by '
- 'running\n\nearthengine authenticate\n\n'
- 'in your command line, and then retry.')
- 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
- 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
- _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
- 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 _get_cloud_api_resource():
- if _cloud_api_resource is None:
- raise ee_exception.EEException(
- 'Earth Engine client library not initialized. Run `ee.Initialize()`')
- return _cloud_api_resource
- def _make_request_headers():
- """Adds headers based on client context."""
- headers = {}
- client_version_header_values = []
- if _cloud_api_client_version is not None:
- client_version_header_values.append('ee-py/' + _cloud_api_client_version)
- client_version_header_values.append('python/' + platform.python_version())
- headers[_API_CLIENT_VERSION_HEADER] = ' '.join(client_version_header_values)
- 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 googleapiclient.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: A googleapiclient.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 _maybe_populate_workload_tag(body):
- """Populates the workload tag on the request body passed in if applicable.
- Defaults to the workload tag set by ee.data.setWorkloadTag() or related
- methods. A workload tag already set on the body takes precedence. The workload
- tag will not be set if it's an empty string.
- Args:
- body: The request body.
- """
- if 'workloadTag' not in body:
- workload_tag = getWorkloadTag()
- if workload_tag:
- body['workloadTag'] = workload_tag
- elif not body['workloadTag']:
- del body['workloadTag']
- 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.
- """
- # Don't use getAsset as it will translate the exception, and we need
- # to handle 404s specially.
- try:
- return _get_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 googleapiclient.errors.HttpError as e:
- if e.resp.status == 404:
- return None
- else:
- raise _translate_cloud_exception(e)
- 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(_get_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 possible values:
- id - (string) The asset id of the collection to list, required.
- starttime - (number) Start time, in msec since the epoch.
- endtime - (number) End time, in msec since the epoch.
- Returns:
- The list call results.
- """
- 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
- def listImages(params):
- """Returns the images in an image collection or folder.
- Args:
- params: An object containing request parameters with the following possible
- values, all but 'parent` are optional:
- parent - (string) The ID of the image collection to list, required.
- pageSize - (string) The number of results to return. Defaults to 1000.
- pageToken - (string) The token page of results to return.
- startTime - (ISO 8601 string): The minimum start time (inclusive).
- endTime - (ISO 8601 string): The maximum end time (exclusive).
- region - (GeoJSON or WKT string): A region to filter on.
- properties - (list of strings): A list of property filters to apply, for
- example, ["classification=urban", "size>=2"].
- filter - (string) An additional filter query to apply. Example query:
- `properties.my_property>=1 AND properties.my_property<2 AND
- startTime >= "2019-01-01T00:00:00.000Z" AND
- endTime < "2020-01-01T00:00:00.000Z" AND
- intersects("{'type':'Point','coordinates':[0,0]}")`
- See https://google.aip.dev/160 for how to construct a query.
- view - (string) Specifies how much detail is returned in the list. Either
- "FULL" (default) for all image properties or "BASIC".
- """
- images = {'images': []}
- assets = listAssets(
- _cloud_api_utils.convert_list_images_params_to_list_assets_params(params))
- images['images'].extend(assets.get('assets', []))
- return images
- def listAssets(params):
- """Returns the assets in a folder.
- Args:
- params: An object containing request parameters with the following possible
- values, all but 'parent` are optional:
- parent - (string) The ID of the collection or folder to list, required.
- pageSize - (string) The number of results to return. Defaults to 1000.
- pageToken - (string) The token page of results to return.
- filter - (string) An additional filter query to apply. Example query:
- '''properties.my_property>=1 AND properties.my_property<2 AND
- startTime >= "2019-01-01T00:00:00.000Z" AND
- endTime < "2020-01-01T00:00:00.000Z" AND
- intersects("{'type':'Point','coordinates':[0,0]}")'''
- See https://google.aip.dev/160 for how to construct a query.
- view - (string) Specifies how much detail is returned in the list. Either
- "FULL" (default) for all image properties or "BASIC".
- """
- assets = {'assets': []}
- if 'parent' in params:
- params['parent'] = _cloud_api_utils.convert_asset_id_to_asset_name(
- params['parent'])
- if 'parent' in params and _cloud_api_utils.is_asset_root(params['parent']):
- # If the asset name is 'projects/my-project/assets' we assume a user
- # wants to list their cloud assets, to do this we call the alternative
- # listAssets method and remove the trailing '/assets/?'
- params['parent'] = re.sub('/assets/?$', '', params['parent'])
- cloud_resource_root = _get_cloud_api_resource().projects()
- else:
- cloud_resource_root = _get_cloud_api_resource().projects().assets()
- request = cloud_resource_root.listAssets(**params)
- while request is not None:
- response = _execute_cloud_call(request)
- assets['assets'].extend(response.get('assets', []))
- request = cloud_resource_root.listAssets_next(request, response)
- # We currently treat pageSize as a cap on the results, if this param was
- # provided we should break fast and not return more than the asked for
- # amount.
- if 'pageSize' in params:
- break
- return assets
- def listBuckets(project=None):
- if project is None:
- project = _get_projects_path()
- return _execute_cloud_call(
- _get_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 map ID dictionary containing:
- - "mapid" and optional "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 isinstance(params['image'], str):
- 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
- # Returns only the `name` field, otherwise it echoes the entire request, which
- # might be large.
- queryParams = {
- 'fields': 'name',
- 'body': request,
- }
- _maybe_populate_workload_tag(queryParams)
- result = _execute_cloud_call(
- _get_cloud_api_resource().projects().maps().create(
- parent=_get_projects_path(), **queryParams))
- map_name = result['name']
- url_format = '%s/%s/%s/tiles/{z}/{x}/{y}' % (
- _tile_base_url, _cloud_api_utils.VERSION, 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)}
- def getFeatureViewTilesKey(params):
- """Get a tiles key for a given map or asset.
- Args:
- params: An object containing parameters with the following possible values:
- assetId - The asset ID for which to obtain a tiles key.
- visParams - The visualization parameters for this layer.
- Returns:
- A dictionary containing:
- - "token" string: this identifies the FeatureView.
- """
- request = {
- 'asset':
- _cloud_api_utils.convert_asset_id_to_asset_name(
- params.get('assetId'))
- }
- # Only include visParams if it's non-empty.
- if params.get('visParams'):
- request['visualizationExpression'] = serializer.encode(
- params.get('visParams'), for_cloud_api=True)
- # Returns only the `name` field, otherwise it echoes the entire request, which
- # might be large.
- result = _execute_cloud_call(
- _get_cloud_api_resource().projects().featureView().create(
- parent=_get_projects_path(), fields='name', body=request))
- name = result['name']
- version = _cloud_api_utils.VERSION
- format_tile_url = (
- lambda x, y, z: f'{_tile_base_url}/{version}/{name}/tiles/{z}/{x}/{y}')
- token = name.rsplit('/', 1).pop()
- return {
- 'token': token,
- 'formatTileUrl': format_tile_url,
- }
- def listFeatures(params):
- """List features for a given table or FeatureView asset.
- Args:
- params: An object containing parameters with the following possible values:
- assetId - The asset ID for which to list features.
- pageSize - An optional max number of results per page, default is 1000.
- pageToken - An optional token identifying a new page of results the server
- should return, usually taken from the response object.
- region - If present, a geometry defining a query region, specified as a
- GeoJSON geometry string (see RFC 7946).
- filter - If present, specifies additional simple property filters
- (see https://google.aip.dev/160).
- Returns:
- A dictionary containing:
- - "type": always "FeatureCollection" marking this object as a GeoJSON
- feature collection.
- - "features": a list of GeoJSON features.
- - "next_page_token": A token to retrieve the next page of results in a
- subsequent call to this function.
- """
- params = params.copy()
- params['asset'] = _cloud_api_utils.convert_asset_id_to_asset_name(
- params.get('assetId'))
- del params['assetId']
- return _execute_cloud_call(
- _get_cloud_api_resource().projects().assets().listFeatures(**params))
- 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.
- """
- 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
- )
- 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.
- """
- body = {'expression': serializer.encode(obj, for_cloud_api=True)}
- _maybe_populate_workload_tag(body)
- return _execute_cloud_call(
- _get_cloud_api_resource().projects().value().compute(
- body=body,
- project=_get_projects_path(),
- prettyPrint=False))['result']
- @deprecation.Deprecated('Use getThumbId and makeThumbUrl')
- def getThumbnail(params, thumbType=None):
- """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'.
- thumbType: Thumbnail type to get. Only valid values are
- 'video' or 'filmstrip' otherwise the request is treated as a
- regular thumbnail.
- Returns:
- A thumbnail image as raw PNG data.
- """
- thumbid = params['image'].getThumbId(params)['thumbid']
- if thumbType == 'video':
- return _execute_cloud_call(
- _cloud_api_resource_raw.projects().videoThumbnails().getPixels(
- name=thumbid
- ), num_retries=MAX_RETRIES
- )
- elif thumbType == 'filmstrip':
- return _execute_cloud_call(
- _cloud_api_resource_raw.projects().filmstripThumbnails().getPixels(
- name=thumbid
- ), num_retries=MAX_RETRIES
- )
- else:
- return _execute_cloud_call(
- _cloud_api_resource_raw.projects().thumbnails().getPixels(
- name=thumbid
- ), num_retries=MAX_RETRIES
- )
- def getThumbId(params, thumbType=None):
- """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'.
- thumbType: Type of thumbnail to create an ID for, the values
- 'video' or 'filmstrip' will create filmstrip/video ids.
- Returns:
- A dictionary containing "thumbid" and "token" strings, which identify the
- thumbnail.
- """
- # 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'], str):
- 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')),
- }
- # 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
- # Returns only the `name` field, otherwise it echoes the entire request, which
- # might be large.
- queryParams = {
- 'fields': 'name',
- 'body': request,
- }
- _maybe_populate_workload_tag(queryParams)
- if thumbType == 'video':
- if 'framesPerSecond' in params:
- request['videoOptions'] = {
- 'framesPerSecond': params.get('framesPerSecond')
- }
- result = _execute_cloud_call(
- _get_cloud_api_resource().projects().videoThumbnails().create(
- parent=_get_projects_path(), **queryParams))
- elif thumbType == 'filmstrip':
- # Currently only 'VERTICAL' thumbnails are supported.
- request['orientation'] = 'VERTICAL'
- result = _execute_cloud_call(
- _get_cloud_api_resource().projects().filmstripThumbnails().create(
- parent=_get_projects_path(), **queryParams))
- else:
- request['filenamePrefix'] = params.get('name')
- request['bandIds'] = _cloud_api_utils.convert_to_band_list(
- params.get('bands'))
- result = _execute_cloud_call(
- _get_cloud_api_resource().projects().thumbnails().create(
- parent=_get_projects_path(), **queryParams))
- return {'thumbid': result['name'], 'token': ''}
- 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.
- """
- url = '%s/%s/%s:getPixels' % (_tile_base_url, _cloud_api_utils.VERSION,
- thumbId['thumbid'])
- if _cloud_api_key:
- url += '?key=%s' % _cloud_api_key
- return url
- def getDownloadId(params):
- """Get a Download ID.
- Args:
- params: An object containing visualization options with the following
- possible values:
- image - The image to download.
- - name: a base name to use when constructing filenames. Only applicable
- when format is "ZIPPED_GEO_TIFF" (default) or filePerBand is true.
- Defaults to the image id (or "download" for computed images) when
- format is "ZIPPED_GEO_TIFF" or filePerBand is true, otherwise a
- random character string is generated. Band names are appended when
- filePerBand is true.
- - bands: a description of the bands to download. Must be an array of
- band names or 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, 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 are 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 are specified.
- - filePerBand: whether to produce a separate GeoTIFF per band (boolean).
- Defaults to true. If false, a single GeoTIFF is produced and all
- band-level transformations will be ignored.
- - format: the download format. One of:
- "ZIPPED_GEO_TIFF" (GeoTIFF file(s) wrapped in a zip file, default),
- "GEO_TIFF" (GeoTIFF file), "NPY" (NumPy binary format).
- If "GEO_TIFF" or "NPY", filePerBand and all band-level
- transformations will be ignored. Loading a NumPy output results in
- a structured array.
- - id: deprecated, use image parameter.
- Returns:
- A dict containing a docid and token.
- """
- params = params.copy()
- # Previously, the docs required an image ID parameter that was changed
- # to image. Due to the circular dependency, we raise an error and ask the
- # user to supply an ee.Image directly.
- if 'id' in params:
- raise ee_exception.EEException('Image ID string is not supported. '
- 'Construct an image with the ID '
- '(e.g. ee.Image(id)) and use '
- 'ee.Image.getDownloadURL instead.')
- if 'image' not in params:
- raise ee_exception.EEException('Missing image parameter.')
- if isinstance(params['image'], str):
- raise ee_exception.EEException('Image as JSON string not supported.')
- params.setdefault('filePerBand', True)
- params.setdefault(
- 'format', 'ZIPPED_GEO_TIFF_PER_BAND'
- if params['filePerBand'] else 'ZIPPED_GEO_TIFF')
- if 'region' in params and ('scale' in params or 'crs_transform' in params
- ) and 'dimensions' in params:
- raise ee_exception.EEException(
- 'Cannot specify (bounding region, crs_transform/scale, dimensions) '
- 'simultaneously.'
- )
- bands = None
- if 'bands' in params:
- bands = params['bands']
- if isinstance(bands, str):
- bands = _cloud_api_utils.convert_to_band_list(bands)
- if not isinstance(bands, list):
- raise ee_exception.EEException('Bands parameter must be a list.')
- if all(isinstance(band, str) for band in bands):
- # Support expressing the bands list as a list of strings.
- bands = [{'id': band} for band in bands]
- if not all('id' in band for band in bands):
- raise ee_exception.EEException('Each band dictionary must have an id.')
- params['bands'] = bands
- request = {
- 'expression':
- serializer.encode(
- params['image']._build_download_id_image(params), # pylint: disable=protected-access
- for_cloud_api=True),
- 'fileFormat':
- _cloud_api_utils.convert_to_image_file_format(params.get('format')),
- }
- request['filenamePrefix'] = params.get('name')
- if bands:
- request['bandIds'] = _cloud_api_utils.convert_to_band_list(
- [band['id'] for band in bands])
- # Returns only the `name` field, otherwise it echoes the entire request, which
- # might be large.
- queryParams = {
- 'fields': 'name',
- 'body': request,
- }
- _maybe_populate_workload_tag(queryParams)
- result = _execute_cloud_call(
- _get_cloud_api_resource().projects().thumbnails().create(
- parent=_get_projects_path(), **queryParams))
- return {'docid': result['name'], 'token': ''}
- 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/%s/%s:getPixels' % (_tile_base_url, _cloud_api_utils.VERSION,
- downloadId['docid'])
- def getTableDownloadId(params):
- """Get a Download ID.
- Args:
- params: An object containing table download options with the following
- possible values:
- table - The feature collection to download.
- format - The download format, CSV, JSON, KML, KMZ, or TF_RECORD.
- 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.
- Raises:
- KeyError: if "table" is not specified.
- """
- if 'table' not in params:
- raise KeyError('"table" must be specified.')
- table = params['table']
- selectors = None
- if 'selectors' in params:
- selectors = params['selectors']
- if isinstance(selectors, str):
- selectors = selectors.split(',')
- filename = None
- if 'filename' in params:
- filename = params['filename']
- request = {
- 'expression': serializer.encode(table, for_cloud_api=True),
- 'fileFormat':
- _cloud_api_utils.convert_to_table_file_format(params.get('format')),
- 'selectors': selectors,
- 'filename': filename,
- }
- # Returns only the `name` field, otherwise it echoes the entire request, which
- # might be large.
- queryParams = {
- 'fields': 'name',
- 'body': request,
- }
- _maybe_populate_workload_tag(queryParams)
- result = _execute_cloud_call(
- _get_cloud_api_resource().projects().tables().create(
- parent=_get_projects_path(), **queryParams))
- return {'docid': result['name'], 'token': ''}
- 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/%s/%s:getFeatures' % (
- _tile_base_url, _cloud_api_utils.VERSION, downloadId['docid'])
- 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.
- """
- try:
- call = _get_cloud_api_resource().projects().algorithms().list(
- parent=_get_projects_path(), prettyPrint=False)
- except TypeError:
- call = _get_cloud_api_resource().projects().algorithms().list(
- project=_get_projects_path(), prettyPrint=False)
- def inspect(response):
- if _INIT_MESSAGE_HEADER in response:
- print(
- '*** Earth Engine ***',
- response[_INIT_MESSAGE_HEADER],
- file=sys.stderr)
- call.add_response_callback(inspect)
- return _cloud_api_utils.convert_algorithms(_execute_cloud_call(call))
- def createAsset(
- value,
- opt_path=None,
- 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_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 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(
- _get_cloud_api_resource().projects().assets().create(
- parent=parent,
- assetId=asset_id,
- body=asset,
- prettyPrint=False))
- 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.
- """
- request = {
- 'destinationName':
- _cloud_api_utils.convert_asset_id_to_asset_name(destinationId),
- 'overwrite':
- allowOverwrite
- }
- _execute_cloud_call(_get_cloud_api_resource().projects().assets().copy(
- sourceName=_cloud_api_utils.convert_asset_id_to_asset_name(sourceId),
- body=request))
- return
- 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.
- """
- _execute_cloud_call(_get_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
- def deleteAsset(assetId):
- """Deletes the asset with the given id.
- Args:
- assetId: The ID of the asset to delete.
- """
- _execute_cloud_call(_get_cloud_api_resource().projects().assets().delete(
- name=_cloud_api_utils.convert_asset_id_to_asset_name(assetId)))
- return
- 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.
- """
- return [str(uuid.uuid4()) for _ in range(count)]
- @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.
- """
- return [_cloud_api_utils.convert_operation_to_task(o)
- for o in listOperations()]
- def listOperations(project=None):
- """Retrieves a list of the user's tasks.
- Args:
- project: The project to list operations for, uses the default set project
- if none is provided.
- Returns:
- A list of Operation 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 project is None:
- project = _get_projects_path()
- operations = []
- request = _get_cloud_api_resource().projects().operations().list(
- pageSize=_TASKLIST_PAGE_SIZE, name=project)
- while request is not None:
- try:
- response = request.execute(num_retries=MAX_RETRIES)
- operations += response.get('operations', [])
- request = _cloud_api_resource.projects().operations().list_next(
- request, response)
- except googleapiclient.errors.HttpError as e:
- raise _translate_cloud_exception(e)
- return operations
- @deprecation.Deprecated('Use getOperation')
- def getTaskStatus(taskId):
- """Retrieve status of one or more long-running tasks.
- Args:
- taskId: ID of the task or a list of multiple IDs.
- Returns:
- List containing one object for each queried task, in the same order as
- the input array, each object containing the following values:
- id (string) ID of the task.
- state (string) State of the task, one of READY, RUNNING, COMPLETED,
- FAILED, CANCELLED; or UNKNOWN if the task with the specified ID
- doesn't exist.
- error_message (string) For a FAILED task, a description of the error.
- """
- if isinstance(taskId, str):
- taskId = [taskId]
- result = []
- for one_id in taskId:
- try:
- # Don't use getOperation as it will translate the exception, and we need
- # to handle 404s specially.
- operation = _get_cloud_api_resource().projects().operations().get(
- name=_cloud_api_utils.convert_task_id_to_operation_name(
- one_id)).execute(num_retries=MAX_RETRIES)
- result.append(_cloud_api_utils.convert_operation_to_task(operation))
- except googleapiclient.errors.HttpError as e:
- if e.resp.status == 404:
- result.append({'id': one_id, 'state': 'UNKNOWN'})
- else:
- raise _translate_cloud_exception(e)
- return result
- def getOperation(operation_name):
- """Retrieves the status of a long-running operation.
- Args:
- operation_name: The name of the operation to retrieve, in the format
- operations/AAAABBBBCCCCDDDDEEEEFFFF.
- Returns:
- An Operation status dictionary for the requested operation.
- """
- return _execute_cloud_call(
- _get_cloud_api_resource().projects().operations().get(
- name=operation_name))
- @deprecation.Deprecated('Use cancelOperation')
- def cancelTask(taskId):
- """Cancels a batch task."""
- cancelOperation(_cloud_api_utils.convert_task_id_to_operation_name(taskId))
- return
- def cancelOperation(operation_name):
- _execute_cloud_call(_get_cloud_api_resource().projects().operations().cancel(
- name=operation_name, body={}))
- def exportImage(request_id, params):
- """Starts an image export task running.
- This is a low-level method. The higher-level ee.batch.Export.image object
- is generally preferred for initiating image exports.
- Args:
- request_id (string): A unique ID for the task, from newTaskId.
- If you are using the cloud API, this does not need to be from newTaskId,
- (though that's a good idea, as it's a good source of unique strings).
- It can also be empty, but in that case the request is more likely to
- fail as it cannot be safely retried.
- params: The object that describes the export task.
- If you are using the cloud API, this should be an ExportImageRequest.
- However, the "expression" parameter can be the actual Image to be
- exported, not its serialized form.
- Returns:
- A dict with information about the created task.
- If you are using the cloud API, this will be an Operation.
- """
- params = params.copy()
- return _prepare_and_run_export(
- request_id, params,
- _get_cloud_api_resource().projects().image().export)
- def exportTable(request_id, params):
- """Starts a table export task running.
- This is a low-level method. The higher-level ee.batch.Export.table object
- is generally preferred for initiating table exports.
- Args:
- request_id (string): A unique ID for the task, from newTaskId. If you are
- using the cloud API, this does not need to be from newTaskId, (though
- that's a good idea, as it's a good source of unique strings). It can also
- be empty, but in that case the request is more likely to fail as it cannot
- be safely retried.
- params: The object that describes the export task. If you are using the
- cloud API, this should be an ExportTableRequest. However, the "expression"
- parameter can be the actual FeatureCollection to be exported, not its
- serialized form.
- Returns:
- A dict with information about the created task.
- If you are using the cloud API, this will be an Operation.
- """
- params = params.copy()
- return _prepare_and_run_export(
- request_id, params,
- _get_cloud_api_resource().projects().table().export)
- def exportVideo(request_id, params):
- """Starts a video export task running.
- This is a low-level method. The higher-level ee.batch.Export.video object
- is generally preferred for initiating video exports.
- Args:
- request_id (string): A unique ID for the task, from newTaskId.
- If you are using the cloud API, this does not need to be from newTaskId,
- (though that's a good idea, as it's a good source of unique strings).
- It can also be empty, but in that case the request is more likely to
- fail as it cannot be safely retried.
- params: The object that describes the export task.
- If you are using the cloud API, this should be an ExportVideoRequest.
- However, the "expression" parameter can be the actual ImageCollection
- to be exported, not its serialized form.
- Returns:
- A dict with information about the created task.
- If you are using the cloud API, this will be an Operation.
- """
- params = params.copy()
- return _prepare_and_run_export(
- request_id, params,
- _get_cloud_api_resource().projects().video().export)
- def exportMap(request_id, params):
- """Starts a map export task running.
- This is a low-level method. The higher-level ee.batch.Export.map object
- is generally preferred for initiating map tile exports.
- Args:
- request_id (string): A unique ID for the task, from newTaskId.
- If you are using the cloud API, this does not need to be from newTaskId,
- (though that's a good idea, as it's a good source of unique strings).
- It can also be empty, but in that case the request is more likely to
- fail as it cannot be safely retried.
- params: The object that describes the export task.
- If you are using the cloud API, this should be an ExportMapRequest.
- However, the "expression" parameter can be the actual Image to be
- exported, not its serialized form.
- Returns:
- A dict with information about the created task.
- If you are using the cloud API, this will be an Operation.
- """
- params = params.copy()
- return _prepare_and_run_export(
- request_id, params,
- _get_cloud_api_resource().projects().map().export)
- def _prepare_and_run_export(request_id, params, export_endpoint):
- """Starts an export task running.
- Args:
- request_id (string): An optional unique ID for the task.
- params: The object that describes the export task. The "expression"
- parameter can be the actual object to be exported, not its serialized
- form. This may be modified.
- export_endpoint: A callable representing the export endpoint to invoke
- (e.g., _cloud_api_resource.image().export).
- Returns:
- An Operation with information about the created task.
- """
- _maybe_populate_workload_tag(params)
- if request_id:
- if isinstance(request_id, str):
- params['requestId'] = request_id
- # If someone passes request_id via newTaskId() (which returns a list)
- # try to do the right thing and use the first entry as a request ID.
- elif (isinstance(request_id, list) and len(request_id) == 1 and
- isinstance(request_id[0], str)):
- params['requestId'] = request_id[0]
- else:
- raise ValueError('"requestId" must be a string.')
- if isinstance(params['expression'], encodable.Encodable):
- params['expression'] = serializer.encode(
- params['expression'], for_cloud_api=True)
- num_retries = MAX_RETRIES if request_id else 0
- return _execute_cloud_call(
- export_endpoint(project=_get_projects_path(), body=params),
- num_retries=num_retries)
- def startIngestion(request_id, params, allow_overwrite=False):
- """Creates an image asset import task.
- Args:
- request_id (string): A unique ID for the ingestion, from newTaskId.
- If you are using the Cloud API, this does not need to be from newTaskId,
- (though that's a good idea, as it's a good source of unique strings).
- It can also be empty, but in that case the request is more likely to
- fail as it cannot be safely retried.
- params: The object that describes the import task, which can
- have these fields:
- name (string) The destination asset id (e.g.,
- "projects/earthengine-legacy/assets/users/foo/bar").
- tilesets (array) A list of Google Cloud Storage source file paths
- formatted like:
- [{'sources': [
- {'uris': ['foo.tif', 'foo.prj']},
- {'uris': ['bar.tif', 'bar.prj']},
- ]}]
- Where path values correspond to source files' Google Cloud Storage
- object names, e.g. 'gs://bucketname/filename.tif'
- bands (array) An optional list of band names formatted like:
- [{'id': 'R'}, {'id': 'G'}, {'id': 'B'}]
- In general, this is a dict representation of an ImageManifest.
- allow_overwrite: Whether the ingested image can overwrite an
- existing version.
- Returns:
- A dict with notes about the created task. This will include the ID for the
- import task (under 'id'), which may be different from request_id.
- """
- request = {
- 'imageManifest':
- _cloud_api_utils.convert_params_to_image_manifest(params),
- 'requestId':
- request_id,
- 'overwrite':
- allow_overwrite
- }
- # It's only safe to retry the request if there's a unique ID to make it
- # idempotent.
- num_retries = MAX_RETRIES if request_id else 0
- operation = _execute_cloud_call(
- _get_cloud_api_resource().projects().image().import_(
- project=_get_projects_path(), body=request),
- num_retries=num_retries)
- return {
- 'id':
- _cloud_api_utils.convert_operation_name_to_task_id(
- operation['name']),
- 'name': operation['name'],
- 'started': 'OK',
- }
- def startTableIngestion(request_id, params, allow_overwrite=False):
- """Creates a table asset import task.
- Args:
- request_id (string): A unique ID for the ingestion, from newTaskId.
- If you are using the Cloud API, this does not need to be from newTaskId,
- (though that's a good idea, as it's a good source of unique strings).
- It can also be empty, but in that case the request is more likely to
- fail as it cannot be safely retried.
- params: The object that describes the import task, which can
- have these fields:
- name (string) The destination asset id (e.g.,
- "projects/earthengine-legacy/assets/users/foo/bar").
- sources (array) A list of GCS (Google Cloud Storage) file paths
- with optional character encoding formatted like this:
- "sources":[{"uris":["gs://bucket/file.shp"],"charset":"UTF-8"}]
- Here 'charset' refers to the character encoding of the source file.
- In general, this is a dict representation of a TableManifest.
- allow_overwrite: Whether the ingested image can overwrite an
- existing version.
- Returns:
- A dict with notes about the created task. This will include the ID for the
- import task (under 'id'), which may be different from request_id.
- """
- request = {
- 'tableManifest':
- _cloud_api_utils.convert_params_to_table_manifest(params),
- 'requestId':
- request_id,
- 'overwrite':
- allow_overwrite
- }
- # It's only safe to retry the request if there's a unique ID to make it
- # idempotent.
- num_retries = MAX_RETRIES if request_id else 0
- operation = _execute_cloud_call(
- _get_cloud_api_resource().projects().table().import_(
- project=_get_projects_path(), body=request),
- num_retries=num_retries)
- return {
- 'id':
- _cloud_api_utils.convert_operation_name_to_task_id(
- operation['name']),
- 'name': operation['name'],
- 'started': 'OK'
- }
- def getAssetRoots():
- """Returns the list of the root folders the user owns.
- Note: The "id" values for roots are two levels deep, e.g. "users/johndoe"
- not "users/johndoe/notaroot".
- Returns:
- A list of folder descriptions formatted like:
- [
- {"type": "Folder", "id": "users/foo"},
- {"type": "Folder", "id": "projects/bar"},
- ]
- """
- return _cloud_api_utils.convert_list_assets_result_to_get_list_result(
- listBuckets())
- def getAssetRootQuota(rootId):
- """Returns quota usage details for the asset root with the given ID.
- Usage notes:
- - The id *must* be a root folder like "users/foo" (not "users/foo/bar").
- - The authenticated user must own the asset root to see its quota usage.
- Args:
- rootId: The ID of the asset to check.
- Returns:
- A dict describing the asset's quota usage. Looks like, with size in bytes:
- {
- asset_count: {usage: number, limit: number},
- asset_size: {usage: number, limit: number},
- }
- """
- asset = getAsset(rootId)
- if 'quota' not in asset:
- raise ee_exception.EEException('{} is not a root folder.'.format(rootId))
- quota = asset['quota']
- # The quota fields are int64s, and int64s are represented as strings in
- # JSON. Turn them back.
- return {
- 'asset_count': {
- 'usage': int(quota.get('assetCount', 0)),
- 'limit': int(quota.get('maxAssets', quota.get('maxAssetCount', 0)))
- },
- 'asset_size': {
- 'usage': int(quota.get('sizeBytes', 0)),
- 'limit': int(quota.get('maxSizeBytes', 0))
- }
- }
- @deprecation.Deprecated('Use getIamPolicy')
- def getAssetAcl(assetId):
- """Returns the access control list of the asset with the given ID.
- Args:
- assetId: The ID of the asset to check.
- Returns:
- A dict describing the asset's ACL. Looks like:
- {
- "owners" : ["user@domain1.com"],
- "writers": ["user2@domain1.com", "user3@domain1.com"],
- "readers": ["some_group@domain2.com"],
- "all_users_can_read" : True
- }
- If you are using the cloud API, then the entities in the ACL will
- be prefixed by a type tag, such as "user:" or "group:".
- """
- policy = getIamPolicy(assetId)
- return _cloud_api_utils.convert_iam_policy_to_acl(policy)
- def getIamPolicy(asset_id):
- """Loads ACL info for an asset, given an asset id.
- Args:
- asset_id: The asset to be retrieved.
- Returns:
- The asset's ACL, as an IAM Policy.
- """
- return _execute_cloud_call(
- _get_cloud_api_resource().projects().assets().getIamPolicy(
- resource=_cloud_api_utils.convert_asset_id_to_asset_name(asset_id),
- body={},
- prettyPrint=False))
- @deprecation.Deprecated('Use setIamPolicy')
- def setAssetAcl(assetId, aclUpdate):
- """Sets the access control list of the asset with the given ID.
- The owner ACL cannot be changed, and the final ACL of the asset
- is constructed by merging the OWNER entries of the old ACL with
- the incoming ACL record.
- Args:
- assetId: The ID of the asset to set the ACL on.
- aclUpdate: The updated ACL for the asset. Must be formatted like the
- value returned by getAssetAcl but without "owners".
- """
- # The ACL may be a string by the time it gets to us. Sigh.
- if isinstance(aclUpdate, str):
- aclUpdate = json.loads(aclUpdate)
- setIamPolicy(assetId, _cloud_api_utils.convert_acl_to_iam_policy(aclUpdate))
- return
- def setIamPolicy(asset_id, policy):
- """Sets ACL info for an asset.
- Args:
- asset_id: The asset to set the ACL policy on.
- policy: The new Policy to apply to the asset. This replaces
- the current Policy.
- Returns:
- The new ACL, as an IAM Policy.
- """
- return _execute_cloud_call(
- _get_cloud_api_resource().projects().assets().setIamPolicy(
- resource=_cloud_api_utils.convert_asset_id_to_asset_name(asset_id),
- body={'policy': policy},
- prettyPrint=False))
- def setAssetProperties(assetId, properties):
- """Sets metadata properties of the asset with the given ID.
- To delete a property, set its value to None.
- The authenticated user must be a writer or owner of the asset.
- Args:
- assetId: The ID of the asset to set the ACL on.
- properties: A dictionary of keys and values for the properties to update.
- """
- def FieldMaskPathForKey(key):
- return 'properties.\"%s\"' % key
- # Specifying an update mask of 'properties' results in full replacement,
- # which isn't what we want. Instead, we name each property that we'll be
- # updating.
- update_mask = [FieldMaskPathForKey(key) for key in properties]
- updateAsset(assetId, {'properties': properties}, update_mask)
- return
- def updateAsset(asset_id, asset, update_mask):
- """Updates an asset.
- Args:
- asset_id: The ID of the asset to update.
- asset: The updated version of the asset, containing only the new values of
- the fields to be updated. Only the "start_time", "end_time", and
- "properties" fields can be updated. If a value is named in "update_mask",
- but is unset in "asset", then that value will be deleted from the asset.
- update_mask: A list of the values to update. This should contain the strings
- "start_time" or "end_time" to update the corresponding timestamp. If
- a property is to be updated or deleted, it should be named here as
- "properties.THAT_PROPERTY_NAME". If the entire property set is to be
- replaced, this should contain the string "properties". If this list is
- empty, all properties and both timestamps will be updated.
- """
- name = _cloud_api_utils.convert_asset_id_to_asset_name(asset_id)
- _execute_cloud_call(_get_cloud_api_resource().projects().assets().patch(
- name=name, body={
- 'updateMask': {
- 'paths': update_mask
- },
- 'asset': asset
- }))
- def createAssetHome(requestedId):
- """Attempts to create a home root folder for the current user ("users/joe").
- Results in an error if the user already has a home root folder or the
- requested ID is unavailable.
- Args:
- requestedId: The requested ID of the home folder (e.g. "users/joe").
- """
- # This is just a special case of folder creation.
- createAsset({
- 'name': _cloud_api_utils.convert_asset_id_to_asset_name(requestedId),
- 'type': 'FOLDER'
- })
- return
- def authorizeHttp(http):
- if _credentials:
- return AuthorizedHttp(_credentials)
- else:
- return http
- def create_assets(asset_ids, asset_type, mk_parents):
- """Creates the specified assets if they do not exist."""
- for asset_id in asset_ids:
- if getInfo(asset_id):
- print('Asset %s already exists.' % asset_id)
- continue
- if mk_parents:
- parts = asset_id.split('/')
- # We don't need to create the namespace and the user's/project's folder.
- if len(parts) > 2:
- path = parts[0] + '/' + parts[1] + '/'
- for part in parts[2:-1]:
- path += part
- if getInfo(path) is None:
- createAsset({'type': ASSET_TYPE_FOLDER_CLOUD}, path)
- path += '/'
- createAsset({'type': asset_type}, asset_id)
- def convert_asset_id_to_asset_name(asset_id):
- """Converts an internal asset ID to a Cloud API asset name.
- If asset_id already matches the format 'projects/*/assets/**', it is returned
- as-is.
- Args:
- asset_id: The asset ID to convert.
- Returns:
- An asset name string in the format 'projects/*/assets/**'.
- """
- return _cloud_api_utils.convert_asset_id_to_asset_name(asset_id)
- def getWorkloadTag():
- """Returns the currently set workload tag."""
- return _workloadTag.get()
- def setWorkloadTag(tag):
- """Sets the workload tag, used to label computation and exports.
- Workload tag must be 1 - 63 characters, beginning and ending with an
- alphanumeric character ([a-z0-9A-Z]) with dashes (-), underscores (_), dots
- (.), and alphanumerics between, or an empty string to clear the workload tag.
- Args:
- tag: The tag to set.
- """
- _workloadTag.set(tag)
- @contextlib.contextmanager
- def workloadTagContext(tag):
- """Produces a context manager which sets the workload tag, then resets it.
- Workload tag must be 1 - 63 characters, beginning and ending with an
- alphanumeric character ([a-z0-9A-Z]) with dashes (-), underscores (_), dots
- (.), and alphanumerics between, or an empty string to clear the workload tag.
- Args:
- tag: The tag to set.
- Yields:
- None.
- """
- setWorkloadTag(tag)
- try:
- yield
- finally:
- resetWorkloadTag()
- def setDefaultWorkloadTag(tag):
- """Sets the workload tag, and as the default for which to reset back to.
- For example, calling `ee.data.resetWorkloadTag()` will reset the workload tag
- back to the default chosen here. To reset the default back to none, pass in
- an empty string or pass in true to `ee.data.resetWorkloadTag(true)`, like so.
- Workload tag must be 1 - 63 characters, beginning and ending with an
- alphanumeric character ([a-z0-9A-Z]) with dashes (-), underscores (_), dots
- (.), and alphanumerics between, or an empty string to reset the default back
- to none.
- Args:
- tag: The tag to set.
- """
- _workloadTag.setDefault(tag)
- _workloadTag.set(tag)
- def resetWorkloadTag(opt_resetDefault=False):
- """Sets the default tag for which to reset back to.
- If opt_resetDefault parameter is set to true, the default will be set to empty
- before resetting. Defaults to False.
- Args:
- opt_resetDefault: Whether to reset the default back to empty.
- """
- if opt_resetDefault:
- _workloadTag.setDefault('')
- _workloadTag.reset()
- class _WorkloadTag(object):
- """A helper class to manage the workload tag."""
- def __init__(self):
- self._tag = ''
- self._default = ''
- def get(self):
- return self._tag
- def set(self, tag):
- self._tag = self.validate(tag)
- def setDefault(self, newDefault):
- self._default = self.validate(newDefault)
- def reset(self):
- self._tag = self._default
- def validate(self, tag):
- """Throws an error if setting an invalid tag.
- Args:
- tag: the tag to validate.
- Returns:
- The validated tag.
- Raises:
- ValueError if the tag does not match the expected format.
- """
- if not tag and tag != 0:
- return ''
- tag = str(tag)
- if not re.fullmatch(r'([a-z0-9]|[a-z0-9][-_\.a-z0-9]{0,61}[a-z0-9])', tag):
- validationMessage = (
- 'Tags must be 1-63 characters, '
- 'beginning and ending with an lowercase alphanumeric character'
- '([a-z0-9]) with dashes (-), underscores (_), '
- 'dots (.), and lowercase alphanumerics between.')
- raise ValueError(f'Invalid tag, "{tag}". {validationMessage}')
- return tag
- # Tracks the currently set workload tag.
- _workloadTag = _WorkloadTag()
|