"""Representation of an Earth Engine FeatureCollection.""" # Using lowercase function naming to match the JavaScript names. # pylint: disable=g-bad-name from __future__ import annotations from typing import Any, Dict, List, Optional, Tuple, Type, Union from ee import _utils from ee import apifunction from ee import classifier from ee import clusterer from ee import collection from ee import computedobject from ee import data from ee import deprecation from ee import ee_exception from ee import ee_list from ee import ee_number from ee import ee_string from ee import ee_types from ee import errormargin from ee import feature from ee import geometry from ee import image from ee import reducer _ClassifierType = Union['classifier.Classifier', computedobject.ComputedObject] _ClustererType = Union['clusterer.Clusterer', computedobject.ComputedObject] _ErrorMarginType = Union[ float, 'ee_number.Number', errormargin.ErrorMargin, computedobject.ComputedObject, ] _IntegerType = Union[int, 'ee_number.Number', computedobject.ComputedObject] _ListType = Union[List[Any], Tuple[Any, Any], computedobject.ComputedObject] _NumberType = Union[float, 'ee_number.Number', computedobject.ComputedObject] _ReducerType = Union[reducer.Reducer, computedobject.ComputedObject] _StringType = Union[str, 'ee_string.String', computedobject.ComputedObject] class FeatureCollection(collection.Collection): """A representation of a FeatureCollection.""" _initialized = False # Tell pytype to not complain about dynamic attributes. _HAS_DYNAMIC_ATTRIBUTES = True @_utils.accept_opt_prefix('opt_column') @deprecation.WarnForDeprecatedAsset('args') def __init__( self, args: Optional[ Union[ Dict[str, Any], List[Any], str, feature.Feature, geometry.Geometry, computedobject.ComputedObject, ] ], column: Optional[Any] = None, ): """Constructs a collection features. Args: args: constructor argument. One of: 1) A string - assumed to be the name of a collection. 2) A geometry. 3) A feature. 4) An array of features. 5) A GeoJSON FeatureCollection. 6) A computed object - reinterpreted as a collection. column: The name of the geometry column to use. Only useful with the string constructor. Raises: EEException: if passed something other than the above. """ self.initialize() # Wrap geometries with features. if isinstance(args, geometry.Geometry): args = feature.Feature(args) # Wrap single features in an array. if isinstance(args, feature.Feature): args = [args] if ee_types.isString(args): # An ID. actual_args = {'tableId': args} if column: actual_args['geometryColumn'] = column super().__init__( apifunction.ApiFunction.lookup('Collection.loadTable'), actual_args) elif isinstance(args, (list, tuple)): # A list of features. super().__init__( apifunction.ApiFunction.lookup('Collection'), { 'features': [feature.Feature(i) for i in args] }) elif isinstance(args, ee_list.List): # A computed list of features. super().__init__( apifunction.ApiFunction.lookup('Collection'), { 'features': args }) elif isinstance(args, dict) and args.get('type') == self.name(): # A GeoJSON FeatureCollection super().__init__( apifunction.ApiFunction.lookup('Collection'), {'features': [feature.Feature(i) for i in args.get('features', [])]}) elif isinstance(args, computedobject.ComputedObject): # A custom object to reinterpret as a FeatureCollection. super().__init__(args.func, args.args, args.varName) else: raise ee_exception.EEException( 'Unrecognized argument type to convert to a FeatureCollection: %s' % args) @classmethod def initialize(cls) -> None: """Imports API functions to this class.""" if not cls._initialized: super().initialize() apifunction.ApiFunction.importApi(cls, cls.name(), cls.name()) cls._initialized = True @classmethod def reset(cls) -> None: """Removes imported API functions from this class.""" apifunction.ApiFunction.clearApi(cls) cls._initialized = False def getMapId( self, vis_params: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Fetch and return a map id and token, suitable for use in a Map overlay. Args: vis_params: The visualization parameters. Currently only one parameter, 'color', containing a hex RGB color string is allowed. Returns: A map ID dictionary as described in ee.data.getMapId, including an additional 'image' field containing Collection.draw image wrapping a FeatureCollection containing this feature. """ painted = apifunction.ApiFunction.apply_('Collection.draw', { 'collection': self, 'color': (vis_params or {}).get('color', '000000') }) return painted.getMapId({}) def getDownloadURL( self, filetype: Optional[str] = None, selectors: Optional[Any] = None, filename: Optional[str] = None, ) -> str: """Gets a download URL. When the URL is accessed, the FeatureCollection is downloaded in one of several formats. Args: filetype: The format of download, one of: "csv", "json", "geojson", "kml", "kmz" ("json" outputs GeoJSON). If unspecified, defaults to "csv". selectors: Feature property names used to select the attributes to be downloaded. If unspecified, all properties are included. filename: Name of the file to be downloaded; extension is appended by default. If unspecified, defaults to "table". Returns: A URL to download this FeatureCollection. """ request = {} request['table'] = self if filetype is not None: request['format'] = filetype.upper() if filename is not None: request['filename'] = filename if selectors is not None: if isinstance(selectors, (list, tuple)): selectors = ','.join(selectors) request['selectors'] = selectors return data.makeTableDownloadUrl(data.getTableDownloadId(request)) # Deprecated spelling to match the JS library. getDownloadUrl = deprecation.Deprecated('Use getDownloadURL().')( getDownloadURL) # TODO(user): How to handle type annotations for # feature_collection.select('a', 'b', 'c')? # pylint: disable-next=keyword-arg-before-vararg def select( self, propertySelectors: Any, newProperties: Optional[Any] = None, retainGeometry: Union[bool, str] = True, *args, ) -> FeatureCollection: """Select properties from each feature in a collection. Args: propertySelectors: An array of names or regexes specifying the properties to select. newProperties: An array of strings specifying the new names for the selected properties. If supplied, the length must match the number of properties selected. retainGeometry: A boolean. When false, the result will have no geometry. *args: Selector elements as varargs. Returns: The feature collection with selected properties. """ if len(args) or ee_types.isString(propertySelectors): args = list(args) if not isinstance(retainGeometry, bool): args.insert(0, retainGeometry) if newProperties is not None: args.insert(0, newProperties) args.insert(0, propertySelectors) return self.map(lambda feat: feat.select(args, None, True)) else: return self.map( # pylint: disable-next=g-long-lambda lambda feat: feat.select( propertySelectors, newProperties, retainGeometry ) ) @staticmethod def name() -> str: return 'FeatureCollection' @staticmethod def elementType() -> Type[feature.Feature]: return feature.Feature def classify( self, classifier: _ClassifierType, outputName: Optional[_StringType] = None, # pylint: disable=invalid-name ) -> FeatureCollection: """Returns the result of classifying each feature in a collection. Args: classifier: The classifier to use. outputName: The name of the output property to be added. This argument is ignored if the classifier has more than one output. Returns: An ee.FeatureCollection. """ return apifunction.ApiFunction.call_( self.name() + '.classify', self, classifier, outputName ) def cluster( self, clusterer: _ClustererType, outputName: Optional[_StringType] = None, # pylint: disable=invalid-name ) -> FeatureCollection: """Returns the results of clustering each feature in a collection. Clusters each feature in a collection, adding a new column to each feature containing the cluster number to which it has been assigned. Args: clusterer: The clusterer to use. outputName: The name of the output property to be added. """ return apifunction.ApiFunction.call_( self.name() + '.cluster', self, clusterer, outputName ) def inverseDistance( self, range: _NumberType, propertyName: _StringType, # pylint: disable=invalid-name mean: _NumberType, stdDev: _NumberType, # pylint: disable=invalid-name gamma: Optional[_NumberType] = None, reducer: Optional[_ReducerType] = None, ) -> image.Image: """Returns an inverse-distance weighted estimate of the value at each pixel. Args: range: Size of the interpolation window (in meters). propertyName: Name of the numeric property to be estimated. mean: Global expected mean. stdDev: Global standard deviation. gamma: Determines how quickly the estimates tend towards the global mean. reducer: Reducer used to collapse the 'propertyName' value of overlapping points into a single value. """ return apifunction.ApiFunction.call_( self.name() + '.inverseDistance', self, range, propertyName, mean, stdDev, gamma, reducer, ) def kriging( self, propertyName: _StringType, # pylint: disable=invalid-name shape: _StringType, range: _NumberType, sill: _NumberType, nugget: _NumberType, maxDistance: Optional[_NumberType] = None, # pylint: disable=invalid-name reducer: Optional[_ReducerType] = None, ) -> image.Image: """Returns the results of sampling a Kriging estimator at each pixel. Args: propertyName: Property to be estimated (must be numeric). shape: Semivariogram shape (one of {exponential, gaussian, spherical}). range: Semivariogram range, in meters. sill: Semivariogram sill. nugget: Semivariogram nugget. maxDistance: Radius which determines which features are included in each pixel's computation, in meters. Defaults to the semivariogram's range. reducer: Reducer used to collapse the 'propertyName' value of overlapping points into a single value. """ return apifunction.ApiFunction.call_( self.name() + '.kriging', self, propertyName, shape, range, sill, nugget, maxDistance, reducer, ) def makeArray( self, properties: _ListType, name: Optional[_StringType] = None ) -> FeatureCollection: """Returns a collection with a 1-D Array property for each feature. Add a 1-D Array to each feature in a collection by combining a list of properties for each feature into a 1-D Array. All of the properties must be numeric values. If a feature doesn't contain all of the named properties, or any of them aren't numeric, the feature will be dropped from the resulting collection. Args: properties: The properties to select. name: The name of the new array property. """ return apifunction.ApiFunction.call_( self.name() + '.makeArray', self, properties, name ) @staticmethod def randomPoints( region: geometry.Geometry, points: Optional[_IntegerType] = None, seed: Optional[_IntegerType] = None, # pylint: disable-next=invalid-name maxError: Optional[_ErrorMarginType] = None, ) -> FeatureCollection: """Returns a collection of random points. Generates points that are uniformly random in the given geometry. If the geometry is two-dimensional (polygon or multi-polygon) then the returned points are uniformly distributed on the given region of the sphere. If the geometry is one-dimensional (linestrings), the returned points are interpolated uniformly along the geometry's edges. If the geometry has dimension zero (points), the returned points are sampled uniformly from the input points. If a multi-geometry of mixed dimension is given, points are sampled from the component geometries with the highest dimension. Args: region: The region to generate points for. points: The number of points to generate. seed: A seed for the random number generator. maxError: The maximum amount of error tolerated when performing any necessary reprojection. """ return apifunction.ApiFunction.call_( 'FeatureCollection.randomPoints', region, points, seed, maxError )