Пример #1
0
    def wgs84_extent(self) -> gpd.GeoDataFrame:
        """
        Get the WGS84 extent of the file before any reprojection.
        This is useful when the SAR pre-process has not been done yet.

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"1011117-766193"
            >>> prod = Reader().open(path)
            >>> prod.wgs84_extent()
                                                        geometry
            0  POLYGON ((108.09797 15.61011, 108.48224 15.678...

        Returns:
            gpd.GeoDataFrame: WGS84 extent as a gpd.GeoDataFrame

        """
        # Open metadata file
        try:
            mtd_file = glob.glob(os.path.join(self.path, "*.h5.xml"))[0]
        except IndexError as ex:
            raise InvalidProductError(
                f"Metadata file ({self._real_name}.h5.xml) not found in {self.path}"
            ) from ex

        # Open and parse XML
        # pylint: disable=I1101
        xml_tree = etree.parse(mtd_file)
        root = xml_tree.getroot()

        # Open zenith and azimuth angle
        def from_str_to_arr(geo_coord: str):
            return np.array(strings.str_to_list(geo_coord),
                            dtype=float)[:2][::-1]

        bl_corner = br_corner = tr_corner = tl_corner = None
        for element in root:
            if element.tag == "ProductDefinitionData":
                bl_corner = from_str_to_arr(
                    element.findtext("GeoCoordBottomLeft"))
                br_corner = from_str_to_arr(
                    element.findtext("GeoCoordBottomRight"))
                tl_corner = from_str_to_arr(
                    element.findtext("GeoCoordTopLeft"))
                tr_corner = from_str_to_arr(
                    element.findtext("GeoCoordTopRight"))
                break

        if bl_corner is None:
            raise InvalidProductError("Invalid XML: missing extent.")

        extent_wgs84 = gpd.GeoDataFrame(
            geometry=[Polygon([tl_corner, tr_corner, br_corner, bl_corner])],
            crs=vectors.WGS84,
        )

        return extent_wgs84
Пример #2
0
    def _set_product_type(self) -> None:
        """Set products type"""
        # Product type
        if self.name[7] != "1":
            raise InvalidTypeError(
                "Only L1 products are used for Sentinel-3 data.")

        if "OL" in self.name:
            # Instrument
            self._instrument_name = S3Instrument.OLCI

            # Data type
            if S3DataTypes.EFR.value in self.name:
                self._data_type = S3DataTypes.EFR
                self.product_type = S3ProductType.OLCI_EFR
            else:
                raise InvalidTypeError(
                    "Only EFR data type is used for Sentinel-3 OLCI data.")

            # Bands
            self.band_names.map_bands({
                obn.CA: "02",
                obn.BLUE: "03",
                obn.GREEN: "06",
                obn.RED: "08",
                obn.VRE_1: "11",
                obn.VRE_2: "12",
                obn.VRE_3: "16",
                obn.NIR: "17",
                obn.NARROW_NIR: "17",
                obn.WV: "20",
                obn.FAR_NIR: "21",
            })
        elif "SL" in self.name:
            # Instrument
            self._instrument_name = S3Instrument.SLSTR

            # Data type
            if S3DataTypes.RBT.value in self.name:
                self._data_type = S3DataTypes.RBT
                self.product_type = S3ProductType.SLSTR_RBT
            else:
                raise InvalidTypeError(
                    "Only RBT data type is used for Sentinel-3 SLSTR data.")

            # Bands
            self.band_names.map_bands({
                obn.GREEN: "1",  # radiance, 500m
                obn.RED: "2",  # radiance, 500m
                obn.NIR: "3",  # radiance, 500m
                obn.NARROW_NIR: "3",  # radiance, 500m
                obn.SWIR_CIRRUS: "4",  # radiance, 500m
                obn.SWIR_1: "5",  # radiance, 500m
                obn.SWIR_2: "6",  # radiance, 500m
                obn.MIR: "7",  # brilliance temperature, 1km
                obn.TIR_1: "8",  # brilliance temperature, 1km
                obn.TIR_2: "9",  # brilliance temperature, 1km
            })
        else:
            raise InvalidProductError(f"Invalid Sentinel-3 name: {self.name}")
