Module eoreader.products.optical.l7_product

Landsat-7 products

Expand source code
# -*- coding: utf-8 -*-
# Copyright 2021, SERTIT-ICube - France, https://sertit.unistra.fr/
# This file is part of eoreader project
#     https://github.com/sertit/eoreader
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" Landsat-7 products """
import logging

import geopandas as gpd

from eoreader.products.optical.landsat_product import LandsatProduct
from eoreader.utils import EOREADER_NAME
from sertit import rasters

LOGGER = logging.getLogger(EOREADER_NAME)


class L7Product(LandsatProduct):
    """Class of Landsat-7 Products"""

    def _set_resolution(self) -> float:
        """
        Set product default resolution (in meters)
        """
        # DO NOT TAKE INTO ACCOUNT PAN AND TIRS RES
        return 30.0

    def _set_product_type(self) -> None:
        """Get products type"""
        self._set_etm_product_type()

    def footprint(self) -> gpd.GeoDataFrame:
        """
        Get real footprint of the products (without nodata, in french == emprise utile)

        .. WARNING::
            As Landsat 7 is broken (with nodata stripes all over the bands),
            the footprint is not easily computed and may take some time to be delivered.

        ```python
        >>> from eoreader.reader import Reader
        >>> path = r"LC08_L1GT_023030_20200518_20200527_01_T2"
        >>> prod = Reader().open(path)
        >>> prod.footprint()
           index                                           geometry
        0      0  POLYGON ((366165.000 4899735.000, 366165.000 4...
        ```

        Overload of the generic function because landsat nodata seems to be different in QA than in regular bands.
        Indeed, nodata pixels vary according to the band sensor footprint,
        whereas QA nodata is where at least one band has nodata.

        We chose to keep QA nodata values for the footprint in order to show where all bands are valid.

        **TL;DR: We use the QA nodata value to determine the product's footprint**.

        Returns:
            gpd.GeoDataFrame: Footprint as a GeoDataFrame
        """
        LOGGER.warning(
            "Due to the Landsat-7 gaps, this function returns a rounded footprint on the corners. "
            "Sorry for the inconvenience."
        )

        # Read the file with a very low resolution -> use raster_rio that is faster !
        gap_msk = rasters.read(
            self._get_path(self._nodata_band_id),
            resolution=self.resolution * 50,
            masked=False,
        )

        # Vectorize the nodata band
        # Take the convex hull to discard the stripes of L7 to simplify the geometries
        footprint = rasters.vectorize(
            gap_msk, values=1, keep_values=False, dissolve=True
        )

        # Needs a dataframe to be dissolved
        footprint = footprint.convex_hull

        return gpd.GeoDataFrame(geometry=footprint.geometry, crs=footprint.crs)

Classes

class L7Product (product_path, archive_path=None, output_path=None)

Class of Landsat-7 Products

Expand source code
class L7Product(LandsatProduct):
    """Class of Landsat-7 Products"""

    def _set_resolution(self) -> float:
        """
        Set product default resolution (in meters)
        """
        # DO NOT TAKE INTO ACCOUNT PAN AND TIRS RES
        return 30.0

    def _set_product_type(self) -> None:
        """Get products type"""
        self._set_etm_product_type()

    def footprint(self) -> gpd.GeoDataFrame:
        """
        Get real footprint of the products (without nodata, in french == emprise utile)

        .. WARNING::
            As Landsat 7 is broken (with nodata stripes all over the bands),
            the footprint is not easily computed and may take some time to be delivered.

        ```python
        >>> from eoreader.reader import Reader
        >>> path = r"LC08_L1GT_023030_20200518_20200527_01_T2"
        >>> prod = Reader().open(path)
        >>> prod.footprint()
           index                                           geometry
        0      0  POLYGON ((366165.000 4899735.000, 366165.000 4...
        ```

        Overload of the generic function because landsat nodata seems to be different in QA than in regular bands.
        Indeed, nodata pixels vary according to the band sensor footprint,
        whereas QA nodata is where at least one band has nodata.

        We chose to keep QA nodata values for the footprint in order to show where all bands are valid.

        **TL;DR: We use the QA nodata value to determine the product's footprint**.

        Returns:
            gpd.GeoDataFrame: Footprint as a GeoDataFrame
        """
        LOGGER.warning(
            "Due to the Landsat-7 gaps, this function returns a rounded footprint on the corners. "
            "Sorry for the inconvenience."
        )

        # Read the file with a very low resolution -> use raster_rio that is faster !
        gap_msk = rasters.read(
            self._get_path(self._nodata_band_id),
            resolution=self.resolution * 50,
            masked=False,
        )

        # Vectorize the nodata band
        # Take the convex hull to discard the stripes of L7 to simplify the geometries
        footprint = rasters.vectorize(
            gap_msk, values=1, keep_values=False, dissolve=True
        )

        # Needs a dataframe to be dissolved
        footprint = footprint.convex_hull

        return gpd.GeoDataFrame(geometry=footprint.geometry, crs=footprint.crs)

