Skip to content

Catalog Module🔗

The earthdaily.earthone.catalog module provides access to the EarthOne Catalog API for discovering, searching, and managing raster data products and images.

CatalogClient🔗

CatalogClient 🔗

Bases: JsonApiService, DefaultClientMixin

The CatalogClient handles the HTTP communication with the EarthOne catalog. It is almost sufficient to use the default client that is automatically retrieved using get_default_client. However, if you want to adjust e.g. the retries, you can create your own.

Parameters🔗

url : str, optional The URL to use when connecting to the EarthOne catalog. Only change this if you are being asked to use a non-default EarthOne catalog. If not set, then earthdaily.earthone.config.get_settings().CATALOG_V2_URL will be used. auth : Auth, optional The authentication object used when connecting to the EarthOne catalog. This is typically the default :class:~earthdaily.earthone.auth.Auth object that uses the cached authentication token retrieved with the shell command "$ earthone auth login". retries : int, optional The number of retries when there is a problem with the connection. Set this to zero to disable retries. The default is 3 retries.

Source code in earthdaily/earthone/core/catalog/catalog_client.py
class CatalogClient(JsonApiService, DefaultClientMixin):
    """
    The CatalogClient handles the HTTP communication with the EarthOne catalog.
    It is almost sufficient to use the default client that is automatically retrieved
    using `get_default_client`.  However, if you want to adjust e.g.  the retries, you
    can create your own.

    Parameters
    ----------
    url : str, optional
        The URL to use when connecting to the EarthOne catalog.  Only change
        this if you are being asked to use a non-default EarthOne catalog.  If
        not set, then ``earthdaily.earthone.config.get_settings().CATALOG_V2_URL`` will be used.
    auth : Auth, optional
        The authentication object used when connecting to the EarthOne catalog.
        This is typically the default :class:`~earthdaily.earthone.auth.Auth` object that uses
        the cached authentication
        token retrieved with the shell command "$ earthone auth login".
    retries : int, optional
        The number of retries when there is a problem with the connection.  Set this to
        zero to disable retries.  The default is 3 retries.
    """

    def __init__(self, url=None, auth=None, retries=None):
        if auth is None:
            auth = Auth.get_default_auth()

        if url is None:
            url = get_settings().catalog_v2_url

        super(CatalogClient, self).__init__(
            url, auth=auth, retries=retries, rewrite_errors=True
        )

Product🔗

Product 🔗

Bases: AuthCatalogObject

A raster product that connects band information to imagery.

Instantiating a product indicates that you want to create a new EarthOne catalog product. If you instead want to retrieve an existing catalog product use Product.get() <earthdaily.earthone.catalog.Product.get>, or if you're not sure use Product.get_or_create() <earthdaily.earthone.catalog.Product.get_or_create>. You can also use Product.search() <earthdaily.earthone.catalog.Product.search>. Also see the example for 🇵🇾meth:~earthdaily.earthone.catalog.Product.save.

Parameters🔗

client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set. kwargs : dict With the exception of readonly attributes (created, modified, resolution_min, and resolution_max) and with the exception of properties (ATTRIBUTES, is_modified, and state), any attribute listed below can also be used as a keyword argument. Also see ~Product.ATTRIBUTES.

Source code in earthdaily/earthone/core/catalog/product.py
class Product(AuthCatalogObject):
    """A raster product that connects band information to imagery.

    Instantiating a product indicates that you want to create a *new* EarthOne
    catalog product.  If you instead want to retrieve an existing catalog product use
    `Product.get() <earthdaily.earthone.catalog.Product.get>`, or if you're not sure
    use `Product.get_or_create() <earthdaily.earthone.catalog.Product.get_or_create>`.
    You can also use `Product.search() <earthdaily.earthone.catalog.Product.search>`.
    Also see the example for :py:meth:`~earthdaily.earthone.catalog.Product.save`.


    Parameters
    ----------
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne catalog.
        The :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.
    kwargs : dict
        With the exception of readonly attributes (`created`, `modified`,
        `resolution_min`, and `resolution_max`) and with the exception of properties
        (`ATTRIBUTES`, `is_modified`, and `state`), any attribute listed below can
        also be used as a keyword argument.  Also see
        `~Product.ATTRIBUTES`.
    """

    _doc_type = "product"
    _url = "/products"
    # _collection_type set below due to circular problems

    # Product Attributes
    name = TypedAttribute(
        str,
        doc="""str: The name of this product.

        This should not be confused with a band name or image name.  Unlike the band
        name and image name, this name is not unique and purely for display purposes
        and is used by :py:meth:`Search.find_text`.  It can contain a string with up
        to 2000 arbitrary characters.

        *Searchable, sortable*.
        """,
    )
    description = TypedAttribute(
        str,
        doc="""str, optional: A description with further details on this product.

        The description can be up to 80,000 characters and is used by
        :py:meth:`Search.find_text`.

        *Searchable*
        """,
    )
    is_core = BooleanAttribute(
        doc="""bool, optional: Whether this is a EarthOne catalog core product.

        A core product is a product that is fully supported by EarthOne.  By
        default this value is ``False`` and you must have a special permission
        (``internal:core:create``) to set it to ``True``.

        *Filterable, sortable*.
        """
    )
    revisit_period_minutes_min = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Minimum length of the time interval between observations.

        The minimum length of the time interval between observations of any given area
        in minutes.

        *Filterable, sortable*.
        """,
    )
    revisit_period_minutes_max = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Maximum length of the time interval between observations.

        The maximum length of the time interval between observations of any given area
        in minutes.

        *Filterable, sortable*.
        """,
    )
    start_datetime = Timestamp(
        doc="""str or datetime, optional: The beginning of the mission for this product.

        *Filterable, sortable*.
        """
    )
    end_datetime = Timestamp(
        doc="""str or datetime, optional: The end of the mission for this product.

        *Filterable, sortable*.
        """
    )
    resolution_min = Resolution(
        readonly=True,
        doc="""Resolution, readonly: Minimum resolution of the bands for this product.

        If applying a filter with a plain unitless number the value is assumed to be
        in meters.

        *Filterable, sortable*.
        """,
    )
    resolution_max = Resolution(
        readonly=True,
        doc="""Resolution, readonly: Maximum resolution of the bands for this product.

        If applying a filter with a plain unitless number the value is assumed to be
        in meters.

        *Filterable, sortable*.
        """,
    )
    default_display_bands = ListAttribute(
        TypedAttribute(str),
        doc="""list(str) or iterable: Which bands to use for RGBA display.

        This field defines the default bands that are used for display purposes.  There are
        four supported formats: ``["greyscale-or-class"]``, ``["greyscale-or-class", "alpha"]``,
        ``["red", "green", "blue"]``, and ``["red", "green", "blue", "alpha"]``.
        """,
    )
    image_index_name = TypedAttribute(
        str,
        doc="""str: The name of the image index for this product.

        This is an internal field, accessible to privileged users only.

        *Filterable, sortable*.
        """,
    )
    product_tier = TypedAttribute(
        str,
        doc="""str: Product tier for this product.

        This field can be set by privileged users only.

        *Filterable, sortable*.
        """,
    )

    def named_id(self, name):
        """Return the ~earthdaily.earthone.catalog.NamedCatalogObject.id` for the given named catalog object.

        Parameters
        ----------
        name : str
            The name of the catalog object within this product, see
            :py:attr:`~earthdaily.earthone.catalog.NamedCatalogObject.name`.

        Returns
        -------
        str
            The named catalog object id within this product.
        """
        return "{}:{}".format(self.id, name)

    @check_deleted
    def get_band(self, name, client=None, request_params=None, headers=None):
        """Retrieve the request band associated with this product by name.

        Parameters
        ----------
        name : str
            The name of the band to retrieve.
        client : CatalogClient, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.  The
            :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
            be used if not set.

        Returns
        -------
        Band or None
            A derived class of `Band` that represents the requested band object if
            found, ``None`` if not found.

        """
        from .band import Band

        return Band.get(
            self.named_id(name),
            client=client,
            request_params=request_params,
            headers=headers,
        )

    @check_deleted
    def get_image(self, name, client=None, request_params=None, headers=None):
        """Retrieve the request image associated with this product by name.

        Parameters
        ----------
        name : str
            The name of the image to retrieve.
        client : CatalogClient, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.  The
            :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
            be used if not set.

        Returns
        -------
        ~earthdaily.earthone.catalog.Image or None
            The requested image if found, or ``None`` if not found.

        """
        from .image import Image

        return Image.get(
            self.named_id(name),
            client=client,
            request_params=request_params,
            headers=headers,
        )

    @check_deleted
    def delete_related_objects(self):
        """Delete all related bands and images for this product.

        Starts an asynchronous operation that deletes all bands and images associated
        with this product. If the product has a large number of associated images, this
        operation could take several minutes, or even hours.

        Returns
        -------
        DeletionTaskStatus
            Returns :py:class:`DeletionTaskStatus` if deletion task was successfully
            started and ``None`` if there were no related objects to delete.


        Raises
        ------
        ConflictError
            If a deletion process is already in progress.
        DeletedObjectError
            If this product was deleted.
        ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
            :ref:`Spurious exception <network_exceptions>` that can occur during a
            network request.
        """
        r = self._client.session.post(
            "/products/{}/delete_related_objects".format(self.id),
            json={"data": {"type": "product_delete_task"}},
        )
        if r.status_code == 201:
            response = r.json()
            return DeletionTaskStatus(
                id=self.id, _client=self._client, **response["data"]["attributes"]
            )
        else:
            return None

    @check_deleted
    def get_delete_status(self):
        """Fetches the status of a deletion task.

        Fetches the status of a deletion task started using
        :py:meth:`delete_related_objects`.

        Returns
        -------
        DeletionTaskStatus

        Raises
        ------
        DeletedObjectError
            If this product was deleted.
        ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
            :ref:`Spurious exception <network_exceptions>` that can occur during a
            network request.
        """
        r = self._client.session.get(
            "/products/{}/delete_related_objects".format(self.id)
        )
        response = r.json()
        return DeletionTaskStatus(
            id=self.id, _client=self._client, **response["data"]["attributes"]
        )

    @check_deleted
    def bands(self, request_params=None, headers=None):
        """A search query for all bands for this product, sorted by default band
        ``sort_order``.

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.Search`
            A :py:class:`~earthdaily.earthone.catalog.Search` instance configured to
            find all bands for this product.

        Raises
        ------
        DeletedObjectError
            If this product was deleted.

        """
        from .band import Band

        return (
            Band.search(
                client=self._client, request_params=request_params, headers=headers
            )
            .filter(properties.product_id == self.id)
            .sort("sort_order")
        )

    @check_deleted
    def images(self, request_params=None, headers=None):
        """A search query for all images in this product.

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.Search`
            A :py:class:`~earthdaily.earthone.catalog.Search` instance configured to
            find all images in this product.

        Raises
        ------
        DeletedObjectError
            If this product was deleted.

        """
        from .image import Image

        return Image.search(
            client=self._client, request_params=request_params, headers=headers
        ).filter(properties.product_id == self.id)

    @check_deleted
    def image_uploads(self):
        """A search query for all uploads in this product created by this user.

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.Search`
            A :py:class:`~earthdaily.earthone.catalog.Search` instance configured to
            find all uploads in this product.

        Raises
        ------
        DeletedObjectError
            If this product was deleted.

        """
        from .image_upload import ImageUpload

        return ImageUpload.search(client=self._client).filter(
            properties.product_id == self.id
        )

    @classmethod
    def namespace_id(cls, id_, client=None):
        """Generate a fully namespaced id.

        Parameters
        ----------
        id_ : str
            The unprefixed part of the id that you want prefixed.
        client : CatalogClient, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.  The
            :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
            be used if not set.

        Returns
        -------
        str
            The fully namespaced id.

        Example
        -------
        >>> product_id = Product.namespace_id("my-product")
        """
        if client is None:
            client = CatalogClient.get_default_client()
        org = client.auth.payload.get("org")
        if org is None:
            org = client.auth.namespace  # defaults to the user namespace

        prefix = "{}:".format(org)
        if id_.startswith(prefix):
            return id_

        return "{}{}".format(prefix, id_)

named_id 🔗

named_id(name)