Пример #3
0
    def read_mtd(self) -> (etree._Element, str):
        """
        Read metadata and outputs the metadata XML root and its namespace

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"1001513-735093"
            >>> prod = Reader().open(path)
            >>> prod.read_mtd()
            (<Element DeliveryNote at 0x2454ad4ee88>, '')

        Returns:
            (etree._Element, str): Metadata XML root and its namespace
        """
        mtd_name = f"DFDN_{self._real_name}.h5.xml"
        try:
            mtd_file = glob.glob(os.path.join(self.path, mtd_name))[0]

            # pylint: disable=I1101:
            # Module 'lxml.etree' has no 'parse' member, but source is unavailable.
            xml_tree = etree.parse(mtd_file)
            root = xml_tree.getroot()
        except IndexError as ex:
            raise InvalidProductError(
                f"Metadata file ({mtd_name}) not found in {self.path}") from ex

        # Get namespace
        namespace = ""  # No namespace here

        return root, namespace
Пример #4
0
    def wgs84_extent(self) -> gpd.GeoDataFrame:
        """
        Get the WGS84 extent of the file before any reprojection.
        This is useful when the SAR pre-process has not been done yet.

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"TSX1_SAR__MGD_SE___SM_S_SRA_20160229T223018_20160229T223023"
            >>> prod = Reader().open(path)
            >>> prod.wgs84_extent()
                                                        geometry
            0  POLYGON ((106.65491 -6.39693, 106.96233 -6.396...

        Returns:
            gpd.GeoDataFrame: WGS84 extent as a gpd.GeoDataFrame

        """
        # Open extent KML file
        vectors.set_kml_driver()
        try:
            extent_file = glob.glob(
                os.path.join(self.path, "SUPPORT", "GEARTH_POLY.kml"))[0]
        except IndexError as ex:
            raise InvalidProductError(
                f"Extent file (products.kml) not found in {self.path}") from ex

        extent_wgs84 = gpd.read_file(extent_file).envelope.to_crs(
            vectors.WGS84)

        return gpd.GeoDataFrame(geometry=extent_wgs84.geometry,
                                crs=extent_wgs84.crs)
Пример #5
0
    def read_mtd(self) -> (etree._Element, str):
        """
        Read metadata and outputs the metadata XML root and its namespace

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"TSX1_SAR__MGD_SE___SM_S_SRA_20200605T042203_20200605T042211"
            >>> prod = Reader().open(path)
            >>> prod.read_mtd()
            (<Element level1Product at 0x1b845b7ab88>, '')

        Returns:
            (etree._Element, str): Metadata XML root and its namespace
        """
        try:
            mtd_file = glob.glob(os.path.join(self.path,
                                              f"{self.name}.xml"))[0]

            # pylint: disable=I1101:
            # Module 'lxml.etree' has no 'parse' member, but source is unavailable.
            xml_tree = etree.parse(mtd_file)
            root = xml_tree.getroot()
        except IndexError as ex:
            raise InvalidProductError(
                f"Metadata file ({self.name}.xml) not found in {self.path}"
            ) from ex

        # Get namespace
        namespace = ""  # No namespace here

        return root, namespace
Пример #6
0
    def get_mean_sun_angles(self) -> (float, float):
        """
        Get Mean Sun angles (Azimuth and Zenith angles)

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"SENTINEL2A_20190625-105728-756_L2A_T31UEQ_C_V2-2"
            >>> prod = Reader().open(path)
            >>> prod.get_mean_sun_angles()
            (154.554755774838, 27.5941391571236)

        Returns:
            (float, float): Mean Azimuth and Zenith angle
        """
        # Init angles
        zenith_angle = None
        azimuth_angle = None

        # Get MTD XML file
        root, _ = self.read_mtd()

        try:
            mean_sun_angles = root.find(".//Sun_Angles")
            zenith_angle = float(mean_sun_angles.findtext("ZENITH_ANGLE"))
            azimuth_angle = float(mean_sun_angles.findtext("AZIMUTH_ANGLE"))
        except TypeError:
            raise InvalidProductError("Azimuth or Zenith angles not found")

        return azimuth_angle, zenith_angle