Ancestors

Instance variables

var output

Inherited from: LandsatProduct.output

Output directory of the product, to write orthorectified data for example.

var name

Inherited from: LandsatProduct.name

Product name (its filename without any extension).

var split_name

Inherited from: LandsatProduct.split_name

Split name, to retrieve every information from its filename (dates, tile, product type…).

var archive_path

Inherited from: LandsatProduct.archive_path

Archive path, same as the product path if not specified. Useful when you want to know where both the extracted and archived version of your product …

var path

Inherited from: LandsatProduct.path

Usable path to the product, either extracted or archived path, according to the satellite.

var is_archived

Inherited from: LandsatProduct.is_archived

Is the archived product is processed (a products is considered as archived if its products path is a directory).

var needs_extraction

Inherited from: LandsatProduct.needs_extraction

Does this products needs to be extracted to be processed ? (True by default).

var date

Inherited from: LandsatProduct.date

Acquisition date.

var datetime

Inherited from: LandsatProduct.datetime

Acquisition datetime.

var tile_name

Inherited from: LandsatProduct.tile_name

Tile if possible (for data that can be piled, for example S2 and Landsats).

var sensor_type

Inherited from: LandsatProduct.sensor_type

Sensor type, SAR or optical.

var product_type

Inherited from: LandsatProduct.product_type

Product type, satellite-related field, such as L1C or L2A for Sentinel-2 data.

var band_names

Inherited from: LandsatProduct.band_names

Band mapping between band wrapping names such as GREEN and band real number such as 03 for Sentinel-2.

var is_reference

Inherited from: LandsatProduct.is_reference

If the product is a reference, used for algorithms that need pre and post data, such as fire detection.

var corresponding_ref

Inherited from: LandsatProduct.corresponding_ref

The corresponding reference products to the current one (if the product is not a reference but has a reference data corresponding to it). A list …

var nodata

Inherited from: LandsatProduct.nodata

Product nodata, set to 0 by default. Please do not touch this or all index will fail.

var platform

Inherited from: LandsatProduct.platform

Product platform, such as Sentinel-2

var resolution

Inherited from: LandsatProduct.resolution

Default resolution in meters of the current product. For SAR product, we use Ground Range resolution as we will automatically orthorectify the tiles.

var condensed_name

Inherited from: LandsatProduct.condensed_name

Condensed name, the filename with only useful data to keep the name unique (ie. 20191215T110441_S2_30TXP_L2A_122756). Used to shorten names and paths.

var sat_id

Inherited from: LandsatProduct.sat_id

Satellite ID, i.e. S2 for Sentinel-2

Methods

def footprint(

self)

Get real footprint of the products (without nodata, in french == emprise utile)

Warning

As Landsat 7 is broken (with nodata stripes all over the bands), the footprint is not easily computed and may take some time to be delivered.

>>> from eoreader.reader import Reader
>>> path = r"LC08_L1GT_023030_20200518_20200527_01_T2"
>>> prod = Reader().open(path)
>>> prod.footprint()
   index                                           geometry
