Source code for sen2nbar.metadata

import os
import warnings

import numpy as np
import requests
import xarray as xr
import xmltodict


def _get_angle_values(values_list: dict, angle: str) -> np.ndarray:
    """Gets the angle values per detector in Sentinel-2 granule metadata.

    Parameters
    ----------
    values_list : dict
        Dictionary of angle values in the metadata.
    angle : str
        Angle to retrieve. Either 'Zenith' oder 'Azimuth'.

    Returns
    -------
    numpy.ndarray
        Angle values per detector.
    """
    values = values_list[angle]["Values_List"]["VALUES"]
    array = np.array([row.split(" ") for row in values]).astype(float)

    return array


[docs] def angles_from_metadata(metadata: str) -> xr.DataArray: """Gets the angle values per band (and Sun) in Sentinel-2 granule metadata. The angle values are retrieved for the Sun and View modes as a :code:`xarray.DataArray` with a shape (band, angle, y, x). Parameters ---------- metadata : str Path to the metadata file. An URL can also be used. Returns ------- xarray.DataArray Angle values per band and Sun. """ # Convert the xml into a dict if os.path.exists(metadata): data = xmltodict.parse(open(metadata, "r").read()) else: data = xmltodict.parse(requests.get(metadata).content) # Extract the geocoding and angles, all the stuff we need is here Tile_Geocoding = data["n1:Level-2A_Tile_ID"]["n1:Geometric_Info"]["Tile_Geocoding"] Tile_Angles = data["n1:Level-2A_Tile_ID"]["n1:Geometric_Info"]["Tile_Angles"] # Save the upper left corner for the array ULX = float(Tile_Geocoding["Geoposition"][0]["ULX"]) ULY = float(Tile_Geocoding["Geoposition"][0]["ULY"]) # Band names band_names = ["B" + f"0{x}"[-2:] for x in np.arange(1, 13)] band_names.insert(8, "B8A") # Angles to work with ANGLES = ["Zenith", "Azimuth"] # Create a dictionary to store the angles per band (and the Sun) bands_dict = dict() for key in ["Sun"] + band_names: bands_dict[key] = dict(Zenith=list(), Azimuth=list()) # Each band has multiple detectors, so we have to go through all of them # and save them in a list to later do a nanmean for single_angle_detector in Tile_Angles["Viewing_Incidence_Angles_Grids"]: band_id = int(single_angle_detector["@bandId"]) band_name = band_names[band_id] for angle in ANGLES: bands_dict[band_name][angle].append( _get_angle_values(single_angle_detector, angle) ) # Do the same for the Sun, but there is just one, of course, duh for angle in ANGLES: bands_dict["Sun"][angle].append( _get_angle_values(Tile_Angles["Sun_Angles_Grid"], angle) ) # Do the nanmean of the detectors angles per band for key, value in bands_dict.items(): for angle in ANGLES: with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) bands_dict[key][angle] = np.nanmean( np.array(bands_dict[key][angle]), axis=0 ) bands_dict[key] = np.array( [bands_dict[key]["Zenith"], bands_dict[key]["Azimuth"]] ) # x and y coordinates of the array to create y = np.arange(ULY, ULY - 5000 * 23, -5000) - 2500 x = np.arange(ULX, ULX + 5000 * 23, 5000) + 2500 # Create the array try: da = xr.DataArray( list(bands_dict.values()), dims=["band", "angle", "y", "x"], coords=dict(band=list(bands_dict.keys()), angle=ANGLES, x=x, y=y), ) except ValueError: raise ValueError("Not all bands include angles values.") # Add attributes da.attrs["epsg"] = Tile_Geocoding["HORIZONTAL_CS_CODE"] return da
[docs] def get_processing_baseline(metadata: str) -> float: """Gets the processing baseline in Sentinel-2 user metadata. The processing baseline is retrieved as a float. Parameters ---------- metadata : str Path to the metadata file. An URL can also be used. Returns ------- float Processing baseline. """ # Convert the xml into a dict if os.path.exists(metadata): data = xmltodict.parse(open(metadata, "r").read()) else: data = xmltodict.parse(requests.get(metadata).content) # Get the processing baseline PROCESSING_BASELINE = data["n1:Level-2A_User_Product"]["n1:General_Info"][ "Product_Info" ]["PROCESSING_BASELINE"] return float(PROCESSING_BASELINE)