Пример #7
0
    def _get_slstr_quality_flags_name(self, band: obn) -> str:
        """
        Get SNAP band name.
        Args:
            band (obn): Band as an OpticalBandNames

        Returns:
            str: Quality flag name with SNAP format
        """
        # Get band number
        band_nb = self.band_names[band]
        if band_nb is None:
            raise InvalidProductError(
                f"Non existing band ({band.name}) for S3-{self._data_type.name} products"
            )

        # Get quality flag name
        if self._data_type == S3DataTypes.RBT:
            snap_bn = f"S{band_nb}_exception_{'i' if band in BT_BANDS else 'a'}n"
        else:
            raise InvalidTypeError(
                f"This function only works for Sentinel-3 SLSTR data: {self._data_type}"
            )

        return snap_bn
Пример #8
0
    def get_default_band(self) -> BandNames:
        """
        Get default band:
        The first existing one between `VV` and `HH` for SAR data.

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"S1A_IW_GRDH_1SDV_20191215T060906_20191215T060931_030355_0378F7_3696.zip"
            >>> prod = Reader().open(path)
            >>> prod.get_default_band()
            <SarBandNames.VV: 'VV'>

        Returns:
            str: Default band
        """
        existing_bands = self._get_raw_bands()
        if not existing_bands:
            raise InvalidProductError(
                f"No band exists for products: {self.name}")

        # The order matters, as we almost always prefer VV and HH
        if sbn.VV in existing_bands:
            default_band = sbn.VV
        elif sbn.HH in existing_bands:
            default_band = sbn.HH
        elif sbn.VH in existing_bands:
            default_band = sbn.VH
        elif sbn.HV in existing_bands:
            default_band = sbn.HV
        else:
            raise InvalidTypeError(
                f"Invalid bands for products: {existing_bands}")

        return default_band
Пример #9
0
    def get_mean_sun_angles(self) -> (float, float):
        """
        Get Mean Sun angles (Azimuth and Zenith angles)

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE.zip"
            >>> prod = Reader().open(path)
            >>> prod.get_mean_sun_angles()
            (149.148155074489, 32.6627897525474)

        Returns:
            (float, float): Mean Azimuth and Zenith angle
        """
        # Read metadata
        root, _ = self.read_mtd()

        try:
            mean_sun_angles = root.find(".//Mean_Sun_Angle")
            zenith_angle = float(mean_sun_angles.findtext("ZENITH_ANGLE"))
            azimuth_angle = float(mean_sun_angles.findtext("AZIMUTH_ANGLE"))
        except TypeError:
            raise InvalidProductError("Azimuth or Zenith angles not found")

        return azimuth_angle, zenith_angle
Пример #10
0
    def _get_snap_band_name(self, band: obn) -> str:
        """
        Get SNAP band name.
        Args:
            band (obn): Band as an OpticalBandNames

        Returns:
            str: Band name with SNAP format
        """
        # Get band number
        band_nb = self.band_names[band]
        if band_nb is None:
            raise InvalidProductError(
                f"Non existing band ({band.name}) for S3-{self._data_type.name} products"
            )

        # Get band name
        if self._data_type == S3DataTypes.EFR:
            snap_bn = f"Oa{band_nb}_reflectance"  # Converted into reflectance previously in the graph
        elif self._data_type == S3DataTypes.RBT:
            if band in BT_BANDS:
                snap_bn = f"S{band_nb}_BT_in"
            else:
                snap_bn = f"S{band_nb}_reflectance_an"  # Conv into reflectance previously in the graph
        else:
            raise InvalidTypeError(
                f"Unknown data type for Sentinel-3 data: {self._data_type}")

        return snap_bn
Пример #11
0
 def _set_product_type(self) -> None:
     """Set products type"""
     if "LT04" in self.name:
         self._set_tm_product_type()
     elif "LM04" in self.name:
         self._set_mss_product_type(version=4)
     else:
         raise InvalidProductError(f"Invalid Landsat-4 name: {self.name}")
Пример #12
0
    def get_band_paths(self,
                       band_list: list,
                       resolution: float = None) -> dict:
        """
        Return the paths of required bands.

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> from eoreader.bands.alias import *
            >>> path = r"S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE.zip"
            >>> prod = Reader().open(path)
            >>> prod.get_band_paths([GREEN, RED])
            {
                <OpticalBandNames.GREEN: 'GREEN'>:
                    'LC08_L1GT_023030_20200518_20200527_01_T2\\LC08_L1GT_023030_20200518_20200527_01_T2_B3.TIF',
                <OpticalBandNames.RED: 'RED'>:
                    'LC08_L1GT_023030_20200518_20200527_01_T2\\LC08_L1GT_023030_20200518_20200527_01_T2_B4.TIF'
            }

        Args:
            band_list (list): List of the wanted bands
            resolution (float): Useless here

        Returns:
            dict: Dictionary containing the path of each queried band
        """
        band_paths = {}
        for band in band_list:
            if not self.has_band(band):
                raise InvalidProductError(
                    f"Non existing band ({band.name}) "
                    f"for Landsat-{self.product_type.name} products")
            band_nb = self.band_names[band]

            try:
                band_paths[band] = self._get_path(f"_B{band_nb}")
            except FileNotFoundError as ex:
                raise InvalidProductError(
                    f"Non existing {band} ({band_nb}) band for {self.path}"
                ) from ex

        return band_paths