0      0  POLYGON ((366165.000 4899735.000, 366165.000 4...

Overload of the generic function because landsat nodata seems to be different in QA than in regular bands. Indeed, nodata pixels vary according to the band sensor footprint, whereas QA nodata is where at least one band has nodata.

We chose to keep QA nodata values for the footprint in order to show where all bands are valid.

TL;DR: We use the QA nodata value to determine the product's footprint.

Returns

gpd.GeoDataFrame
Footprint as a GeoDataFrame
Expand source code
def footprint(self) -> gpd.GeoDataFrame:
    """
    Get real footprint of the products (without nodata, in french == emprise utile)

    .. WARNING::
        As Landsat 7 is broken (with nodata stripes all over the bands),
        the footprint is not easily computed and may take some time to be delivered.

    ```python
    >>> from eoreader.reader import Reader
    >>> path = r"LC08_L1GT_023030_20200518_20200527_01_T2"
    >>> prod = Reader().open(path)
    >>> prod.footprint()
       index                                           geometry
    0      0  POLYGON ((366165.000 4899735.000, 366165.000 4...
    ```

    Overload of the generic function because landsat nodata seems to be different in QA than in regular bands.
    Indeed, nodata pixels vary according to the band sensor footprint,
    whereas QA nodata is where at least one band has nodata.

    We chose to keep QA nodata values for the footprint in order to show where all bands are valid.

    **TL;DR: We use the QA nodata value to determine the product's footprint**.

    Returns:
        gpd.GeoDataFrame: Footprint as a GeoDataFrame
    """
    LOGGER.warning(
        "Due to the Landsat-7 gaps, this function returns a rounded footprint on the corners. "
        "Sorry for the inconvenience."
    )

    # Read the file with a very low resolution -> use raster_rio that is faster !
    gap_msk = rasters.read(
        self._get_path(self._nodata_band_id),
        resolution=self.resolution * 50,
        masked=False,
    )

    # Vectorize the nodata band
    # Take the convex hull to discard the stripes of L7 to simplify the geometries
    footprint = rasters.vectorize(
        gap_msk, values=1, keep_values=False, dissolve=True
    )

    # Needs a dataframe to be dissolved
    footprint = footprint.convex_hull

    return gpd.GeoDataFrame(geometry=footprint.geometry, crs=footprint.crs)

def get_datetime(

self,
as_datetime=False)

Inherited from: LandsatProduct.get_datetime

Get the product's acquisition datetime, with format YYYYMMDDTHHMMSS <-> %Y%m%dT%H%M%S

def get_band_paths(

self,
band_list,
resolution=None)

Inherited from: LandsatProduct.get_band_paths

Return the paths of required bands …

def read_mtd(

self,
force_pd=False)

Inherited from: LandsatProduct.read_mtd

Read Landsat metadata as: …

def get_mean_sun_angles(

self)

Inherited from: LandsatProduct.get_mean_sun_angles

Get Mean Sun angles (Azimuth and Zenith angles) …

def get_default_band(

self)

Inherited from: LandsatProduct.get_default_band

Get default band: GREEN for optical data as every optical satellite has a GREEN band …

def get_default_band_path(

self)

Inherited from: LandsatProduct.get_default_band_path

Get default band (GREEN for optical data) path …

def crs(

self)

Inherited from: LandsatProduct.crs

Get UTM projection of the tile …

def extent(

self)

Inherited from: LandsatProduct.extent

Get UTM extent of the tile …

def get_existing_bands(

self)

Inherited from: LandsatProduct.get_existing_bands

Return the existing band paths …

def get_existing_band_paths(

self)

Inherited from: LandsatProduct.get_existing_band_paths

Return the existing band paths …

def get_date(

self,
as_date=False)

Inherited from: LandsatProduct.get_date

Get the product's acquisition date …

def load(

self,
bands,
resolution=None,
size=None)

Inherited from: LandsatProduct.load

Open the bands and compute the wanted index …

def has_band(

self,
band)

Inherited from: LandsatProduct.has_band

Does this products has the specified band ? …

def stack(

self,
bands,
resolution=None,
stack_path=None,
save_as_int=False)

Inherited from: LandsatProduct.stack

Stack bands and index of a products …