mirror of
https://github.com/google/earthengine-api.git
synced 2025-12-08 19:26:12 +00:00
339 lines
10 KiB
Python
339 lines
10 KiB
Python
"""Collection filters.
|
|
|
|
Example usage:
|
|
Filter('time', low, high)
|
|
.bounds(ring)
|
|
.eq('time', value)
|
|
.lt('time', value)
|
|
"""
|
|
|
|
|
|
|
|
# Using lowercase function naming to match the JavaScript names.
|
|
# pylint: disable=g-bad-name
|
|
|
|
# Our custom instance/static decorator is not recognized by lint.
|
|
# pylint: disable=no-self-argument, no-method-argument, g-doc-args
|
|
|
|
import functools
|
|
|
|
import apifunction
|
|
import computedobject
|
|
import ee_exception
|
|
|
|
|
|
class _FilterAutoCreator(object):
|
|
"""A decorator to make Filter methods both static and instance.
|
|
|
|
If the decorated method is called as an instance method, its result is passed
|
|
through _append().
|
|
"""
|
|
|
|
def __init__(self, func):
|
|
self.func = func
|
|
|
|
def __get__(self, filter_instance, cls=None):
|
|
if filter_instance is None:
|
|
return self.func
|
|
|
|
@functools.wraps(self.func)
|
|
def PassThroughAppend(*args, **kwargs):
|
|
return filter_instance._append( # pylint: disable=protected-access
|
|
self.func(*args, **kwargs))
|
|
|
|
return PassThroughAppend
|
|
|
|
|
|
# A map from the deprecated old-style comparison operator names to API
|
|
# function names, implicitly prefixed with "Filter.". Negative operators
|
|
# (those starting with "not_") are not included.
|
|
_FUNCTION_NAMES = {
|
|
'equals': 'equals',
|
|
'less_than': 'lessThan',
|
|
'greater_than': 'greaterThan',
|
|
'contains': 'stringContains',
|
|
'starts_with': 'stringStartsWith',
|
|
'ends_with': 'stringEndsWith',
|
|
}
|
|
|
|
|
|
class Filter(computedobject.ComputedObject):
|
|
"""An object to represent collection filters."""
|
|
|
|
_initialized = False
|
|
|
|
def __init__(self, opt_filter=None):
|
|
"""Construct a filter.
|
|
|
|
This constuctor accepts the following args:
|
|
1) Another filter.
|
|
2) An array of filters (which are implicitly ANDed together).
|
|
3) A ComputedObject returning a filter. Users shouldn't be making these;
|
|
they're produced by the generator functions below.
|
|
|
|
Args:
|
|
opt_filter: Optional filter to add.
|
|
"""
|
|
self.initialize()
|
|
|
|
if isinstance(opt_filter, (list, tuple)):
|
|
if not opt_filter:
|
|
raise ee_exception.EEException('Empty list specified for ee.Filter().')
|
|
elif len(opt_filter) == 1:
|
|
opt_filter = opt_filter[0]
|
|
else:
|
|
self._filter = tuple(opt_filter)
|
|
super(Filter, self).__init__(
|
|
apifunction.ApiFunction.lookup('Filter.and'),
|
|
{'filters': self._filter})
|
|
return
|
|
|
|
if isinstance(opt_filter, computedobject.ComputedObject):
|
|
super(Filter, self).__init__(
|
|
opt_filter.func, opt_filter.args, opt_filter.varName)
|
|
self._filter = (opt_filter,)
|
|
elif opt_filter is None:
|
|
# A silly call with no arguments left for backward-compatibility.
|
|
# Encoding such a filter is expected to fail, but it can be composed
|
|
# by calling the various methods that end up in _append().
|
|
super(Filter, self).__init__(None, None)
|
|
self._filter = ()
|
|
else:
|
|
raise ee_exception.EEException(
|
|
'Invalid argument specified for ee.Filter(): %s' % opt_filter)
|
|
|
|
@classmethod
|
|
def initialize(cls):
|
|
"""Imports API functions to this class."""
|
|
if not cls._initialized:
|
|
apifunction.ApiFunction.importApi(cls, 'Filter', 'Filter')
|
|
cls._initialized = True
|
|
|
|
@classmethod
|
|
def reset(cls):
|
|
"""Removes imported API functions from this class."""
|
|
apifunction.ApiFunction.clearApi(cls)
|
|
cls._initialized = False
|
|
|
|
def predicateCount(self):
|
|
"""Return the number of predicates that have been added to this filter.
|
|
|
|
Returns:
|
|
The number of predicates that have been added to this filter.
|
|
This does not count nested predicates.
|
|
"""
|
|
return len(self._filter)
|
|
|
|
def _append(self, new_filter):
|
|
"""Append a predicate to this filter.
|
|
|
|
These are implicitly ANDed.
|
|
|
|
Args:
|
|
new_filter: The filter to append to this one. Possible types are:
|
|
1) another fully constructed Filter,
|
|
2) a JSON representation of a filter,
|
|
3) an array of 1 or 2.
|
|
|
|
Returns:
|
|
A new filter that is the combination of both.
|
|
"""
|
|
if new_filter is not None:
|
|
prev = list(self._filter)
|
|
if isinstance(new_filter, Filter):
|
|
prev.extend(new_filter._filter) # pylint: disable=protected-access
|
|
elif isinstance(new_filter, list):
|
|
prev.extend(new_filter)
|
|
else:
|
|
prev.append(new_filter)
|
|
return Filter(prev)
|
|
|
|
def Not(self):
|
|
"""Returns the opposite of this filter.
|
|
|
|
Returns:
|
|
The negated filter, which will match iff this filter doesn't.
|
|
"""
|
|
return apifunction.ApiFunction.call_('Filter.not', self)
|
|
|
|
@_FilterAutoCreator
|
|
def metadata_(name, operator, value):
|
|
"""Filter on metadata. This is deprecated.
|
|
|
|
Args:
|
|
name: The property name to filter on.
|
|
operator: The type of comparison. One of:
|
|
"equals", "less_than", "greater_than", "contains", "begins_with",
|
|
"ends_with", or any of these prefixed with "not_".
|
|
value: The value to compare against.
|
|
|
|
Returns:
|
|
The new filter.
|
|
"""
|
|
operator = operator.lower()
|
|
|
|
# Check for negated filters.
|
|
negated = False
|
|
if operator.startswith('not_'):
|
|
negated = True
|
|
operator = operator[4:]
|
|
|
|
# Convert the operator to a function.
|
|
if operator not in _FUNCTION_NAMES:
|
|
raise ee_exception.EEException(
|
|
'Unknown filtering operator: %s' % operator)
|
|
func_name = 'Filter.' + _FUNCTION_NAMES[operator]
|
|
new_filter = apifunction.ApiFunction.call_(func_name, name, value)
|
|
|
|
return new_filter.Not() if negated else new_filter
|
|
|
|
@_FilterAutoCreator
|
|
def eq(name, value):
|
|
"""Filter to metadata equal to the given value."""
|
|
return apifunction.ApiFunction.call_('Filter.equals', name, value)
|
|
|
|
@_FilterAutoCreator
|
|
def neq(name, value):
|
|
"""Filter to metadata not equal to the given value."""
|
|
return Filter.eq(name, value).Not()
|
|
|
|
@_FilterAutoCreator
|
|
def lt(name, value):
|
|
"""Filter to metadata less than the given value."""
|
|
return apifunction.ApiFunction.call_('Filter.lessThan', name, value)
|
|
|
|
@_FilterAutoCreator
|
|
def gte(name, value):
|
|
"""Filter on metadata greater than or equal to the given value."""
|
|
return Filter.lt(name, value).Not()
|
|
|
|
@_FilterAutoCreator
|
|
def gt(name, value):
|
|
"""Filter on metadata greater than the given value."""
|
|
return apifunction.ApiFunction.call_('Filter.greaterThan', name, value)
|
|
|
|
@_FilterAutoCreator
|
|
def lte(name, value):
|
|
"""Filter on metadata less than or equal to the given value."""
|
|
return Filter.gt(name, value).Not()
|
|
|
|
@_FilterAutoCreator
|
|
def contains(name, value):
|
|
"""Filter on metadata containing the given string."""
|
|
return apifunction.ApiFunction.call_('Filter.stringContains', name, value)
|
|
|
|
@_FilterAutoCreator
|
|
def not_contains(name, value):
|
|
"""Filter on metadata not containing the given string."""
|
|
return Filter.contains(name, value).Not()
|
|
|
|
@_FilterAutoCreator
|
|
def starts_with(name, value):
|
|
"""Filter on metadata begining with the given string."""
|
|
return apifunction.ApiFunction.call_('Filter.stringStartsWith', name, value)
|
|
|
|
@_FilterAutoCreator
|
|
def not_starts_with(name, value):
|
|
"""Filter on metadata not begining with the given string."""
|
|
return Filter.starts_with(name, value).Not()
|
|
|
|
@_FilterAutoCreator
|
|
def ends_with(name, value):
|
|
"""Filter on metadata ending with the given string."""
|
|
return apifunction.ApiFunction.call_('Filter.stringEndsWith', name, value)
|
|
|
|
@_FilterAutoCreator
|
|
def not_ends_with(name, value):
|
|
"""Filter on metadata not ending with the given string."""
|
|
return Filter.ends_with(name, value).Not()
|
|
|
|
@_FilterAutoCreator
|
|
def And(*args):
|
|
"""Combine two or more filters using boolean AND."""
|
|
if len(args) == 1 and isinstance(args[0], (list, tuple)):
|
|
args = args[0]
|
|
return apifunction.ApiFunction.call_('Filter.and', args)
|
|
|
|
@staticmethod
|
|
def Or(*args):
|
|
"""Combine two or more filters using boolean OR."""
|
|
if len(args) == 1 and isinstance(args[0], (list, tuple)):
|
|
args = args[0]
|
|
return apifunction.ApiFunction.call_('Filter.or', args)
|
|
|
|
@_FilterAutoCreator
|
|
def date(start, opt_end=None):
|
|
"""Filter images by date.
|
|
|
|
Args:
|
|
start: The start date as a UTC datetime or ms since Unix epoch.
|
|
opt_end: The end date as a UTC datetime or ms since Unix epoch.
|
|
|
|
Returns:
|
|
The modified filter.
|
|
"""
|
|
date_range = apifunction.ApiFunction.call_('DateRange', start, opt_end)
|
|
return apifunction.ApiFunction.apply_('Filter.dateRangeContains', {
|
|
'leftValue': date_range,
|
|
'rightField': 'system:time_start'
|
|
})
|
|
|
|
@_FilterAutoCreator
|
|
def inList(opt_leftField=None,
|
|
opt_rightValue=None,
|
|
opt_rightField=None,
|
|
opt_leftValue=None):
|
|
"""Filter on metadata contained in a list.
|
|
|
|
Args:
|
|
opt_leftField: A selector for the left operand.
|
|
Should not be specified if leftValue is specified.
|
|
opt_rightValue: The value of the right operand.
|
|
Should not be specified if rightField is specified.
|
|
opt_rightField: A selector for the right operand.
|
|
Should not be specified if rightValue is specified.
|
|
opt_leftValue: The value of the left operand.
|
|
Should not be specified if leftField is specified.
|
|
|
|
Returns:
|
|
The constructed filter.
|
|
"""
|
|
# Implement this in terms of listContains, with the arguments switched.
|
|
# In listContains the list is on the left side, while in inList it's on
|
|
# the right.
|
|
return apifunction.ApiFunction.apply_('Filter.listContains', {
|
|
'leftField': opt_rightField,
|
|
'rightValue': opt_leftValue,
|
|
'rightField': opt_leftField,
|
|
'leftValue': opt_rightValue
|
|
})
|
|
|
|
@_FilterAutoCreator
|
|
def geometry(geometry, opt_errorMargin=None):
|
|
"""Filter on bounds.
|
|
|
|
Items in the collection with a footprint that fails to intersect
|
|
the bounds will be excluded when the collection is evaluated.
|
|
|
|
Args:
|
|
geometry: The geometry to filter to either as a GeoJSON geometry,
|
|
or a FeatureCollection, from which a geometry will be extracted.
|
|
opt_errorMargin: An optional error margin. If a number, interpreted as
|
|
sphere surface meters.
|
|
|
|
Returns:
|
|
The modified filter.
|
|
"""
|
|
# Invoke geometry promotion then manually promote to a Feature.
|
|
args = {
|
|
'leftField': '.all',
|
|
'rightValue': apifunction.ApiFunction.call_('Feature', geometry)
|
|
}
|
|
if opt_errorMargin is not None:
|
|
args['maxError'] = opt_errorMargin
|
|
return apifunction.ApiFunction.apply_('Filter.intersects', args)
|
|
|
|
@staticmethod
|
|
def name():
|
|
return 'Filter'
|