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
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}")
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
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)
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
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
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
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
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
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
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}")
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
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
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)
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}")
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
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
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")
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
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}")
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
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.")
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
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
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
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
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
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
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")
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}")