Michael DeWitt 6c251b3aae v0.1.192
2019-08-22 14:46:22 -07:00

290 lines
8.9 KiB
Python

#!/usr/bin/env 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
from . import apifunction
from . import computedobject
from . import ee_exception
# 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 constructor 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)
@staticmethod
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.
Deprecated. Use ee.Filter.eq(), ee.Filter.gte(), etc.'
"""
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
@staticmethod
def eq(name, value):
"""Filter to metadata equal to the given value."""
return apifunction.ApiFunction.call_('Filter.equals', name, value)
@staticmethod
def neq(name, value):
"""Filter to metadata not equal to the given value."""
return Filter.eq(name, value).Not()
@staticmethod
def lt(name, value):
"""Filter to metadata less than the given value."""
return apifunction.ApiFunction.call_('Filter.lessThan', name, value)
@staticmethod
def gte(name, value):
"""Filter on metadata greater than or equal to the given value."""
return Filter.lt(name, value).Not()
@staticmethod
def gt(name, value):
"""Filter on metadata greater than the given value."""
return apifunction.ApiFunction.call_('Filter.greaterThan', name, value)
@staticmethod
def lte(name, value):
"""Filter on metadata less than or equal to the given value."""
return Filter.gt(name, value).Not()
@staticmethod
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)
@staticmethod
def date(start, opt_end=None):
"""Filter images by date.
The start and end may be a Date, numbers (interpreted as milliseconds since
1970-01-01T00:00:00Z), or strings (such as '1996-01-01T08:00'). Based on
'system:time_start'.
Args:
start: The inclusive start date.
opt_end: The optional exclusive end date, If not specified, a
1-millisecond range starting at 'start' is created.
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'
})
@staticmethod
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
})
@staticmethod
def geometry(geometry, opt_errorMargin=None):
"""Filter on intersection with geometry.
Items in the collection with a footprint that fails to intersect
the given geometry will be excluded.
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 constructed 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'