Пример #13
0
    def get_band_paths(self,
                       band_list: list,
                       resolution: float = None) -> dict:
        """
        Return the paths of required bands.

        .. WARNING:: This functions orthorectifies SAR bands if not existing !

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> from eoreader.bands.alias import *
            >>> path = r"S1A_IW_GRDH_1SDV_20191215T060906_20191215T060931_030355_0378F7_3696.zip"
            >>> prod = Reader().open(path)
            >>> prod.get_band_paths([VV, HH])
            {
                <SarBandNames.VV: 'VV'>: '20191215T060906_S1_IW_GRD\\20191215T060906_S1_IW_GRD_VV.tif'  # HH doesn't exist
            }

        Args:
            band_list (list): List of the wanted bands
            resolution (float): Band resolution

        Returns:
            dict: Dictionary containing the path of each queried band
        """
        band_paths = {}
        for band in band_list:
            bname = self.band_names[band]
            if bname is None:
                raise InvalidProductError(
                    f"Non existing band ({band.name}) for {self.name}")
            try:
                # Try to load orthorectified bands
                band_paths[band] = files.get_file_in_dir(
                    self._get_band_folder(),
                    f"{self.condensed_name}_{bname}.tif",
                    exact_name=True,
                )
            except FileNotFoundError:
                speckle_band = sbn.corresponding_speckle(band)
                if speckle_band in self.pol_channels:
                    if sbn.is_despeckle(band):
                        # Despeckle the noisy band
                        band_paths[band] = self._despeckle_sar(speckle_band)
                    else:
                        all_band_paths = self._pre_process_sar(resolution)
                        band_paths = {
                            band: path
                            for band, path in all_band_paths.items()
                            if band in band_list
                        }

        return band_paths
Пример #14
0
    def __init__(self,
                 product_path: str,
                 archive_path: str = None,
                 output_path=None) -> None:
        try:
            self._img_path = glob.glob(os.path.join(product_path, "*.h5"))[0]
        except IndexError as ex:
            raise InvalidProductError(
                f"Image file (*.h5) not found in {product_path}") from ex
        self._real_name = files.get_filename(self._img_path)

        # Initialization from the super class
        super().__init__(product_path, archive_path, output_path)
Пример #15
0
    def _set_sensor_mode(self) -> None:
        """
        Get products type from S2 products name (could check the metadata too)
        """
        sensor_mode_name = self.split_name[3]

        # Get sensor mode
        for sens_mode in CskSensorMode:
            if sens_mode.value in sensor_mode_name:
                self.sensor_mode = sens_mode

        if not self.sensor_mode:
            raise InvalidProductError(
                f"Invalid {self.platform.value} name: {self._real_name}")
Пример #16
0
    def get_mean_sun_angles(self) -> (float, float):
        """
        Get Mean Sun angles (Azimuth and Zenith angles)

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = "S3B_SL_1_RBT____20191115T233722_20191115T234022_20191117T031722_0179_032_144_3420_LN2_O_NT_003.SEN3"
            >>> prod = Reader().open(path)
            >>> prod.get_mean_sun_angles()
            (78.55043955912154, 31.172127033319388)

        Returns:
            (float, float): Mean Azimuth and Zenith angle
        """
        if self._data_type == S3DataTypes.EFR:
            geom_file = os.path.join(self.path, "tie_geometries.nc")
            sun_az = "SAA"
            sun_ze = "SZA"
        elif self._data_type == S3DataTypes.RBT:
            geom_file = os.path.join(self.path,
                                     "geometry_tn.nc")  # Only use nadir files
            sun_az = "solar_azimuth_tn"
            sun_ze = "solar_zenith_tn"
        else:
            raise InvalidTypeError(
                f"Unknown/Unsupported data type for Sentinel-3 data: {self._data_type}"
            )

        # Open file
        if os.path.isfile(geom_file):
            # Bug pylint with netCDF4
            # pylint: disable=E1101
            netcdf_ds = netCDF4.Dataset(geom_file)

            # Get variables
            sun_az_var = netcdf_ds.variables[sun_az]
            sun_ze_var = netcdf_ds.variables[sun_ze]

            # Get sun angles as the mean of whole arrays
            azimuth_angle = float(np.mean(sun_az_var[:]))
            zenith_angle = float(np.mean(sun_ze_var[:]))

            # Close dataset
            netcdf_ds.close()
        else:
            raise InvalidProductError(f"Geometry file {geom_file} not found")

        return azimuth_angle, zenith_angle