Return the ~earthdaily.earthone.catalog.NamedCatalogObject.id` for the given named catalog object.

Parameters🔗

name : str The name of the catalog object within this product, see 🇵🇾attr:~earthdaily.earthone.catalog.NamedCatalogObject.name.

Returns🔗

str The named catalog object id within this product.

Source code in earthdaily/earthone/core/catalog/product.py
def named_id(self, name):
    """Return the ~earthdaily.earthone.catalog.NamedCatalogObject.id` for the given named catalog object.

    Parameters
    ----------
    name : str
        The name of the catalog object within this product, see
        :py:attr:`~earthdaily.earthone.catalog.NamedCatalogObject.name`.

    Returns
    -------
    str
        The named catalog object id within this product.
    """
    return "{}:{}".format(self.id, name)

get_band 🔗

get_band(name, client=None, request_params=None, headers=None)

Retrieve the request band associated with this product by name.

Parameters🔗

name : str The name of the band to retrieve. client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set.

Returns🔗

Band or None A derived class of Band that represents the requested band object if found, None if not found.

Source code in earthdaily/earthone/core/catalog/product.py
@check_deleted
def get_band(self, name, client=None, request_params=None, headers=None):
    """Retrieve the request band associated with this product by name.

    Parameters
    ----------
    name : str
        The name of the band to retrieve.
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.  The
        :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.

    Returns
    -------
    Band or None
        A derived class of `Band` that represents the requested band object if
        found, ``None`` if not found.

    """
    from .band import Band

    return Band.get(
        self.named_id(name),
        client=client,
        request_params=request_params,
        headers=headers,
    )

get_image 🔗

get_image(name, client=None, request_params=None, headers=None)

Retrieve the request image associated with this product by name.

Parameters🔗

name : str The name of the image to retrieve. client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set.

Returns🔗

~earthdaily.earthone.catalog.Image or None The requested image if found, or None if not found.

Source code in earthdaily/earthone/core/catalog/product.py
@check_deleted
def get_image(self, name, client=None, request_params=None, headers=None):
    """Retrieve the request image associated with this product by name.

    Parameters
    ----------
    name : str
        The name of the image to retrieve.
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.  The
        :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.

    Returns
    -------
    ~earthdaily.earthone.catalog.Image or None
        The requested image if found, or ``None`` if not found.

    """
    from .image import Image

    return Image.get(
        self.named_id(name),
        client=client,
        request_params=request_params,
        headers=headers,
    )
delete_related_objects()

Delete all related bands and images for this product.

Starts an asynchronous operation that deletes all bands and images associated with this product. If the product has a large number of associated images, this operation could take several minutes, or even hours.

DeletionTaskStatus Returns 🇵🇾class:DeletionTaskStatus if deletion task was successfully started and None if there were no related objects to delete.

ConflictError If a deletion process is already in progress. DeletedObjectError If this product was deleted. ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError :ref:Spurious exception <network_exceptions> that can occur during a network request.

Source code in earthdaily/earthone/core/catalog/product.py
@check_deleted
def delete_related_objects(self):
    """Delete all related bands and images for this product.

    Starts an asynchronous operation that deletes all bands and images associated
    with this product. If the product has a large number of associated images, this
    operation could take several minutes, or even hours.

    Returns
    -------
    DeletionTaskStatus
        Returns :py:class:`DeletionTaskStatus` if deletion task was successfully
        started and ``None`` if there were no related objects to delete.


    Raises
    ------
    ConflictError
        If a deletion process is already in progress.
    DeletedObjectError
        If this product was deleted.
    ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
        :ref:`Spurious exception <network_exceptions>` that can occur during a
        network request.
    """
    r = self._client.session.post(
        "/products/{}/delete_related_objects".format(self.id),
        json={"data": {"type": "product_delete_task"}},
    )
    if r.status_code == 201:
        response = r.json()
        return DeletionTaskStatus(
            id=self.id, _client=self._client, **response["data"]["attributes"]
        )
    else:
        return None

get_delete_status 🔗

get_delete_status()

Fetches the status of a deletion task.

Fetches the status of a deletion task started using 🇵🇾meth:delete_related_objects.

Returns🔗

DeletionTaskStatus

Raises🔗

DeletedObjectError If this product was deleted. ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError :ref:Spurious exception <network_exceptions> that can occur during a network request.

Source code in earthdaily/earthone/core/catalog/product.py
@check_deleted
def get_delete_status(self):
    """Fetches the status of a deletion task.

    Fetches the status of a deletion task started using
    :py:meth:`delete_related_objects`.

    Returns
    -------
    DeletionTaskStatus

    Raises
    ------
    DeletedObjectError
        If this product was deleted.
    ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
        :ref:`Spurious exception <network_exceptions>` that can occur during a
        network request.
    """
    r = self._client.session.get(
        "/products/{}/delete_related_objects".format(self.id)
    )
    response = r.json()
    return DeletionTaskStatus(
        id=self.id, _client=self._client, **response["data"]["attributes"]
    )

bands 🔗

bands(request_params=None, headers=None)

A search query for all bands for this product, sorted by default band sort_order.

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.Search A 🇵🇾class:~earthdaily.earthone.catalog.Search instance configured to find all bands for this product.

Raises🔗

DeletedObjectError If this product was deleted.

Source code in earthdaily/earthone/core/catalog/product.py
@check_deleted
def bands(self, request_params=None, headers=None):
    """A search query for all bands for this product, sorted by default band
    ``sort_order``.

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.Search`
        A :py:class:`~earthdaily.earthone.catalog.Search` instance configured to
        find all bands for this product.

    Raises
    ------
    DeletedObjectError
        If this product was deleted.

    """
    from .band import Band

    return (
        Band.search(
            client=self._client, request_params=request_params, headers=headers
        )
        .filter(properties.product_id == self.id)
        .sort("sort_order")
    )

images 🔗

images(request_params=None, headers=None)

A search query for all images in this product.

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.Search A 🇵🇾class:~earthdaily.earthone.catalog.Search instance configured to find all images in this product.

Raises🔗

DeletedObjectError If this product was deleted.

Source code in earthdaily/earthone/core/catalog/product.py
@check_deleted
def images(self, request_params=None, headers=None):
    """A search query for all images in this product.

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.Search`
        A :py:class:`~earthdaily.earthone.catalog.Search` instance configured to
        find all images in this product.

    Raises
    ------
    DeletedObjectError
        If this product was deleted.

    """
    from .image import Image

    return Image.search(
        client=self._client, request_params=request_params, headers=headers
    ).filter(properties.product_id == self.id)

image_uploads 🔗

image_uploads()

A search query for all uploads in this product created by this user.

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.Search A 🇵🇾class:~earthdaily.earthone.catalog.Search instance configured to find all uploads in this product.

Raises🔗

DeletedObjectError If this product was deleted.

Source code in earthdaily/earthone/core/catalog/product.py
@check_deleted
def image_uploads(self):
    """A search query for all uploads in this product created by this user.

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.Search`
        A :py:class:`~earthdaily.earthone.catalog.Search` instance configured to
        find all uploads in this product.

    Raises
    ------
    DeletedObjectError
        If this product was deleted.

    """
    from .image_upload import ImageUpload

    return ImageUpload.search(client=self._client).filter(
        properties.product_id == self.id
    )

namespace_id classmethod 🔗

namespace_id(id_, client=None)

Generate a fully namespaced id.

Parameters🔗

id_ : str The unprefixed part of the id that you want prefixed. client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set.

Returns🔗

str The fully namespaced id.

Example🔗

product_id = Product.namespace_id("my-product")

Source code in earthdaily/earthone/core/catalog/product.py
@classmethod
def namespace_id(cls, id_, client=None):
    """Generate a fully namespaced id.

    Parameters
    ----------
    id_ : str
        The unprefixed part of the id that you want prefixed.
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.  The
        :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.

    Returns
    -------
    str
        The fully namespaced id.

    Example
    -------
    >>> product_id = Product.namespace_id("my-product")
    """
    if client is None:
        client = CatalogClient.get_default_client()
    org = client.auth.payload.get("org")
    if org is None:
        org = client.auth.namespace  # defaults to the user namespace

    prefix = "{}:".format(org)
    if id_.startswith(prefix):
        return id_

    return "{}{}".format(prefix, id_)

Image🔗

Image 🔗

Bases: NamedCatalogObject

An image with raster data.

Instantiating an image indicates that you want to create a new EarthOne catalog image. If you instead want to retrieve an existing catalog image use Image.get() <earthdaily.earthone.catalog.Image.get>, or if you're not sure use Image.get_or_create() <~earthdaily.earthone.catalog.Image.get_or_create>. You can also use Image.search() <earthdaily.earthone.catalog.Image.search>. Also see the example for 🇵🇾meth:~earthdaily.earthone.catalog.Image.save.

Parameters🔗

client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set. kwargs : dict With the exception of readonly attributes (created, modified) and with the exception of properties (ATTRIBUTES, is_modified, and state), any attribute listed below can also be used as a keyword argument. Also see ~Image.ATTRIBUTES.

Source code in earthdaily/earthone/core/catalog/image.py
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
class Image(NamedCatalogObject):
    """An image with raster data.

    Instantiating an image indicates that you want to create a *new* EarthOne
    catalog image.  If you instead want to retrieve an existing catalog image use
    `Image.get() <earthdaily.earthone.catalog.Image.get>`, or if you're not sure use
    `Image.get_or_create() <~earthdaily.earthone.catalog.Image.get_or_create>`.  You
    can also use `Image.search() <earthdaily.earthone.catalog.Image.search>`.  Also
    see the example for :py:meth:`~earthdaily.earthone.catalog.Image.save`.

    Parameters
    ----------
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne catalog.
        The :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.
    kwargs : dict
        With the exception of readonly attributes (`created`, `modified`) and with the
        exception of properties (`ATTRIBUTES`, `is_modified`, and `state`), any
        attribute listed below can also be used as a keyword argument.  Also see
        `~Image.ATTRIBUTES`.
    """

    _doc_type = "image"
    _url = "/images"
    _default_includes = ["product"]
    # _collection_type set below due to circular import problems
    _upload_service = ThirdPartyService()

    # Geo referencing
    geometry = GeometryAttribute(
        doc="""str or shapely.geometry.base.BaseGeometry: Geometry representing the image coverage.

        *Filterable*

        (use :py:meth:`ImageSearch.intersects
        <earthdaily.earthone.catalog.ImageSearch.intersects>` to search based on geometry)
        """
    )
    cs_code = TypedAttribute(
        str,
        doc="""str: The coordinate reference system used by the image as an EPSG or ESRI code.

        An example of a EPSG code is ``"EPSG:4326"``.  One of `cs_code` and `projection`
        is required.  If both are set and disagree, `cs_code` takes precedence.
        """,
    )
    projection = TypedAttribute(
        str,
        doc="""str: The spatial reference system used by the image.

        The projection can be specified as either a proj.4 string or a a WKT string.
        One of `cs_code` and `projection` is required.  If both are set and disagree,
        `cs_code` takes precedence.
        """,
    )
    geotrans = TupleAttribute(
        min_length=6,
        max_length=6,
        coerce=True,
        attribute_type=float,
        doc="""tuple of six float elements, optional if `~StorageState.REMOTE`: GDAL-style geotransform matrix.

        A GDAL-style `geotransform matrix
        <https://gdal.org/user/raster_data_model.html#affine-geotransform>`_ that
        transforms pixel coordinates into the spatial reference system defined by the
        `cs_code` or `projection` attributes.
        """,
    )
    x_pixels = TypedAttribute(
        int,
        doc="int, optional if `~StorageState.REMOTE`: X dimension of the image in pixels.",
    )
    y_pixels = TypedAttribute(
        int,
        doc="int, optional if `~StorageState.REMOTE`: Y dimension of the image in pixels.",
    )

    # Time dimensions
    acquired = Timestamp(
        doc="""str or datetime: Timestamp when the image was captured by its sensor or created.

        *Filterable, sortable*.
        """
    )
    acquired_end = Timestamp(
        doc="""str or datetime, optional: Timestamp when the image capture by its sensor was completed.

        *Filterable, sortable*.
        """
    )
    published = Timestamp(
        doc="""str or datetime, optional: Timestamp when the data provider published this image.

        *Filterable, sortable*.
        """
    )

    # Stored files
    storage_state = EnumAttribute(
        StorageState,
        doc="""str or StorageState: Storage state of the image.

        The state is `~StorageState.AVAILABLE` if the data is available and can be
        rastered, `~StorageState.REMOTE` if the data is not currently available.

        *Filterable, sortable*.
        """,
    )
    files = ListAttribute(
        File, doc="list(File): The list of files holding data for this image."
    )

    # Image properties
    area = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Surface area the image covers.

        *Filterable, sortable*.
        """,
    )
    azimuth_angle = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Sensor azimuth angle in degrees.

        *Filterable, sortable*.
        """,
    )
    bits_per_pixel = ListAttribute(
        TypedAttribute(float, coerce=True),
        doc="list(float), optional: Average bits of data per pixel per band.",
    )
    bright_fraction = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Fraction of the image that has reflectance greater than .4 in the blue band.

        *Filterable, sortable*.
        """,
    )
    brightness_temperature_k1_k2 = ListAttribute(
        ListAttribute(TypedAttribute(float, coerce=True)),
        doc="""list(list(float), optional: radiance to brightness temperature
        conversion coefficients.

        Outer list indexed by ``Band.vendor_order``, inner lists are ``[k1, k2]`` or
        empty if not applicable.
        """,
    )
    cloud_fraction = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Fraction of pixels which are obscured by clouds.

        *Filterable, sortable*.
        """,
    )
    alt_cloud_fraction = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Fraction of pixels which are obscured by clouds.

        This is as per an alternative algorithm.  See the product documentation in the
        `EarthOne catalog <catalog.earthone.earthdaily.com>`_ for more information.

        *Filterable, sortable*.
        """,
    )
    processing_pipeline_id = TypedAttribute(
        str,
        doc="""str, optional: Identifier for the pipeline that processed this image from raw data.

        *Filterable, sortable*.
        """,
    )
    fill_fraction = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Fraction of this image which has data.

        *Filterable, sortable*.
        """,
    )
    incidence_angle = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Sensor incidence angle in degrees.

        *Filterable, sortable*.
        """,
    )
    radiance_gain_bias = ListAttribute(
        ListAttribute(TypedAttribute(float, coerce=True)),
        doc="""list(list(float), optional: radiance conversion gain and bias.

        Outer list indexed by ``Band.vendor_order``, inner lists are ``[gain, bias]`` or
        empty if not applicable.
        """,
    )
    reflectance_gain_bias = ListAttribute(
        ListAttribute(TypedAttribute(float, coerce=True)),
        doc="""list(list(float), optional: reflectance conversion gain and bias.

        Outer list indexed by ``Band.vendor_order``, inner lists are ``[gain, bias]`` or
        empty if not applicable.
        """,
    )
    reflectance_scale = ListAttribute(
        TypedAttribute(float, coerce=True),
        doc="list(float), optional: Scale factors converting TOA radiances to TOA reflectances.",
    )
    roll_angle = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Applicable only to Landsat 8, roll angle in degrees.

        *Filterable, sortable*.
        """,
    )
    solar_azimuth_angle = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Solar azimuth angle at capture time.

        *Filterable, sortable*.
        """,
    )
    solar_elevation_angle = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Solar elevation angle at capture time.

        *Filterable, sortable*.
        """,
    )
    temperature_gain_bias = ListAttribute(
        ListAttribute(TypedAttribute(float, coerce=True)),
        doc="""list(list(float), optional: surface temperature conversion coefficients.

        Outer list indexed by ``Band.vendor_order``, inner lists are ``[gain, bias]`` or
        empty if not applicable.
        """,
    )
    view_angle = TypedAttribute(
        float,
        coerce=True,
        doc="""float, optional: Sensor view angle in degrees.

        *Filterable, sortable*.
        """,
    )
    satellite_id = TypedAttribute(
        str,
        doc="""str, optional: Id of the capturing satellite/sensor among a constellation of many satellites.

        *Filterable, sortable*.
        """,
    )

    # Provider info
    provider_id = TypedAttribute(
        str,
        doc="""str, optional: Id that uniquely ties this image to an entity as understood by the data provider.

        *Filterable, sortable*.
        """,
    )
    provider_url = TypedAttribute(
        str,
        doc="str, optional: An external (http) URL that has more details about the image",
    )
    preview_url = TypedAttribute(
        str,
        doc="""str, optional: An external (http) URL to a preview image.

        This image could be inlined in a UI to show a preview for the image.
        """,
    )
    preview_file = TypedAttribute(
        str,
        doc="""str, optional: A GCS URL with a georeferenced image.

        Use a GCS URL (``gs://...```) with appropriate access permissions.  This
        referenced image can be used to raster the image in a preview context, generally
        low resolution.  It should be a 3-band (RBG) or a 4-band (RGBA) image suitable
        for visual preview.  (It's not expected to conform to the bands of the
        products.)
        """,
    )

    SUPPORTED_DATATYPES = (
        "int8",
        "uint8",
        "int16",
        "uint16",
        "int32",
        "uint32",
        "int64",
        "uint64",
        "float16",
        "float32",
        "float64",
    )

    def __init__(self, **kwargs):
        super(Image, self).__init__(**kwargs)
        self._geocontext = None

    @property
    def geocontext(self):
        """
        `~earthdaily.earthone.common.geo.AOI`: A geocontext for loading this Image's original, unwarped data.

        These defaults are used:

        * resolution: resolution determined from the Image's ``geotrans``
        * crs: native CRS of the Image (often, a UTM CRS)
        * bounds: bounds determined from the Image's ``geotrans``, ``x_pixels`` and ``y_pixels``
        * bounds_crs: native CRS of the Image
        * align_pixels: False, to prevent interpolation snapping pixels to a new grid
        * geometry: None

        .. note::

            Using this :class:`~earthdaily.earthone.common.geo.GeoContext` will only
            return original, unwarped data if the Image is axis-aligned ("north-up")
            within the CRS. If its ``geotrans`` applies a rotation, a warning will be raised.
            In that case, use `Raster.ndarray` or `Raster.raster` to retrieve
            original data. (The :class:`~earthdaily.earthone.common.geo.GeoContext`
            paradigm requires bounds for consistency, which are inherently axis-aligned.)
        """
        if self._geocontext is None:
            shape = None
            bounds = None
            bounds_crs = None
            crs = self.cs_code or self.projection
            resolution = None

            geotrans = self.geotrans
            if geotrans is not None:
                geotrans = Affine.from_gdal(*geotrans)
                if not geotrans.is_rectilinear:
                    # NOTE: this may still be an insufficient check for some CRSs, i.e. polar stereographic?
                    warnings.warn(
                        "The GeoContext will *not* return this Image's original data, "
                        "since it's rotated compared to the grid of the CRS. "
                        "The array will be 'north-up', with the data rotated within it, "
                        "and extra empty pixels padded around the side(s). "
                        "To get the original, unrotated data, you must use the Raster API: "
                        "`earthdaily.earthone.client.services.raster.Raster.ndarray(image.id, ...)`."
                    )

                if self.x_pixels is not None and self.y_pixels is not None:
                    # prefer to raster by image shape, to best preserve original data.
                    # upper-left, upper-right, lower-left, lower-right in pixel coordinates
                    pixel_corners = [
                        (0, 0),
                        (self.x_pixels, 0),
                        (0, self.y_pixels),
                        (self.x_pixels, self.y_pixels),
                    ]
                    geo_corners = [geotrans * corner for corner in pixel_corners]
                    xs, ys = zip(*geo_corners)
                    bounds = (min(xs), min(ys), max(xs), max(ys))
                    bounds_crs = crs
                    shape = (self.y_pixels, self.x_pixels)
                else:
                    x_res, y_res = geotrans._scaling
                    if x_res != y_res:
                        # if pixels aren't square (unlikely), we won't just pick a resolution,
                        # the user has to figure that out.
                        raise ValueError(
                            "Image has no size and non-square pixels, so resolution is ambiguous. "
                            "You must manually define a geocontext for this image."
                        )
                    resolution = x_res

            self._geocontext = AOI(
                geometry=self.geometry,
                resolution=resolution,
                bounds=bounds,
                bounds_crs=bounds_crs,
                crs=crs,
                shape=shape,
                align_pixels=False,
            )

        return self._geocontext

    @property
    def __geo_interface__(self):
        return self.geocontext.__geo_interface__

    # convenience property
    @property
    def date(self):
        return self.acquired

    @classmethod
    def search(cls, client=None, request_params=None, headers=None):
        """A search query for all images.

        Return an `~earthdaily.earthone.catalog.ImageSearch` instance for searching
        images in the EarthOne catalog.  This instance extends the
        :py:class:`~earthdaily.earthone.catalog.Search` class with the
        :py:meth:`~earthdaily.earthone.catalog.ImageSearch.summary` and
        :py:meth:`~earthdaily.earthone.catalog.ImageSearch.summary_interval` methods
        which return summary statistics about the images that match the search query.

        Parameters
        ----------
        client : :class:`CatalogClient`, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.

        Returns
        -------
        :class:`~earthdaily.earthone.catalog.ImageSearch`
            An instance of the `~earthdaily.earthone.catalog.ImageSearch` class

        Example
        -------
        >>> from earthdaily.earthone.catalog import Image
        >>> search = Image.search().limit(10)
        >>> for result in search: # doctest: +SKIP
        ...     print(result.name) # doctest: +SKIP

        """
        return ImageSearch(
            cls, client=client, request_params=request_params, headers=headers
        )

    @check_deleted
    def upload(self, files, upload_options=None, overwrite=False):
        """Uploads imagery from a file (or files).

        Uploads imagery from a file (or files) in GeoTIFF or JP2 format to be ingested
        as an Image.

        The Image must be in the state `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
        The `product` or `product_id` attribute, the `name` attribute, and the
        `acquired` attribute must all be set. If either the `cs_code` or `projection`
        attributes is set (deprecated), it must agree with the projection defined in the file,
        otherwise an upload error will occur during processing.

        Parameters
        ----------
        files : str or io.IOBase or iterable of same
            File or files to be uploaded.  Can be string with path to the file in the
            local filesystem, or an opened file (``io.IOBase``), or an iterable of
            either of these when multiple files make up the image.
        upload_options : `~earthdaily.earthone.catalog.ImageUploadOptions`, optional
            Control of the upload process.
        overwrite : bool, optional
            If True, then permit overwriting of an existing image with the same id
            in the catalog. Defaults to False. Note that in all cases, the image
            object must have a state of `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
            USE WITH CAUTION: This can cause data cache inconsistencies in the platform,
            and should only be used for infrequent needs to update the image file
            contents. You can expect inconsistencies to endure for a period afterwards.

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.ImageUpload`
            An `~earthdaily.earthone.catalog.ImageUpload` instance which can
            be used to check the status or wait on the asynchronous upload process to
            complete.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        DeletedObjectError
            If this image was deleted.
        """
        from .image_upload import ImageUploadOptions, ImageUploadType

        if not self.id:
            raise ValueError("id field required")
        if not self.acquired:
            raise ValueError("acquired field required")
        if self.cs_code or self.projection:
            warnings.warn("cs_code and projection fields not permitted", FutureWarning)
            # raise ValueError("cs_code and projection fields not permitted")

        if self.state != DocumentState.UNSAVED:
            raise ValueError(
                "Image {} has been saved. Please use an unsaved image for uploading".format(
                    self.id
                )
            )

        if not overwrite and Image.exists(self.id, self._client):
            raise ValueError(
                "Image {} already exists in the catalog. Please either use a new image id or overwrite=True".format(
                    self.id
                )
            )

        if self.product.state != DocumentState.SAVED:
            raise ValueError(
                "Product {} has not been saved. Please save before uploading images".format(
                    self.product_id
                )
            )

        # convert file to a list, validating and extracting file names
        if isinstance(files, str) or isinstance(files, io.IOBase):
            files = [files]
        elif not isinstance(files, abc.Iterable):
            raise ValueError(
                "Invalid files value: must be string, IOBase, or iterable of the same"
            )
        filenames = []
        for f in files:
            if isinstance(f, str):
                filenames.append(f)
            elif isinstance(f, io.IOBase):
                filenames.append(f.name)
            else:
                raise ValueError(
                    "Invalid files value: must be string, IOBase, or iterable of the same"
                )
        if not filenames:
            raise ValueError("Invalid files value has zero length")

        if not upload_options:
            upload_options = ImageUploadOptions()

        upload_options.upload_type = ImageUploadType.FILE
        upload_options.image_files = filenames

        return self._do_upload(files, upload_options)

    @check_deleted
    def upload_ndarray(
        self,
        ndarray,
        upload_options=None,
        raster_meta=None,
        overviews=None,
        overview_resampler=None,
        overwrite=False,
    ):
        """Uploads imagery from an ndarray to be ingested as an Image.

        The Image must be in the state `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
        The `product` or `product_id` attribute, the `name` attribute, and the
        `acquired` attribute must all be set. Either (but not both) the `cs_code`
        or `projection` attributes must be set, or the `raster_meta` parameter must be provided.
        Similarly, either the `geotrans` attribute must be set or `raster_meta` must be provided.

        Note that one of the spatial reference attributes (`cs_code` or
        `projection`), or the `geotrans` attribute can be
        specified explicitly in the image, or the `raster_meta` parameter can be
        specified.  Likewise, `overviews` and `overview_resampler` can be
        specified explicitly, or via the `upload_options` parameter.


        Parameters
        ----------
        ndarray : np.array, Iterable(np.array)
            A numpy array or list of numpy arrays with image data, either with 2
            dimensions of shape ``(x, y)`` for a single band or with 3 dimensions of
            shape ``(band, x, y)`` for any number of bands.  If providing a 3d array
            the first dimension must index the bands.  The ``dtype`` of the array must
            also be one of the following:
            [``uint8``, ``int8``, ``uint16``, ``int16``, ``uint32``, ``int32``,
            ``uint64``, ``int64``, ``float16``, ``float32``, ``float64``]
        upload_options : :py:class:`~earthdaily.earthone.catalog.ImageUploadOptions`, optional
            Control of the upload process.
        raster_meta : dict, optional
            Metadata returned from the :meth:`Raster.ndarray()
            <earthdaily.earthone.client.services.raster.Raster.ndarray>` request which
            generated the initial data for the `ndarray` being uploaded.  Specifying
            `geotrans` and one of the spatial reference attributes (`cs_code` or
            `projection`) is unnecessary in this case but will take precedence over
            the value in `raster_meta`.
        overviews : list(int), optional
            Overview resolution magnification factors e.g.  [2, 4] would make two
            overviews at 2x and 4x the native resolution.  Maximum number of overviews
            allowed is 16.  Can also be set in the `upload_options` parameter.
        overview_resampler : `ResampleAlgorithm`, optional
            Resampler algorithm to use when building overviews.  Controls how pixels
            are combined to make lower res pixels in overviews. Can also be set in
            the `upload_options` parameter.
        overwrite : bool, optional
            If True, then permit overwriting of an existing image with the same id
            in the catalog. Defaults to False. Note that in all cases, the image
            object must have a state of `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
            USE WITH CAUTION: This can cause data cache inconsistencies in the platform,
            and should only be used for infrequent needs to update the image file
            contents. You can expect inconsistencies to endure for a period afterwards.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        DeletedObjectError
            If this image was deleted.

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.ImageUpload`
            An `~earthdaily.earthone.catalog.ImageUpload` instance which can
            be used to check the status or wait on the asynchronous upload process to
            complete.
        """
        from .image_upload import ImageUploadOptions, ImageUploadType

        if not self.id:
            raise ValueError("id field required")
        if not self.acquired:
            raise ValueError("acquired field required")
        if self.cs_code and self.projection:
            warnings.warn(
                "Only one of cs_code and projection fields permitted",
                FutureWarning,
            )
            # raise ValueError("only one of cs_code and projection fields permitted")

        if self.state != DocumentState.UNSAVED:
            raise ValueError(
                "Image {} has been saved. Please use an unsaved image for uploading".format(
                    self.id
                )
            )

        if not overwrite and Image.exists(self.id, self._client):
            raise ValueError(
                "Image {} already exists in the catalog. Please either use a new image id or overwrite=True".format(
                    self.id
                )
            )

        if self.product.state != DocumentState.SAVED:
            raise ValueError(
                "Product {} has not been saved. Please save before uploading images".format(
                    self.product_id
                )
            )

        if isinstance(ndarray, (np.ndarray, np.generic)):
            ndarray = [ndarray]
        elif not isinstance(ndarray, abc.Iterable):
            raise ValueError(
                "The array must be an instance of ndarray or an Iterable of ndarrays"
                "such as a list."
            )

        # validate the shape of each ndarray
        # modify image data to shift axes to what ingest expects
        for idx, image_data in enumerate(ndarray):
            if not isinstance(image_data, (np.ndarray, np.generic)):
                raise ValueError(f"The item at index {idx} is not an ndarray")

            if len(image_data.shape) not in (2, 3):
                raise ValueError(
                    "The array must have 2 dimensions (shape '(x, y)') or 3 dimensions with the band "
                    "axis in the first dimension (shape '(band, x, y)'). The given array has shape "
                    "'{}' instead.".format(image_data.shape)
                )

            if image_data.dtype.name not in self.SUPPORTED_DATATYPES:
                raise ValueError(
                    "The array has an unsupported data type {}. Only the following data types are supported: {}".format(
                        image_data.dtype.name, ",".join(self.SUPPORTED_DATATYPES)
                    )
                )

            if len(image_data.shape) == 3:
                scale_factor = 5
                scaled_band_dim = image_data.shape[0] * scale_factor

                if (
                    scaled_band_dim > image_data.shape[1]
                    or scaled_band_dim > image_data.shape[2]
                ):
                    warnings.warn(
                        "The shape '{}' of the given 3d-array looks like it might not have the band "
                        "axis as the first dimension. Verify that your array conforms to the shape "
                        "'(band, x, y)'".format(image_data.shape)
                    )
                # v1 ingest expects (X,Y,bands)
                ndarray[idx] = np.moveaxis(image_data, 0, -1)

        # default to raster_meta fields if not explicitly provided
        if raster_meta:
            if not self.geotrans:
                self.geotrans = raster_meta.get("geoTransform")
            if not self.cs_code and not self.projection:
                # doesn't yet exist!
                self.projection = raster_meta.get("coordinateSystem", {}).get("proj4")

        if not self.geotrans:
            raise ValueError("geotrans field or raster_meta parameter is required")
        if not self.cs_code and not self.projection:
            raise ValueError(
                "cs_code or projection field is required if "
                + "raster_meta parameter is not given"
            )

        if not upload_options:
            upload_options = ImageUploadOptions()
        upload_options.upload_type = ImageUploadType.NDARRAY
        if overviews:
            upload_options.overviews = overviews
        if overview_resampler:
            upload_options.overview_resampler = overview_resampler

        # write all the ndarrays to files so that _do_upload can read them
        files = []
        upload_size = 0

        try:
            for image_data in ndarray:
                upload_size += image_data.nbytes
                tmp = NamedTemporaryFile(delete=False)
                files.append(tmp)
                np.save(tmp, image_data, allow_pickle=False)
                # From tempfile docs:
                # Whether the name can be used to open the file a second time,
                # while the named temporary file is still open, varies across
                # platforms (it can be so used on Unix; it cannot on Windows
                # NT or later)
                # We close the underlying file object so _do_upload can open
                # the path again in a cross platform compatible way.
                # Cleanup is manual in the finally block.
                tmp.close()

            file_names = [f.name for f in files]
            upload_options.upload_size = upload_size
            upload_options.image_files = file_names

            return self._do_upload(file_names, upload_options)
        finally:
            for file in files:
                try:
                    os.unlink(file.name)
                except OSError:
                    pass

    def image_uploads(self):
        """A search query for all uploads for this image created by this user.

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.Search`
            A :py:class:`~earthdaily.earthone.catalog.Search` instance configured to
            find all uploads for this image.
        """
        from .image_upload import ImageUpload

        return ImageUpload.search(client=self._client).filter(
            (properties.product_id == self.product_id)
            & (properties.image_id == self.id)
        )

    # the upload implementation is broken out so it can be used from multiple methods
    def _do_upload(self, files, upload_options):
        from .image_upload import ImageUpload, ImageUploadStatus

        upload = ImageUpload(
            client=self._client, image=self, image_upload_options=upload_options
        )

        upload.save()

        headers = {"content-type": "application/octet-stream"}

        for file, upload_url in zip(files, upload.resumable_urls):
            if isinstance(file, io.IOBase):
                if "b" not in file.mode:
                    file.close()
                    file = io.open(file.name, "rb")
                f = file
            else:
                f = io.open(file, "rb")

            try:
                self._upload_service.session.put(upload_url, data=f, headers=headers)
            finally:
                f.close()

        upload.status = ImageUploadStatus.PENDING
        upload.save()

        return upload

    def coverage(self, geom):
        """
        The fraction of a geometry-like object covered by this Image's geometry.

        Parameters
        ----------
        geom : GeoJSON-like dict, :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, or object with __geo_interface__
            Geometry to which to compare this Image's geometry

        Returns
        -------
        coverage: float
            The fraction of ``geom``'s area that overlaps with this Image,
            between 0 and 1.

        Example
        -------
        >>> import earthdaily.earthone as eo
        >>> image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1")  # doctest: +SKIP
        >>> image.coverage(image.geometry.buffer(1))  # doctest: +SKIP
        0.258370644415335
        """  # noqa: E501

        if isinstance(geom, GeoContext):
            shape = geom.geometry
        else:
            shape = geometry_like_to_shapely(geom)

        intersection = shape.intersection(self.geometry)
        return intersection.area / shape.area

    def ndarray(
        self,
        bands,
        geocontext=None,
        crs=None,
        resolution=None,
        all_touched=None,
        mask_nodata=True,
        mask_alpha=None,
        bands_axis=0,
        raster_info=False,
        resampler=ResampleAlgorithm.NEAR,
        processing_level=None,
        scaling=None,
        data_type=None,
        progress=None,
        **kwargs,
    ):
        """
        Load bands from this image as an ndarray, optionally masking invalid data.

        If the selected bands have different data types the resulting ndarray
        has the most general of those data types. This table defines which data types
        can be cast to which more general data types:

        * ``Byte`` to: ``UInt16``, ``UInt32``, ``UInt64``, ``Int16``, ``Int32``, ``Int64``,
          ``Float16``, ``Float32``, ``Float64``
        * ``Int8`` to: ``Int16``, ``Int32``, ``Int64``, ``Float16``, ``Float32``, ``Float64``
        * ``UInt16`` to: ``UInt32``, ``UInt64``, ``Int32``, ``Int64``, ``Float32``, ``Float64``
        * ``UInt32`` to: ``UInt64``, ``Int64``, ``Float64``
        * ``UInt64`` to: ``Float64``
        * ``Int16`` to: ``Int32``, ``Int64``, ``Float32``, ``Float64``
        * ``Int32`` to: ``Int64``, ``Float32``, ``Float64``
        * ``Float16`` to: ``Float32``, ``Float64``
        * ``Float32`` to: ``Float64``
        * ``Float64`` to: No possible casts

        Parameters
        ----------
        bands : str or Sequence[str]
            Band names to load. Can be a single string of band names
            separated by spaces (``"red green blue"``),
            or a sequence of band names (``["red", "green", "blue"]``).
            Names must be keys in ``self.properties.bands``.
            If the alpha band is requested, it must be last in the list
            to reduce rasterization errors.
        geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
            A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading this Image.
            If ``None`` then the default geocontext of the image will be used.
        crs : str, default None
            if not None, update the gecontext with this value to set the output CRS.
        resolution : float, default None
            if not None, update the geocontext with this value to set the output resolution
            in the units native to the specified or defaulted output CRS.
        all_touched : float, default None
            if not None, update the geocontext with this value to control rastering behavior.
        mask_nodata : bool, default True
            Whether to mask out values in each band that equal
            that band's ``nodata`` sentinel value.
        mask_alpha : bool or str or None, default None
            Whether to mask pixels in all bands where the alpha band of the image is 0.
            Provide a string to use an alternate band name for masking.
            If the alpha band is available and ``mask_alpha`` is None, ``mask_alpha``
            is set to True. If not, mask_alpha is set to False.
        bands_axis : int, default 0
            Axis along which bands should be located in the returned array.
            If 0, the array will have shape ``(band, y, x)``, if -1,
            it will have shape ``(y, x, band)``.

            It's usually easier to work with bands as the outermost axis,
            but when working with large arrays, or with many arrays concatenated
            together, NumPy operations aggregating each xy point across bands
            can be slightly faster with bands as the innermost axis.
        raster_info : bool, default False
            Whether to also return a dict of information about the rasterization
            of the image, including the coordinate system WKT and geotransform matrix.
            Generally only useful if you plan to upload data derived
            from this image back to the EarthOne catalog, or use it with GDAL.
        resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
            Algorithm used to interpolate pixel values when scaling and transforming
            the image to its new resolution or CRS.
        processing_level : str, optional
            How the processing level of the underlying data should be adjusted. Possible
            values depend on the product and bands in use. Legacy products support
            ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
            available ``processing_levels`` in the product bands to understand what
            is available.
        scaling : None, str, list, dict
            Band scaling specification. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        data_type : None, str
            Output data type. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        progress : None, bool
            Controls display of a progress bar.

        Returns
        -------
        arr : ndarray
            Returned array's shape will be ``(band, y, x)`` if bands_axis is 0,
            ``(y, x, band)`` if bands_axis is -1.
            If ``mask_nodata`` or ``mask_alpha`` is True, arr will be a masked array.
            The data type ("dtype") of the array is the most general of the data
            types among the bands being rastered.
        raster_info : dict
            If ``raster_info=True``, a raster information dict is also returned.

        Example
        -------
        >>> import earthdaily.earthone as eo
        >>> image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1")  # doctest: +SKIP
        >>> arr = image.ndarray("red green blue", resolution=120.)  # doctest: +SKIP
        >>> type(arr)  # doctest: +SKIP
        <class 'numpy.ma.core.MaskedArray'>
        >>> arr.shape  # doctest: +SKIP
        (3, 1995, 1962)
        >>> red_band = arr[0]  # doctest: +SKIP

        Raises
        ------
        ValueError
            If requested bands are unavailable.
            If band names are not given or are invalid.
            If the requested bands have incompatible dtypes.
        NotFoundError
            If a Image's ID cannot be found in the EarthOne catalog
        BadRequestError
            If the EarthOne Platform is given invalid parameters
        """
        if geocontext is None:
            # Lose the image's geometry (which is only used as a cutline),
            # as it might cause some unexpected clipping when rasterizing, due
            # to imperfect simplified geometries used when native image CRS is not WGS84.
            geocontext = self.geocontext.assign(geometry=None)
        if crs is not None or resolution is not None:
            try:
                params = {}
                if crs is not None:
                    params["crs"] = crs
                if resolution is not None:
                    params["resolution"] = resolution
                geocontext = geocontext.assign(**params)
            except TypeError:
                raise ValueError(
                    f"{type(geocontext)} geocontext does not support modifying crs or resolution"
                ) from None
        if all_touched is not None:
            geocontext = geocontext.assign(all_touched=all_touched)

        return self._ndarray(
            bands,
            geocontext,
            mask_nodata=mask_nodata,
            mask_alpha=mask_alpha,
            bands_axis=bands_axis,
            raster_info=raster_info,
            resampler=resampler,
            processing_level=processing_level,
            scaling=scaling,
            data_type=data_type,
            progress=progress,
            **kwargs,
        )

    # the ndarray implementation is broken out so it can be used directly from ImageCollection
    def _ndarray(
        self,
        bands,
        geocontext,
        mask_nodata=True,
        mask_alpha=None,
        bands_axis=0,
        raster_info=False,
        resampler=ResampleAlgorithm.NEAR,
        processing_level=None,
        scaling=None,
        data_type=None,
        progress=None,
        **kwargs,
    ):
        if not (-3 < bands_axis < 3):
            raise ValueError(
                "Invalid bands_axis; axis {} would not exist in a 3D array".format(
                    bands_axis
                )
            )

        bands = bands_to_list(bands)
        product_bands = cached_bands_by_product(self.product_id, self._client)

        scales, data_type = scaling_parameters(
            product_bands, bands, processing_level, scaling, data_type
        )

        mask_nodata = bool(mask_nodata)

        alpha_band_name = "alpha"
        if isinstance(mask_alpha, str):
            alpha_band_name = mask_alpha
            mask_alpha = True
        elif mask_alpha is None:
            # if user does not set mask_alpha, only attempt to mask_alpha if
            # alpha band is exists in the image.
            mask_alpha = alpha_band_name in product_bands
        elif type(mask_alpha) is not bool:
            raise ValueError("'mask_alpha' must be None, a band name, or a bool.")

        drop_alpha = False
        if mask_alpha:
            if alpha_band_name not in product_bands:
                raise ValueError(
                    "Cannot mask alpha: no {} band for the product '{}'. "
                    "Try setting 'mask_alpha=False'.".format(
                        alpha_band_name, self.product_id
                    )
                )
            try:
                alpha_i = bands.index(alpha_band_name)
            except ValueError:
                bands.append(alpha_band_name)
                drop_alpha = True
            else:
                if alpha_i != len(bands) - 1:
                    raise ValueError(
                        "Alpha must be the last band in order to reduce rasterization errors"
                    )

        raster_params = geocontext.raster_params
        full_raster_args = dict(
            inputs=[self.id],
            order="gdal",
            bands=bands,
            scales=scales,
            data_type=data_type,
            resampler=resampler,
            processing_level=processing_level,
            masked=mask_nodata or mask_alpha,
            mask_nodata=mask_nodata,
            mask_alpha=mask_alpha,
            drop_alpha=drop_alpha,
            progress=progress,
            **raster_params,
        )

        raster = kwargs.pop("raster_client", None) or Raster.get_default_client()
        if kwargs:
            raise TypeError(f"Unexpected keyword arguments: {kwargs}")
        try:
            arr, info = raster.ndarray(**full_raster_args)

        except NotFoundError:
            raise NotFoundError(
                "'{}' does not exist in the EarthOne catalog".format(self.id)
            ) from None
        except BadRequestError as e:
            msg = (
                "Error with request:\n"
                "{err}\n"
                "For reference, Raster.ndarray was called with these arguments:\n"
                "{args}"
            )
            msg = msg.format(err=e, args=json.dumps(full_raster_args, indent=2))
            raise BadRequestError(msg) from None

        if len(arr.shape) == 2:
            # if only 1 band requested, still return a 3d array
            arr = arr[np.newaxis]

        if bands_axis != 0:
            arr = np.moveaxis(arr, 0, bands_axis)
        if raster_info:
            return arr, info
        else:
            return arr

    def download(
        self,
        bands,
        geocontext=None,
        crs=None,
        resolution=None,
        all_touched=None,
        dest=None,
        format=DownloadFileFormat.TIF,
        resampler=ResampleAlgorithm.NEAR,
        processing_level=None,
        scaling=None,
        data_type=None,
        nodata=None,
        progress=None,
    ):
        """
        Save bands from this image as a GeoTIFF, PNG, or JPEG, writing to a path.

        Parameters
        ----------
        bands : str or Sequence[str]
            Band names to load. Can be a single string of band names
            separated by spaces (``"red green blue"``),
            or a sequence of band names (``["red", "green", "blue"]``).
            Names must be keys in ``self.properties.bands``.
        geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
            A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading this image.
            If ``None`` then use the default context for the image.
        crs : str, default None
            if not None, update the gecontext with this value to set the output CRS.
        resolution : float, default None
            if not None, update the geocontext with this value to set the output resolution
            in the units native to the specified or defaulted output CRS.
        all_touched : float, default None
            if not None, update the geocontext with this value to control rastering behavior.
        dest : str or path-like object, default None
            Where to write the image file.

            * If None (default), it's written to an image file of the given ``format``
              in the current directory, named by the image's ID and requested bands,
              like ``"sentinel-2:L1C:2018-08-10_10TGK_68_S2A_v1-red-green-blue.tif"``
            * If a string or path-like object, it's written to that path.

              Any file already existing at that path will be overwritten.

              Any intermediate directories will be created if they don't exist.

              Note that path-like objects (such as pathlib.Path) are only supported
              in Python 3.6 or later.
        format : `DownloadFileFormat`, default `DownloadFileFormat.TIF`
            Output file format to use
            If a str or path-like object is given as ``dest``, ``format`` is ignored
            and determined from the extension on the path (one of ".tif", ".png", or ".jpg").
        resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
            Algorithm used to interpolate pixel values when scaling and transforming
            the image to its new resolution or SRS.
        processing_level : str, optional
            How the processing level of the underlying data should be adjusted. Possible
            values depend on the product and bands in use. Legacy products support
            ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
            available ``processing_levels`` in the product bands to understand what
            is available.
        scaling : None, str, list, dict
            Band scaling specification. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        data_type : None, str
            Output data type. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        nodata : None, number
            NODATA value for a geotiff file. Will be assigned to any masked pixels.
        progress : None, bool
            Controls display of a progress bar.

        Returns
        -------
        path : str or None
            If ``dest`` is None or a path, the path where the image file was written is returned.
            If ``dest`` is file-like, nothing is returned.

        Example
        -------
        >>> import earthdaily.earthone as eo
        >>> image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1")  # doctest: +SKIP
        >>> image.download("red green blue", resolution=120.)  # doctest: +SKIP
        "landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1_red-green-blue.tif"
        >>> import os
        >>> os.listdir(".")  # doctest: +SKIP
        ["landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1_red-green-blue.tif"]
        >>> image.download(
        ...     "nir swir1",
        ...     "rasters/{geocontext.resolution}-{image_id}.jpg".format(geocontext=image.geocontext, image_id=image.id)
        ... )  # doctest: +SKIP
        "rasters/15-landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1.tif"

        Raises
        ------
        ValueError
            If requested bands are unavailable.
            If band names are not given or are invalid.
            If the requested bands have incompatible dtypes.
            If ``format`` is invalid, or the path has an invalid extension.
        NotFoundError
            If a image's ID cannot be found in the EarthOne catalog
        BadRequestError
            If the EarthOne Platform is given invalid parameters
        """
        if geocontext is None:
            # Lose the image's geometry (which is only used as a cutline),
            # as it might cause some unexpected clipping when rasterizing, due
            # to imperfect simplified geometries used when native image CRS is not WGS84.
            geocontext = self.geocontext.assign(geometry=None)
        if crs is not None or resolution is not None:
            try:
                params = {}
                if crs is not None:
                    params["crs"] = crs
                if resolution is not None:
                    params["resolution"] = resolution
                geocontext = geocontext.assign(**params)
            except TypeError:
                raise ValueError(
                    f"{type(geocontext)} geocontext does not support modifying crs or resolution"
                ) from None
        if all_touched is not None:
            geocontext = geocontext.assign(all_touched=all_touched)

        return self._download(
            bands,
            geocontext,
            dest=dest,
            format=format,
            resampler=resampler,
            processing_level=processing_level,
            scaling=scaling,
            data_type=data_type,
            nodata=nodata,
            progress=progress,
        )

    # the download implementation is broken out so it can be used directly from ImageCollection
    def _download(
        self,
        bands,
        geocontext,
        dest=None,
        format=DownloadFileFormat.TIF,
        resampler=ResampleAlgorithm.NEAR,
        processing_level=None,
        scaling=None,
        data_type=None,
        nodata=None,
        progress=None,
    ):
        bands = bands_to_list(bands)
        scales, data_type = scaling_parameters(
            cached_bands_by_product(self.product_id, self._client),
            bands,
            processing_level,
            scaling,
            data_type,
        )

        return download(
            inputs=[self.id],
            bands_list=bands,
            geocontext=geocontext,
            data_type=data_type,
            dest=dest,
            format=format,
            resampler=resampler,
            processing_level=processing_level,
            scales=scales,
            nodata=nodata,
            progress=progress,
        )

    def scaling_parameters(
        self, bands, processing_level=None, scaling=None, data_type=None
    ):
        """
        Computes fully defaulted scaling parameters and output data_type
        from provided specifications.

        This method makes accessible the scales and data_type parameters
        which will be generated and passed to the Raster API by methods
        such as :meth:`ndarray` and :meth:`download`. It is provided
        as a convenience to the user to aid in understanding how the
        ``scaling`` and ``data_type`` parameters will be handled by
        those methods. It would not usually be used in a normal workflow.

        Parameters
        ----------
        bands : list
            List of bands to be scaled.
        processing_level : str, optional
            How the processing level of the underlying data should be adjusted. Possible
            values depend on the product and bands in use. Legacy products support
            ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
            available ``processing_levels`` in the product bands to understand what
            is available.
        scaling : None or str or list or dict, default None
            Supplied scaling specification, see below.
        data_type : None or str, default None
            Result data type desired, as a standard data type string (e.g.
            ``"Byte"``, ``"Uint16"``, or ``"Float64"``). If not specified,
            will be deduced from the ``scaling`` specification. Typically
            this is left unset and the appropriate type will be determined
            automatically.

        Returns
        -------
        scales : list(tuple)
            The fully specified scaling parameter, compatible with the
            :class:`~earthdaily.earthone.client.services.raster.Raster` API and the
            output data type.
        data_type : str
            The result data type as a standard GDAL type string.

        Raises
        ------
        ValueError
            If any invalid or incompatible value is passed to any of the
            three parameters.


        Scaling is determined on a band-by-band basis, incorporating the user
        provided specification, the output data_type, and properties for the
        band, such as the band type, the band data type, and the
        ``default_range``, ``data_range``, and ``physical_range`` properties.
        Ultimately the scaling for each band will be resolved to either
        ``None`` or a tuple of numeric values of length 0, 2, or 4, as
        accepted by the Raster API. The result is a list (with length equal
        to the number of bands) of one of these values, or may be a None
        value which is just a shorthand equivalent for a list of None values.

        A ``None`` indicates that no scaling should be performed.

        A 0-tuple ``()`` indicates that the band data should be automatically
        scaled from the minimum and maximum values present in the image data
        to the display range 0-255.

        A 2-tuple ``(input-min, input-max)`` indicates that the band data
        should be scaled from the specified input range to the display
        range of 0-255.

        A 4-tuple ``(input-min, input-max, output-min, output-max)``
        indicates that the band data should be scaled from the input range
        to the output range.

        In all cases, the scaling will be performed as a multiply and add,
        and the resulting values are only clipped as necessary to fit in
        the output data type. As such, if the input and output ranges are
        the same, it is effectively a no-op equivalent to ``None``.

        The support for scaling parameters in the Catalog API includes
        the concept of an automated scaling mode. The four supported modes
        are as follows.

        ``"raw"``:
            Equivalent to a ``None``, the data should not be scaled.
        ``"auto"``:
            Equivalent to a 0-tuple, the data should be scaled by
            the Raster service so that the actual range of data in the
            input is scaled up to the full display range (0-255). It
            is not possible to determine the bounds of this input range
            in the client as the actual band data is not accessible.
        ``"display"``:
            The data should be scaled from any specified input bounds,
            defaulting to the ``default_range`` property for the band,
            to the output range, defaulting to 0-255.
        ``"physical"``:
            The data should be scaled from the input range, defaulting
            to the ``data_range`` property for the band, to the output
            range, defaulting to the ``physical_range`` property for
            the band.

        The mode may be explicitly specified, or it may be determined
        implicitly from other characteristics such as the length
        and contents of the tuples for each band, or from the output
        data_type if this is explicitly specified (e.g. ``"Byte"``
        implies display mode, ``"Float64"`` implies physical mode).

        If it is not possible to infer the mode, and a mode is required
        in order to fully determine the results of this method, an
        error will be raised. It is also an error to explicitly
        specify more than one mode, with several exceptions: auto
        and display mode are compatible, while a raw display mode
        for a band which is of type "mask" or type "class" does
        not conflict with any other mode specification.

        Normally the ``data_type`` parameter is not provided by the
        user, and is instead determined from the mode as follows.

        ``"raw"``:
            The data type that best matches the data types of all
            the bands, preserving the precision and range of the
            original data.
        ``"auto"`` and ``"display"``:
            ``"Byte"``
        ``"physical"``:
            ``"Float64"``

        The ``scaling`` parameter passed to this method can be any
        of the following:

        None:
            No scaling for all bands. Equivalent to ``[None, ...]``.
        str:
            Any of the four supported automatic modes as
            described above.
        list or Iterable:
            A list or similar iterable must contain a number of
            elements equal to the number of bands specified. Each
            element must either be a None, a 0-, 2-, or 4-tuple, or
            one of the above four automatic mode strings. The
            elements of each tuple must either be a numeric value
            or a string containing a valid numerical string followed
            by a "%" character. The latter will be interpreted as a
            percentage of the appropriate range (e.g. ``default_range``,
            ``data_range``, or ``physical_range``) according to the mode.
            For example, a tuple of ``("25%", "75%")`` with a
            ``default_range`` of ``[0, 4000]`` will yield ``(1000, 3000)``.
        dict or Mapping:
            A dictionary or similar mapping with keys corresponding to
            band names and values as accepted as elements for each band
            as with a list described above. Each band name is used to
            lookup a value in the mapping. If none is found, and the
            band is not of type "mask" or "class", then the special
            key ``"default_"`` is looked up in the mapping if it exists.
            Otherwise a value of ``None`` will be used for the band.
            This is strictly a convenience for constructing a list of
            scale values, one for each band, but can be useful if a
            single general-purpose mapping is defined for all possible
            or relevant bands and then reused across many calls to the
            different methods in the Catalog API which accept a ``scaling``
            parameter.

        See Also
        --------
        :doc:`Catalog Guide <guides/catalog>` : This contains many examples of the use of
        the ``scaling`` and ``data_type`` parameters.
        """
        bands = bands_to_list(bands)
        return scaling_parameters(
            cached_bands_by_product(self.product_id, self._client),
            bands,
            processing_level,
            scaling,
            data_type,
        )

geocontext property 🔗

geocontext

~earthdaily.earthone.common.geo.AOI: A geocontext for loading this Image's original, unwarped data.

These defaults are used:

  • resolution: resolution determined from the Image's geotrans
  • crs: native CRS of the Image (often, a UTM CRS)
  • bounds: bounds determined from the Image's geotrans, x_pixels and y_pixels
  • bounds_crs: native CRS of the Image
  • align_pixels: False, to prevent interpolation snapping pixels to a new grid
  • geometry: None

.. note::

Using this :class:`~earthdaily.earthone.common.geo.GeoContext` will only
return original, unwarped data if the Image is axis-aligned ("north-up")
within the CRS. If its ``geotrans`` applies a rotation, a warning will be raised.
In that case, use `Raster.ndarray` or `Raster.raster` to retrieve
original data. (The :class:`~earthdaily.earthone.common.geo.GeoContext`
paradigm requires bounds for consistency, which are inherently axis-aligned.)

search classmethod 🔗

search(client=None, request_params=None, headers=None)

A search query for all images.

Return an ~earthdaily.earthone.catalog.ImageSearch instance for searching images in the EarthOne catalog. This instance extends the 🇵🇾class:~earthdaily.earthone.catalog.Search class with the 🇵🇾meth:~earthdaily.earthone.catalog.ImageSearch.summary and 🇵🇾meth:~earthdaily.earthone.catalog.ImageSearch.summary_interval methods which return summary statistics about the images that match the search query.

Parameters🔗

client : :class:CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog.

Returns🔗

:class:~earthdaily.earthone.catalog.ImageSearch An instance of the ~earthdaily.earthone.catalog.ImageSearch class

Example🔗

from earthdaily.earthone.catalog import Image search = Image.search().limit(10) for result in search: # doctest: +SKIP ... print(result.name) # doctest: +SKIP

Source code in earthdaily/earthone/core/catalog/image.py
@classmethod
def search(cls, client=None, request_params=None, headers=None):
    """A search query for all images.

    Return an `~earthdaily.earthone.catalog.ImageSearch` instance for searching
    images in the EarthOne catalog.  This instance extends the
    :py:class:`~earthdaily.earthone.catalog.Search` class with the
    :py:meth:`~earthdaily.earthone.catalog.ImageSearch.summary` and
    :py:meth:`~earthdaily.earthone.catalog.ImageSearch.summary_interval` methods
    which return summary statistics about the images that match the search query.

    Parameters
    ----------
    client : :class:`CatalogClient`, optional
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.

    Returns
    -------
    :class:`~earthdaily.earthone.catalog.ImageSearch`
        An instance of the `~earthdaily.earthone.catalog.ImageSearch` class

    Example
    -------
    >>> from earthdaily.earthone.catalog import Image
    >>> search = Image.search().limit(10)
    >>> for result in search: # doctest: +SKIP
    ...     print(result.name) # doctest: +SKIP

    """
    return ImageSearch(
        cls, client=client, request_params=request_params, headers=headers
    )

upload 🔗

upload(files, upload_options=None, overwrite=False)

Uploads imagery from a file (or files).

Uploads imagery from a file (or files) in GeoTIFF or JP2 format to be ingested as an Image.

The Image must be in the state ~earthdaily.earthone.catalog.DocumentState.UNSAVED. The product or product_id attribute, the name attribute, and the acquired attribute must all be set. If either the cs_code or projection attributes is set (deprecated), it must agree with the projection defined in the file, otherwise an upload error will occur during processing.

Parameters🔗

files : str or io.IOBase or iterable of same File or files to be uploaded. Can be string with path to the file in the local filesystem, or an opened file (io.IOBase), or an iterable of either of these when multiple files make up the image. upload_options : ~earthdaily.earthone.catalog.ImageUploadOptions, optional Control of the upload process. overwrite : bool, optional If True, then permit overwriting of an existing image with the same id in the catalog. Defaults to False. Note that in all cases, the image object must have a state of ~earthdaily.earthone.catalog.DocumentState.UNSAVED. USE WITH CAUTION: This can cause data cache inconsistencies in the platform, and should only be used for infrequent needs to update the image file contents. You can expect inconsistencies to endure for a period afterwards.

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.ImageUpload An ~earthdaily.earthone.catalog.ImageUpload instance which can be used to check the status or wait on the asynchronous upload process to complete.

Raises🔗

ValueError If any improper arguments are supplied. DeletedObjectError If this image was deleted.

Source code in earthdaily/earthone/core/catalog/image.py
@check_deleted
def upload(self, files, upload_options=None, overwrite=False):
    """Uploads imagery from a file (or files).

    Uploads imagery from a file (or files) in GeoTIFF or JP2 format to be ingested
    as an Image.

    The Image must be in the state `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
    The `product` or `product_id` attribute, the `name` attribute, and the
    `acquired` attribute must all be set. If either the `cs_code` or `projection`
    attributes is set (deprecated), it must agree with the projection defined in the file,
    otherwise an upload error will occur during processing.

    Parameters
    ----------
    files : str or io.IOBase or iterable of same
        File or files to be uploaded.  Can be string with path to the file in the
        local filesystem, or an opened file (``io.IOBase``), or an iterable of
        either of these when multiple files make up the image.
    upload_options : `~earthdaily.earthone.catalog.ImageUploadOptions`, optional
        Control of the upload process.
    overwrite : bool, optional
        If True, then permit overwriting of an existing image with the same id
        in the catalog. Defaults to False. Note that in all cases, the image
        object must have a state of `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
        USE WITH CAUTION: This can cause data cache inconsistencies in the platform,
        and should only be used for infrequent needs to update the image file
        contents. You can expect inconsistencies to endure for a period afterwards.

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.ImageUpload`
        An `~earthdaily.earthone.catalog.ImageUpload` instance which can
        be used to check the status or wait on the asynchronous upload process to
        complete.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    DeletedObjectError
        If this image was deleted.
    """
    from .image_upload import ImageUploadOptions, ImageUploadType

    if not self.id:
        raise ValueError("id field required")
    if not self.acquired:
        raise ValueError("acquired field required")
    if self.cs_code or self.projection:
        warnings.warn("cs_code and projection fields not permitted", FutureWarning)
        # raise ValueError("cs_code and projection fields not permitted")

    if self.state != DocumentState.UNSAVED:
        raise ValueError(
            "Image {} has been saved. Please use an unsaved image for uploading".format(
                self.id
            )
        )

    if not overwrite and Image.exists(self.id, self._client):
        raise ValueError(
            "Image {} already exists in the catalog. Please either use a new image id or overwrite=True".format(
                self.id
            )
        )

    if self.product.state != DocumentState.SAVED:
        raise ValueError(
            "Product {} has not been saved. Please save before uploading images".format(
                self.product_id
            )
        )

    # convert file to a list, validating and extracting file names
    if isinstance(files, str) or isinstance(files, io.IOBase):
        files = [files]
    elif not isinstance(files, abc.Iterable):
        raise ValueError(
            "Invalid files value: must be string, IOBase, or iterable of the same"
        )
    filenames = []
    for f in files:
        if isinstance(f, str):
            filenames.append(f)
        elif isinstance(f, io.IOBase):
            filenames.append(f.name)
        else:
            raise ValueError(
                "Invalid files value: must be string, IOBase, or iterable of the same"
            )
    if not filenames:
        raise ValueError("Invalid files value has zero length")

    if not upload_options:
        upload_options = ImageUploadOptions()

    upload_options.upload_type = ImageUploadType.FILE
    upload_options.image_files = filenames

    return self._do_upload(files, upload_options)

upload_ndarray 🔗

upload_ndarray(ndarray, upload_options=None, raster_meta=None, overviews=None, overview_resampler=None, overwrite=False)

Uploads imagery from an ndarray to be ingested as an Image.

The Image must be in the state ~earthdaily.earthone.catalog.DocumentState.UNSAVED. The product or product_id attribute, the name attribute, and the acquired attribute must all be set. Either (but not both) the cs_code or projection attributes must be set, or the raster_meta parameter must be provided. Similarly, either the geotrans attribute must be set or raster_meta must be provided.

Note that one of the spatial reference attributes (cs_code or projection), or the geotrans attribute can be specified explicitly in the image, or the raster_meta parameter can be specified. Likewise, overviews and overview_resampler can be specified explicitly, or via the upload_options parameter.

Parameters🔗

ndarray : np.array, Iterable(np.array) A numpy array or list of numpy arrays with image data, either with 2 dimensions of shape (x, y) for a single band or with 3 dimensions of shape (band, x, y) for any number of bands. If providing a 3d array the first dimension must index the bands. The dtype of the array must also be one of the following: [uint8, int8, uint16, int16, uint32, int32, uint64, int64, float16, float32, float64] upload_options : 🇵🇾class:~earthdaily.earthone.catalog.ImageUploadOptions, optional Control of the upload process. raster_meta : dict, optional Metadata returned from the :meth:Raster.ndarray() <earthdaily.earthone.client.services.raster.Raster.ndarray> request which generated the initial data for the ndarray being uploaded. Specifying geotrans and one of the spatial reference attributes (cs_code or projection) is unnecessary in this case but will take precedence over the value in raster_meta. overviews : list(int), optional Overview resolution magnification factors e.g. [2, 4] would make two overviews at 2x and 4x the native resolution. Maximum number of overviews allowed is 16. Can also be set in the upload_options parameter. overview_resampler : ResampleAlgorithm, optional Resampler algorithm to use when building overviews. Controls how pixels are combined to make lower res pixels in overviews. Can also be set in the upload_options parameter. overwrite : bool, optional If True, then permit overwriting of an existing image with the same id in the catalog. Defaults to False. Note that in all cases, the image object must have a state of ~earthdaily.earthone.catalog.DocumentState.UNSAVED. USE WITH CAUTION: This can cause data cache inconsistencies in the platform, and should only be used for infrequent needs to update the image file contents. You can expect inconsistencies to endure for a period afterwards.

Raises🔗

ValueError If any improper arguments are supplied. DeletedObjectError If this image was deleted.

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.ImageUpload An ~earthdaily.earthone.catalog.ImageUpload instance which can be used to check the status or wait on the asynchronous upload process to complete.

Source code in earthdaily/earthone/core/catalog/image.py
@check_deleted
def upload_ndarray(
    self,
    ndarray,
    upload_options=None,
    raster_meta=None,
    overviews=None,
    overview_resampler=None,
    overwrite=False,
):
    """Uploads imagery from an ndarray to be ingested as an Image.

    The Image must be in the state `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
    The `product` or `product_id` attribute, the `name` attribute, and the
    `acquired` attribute must all be set. Either (but not both) the `cs_code`
    or `projection` attributes must be set, or the `raster_meta` parameter must be provided.
    Similarly, either the `geotrans` attribute must be set or `raster_meta` must be provided.

    Note that one of the spatial reference attributes (`cs_code` or
    `projection`), or the `geotrans` attribute can be
    specified explicitly in the image, or the `raster_meta` parameter can be
    specified.  Likewise, `overviews` and `overview_resampler` can be
    specified explicitly, or via the `upload_options` parameter.


    Parameters
    ----------
    ndarray : np.array, Iterable(np.array)
        A numpy array or list of numpy arrays with image data, either with 2
        dimensions of shape ``(x, y)`` for a single band or with 3 dimensions of
        shape ``(band, x, y)`` for any number of bands.  If providing a 3d array
        the first dimension must index the bands.  The ``dtype`` of the array must
        also be one of the following:
        [``uint8``, ``int8``, ``uint16``, ``int16``, ``uint32``, ``int32``,
        ``uint64``, ``int64``, ``float16``, ``float32``, ``float64``]
    upload_options : :py:class:`~earthdaily.earthone.catalog.ImageUploadOptions`, optional
        Control of the upload process.
    raster_meta : dict, optional
        Metadata returned from the :meth:`Raster.ndarray()
        <earthdaily.earthone.client.services.raster.Raster.ndarray>` request which
        generated the initial data for the `ndarray` being uploaded.  Specifying
        `geotrans` and one of the spatial reference attributes (`cs_code` or
        `projection`) is unnecessary in this case but will take precedence over
        the value in `raster_meta`.
    overviews : list(int), optional
        Overview resolution magnification factors e.g.  [2, 4] would make two
        overviews at 2x and 4x the native resolution.  Maximum number of overviews
        allowed is 16.  Can also be set in the `upload_options` parameter.
    overview_resampler : `ResampleAlgorithm`, optional
        Resampler algorithm to use when building overviews.  Controls how pixels
        are combined to make lower res pixels in overviews. Can also be set in
        the `upload_options` parameter.
    overwrite : bool, optional
        If True, then permit overwriting of an existing image with the same id
        in the catalog. Defaults to False. Note that in all cases, the image
        object must have a state of `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
        USE WITH CAUTION: This can cause data cache inconsistencies in the platform,
        and should only be used for infrequent needs to update the image file
        contents. You can expect inconsistencies to endure for a period afterwards.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    DeletedObjectError
        If this image was deleted.

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.ImageUpload`
        An `~earthdaily.earthone.catalog.ImageUpload` instance which can
        be used to check the status or wait on the asynchronous upload process to
        complete.
    """
    from .image_upload import ImageUploadOptions, ImageUploadType

    if not self.id:
        raise ValueError("id field required")
    if not self.acquired:
        raise ValueError("acquired field required")
    if self.cs_code and self.projection:
        warnings.warn(
            "Only one of cs_code and projection fields permitted",
            FutureWarning,
        )
        # raise ValueError("only one of cs_code and projection fields permitted")

    if self.state != DocumentState.UNSAVED:
        raise ValueError(
            "Image {} has been saved. Please use an unsaved image for uploading".format(
                self.id
            )
        )

    if not overwrite and Image.exists(self.id, self._client):
        raise ValueError(
            "Image {} already exists in the catalog. Please either use a new image id or overwrite=True".format(
                self.id
            )
        )

    if self.product.state != DocumentState.SAVED:
        raise ValueError(
            "Product {} has not been saved. Please save before uploading images".format(
                self.product_id
            )
        )

    if isinstance(ndarray, (np.ndarray, np.generic)):
        ndarray = [ndarray]
    elif not isinstance(ndarray, abc.Iterable):
        raise ValueError(
            "The array must be an instance of ndarray or an Iterable of ndarrays"
            "such as a list."
        )

    # validate the shape of each ndarray
    # modify image data to shift axes to what ingest expects
    for idx, image_data in enumerate(ndarray):
        if not isinstance(image_data, (np.ndarray, np.generic)):
            raise ValueError(f"The item at index {idx} is not an ndarray")

        if len(image_data.shape) not in (2, 3):
            raise ValueError(
                "The array must have 2 dimensions (shape '(x, y)') or 3 dimensions with the band "
                "axis in the first dimension (shape '(band, x, y)'). The given array has shape "
                "'{}' instead.".format(image_data.shape)
            )

        if image_data.dtype.name not in self.SUPPORTED_DATATYPES:
            raise ValueError(
                "The array has an unsupported data type {}. Only the following data types are supported: {}".format(
                    image_data.dtype.name, ",".join(self.SUPPORTED_DATATYPES)
                )
            )

        if len(image_data.shape) == 3:
            scale_factor = 5
            scaled_band_dim = image_data.shape[0] * scale_factor

            if (
                scaled_band_dim > image_data.shape[1]
                or scaled_band_dim > image_data.shape[2]
            ):
                warnings.warn(
                    "The shape '{}' of the given 3d-array looks like it might not have the band "
                    "axis as the first dimension. Verify that your array conforms to the shape "
                    "'(band, x, y)'".format(image_data.shape)
                )
            # v1 ingest expects (X,Y,bands)
            ndarray[idx] = np.moveaxis(image_data, 0, -1)

    # default to raster_meta fields if not explicitly provided
    if raster_meta:
        if not self.geotrans:
            self.geotrans = raster_meta.get("geoTransform")
        if not self.cs_code and not self.projection:
            # doesn't yet exist!
            self.projection = raster_meta.get("coordinateSystem", {}).get("proj4")

    if not self.geotrans:
        raise ValueError("geotrans field or raster_meta parameter is required")
    if not self.cs_code and not self.projection:
        raise ValueError(
            "cs_code or projection field is required if "
            + "raster_meta parameter is not given"
        )

    if not upload_options:
        upload_options = ImageUploadOptions()
    upload_options.upload_type = ImageUploadType.NDARRAY
    if overviews:
        upload_options.overviews = overviews
    if overview_resampler:
        upload_options.overview_resampler = overview_resampler

    # write all the ndarrays to files so that _do_upload can read them
    files = []
    upload_size = 0

    try:
        for image_data in ndarray:
            upload_size += image_data.nbytes
            tmp = NamedTemporaryFile(delete=False)
            files.append(tmp)
            np.save(tmp, image_data, allow_pickle=False)
            # From tempfile docs:
            # Whether the name can be used to open the file a second time,
            # while the named temporary file is still open, varies across
            # platforms (it can be so used on Unix; it cannot on Windows
            # NT or later)
            # We close the underlying file object so _do_upload can open
            # the path again in a cross platform compatible way.
            # Cleanup is manual in the finally block.
            tmp.close()

        file_names = [f.name for f in files]
        upload_options.upload_size = upload_size
        upload_options.image_files = file_names

        return self._do_upload(file_names, upload_options)
    finally:
        for file in files:
            try:
                os.unlink(file.name)
            except OSError:
                pass

image_uploads 🔗

image_uploads()

A search query for all uploads for this image created by this user.

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.Search A 🇵🇾class:~earthdaily.earthone.catalog.Search instance configured to find all uploads for this image.

Source code in earthdaily/earthone/core/catalog/image.py
def image_uploads(self):
    """A search query for all uploads for this image created by this user.

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.Search`
        A :py:class:`~earthdaily.earthone.catalog.Search` instance configured to
        find all uploads for this image.
    """
    from .image_upload import ImageUpload

    return ImageUpload.search(client=self._client).filter(
        (properties.product_id == self.product_id)
        & (properties.image_id == self.id)
    )

coverage 🔗

coverage(geom)

The fraction of a geometry-like object covered by this Image's geometry.

Parameters🔗

geom : GeoJSON-like dict, :class:~earthdaily.earthone.common.geo.geocontext.GeoContext, or object with geo_interface Geometry to which to compare this Image's geometry

Returns🔗

coverage: float The fraction of geom's area that overlaps with this Image, between 0 and 1.

Example🔗

import earthdaily.earthone as eo image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1") # doctest: +SKIP image.coverage(image.geometry.buffer(1)) # doctest: +SKIP 0.258370644415335

Source code in earthdaily/earthone/core/catalog/image.py
def coverage(self, geom):
    """
    The fraction of a geometry-like object covered by this Image's geometry.

    Parameters
    ----------
    geom : GeoJSON-like dict, :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, or object with __geo_interface__
        Geometry to which to compare this Image's geometry

    Returns
    -------
    coverage: float
        The fraction of ``geom``'s area that overlaps with this Image,
        between 0 and 1.

    Example
    -------
    >>> import earthdaily.earthone as eo
    >>> image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1")  # doctest: +SKIP
    >>> image.coverage(image.geometry.buffer(1))  # doctest: +SKIP
    0.258370644415335
    """  # noqa: E501

    if isinstance(geom, GeoContext):
        shape = geom.geometry
    else:
        shape = geometry_like_to_shapely(geom)

    intersection = shape.intersection(self.geometry)
    return intersection.area / shape.area

ndarray 🔗

ndarray(bands, geocontext=None, crs=None, resolution=None, all_touched=None, mask_nodata=True, mask_alpha=None, bands_axis=0, raster_info=False, resampler=ResampleAlgorithm.NEAR, processing_level=None, scaling=None, data_type=None, progress=None, **kwargs)

Load bands from this image as an ndarray, optionally masking invalid data.

If the selected bands have different data types the resulting ndarray has the most general of those data types. This table defines which data types can be cast to which more general data types:

  • Byte to: UInt16, UInt32, UInt64, Int16, Int32, Int64, Float16, Float32, Float64
  • Int8 to: Int16, Int32, Int64, Float16, Float32, Float64
  • UInt16 to: UInt32, UInt64, Int32, Int64, Float32, Float64
  • UInt32 to: UInt64, Int64, Float64
  • UInt64 to: Float64
  • Int16 to: Int32, Int64, Float32, Float64
  • Int32 to: Int64, Float32, Float64
  • Float16 to: Float32, Float64
  • Float32 to: Float64
  • Float64 to: No possible casts
Parameters🔗

bands : str or Sequence[str] Band names to load. Can be a single string of band names separated by spaces ("red green blue"), or a sequence of band names (["red", "green", "blue"]). Names must be keys in self.properties.bands. If the alpha band is requested, it must be last in the list to reduce rasterization errors. geocontext : :class:~earthdaily.earthone.common.geo.geocontext.GeoContext, default None A :class:~earthdaily.earthone.common.geo.geocontext.GeoContext to use when loading this Image. If None then the default geocontext of the image will be used. crs : str, default None if not None, update the gecontext with this value to set the output CRS. resolution : float, default None if not None, update the geocontext with this value to set the output resolution in the units native to the specified or defaulted output CRS. all_touched : float, default None if not None, update the geocontext with this value to control rastering behavior. mask_nodata : bool, default True Whether to mask out values in each band that equal that band's nodata sentinel value. mask_alpha : bool or str or None, default None Whether to mask pixels in all bands where the alpha band of the image is 0. Provide a string to use an alternate band name for masking. If the alpha band is available and mask_alpha is None, mask_alpha is set to True. If not, mask_alpha is set to False. bands_axis : int, default 0 Axis along which bands should be located in the returned array. If 0, the array will have shape (band, y, x), if -1, it will have shape (y, x, band).

It's usually easier to work with bands as the outermost axis,
but when working with large arrays, or with many arrays concatenated
together, NumPy operations aggregating each xy point across bands
can be slightly faster with bands as the innermost axis.

raster_info : bool, default False Whether to also return a dict of information about the rasterization of the image, including the coordinate system WKT and geotransform matrix. Generally only useful if you plan to upload data derived from this image back to the EarthOne catalog, or use it with GDAL. resampler : ResampleAlgorithm, default ResampleAlgorithm.NEAR Algorithm used to interpolate pixel values when scaling and transforming the image to its new resolution or CRS. processing_level : str, optional How the processing level of the underlying data should be adjusted. Possible values depend on the product and bands in use. Legacy products support toa (top of atmosphere) and in some cases surface. Consult the available processing_levels in the product bands to understand what is available. scaling : None, str, list, dict Band scaling specification. Please see :meth:scaling_parameters for a full description of this parameter. data_type : None, str Output data type. Please see :meth:scaling_parameters for a full description of this parameter. progress : None, bool Controls display of a progress bar.

Returns🔗

arr : ndarray Returned array's shape will be (band, y, x) if bands_axis is 0, (y, x, band) if bands_axis is -1. If mask_nodata or mask_alpha is True, arr will be a masked array. The data type ("dtype") of the array is the most general of the data types among the bands being rastered. raster_info : dict If raster_info=True, a raster information dict is also returned.

Example🔗

import earthdaily.earthone as eo image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1") # doctest: +SKIP arr = image.ndarray("red green blue", resolution=120.) # doctest: +SKIP type(arr) # doctest: +SKIP arr.shape # doctest: +SKIP (3, 1995, 1962) red_band = arr[0] # doctest: +SKIP

Raises🔗

ValueError If requested bands are unavailable. If band names are not given or are invalid. If the requested bands have incompatible dtypes. NotFoundError If a Image's ID cannot be found in the EarthOne catalog BadRequestError If the EarthOne Platform is given invalid parameters

Source code in earthdaily/earthone/core/catalog/image.py
def ndarray(
    self,
    bands,
    geocontext=None,
    crs=None,
    resolution=None,
    all_touched=None,
    mask_nodata=True,
    mask_alpha=None,
    bands_axis=0,
    raster_info=False,
    resampler=ResampleAlgorithm.NEAR,
    processing_level=None,
    scaling=None,
    data_type=None,
    progress=None,
    **kwargs,
):
    """
    Load bands from this image as an ndarray, optionally masking invalid data.

    If the selected bands have different data types the resulting ndarray
    has the most general of those data types. This table defines which data types
    can be cast to which more general data types:

    * ``Byte`` to: ``UInt16``, ``UInt32``, ``UInt64``, ``Int16``, ``Int32``, ``Int64``,
      ``Float16``, ``Float32``, ``Float64``
    * ``Int8`` to: ``Int16``, ``Int32``, ``Int64``, ``Float16``, ``Float32``, ``Float64``
    * ``UInt16`` to: ``UInt32``, ``UInt64``, ``Int32``, ``Int64``, ``Float32``, ``Float64``
    * ``UInt32`` to: ``UInt64``, ``Int64``, ``Float64``
    * ``UInt64`` to: ``Float64``
    * ``Int16`` to: ``Int32``, ``Int64``, ``Float32``, ``Float64``
    * ``Int32`` to: ``Int64``, ``Float32``, ``Float64``
    * ``Float16`` to: ``Float32``, ``Float64``
    * ``Float32`` to: ``Float64``
    * ``Float64`` to: No possible casts

    Parameters
    ----------
    bands : str or Sequence[str]
        Band names to load. Can be a single string of band names
        separated by spaces (``"red green blue"``),
        or a sequence of band names (``["red", "green", "blue"]``).
        Names must be keys in ``self.properties.bands``.
        If the alpha band is requested, it must be last in the list
        to reduce rasterization errors.
    geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
        A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading this Image.
        If ``None`` then the default geocontext of the image will be used.
    crs : str, default None
        if not None, update the gecontext with this value to set the output CRS.
    resolution : float, default None
        if not None, update the geocontext with this value to set the output resolution
        in the units native to the specified or defaulted output CRS.
    all_touched : float, default None
        if not None, update the geocontext with this value to control rastering behavior.
    mask_nodata : bool, default True
        Whether to mask out values in each band that equal
        that band's ``nodata`` sentinel value.
    mask_alpha : bool or str or None, default None
        Whether to mask pixels in all bands where the alpha band of the image is 0.
        Provide a string to use an alternate band name for masking.
        If the alpha band is available and ``mask_alpha`` is None, ``mask_alpha``
        is set to True. If not, mask_alpha is set to False.
    bands_axis : int, default 0
        Axis along which bands should be located in the returned array.
        If 0, the array will have shape ``(band, y, x)``, if -1,
        it will have shape ``(y, x, band)``.

        It's usually easier to work with bands as the outermost axis,
        but when working with large arrays, or with many arrays concatenated
        together, NumPy operations aggregating each xy point across bands
        can be slightly faster with bands as the innermost axis.
    raster_info : bool, default False
        Whether to also return a dict of information about the rasterization
        of the image, including the coordinate system WKT and geotransform matrix.
        Generally only useful if you plan to upload data derived
        from this image back to the EarthOne catalog, or use it with GDAL.
    resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
        Algorithm used to interpolate pixel values when scaling and transforming
        the image to its new resolution or CRS.
    processing_level : str, optional
        How the processing level of the underlying data should be adjusted. Possible
        values depend on the product and bands in use. Legacy products support
        ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
        available ``processing_levels`` in the product bands to understand what
        is available.
    scaling : None, str, list, dict
        Band scaling specification. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    data_type : None, str
        Output data type. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    progress : None, bool
        Controls display of a progress bar.

    Returns
    -------
    arr : ndarray
        Returned array's shape will be ``(band, y, x)`` if bands_axis is 0,
        ``(y, x, band)`` if bands_axis is -1.
        If ``mask_nodata`` or ``mask_alpha`` is True, arr will be a masked array.
        The data type ("dtype") of the array is the most general of the data
        types among the bands being rastered.
    raster_info : dict
        If ``raster_info=True``, a raster information dict is also returned.

    Example
    -------
    >>> import earthdaily.earthone as eo
    >>> image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1")  # doctest: +SKIP
    >>> arr = image.ndarray("red green blue", resolution=120.)  # doctest: +SKIP
    >>> type(arr)  # doctest: +SKIP
    <class 'numpy.ma.core.MaskedArray'>
    >>> arr.shape  # doctest: +SKIP
    (3, 1995, 1962)
    >>> red_band = arr[0]  # doctest: +SKIP

    Raises
    ------
    ValueError
        If requested bands are unavailable.
        If band names are not given or are invalid.
        If the requested bands have incompatible dtypes.
    NotFoundError
        If a Image's ID cannot be found in the EarthOne catalog
    BadRequestError
        If the EarthOne Platform is given invalid parameters
    """
    if geocontext is None:
        # Lose the image's geometry (which is only used as a cutline),
        # as it might cause some unexpected clipping when rasterizing, due
        # to imperfect simplified geometries used when native image CRS is not WGS84.
        geocontext = self.geocontext.assign(geometry=None)
    if crs is not None or resolution is not None:
        try:
            params = {}
            if crs is not None:
                params["crs"] = crs
            if resolution is not None:
                params["resolution"] = resolution
            geocontext = geocontext.assign(**params)
        except TypeError:
            raise ValueError(
                f"{type(geocontext)} geocontext does not support modifying crs or resolution"
            ) from None
    if all_touched is not None:
        geocontext = geocontext.assign(all_touched=all_touched)

    return self._ndarray(
        bands,
        geocontext,
        mask_nodata=mask_nodata,
        mask_alpha=mask_alpha,
        bands_axis=bands_axis,
        raster_info=raster_info,
        resampler=resampler,
        processing_level=processing_level,
        scaling=scaling,
        data_type=data_type,
        progress=progress,
        **kwargs,
    )

download 🔗

download(bands, geocontext=None, crs=None, resolution=None, all_touched=None, dest=None, format=DownloadFileFormat.TIF, resampler=ResampleAlgorithm.NEAR, processing_level=None, scaling=None, data_type=None, nodata=None, progress=None)

Save bands from this image as a GeoTIFF, PNG, or JPEG, writing to a path.

Parameters🔗

bands : str or Sequence[str] Band names to load. Can be a single string of band names separated by spaces ("red green blue"), or a sequence of band names (["red", "green", "blue"]). Names must be keys in self.properties.bands. geocontext : :class:~earthdaily.earthone.common.geo.geocontext.GeoContext, default None A :class:~earthdaily.earthone.common.geo.geocontext.GeoContext to use when loading this image. If None then use the default context for the image. crs : str, default None if not None, update the gecontext with this value to set the output CRS. resolution : float, default None if not None, update the geocontext with this value to set the output resolution in the units native to the specified or defaulted output CRS. all_touched : float, default None if not None, update the geocontext with this value to control rastering behavior. dest : str or path-like object, default None Where to write the image file.

* If None (default), it's written to an image file of the given ``format``
  in the current directory, named by the image's ID and requested bands,
  like ``"sentinel-2:L1C:2018-08-10_10TGK_68_S2A_v1-red-green-blue.tif"``
* If a string or path-like object, it's written to that path.

  Any file already existing at that path will be overwritten.

  Any intermediate directories will be created if they don't exist.

  Note that path-like objects (such as pathlib.Path) are only supported
  in Python 3.6 or later.

format : DownloadFileFormat, default DownloadFileFormat.TIF Output file format to use If a str or path-like object is given as dest, format is ignored and determined from the extension on the path (one of ".tif", ".png", or ".jpg"). resampler : ResampleAlgorithm, default ResampleAlgorithm.NEAR Algorithm used to interpolate pixel values when scaling and transforming the image to its new resolution or SRS. processing_level : str, optional How the processing level of the underlying data should be adjusted. Possible values depend on the product and bands in use. Legacy products support toa (top of atmosphere) and in some cases surface. Consult the available processing_levels in the product bands to understand what is available. scaling : None, str, list, dict Band scaling specification. Please see :meth:scaling_parameters for a full description of this parameter. data_type : None, str Output data type. Please see :meth:scaling_parameters for a full description of this parameter. nodata : None, number NODATA value for a geotiff file. Will be assigned to any masked pixels. progress : None, bool Controls display of a progress bar.

Returns🔗

path : str or None If dest is None or a path, the path where the image file was written is returned. If dest is file-like, nothing is returned.

Example🔗

import earthdaily.earthone as eo image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1") # doctest: +SKIP image.download("red green blue", resolution=120.) # doctest: +SKIP "landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1_red-green-blue.tif" import os os.listdir(".") # doctest: +SKIP ["landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1_red-green-blue.tif"] image.download( ... "nir swir1", ... "rasters/{geocontext.resolution}-{image_id}.jpg".format(geocontext=image.geocontext, image_id=image.id) ... ) # doctest: +SKIP "rasters/15-landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1.tif"

Raises🔗

ValueError If requested bands are unavailable. If band names are not given or are invalid. If the requested bands have incompatible dtypes. If format is invalid, or the path has an invalid extension. NotFoundError If a image's ID cannot be found in the EarthOne catalog BadRequestError If the EarthOne Platform is given invalid parameters

Source code in earthdaily/earthone/core/catalog/image.py
def download(
    self,
    bands,
    geocontext=None,
    crs=None,
    resolution=None,
    all_touched=None,
    dest=None,
    format=DownloadFileFormat.TIF,
    resampler=ResampleAlgorithm.NEAR,
    processing_level=None,
    scaling=None,
    data_type=None,
    nodata=None,
    progress=None,
):
    """
    Save bands from this image as a GeoTIFF, PNG, or JPEG, writing to a path.

    Parameters
    ----------
    bands : str or Sequence[str]
        Band names to load. Can be a single string of band names
        separated by spaces (``"red green blue"``),
        or a sequence of band names (``["red", "green", "blue"]``).
        Names must be keys in ``self.properties.bands``.
    geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
        A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading this image.
        If ``None`` then use the default context for the image.
    crs : str, default None
        if not None, update the gecontext with this value to set the output CRS.
    resolution : float, default None
        if not None, update the geocontext with this value to set the output resolution
        in the units native to the specified or defaulted output CRS.
    all_touched : float, default None
        if not None, update the geocontext with this value to control rastering behavior.
    dest : str or path-like object, default None
        Where to write the image file.

        * If None (default), it's written to an image file of the given ``format``
          in the current directory, named by the image's ID and requested bands,
          like ``"sentinel-2:L1C:2018-08-10_10TGK_68_S2A_v1-red-green-blue.tif"``
        * If a string or path-like object, it's written to that path.

          Any file already existing at that path will be overwritten.

          Any intermediate directories will be created if they don't exist.

          Note that path-like objects (such as pathlib.Path) are only supported
          in Python 3.6 or later.
    format : `DownloadFileFormat`, default `DownloadFileFormat.TIF`
        Output file format to use
        If a str or path-like object is given as ``dest``, ``format`` is ignored
        and determined from the extension on the path (one of ".tif", ".png", or ".jpg").
    resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
        Algorithm used to interpolate pixel values when scaling and transforming
        the image to its new resolution or SRS.
    processing_level : str, optional
        How the processing level of the underlying data should be adjusted. Possible
        values depend on the product and bands in use. Legacy products support
        ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
        available ``processing_levels`` in the product bands to understand what
        is available.
    scaling : None, str, list, dict
        Band scaling specification. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    data_type : None, str
        Output data type. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    nodata : None, number
        NODATA value for a geotiff file. Will be assigned to any masked pixels.
    progress : None, bool
        Controls display of a progress bar.

    Returns
    -------
    path : str or None
        If ``dest`` is None or a path, the path where the image file was written is returned.
        If ``dest`` is file-like, nothing is returned.

    Example
    -------
    >>> import earthdaily.earthone as eo
    >>> image = eo.catalog.Image.get("landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1")  # doctest: +SKIP
    >>> image.download("red green blue", resolution=120.)  # doctest: +SKIP
    "landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1_red-green-blue.tif"
    >>> import os
    >>> os.listdir(".")  # doctest: +SKIP
    ["landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1_red-green-blue.tif"]
    >>> image.download(
    ...     "nir swir1",
    ...     "rasters/{geocontext.resolution}-{image_id}.jpg".format(geocontext=image.geocontext, image_id=image.id)
    ... )  # doctest: +SKIP
    "rasters/15-landsat:LC08:PRE:TOAR:meta_LC80270312016188_v1.tif"

    Raises
    ------
    ValueError
        If requested bands are unavailable.
        If band names are not given or are invalid.
        If the requested bands have incompatible dtypes.
        If ``format`` is invalid, or the path has an invalid extension.
    NotFoundError
        If a image's ID cannot be found in the EarthOne catalog
    BadRequestError
        If the EarthOne Platform is given invalid parameters
    """
    if geocontext is None:
        # Lose the image's geometry (which is only used as a cutline),
        # as it might cause some unexpected clipping when rasterizing, due
        # to imperfect simplified geometries used when native image CRS is not WGS84.
        geocontext = self.geocontext.assign(geometry=None)
    if crs is not None or resolution is not None:
        try:
            params = {}
            if crs is not None:
                params["crs"] = crs
            if resolution is not None:
                params["resolution"] = resolution
            geocontext = geocontext.assign(**params)
        except TypeError:
            raise ValueError(
                f"{type(geocontext)} geocontext does not support modifying crs or resolution"
            ) from None
    if all_touched is not None:
        geocontext = geocontext.assign(all_touched=all_touched)

    return self._download(
        bands,
        geocontext,
        dest=dest,
        format=format,
        resampler=resampler,
        processing_level=processing_level,
        scaling=scaling,
        data_type=data_type,
        nodata=nodata,
        progress=progress,
    )

scaling_parameters 🔗

scaling_parameters(bands, processing_level=None, scaling=None, data_type=None)

Computes fully defaulted scaling parameters and output data_type from provided specifications.

This method makes accessible the scales and data_type parameters which will be generated and passed to the Raster API by methods such as :meth:ndarray and :meth:download. It is provided as a convenience to the user to aid in understanding how the scaling and data_type parameters will be handled by those methods. It would not usually be used in a normal workflow.

Parameters🔗

bands : list List of bands to be scaled. processing_level : str, optional How the processing level of the underlying data should be adjusted. Possible values depend on the product and bands in use. Legacy products support toa (top of atmosphere) and in some cases surface. Consult the available processing_levels in the product bands to understand what is available. scaling : None or str or list or dict, default None Supplied scaling specification, see below. data_type : None or str, default None Result data type desired, as a standard data type string (e.g. "Byte", "Uint16", or "Float64"). If not specified, will be deduced from the scaling specification. Typically this is left unset and the appropriate type will be determined automatically.

Returns🔗

scales : list(tuple) The fully specified scaling parameter, compatible with the :class:~earthdaily.earthone.client.services.raster.Raster API and the output data type. data_type : str The result data type as a standard GDAL type string.

Raises🔗

ValueError If any invalid or incompatible value is passed to any of the three parameters.

Scaling is determined on a band-by-band basis, incorporating the user provided specification, the output data_type, and properties for the band, such as the band type, the band data type, and the default_range, data_range, and physical_range properties. Ultimately the scaling for each band will be resolved to either None or a tuple of numeric values of length 0, 2, or 4, as accepted by the Raster API. The result is a list (with length equal to the number of bands) of one of these values, or may be a None value which is just a shorthand equivalent for a list of None values.

A None indicates that no scaling should be performed.

A 0-tuple () indicates that the band data should be automatically scaled from the minimum and maximum values present in the image data to the display range 0-255.

A 2-tuple (input-min, input-max) indicates that the band data should be scaled from the specified input range to the display range of 0-255.

A 4-tuple (input-min, input-max, output-min, output-max) indicates that the band data should be scaled from the input range to the output range.

In all cases, the scaling will be performed as a multiply and add, and the resulting values are only clipped as necessary to fit in the output data type. As such, if the input and output ranges are the same, it is effectively a no-op equivalent to None.

The support for scaling parameters in the Catalog API includes the concept of an automated scaling mode. The four supported modes are as follows.

"raw": Equivalent to a None, the data should not be scaled. "auto": Equivalent to a 0-tuple, the data should be scaled by the Raster service so that the actual range of data in the input is scaled up to the full display range (0-255). It is not possible to determine the bounds of this input range in the client as the actual band data is not accessible. "display": The data should be scaled from any specified input bounds, defaulting to the default_range property for the band, to the output range, defaulting to 0-255. "physical": The data should be scaled from the input range, defaulting to the data_range property for the band, to the output range, defaulting to the physical_range property for the band.

The mode may be explicitly specified, or it may be determined implicitly from other characteristics such as the length and contents of the tuples for each band, or from the output data_type if this is explicitly specified (e.g. "Byte" implies display mode, "Float64" implies physical mode).

If it is not possible to infer the mode, and a mode is required in order to fully determine the results of this method, an error will be raised. It is also an error to explicitly specify more than one mode, with several exceptions: auto and display mode are compatible, while a raw display mode for a band which is of type "mask" or type "class" does not conflict with any other mode specification.

Normally the data_type parameter is not provided by the user, and is instead determined from the mode as follows.

"raw": The data type that best matches the data types of all the bands, preserving the precision and range of the original data. "auto" and "display": "Byte" "physical": "Float64"

The scaling parameter passed to this method can be any of the following:

None

No scaling for all bands. Equivalent to [None, ...].

str: Any of the four supported automatic modes as described above. list or Iterable: A list or similar iterable must contain a number of elements equal to the number of bands specified. Each element must either be a None, a 0-, 2-, or 4-tuple, or one of the above four automatic mode strings. The elements of each tuple must either be a numeric value or a string containing a valid numerical string followed by a "%" character. The latter will be interpreted as a percentage of the appropriate range (e.g. default_range, data_range, or physical_range) according to the mode. For example, a tuple of ("25%", "75%") with a default_range of [0, 4000] will yield (1000, 3000). dict or Mapping: A dictionary or similar mapping with keys corresponding to band names and values as accepted as elements for each band as with a list described above. Each band name is used to lookup a value in the mapping. If none is found, and the band is not of type "mask" or "class", then the special key "default_" is looked up in the mapping if it exists. Otherwise a value of None will be used for the band. This is strictly a convenience for constructing a list of scale values, one for each band, but can be useful if a single general-purpose mapping is defined for all possible or relevant bands and then reused across many calls to the different methods in the Catalog API which accept a scaling parameter.

See Also🔗

:doc:Catalog Guide <guides/catalog> : This contains many examples of the use of the scaling and data_type parameters.

Source code in earthdaily/earthone/core/catalog/image.py
def scaling_parameters(
    self, bands, processing_level=None, scaling=None, data_type=None
):
    """
    Computes fully defaulted scaling parameters and output data_type
    from provided specifications.

    This method makes accessible the scales and data_type parameters
    which will be generated and passed to the Raster API by methods
    such as :meth:`ndarray` and :meth:`download`. It is provided
    as a convenience to the user to aid in understanding how the
    ``scaling`` and ``data_type`` parameters will be handled by
    those methods. It would not usually be used in a normal workflow.

    Parameters
    ----------
    bands : list
        List of bands to be scaled.
    processing_level : str, optional
        How the processing level of the underlying data should be adjusted. Possible
        values depend on the product and bands in use. Legacy products support
        ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
        available ``processing_levels`` in the product bands to understand what
        is available.
    scaling : None or str or list or dict, default None
        Supplied scaling specification, see below.
    data_type : None or str, default None
        Result data type desired, as a standard data type string (e.g.
        ``"Byte"``, ``"Uint16"``, or ``"Float64"``). If not specified,
        will be deduced from the ``scaling`` specification. Typically
        this is left unset and the appropriate type will be determined
        automatically.

    Returns
    -------
    scales : list(tuple)
        The fully specified scaling parameter, compatible with the
        :class:`~earthdaily.earthone.client.services.raster.Raster` API and the
        output data type.
    data_type : str
        The result data type as a standard GDAL type string.

    Raises
    ------
    ValueError
        If any invalid or incompatible value is passed to any of the
        three parameters.


    Scaling is determined on a band-by-band basis, incorporating the user
    provided specification, the output data_type, and properties for the
    band, such as the band type, the band data type, and the
    ``default_range``, ``data_range``, and ``physical_range`` properties.
    Ultimately the scaling for each band will be resolved to either
    ``None`` or a tuple of numeric values of length 0, 2, or 4, as
    accepted by the Raster API. The result is a list (with length equal
    to the number of bands) of one of these values, or may be a None
    value which is just a shorthand equivalent for a list of None values.

    A ``None`` indicates that no scaling should be performed.

    A 0-tuple ``()`` indicates that the band data should be automatically
    scaled from the minimum and maximum values present in the image data
    to the display range 0-255.

    A 2-tuple ``(input-min, input-max)`` indicates that the band data
    should be scaled from the specified input range to the display
    range of 0-255.

    A 4-tuple ``(input-min, input-max, output-min, output-max)``
    indicates that the band data should be scaled from the input range
    to the output range.

    In all cases, the scaling will be performed as a multiply and add,
    and the resulting values are only clipped as necessary to fit in
    the output data type. As such, if the input and output ranges are
    the same, it is effectively a no-op equivalent to ``None``.

    The support for scaling parameters in the Catalog API includes
    the concept of an automated scaling mode. The four supported modes
    are as follows.

    ``"raw"``:
        Equivalent to a ``None``, the data should not be scaled.
    ``"auto"``:
        Equivalent to a 0-tuple, the data should be scaled by
        the Raster service so that the actual range of data in the
        input is scaled up to the full display range (0-255). It
        is not possible to determine the bounds of this input range
        in the client as the actual band data is not accessible.
    ``"display"``:
        The data should be scaled from any specified input bounds,
        defaulting to the ``default_range`` property for the band,
        to the output range, defaulting to 0-255.
    ``"physical"``:
        The data should be scaled from the input range, defaulting
        to the ``data_range`` property for the band, to the output
        range, defaulting to the ``physical_range`` property for
        the band.

    The mode may be explicitly specified, or it may be determined
    implicitly from other characteristics such as the length
    and contents of the tuples for each band, or from the output
    data_type if this is explicitly specified (e.g. ``"Byte"``
    implies display mode, ``"Float64"`` implies physical mode).

    If it is not possible to infer the mode, and a mode is required
    in order to fully determine the results of this method, an
    error will be raised. It is also an error to explicitly
    specify more than one mode, with several exceptions: auto
    and display mode are compatible, while a raw display mode
    for a band which is of type "mask" or type "class" does
    not conflict with any other mode specification.

    Normally the ``data_type`` parameter is not provided by the
    user, and is instead determined from the mode as follows.

    ``"raw"``:
        The data type that best matches the data types of all
        the bands, preserving the precision and range of the
        original data.
    ``"auto"`` and ``"display"``:
        ``"Byte"``
    ``"physical"``:
        ``"Float64"``

    The ``scaling`` parameter passed to this method can be any
    of the following:

    None:
        No scaling for all bands. Equivalent to ``[None, ...]``.
    str:
        Any of the four supported automatic modes as
        described above.
    list or Iterable:
        A list or similar iterable must contain a number of
        elements equal to the number of bands specified. Each
        element must either be a None, a 0-, 2-, or 4-tuple, or
        one of the above four automatic mode strings. The
        elements of each tuple must either be a numeric value
        or a string containing a valid numerical string followed
        by a "%" character. The latter will be interpreted as a
        percentage of the appropriate range (e.g. ``default_range``,
        ``data_range``, or ``physical_range``) according to the mode.
        For example, a tuple of ``("25%", "75%")`` with a
        ``default_range`` of ``[0, 4000]`` will yield ``(1000, 3000)``.
    dict or Mapping:
        A dictionary or similar mapping with keys corresponding to
        band names and values as accepted as elements for each band
        as with a list described above. Each band name is used to
        lookup a value in the mapping. If none is found, and the
        band is not of type "mask" or "class", then the special
        key ``"default_"`` is looked up in the mapping if it exists.
        Otherwise a value of ``None`` will be used for the band.
        This is strictly a convenience for constructing a list of
        scale values, one for each band, but can be useful if a
        single general-purpose mapping is defined for all possible
        or relevant bands and then reused across many calls to the
        different methods in the Catalog API which accept a ``scaling``
        parameter.

    See Also
    --------
    :doc:`Catalog Guide <guides/catalog>` : This contains many examples of the use of
    the ``scaling`` and ``data_type`` parameters.
    """
    bands = bands_to_list(bands)
    return scaling_parameters(
        cached_bands_by_product(self.product_id, self._client),
        bands,
        processing_level,
        scaling,
        data_type,
    )

ImageCollection🔗

ImageCollection 🔗

Bases: Collection

Holds Images, with methods for loading their data.

As a subclass of Collection, the filter, map, and groupby methods and each property simplify inspection and subselection of contained Images.

stack and mosaic rasterize all contained images into an ndarray using the a :class:~earthdaily.earthone.common.geo.geocontext.GeoContext.

Source code in earthdaily/earthone/core/catalog/image_collection.py
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
class ImageCollection(Collection):
    """
    Holds Images, with methods for loading their data.

    As a subclass of `Collection`, the `filter`, `map`, and `groupby`
    methods and `each` property simplify inspection and subselection of
    contained Images.

    `stack` and `mosaic` rasterize all contained images into an ndarray
    using the a :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
    """

    # _item_type set below due to circular imports

    def __init__(self, iterable=None, geocontext=None):
        super(ImageCollection, self).__init__(iterable)

        # try to form a default context
        if geocontext is not None:
            if not isinstance(geocontext, GeoContext):
                geocontext = AOI(geometry=geocontext)

        if len(self) > 0 and isinstance(geocontext, AOI):
            # possibly update from contained images
            if geocontext.crs is None:
                crs = collections.Counter(
                    i.cs_code or i.projection for i in self._list
                ).most_common(1)[0][0]
                geocontext = geocontext.assign(crs=crs)

            if geocontext.resolution is None and geocontext.shape is None:
                product_bands = self._product_bands()

                # The resolution must be in the same units as the CRS. However,
                # we don't have any means here to determine the units of the CRS.
                # Instead the best we can do is trust the band resolution definitions.
                resolution = None

                # gather up all the resolutions for all the bands
                resolutions = [
                    band.resolution
                    for product_id in product_bands
                    for band in product_bands[product_id].values()
                    if band.resolution is not None and band.resolution.value
                ]

                if resolutions:
                    # determine whether degrees or meters is more common
                    unit_counter = collections.Counter(
                        resolution.unit
                        for resolution in resolutions
                        if resolution.unit is not None
                    )
                    if len(unit_counter) > 0:
                        unit = unit_counter.most_common(1)[0][0]
                    else:
                        unit = ResolutionUnit.METERS

                    # define factors to convert to most common unit
                    if unit == ResolutionUnit.DEGREES:
                        factors = {
                            ResolutionUnit.METERS: 1 / 111111,
                            ResolutionUnit.DEGREES: 1,
                        }
                    else:
                        factors = {
                            ResolutionUnit.METERS: 1,
                            ResolutionUnit.DEGREES: 111111,
                        }

                    # find the minimum of all values
                    values = (
                        resolution.value
                        * factors[resolution.unit or ResolutionUnit.METERS]
                        for resolution in resolutions
                    )
                    resolution = min(values)

                geocontext = geocontext.assign(resolution=resolution)

        self._geocontext = geocontext

    @property
    def _client(self):
        # pick a client, any client. Sure hope they're all the same
        return self._list[0]._client

    @property
    def geocontext(self):
        return self._geocontext

    def filter_coverage(self, geom, minimum_coverage=1):
        """
        Include only images overlapping with ``geom`` by some fraction.

        See `Image.coverage <earthdaily.earthone.catalog.image.Image.coverage>`
        for getting coverage information for an image.

        Parameters
        ----------
        geom : GeoJSON-like dict, :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, or object with __geo_interface__  # noqa: E501
            Geometry to which to compare each image's geometry.
        minimum_coverage : float
            Only include images that cover ``geom`` by at least this fraction.

        Returns
        -------
        images : ImageCollection

        Example
        -------
        >>> import earthdaily.earthone as eo
        >>> aoi_geometry = {
        ...    'type': 'Polygon',
        ...    'coordinates': [[[-95, 42],[-93, 42],[-93, 40],[-95, 41],[-95, 42]]]}
        >>> product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR")  # doctest: +SKIP
        >>> images = product.images().intersects(aoi_geometry).limit(20).collect()  # doctest: +SKIP
        >>> filtered_images = images.filter_coverage(images.geocontext, 0.01)  # doctest: +SKIP
        >>> assert len(filtered_images) < len(images)  # doctest: +SKIP
        """

        return self.filter(lambda i: i.coverage(geom) >= minimum_coverage)

    def stack(
        self,
        bands,
        geocontext=None,
        crs=None,
        resolution=None,
        all_touched=None,
        flatten=None,
        mask_nodata=True,
        mask_alpha=None,
        bands_axis=1,
        raster_info=False,
        resampler=ResampleAlgorithm.NEAR,
        processing_level=None,
        scaling=None,
        data_type=None,
        progress=None,
        max_workers=None,
        **kwargs,
    ):
        """
        Load bands from all images and stack them into a 4D ndarray,
        optionally masking invalid data.

        If the selected bands and images have different data types the resulting
        ndarray has the most general of those data types. See
        `Image.ndarray() <earthdaily.earthone.catalog.image.Image.ndarray>` for details
        on data type conversions.

        Parameters
        ----------
        bands : str or Sequence[str]
            Band names to load. Can be a single string of band names
            separated by spaces (``"red green blue"``),
            or a sequence of band names (``["red", "green", "blue"]``).
            If the alpha band is requested, it must be last in the list
            to reduce rasterization errors.
        geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
            A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading each image.
            If ``None`` then the default context of the collection will be used. If
            this is ``None``, a ValueError is raised.
        crs : str, default None
            if not None, update the gecontext with this value to set the output CRS.
        resolution : float, default None
            if not None, update the geocontext with this value to set the output resolution
            in the units native to the specified or defaulted output CRS.
        all_touched : float, default None
            if not None, update the geocontext with this value to control rastering behavior.
        flatten : str, Sequence[str], callable, or Sequence[callable], default None
            "Flatten" groups of images in the stack into a single layer by mosaicking
            each group (such as images from the same day), then stacking the mosaics.

            ``flatten`` takes the same predicates as `Collection.groupby`, such as
            ``"properties.date"`` to mosaic images acquired at the exact same timestamp,
            or ``["properties.date.year", "properties.date.month", "properties.date.day"]``
            to combine images captured on the same day (but not necessarily the same time).

            This is especially useful when ``geocontext`` straddles an image boundary
            and contains one image captured right after another. Instead of having
            each as a separate layer in the stack, you might want them combined.

            Note that indicies in the returned ndarray will no longer correspond to
            indicies in this ImageCollection, since multiple images may be combined into
            one layer in the stack. You can call ``groupby`` on this ImageCollection
            with the same parameters to iterate through groups of images in equivalent
            order to the returned ndarray.

            Additionally, the order of images in the ndarray will change:
            they'll be sorted by the parameters to ``flatten``.
        mask_nodata : bool, default True
            Whether to mask out values in each band of each image that equal
            that band's ``nodata`` sentinel value.
        mask_alpha : bool or str or None, default None
            Whether to mask pixels in all bands where the alpha band of all images is 0.
            Provide a string to use an alternate band name for masking.
            If the alpha band is available for all images in the collection and
            ``mask_alpha`` is None, ``mask_alpha`` is set to True. If not,
            mask_alpha is set to False.
        bands_axis : int, default 1
            Axis along which bands should be located.
            If 1, the array will have shape ``(image, band, y, x)``, if -1,
            it will have shape ``(image, y, x, band)``, etc.
            A bands_axis of 0 is currently unsupported.
        raster_info : bool, default False
            Whether to also return a list of dicts about the rasterization of
            each image, including the coordinate system WKT and geotransform matrix.
            Generally only useful if you plan to upload data derived from this
            image back to the EarthOne catalog, or use it with GDAL.
        resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
            Algorithm used to interpolate pixel values when scaling and transforming
            each image to its new resolution or SRS.
        processing_level : str, optional
            How the processing level of the underlying data should be adjusted. Possible
            values depend on the product and bands in use. Legacy products support
            ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
            available ``processing_levels`` in the product bands to understand what
            is available.
        scaling : None, str, list, dict
            Band scaling specification. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        data_type : None, str
            Output data type. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        progress : None, bool
            Controls display of a progress bar.
        max_workers : int, default None
            Maximum number of threads to use to parallelize individual ndarray
            calls to each image.
            If None, it defaults to the number of processors on the machine,
            multiplied by 5.
            Note that unnecessary threads *won't* be created if ``max_workers``
            is greater than the number of images in the ImageCollection.

        Returns
        -------
        arr : ndarray
            Returned array's shape is ``(image, band, y, x)`` if bands_axis is 1,
            or ``(image, y, x, band)`` if bands_axis is -1.
            If ``mask_nodata`` or ``mask_alpha`` is True, arr will be a masked array.
            The data type ("dtype") of the array is the most general of the data
            types among the images being rastered.
        raster_info : List[dict]
            If ``raster_info=True``, a list of raster information dicts for each image
            is also returned

        Raises
        ------
        ValueError
            If requested bands are unavailable, or band names are not given
            or are invalid.
            If the context is None and no default context for the ImageCollection
            is defined, or if not all required parameters are specified in the
            :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
            If the ImageCollection is empty.
        NotFoundError
            If a Image's ID cannot be found in the EarthOne catalog
        BadRequestError
            If the EarthOne Platform is given unrecognized parameters
        """
        if len(self) == 0:
            raise ValueError("This ImageCollection is empty")

        if geocontext is None:
            geocontext = self.geocontext
            if geocontext is None:
                raise ValueError(
                    "No geocontext supplied, and no default geocontext is defined for this ImageCollection"
                )
        if crs is not None or resolution is not None:
            try:
                params = {}
                if crs is not None:
                    params["crs"] = crs
                if resolution is not None:
                    params["resolution"] = resolution
                geocontext = geocontext.assign(**params)
            except TypeError:
                raise ValueError(
                    f"{type(geocontext)} geocontext does not support modifying crs or resolution"
                ) from None
        if all_touched is not None:
            geocontext = geocontext.assign(all_touched=all_touched)

        kwargs = dict(
            mask_nodata=mask_nodata,
            mask_alpha=mask_alpha,
            bands_axis=bands_axis,
            raster_info=raster_info,
            resampler=resampler,
            processing_level=processing_level,
            progress=progress,
            **kwargs,
        )

        if bands_axis == 0 or bands_axis == -4:
            raise NotImplementedError(
                "bands_axis of 0 is currently unsupported for `ImageCollection.stack`. "
                "If you require this shape, try ``np.moveaxis(my_stack, 1, 0)`` on the returned ndarray."
            )
        elif bands_axis > 0:
            kwargs["bands_axis"] = (
                bands_axis - 1
            )  # the bands axis for each component ndarray call in the stack

        if flatten is not None:
            if isinstance(flatten, str) or not hasattr(flatten, "__len__"):
                flatten = [flatten]
            images = [
                ic if len(ic) > 1 else ic[0] for group, ic in self.groupby(*flatten)
            ]
        else:
            images = self

        full_stack = None
        mask = None
        if raster_info:
            raster_infos = [None] * len(images)

        bands = bands_to_list(bands)
        product_bands = self._product_bands()
        (bands, scaling, mask_alpha, pop_alpha) = self._mask_alpha_if_applicable(
            product_bands, bands, mask_alpha=mask_alpha, scaling=scaling
        )
        scales, data_type = multiproduct_scaling_parameters(
            product_bands, bands, processing_level, scaling, data_type
        )

        if pop_alpha:
            bands.pop(-1)
            if scales:
                scales.pop(-1)

        kwargs["scaling"] = scales
        kwargs["data_type"] = data_type

        def threaded_ndarrays():
            def data_loader(image_or_imagecollection, bands, geocontext, **kwargs):
                if isinstance(image_or_imagecollection, self.__class__):
                    return lambda: image_or_imagecollection.mosaic(
                        bands, geocontext, **kwargs
                    )
                else:
                    return lambda: image_or_imagecollection._ndarray(
                        bands, geocontext, **kwargs
                    )

            with concurrent.futures.ThreadPoolExecutor(
                max_workers=max_workers
            ) as executor:
                future_ndarrays = {}
                for i, image_or_imagecollection in enumerate(images):
                    future_ndarray = executor.submit(
                        data_loader(
                            image_or_imagecollection, bands, geocontext, **kwargs
                        )
                    )
                    future_ndarrays[future_ndarray] = i
                for future in concurrent.futures.as_completed(future_ndarrays):
                    i = future_ndarrays[future]
                    result = future.result()
                    yield i, result

        for i, arr in threaded_ndarrays():
            if raster_info:
                arr, raster_meta = arr
                raster_infos[i] = raster_meta

            if full_stack is None:
                stack_shape = (len(images),) + arr.shape
                full_stack = np.empty(stack_shape, dtype=arr.dtype)
                if isinstance(arr, np.ma.MaskedArray):
                    mask = np.empty(stack_shape, dtype=bool)

            if isinstance(arr, np.ma.MaskedArray):
                full_stack[i] = arr.data
                mask[i] = arr.mask
            else:
                full_stack[i] = arr

        if mask is not None:
            full_stack = np.ma.MaskedArray(full_stack, mask, copy=False)
        if raster_info:
            return full_stack, raster_infos
        else:
            return full_stack

    def mosaic(
        self,
        bands,
        geocontext=None,
        crs=None,
        resolution=None,
        all_touched=None,
        mask_nodata=True,
        mask_alpha=None,
        bands_axis=0,
        resampler=ResampleAlgorithm.NEAR,
        processing_level=None,
        scaling=None,
        data_type=None,
        progress=None,
        raster_info=False,
        **kwargs,
    ):
        """
        Load bands from all images, combining them into a single 3D ndarray
        and optionally masking invalid data.

        Where multiple images overlap, only data from the image that comes last
        in the ImageCollection is used.

        If the selected bands and images have different data types the resulting
        ndarray has the most general of those data types. See
        `Image.ndarray() <earthdaily.earthone.catalog.image.Image.ndarray>` for details
        on data type conversions.

        Parameters
        ----------
        bands : str or Sequence[str]
            Band names to load. Can be a single string of band names
            separated by spaces (``"red green blue"``),
            or a sequence of band names (``["red", "green", "blue"]``).
            If the alpha band is requested, it must be last in the list
            to reduce rasterization errors.
        geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
            A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading each image.
            If ``None`` then the default context of the collection will be used. If
            this is ``None``, a ValueError is raised.
        crs : str, default None
            if not None, update the gecontext with this value to set the output CRS.
        resolution : float, default None
            if not None, update the geocontext with this value to set the output resolution
            in the units native to the specified or defaulted output CRS.
        all_touched : float, default None
            if not None, update the geocontext with this value to control rastering behavior.
        mask_nodata : bool, default True
            Whether to mask out values in each band that equal
            that band's ``nodata`` sentinel value.
        mask_alpha : bool or str or None, default None
            Whether to mask pixels in all bands where the alpha band of all images is 0.
            Provide a string to use an alternate band name for masking.
            If the alpha band is available for all images in the collection and
            ``mask_alpha`` is None, ``mask_alpha`` is set to True. If not,
            mask_alpha is set to False.
        bands_axis : int, default 0
            Axis along which bands should be located in the returned array.
            If 0, the array will have shape ``(band, y, x)``,
            if -1, it will have shape ``(y, x, band)``.

            It's usually easier to work with bands as the outermost axis,
            but when working with large arrays, or with many arrays concatenated
            together, NumPy operations aggregating each xy point across bands
            can be slightly faster with bands as the innermost axis.
        raster_info : bool, default False
            Whether to also return a dict of information about the rasterization
            of the images, including the coordinate system WKT and geotransform matrix.
            Generally only useful if you plan to upload data derived
            from this image back to the EarthOne catalog, or use it with GDAL.
        resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
            Algorithm used to interpolate pixel values when scaling and transforming
            the image to its new resolution or SRS.
        processing_level : str, optional
            How the processing level of the underlying data should be adjusted. Possible
            values depend on the product and bands in use. Legacy products support
            ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
            available ``processing_levels`` in the product bands to understand what
            is available.
        scaling : None, str, list, dict
            Band scaling specification. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        data_type : None, str
            Output data type. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        progress : None, bool
            Controls display of a progress bar.


        Returns
        -------
        arr : ndarray
            Returned array's shape will be ``(band, y, x)`` if ``bands_axis``
            is 0, and ``(y, x, band)`` if ``bands_axis`` is -1.
            If ``mask_nodata`` or ``mask_alpha`` is True, arr will be a masked array.
            The data type ("dtype") of the array is the most general of the data
            types among the images being rastered.
        raster_info : dict
            If ``raster_info=True``, a raster information dict is also returned.

        Raises
        ------
        ValueError
            If requested bands are unavailable, or band names are not given
            or are invalid.
            If not all required parameters are specified in the
            :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
            If the ImageCollection is empty.
        NotFoundError
            If a Image's ID cannot be found in the EarthOne catalog
        BadRequestError
            If the EarthOne Platform is given unrecognized parameters
        """
        if len(self) == 0:
            raise ValueError("This ImageCollection is empty")

        if geocontext is None:
            geocontext = self.geocontext
            if geocontext is None:
                raise ValueError(
                    "No geocontext supplied, and no default geocontext is defined for this ImageCollection"
                )
        if crs is not None or resolution is not None:
            try:
                params = {}
                if crs is not None:
                    params["crs"] = crs
                if resolution is not None:
                    params["resolution"] = resolution
                geocontext = geocontext.assign(**params)
            except TypeError:
                raise ValueError(
                    f"{type(geocontext)} geocontext does not support modifying crs or resolution"
                ) from None
        if all_touched is not None:
            geocontext = geocontext.assign(all_touched=all_touched)

        if not (-3 < bands_axis < 3):
            raise ValueError(
                "Invalid bands_axis; axis {} would not exist in a 3D array".format(
                    bands_axis
                )
            )

        bands = bands_to_list(bands)
        product_bands = self._product_bands()
        (bands, scaling, mask_alpha, drop_alpha) = self._mask_alpha_if_applicable(
            product_bands, bands, mask_alpha=mask_alpha, scaling=scaling
        )
        scales, data_type = multiproduct_scaling_parameters(
            product_bands, bands, processing_level, scaling, data_type
        )

        raster_params = geocontext.raster_params
        full_raster_args = dict(
            inputs=[image.id for image in self],
            order="gdal",
            bands=bands,
            scales=scales,
            data_type=data_type,
            resampler=resampler,
            processing_level=processing_level,
            mask_nodata=bool(mask_nodata),
            mask_alpha=mask_alpha,
            drop_alpha=drop_alpha,
            masked=mask_nodata or mask_alpha,
            progress=progress,
            **raster_params,
        )

        raster = kwargs.pop("raster_client", None) or Raster.get_default_client()
        if kwargs:
            raise TypeError(f"Unexpected keyword arguments: {kwargs}")
        try:
            arr, info = raster.ndarray(**full_raster_args)
        except NotFoundError:
            raise NotFoundError(
                "Some or all of these IDs don't exist in the EarthOne catalog: {}".format(
                    full_raster_args["inputs"]
                )
            )
        except BadRequestError as e:
            msg = (
                "Error with request:\n"
                "{err}\n"
                "For reference, Raster.ndarray was called with these arguments:\n"
                "{args}"
            )
            msg = msg.format(err=e, args=json.dumps(full_raster_args, indent=2))
            raise BadRequestError(msg) from None

        if len(arr.shape) == 2:
            # if only 1 band requested, still return a 3d array
            arr = arr[np.newaxis]

        if bands_axis != 0:
            arr = np.moveaxis(arr, 0, bands_axis)
        if raster_info:
            return arr, info
        else:
            return arr

    def download(
        self,
        bands,
        geocontext=None,
        crs=None,
        resolution=None,
        all_touched=None,
        dest=None,
        format=DownloadFileFormat.TIF,
        resampler=ResampleAlgorithm.NEAR,
        processing_level=None,
        scaling=None,
        data_type=None,
        progress=None,
        max_workers=None,
        **kwargs,
    ):
        """
        Download images as image files in parallel.

        Parameters
        ----------
        bands : str or Sequence[str]
            Band names to load. Can be a single string of band names
            separated by spaces (``"red green blue"``),
            or a sequence of band names (``["red", "green", "blue"]``).
        geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
            A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading each image.
            If ``None`` then the default context of the collection will be used. If
            this is ``None``, a ValueError is raised.
        crs : str, default None
            if not None, update the gecontext with this value to set the output CRS.
        resolution : float, default None
            if not None, update the geocontext with this value to set the output resolution
            in the units native to the specified or defaulted output CRS.
        all_touched : float, default None
            if not None, update the geocontext with this value to control rastering behavior.
        dest : str, path-like, or sequence of str or path-like, default None
            Directory or sequence of paths to which to write the image files.

            If ``None``, the current directory is used.

            If a directory, files within it will be named by
            their image IDs and the bands requested, like
            ``"sentinel-2:L1C:2018-08-10_10TGK_68_S2A_v1-red-green-blue.tif"``.

            If a sequence of paths of the same length as the ImageCollection is given,
            each Image will be written to the corresponding path. This lets you use your
            own naming scheme, or even write images to multiple directories.

            Any intermediate paths are created if they do not exist,
            for both a single directory and a sequence of paths.
        format : `DownloadFileFormat`, default `DownloadFileFormat.TIF`
            Output file format to use.
            If ``dest`` is a sequence of paths, ``format`` is ignored
            and determined by the extension on each path.
        resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
            Algorithm used to interpolate pixel values when scaling and transforming
            the image to its new resolution or SRS.
        processing_level : str, optional
            How the processing level of the underlying data should be adjusted. Possible
            values depend on the product and bands in use. Legacy products support
            ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
            available ``processing_levels`` in the product bands to understand what
            is available.
        scaling : None, str, list, dict
            Band scaling specification. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        data_type : None, str
            Output data type. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        progress : None, bool
            Controls display of a progress bar.
        max_workers : int, default None
            Maximum number of threads to use to parallelize individual ``download``
            calls to each Image.
            If None, it defaults to the number of processors on the machine,
            multiplied by 5.
            Note that unnecessary threads *won't* be created if ``max_workers``
            is greater than the number of Images in the ImageCollection.

        Returns
        -------
        paths : Sequence[str]
            A list of all the paths where files were written.

        Example
        -------
        >>> import earthdaily.earthone as eo
        >>> tile = eo.common.geo.DLTile.from_key("256:0:75.0:15:-5:230")  # doctest: +SKIP
        >>> product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR")  # doctest: +SKIP
        >>> images = product.images().intersects(tile).limit(5).collect()  # doctest: +SKIP
        >>> images.download("red green blue", tile, "rasters")  # doctest: +SKIP
        ["rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013108_v1-red-green-blue.tif",
         "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013124_v1-red-green-blue.tif",
         "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013140_v1-red-green-blue.tif",
         "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013156_v1-red-green-blue.tif",
         "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013172_v1-red-green-blue.tif"]
        >>> # use explicit paths for a custom naming scheme:
        >>> paths = [
        ...     "{tile.key}/l8-{image.date:%Y-%m-%d-%H:%m}.tif".format(tile=tile, image=image)
        ...     for image in images
        ... ]  # doctest: +SKIP
        >>> images.download("nir red", tile, paths)  # doctest: +SKIP
        ["256:0:75.0:15:-5:230/l8-2013-04-18-16:04.tif",
         "256:0:75.0:15:-5:230/l8-2013-05-04-16:05.tif",
         "256:0:75.0:15:-5:230/l8-2013-05-20-16:05.tif",
         "256:0:75.0:15:-5:230/l8-2013-06-05-16:06.tif",
         "256:0:75.0:15:-5:230/l8-2013-06-21-16:06.tif"]

        Raises
        ------
        RuntimeError
            If the paths given are not all unique.
            If there is an error generating default filenames.
        ValueError
            If requested bands are unavailable, or band names are not given
            or are invalid.
            If not all required parameters are specified in the
            :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
            If the ImageCollection is empty.
            If ``dest`` is a sequence not equal in length to the ImageCollection.
            If ``format`` is invalid, or a path has an invalid extension.
        TypeError
            If ``dest`` is not a string or a sequence type.
        NotFoundError
            If a Image's ID cannot be found in the EarthOne catalog
        BadRequestError
            If the EarthOne Platform is given unrecognized parameters
        """
        if len(self) == 0:
            raise ValueError("This ImageCollection is empty")

        if geocontext is None:
            geocontext = self.geocontext
            if geocontext is None:
                raise ValueError(
                    "No geocontext supplied, and no default geocontext is defined for this ImageCollection"
                )
        if crs is not None or resolution is not None:
            try:
                params = {}
                if crs is not None:
                    params["crs"] = crs
                if resolution is not None:
                    params["resolution"] = resolution
                geocontext = geocontext.assign(**params)
            except TypeError:
                raise ValueError(
                    f"{type(geocontext)} geocontext does not support modifying crs or resolution"
                ) from None
        if all_touched is not None:
            geocontext = geocontext.assign(all_touched=all_touched)

        if dest is None:
            dest = "."

        bands = bands_to_list(bands)
        scales, data_type = multiproduct_scaling_parameters(
            self._product_bands(), bands, processing_level, scaling, data_type
        )

        if is_path_like(dest):
            default_pattern = "{image.id}-{bands}.{ext}"
            bands_str = "-".join(bands)
            try:
                dest = [
                    os.path.join(
                        dest,
                        default_pattern.format(
                            image=image, bands=bands_str, ext=format
                        ),
                    )
                    for image in self
                ]
            except Exception as e:
                raise RuntimeError(
                    "Error while generating default filenames:\n{}\n"
                    "This is likely due to missing or unexpected data "
                    "in Images in this ImageCollection.".format(e)
                ) from None

        try:
            if len(dest) != len(self):
                raise ValueError(
                    "`dest` contains {} items, but the ImageCollection contains {}".format(
                        len(dest), len(self)
                    )
                )
        except TypeError:
            raise TypeError(
                "`dest` should be a sequence of strings or path-like objects; "
                "instead found type {}, which has no length".format(type(dest))
            ) from None

        # check for duplicate paths to prevent the confusing situation where
        # multiple rasters overwrite the same filename
        unique = set()
        for path in dest:
            if path in unique:
                raise RuntimeError(
                    "Paths must be unique, but '{}' occurs multiple times".format(path)
                )
            else:
                unique.add(path)

        download_args = dict(
            resampler=resampler,
            processing_level=processing_level,
            scaling=scales,
            data_type=data_type,
            progress=progress,
            **kwargs,
        )
        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = {
                executor.submit(
                    image._download, bands, geocontext, dest=path, **download_args
                ): path
                for image, path in zip(self, dest)
            }
            exceptions = []
            for future in concurrent.futures.as_completed(futures):
                try:
                    future.result()
                except Exception as ex:
                    exceptions.append((futures[future], ex))
            if exceptions:
                raise RuntimeError(
                    "One or more downloads failed: {}".format(exceptions)
                )
        return dest

    def download_mosaic(
        self,
        bands,
        geocontext=None,
        crs=None,
        resolution=None,
        all_touched=None,
        dest=None,
        format=DownloadFileFormat.TIF,
        resampler=ResampleAlgorithm.NEAR,
        processing_level=None,
        scaling=None,
        data_type=None,
        mask_alpha=None,
        nodata=None,
        progress=None,
        **kwargs,
    ):
        """
        Download all images as a single image file.
        Where multiple images overlap, only data from the image that comes last
        in the ImageCollection is used.

        Parameters
        ----------
        bands : str or Sequence[str]
            Band names to load. Can be a single string of band names
            separated by spaces (``"red green blue"``),
            or a sequence of band names (``["red", "green", "blue"]``).
        geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
            A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading each image.
            If ``None`` then the default context of the collection will be used. If
            this is ``None``, a ValueError is raised.
        crs : str, default None
            if not None, update the gecontext with this value to set the output CRS.
        resolution : float, default None
            if not None, update the geocontext with this value to set the output resolution
            in the units native to the specified or defaulted output CRS.
        all_touched : float, default None
            if not None, update the geocontext with this value to control rastering behavior.
        dest : str or path-like object, default None
            Where to write the image file.

            * If None (default), it's written to an image file of the given ``format``
              in the current directory, named by the requested bands,
              like ``"mosaic-red-green-blue.tif"``
            * If a string or path-like object, it's written to that path.

              Any file already existing at that path will be overwritten.

              Any intermediate directories will be created if they don't exist.

              Note that path-like objects (such as pathlib.Path) are only supported
              in Python 3.6 or later.
        format : `DownloadFileFormat`, default `DownloadFileFormat.TIF`
            Output file format to use.
            If a str or path-like object is given as ``dest``, ``format`` is ignored
            and determined from the extension on the path (one of ".tif", ".png", or ".jpg").
        resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
            Algorithm used to interpolate pixel values when scaling and transforming
            the image to its new resolution or SRS.
        processing_level : str, optional
            How the processing level of the underlying data should be adjusted. Possible
            values depend on the product and bands in use. Legacy products support
            ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
            available ``processing_levels`` in the product bands to understand what
            is available.
        scaling : None, str, list, dict
            Band scaling specification. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        data_type : None, str
            Output data type. Please see :meth:`scaling_parameters` for a full
            description of this parameter.
        mask_alpha : bool or str or None, default None
            Whether to mask pixels in all bands where the alpha band of all images is 0.
            Provide a string to use an alternate band name for masking.
            If the alpha band is available for all images in the collection and
            ``mask_alpha`` is None, ``mask_alpha`` is set to True. If not,
            mask_alpha is set to False.
        nodata : None, number
            NODATA value for a geotiff file. Will be assigned to any masked pixels.
        progress : None, bool
            Controls display of a progress bar.

        Returns
        -------
        path : str or None
            If ``dest`` is a path or None, the path where the image file was written is returned.
            If ``dest`` is file-like, nothing is returned.

        Example
        -------
        >>> import earthdaily.earthone as eo
        >>> tile = eo.common.geo.DLTile.from_key("256:0:75.0:15:-5:230")  # doctest: +SKIP
        >>> product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR")  # doctest: +SKIP
        >>> images = product.images().intersects(tile).limit(5).collect()  # doctest: +SKIP
        >>> images.download_mosaic("nir red", mask_alpha=False)  # doctest: +SKIP
        'mosaic-nir-red.tif'
        >>> images.download_mosaic("red green blue", dest="mosaics/{}.png".format(tile.key))  # doctest: +SKIP
        'mosaics/256:0:75.0:15:-5:230.tif'


        Raises
        ------
        ValueError
            If requested bands are unavailable, or band names are not given
            or are invalid.
            If not all required parameters are specified in the
            :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
            If the ImageCollection is empty.
            If ``format`` is invalid, or the path has an invalid extension.
        NotFoundError
            If a Image's ID cannot be found in the EarthOne catalog
        BadRequestError
            If the EarthOne Platform is given unrecognized parameters
        """
        if len(self) == 0:
            raise ValueError("This ImageCollection is empty")

        if geocontext is None:
            geocontext = self.geocontext
            if geocontext is None:
                raise ValueError(
                    "No geocontext supplied, and no default geocontext is defined for this ImageCollection"
                )
        if crs is not None or resolution is not None:
            try:
                params = {}
                if crs is not None:
                    params["crs"] = crs
                if resolution is not None:
                    params["resolution"] = resolution
                geocontext = geocontext.assign(**params)
            except TypeError:
                raise ValueError(
                    f"{type(geocontext)} geocontext does not support modifying crs or resolution"
                ) from None
        if all_touched is not None:
            geocontext = geocontext.assign(all_touched=all_touched)

        bands = bands_to_list(bands)
        product_bands = self._product_bands()
        (bands, scaling, mask_alpha, drop_alpha) = self._mask_alpha_if_applicable(
            product_bands, bands, mask_alpha=mask_alpha, scaling=scaling
        )
        scales, data_type = multiproduct_scaling_parameters(
            product_bands, bands, processing_level, scaling, data_type
        )

        return download(
            inputs=self.each.id.collect(list),
            bands_list=bands,
            geocontext=geocontext,
            scales=scales,
            data_type=data_type,
            dest=dest,
            format=format,
            resampler=resampler,
            processing_level=processing_level,
            nodata=nodata,
            progress=progress,
            **kwargs,
        )

    def scaling_parameters(
        self, bands, processing_level=None, scaling=None, data_type=None
    ):
        """
        Computes fully defaulted scaling parameters and output data_type
        from provided specifications.

        This method is provided as a convenience to the user to help with
        understanding how ``scaling`` and ``data_type`` parameters passed
        to other methods on this class (e.g. :meth:`stack` or :meth:`mosaic`)
        will be interpreted. It would not usually be used in a normal
        workflow.

        A image collection may contain images from more than one product,
        introducing the possibility that the band properties for a band
        of a given name may differ from product to product. This method
        works in a similar fashion to the
        :meth:`Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters>`
        method, but it additionally ensures that the resulting scale
        elements are compatible across the multiple products. If there
        is an incompatibility, an appropriate ValueError will be raised.

        Parameters
        ----------
        bands : list(str)
            List of bands to be scaled.
        processing_level : str, optional
            How the processing level of the underlying data should be adjusted. Possible
            values depend on the product and bands in use. Legacy products support
            ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
            available ``processing_levels`` in the product bands to understand what
            is available.
        scaling : None or str or list or dict
            Band scaling specification. See
            :meth:`Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters>`
            for a full description of this parameter.
        data_type : None or str
            Result data type desired, as a standard data type string (e.g.
            ``"Byte"``, ``"Uint16"``, or ``"Float64"``). If not specified,
            will be deduced from the ``scaling`` specification. See
            :meth:`Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters>`
            for a full description of this parameter.

        Returns
        -------
        scales : list(tuple)
            The fully specified scaling parameter, compatible with the
            :class:`~earthdaily.earthone.client.services.raster.Raster` API and the
            output data type.
        data_type : str
            The result data type as a standard GDAL type string.

        Raises
        ------
        ValueError
            If any invalid or incompatible value is passed to any of the
            three parameters.

        See Also
        --------
        :doc:`Catalog Guide </guides/catalog>` : This contains many examples of the use of
        the ``scaling`` and ``data_type`` parameters.
        """
        bands = bands_to_list(bands)
        return multiproduct_scaling_parameters(
            self._product_bands(), bands, processing_level, scaling, data_type
        )

    def __repr__(self):
        parts = [
            "ImageCollection of {} image{}".format(
                len(self), "" if len(self) == 1 else "s"
            )
        ]
        try:
            first = min(self.each.date)
            last = max(self.each.date)
            dates = "  * Dates: {:%b %d, %Y} to {:%b %d, %Y}".format(first, last)
            parts.append(dates)
        except Exception:
            pass

        try:
            products = self.each.product_id.combine(collections.Counter)
            if len(products) > 0:
                products = ", ".join("{}: {}".format(k, v) for k, v in products.items())
                products = "  * Products: {}".format(products)
                parts.append(products)
        except Exception:
            pass

        return "\n".join(parts)

    def _product_bands(self):
        product_ids = set(map(lambda i: i.product_id, self._list))
        return {
            product_id: cached_bands_by_product(product_id, self._client)
            for product_id in product_ids
        }

    def _mask_alpha_if_applicable(
        self, product_bands, bands, mask_alpha=None, scaling=None
    ):
        alpha_band_name = "alpha"
        if isinstance(mask_alpha, str):
            alpha_band_name = mask_alpha
            mask_alpha = True
        elif mask_alpha is None:
            mask_alpha = all(
                map(lambda b: alpha_band_name in b, product_bands.values())
            )
        elif type(mask_alpha) is not bool:
            raise ValueError("'mask_alpha' must be None, a band name, or a bool.")

        drop_alpha = False
        if mask_alpha:
            try:
                alpha_i = bands.index(alpha_band_name)
            except ValueError:
                bands.append(alpha_band_name)
                drop_alpha = True
                scaling = append_alpha_scaling(scaling)
            else:
                if alpha_i != len(bands) - 1:
                    raise ValueError(
                        "Alpha must be the last band in order to reduce rasterization errors"
                    )
        return (bands, scaling, mask_alpha, drop_alpha)

filter_coverage 🔗

filter_coverage(geom, minimum_coverage=1)

Include only images overlapping with geom by some fraction.

See Image.coverage <earthdaily.earthone.catalog.image.Image.coverage> for getting coverage information for an image.

Parameters🔗

geom : GeoJSON-like dict, :class:~earthdaily.earthone.common.geo.geocontext.GeoContext, or object with geo_interface # noqa: E501 Geometry to which to compare each image's geometry. minimum_coverage : float Only include images that cover geom by at least this fraction.

Returns🔗

images : ImageCollection

Example🔗

import earthdaily.earthone as eo aoi_geometry = { ... 'type': 'Polygon', ... 'coordinates': [[[-95, 42],[-93, 42],[-93, 40],[-95, 41],[-95, 42]]]} product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR") # doctest: +SKIP images = product.images().intersects(aoi_geometry).limit(20).collect() # doctest: +SKIP filtered_images = images.filter_coverage(images.geocontext, 0.01) # doctest: +SKIP assert len(filtered_images) < len(images) # doctest: +SKIP

Source code in earthdaily/earthone/core/catalog/image_collection.py
def filter_coverage(self, geom, minimum_coverage=1):
    """
    Include only images overlapping with ``geom`` by some fraction.

    See `Image.coverage <earthdaily.earthone.catalog.image.Image.coverage>`
    for getting coverage information for an image.

    Parameters
    ----------
    geom : GeoJSON-like dict, :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, or object with __geo_interface__  # noqa: E501
        Geometry to which to compare each image's geometry.
    minimum_coverage : float
        Only include images that cover ``geom`` by at least this fraction.

    Returns
    -------
    images : ImageCollection

    Example
    -------
    >>> import earthdaily.earthone as eo
    >>> aoi_geometry = {
    ...    'type': 'Polygon',
    ...    'coordinates': [[[-95, 42],[-93, 42],[-93, 40],[-95, 41],[-95, 42]]]}
    >>> product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR")  # doctest: +SKIP
    >>> images = product.images().intersects(aoi_geometry).limit(20).collect()  # doctest: +SKIP
    >>> filtered_images = images.filter_coverage(images.geocontext, 0.01)  # doctest: +SKIP
    >>> assert len(filtered_images) < len(images)  # doctest: +SKIP
    """

    return self.filter(lambda i: i.coverage(geom) >= minimum_coverage)

stack 🔗

stack(bands, geocontext=None, crs=None, resolution=None, all_touched=None, flatten=None, mask_nodata=True, mask_alpha=None, bands_axis=1, raster_info=False, resampler=ResampleAlgorithm.NEAR, processing_level=None, scaling=None, data_type=None, progress=None, max_workers=None, **kwargs)

Load bands from all images and stack them into a 4D ndarray, optionally masking invalid data.

If the selected bands and images have different data types the resulting ndarray has the most general of those data types. See Image.ndarray() <earthdaily.earthone.catalog.image.Image.ndarray> for details on data type conversions.

Parameters🔗

bands : str or Sequence[str] Band names to load. Can be a single string of band names separated by spaces ("red green blue"), or a sequence of band names (["red", "green", "blue"]). If the alpha band is requested, it must be last in the list to reduce rasterization errors. geocontext : :class:~earthdaily.earthone.common.geo.geocontext.GeoContext, default None A :class:~earthdaily.earthone.common.geo.geocontext.GeoContext to use when loading each image. If None then the default context of the collection will be used. If this is None, a ValueError is raised. crs : str, default None if not None, update the gecontext with this value to set the output CRS. resolution : float, default None if not None, update the geocontext with this value to set the output resolution in the units native to the specified or defaulted output CRS. all_touched : float, default None if not None, update the geocontext with this value to control rastering behavior. flatten : str, Sequence[str], callable, or Sequence[callable], default None "Flatten" groups of images in the stack into a single layer by mosaicking each group (such as images from the same day), then stacking the mosaics.

``flatten`` takes the same predicates as `Collection.groupby`, such as
``"properties.date"`` to mosaic images acquired at the exact same timestamp,
or ``["properties.date.year", "properties.date.month", "properties.date.day"]``
to combine images captured on the same day (but not necessarily the same time).

This is especially useful when ``geocontext`` straddles an image boundary
and contains one image captured right after another. Instead of having
each as a separate layer in the stack, you might want them combined.

Note that indicies in the returned ndarray will no longer correspond to
indicies in this ImageCollection, since multiple images may be combined into
one layer in the stack. You can call ``groupby`` on this ImageCollection
with the same parameters to iterate through groups of images in equivalent
order to the returned ndarray.

Additionally, the order of images in the ndarray will change:
they'll be sorted by the parameters to ``flatten``.

mask_nodata : bool, default True Whether to mask out values in each band of each image that equal that band's nodata sentinel value. mask_alpha : bool or str or None, default None Whether to mask pixels in all bands where the alpha band of all images is 0. Provide a string to use an alternate band name for masking. If the alpha band is available for all images in the collection and mask_alpha is None, mask_alpha is set to True. If not, mask_alpha is set to False. bands_axis : int, default 1 Axis along which bands should be located. If 1, the array will have shape (image, band, y, x), if -1, it will have shape (image, y, x, band), etc. A bands_axis of 0 is currently unsupported. raster_info : bool, default False Whether to also return a list of dicts about the rasterization of each image, including the coordinate system WKT and geotransform matrix. Generally only useful if you plan to upload data derived from this image back to the EarthOne catalog, or use it with GDAL. resampler : ResampleAlgorithm, default ResampleAlgorithm.NEAR Algorithm used to interpolate pixel values when scaling and transforming each image to its new resolution or SRS. processing_level : str, optional How the processing level of the underlying data should be adjusted. Possible values depend on the product and bands in use. Legacy products support toa (top of atmosphere) and in some cases surface. Consult the available processing_levels in the product bands to understand what is available. scaling : None, str, list, dict Band scaling specification. Please see :meth:scaling_parameters for a full description of this parameter. data_type : None, str Output data type. Please see :meth:scaling_parameters for a full description of this parameter. progress : None, bool Controls display of a progress bar. max_workers : int, default None Maximum number of threads to use to parallelize individual ndarray calls to each image. If None, it defaults to the number of processors on the machine, multiplied by 5. Note that unnecessary threads won't be created if max_workers is greater than the number of images in the ImageCollection.

Returns🔗

arr : ndarray Returned array's shape is (image, band, y, x) if bands_axis is 1, or (image, y, x, band) if bands_axis is -1. If mask_nodata or mask_alpha is True, arr will be a masked array. The data type ("dtype") of the array is the most general of the data types among the images being rastered. raster_info : List[dict] If raster_info=True, a list of raster information dicts for each image is also returned

Raises🔗

ValueError If requested bands are unavailable, or band names are not given or are invalid. If the context is None and no default context for the ImageCollection is defined, or if not all required parameters are specified in the :class:~earthdaily.earthone.common.geo.geocontext.GeoContext. If the ImageCollection is empty. NotFoundError If a Image's ID cannot be found in the EarthOne catalog BadRequestError If the EarthOne Platform is given unrecognized parameters

Source code in earthdaily/earthone/core/catalog/image_collection.py
def stack(
    self,
    bands,
    geocontext=None,
    crs=None,
    resolution=None,
    all_touched=None,
    flatten=None,
    mask_nodata=True,
    mask_alpha=None,
    bands_axis=1,
    raster_info=False,
    resampler=ResampleAlgorithm.NEAR,
    processing_level=None,
    scaling=None,
    data_type=None,
    progress=None,
    max_workers=None,
    **kwargs,
):
    """
    Load bands from all images and stack them into a 4D ndarray,
    optionally masking invalid data.

    If the selected bands and images have different data types the resulting
    ndarray has the most general of those data types. See
    `Image.ndarray() <earthdaily.earthone.catalog.image.Image.ndarray>` for details
    on data type conversions.

    Parameters
    ----------
    bands : str or Sequence[str]
        Band names to load. Can be a single string of band names
        separated by spaces (``"red green blue"``),
        or a sequence of band names (``["red", "green", "blue"]``).
        If the alpha band is requested, it must be last in the list
        to reduce rasterization errors.
    geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
        A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading each image.
        If ``None`` then the default context of the collection will be used. If
        this is ``None``, a ValueError is raised.
    crs : str, default None
        if not None, update the gecontext with this value to set the output CRS.
    resolution : float, default None
        if not None, update the geocontext with this value to set the output resolution
        in the units native to the specified or defaulted output CRS.
    all_touched : float, default None
        if not None, update the geocontext with this value to control rastering behavior.
    flatten : str, Sequence[str], callable, or Sequence[callable], default None
        "Flatten" groups of images in the stack into a single layer by mosaicking
        each group (such as images from the same day), then stacking the mosaics.

        ``flatten`` takes the same predicates as `Collection.groupby`, such as
        ``"properties.date"`` to mosaic images acquired at the exact same timestamp,
        or ``["properties.date.year", "properties.date.month", "properties.date.day"]``
        to combine images captured on the same day (but not necessarily the same time).

        This is especially useful when ``geocontext`` straddles an image boundary
        and contains one image captured right after another. Instead of having
        each as a separate layer in the stack, you might want them combined.

        Note that indicies in the returned ndarray will no longer correspond to
        indicies in this ImageCollection, since multiple images may be combined into
        one layer in the stack. You can call ``groupby`` on this ImageCollection
        with the same parameters to iterate through groups of images in equivalent
        order to the returned ndarray.

        Additionally, the order of images in the ndarray will change:
        they'll be sorted by the parameters to ``flatten``.
    mask_nodata : bool, default True
        Whether to mask out values in each band of each image that equal
        that band's ``nodata`` sentinel value.
    mask_alpha : bool or str or None, default None
        Whether to mask pixels in all bands where the alpha band of all images is 0.
        Provide a string to use an alternate band name for masking.
        If the alpha band is available for all images in the collection and
        ``mask_alpha`` is None, ``mask_alpha`` is set to True. If not,
        mask_alpha is set to False.
    bands_axis : int, default 1
        Axis along which bands should be located.
        If 1, the array will have shape ``(image, band, y, x)``, if -1,
        it will have shape ``(image, y, x, band)``, etc.
        A bands_axis of 0 is currently unsupported.
    raster_info : bool, default False
        Whether to also return a list of dicts about the rasterization of
        each image, including the coordinate system WKT and geotransform matrix.
        Generally only useful if you plan to upload data derived from this
        image back to the EarthOne catalog, or use it with GDAL.
    resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
        Algorithm used to interpolate pixel values when scaling and transforming
        each image to its new resolution or SRS.
    processing_level : str, optional
        How the processing level of the underlying data should be adjusted. Possible
        values depend on the product and bands in use. Legacy products support
        ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
        available ``processing_levels`` in the product bands to understand what
        is available.
    scaling : None, str, list, dict
        Band scaling specification. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    data_type : None, str
        Output data type. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    progress : None, bool
        Controls display of a progress bar.
    max_workers : int, default None
        Maximum number of threads to use to parallelize individual ndarray
        calls to each image.
        If None, it defaults to the number of processors on the machine,
        multiplied by 5.
        Note that unnecessary threads *won't* be created if ``max_workers``
        is greater than the number of images in the ImageCollection.

    Returns
    -------
    arr : ndarray
        Returned array's shape is ``(image, band, y, x)`` if bands_axis is 1,
        or ``(image, y, x, band)`` if bands_axis is -1.
        If ``mask_nodata`` or ``mask_alpha`` is True, arr will be a masked array.
        The data type ("dtype") of the array is the most general of the data
        types among the images being rastered.
    raster_info : List[dict]
        If ``raster_info=True``, a list of raster information dicts for each image
        is also returned

    Raises
    ------
    ValueError
        If requested bands are unavailable, or band names are not given
        or are invalid.
        If the context is None and no default context for the ImageCollection
        is defined, or if not all required parameters are specified in the
        :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
        If the ImageCollection is empty.
    NotFoundError
        If a Image's ID cannot be found in the EarthOne catalog
    BadRequestError
        If the EarthOne Platform is given unrecognized parameters
    """
    if len(self) == 0:
        raise ValueError("This ImageCollection is empty")

    if geocontext is None:
        geocontext = self.geocontext
        if geocontext is None:
            raise ValueError(
                "No geocontext supplied, and no default geocontext is defined for this ImageCollection"
            )
    if crs is not None or resolution is not None:
        try:
            params = {}
            if crs is not None:
                params["crs"] = crs
            if resolution is not None:
                params["resolution"] = resolution
            geocontext = geocontext.assign(**params)
        except TypeError:
            raise ValueError(
                f"{type(geocontext)} geocontext does not support modifying crs or resolution"
            ) from None
    if all_touched is not None:
        geocontext = geocontext.assign(all_touched=all_touched)

    kwargs = dict(
        mask_nodata=mask_nodata,
        mask_alpha=mask_alpha,
        bands_axis=bands_axis,
        raster_info=raster_info,
        resampler=resampler,
        processing_level=processing_level,
        progress=progress,
        **kwargs,
    )

    if bands_axis == 0 or bands_axis == -4:
        raise NotImplementedError(
            "bands_axis of 0 is currently unsupported for `ImageCollection.stack`. "
            "If you require this shape, try ``np.moveaxis(my_stack, 1, 0)`` on the returned ndarray."
        )
    elif bands_axis > 0:
        kwargs["bands_axis"] = (
            bands_axis - 1
        )  # the bands axis for each component ndarray call in the stack

    if flatten is not None:
        if isinstance(flatten, str) or not hasattr(flatten, "__len__"):
            flatten = [flatten]
        images = [
            ic if len(ic) > 1 else ic[0] for group, ic in self.groupby(*flatten)
        ]
    else:
        images = self

    full_stack = None
    mask = None
    if raster_info:
        raster_infos = [None] * len(images)

    bands = bands_to_list(bands)
    product_bands = self._product_bands()
    (bands, scaling, mask_alpha, pop_alpha) = self._mask_alpha_if_applicable(
        product_bands, bands, mask_alpha=mask_alpha, scaling=scaling
    )
    scales, data_type = multiproduct_scaling_parameters(
        product_bands, bands, processing_level, scaling, data_type
    )

    if pop_alpha:
        bands.pop(-1)
        if scales:
            scales.pop(-1)

    kwargs["scaling"] = scales
    kwargs["data_type"] = data_type

    def threaded_ndarrays():
        def data_loader(image_or_imagecollection, bands, geocontext, **kwargs):
            if isinstance(image_or_imagecollection, self.__class__):
                return lambda: image_or_imagecollection.mosaic(
                    bands, geocontext, **kwargs
                )
            else:
                return lambda: image_or_imagecollection._ndarray(
                    bands, geocontext, **kwargs
                )

        with concurrent.futures.ThreadPoolExecutor(
            max_workers=max_workers
        ) as executor:
            future_ndarrays = {}
            for i, image_or_imagecollection in enumerate(images):
                future_ndarray = executor.submit(
                    data_loader(
                        image_or_imagecollection, bands, geocontext, **kwargs
                    )
                )
                future_ndarrays[future_ndarray] = i
            for future in concurrent.futures.as_completed(future_ndarrays):
                i = future_ndarrays[future]
                result = future.result()
                yield i, result

    for i, arr in threaded_ndarrays():
        if raster_info:
            arr, raster_meta = arr
            raster_infos[i] = raster_meta

        if full_stack is None:
            stack_shape = (len(images),) + arr.shape
            full_stack = np.empty(stack_shape, dtype=arr.dtype)
            if isinstance(arr, np.ma.MaskedArray):
                mask = np.empty(stack_shape, dtype=bool)

        if isinstance(arr, np.ma.MaskedArray):
            full_stack[i] = arr.data
            mask[i] = arr.mask
        else:
            full_stack[i] = arr

    if mask is not None:
        full_stack = np.ma.MaskedArray(full_stack, mask, copy=False)
    if raster_info:
        return full_stack, raster_infos
    else:
        return full_stack

mosaic 🔗

mosaic(bands, geocontext=None, crs=None, resolution=None, all_touched=None, mask_nodata=True, mask_alpha=None, bands_axis=0, resampler=ResampleAlgorithm.NEAR, processing_level=None, scaling=None, data_type=None, progress=None, raster_info=False, **kwargs)

Load bands from all images, combining them into a single 3D ndarray and optionally masking invalid data.

Where multiple images overlap, only data from the image that comes last in the ImageCollection is used.

If the selected bands and images have different data types the resulting ndarray has the most general of those data types. See Image.ndarray() <earthdaily.earthone.catalog.image.Image.ndarray> for details on data type conversions.

Parameters🔗

bands : str or Sequence[str] Band names to load. Can be a single string of band names separated by spaces ("red green blue"), or a sequence of band names (["red", "green", "blue"]). If the alpha band is requested, it must be last in the list to reduce rasterization errors. geocontext : :class:~earthdaily.earthone.common.geo.geocontext.GeoContext, default None A :class:~earthdaily.earthone.common.geo.geocontext.GeoContext to use when loading each image. If None then the default context of the collection will be used. If this is None, a ValueError is raised. crs : str, default None if not None, update the gecontext with this value to set the output CRS. resolution : float, default None if not None, update the geocontext with this value to set the output resolution in the units native to the specified or defaulted output CRS. all_touched : float, default None if not None, update the geocontext with this value to control rastering behavior. mask_nodata : bool, default True Whether to mask out values in each band that equal that band's nodata sentinel value. mask_alpha : bool or str or None, default None Whether to mask pixels in all bands where the alpha band of all images is 0. Provide a string to use an alternate band name for masking. If the alpha band is available for all images in the collection and mask_alpha is None, mask_alpha is set to True. If not, mask_alpha is set to False. bands_axis : int, default 0 Axis along which bands should be located in the returned array. If 0, the array will have shape (band, y, x), if -1, it will have shape (y, x, band).

It's usually easier to work with bands as the outermost axis,
but when working with large arrays, or with many arrays concatenated
together, NumPy operations aggregating each xy point across bands
can be slightly faster with bands as the innermost axis.

raster_info : bool, default False Whether to also return a dict of information about the rasterization of the images, including the coordinate system WKT and geotransform matrix. Generally only useful if you plan to upload data derived from this image back to the EarthOne catalog, or use it with GDAL. resampler : ResampleAlgorithm, default ResampleAlgorithm.NEAR Algorithm used to interpolate pixel values when scaling and transforming the image to its new resolution or SRS. processing_level : str, optional How the processing level of the underlying data should be adjusted. Possible values depend on the product and bands in use. Legacy products support toa (top of atmosphere) and in some cases surface. Consult the available processing_levels in the product bands to understand what is available. scaling : None, str, list, dict Band scaling specification. Please see :meth:scaling_parameters for a full description of this parameter. data_type : None, str Output data type. Please see :meth:scaling_parameters for a full description of this parameter. progress : None, bool Controls display of a progress bar.

Returns🔗

arr : ndarray Returned array's shape will be (band, y, x) if bands_axis is 0, and (y, x, band) if bands_axis is -1. If mask_nodata or mask_alpha is True, arr will be a masked array. The data type ("dtype") of the array is the most general of the data types among the images being rastered. raster_info : dict If raster_info=True, a raster information dict is also returned.

Raises🔗

ValueError If requested bands are unavailable, or band names are not given or are invalid. If not all required parameters are specified in the :class:~earthdaily.earthone.common.geo.geocontext.GeoContext. If the ImageCollection is empty. NotFoundError If a Image's ID cannot be found in the EarthOne catalog BadRequestError If the EarthOne Platform is given unrecognized parameters

Source code in earthdaily/earthone/core/catalog/image_collection.py
def mosaic(
    self,
    bands,
    geocontext=None,
    crs=None,
    resolution=None,
    all_touched=None,
    mask_nodata=True,
    mask_alpha=None,
    bands_axis=0,
    resampler=ResampleAlgorithm.NEAR,
    processing_level=None,
    scaling=None,
    data_type=None,
    progress=None,
    raster_info=False,
    **kwargs,
):
    """
    Load bands from all images, combining them into a single 3D ndarray
    and optionally masking invalid data.

    Where multiple images overlap, only data from the image that comes last
    in the ImageCollection is used.

    If the selected bands and images have different data types the resulting
    ndarray has the most general of those data types. See
    `Image.ndarray() <earthdaily.earthone.catalog.image.Image.ndarray>` for details
    on data type conversions.

    Parameters
    ----------
    bands : str or Sequence[str]
        Band names to load. Can be a single string of band names
        separated by spaces (``"red green blue"``),
        or a sequence of band names (``["red", "green", "blue"]``).
        If the alpha band is requested, it must be last in the list
        to reduce rasterization errors.
    geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
        A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading each image.
        If ``None`` then the default context of the collection will be used. If
        this is ``None``, a ValueError is raised.
    crs : str, default None
        if not None, update the gecontext with this value to set the output CRS.
    resolution : float, default None
        if not None, update the geocontext with this value to set the output resolution
        in the units native to the specified or defaulted output CRS.
    all_touched : float, default None
        if not None, update the geocontext with this value to control rastering behavior.
    mask_nodata : bool, default True
        Whether to mask out values in each band that equal
        that band's ``nodata`` sentinel value.
    mask_alpha : bool or str or None, default None
        Whether to mask pixels in all bands where the alpha band of all images is 0.
        Provide a string to use an alternate band name for masking.
        If the alpha band is available for all images in the collection and
        ``mask_alpha`` is None, ``mask_alpha`` is set to True. If not,
        mask_alpha is set to False.
    bands_axis : int, default 0
        Axis along which bands should be located in the returned array.
        If 0, the array will have shape ``(band, y, x)``,
        if -1, it will have shape ``(y, x, band)``.

        It's usually easier to work with bands as the outermost axis,
        but when working with large arrays, or with many arrays concatenated
        together, NumPy operations aggregating each xy point across bands
        can be slightly faster with bands as the innermost axis.
    raster_info : bool, default False
        Whether to also return a dict of information about the rasterization
        of the images, including the coordinate system WKT and geotransform matrix.
        Generally only useful if you plan to upload data derived
        from this image back to the EarthOne catalog, or use it with GDAL.
    resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
        Algorithm used to interpolate pixel values when scaling and transforming
        the image to its new resolution or SRS.
    processing_level : str, optional
        How the processing level of the underlying data should be adjusted. Possible
        values depend on the product and bands in use. Legacy products support
        ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
        available ``processing_levels`` in the product bands to understand what
        is available.
    scaling : None, str, list, dict
        Band scaling specification. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    data_type : None, str
        Output data type. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    progress : None, bool
        Controls display of a progress bar.


    Returns
    -------
    arr : ndarray
        Returned array's shape will be ``(band, y, x)`` if ``bands_axis``
        is 0, and ``(y, x, band)`` if ``bands_axis`` is -1.
        If ``mask_nodata`` or ``mask_alpha`` is True, arr will be a masked array.
        The data type ("dtype") of the array is the most general of the data
        types among the images being rastered.
    raster_info : dict
        If ``raster_info=True``, a raster information dict is also returned.

    Raises
    ------
    ValueError
        If requested bands are unavailable, or band names are not given
        or are invalid.
        If not all required parameters are specified in the
        :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
        If the ImageCollection is empty.
    NotFoundError
        If a Image's ID cannot be found in the EarthOne catalog
    BadRequestError
        If the EarthOne Platform is given unrecognized parameters
    """
    if len(self) == 0:
        raise ValueError("This ImageCollection is empty")

    if geocontext is None:
        geocontext = self.geocontext
        if geocontext is None:
            raise ValueError(
                "No geocontext supplied, and no default geocontext is defined for this ImageCollection"
            )
    if crs is not None or resolution is not None:
        try:
            params = {}
            if crs is not None:
                params["crs"] = crs
            if resolution is not None:
                params["resolution"] = resolution
            geocontext = geocontext.assign(**params)
        except TypeError:
            raise ValueError(
                f"{type(geocontext)} geocontext does not support modifying crs or resolution"
            ) from None
    if all_touched is not None:
        geocontext = geocontext.assign(all_touched=all_touched)

    if not (-3 < bands_axis < 3):
        raise ValueError(
            "Invalid bands_axis; axis {} would not exist in a 3D array".format(
                bands_axis
            )
        )

    bands = bands_to_list(bands)
    product_bands = self._product_bands()
    (bands, scaling, mask_alpha, drop_alpha) = self._mask_alpha_if_applicable(
        product_bands, bands, mask_alpha=mask_alpha, scaling=scaling
    )
    scales, data_type = multiproduct_scaling_parameters(
        product_bands, bands, processing_level, scaling, data_type
    )

    raster_params = geocontext.raster_params
    full_raster_args = dict(
        inputs=[image.id for image in self],
        order="gdal",
        bands=bands,
        scales=scales,
        data_type=data_type,
        resampler=resampler,
        processing_level=processing_level,
        mask_nodata=bool(mask_nodata),
        mask_alpha=mask_alpha,
        drop_alpha=drop_alpha,
        masked=mask_nodata or mask_alpha,
        progress=progress,
        **raster_params,
    )

    raster = kwargs.pop("raster_client", None) or Raster.get_default_client()
    if kwargs:
        raise TypeError(f"Unexpected keyword arguments: {kwargs}")
    try:
        arr, info = raster.ndarray(**full_raster_args)
    except NotFoundError:
        raise NotFoundError(
            "Some or all of these IDs don't exist in the EarthOne catalog: {}".format(
                full_raster_args["inputs"]
            )
        )
    except BadRequestError as e:
        msg = (
            "Error with request:\n"
            "{err}\n"
            "For reference, Raster.ndarray was called with these arguments:\n"
            "{args}"
        )
        msg = msg.format(err=e, args=json.dumps(full_raster_args, indent=2))
        raise BadRequestError(msg) from None

    if len(arr.shape) == 2:
        # if only 1 band requested, still return a 3d array
        arr = arr[np.newaxis]

    if bands_axis != 0:
        arr = np.moveaxis(arr, 0, bands_axis)
    if raster_info:
        return arr, info
    else:
        return arr

download 🔗

download(bands, geocontext=None, crs=None, resolution=None, all_touched=None, dest=None, format=DownloadFileFormat.TIF, resampler=ResampleAlgorithm.NEAR, processing_level=None, scaling=None, data_type=None, progress=None, max_workers=None, **kwargs)

Download images as image files in parallel.

Parameters🔗

bands : str or Sequence[str] Band names to load. Can be a single string of band names separated by spaces ("red green blue"), or a sequence of band names (["red", "green", "blue"]). geocontext : :class:~earthdaily.earthone.common.geo.geocontext.GeoContext, default None A :class:~earthdaily.earthone.common.geo.geocontext.GeoContext to use when loading each image. If None then the default context of the collection will be used. If this is None, a ValueError is raised. crs : str, default None if not None, update the gecontext with this value to set the output CRS. resolution : float, default None if not None, update the geocontext with this value to set the output resolution in the units native to the specified or defaulted output CRS. all_touched : float, default None if not None, update the geocontext with this value to control rastering behavior. dest : str, path-like, or sequence of str or path-like, default None Directory or sequence of paths to which to write the image files.

If ``None``, the current directory is used.

If a directory, files within it will be named by
their image IDs and the bands requested, like
``"sentinel-2:L1C:2018-08-10_10TGK_68_S2A_v1-red-green-blue.tif"``.

If a sequence of paths of the same length as the ImageCollection is given,
each Image will be written to the corresponding path. This lets you use your
own naming scheme, or even write images to multiple directories.

Any intermediate paths are created if they do not exist,
for both a single directory and a sequence of paths.

format : DownloadFileFormat, default DownloadFileFormat.TIF Output file format to use. If dest is a sequence of paths, format is ignored and determined by the extension on each path. resampler : ResampleAlgorithm, default ResampleAlgorithm.NEAR Algorithm used to interpolate pixel values when scaling and transforming the image to its new resolution or SRS. processing_level : str, optional How the processing level of the underlying data should be adjusted. Possible values depend on the product and bands in use. Legacy products support toa (top of atmosphere) and in some cases surface. Consult the available processing_levels in the product bands to understand what is available. scaling : None, str, list, dict Band scaling specification. Please see :meth:scaling_parameters for a full description of this parameter. data_type : None, str Output data type. Please see :meth:scaling_parameters for a full description of this parameter. progress : None, bool Controls display of a progress bar. max_workers : int, default None Maximum number of threads to use to parallelize individual download calls to each Image. If None, it defaults to the number of processors on the machine, multiplied by 5. Note that unnecessary threads won't be created if max_workers is greater than the number of Images in the ImageCollection.

Returns🔗

paths : Sequence[str] A list of all the paths where files were written.

Example🔗

import earthdaily.earthone as eo tile = eo.common.geo.DLTile.from_key("256:0:75.0:15:-5:230") # doctest: +SKIP product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR") # doctest: +SKIP images = product.images().intersects(tile).limit(5).collect() # doctest: +SKIP images.download("red green blue", tile, "rasters") # doctest: +SKIP ["rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013108_v1-red-green-blue.tif", "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013124_v1-red-green-blue.tif", "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013140_v1-red-green-blue.tif", "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013156_v1-red-green-blue.tif", "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013172_v1-red-green-blue.tif"]

use explicit paths for a custom naming scheme:🔗

paths = [ ... "{tile.key}/l8-{image.date:%Y-%m-%d-%H:%m}.tif".format(tile=tile, image=image) ... for image in images ... ] # doctest: +SKIP images.download("nir red", tile, paths) # doctest: +SKIP ["256:0:75.0:15:-5:230/l8-2013-04-18-16:04.tif", "256:0:75.0:15:-5:230/l8-2013-05-04-16:05.tif", "256:0:75.0:15:-5:230/l8-2013-05-20-16:05.tif", "256:0:75.0:15:-5:230/l8-2013-06-05-16:06.tif", "256:0:75.0:15:-5:230/l8-2013-06-21-16:06.tif"]

Raises🔗

RuntimeError If the paths given are not all unique. If there is an error generating default filenames. ValueError If requested bands are unavailable, or band names are not given or are invalid. If not all required parameters are specified in the :class:~earthdaily.earthone.common.geo.geocontext.GeoContext. If the ImageCollection is empty. If dest is a sequence not equal in length to the ImageCollection. If format is invalid, or a path has an invalid extension. TypeError If dest is not a string or a sequence type. NotFoundError If a Image's ID cannot be found in the EarthOne catalog BadRequestError If the EarthOne Platform is given unrecognized parameters

Source code in earthdaily/earthone/core/catalog/image_collection.py
def download(
    self,
    bands,
    geocontext=None,
    crs=None,
    resolution=None,
    all_touched=None,
    dest=None,
    format=DownloadFileFormat.TIF,
    resampler=ResampleAlgorithm.NEAR,
    processing_level=None,
    scaling=None,
    data_type=None,
    progress=None,
    max_workers=None,
    **kwargs,
):
    """
    Download images as image files in parallel.

    Parameters
    ----------
    bands : str or Sequence[str]
        Band names to load. Can be a single string of band names
        separated by spaces (``"red green blue"``),
        or a sequence of band names (``["red", "green", "blue"]``).
    geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
        A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading each image.
        If ``None`` then the default context of the collection will be used. If
        this is ``None``, a ValueError is raised.
    crs : str, default None
        if not None, update the gecontext with this value to set the output CRS.
    resolution : float, default None
        if not None, update the geocontext with this value to set the output resolution
        in the units native to the specified or defaulted output CRS.
    all_touched : float, default None
        if not None, update the geocontext with this value to control rastering behavior.
    dest : str, path-like, or sequence of str or path-like, default None
        Directory or sequence of paths to which to write the image files.

        If ``None``, the current directory is used.

        If a directory, files within it will be named by
        their image IDs and the bands requested, like
        ``"sentinel-2:L1C:2018-08-10_10TGK_68_S2A_v1-red-green-blue.tif"``.

        If a sequence of paths of the same length as the ImageCollection is given,
        each Image will be written to the corresponding path. This lets you use your
        own naming scheme, or even write images to multiple directories.

        Any intermediate paths are created if they do not exist,
        for both a single directory and a sequence of paths.
    format : `DownloadFileFormat`, default `DownloadFileFormat.TIF`
        Output file format to use.
        If ``dest`` is a sequence of paths, ``format`` is ignored
        and determined by the extension on each path.
    resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
        Algorithm used to interpolate pixel values when scaling and transforming
        the image to its new resolution or SRS.
    processing_level : str, optional
        How the processing level of the underlying data should be adjusted. Possible
        values depend on the product and bands in use. Legacy products support
        ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
        available ``processing_levels`` in the product bands to understand what
        is available.
    scaling : None, str, list, dict
        Band scaling specification. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    data_type : None, str
        Output data type. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    progress : None, bool
        Controls display of a progress bar.
    max_workers : int, default None
        Maximum number of threads to use to parallelize individual ``download``
        calls to each Image.
        If None, it defaults to the number of processors on the machine,
        multiplied by 5.
        Note that unnecessary threads *won't* be created if ``max_workers``
        is greater than the number of Images in the ImageCollection.

    Returns
    -------
    paths : Sequence[str]
        A list of all the paths where files were written.

    Example
    -------
    >>> import earthdaily.earthone as eo
    >>> tile = eo.common.geo.DLTile.from_key("256:0:75.0:15:-5:230")  # doctest: +SKIP
    >>> product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR")  # doctest: +SKIP
    >>> images = product.images().intersects(tile).limit(5).collect()  # doctest: +SKIP
    >>> images.download("red green blue", tile, "rasters")  # doctest: +SKIP
    ["rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013108_v1-red-green-blue.tif",
     "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013124_v1-red-green-blue.tif",
     "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013140_v1-red-green-blue.tif",
     "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013156_v1-red-green-blue.tif",
     "rasters/landsat:LC08:PRE:TOAR:meta_LC80260322013172_v1-red-green-blue.tif"]
    >>> # use explicit paths for a custom naming scheme:
    >>> paths = [
    ...     "{tile.key}/l8-{image.date:%Y-%m-%d-%H:%m}.tif".format(tile=tile, image=image)
    ...     for image in images
    ... ]  # doctest: +SKIP
    >>> images.download("nir red", tile, paths)  # doctest: +SKIP
    ["256:0:75.0:15:-5:230/l8-2013-04-18-16:04.tif",
     "256:0:75.0:15:-5:230/l8-2013-05-04-16:05.tif",
     "256:0:75.0:15:-5:230/l8-2013-05-20-16:05.tif",
     "256:0:75.0:15:-5:230/l8-2013-06-05-16:06.tif",
     "256:0:75.0:15:-5:230/l8-2013-06-21-16:06.tif"]

    Raises
    ------
    RuntimeError
        If the paths given are not all unique.
        If there is an error generating default filenames.
    ValueError
        If requested bands are unavailable, or band names are not given
        or are invalid.
        If not all required parameters are specified in the
        :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
        If the ImageCollection is empty.
        If ``dest`` is a sequence not equal in length to the ImageCollection.
        If ``format`` is invalid, or a path has an invalid extension.
    TypeError
        If ``dest`` is not a string or a sequence type.
    NotFoundError
        If a Image's ID cannot be found in the EarthOne catalog
    BadRequestError
        If the EarthOne Platform is given unrecognized parameters
    """
    if len(self) == 0:
        raise ValueError("This ImageCollection is empty")

    if geocontext is None:
        geocontext = self.geocontext
        if geocontext is None:
            raise ValueError(
                "No geocontext supplied, and no default geocontext is defined for this ImageCollection"
            )
    if crs is not None or resolution is not None:
        try:
            params = {}
            if crs is not None:
                params["crs"] = crs
            if resolution is not None:
                params["resolution"] = resolution
            geocontext = geocontext.assign(**params)
        except TypeError:
            raise ValueError(
                f"{type(geocontext)} geocontext does not support modifying crs or resolution"
            ) from None
    if all_touched is not None:
        geocontext = geocontext.assign(all_touched=all_touched)

    if dest is None:
        dest = "."

    bands = bands_to_list(bands)
    scales, data_type = multiproduct_scaling_parameters(
        self._product_bands(), bands, processing_level, scaling, data_type
    )

    if is_path_like(dest):
        default_pattern = "{image.id}-{bands}.{ext}"
        bands_str = "-".join(bands)
        try:
            dest = [
                os.path.join(
                    dest,
                    default_pattern.format(
                        image=image, bands=bands_str, ext=format
                    ),
                )
                for image in self
            ]
        except Exception as e:
            raise RuntimeError(
                "Error while generating default filenames:\n{}\n"
                "This is likely due to missing or unexpected data "
                "in Images in this ImageCollection.".format(e)
            ) from None

    try:
        if len(dest) != len(self):
            raise ValueError(
                "`dest` contains {} items, but the ImageCollection contains {}".format(
                    len(dest), len(self)
                )
            )
    except TypeError:
        raise TypeError(
            "`dest` should be a sequence of strings or path-like objects; "
            "instead found type {}, which has no length".format(type(dest))
        ) from None

    # check for duplicate paths to prevent the confusing situation where
    # multiple rasters overwrite the same filename
    unique = set()
    for path in dest:
        if path in unique:
            raise RuntimeError(
                "Paths must be unique, but '{}' occurs multiple times".format(path)
            )
        else:
            unique.add(path)

    download_args = dict(
        resampler=resampler,
        processing_level=processing_level,
        scaling=scales,
        data_type=data_type,
        progress=progress,
        **kwargs,
    )
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(
                image._download, bands, geocontext, dest=path, **download_args
            ): path
            for image, path in zip(self, dest)
        }
        exceptions = []
        for future in concurrent.futures.as_completed(futures):
            try:
                future.result()
            except Exception as ex:
                exceptions.append((futures[future], ex))
        if exceptions:
            raise RuntimeError(
                "One or more downloads failed: {}".format(exceptions)
            )
    return dest

download_mosaic 🔗

download_mosaic(bands, geocontext=None, crs=None, resolution=None, all_touched=None, dest=None, format=DownloadFileFormat.TIF, resampler=ResampleAlgorithm.NEAR, processing_level=None, scaling=None, data_type=None, mask_alpha=None, nodata=None, progress=None, **kwargs)

Download all images as a single image file. Where multiple images overlap, only data from the image that comes last in the ImageCollection is used.

Parameters🔗

bands : str or Sequence[str] Band names to load. Can be a single string of band names separated by spaces ("red green blue"), or a sequence of band names (["red", "green", "blue"]). geocontext : :class:~earthdaily.earthone.common.geo.geocontext.GeoContext, default None A :class:~earthdaily.earthone.common.geo.geocontext.GeoContext to use when loading each image. If None then the default context of the collection will be used. If this is None, a ValueError is raised. crs : str, default None if not None, update the gecontext with this value to set the output CRS. resolution : float, default None if not None, update the geocontext with this value to set the output resolution in the units native to the specified or defaulted output CRS. all_touched : float, default None if not None, update the geocontext with this value to control rastering behavior. dest : str or path-like object, default None Where to write the image file.

* If None (default), it's written to an image file of the given ``format``
  in the current directory, named by the requested bands,
  like ``"mosaic-red-green-blue.tif"``
* If a string or path-like object, it's written to that path.

  Any file already existing at that path will be overwritten.

  Any intermediate directories will be created if they don't exist.

  Note that path-like objects (such as pathlib.Path) are only supported
  in Python 3.6 or later.

format : DownloadFileFormat, default DownloadFileFormat.TIF Output file format to use. If a str or path-like object is given as dest, format is ignored and determined from the extension on the path (one of ".tif", ".png", or ".jpg"). resampler : ResampleAlgorithm, default ResampleAlgorithm.NEAR Algorithm used to interpolate pixel values when scaling and transforming the image to its new resolution or SRS. processing_level : str, optional How the processing level of the underlying data should be adjusted. Possible values depend on the product and bands in use. Legacy products support toa (top of atmosphere) and in some cases surface. Consult the available processing_levels in the product bands to understand what is available. scaling : None, str, list, dict Band scaling specification. Please see :meth:scaling_parameters for a full description of this parameter. data_type : None, str Output data type. Please see :meth:scaling_parameters for a full description of this parameter. mask_alpha : bool or str or None, default None Whether to mask pixels in all bands where the alpha band of all images is 0. Provide a string to use an alternate band name for masking. If the alpha band is available for all images in the collection and mask_alpha is None, mask_alpha is set to True. If not, mask_alpha is set to False. nodata : None, number NODATA value for a geotiff file. Will be assigned to any masked pixels. progress : None, bool Controls display of a progress bar.

Returns🔗

path : str or None If dest is a path or None, the path where the image file was written is returned. If dest is file-like, nothing is returned.

Example🔗

import earthdaily.earthone as eo tile = eo.common.geo.DLTile.from_key("256:0:75.0:15:-5:230") # doctest: +SKIP product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR") # doctest: +SKIP images = product.images().intersects(tile).limit(5).collect() # doctest: +SKIP images.download_mosaic("nir red", mask_alpha=False) # doctest: +SKIP 'mosaic-nir-red.tif' images.download_mosaic("red green blue", dest="mosaics/{}.png".format(tile.key)) # doctest: +SKIP 'mosaics/256:0:75.0:15:-5:230.tif'

Raises🔗

ValueError If requested bands are unavailable, or band names are not given or are invalid. If not all required parameters are specified in the :class:~earthdaily.earthone.common.geo.geocontext.GeoContext. If the ImageCollection is empty. If format is invalid, or the path has an invalid extension. NotFoundError If a Image's ID cannot be found in the EarthOne catalog BadRequestError If the EarthOne Platform is given unrecognized parameters

Source code in earthdaily/earthone/core/catalog/image_collection.py
def download_mosaic(
    self,
    bands,
    geocontext=None,
    crs=None,
    resolution=None,
    all_touched=None,
    dest=None,
    format=DownloadFileFormat.TIF,
    resampler=ResampleAlgorithm.NEAR,
    processing_level=None,
    scaling=None,
    data_type=None,
    mask_alpha=None,
    nodata=None,
    progress=None,
    **kwargs,
):
    """
    Download all images as a single image file.
    Where multiple images overlap, only data from the image that comes last
    in the ImageCollection is used.

    Parameters
    ----------
    bands : str or Sequence[str]
        Band names to load. Can be a single string of band names
        separated by spaces (``"red green blue"``),
        or a sequence of band names (``["red", "green", "blue"]``).
    geocontext : :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`, default None
        A :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext` to use when loading each image.
        If ``None`` then the default context of the collection will be used. If
        this is ``None``, a ValueError is raised.
    crs : str, default None
        if not None, update the gecontext with this value to set the output CRS.
    resolution : float, default None
        if not None, update the geocontext with this value to set the output resolution
        in the units native to the specified or defaulted output CRS.
    all_touched : float, default None
        if not None, update the geocontext with this value to control rastering behavior.
    dest : str or path-like object, default None
        Where to write the image file.

        * If None (default), it's written to an image file of the given ``format``
          in the current directory, named by the requested bands,
          like ``"mosaic-red-green-blue.tif"``
        * If a string or path-like object, it's written to that path.

          Any file already existing at that path will be overwritten.

          Any intermediate directories will be created if they don't exist.

          Note that path-like objects (such as pathlib.Path) are only supported
          in Python 3.6 or later.
    format : `DownloadFileFormat`, default `DownloadFileFormat.TIF`
        Output file format to use.
        If a str or path-like object is given as ``dest``, ``format`` is ignored
        and determined from the extension on the path (one of ".tif", ".png", or ".jpg").
    resampler : `ResampleAlgorithm`, default `ResampleAlgorithm.NEAR`
        Algorithm used to interpolate pixel values when scaling and transforming
        the image to its new resolution or SRS.
    processing_level : str, optional
        How the processing level of the underlying data should be adjusted. Possible
        values depend on the product and bands in use. Legacy products support
        ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
        available ``processing_levels`` in the product bands to understand what
        is available.
    scaling : None, str, list, dict
        Band scaling specification. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    data_type : None, str
        Output data type. Please see :meth:`scaling_parameters` for a full
        description of this parameter.
    mask_alpha : bool or str or None, default None
        Whether to mask pixels in all bands where the alpha band of all images is 0.
        Provide a string to use an alternate band name for masking.
        If the alpha band is available for all images in the collection and
        ``mask_alpha`` is None, ``mask_alpha`` is set to True. If not,
        mask_alpha is set to False.
    nodata : None, number
        NODATA value for a geotiff file. Will be assigned to any masked pixels.
    progress : None, bool
        Controls display of a progress bar.

    Returns
    -------
    path : str or None
        If ``dest`` is a path or None, the path where the image file was written is returned.
        If ``dest`` is file-like, nothing is returned.

    Example
    -------
    >>> import earthdaily.earthone as eo
    >>> tile = eo.common.geo.DLTile.from_key("256:0:75.0:15:-5:230")  # doctest: +SKIP
    >>> product = eo.catalog.Product.get("landsat:LC08:PRE:TOAR")  # doctest: +SKIP
    >>> images = product.images().intersects(tile).limit(5).collect()  # doctest: +SKIP
    >>> images.download_mosaic("nir red", mask_alpha=False)  # doctest: +SKIP
    'mosaic-nir-red.tif'
    >>> images.download_mosaic("red green blue", dest="mosaics/{}.png".format(tile.key))  # doctest: +SKIP
    'mosaics/256:0:75.0:15:-5:230.tif'


    Raises
    ------
    ValueError
        If requested bands are unavailable, or band names are not given
        or are invalid.
        If not all required parameters are specified in the
        :class:`~earthdaily.earthone.common.geo.geocontext.GeoContext`.
        If the ImageCollection is empty.
        If ``format`` is invalid, or the path has an invalid extension.
    NotFoundError
        If a Image's ID cannot be found in the EarthOne catalog
    BadRequestError
        If the EarthOne Platform is given unrecognized parameters
    """
    if len(self) == 0:
        raise ValueError("This ImageCollection is empty")

    if geocontext is None:
        geocontext = self.geocontext
        if geocontext is None:
            raise ValueError(
                "No geocontext supplied, and no default geocontext is defined for this ImageCollection"
            )
    if crs is not None or resolution is not None:
        try:
            params = {}
            if crs is not None:
                params["crs"] = crs
            if resolution is not None:
                params["resolution"] = resolution
            geocontext = geocontext.assign(**params)
        except TypeError:
            raise ValueError(
                f"{type(geocontext)} geocontext does not support modifying crs or resolution"
            ) from None
    if all_touched is not None:
        geocontext = geocontext.assign(all_touched=all_touched)

    bands = bands_to_list(bands)
    product_bands = self._product_bands()
    (bands, scaling, mask_alpha, drop_alpha) = self._mask_alpha_if_applicable(
        product_bands, bands, mask_alpha=mask_alpha, scaling=scaling
    )
    scales, data_type = multiproduct_scaling_parameters(
        product_bands, bands, processing_level, scaling, data_type
    )

    return download(
        inputs=self.each.id.collect(list),
        bands_list=bands,
        geocontext=geocontext,
        scales=scales,
        data_type=data_type,
        dest=dest,
        format=format,
        resampler=resampler,
        processing_level=processing_level,
        nodata=nodata,
        progress=progress,
        **kwargs,
    )

scaling_parameters 🔗

scaling_parameters(bands, processing_level=None, scaling=None, data_type=None)

Computes fully defaulted scaling parameters and output data_type from provided specifications.

This method is provided as a convenience to the user to help with understanding how scaling and data_type parameters passed to other methods on this class (e.g. :meth:stack or :meth:mosaic) will be interpreted. It would not usually be used in a normal workflow.

A image collection may contain images from more than one product, introducing the possibility that the band properties for a band of a given name may differ from product to product. This method works in a similar fashion to the :meth:Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters> method, but it additionally ensures that the resulting scale elements are compatible across the multiple products. If there is an incompatibility, an appropriate ValueError will be raised.

Parameters🔗

bands : list(str) List of bands to be scaled. processing_level : str, optional How the processing level of the underlying data should be adjusted. Possible values depend on the product and bands in use. Legacy products support toa (top of atmosphere) and in some cases surface. Consult the available processing_levels in the product bands to understand what is available. scaling : None or str or list or dict Band scaling specification. See :meth:Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters> for a full description of this parameter. data_type : None or str Result data type desired, as a standard data type string (e.g. "Byte", "Uint16", or "Float64"). If not specified, will be deduced from the scaling specification. See :meth:Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters> for a full description of this parameter.

Returns🔗

scales : list(tuple) The fully specified scaling parameter, compatible with the :class:~earthdaily.earthone.client.services.raster.Raster API and the output data type. data_type : str The result data type as a standard GDAL type string.

Raises🔗

ValueError If any invalid or incompatible value is passed to any of the three parameters.

See Also🔗

:doc:Catalog Guide </guides/catalog> : This contains many examples of the use of the scaling and data_type parameters.

Source code in earthdaily/earthone/core/catalog/image_collection.py
def scaling_parameters(
    self, bands, processing_level=None, scaling=None, data_type=None
):
    """
    Computes fully defaulted scaling parameters and output data_type
    from provided specifications.

    This method is provided as a convenience to the user to help with
    understanding how ``scaling`` and ``data_type`` parameters passed
    to other methods on this class (e.g. :meth:`stack` or :meth:`mosaic`)
    will be interpreted. It would not usually be used in a normal
    workflow.

    A image collection may contain images from more than one product,
    introducing the possibility that the band properties for a band
    of a given name may differ from product to product. This method
    works in a similar fashion to the
    :meth:`Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters>`
    method, but it additionally ensures that the resulting scale
    elements are compatible across the multiple products. If there
    is an incompatibility, an appropriate ValueError will be raised.

    Parameters
    ----------
    bands : list(str)
        List of bands to be scaled.
    processing_level : str, optional
        How the processing level of the underlying data should be adjusted. Possible
        values depend on the product and bands in use. Legacy products support
        ``toa`` (top of atmosphere) and in some cases ``surface``. Consult the
        available ``processing_levels`` in the product bands to understand what
        is available.
    scaling : None or str or list or dict
        Band scaling specification. See
        :meth:`Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters>`
        for a full description of this parameter.
    data_type : None or str
        Result data type desired, as a standard data type string (e.g.
        ``"Byte"``, ``"Uint16"``, or ``"Float64"``). If not specified,
        will be deduced from the ``scaling`` specification. See
        :meth:`Image.scaling_parameters <earthdaily.earthone.catalog.image.Image.scaling_parameters>`
        for a full description of this parameter.

    Returns
    -------
    scales : list(tuple)
        The fully specified scaling parameter, compatible with the
        :class:`~earthdaily.earthone.client.services.raster.Raster` API and the
        output data type.
    data_type : str
        The result data type as a standard GDAL type string.

    Raises
    ------
    ValueError
        If any invalid or incompatible value is passed to any of the
        three parameters.

    See Also
    --------
    :doc:`Catalog Guide </guides/catalog>` : This contains many examples of the use of
    the ``scaling`` and ``data_type`` parameters.
    """
    bands = bands_to_list(bands)
    return multiproduct_scaling_parameters(
        self._product_bands(), bands, processing_level, scaling, data_type
    )

Band🔗

Band 🔗

Bases: NamedCatalogObject

A data band in images of a specific product.

This is an abstract class that cannot be instantiated, but can be used for searching across all types of bands. The concrete bands are represented by the derived classes.

Common attributes: :attr:~earthdaily.earthone.catalog.GenericBand.id, :attr:~earthdaily.earthone.catalog.GenericBand.name, :attr:~earthdaily.earthone.catalog.GenericBand.product_id, :attr:~earthdaily.earthone.catalog.GenericBand.description, :attr:~earthdaily.earthone.catalog.GenericBand.type, :attr:~earthdaily.earthone.catalog.GenericBand.sort_order, :attr:~earthdaily.earthone.catalog.GenericBand.vendor_order, :attr:~earthdaily.earthone.catalog.GenericBand.data_type, :attr:~earthdaily.earthone.catalog.GenericBand.nodata, :attr:~earthdaily.earthone.catalog.GenericBand.data_range, :attr:~earthdaily.earthone.catalog.GenericBand.display_range, :attr:~earthdaily.earthone.catalog.GenericBand.resolution, :attr:~earthdaily.earthone.catalog.GenericBand.band_index, :attr:~earthdaily.earthone.catalog.GenericBand.file_index, :attr:~earthdaily.earthone.catalog.GenericBand.vendor_band_name.

To create a new band instantiate one of those specialized classes:

  • SpectralBand: A band that lies somewhere on the visible/NIR/SWIR electro-optical wavelength spectrum. Specific attributes: :attr:~SpectralBand.physical_range, :attr:~SpectralBand.physical_range_unit, :attr:~SpectralBand.wavelength_nm_center, :attr:~SpectralBand.wavelength_nm_min, :attr:~SpectralBand.wavelength_nm_max, :attr:~SpectralBand.wavelength_nm_fwhm, :attr:~SpectralBand.processing_levels, :attr:~SpectralBand.derived_params.
  • MicrowaveBand: A band that lies in the microwave spectrum, often from SAR or passive radar sensors. Specific attributes: :attr:~MicrowaveBand.frequency, :attr:~MicrowaveBand.bandwidth, :attr:~MicrowaveBand.physical_range, :attr:~MicrowaveBand.physical_range_unit.
  • MaskBand: A binary band where by convention a 0 means masked and 1 means non-masked. The :attr:~Band.data_range and :attr:~Band.display_range for masks is implicitly [0, 1]. Specific attributes: :attr:~MaskBand.is_alpha.
  • ClassBand: A band that maps a finite set of values that may not be continuous to classification categories (e.g. a land use classification). A visualization with straight pixel values is typically not useful, so commonly a :attr:~ClassBand.colormap is used. Specific attributes: :attr:~ClassBand.colormap, :attr:~ClassBand.colormap_name, :attr:~ClassBand.class_labels.
  • GenericBand: A generic type for bands that are not represented by the other band types, e.g., mapping physical values like temperature or angles. Specific attributes: :attr:~GenericBand.colormap, :attr:~GenericBand.colormap_name, :attr:~GenericBand.physical_range, :attr:~GenericBand.physical_range_unit, :attr:~GenericBand.processing_levels, :attr:~GenericBand.derived_params.
Source code in earthdaily/earthone/core/catalog/band.py
class Band(NamedCatalogObject):
    """A data band in images of a specific product.

    This is an abstract class that cannot be instantiated, but can be used for searching
    across all types of bands.  The concrete bands are represented by the derived
    classes.

    Common attributes:
    :attr:`~earthdaily.earthone.catalog.GenericBand.id`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.name`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.product_id`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.description`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.type`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.sort_order`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.vendor_order`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.data_type`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.nodata`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.data_range`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.display_range`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.resolution`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.band_index`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.file_index`,
    :attr:`~earthdaily.earthone.catalog.GenericBand.vendor_band_name`.

    To create a new band instantiate one of those specialized classes:

    * `SpectralBand`: A band that lies somewhere on the visible/NIR/SWIR electro-optical
      wavelength spectrum. Specific attributes:
      :attr:`~SpectralBand.physical_range`,
      :attr:`~SpectralBand.physical_range_unit`,
      :attr:`~SpectralBand.wavelength_nm_center`,
      :attr:`~SpectralBand.wavelength_nm_min`,
      :attr:`~SpectralBand.wavelength_nm_max`,
      :attr:`~SpectralBand.wavelength_nm_fwhm`,
      :attr:`~SpectralBand.processing_levels`,
      :attr:`~SpectralBand.derived_params`.
    * `MicrowaveBand`: A band that lies in the microwave spectrum, often from SAR or
      passive radar sensors. Specific attributes:
      :attr:`~MicrowaveBand.frequency`,
      :attr:`~MicrowaveBand.bandwidth`,
      :attr:`~MicrowaveBand.physical_range`,
      :attr:`~MicrowaveBand.physical_range_unit`.
    * `MaskBand`: A binary band where by convention a 0 means masked and 1 means
      non-masked. The :attr:`~Band.data_range` and :attr:`~Band.display_range` for
      masks is implicitly ``[0, 1]``. Specific attributes:
      :attr:`~MaskBand.is_alpha`.
    * `ClassBand`: A band that maps a finite set of values that may not be continuous to
      classification categories (e.g. a land use classification). A visualization with
      straight pixel values is typically not useful, so commonly a
      :attr:`~ClassBand.colormap` is used. Specific attributes:
      :attr:`~ClassBand.colormap`,
      :attr:`~ClassBand.colormap_name`,
      :attr:`~ClassBand.class_labels`.
    * `GenericBand`: A generic type for bands that are not represented by the other band
      types, e.g., mapping physical values like temperature or angles. Specific
      attributes:
      :attr:`~GenericBand.colormap`,
      :attr:`~GenericBand.colormap_name`,
      :attr:`~GenericBand.physical_range`,
      :attr:`~GenericBand.physical_range_unit`,
      :attr:`~GenericBand.processing_levels`,
      :attr:`~GenericBand.derived_params`.
    """

    _DOC_DESCRIPTION = """A description with further details on the band.

        The description can be up to 80,000 characters and is used by
        :py:meth:`Search.find_text`.

        *Searchable*
        """
    _DOC_DATATYPE = "The data type for pixel values in this band."
    _DOC_DATARANGE = """The range of pixel values stored in the band.

        The two floats are the minimum and maximum pixel values stored in this band.
        """
    _DOC_COLORMAPNAME = """str or Colormap, optional: Name of a predefined colormap for display purposes.

        The colormap is applied when this band is rastered by itself in PNG or TIFF
        format, including in UIs where imagery is visualized.
        """
    _DOC_COLORMAP = """list(tuple), optional: A custom colormap for this band.

        A list of tuples, where each nested tuple is a 4-tuple of RGBA values to map
        pixels whose value is the index of the list.  E.g.  the colormap ``[(100, 20,
        200, 255)]`` would map pixels whose value is 0 in the original band to the
        RGBA color defined by ``(100, 20, 200, 255)``.  The number of 4-tuples provided
        can be up to the maximum of this band's data range.  Omitted values will map
        to black by default.
        """
    _DOC_PHYSICALRANGE = (
        "tuple(float, float), optional: A physical range that pixel values map to."
    )

    _doc_type = "band"
    _url = "/bands"
    _derived_type_switch = "type"
    _default_includes = ["product"]
    # _collection_type set below due to circular problems

    description = Attribute(doc="str, optional: " + _DOC_DESCRIPTION)
    type = EnumAttribute(
        BandType,
        doc="""str or BandType: The type of this band, directly corresponding to a `Band` derived class.

        The derived classes are `SpectralBand`, `MicrowaveBand`, `MaskBand`,
        `ClassBand`, and `GenericBand`.  The type never needs to be set explicitly,
        this attribute is implied by the derived class used.  The type of a band does
        not necessarily affect how it is rastered, it mainly conveys useful information
        about the data it contains.

        *Filterable*.
        """,
    )
    sort_order = TypedAttribute(
        int,
        doc="""int, optional: A number defining the default sort order for bands within a product.

        If not set for newly created bands, this will default to the current maximum
        sort order + 1 in the product.

        *Sortable*.
        """,
    )
    vendor_order = TypedAttribute(
        int,
        doc="""int, optional: A number defining the ordering of bands within a product
        as defined by the data vendor. 1-based. Generally only used internally by certain
        core products.

        *Sortable*.
        """,
    )
    data_type = EnumAttribute(DataType, doc="str or DataType: " + _DOC_DATATYPE)
    nodata = Attribute(
        doc="""float, optional: A value representing missing data in a pixel in this band."""
    )
    data_range = TupleAttribute(
        min_length=2,
        max_length=2,
        coerce=True,
        attribute_type=float,
        doc="tuple(float, float): " + _DOC_DATARANGE,
    )
    display_range = TupleAttribute(
        min_length=2,
        max_length=2,
        coerce=True,
        attribute_type=float,
        doc="""tuple(float, float): The range of pixel values for display purposes.

        The two floats are the minimum and maximum values indicating a default reasonable
        range of pixel values usd when rastering this band for display purposes.
        """,
    )
    resolution = Resolution(
        doc="""Resolution, optional: The spatial resolution of this band.

        *Filterable, sortable*.
        """
    )
    band_index = TypedAttribute(
        int, doc="int: The 0-based index into the source data to access this band."
    )
    file_index = Attribute(
        doc="""int, optional: The 0-based index into the list of source files.

        If there are multiple files, it maps the band index to the file index.  It defaults
        to 0 (first file).
        """
    )
    vendor_band_name = TypedAttribute(
        str,
        doc="""str, optional: The name of the band in the source file.

        Some source file types require that the band be indexed by name rather than by the ``band_index``.
        """,
    )
    processing_levels = ProcessingLevelsAttribute()
    derived_params = DerivedParamsAttribute()

    def __new__(cls, *args, **kwargs):
        return _new_abstract_class(cls, Band)

    def __init__(self, **kwargs):
        if self._derived_type_switch not in kwargs:
            kwargs[self._derived_type_switch] = self._derived_type

        super(Band, self).__init__(**kwargs)

    @classmethod
    def search(cls, client=None, request_params=None, headers=None):
        """A search query for all bands.

        Returns an instance of the
        :py:class:`~earthdaily.earthone.catalog.Search` class configured for
        searching bands.  Call this on the :py:class:`Band` base class to search all
        types of bands or classes :py:class:`SpectralBand`, :py:class:`MicrowaveBand`,
        :py:class:`MaskBand`, :py:class:`ClassBand` and :py:class:`GenericBand` to search
        only a specific type of band.


        Parameters
        ----------
        client : :py:class:`CatalogClient`
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.Search`
            An instance of the :py:class:`~earthdaily.earthone.catalog.Search` class
        """
        search = super(Band, cls).search(
            client, request_params=request_params, headers=headers
        )
        if cls._derived_type:
            search = search.filter(properties.type == cls._derived_type)
        return search

search classmethod 🔗

search(client=None, request_params=None, headers=None)

A search query for all bands.

Returns an instance of the 🇵🇾class:~earthdaily.earthone.catalog.Search class configured for searching bands. Call this on the 🇵🇾class:Band base class to search all types of bands or classes 🇵🇾class:SpectralBand, 🇵🇾class:MicrowaveBand, 🇵🇾class:MaskBand, 🇵🇾class:ClassBand and 🇵🇾class:GenericBand to search only a specific type of band.

Parameters🔗

client : 🇵🇾class:CatalogClient A CatalogClient instance to use for requests to the EarthOne catalog.

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.Search An instance of the 🇵🇾class:~earthdaily.earthone.catalog.Search class

Source code in earthdaily/earthone/core/catalog/band.py
@classmethod
def search(cls, client=None, request_params=None, headers=None):
    """A search query for all bands.

    Returns an instance of the
    :py:class:`~earthdaily.earthone.catalog.Search` class configured for
    searching bands.  Call this on the :py:class:`Band` base class to search all
    types of bands or classes :py:class:`SpectralBand`, :py:class:`MicrowaveBand`,
    :py:class:`MaskBand`, :py:class:`ClassBand` and :py:class:`GenericBand` to search
    only a specific type of band.


    Parameters
    ----------
    client : :py:class:`CatalogClient`
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.Search`
        An instance of the :py:class:`~earthdaily.earthone.catalog.Search` class
    """
    search = super(Band, cls).search(
        client, request_params=request_params, headers=headers
    )
    if cls._derived_type:
        search = search.filter(properties.type == cls._derived_type)
    return search

Blob🔗

Blob 🔗

Bases: AuthCatalogObject

A stored blob (arbitrary bytes) that can be searched and retrieved.

Instantiating a blob indicates that you want to create a new EarthOne storage blob. If you instead want to retrieve an existing blob use Blob.get() <earthdaily.earthone.catalog.Blob.get>. You can also use Blob.search() <earthdaily.earthone.catalog.Blob.search>. Also see the example for 🇵🇾meth:~earthdaily.earthone.catalog.Blob.upload.

Parameters🔗

client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set. kwargs : dict With the exception of readonly attributes (created, modified) and with the exception of properties (ATTRIBUTES, is_modified, and state), any attribute listed below can also be used as a keyword argument. Also see ~Blob.ATTRIBUTES.

Source code in earthdaily/earthone/core/catalog/blob.py
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
class Blob(AuthCatalogObject):
    """A stored blob (arbitrary bytes) that can be searched and retrieved.

    Instantiating a blob indicates that you want to create a *new* EarthOne
    storage blob.  If you instead want to retrieve an existing blob use
    `Blob.get() <earthdaily.earthone.catalog.Blob.get>`.
    You can also use `Blob.search() <earthdaily.earthone.catalog.Blob.search>`.
    Also see the example for :py:meth:`~earthdaily.earthone.catalog.Blob.upload`.


    Parameters
    ----------
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne catalog.
        The :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.
    kwargs : dict
        With the exception of readonly attributes (`created`, `modified`) and with
        the exception of properties (`ATTRIBUTES`, `is_modified`, and `state`), any
        attribute listed below can also be used as a keyword argument.  Also see
        `~Blob.ATTRIBUTES`.
    """

    _doc_type = "storage"
    _url = "/storage"
    # _collection_type set below due to circular problems
    _url_client = ThirdPartyService()

    # Blob Attributes
    namespace = TypedAttribute(
        str,
        doc="""str: The namespace of this blob.

        All blobs are stored and indexed under a namespace. Namespaces are allowed
        a restricted alphabet (``a-zA-Z0-9:._-``), and must begin with the user's
        org name, or their unique user hash if the user has no org. The required
        prefix is seperated from the rest of the namespace name (if any) by a ``:``.
        If not provided, the namespace will default to the users org (if any) and
        the unique user hash. The combined length of the ``namespace`` and the
        ``name`` cannot exceed 979 bytes.

        *Searchable, sortable*.
        """,
    )
    name = TypedAttribute(
        str,
        doc="""str: The name of this blob.

        All blobs are stored and indexed by name. Names are allowed
        a restricted alphabet (``a-zA-Z0-9:._/-``), but may not begin or end with a
        ``/``. The combined length of the ``namespace`` and the ``name`` cannot exceed
        979 bytes.

        The ``/`` is intended to be used like a directory in a pathname to allow for
        prefix search operations, but otherwise has no special meaning.

        *Searchable, sortable*.
        """,
    )
    storage_state = EnumAttribute(
        StorageState,
        doc="""str or StorageState: Storage state of the blob.

        The state is `~StorageState.AVAILABLE` if the data is available and can be
        retrieved, `~StorageState.REMOTE` if the data is not currently available.

        *Filterable, sortable*.
        """,
    )
    storage_type = EnumAttribute(
        StorageType,
        doc="""str or StorageType: Storage type of the blob.

        `~StorageType.DATA` is managed by end users (e.g. via
        :py:meth:`earthdaily.earthone.catalog.Blob.upload`.
        Other types are generated and managed by various components of the platform.

        *Filterable, sortable*.
        """,
    )
    description = TypedAttribute(
        str,
        doc="""str, optional: A description with further details on this blob.

        The description can be up to 80,000 characters and is used by
        :py:meth:`Search.find_text`.

        *Searchable*
        """,
    )
    geometry = GeometryAttribute(
        doc="""str or shapely.geometry.base.BaseGeometry, optional: Geometry representing the location for the blob.

        *Filterable*

        (use :py:meth:`BlobSearch.intersects
        <earthdaily.earthone.catalog.BlobSearch.intersects>` to search based on geometry)
        """
    )
    expires = Timestamp(
        doc="""str or datetime, optional: Timestamp when the blob should be expired and deleted.

        *Filterable, sortable*.
        """
    )
    href = TypedAttribute(
        str,
        doc="""str, optional: Storage location for the blob.

        This attribute may not be set by the end user.
        """,
    )
    size_bytes = TypedAttribute(
        int,
        doc="""int, optional: Size of the blob in bytes.

        *Filterable, sortable*.
        """,
    )
    hash = TypedAttribute(
        str, doc="""str, optional: Content hash (MD5) for the blob."""
    )

    @classmethod
    def namespace_id(cls, namespace_id, client=None):
        """Generate a fully namespaced id.

        Parameters
        ----------
        namespace_id : str or None
            The unprefixed part of the id that you want prefixed.
        client : CatalogClient, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.  The
            :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
            be used if not set.

        Returns
        -------
        str
            The fully namespaced id.

        Example
        -------
        >>> namespace = Blob.namespace_id("myproject") # doctest: +SKIP
        'myorg:myproject' # doctest: +SKIP
        """
        if client is None:
            client = CatalogClient.get_default_client()
        org = client.auth.payload.get("org")
        namespace = client.auth.namespace

        if not namespace_id:
            if org:
                return f"{org}:{namespace}"
            else:
                return namespace
        elif org:
            if namespace_id == org or namespace_id.startswith(org + ":"):
                return namespace_id
            else:
                return f"{org}:{namespace_id}"
        elif namespace_id == namespace or namespace_id.startswith(namespace + ":"):
            return namespace_id
        else:
            return f"{namespace}:{namespace_id}"

    @classmethod
    def get(
        cls,
        id=None,
        storage_type=StorageType.DATA,
        namespace=None,
        name=None,
        client=None,
        request_params=None,
        headers=None,
    ):
        """Get an existing Blob from the EarthOne catalog.

        If the Blob is found, it will be returned in the
        `~earthdaily.earthone.catalog.DocumentState.SAVED` state.  Subsequent changes will
        put the instance in the `~earthdaily.earthone.catalog.DocumentState.MODIFIED` state,
        and you can use :py:meth:`save` to commit those changes and update the EarthOne
        catalog object.  Also see the example for :py:meth:`save`.

        Exactly one of the ``id`` and ``name`` parameters must be specified. If ``name``
        is specified, it is used together with the ``storage_type`` and ``namespace``
        parameters to form the corresponding ``id``.

        Parameters
        ----------
        id : str, optional
            The id of the object you are requesting. Required unless ``name`` is supplied.
            May not be specified if ``name`` is specified.
        storage_type : StorageType, optional
            The storage type of the Blob you wish to retrieve. Defaults to ``data``. Ignored
            unless ``name`` is specified.
        namespace : str, optional
            The namespace of the Blob you wish to retrieve. Defaults to the user's org name
            (if any) plus the unique user hash. Ignored unless ``name`` is specified.
        name : str, optional
            The name of the Blob you wish to retrieve. Required if ``id`` is not specified.
            May not be specified if ``id`` is specified.
        client : CatalogClient, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.  The
            :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
            be used if not set.

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.CatalogObject` or None
            The object you requested, or ``None`` if an object with the given `id`
            does not exist in the EarthOne catalog.

        Raises
        ------
        ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
            :ref:`Spurious exception <network_exceptions>` that can occur during a
            network request.
        """
        if (not id and not name) or (id and name):
            raise TypeError("Must specify exactly one of id or name parameters")
        if not id:
            id = f"{storage_type}/{Blob.namespace_id(namespace)}/{name}"
        return super(cls, Blob).get(
            id, client=client, request_params=request_params, headers=headers
        )

    @classmethod
    def get_or_create(
        cls,
        id=None,
        storage_type=StorageType.DATA,
        namespace=None,
        name=None,
        client=None,
        **kwargs,
    ):
        """Get an existing object from the EarthOne catalog or create a new object.

        If the EarthOne catalog object is found, and the remainder of the
        arguments do not differ from the values in the retrieved instance, it will be
        returned in the `~earthdaily.earthone.catalog.DocumentState.SAVED` state.

        If the EarthOne catalog object is found, and the remainder of the
        arguments update one or more values in the instance, it will be returned in
        the `~earthdaily.earthone.catalog.DocumentState.MODIFIED` state.

        If the EarthOne catalog object is not found, it will be created and the
        state will be `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.  Also see the
        example for :py:meth:`save`.

        Parameters
        ----------
        id : str, optional
            The id of the object you are requesting. Required unless ``name`` is supplied.
            May not be specified if ``name`` is specified.
        storage_type : StorageType, optional
            The storage type of the Blob you wish to retrieve. Defaults to ``data``. Ignored
            unless ``name`` is specified.
        namespace : str, optional
            The namespace of the Blob you wish to retrieve. Defaults to the user's org name
            (if any) plus the unique user hash. Ignored unless ``name`` is specified.
        name : str, optional
            The name of the Blob you wish to retrieve. Required if ``id`` is not specified.
            May not be specified if ``id`` is specified.
        client : CatalogClient, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.  The
            :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
            be used if not set.
        kwargs : dict, optional
            With the exception of readonly attributes (`created`, `modified`), any
            attribute of a catalog object can be set as a keyword argument (Also see
            `ATTRIBUTES`).

        Returns
        -------
        :py:class:`~earthdaily.earthone.catalog.CatalogObject`
            The requested catalog object that was retrieved or created.

        """
        if (not id and not name) or (id and name):
            raise TypeError("Must specify exactly one of id or name parameters")
        if not id:
            namespace = cls.namespace_id(namespace)
            id = f"{storage_type}/{namespace}/{name}"
            kwargs["storage_type"] = storage_type
            kwargs["namespace"] = namespace
            kwargs["name"] = name

        return super(cls, Blob).get_or_create(id, client=client, **kwargs)

    @classmethod
    def search(cls, client=None, request_params=None, headers=None):
        """A search query for all blobs.

        Return an `~earthdaily.earthone.catalog.BlobSearch` instance for searching
        blobs in the EarthOne catalog.  This instance extends the
        :py:class:`~earthdaily.earthone.catalog.Search` class with the
        :py:meth:`~earthdaily.earthone.catalog.BlobSearch.summary` and
        :py:meth:`~earthdaily.earthone.catalog.BlobSearch.summary_interval` methods
        which return summary statistics about the blobs that match the search query.

        Parameters
        ----------
        client : :class:`CatalogClient`, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.

        Returns
        -------
        :class:`~earthdaily.earthone.catalog.BlobSearch`
            An instance of the `~earthdaily.earthone.catalog.BlobSearch` class

        Example
        -------
        >>> from earthdaily.earthone.catalog import Blob
        >>> search = Blob.search().limit(10)
        >>> for result in search: # doctest: +SKIP
        ...     print(result.name) # doctest: +SKIP

        """
        return BlobSearch(
            cls, client=client, request_params=request_params, headers=headers
        )

    @check_deleted
    def upload(self, file):
        """Uploads storage blob from a file.

        Uploads data from a file and creates the Blob.

        The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
        The `storage_state`, `storage_type`, `namespace`, and the `name` attributes,
        must all be set. If either the `size_bytes` and the `hash` attributes are set,
        they must agree with the actual file to be uploaded, and will be validated
        during the upload process.

        On return, the Blob object will be updated to reflect the full state of the
        new blob.

        Parameters
        ----------
        file : str or io.IOBase
            File or files to be uploaded.  Can be string with path to the file in the
            local filesystem, or a file-like object (``io.IOBase``). If a file like
            object and already open, must be binary mode and readable. Open file-like
            objects remain open on return and must be closed by the caller.

        Returns
        -------
        Blob
            The uploaded instance.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        DeletedObjectError
            If this blob was deleted.
        """
        self.namespace = self.__class__.namespace_id(self.namespace)
        if not self.name:
            raise ValueError("name field required")
        if not self.storage_state:
            self.storage_state = StorageState.AVAILABLE
        if not self.storage_type:
            self.storage_type = StorageType.DATA

        if self.state != DocumentState.UNSAVED:
            raise ValueError(
                "Blob {} has been saved. Please use an unsaved blob for uploading".format(
                    self.id
                )
            )

        if isinstance(file, str):
            file = io.open(file, "rb")
            close = True
        elif isinstance(file, io.IOBase):
            close = file.closed
            if close:
                file = io.open(file.name, "rb")
            elif not file.readable() or "b" not in file.mode:
                raise ValueError("Invalid file is open but not readable or binary mode")
        else:
            raise ValueError("Invalid file value: must be string or IOBase")

        try:
            return self._do_upload(file)
        finally:
            if close:
                file.close()

    @check_deleted
    def upload_data(self, data):
        """Uploads storage blob from a bytes or str.

        Uploads data from a string or bytes and creates the Blob.

        The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
        The `storage_state`, `storage_type`, `namespace`, and the `name` attributes,
        must all be set. If either the `size_bytes` and the `hash` attributes are set,
        they must agree with the actual data to be uploaded, and will be validated
        during the upload process.

        On return, the Blob object will be updated to reflect the full state of the
        new blob.

        Parameters
        ----------
        data : str or bytes
            Data to be uploaded. A str will be default encoded to bytes.

        Returns
        -------
        Blob
            The uploaded instance.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        DeletedObjectError
            If this blob was deleted.
        """
        self.namespace = self.__class__.namespace_id(self.namespace)
        if not self.name:
            raise ValueError("name field required")
        if not self.storage_state:
            self.storage_state = StorageState.AVAILABLE
        if not self.storage_type:
            self.storage_type = StorageType.DATA

        if self.state != DocumentState.UNSAVED:
            raise ValueError(
                "Blob {} has been saved. Please use an unsaved blob for uploading".format(
                    self.id
                )
            )

        if isinstance(data, str):
            data = data.encode()
        elif not isinstance(data, bytes):
            raise ValueError("Invalid data value: must be string or bytes")

        return self._do_upload(data)

    # the upload implementation is broken out so it can be used from multiple methods
    def _do_upload(self, src):
        # import here for circular dependency
        from .blob_upload import BlobUpload

        # Request an upload url
        upload = BlobUpload(client=self._client, storage=self)

        upload.save()

        headers = {}
        headers["content-type"] = "application/octet-stream"
        if upload.storage.size_bytes:
            headers["content-length"] = str(upload.storage.size_bytes)

        # This should work but it doesn't. The header must be the base64
        # encoding of the 16 binary MD5 checksum bytes. But the value
        # that is is checked against by S3 is the hex-ified version of the
        # 16 binary bytes. So even though they mean the same thing,
        # they miscompare at S3 and the file upload fails.
        # if upload.storage.hash:
        #     headers["content-md5"] = upload.storage.hash

        # do the upload
        self._url_client.session.put(upload.resumable_url, data=src, headers=headers)

        # save the blob
        upload.storage.save(request_params={"upload_signature": upload.signature})

        # replenish our state, like reload but no need to go to server.
        # this will effectively wipe all current state & caching.
        self._initialize(
            saved=True,
            **upload.storage._attributes,
        )

        return self

    @check_deleted
    def download(self, file, range=None):
        """Downloads storage blob to a file.

        Downloads data from the blob to a file.

        The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.SAVED`.

        Parameters
        ----------
        file : str or io.IOBase
            Where to write the downloaded blob. Can be string with path to the file in the
            local filesystem, or an file opened for writing (``io.IOBase``). If a file like
            object and already open, must be binary mode and writable. Open file-like
            objects remain open on return and must be closed by the caller.
        range : str or list, optional
            Range(s) of blob to be downloaded. Can either be a string in the standard
            HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing
            one or two integers (e.g. ``(0, 99)``), or a list or tuple of the same
            (e.g. ``((0, 99), (200-299))``). A list or tuple of one integer implies
            no upper bound; in this case the integer can be negative, indicating the
            count back from the end of the blob.

        Returns
        -------
        str
            The name of the downloaded file.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        DeletedObjectError
            If this blob was deleted.
        """
        if self.state != DocumentState.SAVED:
            raise ValueError("Blob {} has not been saved".format(self.id))

        if isinstance(file, str):
            file = io.open(file, "wb")
        elif isinstance(file, io.IOBase):
            close = file.closed
            if close:
                file = io.open(file.name, "wb")
            elif not file.writable() or "b" not in file.mode:
                raise ValueError("Invalid file is open but not writable or binary mode")
        else:
            raise ValueError("Invalid file value: must be string or IOBase")

        return self._do_download(dest=file, range=range)

    @check_deleted
    def data(self, range=None):
        """Downloads storage blob data.

        Downloads data from the blob and returns as a bytes object.

        The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.SAVED`.

        Parameters
        ----------
        range : str or list, optional
            Range(s) of blob to be downloaded. Can either be a string in the standard
            HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing
            one or two integers (e.g. ``(0, 99)``), or a list or tuple of the same
            (e.g. ``((0, 99), (200-299))``). A list or tuple of one integer implies
            no upper bound; in this case the integer can be negative, indicating the
            count back from the end of the blob.

        Returns
        -------
        bytes
            The data retrieved from the Blob.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        DeletedObjectError
            If this blob was deleted.
        """
        if self.state != DocumentState.SAVED:
            raise ValueError("Blob {} has not been saved".format(self.id))

        return self._do_download(range=range)

    @check_deleted
    def iter_data(self, chunk_size=None, range=None):
        """Downloads storage blob data.

        Downloads data from the blob and returns as an iterator (generator)
        which will yield the data (as a bytes) in chunks. This enables the
        processing of very large files.

        The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.SAVED`.

        Parameters
        ----------
        chunk_size : int, optional
            Size of chunks over which to iterate. Default is whatever size chunks
            are received.
        range : str or list, optional
            Range(s) of blob to be downloaded. Can either be a string in the standard
            HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing
            one or two integers (e.g. ``(0, 99)``), or a list or tuple of the same
            (e.g. ``((0, 99), (200-299))``). A list or tuple of one integer implies
            no upper bound; in this case the integer can be negative, indicating the
            count back from the end of the blob.

        Returns
        -------
        generator
            An iterator over the blob data.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        DeletedObjectError
            If this blob was deleted.
        """
        if self.state != DocumentState.SAVED:
            raise ValueError("Blob {} has not been saved".format(self.id))

        def generator(response):
            try:
                yield from response.iter_content(chunk_size)
            finally:
                response.close()

        return self._do_download(dest=generator, range=range)

    @check_deleted
    def iter_lines(self, decode_unicode=False, delimiter=None):
        """Downloads storage blob data.

        Downloads data from the blob and returns as an iterator (generator)
        which will yield the data as text lines.  This enables the
        processing of very large files.

        The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.SAVED`.
        The data within the blob must represent encoded text.

        .. note:: This method is not reentrant safe.

        Parameters
        ----------
        decode_unicode : bool, optional
            If true, then decode unicode in the incoming data and return
            strings. Default is to return bytes.
        delimiter : str or byte, optional
            Delimiter for lines. Type depends on setting of `decode_unicode`.
            Default is to use default line break sequence.

        Returns
        -------
        generator
            An iterator over the blob byte or text lines, depending on
            value of `decode_unicode`.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        DeletedObjectError
            If this blob was deleted.
        """
        if self.state != DocumentState.SAVED:
            raise ValueError("Blob {} has not been saved".format(self.id))

        def generator(response):
            if decode_unicode:
                # response will always claim to be application/octet-stream
                response.encoding = "utf-8"
            try:
                yield from response.iter_lines(
                    decode_unicode=decode_unicode, delimiter=delimiter
                )
            finally:
                response.close()

        return self._do_download(dest=generator)

    @classmethod
    def get_data(
        cls,
        id=None,
        storage_type=StorageType.DATA,
        namespace=None,
        name=None,
        client=None,
        range=None,
        stream=False,
        chunk_size=None,
    ):
        """Downloads storage blob data.

        Downloads data for a given blob id and returns as a bytes object.

        Parameters
        ----------
        id : str, optional
            The id of the object you are requesting. Required unless ``name`` is supplied.
            May not be specified if ``name`` is specified.
        storage_type : StorageType, optional
            The storage type of the Blob you wish to retrieve. Defaults to ``data``. Ignored
            unless ``name`` is specified.
        namespace : str, optional
            The namespace of the Blob you wish to retrieve. Defaults to the user's org name
            (if any) plus the unique user hash. Ignored unless ``name`` is specified.
        name : str, optional
            The name of the Blob you wish to retrieve. Required if ``id`` is not specified.
            May not be specified if ``id`` is specified.
        client : Client, optional
            Client instance. If not given, the default client will be used.
        range : str or list, optional
            Range(s) of blob to be downloaded. Can either be a string in the standard
            HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing
            one or two integers (e.g. ``(0, 99)``), or a list or tuple of the same
            (e.g. ``((0, 99), (200-299))``). A list or tuple of one integer implies
            no upper bound; in this case the integer can be negative, indicating the
            count back from the end of the blob.
        stream : bool, optional
            If True, return a generator that will yield the data in chunks. Defaults to False.
        chunk_size : int, optional
            If stream is True, the size of chunks over which to stream. Default is whatever
            chunks are received on the wire.

        Returns
        -------
        bytes or generator
            The data retrieved from the Blob. If stream is True, returned as an iterator
            (generator) which will yeild the data in chunks.

        Raises
        ------
        ValueError
            If any improper arguments are supplied.
        NotFoundError
            If the Blob does not exist.
        DeletedObjectError
            If this blob was deleted.
        """
        if (not id and not name) or (id and name):
            raise TypeError("Must specify exactly one of id or name parameters")
        if not id:
            id = f"{storage_type}/{cls.namespace_id(namespace)}/{name}"

        dest = None
        if stream:

            def generator(response):
                try:
                    yield from response.iter_content(chunk_size)
                finally:
                    response.close()

            dest = generator

        return cls(id=id, client=client)._do_download(dest=dest, range=range)

    @classmethod
    def delete_many(
        cls, ids, raise_on_missing=False, wait_for_completion=False, client=None
    ):
        """Delete many blobs from the EarthOne catalog.

        Only those blobs that exist and are owned by the user will be deleted.
        No errors will be raised for blobs that do not exist or are visible but
        not owned by the user. If you need to know, compare the supplied list of
        ids with the returned list of deleted ids.

        All blobs to be deleted must belong to the same purchase.

        Parameters
        ----------
        ids : list(str)
            A list of blob ids to delete.
        raise_on_missing : bool, optional
            If True, raise an exception if any of the blobs are not found, otherwise ignore
            missing blobs. Defaults to False.
        wait_for_completion : bool, optional
            If True, wait for the deletion to complete before returning. Defaults to False.
        client : CatalogClient, optional
            A `CatalogClient` instance to use for requests to the EarthOne catalog.
            The :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
            be used if not set.

        Returns
        -------
        list(str)
            A list of the ids of the blobs that were successfully deleted.

        Raises
        ------
        ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
            :ref:`Spurious exception <network_exceptions>` that can occur during a
            network request.
        """
        if client is None:
            client = CatalogClient.get_default_client()

        task_status = BlobDeletionTaskStatus.create(
            ids=ids, raise_on_missing=raise_on_missing, client=client
        )

        if wait_for_completion:
            task_status.wait_for_completion()

        return task_status.ids

    def _do_download(self, dest=None, range=None):
        download = BlobDownload.get(id=self.id, client=self._client)

        # BlobDownload.get() returns None if the blob does not exist
        # raise a NotFoundError in this case
        if not download:
            raise NotFoundError("Blob {} does not exist".format(self.id))

        headers = {}
        if self.hash:
            headers["if-match"] = self.hash
        if range:
            if isinstance(range, str):
                range_str = range
            elif isinstance(range, (list, tuple)) and all(
                map(lambda x: isinstance(x, int), range)
            ):
                if len(range) == 1:
                    range_str = f"bytes={range[0]}"
                elif len(range) == 2:
                    range_str = f"bytes={range[0]}-{range[1]}"
                else:
                    raise ValueError("invalid range value")
            else:
                raise ValueError("invalid range value")

            headers["range"] = range_str

        r = self._url_client.session.get(
            download.resumable_url, headers=headers, stream=True
        )
        r.raise_for_status()
        if callable(dest):
            # generator will close response
            return dest(r)
        else:
            try:
                if dest is None:
                    return r.raw.read()
                else:
                    for chunk in r.iter_content(1048576):
                        dest.write(chunk)
                    return dest.name
            finally:
                r.close()

    @hybridmethod
    @check_derived
    def delete(cls, id, client=None):
        """Delete the catalog object with the given `id`.

        Parameters
        ----------
        id : str
            The id of the object to be deleted.
        client : CatalogClient, optional
            A `CatalogClient` instance to use for requests to the EarthOne
            catalog.  The
            :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
            be used if not set.

        Returns
        -------
        BlobDeletionTaskStatus
            The status of the deletion task which can be used to wait for completion. ``None`` if the
            object was not found.

        Raises
        ------
        ConflictError
            If the object has related objects (bands, images) that exist.
        ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
            :ref:`Spurious exception <network_exceptions>` that can occur during a
            network request.

        Example
        -------
        >>> Image.delete('my-image-id') # doctest: +SKIP

        There is also an instance ``delete`` method that can be used to delete a blob.
        It accepts no parameters and also returns a ``BlobDeletionTaskStatus``. Once
        deleted, you cannot use the blob and should release any references.
        """
        if client is None:
            client = CatalogClient.get_default_client()

        try:
            return BlobDeletionTaskStatus.create(
                ids=[id], raise_on_missing=True, client=client
            )
        except NotFoundError:
            return None

    @delete.instancemethod
    @check_deleted
    def delete(self):
        """Delete this catalog object from the EarthOne catalog.

        Once deleted, you cannot use the catalog object and should release any
        references.

        Returns
        -------
        BlobDeletionTaskStatus
            The status of the deletion task which can be used to wait for completion.

        Raises
        ------
        DeletedObjectError
            If this catalog object was already deleted.
        UnsavedObjectError
            If this catalog object is being deleted without having been saved.
        ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
            :ref:`Spurious exception <network_exceptions>` that can occur during a
            network request.
        """
        if self.state == DocumentState.UNSAVED:
            raise UnsavedObjectError("You cannot delete an unsaved object.")

        task_status = BlobDeletionTaskStatus.create(
            ids=[self.id], raise_on_missing=True, client=self._client
        )
        self._deleted = True  # non-200 will raise an exception
        return task_status

namespace_id classmethod 🔗

namespace_id(namespace_id, client=None)

Generate a fully namespaced id.

Parameters🔗

namespace_id : str or None The unprefixed part of the id that you want prefixed. client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set.

Returns🔗

str The fully namespaced id.

Example🔗

namespace = Blob.namespace_id("myproject") # doctest: +SKIP 'myorg:myproject' # doctest: +SKIP

Source code in earthdaily/earthone/core/catalog/blob.py
@classmethod
def namespace_id(cls, namespace_id, client=None):
    """Generate a fully namespaced id.

    Parameters
    ----------
    namespace_id : str or None
        The unprefixed part of the id that you want prefixed.
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.  The
        :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.

    Returns
    -------
    str
        The fully namespaced id.

    Example
    -------
    >>> namespace = Blob.namespace_id("myproject") # doctest: +SKIP
    'myorg:myproject' # doctest: +SKIP
    """
    if client is None:
        client = CatalogClient.get_default_client()
    org = client.auth.payload.get("org")
    namespace = client.auth.namespace

    if not namespace_id:
        if org:
            return f"{org}:{namespace}"
        else:
            return namespace
    elif org:
        if namespace_id == org or namespace_id.startswith(org + ":"):
            return namespace_id
        else:
            return f"{org}:{namespace_id}"
    elif namespace_id == namespace or namespace_id.startswith(namespace + ":"):
        return namespace_id
    else:
        return f"{namespace}:{namespace_id}"

get classmethod 🔗

get(id=None, storage_type=StorageType.DATA, namespace=None, name=None, client=None, request_params=None, headers=None)

Get an existing Blob from the EarthOne catalog.

If the Blob is found, it will be returned in the ~earthdaily.earthone.catalog.DocumentState.SAVED state. Subsequent changes will put the instance in the ~earthdaily.earthone.catalog.DocumentState.MODIFIED state, and you can use 🇵🇾meth:save to commit those changes and update the EarthOne catalog object. Also see the example for 🇵🇾meth:save.

Exactly one of the id and name parameters must be specified. If name is specified, it is used together with the storage_type and namespace parameters to form the corresponding id.

Parameters🔗

id : str, optional The id of the object you are requesting. Required unless name is supplied. May not be specified if name is specified. storage_type : StorageType, optional The storage type of the Blob you wish to retrieve. Defaults to data. Ignored unless name is specified. namespace : str, optional The namespace of the Blob you wish to retrieve. Defaults to the user's org name (if any) plus the unique user hash. Ignored unless name is specified. name : str, optional The name of the Blob you wish to retrieve. Required if id is not specified. May not be specified if id is specified. client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set.

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.CatalogObject or None The object you requested, or None if an object with the given id does not exist in the EarthOne catalog.

Raises🔗

~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError :ref:Spurious exception <network_exceptions> that can occur during a network request.

Source code in earthdaily/earthone/core/catalog/blob.py
@classmethod
def get(
    cls,
    id=None,
    storage_type=StorageType.DATA,
    namespace=None,
    name=None,
    client=None,
    request_params=None,
    headers=None,
):
    """Get an existing Blob from the EarthOne catalog.

    If the Blob is found, it will be returned in the
    `~earthdaily.earthone.catalog.DocumentState.SAVED` state.  Subsequent changes will
    put the instance in the `~earthdaily.earthone.catalog.DocumentState.MODIFIED` state,
    and you can use :py:meth:`save` to commit those changes and update the EarthOne
    catalog object.  Also see the example for :py:meth:`save`.

    Exactly one of the ``id`` and ``name`` parameters must be specified. If ``name``
    is specified, it is used together with the ``storage_type`` and ``namespace``
    parameters to form the corresponding ``id``.

    Parameters
    ----------
    id : str, optional
        The id of the object you are requesting. Required unless ``name`` is supplied.
        May not be specified if ``name`` is specified.
    storage_type : StorageType, optional
        The storage type of the Blob you wish to retrieve. Defaults to ``data``. Ignored
        unless ``name`` is specified.
    namespace : str, optional
        The namespace of the Blob you wish to retrieve. Defaults to the user's org name
        (if any) plus the unique user hash. Ignored unless ``name`` is specified.
    name : str, optional
        The name of the Blob you wish to retrieve. Required if ``id`` is not specified.
        May not be specified if ``id`` is specified.
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.  The
        :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.CatalogObject` or None
        The object you requested, or ``None`` if an object with the given `id`
        does not exist in the EarthOne catalog.

    Raises
    ------
    ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
        :ref:`Spurious exception <network_exceptions>` that can occur during a
        network request.
    """
    if (not id and not name) or (id and name):
        raise TypeError("Must specify exactly one of id or name parameters")
    if not id:
        id = f"{storage_type}/{Blob.namespace_id(namespace)}/{name}"
    return super(cls, Blob).get(
        id, client=client, request_params=request_params, headers=headers
    )

get_or_create classmethod 🔗

get_or_create(id=None, storage_type=StorageType.DATA, namespace=None, name=None, client=None, **kwargs)

Get an existing object from the EarthOne catalog or create a new object.

If the EarthOne catalog object is found, and the remainder of the arguments do not differ from the values in the retrieved instance, it will be returned in the ~earthdaily.earthone.catalog.DocumentState.SAVED state.

If the EarthOne catalog object is found, and the remainder of the arguments update one or more values in the instance, it will be returned in the ~earthdaily.earthone.catalog.DocumentState.MODIFIED state.

If the EarthOne catalog object is not found, it will be created and the state will be ~earthdaily.earthone.catalog.DocumentState.UNSAVED. Also see the example for 🇵🇾meth:save.

Parameters🔗

id : str, optional The id of the object you are requesting. Required unless name is supplied. May not be specified if name is specified. storage_type : StorageType, optional The storage type of the Blob you wish to retrieve. Defaults to data. Ignored unless name is specified. namespace : str, optional The namespace of the Blob you wish to retrieve. Defaults to the user's org name (if any) plus the unique user hash. Ignored unless name is specified. name : str, optional The name of the Blob you wish to retrieve. Required if id is not specified. May not be specified if id is specified. client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set. kwargs : dict, optional With the exception of readonly attributes (created, modified), any attribute of a catalog object can be set as a keyword argument (Also see ATTRIBUTES).

Returns🔗

🇵🇾class:~earthdaily.earthone.catalog.CatalogObject The requested catalog object that was retrieved or created.

Source code in earthdaily/earthone/core/catalog/blob.py
@classmethod
def get_or_create(
    cls,
    id=None,
    storage_type=StorageType.DATA,
    namespace=None,
    name=None,
    client=None,
    **kwargs,
):
    """Get an existing object from the EarthOne catalog or create a new object.

    If the EarthOne catalog object is found, and the remainder of the
    arguments do not differ from the values in the retrieved instance, it will be
    returned in the `~earthdaily.earthone.catalog.DocumentState.SAVED` state.

    If the EarthOne catalog object is found, and the remainder of the
    arguments update one or more values in the instance, it will be returned in
    the `~earthdaily.earthone.catalog.DocumentState.MODIFIED` state.

    If the EarthOne catalog object is not found, it will be created and the
    state will be `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.  Also see the
    example for :py:meth:`save`.

    Parameters
    ----------
    id : str, optional
        The id of the object you are requesting. Required unless ``name`` is supplied.
        May not be specified if ``name`` is specified.
    storage_type : StorageType, optional
        The storage type of the Blob you wish to retrieve. Defaults to ``data``. Ignored
        unless ``name`` is specified.
    namespace : str, optional
        The namespace of the Blob you wish to retrieve. Defaults to the user's org name
        (if any) plus the unique user hash. Ignored unless ``name`` is specified.
    name : str, optional
        The name of the Blob you wish to retrieve. Required if ``id`` is not specified.
        May not be specified if ``id`` is specified.
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.  The
        :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.
    kwargs : dict, optional
        With the exception of readonly attributes (`created`, `modified`), any
        attribute of a catalog object can be set as a keyword argument (Also see
        `ATTRIBUTES`).

    Returns
    -------
    :py:class:`~earthdaily.earthone.catalog.CatalogObject`
        The requested catalog object that was retrieved or created.

    """
    if (not id and not name) or (id and name):
        raise TypeError("Must specify exactly one of id or name parameters")
    if not id:
        namespace = cls.namespace_id(namespace)
        id = f"{storage_type}/{namespace}/{name}"
        kwargs["storage_type"] = storage_type
        kwargs["namespace"] = namespace
        kwargs["name"] = name

    return super(cls, Blob).get_or_create(id, client=client, **kwargs)

search classmethod 🔗

search(client=None, request_params=None, headers=None)

A search query for all blobs.

Return an ~earthdaily.earthone.catalog.BlobSearch instance for searching blobs in the EarthOne catalog. This instance extends the 🇵🇾class:~earthdaily.earthone.catalog.Search class with the 🇵🇾meth:~earthdaily.earthone.catalog.BlobSearch.summary and 🇵🇾meth:~earthdaily.earthone.catalog.BlobSearch.summary_interval methods which return summary statistics about the blobs that match the search query.

Parameters🔗

client : :class:CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog.

Returns🔗

:class:~earthdaily.earthone.catalog.BlobSearch An instance of the ~earthdaily.earthone.catalog.BlobSearch class

Example🔗

from earthdaily.earthone.catalog import Blob search = Blob.search().limit(10) for result in search: # doctest: +SKIP ... print(result.name) # doctest: +SKIP

Source code in earthdaily/earthone/core/catalog/blob.py
@classmethod
def search(cls, client=None, request_params=None, headers=None):
    """A search query for all blobs.

    Return an `~earthdaily.earthone.catalog.BlobSearch` instance for searching
    blobs in the EarthOne catalog.  This instance extends the
    :py:class:`~earthdaily.earthone.catalog.Search` class with the
    :py:meth:`~earthdaily.earthone.catalog.BlobSearch.summary` and
    :py:meth:`~earthdaily.earthone.catalog.BlobSearch.summary_interval` methods
    which return summary statistics about the blobs that match the search query.

    Parameters
    ----------
    client : :class:`CatalogClient`, optional
        A `CatalogClient` instance to use for requests to the EarthOne
        catalog.

    Returns
    -------
    :class:`~earthdaily.earthone.catalog.BlobSearch`
        An instance of the `~earthdaily.earthone.catalog.BlobSearch` class

    Example
    -------
    >>> from earthdaily.earthone.catalog import Blob
    >>> search = Blob.search().limit(10)
    >>> for result in search: # doctest: +SKIP
    ...     print(result.name) # doctest: +SKIP

    """
    return BlobSearch(
        cls, client=client, request_params=request_params, headers=headers
    )

upload 🔗

upload(file)

Uploads storage blob from a file.

Uploads data from a file and creates the Blob.

The Blob must be in the state ~earthdaily.earthone.catalog.DocumentState.UNSAVED. The storage_state, storage_type, namespace, and the name attributes, must all be set. If either the size_bytes and the hash attributes are set, they must agree with the actual file to be uploaded, and will be validated during the upload process.

On return, the Blob object will be updated to reflect the full state of the new blob.

Parameters🔗

file : str or io.IOBase File or files to be uploaded. Can be string with path to the file in the local filesystem, or a file-like object (io.IOBase). If a file like object and already open, must be binary mode and readable. Open file-like objects remain open on return and must be closed by the caller.

Returns🔗

Blob The uploaded instance.

Raises🔗

ValueError If any improper arguments are supplied. DeletedObjectError If this blob was deleted.

Source code in earthdaily/earthone/core/catalog/blob.py
@check_deleted
def upload(self, file):
    """Uploads storage blob from a file.

    Uploads data from a file and creates the Blob.

    The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
    The `storage_state`, `storage_type`, `namespace`, and the `name` attributes,
    must all be set. If either the `size_bytes` and the `hash` attributes are set,
    they must agree with the actual file to be uploaded, and will be validated
    during the upload process.

    On return, the Blob object will be updated to reflect the full state of the
    new blob.

    Parameters
    ----------
    file : str or io.IOBase
        File or files to be uploaded.  Can be string with path to the file in the
        local filesystem, or a file-like object (``io.IOBase``). If a file like
        object and already open, must be binary mode and readable. Open file-like
        objects remain open on return and must be closed by the caller.

    Returns
    -------
    Blob
        The uploaded instance.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    DeletedObjectError
        If this blob was deleted.
    """
    self.namespace = self.__class__.namespace_id(self.namespace)
    if not self.name:
        raise ValueError("name field required")
    if not self.storage_state:
        self.storage_state = StorageState.AVAILABLE
    if not self.storage_type:
        self.storage_type = StorageType.DATA

    if self.state != DocumentState.UNSAVED:
        raise ValueError(
            "Blob {} has been saved. Please use an unsaved blob for uploading".format(
                self.id
            )
        )

    if isinstance(file, str):
        file = io.open(file, "rb")
        close = True
    elif isinstance(file, io.IOBase):
        close = file.closed
        if close:
            file = io.open(file.name, "rb")
        elif not file.readable() or "b" not in file.mode:
            raise ValueError("Invalid file is open but not readable or binary mode")
    else:
        raise ValueError("Invalid file value: must be string or IOBase")

    try:
        return self._do_upload(file)
    finally:
        if close:
            file.close()

upload_data 🔗

upload_data(data)

Uploads storage blob from a bytes or str.

Uploads data from a string or bytes and creates the Blob.

The Blob must be in the state ~earthdaily.earthone.catalog.DocumentState.UNSAVED. The storage_state, storage_type, namespace, and the name attributes, must all be set. If either the size_bytes and the hash attributes are set, they must agree with the actual data to be uploaded, and will be validated during the upload process.

On return, the Blob object will be updated to reflect the full state of the new blob.

Parameters🔗

data : str or bytes Data to be uploaded. A str will be default encoded to bytes.

Returns🔗

Blob The uploaded instance.

Raises🔗

ValueError If any improper arguments are supplied. DeletedObjectError If this blob was deleted.

Source code in earthdaily/earthone/core/catalog/blob.py
@check_deleted
def upload_data(self, data):
    """Uploads storage blob from a bytes or str.

    Uploads data from a string or bytes and creates the Blob.

    The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.UNSAVED`.
    The `storage_state`, `storage_type`, `namespace`, and the `name` attributes,
    must all be set. If either the `size_bytes` and the `hash` attributes are set,
    they must agree with the actual data to be uploaded, and will be validated
    during the upload process.

    On return, the Blob object will be updated to reflect the full state of the
    new blob.

    Parameters
    ----------
    data : str or bytes
        Data to be uploaded. A str will be default encoded to bytes.

    Returns
    -------
    Blob
        The uploaded instance.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    DeletedObjectError
        If this blob was deleted.
    """
    self.namespace = self.__class__.namespace_id(self.namespace)
    if not self.name:
        raise ValueError("name field required")
    if not self.storage_state:
        self.storage_state = StorageState.AVAILABLE
    if not self.storage_type:
        self.storage_type = StorageType.DATA

    if self.state != DocumentState.UNSAVED:
        raise ValueError(
            "Blob {} has been saved. Please use an unsaved blob for uploading".format(
                self.id
            )
        )

    if isinstance(data, str):
        data = data.encode()
    elif not isinstance(data, bytes):
        raise ValueError("Invalid data value: must be string or bytes")

    return self._do_upload(data)

download 🔗

download(file, range=None)

Downloads storage blob to a file.

Downloads data from the blob to a file.

The Blob must be in the state ~earthdaily.earthone.catalog.DocumentState.SAVED.

Parameters🔗

file : str or io.IOBase Where to write the downloaded blob. Can be string with path to the file in the local filesystem, or an file opened for writing (io.IOBase). If a file like object and already open, must be binary mode and writable. Open file-like objects remain open on return and must be closed by the caller. range : str or list, optional Range(s) of blob to be downloaded. Can either be a string in the standard HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing one or two integers (e.g. (0, 99)), or a list or tuple of the same (e.g. ((0, 99), (200-299))). A list or tuple of one integer implies no upper bound; in this case the integer can be negative, indicating the count back from the end of the blob.

Returns🔗

str The name of the downloaded file.

Raises🔗

ValueError If any improper arguments are supplied. DeletedObjectError If this blob was deleted.

Source code in earthdaily/earthone/core/catalog/blob.py
@check_deleted
def download(self, file, range=None):
    """Downloads storage blob to a file.

    Downloads data from the blob to a file.

    The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.SAVED`.

    Parameters
    ----------
    file : str or io.IOBase
        Where to write the downloaded blob. Can be string with path to the file in the
        local filesystem, or an file opened for writing (``io.IOBase``). If a file like
        object and already open, must be binary mode and writable. Open file-like
        objects remain open on return and must be closed by the caller.
    range : str or list, optional
        Range(s) of blob to be downloaded. Can either be a string in the standard
        HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing
        one or two integers (e.g. ``(0, 99)``), or a list or tuple of the same
        (e.g. ``((0, 99), (200-299))``). A list or tuple of one integer implies
        no upper bound; in this case the integer can be negative, indicating the
        count back from the end of the blob.

    Returns
    -------
    str
        The name of the downloaded file.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    DeletedObjectError
        If this blob was deleted.
    """
    if self.state != DocumentState.SAVED:
        raise ValueError("Blob {} has not been saved".format(self.id))

    if isinstance(file, str):
        file = io.open(file, "wb")
    elif isinstance(file, io.IOBase):
        close = file.closed
        if close:
            file = io.open(file.name, "wb")
        elif not file.writable() or "b" not in file.mode:
            raise ValueError("Invalid file is open but not writable or binary mode")
    else:
        raise ValueError("Invalid file value: must be string or IOBase")

    return self._do_download(dest=file, range=range)

data 🔗

data(range=None)

Downloads storage blob data.

Downloads data from the blob and returns as a bytes object.

The Blob must be in the state ~earthdaily.earthone.catalog.DocumentState.SAVED.

Parameters🔗

range : str or list, optional Range(s) of blob to be downloaded. Can either be a string in the standard HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing one or two integers (e.g. (0, 99)), or a list or tuple of the same (e.g. ((0, 99), (200-299))). A list or tuple of one integer implies no upper bound; in this case the integer can be negative, indicating the count back from the end of the blob.

Returns🔗

bytes The data retrieved from the Blob.

Raises🔗

ValueError If any improper arguments are supplied. DeletedObjectError If this blob was deleted.

Source code in earthdaily/earthone/core/catalog/blob.py
@check_deleted
def data(self, range=None):
    """Downloads storage blob data.

    Downloads data from the blob and returns as a bytes object.

    The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.SAVED`.

    Parameters
    ----------
    range : str or list, optional
        Range(s) of blob to be downloaded. Can either be a string in the standard
        HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing
        one or two integers (e.g. ``(0, 99)``), or a list or tuple of the same
        (e.g. ``((0, 99), (200-299))``). A list or tuple of one integer implies
        no upper bound; in this case the integer can be negative, indicating the
        count back from the end of the blob.

    Returns
    -------
    bytes
        The data retrieved from the Blob.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    DeletedObjectError
        If this blob was deleted.
    """
    if self.state != DocumentState.SAVED:
        raise ValueError("Blob {} has not been saved".format(self.id))

    return self._do_download(range=range)

iter_data 🔗

iter_data(chunk_size=None, range=None)

Downloads storage blob data.

Downloads data from the blob and returns as an iterator (generator) which will yield the data (as a bytes) in chunks. This enables the processing of very large files.

The Blob must be in the state ~earthdaily.earthone.catalog.DocumentState.SAVED.

Parameters🔗

chunk_size : int, optional Size of chunks over which to iterate. Default is whatever size chunks are received. range : str or list, optional Range(s) of blob to be downloaded. Can either be a string in the standard HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing one or two integers (e.g. (0, 99)), or a list or tuple of the same (e.g. ((0, 99), (200-299))). A list or tuple of one integer implies no upper bound; in this case the integer can be negative, indicating the count back from the end of the blob.

Returns🔗

generator An iterator over the blob data.

Raises🔗

ValueError If any improper arguments are supplied. DeletedObjectError If this blob was deleted.

Source code in earthdaily/earthone/core/catalog/blob.py
@check_deleted
def iter_data(self, chunk_size=None, range=None):
    """Downloads storage blob data.

    Downloads data from the blob and returns as an iterator (generator)
    which will yield the data (as a bytes) in chunks. This enables the
    processing of very large files.

    The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.SAVED`.

    Parameters
    ----------
    chunk_size : int, optional
        Size of chunks over which to iterate. Default is whatever size chunks
        are received.
    range : str or list, optional
        Range(s) of blob to be downloaded. Can either be a string in the standard
        HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing
        one or two integers (e.g. ``(0, 99)``), or a list or tuple of the same
        (e.g. ``((0, 99), (200-299))``). A list or tuple of one integer implies
        no upper bound; in this case the integer can be negative, indicating the
        count back from the end of the blob.

    Returns
    -------
    generator
        An iterator over the blob data.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    DeletedObjectError
        If this blob was deleted.
    """
    if self.state != DocumentState.SAVED:
        raise ValueError("Blob {} has not been saved".format(self.id))

    def generator(response):
        try:
            yield from response.iter_content(chunk_size)
        finally:
            response.close()

    return self._do_download(dest=generator, range=range)

iter_lines 🔗

iter_lines(decode_unicode=False, delimiter=None)

Downloads storage blob data.

Downloads data from the blob and returns as an iterator (generator) which will yield the data as text lines. This enables the processing of very large files.

The Blob must be in the state ~earthdaily.earthone.catalog.DocumentState.SAVED. The data within the blob must represent encoded text.

.. note:: This method is not reentrant safe.

Parameters🔗

decode_unicode : bool, optional If true, then decode unicode in the incoming data and return strings. Default is to return bytes. delimiter : str or byte, optional Delimiter for lines. Type depends on setting of decode_unicode. Default is to use default line break sequence.

Returns🔗

generator An iterator over the blob byte or text lines, depending on value of decode_unicode.

Raises🔗

ValueError If any improper arguments are supplied. DeletedObjectError If this blob was deleted.

Source code in earthdaily/earthone/core/catalog/blob.py
@check_deleted
def iter_lines(self, decode_unicode=False, delimiter=None):
    """Downloads storage blob data.

    Downloads data from the blob and returns as an iterator (generator)
    which will yield the data as text lines.  This enables the
    processing of very large files.

    The Blob must be in the state `~earthdaily.earthone.catalog.DocumentState.SAVED`.
    The data within the blob must represent encoded text.

    .. note:: This method is not reentrant safe.

    Parameters
    ----------
    decode_unicode : bool, optional
        If true, then decode unicode in the incoming data and return
        strings. Default is to return bytes.
    delimiter : str or byte, optional
        Delimiter for lines. Type depends on setting of `decode_unicode`.
        Default is to use default line break sequence.

    Returns
    -------
    generator
        An iterator over the blob byte or text lines, depending on
        value of `decode_unicode`.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    DeletedObjectError
        If this blob was deleted.
    """
    if self.state != DocumentState.SAVED:
        raise ValueError("Blob {} has not been saved".format(self.id))

    def generator(response):
        if decode_unicode:
            # response will always claim to be application/octet-stream
            response.encoding = "utf-8"
        try:
            yield from response.iter_lines(
                decode_unicode=decode_unicode, delimiter=delimiter
            )
        finally:
            response.close()

    return self._do_download(dest=generator)

get_data classmethod 🔗

get_data(id=None, storage_type=StorageType.DATA, namespace=None, name=None, client=None, range=None, stream=False, chunk_size=None)

Downloads storage blob data.

Downloads data for a given blob id and returns as a bytes object.

Parameters🔗

id : str, optional The id of the object you are requesting. Required unless name is supplied. May not be specified if name is specified. storage_type : StorageType, optional The storage type of the Blob you wish to retrieve. Defaults to data. Ignored unless name is specified. namespace : str, optional The namespace of the Blob you wish to retrieve. Defaults to the user's org name (if any) plus the unique user hash. Ignored unless name is specified. name : str, optional The name of the Blob you wish to retrieve. Required if id is not specified. May not be specified if id is specified. client : Client, optional Client instance. If not given, the default client will be used. range : str or list, optional Range(s) of blob to be downloaded. Can either be a string in the standard HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing one or two integers (e.g. (0, 99)), or a list or tuple of the same (e.g. ((0, 99), (200-299))). A list or tuple of one integer implies no upper bound; in this case the integer can be negative, indicating the count back from the end of the blob. stream : bool, optional If True, return a generator that will yield the data in chunks. Defaults to False. chunk_size : int, optional If stream is True, the size of chunks over which to stream. Default is whatever chunks are received on the wire.

Returns🔗

bytes or generator The data retrieved from the Blob. If stream is True, returned as an iterator (generator) which will yeild the data in chunks.

Raises🔗

ValueError If any improper arguments are supplied. NotFoundError If the Blob does not exist. DeletedObjectError If this blob was deleted.

Source code in earthdaily/earthone/core/catalog/blob.py
@classmethod
def get_data(
    cls,
    id=None,
    storage_type=StorageType.DATA,
    namespace=None,
    name=None,
    client=None,
    range=None,
    stream=False,
    chunk_size=None,
):
    """Downloads storage blob data.

    Downloads data for a given blob id and returns as a bytes object.

    Parameters
    ----------
    id : str, optional
        The id of the object you are requesting. Required unless ``name`` is supplied.
        May not be specified if ``name`` is specified.
    storage_type : StorageType, optional
        The storage type of the Blob you wish to retrieve. Defaults to ``data``. Ignored
        unless ``name`` is specified.
    namespace : str, optional
        The namespace of the Blob you wish to retrieve. Defaults to the user's org name
        (if any) plus the unique user hash. Ignored unless ``name`` is specified.
    name : str, optional
        The name of the Blob you wish to retrieve. Required if ``id`` is not specified.
        May not be specified if ``id`` is specified.
    client : Client, optional
        Client instance. If not given, the default client will be used.
    range : str or list, optional
        Range(s) of blob to be downloaded. Can either be a string in the standard
        HTTP Range header format (e.g. "bytes=0-99"), or a list or tuple containing
        one or two integers (e.g. ``(0, 99)``), or a list or tuple of the same
        (e.g. ``((0, 99), (200-299))``). A list or tuple of one integer implies
        no upper bound; in this case the integer can be negative, indicating the
        count back from the end of the blob.
    stream : bool, optional
        If True, return a generator that will yield the data in chunks. Defaults to False.
    chunk_size : int, optional
        If stream is True, the size of chunks over which to stream. Default is whatever
        chunks are received on the wire.

    Returns
    -------
    bytes or generator
        The data retrieved from the Blob. If stream is True, returned as an iterator
        (generator) which will yeild the data in chunks.

    Raises
    ------
    ValueError
        If any improper arguments are supplied.
    NotFoundError
        If the Blob does not exist.
    DeletedObjectError
        If this blob was deleted.
    """
    if (not id and not name) or (id and name):
        raise TypeError("Must specify exactly one of id or name parameters")
    if not id:
        id = f"{storage_type}/{cls.namespace_id(namespace)}/{name}"

    dest = None
    if stream:

        def generator(response):
            try:
                yield from response.iter_content(chunk_size)
            finally:
                response.close()

        dest = generator

    return cls(id=id, client=client)._do_download(dest=dest, range=range)

delete_many classmethod 🔗

delete_many(ids, raise_on_missing=False, wait_for_completion=False, client=None)

Delete many blobs from the EarthOne catalog.

Only those blobs that exist and are owned by the user will be deleted. No errors will be raised for blobs that do not exist or are visible but not owned by the user. If you need to know, compare the supplied list of ids with the returned list of deleted ids.

All blobs to be deleted must belong to the same purchase.

Parameters🔗

ids : list(str) A list of blob ids to delete. raise_on_missing : bool, optional If True, raise an exception if any of the blobs are not found, otherwise ignore missing blobs. Defaults to False. wait_for_completion : bool, optional If True, wait for the deletion to complete before returning. Defaults to False. client : CatalogClient, optional A CatalogClient instance to use for requests to the EarthOne catalog. The 🇵🇾meth:~earthdaily.earthone.catalog.CatalogClient.get_default_client will be used if not set.

Returns🔗

list(str) A list of the ids of the blobs that were successfully deleted.

Raises🔗

~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError :ref:Spurious exception <network_exceptions> that can occur during a network request.

Source code in earthdaily/earthone/core/catalog/blob.py
@classmethod
def delete_many(
    cls, ids, raise_on_missing=False, wait_for_completion=False, client=None
):
    """Delete many blobs from the EarthOne catalog.

    Only those blobs that exist and are owned by the user will be deleted.
    No errors will be raised for blobs that do not exist or are visible but
    not owned by the user. If you need to know, compare the supplied list of
    ids with the returned list of deleted ids.

    All blobs to be deleted must belong to the same purchase.

    Parameters
    ----------
    ids : list(str)
        A list of blob ids to delete.
    raise_on_missing : bool, optional
        If True, raise an exception if any of the blobs are not found, otherwise ignore
        missing blobs. Defaults to False.
    wait_for_completion : bool, optional
        If True, wait for the deletion to complete before returning. Defaults to False.
    client : CatalogClient, optional
        A `CatalogClient` instance to use for requests to the EarthOne catalog.
        The :py:meth:`~earthdaily.earthone.catalog.CatalogClient.get_default_client` will
        be used if not set.

    Returns
    -------
    list(str)
        A list of the ids of the blobs that were successfully deleted.

    Raises
    ------
    ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
        :ref:`Spurious exception <network_exceptions>` that can occur during a
        network request.
    """
    if client is None:
        client = CatalogClient.get_default_client()

    task_status = BlobDeletionTaskStatus.create(
        ids=ids, raise_on_missing=raise_on_missing, client=client
    )

    if wait_for_completion:
        task_status.wait_for_completion()

    return task_status.ids

delete 🔗

delete()

Delete this catalog object from the EarthOne catalog.

Once deleted, you cannot use the catalog object and should release any references.

Returns🔗

BlobDeletionTaskStatus The status of the deletion task which can be used to wait for completion.

Raises🔗

DeletedObjectError If this catalog object was already deleted. UnsavedObjectError If this catalog object is being deleted without having been saved. ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError :ref:Spurious exception <network_exceptions> that can occur during a network request.

Source code in earthdaily/earthone/core/catalog/blob.py
@delete.instancemethod
@check_deleted
def delete(self):
    """Delete this catalog object from the EarthOne catalog.

    Once deleted, you cannot use the catalog object and should release any
    references.

    Returns
    -------
    BlobDeletionTaskStatus
        The status of the deletion task which can be used to wait for completion.

    Raises
    ------
    DeletedObjectError
        If this catalog object was already deleted.
    UnsavedObjectError
        If this catalog object is being deleted without having been saved.
    ~earthdaily.earthone.exceptions.ClientError or ~earthdaily.earthone.exceptions.ServerError
        :ref:`Spurious exception <network_exceptions>` that can occur during a
        network request.
    """
    if self.state == DocumentState.UNSAVED:
        raise UnsavedObjectError("You cannot delete an unsaved object.")

    task_status = BlobDeletionTaskStatus.create(
        ids=[self.id], raise_on_missing=True, client=self._client
    )
    self._deleted = True  # non-200 will raise an exception
    return task_status