mirror of
https://github.com/google/earthengine-api.git
synced 2025-12-08 19:26:12 +00:00
224 lines
7.6 KiB
Python
224 lines
7.6 KiB
Python
#!/usr/bin/env python
|
|
"""A deserializer that decodes EE object trees from JSON DAGs."""
|
|
|
|
|
|
|
|
# Using lowercase function naming to match the JavaScript names.
|
|
# pylint: disable=g-bad-name
|
|
|
|
# pylint: disable=g-bad-import-order
|
|
import json
|
|
import numbers
|
|
import six
|
|
|
|
from . import apifunction
|
|
from . import computedobject
|
|
from . import customfunction
|
|
from . import ee_date
|
|
from . import ee_exception
|
|
from . import encodable
|
|
from . import function
|
|
from . import geometry
|
|
|
|
|
|
def fromJSON(json_obj):
|
|
"""Deserialize an object from a JSON string appropriate for API calls.
|
|
|
|
Args:
|
|
json_obj: The JSON represenation of the input.
|
|
|
|
Returns:
|
|
The deserialized object.
|
|
"""
|
|
return decode(json.loads(json_obj))
|
|
|
|
|
|
def decode(json_obj):
|
|
"""Decodes an object previously encoded using the EE API v2 (DAG) format.
|
|
|
|
Args:
|
|
json_obj: The serialied object to decode.
|
|
|
|
Returns:
|
|
The decoded object.
|
|
"""
|
|
if 'values' in json_obj and 'result' in json_obj:
|
|
return decodeCloudApi(json_obj)
|
|
named_values = {}
|
|
|
|
# Incrementally decode scope entries if there are any.
|
|
if isinstance(json_obj, dict) and json_obj['type'] == 'CompoundValue':
|
|
for i, (key, value) in enumerate(json_obj['scope']):
|
|
if key in named_values:
|
|
raise ee_exception.EEException(
|
|
'Duplicate scope key "%s" in scope #%d.' % (key, i))
|
|
named_values[key] = _decodeValue(value, named_values)
|
|
json_obj = json_obj['value']
|
|
|
|
# Decode the final value.
|
|
return _decodeValue(json_obj, named_values)
|
|
|
|
|
|
def _decodeValue(json_obj, named_values):
|
|
"""Decodes an object previously encoded using the EE API v2 (DAG) format.
|
|
|
|
This uses a provided scope for ValueRef lookup and does not not allow the
|
|
input to be a CompoundValue.
|
|
|
|
Args:
|
|
json_obj: The serialied object to decode.
|
|
named_values: The objects that can be referenced by ValueRefs.
|
|
|
|
Returns:
|
|
The decoded object.
|
|
"""
|
|
|
|
# Check for primitive values.
|
|
if (json_obj is None or
|
|
isinstance(json_obj, (bool, numbers.Number, six.string_types))):
|
|
return json_obj
|
|
|
|
# Check for array values.
|
|
if isinstance(json_obj, (list, tuple)):
|
|
return [_decodeValue(element, named_values) for element in json_obj]
|
|
|
|
# Ensure that we've got a proper object at this point.
|
|
if not isinstance(json_obj, dict):
|
|
raise ee_exception.EEException('Cannot decode object: ' + json_obj)
|
|
|
|
# Check for explicitly typed values.
|
|
type_name = json_obj['type']
|
|
if type_name == 'ValueRef':
|
|
if json_obj['value'] in named_values:
|
|
return named_values[json_obj['value']]
|
|
else:
|
|
raise ee_exception.EEException('Unknown ValueRef: ' + json_obj)
|
|
elif type_name == 'ArgumentRef':
|
|
var_name = json_obj['value']
|
|
if not isinstance(var_name, six.string_types):
|
|
raise ee_exception.EEException('Invalid variable name: ' + var_name)
|
|
return customfunction.CustomFunction.variable(None, var_name) # pylint: disable=protected-access
|
|
elif type_name == 'Date':
|
|
microseconds = json_obj['value']
|
|
if not isinstance(microseconds, numbers.Number):
|
|
raise ee_exception.EEException('Invalid date value: ' + microseconds)
|
|
return ee_date.Date(microseconds / 1e3)
|
|
elif type_name == 'Bytes':
|
|
result = encodable.Encodable()
|
|
result.encode = lambda encoder: json_obj
|
|
node = {'bytesValue': json_obj['value']}
|
|
result.encode_cloud_value = lambda encoder: node
|
|
return result
|
|
elif type_name == 'Invocation':
|
|
if 'functionName' in json_obj:
|
|
func = apifunction.ApiFunction.lookup(json_obj['functionName'])
|
|
else:
|
|
func = _decodeValue(json_obj['function'], named_values)
|
|
args = dict((key, _decodeValue(value, named_values))
|
|
for (key, value) in json_obj['arguments'].items())
|
|
return _invocation(func, args)
|
|
elif type_name == 'Dictionary':
|
|
return dict((key, _decodeValue(value, named_values))
|
|
for (key, value) in json_obj['value'].items())
|
|
elif type_name == 'Function':
|
|
body = _decodeValue(json_obj['body'], named_values)
|
|
signature = {
|
|
'name': '',
|
|
'args': [{'name': arg_name, 'type': 'Object', 'optional': False}
|
|
for arg_name in json_obj['argumentNames']],
|
|
'returns': 'Object'
|
|
}
|
|
return customfunction.CustomFunction(signature, lambda *args: body)
|
|
elif type_name in ('Point', 'MultiPoint', 'LineString', 'MultiLineString',
|
|
'Polygon', 'MultiPolygon', 'LinearRing',
|
|
'GeometryCollection'):
|
|
return geometry.Geometry(json_obj)
|
|
elif type_name == 'CompoundValue':
|
|
raise ee_exception.EEException('Nested CompoundValues are disallowed.')
|
|
else:
|
|
raise ee_exception.EEException('Unknown encoded object type: ' + type_name)
|
|
|
|
|
|
def _invocation(func, args):
|
|
"""Creates an EE object representing the application of `func` to `args`."""
|
|
if isinstance(func, function.Function):
|
|
return func.apply(args)
|
|
elif isinstance(func, computedobject.ComputedObject):
|
|
# We have to allow ComputedObjects for cases where invocations
|
|
# return a function, e.g. Image.parseExpression().
|
|
return computedobject.ComputedObject(func, args)
|
|
raise ee_exception.EEException('Invalid function value: %s' % func)
|
|
|
|
|
|
def fromCloudApiJSON(json_obj):
|
|
"""Deserializes an object from the JSON string used in Cloud API calls.
|
|
|
|
Args:
|
|
json_obj: The JSON representation of the input.
|
|
|
|
Returns:
|
|
The deserialized object.
|
|
"""
|
|
return decodeCloudApi(json.loads(json_obj))
|
|
|
|
|
|
def decodeCloudApi(json_obj):
|
|
"""Decodes an object previously encoded using the EE Cloud API format.
|
|
|
|
Args:
|
|
json_obj: The serialized object to decode.
|
|
|
|
Returns:
|
|
The decoded object.
|
|
"""
|
|
|
|
decoded = {}
|
|
def lookup(reference, kind):
|
|
if reference not in decoded:
|
|
if reference not in json_obj['values']:
|
|
raise ee_exception.EEException('Cannot find %s %s' % (reference, kind))
|
|
decoded[reference] = decode_node(json_obj['values'][reference])
|
|
return decoded[reference]
|
|
|
|
def decode_node(node):
|
|
if 'constantValue' in node:
|
|
return node['constantValue']
|
|
elif 'arrayValue' in node:
|
|
return [decode_node(x) for x in node['arrayValue']['values']]
|
|
elif 'dictionaryValue' in node:
|
|
return {key: decode_node(x)
|
|
for key, x in six.iteritems(node['dictionaryValue']['values'])}
|
|
elif 'argumentReference' in node:
|
|
return customfunction.CustomFunction.variable(
|
|
None, node['argumentReference']) # pylint: disable=protected-access
|
|
elif 'functionDefinitionValue' in node:
|
|
return decode_function_definition(node['functionDefinitionValue'])
|
|
elif 'functionInvocationValue' in node:
|
|
return decode_function_invocation(node['functionInvocationValue'])
|
|
elif 'bytesValue' in node:
|
|
return _decodeValue({'type': 'Bytes', 'value': node['bytesValue']}, {})
|
|
elif 'integerValue' in node:
|
|
return int(node['integerValue'])
|
|
elif 'valueReference' in node:
|
|
return lookup(node['valueReference'], 'reference')
|
|
return None
|
|
|
|
def decode_function_definition(defined):
|
|
body = lookup(defined['body'], 'function body')
|
|
signature_args = [{'name': name, 'type': 'Object', 'optional': False}
|
|
for name in defined['argumentNames']]
|
|
signature = {'args': signature_args, 'name': '', 'returns': 'Object'}
|
|
return customfunction.CustomFunction(signature, lambda *args: body)
|
|
|
|
def decode_function_invocation(invoked):
|
|
if 'functionReference' in invoked:
|
|
func = lookup(invoked['functionReference'], 'function')
|
|
else:
|
|
func = apifunction.ApiFunction.lookup(invoked['functionName'])
|
|
args = {
|
|
key: decode_node(x) for key, x in six.iteritems(invoked['arguments'])
|
|
}
|
|
return _invocation(func, args)
|
|
|
|
return lookup(json_obj['result'], 'result value')
|