Пример #17
0
    def get_band_paths(self,
                       band_list: list,
                       resolution: float = None) -> dict:
        """
        Return the paths of required bands.

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> from eoreader.bands.alias import *
            >>> path = r"S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE.zip"
            >>> prod = Reader().open(path)
            >>> prod.get_band_paths([GREEN, RED])
            {
                <OpticalBandNames.GREEN: 'GREEN'>: 'zip+file://S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE.zip!/S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE/GRANULE/L1C_T30TTK_A027018_20200824T111345/IMG_DATA/T30TTK_20200824T110631_B03.jp2',
                <OpticalBandNames.RED: 'RED'>: 'zip+file://S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE.zip!/S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE/GRANULE/L1C_T30TTK_A027018_20200824T111345/IMG_DATA/T30TTK_20200824T110631_B04.jp2'
            }

        Args:
            band_list (list): List of the wanted bands
            resolution (float): Band resolution

        Returns:
            dict: Dictionary containing the path of each queried band
        """
        band_folders = self._get_res_band_folder(band_list, resolution)
        band_paths = {}
        for band in band_list:
            try:
                if self.is_archived:
                    band_paths[band] = files.get_archived_rio_path(
                        self.path,
                        f".*{band_folders[band]}.*_B{self.band_names[band]}.*.jp2",
                    )
                else:
                    band_paths[band] = files.get_file_in_dir(
                        band_folders[band],
                        "_B" + self.band_names[band],
                        extension="jp2",
                    )
            except (FileNotFoundError, IndexError) as ex:
                raise InvalidProductError(
                    f"Non existing {band} ({self.band_names[band]}) band for {self.path}"
                ) from ex

        return band_paths
Пример #18
0
 def _set_tm_product_type(self) -> None:
     """Set TM product type and map corresponding bands"""
     if "L1" in self.name:
         self.product_type = LandsatProductType.L1_TM
         self.band_names.map_bands({
             obn.BLUE: "1",
             obn.GREEN: "2",
             obn.RED: "3",
             obn.NIR: "4",
             obn.NARROW_NIR: "4",
             obn.SWIR_1: "5",
             obn.SWIR_2: "7",
             obn.TIR_1: "6",
             obn.TIR_2: "6",
         })
     else:
         raise InvalidProductError(
             "Only Landsat level 1 are managed in EOReader")
Пример #19
0
    def _load_clouds(self,
                     bands: list,
                     resolution: float = None,
                     size: Union[list, tuple] = None) -> dict:
        """
        Load cloud files as numpy arrays with the same resolution (and same metadata).

        Read Landsat clouds from QA mask.
        See here for clouds_values:

        - (COL 1)[https://www.usgs.gov/land-resources/nli/landsat/landsat-collection-1-level-1-quality-assessment-band]
        - (COL 2)[https://www.usgs.gov/core-science-systems/nli/landsat/landsat-collection-2-quality-assessment-bands]


        Args:
            bands (list): List of the wanted bands
            resolution (int): Band resolution in meters
            size (Union[tuple, list]): Size of the array (width, height). Not used if resolution is provided.
        Returns:
            dict: Dictionary {band_name, band_xarray}
        """
        band_dict = {}

        if bands:
            # Open QA band
            landsat_qa_path = self._get_path(self._quality_id)
            qa_arr = self._read_band(landsat_qa_path,
                                     resolution=resolution,
                                     size=size)

            if self.product_type == LandsatProductType.L1_OLCI:
                band_dict = self._load_olci_clouds(qa_arr, bands)
            elif self.product_type in [
                    LandsatProductType.L1_ETM,
                    LandsatProductType.L1_TM,
            ]:
                band_dict = self._load_e_tm_clouds(qa_arr, bands)
            elif self.product_type == LandsatProductType.L1_MSS:
                band_dict = self._load_mss_clouds(qa_arr, bands)
            else:
                raise InvalidProductError(
                    f"Invalid product type: {self.product_type}")

        return band_dict
Пример #20
0
    def _set_sensor_mode(self) -> None:
        """
        Get products type from S1 products name (could check the metadata too)
        """
        sensor_mode_name = self.split_name[1]

        # Get sensor mode
        for sens_mode in S1SensorMode:
            if sens_mode.value in sensor_mode_name:
                self.sensor_mode = sens_mode

        # Discard invalid sensor mode
        if self.sensor_mode != S1SensorMode.IW:
            raise NotImplementedError(
                f"For now, only IW sensor mode is used in EOReader processes: {self.name}"
            )
        if not self.sensor_mode:
            raise InvalidProductError(
                f"Invalid {self.platform.value} name: {self.name}")
Пример #21
0
    def _has_cloud_band(self, band: BandNames) -> bool:
        """
        Does this products has the specified cloud band ?

        - (COL 1)[https://www.usgs.gov/land-resources/nli/landsat/landsat-collection-1-level-1-quality-assessment-band]
        - (COL 2)[https://www.usgs.gov/core-science-systems/nli/landsat/landsat-collection-2-quality-assessment-bands]
        """
        if self.product_type == LandsatProductType.L1_OLCI:
            has_band = True
        elif self.product_type in [
                LandsatProductType.L1_ETM, LandsatProductType.L1_TM
        ]:
            has_band = self._e_tm_has_cloud_band(band)
        elif self.product_type == LandsatProductType.L1_MSS:
            has_band = self._mss_has_cloud_band(band)
        else:
            raise InvalidProductError(
                f"Invalid product type: {self.product_type}")

        return has_band
Пример #22
0
    def _compute_hillshade(
        self,
        dem_path: str = "",
        resolution: Union[float, tuple] = None,
        size: Union[list, tuple] = None,
        resampling: Resampling = Resampling.bilinear,
    ) -> str:
        """
        Compute Hillshade mask

        Args:
            dem_path (str): DEM path, using EUDEM/MERIT DEM if none
            resolution (Union[float, tuple]): Resolution in meters. If not specified, use the product resolution.
            resampling (Resampling): Resampling method
            size (Union[tuple, list]): Size of the array (width, height). Not used if resolution is provided.
        Returns:
            str: Hillshade mask path
        """
        raise InvalidProductError(
            "Impossible to compute hillshade mask for SAR data.")
Пример #23
0
    def get_band_paths(self, band_list: list, resolution: float = None) -> dict:
        """
        Return the paths of required bands.

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> from eoreader.bands.alias import *
            >>> path = r"SENTINEL2A_20190625-105728-756_L2A_T31UEQ_C_V2-2"
            >>> prod = Reader().open(path)
            >>> prod.get_band_paths([GREEN, RED])
            {
                <OpticalBandNames.GREEN: 'GREEN'>: 'SENTINEL2A_20190625-105728-756_L2A_T31UEQ_C_V2-2\\SENTINEL2A_20190625-105728-756_L2A_T31UEQ_C_V2-2_FRE_B3.tif',
                <OpticalBandNames.RED: 'RED'>: 'SENTINEL2A_20190625-105728-756_L2A_T31UEQ_C_V2-2\\SENTINEL2A_20190625-105728-756_L2A_T31UEQ_C_V2-2_FRE_B4.tif'
            }

        Args:
            band_list (list): List of the wanted bands
            resolution (float): Band resolution

        Returns:
            dict: Dictionary containing the path of each queried band
        """
        band_paths = {}
        for band in band_list:
            try:
                if self.is_archived:
                    band_paths[band] = files.get_archived_rio_path(
                        self.path, f".*FRE_B{self.band_names[band]}\.tif"
                    )
                else:
                    band_paths[band] = files.get_file_in_dir(
                        self.path, f"FRE_B{self.band_names[band]}.tif"
                    )
            except (FileNotFoundError, IndexError) as ex:
                raise InvalidProductError(
                    f"Non existing {band} ({self.band_names[band]}) band for {self.path}"
                ) from ex

        return band_paths
Пример #24
0
    def _get_raw_band_paths(self) -> dict:
        """
        Return the existing band paths (as they come with th archived products).

        Returns:
            dict: Dictionary containing the path of every band existing in the raw products
        """
        extended_fmt = _ExtendedFormatter()
        band_paths = {}
        for band, band_name in self.band_names.items():
            band_regex = extended_fmt.format(self._raw_band_regex, band_name)

            if self.is_archived:
                if self.path.endswith(".zip"):
                    # Open the zip file
                    with zipfile.ZipFile(self.path, "r") as zip_ds:
                        # Get the correct band path
                        regex = re.compile(band_regex.replace("*", ".*"))
                        try:
                            band_paths[band] = list(
                                filter(regex.match,
                                       [f.filename
                                        for f in zip_ds.filelist]))[0]
                        except IndexError:
                            continue
                else:
                    raise InvalidProductError(
                        f"Only zipped products can be processed without extraction: {self.path}"
                    )
            else:
                try:
                    band_paths[band] = files.get_file_in_dir(self._band_folder,
                                                             band_regex,
                                                             exact_name=True,
                                                             get_list=True)
                except FileNotFoundError:
                    continue

        return band_paths
Пример #25
0
    def get_mean_sun_angles(self) -> (float, float):
        """
        Get Mean Sun angles (Azimuth and Zenith angles)

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE.zip"
            >>> prod = Reader().open(path)
            >>> prod.get_mean_sun_angles()
            (149.148155074489, 32.6627897525474)

        Returns:
            (float, float): Mean Azimuth and Zenith angle
        """
        # Init angles
        zenith_angle = None
        azimuth_angle = None

        # Read metadata
        root, namespace = self.read_mtd()

        # Open zenith and azimuth angle
        for element in root:
            if element.tag == namespace + "Geometric_Info":
                for node in element:
                    if node.tag == "Tile_Angles":
                        mean_sun_angles = node.find("Mean_Sun_Angle")
                        zenith_angle = float(
                            mean_sun_angles.findtext("ZENITH_ANGLE"))
                        azimuth_angle = float(
                            mean_sun_angles.findtext("AZIMUTH_ANGLE"))
                        break  # Only one Mean_Sun_Angle
                break  # Only one Geometric_Info

        if not zenith_angle or not azimuth_angle:
            raise InvalidProductError("Azimuth or Zenith angles not found")

        return azimuth_angle, zenith_angle
Пример #26
0
    def read_mtd(self) -> (etree._Element, str):
        """
        Read metadata and outputs the metadata XML root and its namespace

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"LC08_L1GT_023030_20200518_20200527_01_T2"
            >>> prod = Reader().open(path)
            >>> prod.read_mtd()
            (<Element {http://www.rsi.ca/rs2/prod/xml/schemas}product at 0x1c0efbd37c8>,
            '{http://www.rsi.ca/rs2/prod/xml/schemas}')

        Returns:
            (etree._Element, str): Metadata XML root and its namespace
        """
        # Get MTD XML file
        if self.is_archived:
            root = files.read_archived_xml(self.path, ".*product\.xml")
        else:
            # Open metadata file
            try:
                mtd_file = glob.glob(os.path.join(self.path, "product.xml"))[0]

                # pylint: disable=I1101:
                # Module 'lxml.etree' has no 'parse' member, but source is unavailable.
                xml_tree = etree.parse(mtd_file)
                root = xml_tree.getroot()
            except IndexError as ex:
                raise InvalidProductError(
                    f"Metadata file (product.xml) not found in {self.path}"
                ) from ex

        # Get namespace
        idx = root.tag.rindex("}")
        namespace = root.tag[:idx + 1]

        return root, namespace
Пример #27
0
    def get_mask_path(self, mask_id: str, res_id: str) -> str:
        """
        Get mask path from its id and file_id (`R1` for 10m resolution, `R2` for 20m resolution)

        Accepted mask IDs:

        - `DFP`: Defective pixels (do not always exist ! Will raise `InvalidProductError` if not)
        - `EDG`: Nodata pixels mask
        - `SAT`: Saturated pixels mask
        - `MG2`: Geophysical mask (classification)
        - `IAB`: Mask where water vapor and TOA pixels have been interpolated
        - `CLM`: Cloud mask

        Args:
            mask_id (str): Mask ID
            res_id (str): Resolution ID (`R1` or `R2`)

        Returns:
            str: Mask path
        """
        assert res_id in ["R1", "R2"]

        mask_regex = f"*{mask_id}_{res_id}.tif"
        try:
            if self.is_archived:
                mask_path = files.get_archived_rio_path(
                    self.path, mask_regex.replace("*", ".*")
                )
            else:
                mask_path = files.get_file_in_dir(
                    os.path.join(self.path, "MASKS"), mask_regex, exact_name=True
                )
        except (FileNotFoundError, IndexError) as ex:
            raise InvalidProductError(
                f"Non existing mask {mask_regex} in {self.name}"
            ) from ex

        return mask_path
Пример #28
0
    def read_mtd(self) -> (etree._Element, str):
        """
        Read metadata and outputs the metadata XML root and its namespace

        .. code-block:: python

            >>> from eoreader.reader import Reader
            >>> path = r"S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE.zip"
            >>> prod = Reader().open(path)
            >>> prod.read_mtd()
            (<Element {https://psd-14.sentinel2.eo.esa.int/PSD/S2_PDI_Level-2A_Tile_Metadata.xsd}Level-2A_Tile_ID at ...>,
            '{https://psd-14.sentinel2.eo.esa.int/PSD/S2_PDI_Level-2A_Tile_Metadata.xsd}')

        Returns:
            (etree._Element, str): Metadata XML root and its namespace
        """
        # Get MTD XML file
        if self.is_archived:
            root = files.read_archived_xml(self.path, ".*GRANULE.*\.xml")
        else:
            # Open metadata file
            try:
                mtd_file = glob.glob(
                    os.path.join(self.path, "GRANULE", "*", "*.xml"))[0]

                # pylint: disable=I1101:
                # Module 'lxml.etree' has no 'parse' member, but source is unavailable.
                xml_tree = etree.parse(mtd_file)
                root = xml_tree.getroot()
            except IndexError as ex:
                raise InvalidProductError(
                    f"Metadata file not found in {self.path}") from ex

        # Get namespace
        idx = root.tag.rindex("}")
        namespace = root.tag[:idx + 1]

        return root, namespace
Пример #29
0
 def _set_mss_product_type(self, version: int) -> None:
     """Set MSS product type and map corresponding bands"""
     if "L1" in self.name:
         self.product_type = LandsatProductType.L1_MSS
         self.band_names.map_bands({
             obn.GREEN:
             "4" if version < 4 else "1",
             obn.RED:
             "5" if version < 4 else "2",
             obn.VRE_1:
             "6" if version < 4 else "3",
             obn.VRE_2:
             "6" if version < 4 else "3",
             obn.VRE_3:
             "6" if version < 4 else "3",
             obn.NIR:
             "7" if version < 4 else "4",
             obn.NARROW_NIR:
             "7" if version < 4 else "4",
         })
     else:
         raise InvalidProductError(
             "Only Landsat level 1 are managed in EOReader")
Пример #30
0
 def _set_product_type(self) -> None:
     """Get products type"""
     if "MSIL2A" in self.name:
         self.product_type = S2ProductType.L2A
         self.band_names.map_bands({
             obn.CA: "01",
             obn.BLUE: "02",
             obn.GREEN: "03",
             obn.RED: "04",
             obn.VRE_1: "05",
             obn.VRE_2: "06",
             obn.VRE_3: "07",
             obn.NIR: "08",
             obn.NARROW_NIR: "8A",
             obn.WV: "09",
             obn.SWIR_1: "11",
             obn.SWIR_2: "12",
         })
     elif "MSIL1C" in self.name:
         self.product_type = S2ProductType.L1C
         self.band_names.map_bands({
             obn.CA: "01",
             obn.BLUE: "02",
             obn.GREEN: "03",
             obn.RED: "04",
             obn.VRE_1: "05",
             obn.VRE_2: "06",
             obn.VRE_3: "07",
             obn.NIR: "08",
             obn.NARROW_NIR: "8A",
             obn.WV: "09",
             obn.SWIR_CIRRUS: "10",
             obn.SWIR_1: "11",
             obn.SWIR_2: "12",
         })
     else:
         raise InvalidProductError(f"Invalid Sentinel-2 name: {self.name}")